Last night I got my super slick, Funky Calendar populated with events from a CouchDB backend via AJAX. As I consider the harsh realities of what faces me when trying to create, update, delete those events in the UI, I think, maybe, I could use some help. So let's see if Backbone.js might do the trick.
First up, I install
backbone.js
locally and source it in my Jade layout:!!! html head title= title link(rel='stylesheet', href='/stylesheets/style.css') script(src='/javascripts/jquery.min.js') script(src='/javascripts/backbone.js') body!= bodyNow, I need a Backbone model to encapsulate my events:
script $(function() { window.Event = Backbone.Model.extend({}); }); });I do not think that I need any default values, specialized construction methods, or any other helper methods. Before moving on, I do a quick sanity check to make sure that that very simple thing is working. In fact, it is not. In my Javascript console, I see:
Uncaught TypeError: Cannot call method 'extend' of undefined backbone.js:150Checking out line 150 of
backbone.js
, I find:// Attach all inheritable methods to the Model prototype. _.extend(Backbone.Model.prototype, Backbone.Events, { // ...Say, what's that funny underscore at the beginning of that line? Ohhh....
What I meant to say earlier was that I install backbone.js and underscore.js locally. I then source both in my site layout file:
!!! html head title= title link(rel='stylesheet', href='/stylesheets/style.css') script(src='/javascripts/jquery.min.js') script(src='/javascripts/underscore.js') script(src='/javascripts/backbone.js') body!= body(and it seems that underscore.js needs to come before backbone.js)
Now when I load the page, I get no errors. But, of course, I hope to do a little more than no errors, so now it is time to create a collection of events:
window.EventList = Backbone.Collection.extend({ model: Event });Now I need to populate the
EventList
collection. The Backbone.js documentation seems to suggest bootstrapping the data inside the template as a preferred solution. I am not really a fan of this as it seems an opportunity for blocking the request as I lookup the data. Besides this is not how I did it with the AJAX version of Funky Calendar—I sent the static page, then loaded the events via an AJAX call.To do the same in Backbone, I think I need to specify the URL for the collection and then add a
parse()
method to massage the CouchDB _all_docs
resource into something that can be consumed by my Event model's initializer. CouchDB's response, proxied thru my app's /events
resource, looks like:{"total_rows":2,"offset":0,"rows":[ {"id":"fdbed27594feb433c74e82eb910015e0", "key":"fdbed27594feb433c74e82eb910015e0", "value":{"rev":"2-b7c22d428e648a6cdd2978c213f79ec0"}, "doc":{"_id":"fdbed27594feb433c74e82eb910015e0", "_rev":"2-b7c22d428e648a6cdd2978c213f79ec0", "startDate":"2011-08-25", "title":"create blog post", "description":"talk about node and CouchDB"}}, {"id":"fdbed27594feb433c74e82eb91001f45", "key":"fdbed27594feb433c74e82eb91001f45", "value":{"rev":"1-2b18432cf6e63b82c6507ff28af9724c"}, "doc":{"_id":"fdbed27594feb433c74e82eb91001f45", "_rev":"1-2b18432cf6e63b82c6507ff28af9724c", "startDate":"2011-08-26", "title":"blog again", "description":"add backbone into the node + couch mix"}} ]}The following
url
and parse
attributes on my collection ought to translate into an array of attributes that Backbone will send directly into the Event model's initializer:window.EventList = Backbone.Collection.extend({ model: Event, url: '/events', parse: function(response) { return _(response.rows).map(function(row) { return row.doc ;}); } });I am using underscore.js to massage the rows in the CouchDB response into something with a
map()
function. I then map the rows to return just the doc
attribute. This ought to produce the following array:[{"_id":"fdbed27594feb433c74e82eb910015e0", "_rev":"2-b7c22d428e648a6cdd2978c213f79ec0", "startDate":"2011-08-25", "title":"create blog post", "description":"talk about node and CouchDB"}, {"_id":"fdbed27594feb433c74e82eb91001f45", "_rev":"1-2b18432cf6e63b82c6507ff28af9724c", "startDate":"2011-08-26", "title":"blog again", "description":"add backbone into the node + couch mix"}]To test this out, I instantiate an
Events
collection object and tell it to fetch()
results from the server:window.Events = new EventList; Events.fetch();Since I don't have this hooked up to anything in my UI, I drop down to the console to try this out:
Nice! Just like that, I have direct access to objects with the appropriate calendar event attributes.
Last up is to actually get the event data to display in my currently empty calendar:
For that, I will need a Backbone.js view object, which I cleverly name
AppView
:window.AppView = Backbone.View.extend({ });In there, I need to initialize the object to render whenever my
Events
collections loads all of its data. I also need to ensure that all of the data is fetched from the /events
resource:window.AppView = Backbone.View.extend({ initialize: function() { Events.bind('all', this.render, this); Events.fetch(); } });If that does what I expect it to, then all I need is a render method for this
AppView
class:window.AppView = Backbone.View.extend({ initialize: function() { Events.bind('all', this.render, this); Events.fetch(); }, render: function() { Events.each(function(event) { var start_date = event.get("startDate"), title = event.get("title"), description = event.get("description"), el = '#' + start_date; $(el).html( '<span title="' + description + '">' + title + '</span>' ); }); } });I am not getting into Backbone's templating in that
render
function. Rather I am just using jQuery for now—just like I did last night in my pure AJAX solution. I get the start data of the event, knowing that my HTML calendar cells have ISO 8601 date IDs just like my start dates (e.g. '#2011-08-27'). If the event start date is on my calendar, then I replace the matching element's html with a <span>
containing the event title.Once I have this class in place, all that is left is to instantiate it:
window.AppView = Backbone.View.extend({ initialize: function() { /* ... */}, render: function() { /* ... */} }); window.App = new AppView;And now, I have my calendar events populated on my calendar via Backbone.js:
In the end, that turns out to be a heck of a lot more code than my pure AJAX solution (34 LOC vs 16). The idea behind Backbone.js is not to simplify simple applications. Rather it provides structure when building complex client-side applications. So I will pick back up tomorrow adding some complexity like creating, removing, and moving calendar events.
Day #126
No comments:
Post a Comment