In this section of the book, you’ll see some frameworks in action to help you at least narrow down your decision to a manageable field. I’ll start with the major players: BackboneJS, AngularJS, and EmberJS, and then cover a few smaller frameworks, because when you're evaluating frameworks for a project, you might not necessarily go with one of those three.
Before I begin, a word of caution: BackboneJS [Backbone] and AngularJS [Angular] can also be considered toolsets rather than frameworks. The language people use when referring to them changes on a regular basis. In practice, these toolsets are rarely combined with another framework (even if it’s possible), and generally inform the architecture, coding style, and maintenance of the application they’re used in. Refer back to the preface if you’re curious about this book’s stance on what a framework is.
Backbone.js
Background
Basic Facts
Released: 2010
Stars on GitHub: 16k+
Dependencies: Underscore.js (also came from DocumentCloud). jQuery optional.
License: MIT License
Backbone describes itself as a means to “suppl[y] structure to JavaScript-heavy applications”1 Backbone comes out of the journalism field, was started by Jeremy Ashkenas at DocumentCloud, first released in 2010. Projects coming out of journalistic environments (like Django or d3) usually have a few telltale characteristics – they’re built for rapid development, but with an eye for responsible management.
Backbone was originally extracted from a Rails application (DocumentCloud), and many of the data concepts correspond with Rails conventions, such as the model, router (you’re your routes.rb file) and leveraging templating.
Strengths and Weaknesses
Backbone is widely respected as a major JavaScript framework and lives up to its name -- it provides a “backbone” for your application. Backbone provides a skeleton or structure to help you put pieces of code in the right places. It’s also lightly opinionated, so you can build out your application as you wish.
Because it’s only lightly opinionated, it integrates well with many kinds of implementations and back-ends. Yes, you could write much of what Backbone gives you yourself … but you don’t have to. Couple that with the fact that when using a common framework, you can more easily plug new developers familiar with it into your team, and Backbone has some serious strengths.
As for the weaknesses, Backbone doesn’t have a very strong domain specific language, that is, there’re plenty of ways to write the exact same thing. As such, it can be harder for developers new to application-quality JavaScript to get up to speed and prevent bad habits from developing. Even Backbone itself says, “There’s more than one way to do it”:
It's common for folks just getting started to treat the examples listed on this page as some sort of gospel truth. In fact, Backbone.js is intended to be fairly agnostic about many common patterns in client-side code. For example …
The authors then go on to reference how you can handle view and model references in 3+ ways, all of which are valid.
There’s also a lot of work left to do in your application once you have Backbone. It lives up to its name, because it gives you the idea of the structure for an application, but it doesn’t dictate lots of rules and it’s up to you to architect your code how you want, be it modular, testable, etc.
How big is it? How fast is it?
Backbone is 6.4kb when gzipped, or about 60kb for a development version that contains lots of comments. In other words, it’s quite small for what you get out of using it as a very basic framework for creating your application.
As for performance, as with any framework, it will depend greatly on how you are implementing your application. If you're interested in performance comparisons among frameworks, unfortunately, performance metrics across frameworks are very tricky. Because you can rewrite an application in a faster or slower way, writing apps to compare frameworks is difficult.
Check it out – Backbone in action
Structuring your app
The first step in building a Backbone application is deciding how you’re going to have a sane structure. Because Backbone is loose about the rules it imposes on you, you can mitigate some of that by deciding to have an obvious folder tree. For a very simple example:
index.html
vendor/
underscore-min.js
backbone-min.js
jquery-1.x.x-min.js [optional]
js/
views.js
models.js
collections.js
app.js
This would be the bare minimum of what you can do to keep your project sane. Backbone allows you to put everything in “app.js” or “foo.js,” but it’s not a great way to keep your application maintainable.
Require.js is a library that can help keep your architecture sane, and is simple enough to be used with many frameworks. Require.js is used for asynchronous module definition. That is, define small modules and “require” them only when needed, asynchronously loading them. The async part is optional if you want it to be, you can minify your modules together in the same file during the build process. A require definition looks like this:
require('mymodule', ['jquery'], function($) {
var MyModule = 'foo';
return MyModule;
});
If you imagine replacing MyModule with MyCollection, MyModel, or MyView, you see how it becomes very useful for splitting your Backbone project into many small manageable files rather than super files.
Additionally, Marionette is a popular library that adds on to BackboneJS to give it a stricter set of ‘rules,’ so to speak, or to help you with architecture strategy.
App skeleton
For our purposes, this example will separate out into the basic structure we saw earlier, with separate files for models, views, and controllers, and an app.js for initialization. I’ll build out a portion of the Realtors and Co app, to show listings and individual properties. Here’s the file tree thus far:
index.html
js/
app.js
models.js
controllers.js
views.js
vendor/
backbone-min.js
underscore-min.js
And the HTML page:
<!DOCTYPE html>
<html>
<head>
<title>Realtors & Co.</title>
</head>
<body>
<h1>Realtor's & Co Properties</h1>
<div id="content"></div>
<script type="text/javascript" src="vendor/underscore-min.js"></script>
<script type="text/javascript" src="vendor/backbone-min.js"></script>
<script type="text/javascript" src="js/models.js"></script>
<script type="text/javascript" src="js/views.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>
I’m loading each file separately, but you would likely use a minification (or uglification) process to obfuscate and minify your code. Of course, it’s necessary to load Backbone first before you load your other files that depend on it. I’m loading my models, then views, then app.js because that’s the order that they depend on each other.
At the top of each file (since they’re loaded separately), I have an app namespace like so:
var App = App || {};
This way, I don’t define my models, views, etc. in the global window namespace. In a more complex environment, (like minification), the code would be wrapped in a closure to completely obfuscate it from the global namespace.
In the example apps, I’ll assume away the backend architecture, which would generally be a REST API. I populate my app’s data with some bootstrapped JSON.
Representing data: Models
The first thing I want to do when coding my app is to represent my data through models. Models contain core data and actions surrounding them (such as CRUD). Models, collections, and views in Backbone are created using the .extend() method on their respective base class, i.e. Backbone.Model.extend(properties). For this mini-app, I’ll need a property model to store information about a property:
App.Property = Backbone.Model.extend({
defaults: {
streetAddress: '',
zipCode: 0,
currentAsk: 0,
imagePath: ''
}
});
I put in some defaults, partially to remind myself of what data I want to store, and also to ensure I don’t have undefined values. I have just a few values to start with, making a minimally viable data object. Right now, there’s a street address and zip code (which can be used for a display name, and the zip code could be used in mapping/neighborhood functionality), an asking price, and a path for an image.
Displaying data – Views
In Backbone, you create views using the now familiar .extend() method as well. To make a simple view for showing a single property:
App.PropertyShowView = Backbone.View.extend({
tagName: 'div',
className: 'property',
template: _.template('<h1><%= streetAddress %></h1>'
+ '<p>This lovely property lies in the <%= zipCode %> area,'
+ ' with an asking price of <%= currentAsk %></p>'),
render: function() {
this.el.innerHTML = this.template(this.model.attributes);
return this;
}
});
I’ve specified the tagName and className to use in the view; the default tag is a <div> element. Backbone uses these attributes to generate the el attribute, or you can specify el directly.
template: _.template('<h1><%= streetAddress %></h1>'
+ '<p>This lovely property lies in the <%= zipCode %> area,'
+ ' with an asking price of <%= currentAsk %></p>')
Because Backbone requires Underscore, you can leverage Underscore templating. I’m writing the template directly to the file, but you can also place it in a <script type= "text/template"> tag to put it on your page and keep markup out of your JavaScript.
Render is where you tell your view how to display. The return this at the bottom of your render function allows for chaining, such as using view.render().el.
Now I’ll create a simple view to show one property item as a list item to use later on in the homepage/list all items view.
App.PropertyListItem = Backbone.View.extend({
tagName: 'tr',
className: 'property-item',
template: _.template('<td><a href="/#property/<%= id %>"><%= streetAddress %></a></td>'
+ '<td><%= zipCode %></td>'
+ '<td><%= currentAsk %></td>'),
render: function() {
this.el.innerHTML = this.template(this.model.attributes);
return this;
}});
A Backbone view is a reusable piece of UI. So when I make a new view, I’ll generally call it with an associated model so that I can pass that model data to the view, which then goes on to use that model’s attributes in the template.
More than one – Collections
I have a simple model and a corresponding view, but I want to show a list of realty properties. Collections are ordered lists of models that come with some handy operations (get, sort, filter, each, map, etc.) that make them appealing to use. So let’s make a collection to store all those property objects together:
App.PropertyCollection = Backbone.Collection.extend({
model: App.Property
});
Now that I have a collection, I’ll make a complementing view to use for the “view all” list to show on the homepage of the app. This view uses the each method of the collection to create new row views for each item.
App.PropertyListView = Backbone.View.extend({
className: 'property-list',
tagName: 'table',
render: function() {
this.collection.each(function(item) {
this.renderProperty(item);
}, this);
return this;
},
renderProperty: function(item) {
var item = new App.PropertyListItem({ model: item });
this.el.appendChild(item.render().el);
}
});
Gluing it all together
Right now there’s data, there’s ways to view the data, but not ways to get to where we view the data. I’ll use a router in my app.js file to do that:
// Define my container where I’ll put my views
App.Container = document.getElementById('content');
App.Router = Backbone.Router.extend({
routes: {
"property/:id": "showProperty",
"*other": "defaultRoute"
},
showProperty: function(id) {
$(App.Container).html(new App.PropertyShowView({ model: App.defaultCollection.get(id) }).render().el);
},
defaultRoute: function() {
$(App.Container).html(new App.PropertyListView({ collection: App.defaultCollection }).render().el);
}
});
var appRouter = new App.Router();
Backbone.history.start();
And then when I load the page, I have routes ready, with a default route at the root to show my list view. I defined the routes in the ‘routes’ property, and the assigned action is defined in the router object, with any necessary parameters assigned with a colon, and those can then be passed on to the action (like in the show action).
After defining the kind of router I want to make, I make a new one (appRouter) and then start the Backbone.history. By default, this has hashes in the URL, but Backbone has pushState capability, meaning you can update the browser’s url field without a refresh. You can turn that on with Backbone.history.start({pushState: true}), but permalinks (going to /path/to/route directly) can still take some work, and aren’t quite there out of the box.
The demo apps for this book live at [site] with the source code available at https://github.com/pselle/choosing-javascript-framework.
Learning more
Backbone has great documentation at http://backbonejs.org/, and a valuable annotated source to help you understand the way it works: http://backbonejs.org/docs/backbone.html.
Bleeding Edge Press also has a Backbone book, find out more at http://bleedingedgepress.com/our-books/backbone-js/
Other websites
There has been error in communication with Booktype server. Not sure right now where is the problem.
You should refresh this page.