Backbone.js: दृश्य को फिर से दिखाना या फिर से बनाना?


83

मेरे वेब एप्लिकेशन में, मेरे पास बाईं ओर एक तालिका में एक उपयोगकर्ता सूची और दाईं ओर एक उपयोगकर्ता विवरण फलक है। जब व्यवस्थापक तालिका में किसी उपयोगकर्ता को क्लिक करता है, तो उसका विवरण दाईं ओर प्रदर्शित किया जाना चाहिए।

मेरे पास बाईं ओर एक UserListView और UserRowView है, और दाईं ओर एक UserDetailView है। काम की तरह बातें, लेकिन मेरा एक अजीब व्यवहार है। यदि मैं बाईं ओर कुछ उपयोगकर्ताओं को क्लिक करता हूं, तो उनमें से एक पर डिलीट पर क्लिक करें, मुझे उन सभी उपयोगकर्ताओं के लिए लगातार जावास्क्रिप्ट पुष्टि बॉक्स मिलते हैं जो प्रदर्शित किए गए हैं।

ऐसा लगता है कि पहले प्रदर्शित किए गए सभी विचारों के ईवेंट बाइंडिंग को हटाया नहीं गया है, जो सामान्य प्रतीत होता है। मुझे UserRowView पर हर बार एक नया UserDetailView नहीं करना चाहिए? क्या मुझे एक दृश्य बनाए रखना चाहिए और उसका संदर्भ मॉडल बदलना चाहिए? क्या मुझे वर्तमान दृश्य का ट्रैक रखना चाहिए और नया बनाने से पहले इसे हटा देना चाहिए? मैं हारा हुआ हूँ और किसी भी विचार का स्वागत किया जाएगा। धन्यवाद !

यहाँ बाएं दृश्य का कोड है (पंक्ति प्रदर्शन, क्लिक करें घटना, सही दृश्य निर्माण)

