jQuery is not enough | NetEngine

jQuery is not enough

Olivier Monday, 11 November 2013

For a few years now, a lot of attention in the Hacker/Developer community has been drawn to JS MVC / MVVM frameworks. There is a huge number of available resources, often comparing one JS framework to another, like this great article, or picking-up one framework and trying to explain how to make it work.

All of them are really useful but as a software developer, I happened to find myself missing one important part:

Why should I start using a MVC framework?

In one of his talks, DHH suggests that the great enthusiasm related to JS frameworks is due to developers willing to work on something new.

To work on something incipient is surely appealing. But what kind of problems are those libraries actually solving? Some claim that they make cleaner and more maintainable code. That may be true but how do they achieve it?

In this article I will explain why standard JavaScript / jQuery programming is not enough. For each of the related limitations, I will introduce the component(s) of a JS framework which assists in overcoming it.

Run the proper piece of code

One of the best practices introduced by some Back-End frameworks (such as Rails with its Asset Pipeline) is to have a single minified JS bundle used by the entire Web App. This had some great impacts on performance, particularly on the size of the assets sent over the network. However, when a user browses one particular page of your app, only a part of that JavaScript code should run, rather than of all the code contained in the bundle. To be able to do that, Web developers often rely upon DOM element detection:

$(document).ready(function() {
  if ($("#category1").length > 0){
    // launch the dedicated code responsible for handling this part
  }
});

The above has two severe drawbacks:

  1. On a pure design point of view, it is broken. Instead of having all those code snippets trying to figure out if they should run or not, there ought to be a single conductor / piece of software which should fire up the appropriate code.
  2. It dims the app performance. It is not rare to have a JS bundle which handles a tremendous amount of view components throughout the app. If at every page load those many JS snippets have to detect whether they should run using some DOM element detections, there will be tons of jQuery objects instantiated.

The obvious wise way to fix this would be to write a JavaScript component which is the first to be executed by the browser when a page is loaded. Based on the current URL (and associated URL matching rules defined by the developers), this component would start the right JavaScript code. The good news is that you do not need to implement such a component because it already exists in various JS frameworks - it is called a router.

Some basic routers can also be added to your current projects without requiring to use a full JS framework.

Here is an example on how to invoke it:

$(function() {

    App.router.add('/download',function() { App.downloadPage(); });
    App.router.add('/news',function() { App.newsPage(); });
    ...
});

Generate some HTML based on the received data

Data sources for a Web App are not what they used to be: a Back-End connected to a RDBMS (Postgres, MySQL…). Instead, you may want to access your Facebook friends’ current location and plot them on a map. Neither your friends information or the map data (fortunately for your database) is stored on your server. Those external services in the cloud provide us (most of the time) with data and not HTML (simply because they do not know how you want to represent that data).

A jQuery developer would probably write a piece of code which concatenates a string of HTML with the received data and then append the result to the DOM:

...
var $select   = $('<select id="friends_selector"></select>');
var $meOption = $('<option selected="selected" value="' + this.currentUserID + '">My Checkins</option>');
$select.append($meOption);
$.each(friends, function(index, friend) {
  var $friendOption;
  $friendOption = $('<option value="' + friend.id + '">' + friend.name + '</option>');
  $select.append($friendOption);
});

At first glance, the above code is quite short but it already feels complicated to figure out what the HTML snippet will be like: we are mixing two languages in the same line of code! As an alternative, it would be great to instantiate an HTML template with a given object: we need a templating system.

Regardless if they are String-based (like Handlebars.js) or DOM based, the sheer JS frameworks will provide you with a templating system.

And here again, even if you don’t want to use a JS framework, some basic templating systems can be included straight away in your projects!

The resulting template could look like this:

<select id="friends_selector"></select>
<option selected="selected" value="<%= @attributes.currentUserID %>">My Checkins</option>
<% for friend in @attributes.friends: %>
  <option value="<%= friend.id %>"><%= friend.name %></option>
<% end %>

Serialize data from the view all the way to the database and vice-versa

When developing some dynamic Web Application with inline editing for instance, we often end up binding the change of one field in the view to some Business Logic. This one usually ends with an Ajax query saving the associated change in the database. If we want to have this behaviour for a lot of different fields, the associated code will be really long with high risks of mistakes: for each property, its jQuery selector, Ajax URL & HTTP method… should be correct. Plus, when a change is made in the HTML structure, there are high risks for these bindings to cease to work.

Some JS frameworks address this problem by providing what are, in my opinion, their most challenging components:

Those two main components have different duties. Yet, they both represent the Business Logic (a JavaScript object) and can alter its state.

Two way bindings, computed property and dependency detection

A standard templating system as described before loses the context of its input object when it has finished rendering. A templating system with two way bindings however, really represents the object: when the JavaScript object is updated by an altering method, the view automatically reflects that change. On the other hand, when a user changes a field in the view, the corresponding JS object is updated straight away.

Some even more complex behaviours can be achieved via an observable model and dependency detection: If one field in your App is the result of the sum of two other fields, modifying one of the fields will automatically update the sum. Knockout.js is one of the frameworks which handles templating and declarative bindings ridiculously well.

Data access library

The data access library is responsible for properly representing the Business Logic for the server which persists it. It should also be able to change the state of the originated JS object if, for instance, the server cancels an unauthorized transaction. As this layer inherently deals with asynchronous events, it is easy to end up with race conditions. Ember.js with Ember Data uses a state machine in a sophisticated attempt to solve this problem. For example, this one can prevent you from modifying some records which may be overriden by the server. These mechanisms, as well as the challenges faced by Ember Data such as relationships between models, are explained in this great article.

The right tool for the job

When dealing with something new like JavaScript frameworks, it is easy to make a choice based on what most brilliant engineers think. But as clever as they are, they will always be missing an important part: the application you are working on. By outlining the limitations that may occur in your application, I am confident you will pick-up the right JS framework by asking yourself the right questions:

It would be too sad to give up on all those tools just because you endeavoured to use a framework too complicated for your needs.

comments powered by Disqus