AJAX & the Adventure PHP Framework

1. Introduction

AJAX is noted for being a common technique to build web applications, that are more agile compared to standard applications. It gives the possibility to change specific parts of the DOM of a HTML page by using the XMLHTTPRequest implementation and some lines of java script code.

AJAX is not brand-new and there are a plenty of online arcticles about building AJAX applications. At a glance, the following resources are worth being read: Starting to write AJAX applications you will face with the problem to integrate AJAX widgets and dynamic updates of certain areas of your web page into existing application structures and designs. This fact often leads to the problem, that AJAX-featured applications tend to be not well-designed. Im most cases, the client <-> server communication is done by requesting a file (e.g. a PHP file), that sends back an XML string containing the information the client needs. In common for each AJAX "action" one appropriate file is created. This does not only lead to complexity but also to redundant code in the server side scripts. A second problem is security. Due to the fact, that the application code merely resides on the client and hence can be analysed and manipulated easily, an application tends to get insecure.

The two problems described above are well known problems. On this account many server and client side AJAX frameworks have been built to solve the daily life problems a programmer is facing during AJAX application development. However, building AJAX aplications is not as easy as it is described in many of those "getting started" tutorials.


2. Which tools are included in the APF?

Basically the Adventure PHP Framework features no special AJAX component to use either on the client or on the server side. Having no special module does not mean, that the APF does not have a way to facilitate server side programming within AJAX applications.

To assist implementation of server side AJAX application parts, the front controller can be used. Taking the front controller supports the programmer to build server side AJAX "actions" in a standardized and powerful way. He is not forced to create several action files and reimplement the functionality included in those files several times. Each AJAX action can be represented by one special front controller action. Due to the fact that the front controller pattern is located in the business layer, the front controller action can easily use existing application components (e.g. business or data layer classes). Moreover using the front controller makes it easy to access the parameters the action is called with. In other words, all modules known for building server side applications can be involved.

Due to a wide range of java sctip AJAX frameworks the Adventure PHP Framework features no java script libraries. For client side purpose any common AJAX framework can be used to create the desired widgets. The following list shows a selection of famous java script frameworks:

3. My first AJAX application

To not getting you bored with theory, this chapter shows you, how to build a AJAX web application with the Adventure PHP Framework from the scratch.

3.1. Requirements and considerations

Due to the fact, that we both are good software architects, we need to think about the requirements of the software, we want to create. To keep things simple, the application to design should be simple and easy to imagine. I think a news box would be a good example to show how an AJAX application can be built with the Adventure PHP Framework.

Let me give you a brief description of the major features:
  • news box can be included in any Adventure PHP Framework application
  • news can be paged clockwise and vice versa
  • news are read from several text files, each containing one news page, to keep maintenance easy

3.2. Design of the application

Compared to a standard application only the paging functionality is realized in AJAX style. The rest of the module can be implemented as customary. The concepts used here (commonly known as patterns) can be comprehended reading the Comment function. But the focus of this article lies on the design of the AJAX part of the application.

In order to implement the paging functionality we need client and server addons. On the client side we will use some java script code around the XMLHTTPRequest object to draw the widgets and to catch user interaction. The server side is done by a special front controller action, that reads the desired news page and gives it back to the client.


4. Implementation

To make sure, that the news box can be included in any APF style application we create a new module named newspager. In the first instance, the folders and files must be created as follows:
Code
/apps/ modules/ newspager/ biz/ actions/ data/ pres/ documentcontroller/ templates/
As a next step, the files necessary must be created. As already mentioned above, we need to create the following files:
  • biz/newspagerManager.php:
    business component, that is used to get the news page content
  • biz/newspagerContent.php:
    the domain object, that contains the content of one news page
  • biz/actions/NewspagerAction.php:
    file that contains the front controller action class
  • biz/actions/newspagerInput.php:
    file that contains the front controller input class
  • data/newspagerMapper.php:
    data layer component, that loads the newspager content from text files
  • pres/documentcontroller/NewsPagerController.php:
    document controller, that generates the output of the module
  • pres/templates/newspager.html:
    template file, that contains the style of the module and the additional java script code needed to add AJAX style paging

