Friday, September 30, 2011

Worky Faye Updates with Backbone.js

‹prev | My Chain | next›

Up tonight, I hope to get multiple updates working with my Backbone.js calendar application when using faye as the persistence layer.

When I make changes, I am sending the update as a message from my Backbone application to the /calendars/update faye channel. The updated information is sent back from the server on the /calendars/changes faye channel:
The problem is that I am not doing anything with that change information. Ordinarily that would not be a problem, but my backend storage is CouchDB. CouchDB really needs that rev attribute. If a subsequent update is sent with the old rev, CouchDB's optimistic locking will kick in and reject the update:
Got response: 409 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ error: 'conflict',
  reason: 'Document update conflict.' }
So, in my client, I subscribe to the /calendars/changes channel. In the subscription's callback, I use the revision published by the server to update the Backbone model:
    faye.subscribe('/calendars/changes', function(message) {
      console.log('[/calendars/changes]');
      console.log(message);

      var model = calendar.appointments.get(message);
      model.set({rev: message.rev});
      model.set({_rev: message.rev});
    });
With, that, I can update an appointment. And update it again... and it works:
Got response: 201 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ ok: true,
  id: 'cd823d4aaaf358069f9a800410000b95',
  rev: '7-55b3b95b6996072aa1519b6070cf12b6' }

Got response: 201 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ ok: true,
  id: 'cd823d4aaaf358069f9a800410000b95',
  rev: '8-a06160ea8bef50bd71d92a73db61c14b' }
Yay! Except... immediately after I see the successful update, I see:
Got response: 201 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ ok: true,
  id: 'cd823d4aaaf358069f9a800410000b95',
  rev: '8-a06160ea8bef50bd71d92a73db61c14b' }
Got response: 409 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ error: 'conflict',
  reason: 'Document update conflict.' }
When I update again, I successfully update the record, which is followed immediately by two conflicts:
Got response: 201 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ ok: true,
  id: 'cd823d4aaaf358069f9a800410000b95',
  rev: '9-c09e4a0a8bbd5cc25a9e67b19b6a254b' }
Got response: 409 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ error: 'conflict',
  reason: 'Document update conflict.' }
Got response: 409 localhost:5984/calendar/cd823d4aaaf358069f9a800410000b95
{ error: 'conflict',
  reason: 'Document update conflict.' }
Ah. I know what that is. And I will fix it. But first, I need to finish proof reading the recipes that taught me how to solve it.

Recipes with Backbone. Going alpha tonight.


Day #149

Thursday, September 29, 2011

Backbone.js Updates with Faye as the Persistence Layer

‹prev | My Chain | next›

I need to focus on writing Recipes with Backbone tonight, but I still hope to build some on the progress from last night. My efforts to switch to faye as the persistence layer in my Backbone.js calendar application have gone quite well to date. I can create, delete and read objects over faye at this point. So all that remains is update.

The Backbone view code from prior to the persistence layer switch still works, so I can still open an edit dialog to make changes:
Deciding that I should be more humble in my plea to the coding gods, I change the description from "dammit" to "please". Clicking OK seemingly updates the appointment on my calendar (mouseovers reveal the description). Even attempting to re-edit the appointment includes the updated description:
So is that it? Does it just work?

Of course not. I have not subscribed to the /calendars/udpate faye channel on my backend. My debug subscription in the client verifies that the message is being published to that channel:
So all I ought to need is to add a backend subscription to that channel. This follows a node.js / express.js pattern that has become familiar over the past few nights:
client.subscribe('/calendars/update', function(message) {
  // HTTP request options
  var options = {...};

  // The request object
  var req = http.request(options, function(response) {...});

  // Rudimentary connection error handling
  req.on('error', function(e) {...});

  // Write the PUT body and send the request
  req.write(JSON.stringify(message));
  req.end();
});
The pattern is to set HTTP options, here a PUT, to update the existing record:
  // HTTP request options
  var options = {
    method: 'PUT',
    host: 'localhost',
    port: 5984,
    path: '/calendar/' + message._id,
    headers: {
      'content-type': 'application/json',
      'if-match': message._rev
    }
  };
(the if-match is a CouchDB optimistic locking thing)

Next, I build the http request object which includes a response handler callback. This callback parses the JSON response from CouchDB and sends it back on the /calendars/changes channel:
  // The request object
  var req = http.request(options, function(response) {
    console.log("Got response: %s %s:%d%s", response.statusCode, options.host, options.port, options.path);

    // Accumulate the response and publish when done
    var data = '';
    response.on('data', function(chunk) { data += chunk; });
    response.on('end', function() {
      var couch_response = JSON.parse(data);
      client.publish('/calendars/changes', couch_response);
    });
  });
Last I send the message/record via the request object and close the request so that the CouchDB server knows that I have no more HTTP PUT data to send:
  // Write the PUT body and send the request
  req.write(JSON.stringify(message));
  req.end();
If all goes according to plan, the messages that I already know are being sent on /calendars/update will be seen by my server-side subscription, which will tell CouchDB to update the record and finally the browser will see the update on the /calendars/changes channel.

And that is exactly what happens. The PUT is logged as a successful HTTP 201 response from CouchDB:
Got response: 201 localhost:5984/calendar/66543e3457df7597f0e41764e500067c
{ ok: true,
  id: '66543e3457df7597f0e41764e500067c',
  rev: '3-243c4d7084fcdcb7c728917a73e94b97' }
And I even see that response back in the browser:
Nice!

That almost seems too easy. And sadly, it is. If I try to make another change on the same record, the CouchDB updates fail with a HTTP 409 / Document Conflict:
Got response: 409 localhost:5984/calendar/66543e3457df7597f0e41764e500067c
{ error: 'conflict',
  reason: 'Document update conflict.' }
This is because the revision ID that is stored in the Backbone model is now out of date. I need to take the revision returned from the first update and ensure that model becomes aware of it. Otherwise, CouchDB's optimistic locking kicks in, rejecting the update.

True to my word, I call it a night here. I will pick back up tomorrow solving this last mystery. Then, perhaps, some refactoring because this code is extremely soggy (i.e. not DRY).




Day #149

Wednesday, September 28, 2011

Deleting Backbone.js Records with Faye as the Persistence Layer

‹prev | My Chain | next›

To date, I have enjoyed decent success replacing the persistence layer in my Backbone.js calendar application. With only a few hiccups, I have replaced the normal REST-based persistence layer with faye pub-sub channels. The hope is that this will make it easier to respond to asynchronous change from other sources.

So far, I am able to fetch and populate a calendar appointment collection. I can also create new appointments. Now, I really need to get deleting appointments working:
Thanks to replacing Backbone.sync(), in my Backbone application, I already have delete requests being sent to the /caledars/delete faye channel:
    var faye = new Faye.Client('/faye');
    Backbone.sync = function(method, model, options) {
      faye.publish("/calendars/" + method, model);
    }
