AJAX & the Adventure PHP Framework

1. Introduction

AJAX is an acronym for Asynchronous JavaScript and XML. It is a common technique to build web applications, that are more responsive compared to standard applications. It gives you the possibility to communicate with your servers without changing the whole page but only specific parts of the DOM of an HTML page. The only thing you need is the XMLHTTPRequest implementation and some lines of java script code.

AJAX is not brand-new and there are a plenty of online articles about building AJAX applications. At a glance, the following resources are worth being read:

Starting to write AJAX applications you will be facing 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 applications 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. Using the front controller supports the programmer to build server side AJAX actions in a standardized and powerful way.

You are not forced to create several action files and re-implement the functionality included in those files several times. Each AJAX action can be represented by one special front controller action. Moreover using the front controller makes it easy to access parameters the action is applied.

Due to a wide range of java script 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 bothering you with too much 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 want to build a clean and maintainable software thinking about the requirements first is a good idea. To keep things simple, the application to design should be simple and easy to imagine. We think a news box would be a good example to show how an AJAX application can be built with the Adventure PHP Framework.

Let us give you a brief description of the major features:

  • news box can be included in any APF 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 for others.

In order to implement the paging functionality we need client and server add-ons. On the client side we will use some java script code around jQuery 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
/APF/ modules/ newspager/ biz/ actions/ data/ news/ pres/ css/ js/ controller/ templates/

As a next step, the files necessary must be created. As already mentioned above, we need to create the following files:

  • biz/NewsItem.php: Domain object that contains the content of one news page.
  • biz/actions/NewsPagerAjaxBackendAction.php: Contains the Front controller action class.
  • data/NewsPagerProvider.php: Data layer component that loads the news content from JSON text files.
  • pres/controller/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
  • pres/css/newspager.css: CSS file containing sample styling.
  • pres/js/newspager.js: Java script file containing the necessary java script code to run the module.

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

4.1. Data layer implementation

The data layer component - the NewsPagerProvider - loads one page's content from a JSON file located at any desired place on the local disk.

Each file contains one page's content as follows:

Code
{ "headline": "...", "subline": "...", "content": "..." }

To support multi-language news definition each file is named using the following scheme::

APF template
news_{LANG}_{PAGE-NUMBER}.json

{LANG} refers to the language of the application as configured within the index.php file. {PAGE-NUMBER} is a number beginning with 1. If you want to present 3 news items, please create the following files:

Code
news_en_1.json news_en_2.json news_en_3.json

In order to easily read the news pages the NewsPagerProvider has been introduced. Method getNewsByPage() returns an instance of NewsItem which in turn provides appropriate getter methods to retrieve eache page's content.

The source code of the data layer class can be taken from the code box below:

