Backbone View: Mewarisi dan memperpanjang acara dari induk


115

Dokumentasi Backbone menyatakan:

Properti peristiwa juga dapat didefinisikan sebagai fungsi yang mengembalikan hash peristiwa, untuk membuatnya lebih mudah untuk menentukan peristiwa Anda secara terprogram, serta mewarisinya dari tampilan induk.

Bagaimana Anda mewarisi peristiwa tampilan orang tua dan memperluasnya?

Tampilan Orang Tua

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

Tampilan Anak

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

Jawaban:


189

Salah satu caranya adalah:

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

Yang lainnya adalah:

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

Untuk memeriksa apakah Peristiwa adalah fungsi atau objek

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

Bagus sekali ... Mungkin Anda bisa memperbarui ini untuk menunjukkan bagaimana Anda akan mewarisi dari ChildView (periksa apakah peristiwa prototipe adalah fungsi atau objek) ... Atau mungkin saya terlalu banyak berpikir tentang hal-hal warisan ini.
brent

@ Brent Tentu, hanya menambahkan kasus ketiga
soldier.moth

14
Jika saya tidak salah, Anda harus dapat menggunakan parentEvents = _.result(ParentView.prototype, 'events');alih-alih 'secara manual' memeriksa apakah eventsitu suatu fungsi.
Koen.

3
@Bayu_joo 1 untuk menyebutkan fungsi utilitas garis bawah _.result, yang tidak saya perhatikan sebelumnya. Bagi siapa saja yang tertarik, berikut adalah jsfiddle dengan sekelompok variasi pada tema ini: jsfiddle
EleventyOne

1
Hanya dengan membuang dua sen saya di sini, saya yakin opsi kedua adalah solusi terbaik. Saya mengatakan ini karena fakta bahwa ini adalah satu-satunya metode yang benar-benar dikemas. satu-satunya konteks yang digunakan adalah thisversus harus memanggil kelas induk dengan nama instance. Terima kasih banyak untuk ini.
jessie james jackson taylor

79

Prajurit itu. Jawaban keduanya bagus. Untuk lebih menyederhanakannya, Anda bisa melakukan hal berikut

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

Kemudian, cukup tentukan acara Anda di salah satu kelas dengan cara yang biasa.


8
Keputusan yang bagus, meskipun Anda mungkin ingin menukar this.events& ParentView.prototype.eventssebaliknya jika keduanya mendefinisikan penangan pada acara yang sama, penangan Induk akan menimpa Pengendali Anak.
prajurit. Keduanya

1
@ Soldier.moth, oke saya sudah mengeditnya menjadi{},ParentView.prototype.events,this.events
AJP

1
Jelas ini berfungsi, tetapi seperti yang saya tahu, delegateEventsdipanggil dalam konstruktor untuk mengikat acara. Jadi ketika Anda memperpanjangnya di initialize, kenapa belum terlambat?
SelimOber

2
Ini rewel, tetapi masalah saya dengan solusi ini adalah: Jika Anda memiliki hierarki pandangan yang beragam dan berlimpah, Anda pasti akan menemukan diri Anda menulis initializedalam beberapa kasus (kemudian harus berurusan dengan mengelola hierarki fungsi itu juga) hanya untuk menggabungkan objek acara. Tampak lebih bersih bagi saya untuk menjaga eventspenggabungan dalam dirinya sendiri. Karena itu, saya tidak akan memikirkan pendekatan ini, dan selalu menyenangkan dipaksa untuk melihat sesuatu dengan cara yang berbeda :)
EleventyOne

1
jawaban ini tidak lagi valid karena delegateEvents dipanggil sebelum menginisialisasi (ini benar untuk versi 1.2.3) - mudah untuk melakukannya di sumber yang dianotasi.
Roey

12

Anda juga bisa menggunakan defaultsmetode ini untuk menghindari pembuatan objek kosong {}.

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

2
Ini menyebabkan penangan orang tua terikat setelah penangan anak. Dalam kebanyakan kasus tidak menjadi masalah, tetapi jika acara anak harus membatalkan (tidak menimpa) acara induk itu tidak mungkin.
Koen.

10

Jika Anda menggunakan CoffeeScript dan mengatur fungsi ke events, Anda dapat menggunakan super.

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

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

