MVC for JavaScript Developers

We’ve reviewed the 1970s, but let us now return to the here and now. In modern times, the MVC pattern has been applied to a diverse range of programming languages, including of most relevance to us: JavaScript. JavaScript now has a number of frameworks boasting support for MVC (or variations on it, which we refer to as the MV* family), allowing developers to easily add structure to their applications without great effort.

These frameworks include the likes of Backbone, Ember.js, and AngularJS. Given the importance of avoiding “spaghetti” code, a term that describes code that is very difficult to read or maintain due to its lack of structure, it’s imperative that the modern JavaScript developer understand what this pattern provides. This allows us to effectively appreciate what these frameworks enable us to do differently (Figure 10-1).

MVC pattern

Figure 10-1. MVC pattern

We know that MVC is composed of three core components, described in the following sections.

Models

Models manage the data for an application. They are concerned with neither the user-interface nor presentation layers but instead represent unique forms of data that an application may require. When a model changes (e.g., when it is updated), it will typically notify its observers (e.g., views, a concept we will cover shortly) that a change has occurred so that they may react accordingly.

To understand models further, let us imagine we have a JavaScript photo gallery application. In a photo gallery, the concept of a photo would merit its own model, as it represents a unique kind of domain-specific data. Such a model may contain related attributes such as a caption, image source, and additional metadata. A specific photo would be stored in an instance of a model, and a model may also be reusable. Below, we can see an example of a very simplistic model implemented using Backbone.

var Photo = Backbone.Model.extend({

    // Default attributes for the photo
    defaults: {
      src: "placeholder.jpg",
      caption: "A default image",
      viewed: false
    },

    // Ensure that each photo created has an `src`.
    initialize: function() {
       this.set( { "src": this.defaults.src} );
    }

});

The built-in capabilities of models vary across frameworks, however it is quite common for them to support validation of attributes, where attributes represent the properties of the model, such as a model identifier. When using models in real-world applications we generally also desire model persistence. Persistence allows us to edit and update models with the knowledge that its most recent state will be saved in either memory, in a user’s localStorage data store, or synchronized with a database.

In addition, a model may have multiple views observing it. If, say, our photo model contained metadata, such as its location (longitude and latitude), friends who were present in the photo (a list of identifiers), and a list of tags, a developer may decide to provide a single view to display each of these three facets.

It is not uncommon for modern MVC/MV* frameworks to provide a means to group models together (e.g., in Backbone, these groups are referred to as “collections”). Managing models in groups allows us to write application logic based on notifications from the group should any model it contains be changed. This avoids the need to manually observe individual model instances.

A sample grouping of models into a simplified Backbone collection can be seen here.

var PhotoGallery = Backbone.Collection.extend({

    // Reference to this collection's model.
    model: Photo,

    // Filter down the list of all photos 
    // that have been viewed
    viewed: function() {
        return this.filter(function( photo ){ 
           return photo.get( "viewed" ); 
        });
    },

    // Filter down the list to only photos that 
    // have not yet been viewed
    unviewed: function() {
      return this.without.apply( this, this.viewed() );
    }
});

Older texts on MVC may also refer to a notion of models managing application state. In JavaScript applications, state has a different connotation, typically referring to the current “state”—i.e., view or subview (with specific data) on a users screen at a fixed point. State is regularly discussed when looking at single-page applications, where the concept of state needs to be simulated.

So to summarize, models are primarily concerned with business data.

Views

Views are a visual representation of models that present a filtered view of their current state. While Smalltalk views are about painting and maintaining a bitmap, JavaScript views are about building and maintaining a DOM element.

A view typically observes a model and is notified when the model changes, allowing the view to update itself accordingly. Design pattern literature commonly refers to views as “dumb,” given that their knowledge of models and controllers in an application is limited.

Users are able to interact with views, and this includes the ability to read and edit (i.e., get or set the attribute values in) models. As the view is the presentation layer, we generally present the ability to edit and update in a user-friendly fashion. For example, in the former photo gallery application we discussed earlier, model editing could be facilitated through an “edit” view where a user who has selected a specific photo could edit its metadata.

The actual task of updating the model falls to controllers (which we will be covering shortly).

Let’s explore views a little further using a vanilla JavaScript sample implementation. Below we can see a function that creates a single photo view, consuming both a model instance and a controller instance.

