ED php framework :: Tutorial #4
How the framework works or what happens behind the scene
Probably instead of drawing charts and dataflow diagrams the best way to explain the conceptions is by showing a basic example.

If you didn't take a look so far at any of the source code files under the LIVE DEMO section, please do it before continuing reading this tutorial. If you have done this, then keep reading.

The most basic unit when you create a website is the page. With the ED Framework every page is separated in 2 different files. The first one is *.php and the second one is *.php.script. We keep the presentation logic in the first one and the business logic in the second one. In a traditional Model-View-Controller architecture the first file is our View and the second is our Controller. We'll see later where is the Model. For our demo we will use test.php.

./
test.php
<?php include "test.php.script"; $page = TPage::create(); ?>
 
<html>
<head>
<?php $page->head->render(); ?>
<title>Test</title>
 
<script type="text/javascript">
 
    function btnSubmit_OnClick() {
        var user = page.txtUsername.getValue();
        if (!user) {
            return false;
        }
    }
 
</script>
 
</head>
 
<body>
 
<?php $page->txtUsername->render(Array(
                                    "Style" => "width:200px"
                                   )); ?>
 
<?php $page->btnSubmit->render(Array(
                                   "Text" => "SUBMIT",
                                   "SubmitMode" => "post"
                                  )); ?>
 
 
<?php $page->finalize(); ?>
</body>
 
</html>
./
test.php.script
<?php
 
   include_once("../../../../../appconfig.inc");
 
 
   /**
    * TestPage
    */
   class TestPage extends TPage {
 
       public $txtUsername;
       public $btnSubmit;
 
 
 
       /**
        * Constructor
        */
       public function TestPage() {
           $this->txtUsername = new TTextBox();
           $this->btnSubmit = new TButton();
       }
 
 
       /**
        * On page load event
        */
       public function OnPageLoad() {
           if (!$this->isPostback()) {
               $this->txtUsername->setValue("my username");
           }
       }
 
 
       /**
        * On click button event
        */
       public function btnSubmit_OnClick() {
           $this->addMessage("Username:" . $this->txtUsername->getValue());
       }
 
    }
?>

This is the generated file which is sent to the browser.
./
test.html
<html>
<head>
<!-- HEAD GENERATED BY THE FRAMEWORK -->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link id='idLafCssLink' rel='stylesheet' type='text/css' href='/assets/css/skin1/index.php?2026593187'/>
<script type='text/javascript' src='/system/scripts/qphp.js.php?2026593187'></script>
<script type="text/javascript">
    var page;
    window.addEventListener('load', function() {
        page = new TPage();
        page.submitUri = "/test.php";
        page.devMode = true;
        page.ajax = new THttpAjax();
        if (typeof window.finalizePage == 'function') eval('finalizePage()');
        if (typeof window.OnPageLoad == 'function') eval('OnPageLoad()');
    });
</script>
<!-- // HEAD GENERATED BY THE FRAMEWORK -->
<title>Test</title>
 
<script type="text/javascript">
 
    function btnSubmit_OnClick() {
        var user = page.txtUsername.getValue();
        if (!user) {
            return false;
        }
    }
 
</script>
 
</head>
 
<body>
 
<input type='text' id='txtUsername' name='txtUsername' style='width:200px' />
<input type='button' id='btnSubmit' name='btnSubmit' value='Submit (ajax)' />
 
<script type='text/javascript'>function finalizePage(){page.registerWidget('txtUsername', new TTextBox('txtUsername', []));page.registerWidget('btnSubmit', new TButton('btnSubmit', {"mode":"ajax","cm":null}));}</script>
</body>
 
</html>


First take a look at the very first line.
include "test.php.script"; $page = TPage::create();
Here we include the Controller and through TPage::create() initialize the class, which will handle the requests. This static method create() is smart enough to find the target class only if there is only one class included, which extends TPage. If you want you can do also $page = TPage::create("TestPage"); In 99% of the cases it is not necessary. During initialization TestPage will call many methods like: OnPageInit, OnPageLoad, OnLoadState, OnSaveState, OnPostBackData, etc. They will guarantee the correct page construction. You can override any of them, but probably you'll never need to do it except one of them.
       public function OnPageLoad() {
           if (!$this->isPostback()) {
               $this->txtUsername->setValue("my username");
           }
       }
