A Backbone Collection.fetch() can do lot more

Tags

, , , , , , , , , , , , ,

bbone

In UX development, a lot of attention is given to page load or application loading. There are several method to calculate the page load time, the most commonly used method is by finding the difference of the start time and the end time using the Javascript new Date().getTime(). However, this Javascript Time is not accurate, find out why its not here. But it does provides a time which is approximate. With the introduction of HTML5, a new window method window.performance.timing has been introduced, which is incredible. Why I said incredible? See it by yourself by following the steps,

  1. Open your Chrome browser
  2. Go to any website or your website or application.
  3. Click button F12 or right click and select ‘Inspect element’ to open the Chrome developer tools.
  4. Once the developer tools is enabled, select the tab ‘Console’
  5. There you could see a blue > icon, click there and you will be able to enter text there.
  6. Now type window.performance.timing and hit enter.

If you have done the steps above correctly you would see a screen as shown below,

developer_tools

That’s seriously a huge set of information; you could read more about this here.

This window.performance.timing is extremely good to profiling multipage application. You could track when the page rendered completed, the time take for the requests etc. But this doesn’t work for single page application (SPA). The reason is that this window.performance.timing method is being refreshed when a new page is hit, but for SPA’s its only one main page, and remaining are like cards handled with in them.

I could not find any existing solution online, hence decided to workout one at least to calculate the time taken to fetch the data from the server. I could achieve this by overriding the Backbone Collection.fetch().

Backbone Collection

Collections are ordered sets of models. You can bind “change” events to be notified when any model in the collection has been modified, listen for “add” and “remove” events, fetch the collection from the server, and use a full suite of Underscore.js methods.

Above is the definition of Backbone Collection from their official website. Backbone Collections fetch() is the method that loads remote data and binds to the view template.

Overriding the Collection.fetch()

Let’s create a simple model and collection

myApp.models.Employee = Backbone.Model.extend();
myApp.collections.Employee = Backbone.Collection.extend({
    url: 'application/service/employee',
    defaults: {
        model: myApp.models.Employee
    },
    model: myApp.models.Employee,
});

Normally, we directly call the collection fetch as shown below and mostly with the success and error callbacks handling to ensure doing the next step on fetch complete,

var employee = new myApp.collection.Employee();
employee.fetch({
    data: {department: 'IT'},
    success: function() {
         // Success callback code.
    },
    error: function() {
         // Error callback code.
    }
});

To override the Backbone Collection fetch, we will make few changes to the Employee collection,

myApp.models.Employee = Backbone.Model.extend();
myApp.collections.Employee = Backbone.Collection.extend({
    loadTime: 0, // accessible anytime after loading the collection
    url: 'application/service/employee',
    defaults: {
        model: myApp.models.Employee
    },
    model: myApp.models.Employee,
    initialize: function() {
        console.log('myApp.collections.Employee initialized');
    },
    fetch: function(opts) { // Custom fetch
        var fetchStart = new Date().getTime(),
                fetchStop = 0, collection = this;
        $.get(collection.url, opts.data, function(data) {
            fetchStop = new Date().getTime();
            collection.loadTime = fetchStop - fetchStart;
            collection.reset(data);
            opts.callback();
        });
    }
});

If you notice the above code the collection.fetch() internally calls the jQuery $.get(). Hence while calling the fetch you need to specify the params and the callback for executing the $.get(). The fetch options are,

data Type of Object, which is the query params to be sent to the service. Accepts empty {} in case there is no params.
callback Type of function, which is the callback function for $.get().

So, here is how we call the custom fetch method.

$(function() {
    var employee = new myApp.collections.Employee();
    employee.fetch({data: {department: 'IT'}, callback: function() {
            console.log(employee.toJSON());
            console.log('It took ' + employee.loadTime + "ms to load the service " + employee.url);
        }
    });
});

On successful executing you will see the following text on your browser console (developer tools)

It took 1250ms to load the service application/service/employee

Let us also see how this is implemented inside a Backbone View,

$(function() {
    var employee = new myApp.collections.Employee();
    new myApp.views.Employee({el: $('body'), collection: employee});
});

And the myApp.views.Employee looks like this,

myApp.views.Employee = Backbone.View.extend({
    initialize: function() {
        var self = this;
        this.collection.fetch({data: {department: 'IT'}, callback: function() {
                self.render();
            }});
    },
    render: function() {
        console.log('Employee view rendering');
        console.log(this.collection.toJSON());
        console.log('It took ' + this.collection.loadTime + "ms to load the service " + this.collection.url);
    }
});

One fetch for all Collections
If your project have multiple collections, then writing $.get() in each collection is not recommended because it will end up in you managing too many codes. We could write a common function that could be used across all the collections in your application.

myApp.fetchCollection = function(collection, opts) {
    var fetchStart = new Date().getTime(),
            fetchStop = 0;
    $.get(collection.url, opts.data, function(data) {
        fetchStop = new Date().getTime();
        collection.loadTime = fetchStop - fetchStart;
        collection.reset(data);
        opts.callback();
    });
};

And to use it,

myApp.collections.Employee = Backbone.Collection.extend({
    loadTime: 0, // accessible anytime after loading the collection
    url: 'application/service/employee',
    defaults: {
        model: myApp.models.Employee
    },
    model: myApp.models.Employee,
    initialize: function() {
        console.log('myApp.collections.Employee initialized');
    },
    fetch: function(opts) { // Custom fetch
        myApp.fetchCollection(this, opts);
    }
});

Conclusion
As I have mentioned earlier, ‘this is not an elegant solution‘. But, this will be useful when we are left with no better solutions. I’m working on, to find even better solution but if you have a better solution for this case, please to write to me.

Cheers!