Building better client-side JavaScript applications

data: 2 lipca, 2014
czas czytania: 6 min
autor: Marcin Róg

For some time now, you could have noticed a trend in web applications development to build more and more complicated applications, moving more logic from the server to the client side. Is it an appropriate trend? I don’t know. The discussion between supporters and opponents of each approach may look like discussion concerning superiority of Easter over Christmas; it’s hard to concede the point to any side as they have their arguments. That is why, this article won’t be another for or against argument in this dispute. However, I’ll attempt to answer the question whether a well-known object-oriented programming approach may be successfully adopted to the client-side programming.

Example of bad code

Looking for responsiveness of an application and user experience, we create increasingly complex client code, which becomes difficult to understand and maintain. You can easily imagine situation in which, without any architecture and following the rules of building JavaScript applications, client’s code may look as follows:

$(function(){
    $('#form').submit(function(e) {
        e.preventDefault();

        $.ajax({
            url: '/animals',
            type: 'POST',
            dataType: 'json',
            data: { text: $('#new-animal').find('textarea').val() },
            success: function(data) {
                $('#animals').append('
  • ’ + data.text + ’

’); $(’#new-animal’).find(’textarea’).val(”); } }); }); });

Maintaining this kind of code can be difficult. Why? Because, this short piece of code is responsible for many things: it monitors events from various sources (site, user, network events), it handles data inserted by the user, reparses answer from server and renders HTML. Someone may say: “Yes, you’re right, but what if it isn’t client-side/single page application? Maybe, it’s simply a heavy usage of jQuery library?” – not very persuasive argument, as it is commonly known that easy to maintain and well-designed code is very important. Especially that many tools and frameworks dedicated to maintain client code are available, so that it would be easier to test, maintain, reuse and expand it.

What is MV*?

Getting to the point. We can benefit from the help of JavaScript frameworks based on MVC, most of which … doesn’t use MVC, and rather look like a creation with Model and View, and probably something else in between, which is hard to call. That is why, it is easier to say that the majority of JavaScript frameworks are based on MV*.

Changing the approach may easily provide structure and organization of client code in the project, which will make it easier to maintain in a long term. Even refactoring of existing (spaghetti) code is relatively simple. It is enough to remember how it works and know the answers to these simple questions:

  • What kind of data are presented in my application? – Model.
  • What should the user be able to see? – View.
  • What does handle interaction with the user? – Controller*.

Refactoring the code using MV* framework

How can we benefit from using MV* framework for a given code?

  • Deleting dependencies between DOM and Ajax.
  • Creating code with better structure and easier to test.
  • Deleting unnecessary code from $(document).ready() leaving only a part responsible for creating links with the model.

Let’s try to refactor an exemplary piece of code in a few simple steps.

Step 1: Creating views and moving Ajax call.

Let’s begin with deleting dependencies between Ajax and DOM. We create „Animals” object and expand it with „add” method using constructor pattern with prototypes. Let’s also create „NewAnimalView” view and expand it with „addAnimal”, „appendAnimal” and „clearInput” methods.

Our code will look like this:

var Animals = function() {
};

Animals.prototype.add = function (options) {
     $.ajax({
         url: '/animals',
         type: 'POST',
         dataType: 'json',
         data: { text: options.text },
         success: options.success
     });
};

 var NewAnimalView = function (options) {
    this.animals = options.animals;
    var add = $.proxy(this.addAnimal, this);
    $('# form').submit(add);
 };

 NewAnimalView.prototype.addAnimal = function(e) {
     e.preventDefault();
     var self = this;

     this.animals.add({
         text: $('#new-animal textarea').val(),
         success: function(data) {
             self.appendAnimal (data.text);
             self.clearInput();          
         }
     });
 };

NewAnimalView.prototype.appendAnimal = function(text) {
    $('#animals ul').append('
  • ’ + data.text + ’

’); }; NewAnimalView.prototype.clearInput = function() { $(’#new-animal textarea’).val(”); }; $(document).ready(function() { var animals = new Animals(); new NewAnimalView({ animals: animals }); });

Step 2: Reversing dependencies with the use of events. Distinguishing new views.

Let’s harness MV* framework to work. In our case, it’ll be Backbone. We’ll use events module, which gives us possibility to combine and call custom events. Then, we distinguish new „AnimalsView” from „NewAnimalView” and make it responsible for displaying animals. Separating those responsibilities is particularly simple as we use events. Having transferred responsibility between methods and applying events our code looks as follows:

var events = _.clone(Backbone.Events);
var Animals = function() {
};

Animals.prototype.add = function(text) {
     $.ajax({
         url: '/animals',
         type: 'POST',
         dataType: 'json',
         data: { text: text },
         success: function(data) {
            events.trigger('animal:add', data.text);
         }
     });
};

var NewAnimalView = function(options) {
    this.animals = options.animals;
    events.on('animal:add', this.clearAnimal, this);
    var add = $.proxy(this.addAnimal, this);
    $('# form').submit(add);
 };

NewAnimalView.prototype.addAnimal = function(e) {
     e.preventDefault();
     this.animals.add($('#new-animal textarea').val());
 };

NewAnimalView.prototype.clearInput = function() {
    $('#new-animal textarea').val('');
};

var AnimalsView = function() {
    events.on('animal:add', this.appendAnimal, this);
};

AnimalsView.prototype.appendAnimal = function(text) {
    $('#animals ul').append('
  • ’ + data.text + ’

’); }; $(document).ready(function() { var animals = new Animals(); new NewAnimalView({ animals: animals }); new AnimalsView(); });

Step 3: Transferring the existing structure to Backbone framework.

At the end, let’s use what is the best in Backbone: models, views and collections.

var Animal = Backbone.Model.extend({
    url: '/animals'
});

var Animals = Backbone.Collection.extend({
    model: Animal
});

var AnimalsView = Backbone.View.extend({
    initialize: function() {
        this.collection.on('add', this.appendAnimal, this);
    },

    appendAnimal: function(animal) {
        this.$('ul').append('
  • ’ + animal.escape(’text’) + ’

’); } }); var NewAnimalView = Backbone.View.extend({ events: { 'submit form’: 'addAnimal’ }, initialize: function() { this.collection.on(’add’, this.clearInput, this); }, addAnimal: function(e) { e.preventDefault(); this.collection.create({ text: this.$(’textarea’).val() }); }, clearInput: function() { this.$(’textarea’).val(”); } }); $(document).ready(function() { var animals = new Animals(); new NewAnimalView({ el: $(’#new-animal’), collection: animals }); new AnimalsView({ el: $(’#animals’), collection: animals }); });

Summary

What have we achieved? We work on the higher level of abstraction. Code is easier to maintain and can be reused as well as expanded. As a result, we significantly improved the code structure. Fascinated? Great! However, I have to cool down your emotions: using even the best framework, created code can still be just weak and hard to maintain. Therefore, if you think that implementing one of top MV* frameworks will solve, in a magical way, all the problems with client code you are totally wrong. Note that during refactoring, after the second step, the code looks a lot better and we didn’t use main components of a given framework!

It is also worth remembering that each MV* framework is good, however all focuses on “how” to build an application, leaving the decision „what” to build to the developer. A complement to each framework, especially when the domain of the project is complex, will be the Domain Driven Design approach, which focuses back on the key aspects: “what”, a process of transferring requirements to the actual product. However, it is a topic for different discussion.

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.