ChatGPT解决这个技术问题 Extra ChatGPT

Backbone View: Inherit and extend events from parent

Backbone's documentation states:

The events property may also be defined as a function that returns an events hash, to make it easier to programmatically define your events, as well as inherit them from parent views.

How do you inherit a parent's view events and extend them?

Parent View

var ParentView = Backbone.View.extend({
   events: {
      'click': 'onclick'
   }
});

Child View

var ChildView = ParentView.extend({
   events: function(){
      ????
   }
});

B
Bobby

One way is:

var ChildView = ParentView.extend({
   events: function(){
      return _.extend({},ParentView.prototype.events,{
          'click' : 'onclickChild'
      });
   }
});

Another would be:

var ParentView = Backbone.View.extend({
   originalEvents: {
      'click': 'onclick'
   },
   //Override this event hash in
   //a child view
   additionalEvents: {
   },
   events : function() {
      return _.extend({},this.originalEvents,this.additionalEvents);
   }
});

var ChildView = ParentView.extend({
   additionalEvents: {
      'click' : ' onclickChild'
   }
});

To check whether Events is function or object

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});

That's great... Maybe you could update this to show how you would inherit from a ChildView (check if the prototype events is a function or object)... Or maybe I'm overthinking this whole inheritance stuff.
@brent Sure, just added third case
If i'm not mistaken you should be able to use parentEvents = _.result(ParentView.prototype, 'events'); instead of 'manually' checking if events is a function.
@Koen. +1 for mentioning the underscore utility function _.result, which I hadn't noticed before. For anyone who's interested, here's a jsfiddle with a bunch of variations on this theme: jsfiddle
Just to throw my two cents in here, i believe the second option is the best solution. I say this because of the sheer fact that it is the only method that is truly encapsulated. the only context used is this versus having to call the parent class by instance name. thank you very much for this.
y
yves amsellem

The soldier.moth answer is a good one. Simplifying it further you could just do the following

var ChildView = ParentView.extend({
   initialize: function(){
       _.extend(this.events, ParentView.prototype.events);
   }
});

Then just define your events in either class in the typical way.


Good call, though you probably want to swap this.events & ParentView.prototype.events otherwise if both define handlers on the same event the Parent's handler will override the Child's.
@Soldier.moth, okay I've edited it to be as {},ParentView.prototype.events,this.events
Obviously this works, but as I know, delegateEvents is called in the constructor to bind events. So when you extend it in the initialize, how come that it isn't too late?
It's nit-picky, but my issue with this solution is: If you have a diverse and plentiful hierarchy of views, you will inevitably find yourself writing initialize in a few cases (then having to deal with managing the hierarchy of that function too) simply to merge the event objects. Seems cleaner to me to keep the events merging within itself. That being said, I wouldn't have thought of this approach, and it's always nice to be forced to look at things in a different way :)
this answer is not longer valid because delegateEvents is called before initialize (this is true for version 1.2.3) - it's easy to this in the annotated source.
j
jermel

You could also use the defaults method to avoid creating the empty object {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});

This causes the parent handlers to be bound after the child handlers. In most cases not a problem, but if a child event should cancel (not override) a parent event it is not possible.
S
Shuhei Kagawa

If you use CoffeeScript and set a function to events, you can use super.

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'

This only works if the parent events variable is a function rather than an object.
S
Shaw W

Wouldn't it be easier to create specialized base constructor from Backbone.View that handles the inheritance of events up the hierarchy.

BaseView = Backbone.View.extend {
    # your prototype defaults
},
{
    # redefine the 'extend' function as decorated function of Backbone.View
    extend: (protoProps, staticProps) ->
      parent = this

      # we have access to the parent constructor as 'this' so we don't need
      # to mess around with the instance context when dealing with solutions
      # where the constructor has already been created - we won't need to
      # make calls with the likes of the following:   
      #    this.constructor.__super__.events
      inheritedEvents = _.extend {}, 
                        (parent.prototype.events ?= {}),
                        (protoProps.events ?= {})

      protoProps.events = inheritedEvents
      view = Backbone.View.extend.apply parent, arguments

      return view
}

This allows us to reduce(merge) the events hash down the hierarchy whenever we create a new 'subclass'(child constructor) by using the redefined extend function.

# AppView is a child constructor created by the redefined extend function
# found in BaseView.extend.
AppView = BaseView.extend {
    events: {
        'click #app-main': 'clickAppMain'
    }
}

# SectionView, in turn inherits from AppView, and will have a reduced/merged
# events hash. AppView.prototype.events = {'click #app-main': ...., 'click #section-main': ... }
SectionView = AppView.extend {
    events: {
        'click #section-main': 'clickSectionMain'
    }
}

# instantiated views still keep the prototype chain, nothing has changed
# sectionView instanceof SectionView => true 
# sectionView instanceof AppView => true
# sectionView instanceof BaseView => true
# sectionView instanceof Backbone.View => also true, redefining 'extend' does not break the prototype chain. 
sectionView = new SectionView { 
    el: ....
    model: ....
} 

By creating a specialized view: BaseView that redefines the extend function, we can have subviews(like AppView, SectionView) that want to inherit their parent view's declared events simply do so by extending from BaseView or one of its derivatives.

We avoid the need to programmatically define our event functions in our subviews, which in most cases need to refer to the parent constructor explicitly.


K
Koen.

Short version of @soldier.moth's last suggestion:

var ChildView = ParentView.extend({
  events: function(){
    return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
      'click' : 'onclickChild'
    });
  }
});

y
yolk

This would also work:

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(_super::, 'events') || {},
      'bar' : 'doOtherThing')