window.UserRowView = Backbone.View.extend({
    tagName : "tr",
    events : {
        "click" : "click",
    },
    render : function() {
        $(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
        return this;
    },
    click : function() {
        var view = new UserDetailView({model:this.model})
        view.render()
    }
})

और सही देखने के लिए कोड (बटन हटाएं)

window.UserDetailView = Backbone.View.extend({
    el : $("#bbBoxUserDetail"),
    events : {
        "click .delete" : "deleteUser"
    },
    initialize : function() {
        this.model.bind('destroy', function(){this.el.hide()}, this);
    },
    render : function() {
        this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
        this.el.show();
    },
    deleteUser : function() {
        if (confirm("Really delete user " + this.model.get("login") + "?")) 
            this.model.destroy();
        return false;
    }
})

जवाबों:


28

मैंने हाल ही में इसके बारे में ब्लॉग किया, और इन परिदृश्यों को संभालने के लिए मैंने अपने ऐप्स में कई चीजें दिखाईं:

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/


1
सिर्फ delete viewराउटर में क्यों नहीं ?
Trantor लियू

मैंने आपके उत्तर को बढ़ा दिया है, लेकिन यह उत्तर के अंदर ब्लॉग पोस्ट के संबंधित भागों के होने से वास्तव में लाभ होगा क्योंकि यह यहाँ का लक्ष्य है।
एमिल बर्जरॉन

136

मैं हमेशा विचारों को नष्ट करता हूं और बनाता हूं क्योंकि जैसे ही मेरा एकल पेज ऐप बड़ा और बड़ा हो जाता है, अप्रयुक्त लाइव विचारों को स्मृति में रखते हुए, ताकि मैं उन्हें फिर से उपयोग कर सकूं, उन्हें बनाए रखना मुश्किल हो जाएगा।

यहाँ एक तकनीक का सरलीकृत संस्करण है जिसका उपयोग मैं मेमोरी लीक से बचने के लिए अपने दृश्यों को साफ करने के लिए करता हूं।

मैं पहले एक BaseView बनाता हूं, जिससे मेरे सभी विचार विरासत में मिलते हैं। मूल विचार यह है कि मेरा दृश्य उन सभी घटनाओं का संदर्भ रखेगा, जिनके लिए इसे सब्सक्राइब किया गया है, ताकि जब यह व्यू को निपटाने का समय हो, तो उन सभी बाइंडिंग स्वचालित रूप से अनबाउंड हो जाएंगे। यहाँ मेरा BaseView का एक उदाहरण कार्यान्वयन है:

var BaseView = function (options) {

    this.bindings = [];
    Backbone.View.apply(this, [options]);
};

_.extend(BaseView.prototype, Backbone.View.prototype, {

    bindTo: function (model, ev, callback) {

        model.bind(ev, callback, this);
        this.bindings.push({ model: model, ev: ev, callback: callback });
    },

    unbindFromAll: function () {
        _.each(this.bindings, function (binding) {
            binding.model.unbind(binding.ev, binding.callback);
        });
        this.bindings = [];
    },

    dispose: function () {
        this.unbindFromAll(); // Will unbind all events this view has bound to
        this.unbind();        // This will unbind all listeners to events from 
                              // this view. This is probably not necessary 
                              // because this view will be garbage collected.
        this.remove(); // Uses the default Backbone.View.remove() method which
                       // removes this.el from the DOM and removes DOM events.
    }

});

BaseView.extend = Backbone.View.extend;

जब भी किसी मॉडल या संग्रह पर किसी ईवेंट को देखने के लिए बाइंड करने की आवश्यकता होती है, मैं बाइंडटो विधि का उपयोग करता हूं। उदाहरण के लिए:

var SampleView = BaseView.extend({

    initialize: function(){
        this.bindTo(this.model, 'change', this.render);
        this.bindTo(this.collection, 'reset', this.doSomething);
    }
});

जब भी मैं किसी दृश्य को हटाता हूं, तो मैं बस डिस्पोज़ विधि को कॉल करता हूं जो सब कुछ स्वचालित रूप से साफ कर देगा:

var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();

मैंने इस तकनीक को उन लोगों के साथ साझा किया, जो "बैकबोन.जेएस ऑन रेल्स" ईबुक लिख रहे हैं और मेरा मानना ​​है कि यह वह तकनीक है जिसे उन्होंने पुस्तक के लिए अपनाया है।

अपडेट: 2014-03-24

बैकऑन 0.9.9 के रूप में, लिंडटो और स्टॉप लिस्टेनिंग को एक ही बाइंडटो और अनबाइंडफ्रॉमएल तकनीक के साथ ऊपर दिखाए गए इवेंट्स में जोड़ा गया। इसके अलावा, View.remove कॉल को स्वचालित रूप से बंद कर देता है, इसलिए बाइंडिंग और अनबाइंडिंग अब के रूप में आसान है:

var SampleView = BaseView.extend({

    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
    }
});

var sampleView = new SampleView({model: some_model});
sampleView.remove();

क्या आपके पास कोई सुझाव है कि कैसे नेस्टेड विचारों को निपटाना है? अभी मैं bindTo: gist.github.com/1288947 के समान काम कर रहा हूं, लेकिन मुझे लगता है कि कुछ और बेहतर करना संभव है।
दिमित्री पॉलुस्किन

दिमित्री, मैं कुछ ऐसा ही करता हूं जो आप नेस्टेड विचारों को निपटाने के लिए कर रहे हैं। मैंने अभी तक एक बेहतर समाधान नहीं देखा है, लेकिन मुझे यह जानने में दिलचस्पी होगी कि क्या कोई है। यहाँ एक और चर्चा है जो इस पर भी है: group.google.com/forum/# ​​.topic / backbonejs / 3ZFm-lteN-A । मैंने देखा कि आपके समाधान में, आप उस परिदृश्य को ध्यान में नहीं रख रहे हैं जहाँ एक नेस्टेड दृश्य सीधे निपट जाता है। ऐसे परिदृश्य में, पैरेंट दृश्य तब भी नेस्टेड दृश्य का संदर्भ रखेगा, भले ही नेस्टेड दृश्य का निपटान किया गया हो। मुझे नहीं पता कि क्या आपको इसके लिए जिम्मेदार होना चाहिए।
जॉनी ओशिका

