JavaScript Architecture: Backbone.js Models

12.26.2011

Updated Aug 11, 2012 to reflect current library versions.

Models contain the data or state of an application. Examples of a model would be a book, car, or customer. A book model would contain typical attributes of a book: title, author, genre, and publisher. A regular JavaScript object could contain this data like so:

var book = {
	title: 'A Tale of Two Cities',
	author: 'Charles Dickens',
	genre: 'historical',
	publisher: 'Chapman & Hall'
};

But this will soon present a problem. For illustration purposes let’s say you have view A that shows the book information and a separate view B where the user can change the book information. When the information is changed from view B, view A needs to know about it so it can update to show the new info to the user. Because our regular JavaScript object doesn’t have any way to notify view A of the change, view B would need a reference to view A so view B can call a method on view A telling it that the book object has been updated. Better yet, maybe we give view B event triggering powers and view A could just bind to view B’s events. Either way, these options should be frantically waiving red flags in your skull. Your views would now have to be aware of each other in at least one direction and that’s one direction too many. We want to free our views from the necessity of being aware of each other.

Eventful models

Backbone’s models take advantage of the Backbone.Events observer pattern implementation so we can deal with this appropriately:

var book = new Backbone.Model({
	title: 'A Tale of Two Cities',
	author: 'Charles Dickens',
	genre: 'historical',
	publisher: 'Chapman & Hall'
});
 
book.on('change:genre', function(model, genre) {
	// Method 1: Use arguments.
	alert('genre for ' + model.get('title') + ' changed to ' + genre);
	// Method 2: Use book variable captured in closure.
	alert('genre for ' + book.get('title') + ' changed to ' + book.get('genre'));
});
 
book.set({genre: 'social criticism'});

When we instantiate a Backbone model and pass in the native object containing our book information, the model then wraps these attributes. Attributes can then be retrieved and stored using the get and set methods. You can set brand new attributes, unset old ones, or clear them all if you feel the need. If you try to access the attributes directly (e.g., alert(book.genre);) you won’t get what you’re expecting because the attributes aren’t there. The get and set methods are required to deal with a JavaScript limitation in order to trigger events when attributes are changed.

By using a Backbone model our views can now watch the book directly for when the genre changes. Notice that Backbone automatically took the name of the attribute we changed, genre, prepended it with change:, then triggered an event using the resulting string as the key. By watching for the change:genre event, we can be notified any time the genre attribute is changed.

We can also extend Backbone.Model and set defaults using the defaults hash:

var Book = Backbone.Model.extend({
	defaults: {
		genre: 'historical'
	}
});
 
var taleOfTwoCities = new Book({
	title: 'A Tale of Two Cities',
	author: 'Charles Dickens',
	publisher: 'Chapman & Hall'
});
 
var goodEarth = new Book({
	title: 'The Good Earth',
	author: 'Pearl S. Buck',
	publisher: 'John Day'
});
 
// Alerts "A Tale of Two Cities: historical"
alert(taleOfTwoCities.get('title') + ': ' + taleOfTwoCities.get('genre'));
 
// Alerts "The Good Earth: historical"
alert(goodEarth.get('title') + ': ' + goodEarth.get('genre'));

Now every book that is instantiated will receive the “historical” genre by default which, of course, can be overridden.

Persistence using a static URL

How do we get data to and from a remote server? Glad you asked. Models have syncing functionality built-in. You don’t have to use it, but it can be really nice. Here’s the simplest form:

var book = new Backbone.Model();
book.url = '/book.php';
 
var fetchSuccess = function(book) {
	alert(book.get('title'));
};
 
book.fetch({success: fetchSuccess});

In this case, there’s no technical need to even extend Backbone.Model. We just instantiate it, set our endpoint url, and call fetch(). As long as the endpoint returns JSON for a single book, the JSON properties will be merged into our model. The result is a model object that then behaves just as any other Backbone model would. Note that we passed a callback function to fetch() that will be executed after a successful response. We could have also passed an error callback function to handle any issues encountered while communicating with the server.

As far as saving data, here’s how we can save a new book to the server:

var book = new Backbone.Model({
	title: 'The Tragedy of the Commons'
});
 
// Or we could've done this instead of passing it into the constructor:
// book.set({title: 'The Tragedy of the Commons'});
 