Using straight super wasn't working for me, either was manually specifying the ParentView or inherited class.

Access to the _super var which is available within any coffeescript Class … extends …


v
vovan

// ModalView.js var ModalView = Backbone.View.extend({ events: { 'click .close-button': 'closeButtonClicked' }, closeButtonClicked: function() { /* Whatever */ } // Other stuff that the modal does }); ModalView.extend = function(child) { var view = Backbone.View.extend.apply(this, arguments); view.prototype.events = _.extend({}, this.prototype.events, child.events); return view; }; // MessageModalView.js var MessageModalView = ModalView.extend({ events: { 'click .share': 'shareButtonClicked' }, shareButtonClicked: function() { /* Whatever */ } }); // ChatModalView.js var ChatModalView = ModalView.extend({ events: { 'click .send-button': 'sendButtonClicked' }, sendButtonClicked: function() { /* Whatever */ } });

http://danhough.com/blog/backbone-view-inheritance/


K
Kafoso

For Backbone version 1.2.3, __super__ works fine, and may even be chained. E.g.:

// A_View.js
var a_view = B_View.extend({
    // ...
    events: function(){
        return _.extend({}, a_view.__super__.events.call(this), { // Function - call it
            "click .a_foo": "a_bar",
        });
    }
    // ...
});

// B_View.js
var b_view = C_View.extend({
    // ...
    events: function(){
        return _.extend({}, b_view.__super__.events, { // Object refence
            "click .b_foo": "b_bar",
        });
    }
    // ...
});

// C_View.js
var c_view = Backbone.View.extend({
    // ...
    events: {
        "click .c_foo": "c_bar",
    }
    // ...
});

... which - in A_View.js - will result in:

events: {
    "click .a_foo": "a_bar",
    "click .b_foo": "b_bar",
    "click .c_foo": "c_bar",
}

f
firebird631

I've found a more interesting solutions in this article

It use of the Backbone’s super and ECMAScript’s hasOwnProperty. The second of its progressives examples works like a charm. Here's a bit a code :

var ModalView = Backbone.View.extend({
    constructor: function() {
        var prototype = this.constructor.prototype;

        this.events = {};
        this.defaultOptions = {};
        this.className = "";

        while (prototype) {
            if (prototype.hasOwnProperty("events")) {
                _.defaults(this.events, prototype.events);
            }
            if (prototype.hasOwnProperty("defaultOptions")) {
                _.defaults(this.defaultOptions, prototype.defaultOptions);
            }
            if (prototype.hasOwnProperty("className")) {
                this.className += " " + prototype.className;
            }
            prototype = prototype.constructor.__super__;
        }

        Backbone.View.apply(this, arguments);
    },
    ...
});

You can also do that for ui and attributes.

This example does not take care of the properties set by a function, but the author of the article offers a solution in that case.


G
Greg Ross

To do this entirely in the parent class and support a function-based events hash in the child class so that children can be agnostic of inheritance (the child will have to call MyView.prototype.initialize if it overrides initialize):

var MyView = Backbone.View.extend({
  events: { /* ... */ },

  initialize: function(settings)
  {
    var origChildEvents = this.events;
    this.events = function() {
      var childEvents = origChildEvents;
      if(_.isFunction(childEvents))
         childEvents = childEvents.call(this);
      return _.extend({}, MyView.prototype.events, childEvents);
    };
  }
});

m
mikwat

This CoffeeScript solution worked for me (and takes into account @soldier.moth's suggestion):

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(ParentView.prototype, 'events') || {},
      'bar' : 'doOtherThing')

g
gabriele.genta

If you are sure that the ParentView has the events defined as object and you don't need to define events dynamically in ChildView it is possible to simplify soldier.moth's answer further by getting rid of the function and using _.extend directly:

var ParentView = Backbone.View.extend({
    events: {
        'click': 'onclick'
    }
});

var ChildView = ParentView.extend({
    events: _.extend({}, ParentView.prototype.events, {
        'click' : 'onclickChild'
    })
});

j
jtrumbull

A pattern for this that I am fond of is modifying the constructor and adding some additional functionality:

// App View
var AppView = Backbone.View.extend({

    constructor: function(){
        this.events = _.result(this, 'events', {});
        Backbone.View.apply(this, arguments);
    },

    _superEvents: function(events){
        var sooper = _.result(this.constructor.__super__, 'events', {});
        return _.extend({}, sooper, events);
    }

});

// Parent View
var ParentView = AppView.extend({

    events: {
        'click': 'onclick'
    }

});

// Child View
var ChildView = ParentView.extend({

    events: function(){
        return this._superEvents({
            'click' : 'onclickChild'
        });
    }

});

I prefer this method because you do not have to identify the parent -one less variable to change. I use the same logic for attributes and defaults.


m
machineghost

Wow, lots of answers here but I thought I'd offer one more. If you use the BackSupport library, it offers extend2. If you use extend2 it automatically takes care of merging events (as well as defaults and similar properties) for you.

Here's a quick example:

var Parent = BackSupport.View.extend({
    events: {
        change: '_handleChange'
    }
});
var Child = parent.extend2({
    events: {
        click: '_handleClick'
    }
});
Child.prototype.events.change // exists
Child.prototype.events.click // exists

https://github.com/machineghost/BackSupport


I like the concept, but, on principle alone, I'd pass on any library that thinks "extend2" is a proper function name.
I would welcome any suggestions you can offer for what to name a function that's essentially "Backbone.extend, but with improved functionality". Extend 2.0 (extend2) was the best I could come up with, and I don't think it's all that terrible: anyone used to Backbone is already used to using extend, so this way they don't need to memorize a new command.
Opened an issue on the Github repo about it. :)