क्या होगा अगर मेरे पास कार्यक्षमता है जो समान दृश्य को खोलता और बंद करता है। मेरे पास एक आगे और पीछे के बटन हैं। यदि मैं निपटान कहता हूं तो यह DOM से तत्व को हटा देगा। क्या मुझे पूरे समय स्मृति में दृश्य रखना चाहिए?
dagda1

1
हाय फिशरदेव। आप इस तकनीक का उपयोग Backbone.View.extend के साथ भी कर सकते हैं, लेकिन इसके लिए आपको BaseView.initialize पद्धति में इसे शुरू करना होगा। इसके साथ समस्या यह है कि यदि आपका विरासत वाला दृश्य अपनी प्रारंभिक विधि को लागू करता है, तो इसे बेस व्यू के आरंभीकरण विधि को स्पष्ट रूप से कॉल करने की आवश्यकता होगी। मैंने इस समस्या को यहाँ और अधिक विस्तार से समझाया: stackoverflow.com/a/7736030/188740
जॉनी ओशिका

2
हाय सनीरेड, मैंने विचारों को नष्ट करने के अपने कारण को बेहतर ढंग से दर्शाने के लिए अपने उत्तर को अपडेट किया। बैकबोन के साथ, मुझे ऐप शुरू होने के बाद एक पेज को फिर से लोड करने का कोई कारण नहीं दिखता है, इसलिए मेरा एकल पेज ऐप काफी बड़ा हो गया है। चूंकि उपयोगकर्ता मेरे ऐप के साथ बातचीत करते हैं, इसलिए मैं पृष्ठ के विभिन्न खंडों (जैसे दृश्य को संपादित करने के लिए विस्तार से स्विच) पर लगातार पुन: प्रतिपादन कर रहा हूं, इसलिए मुझे हमेशा नए विचार बनाने में बहुत आसान लगता है, चाहे वह खंड पहले प्रस्तुत किया गया हो या नहीं नहीं। दूसरी ओर मॉडल व्यावसायिक वस्तुओं का प्रतिनिधित्व करते हैं, इसलिए मैं केवल उन्हें संशोधित करूंगा यदि वस्तु वास्तव में बदल गई।
जॉनी ओशिका

8

यह एक सामान्य स्थिति है। यदि आप हर बार एक नया दृश्य बनाते हैं, तो सभी पुराने दृश्य अभी भी सभी घटनाओं के लिए बाध्य होंगे। एक चीज जो आप कर सकते हैं, वह है आपके नाम पर एक फ़ंक्शन बनाना detatch:

