2012-02-14 15 views
132

में उप-दृश्य प्रस्तुत करने और जोड़ने के लिए मेरे पास एक नेस्टेड-व्यू सेटअप है जो मेरे आवेदन में कुछ हद तक गहरा हो सकता है। उप-विचारों को आरंभ करने, प्रतिपादन और संलग्न करने के बारे में सोचने के तरीकों का एक समूह है, लेकिन मुझे आश्चर्य है कि आम अभ्यास क्या है।Backbone.js

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

पेशेवरों: आप जोड़कर साथ सही डोम व्यवस्था बनाए रखने के बारे में चिंता करने की ज़रूरत नहीं

यहां कुछ मैं के बारे में सोचा गया है कर रहे हैं। विचारों को प्रारंभ में प्रारंभ किया जाता है, इसलिए रेंडर फ़ंक्शन में एक साथ सभी को करने के लिए बहुत कुछ नहीं है।

विपक्ष: आपको फिर से प्रतिनिधि देने के लिए मजबूर होना चाहिए(), जो महंगा हो सकता है? अभिभावक दृश्य का प्रस्तुत करने का कार्य सभी सबव्यू प्रस्तुत करने के साथ अव्यवस्थित है जो होने की आवश्यकता है? आपके पास तत्वों के tagName सेट करने की क्षमता नहीं है, इसलिए टेम्पलेट को सही टैग नाम बनाए रखने की आवश्यकता है।

एक और तरीका है:

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

सकारात्मक: आप को फिर से प्रतिनिधि घटनाओं जरूरत नहीं है। आपको ऐसे टेम्पलेट की आवश्यकता नहीं है जिसमें केवल खाली प्लेसहोल्डर हैं और आपके टैगनाम को दृश्य द्वारा परिभाषित किया गया है।

विपक्ष: अब आपको सही क्रम में चीजों को जोड़ना सुनिश्चित करना है। अभिभावक दृश्य प्रस्तुत करना अभी भी सबव्यू प्रतिपादन द्वारा अव्यवस्थित है।

एक 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(); 
} 

सकारात्मक: subview तर्क अब देखने के 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 में

  • का दृष्टांत subviews?
  • render में या onRender में सभी सब-व्यू रेंडरिंग तर्क करें?
  • setElement या append/appendTo का उपयोग करें?
+0

मैं बिना हटाए नए के बारे में सावधान रहूंगा, आपको वहां मेमोरी लीक मिल जाएगी। – vimdude

+1

चिंता न करें, मेरे पास 'क्लोज़' विधि है और 'ऑनक्लोस' है जो बच्चों को साफ करती है, लेकिन मैं केवल पहले स्थान पर उन्हें तुरंत कैसे प्रस्तुत करना और प्रस्तुत करना चाहता हूं। –

+3

@abdelsaid: जावास्क्रिप्ट में, जीसी स्मृति की deallocation संभालती है। जेएस में 'हटाएं' सी ++ से 'हटाएं' जैसा नहीं है। यदि आप मुझसे पूछें तो यह बहुत खराब नामित कीवर्ड है। –

उत्तर

30

यह बैकबोन के साथ एक बारहमासी समस्या है और, मेरे अनुभव में, वास्तव में इस प्रश्न का एक संतोषजनक उत्तर नहीं है। मैं आपकी निराशा साझा करता हूं, खासतौर पर क्योंकि इस उपयोग के मामले में कितना आम है इसके बावजूद बहुत कम मार्गदर्शन है। उस ने कहा, मैं आमतौर पर आपके दूसरे उदाहरण के समान कुछ के साथ जाता हूं।

सबसे पहले, मैं हाथ से बाहर खारिज कर दूंगा जिसके लिए आपको घटनाओं को दोबारा प्रतिनिधि बनाना होगा। बैकबोन का इवेंट-संचालित व्यू मॉडल अपने सबसे महत्वपूर्ण घटकों में से एक है, और उस कार्यक्षमता को खोने के लिए क्योंकि आपका एप्लिकेशन गैर-तुच्छ है, किसी भी प्रोग्रामर के मुंह में खराब स्वाद छोड़ देगा। तो स्क्रैच नंबर एक।

आपके तीसरे उदाहरण के बारे में, मुझे लगता है कि यह परंपरागत प्रतिपादन अभ्यास के आसपास सिर्फ एक अंत-दौड़ है और इसमें अधिक अर्थ नहीं है। शायद यदि आप वास्तविक घटना ट्रिगरिंग कर रहे हैं (यानी, एक प्रदूषित नहीं "onRender" ईवेंट), तो उन घटनाओं को केवल render पर बाध्य करने के लायक होगा। यदि आपको render अनावश्यक और जटिल हो रहा है, तो आपके पास बहुत कम सबव्यूव हैं।

आपके दूसरे उदाहरण पर वापस जाएं, जो शायद तीन बुराइयों में से कम है। यहाँ उदाहरण कोड, Recipes With Backbone से उठाया मेरी पीडीएफ संस्करण के पेज 42 पर पाया जाता है:

