Codementor Events

Backbone as the Next Step After jQuery

Published Apr 01, 2016Last updated Jan 17, 2017
Backbone as the Next Step After jQuery

Why Backbone?

Backbone is a lightweight library to organize your code in an object-oriented style. If you're used to getting it done with jQuery, you might sometimes find it hard to maintain the code, when it is anything beyond trivial. Backbone to the rescue!

Integration with jQuery

Backbone depends on jQuery to query and manipulate DOM elements in your app. For many developers that means being able to take very familiar steps to make things happen in your app.

Perhaps you're aware of a good practice to save a jQuery object with DOM element references to a variable, especially if you're going to work with the same set of elements more than once:

var $submenu = $('nav .submenu'); /* <- save the `ul` element, so that the browser doesn't have to do the work of finding it again */
$submenu.toggle(); /* <- use the previously found element */

With Backbone you will go one step further, and use a View object to keep a reference to the DOM element:

var submenu = new Backbone.View({ el: 'nav .submenu' });
submenu.$el.toggle();

There are several things to notice here:

  • Use Backbone.View prototype to create a new object instanse( How does that work? )
  • Pass object with options to the constructor
  • el option can be a selector string. Backbone will use jQuery to find the element.
  • The created submenu object has $el property, which is a jQuery object, so you can call any jQuery methods on it.

Now, why go all the trouble of creating an object with an $el property, when you could simply create a variable, as in the first example? Because that's when object-oriented programming starts, which will help understand, write and maintain your application so much more.

Backbone.View

You will actually use Backbone.View to create your own types of elements, that will have their own templates, logic and interaction, shared between all items of a particular view prototype.

var UserView = Backbone.View.extend({});
var joe = new UserView();
joe.$el.appendTo( document.body );

Notice:

  • We used extend method to create a copy of Backbone.View prototype.
  • We could, but we didn't have to pass el option when we created instance of the UserView
  • An $el property was created for us by Backbone, and contains a div element by default. The div DOM element exists only in memory.
  • We can still use all jQuery methods, which is useful to add the newly created element to the page, in our example.

Right now we have an empty <div></div> appended to our page, which we can only see by inspecting the page. How do we display anything within that div?

View: render method

Meet .render method:

var UserView = Backbone.View.extend({
  render: function(){
    this.$el.html( '<h1>Hello, World, I am a user of this app</h1>');
    return this;
  }
});
var joe = new UserView();
joe.render().$el.appendTo( document.body );

Notice:

  • We created render method, which is used to keep rendering logic.
  • The "render" name is just a convention, Backbone doesn't call this method for you.
  • You can access $el property within a prototype method with this.$el
  • You can use any jQuery methods to manipulate the element
  • Remember to return this in the last line of the render to make chaining work, something we've all learned to appreciate with jQuery
  • Lastly remember to actually call render, and the template will be rendered within the element.

Backbone.Model

So now we have a template, but how do we pass data there, and have an actual user's name in the template?

Meet Model — where data should be kept and maintained. Model prototype is a blueprint for the data, and model instances are data items that will follow the pattern.

var User = Backbone.Model.extend({
  defaults: {
    name: '',
    age: 0
  }
});
var joe = new User({ name: 'Joe', age: 22 });

Notice:

  • We created a User prototype by extending Backbone.Model, just as we did with Backbone.View.
  • We used defaults property to set default values for properties of the model.
  • While defaults method is not required, it will help document which properties a particular model is going to have, and will define default values for the properties, which will help avoid some bugs.
  • We create an instance of the User model and pass an object with actual data to the constructor.

Object joe, except for just storing data, includes some useful methods and logic, that will greatly help maintain the data in an application.

Interaction with a model instance.

At the very least, you should use get and set methods to read and change the data in your model.

console.log( joe.get('name') );
joe.set({ age: 23 });
  • Use get method to read a property value.
  • Pass object with the keys and values that should be changed to the set method.

Model and View together

Now that we know what a model looks like, it's time to use it together with a view to render actual data on a page.

Right at this point it is important to remember, that data should never be aware of how it is displayed. We will pass a model object to a view, and the view will own the model object as it's property, will read the data from it, and will be allowed to update the data.

var User = Backbone.Model.extend({});

var UserView = Backbone.View.extend({
  render: function(){
    this.$el.html('<h1>Hello, I am ' + this.model.get('name') +  '</h1>');
    return this;
  }
});

var joe = new User({ name: 'Joe', age: 22 }); // <-- data is stored here
var joeView = new UserView({ model: joe }); // <-- passing the model to the view
joeView.render().$el.appendTo( document.body ); // <-- render and append to the page

Notice:

  • When creating a UserView instance, we passed model property, and a model instance as it's value.
  • That makes the model instance available as model property of the view
  • Just as we use this.$el to get to the view's DOM element, we'll use this.model to get to its data

