Vue dorsale: hériter et étendre les événements du parent


115

La documentation de Backbone indique:

La propriété events peut également être définie comme une fonction qui renvoie un hachage d'événements, pour faciliter la définition par programme de vos événements, ainsi que pour les hériter des vues parent.

Comment héritez-vous des événements d'affichage d'un parent et comment les étendez-vous?

Vue parent

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

Vue enfant

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

Réponses:


189

Une façon est:

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

Un autre serait:

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'
   }
});

Pour vérifier si les événements sont une fonction ou un objet

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

C'est génial ... Peut-être pourriez-vous mettre à jour ceci pour montrer comment vous hériteriez d'un ChildView (vérifiez si les événements prototypes sont une fonction ou un objet) ... Ou peut-être que je réfléchis trop à tout ce truc d'héritage.
brent

@brent Bien sûr, vient d'ajouter un troisième cas
soldier.moth

14
Si je ne me trompe pas, vous devriez pouvoir utiliser parentEvents = _.result(ParentView.prototype, 'events');au lieu de vérifier «manuellement» si eventsc'est une fonction.
Koen.

3
@Koen. +1 pour avoir mentionné la fonction utilitaire de soulignement _.result, que je n'avais pas remarquée auparavant. Pour tous ceux qui sont intéressés, voici un jsfiddle avec un tas de variations sur ce thème: jsfiddle
EleventyOne

1
Juste pour jeter mes deux cents ici, je pense que la deuxième option est la meilleure solution. Je dis cela à cause du simple fait que c'est la seule méthode qui soit vraiment encapsulée. le seul contexte utilisé est celui de thisdevoir appeler la classe parente par nom d'instance. Merci beaucoup pour cela.
jessie james jackson taylor

79

La réponse du soldat.moth est bonne. En simplifiant davantage, vous pouvez simplement faire ce qui suit

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

Ensuite, définissez simplement vos événements dans l'une ou l'autre classe de la manière habituelle.


8
Bon appel, bien que vous souhaitiez probablement permuter this.eventset ParentView.prototype.eventssinon, si les deux définissent des gestionnaires sur le même événement, le gestionnaire du parent remplacera celui de l'enfant.
soldier.moth

1
@ Soldier.moth, d'accord, je l'ai modifié pour être comme{},ParentView.prototype.events,this.events
AJP

1
Évidemment, cela fonctionne, mais comme je le sais, delegateEventsest appelé dans le constructeur pour lier des événements. Alors, quand vous le prolongez dans le initialize, pourquoi il n'est pas trop tard?
SelimOber

2
C'est un peu difficile, mais mon problème avec cette solution est le suivant: si vous avez une hiérarchie de points de vue diversifiée et abondante, vous vous retrouverez inévitablement à écrire initializedans quelques cas (puis à gérer la hiérarchie de cette fonction également) simplement pour fusionner les objets événement. Cela me semble plus propre de garder la eventsfusion en elle-même. Cela étant dit, je n'aurais pas pensé à cette approche, et c'est toujours agréable d'être obligé de regarder les choses d'une manière différente :)
EleventyOne

1
cette réponse n'est plus valide car delegateEvents est appelé avant l'initialisation (c'est vrai pour la version 1.2.3) - c'est facile à cela dans la source annotée.
Roey

12

Vous pouvez également utiliser la defaultsméthode pour éviter de créer l'objet vide {}.

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

2
Cela entraîne la liaison des gestionnaires parents après les gestionnaires enfants. Dans la plupart des cas, ce n'est pas un problème, mais si un événement enfant doit annuler (et non remplacer) un événement parent, ce n'est pas possible.
Koen.

10

Si vous utilisez CoffeeScript et définissez une fonction sur events, vous pouvez utiliser super.

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

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

Cela ne fonctionne que si la variable d'événements parent est une fonction plutôt qu'un objet.
Michael

6

Ne serait-il pas plus facile de créer un constructeur de base spécialisé à partir de Backbone.View qui gère l'héritage des événements dans la hiérarchie.

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
}