We define a render() utility within our view, which is responsible for rendering the contents of the photoModel using a JavaScript templating engine (Underscore templating) and updating the contents of our view, referenced by photoEl.

The photoModel then adds our render() callback as one of its subscribers so that through the Observer pattern we can trigger the view to update when the model changes.

One may wonder where user interaction comes into play here. When users click on any elements within the view, it’s not the view’s responsibility to know what to do next. It relies on a controller to make this decision for it. In our sample implementation, this is achieved by adding an event listener to photoEl, which will delegate handling the click behavior back to the controller, passing the model information along with it in case it’s needed.

The benefit of this architecture is that each component plays its own separate role in making the application function as needed.

var buildPhotoView = function ( photoModel, photoController ) {

  var base = document.createElement( "div" ),
      photoEl = document.createElement( "div" );

  base.appendChild(photoEl);

  var render = function () {
          // We use a templating library such as Underscore
          // templating which generates the HTML for our 
          // photo entry
          photoEl.innerHTML = _.template( "#photoTemplate" , {
              src: photoModel.getSrc()
          });
      };

  photoModel.addSubscriber( render );

  photoEl.addEventListener( "click", function () {
    photoController.handleEvent( "click", photoModel );
  });

  var show = function () {
    photoEl.style.display = "";
  };

  var hide = function () {
    photoEl.style.display = "none";
  };

  return {
    showView: show,
    hideView: hide
  };

};

Templating

In the context of JavaScript frameworks that support MVC/MV*, it is worth briefly discussing JavaScript templating and its relationship to views, as we briefly touched upon it in the last section.

It has long been considered (and proven) a performance bad practice to manually create large blocks of HTML markup in memory through string concatenation. Developers doing so have fallen prey to unperformant iterating through their data, wrapping it in nested divs, and using outdated techniques such as document.write to inject the “template” into the DOM. As this typically means keeping scripted markup inline with our standard markup, it can quickly become both difficult to read and, more importantly, maintain such disasters, especially when building nontrivially sized applications.

JavaScript templating solutions (such as Handlebars.js and Mustache) are often used to define templates for views as markup (either stored externally or within script tags with a custom type—e.g., text/template) containing template variables. Variables may be delimited using a variable syntax (e.g., {{name}}), and frameworks are typically smart enough to accept data in a JSON form (which model instances can be converted to), such that we only need be concerned with maintaining clean models and clean templates. Most of the grunt work to do with population is taken care of by the framework itself. This has a large number of benefits, particularly when opting to store templates externally, as this can give way to templates being dynamically loaded on an as-needed basis when it comes to building larger applications.

Here, we can see two examples of HTML templates (Examples 10-1 and 10-2). One implemented using the popular Handlebars.js framework and another using Underscore’s templates.

Example 10-1. Handlebars.js code

<li class="photo">
  <h2>{{caption}}</h2>
  <img class="source" src="{{src}}"/>
  <div class="meta-data"> 
    {{metadata}}
  </div>
</li>

Example 10-2. Underscore.js Microtemplates

<li class="photo">
  <h2><%= caption %></h2>
  <img class="source" src="<%= src %>"/>
  <div class="meta-data"> 
    <%= metadata %>
  </div>
</li>

Note that templates are not themselves views. Developers coming from a Struts Model 2 architecture may feel like a template is a view, but it isn’t. A view is an object that observes a model and keeps the visual representation up to date. A template might be a declarative way to specify part or even all of a view object so that it can be generated from the template specification.

It is also worth noting that in classical web development, navigating between independent views required the use of a page refresh. In single-page JavaScript applications, however, once data is fetched from a server via Ajax, it can simply be dynamically rendered in a new view within the same page without any such refresh being necessary. The role of navigation thus falls to a router, which assists in managing application state (e.g., allowing users to bookmark a particular view they have navigated to). As routers are however neither a part of MVC nor present in every MVC-like framework, I will not be going into them in greater detail in this section.

To summarize, views are a visual representation of our application data.

Controllers

Controllers are intermediaries between models and views, which are classically responsible for updating the model when the user manipulates the view.

In our photo gallery application, a controller would be responsible for handling changes the user made to the edit view for a particular photo, updating a specific photo model when a user has finished editing.

