Widok szkieletu: dziedziczenie i rozszerzanie zdarzeń od rodzica

115

Dokumentacja Backbone stwierdza:

Właściwość events można również zdefiniować jako funkcję zwracającą skrót zdarzenia, aby ułatwić programowe definiowanie zdarzeń, a także dziedziczenie ich z widoków nadrzędnych.

Jak możesz dziedziczyć zdarzenia widoku rodzica i rozszerzać je?

Widok rodzica

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

Widok dziecka

var ChildView = ParentView.extend({
   events: function(){
      ????
   }
});
brent
źródło

Odpowiedzi:

189

Jednym ze sposobów jest:

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

Innym byłoby:

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

Aby sprawdzić, czy zdarzenia są funkcją czy przedmiotem

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});
żołnierz. mol
źródło
To świetnie ... Może mógłbyś zaktualizować to, aby pokazać, jak dziedziczysz z ChildView (sprawdź, czy zdarzenia prototypu są funkcją lub obiektem) ... A może nadmiernie zastanawiam się nad tym całym dziedziczeniem.
brent
@brent Sure, właśnie dodałem trzecią sprawę
soldier.moth
14
Jeśli się nie mylę, powinieneś móc użyć parentEvents = _.result(ParentView.prototype, 'events');zamiast „ręcznego” sprawdzenia, czy eventsjest to funkcja.
Koen.
3
@Koen. +1 za wzmiankę o funkcji podkreślenia _.result, której wcześniej nie zauważyłem. Dla wszystkich zainteresowanych, oto jsfiddle z kilkoma wariacjami na ten temat: jsfiddle
EleventyOne,
1
Żeby wrzucić tutaj moje dwa centy, uważam, że druga opcja jest najlepszym rozwiązaniem. Mówię to ze względu na fakt, że jest to jedyna metoda, która jest naprawdę zamknięta. jedyny używany kontekst to thisbrak wywoływania klasy nadrzędnej według nazwy instancji. Bardzo ci za to dziękuję.
jessie james jackson taylor
79

Żołnierz. Żółta odpowiedź jest dobra. Upraszczając to dalej, możesz po prostu wykonać następujące czynności

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

Następnie po prostu zdefiniuj zdarzenia w jednej z klas w typowy sposób.

34m0
źródło
8
Dobre wywołanie, chociaż prawdopodobnie chcesz zamienić this.events& w ParentView.prototype.eventsprzeciwnym razie, jeśli oba zdefiniują funkcje obsługi w tym samym zdarzeniu, funkcja obsługi Parent przesłoni funkcję elementu Child.
soldier.moth
1
@ Soldier.moth, ok, zredagowałem to na{},ParentView.prototype.events,this.events
AJP
1
Oczywiście to działa, ale jak wiem, delegateEventsjest wywoływane w konstruktorze do wiązania zdarzeń. Skoro więc ją przedłużasz initialize, dlaczego nie jest za późno?
SelimOber
2
Jest to drobiazgowe, ale mój problem z tym rozwiązaniem jest następujący: jeśli masz zróżnicowaną i bogatą hierarchię poglądów, nieuchronnie napotkasz initializew kilku przypadkach (wtedy też będziesz musiał zajmować się zarządzaniem hierarchią tej funkcji) po prostu scal obiekty zdarzeń. Wydaje mi się czystsze, aby zachować eventspołączenie w sobie. To powiedziawszy, nie pomyślałbym o takim podejściu i zawsze miło jest być zmuszonym do spojrzenia na rzeczy w inny sposób :)
EleventyOne
1
ta odpowiedź nie jest już poprawna, ponieważ delegateEvents jest wywoływana przed zainicjowaniem (dotyczy to wersji 1.2.3) - jest to łatwe w źródle z adnotacjami.
Roey
12

Możesz również użyć tej defaultsmetody, aby uniknąć tworzenia pustego obiektu {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});
jermel
źródło
2
Powoduje to, że programy obsługi nadrzędne są powiązane po funkcjach obsługi podrzędnych. W większości przypadków nie stanowi to problemu, ale jeśli zdarzenie podrzędne powinno anulować (nie przesłonić) zdarzenie nadrzędne, nie jest to możliwe.
Koen.
10

Jeśli używasz CoffeeScript i ustawisz funkcję na events, możesz użyć super.

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

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'
Shuhei Kagawa
źródło
Działa to tylko wtedy, gdy zmienna zdarzenia nadrzędnego jest funkcją, a nie obiektem.
Michael
6

Czy nie byłoby łatwiej stworzyć wyspecjalizowany konstruktor bazowy z Backbone.View, który obsługuje dziedziczenie zdarzeń w górę hierarchii.

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
}

