I have a main section of code that initialises of all my views. How should I handle / listen / bind events from the main sections (probably will turn this into a main app view eventually) with elements of these initialised views? I could pass a reference of a view into another view and do something from inside that view but I'd rather have all this logic in the main / parent. Is this possible or good practice?
If they are all DOM events. No, each view should handle it's own DOM events.
If one view needs to know if something is happening in another view...well, there're lots of ways to do it. I'd usually prefer the subscriber/publisher pattern.
See my answer to another question for code examples:
Call a function in another Marionette.ItemView
You do not need Marionette for that.
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 dealing with some poorly planned, poorly written code right now, and I don't have the time to do a rewrite. I need to be able to access a view that is rendered on the same page, but otherwise separate from the view I need to access it from.
Given the element that contains the external view, how can I access it? Is there a better way to make the view available (without rewriting the way they are wired together)?
I suppose that you are using marionette. Your question is in particular a design question rather than a simple line of code to do another nasty thing. Ideally , something like $("#element") is not a good practice because it goes through whole of your DOM to figure out the element.
Rather I would suggest you the below approach which works well in most of the places given both your views are view1 and view2 :
Make a Layout View in specifying the regions which contain both these views.
Now all you have to do is initialize both these views inside the above created Layout View.
When you want to access anything from view1 to view2, all you do inside view1 is something like below:
From the child Item view of layout :
this.triggerMethod('show:message', msg);
and The layout has some code like :
childEvents: {
'show:message': function (childView, msg) {
view2.dosomething();
}
}
This way you can keep the views functionality separate from each other and also you code remains clean.
I guess for this you can also look at Marionette documentation http://marionettejs.com/docs/marionette.layoutview.html
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.
How to send data from one view to another which are on different urls?
A little bit strange question, but OK, let's try to imagine your problem in complex and give an answer.
Imagine application for reading books (like iBooks in web). We have one parent View called ApplicationView which creates several children views and some of them are BookshelfView (available on #bookshelf url) and BookView (available on #book/:id url).
Now, you mark your book as unread from your BookView and you know that your BookshelfView should change the appearance of this book. OK, it is not "moving data from one view to another". You just change the state of your model and your views catch this "change" event and update their html.
Let's describe more complicated situation. Imagine the same application. But at this time you decide to switch portrait orientation to landscape orientation. You make it in one view and you want this change to affect other application views. This can be done in several ways:
Views should stay loosely coupled you should use some kind of mediator pattern.
From Backbone 0.9.9 you can use global Backbone object as Mediator, as Backbone supports Events interface
You can create and additional model for Mediation between two or more views but there is more beautiful solution:
If you have one parent view for several child views you already have that mediator. Just send events to parent view from one view and listen to that events on parent from other view.
I also recommend you check this question
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