MVC Architecture for JavaScript Applications

There isn’t enough discussion to read about using the model-view-controller architecture for client-side JavaScript web application development and it’s a shame.

History

When I first ventured into client-side web development, “graceful degradation” or “progressive enhancements” were all the rage. Most web pages were required to work in browsers that did not have JavaScript enabled or had very poor JavaScript support. This is still true in many cases. If feature tests showed that the browser supported the required JavaScript then little bits of interactivity were added to the page usually to save full page reloads or make a little animation. The bits of JavaScript were often so small that they didn’t require being architected. Spaghetti code was just fine and anything fancier was overkill.

Early server-side programs were similar. The mainly disorganized CGI scripts accomplished their simple tasks with spaghetti code but they were manageable. Web applications became more sophisticated and the server-side shouldered the brunt of the workload as the client-side was too wimpy to do its share. The server-side code quickly grew larger and required organization. Eventually the Struts server-side web framework came along with its “Model 2” architecture and ever since most server-side web frameworks (e.g. Rails, Catalyst, etc) have touted “MVC” as the optimal way to take requests and generate responses. The request arrives to the controller. The controller gathers data from a model that talks with a database. The controller finally forwards that data to a view template to generate the response. It doesn’t have to work exactly like that but it usually does. Somewhere along the line, the terms “Model 2” and “MVC” became unnecessarily conflated but that can be left as a side discussion. Whatever you call it, the Struts-style architecture has been highly successful at organizing server-side code for its specific request-response functionality and at the very least is MVC-like...but it is definitely not the classic MVC architecture.

It seems that history will mark the release of GMail as the turning point to big “Ajax” client-side web applications. The browsers had grown powerful enough and JavaScript programmers had figured out clever ways of using its features that had been around for a while. The client-side could really start doing some thinking of its own. Full blown “one-page apps” that had functionality resembling desktop application were, and still are, exciting to use. The problem is the code is big and the spaghetti style that is fine for small enhancements is definitely not fine anymore. Some kind of architecture is required for developer sanity.

Many web developers have heard about MVC and have probably been introduced to it in the non-traditional Struts-like incarnation. There have even been attempts to subdue client-side program complexity by porting the Struts-style MVC that was designed for a request-response application to the client-side. Ugg. Not the right choice.

I struggled looking for books on the right way, or even just a good way, to organize large bodies of JavaScript code. I was disappointed that I couldn’t find any. I started venturing out to books on design patterns, Scheme, Java, and one particularly good book ostensibly on ActionScript but more importantly about design patterns. I learned that the classic MVC that started in the Smalltalk world and works so well for desktop applications is a perfect fit for what we do as JavaScript web application developers. I’ve been building JavaScript web applications this way for several years now and feel like there is an MVC hole in my collection articles. This article is an attempt to fill that hole.

Real MVC

In a nutshell the classic MVC architecture work like this. There is a model that is at the heart of the whole thing. If the model changes, it notifies its observers that a change occurred. The view is the stuff you can see and the view observes the model. When the view is notified that the model has changed, the view changes its appearance. The user can interact with the view (e.g. clicking stuff) but the view doesn’t know what to do. So the view tells the controller what the user did and assumes the controller knows what to do. The controller appropriately changes the model. And around and around it goes.

This description is probably a nice summary for those who already understand the MVC architecture but it is way too short for someone who doesn’t know it already. Describing all the ins and outs of MVC is a task I’m not going to attempt in full. I will do three things:

  1. Recommend the books I found that explain MVC well.
  2. Describe some tips about MVC for JavaScript web applications in particular that I have found helpful.
  3. Provide a complete example.

Good MVC Books

There at two books in particular that were a big help for me understanding the classic MVC architecture.

Without question, the best description of MVC I’ve read is in Colin Moock’s book “Essential ActionScript 2.0”. His follow-up book “Essential ActionScript 3.0” does not contain the in depth MVC discussion so make sure you find the correct edition. Although the book examples are in ActionScript, ActionScript is pretty darn close to JavaScript as they are both ECMAScript implementations. I don’t think I could improve on Moock’s discussions of the observer pattern and MVC. Although I’ve had the book for many years, I re-read the MVC chapter last night and it was a treat. I must be getting old if I’m drawing inspiration from books that are out of print. Maybe actually reading paper books is enough of an indicator that I’m getting old.