Thanks to simple client logging:
    _(['create', 'update', 'delete', 'read', 'changes']).each(function(method) {
      faye.subscribe('/calendars/' + method, function(message) {
        console.log('[/calendars/' + method + ']');
        console.log(message);
      });
    });
...I can already see that, indeed, deletes are being published as expected:
To actually delete things from my CouchDB backend, I have to subscribe to the /calendars/delete faye channel in my express.js server. I already have this working for adding appointments, so I can adapt the overall structure of the /calendars/add listener for /calendars/delete:
client.subscribe('/calendars/delete', function(message) {
  // HTTP request options
  var options = {...};

  // The request object
  var req = http.request(options, function(response) {...});

  // Rudimentary connection error handling
  req.on('error', function(e) {...});

  // Send the request
  req.end();
});
The HTTP options for the DELETE request are standard node.js HTTP request parameters:
  // HTTP request options
  var options = {
    method: 'DELETE',
    host: 'localhost',
    port: 5984,
    path: '/calendar/' + message._id,
    headers: {
      'content-type': 'application/json',
      'if-match': message._rev
    }
  };
Experience has taught me that I need to send the CouchDB revisions along with operations on existing records. The if-match HTTP header ought to work. The _rev attribute on the record/message sent to /calendars/delete holds the latest revision that the client holds. Similarly, I can delete the correct object from CouchDB by specifying the object ID in the path attribute—the ID coming from the _id attribute of the record/message.

That should be sufficient to delete the record from CouchDB. To tell my Backbone app to remove the deleted element from the UI, I need to send a message back on a separate faye channel. The convention that I have been following is to send requests to the server on a channel named after a CRUD operation and to send responses back on a channel named after the Backbone collection method to be used. In this case, I want to send back the deleted record on the /calendars/remove channel.

To achieve this, I do the normal node.js thing of passing an accumulator callback to http.request(). This accumulator callback accumulates chunks of the reply into a local data variable. When all of the data has been received, the response is parsed as JSON (of course it's JSON—this is a CouchDB data store), and the JSON object is sent back to the client:
  var req = http.request(options, function(response) {
    console.log("Got response: %s %s:%d%s", response.statusCode, options.host, options.port, options.path);

    // Accumulate the response and publish when done
    var data = '';
    response.on('data', function(chunk) { data += chunk; });
    response.on('end', function() {
      var couch_response = JSON.parse(data);

      console.log(couch_response)

      client.publish('/calendars/remove', couch_response);
    });
  });
Before hooking a Backbone subscription to this /calendars/remove channel, I supply a simple logging tracer bullet:
    faye.subscribe('/calendars/remove', function(message) {
      console.log('[/calendars/remove]');
      console.log(message);
    });
So, if I have done this correctly, clicking the delete icon in the calendar UI should send a message on the /calendars/delete channel (which we have already seen working above). The faye subscription on the server should be able to use this to remove the object from the CouchDB database. Finally, this should result in another message being broadcast on the /calendars/remove channel. This /calendars/remove message should be logged in the browser. So let's see what actually happens...
Holy cow! That actually worked.

If I reload the web page, I see one less fake appointment on my calendar. Of course, I would like to have the appointment removed immediately. Could it be as simple as sending that JSON message as an argument to the remove() method of the collection?
    faye.subscribe('/calendars/remove', function(message) {
      console.log('[/calendars/remove]');
      console.log(message);

      calendar.appointments.remove(message);
    });
Well, no. It is not that simple. That has no effect on the page. But wait...

After digging through the Backbone code a bit, it ought to work. The remove() method looks up models to be deleted via get():
    _remove : function(model, options) {
      options || (options = {});
      model = this.getByCid(model) || this.get(model);
      // ...
    }
The get() method looks up models by the id attribute:
    // Get a model from the set by id.
    get : function(id) {
      if (id == null) return null;
      return this._byId[id.id != null ? id.id : id];
    },
The id attribute was set on the message/record received on the /calendars/remove channel:
So why is the UI not being updated by removing the appointment from the calendar?

After a little more digging, I realize that it is being removed from the collection, but the necessary events are not being generated to trigger the Backbone view to remove itself. The events that the view currently subscribes to are:
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
            // ....
            options.model.bind('destroy', this.remove, this);
            options.model.bind('error', this.deleteError, this);
            options.model.bind('change', this.render, this);
          },
          // ...
        });
It turns out that the destroy event is only emitted if the default Backbone.sync is in place. If the XHR DELETE is successful, a success callback is fired that emits destroy. Since I have replaced Backbone.sync, that event never fires.

What does fire is the remove event. So all I need to change in order to make this work is to replace destroy with remove:
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
            // ....
            options.model.bind('remove', this.remove, this);
            options.model.bind('error', this.deleteError, this);
            options.model.bind('change', this.render, this);
          },
          // ...
        });
And it works! I can now remove all of those fake appointments:
Ah. Much better.

It took a bit of detective work, but, in the end, not much really needed to change.

I really need to cut back on my chain posts so that I can focus on writing Recipes with Backbone. So I sincerely hope that lessons learned tonight will make updates easier tomorrow. Fingers crossed.


Day #147

Tuesday, September 27, 2011

Adding Backbone.js Models Over Faye

‹prev | My Chain | next›

OK now I am going to go back to working with faye as the persistence layer of my Backbone.js calendar application. For the past two days, I have been on an educational detour in the bowels of Javascript minutiae and Backbone recipes. Good stuff to be sure, but I had started out to explore swapping persistence layers.

When I left off, my add-appointment view was able to open a jQuery dialog to collect the necessary appointment information:
I am already able to publish that information to a faye channel ("/calendars/create"). I even have my backend listening on that channel and creating new appointments when it receives appointments to be created. I can see the HTTP 201 response from CouchDB when that is successful:
Got response: 201 localhost:5984/calendar
I even see the newly added appointment broadcast back on the "/calendars/add" channel:
The only missing piece, then, is listening in the browser for this /calendars/add message and adding it to the appointments Collection.

In the normal RESTful Backbone implementation, the sync() method (either defined directly on the model or the default Backbone.sync()) would call the success() options callback. In my overriden-for-faye sync(), however, I am completely ignoring the entire options object:
    Backbone.sync = function(method, model, options) {
      faye.publish("/calendars/" + method, model);
    }
If I cannot call the success() callback, how does the new model object get created and added to the appointments collection?

For that, I can listen to the /calendars/add channel and manually add the object to the collection:
    faye.subscribe('/calendars/add', function(message) {
      console.log('[/calendars/add]');
      console.log(message);

      calendar.appointments.add(message);
    });
That turns out to be perfectly OK. Even though I am passing in an object literal (hash of attributes), the Backbone collection's add() method does all of the heavy lifting. It turns the attributes into a model, associates that model with the collection and fires the "add" event that can drive the UI to actually add the appointment to the calendar.

