Implementation Specifics

Saturday, 26 October 2013

Implementation Specifics


An SPA is loaded into the browser using a normal HTTP request and response. The page may simply be an HTML file, as in our example above, or it could be a view constructed by a server-side MVC implementation.
Once loaded, a client-side Router intercepts URLs and invokes client-side logic in place of sending a new request to the server. The picture below shows typical request handling for client-side MVC as implemented by Backbone:
URL routing, DOM events (e.g., mouse clicks), and Model events (e.g., attribute changes) all trigger handling logic in the View. The handlers update the DOM and Models, which may trigger additional events. Models are synced with Data Sources which may involve communicating with back-end servers.

Models

  • The built-in capabilities of Models vary across frameworks; however, it’s 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 need a way of persisting Models. Persistence allows us to edit and update Models with the knowledge that their most recent states will be saved somewhere, for example in a web browser’s localStorage data-store or synchronized with a database.
  • A Model may have multiple Views observing it for changes. By observing we mean that a View has registered an interest in being informed whenever an update is made to the Model. This allows the View to ensure that what is displayed on screen is kept in sync with the data contained in the model. Depending on your requirements, you might create a single View displaying all Model attributes, or create separate Views displaying different attributes. The important point is that the Model doesn’t care how these Views are organized, it simply announces updates to its data as necessary through the framework’s event system.
  • It is not uncommon for modern MVC/MV* frameworks to provide a means of grouping Models together. In Backbone, these groups are called Collections. Managing Models in groups allows us to write application logic based on notifications from the group when a Model within the group changes. This avoids the need to manually observe individual Model instances. We’ll see this in action later in the book. Collections are also useful for performing any aggregate computations across more than one model.

Views

  • Users interact with Views, which usually means reading and editing Model data. For example, in our Todo application, Todo Model viewing happens in the user interface in the list of all Todo items. Within it, each Todo is rendered with its title and completed checkbox. Model editing is done through an edit View where a user who has selected a specific Todo edits its title in a form.
  • We define a render() utility within our View which is responsible for rendering the contents of the Model using a JavaScript templating engine (provided by Underscore.js) and updating the contents of our View, referenced by this.el.
  • We then add our render() callback as a Model subscriber, so the View can be triggered to update when the Model changes.
  • You may wonder where user interaction comes into play here. When users click on a Todo element within the View, it’s not the View’s responsibility to know what to do next. A Controller makes this decision. In Backbone, this is achieved by adding an event listener to the Todo’s element which delegates handling of the click to an event handler.
Templating
In the context of JavaScript frameworks that support MVC/MV*, it is worth looking more closely at JavaScript templating and its relationship to Views.
It has long been considered bad practice (and computationally expensive) to manually create large blocks of HTML markup in-memory through string concatenation. Developers using this technique often find themselves iterating through their data, wrapping it in nested divs and using outdated techniques such as document.write to inject thetemplate into the DOM. This approach often means keeping scripted markup inline with standard markup, which can quickly become difficult to read and maintain, especially when building large applications.
JavaScript templating libraries (such as Mustache or Handlebars.js) are often used to define templates for Views as HTML markup containing template variables. These template blocks can be either stored externally or within script tags with a custom type (e.g text/template). Variables are delimited using a variable syntax (e.g <%= title %> for Underscore and {{title}} for Handlebars).
JavaScript template libraries typically accept data in a number of formats, including JSON; a serialisation format that is always a string. The grunt work of populating templates with data is generally taken care of by the framework itself. This has several benefits, particularly when opting to store templates externally which enables applications to load templates dynamically on an as-needed basis.
Let’s compare two examples of HTML templates. One is implemented using the popular Handlebars.js library, and the other uses Underscore’s microtemplates.
Handlebars.js:
class="view"> class="toggle" type="checkbox" {{#if completed}} "checked" {{/if}}> {{title}}
class="edit" value="{{title}}">
Underscore.js Microtemplates:
class="view"> class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>> <%- title %>
class="edit" value="<%= title %>">
You may also use double curly brackets (i.e {{}}) (or any other tag you feel comfortable with) in Microtemplates. In the case of curly brackets, this can be done by setting the Underscore templateSettings attribute as follows:
_.templateSettings = { interpolate : /\{\{(.+?)\}\}/g };
A note on Navigation and State
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 be dynamically rendered in a new view within the same page. Since this doesn’t automatically update the URL, 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 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.

Controllers

In our Todo application, a Controller would be responsible for handling changes the user made in the edit View for a particular Todo, updating a specific Todo Model when a user has finished editing.
It’s with Controllers that most JavaScript MVC frameworks depart from the traditional interpretation of the MVC pattern. The reasons for this vary, but in my opinion, JavaScript framework authors likely initially looked at server-side interpretations of MVC (such as Ruby on Rails), realized that the approach didn’t translate 1:1 on the client-side, and so re-interpreted the C in MVC to solve their state management problem. This was a clever approach, but it can make it hard for developers coming to MVC for the first time to understand both the classical MVC pattern and the properrole of Controllers in other JavaScript frameworks.
So does Backbone.js have Controllers? Not really. Backbone’s Views typically contain Controller logic, and Routers are used to help manage application state, but neither are true Controllers according to classical MVC.
In this respect, contrary to what might be mentioned in the official documentation or in blog posts, Backbone isn’t truly an MVC framework. It’s in fact better to see it a member of the MV* family which 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 you be relying on discussions of MVC to help with your Backbone projects.

No comments:

Post a Comment

 

Total Pageviews

Blogroll

Most Reading