Saturday, November 23, 2013

Writing a Backbone application without Backbone.js

Introduction

Recently, I was in the position of setting up the stack to use for one my side projects. The project contains a single-page HTML5 frontend, so naturally the choice of which JavaScript MVC framework to use came up. I had quite a bit of experience using Backbone.js from my day job and I mostly like using it, so it was a natural choice. Still, I couldn't help but feel there were a few shortcomings about it too, as well as a decent amount of functionality included in it that I never used anyway. Backbone.js would be a good start for me, but not a perfect match either.

Meanwhile, I also have another side project called Laces.js. It is a model micro-library and in my opinion, its model implementation beats Backbone's hands down. However, there's a large gap in functionality between a micro-library that just provides a model and the complete framework provided by Backbone.js. Still, it got me thinking, how much work would it be to cross that gap and what would the end result look like? I like programming with Backbone, but I also wanted something a bit more to my taste, and I really wanted to use Laces' models. I decided to take the plunge.

In this post I will describe piece by piece how I managed to replace Backbone.js with something eerily similar, yet feeling a lot more modern and improved in many ways. First, I will describe how I replaced Backbone Model and Backbone Collection using custom implementations based on Laces Model and Laces Array. Next, I will describe my replacement for Backbone View and show some of the potential of how it all comes together by showing some of my own Continuous Pager class. Finally, before I wrap up with a conclusion, I will give a little insight how I took Backbone Router to use it in a more standalone fashion. But first...

Extending

Everyone who has worked a great deal with Backbone.js knows how to use the extend() method to create subclasses of models, collections and views. Personally, I've gotten used to this method of subclassing and I like how it works. I'm not going to throw out the baby with the bath water, so I like to maintain this method of subclassing. Fortunately, it was easy to port using the following snippet, taken directly from Backbone.js:

Models and Collections

My experience with Backbone Model and Backbone Collection has always been that I would create my own Model and Collection subclasses that would serve to handle common functionality across models and collections but not found in Backbone.js itself. This time, I would again create my own Model and Collection classes, but there would be no Backbone Model and no Backbone Collection to start from. This meant I would have to implement more functionality myself, but it also meant I could improve on some things I didn't like about Backbone and I could integrate Laces.js into the mix. Specifically, there was a number of things I wanted to change:
  • Backbone's models do not support dot-notation for accessing attributes, instead developers always have to call the get() method. By letting my Model inherit from Laces Model, I automatically get support for using dot-notation. Having support for dot-notation means I never have to use toJSON() anymore before feeding my model to a template, but more on that later. And also note that Laces Model automatically has support for nested attributes, something for which Backbone would require use of the Backbone Deep Model plugin.
  • One of my main annoyances with Backbone was that Model and Collection were completely separated. In my experience, there's typically quite a bit of common ground between the two and there are often situations where you want to set attributes of a collection as if it were a model. For example, when a collection contains a subset of models stored on the server, you may want to have an attribute containing the total number of models on the server. Or when a collection contains search results, you may want to have attributes containing meta-data about the query. To enable these use-cases I made sure my Collection class inherited from Model. The collection's models are stored in a models property, just like with Backbone.js, except the property is now actually a Laces Array. Events generated by the models array are proxied by the Collection class.
  • Backbone.js provides excellent fetch() and save() methods, though experience taught me I would usually override these methods anyway to provide for some extra functionality, like preventing fetching of models which are already in the process of being fetched. In addition, I would normally provide a custom implementation of Backbone.sync(), which can now be handled straight in my own fetch and save implementations.
I have created two more snippets where you can see what these Model and Collection classes may look like. Here's a Model class with support for fetch(), fire()initialize(), isNew()on(), off(), remove(), save(), set(), toJSON()unset() and url() methods:

And here's a Collection class with support for all the Model methods, in addition to add(), any()at(), each(), find(), findIndex()indexOf(), push(), reject()remove(), shift(), slice(), splice() and unshift():