Unfortunately, when I give it a try, I am greeted with a missing "description" attribute:
I did send in a description, so where did it get lost? Answer: CouchDB (my storage backend).

When new documents are created in CouchDB, the return value is not the document itself. If you think about it, that would be silly—create a new document and the return value is that document. I just passed that document to you, why are you giving it back?

Instead, when a new document is created, CouchDB replies with a document describing meta information about the newly created document (the id, the rev, etc). Back in the browser, the function listening on the /caledars/add channel has no idea what the original message looked like. All it knows is what the sever broadcasts on /calendars/add (which is currently only meta data):
    faye.subscribe('/calendars/add', function(message) {
      console.log('[/calendars/add]');
      console.log(message);

      calendar.appointments.add(message);
    });
I might be able to create a global variable in the browser code to store pending model creations and try to match them up in the /calendars/add callback. I might be able to do that, but that would be silly.

Instead, the node.js server that is receiving the create request can combine the request with the CouchDB meta information. This combination of data should be sufficient for Backbone to create models:
client.subscribe('/calendars/create', function(message) {
    // ...
    response.on('end', function() {
      var couch_response = JSON.parse(data),
          model = {
            id: couch_response.id,
            rev: couch_response.rev,
            title: message.title,
            description: message.description,
            startDate: message.startDate
          };

      client.publish('/calendars/add', model);
    });
    // ...
  });
With that, I am sending back an object that can be converted into a model. And it works:
After the appointment is returned from the node.js / CouchDB server (via Faye), the /calendars/add callback is able to successfully add it to the appointments collection. From there, all of my pre-Faye UI code kicks in, populating the "#1" appointment on the 26th in the UI.

I am unsure how this will work with update and delete, but I will find out. Tomorrow



Day #146

Monday, September 26, 2011

Singleton Views

‹prev | My Chain | next›

Last night, I ran into an unexpected conflict between Backbone.js and external widgeting libraries. In this case, I was using jQuery UI dialogs to edit and create calendar appointments. The conflict arose when the dialog was opened for a second time, resulting in a second copy of the same Backbone event handlers being attached. Hilarity ensued:
The eventual fix took some exploration, but was ultimately very small. Per a comment from my co-author on the forthcoming Recipes with Backbone, however, there is a better solution. It turns out to be one of the many recipes in the book. At the risk of putting giving away too much of the coolness that will be our book, I will endeavor to implement said recipe—the singleton view.

The idea behind the singleton view is that certain Backbone views, such as those attached to widgets from external libraries, only need to be instantiated once. They can then be reset() on demand.

My non-singleton view looks like a typical Backbone view:
        var AppointmentAdd = Backbone.View.extend({...});

        var Day = Backbone.View.extend({
          events : {
            'click': 'addClick'
          },
          addClick: function(e) {
            console.log("addClick");

            var add_view = new AppointmentAdd({
              // The id attributes of my calendar are ISO 8601 dates
              startDate: this.el.id
            });
            add_view.render();
          }
        });
The AppointmentAdd class is defined as an extension of the standard Backbone.View. It then gets initialized and rendered when the day view is clicked. In addition to requiring extra code to prevent duplicate event handlers from being attached to the jQuery UI dialog inside AppointmentAdd, I have a more fundamental problem. I am creating a "new" add-appointment view without creating a new, associated element. In retrospect, problems were bound to occur.

At any rate, the singleton view to the rescue. The idea here is, instead of a class, create an instance of a Backbone view. In this case, the AppointmentAdd view is no longer a class, but an instance of an anonymous class:
        var AppointmentAdd = new Backbone.View.extend({...}));

        var Day = Backbone.View.extend({
          events : {
            'click': 'addClick'
          },
          addClick: function(e) {
            console.log("addClick");

            AppointmentAdd.reset({startDate: this.el.id});
          }
        });
I can then call reset() on that singleton instance whenever the user clicks on the day. Now, there is no need to futz with any View internals to get the add-appointment view to attach the events correctly—the add-appointment view is only instantiated once and the events are attached only once. The reset() is then responsible for assigning the default startDate for the add-appointment dialog and for telling the dialog to render itself:
        var AppointmentAdd = new Backbone.View.extend({
          reset: function(options) {
            this.startDate = options.startDate;
            this.render();
          },
          // ...
        });
Unfortunately, this does not work. Instead of opening the dialog upon click, nothing happens. Nothing except for an error message in Chrome's Javascript console:
Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'reset'
Hunh?! There most certainly is a reset() method!

What gives?

Well, what gives is Javascript operator precedence. As much as I would like to believe that the extend() call in new Backbone.View.extend({ /* ... */}) is a keyword, it is not. It is an underscore.js / Backbone method that is called on the Backbone.View object. The thing is, method calls have a lower precedence than both new and object lookup (e.g. Backbone.View). In other words, instead of getting this:
new ( Backbone.View.extend({ /* ... */}) )
What I am really getting is this:
(new Backbone.View).extend({ /* ... */})
Put another way:
> foo = function() { return { hello: function() {console.log("hello")} } }
[Function]
> it = new foo
{ hello: [Function] }
> it.hello()
hello

> bar = function() { return foo }
[Function]
> it = new bar()
[Function]
> it.hello()
TypeError: Object function () { return { hello: function() {console.log("hello")} } } has no method 'hello'
    at repl:1:4
    at Interface. (repl.js:168:22)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:153:10)
    at Interface._line (readline.js:408:8)
    at Interface._ttyWrite (readline.js:585:14)
    at ReadStream. (readline.js:73:12)
    at ReadStream.emit (events.js:88:20)
    at ReadStream._emitKey (tty_posix.js:306:10)
    at ReadStream.onData (tty_posix.js:69:12)
> it = new (bar())
{ hello: [Function] }
> it.hello()
hello
Thankfully, the solution is simple enough—don't skimp on the parentheses:
        var AppointmentAdd = new (Backbone.View.extend({...}));
With that, I have my singleton add-appointment view working:
Diversions into Javascript operator precedence aside, the singleton view recipe is full of win. It applies much cleaner into my domain and does not require any funky workarounds.


Day #145

Sunday, September 25, 2011

Duplicate Backbone Events with jQuery UI Dialogs

‹prev | My Chain | next›

Up tonight, I hope to continue making progress switching entirely to faye for my Backbone.js application's persistence layer. I can populate appointments in the calendar via Faye, so up tonight I hope to be able to add and update calendar appointments.

To figure out where to start, I open the add-dialog and press OK:
Since the persistence layer has already been swapped out by replacing Backbone.sync:
    var faye = new Faye.Client('/faye');
    Backbone.sync = function(method, model, options) {
      faye.publish("/calendars/" + method, model);
    }
And, since I am logging the various CRUD operations:
    _(['create', 'update', 'delete', 'read', 'changes']).each(function(method) {
      faye.subscribe('/calendars/' + method, function(message) {
        console.log('[/calendars/' + method + ']');
        console.log(message);
      });
    });