This method is called every time a page is loaded. It is also called when a control submits back a "post" action, like when a button is clicked, a drop down control generates OnChange event, etc. This method is never called on "ajax" action.

       $page->btnSubmit->render(Array(
                                   "Text" => "SUBMIT",
                                   "SubmitMode" => "post"
                                  ));
For all the widgets, which need to submit back data to the Controller we can set the "SubmitMode" property. Possible values are "post", "ajax", "js" or "none". We can set a widgets' properties 2 ways. The 1st way is like the one above and the second way like the one below. All the keys are converted to lowercase, so "Text" in this case is the same as "teXt".
    $page->btnSubmit->setProperty("teXt", "SUBMIT");
    $page->btnSubmit->setProperty("submitmode", "post");
    $page->btnSubmit->render();
Let's see how the events are triggered. Let's take for example the submit button. If it's "SubmitMode" is "post" or "ajax", then it's event handler will reside in the test.php.script. If it is "js" it will reside as javascript function in test.php. And here comes a little trick, when it is "ajax" or "post" and we have defined a javascript handler it will be called before the server handler. If the javascript handler returns "false", then the server method won't be called. This is very practical when we want to do input validation on the client before sending data to the server.

It is very important always to place in every *.php file just after the <head> tag this code:
$page->head->render();
When the output is rendered it will become:
 var page;
 function initializePage() {
     page = new TPage();
     page.submitUri = "/tutorials/index.php";
     page.devMode = true;
     page.ajax = new THttpAjax();
     if (typeof window.finalizePage == 'function') eval('finalizePage()');
     if (typeof window.OnPageLoad == 'function') eval('OnPageLoad()');
     page.setFinalized();
 }
 
 if (window.attachEvent) {
     window.attachEvent('onload', function() {initializePage();});
 }
 else {
     window.addEventListener('load', function() {initializePage();}, false);
 };
It provides us with a javascript variable "page" of type "TPage", which is responsible for many things. It is also very important always to place in every *.php file just before the </body> tag this code:
$page->finalize();
When the output is rendered it will become:
function finalizePage(){
    page.registerWidget(new TTextBox('txtUsername', []));
    page.registerWidget(new TButton('btnSubmit', {"submitMode":"ajax"}));
}
When using templates through the TPageTemplate class make sure both tags exist. The framework knows how to plug the needed javascripts.




How client -> server -> client data exchange works
There is a very well organized communication channel between the PHP variable "page" and the javascript variable "page", both of them of type "TPage". Also for every widget, which instance is TSomeWidget, there is a javascript widget with the same instance name and same class. This way the widget knows how to pass data in both directions. All the widgets are registered with the page on the server and on the client. The mechanism is well optimized and keeps the traffic low. Every javascript widget provides these methods: setValue, getValue, setEnabled, setVisible, getLocation, setLocation, getDimension, setDimension, addLocaleStrings and applyClass. When a button is clicked, the framework enumerates all the registered widgets and sends back to the Controller their values. This model is designed in such a fashion, because when we have a more complicated widget like a date picker, which renders to 3 dropdown controls, it's getValue method knows very well how to construct the final value. Also there are many free javascript widgets, which can be used just with a simple PHP wrapper around them.

Another possible way of communication is through the Model. There are again 2 objects of type TDataModel both on the client and server, which exchange data. If on the server we set:
    $page->model->setProperty("key2", "City of London", TRUE);
    $page->model->setProperty("key3", "The girl from Paris", TRUE, TRUE);
Then on the client both keys would be accessible through:
    var value2 = page.model.getProperty("key2");
    var value3 = page.model.getProperty("key3");
And if later a "post" submit or an "ajax" call is made to the server, "key3" will still be available(notice the 2 TRUE parameters). Also we can achieve the same thing using javascript:
    page.model.setProperty("key3", "The girl from Paris", true);
The framework has been optimized for years, it is very fast exchanging data back and forth.