วิธีการแสดงผลและผนวกมุมมองย่อยใน Backbone.js


133

ฉันมีการตั้งค่ามุมมองที่ซ้อนกันซึ่งสามารถเข้าถึงแอปพลิเคชันของฉันได้ค่อนข้างลึก มีหลายวิธีที่ฉันสามารถคิดเริ่มต้นแสดงผลและต่อท้ายมุมมองย่อยได้ แต่ฉันสงสัยว่าแนวทางปฏิบัติทั่วไปคืออะไร

นี่คือสองสามข้อที่ฉันคิดไว้:

initialize : function () {

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template());

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

ข้อดี:คุณไม่ต้องกังวลเกี่ยวกับการรักษาคำสั่ง DOM ที่ถูกต้องด้วยการต่อท้าย มุมมองจะเริ่มต้นในช่วงต้นดังนั้นจึงไม่มีสิ่งที่ต้องทำทั้งหมดพร้อมกันในฟังก์ชันการแสดงผล

จุดด้อย:คุณถูกบังคับให้มอบหมายงานใหม่ () ซึ่งอาจมีค่าใช้จ่ายสูง? ฟังก์ชันการแสดงผลของมุมมองพาเรนต์ยุ่งเหยิงกับการแสดงผลมุมมองย่อยทั้งหมดที่ต้องเกิดขึ้น? คุณไม่มีความสามารถในการตั้งค่าtagNameองค์ประกอบดังนั้นเทมเพลตจำเป็นต้องดูแล tagNames ที่ถูกต้อง

อีกวิธีหนึ่ง:

initialize : function () {

},

render : function () {

    this.$el.empty();

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});

    this.$el.append(this.subView1.render().el, this.subView2.render().el);
}

ข้อดี:คุณไม่จำเป็นต้องมอบหมายกิจกรรมใหม่ คุณไม่จำเป็นต้องมีเทมเพลตที่มีเพียงตัวยึดตำแหน่งว่างและ tagName ของคุณจะถูกกำหนดโดยมุมมอง

จุดด้อย:ตอนนี้คุณต้องแน่ใจว่าได้ต่อท้ายสิ่งต่างๆตามลำดับที่ถูกต้อง การเรนเดอร์ของมุมมองพาเรนต์ยังคงเกะกะโดยการแสดงผลมุมมองย่อย

ด้วยonRenderเหตุการณ์:

initialize : function () {
    this.on('render', this.onRender);
    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

ข้อดี:ตอนนี้ตรรกะมุมมองย่อยถูกแยกออกจากrender()วิธีการของมุมมอง

ด้วยonRenderเหตุการณ์:

initialize : function () {
    this.on('render', this.onRender);
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {
    this.subView1 = new Subview();
    this.subView2 = new Subview();
    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

ฉันได้ผสมและจับคู่แนวทางปฏิบัติที่แตกต่างกันมากมายในตัวอย่างเหล่านี้ทั้งหมด (ขออภัยด้วย) แต่สิ่งใดที่คุณจะเก็บไว้หรือเพิ่ม? แล้วคุณจะไม่ทำอะไร?

สรุปแนวทางปฏิบัติ:

  • สร้างอินสแตนซ์มุมมองย่อยในinitializeหรือในrender?
  • ใช้ตรรกะการแสดงผลมุมมองย่อยทั้งหมดในrenderหรือในonRender?
  • ใช้setElementหรือappend/appendTo?

ฉันจะระวังเกี่ยวกับใหม่โดยไม่ต้องลบคุณมีหน่วยความจำรั่วที่นั่น
vimdude

1
ไม่ต้องกังวลฉันมีcloseวิธีการและวิธีonCloseที่ทำความสะอาดเด็ก ๆ แต่ฉันแค่อยากรู้เกี่ยวกับวิธีการสร้างอินสแตนซ์และแสดงผลตั้งแต่แรก
Ian Storm Taylor

3
@abdelsaid: ใน JavaScript GC จะจัดการกับการจัดสรรหน่วยความจำ deleteใน JS ไม่เหมือนกับdeleteจาก C ++ เป็นคีย์เวิร์ดที่มีชื่อไม่ดีมากหากคุณถามฉัน
Mike Bailey

@MikeBantegui ได้รับ แต่มันเหมือนกับใน java ยกเว้นใน JS เพื่อเพิ่มหน่วยความจำคุณเพียงแค่ต้องกำหนด null เพื่อชี้แจงว่าฉันหมายถึงอะไรลองสร้างลูปโดยมีวัตถุใหม่อยู่ภายในและตรวจสอบหน่วยความจำ แน่นอนว่า GC จะเข้าถึงได้ แต่คุณจะสูญเสียความทรงจำก่อนที่จะไปถึงมัน ในกรณีนี้ Render ซึ่งอาจถูกเรียกหลายครั้ง
vimdude

3
ฉันเป็นนักพัฒนา Backbone มือใหม่ ใครช่วยอธิบายได้ไหมว่าทำไมตัวอย่างที่ 1 บังคับให้เรามอบหมายงานอีกครั้ง (หรือฉันควรถามในคำถามของตัวเอง?) ขอบคุณ
pilau

คำตอบ:


58

โดยทั่วไปฉันเคยเห็น / ใช้วิธีแก้ปัญหาต่างๆสองสามวิธี:

โซลูชันที่ 1

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.$el.append(this.inner.$el);
        this.inner.render();
    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);
        this.delegateEvents();
    }
});