Remember that the controllers fulfill one role in MVC: the facilitation of the Strategy pattern for the view. In the Strategy pattern regard, the view delegates to the controller at the view’s discretion; that’s how the strategy pattern works. The view could delegate handling user events to the controller when the view sees fit. The view could delegate handling model change events to the controller if the view sees fit, but this is not the traditional role of the controller.

In terms of where most JavaScript MVC frameworks detract from what is conventionally considered “MVC” however, it is with controllers. The reasons for this vary, but in my honest opinion, it is that framework authors initially look at the server-side interpretation of MVC, realize that it doesn’t translate 1:1 on the client side, and reinterpret the C in MVC to mean something they feel makes more sense. The issue with this, however, is that it is subjective and increases the complexity in both understanding the classical MVC pattern and the role of controllers in modern frameworks.

As an example, let’s briefly review the architecture of the popular architectural framework Backbone.js. Backbone contains models and views (somewhat similar to what we reviewed earlier); however, it doesn’t actually have true controllers. Its views and routers act a little similar to a controller, but neither are actually controllers on their own.

In this respect, contrary to what might be mentioned in the official documentation or in blog posts, Backbone is neither a truly MVC/MVP nor MVVM framework. It’s in fact better to consider it a member of the MV* family that approaches architecture in its own way. There is of course nothing wrong with this, but it is important to distinguish between classical MVC and MV*, should we begin relying on advice from classical literature on the former to help with the latter.

Controllers in Another Library (Spine.js) Versus Backbone.js

Spine.js

We now know that controllers are traditionally responsible for updating the model when the user updates the view. It’s interesting to note that the most popular JavaScript MVC/MV* framework at the time of writing (Backbone) does not have its own explicit concept of controllers.

It can thus be useful for us to review the controller from another MVC framework to appreciate the difference in implementations and further demonstrate how nontraditionally frameworks approach the role of the controller. For this, let’s take a look at a sample controller from Spine.js.

In this example, we’re going to have a controller called PhotosController, which will be in charge of individual photos in the application. It will ensure that when the view updates (e.g., a user edited the photo metadata), the corresponding model does, too.

We won’t be delving heavily into Spine.js at all, but will just take a 10-foot view of what its controllers can do:

// Controllers in Spine are created by inheriting from Spine.Controller

var PhotosController = Spine.Controller.sub({  

  init: function () {
    this.item.bind( "update" , this.proxy( this.render ));
    this.item.bind( "destroy", this.proxy( this.remove ));
  },

  render: function () {
    // Handle templating
    this.replace( $( "#photoTemplate" ).tmpl( this.item ) );
    return this;
  },

  remove: function () {
    this.el.remove();
    this.release();
  }
});

In Spine, controllers are considered the glue for an application, adding and responding to DOM events, rendering templates, and ensuring that views and models are kept in sync (which makes sense in the context of what we know to be a controller).

What we’re doing in the above example is setting up listeners in the update and destroy events using render() and remove(). When a photo entry gets updated, we re-render the view to reflect the changes to the metadata. Similarly, if the photo gets deleted from the gallery, we remove it from the view. In the render() function, we’re using Underscore microtemplating (via _.template()) to render a JavaScript template with the ID #photoTemplate. This simply returns a compiled HTML string used to populate the contents of photoEl.

What this provides us with is a very lightweight, simple way to manage changes between the model and the view.

Backbone.js

Later in this section, we’re going to revisit the differences between Backbone and traditional MVC, but for now, let’s focus on controllers.

In Backbone, one shares the responsibility of a controller with both Backbone.View and Backbone.Router. Some time ago, Backbone did come with its own Backbone.Controller, but as the naming for this component didn’t make sense for the context in which it was being used, it was later renamed to Router.

Routers handle a little more of the controller responsibility, as it’s possible to bind the events there for models and have our view respond to DOM events and rendering. As Tim Branyen (another Bocoup-based Backbone contributor) has also previously pointed out, it’s possible to get away with not needing Backbone.Router at all for this, so a way to think about it using the Router paradigm is probably:

var PhotoRouter = Backbone.Router.extend({
  routes: { "photos/:id": "route" },

  route: function( id ) {
    var item = photoCollection.get( id );
    var view = new PhotoView( { model: item } );

    $('.content').html( view.render().el );
  }
});

To summarize, the takeaway from this section is that controllers manage the logic and coordination between models and views in an application.

Get Learning JavaScript Design Patterns now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.