Pozwala nam to zredukować (scalić) skróty zdarzeń w dół hierarchii za każdym razem, gdy tworzymy nową „podklasę” (konstruktor potomny) za pomocą przedefiniowanej funkcji rozszerzającej.

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

Tworząc wyspecjalizowany widok: BaseView, który redefiniuje funkcję rozszerzania, możemy mieć podwidoki (takie jak AppView, SectionView), które chcą dziedziczyć zadeklarowane zdarzenia ich widoku nadrzędnego, po prostu robią to, rozszerzając z BaseView lub jednej z jego pochodnych.

Unikamy potrzeby programistycznego definiowania naszych funkcji zdarzeń w naszych podglądzie, które w większości przypadków muszą jawnie odwoływać się do konstruktora nadrzędnego.

Shaw W.
źródło
2

Krótka wersja ostatniej sugestii @ soldier.moth:

var ChildView = ParentView.extend({
  events: function(){
    return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
      'click' : 'onclickChild'
    });
  }
});
Koen.
źródło
2

To również zadziała:

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

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

Używanie straight supernie działało dla mnie, albo ręcznie określało ParentViewalbo dziedziczoną klasę.

Dostęp do _supervar, który jest dostępny w każdym skrypcieClass … extends …

żółtko
źródło
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/

vovan
źródło
1

W przypadku wersji Backbone 1.2.3 __super__działa dobrze, a nawet może być połączona łańcuchowo. Na przykład:

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

... co - w A_View.js- spowoduje:

events: {
    "click .a_foo": "a_bar",
    "click .b_foo": "b_bar",
    "click .c_foo": "c_bar",
}
Kafoso
źródło
1

W tym artykule znalazłem ciekawsze rozwiązania

Wykorzystuje super właściwości Backbone i hasOwnProperty ECMAScript. Drugi z jego postępowych przykładów działa jak urok. Oto fragment kodu:

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

Możesz to również zrobić dla interfejsu użytkownika i atrybutów .

W tym przykładzie nie uwzględniono właściwości ustawianych przez funkcję, ale autor artykułu oferuje rozwiązanie w takim przypadku.

firebird631
źródło
1

Aby zrobić to całkowicie w klasie nadrzędnej i obsługiwać skrót zdarzeń oparty na funkcjach w klasie podrzędnej, aby dzieci mogły być agnostykami dziedziczenia (dziecko będzie musiało wywołać, MyView.prototype.initializejeśli nadpisuje 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);
    };
  }
});
Kevin Borders
źródło
0

To rozwiązanie CoffeeScript zadziałało dla mnie (i uwzględnia sugestię @ soldier.moth):

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

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(ParentView.prototype, 'events') || {},
      'bar' : 'doOtherThing')
mikwat
źródło
0

Jeśli masz pewność, że ParentViewzdarzenia są zdefiniowane jako obiekt i nie musisz definiować zdarzeń dynamicznie ChildView, można uprościć odpowiedź żołnierza. Motha dalej, pozbywając się funkcji i używając _.extendbezpośrednio:

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

var ChildView = ParentView.extend({
    events: _.extend({}, ParentView.prototype.events, {
        'click' : 'onclickChild'
    })
});
gabriele.genta
źródło
0

Wzorzec, który mi się podoba, to modyfikacja konstruktora i dodanie dodatkowej funkcjonalności:

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

});

Wolę tę metodę, ponieważ nie musisz identyfikować zmiennej o jeden rodzic, aby ją zmienić. Używam tej samej logiki dla attributesi defaults.

jtrumbull
źródło
0

Wow, wiele odpowiedzi tutaj, ale pomyślałem, że zaoferuję jeszcze jedną. Jeśli korzystasz z biblioteki BackSupport, oferuje extend2. Jeśli extend2go używasz , automatycznie dba o scalanie events(a także defaultsi podobne właściwości) za Ciebie.

Oto krótki przykład:

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

machineghost
źródło
3
Podoba mi się ta koncepcja, ale tylko w zasadzie przekazałbym dalej każdą bibliotekę, która uważa, że ​​„rozszerzenie2” jest właściwą nazwą funkcji.
Janów
Z zadowoleniem przyjmuję wszelkie sugestie, które możesz zaproponować, co do nazwania funkcji, która zasadniczo jest „Backbone.extend, ale ma ulepszoną funkcjonalność”. Extend 2.0 ( extend2) był najlepszym, jaki mogłem wymyślić, i nie sądzę, że to wszystko takie straszne: każdy przyzwyczajony do Backbone jest już przyzwyczajony do używania extend, więc w ten sposób nie musi zapamiętywać nowego polecenia.
machineghost
Otworzyłem problem w repozytorium Github na ten temat. :)
Janów