สิ่งนี้คล้ายกับตัวอย่างแรกของคุณโดยมีการเปลี่ยนแปลงเล็กน้อย:

  1. ลำดับที่คุณต่อท้ายองค์ประกอบย่อยมีความสำคัญ
  2. มุมมองด้านนอกไม่มีองค์ประกอบ html ที่จะตั้งค่าในมุมมองด้านใน (หมายความว่าคุณยังสามารถระบุ tagName ในมุมมองด้านในได้)
  3. render()เรียกว่าหลังจากที่องค์ประกอบของมุมมองภายในถูกวางไว้ใน DOM ซึ่งจะมีประโยชน์หากrender()วิธีการของมุมมองภายในของคุณวาง / ปรับขนาดตัวเองบนหน้าตามตำแหน่ง / ขนาดขององค์ประกอบอื่น ๆ (ซึ่งเป็นกรณีการใช้งานทั่วไปจากประสบการณ์ของฉัน)

โซลูชันที่ 2

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.inner = new InnerView();
        this.$el.append(this.inner.$el);
    }
});

var InnerView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template);
    }
});

โซลูชันที่ 2 อาจดูสะอาดกว่า แต่มันทำให้เกิดสิ่งแปลก ๆ ในประสบการณ์ของฉันและส่งผลต่อประสิทธิภาพในทางลบ

โดยทั่วไปฉันใช้โซลูชัน 1 ด้วยเหตุผลสองประการ:

  1. มุมมองส่วนใหญ่ของฉันอาศัยอยู่ใน DOM แล้วในrender()วิธีการของพวกเขา
  2. เมื่อแสดงมุมมองภายนอกใหม่มุมมองไม่จำเป็นต้องเริ่มต้นใหม่ซึ่งการเริ่มต้นใหม่อาจทำให้หน่วยความจำรั่วไหลและยังทำให้เกิดปัญหานอกลู่นอกทางกับการผูกที่มีอยู่

โปรดทราบว่าหากคุณกำลังเริ่มต้นnew View()ทุกครั้งที่render()มีการเรียกการเริ่มต้นนั้นจะถูกเรียกdelegateEvents()ต่อไป นั่นจึงไม่จำเป็นต้องเป็น "ข้อเสีย" อย่างที่คุณเคยแสดงออกมา


1
วิธีแก้ปัญหาเหล่านี้ไม่ทำงานกับแผนผังมุมมองย่อยที่เรียกว่า View.remove ซึ่งอาจมีความสำคัญในการล้างข้อมูลแบบกำหนดเองในมุมมองซึ่งจะป้องกันการเก็บขยะ
Dominic

31

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

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

