I got a problem with backbone.js collection.add used in a view initialize(); in my app when you change sections I remove my views but I dot not remove my collections used in that section (collections are initialized empty on app load).
If someone come back in my section I use the same collection, and initialize a this.collection.add() on it. However as a side effect the collection add 2 times the model in my new view now, is there a way to prevent that behavior?
Event stranger it only happen in Firefox.. Also, this double collection.add make no sense, since I clearly see that its not the same view number (even is it comes from the same variable.)
You have to manually unbind the event listeners when you change views - otherwise the event listeners will keep references to the removed views in the memory and they will never be garbage collected. In long term it can cause massive memory leaks and whole system slow downs as your app starts eating resources.
Currently there is no built in solution for that in Backbone so you have to handle it yourself .
pro tip :)
you can for example listen to route change from the view and if route changes then call a "onDestroy" method which could clean up all the references
other option is - instead of removing views - use jquery detach method which doesn't clean up all the jquery dom/custom events and reuse same view multiple times
Related
What are the advantages and disadvantages of the following 2 lines of code? I don't understand why there are 2 different ways to do the same thing.
this.listenTo(app.Todos, 'change:completed', this.filterOne);
app.Todos.on('change:completed', this.filterOne);
Also when using .on, how do I determine is the default context?
listenTo is the newer and better option because these listeners will be automatically removed for you during stopListening which is called when a view gets removed (via remove()). Prior to listenTo there was a really insidious problem with phantom views hanging around forever (leaking memory and causing misbehavior) because view methods were referenced as event listeners on models even though the view instances themselves were long gone and no longer in the DOM.
If you want to read the back story for listenTo, search the backbone github repository for listenTo and read through some of the longer issue discussions.
As to the default context, several things can end up bound to this:
if you do the binding via this.listenTo, it will always be the view instance (pointed out by Wim Leers in the comments)
without this.listenTo, the story gets complicated
For misc events, it will be the global object (best to avoid this)
for DOM events, it will be the source element just like in regular DOM event binding
If you provide an explicit context (the 3rd argument to foo.on), backbone will use that (thus this is a more robust approach)
If you use the ECMA standard function () {//your event handler}.bind(this), you can also manually control the context (also recommended)
As #mu pointed out, _.bind or $.proxy are available alternatives to ECMA function.bind
For backbone views, doing this.bindAll('onClick', ...) will ensure the view instance is the this context when any view methods are used as event handlers
any events wired up by using the view's standard events property will get bound for you automatically to the view instance by backbone (this is belt & suspenders with bindAll)
So to summarize into some guidelines:
use the events property whenever possible as it is concise and correct
use this.listenTo for all bindings to models and collections
any additional bindings remember to bind the context reliably using your preferred method. I usually use ECMA Function.bind because hey, standards, but there are several good options here.
With listenTo, the object whose events you want to listen to is passed as the first argument. In the case of on, it is actually a method on that object.
The advantages of listenTo over on are:
The listener keeps track of all the event handlers, making it easier to remove them all at once when needed.
The callback’s context is always set to the listener itself.
I am building a SPA and am trying to figure out a safe and clean way to delete all backbone entities when navigating away from one section of the application. I am aware of the Model.destroy(), View.remove() and Collection.reset() methods. My main concerns are:
Model.destroy() takes care of destroying the model on the server. Does one still need to manually delete the Javascript model?
Similariy, I realize that View.remove() will remove the view from the DOM. How should one safely get rid of the view object?
Collection.reset() clears the models in the collection. Will this also delete the underlying Javascript model objects or do they need to be explicitly deleted?
How would one get rid of the collection object itself?
My questions may seem simple to some but they have been confusing me for a while. I have not found any useful information regarding this exact problem which is why I decided to post here. I am also a relatively new to Javascript and am unaware of Javascript's garbage collection scheme. Does Javascript have a garbage collector and will it take care of deleting all such objects?
I am also looking into understanding the best way to remove Views.
View.remove() only removes the view from the DOM. My guess is that one still needs to take care of removing/destroying the underlying Model and unbinding all event listeners. Is that correct?
I often use _.bindAll to change the context of a function call. I have not come across a way to unbind this binding. My understanding says that this is unnecessary. Am I right?
Here is a good resource for learning about garbage collection in javascript:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
To summarize, the only thing you need to worry about with Backbone is removing custom event handlers in your view, and a common way to do this is to override remove and remove the handlers before calling Backbone.View.prototype.remove. You don't need to manually remove events that you declared in the events property of the view, if you are using that.
To be a little more specific, lets consider the three types of objects you are asking about in turn. If you are using the backbone router, then you are probably creating a view object in a router method, maybe assigning it to a var, and then setting the html somewhere on the page. When the user navigates away, a different router method is invoked, and the reference you created to this view is unreachable. It will get garbage collected assuming you didn't create it as a property on window or your root level object or something like that.
The remove method is about removing the view from the DOM. The garbage collector takes care of removing the view from memory. The remove method is a convenient place to put any cleanup code that you need to run before taking the view off the page, so unhook your custom event handlers here.
Likewise, model.destroy is not about destroying the model object stored in memory, its about sending an AJAX DELETE request to the server. Model objects are garbage collected like everything else, and will go away once they are unreachable. If the only references to a model are contained within a view, then removing that view will cause the model to be garbage collected. Same goes for collections.
And for your last point, the underscore bind will not be an issue. Its about binding context (the value of this that a function is invoked with), not about binding event handlers. Remove those event handlers as usual.
Model.destroy() takes care of destroying the model on the server. Does one still need to manually delete the Javascript model?
Yes, you still need to manually delete the model. Model.destroy() does not delete it.
Similariy, I realize that View.remove() will remove the view from the DOM. How should one safely get rid of the view object?
Remove all references to it. If nothing is pointing at your object it will get garbage collected. Generally speaking, unless you go out of your way to make sure references to an object stay alive (such as global variables) or get careless with .on() you should be ok.
Note that using .listenTo() on your views instead of .on() makes your life easier. Memory leaks with views used to be a big problem when we just had .on()(See here for more).
Collection.reset() clears the models in the collection. Will this also delete the underlying Javascript model objects or do they need to be explicitly deleted?
How would one get rid of the collection object itself?
Does Javascript have a garbage collector and will it take care of deleting all such objects?
As long as there's no remaining reference to your objects they will get garbage collected.
I already described the birds-eye view of this (make sure you're no longer referencing your objects!), but here is a pretty good article about garbage collection in the context of Backbone.
View.remove() only removes the view from the DOM. My guess is that one still needs to take care of removing/destroying the underlying Model and unbinding all event listeners. Is that correct?
Note that it also calls .stopListening(), which unbinds your listeners if you used .listenTo() in your views. You do not need to remove/destroy the underlying model to let it get GC'd, again as long as there are no remaining references to it. Using .listenTo() with a model is a reference to it, but if you .remove() your view that will remove that reference.
I often use _.bindAll to change the context of a function call. I have not come across a way to unbind this binding. My understanding says that this is unnecessary. Am I right?
It's not clear to me what you would be trying to achieve by "unbinding," so I would say that it isn't necessary.
When i call destroy on a marionette view and later try to use it throws an error saying it has been destroyed. Also when i remove the view from the DOM directly it also throws the same error. So i need to know how i can certain that the view is destroyed.
You can check whether a view has been destroyed or not using the view's isDestroyed property.
// Example
if (view.isDestroyed) {
view = new View({});
}
view.doSomething();
So #JonathanMiles is correct but I think some elaboration may help you and our Googlers.
You aptly mentioned two ways in which you may destroy a view.
Call this.destroy() on your view. The .destroy() method does several crucial things: 1. sets the view.isDestroyed bit so that Marionette can know that your view has been removed from the DOM; removes the view from the DOM and most bindings attached to it by calling view.remove() which eventually calls view.$el.remove(), and removes other bindings that view.$el.remove() does not (like Marionette ui elements and behaviors).
Simpy do this.$el.remove() on your view. This removes the HTML associated with the view from the DOM as well as events bound to that HTML, but not events that are created on cached HTML elements.
Now, with the first mentioned, it's obvious that Marionette will know that the view is destroyed. If you need to know whether the view is destroyed, like #JonathanMiles pointed out, simply obtain a reference to your view and check the value of view.isDestroyed.
But, if you decide to remove the view HTML from the DOM yourself, Marionette will not know if the view is not in the DOM. You may get errors, specifically since you may still have events bound to a view without HTML associated with it (although most of these situations would probably just pass silently unnoticed), but not from Marionette. Even worse, you may decide that to replace the rejected view, you'll spawn new views. The old view instance however will still live. While you won't have zombie events, this is a potential memory leak.
The moral of the story is don't simply do view.$el.remove(). Use Marionette's view.destroy() to completely free the view object for garbage collection, and reap the advantage of knowing if the view .isDestroyed.
Pretty cryptic question I admit.
I'm looking for references / best practice in updating views based on a GUI event.
Basically I'm seeing 2 different ways to do this:
every view-change is a reaction to a model-change
Gui event
viewhandler (custom or 2-way binding lib) that
updates Model based on view
the View has a listenTo defined on the MOdel that was updated, which gets invoked
do whatever DOM-changes we want
Do DOM-changes directly in the viewhandler
Gui event
viewhandler (custom or 2-way binding lib) that
updates Model based on view
do whatever DOM-changes we want
the View has a listenTo defined on the MOdel that was updated, which gets invoked
The fist approach seems the most clean to me: it can use validation rules on the model, before changing the DOM and the flow just feels better. However, I can't find any good references to back this up. What is generally considered best practice here?
I wouldn't know what is Overall Internet Agreement on that topic, but working on a fairly large Backbone application with complex views, the first approach is what has proven the most maintainable.
One way to see it is that as you know, Backbone's Views and Backbone's Routers are kinda sharing the workload of what would be an actual Controller in a standard MVC (say, Rails). What that means is that in a view, you'll typically have the logic to translate GUI events (click on X) to Model Change (do Y) and the logic to translate Model Changes (new state after Y) into DOM changes. That doesn't mean the logic needs to be in the same place, those are two separate things. Your model may be shared among multiple views and you need your View to react regardless of where the change is originating. You could also be waiting for the server to answer whatever, and so, the DOM update would be in a callback anyway.
A pattern I have been using a lot lately is to have a _renderUpdating() to update the DOM to let the user know that his action has been taking into account (add a spinner near to whatever triggered the change), but I let events do the actual re-rendering once the server has returned the new model.
There is nothing that prevents you from going with the 2nd approach in simple cases, but as your application grow, you'll be very happy to have more than just a single render() that erases everything (including subviews) and you'll be looking at having a clean break between view -> model (which is the role of a controller) and model -> view to allow for more flexibility.
I need to setup a quite huge Application with arround >20 main views plus forms. A mainview will at least contain one grid but can contain upto ten grids. There are also some mainviews that contains a Portal-Panel.
Now it comes that it take quite some time till a mainview opens which don't happends as I tested it with just one instead of >20. The Application lays within a Viewport with fit layout which holds a container with Borderlayout. The Mainview always render within the center while the other regions are used for navigation.
My first approach caching things within a tabpanel
My first approach was to got with a Tab-Panel with hidden tabs. Parallel to that I manage a MixedCollection where I make lookups if the View has already been inserted into the tabpanel or has to be created. If it has already been inserted I fetch the positon from the MixedCollection and run the setActiveTab(). But it seems worthless cause it takes all the same time to insert a new mainview or to activate a existing with setActiveTab().
So what I am doing wrong and how can I make this better?
Edit
The problem seems to come from the rendertime and that the component seems to get reredenred each time setActiveTab() is used. It takes up to 2-3 secs to render a view into the center panel. Therefore I thought I can be speed it up by caching a already created view so that the rendering and sizing didn't need to be done. I guess I should mention that in the north region a menu is also rendered each time, which not get's chached but that shouldn't matter, shouldn't it?
How the views are switched
I have a extra controller that manages the menue-view and the main-view changes. For that the menue gets removed from the container and the new menue is added and for the main-view a lookup is done in the mentioned mixed-collection if the view has already been created and if so the tab-index is received and the tab activated. If not the view get added as new tab and is afterwards added with it's ident and index to the mixed collection.
It may help you to take a look at suspendLayouts() and resumeLayouts()
You run Ext.suspendLayouts(); before you begin changing the views and Ext.resumeLayouts(true) after you are done with all views.
You should also check for overnesting, meaning you have nested to much components into each other.
Example:
If a grid is the only component within a tabpanel then it would be overnesting if you place that grid in a extra panel and then into the tabpanel.
Firstly what I found that non active tabs will be populated anyway. I don't know why. But my stores for not active tabs loaded anyway.
Second, it is really doesn't matter the size of your app. We have a lot of views and everything works pretty fast.
In most cases it is depends:
First is nesting - review your code. Less nesting more speed. For example instead of panel if you dont need all stuff that panel provide- use container. Because all this stuff is add more divs to your dom.
Depends how you create your view. Without your code it is really hard to say, why it is slow. Maybe you create your view every time when you open tab, or go to another view.
As mention #sra try to use suspendLayouts - it means that your browser will render your stuff only resumeLayouts, instead of rerender everything everytime when you add any component.
If you use windows - use closeAction:hide instead of destroy.
Not use Ext.getCmp(). I also hear that refs can slow down application because they start searching your component from the body. But it not proved info :) Use component.down('id'), component.up('xtype') it will search only from your component not from the body.
Not create your view from controller everytime when you do something using Ext.create('Panel'). It means it will created everytime.
Use less global events because this is also slowdown your app.
It is really hard to say what is the issue in your case without code. This is only few point that can help you. But my suggestion to looking to nesting and how and where you create a view.