Now you might be asking yourself, why did we have to create a model, if we could have just passed a JavaScript object with the same data to the view? Meet Backbone.Events.

Backbone.Events

Backbone.Events prototype is not used directly, but because it is included into every Backbone prototype. It can be used as a publish/subscribe mechanism, a means of communication between Backbone instances.

Simple example:

var joe = new Backbone.Model({ name: 'Joe', age: 22 });
var callback = function(m){
  console.log( 'Model changed:', m.toJSON() );
}
joe.on('change', callback); // <- subscribe to any change in the model

joe.set({ age: 23 }); // make a change, and that will fire the callback

Notice:

  • You can subscribe to a Backbone event with on method, available in any Backbone instance
  • "change" is one of built-in Backbone events, which is fired when a model's data changes.
  • The model is passed to the callback function as an argument
  • Model has toJSON method, which returns a javascript object with properties and values of the model. That's right, it doesn't really returns a JSON string, but a JavaScript object.

Listening to data change from a view

var User = Backbone.Model.extend({});

var UserView = Backbone.View.extend({
  initialize: function(){
    this.listenTo( this.model, 'change', this.render );
  },
  render: function(){
    this.$el.html('<h1>Hello, I am ' + this.model.get('name') +  '</h1>');
    return this;
  }
});

var joe = new User({ name: 'Joe', age: 22 });
var joeView = new UserView({ model: joe });
joeView.render().$el.appendTo( document.body );

joe.set({ age: 23 }); // make a change, view will call render

Notice:

  • initialize is a special method which is called when an instance of a Backbone prototype is created (exactly where you see the new keyword)
  • Instead of calling model.on it is better to use view.listenTo, which will make sure the listener stays in the object that listens, and not in the one that emits events. That makes it easy to remove a view with remove method, without having to worry about the listeners.
  • Whenever "change" event happens in the model instance of that particular view, render method is called, which takes care of the up-to-date date being displayed.

Backbone.Collection

Backbone.Collection is an array on steroids.

Often it makes little sense to create an instance of a model directly from a model. It would be hard to manually create instances of a User model for every joe, alice, carl and every other user of your application. It's much easier to imagine you would have an array of users.

To easily get models from an array into your application, use Backbone.Collection.

var User = Backbone.Model.extend({});
var Users = Backbone.Collection.extend({
  model: User
});
var users = new Users();

Notice how the User prototype was passed to the Users prototype. Now Users collection knows that it is going to contain items of User model.

That means we can just add data objects to the Users collection, and internally Backbone will create instances of User model.

users.add({ name: 'Joe', age: 22 });

Or add an array of objects:

var users_data = [
  { name: 'Alice', age: 21 },
  { name: 'Carl', age: 31 }
];
users.add( users_data);

Note:

  • Mind Users vs users. Users is the prototype of the collection, while users is an instance of the collection, which will actually contain data.

Rendering Collection

Backbone.Collection contains all the usual methods to work with an array, and more. Going through a collection is straightforward with each method:

users.each(function( user ){
  var view = new UserView({ model: user });
  view.render().$el.appendTo( document.body );
});

Notice:

  • The callback function passed to each method will expectedly be called for every item of the collection.
  • The callback will be called with a model instance as an argument.
  • Typically, within the callback function you would create an instance of the respective view, pass current model there, render and append the view to a desired element.

Views and DOM events

It is particularly interesting that it's easy to define DOM event callbacks that will be contained within each view instance.

var UserView = Backbone.View.extend({
  render: function(){
    this.$el.html('<h1>Hello, I am ' +
        this.model.get('name') + 
        '</h1><span class="close">X</span>');
    return this;
  },
  events: {
    'click .close': 'remove'
  }
});

Notice:

  • events property is special in Backbone.View. Backbone will look into this property when a new instance of the view is created, and will use it to define callbacks for DOM events that will fire within that particular instance.
  • Every Backbone.View has remove method, which will remove the whole element from the screen.
  • "remove" method is called when you click ".close" element, and only that one instance of the UserView will be removed from the screen, others will stay untouched.
  • Read more about how DOM events and callbacks are defined: backbonejs.org/#View-events

Complete example

See the Pen LNjOmO by Yuriy Linnyk (@yuraji) on CodePen

Cheers

I hope this quick overview have shown you how straightforward it is to get started with Backbone, and how it's simplicity keeps Backbone heard among the most popular frameworks, without major changes over past years. Sometimes all you need is a simple tool that gets the job done without too much behind the scenes magic.

If you would like to learn more, or discuss Backbone and the tooling around it, definitely let me know.

Discover and read more posts from Yuriy Linnyk
get started
post commentsBe the first to share your opinion
hotJS
8 years ago

Get the latest and best resources about web development

https://www.hotjs.net/

Show more replies