เกี่ยวกับตัวอย่างที่สามของคุณฉันคิดว่ามันเป็นเพียงการสิ้นสุดของการฝึกฝนการเรนเดอร์ทั่วไปและไม่ได้เพิ่มความหมายมากนัก บางทีถ้าคุณกำลังทริกเกอร์เหตุการณ์ที่เกิดขึ้นจริง (เช่นไม่ใช่onRenderเหตุการณ์ "" ที่สร้างขึ้น) มันก็คุ้มค่าที่จะผูกเหตุการณ์เหล่านั้นเข้ากับrenderตัวมันเอง หากคุณพบว่าดูrenderเทอะทะและซับซ้อนแสดงว่าคุณมีมุมมองย่อยน้อยเกินไป

กลับไปที่ตัวอย่างที่สองของคุณซึ่งน่าจะน้อยกว่าของความชั่วร้ายทั้งสาม นี่คือตัวอย่างโค้ดที่ยกมาจากRecipes With Backboneซึ่งอยู่ในหน้า 42 ของฉบับ PDF ของฉัน:

...
render: function() {
    $(this.el).html(this.template());
    this.addAll();
    return this;
},
  addAll: function() {
    this.collection.each(this.addOne);
},
  addOne: function(model) {
    view = new Views.Appointment({model: model});
    view.render();
    $(this.el).append(view.el);
    model.bind('remove', view.remove);
}

นี่เป็นเพียงการตั้งค่าที่ซับซ้อนกว่าตัวอย่างที่สองของคุณเล็กน้อยพวกเขาระบุชุดของฟังก์ชันaddAllและaddOneการทำงานที่สกปรก ฉันคิดว่าวิธีนี้ใช้ได้ (และฉันก็ใช้มันอย่างแน่นอน); แต่มันยังคงค้างอยู่ในคอที่แปลกประหลาด (ให้อภัยคำเปรียบเปรยลิ้นทั้งหมดนี้)

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

น่าเสียดายที่โซลูชัน # 2 น่าจะเป็นวิธีที่ดีที่สุดที่คุณสามารถคาดหวังได้สำหรับการใช้ Backbone แบบนอกกรอบ หากคุณสนใจที่จะตรวจสอบไลบรารีของบุคคลที่สามสิ่งที่ฉันได้ตรวจสอบ (แต่ยังไม่มีเวลาเล่นจริงๆ) คือBackbone.LayoutManagerซึ่งดูเหมือนว่าจะมีวิธีการที่ดีกว่าในการเพิ่มมุมมองย่อย อย่างไรก็ตามแม้ว่าพวกเขาจะมีการถกเถียงกันเมื่อเร็ว ๆ นี้ในประเด็นที่คล้ายคลึงกัน


4
บรรทัดสุดท้าย - model.bind('remove', view.remove);- คุณไม่ควรทำอย่างนั้นในฟังก์ชั่นเริ่มต้นของการนัดหมายเพื่อแยกพวกเขาออกจากกันหรือไม่?
atp

2
จะเกิดอะไรขึ้นเมื่อมุมมองไม่สามารถสร้างอินสแตนซ์ใหม่ได้ทุกครั้งที่พาเรนเดอร์แสดงผลเนื่องจากยังคงสถานะ
หมอ

หยุดความบ้าคลั่งทั้งหมดนี้และใช้ปลั๊กอินBackbone.subviews !
Brave Dave

6

ประหลาดใจนี้ยังไม่ได้รับการกล่าวถึง แต่ฉันอย่างจริงจังจะพิจารณาการใช้หุ่นกระบอก

มันบังคับใช้โครงสร้างมากขึ้นอีกนิดปพลิเคชัน Backbone รวมถึงประเภทมุมมองเฉพาะ ( ListView, ItemView, RegionและLayout) การเพิ่มที่เหมาะสมControllerและมากขึ้น

นี่คือโครงการใน Githubและคำแนะนำที่ยอดเยี่ยมโดย Addy Osmani ในหนังสือ Backbone Fundamentalsเพื่อให้คุณเริ่มต้น


3
สิ่งนี้ไม่ตอบคำถาม
Ceasar Bautista