The other book I found that covers MVC accessibly is “Head First Design Patterns”. The examples are in Java. Even if Java is not your favourite language, it is a perfectly suitable language for depicting an object-oriented architecture like MVC. This book gives a solid description of why the architecture is beneficial. There is also a section on the Model 2 architecture so you can compare and contrast the two.

Another book worth mention is the standard on design patterns. “Design Patterns: Elements of Reusable Object-Oriented Software” describes the three patterns that comprise MVC: observer, composite, and strategy.

I feel a bit like I’m hawking books here but my earned Amazon affiliate commissions over the years is a measly $9.22. I guess I’m not a good salesman. Anyway these books are all well worthwhile and if you can borrow them from a friend or library then you are doing yourself a great favour.

MVC Example

I was planning on giving the example third but perhaps now is a better time.

The MVC clock example is a port to JavaScript of the MVC example in Moock’s book. It is a fancy stopwatch. There is a single clock model. There are two views, digital and analog, that show the model’s time. There is another view that allows the user to click on knobs and a single controller that knows how to handle those clicks to manipulate the model. Click on “start”, “stop”, and “reset”.

To ensure the MVC concepts are shown clearly I have not used a library like YUI, jQuery, etc. The example uses addEventListener directly. It uses canvas with no feature testing. These choices make the MVC aspects of the example much more accessible which is the goal. Use a modern version of Firefox, Safari, or Chrome and all will be well.

You can meditate on the example for days and days. I have. There is no doubt that you will see bits of the code and decide you would do things differently. If that is the case then both you and the example are doing your jobs well. I use the Scheme-style OOP that Crockford has promoted. You may not. I use has-a composition for the observer pattern. You may use is-a inheritance. I use a pull model for the observer pattern here. You may want to use a push model.

To be completely honest, and slightly heretical, I don’t always use MVC properly myself. Gasp! I haven’t found that the flexibility provided by the strategy pattern (i.e. separation of view and controller) is always beneficial in the type of applications I program. Sometimes I glue them together into widgets and this does save a bit of code. I guess I could tell people I use a model-widget architecture. Here is a MW clock example which is close to how I sometimes program. Please feel free to flame me for my lack of separate views and controllers in the comments but at least do it nicely and in good humour.

MVC in JavaScript

Model

The model is all about data and the model is where the primary data in the system lives.

If your application needs to gather data from the server, local storage, cookies, etc then the model is where this should happen. In fact, the model is the only place in the whole system that should know anything about XMLHttpRequest, for example. You may have code in the model layer that loads code for you and creates model objects. For example,

// model constructor function
//
var makeEmailModel = function(data) {
    // ...
    
    return {
        // ...
    };
}; 

// load data from the server and create
// model objects
//
var loadEmails = function(callbacks) {
    doXHR('GET', '/emails', {
        on200: function(xhr) {
            var emailsData = fromJSON(xhr.responseText);
            var emails = [];
            for (var i=0, ilen=emailsData.length; i<ilen; i++) {
                emails.push(makeEmailModel(emailsData[i]));
            }
            callbacks.success(emails);
        },
        onComplete: function(xhr) {
            callbacks.failure();
        }
    })
};

The loadEmails function above is called by bootstrap or controller code. Give the callback properties names like “success”, “failure”, and “invalid” that are semantically meaningful to the calling code as shown in the example. Don’t use names like “on200”, “on500”, and “on400” as those names are related to the mechanics of the actual data getting. Only the model should know about those mechanics and those mechanics may change.

When the model notifies its observers, the model should tell the observers what happened to the model. A model may say “hey, I changed!” and the observers then decide what they do based on that information. The model should never tell the observers what they need to do. The model will never say “you need to redraw the time you are displaying” because the model has no idea if there is any view that is drawing time. The model is oblivious to what its observers might be doing.

The model can notify different groups of observers. For example some observers may only be interested in withdrawals in the following account model.