PHP code
use APF\core\pagecontroller\APFObject; use APF\core\pagecontroller\IncludeException; use APF\modules\newspager\biz\NewsItem; use APF\tools\filesystem\FilesystemItem; use APF\tools\filesystem\Folder; class NewsPagerProvider extends APFObject { public function getNewsByPage($dataDir, $page) { // cut trailing slash if necessary if (substr($dataDir, strlen($dataDir) - 1) == '/') { $dataDir = substr($dataDir, 0, strlen($dataDir) - 1); } // read all files located there $folder = new Folder(); $rawFiles = $folder->open($dataDir)->getContent(); // get files, that match the current language /* @var $files FilesystemItem[] */ $files = array(); foreach ($rawFiles as $data) { if (substr_count($data->getName(), 'news_' . $this->getLanguage() . '_') > 0) { $files[] = $data; } } // throw error when page count is zero! $newsCount = count($files); if ($newsCount == 0) { throw new IncludeException('[NewsPagerProvider::getNewsByPage()] No news files are ' . 'given for language ' . $this->getLanguage(), E_USER_ERROR); } // if page number is lower then zero, correct it! if ($page <= 0) { $page = 1; } // if page number is higher then max, correct it! if ($page > $newsCount) { $page = $newsCount; } // read content of file $rawItem = json_decode(file_get_contents($dataDir . '/' . $files[$page - 1]->getName())); // fill a new news content object $item = new NewsItem(); $item->setHeadline($rawItem->headline); $item->setSubHeadline($rawItem->subline); $item->setContent($rawItem->content); $item->setNewsCount($newsCount); return $item; } }

4.2. AJAX data source

To enhance the module with an AJAX-style pager there is the need for a server side component that returns the data for one particular page without reloading the entire HTML document.

The Front controller offers a simple option to run different actions referred to by URL without having to change your application's bootstrap file or creating separate files and scatter functionality. Since we've introduced a simple component to read the news pages (NewsPagerProvider) action implementation is quite simple: read the content of the desired page and send back the content in JSON format.

To indicate which page to serve the front controller action is provided the the page and the datadir to load the content from.

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.

Please find the necessary source code within the below box:

PHP code
use APF\core\frontcontroller\AbstractFrontcontrollerAction; use APF\modules\newspager\data\NewsPagerProvider; class NewsPagerAjaxBackendAction extends AbstractFrontcontrollerAction { public function run() { $input = $this->getInput(); $page = $input->getParameter('page'); $dataDir = base64_decode($input->getParameter('datadir')); // inject the language here to ease service creation $this->setLanguage($input->getParameter('lang')); /* @var $provider NewsPagerProvider */ // load news object $provider = & $this->getServiceObject('APF\modules\newspager\data\NewsPagerProvider'); $news = $provider->getNewsByPage($dataDir, $page); // send json $response = $this->getResponse(); $response->setHeader(new HeaderImpl('Content-Type', 'application/json')); $response->setBody(json_encode(array( 'headline' => $news->getHeadline(), 'subheadline' => $news->getSubHeadline(), 'content' => $news->getContent(), 'newscount' => $news->getNewsCount() ))); $response->send(); } }

To run the action a configuration file must be created. Assuming that the action can be executed using url

Code
/~/APF_modules_newspager_biz-action/Pager/page/1/lang/en/datadir/...

the front controller expects configuration file {CONTEXT}_actionconfig.ini to be located under /APF/config/modules/newspager/biz/actions/{CONTEXT}/.

Content of the file is as follows:

APF configuration
[Pager] ActionClass = "APF\modules\newspager\biz\actions\NewsPagerAjaxBackendAction"

Details on the configuration of front controller actions can be read about under Configuration. where {PAGE} must be replaced by any integer and LANG a two lette

Calling the above-described url the action might return something like this:

Code
{ "headline": "Article summary I", "subline": "Encoded slashes lead to 404 error!", "content": "Using a front controller action as a server side component, ..." }

4.3. Presentation layer implementation

The presentation layer of the news pager has two main purposes: create the news box on page creation and provide the necessary java script code to update the news box in AJAX style.

First of all, we are creating the server-side news box code since this can be done with standard Adventure PHP Framework tools. Due to the fact, that the news box is created through dynamically generated content (here: text files), we are using a (Document-)Controller to fill content to 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

To display the news content of one single page let's use the following template:

APF template
<@controller class="APF\modules\newspager\pres\controller\NewsPagerController" @> <div class="apf-news-pager"> <div class="news"> <h3><html:placeholder name="Headline" /></h3> <h4><html:placeholder name="SubHeadline" /></h4> <p><html:placeholder name="Content" /></p> </div> <a class="prev" href="#"><</a> <a class="next" href="#">></a> </div>

As you can take from the source code, the template defines the basic structure of the page that we are going to fill within the next chapter.

4.3.2. The document controller

To display the content, the NewsPagerController fetches the desired page and pushes the content to the template:

PHP code
use APF\core\pagecontroller\BaseDocumentController; use APF\modules\newspager\data\NewsPagerProvider; class NewsPagerController extends BaseDocumentController { public function transformContent() { // get current data dir or trigger error $dataDir = $this->getDocument()->getAttribute('datadir'); if ($dataDir === null) { throw new \InvalidArgumentException('[NewsPagerController::transformContent()] Tag ' . 'attribute "datadir" was not present in the <core:importdesign /> tag ' . 'definition! Please specify a news content directory!'); } // load default news page /* @var $provider NewsPagerProvider */ $provider = & $this->getServiceObject('APF\modules\newspager\data\NewsPagerProvider'); $newsItem = $provider->getNewsByPage($dataDir, 1); // fill place holders $this->setPlaceHolder('Headline', $newsItem->getHeadline()); $this->setPlaceHolder('SubHeadline', $newsItem->getSubHeadline()); $this->setPlaceHolder('Content', $newsItem->getContent()); } }
4.3.3. The AJAX java script code

The dynamic paging functionality of the news is built upon some lines of java script code based on jQuery.

Please note the following code box containing the necessary code:

Java script code
$(document).ready(function () { var APFNP = {}; APFNP.page = 1; APFNP.newsPager = $('.apf-news-pager'); APFNP.totalPages = APFNP.newsPager.data('news-count'); APFNP.lang = APFNP.newsPager.data('lang'); APFNP.errorMessage = APFNP.newsPager.data('error-message'); APFNP.actionUrl = APFNP.newsPager.data('action-url'); APFNP.dataDir = APFNP.newsPager.data('data-dir'); APFNP.getUrl = function (page) { return APFNP.actionUrl + 'page/' + page + '/lang/' + APFNP.lang + '/datadir/' + APFNP.dataDir; }; APFNP.activate = function(){ APFNP.newsPager.fadeTo('fast', '1'); }; APFNP.deactivate = function(){ APFNP.newsPager.fadeTo('fast', '0.3'); }; APFNP.getContent = function (direction) { APFNP.deactivate(); if (direction == 'prev') { APFNP.page--; if (APFNP.page == 0) { APFNP.page = 1; APFNP.activate(); return; } } else { APFNP.page++; if (APFNP.page > APFNP.totalPages) { APFNP.page = APFNP.totalPages; APFNP.activate(); return; } } $.ajax({ dataType: 'json', cache: false, url: APFNP.getUrl(APFNP.page), success: function (data, textStatus, jqXHR) { $('.apf-news-pager .news h3').html(data.headline); $('.apf-news-pager .news h4').html(data.subheadline); $('.apf-news-pager .news p').html(data.content); APFNP.totalPages = data.newscount; APFNP.activate(); }, error: function (jqXHR, textStatus, errorThrown) { $('.apf-news-pager .news').html('

' + APFNP.errorMessage + '

'); APFNP.activate(); } }); }; $('.apf-news-pager > .prev').click(function (event) { APFNP.getContent('prev'); event.preventDefault(); }); $('.apf-news-pager > .next').click(function (event) { APFNP.getContent('next'); event.preventDefault(); }); });

To both allow dynamic paging the java script code requires a couple of information to be present at the module's HTML code. These are:

  • ... current language
  • ... amount of total news
  • ... error message text in case the content cannot be retrieved
  • ... the AJAX data source
  • ... the directory to read the content from

As of HTML 5 data-* attributes can be added to every HTML tag without adding to the visual appearance of the respective element. This is ideal for exchanging data. Hence, our template is added attributes for the requested information as follows:

APF template
<@controller class="APF\modules\newspager\pres\controller\NewsPagerController" @> <div class="apf-news-pager" data-lang="<html:placeholder name="NewsLanguage" />" data-news-count="<html:placeholder name="NewsCount" />" data-error-message="<html:placeholder name="ErrorMsg" />" data-action-url="<html:placeholder name="ActionUrl" />" data-data-dir="<html:placeholder name="DataDir" />"> <div class="news"> <h3><html:placeholder name="Headline" /></h3> <h4><html:placeholder name="SubHeadline" /></h4> <p><html:placeholder name="Content" /></p> </div> <a class="prev" href="#"><</a> <a class="next" href="#">></a> </div>

Filling the additional attributes is done within the document controller as follows:

PHP code
use APF\core\pagecontroller\BaseDocumentController; use APF\modules\newspager\data\NewsPagerProvider; use APF\tools\link\LinkGenerator; use APF\tools\link\Url; class NewsPagerController extends BaseDocumentController { public function transformContent() { // get current data dir or trigger error $dataDir = $this->getDocument()->getAttribute('datadir'); if ($dataDir === null) { throw new \InvalidArgumentException('[NewsPagerController::transformContent()] Tag ' . 'attribute "datadir" was not present in the <core:importdesign /> tag ' . 'definition! Please specify a news content directory!'); } // load default news page /* @var $provider NewsPagerProvider */ $provider = & $this->getServiceObject('APF\modules\newspager\data\NewsPagerProvider'); $newsItem = $provider->getNewsByPage($dataDir, 1); // fill place holders $this->setPlaceHolder('NewsLanguage', $this->getLanguage()); $this->setPlaceHolder('NewsCount', $newsItem->getNewsCount()); $this->setPlaceHolder('Headline', $newsItem->getHeadline()); $this->setPlaceHolder('SubHeadline', $newsItem->getSubHeadline()); $this->setPlaceHolder('Content', $newsItem->getContent()); // set news service base url $url = LinkGenerator::generateActionUrl( Url::fromCurrent(), 'APF\modules\newspager\biz', 'Pager' ); $this->setPlaceHolder('ActionUrl', $url); $this->setPlaceHolder('DataDir', base64_encode($dataDir)); if ($this->getLanguage() == 'de') { $this->setPlaceHolder('ErrorMsg', 'Es ist ein Fehler beim Aufrufen der News aufgetreten! Bitte versuchen Sie es später wieder.'); } else { $this->setPlaceHolder('ErrorMsg', 'Requesting the next news page failed! Please try again later.'); } } }

5. Lessons learned

This chapter is intended to list pitfalls that may come up during AJAX application development.

Article summary I

Encoded slashes lead to 404 error!

Using a front controller action as a server side component, encoded slashes and backslashes lead to a 404 error if the "AllowEncodedSlashes = On" is not set in your VHOST configuration.

  • 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 rewritten url string, having encoded slashes or backslashes will result in a 404 error code. The problem is, that the apache web server 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!
  • Character encoding issues: Different browsers do handle character sets different for AJAX and normal HTTP requests. For instance, 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: 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
    $('#foo')
    or
    APF template
    $(foo).text(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 really 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