AJAX MVC (so to speak)

September 19th, 2006. Tagged: Ajax, JavaScript, mvc, yui

This is sort of a framework thing to create AJAX applications, based on the MVC design pattern. Yep, I have a lot of buzzwords here, I admit, but this shouldn't be taken too seriously. I was doing a bunch of small projects lately and I found myself using something like this little framework, without even thinking about it. Then I thought about it and I found that the scripts and the organization of them may resamble MVC a bit. So how does MVC fit when you mix things like thin and fatter client, HTML, JavaScript, XMLHttpRequest, PHP and CSS?

Usual AJAX app flow

What usually happens in an AJAX application is:

  1. you have an HTML page, styled with CSS
  2. you click on something
  3. JS sends request to the server (to a PHP script)
  4. JS updates the original HTML page

Mapping to the MVC pattern

OK, so what part of this process can be associated with a View, or a Model or a Controller? The Model is easy, it's the business logic, writing to a database and so on. This is the PHP script. The View? Obviously this is the HTML page and the CSS. But I'd like to think also about the JS that updates the page as part of the View. I mean it makes sense, it's updating the presentation part. Sometimes you even use innerHTML in the JS, but even if you use DOM, it becomes part of the HTML anyway. How about the Controller? Well, we have two controllers here. One that is on the server side, a PHP script that receives requests and "asks" the Model for the response. The other controller is on the client side, this is the JavaScript that decides what happens on a click of a button and sends an appropriate AJAX request to the PHP controller. Therefore I would consider any behavioural JS as part of the Controller, including attaching events as well as sending HTTP requests.

Here's an illustration:
AJAX MVC

In action (example)

I went ahead and implemented a very simple application to prove the concept. It's just a blank styled HTML page with a button. The HTML page includes two JavaScripts responsible for behaviours (Controller) and page updates (View). The page also includes a few unrelated helper javascripts, in my case I'm using the YUI library. The JS Controller attaches an event to the button. Then when you click the button, the JS Controller sends a request to the PHP controller. The PHP controller (just a simple switch) figures out what was requested and calls the appropriate object of the business model. In my simplistic case, the abovementioned "model object" is just a simple function, but this can be easily built upon. The Model returns (JSON-encoded) response, in this case it's a list of installed PHP extensions. Now the response is received by the View JS and it updates the page. After that the View calls another function from the JS controller that attaches new events to the new content. (Yep, a little glitch here, maybe it would have been better if the Model's response is handled by the JS controller which in turn calls the JS view updater, but anyway this is easy to fix)

Directory layout

Here's the directory structure:
AJAX MVC dir

One might argue that it's better if you don't mix .js, .css and .php files in the same directory but the whole idea is open to interpretations anyway, it's just an illustration of the idea.

The code for the example

We get to the fun part, the actual implementation. So we start with a simple .html page, the initial part of the view.

This is index.html

<?xml version="1.1" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <title>Welcome</title>
  <link rel="stylesheet" href="../view/styles.css" type="text/css" media="all" title="Default styles" />
  <script language="javascript" type="text/javascript" src="../_extras/yui/build/yahoo/yahoo-min.js"></script>
  <script language="javascript" type="text/javascript" src="../_extras/yui/build/event/event-min.js"></script>
  <script language="javascript" type="text/javascript" src="../_extras/yui/build/connection/connection-min.js"></script>
  <script language="javascript" type="text/javascript" src="../view/updates.js"></script>
  <script language="javascript" type="text/javascript" src="../controller/behaviours.js"></script>
</head>
<body>
 
  Welcome to my app!
  <br />
  <form action="" method="post">
    <input type="button" name="b" id="thebutton" value="I'm a button, click me!" />
  </form>
  <div id="content">&nbsp;</div>
 
</body>
</html>

As you can see, nothing special, simply including the CSS styles, the YUI "extras" and two other javascripts - one part of the View and one that is part of the Controller.

The Controller JS is responsible for attaching an event listener to the button.

This is an excerpt from the behaviours.js

// the behaviour class
var behaviours = {
 
    phpcontroller: "../controller/switch.php?request=",
 
    // more behaviour.methods....
}
 
 
// initial page load, attach onload event(s)
YAHOO.util.Event.addListener(
    'thebutton', 'click', behaviours.theButtonClick);

Now when the user clicks the button, the method behaviours.theButtonClick() is executed. It fires a request to the PHP controller switch and says that the request type is "loadSomething":

