Thursday, November 10, 2011

Cannot Namespace Backbone-Relational

‹prev | My Chain | next›

Tonight, I conclude my investigation of backbone-relational by trying once more to avoid the need for global variables.

I am currently structuring my Backbone.js applications so that everything is encapsulated within a class constructor:
window.Cal = function(root_el) {
  var application = this;

  var Models = (function() { /* ... */ })();

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

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

  var Routes = Backbone.Router.extend({ /* ... */ });

  var Helpers = (function() { /* ... */ })();

  // Initialize the app
  // ....

  return {
    Models: Models,
    Collections: Collections,
    Views: Views,
    Helpers: Helpers,
    appointments: appointments
  };
};
Inside the class, everything can refer to each other without using the global namespace. Externally, the only way to get direct access to things that live inside my application class is via an instance of that class.

Where this causes problems is when defining HasMany backbone-relational relations:
    var Appointment = Backbone.RelationalModel.extend({
      // ...
      relations: [
        {
          type: Backbone.HasMany,
          key: 'invitees',
          relatedModel: 'Invitee',
          collectionType: Collections.Invitees
        }
      ],
      // ...
    });
Here I try to specify the has-many collection class as Collections.Invitees (an appointment has many people invited). The problem is that, at compile time, Collections.Invitees is not defined, giving me an uncaught type error in Chrome's Javascript console:
Uncaught TypeError: Cannot read property 'Invitees' of undefined
Collections.Invitees is not defined because Collections is not defined—I am defining an Appointment model here, but my collections are not defined until after my models.

No problem, you might think, because backbone-relational exposes the ability to defer evaluation of relations until run time by specifying the collectionType as a string:
    var Appointment = Backbone.RelationalModel.extend({
      // ...
      relations: [
        {
          type: Backbone.HasMany,
          key: 'invitees',
          relatedModel: 'Invitee',
          collectionType: 'Collections.Invitees'
        }
      ],
      // ...
    });
Except I get the same darn error:
Uncaught TypeError: Cannot read property 'Invitees' of undefined
Upon closer inspection, however, the error message might be the same, but the location is different. Earlier, the message had come directly on the collectionType line. Now, it is coming from deep within the bowels of backbone-relational.

Specifically, it comes from getObjectByName():
  /**
   * Find a type on the global object by name. Splits name on dots.
   * @param {string} name
   */
  getObjectByName: function( name ) {
   var type = _.reduce( name.split( '.' ), function( memo, val ) {
    return memo[ val ];
   }, exports);
   return type !== exports ? type: null;
  }
This method splits collectionType string and then resolve resulting pieces as objects within the global namespace. It is the global namespace by virtue of the context set on the reduce. Specifically, exports is initialized to the global window variable.

In other words, for 'Collections.Invitees' to resolve to something, there needs to be a global variable named Collections that represents an object with an Invitees property. Something along the lines of:
window.Collections = {
  Invitees = Backbone.RelationalModel.extend({...});
}
But the whole point of namespacing things like I do is so that I do not have to pollute the global namespace.

Sticking with the static definition and moving the Collections definition to before the Models definition does solve the has many problem, but then introduces a new error in that the collection tries to access a model that has not yet been defined:
     var Appointments = Backbone.Collection.extend({
      model: Models.Appointment,
Since Models would now be defined after Collections, the model definition line would fail a compile time.

So it seems that either I cannot use backbone-relational or my namespacing scheme. Since I already have a much lighter weight relational solution that does not rely on global variables, I think I will stick with my namespacing scheme.



Day #201

No comments:

Post a Comment