... 
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 का एक सेट specifiy, ऐसा है कि गंदे काम मुझे लगता है कि यह दृष्टिकोण व्यावहारिक है (और मैं निश्चित रूप से इसका उपयोग करता हूं); लेकिन यह अभी भी एक विचित्र बाद में छोड़ देता है। (इन सभी जीभ रूपकों को क्षमा करें।)

सही क्रम में जोड़ने पर आपके बिंदु पर: यदि आप कड़ाई से जोड़ रहे हैं, तो निश्चित रूप से, यह एक सीमा है। लेकिन सुनिश्चित करें कि आप सभी संभावित टेम्पलेटिंग योजनाओं पर विचार करें। शायद आप वास्तव में प्लेसहोल्डर तत्व (उदा।, खाली div या ul) पसंद करेंगे कि आप replaceWith एक नया (DOM) तत्व कर सकते हैं जो उपयुक्त सबव्यूव रखता है। संलग्न करना एकमात्र समाधान नहीं है, और यदि आप इसके बारे में बहुत अधिक ध्यान रखते हैं तो आप निश्चित रूप से ऑर्डरिंग समस्या के आसपास आ सकते हैं, लेकिन मुझे लगता है कि यदि आपके पास ट्रिपिंग हो रही है तो आपके पास डिज़ाइन समस्या है। याद रखें, सबव्यूव में सबव्यूव हो सकते हैं, और यदि यह उचित है तो उन्हें चाहिए। इस तरह, आपके पास पेड़ की तरह एक संरचना है, जो काफी अच्छा है: प्रत्येक सबव्यू इसके सभी सबव्यूव्स जोड़ता है, क्रम में, माता-पिता दृश्य दूसरे को जोड़ने से पहले, और इसी तरह।

दुर्भाग्यवश, समाधान # 2 शायद सबसे अच्छा है जिसे आप आउट-ऑफ-द-बॉक्स बैकबोन का उपयोग करने के लिए उम्मीद कर सकते हैं। यदि आप तीसरे पक्ष के पुस्तकालयों की जांच करने में रुचि रखते हैं, जिसे मैंने देखा है (लेकिन वास्तव में अभी तक खेलने के लिए कोई समय नहीं है) Backbone.LayoutManager है, जो उप-दृश्य जोड़ने का एक स्वस्थ तरीका प्रतीत होता है। हालांकि, यहां तक ​​कि उनके पास इसी तरह के मुद्दों पर recent debates भी है।

+3

अंतिम पंक्ति - 'model.bind (' हटाएं ', view.remove);' - क्या आप उन्हें अपॉइंटमेंट के प्रारंभिक फ़ंक्शन में अलग-अलग रखने के लिए नहीं करना चाहिए? – ash

+2

जब कोई माता-पिता माता-पिता प्रस्तुत करता है, तब कोई दृश्य फिर से तत्काल नहीं किया जा सकता है क्योंकि यह एक राज्य रखता है? – mor

+0