Then the create operation shows up in the Javascript console:
Thus, I know that the create operation is being published to Faye, so I need to handle it on the backend. For that, I adopt my server side faye listener from last night. This time, I subscribe to the /calendars/create channel. When a message is received there, I POST into the CouchDB backend store, accumulate the CouchDB response and, when ready, send back the actual Javascript object:
client.subscribe('/calendars/create', function(message) {
  // HTTP request options
  var options = {
    method: 'POST',
    host: 'localhost',
    port: 5984,
    path: '/calendar',
    headers: {'content-type': 'application/json'}
  };

  // The request object
  var req = http.request(options, function(response) {
    console.log("Got response: %s %s:%d%s", response.statusCode, options.host, options.port, options.path);

    // Accumulate the response and publish when done
    var data = '';
    response.on('data', function(chunk) { data += chunk; });
    response.on('end', function() {
      client.publish('/calendars/add', JSON.parse(data));
    });
  });

  // Rudimentary connection error handling
  req.on('error', function(e) {
    console.log("Got error: " + e.message);
  });

  // Write the POST body and send the request
  req.write(JSON.stringify(message));
  req.end();
});
And that seems to work. I get my HTTP 201 response back from CouchDB (man, I love me a HTTP DB) and the newly created object is broadcast back on the /calendars/add faye channel. Even the browser sees it. But something weird happens when I create a second appointment—it gets created twice. And something weird happens when I create a third appointment—it is created three times. I see this in the console.log() output in Chrome's Javascript console and I see it in the server's logs:
{"title":"#1","description":"asdf","startDate":"2011-09-03"}
Got response: 201 localhost:5984/calendar
{"title":"#2","description":"asdf","startDate":"2011-09-03"}
Got response: 201 localhost:5984/calendar
{"title":"#2","description":"asdf","startDate":"2011-09-03"}
Got response: 201 localhost:5984/calendar
{"title":"#3","description":"asdf","startDate":"2011-09-03"}
{"title":"#3","description":"asdf","startDate":"2011-09-03"}
{"title":"#3","description":"asdf","startDate":"2011-09-03"}
Got response: 201 localhost:5984/calendar
Got response: 201 localhost:5984/calendar
Got response: 201 localhost:5984/calendar
So I add a debugger statement to the add-appointment View's click handler:
        var AppointmentAdd = Backbone.View.extend({
          // ...
          events: {
            'click .ok':  'create'
          },
          create: function() {
            debugger;
            appointment_collection.create({
              title: this.el.find('input.title').val(),
              description: this.el.find('input.description').val(),
              startDate: this.el.find('.startDate').html()
            });
          }
        });
The first time I hit that debugger statement, I allow code execution to continue. And then something strange happens... I hit that same event handler again:
Dammit. Every time I open that jQuery UI dialog for adding appointments, I am adding another copy of the same event handler to it. This is not being caused by my switch to faye—this is something that I had not noticed until inadvertent testing while mucking with faye.

Backbone actually has a strategy for preventing this kind of thing from happening. Unfortunately for me, that strategy expects that the element in question is created anew each time the view is initialized. In this case, I re-show() a dialog that was previously hidden.

Backbone's strategy involves assigning a unique ID to the view instance. Before assigning new events to the View's element, Backbone first removes any existing event handlers bound with that unique ID. I wonder if I can use this to my advantage. Perhaps when I initialize the view, I can always assign the same unique ID:
        var AppointmentAdd = Backbone.View.extend({
          initialize: function(options) {
            this.startDate = options.startDate;
            this.cid = 'add-dialog';
          },
          // ...
        });
Sadly, that does not work. I am still getting multiple appointments added on second and third add dialogs. Rooting through the Backbone code, I find that this is because the delegateEvents() method is called before initialize():
  Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    this._configure(options || {});
    this._ensureElement();
    this.delegateEvents();
    this.initialize.apply(this, arguments);
  };
Thus, by the time I assign my constant cid, it is already too late—events have been bound to the non-worky cid. I am hesitant to hook into any of those other methods—the underscore at the front of the method names indicate that these ought to be treated as private methods. Sure, I could do it, but I would feel ashamed at reaching under the covers like that.

This leaves the delegateEvents() method, which is not only a public method, but also one documented in the API. Surely that is fair game. So I override it, setting the constant cid and then call() the original version of delegateEvents() from the Backbone.View's prototype:
        var AppointmentAdd = Backbone.View.extend({
          // ...
          delegateEvents: function(events) {
            this.cid = 'add-dialog';
            Backbone.View.prototype.delegateEvents.call(this, events);
          },
          // ...
        });
And that works! Now, when I add a second or third appointment to my calendar, it only adds a single event:
{"title":"#1","description":"asdf","startDate":"2011-09-05"}
Got response: 201 localhost:5984/calendar
{"title":"#2","description":"asdf","startDate":"2011-09-05"}
Got response: 201 localhost:5984/calendar
I call it a night there. I still need to finish the add circle to ensure that new appointments are added to the existing collection (and to the UI). Then I really need to get delete working. I have a lot of appointments cluttering up my calendar now...


Day #144

Saturday, September 24, 2011

Faye as the Persistence Layer in Backbone.js

‹prev | My Chain | next›

Yesterday I was able to override the sync() method in my Backbone.js model to achieve an added layer of persistence. In addition to the normal REST persistence, my model also persists newly created appointments on a Faye pub-sub channel:
          var Appointment = Backbone.Model.extend({
          urlRoot : '/appointments',
          initialize: function(attributes) {
            // ...
            this.faye = new Faye.Client('/faye');
          },
          // ...
          sync: function(method, model, options) {
            if (method == "create") {
              this.faye.publish("/calendars/public", model);
            }
            Backbone.sync.call(this, method, this, options);
          }
        });
As my esteemed Recipes with Backbone co-author pointed out yesterday, it might make sense to switch entirely to Faye for persistence. It is hard for me to wrap my brain around all of the implications for such a change. At the very least, it is going to break my tests, which stub out XHR REST calls (via sinon.js). That aside, will it clean up my backend code?

Only one way to find out and that is to get started. So, in my browser code, I redefine Backbone.sync() to send any sync requests for create, update, delete or read to a faye channel named accordingly:
    var faye = new Faye.Client('/faye');
    Backbone.sync = function(method, model, options) {
      faye.publish("/calendars/" + method, model);
    }

    // Simple logging of Backbone sync messages
    _(['create', 'update', 'delete', 'read']).each(function(method) {
      faye.subscribe('/calendars/' + method, function(message) {
        console.log('[/calendars/' + method + ']');
        console.log(message);
      });
    });
With that, when I reload my funky calendar Backbone application, I see an empty calendar:
There ought to be 10 appointments on that calendar. I just switched persistence transports, so a few other things need to change as well. To figure out where to start, I check Chrome's Javascript console. There, I see that the request for "read" did go out:
That read request comes when the application is initialized—which includes a fetch() of the collection:
      // Initialize the app
      var appointments = new Collections.Appointments;

      new Views.Application({collection: appointments});
      appointments.fetch();
