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