theButtonClick: function(e) {
  alert('Ouch! \n\nOK, I\'ll make a request for ya, buddy!');
  YAHOO.util.Connect.asyncRequest (
      'GET',
      behaviours.phpcontroller + 'loadSomething',
      {success: updates.writeContent}
  );
},

The PHP controller (controller/switch.php) receives the request, does a simple switch to validate the request type and then calls the appropriate (in my case just a simple) function from the business model. Here's the full switch.php code:

<?php
// is this a request?
if (empty($_GET['request'])) {
  die();
}
// get the business logic
include_once '../model/business.php';
 
// figure out the request
// and call the business logic object
switch ($_GET['request']) 
{
  case 'loadSomething':
    echo loadSomething();
    break;
  case 'loadSomeMore': // not used, example
    echo loadSomeMore();
    break;
}
?>

The function loadSomething() from the PHP model gets a list of installed PHP extensions, encodes them into JSON and sends them back. This is a full listing of the ../model/business.php

<?php
function loadSomething() {
  $extensions = get_loaded_extensions();
  return '["'. implode('","', $extensions) . '"]'; 
}
?>

If you go back and look at the AJAX request, you'll see that on success, I call the updates.writeContent() method. The ../view/updates.js script contains stuff that updates the HTML of the original page, so its place is in the View part of the app. writeContent simply creates an HTML table with the results (the list of PHP extensions). Then I wanted to attach event listeners to this table just to change color, but it can be more than that. Attaching events is a job for the JS Controller, therefore a method of its class is called. Here's a full listing of updates.js:

var updates = {
 
  writeContent: function (xmlhttp) {
    if (!xmlhttp.responseText) {
      alert("I got nothing from the server");
    }
    var data = eval(xmlhttp.responseText);
    var write_to = document.getElementById('content');
    write_to.innerHTML = ''; // yeah, I know
    
    var html2dom_root = write_to;
    var table = document.createElement("table");
    var table_1_tbody = document.createElement("tbody");
    for (var i in data) {
      table_1_tbody_2_tr = document.createElement("tr");
      table_1_tbody_2_tr_1_td = document.createElement("td");
      num = 1 + parseInt(i);
      table_1_tbody_2_tr_1_td_1_text = document.createTextNode(num);
      table_1_tbody_2_tr_1_td.appendChild(table_1_tbody_2_tr_1_td_1_text);
      table_1_tbody_2_tr.appendChild(table_1_tbody_2_tr_1_td);
      table_1_tbody_2_tr_2_td = document.createElement("td");
      table_1_tbody_2_tr_2_td_1_text = document.createTextNode(data[i]);
      table_1_tbody_2_tr_2_td.appendChild(table_1_tbody_2_tr_2_td_1_text);
      table_1_tbody_2_tr.appendChild(table_1_tbody_2_tr_2_td);
      table_1_tbody.appendChild(table_1_tbody_2_tr);
    }
    table.appendChild(table_1_tbody);
    html2dom_root.appendChild(table);
    
    behaviours.updateTableBehaviour();
  }
}

(BTW, for the DOM part I'm used the help from my little tool html2dom to make my life a bit easier)

And finally here's the rest of the JS controller (behaviours.js), the method behaviours.updateTableBehaviour() that adds an event listener to the new table and the trClick() that handles clicks on this table. On click, it justs changes the color of the underlying row.

  trClick: function (e) {
    var target = (e.srcElement) ? 
      e.srcElement.parentNode : e.target.parentNode;
    if (target.tagName == 'TR') {
      if (target.className == 'tr-on') {
        target.className = '';
      } else {
        target.className = 'tr-on';
      }
    }
  },
  
  updateTableBehaviour: function () {
    var el = document.getElementById('content').firstChild;
    YAHOO.util.Event.addListener(
      el, 'click', behaviours.trClick);
  }

Demo and downloads

  • Demo - the live example
  • Zipped demo - all the source code for the example
  • Template - the source code for the example but with the example part commented, so you can use it as a template for your next AJAX project. The only thing you need to do is to drop the YUI in the _extras/yui folder.

Thank you for reading, any comments welcome!

Tell your friends about this post on Facebook and Twitter

Sorry, comments disabled and hidden due to excessive spam.

Meanwhile, hit me up on twitter @stoyanstefanov