Skip to content

An argument for _configure #1462

Closed
Closed
@ianstormtaylor

Description

@ianstormtaylor

I don't think I was communicating this well, so I tried to write up my thoughts on it and hopefully it is convincing. Would love to know what you guys think.


For the most part, you can augment Backbone however you want easily and everything is peachy. But changing the initial Backbone configuration logic is not one of those cases.

It's common practice to make "base" classes when using Backbone so that your customatizations apply to all of your app's classes. Instead of extending from Backbone.Model, you always extend from BaseModel.

That works amazingly for customization on a per-class level. What I mean by that is that if you have an AuthorModel, BookModel, and PageModel, they will all need different initialization logic. All you need to do is extend from BaseModel and then write a custom initialize function in each of those models.

It also works well for customization on a class-wide level for anything post-configuration. By that I mean that despite AuthorModel and BookModel being significantly different, they might both make use of a cleanAttributes method you've defined in BaseModel.

Where the whole thing breaks down is when you want to do customize the configuration on class-wide level. There's nothing to hook into to augment the configuration logic of anything that extends from BaseModel before initialize is called. Say I want to change the way BaseModel works a bit by copying the options that get passed in onto this.options to match how Views treat options. Or maybe I want to add a way for BaseModel to store extra UI state in a this.state property instead of inside attributes. Or maybe I want to have my models inherit defaults all the way up the chain by _.defaulting them.

Another way to think about it is that there are four main places you might want to hook into to add custom logic in to a Backbone class. The first two are handled fine as is, but there isn't a nice way to handle the third:

Before anything is configured: Here you could provide your own augmented constructor. (But this could be done by augmenting _configure instead, which I also think is preferable.)

After it is initialized: This is completely up to you, and most of what you're writing as a Backbone user fits in this category.

After things are configured, but before initialize: Backbone doesn't give you a nice way to do this! There are only two annoying ways and one incomplete way I could think to do it:

Augment the constructor. (incomplete)

constructor : function (attributes, options) {
    this._configureOptions();
    Backbone.Model.prototype.constructor.apply(this, arguments);
}

That does work, but only if your added logic doesn't rely on anything else Backbone does in its constructor. For example, if you need to access this.attributes which have been parsed and attached directly to the instance, you're out of luck. Or if need to reference the cid? tough.

Re-call your logic in every single initialize method. (annoying)

var AuthorModel = BaseModel.extend({

    initialize : function (attributes, options) {
        this._configureOptions();
        // Real initialization logic.
    }
});

var BookModel = BaseModel.extend({

    initialize : function (attributes, options) {
        this._configureOptions();
        // Real initialization logic.
    }
});

var PageModel = BaseModel.extend({

    initialize : function (attributes, options) {
        this._configureOptions();
        // Real initialization logic.
    }
});

Yeah I could do that. But I shouldn't need to keep copying and pasting that code around all over my codebase when I know upfront that I want everything that extends BaseModel to be configured that way.

Give up initialize and create a new initialize. (annoying)

initialize : function (attributes, options) {
    this._configureOptions();
    this.start.apply(this, arguments);
}

start : function (attributes, options) {
    // Real initialization logic.
}

That's also not great. Everyone who reads my code needs to be informed that what they have always known as initialize is now start. Any time I'm talking/reading about Backbone I have to mentally convert initialize to start and back. And if you accidentally override initialize instead, you might not even realize it until things get weird because the instance wasn't configurated properly.


So how should it be done? My suggestion is to make all the Backbone classes have a _configure method that does the configuration that happens before initialize in the constructor. With a _configure method the problem is easily solved and you end up with this:

_configure : function (attributes, options) {
    Backbone.Model.prototype._configure.apply(this, arguments);
    // Add custom logic here.
}

You just augment _configure in your BaseModel and then you don't ever need to touch it again! All of your other extended Models will get the augmented configuration options. Class-wide configuration solved!

Look familiar? That's because Backbone.View already has a _configure method that makes this kind of augmentation incredibly easy. But the other Backbone classes should get _configure too.

And the _configure solution doesn't have any effects on the rest of Backbone source.

Bonus: it's great for mixins too!

The best way I've found to add functionality to an existing Backbone class without extending a completely new class is the mixin pattern. It's like the jQuery plugins of Backbone. Having a _configure would make it extremely easy for mixins to be grafted onto existing classes (regardless of whether it's a View, Model, Collection or Router!).

// Augmented `_configure` to call `_inherit`
var _configure = this.prototype._configure;
this.prototype._configure = function () {
  this._inherit(this.inherits || []);
  _configure.apply(this, arguments);
};

Since the classes all have the _configure method, you can attach your custom logic to each of them the same way in your mixin. And then when you package your mixin for others, they can add inheritance to any class they want with the same syntax:

inheritMixin.call(BaseView);
inheritMixin.call(DropdownModel);
inheritMixin.call(AppRouter);

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions