gmac / backbone.epoxy

Declarative data binding and computed models for Backbone
http://epoxyjs.org
MIT License
614 stars 89 forks source link

Issue with inheritance. #1

Closed foxyblocks closed 11 years ago

foxyblocks commented 11 years ago

Is there a way to use Epoxy.View without extending it directly? i.e:

var MyView = Backbone.Epoxy.View.extend({});

Many people when using backbone have their own base view class:

var MyView = App.BaseView.extend({})

How can multiple inheritance be achieved?

gmac commented 11 years ago

You could certainly extend your base view from Epoxy.View to add Epoxy into your inheritance chain, although I wouldn't suggest it. That would make every view in your app binding, which probably isn't appropriate. Sounds like you'd be best off just creating a new BindingBaseView, which would be a new extension of Epoxy.View that mixes in your base view's prototype along with additional (optional) implementation, as in:

App.BindingBaseView = Backbone.Epoxy.View.extend( _.extend({}, App.BaseView.prototype, {
    // additional optional implementation here...
}) );

That should work unless your view overrides the native Backbone.View constructor, at which time you'll need to define a new constructor implementation that calls up to the Epoxy view constructor.

foxyblocks commented 11 years ago

Is it not possible to do it the opposite direction?

App.BindingBaseView = App.BaseView.extend(Backbone.Epoxy.View.prototype,{ // additional optional implementation here... });

Thus treating epoxy as the plugin of the relationship?

It just seems that there are so many backbone.x libraries that want you to inherit from their View or Model prototypes.

Maybe I'm wrong and this isn't a real issue but I tend to want my libraries to be as pluggable as possible.

gmac commented 11 years ago

I definitely hear you; unfortunately the Backbone inheritance model is a bit tricky in this regard. I just amended my previous response, where I'd actually misquoted the usage of Backbone's class extension. The most deceptive point here is that the Backbone.[Class].extend method is not just another implementation of Underscore's extend method. While Underscore extension is just simple property mixins between objects, the Backbone class extension is actually setting up constructor chains used to control how objects are built. If a library component defines a custom constructor operation (many do, including Epoxy), then its class must be extended to preserve the constructor's role. This also preserves class identity, as determined using instanceof.

For the kind of extension you're referring it, that really only works for mixin libraries that simply offer a suite of utilities to graft onto your object prototypes... for example: Backbone.Events is a mixin library, versus a constructed component such a Backbone.View which must be extended.

jpurcell001 commented 11 years ago

We use Marionette which has its own view hierarchy, so we can't extend Epoxy.View directly. Instead, we're just mixing in Epoxy by adding the following to the initialize method (or the constructor) of our view base classes:

_.extend(this, _.pick(Epoxy.View.prototype, "applyBindings", "removeBindings", "bindings"), {
  _bind: []
});
this.listenTo(this, "render", this.applyBindings);
this.listenTo(this, "before:close", this.removeBindings);

Obviously this isn't future-proofed as it won't automatically pick up new Epoxy.View methods, but it does work.

gmac commented 11 years ago

Formal class mixins have now been added into the edge branch. The next release of Epoxy provides calls to Backbone.Epoxy.View.mixin() and Backbone.Epoxy.Model.mixin(); these methods will generate an Epoxy class abstract that may be mixed into your class prototypes... from there you'll just need to manually perform some simple setup calls while initializing your class instances.

ghost commented 11 years ago

Can you elaborate on "you'll just need to manually perform some simple setup calls while initializing your class instances"? I'm also trying to mixin the Epoxy.View to my BaseBindingView which extends from Marionette. I got my ideas from here: http://www.salsify.com/blog/data-binding-in-backbone-with-epoxy and from this thread.

BaseBindingItemView = Marionette.ItemView.extend({
        constructor: function(){
            Marionette.ItemView.apply(this, arguments);
            this.epoxify();
        },
        epoxify: function () {
            Epoxy.View.mixin(this);
            this.listenTo(this, "ui:bind", this.applyBindings);
            this.listenTo(this, "before:close", this.removeBindings);
        },
        bindUIElements: function(){
            this.trigger("ui:bind");
            Marionette.View.prototype.bindUIElements.apply(this, arguments);
        }
});

I'm struggling with my child views which extend BaseBindingItemView.

var ChildView = BaseBindingItemView.extend({
    template: //a handlebars template function
    bindings: {
        ".selector": "text:attribute"
    }
});

The problem is that bindings in ChildView is added to the prototype, and then Epoxy.View.mixin(this) adds the default bindings: "data-bind" to the instance. Then applyBindings has nothing to do.

The only way I can think to workaround this is to add the bindings in the template, which is something I'd rather not have to.

Any suggestions?

dspangen commented 11 years ago

I'd recommend just re-implementing mixin() on Epoxy. The function is pretty simple.

Still, seems like a bug.

Nice to see you're following our post!