August 2008

Developer: Dynamic loading of lists using AJAX in a MVC component

Ajax, Mootools, JSon, and MVC

Written by Mathieu Chauvinc

Difficulty: Moderate

Requires: Joomla! 1.5.3 or later, Mootools 1.1, PHP5 (or PHP4 with the json library).

Desc: Tutorial on using Ajax and Mootools for a Form in a MVC component. The example used aims at dynamically loading a list of MySQL table fields when a table is selected from another list.

Useful tools: Firebug

Notes:
  • JS stands for Javascript
  • Words found in bold in the text are references to parts of the code such as methods, variables, ...
  • In purple color are the names of the files where code needs to be inserted

Getting started: Understanding the tools

AJAX

Ajax is a methodology which uses existing technologies to enable a website to make asynchronous requests to a server, meaning requests that are being sent without the page reloading. It has great advantages in terms of user-friendliness of websites.
Ajax is not a programming language. It uses Javascript + your server + XML (although we will use JSon instead of XML).

Mootools

Mootools is a lightweight but extremely powerful, object-oriented javascript framework. It works on any browser. All the Mootools code we will use could be re-written in "classic" javascript, without the Mootools framework, but that would be a shame.

JSon (read Jason)

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It has become the choice data-interchange format for AJAX calls, as it is much easier to parse than XML, both on the server side and the client side. What it does is:
  • from the client side, take a JS object and transform it into a URL-safe string
  • on the server side rebuild the object from the string (the object is then a regular PHP object)
  • when the response to the request is being sent, encode the PHP object into a string
  • on the client side, decode the string into a regular JS object

MVC

The Joomla! model-view-component is well documented and plenty of tutorials exist. We'll assume that you have a working MVC back-end component called com_mycomponent.

Our example

A large number of applications can be imagined for loading data using Ajax requests. These include:
  • selecting a country and dynamically loading the list of states / cities
  • dynamically loading a list of users using filters (such as type, part of the username, etc...)
  • dynamically testing that a username / an email address is not yet in use before even submitting the form
  • displaying the number of results that will be returned by a search, even before actually hitting the search button
  • HTML-based chat (GChat style)
  • ...


We will use the following example: when the page loads, a form is displayed, with a select list of all the tables in our database. When the user selects one of the tables, we will dynamically (without page reload) load the list of fields in that table.

Building the form

Assuming that you have a working MVC component; in the view.html.php, let's prepare our first list: the list of tables in the database. Joomla! has a built-in method for this: getTableList
The code is:
$db =& JFactory::getDBO();
$options[] = JHTML::_( 'select.option', '', 'Select a table' );
$tables = $db->getTableList();
       
// The function returns an array, so we cant use it straight into a JHTML::_( 'select.genericlist' , we have to create the options
foreach( $tables as $t ) {
       $options[] = JHTML::_( 'select.option', $t, $t );
}
       
$this->tables =& JHTML::_( 'select.genericlist', $options, 'db_tables', null, 'value', 'text', '', 'db_tables' );
Something very important is that Mootools works easily with the id attribute of DOM elements, which is why we set the id (not only the name) of the list to "db_tables"

Next we work on the layout (assuming that the layout is "default"). In default.php, the code is:
<form action="index.php" method="post" name="adminForm">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
    <td align="left" width="33%" id="tables-container">
        <?php echo $this->tables; ?>
    </td>
    <td align="left" width="33%" id="fields-container">
    </td>
    <td align="left" width="33%" id="console-container">
    </td>
</tr>
</table>
</form>
The name, action and method of the form have no influence on our Ajax call. Note that we've added ids to the three HTML <td> tags to make it easier for Mootools to work.
Ok, the layout is ready. Let's move on to preparing the client-side Ajax call.

Basic Client-side request

