In a
traditional web application user interaction may be facilitated by JavaScript (validating form fields, cute animations etc.) but when the user needs new information from the server, form fields are POSTed to the server and the server responds with a whole new page.
This is actually quite an awkward way to work because HTTP is a stateless protocol: the only way for the server to keep track of where the user is with something is for the POST to tell it. That could mean posting back the entire state of the page, posting back an identifier for the session so that the server can remember the state, or some combination of the two. (Strictly speaking one can also GET the next page and send state in url parameters, though this is likely to violate the intention that GETs should not modify server state in any meaningful way and could fall foul of HTTP caching.)
It's not surprising then that a plethora of web frameworks have been developed to make things easier. Using one of these frameworks we can design the page layout and express it in some form of templating language. Parts of the page will be form fields and other controls (widgets). The framework takes care of re-rendering the page each time it is posted back to the server by some user interaction. It also takes care of tracking the state of each widget. Somewhere in the page's definition we can write handlers for events which will appear to come directly from user actions, but in fact are generated by the framework when the server receives a post.
With such a framework most code is executed on the server, and the approach to building an application is very like that of building a GUI for a desktop application. Thus we can practice MVC in just the way we usually would.
The downside of this approach is that each time the user does anything on a page they will see it reload. Depending on server load and network latency, they could even be waiting for some time before they can carry on.
The alternative to the traditional approach is what has become known as AJAX. I'm not a big fan of the term. Given that it basically just means using XMLHttpRequest (nowadays), and that XML is often not involved at all, it is hardly an illuminating acronym. Nonetheless, that's the term which has stuck.
As with a traditional application, a page will be rendered with various widgets and whatever other eye-candy the designer and developer care to throw in. The difference is that most of the code running the application is written in JavaScript and runs on the client. When the user does something which requires information to pass to or from the server, the JavaScript code sends a GET or a POST to the server using an XMLHttpRequest. The server responds to requests not with a complete web page but with some fragment of information, which may be HTML, XML, or very commonly JSON. The response is passed back to the client side code and is used to update the page (without a reload).
Curiously, despite the various difficulties involved, it is actually a much more natural way to think about running a web application. The page just stays where it is so there is no problem with remembering state. The page communicates with the server via messages which need only contain enough information for the task at hand. For operations which may affect the appearance of the page but not require information from the server, there is no need to talk to the server at all. If web frameworks hadn't made the traditional approach manageable, I'm sure we wouldn't ever consider writing a web application any other way.
Now, a client-server application, of which a web application is one example, usually has multiple tiers. One of these is the client tier which talks directly to the user. It contains the code for managing presentation and user interaction. It also talks to the middle tier(s). The middle tier takes care of authentication, validation and other business logic, load balancing and what not. The last tier is typically a database. It takes care of the integrity and reliable persistence of data.
It may be tempting to think that the layers of a three-tier application are synonymous with the view, controller and model respectively, however (as far as I can tell) this isn't really the case.
In my recipe builder, described in part one, the initial page load shows a list of recipes and two empty lists. JavaScript is used to make list items selectable. The list of recipes is built as an HTML table server side using PHP:
<?PHP require "php/getRecipes.php"; ?>
The named PHP script queries the database for a list of recipes and writes them out as an HTML table. The rest of the main page is pure HTML. I could mix PHP and HTML but I don't. If that's your dirty little habit, just don't come near me with it.
Quick summary:
- The database contains a table of recipes with numeric ids, a table of ingredients with numeric ids, and a table of ingredient_id, recipe_id pairs.
- When the user selects a recipe, an XMLHttpRequest is sent for php/getIngredients.php?recipe_id=nn. This is then inserted as the innerHTML of the ingredients list.
- When the user selects items from the ingredients list and clicks the 'Add selected' button they are copied to the shopping list. No server interaction is involved because I have not yet made shopping lists persistent.
- Other features, such as the ability to add and delete both recipes and ingredients but that's probably enough for now..
There may be more than one way to realize this as MVC but I'll now describe what I did.
Because we're going to be using events quite a bit, I have Publisher class which all my model and view classes can inherit from (or mix in). There are two methods called 'on' and 'fire'.
// f is some function.
var p = new Publisher;
// subscribe f to select events on p.
p.on('select', f);
// cause f to be called with argument 'a'
p.fire('select', 'a');
What's the model? Certainly the database is part of the model. But if the model lives entirely on the server we're going to have a problem doing MVC because the model is supposed to publish events to which the view can subscribe. There are ways of getting the server to push events to the client but none that I know of are particularly straightforward and I'd rather not go there. Instead we'll make a proxy for the model on the client.
In fact, we make proxies for three models: the RecipeListModel, IngredientListModel and the ShoppingListModel. All of them implement methods 'addItem', 'removeItems' and may fire events 'ItemAdded' and 'ItemsRemoved'. The IngredientListModel also implements 'setRecipeId' and fires 'ItemsChanged'. Using David Flanagan's 'http.js' script, the code for 'addItem' on the IngredientListModel is
this.addItem = function(ingredient) {
var info = {recipe_id:self.recipe_id, ingredient:ingredient};
HTTP.post('php/addIngredient.php', info, function(id) {
self.fire('ItemAdded', ingredient, id);
});
};
In other words, we send the current recipe_id and the new ingredient to the server and when it tells us what id it has assigned to the new ingredient, we fire the 'ItemAdded' event.
So our models now behave pretty much as they need to to participate in MVC. We don't handle the case of another page updating the database at the same time. For that we'd need to get extra messages (events) from the server to the client, either piggybacking on the response to one of our posts or by some other means. Be that as it may, it's at least reduced to a question of exactly how full-featured we want our model implementation to be; the interface doesn't need to change.
What about the views? There is some JavaScript code for making a table selectable and registering handlers for 'Select' and 'SelectionChanged' events. On top of this is a ListView class in JavaScript to which I can pass a model with the interface described above. Select events from the underlying JavaScript augmented table simply fire 'Select' events of the ListView.
When I first wrote this there were separate classes for the RecipeListView and the IngredientListView. They talked directly to the server, not to model proxies, made different method calls for recipes v.s. ingredients, and directly implemented the required results of the 'Select' event. As you can imagine there was nothing remotely testable or reusable about them.
Now there are only two differences between the ListViews we create for the recipe list and the ingredients list: the recipe list is single-select and it is passed a table already present on the page; the ingredients list is multi-select and creates its table element from scratch, inserting it into a div which it is given as the designated container.
Which brings me to the point which gave me the most trouble in thinking about how to apply MVC here. When the model fires an 'ItemsChanged' event the ListView has to reconstruct its table from scratch. (Only when we're using a table in a container div do we handle 'ItemsChanged', which is not to say we couldn't do it but not in quite the way we do.) I did not want to write PHP code to send back the list of ingredients as JSON and then have the JavaScript build its table from that. Why? Because getting PHP to write HTML is just as easy, and installing HTML into a div on the client is just 'container.innerHTML = text' rather than some big ugly mess. So I endowed my IngredientListModel with a getAsHTML method (passing the response to a callback because XMLHttpRequest is asynchronous) and let the ListView call that when it gets an 'ItemsChanged' event.
Now we've mixed model and view: the model is constructing part of the view by returning HTML. Not only that but it's doing it on the server!
This is interesting though. We already were constructing part of the view on the server with the original page load. We certainly don't want to start with a blank page and construct everything on it using JavaScript. So it looks like some of the view will always be implemented server-side.
So perhaps these are not really pure model proxies, they are proxies for calls to the server and they allow us access to parts of the view there as well. As far as the ListView is concerned, some part of its implementation is moved server-side via the getAsHTML method. I'm not sure I have any good way to explain this away other than to say that I think it would have to be a common compromise to have to make when applying MVC to an AJAX web application.
Funnily enough, it doesn't look so bad from the server-side perspective. We have a bunch of PHP scripts each of which represents a remote procedure call essentially. They query the model, or update it and they return their results in HTML or JSON. The ones that return HTML (including the initial page load) are part of the view (already bound server-side to a fixed model) and are necessarily concerned with layout and presentation. And indeed there are other cases where we want to do some of our view-related work server-side; notably computation and storage intensive operations like image processing.
With that out of the way, the rest turns out to be just as simple and elegant as MVC in a desktop application.
There is a top-level JavaScript view object which is associated with the entire page. It locates all controls on the page by id, hooks up event handlers to forward events for user interactions, exposes properties to allow the controller to query the state of the page, and instantiates ListView objects for its non-trivial sub-views.
function MainView(rlModel, model, slModel)
{
var recipeList = this.recipeList =
new ListView(rlModel, {table:id('recipesTable')});
var holder = id('ingredientListHolder');
var ingredients = this.ingredients =
new ListView(model, {
title:'Ingredients', holder:holder, multiSelect:true});
var recipeSearch = this.recipeSearch = new SearchBox('recipeSearch');
recipeSearch.fetchCompletions = function(prefix, cb) {
HTTP.post('php/completions.php', {prefix:prefix, table:'recipe'}, cb);
};
...
}
The 'id' function is a short-cut for 'document.getElementById'. I have not bothered here to go through a model proxy when hooking up the fetchCompletions callback on the SearchBox view. (This component was not written with MVC in mind; I might do it a little differently now.)
The controller is also quite simple and straightforward.
function Controller(rlModel, iModel, slModel, view)
{
var focusList = null;
var compact = false;
view.recipeList.on('Select', function(recipe_id)
{
iModel.setRecipeId(recipe_id);
focusList = this;
});
view.ingredientSearch.on('Enter', function()
{
var ingredient = view.ingredientSearch.getValue();
iModel.addItem(ingredient);
view.ingredientSearch.clear();
});
...
}
And that's really about it.
The conclusions I've drawn at this point (which may be subject to future revision) are that
- Part of the model has to live on the client as some form of proxy. Notifications from the model proxies may be generated purely client-side.
- Parts of the view will always live server side. Some of the binding of views to models will therefore also be taking place server-side. This appears to mean that the model proxies will be a little impure.
- Overall MVC still works really well in this setting.