Backbone View: รับช่วงและขยายกิจกรรมจากพาเรนต์


115

สถานะเอกสารของกระดูกสันหลัง:

คุณสมบัติเหตุการณ์อาจถูกกำหนดให้เป็นฟังก์ชันที่ส่งคืนแฮชของเหตุการณ์เพื่อให้ง่ายต่อการกำหนดเหตุการณ์ของคุณทางโปรแกรมรวมทั้งรับช่วงจากมุมมองหลัก

คุณสืบทอดเหตุการณ์มุมมองของผู้ปกครองและขยายออกไปอย่างไร

มุมมองผู้ปกครอง

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

มุมมองเด็ก

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

คำตอบ:


189

วิธีหนึ่งคือ:

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

อีกอย่างจะเป็น:

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

เพื่อตรวจสอบว่าเหตุการณ์เป็นฟังก์ชันหรือวัตถุ

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

เยี่ยมมาก ... บางทีคุณอาจอัปเดตสิ่งนี้เพื่อแสดงว่าคุณจะสืบทอดจาก ChildView ได้อย่างไร (ตรวจสอบว่าเหตุการณ์ต้นแบบเป็นฟังก์ชันหรือวัตถุ) ... หรือบางทีฉันอาจคิดมากเกินไปเกี่ยวกับมรดกทั้งหมดนี้
brent

@brent Sure เพิ่งเพิ่มกรณีที่สาม
ทหาร

14
ถ้าฉันไม่เข้าใจผิดคุณควรจะสามารถใช้parentEvents = _.result(ParentView.prototype, 'events');แทนการตรวจสอบ 'ด้วยตนเอง' ว่าeventsเป็นฟังก์ชันหรือไม่
โคเอ็น.

3
@Koen +1 สำหรับการกล่าวถึงฟังก์ชันยูทิลิตี้ขีดล่าง_.resultซึ่งฉันไม่เคยสังเกตมาก่อน สำหรับใครที่สนใจนี่คือ jsfiddle ที่มีรูปแบบต่างๆมากมายในธีมนี้: jsfiddle
EleventyOne

1
เพียงแค่โยนสองเซ็นต์ของฉันที่นี่ฉันเชื่อว่าตัวเลือกที่สองเป็นทางออกที่ดีที่สุด ฉันพูดแบบนี้เพราะความจริงที่ว่ามันเป็นวิธีเดียวที่ห่อหุ้มอย่างแท้จริง บริบทเดียวที่ใช้คือthisเมื่อเทียบกับการต้องเรียกคลาสพาเรนต์ตามชื่ออินสแตนซ์ ขอบคุณมากสำหรับสิ่งนี้
jessie james jackson taylor

79

ทหารคำตอบมอ ธ เป็นคำตอบที่ดี ทำให้ง่ายขึ้นคุณสามารถทำสิ่งต่อไปนี้ได้

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

จากนั้นกำหนดกิจกรรมของคุณในชั้นเรียนใดชั้นหนึ่งตามแบบฉบับ


8
การโทรที่ดีแม้ว่าคุณอาจต้องการสลับthis.eventsและไม่ParentView.prototype.eventsเช่นนั้นหากทั้งคู่กำหนดตัวจัดการในเหตุการณ์เดียวกันตัวจัดการของผู้ปกครองจะแทนที่ Child's
ทหาร

1
@ Soldier.moth โอเคฉันแก้ไขให้เป็น{},ParentView.prototype.events,this.events
AJP

1
เห็นได้ชัดว่าสิ่งนี้ใช้งานได้ แต่อย่างที่ฉันรู้delegateEventsถูกเรียกในตัวสร้างเพื่อผูกเหตุการณ์ ดังนั้นเมื่อคุณขยายมันออกไปinitializeทำไมมันถึงไม่สายเกินไป?
SelimOber