In this tutorial we are going to use the bottom-up approch during implementation phase.


4.1. Data layer implementation

The data layer's duty is to load content from plain text files, map a news file into a domain object and give it back to the business layer. The news files are located in the ./frontend/news/ folder located in parallel to the index.php file of a project. Each file contains news text in a certain semantic: the first line is interpreted as the headline, the second as a subheadline and the rest of the file is assumed to belong to the content. On this account the domain object mentioned above has three properties: title, subtitle and content. To have multilanguage support, each file should be named like
APF template
news_{LANG}_{PAGENUMBER}.txt
Here {LANG} must be any two letter ISO language code, {PAGENUMBER} is a number beginning with 1. As an example, the news pager module can have the following news files located in ./frontend/news/:
APF template
news_en_1.txt news_en_2.txt news_en_3.txt news_en_4.txt news_en_5.txt news_it_1.txt news_it_2.txt news_it_3.txt news_it_4.txt
The source code of the data layer class can be seen in the code box below:
PHP code
import('modules::newspager::biz','newspagerContent'); import('core::filesystem','filesystemHandler'); class newspagerMapper extends APFObject { /** * @private * Defines the dir, where the news content is located. */ private $dataDir = null; public function init($DataDir){ $this->dataDir = $DataDir; } public function getNewsByPage($PageNumber){ // create filesystem handler $fM = new filesystemHandler($this->dataDir); // read all files located there $RawFiles = $fM->showDirContent(); // get files, that match the current language $Files = array(); $count = count($RawFiles); for($i = 0; $i < $count; $i++){ if(substr_count($RawFiles[$i],'news_'.$this->language.'_') > 0){ $Files[] = $RawFiles[$i]; } } // throw error when page count is zero! $NewsCount = count($Files); if($NewsCount == 0){ trigger_error('[newspagerMapper::getNewsByPage()] No news files are given for language '.$this->language,E_USER_ERROR); exit; } // if page number is lower then zero, correct it! if($PageNumber <= 0){ $PageNumber = 1; } // if page number is higher then max, correct it! if($PageNumber > $NewsCount){ $PageNumber = $NewsCount; } // read content of file $NewsArray = file($this->dataDir.'/'.$Files[$PageNumber - 1]); // initialize a new news content object $N = new newspagerContent(); // fill headline if(isset($NewsArray[0])){ $N->set('Headline',trim($NewsArray[0])); } // fill sub headline if(isset($NewsArray[1])){ $N->set('Subheadline',trim($NewsArray[1])); } // fill content $count = count($NewsArray); if($count >= 3){ $Content = (string)''; for($i = 2; $i < $count; $i++){ $Content .= $NewsArray[$i]; } $N->set('Content',trim($Content)); } // set news count $N->set('NewsCount',$NewsCount); return $N; } }
Calling the mapper class like
PHP code
$M = new newspagerMapper(); $M->setContext('sites::apfdocupage'); $M->setLanguage('en'); echo printObject($M->getNewsByPage(1));
is will return an newspagerContent object, that contains the information of the first news page residing in the news_en_1.txt file. This object can later on be used to deliver the xml requested by the java script code executed on the client machine or to generate the output of the news box during rendering of the page.


4.2. Business layer implementation

4.2.1. Domain object
First of all, we must define the newspagerContent class, that was already used in the data layer component. This class is a simple data storage class, that contains the information of a certain news page. Due to the fact, that this class inherits from APFObject no more get() and set() methods must be implemented. The source code looks like this:
PHP code
class newspagerContent extends APFObject { private $headline; private $subHeadline; private $content; private $newsCount; }

The attribute $newsCount was introduced to control the pager widgets.

4.2.2. The manager
To be compilant to the classical thee tier architecture described in the pattern of the same name, we introduce a business component, that contains the business logic of the application. In this case the business layer component (often called manager) only contains of one singe method: getNewsByPage(). The implementation is quite easy and so the php code looks as follows:
PHP code
import('modules::newspager::data','newspagerMapper'); class newspagerManager extends APFObject { private $dataDir = null; public function init($DataDir){ // cut trailing slash if necessary if(substr($DataDir,strlen($DataDir) - 1) == '/'){ $this->dataDir = substr($DataDir,0,strlen($DataDir) -1); } else{ $this->dataDir = $DataDir; } } public function getNewsByPage($PageNumber = 1){ // get mapper $nM = &$this->getAndInitServiceObject('modules::newspager::data','newspagerMapper',$this->dataDir); // load and return news object return $nM->getNewsByPage($PageNumber); } }
A unit test for the class and the underlaying data layer may look like this:
PHP code
$M = new newspagerManager(); $M->setContext('sites::apfdocupage'); $M->setLanguage('en'); echo printObject($M->getNewsByPage());
This statement will print out the content of the loaded newspagerContent object.


4.2.3. AJAX data source
As mentioned above, the client side java script codes needs a server side data source to obtain the desired news page via xml. For this reason a front controller action is implemented, that delivers the desired page. Due to the fact, that we have a business layer component (manager), the action can make use of this class. The only task is then to transform the object returned by the business layer component into valid xml and transfer it to the client machine. To indicate, which page should be served, the front controller action url string does contains the parameter page. Adventure php framework front controller actions normally contain three main parts: the action class definition, the input class definition and the action configuration that defines the parameters of the front controller action. For details, please refer to Front controller. Because this example has no special requirements, the implementation is straight forward.

First of all, we have to implement the input object, that is filled with the url parameters present in the url string when requesting the action. The class must inherit from the FrontcontrollerInput class and the file only contains the following body:
PHP code
class newspagerInput extends FrontcontrollerInput { }
The action class definition encapsulates the functionality described a few lines above. As described on the Front controller page, each action must implement the interface method run(), that is called during action execution by the front controller. This function includes the main capacity and delivers the xml string to the requestor. The source code may look like follows:
PHP code
import('modules::newspager::biz','newspagerManager'); class NewspagerAction extends AbstractFrontcontrollerAction { public function run(){ // get desired page number, language and data dir $page = $this->input->getAttribute('page'); $Language = $this->input->getAttribute('lang'); $DataDir = base64_decode($this->input->getAttribute('datadir')); // get manager $nM = &$this->getAndInitServiceObject('modules::newspager::data','newspagerManager',$DataDir); // set language $nM->setLanguage($Language); // load news object $N = $nM->getNewsByPage($page); // create xml $XML = (string)''; $XML .= '<?xml version="1.0" encoding="utf-8" ?>'; $XML .= '<news>'; $XML .= '<headline>'.$N->get('Headline').'</headline>'; $XML .= '<subheadline>'.$N->get('Subheadline').'</subheadline>'; $XML .= '<content>'.$N->getContent().'</content>'; $XML .= '<newscount>'.$N->get('NewsCount').'</newscount>'; $XML .= '</news>'; // send xml header('Content-Type: text/xml; charset=iso-8859-1'); echo $XML; // close application exit(0); } }
To be able to write a unit test, or to call the action, the third part of an APF style front controller action must be added: the configuration. To create this file, we must define, under which namespace the action should be callable. For this example, we assume, that the action can be executed using the url definition
Code
http://adventure-php-framework.org/~/modules_newspager_biz-action/Pager/page/1/lang/en/~/modules_newspager_biz-action/Pager/page/{PAGE}/lang/{LANG}
where {PAGE} must be replaced by any integer and LANG a two letter ISO language code. Hence, the namespace of the action is modules::newspager::biz and though the configuration file for this action must be created under the folder
Code
/apps/config/modules/newspager/biz/actions/{CONTEXT}/
Thereby {CONTEXT} is the folder path resulting from the current context of the application. In this example, the context is sites::demosite, and consequently, the full path must be
Code
/apps/config/modules/newspager/biz/actions/sites/demosite/
The config file itself must be named
Code
DEFAULT_actionconfig.ini
because the current environment variable is set to DEFAULT. In common, the action definition file contains the following lines:
APF configuration
[Pager] FC.ActionNamespace = "modules::newspager::biz::actions" FC.ActionClass = "NewspagerAction" FC.InputClass = "NewspagerInput" FC.InputParams = ""
As described on the Front controller page, the section name corresponds to the action name, the section itself defines which files contain the action and input classes. The sixth parameter can be used to specify standard parameters for the input object.

Calling the url
APF template
/~/modules_newspager_biz-action/Pager/page/1/lang/en
will display a XML similar to that:
APF template
<news> <headline>APF news</headline> <subheadline>New version 1.5 available!</subheadline> <content> We are proud to present the new version 1.5 of the Adventure PHP Framework. It includes bugfixes and enhancements of the front controller component and a complete review of the documentation plus translation of the entire documentation chapters into english language. </content> <newscount>4</newscount> </news>

4.3. Presentation layer implementation

The presentation layer faces two main issues: create the news box on page creation and provide the necessary java script code to update the news box in AJAX style. Sensibly, we first deal with the generation of the news box, because this can be done with standard Adventure PHP Framework appliances. Due to the fact, that the news box is created through dynamically generated content (here: text files), we need a document controller and a template file. The template file defines the structure of the box according to the MVC pattern and the document controller generates the view content using the template file definition.


4.3.1. The template file
Due to the fact, that we have to display a box with three values filled in it, the template file looks as follows:
APF template
<@controller namespace="modules::newspager::pres::documentcontroller" class="NewsPagerController" @> <div style="width: 300px; float: right; border: 1px dashed #777BB4; margin: 10px;"> <table cellpadding="0" cellspacing="0"> <tr> <td style="text-align: center; vertical-align: center; width: 15px; background-color: #777BB4; font-size: 16px;"> <span style="font-weight: bold;">&lt;</span> </td> <td style="width: 270px; height: 80px; padding: 2px; vertical-align: top; text-align: left;"> <span style="font-variant: small-caps; font-size: 16px; font-weight: bold;"> <html:placeholder name="Headline" /> </span> <br /> <span style="font-size: 12px; font-style: italic;"><html:placeholder name="Subheadline" /></span> <br /> <br /> <span style="font-size: 11px;"><html:placeholder name="Content" /></span> </td> <td style="text-align: center; vertical-align: center; width: 15px; background-color: #777BB4; font-size: 16px;"> <span style="font-weight: bold;">&gt;</span> </td> </tr> </table> </div>
As the html code box shows, the template file contains a document controller specification and three place holders to be filled with the content of the domain object.


4.3.2. The document controller
The corresponding document controller loads the newspagerContent object and filles the place holders:
PHP code
import('modules::newspager::biz','newspagerManager'); class NewsPagerController extends BaseDocumentController { public function transformContent(){ // get manager $nM = &$this->getServiceObject('modules::newspager::data','newspagerManager'); // load default news page $N = $nM->getNewsByPage(); // fill place holders $this->setPlaceHolder('Headline',$N->get('Headline')); $this->setPlaceHolder('Subheadline',$N->get('Subheadline')); $this->setPlaceHolder('Content',$N->getContent()); } }

4.3.3. The AJAX java script code
To add dynamic paging to the news box, some java script code must be added. Therefore, we add a
APF template
<script type="text/javascript"> </script>
to the newspager.html template file. As we have learned above, the XMLHTTPRequest object is used to send a request via java script to retrieve dynamic information from a server script to update a page part without reloading the page. For this reason we use the following java script function to create an instance of the XMLHTTPRequest object:
APF template
function createXMLHttpRequest(){ if(window.ActiveXObject){ try{ // IE 6 and higher xhttp = new ActiveXObject("MSXML2.XMLHTTP"); } catch (e){ try{ // IE 5 xhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e){ xhttp = false; } } } else if(window.XMLHttpRequest){ try{ // Mozilla, Opera, Safari ... xhttp = new XMLHttpRequest(); } catch (e){ xhttp = false; } } } // execute createXMLHttpRequest() on window load window.onload = createXMLHttpRequest;
The window.onload tells the browser to execute the createXMLHttpRequest after having loaded the page. In order to be able to manipulate the content of the page, we have to upgrade the template definition with ids for each tag. So we can access each tag by using the
APF template
var dom_node = document.getElementById('dom_node_id');
construct. Moreover, we must add an event handler to the two pager signs, so that the content of the box can be changed. Therefore I defined two different java script functions (prev() and next()), that wrap the functionality of getting forward or backward. Te next code box shows the changes, we have to apply to the html code of the template file:
APF template
<div style="width: 300px; float: right; border: 1px dashed #777BB4; margin: 10px;"> <table cellpadding="0" cellspacing="0"> <tr> <td style="text-align: center; vertical-align: center; width: 15px; background-color: #777BB4; font-size: 16px;"> <span id="newspager_prev_button" style="font-weight: bold; cursor: pointer; visibility: hidden;" onclick="prev();"> &lt; </span> </td> <td style="width: 270px; height: 80px; padding: 2px; vertical-align: top; text-align: left;"> <span id="newspager_headline" style="font-variant: small-caps; font-size: 16px; font-weight: bold;"> <html:placeholder name="Headline" /> </span> <br /> <span id="newspager_subheadline" style="font-size: 12px; font-style: italic;"> <html:placeholder name="Subheadline" /> </span> <br /> <br /> <span id="newspager_content" style="font-size: 11px;"><html:placeholder name="Content" /></span> </td> <td style="text-align: center; vertical-align: center; width: 15px; background-color: #777BB4; font-size: 16px;"> <span id="newspager_next_button" style="font-weight: bold; cursor: pointer;" onclick="next();"> &gt; </span> </td> </tr> </table> </div>
The functions mentioned above contain the following lines:
APF template
function next(){ // check if XMLHTTPRequest object is initialized correctly if(!xhttp){ alert("An Error occurred when trying to initialize the XMLHttpRequest object!"); return; } // increment page number pagenumber = pagenumber + 1; // create request var request = "/~/modules_newspager_biz-action/Pager/page/" + pagenumber + "/lang/" + language; // send request for next news xhttp.open("GET",request,true); xhttp.onreadystatechange = updateNewsContent; xhttp.send(null); } function prev(){ // check if XMLHTTPRequest object is initialized correctly if(!xhttp){ alert("An Error occured when trying to initialize the XMLHttpRequest object!"); return; } // decrement page number pagenumber = pagenumber - 1; // create request var request = "/~/modules_newspager_biz-action/Pager/page/" + pagenumber + "/lang/" + language; // send request for next or prev news page xhttp.open("GET",request,true); xhttp.onreadystatechange = updateNewsContent; xhttp.send(null); }
First of all, each function checks, wheather the XMLHTTPRequest object was created successfully. Second, the page number is either incremented or decremented to indicate the page number to load. After that, the front controller style request is constructed. Due to the fact, that xml http requests only can be done for the same domain, the page is delivered, we can define the url string relatively. The last code block then opens the defined url for a GET request, defines a event handler for the readystatechange event and sends the request.

Some of you now may say, that the function definition should be more generic. The answer is: yes, it may. But this example is there to demonstrate the way of how you can add AJAX support to your application. As this module is quite simple, complexity drastically raises, when implementing more substantial applications.

Let's now discuss the event handler function for updating the content view of the news box. This method has two main tasks to execute: display the content of the XMLHTTPRequest request and control the pager arrows, when the maximum or minimum number of pages has been reached. To do so, we have to create the following java script code:
APF template
function updateNewsContent() { // check for ready loadad and HTTP status 200 (OK) if(xhttp.readyState == 4 && xhttp.status == 200){ // get request xml var responseXML = xhttp.responseXML; // insert xml values into the desired spans var headline = responseXML.getElementsByTagName('news').item(0).childNodes[0].textContent; document.getElementById("newspager_headline").innerHTML = headline; var subheadline = responseXML.getElementsByTagName('news').item(0).childNodes[1].textContent; document.getElementById("newspager_subheadline").innerHTML = subheadline; var content = responseXML.getElementsByTagName('news').item(0).childNodes[2].textContent; document.getElementById("newspager_content").innerHTML = content; // extract news count out of the xml var newscount = responseXML.getElementsByTagName('news').item(0).childNodes[3].textContent; // enable prev and next button by default document.getElementById("newspager_prev_button").style.visibility = "visible"; document.getElementById("newspager_next_button").style.visibility = "visible"; // disable next button if desired if(pagenumber >= newscount){ document.getElementById("newspager_next_button").style.visibility = "hidden"; } // disable prev button if desired if(pagenumber <= 1){ document.getElementById("newspager_prev_button").style.visibility = "hidden"; pagenumber = 1; } }
As you can see in line 4, the function waits for the request being completed. If this is the case, the content of the request is read from the xhttp object created before. The responseXML attribute contains a DOM document, that can be accessed via java script functions. The second code block updates the spans addressed by the given DOM ids. To be able to control the pager buttons, we have to know the maximum number of pages and the current page number. For this reason the news count is initially filled and is included in each response. The initialisation of the newscount value is simply done by adding the declaration block
APF template
// declare variable to be initialized with the XMLHttpRequest object var xhttp; // declare current page number var pagenumber = 1; // declace news count var newscount = <html:placeholder name="NewsCount" />; // declare current language var language = "<html:placeholder name="NewsLanguage" />";
to the java script code and fill the place holders by append the following php code to the document controller:
PHP code
$this->setPlaceHolder('NewsLanguage',$this->language); $this->setPlaceHolder('NewsCount',$N->get('NewsCount'));

5. Lessons learned

This chapter is intended to list the pitfalls, that can appear during AJAX application development.
  • Apache answers request with a 404 error page:
    If you write any application, that saves data in your data base using an AJAX request, it is recommended to encode your form data using the encodeURIComponent() function. Doing so, also slashes and backslashes are encoded. If your server side AJAX component is a front controller action, that is requested by a url rewrited url string, having encoded slashes or backslashes will result in a 404 error code. The problem is, that the apache webserver decodes the slashes and thus creates a wrong url.

    The solution is quite easy: you just have to add the directive
    APF template
    AllowEncodedSlashes On
    to your VHOST configuration. This apache bug cost me three days to find!

  • Degugging tools:
    Unfortunately only the firefox browser contains a powerful debugging and analyzing tool (firebug), that really helps tracing your application. Opera features a firefox-like toolbar, but the functionality is less. For the internet explorer I have not found any equivalent product to be able to see the browser objects (especially the content of the xhttp variable). The IE toolbar only has a DOM tree browser and some other useful features.

    Due to this fact, I was not able to get the news box application running on IE browsers. Perhaps someone could give me advice. Please refer to the following thread, that contains a description: http://www.phpfriend.de/forum/ftopic63486.html.

  • Character encoding issues:
    As I mentioned during tests with user input data, firefox and opera send data in UTF-8 format to the server although the page is delivered in ISO-8859-1 encoding. This leads to the problem, that data, that is sent back to the browser is encoded with the wrong character set. To solve this, the data must be translated into the target charset using utf8_decode().

  • Implementation review:
    In my opinion it is much more complex to build AJAX applications from the scratch, because you are forced to implement all the features you have included in a server side php framework like the Adventure PHP Framework once more. Otherwise you are getting crazy with all that
    APF template
    document.getElementById()
    or
    APF template
    foo.innerHTML = bar
    So adding AJAX features to your application means, that you have to think very carefully about your application or module design. I even claim, that writing AJAX applications needs much more understanding of good application design. Otherwise, this leads to bad software architecture!

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
« 1   »
Entries/Page: | 5 | 10 | 15 | 20 |
1
cheap_cialis 18.10.2016, 08:06:31
Answered: Is there a generic for One-A-Day cheap cialis?
2
buy_viagra 09.09.2016, 11:40:47

buy viagra for women, WbpOYFs, [url=/buy-viagra-usa.
3
Christian 06.09.2014, 09:22:30
Hallo apffan200,

das ist natürlich auch möglich. Hierzu einfach den $.ajax()-Aufruf entsprechend abändern. Details hierzu findest du unter http://api.jquery.com/jQuery.ajax/.
4
apffan200 09.01.2014, 16:24:43
Schönes Tutorial,

aber ist es auch möglich, Daten via Post zu versenden? Das wäre in einigen Fällen aus Sicherheitsgründen ganz gut

LG apffan200