2
@CeasarBautista ฉันไม่เข้าใจวิธีใช้ Marionette เพื่อทำสิ่งนี้ให้สำเร็จ แต่ Marionette แก้ปัญหาข้างต้นได้อย่างแท้จริง
Dana Woodman

4

ฉันมีสิ่งที่ฉันเชื่อว่าเป็นวิธีแก้ปัญหาที่ค่อนข้างครอบคลุมสำหรับปัญหานี้ อนุญาตให้โมเดลภายในคอลเล็กชันเปลี่ยนแปลงและมีการแสดงผลเฉพาะมุมมองเท่านั้น (แทนที่จะเป็นคอลเล็กชันทั้งหมด) นอกจากนี้ยังจัดการการลบมุมมองซอมบี้ด้วยวิธีปิด ()

var SubView = Backbone.View.extend({
    // tagName: must be implemented
    // className: must be implemented
    // template: must be implemented

    initialize: function() {
        this.model.on("change", this.render, this);
        this.model.on("close", this.close, this);
    },

    render: function(options) {
        console.log("rendering subview for",this.model.get("name"));
        var defaultOptions = {};
        options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions;
        this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast");
        return this;
    },

    close: function() {
        console.log("closing subview for",this.model.get("name"));
        this.model.off("change", this.render, this);
        this.model.off("close", this.close, this);
        this.remove();
    }
});
var ViewCollection = Backbone.View.extend({
    // el: must be implemented
    // subViewClass: must be implemented

    initialize: function() {
        var self = this;
        self.collection.on("add", self.addSubView, self);
        self.collection.on("remove", self.removeSubView, self);
        self.collection.on("reset", self.reset, self);
        self.collection.on("closeAll", self.closeAll, self);
        self.collection.reset = function(models, options) {
            self.closeAll();
            Backbone.Collection.prototype.reset.call(this, models, options);
        };
        self.reset();
    },

    reset: function() {
        this.$el.empty();
        this.render();
    },

    render: function() {
        console.log("rendering viewcollection for",this.collection.models);
        var self = this;
        self.collection.each(function(model) {
            self.addSubView(model);
        });
        return self;
    },

    addSubView: function(model) {
        var sv = new this.subViewClass({model: model});
        this.$el.append(sv.render().el);
    },

    removeSubView: function(model) {
        model.trigger("close");
    },

    closeAll: function() {
        this.collection.each(function(model) {
            model.trigger("close");
        });
    }
});

การใช้งาน:

var PartView = SubView.extend({
    tagName: "tr",
    className: "part",
    template: _.template($("#part-row-template").html())
});

var PartListView = ViewCollection.extend({
    el: $("table#parts"),
    subViewClass: PartView
});

2

ตรวจสอบมิกซ์อินนี้เพื่อสร้างและแสดงผลมุมมองย่อย:

https://github.com/rotundasoftware/backbone.subviews

เป็นโซลูชันที่เรียบง่ายซึ่งจัดการกับปัญหาต่างๆที่กล่าวถึงในชุดข้อความนี้รวมถึงลำดับการแสดงผลโดยไม่ต้องมอบหมายเหตุการณ์ซ้ำ ฯลฯ โปรดทราบว่ากรณีของมุมมองคอลเลคชัน (โดยที่แต่ละรุ่นในคอลเลกชันจะแสดงด้วยหนึ่ง subview) เป็นหัวข้ออื่น การแก้ปัญหาทั่วไปที่ดีที่สุดฉันรู้กับกรณีที่เป็นCollectionView ใน Marionette


0

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

  • views อาจเป็นฟังก์ชันหรือวัตถุที่ส่งคืนวัตถุของนิยามมุมมอง
  • เมื่อมีการ.removeเรียกพาเรนต์.removeควรเรียกเด็กที่ซ้อนกันจากลำดับต่ำสุดขึ้นไป (ตลอดทางจากมุมมองย่อยย่อยย่อย)
  • ตามค่าเริ่มต้นมุมมองหลักจะส่งผ่านโมเดลและคอลเลกชันของตัวเอง แต่สามารถเพิ่มและแทนที่ตัวเลือกได้

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