It can be argued that I should not be fetching here, which requires a round trip to the server. The Backbone documentation itself suggests fetching the data in the backend (node.js / express.js in my case). The data can then be interpolated into the page as a Backbone reset() call. Personally, I prefer serving up a static file that shows something almost immediately followed by quick requests to populate the page with useful, actionable information.

To get "actionable" stuff in my currently empty calendar, I need something on the server side to reply to the request on the /calendars/read channel. Doing faye things on the server is relatively easy. I already have the faye node.js adapter hooked in to my express.js application. I can then call to getClient() to gain access to client actions like subscribe():
// Faye server
var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
bayeux.attach(app);

// Faye clients
var client = bayeux.getClient();

client.subscribe('/calendars/read', function() {
  // do awesome stuff here
});
Now, when the client receives a message on the "read" channel, I can do awesome stuff. In this case, I need to read from my CouchDB backend store:
client.subscribe('/calendars/read', function() {
  //  CouchDB connection options
  var options = {
    host: 'localhost',
    port: 5984,
    path: '/calendar/_all_docs?include_docs=true'
  };

  // Send a GET request to CouchDB
  var req = http.get(options, function(couch_response) {
    console.log("Got response: %s %s:%d%s", couch_response.statusCode, options.host, options.port, options.path);

    // Accumulate the response and publish when done
    var data = '';
    couch_response.on('data', function(chunk) { data += chunk; });
    couch_response.on('end', function() {
      var all_docs = JSON.parse(data);
      client.publish('/calendars/reset', all_docs);
    });
  });

  // If anything goes wrong, log it (TODO: publish to the /errors ?)
  req.on('error', function(e) {
    console.log("Got error: " + e.message);
  });
});
This is a bit more work than with the normal REST interface. With pure REST, I could make the request to CouchDB and pipe() the response back to the client. Backbone (more accurately jQuery) itself takes care of parsing the JSON. Here, I have to accumulate the data response from CouchDB and parse it into a JSON object to be published on a Faye channel. I could send back a JSON string, requiring the client to parse, but that feels like bad form. Faye channels can transmit actual data structures, so that is what I ought to do.

Anyhow, I publish to the /calendars/reset channel because that is what the client will do with this information—reset the the currently empty appointments collection:
    window.calendar = new Cal();

    faye.subscribe('/calendars/reset', function(all_docs) {
      console.log('[/calendars/reset]');
      console.log(all_docs);

      calendar.appointments.reset(all_docs);
    });
Upon reloading the page, however, I still see no appointments on the calendar. In the Javascript console, I can see that the /calendar/read message is still going out. I also see that I am getting a response back that includes the ten appointments already scheduled for this month:
So the message is coming back over the /calendars/reset channel as expected. It is the CouchDB query results as expected, but something is going wrong in the call to reset() on the appointments collection. Probably, something related to the "Uncaught ReferenceError: description is not defined" error message at the bottom of the Javascript console.

Digging through the Backbone code a bit (have I mentioned how nice it is to read that?), I find that calls to reset() or add() need to be run through parse() first. Well, not always, just when parse() does something with the data. Something like I had to do with the CouchDB results:
        var Appointments = Backbone.Collection.extend({
          model: Models.Appointment,
          parse: function(response) {
            return _(response.rows).map(function(row) { return row.doc ;});
          }
        });
Anyhow, the fix is easy enough—just run the results through parse():
    faye.subscribe('/calendars/reset', function(message) {
      console.log('[/calendars/reset]');
      console.log(message);

      var all_docs = calendar.appointments.parse(message);
      calendar.appointments.reset(all_docs);
    });
With that, I have my calendar appointments again populating my calendar:
Only now they are being populated via Faye with an assist from overriding Backbone's sync() function.

On the plus side, it was relatively easy to swap out the entire persistence layer in Backbone. A simple (and in this case very small) override of Backbone.sync() did the trick. On the minus side, I had to do a little more work to convert CouchDB responses into real Javascript data structures. That is not a huge negative (and one that I can easily push into a helper function). Still outstanding it how this will affect the entire application. Also, I have the feeling that I could choose faye channel names better. Questions for another day...


Day #143

Friday, September 23, 2011

Using Faye with Backbone.js

‹prev | My Chain | next›

It has been quite a while since I had an excuse to play with Faye—the awesome little pub-sub library for the browser, node.js and Ruby. That seems like a good excuse to get it working with Backbone.js.

First up is installation, which is a breeze:
➜  calendar git:(faye) npm install faye
faye@0.6.6 ./node_modules/faye
└── redis@0.6.7
Adding support to my existing express.js server is similarly trivial. Just require() the npm package and attach the server:
var express = require('express-unstable'),
    http = require('http'),
    faye = require('faye');

var app = module.exports = express.createServer();

// Lots of configuration here

var bayeux = new faye.NodeAdapter({mount: '/faye', timeout: 45});
bayeux.attach(app);

app.listen(3000);
I need to add faye the list of scripts sourced by my HTML:
...
<script src="/faye.js"></script>
...
And then I am ready to try some simple pub-sub messaging:
Damn that's easy. I subscribe to the /foo channel to log any message sent there. Then I publish a message to the same channel and, it gets logged. If I publish a message to another channel, nothing happens since there are no subscriptions. Network round-trip pub-sub is verified to work. Easy-peasy.

I am not yet certain how (or if) I want to use Faye in my Backbone calendar app. For now, I think it would be cool if public appointments showed up on the /calendars/public Faye channel. To verify something like that is working, in the web page, I add a subscription to the /calendars/public Faye channel. The subscription simply logs the channel name and the message published to the channel:
  $(function() {
    var client = new Faye.Client('/faye')
    client.subscribe('/calendars/public', function(message) {
      console.log('[/calendars/public]');
      console.log(message);
    });
  });
Now all I need to do is get the model to send appointments to that channel. I will worry about the distinction between pubic and private events another day. For now, it is a party and everybody knows everybody.

I could attach a callback to the add model event, but I think it more appropriate to do this in the model's sync() method. When an updated model needs to be synchronized, it now needs to be synced with the Faye channel in addition to the backend store. Happily, overriding sync() is pretty easy in Backbone:
        var Appointment = Backbone.Model.extend({
          urlRoot : '/appointments',
          initialize: function(attributes) {
            // ...
            this.faye = new Faye.Client('/faye');
          },
          // ...
          sync: function(method, model, options) {
            if (method == "create") {
              this.faye.publish("/calendars/public", model);
            }
            Backbone.sync.call(this, method, this, options);
          }
        });
So, when syncing a "create", I publish the model to the target /calendars/public channel. To ensure that the data is still sent to the backend, I call() the Backbone.sync() function with the same arguments that it would have received had I not overridden it.