2
เป็นเรื่องที่จู้จี้จุกจิก แต่ปัญหาของฉันในการแก้ปัญหานี้คือ: หากคุณมีลำดับชั้นของมุมมองที่หลากหลายและมากมายคุณจะพบว่าตัวเองกำลังเขียนinitializeในบางกรณีอย่างหลีกเลี่ยงไม่ได้(จากนั้นต้องจัดการกับลำดับชั้นของฟังก์ชันนั้นด้วย) เพียงเพื่อ ผสานวัตถุเหตุการณ์ ดูเหมือนจะสะอาดกว่าสำหรับฉันที่จะให้การeventsรวมอยู่ในตัวเอง ดังที่กล่าวมาฉันคงไม่ได้คิดถึงแนวทางนี้และเป็นเรื่องดีเสมอที่ถูกบังคับให้มองสิ่งต่างๆในมุมมองที่แตกต่างออกไป :)
EleventyOne

1
คำตอบนี้ใช้ไม่ได้อีกต่อไปเนื่องจาก delegateEvents ถูกเรียกก่อนเริ่มต้น (ซึ่งเป็นจริงสำหรับเวอร์ชัน 1.2.3) ซึ่งทำได้ง่ายในแหล่งข้อมูลที่มีคำอธิบายประกอบ
Roey

12

คุณยังสามารถใช้defaultsวิธีนี้เพื่อหลีกเลี่ยงการสร้างวัตถุว่าง{}ได้

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

2
สิ่งนี้ทำให้ตัวจัดการพาเรนต์ถูกผูกไว้หลังจากตัวจัดการเด็ก ในกรณีส่วนใหญ่ไม่ใช่ปัญหา แต่ถ้าเหตุการณ์ย่อยควรยกเลิก (ไม่ลบล้าง) เหตุการณ์หลักจะเป็นไปไม่ได้
โคเอ็น.

10

หากคุณใช้ CoffeeScript และตั้งค่าฟังก์ชันเป็นeventsคุณสามารถใช้superไฟล์.

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

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

สิ่งนี้ใช้ได้เฉพาะในกรณีที่ตัวแปรเหตุการณ์หลักเป็นฟังก์ชันแทนที่จะเป็นอ็อบเจ็กต์
Michael

6

การสร้างตัวสร้างฐานพิเศษจาก Backbone จะง่ายกว่าไหมดูที่จัดการการสืบทอดเหตุการณ์ตามลำดับชั้น

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
}

สิ่งนี้ช่วยให้เราสามารถลด (ผสาน) เหตุการณ์ที่แฮชตามลำดับชั้นเมื่อใดก็ตามที่เราสร้าง 'คลาสย่อย' ใหม่ (ตัวสร้างลูก) ใหม่โดยใช้ฟังก์ชันขยายที่กำหนดใหม่

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

ด้วยการสร้างมุมมองพิเศษ: BaseView ที่กำหนดฟังก์ชันการขยายใหม่เราสามารถมีมุมมองย่อย (เช่น AppView, SectionView) ที่ต้องการสืบทอดเหตุการณ์ที่ประกาศของมุมมองหลักของพวกเขาก็ทำได้โดยการขยายจาก BaseView หรืออนุพันธ์อย่างใดอย่างหนึ่ง

เราหลีกเลี่ยงความจำเป็นในการกำหนดฟังก์ชันเหตุการณ์ของเราโดยใช้โปรแกรมในมุมมองย่อยของเราซึ่งในกรณีส่วนใหญ่จำเป็นต้องอ้างถึงตัวสร้างหลักอย่างชัดเจน


2

คำแนะนำสุดท้ายของ @ soldier.moth ฉบับย่อ:

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

2

สิ่งนี้ยังใช้ได้:

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

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

การใช้เส้นตรงsuperไม่ได้ผลสำหรับฉันทั้งเป็นการระบุParentViewคลาสที่สืบทอดด้วยตนเองหรือ

เข้าถึง_supervar ที่มีอยู่ในกาแฟใด ๆClass … 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

