Learn how Backbone.js Models provide abstraction that simplifies data management in your single page application
In my previous articles about Backbone.js, I covered the basics of routes and views. I chose those areas as starting points because they are a bit easier to digest and put to use. For example, you can put together simple pages that demonstrate routes and views. To me, these are “quick wins.”
But data is where the real action is. The whole point of a single page application (aka “spa”), is that the page loads, you pull in data, display the data, possibly allow the user to change some of the data, and update the page accordingly. If there is no data in the mix, it’s just a static web page. Even in that scenario, Backbone routes and views can be very helpful because they force you to organize your code so that it is easy to read, manage and extend. But when you need to consume, display and manage data, Backbone really shines.
Why Backbone Models?
Well, in short: Backbone Models wrap your data with super-useful functionality, providing time-saving abstraction.
What?
Ok, consider the following data:
|
{ firstName : “Tom”, lastName : “Corbett”, totalSales: “$250” } |
Now, that all seems nice. But what if you needed to run a function as soon as that object is created? You would need to write a function that “creates” the object first, and then calls a callback (a typical approach). Ok, but now what if you wanted to have a function that was executed any time that the data changes? Well, then you would need a “setter” method, that… yep…. calls a callback. And then what about if the “setter” method had to return only the data that was changed? Oh yeah, and it has to return the previous value of any changed data as well. This is more than a few lines of code.
Now, what if you realized that a giant e-commerce app you are building will probably need all of that functionality, but there will be different kinds of data and you will probably need even more data management tools
All of this (and more) has already been done for you: Backbone.js Models.
Example # 1
|
var SalesPerson = Backbone.Model.extend({}); |
In Example # 1, we have created a variable named “salesperson”. As a result, this variable becomes a constructor function. (If you are not familiar with the concept of JavaScript constructor functions, you might want to review this article: What is the difference between an Object Literal and an Instance Object in JavaScript ? | Kevin Chisholm – Blog )
It is important to understand that you are “extending” the Backbone Model “class”. This means that you are creating a constructor function that inherits from the Backbone Model constructor. You will never execute your SalesPerson constructor. In other words, you will never do this:
You will instantiate that constructor in this manner:
|
var someVariable = new SalesPerson(); |
In this scenario, “someVariable” becomes an “instance” of the SalesPerson constructor, which inherits from the Backbone Model constructor. So far we have not added any new functionality to our “sub-class” of the SalesPerson constructor, which means that it is virtually identical to the Model constructor. Let’s change that.
Example # 2A
|
var SalesPerson = Backbone.Model.extend({ defaults : { fname : "John", lname : "Smith", totalSales : "$0" } }); |
In Example # 2A, we passed an object into the extend method of the Backbone.Model constructor. This is where we start to see some value in our efforts to sub-class this constructor. This object tells the extend method: “Hey, give me the Backbone.Model “class” but add the properties and methods that I provide in this object.
The “defaults” property is a big first step. What you are doing with that property is proving values for all of the properties that EVERY instance of the SalesPerson will constructor will have. In most cases, you will want to overwrite those properties (i.e. there is not too much use in having 700 data objects with the same “fname” of “John”. This approach makes more sense with properties like “totalSales” or “title”. These are the kinds of values that can have a default, and then on a per-item basis, when appropriate, a specific value can be provided.
Example # 2B
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
var SalesPerson = Backbone.Model.extend({ defaults : { fname : "John", lname : "Smith", totalSales : "Employee" } }); var tom = new SalesPerson({ fname : "Tom", lname : "Corbett", totalSales : "$5,750" }); var andy = new SalesPerson({ fname : "Andy", lname : "Taylor", totalSales : "$2,450" }); var jane = new SalesPerson({ fname : "Jane", lname : "McIntyre" }); |
In Example # 2B, we have instantiated our new SalesPerson constructor three times. The first two instances: “tom” and “andy” provide all three properties that our Backbone Model expects: “fname”, “lname” and “totalSales”. But you might have noticed that the third instance: “jane”, does not provide a “totalSales” property when instantiated, which means that the default value of “$0” will be used.
Example # 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
var myJsonData = [ { "fname": "Tom", "lname": "Corbett", "totalSales": "5750" }, { "fname": "Andy", "lname": "Taylor", "totalSales": "2450" }, { "fname": "Jane", "lname": "McIntyre" } ] var SalesPerson = Backbone.Model.extend({ defaults : { fname : "John", lname : "Smith", totalSales : "0" } }); var tom = new SalesPerson({ fname : "Tom", lname : "Corbett", totalSales : "5,750" }); var andy = new SalesPerson({ fname : "Andy", lname : "Taylor", totalSales : "2,450" }); var jane = new SalesPerson({ fname : "Jane", lname : "McIntyre" }); var kendall = new SalesPerson({ "fname":"Kendall", "totalSales":"6500" }); //used for CSS isNewItem = false; function makeLi(model){ var html = [ ('<li id="item_' + model.cid + '" class="salesPersonItem' + (isNewItem ? ' selected willFadeIn' : '') + '">'), ('<p><span class="label">ID:</span> ' + model.cid + '</p>'), ('<p><span class="label">First Name: </span>' + model.get('fname') + '</p>'), ('<p><span class="label">Last Name: </span>' + model.get('lname') + '</p>'), ('<p><span class="label">Total Sales:</span> $' + model.get('totalSales') + '</p>'), ('</li>') ].join(''); return $(html); } function renderInitialSalesData(){ //create a UL for our sales team data var $ul = $('<ul id="salesList"></ul>'); //append LI elements to the UL, using our sales team data: "myJsonData" $ul.append(makeLi(tom)); $ul.append(makeLi(andy)); $ul.append(makeLi(jane)); $ul.append(makeLi(kendall)); //inject the UL into the DOM $('#output').append($ul); }; $(document).ready(function() { //inject the JSON data from the top of this script //into the DOM renderInitialSalesData(); //from now on, any user-added LIs have the "selected" class isNewItem = true; //add a click handler for the "Add a New Salesperson" form $('#addModelSubmit').click(function(e){ e.preventDefault(); e.stopPropagation(); var $newLi = null, newModel = null, newModelData = {}, newFname = $('#new_fname').val(), newLname = $('#new_lname').val(), newTotalSales = $('#new_totalSales').val(); //if the user has entered anything for first name, //add that data to the object we will pass to the new model if (newFname && newFname !== ''){ newModelData.fname = newFname; }; //if the user has entered anything for last name, //add that data to the object we will pass to the new model if (newLname && newLname !== ''){ newModelData.lname = newLname; }; //if the user has entered anything for total sales, //add that data to the object we will pass to the new model if (newTotalSales && newTotalSales !== ''){ newModelData.totalSales = newTotalSales }; //instantiate the SalesPerson constructor, //using our new data (which we got from the form) newModel = new SalesPerson(newModelData); //make a new LI element, using the new model $newLi = makeLi(newModel); //inject the new LI into the DOM $('#salesList').append($newLi); //show the new LI $newLi.fadeIn(800); }); }); |
In Example # 3, we have the full JavaScript code for our working example.
DISCLAIMER: There are a number of JavaScript anti-patterns here. I wanted to keep things simple here so please disregard the fact that there are multiple global variables, an overall lack of names-spaced methods, and no client-side templating is used.
First we have a variable named: myJsonData, which simply provides the raw data. We then have our SalesPerson constructor, which extends Backbone.Model, and has default values of “John”, “Smith” and “$0” for the properties “fnam”, “lname” and “totalSales”. We then instantiate the SalesPerson constructor four times, creating instances, which are each Backbone models (i.e. “tom”, “andy”, “jane” and “kendall”).
The variable: “isNewItem” simply provides a way to give any user-created LI elements a different appearance.
The function: “makeLi” takes a Backbone model instance and uses its “get” method to get a property value from the model. That function returns a jQuery object (i.e. an LI DOM element wrapped with jQuery).
The function: renderInitialSalesData passes our four Backbone model instances to the makeLi function, and injects them into the DOM.
Finally, we create a click-handler for the ‘#addModelSubmit” button. This handles the form submit so that the user can add a new Sales Person. On each click, the “First Name”, “Last Name” and “Total Sales” values from the form fields are passed to a new instance of a Backbone model, and then injected into the DOM.
How to Demo:
When the page loads, you’ll notice that there are three items rendered in the DOM. The data for these elements comes from the global variable: myJsonData.
After page load, enter “First Name”, “Last Name” and “Total Sales” values from the form fields, and then click: “Add New Sales Person”. Each time you do so, a new LI is injected into the DOM, using whatever values you entered. Notice that for any field you leave blank, the default value defined in our model constructor is used (i.e. “John”, “Smith”, and “0”).
Summary
In this article we were introduced to Backbone.js Models. We talked about the value that they provide, and main reasons for using them. We learned how to extend the Backbone.Model constructor, add default properties to the new sub-classed constructor, and then instantiate it.
There is much much more to talk about with regards to Backbone.js models. This article barely scratched the surface, but I hope it has been a helpful introduction to the topic.
Helpful Links for Backbone.js models
http://backbonetutorials.com/what-is-a-model/
http://www.codebeerstartups.com/2012/12/3-defining-models-in-backbone-js-learning-backbone-js