Looking at these gists, it is surprising how little actual code is needed to replicate basic Model and Collection functionality. Of course, to a large extend this is because any real complexity is solved by Laces.js and LoDash.js (or Underscore.js).

Views

Backbone Views are incredibly useful, but in my opinion there are 2 things missing:
  • Two-way data-binding. Having to manually update the DOM when a model changes is cumbersome, especially when there are libraries that can handle this for you. Fortunately, we can now use the Laces.js Tie add-on which provides exactly this.
  • Parent-child relationships. In real-world applications it is common for views to have subviews, but manually managing subviews (or child views) is a chore, especially when they need to be properly destroyed to avoid dangling event handlers. This is why I have built this functionality straight into my View class.
Here's my take on the View class, with support for addChild(), delegateEvents(), initialize()remove()removeChild(), removeChildren(), render(), reparent(), setElement()undelegateEvents() and $() methods:

As you can see, the amount of code needed is still only modest, but there's some complexity in reimplementing the un/delegateEvents() methods. There's one notable improvement though over the delegateEvent() method provided by Backbone.js, which is the events object of any class is automatically merged with those of its superclasses (which is a common request as evidenced by Backbone issue #244).

As example, I will show the implementation of a ContinuousPager that's subclassed from the View class. This should give you an idea of how similar to Backbone it really is at a higher level. Note that while this continuous pager has been brought back to the bare essentials, it is still able to render collections, it updates automatically when models are added to or removed from the collection, and even the rendered items update automatically if any of their attributes change by virtue of the two-way data-bindings provided by the Laces.js Tie add-on:

One more thing to note here is that templates can be passed directly into the Laces Tie constructor, regardless of their type. Plain HTML strings, Handlebars.js templates, Hogan.js templates, etc. all work. Interestingly, you also never need to call toJSON() on your model when feeding it to one of the template engines. Because Laces.js models have native support for dot-notation, all attributes can be accessed as if it was already a JSON object.

Router

For the router functionality, I have settled on a small Router class that uses the same type of router object for defining a mapping between paths and callbacks. I have even used Backbone's routerToRegExp() function to achieve compatibility. Here's a slimmed down version of the class I use:
You may notice I rely solely on HTML5 PushState support for history support, which requires the use of pretty recent browsers. Alternatively, there are plenty of other routing micro-frameworks you may opt to use.

Conclusion

I hope this article has been informative when it comes to showing what it takes to build a Backbone application without actually using Backbone.js, as well as giving some insights into potential gains by using this approach. To me, it was an interesting experiment and while of course it took some more time to set up (which I could afford due to the nature of the side project) the extra features and flexibility I got were definitely worth it. I even find myself occasionally wishing I had the same stack available during my work projects.

Some people may be interested to see what the gain in file size is between using Backbone.js and using Laces.js. Backbone.js is 19KB minified (6.4KB gzipped), whereas Laces.js is 7.4KB minified (2.2KB gzipped). However, as soon as you include the Laces.js Tie add-on the total comes to 11KB minified (3.4KB gzipped). Personally I think Backbone itself is already small enough to not worry too much about its size, but any gains are nice of course.

Finally, I'd like to hear what your thoughts are! Did you find this approach interesting? Valuable? Are you eager to incorporate Laces.js into your own projects? Or should I have used some other libraries even? Should I consider wrapping up all the examples and make them available as a ready-to-use MVC framework? Discussion is welcome!


PS.: The side project I based this article on is C.I. Joe. It's my attempt at building a continuous integration solution, though it's currently pre-alpha so don't even try to use it yet. You may be interested to browse its sources though, so you can see the provided examples in a real-world context.

2 comments:

  1. This extension will help you to calculate the driving distance and duration from one location to another on gmap.Route and distance finder using google map

    ReplyDelete
  2. A Casa Dos Artista As Panteras acompanhando um realyti show que passava a muito anos a tras o filme trras as mesmas porpoçoes que era do progama com muito pessoas na casa , mais essa e um pouco diferente a putaria vai rolar solta com muita putaria e foda liberada .
    A Casa Dos Artista As Panteras || Download do vídeo

    ReplyDelete