สำหรับ Backbone เวอร์ชัน 1.2.3 __super__ทำงานได้ดีและอาจถูกล่ามโซ่ด้วยซ้ำ เช่น:

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

... ซึ่ง - ในA_View.js- จะส่งผลให้:

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

1

ฉันพบวิธีแก้ปัญหาที่น่าสนใจกว่าในบทความนี้

โดยใช้superของ Backbone และ ECMAScript's hasOwnProperty ตัวอย่างที่สองของโปรเกรสซีฟทำงานได้อย่างมีเสน่ห์ นี่คือรหัสเล็กน้อย:

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

นอกจากนี้คุณยังสามารถทำเช่นนั้นสำหรับUIและคุณลักษณะ

ตัวอย่างนี้ไม่ได้ดูแลคุณสมบัติที่กำหนดโดยฟังก์ชัน แต่ผู้เขียนบทความเสนอวิธีแก้ปัญหาในกรณีนั้น


1

เมื่อต้องการทำสิ่งนี้ทั้งหมดในคลาสแม่และสนับสนุนแฮชเหตุการณ์ที่อิงตามฟังก์ชันในคลาสลูกเพื่อให้เด็ก ๆ ไม่เชื่อเรื่องพระเจ้าของการถ่ายทอดทางพันธุกรรม (เด็กจะต้องเรียกMyView.prototype.initializeถ้ามันถูกแทนที่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

โซลูชัน CoffeeScript นี้ใช้ได้กับฉัน (และคำนึงถึงคำแนะนำของ @ soldier.moth):

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

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

0

หากคุณแน่ใจว่าParentViewมีเหตุการณ์ที่กำหนดเป็นวัตถุและคุณไม่จำเป็นต้องกำหนดเหตุการณ์แบบไดนามิกในChildViewนั้นเป็นไปได้ที่จะทำให้ทหารง่ายขึ้นคำตอบของมอ ธ เพิ่มเติมโดยการกำจัดฟังก์ชันและใช้_.extendโดยตรง:

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

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

0

รูปแบบสำหรับสิ่งนี้ที่ฉันชอบคือการแก้ไขตัวสร้างและเพิ่มฟังก์ชันเพิ่มเติมบางอย่าง:

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

});

ฉันชอบวิธีนี้เพราะคุณไม่ต้องระบุตัวแปร parent -one less เพื่อเปลี่ยน ฉันใช้ตรรกะเดียวกันสำหรับattributesและdefaults.


0

ว้าวมีคำตอบมากมาย แต่ฉันคิดว่าจะเสนออีกอย่าง ถ้าคุณใช้ BackSupport extend2ห้องสมุดก็มี หากคุณใช้extend2โดยอัตโนมัติจะดูแลการรวมevents(เช่นเดียวกับdefaultsและคุณสมบัติที่คล้ายกัน) ให้กับคุณ

นี่คือตัวอย่างสั้น ๆ :

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
ฉันชอบแนวคิดนี้ แต่โดยหลักการเพียงอย่างเดียวฉันจะส่งต่อไลบรารีใด ๆ ที่คิดว่า "ขยาย 2" เป็นชื่อฟังก์ชันที่เหมาะสม
Yaniv

ฉันยินดีรับข้อเสนอแนะใด ๆ ที่คุณสามารถเสนอได้สำหรับสิ่งที่จะตั้งชื่อฟังก์ชันที่เป็นพื้นฐานว่า "Backbone.extend แต่มีการปรับปรุงฟังก์ชันการทำงาน" Extend 2.0 ( extend2) เป็นสิ่งที่ดีที่สุดที่ฉันสามารถคิดได้และฉันไม่คิดว่ามันจะแย่ขนาดนี้เพราะใคร ๆ ก็เคยชินกับ Backbone อยู่แล้วextendดังนั้นวิธีนี้พวกเขาจึงไม่จำเป็นต้องจำคำสั่งใหม่
machineghost

เปิดปัญหาเกี่ยวกับ repo Github เกี่ยวกับเรื่องนี้ :)
Yaniv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.