To try it out, I just need to fire up my server, add an event through the UI and check Chrome's Javascript console:
Yay! That really worked. The two console.log() messages from the Faye subscription came through. Better still, the POST to the backend goes through immediately afterwards via XHR.

Faye is just as easy to use as I remember it. Coupled with Backbone, the two are super easy to get working with a myriad of possible uses. I think tomorrow I will kick the tires one of those uses.


Day #142

Thursday, September 22, 2011

Using Jasmine Spies with Backbone.js

‹prev | My Chain | next›

Over the past week or so, I have made some pretty hefty changes to my Backbone.js calendar application. I am optimistic that, by keeping my jasmine test suite green, things will still work in the live application.

It does not, however, take long to find a problem while clicking around. When I edit calendar appointments, I am greeted with this message:
Actually, I expected that one. That is my clever default error handling on save. The underlying cause, as the Javascript console indicates, is that I have not yet implemented the appointment PUT message on the backend:


After adopting the POST method to PUT in the backend, I am still seeing errors. These turn out to be related to the save() method in my Backbone app:
        var Appointment = Backbone.Model.extend({
          urlRoot : '/appointments',
          save: function(attributes, options) {
            attributes || (attributes = {});
            attributes['headers'] = {'If-Match': this.get("rev")};
            Backbone.Model.prototype.save.call(this, attributes, options);
          },
          // ...
        }):
If you look closely, you might notice that I am putting the the revision headers (required by CouchDB for updates) in the model attributes. I should be putting that in the options.

The fix is easy enough, but this seems like an opportune time to practice my Backbone BDD skills. So I add a new spec to my jasmine suite. I already have specs verifying most of the update life-cycle. Here, I just want to verify that save is called with an If-Match header.

I am not quite sure how to do this, but I think it will involve a jasmine spy. So I spy on the save method and check that the second argument contains the expected headers:
  describe("updating an appointment", function (){
    it("sets CouchDB revision headers", function() {
      var spy = spyOn(Backbone.Model.prototype, 'save').andCallThrough();
      var appointment = calendar.appointments.at(0);

      appointment.save({title: "Changed"});

      expect(spy.mostRecentCall.args[1])
        .toEqual({headers: { 'If-Match': '1-2345' }});
    });

    // ...
  });
That successfully gives me a red test:
A red test is a good thing. It means that I have successfully written a test that describes the bug that I have in my application. Now I can enter the change-the-message or make-it-pass BDD cycle.

To change the message, I change the save() method. Instead of putting the headers in the model attributes, now I store them in the options where they belong:
        var Appointment = Backbone.Model.extend({
          urlRoot : '/appointments',
          initialize: function(attributes) { this.id = attributes['_id']; },
          save: function(attributes, options) {
            options || (options = {});
            options['headers'] = {'If-Match': this.get("rev")};
            Backbone.Model.prototype.save.call(this, attributes, options);
          },
          // ...
        });
Checking my spec, I now see:
Hrm... Well, at least the message has changed. Actually, upon closer inspection, that does not seem like a big problem. It looks as though the options to save() pick up success and error callbacks. So instead of checking the entire second argument to save(), I only check the headers attribute of the second argument:
  describe("updating an appointment", function (){
    it("sets CouchDB revision headers", function() {
      var spy = spyOn(Backbone.Model.prototype, 'save').andCallThrough();
      var appointment = calendar.appointments.at(0);

      appointment.save({title: "Changed"});

      expect(spy.mostRecentCall.args[1].headers)
        .toEqual({ 'If-Match': '1-2345' });
    });
    // ...
  });
With that, I am green!
Nice. And, after a few quick clicks around the real app, I am satisfied that everything works in real life too!

That is something of a weak test (easy to break if functionality changes or Backbone changes). Still it is good to know that BDDing new Backbone features is not only possible, but pretty darn easy. Even more impressive is that I made massive changes to the application over the past week without breaking a thing. Because I religiously ran my jasmine test suite with each change, I weathered change in solid shape.

And now I have one more thing that I won't break.


Day #141

Wednesday, September 21, 2011

Using Function Objects to Namespace Backbone.js Applicaitons

‹prev | My Chain | next›

In a continuing effort to find my ideal namespacing scheme for my Backbone.js applications, I tried using a sprinkling of Javascript closures last night. The end result gave me the following overall structure:
    var Cal = {};

    Cal.Models = (function() {
      var Appointment = Backbone.Model.extend({...});

      return {Appointment: Appointment};
    })();

    Cal.Collections = (function() {
      var Appointments = Backbone.Collection.extend({...});

      return {Appointments: Appointments};
    })();

    Cal.Views = (function() {
      var appointment_collection;

      var Appointment = Backbone.View.extend({...});
      var AppointmentEdit = Backbone.View.extend({...});
      var AppointmentAdd = Backbone.View.extend({...});
      var Day = Backbone.View.extend({...});
      var Application = Backbone.View.extend({...});

      return {Application: Application};
    })();

    var appointments = window.appointments = new Cal.Collections.Appointments,
        app = new Cal.Views.Application({collection: appointments});

    appointments.fetch();
I get two benefits from this approach. First, when Views spawn other Views (e.g. a click on the day View spawns an add-appointment View), I do not have to use the fully qualified view name. This leads to cleaner, more readable code:
      var Day = Backbone.View.extend({
        addClick: function(e) {
          var add_view = new AppointmentAdd({
            model: this.model,
            startDate: this.el.id
          });
          add_view.render();
        }
      });
The second benefit is that the most of the Views are private. Only the application View was returned / exported by the View function. The other Views in there are attached by the application View or each other, so there is no need for them to leak into the global namespace. Cleaner code, cleaner runtime.

This approach still required me to use the full namespace (e.g. Cal.Models.Appointment) to describe Models inside Collections:
      var Appointments = Backbone.Collection.extend({
        model: Cal.Models.Appointment,
       // ...
      });
I can live with that. As Nick pointed out in a comment the other day, this does not (or at least should not) happen much.

What bothers me is how I have to build all of this outside of the Cal object literal:
 var appointments = new Cal.Collections.Appointments,
        app = new Cal.Views.Application({collection: appointments});

    appointments.fetch();
I do not think that the calling context should be responsible for knowing that the collection should be instantiated first. Nor should it know that the Collection needs to be passed to the application View. And finally the calling context should not be responsible for fetching the models from the server.

Really, all I want to do is create a new "Calendar" application and have the rest taken care of by the object code. I think I can get that by making Cal a function constructor instead of the current object literal. Something along the lines of:
    var Cal = function() {...};

    window.calendar = new Cal(); 
Inside the constructor function, I build up the Models, Collections, and Views just like I did yesterday. I even keep the closures that let me treat much of it as private methods. By returning an object literal from the constructor function, I then create a constructor that can produce an identical API to the Cal object literal from yesterday.