detatch: function() {
   $(this.el).unbind();
   this.model.unbind();

फिर, नया दृश्य बनाने से पहले, detatchपुराने दृश्य पर कॉल करना सुनिश्चित करें ।

बेशक, जैसा कि आपने उल्लेख किया है, आप हमेशा एक "विवरण" दृश्य बना सकते हैं और इसे कभी नहीं बदल सकते। आप अपने आप को फिर से प्रस्तुत करने के लिए मॉडल (दृश्य से) पर "परिवर्तन" घटना के लिए बाध्य कर सकते हैं। इसे अपने इनिशियलाइज़र में जोड़ें:

this.model.bind('change', this.render)

ऐसा करने से विवरण को फिर से प्रस्तुत करने के लिए फलक हो जाएगा, जब भी मॉडल में बदलाव किया जाता है। आप एक ही संपत्ति के लिए देख कर बारीक बारीक हो सकते हैं: "परिवर्तन: प्रोपनाम"।

बेशक, ऐसा करने के लिए एक सामान्य मॉडल की आवश्यकता होती है कि आइटम व्यू में संदर्भ के साथ-साथ उच्च स्तरीय सूची दृश्य और विवरण दृश्य भी हो।

उम्मीद है की यह मदद करेगा!


1
हम्म, मैंने आपके द्वारा सुझाई गई लाइनों के साथ कुछ किया है, लेकिन मुझे अभी भी समस्याएं हैं: उदाहरण के लिए, this.model.unbind()मेरे लिए गलत है क्योंकि यह इस मॉडल से सभी घटनाओं को एक ही उपयोगकर्ता के अन्य विचारों के बारे में घटनाओं सहित, अंधा कर देता है। इसके अलावा, detachफ़ंक्शन को कॉल करने के लिए , मुझे दृश्य के लिए एक स्थिर संदर्भ रखने की आवश्यकता है, और मुझे यह पसंद नहीं है। मुझे लगता है कि अभी भी कुछ मैं undertand नहीं है है ...
solendil

6

कई बार बाध्यकारी घटनाओं को ठीक करने के लिए,

$("#my_app_container").unbind()
//Instantiate your views here

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


यहाँ बहुत अच्छे, विस्तृत उत्तर हैं। मैं निश्चित रूप से कुछ ViewManger सुझावों पर ध्यान देने का इरादा रखता हूं। हालाँकि, यह एक साधारण मृत था और यह मेरे लिए पूरी तरह से काम करता है क्योंकि मेरे दृश्य करीब () विधियों के साथ सभी पैनल हैं, जहां मैं सिर्फ घटनाओं को खोल सकता हूं। धन्यवाद आशान
नेटपोएटिका

2
मुझे लगता है मैं unbind के बाद फिर से प्रस्तुत करने के लिए नहीं कर सकते: \
CodeGuru

@FlyingAtom: यहां तक ​​कि मैं unbinding के बाद विचारों को फिर से प्रस्तुत करने में सक्षम नहीं हो रहा हूं। क्या आपको ऐसा करने का कोई तरीका मिला?
रायसा

। दृश्य $ el.removeData () अनबाइंड ()।;
अलेक्जेंडर मिल्स

2

मुझे लगता है कि बैकबोन से शुरू होने वाले अधिकांश लोग आपके कोड के अनुसार दृश्य बनाएंगे:

var view = new UserDetailView({model:this.model});

यह कोड ज़ोंबी दृश्य बनाता है, क्योंकि हम बिना किसी मौजूदा दृश्य के सफाई के बिना लगातार नया दृश्य बना सकते हैं। हालाँकि यह आपके ऐप में सभी बैकबोन दृश्यों के लिए view.dispose () कॉल करने के लिए सुविधाजनक नहीं है (खासकर अगर हम लूप के लिए विचार बनाते हैं)

मुझे लगता है कि नए दृश्य बनाने से पहले सफाई कोड डालने का सबसे अच्छा समय है। मेरा समाधान इस सफाई को करने के लिए एक सहायक बनाना है:

window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        // Cleanup view
        // Remove all of the view's delegated events
        VM.views[name].undelegateEvents();
        // Remove view from the DOM
        VM.views[name].remove();
        // Removes all callbacks on view
        VM.views[name].off();

        if (typeof VM.views[name].close === 'function') {
            VM.views[name].close();
        }
    }
    VM.views[name] = callback();
    return VM.views[name];
}

VM.reuseView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        return VM.views[name];
    }

    VM.views[name] = callback();
    return VM.views[name];
}

अपने दृश्य बनाने के लिए VM का उपयोग करने से view.dispose () को कॉल किए बिना किसी भी मौजूदा दृश्य को साफ करने में मदद मिलेगी। आप अपने कोड से एक छोटा संशोधन कर सकते हैं

var view = new UserDetailView({model:this.model});

सेवा

var view = VM.createView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

इसलिए यह आपके ऊपर है कि अगर आप लगातार इसे बनाने के बजाय व्यू का पुन: उपयोग करना चाहते हैं, जब तक कि दृश्य साफ है, तो आपको चिंता करने की आवश्यकता नहीं है। बस createView को reuseView में बदलें:

var view = VM.reuseView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

विस्तृत कोड और एट्रिब्यूशन https://github.com/thomasdao/Backbone-View-Manager पर पोस्ट किया गया है


Ive हाल ही में बड़े पैमाने पर रीढ़ की हड्डी के साथ काम कर रहा है और यह जब निर्माण या पुन: उपयोग करने वाले विचारों को संभालने के लिए ज़ोंबी विचारों से निपटने का सबसे fleshed आउट लगता है। मैं सामान्य रूप से डेरिक बेली के उदाहरणों का पालन करता हूं, लेकिन इस मामले में, यह अधिक लचीला लगता है। मेरा सवाल यह है कि इस तकनीक का उपयोग करने वाले अधिक लोग क्यों हैं?
MFD3000