इस पागलपन को रोकें और बस [Backbone.subviews] (https://github.com/rotundasoftware/backbone.subviews) प्लगइन का उपयोग करें! –

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. बाहरी दृश्य contai नहीं है एन एचटीएमएल तत्वों को आंतरिक दृश्य (ओं) पर सेट किया जाना चाहिए (जिसका अर्थ है कि आप अभी भी आंतरिक दृश्य में टैगनाम निर्दिष्ट कर सकते हैं)
  3. render() को आंतरिक दृश्य के तत्व को डोम में रखा गया है, जो आपके भीतर आंतरिक है दृश्य के 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. अपने विचार का एक बहुत पहले से ही अपने render() विधि
  2. जब बाहरी दृश्य फिर से गाया में डोम में किया जा रहा पर भरोसा करते हैं, विचारों को फिर से प्रारंभ करने के लिए नहीं है, फिर से प्रारंभ जो मेमोरी लीक के कारण और भी मौजूदा बाइंडिंग

साथ फ्रीकी समस्याएं हो सकती हैं ध्यान रखें कि यदि आप एक new View() हर बार आरंभ कर रहे हैं render() है कहा जाता है कि, प्रारंभिकरण delegateEvents() को वैसे भी कॉल करने जा रहा है। इसलिए यह जरूरी नहीं है कि आपने "con" होना चाहिए, जैसा कि आपने व्यक्त किया है।

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

यह एक minimalist समाधान है कि मुद्दों इस सूत्र में चर्चा की है, ताकि प्रतिपादन सहित का एक बहुत पते, करने के लिए नहीं है फिर से प्रतिनिधि:

+3

+1 समाधान 1 –

+1

के लिए इनमें से कोई समाधान जो देखने के लिए, जो अन्यथा कचरा संग्रहण –

2

बनाने और subviews प्रतिपादन के लिए इस mixin बाहर की जाँच करें घटनाएं, आदि ध्यान दें कि संग्रह दृश्य का मामला (जहां संग्रह में प्रत्येक मॉडल को एक सबव्यू के साथ दर्शाया जाता है) एक अलग विषय है। सबसे अच्छा सामान्य समाधान मुझे उस मामले के बारे में पता है CollectionView in Marionette है।

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

आश्चर्य यह अभी तक का उल्लेख नहीं किया गया है, लेकिन मैं गंभीरता से Marionette उपयोग करने पर विचार होगा।

यह रीढ़ क्षुधा को थोड़ा और संरचना, विशेष दृश्य प्रकार (ListView, ItemView, Region और Layout), उचित Controller रों जोड़ने और एक बहुत अधिक सहित लागू करता है।

यहां शुरू करने के लिए the project on Github और एक महान guide by Addy Osmani in the book Backbone Fundamentals है।

+2

यह रोका जा सके प्रश्न का उत्तर न में कस्टम सफाई करने में महत्वपूर्ण हो सकता है View.remove बुला उप दृश्य पेड़, ऊपर काम करते हैं। –

+2

@CasarBautista मैं इसे पूरा करने के लिए मैरियनेट का उपयोग कैसे नहीं करता लेकिन मैरियनेट वास्तव में उपर्युक्त समस्या को हल करता है –

0

मुझे उपर्युक्त समाधानों में से कोई भी वास्तव में पसंद नहीं है। मैं रेंडर विधि में मैन्युअल रूप से काम करने के लिए प्रत्येक दृश्य पर इस कॉन्फ़िगरेशन के लिए प्राथमिकता देता हूं।

  • views एक समारोह है या (सभी तरह सब-सब से देखने परिभाषाओं
  • जब माता-पिता .remove कहा जाता है, सबसे कम क्रम में ऊपर से नेस्टेड बच्चों की .remove बुलाया जाना चाहिए की एक वस्तु लौटने आपत्ति कर सकते हैं -sub विचार)
  • डिफ़ॉल्ट रूप से पैरेंट व्यू इसका अपना मॉडल और संग्रह पास करता है, लेकिन विकल्पों को जोड़ा जा सकता है और ओवरराइड किया जा सकता है।

यहाँ एक उदाहरण है:

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

बैकबोन जानबूझकर बनाया गया था ताकि इस के संबंध और कई अन्य मुद्दों में कोई "आम" अभ्यास था। यह यथासंभव अपरिवर्तित होने के लिए है। सैद्धांतिक रूप से, आपको बैकबोन के साथ टेम्पलेट का उपयोग भी नहीं करना है। आप दृश्य में सभी डेटा मैन्युअल रूप से बदलने के लिए render फ़ंक्शन में जावास्क्रिप्ट/jquery का उपयोग कर सकते हैं। इसे अधिक चरम बनाने के लिए, आपको एक विशिष्ट render फ़ंक्शन की भी आवश्यकता नहीं है। आपके पास renderFirstName नामक फ़ंक्शन हो सकता है जो डोम और renderLastName में पहला नाम अपडेट करता है जो डोम में अंतिम नाम अपडेट करता है। यदि आपने यह दृष्टिकोण लिया है, तो प्रदर्शन के संदर्भ में यह बेहतर होगा और आपको कभी भी घटनाओं को मैन्युअल रूप से प्रतिनिधि नहीं करना पड़ेगा। कोड इसे पढ़ने वाले किसी को भी समझ में आता है (हालांकि यह लंबा/गड़बड़ कोड होगा)।

हालांकि, आम तौर पर टेम्पलेट का उपयोग करने और पूरे दृश्य को नष्ट करने और पुनर्निर्माण और प्रत्येक रेंडर कॉल पर इसके सबव्यूव्यू का कोई नकारात्मक पक्ष नहीं है, क्योंकि यह अन्यथा कुछ भी करने के लिए प्रश्नकर्ता के साथ भी नहीं होता है। तो यही कारण है कि ज्यादातर लोग जो भी परिस्थिति में आते हैं, उतना अधिक करते हैं। और यही कारण है कि विचारित ढांचे सिर्फ इसे डिफ़ॉल्ट व्यवहार करते हैं।

0

आप प्रस्तुत किए गए सबव्यूज़ को चर के रूप में मुख्य टेम्पलेट में चर के रूप में भी इंजेक्ट कर सकते हैं। (जब छोरों में प्रयोग किया जाता है कि जिस तरह से आप भी गतिशील स्ट्रिंग subview1 + subview2 की तरह विचारों को श्रेणीबद्ध कर सकता है)

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

और फिर इसे गुरु के पास:

पहले subviews प्रस्तुत करना और उन्हें बदलने के इस तरह HTML करने के लिए ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

और इस तरह अंत में यह इंजेक्षन:

जो इस तरह दिखता है टेम्पलेट

सबव्यूज़ के भीतर की घटनाओं के बारे में: वे इस दृष्टिकोण के साथ उपरोक्त के भीतर माता-पिता (मास्टरव्यू) में कनेक्ट होने की संभावना अधिक होगी।

0

मुझे निम्न दृष्टिकोण का उपयोग करना पसंद है जो बच्चे के विचारों को ठीक से हटाने के लिए भी सुनिश्चित करता है। यहां एडी ओस्मानी द्वारा book का एक उदाहरण दिया गया है।

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() { 
    } });