The constructor function then becomes:
    var Cal = function() {
      var Models = (function() {
        var Appointment = Backbone.Model.extend({...});

        return {Appointment: Appointment};
      })();

      var Collections = (function() {
        var Appointments = Backbone.Collection.extend({...});

        return {Appointments: Appointments};
      })();

      var Views = (function() {
        var Appointment = Backbone.View.extend({...});
        var AppointmentEdit = Backbone.View.extend({...});
        var AppointmentAdd = Backbone.View.extend({...});
        var Day = Backbone.View.extend({...});
        var Application = Backbone.View.extend({...});

        return {Application: Application};
      })();

      // Externally accessible
      return {
        Models: Models,
        Collections: Collections,
        Views: Views
      };
    };
Now, if I wanted to, I could create a calendar object and access the appointment class like so:
var cal = new Cal();
var appt = new cal.Models.Appointment();
So far, I do not have much different than yesterday's closure fest. To get some new functionality, I also add the initializer code to the constructor:
    var Cal = function() {
      var Models = (function() {...})();

      var Collections = (function() {...})();

      var Views = (function() {...})();

      // Initialize the app
      var appointments = new Collections.Appointments;

      new Views.Application({collection: appointments});
      appointments.fetch();

      return {
        Models: Models,
        Collections: Collections,
        Views: Views,
        appointments: appointments
      };
    };
With that, I can switch to the much simpler calendar constructor to start my Backbone app:
    window.calendar = new Cal();
I do have one spec failure:
But it is a simple matter of updating the spec to use the new calendar application object:
it("displays model updates", function () {
      var appointment = calendar.appointments.at(0);
      appointment.save({title: "Changed"});

      server.respondWith('PUT', '/appointments/42', '{"title":"Changed!!!"}');
      server.respond();

      expect($('#2011-09-15')).toHaveText(/Changed/);
    });
With that, I am passing again:
I certainly like the how the application can now be instantiated and run with a single constructor. I also like the closures borrowed from yesterday that give me private methods like the sub-Views. I worry that I will not remember why the closure ceremony was necessary if I return to the code in 6 months. There is not that much ceremony, but it could end up making the code less maintainable. I will ruminate on that.

Aside from that small concern, I do think this overall approach is pretty solid. Inside the constructor, I even get to reference things cross-concerns with one less namespace level — that is, I can create an instance of Models.Appointment instead of Cal.Models.Appointment. Less typing make this a happy stopping point for the night. Well, less typing and a very small external interface.


Day #140

Tuesday, September 20, 2011

Javascript Closures for Namespacing Backbone.js Apps

‹prev | My Chain | next›

Up tonight, I continue exploring namespacing my Backbone.js calendar application.

Last night, I tried manually building up a Cal namespace:
var Cal = {
      "Models": {},
      "Collections": {},
      "Views": {}
    }
This worked, but had at least one annoyance—the necessity of referring to object in the global namespace (e.g. Cal.Models.Appointment) throughout the application. In certain parts this seems unavoidable. For instance, Backbone collections need to reference a model, so I need to specify the fully namespaced model:
    Cal.Collections.AppointmentList = Backbone.Collection.extend({
      model: Cal.Models.Appointment,
      // ...
    });
I could probably live with that, but it just gets annoying in the various views. The day View needs to open the add-appointment View when clicked, necessitating a global namespace:
    Cal.Views.DayView = Backbone.View.extend({
      // ...
      addClick: function(e) {
        console.log("addClick");

        var add_view = new Cal.Views.AppointmentAddView({
          model: this.model,
          startDate: this.el.id
        });
        add_view.render();
      }
    });
The appointment View needs to open the edit-appointment View when clicked—global namespace. Collection events (e.g. 'add', 'reset') need to bind to view actions—global namespace. It winds up being noisy, which decreases the overall readability of the code.

It would require a significantly different approach to avoid global namespaces across concerns (i.e. accessing the model classes from collections). It does not take much effort to avoid that within a single concern. For instance, I can change the View class namespace from:
    Cal.Views.AppointmentView = Backbone.View.extend({...});
    Cal.Views.AppointmentEditView = Backbone.View.extend({...});
    Cal.Views.AppointmentAddView = Backbone.View.extend({...});
    Cal.Views.DayView = Backbone.View.extend({...});
    Cal.Views.AppView = Backbone.View.extend({...});
Instead I can put all of the views inside a single anonymous function that returns an object literal of those views. Setting Cal.Views to the result of that function winds up building the same namespace:
    Cal.Views = (function() {
      var Appointment = Backbone.View.extend({...});
      var AppointmentEdit = Backbone.View.extend({...});
      var AppointmentAdd = Backbone.View.extend({...});
      var Day = Backbone.View.extend({...});
      var Application = Backbone.View.extend({...});

      return {
        Appointment: Appointment,
        AppointmentEdit: AppointmentEdit,
        AppointmentAdd: AppointmentAdd,
        Day: Day,
        Application: Application
      };
    })();
Now, when the Day View class needs to open an AppointmentAdd View, it can call just AppointmentAdd instead of Cal.Views.AppointmentAdd:
      var Day = Backbone.View.extend({
        addClick: function(e) {
          console.log("addClick");

          var add_view = new AppointmentAdd({ /* ... */ });
          add_view.render();
        }
      });
Following along that convention, I can encapsulate my entire Backbone application as:
    var Cal = {};

    Cal.Models = (function() {
      var Appointment = Backbone.Model.extend({...});

      return {Appointment: Appointment};
    })();

    Cal.Collections = (function() {
      var Appointments = Backbone.Collection.extend({...});

      return {Appointments: Appointments};
    })();

    Cal.Views = (function() {
      var appointment_collection;

      var Appointment = Backbone.View.extend({...});
      var AppointmentEdit = Backbone.View.extend({...});
      var AppointmentAdd = Backbone.View.extend({...});
      var Day = Backbone.View.extend({...});
      var Application = Backbone.View.extend({...});

      return {Application: Application};
    })();
I was even able to eliminate much of what the Cal.Views namespace needed because the Application view is responsible for attaching the remainder. Thus, to start the Backbone app, I can instantiate the collection object, pass it to the application view, and fetch appointments from the backend:
   var appointments = new Cal.Collections.Appointments,
        app = new Cal.Views.Application({collection: appointments});

    appointments.fetch();
I rather like that. By using simple Javascript closures, I achieve significant improvement in readability inside the three Backbone namespaces. As a bonus, I have very limited external API required—just enough to build and populate the application collection and to ensure that the various views have access to that collection.

My jasmine tests, however, would like to have a word:
This turns out to be a reference to the collection instance rather than to a class that I have been refactoring. It seems that the "application" View is not the only one that needs access to this collection. The appointment-add View needs to be able to use this collection to create new appointments:
          var AppointmentAdd = Backbone.View.extend({
        // ...
        create: function() {
          Appointments.create({
            title: this.el.find('input.title').val(),
            description: this.el.find('input.description').val(),
            startDate: this.el.find('.startDate').html()
          });
        }
      });