book.url = '/book.php';
 
var saveSuccess = function(book) {
	alert('book saved!');
};
 
book.save({success: saveSuccess});

In this case, the save() method will send a POST request to the server containing the book data in JSON format. If the model already exists on the server (which by default is deemed true if the model contains an id attribute), the save() method will send a PUT request instead of a POST. The destroy() method will issue a DELETE request.

Persistence using a pattern URL

While a static url may do in some cases (previously /book.php), it’s more likely you’ll have a url pattern for retrieving your models. For example, to get the book with ID = 2 your endpoint may be http://example.com/books/2. The “books” part of the path is common for all books, but the id on the end varies. In this case we could do this:

var Book = Backbone.Model.extend({
	urlRoot: '/books'
})
 
var myBook = new Book({id: 2});
 
var fetchSuccess = function() {
	alert(myBook.get('title'));
};
 
// alerts 'using url: /books/2'
alert('using url: ' + myBook.url());
myBook.fetch({success: fetchSuccess});

By creating a Book class and setting the urlRoot property, we can instantiate a book, set the id property, and call fetch(). By default a url will be composed with the pattern we’re looking for: [urlRoot]/[id]. You can modify this pattern to suit your particular needs. For example, we could set our model up to have a [urlRoot]?id=[id] pattern. We do this by overriding the url property:

var Book = Backbone.Model.extend({
	urlRoot: '/books',
	url: function() {
		return this.urlRoot + '?id=' + this.get('id');
	}
})

Notice in our examples url can be either a property or a method.

Even more likely than dealing with a single book here and there is the need to load and manage a list of books. Fortunately, Backbone helps us there too and we’ll discuss this in the next post in the series.

View state

So far we’ve talked about models in the context of representing entities. In our case, our entity is a book. However, models can also come in handy for storing view state.

Take an application like Photoshop, Illustrator, Lightroom, or pretty much any IDE where there are numerous panels that can be shown or hidden. For illustration purposes, let’s say we’re building a similar app and our requirements are that we have two panels. Panel A happens to show a particular icon on it if and only if panel B is visible. How do we notify panel A when panel B’s visibility is toggled? Suffice it to say we also have a JavaScript object managing each of these panels (one per panel).

If we take the most direct approach, we could pass panel A’s JavaScript object to panel B and when panel B’s visibility is toggled (maybe there’s some sort of hide button within the panel itself) it calls a method on panel A. Or we could just pass a function of panel A’s into panel B. Either way, that just smells bad. Again, we’ve coupled our views. This becomes increasingly problematic as we add more and more panels that need to be aware of each others’ visibility. Maybe due to UXD insanity the requirement changes such that 10 panels have to know about each others’ visibility for one reason or another. And then maybe we have a menu that shows a checkbox next to each of the names of the panels that are visible. And then for memory management purposes we decide to completely destroy and create the panels’ DOM elements and managing JavaScript objects altogether rather than just toggling visibility. Now we have this massive web of object references amongst all the various panels and when we see these new requirements an hour before launch we start to panic. You know this happens in the real world.

Unfortunately, we took the most direct, simple approach now for a lack of flexibility later. This might work in the short term, but it adds to technical debt that may become overwhelming later on.

Instead, what if we created a model to store the state of the view. Maybe we call it our panelState model and the attributes we store on the model might be presetsVisible, histogramVisible, and so on. We can then share this model with any of the panels, the menu, or anything else that might need to know about their state. We can also completely destroy a panel’s DOM element and managing JavaScript object when it’s hidden and we’re okay because rather than every panel having a reference to every other panel, they just have a reference to a single view state model that always exists.

Additional functionality

Additional functionality exists in models including validating, extracting a native object from the Backbone.Model wrapper, determining which attributes have changed, and more.

Optionality and customization

Just because Backbone gives you the ability to do all the things we’ve talked about, rest assured you’re not forced to use any of it. It stays out of the way as much as you want. If you choose, you can write your own function for loading your data wherever you want. You can opt out of using Backbone models completely. Likewise, if there’s functionality that doesn’t fit your system requirements, there are ways to customize the various aspects. I’ll leave that for you as homework whenever you need it, but these are good research techniques to get you headed in the right direction:

Read more in the Backbone.Model documentation.