views: {
    '.js-toolbar-left': CancelBtnView, // shorthand
    '.js-toolbar-right': {
        view: DoneBtnView,
        append: true
    },
    '.js-notification': {
        view: Notification.View,
        options: function() { // Options passed when instantiating
            return {
                message: this.state.get('notificationMessage'),
                state: 'information'
            };
        }
    }
}

0

กระดูกสันหลังถูกสร้างขึ้นโดยเจตนาเพื่อให้ไม่มีการปฏิบัติ "ร่วมกัน" ในเรื่องนี้และประเด็นอื่น ๆ อีกมากมาย มีความตั้งใจที่จะไม่เปิดใช้งานให้มากที่สุด ในทางทฤษฎีคุณไม่จำเป็นต้องใช้เทมเพลตกับ Backbone คุณสามารถใช้ javascript / jquery ในrenderฟังก์ชันของมุมมองเพื่อเปลี่ยนข้อมูลทั้งหมดในมุมมองด้วยตนเอง เพื่อให้มันสุดยอดมากขึ้นคุณไม่จำเป็นต้องมีrenderฟังก์ชันเฉพาะอย่างเดียว คุณสามารถมีฟังก์ชันที่เรียกว่าrenderFirstNameซึ่งอัปเดตชื่อในโดมและrenderLastNameอัปเดตนามสกุลในโดม หากคุณใช้แนวทางนี้มันจะดีกว่าในแง่ของประสิทธิภาพและคุณจะไม่ต้องมอบหมายงานด้วยตนเองอีกต่อไป รหัสจะมีความหมายโดยรวมสำหรับคนที่อ่านมัน (แม้ว่าจะเป็นรหัสที่ยาวกว่า / ยุ่งกว่า)

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


0

คุณยังฉีดมุมมองย่อยที่แสดงผลเป็นตัวแปรลงในเทมเพลตหลักเป็นตัวแปรได้

ก่อนอื่นให้แสดงผลมุมมองย่อยและแปลงเป็น html ดังนี้:

var subview1 = $(subview1.render.el).html(); var subview2 = $(subview2.render.el).html();

(ด้วยวิธีนี้คุณสามารถเชื่อมสตริงแบบไดนามิกเชื่อมต่อมุมมองเช่นsubview1 + subview2เมื่อใช้ในลูป) จากนั้นส่งต่อไปยังเทมเพลตหลักซึ่งมีลักษณะดังนี้: ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

และสุดท้ายฉีดเป็นดังนี้:

this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 } ));

เกี่ยวกับเหตุการณ์ภายในมุมมองย่อย: พวกเขามักจะต้องเชื่อมต่อในพาเรนต์ (masterView) โดยวิธีนี้ไม่ได้อยู่ในมุมมองย่อย


0

ฉันชอบใช้แนวทางต่อไปนี้ซึ่งตรวจสอบให้แน่ใจว่าได้ลบมุมมองของเด็กออกอย่างถูกต้อง นี่คือตัวอย่างจากหนังสือของ Addy Osmani

Backbone.View.prototype.close = function() {
    if (this.onClose) {
        this.onClose();
    }
    this.remove(); };

NewView = Backbone.View.extend({
    initialize: function() {
       this.childViews = [];
    },
    renderChildren: function(item) {
        var itemView = new NewChildView({ model: item });
        $(this.el).prepend(itemView.render());
        this.childViews.push(itemView);
    },
    onClose: function() {
      _(this.childViews).each(function(view) {
        view.close();
      });
    } });

NewChildView = Backbone.View.extend({
    tagName: 'li',
    render: function() {
    } });

0

ไม่จำเป็นต้องมอบหมายงานอีกครั้งเนื่องจากมีค่าใช้จ่ายสูง ดูด้านล่าง:

    var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        // first detach subviews            
        this.inner.$el.detach(); 

        // now can set html without affecting subview element's events
        this.$el.html(template);

        // now render and attach subview OR can even replace placeholder 
        // elements in template with the rendered subview element
        this.$el.append(this.inner.render().el);

    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);            
    }
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.