Ugh. I see no way around this, but to add an appointment_collection variable to the Cal.Views closure:
    Cal.Views = (function() {
      var appointment_collection;

      // ...
    });
I can then set that variable when the application View object is initialized, making it available for when the appointment-add View needs it:

    Cal.Views = (function() {
      var appointment_collection;

      var Application = Backbone.View.extend({
        initialize: function(options) {
          this.collection = appointment_collection = options.collection;

          this.initialize_appointment_views();
          this.initialize_day_views();
        },
        // ...
      });

      var AppointmentAdd = Backbone.View.extend({
        create: function() {
          appointment_collection.create({
            title: this.el.find('input.title').val(),
            description: this.el.find('input.description').val(),
            startDate: this.el.find('.startDate').html()
          });
        }
      });

      // ...
      return { Application: Application };
    })();
That is all kinds of ugly, but it has me in the green again for all of my tests:
I suppose this is why many example Backbone applications have the object instances in the same namespace as the classes. I am not overly fond of the idea, but I can seen how it happens. I will continue exploring this tomorrow. I will most likely take the advice of my co-author on Recipes with Backbone by exploring using call() to pass around at least some of this information.

Day #139

Monday, September 19, 2011

Beginning Namespacing in Backbone.js

‹prev | My Chain | next›

Up tonight, I hope to namespace my Backbone.js app. I have been putting everything in the global namespace:
  $(function() {
    window.Appointment = Backbone.Model.extend({...});

    window.AppointmentList = Backbone.Collection.extend({...});
    window.Appointments = new AppointmentList;

    window.AppointmentView = Backbone.View.extend({...});
    window.AppointmentEditView = Backbone.View.extend({...});
    window.AppointmentAddView = Backbone.View.extend({...});
    window.DayView = Backbone.View.extend({...});
    window.AppView = Backbone.View.extend({...});

    new AppView();
  }); 
Maybe something like this would work better:
    Cal.Models.Appointment = Backbone.Model.extend({...});

    Cal.Collections.AppointmentList = Backbone.Collection.extend({...});
    Cal.Appointments = new AppointmentList();

    Cal.Views.AppointmentView = Backbone.View.extend({...});
    Cal.Views.AppointmentEditView = Backbone.View.extend({...});
    Cal.Views.AppointmentAddView = Backbone.View.extend({...});
    Cal.Views.DayView = Backbone.View.extend({...});
    Cal.Views.AppView = Backbone.View.extend({...});

    Cal.App = new AppView();  
I kinda like the module-like namespacing that this yields. It is not real class namespacing though. Rather it is just building up object literals that could be expressed as:
    var Cal = {
      "Models": {...},
      "Collections": {...},
      "Views": {...}
    }
The problem with fake namespacing is that I then need to use the long form everywhere:
    Cal.Collections.AppointmentList = Backbone.Collection.extend({
      model: Cal.Models.Appointment,
      url: '/appointments',
      parse: function(response) {
        return _(response.rows).map(function(row) { return row.doc ;});
      }
    });
Another issue that I have is putting instances of these classes into the "Cal" namespace. If this were Ruby, it would not make any sense to put the Appointments instance into Cal::Appointments. Cal::Appointments would refer to the class name.

I will keep that in my back pocket. Maybe creating yet another object would be the way to go? Something like the following (Underscore.js's extend()):
    var Cal = function() {
      this.initialize();
    }
    _.extend(Cal.prototype, {
      initialize: function () {
        this.appointments = new this.collections.AppointmentList;
      }
      models: {
        Appointment = Backbone.Model.extend({...})
      },
      collections: {
        AppointmentList = Backbone.Collection.extend({
            model: this.models.Appointment,
            url: '/appointments',
            parse: function(response) {...}
        });
      },
      views: {...}
    };
That seems awfully Javascripty and more than a little obscure. At this point, I still don't think I have a good solution, and I am starting to flail so I will call it a night. Despite being somewhat tough to read, I think the object approach may yield some prettier code in the end. Then again, that is probably just fatigue talking -- all the more reason to call it a night here and pick back up tomorrow.

Day #138

Sunday, September 18, 2011

DRYing up Backbone Code

‹prev | My Chain | next›

I have my Backbone.js app in kinda-OK shape after the past few days of cleanup. This is mostly thanks to some pretty decent test coverage courtesy of jasmine:
I would like to take some time to clean up some minor code annoyances. I start with the reset and add functions bound to the calendar appointments collection:
    Appointments.bind('add', function(appointment) {
      var view = new AppointmentView({model: appointment});
      view.render();
    });

    Appointments.bind('reset', function(appointments) {
      appointments.each(function(appointment) {
        var view = new AppointmentView({model: appointment});
        view.render();
      });
    });
If nothing else, I can DRY that up pulling the appointment View instantiation and rendering into a separate function. While I am at it, I can pull the iteration through the appointment list into a separate function as well. This keeps the bound functions at the same level of abstraction. Or, as I like to think of it, reading nicer:
    Appointments.bind('add', render_appointment);
    Appointments.bind('reset', render_appointment_list);

    function render_appointment(appointment) {
      var view = new AppointmentView({model: appointment});
      view.render();
    }

    function render_appointment_list(list) {
      list.each(render_appointment);
    }
Hrm... having two similarly named functions hanging about makes me want to put them into a single class. But what kind of class? A vanilla Javascript class? A Backbone view? Stumped as to the best approach I give a Backbone view a shot:
    window.AppView = Backbone.View.extend({
      initialize: function(options) {
        Appointments.bind('add', _.bind(this.render_appointment, this));
        Appointments.bind('reset', _.bind(this.render_appointment_list, this));

        Appointments.fetch();

        $('#calendar td').each(function() {
          new DayView({el: this});
        });
      },

      render_appointment: function(appointment) {
        var view = new AppointmentView({model: appointment});
        view.render();
      },

      render_appointment_list: function(list) {
        list.each(this.render_appointment);
      }
    });

    new AppView();
I really do not know if I like that. I have named it "AppView", which is the name of a class in the sample TODO app. To me, this seems like an invitation to pull in a bunch of unrelated crap. Case in point, I have dragged in the assignment of the day Views as well as fetching the appointments collections. Really, I just grabbed anything not already encapsulated in a class because there was so little of it and because this a whole "app" view.

Also troublesome is that this is not a Backbone view—it is not rendering anything directly. On the other hand, it is responsible for rendering a "collection view". It is the parent to all appointment views. As such it is responsible for binding render functions to collections events (reset and add).

Then again, needing to use the underscore.js bind() function feels oogy.

Bah! Maybe I am simply over thinking this. I am DRYing evented Javascript code here. Perhaps my original two functions was the proper way of doing this.

Unsure of which approach I like best, I call it a night here. Either way my tests are still passing. First thing tomorrow I will pick whichever approach I like best and then move on to other concerns.


Day #137