हो सकता है क्योंकि वह बैकबोन में विशेषज्ञ है :)। मुझे लगता है कि यह तकनीक उपयोग करने के लिए काफी सरल और काफी सुरक्षित है, मैं इसका उपयोग कर रहा हूं और अब तक समस्या नहीं है :)
thomasdao

0

एक विकल्प यह है कि नए विचारों की एक श्रृंखला बनाने का विरोध किया जाए और फिर उन विचारों को अनदेखा किया जाए। आप इसे कुछ इस तरह पूरा करेंगे:

window.User = Backbone.Model.extend({
});

window.MyViewModel = Backbone.Model.extend({
});

window.myView = Backbone.View.extend({
    initialize: function(){
        this.model.on('change', this.alert, this); 
    },
    alert: function(){
        alert("changed"); 
    }
}); 

आपने myViewModel के लिए myView का मॉडल सेट किया होगा, जो उपयोगकर्ता मॉडल पर सेट किया जाएगा। इस तरह, यदि आप myViewModel को किसी अन्य उपयोगकर्ता के लिए सेट करते हैं (यानी, इसकी विशेषताओं को बदलते हुए) तो यह नई विशेषताओं के साथ दृश्य में रेंडर फ़ंक्शन को ट्रिगर कर सकता है।

एक समस्या यह है कि इससे मूल मॉडल का लिंक टूट जाता है। आप संग्रह ऑब्जेक्ट का उपयोग करके या व्यू मॉडल की विशेषता के रूप में उपयोगकर्ता मॉडल सेट करके इसे प्राप्त कर सकते हैं। फिर, यह myview.model.get ("मॉडल") के रूप में दृश्य में सुलभ होगा।


1
वैश्विक गुंजाइश को प्रदूषित करना एक अच्छा विचार नहीं है। आप विंडो नाम स्थान पर BB.Models और BB.View को तुरंत क्यों भेजेंगे?
वर्नोन

0

बच्चे के विचारों और स्मृति से वर्तमान विचारों को साफ़ करने के लिए इस पद्धति का उपयोग करें।

//FIRST EXTEND THE BACKBONE VIEW....
//Extending the backbone view...
Backbone.View.prototype.destroy_view = function()
{ 
   //for doing something before closing.....
   if (this.beforeClose) {
       this.beforeClose();
   }
   //For destroying the related child views...
   if (this.destroyChild)
   {
       this.destroyChild();
   }
   this.undelegateEvents();
   $(this.el).removeData().unbind(); 
  //Remove view from DOM
  this.remove();  
  Backbone.View.prototype.remove.call(this);
 }



//Function for destroying the child views...
Backbone.View.prototype.destroyChild  = function(){
   console.info("Closing the child views...");
   //Remember to push the child views of a parent view using this.childViews
   if(this.childViews){
      var len = this.childViews.length;
      for(var i=0; i<len; i++){
         this.childViews[i].destroy_view();
      }
   }//End of if statement
} //End of destroyChild function


//Now extending the Router ..
var Test_Routers = Backbone.Router.extend({

   //Always call this function before calling a route call function...
   closePreviousViews: function() {
       console.log("Closing the pervious in memory views...");
       if (this.currentView)
           this.currentView.destroy_view();
   },

   routes:{
       "test"    :  "testRoute"
   },

   testRoute: function(){
       //Always call this method before calling the route..
       this.closePreviousViews();
       .....
   }


   //Now calling the views...
   $(document).ready(function(e) {
      var Router = new Test_Routers();
      Backbone.history.start({root: "/"}); 
   });


  //Now showing how to push child views in parent views and setting of current views...
  var Test_View = Backbone.View.extend({
       initialize:function(){
          //Now setting the current view..
          Router.currentView = this;
         //If your views contains child views then first initialize...
         this.childViews = [];
         //Now push any child views you create in this parent view. 
         //It will automatically get deleted
         //this.childViews.push(childView);
       }
  });
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.