Cela nous permet de réduire (fusionner) le hachage des événements dans la hiérarchie chaque fois que nous créons une nouvelle 'sous-classe' (constructeur enfant) en utilisant la fonction d'extension redéfinie.

# 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: ....
} 

En créant une vue spécialisée: BaseView qui redéfinit la fonction d'extension, nous pouvons avoir des sous-vues (comme AppView, SectionView) qui souhaitent hériter des événements déclarés de leur vue parente, faites-le simplement en étendant à partir de BaseView ou de l'un de ses dérivés.

Nous évitons la nécessité de définir par programme nos fonctions d'événement dans nos sous-vues, qui dans la plupart des cas doivent faire référence explicitement au constructeur parent.


2

Version courte de la dernière suggestion de @ soldier.moth:

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

2

Cela fonctionnerait également:

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

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

Utiliser straight superne fonctionnait pas pour moi, c'était non plus spécifier manuellement leParentView classe ou héritée.

Accès au _supervar qui est disponible dans n'importe quel coffeescriptClass … extends …


2

// 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/


1

Pour Backbone version 1.2.3, __super__fonctionne très bien et peut même être enchaîné. Par exemple:

// 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",
    }
    // ...
});

... ce qui - en A_View.js- entraînera:

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

1

J'ai trouvé des solutions plus intéressantes dans cet article

Il utilise le super du Backbone et le hasOwnProperty d'ECMAScript. Le deuxième de ses exemples progressifs fonctionne comme un charme. Voici un peu un 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);
    },
    ...
});

Vous pouvez également le faire pour l' interface utilisateur et les attributs .

Cet exemple ne prend pas en charge les propriétés définies par une fonction, mais l'auteur de l'article propose une solution dans ce cas.


1

Pour faire cela entièrement dans la classe parent et prendre en charge un hachage d'événements basé sur une fonction dans la classe enfant afin que les enfants puissent être indépendants de l'héritage (l'enfant devra appeler MyView.prototype.initializes'il remplace 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);
    };
  }
});

0

Cette solution CoffeeScript a fonctionné pour moi (et prend en compte la suggestion de @ soldier.moth):

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

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

0

Si vous êtes sûr que les ParentViewévénements sont définis comme objet et que vous n'avez pas besoin de définir les événements dynamiquement, ChildViewil est possible de simplifier davantage la réponse de soldier.moth en supprimant la fonction et en utilisant _.extenddirectement:

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

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

0

Un modèle pour cela que j'aime beaucoup est la modification du constructeur et l'ajout de fonctionnalités supplémentaires:

// 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'
        });
    }

});

Je préfère cette méthode car vous n'avez pas à identifier la variable parent-one less pour changer. J'utilise la même logique pour attributeset defaults.


0

Wow, beaucoup de réponses ici mais je pensais en offrir une de plus. Si vous utilisez la bibliothèque BackSupport, elle propose extend2. Si vous l'utilisez, extend2il s'occupe automatiquement de la fusion events(ainsi quedefaults des propriétés similaires) pour vous.

Voici un exemple rapide:

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


3
J'aime le concept, mais, en principe seul, je transmettrais n'importe quelle bibliothèque qui pense que "extend2" est un nom de fonction propre.
Yaniv

Je serais heureux de recevoir toutes les suggestions que vous pouvez offrir pour nommer une fonction qui est essentiellement "Backbone.extend, mais avec des fonctionnalités améliorées". Extend 2.0 ( extend2) a été le meilleur que j'ai pu proposer, et je ne pense pas que ce soit si terrible: quiconque est habitué à Backbone est déjà habitué à utiliser extend, donc de cette façon, ils n'ont pas besoin de mémoriser une nouvelle commande.
machineghost

Ouverture d'un problème sur le repo Github à ce sujet. :)
Yaniv
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.