<< JavaScript Architecture: Backbone.js EventsJavaScript Architecture: Backbone.js Collections >>

Tags: , , , ,


Comments

12.27.2011 / switcherdav said:

Great article, thank you.

Just a question about book.php, can you give an exemple of this script ?

In it, do you test the POST variable ?
if ( isset( $_POST[ ‘title’ ] ) ){ //save new Book or create one }

If no POST var, just do a SELECT ?

thanks again

12.27.2011 / Aaron Hardy said:

I don’t have an example on hand but you have the right idea. You also have this available that might be more appropriate: if ($_SERVER['REQUEST_METHOD'] == 'POST') .... Thanks for stopping by!

12.31.2011 / BarDev said:

Great articles on JavaScript and architecture. At the beginning of the year I started looking into client side MVC-ish frameworks and looked at Backbone.js. I have experience with MVC in ASP.NET, but I just couldn’t grasp Backbone. I eventually decided to use Knockout for its presumed simplicity over Backbone. This week I started to look at client-side MVC again. These articles and articles by other authors have clarified many things about Backbone. Thanks

04.02.2012 / Dreyfus said:

i would like to ask. I’m creating an application where every model has its own file same as collection and views. I’m also using require.js on it. Since on my model.js I’m creating and instantiating a model is it a good practice to always return new model ? ( return an instantiated model NOTE: also i’m doing it on the collection too ) If it is a bad practice what if i would like to access the model on two different views. see the situation is on view1 i’m adding models to my collection and on view2 the render function should display the added models to my collection.

04.02.2012 / Aaron Hardy said:

Thanks for asking. If I understand right, you’re saying you have made a RequireJS module that returns a model instance instead of a model class. I would recommend returning the class instead. If you need to reference an instance of the model class in two different views, you can pass the model directly from one view to another. See my example in JavaScript Architecture: RequireJS Dependency Management.

If, in the end, you decide to make your module a model instance instead of a class, just realize you’re in essence making it a singleton and it will be available globally. That may sound nice and convenient but could turn your app into a nasty cobweb if you’re not careful. Google a bit to learn why singletons and global variables can be bad then proceed at your own risk.

Hope that helps!

04.02.2012 / Aaron Hardy said:

BTW, BarDev, I appreciate your appreciation! Thanks for taking the time to post a comment.

04.02.2012 / Dreyfus said:

sorry, just like to clarify i so i should do this return model instead of return new model? am i getting it right? also if i pass the model directly from one view to another then i would have to call render everytime an item is added? since my add method in itemview is where i insert models to my cartcollection. so my cartview is just to render and display the contents of cartcollection.

04.03.2012 / Aaron Hardy said:

Yes, return the class instead of an instance of the class. I believe your other question is dependent on how you’ve set up your code. I suggest posting that question along with some code on http://stackoverflow.com/ since they have a better format for posting code and a lot of devs that can help out. I’ll watch for it or you can post a link here.

04.03.2012 / Dreyfus said:

hmm. turns out i’m asking the wrong thing. my problem is not really the model but the collection though. but i think they’re almost the same. btw, here is the link for stackoverflow. http://stackoverflow.com/questions/10003815/access-collection-on-two-views-in-backbone-js

08.21.2012 / Backbone tutorials - Adriel Blog | Adriel Blog said:

[…] JavaScript Architecture: Backbone.js Models […]

01.16.2013 / Eugen said:

For your example with different panels needing to know each other’s state; backbone doesn’t really make that easy since you’d still have to have model references thrown around the application in order to determine attribute states.

Can you clear that idea up a bit?

01.22.2013 / Aaron Hardy said:

Hey Eugen, thanks for the question. You’re right, Backbone doesn’t really proactively facilitate any particular way of passing around models or sharing them. I usually don’t find it difficult or problematic to manually pass models from view to view, however. An alternative is to use a global (eww.) or if you’re using RequireJS make an AMD module that you can access and share.

Other thoughts:

When appropriate I try to use a pub/sub bus like the EventAggregator found in Marionette (https://github.com/marionettejs/backbone.marionette) or postal.js though that deserves a much larger discussion.

I’d like to see more discussion about Inversion of Control containers in the context of Backbone. Many dynamic language hipsters poo-poo it but I think it has its place (as it does in AngularJS).


Leave a Comment

Your email address is required but will not be published.




Comment