To make things clearer at first, let's start with a very simple request that updates the td#fields-container with the populated list of fields. We can spice things up a bit later.
The JS code below needs to be added to your document. Explanations on how to insert JS into a Joomla! document can be found here .
This is the javascript that needs to be added to your document (explanations for the code can be found below):
window.addEvent("domready",function(){
$("db_tables").addEvent("change",function(){
var url="index.php?option=com_mycomponent&format=raw&task=listFields&table="+this.getValue();
var a=new Ajax(url,{
method:"get",
update:$("fields-container")
}).request();
});
});
addEvent is the way Mootools handles events. It is advisable to use this method rather than setting e.g. onchange="" in the HTML tag (you can browse the Mootools forum if you wish to know more about this)
The domready event is similar to onLoad except that it is triggered as soon as the DOM is ready (which means for example that you don't need to wait until images are loaded). Finally, the dollar selector: $('db_tables') is the Mootools shortcut for document.getElementById('db_tables')

So, the first two lines mean:
// As soon as the DOM is ready, do the following
window.addEvent('domready',function(){
// When the select list with id 'db_tables' is changed, do the following
$('db_tables').addEvent('change',function(){
Next line is preparing the request URL. The vital part is to understand that an Ajax request is just another request to your webserver. So imagine if you were to click on a link that has the exact same url, what would happen? You would be directed to the component called com_mycomponent and the task listFields would be called. It's exactly what will happen here. We'll take care of the server side in the next section.
The getValue() method is the best (Mootools) way to get the value of a form element because it works on any type of input: value of a text input, selected option of a select list, even gives you an array if you are working on a mutliple select list.
Finally, understanding the scope in Mootools is a bit tougher and we'll skip this part here.... so to cut it short, the reference this means the $('db_tables') element.
// the url will look like e.g.: index.php?option=com_mycomponent&format=raw&task=listFields&table=jos_content ; this can be used to debug the server-side by putting this url in your browser address
var url='index.php?option=com_mycomponent&format=raw&task=listFields&table='+this.getValue();
Mootools has a class called Ajax which handles Ajax requests. In the next line we're creating an instance of that class. Great thing here: it is cross-browser, so you don't need to worry about checking for IE/Firefox etc...

That object Ajax takes two parameters: the url of the request which we have just built above and an object of options. The options we are setting are the method (post or get) and the DOM element that we want the request to update on success. A lot more options can be set, in particular a onComplete function to handle more than just updating a <td> (see chapter Spicing up the client side).
// Create the Ajax object with the url we just built
var a = new Ajax(url,{
// And setting some options
method:'get',
update:$('fields-container')
Finally, the Ajax class has a method called request() which actually sends the request. Mootools is able to chain functions so after creating the Ajax object (that's actually calling a function called initialize) we straight away send the request.
If you have firebug installed you will see in the console that a request has been send:

Note: the response should be an error for now since we have not yet taken care of the server side and the request would appear red in firebug.
Note2: the option would show com_mycomponent, not com_weblinks in the image above

Server-side response

The server-side response is quite straight forward if you are familiar with the Joomla! MVC. The request is directed to the controller of our component com_mycomponent. So in that controller we create a function with the same name as the task from the request; in controller.php:
    function listFields() {
    // Get the value of the table variable, don't forget to cast its type (cmd should do here)
        if( $table = JRequest::getVar( 'table', '', 'get', 'cmd' ) ) {
            $db =& JFactory::getDBO();
            // Grab the fields for the selected table
            $fields =& $db->getTableFields( $table, true );
            if( sizeof( $fields[$table] ) ) {
                // We found some fields so let's create the HTML list
                $options = array();
                foreach( $fields[$table] as $field => $type ) {
                    $options[] = JHTML::_( 'select.option', $field, $field );
                }
                $list =& JHTML::_( 'select.genericlist', $options, 'table_fields', null, 'value', 'text', '', 'table_fields' );
                // Remember that this is the same as a normal request, so displaying means echo, not return the list
                echo $list;
                // Return to keep the application from going anywhere else
                return;
            }
        } else {
            echo JText::_( 'No table selected' );
        }
    }

That's it! When you change the value selected in your form, you should see the correct list of fields appear in the second <td>
Important note: The value format=raw in the url is necessary to avoid getting the entire Joomla! template as a response.

Arguably, we should probably create a view.raw.php etc... I just think that it's not necessary in this simple case.

Spicing up the client-side

Two of the major issues when using Ajax are:
  • difficulties to bookmark the page if content is loaded using Ajax, especially for IE users
  • the user sometimes does not realize that something has happened, because the "loading" animation in the browser has not shown anything

 

The first problem is not our concern here. You should simply avoid loading page content using Ajax.
The second issue though, can easily be handled by adding a few things to our client side.
So, what we'll do in this section is:
  • Add a "loading" effect in place of the field list while Ajax is working;
  • Display a message stating that we've loaded a list; This is where we'll use JSon and the third <td> in our layout
  • Use a Mootools visual effect
 
Using the update option of the Ajax object is practical and simple but rather limiting. The onComplete function is much more powerful...
Replace the JS code used in the first part with this:
window.addEvent("domready",function(){
            var fx=new Fx.Style($("console-container"), "background-color", {duration:2000});
            $("db_tables").addEvent("change",function(){
                $("fields-container").empty().addClass("ajax-loading");
                var url="index.php?option=com_mycomponent&format=raw&task=listFields&table="+this.getValue();
                var a=new Ajax(url,{
                    method:"get",
                    onComplete: function(response){
                        var resp=Json.evaluate(response);
                        $("fields-container").removeClass("ajax-loading").setHTML(resp.html);
                        $("console-container").setHTML(resp.msg);
                        fx.set("#fff").start("#f60").chain(function(){
                            this.start.delay(2000,this,"#000");
                        });
                    }
                }).request();
            });
        });

JS Code Explanations

var fx=new Fx.Style($('console-container'), 'background-color', {duration:2000});
Create an object of the class Fx.Style, which will affect the DOM element $('console-container'), working on its style: background-color and for a duration of 2 seconds. Remember that Mootools is object oriented. We have just created the instance of an effect object, we'll have to start the effect later on.
$('fields-container').empty().addClass('ajax-loading');
What happens when we change the select list of tables? We empty the $('fields-container') element (clear the old list of fields) and dynamically add a CSS class. What the class looks like is up to you. But usually adding a rotating "load", of any kind, is nice. You can get those as animated gifs at Ajaxload.info . Make that gif the background of the class ajax-loading and you're all set.

Nothing else changes in the code until the onComplete method:
onComplete: function(response){
                        var resp=Json.evaluate(response);
                        $("fields-container").removeClass("ajax-loading").setHTML(resp.html);
                        $("console-container").setHTML(resp.msg);
                        fx.set("#fff").start("#f60").chain(function(){
                            this.start.delay(2000,this,"#000");
                        });
                    }
Again remember that Mootools is object-oriented. Ajax is a class, it has parameters but also methods. onComplete is one that you can define/overload. It's a function and can do anything you want it to.
First of all, we are going to receive a JSon-encoded object as a response (see the modifications on the server side below). So we need to transform it back into a JS object with Json.evaluate.
Second, we remove the dynamically added class (loading gif) and set the HTML inside the fields-container td from the JS object resp (grab it's property html):
$('fields-container').removeClass('ajax-loading').setHTML(resp.html);
Next we add the message stating that we've loaded something, to the message container td.
$('console-container').setHTML(resp.msg);
Finally, we start the Mootools effect. First, set the background to white, then start the 2-second effect from current color (white) to ... orange (#f60).
Chain is another great but a bit more advanced feature of Mootools. It means that after the start function is complete (entire effect) we start another function (the one inside chain()). In that function, this represents a pointer to the fx object.
So we will use the start function of that fx object again (this.start) but with a delay of 2 seconds: this.start.delay(2000,this,'#000') .
In Mootools, a function is also an object with parameters and methods. The function object has a method called delay, to which you need to pass the time to wait, bind the fx object (this) and pass the parameter which you would normally pass to the start function ('#000').
fx.set('#fff').start('#f60').chain(function(){
     this.start.delay(2000,this,'#000');
});


Ok, this last part might have been a bit of a tough one... I just hope that it can be reused and tweaked by most programmers who read this tutorial to suit their needs.
Also please disregard how ugly the effect is, I just wanted you to see clearly that the effect is running.

Server-side

Of course, we need to modify the server side a bit as well to interact properly with the spiced-up client-side.
Note: this will only work with PHP5. If you have PHP4, you need to install the PEAR JSon library.
    function listFields() {
    // Get the value of the table variable, don't forget to cast its type (cmd should do here)
        if( $table = JRequest::getVar( 'table', '', 'get', 'cmd' ) ) {
            $db =& JFactory::getDBO();
            // Grab the fields for the selected table
            $fields =& $db->getTableFields( $table, true );
            if( sizeof( $fields[$table] ) ) {
                // We found some fields so let's create the HTML list
                $options = array();
                foreach( $fields[$table] as $field => $type ) {
                    $options[] = JHTML::_( 'select.option', $field, $field );
                }
                $list =& JHTML::_( 'select.genericlist', $options, 'table_fields', null, 'value', 'text', '', 'table_fields' );
                // If failure, even though we have nothing to respond as HTML, we set response['html'] to an empty string only to avoid JS errors on the client side.
                if( !$list ) {
                    $response['html'] = '';
                    $response['msg'] = JText::_( 'Failed to build fields list for table' ) . ' ' . $table;
                }
                // HTML part of our response is the list, and there is a message as well
                $response['html'] = $list;
                $response['msg'] = JText::_( 'Successfully loaded fields for table' ) . ' ' . $table;
            }
        } else {
            // If failure, even though we have nothing to respond as HTML, we set response['html'] to an empty string only to avoid JS errors on the client side.
            $response['html'] = '';
            $response['msg'] = JText::_( 'No table selected' );
        }
        // Encode and echo
        echo ( json_encode( $response ) );
        // Return to keep the application from going anywhere else
        return;
    }
And now, you can test.

Hope this tutorial will help all who want to use AJAX in their Joomla! applications. Once you are comfortable with Mootools and Ajax, some really powerful features can be created.
Comments and suggestions are always welcome.

Good luck to all!

Mat

 


40 Votes

6 Comments

Feed
  1. Nice tutorial. How about a zip/tar of a completed version that can be installed in J1.5? That would really help to understand how all of the MVC/Mootools stuff works!
  2. very usefull tutorial,this reading help me a lot on developing my ajax login
  3. Hi!!

    I'm development a component and your article help me so much!!

    But i cant do something...

    I have 3 dependent select lists and after select de first option the second list its updated, but I can't get the changes on the second list for update the third list.

    Maybe you can help with this thing posting here something or reply to my mail.

    Thanks!!!

    PD: Sorry for my poor english but doesn't my natural language
  4. hello,

    I'm french. I'm begin Joomla development and i see your bueatiful component.
    I search how make a asynchrone request with Joomla and Mootols and a php file with requets BDD.
    Can you give me a simple example please .

    Thank you very much!!

    See you soon.

    Julien
  5. Hello i have the same doubt that Cronopio.

    I have a form with 2 dependent select lists and i cant apply the change event on the the second one to be able to see the final result of the form.

    Could you give me a clue on that?

    thanks in advance

    PS: Great article very helpful, congrats.
  6. Many thanks :-). I added this to a test site in just a few minutes and it works!

    Only problem is that I get the following error ...

    Warning: Memcache::addserver() expects parameter 2 to be long, string given in /Library/WebServer/Documents/testsite/libraries/joomla/cache/storage/memcache.php on line 84 MODEL::__construct()

    In 'view.html.php', I used $this->assignRef() to assign the the HTML table list to $this->tables in 'default.php'.

Add Comment


    • >:o
    • :-[
    • :'(
    • :-(
    • :-D
    • :-*
    • :-)
    • :P
    • :\
    • 8-)
    • ;-)