var makeBankAccountModel = function() {

    var balance = 0;
    var depositObservableSubject = makeObservableSubject();
    var withdrawalObservableSubject = makeObservableSubject();
    
    var deposit = function(amount) {
        if ((typeof amount !== 'number) || (amount <= 0)) {
            throw new Error('deposits must be positive numbers');
        }
        balance += amount;
        depositObservableSubject.notifyObservers(amount);
    };
    
    var withdrawal = function(amount) {
        if ((typeof amount !== 'number) || (amount <= 0)) {
            throw new Error('withdrawals must be positive numbers');
        }
        if (amount > balance) {
            throw new Error('not enough money in account');
        }
        balance -= amount;
        withdrawalObservableSubject.notifyObservers(amount);
    };
    
    return {
        getBalance: function() {
            return balance;
        },
        addDepositObserver: depositObservableSubject.addObserver,
        addWithdrawalObserver: withdrawalObservableSubject.addObserver
    };
};

Note also that this bank account example pushes the amount of the deposit or the withdrawal to the observers. That information is not available for the observers to pull from the model so pushing the data helps give a more complete description of what happened to the model.

If you have limited time for code reviews start by reviewing the models.

The model definitely should know nothing about the DOM. That would be a break down of all that is good.

View

The view is what you see and what you click.

The view is the only part of the system that knows about the DOM. (Except perhaps some of the bootstrapping code.)

Do your best to avoid using id attributes on DOM elements in the view. The ability to put two views of the same class in a page twice depends on not having the same id value in the two views. There are other strategies but you should not need to find elements in the page if your views are well constructed.

When a user clicks on something in the view, the view is too stupid to know what to do. In the clock example, when the user clicks the “start” button, the view doesn’t understand the user’s desire to get the clock model to start ticking. The view has to rely on its controller to make that decision. Since the view doesn’t know what should be done, it should be calling methods on the controller that explain what happened in the view. For example, the view could call a controller method named “handleStartClick”. The view should not be calling a controller method named “startTheClock” because ironically some other interchangeable controller may not start the clock when the start button is clicked.

A templating library that generates HTML is not a view and this is a confusion that seems to come from the Struts-style architecture. In the classic MVC, a view is a living object that observes a model and stays in sync when the model changes. A view might call a templating library to help update the view but the template is not the view. For example,

var makePersonView = function(personModel) {

    var rootEl = document.createElement('div');

    var personModelChangeObserver = function() {
        // The view calls a templating library to generate
        // HTML that shows the person's name.
        rootEl.innerHTML =
            processTemplate('personTemplate',
                            {name: personModel.getName()});
    };

    personModel.addObserver(personModelChangeObserver);

    return {
        // ...
    };
};

Controller

The controller is a decision maker. When a user clicks in the view, the view forwards that event on to the controller so the controller can decide what needs doing.

There is nothing particularly JavaScript-specific about controllers. They don’t know about host objects like those in the DOM or about XMLHttpRequest.

Controllers mutate models and sometimes mutate views. Controllers mutating views is often not the right choice however. Suppose you have a to-do list web app. When the user checks an item as completed, the click is forwarded to the controller. The controller modifies the model item as complete which makes an async save to the server. The controller could tell the view to show a throbber while the model is saving. For example, suppose we have the following to-do model

var makeTodoModel = function() {
    // The id is used in server's database.
    // It is set here when to-do is created.
    var id = '';
    var complete = false;
    var saving = false;
    var changeObservableSubject = makeObservableSubject();

    // ...

    var saveAsComplete = function(callbacks) {
        saving = true;
        changeObservableSubject.notifyObservers();
        doXHR('POST', '/todo/'+id+'?complete=true', {
            on200: function(xhr) {
                saving = false;
                complete = true;
                changeObservableSubject.notifyObservers();
                callbacks.success();
            },
            onComplete: function() {
                saving = false;
                changeObservableSubject.notifyObservers();
                callbacks.failure();
            }
        });
    };

    return {
        saveAsComplete: saveAsComplete,
        addChangeObserver: changeObservableSubject.addObserver,
        getId: getId,
        getDescription: getDescription,
        getComplete: getComplete,
        getSaving: getSaving
        // ...
    };
};

There are two ways we could use the above model in the view and controller interaction. The first is the controller turns the throbber on in the view.

var makeTodoView = function(todoModel, todoController) {
    var rootEl = document.createElement('div');
    
    var todoEl = document.createElement('div');
    rootEl.appendChild(todoEl);
    
    var throbberEl = document.createELement('div');
    throbberEl.style.display = 'none';
    throbberEl.innerHTML = 'saving...';
    rootEl.appendChild(throbberEl);
    
    var todoModelChangeObserver = function() {
        todoEl.innerHTML =
            (todoModel.getComplete() ? 
                'complete' : 'incomplete') +
            ': ' + todoModel.getDescription;
    };

    todoModel.addChangeObserver(todoModelChangeObserver);

    var showThrobber = function() {
        todoEl.style.display = 'none';
        throbberEl.style.display = '';
    };
    
    var hideThrobber = function() {
        throbberEl.style.display = 'none';
        todoEl.style.display = '';
    };
    
    todoEl.addEventListener('click', function() {
        todoController.handleTodoClick(todoModel);
    }, false);

    // ...

    return {
        showThrobber: showThrobber,
        hideThrobber: hideThrobber
        // ...
    };
};

var makeTodoController = function() {
    
    var handleTodoClick = function(todoModel) {
        if (!todoModel.getComplete()) {
            todoModel.showThrobber();
            todoModel.markAsComplete({
                success: function() {
                    todoModel.hideThrobber();
                },
                failure: function() {
                    todoModel.hideThrobber();
                }
            });
        }
    };
    
    // ...
    
    return {
        handleTodoClick: handleTodoClick
        // ...
    };
};

The potential problem with the above example is if the to-do is represented in the UI twice and that when the to-do is being saved to the server both representations should show the throbber. The fix is to have the view show the throbber based on the model state. Since the model above notifies observers when it is saving we can write the following for the view and controller.

var makeTodoView = function(todoModel, todoController) {
    var rootEl = document.createElement('div');
    
    var todoEl = document.createElement('div');
    rootEl.appendChild(todoEl);
    
    var throbberEl = document.createELement('div');
    throbberEl.style.display = 'none';
    throbberEl.innerHTML = 'saving...';
    rootEl.appendChild(throbberEl);
    
    var todoModelChangeObserver = function() {
        if (todoModel.getSaving()) {
            showThrobber();
        }
        else {
            hideThrobber();
        }
        todoEl.innerHTML =
            (todoModel.getComplete() ?
                'complete' : 'incomplete') +
            ': ' + todoModel.getDescription;
    };

    todoModel.addChangeObserver(todoModelChangeObserver);

    var showThrobber = function() {
        todoEl.style.display = 'none';
        throbberEl.style.display = '';
    };
    
    var hideThrobber = function() {
        throbberEl.style.display = 'none';
        todoEl.style.display = '';
    };
    
    todoEl.addEventListener('click', function() {
        todoController.handleTodoClick(todoModel);
    }, false);

    // ...

    return {
        // ...
    };
};

var makeTodoController = function() {
    
    var handleTodoClick = function(todoModel) {
        if (!todoModel.getComplete()) {
            todoModel.markAsComplete({
                success: function() {
                    // nothing to do
                },
                failure: function() {
                    // nothing to do
                }
            });
        }
    };
    
    // ...
    
    return {
        handleTodoClick: handleTodoClick
        // ...
    };
};

Because the view now depends on the model’s saving state data, all instances of the same to-do visible to the user will stay in sync: when the model is being saved all related views will show the throbber. This is the power of the primary data kept in the model.

Boostrap

The boostrap code is likely a bit messy to get the whole big one-page app up and running at window.onload. Models, views, and controllers need to be created and their relationships need to be established. There is no standard way to do these things. Find a way that is appropriate for your application and try to keep the mess as small as possible.

Conclusion

Hopefully everyone noticed that an MVC framework is not required to build an application using the MVC architecture. It is great that people get excited about a new design pattern or architecture but that doesn’t mean it needs to become a framework. A few helper libraries like the library for observable subjects is sufficient.

Using an MVC style has helped me tame the complexity of some of the bigger projects I’ve work on and it might help you too. If you have experience with the MVC architecture then please leave a comment with any tips that you’ve discovered about how to implement using MVC in the JavaScript world. I’m very curious what others have learned through their experimentation.

I've added another example in a follow-up article: MVC To-Do Application

Comments

Have something to write? Comment on this article.

Timothy Warren February 25, 2011

It seems that an MVC or MW architecture precludes using jQuery or something similar.

I can see using MVC or MW with server-side js, but I'm having a hard time seeing it client side.

Peter Michaux February 25, 2011

Timothy,

Using a cross-browser library to normalize host object inconsistencies and bugs is always useful regardless of architecture. MVC is a useful architecture especially when the client-side code base starts to become more complex.

Joss Crowcroft February 25, 2011

This has to be the best and most thorough Javascript article I’ve read in a long time!

Really cleared a lot of things up for me, though it makes me feel ashamed of some of the obviously awful spaghetti I’ve written in the past...

Thanks!

Timothy Warren February 25, 2011

@Peter Michaux

I agree that jQuery’s platform abstraction is definitely helpful, but what I mean is that jQuery tends to make it much simpler to mix DOM, XMLHTTPREQUEST, and logic, as opposed to using straight javascript.

And those kinds of issues make me thing that it would be nice to have a simple MVC framework for javascript.

Peter Michaux February 25, 2011

Timothy,

I cannot say I agree that the jQuery library makes writing JavaScript code in a big application easier than using “straight JavaScript” and another library.

FG February 25, 2011

Hi,

Do you really use an observer on every model's field?

What about memory consumption & performance (1 model's field = 1 observer object)?

Peter Michaux February 25, 2011

FG,

No I don’t have an observable subject for each model field. For example, the to-do model has several fields but only one update observable subject.

bsparks February 25, 2011

nice article! if you haven’t heard about this check out: http://www.javascriptmvc.com it’s prolly the best attempt yet

Ashit Vora February 25, 2011

Awesome article.

Colin Michael February 25, 2011

Well done. I have been looking at the subject of using MVC with jQuery and I have been looking at the JavascriptMVC framework. It seems like applying the MVC pattern is very useful, but frameworks collide with much of what users like about jQuery.

I’m looking cautiously at the jQuery Templating API and wondering if that will be the bridge to MVC for jQuery folks. As it is, I don’t think jQuery is the right tool for apps, and ExtJS seems limited, as well. Closure is tempting when going to full-blown apps. Sorting out what level of commitment to a framework would be beneficial is still a hard choice for Javascript coders, but all flavors can benefit from your article.

Thanks!

Peter Michaux February 25, 2011

Colin,

I enjoyed your two articles this week about MVC and jQuery and if they can fit well together or not. I think a general library that has event, XHR, and DOM functions does complement an MVC architecture well.

Björn Rixman February 25, 2011

Thanks for a great article -fully agree that there’s not enough writing on this topic, or at least wasn’t before this one.

The Model-Widget approach would probably be a better fit for many small- to medium-sized projects, and a perfect fit for jQuery and the likes, that make DOM creation and eventbinding a snap.

Peter Michaux February 25, 2011

Björn,

I think the the model-widget architecture’s appropriateness is not dependent on project size but rather on the need for behaviour to change during the life of a view. I think there is a lot of JavaScript large and small where this just isn’t necessary; however, using the separate view and controllers wouldn’t hurt much in these cases.

gareth February 25, 2011

Backbone is very nice for folks who want the smallest possible JS MVC framework - http://documentcloud.github.com/backbone/

uplift February 25, 2011

Great article, wish I could have read something like it a year ago.

+1 on Backbone, it’s more of a set of helper objects to get an MVC style architecture going than a full on framework.

Recently I’ve started to use Backbone with RequireJS for Module Loading. Another area of complex JS application development that could do with more exposure alongside MVC architecture.

billy February 25, 2011

What about “Ajax in Action”? I seem to recall an MVC focus, but it's been a long time, so I could be remembering incorrectly

Justin Meyer February 27, 2011

Peter,

Great article. A few semi-random thoughts ...

First about why using an MVC framework vs rolling your own is a good idea.

  1. Ease of use.

    With JMVC, the loadEmails method is exactly the same as:

    $.Model('Email')

    This creates Email.findAll which you can use like

    Email.findAll({}, function(emails){ ... }, error);

    Which by default makes a request to /emails and calls back with an array of email objects.

    If you want to customize where your JSON sevice is:

    $.Model('Email',{
        findAll: "GET /services/emails.json"
    },{})

    Or you can provide a findAll function yourself.

    To add helper functions to email instances:

    $.Model('Email',{},{
        prettyName: function() {
            return this.name + " " + this.address
        }
    })

    And to bind on address changes:

    email.bind('address', function(){ }).
  2. Power

    For just Model, JMVC has plugins for validations, list helpers, caches, backup/restore. I could go on forever about what $.Controller can do. You can scale up complexity much easier with JMVC.

  3. Performance

    JMVC uses prototypal inheritance when creating model instances (or widgets), and even this can be a problem when creating 1000s of instances.

    Check out how JMVC is as fast as possible for creating lists of things: http://jupiterjs.com/news/javascriptmvc-and-list-performance

  4. You get things for free.

    If you create a widget with JMVC that binds to a model. If you remove the element the controller sits on, the controller will unbind all of its event handlers (including its model listeners). This frees up memory. This is really a critical feature for apps that last a long time but no one thinks about.

There are some drawbacks to using a MVC framework like JMVC:

  1. File size. JMVC's core MVC parts are about 7k gzipped. This isn't really that much compared to something like jQuery.

  2. You have to learn someone else's way of doing things. But, you're learning patterns that have scaled to the largest applications.

On the example code....

todoEl.addEventListener('click', function() {
    todoController.handleTodoClick(todoModel);
}, false);

I really dislike a view knowing that a controller exists. I typically have views/widgets produce events that the controller can bind to.

A good example is a 'select' event. When the widget is clicked, it produces a select event that anyone can listen to.

This way the widget can be configured to produce select events on 'click' in some browsers and 'tap' in others, but the controller code stays the same.

As mentioned earlier, I like using prototypal inheritance better than generator functions for big time performance gains.

I'd suggest showing teardown code. It's an extremely important consideration in long-lived apps.

Finally, to those saying backbone is the 'lightweight' MVC solution ... please got to the download builder at http://javascriptmvc.com/builder.html

select just the Model, Controller, and your template engine of choice.

You'll notice JMVC is about within 1k of Backbone gzipped. (assuming you are counting a template engine and underscore as dependencies for backbone).

Favio March 5, 2011

Hi,

This is a fantastic article, I’ve been reading it multiple times for the past week. xD

One thing I’ve been wondering is how to “properly” apply this way of doing things in a form submission event.

What I’m currently doing is telling the controller (from within my view) that the submit button was pressed and, also, that here is the data that was submitted. Then I proceed to call model methods (from within the controller) to validate that data, and if it is valid, save it.

I’m using the jQuery pubsub plugin for notifying changes in the models to the views (like validation failed, form is saving data, etc.).

Anyway, thanks for the great read! :)

Kevin Marks September 25, 2011

The thrust of the article is a reabstraction on the client side, creating a code-heavy application. However, the classic web design model you discuss at the beginning does have a tacit Model View Controller abstraction too.

The HTML is the model - it describes elements, contents and properties in a hierarchy. The CSS is the view - it describes how the model in the HTML is displayed to the user. The Javascript is the controller - it mediates between actions that the user takes, and can update the model accordingly, by DOM manipulation.

This way of thinking informs the microformats philosophy, that the HTML should be meaningful and clear.

Peter Michaux September 25, 2011

Kevin,

I can see your point and certainly when the web pages are more document-like (e.g. Wikipedia or Amazon) and the JavaScript is quite short then dividing the JavaScript layer into model, view, and controller may be overkill.

When the page is more application-like (e.g. GMail) and the JavaScript grows to tens of thousands of lines, some organization is required in the JavaScript. In this situation introducing another MVC stack is helpful. It is normal for large software systems to have multiple MVC stacks working together.

AlexG November 30, 2012

"Hopefully everyone noticed that an MVC framework is not required to build an application using the MVC architecture"

So what changed your mind? :)

Peter Michaux December 14, 2012

AlexG,

Good question. I started work on the Maria framework to experiment with many things I have not used in a long time (e.g. prototypal inheritance) and to explore the Smalltalk MVC classes in depth. There are some things that do become repetitive without a framework (e.g. attaching listeners and removing them when destroying a view) and Maria does handle those things nicely. Generally, I’m quite happy with how Maria has turned out so far.

Have something to write? Comment on this article.