JavaScript interpreters are FAST, and they’re in every browser out there. You can run a rich application in the browser using only open technologies and built in browser-functionality, today.
Backbone.js can help build it. It’s a light-weight MVC for the browser – only ~1000 lines of code, and it’s clean.
There’s no magic. It’s mostly just a structure for the code you write – not a bulky collection of widgets and doodads. It’s fast to learn, and lets you work with whatever technologies you’re already comfortable with.
Trying Out Backbone
If you’re looking to try it out, you’re going to need to load Underscore.js first. It’s Backbone’s only major dependency.
Oh yeah, and Backbone does not have anything to do with the DOM, although it does have some loose dependencies on the $ and ajax calls of jQuery/Zepto. If you aren’t using jQuery or Zepto then you’ll need to do a little more work (like 10 lines of code). I’ll go over the particulars in the next post. For now, play nice and just use jQuery or Zepto if you feel like following along.
What’s going on in there?
There are five main prototypes in Backbone.
- Backbone.Collection – A little more than what you’re thinking.
- Backbone.Model – ActiveRecord implementation, but it’s got a url!
- Backbone.Controller – More or less exactly what you’re thinking.
- Backbone.View – If you’re from Rails, then it’s not what you’re thinking.
- Backbone.Events – Inherit from this guy to give your objects PubSub!
In this blog post we’re going to look at Models, Collections, and Events.
Backbone.Model:
An implementation of the ActiveRecord pattern, but using JSON and a RESTful API instead of directly interfacing with a database.
So let’s say that we’re working on a donut app, and we want to be able to describe our donuts!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // We extend the Backbone.Model prototype to build our own var Donut = Backbone.Model.extend({ // We can pass it default values. defaults : { name : null , sparkles : false , cream_filled : false }, url : function () { // Important! It's got to know where to send its REST calls. // In this case, POST to '/donuts' and PUT to '/donuts/:id' return this .id ? '/donuts/ ' + this.id : ' /donuts'; } }); |
That’s analogous to defining a model class in Rails. Now let’s put it through it’s paces a little.
1
2
3
4
5
6
7
8
9
10
11
12
| // Instantiating var bostonCream = new Donut({ // attributes passed to the Donut constructor will override the defaults name : "Bostan Cream" , cream_filled : true }); // Updating and retrieving attributes // Actually, let's put sprinkles on that... bostonCream.set({ sprinkles : true }); // Saving bostonCream.save(); // this will now POST to the RESTful interface. |
Backbone.Model#save is an asynchronous method. So you’ll need to provide a callback. The following code assumes success.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Now that it's saved it'll have an id.. bostonCream.id; -> 3, or whatever number your JSON api passed up. // Wait, what's the name? bostonCream.get( "name" ); -> "Bostan Cream" // Ugh, sprinkles are gross. And there's a typo in the name. bostonCream.set({ sprinkles : false , name : "Boston Cream" }); // Updating bostonCream.save(); // and now it's a PUT. // it will put directly to "donuts/3", since that's the URI // of the model now. |
Backbone.Collection:
A collection of models, duh. It’s a little smarter than that. It proxies to Underscore to give you a bunch of sweet list functions for working your data out, and lets you keep an eye on what your models are up to through a bunch of handy events. Plus, if you have a RESTful API you can tie them directly to a collection on your backend. Cool, right?
So let’s work out this donut example a little. If you want to manage a collection of donuts, we can use the same API we were working with earlier…
1
2
3
4
5
6
7
8
| var Donuts = Backbone.Collection.extend({ model : Donut, url : "/donuts" }); var donuts = new Donuts; donuts.fetch(); // this makes a call to the server and populates the collection based on the response. |
The call to collection#fetch is asynchronous, so let’s assume this code is being run after the call is complete.
1
2
| donuts.at(0); -> gets donuts by index. donuts.get(0); -> gets donuts by id. |
And a few examples of the Underscore iterator methods…
1
2
3
4
5
6
7
8
9
10
11
12
13
| donuts.each( function (donut) { console.log(donut.get( "name" )); }); // Select donuts with names longer than 2 donuts.select( function (donut) { return donut.get( "name" ).length > 2; }); // Map... donuts.map( function (donut) { return donut.get( "name" ); }); |
Nested Collections
One of the most common uses for collections is to make them nested. In this case, let’s introduce another model, the “Donut Shop” and let’s say that it has a list of Donuts that it serves.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| var DonutShop = Backbone.Model.extend({ defaults : { name : "Untitled" }, initialize : function () { // When you extend a Backbone.Model and give it an initialize function, // the function is called when you instantiate an instance of your Model. // The initialize function is used repeatedly in Backbone's prototypes. We'll be seeing this again this .donuts = new Donuts; this .donuts.url = 'donut_shops/' + this .id + "/donuts" ; } }); |
So now if you have an existing donut shop that has been saved to the server, you can go:
1
| donutShop.donuts.fetch(); |
Backbone.Events:
All of the Backbone core objects give you events to plug in to. For instance, the Collection prototype allows you to bind to “add” and “remove” events.
1
2
3
4
5
6
| donutShop.donuts.bind( "add" , function (donut) { alert( "added " + donut.get( "name" )); }); // now adding a donut will trigger the alert var lemonFilled = donutShop.donuts.add({ name : "Lemon Filled" }); |
Backbone provides you with the Backbone.Events object as well. So, since Underscore is a prereq of Backbone anyway, you can do fancy nonsense like this…
0 comments :
Post a Comment