I've created a Behavior which creates a CompositeView and the CompositeView then creates ItemViews from a collection. The ItemViews have a few events attached to them. These views trigger an event via the Application.vent event aggregator. I then listen for the event ("click:unlink") in the Controller which created the original view that contains the behavior.
But I think I'm starting to see zombie events, at least I assume they're zombie events. When I click the link that triggers the "click:unlink" event, the function that triggers the event I'm only seeing that called once, but I'm seeing the function defined in the listener fired multiple times.
So my question, when a view defines behaviors and those behaviors create their own views, When the original view is destroyed for whatever reason, do the views associated with the attached behavior also get destroyed? I added some onDestroy functions on the views created by the behavior, but I never see them called. Maybe I'm not understanding how behaviors are supposed to be used?
A behavior will not destroy any views it has created, the only way a view created by a behavior could be destroyed is if it was placed in a region which was either emptied or replaced with another view.
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'm currently working with a CompositeView. The problem is, when I call destroy() on one of the child views from the CollectionView part, it still remains in the this.children._view obejct, even do it disappears from the screen.
If I try to do anything with it (let's say call destroy() again) than it says, it has already been destroyed, but than why doesn't it get deleted from the object?
Thanks in advance.
Child views are not parent aware
This may sound annoying, but it's set up like that by design, since a CollectionView child may also exists on its own. As far as I can't tell children of a Collection View are not aware that they are children (except for the case that the model is destroyed [and thus removed from permanent storage]).
CollectionView.removeChildView can help
However, Marionette does expose a CollectionView method that is made exactly for what you want to do, CollectionView.removeChildView(view).
CollectionView.removeChildView will remove the view from the DOM, destroy the view (remove any listeners on the view) and remove it from the children container. Just pass in one of your views.
If you need some tips on how to trigger CollectionView.removeChildView on an event on your child view, drop me a comment.
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.
I'm building a project using the Backbone global as an event bus. The idea is that a view will trigger a custom event and pass in some data, then whatever collection is listening for that event will use the data to create a model and add that model to itself, and give some kind of message.
I notice that if I start from a new instance of my application and trigger one of these events, then in Chrome Dev Tools, Backbone._events looks something like this: {'add_person': [Object] } and the appropriate event handler runs and processes that one object in the value array. So far, so good.
Now, let's say I trigger that event handler again. What I've noticed is that Backbone._events looks like {'add_person': [Object, Object]} and the event handler runs twice, presumably on both members of the value array, including the first which was already processed.
My question is how do I prevent this behavior, short of binding the event with once and re-binding it after each time it's called? Thanks.
I'm not entirely sure what your setup is, i.e. how you're triggering and listening to these events you're firing but it sounds like you might be doing something like:
GlobalEventBus.on('add_person', this.addPerson, this);
Assuming you're binding to the event using on this could well be your problem if you're not manually unbinding the listener when your view is destroyed.
You're better off using Backbone's listenTo when listening to events that are triggered from an object outside the object you're listening in.
So, for example, in your collection you'd instead have:
this.listenTo(GlobalEventBus, 'add_person', this.addPerson)
Again though, i'm not entirely sure what your setup is.
See this codepen for a working example.
Only one events is triggered each time but keep in mind that only one model is being added because each model is a duplicate and has the same id. Assuming each model was unique it would be added to the collection.
Some further info on listenTo
I am working with the Infragistics XamDatagrid. When an event takes place in the view (new record updated), I would like to call a method in the view model. I can't just listen to the CollectionChanged event of the source in the VM because that event gets triggered once the user begins editing the new row in the grid. The RecordUpdating event gets called when the user finished the edits.
What I am doing now is accessing the DataContext directly and calling the method. The problem is that there is nowhere I can find to attach a Command to the gird that would tigger on the event I am interested, nothing like the Command of a Button.
Generally, what is the best way to call into a VM from a view when there is no Command parameter I can set in the view?
Code-behind glue is fine in most cases, but if you find yourself gluing the same type of thing repeatedly, you can look into Attached Behaviors. Here's one old-school approach the introduces the concepts, but doesn't use the new Behavior class: http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx
Here is an example using the new Behavior class, including a demonstration of how it integrates with Blend: http://geekswithblogs.net/lbugnion/archive/2009/04/05/using-a-behavior-to-magnify-your-wpf-applications.aspx