Ini hanya berfungsi jika variabel peristiwa induk adalah fungsi daripada objek.
Michael

6

Bukankah lebih mudah untuk membuat konstruktor dasar khusus dari Backbone.View yang menangani pewarisan peristiwa ke atas hierarki.

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
}

Hal ini memungkinkan kita untuk mengurangi (menggabungkan) peristiwa hash ke bawah hierarki setiap kali kita membuat 'subclass' baru (konstruktor anak) dengan menggunakan fungsi perluasan yang didefinisikan ulang.

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

Dengan membuat tampilan khusus: BaseView yang mendefinisikan ulang fungsi perluasan, kita dapat memiliki subview (seperti AppView, SectionView) yang ingin mewarisi kejadian yang dideklarasikan tampilan induknya, cukup lakukan dengan memperluas dari BaseView atau salah satu turunannya.

Kami menghindari kebutuhan untuk mendefinisikan fungsi acara kami secara terprogram dalam subview kami, yang dalam banyak kasus perlu merujuk ke konstruktor induk secara eksplisit.


2

Versi singkat dari saran terakhir @ soldier.moth:

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

2

Ini juga akan berhasil:

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

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

Menggunakan straight supertidak berhasil untuk saya, baik secara manual menentukanParentView atau warisan.

Akses ke _supervar yang tersedia dalam coffeescript apa punClass … 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

Untuk Backbone versi 1.2.3, __super__berfungsi dengan baik, dan bahkan mungkin dirantai. Misalnya:

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

... yang - dalam A_View.js- akan menghasilkan:

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

1

Saya telah menemukan solusi yang lebih menarik di artikel ini

Ini menggunakan super Backbone dan hasOwnProperty ECMAScript. Contoh progresif kedua bekerja seperti pesona. Ini sedikit kodenya:

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

Anda juga dapat melakukannya untuk ui dan atribut .

Contoh ini tidak menangani properti yang disetel oleh fungsi, tetapi penulis artikel menawarkan solusi dalam kasus tersebut.


1

Untuk melakukan ini sepenuhnya di kelas induk dan mendukung hash peristiwa berbasis fungsi di kelas anak sehingga anak bisa menjadi agnostik warisan (anak harus memanggil MyView.prototype.initializejika diganti 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

Solusi CoffeeScript ini berfungsi untuk saya (dan mempertimbangkan saran @ soldier.moth):

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

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

0

Jika Anda yakin bahwa kejadian tersebut ParentViewtelah didefinisikan sebagai objek dan Anda tidak perlu mendefinisikan kejadian secara dinamis di ChildViewdalamnya adalah mungkin untuk menyederhanakan jawaban soldier.moth lebih jauh dengan menyingkirkan fungsi dan menggunakan _.extendsecara langsung:

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

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

0

Pola untuk ini yang saya suka adalah memodifikasi konstruktor dan menambahkan beberapa fungsi tambahan:

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

});

Saya lebih suka metode ini karena Anda tidak perlu mengidentifikasi variabel induk -satu yang kurang untuk diubah. Saya menggunakan logika yang sama untuk attributesdan defaults.


0

Wow, banyak jawaban di sini tapi saya pikir saya akan menawarkan satu lagi. Jika Anda menggunakan perpustakaan BackSupport, ia menawarkan extend2. Jika Anda menggunakannya extend2secara otomatis menangani penggabungan events(serta defaultsdan properti serupa) untuk Anda.

Berikut contoh singkatnya:

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
Saya suka konsepnya, tetapi, pada prinsipnya saja, saya akan meneruskan library apa pun yang menganggap "extender2" adalah nama fungsi yang sesuai.
Yaniv

Saya akan menyambut baik setiap saran yang dapat Anda tawarkan untuk apa yang memberi nama fungsi yang pada dasarnya "Backbone.extend, tetapi dengan fungsionalitas yang ditingkatkan". Extend 2.0 ( extend2) adalah yang terbaik yang bisa saya hasilkan, dan menurut saya itu tidak terlalu buruk: siapa pun yang terbiasa dengan Backbone sudah terbiasa menggunakannya extend, jadi dengan cara ini mereka tidak perlu mengingat perintah baru.
machineghost

Membuka masalah di repo Github tentang hal itu. :)
Yaniv
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.