I am using durandal and requirejs to compose my viewmodels. I am also hooking into the composition life-cycle callback method deactivate every time I navigate away from the view. I want to dispose of my viewmodel in this method.
I've tried delete this, this = undefined but they don't seem to work.
I am also using the durandal event aggregator like this:
self.activate = () => {
App.trigger("testEvent");
};
App.on("testEvent").then(() =>
{
alert('Event Triggered!');
});
So every time the viewmodel is loaded, the event will be triggered. Now, if I the navigate away from the view, then navigate back (hence the viewmodel will be loaded again), then the event will be triggered twice. If I navigate to the view for the 3rd time, the event will be triggered 3 times and so on and so forth. So the previous viewmodels are still present, which is why the the durandal event is being triggered by each viewmodel. Therefore, to fix this issue, I need to dispose of the viewmodels in deactivate, how can I do this?
Note. the viewmodels in question are transient, not singleton.
Durandal also provides an "attached" lifecycle hook which only gets triggers when the view is attached in the DOM. If you place your event subscriptions here they should only get subscribed to the one time.
http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks
EDIT:
As far as unsubscribing from events goes, if you save the return value from the subscription you can call .off later in your deactivate or detached methods
var subscription = App.on("testEvent");
...
subscription.off();
http://durandaljs.com/documentation/Leveraging-Publish-Subscribe.html
The viewmodel itself is just a managed javascript object and will be disposed automatically after all references to it disappear.
It's probably keeping a reference to your view model because you are subscribing to the event 'testEvent' but never unsubscribe. I'm not an expert in how it would dispose in javascript but have come across similar scenarios in .net
Your event won't get triggered three times, it would get triggered once but the event handler function will get called per subscription so after 3 navigations, 3 times.
Try unsubscribing in the deactivate method and see if it works. This will at least stop the event handler firing three times. I'm not certain this will dispose of your view model correctly though.
Related
Currently using ExtJS 4 and I am trying to use the controller for implementing all events but there are some events that i am handling in the Window itself which are the close and destroy events. I need to execute some code here.
Is this bad practice ? If the events happen in the Window (an instance of Ext.window.Window).
Should I but forwarding these events to the controller to handle ?
I am unsure the correct way of doing this but I presume I would have to get a reference to the controller from my "window" in it's event and then call fireEvent on the controller?
What is the best practice here?
I am using ExtJS 4.2 so cannot use MVVM.
ExtJS seems to let me implement the events directly in "Components" but following the MVC pattern, is this not bad practice and everything should really pass through the controller.
The controller or viewport should handle the event.
You don't need a reference to the controller inside the window.
If you want custom events, you need to fire the event from inside the window like:
this.fireEvent("myCustomEvent"[,elementThatTriggeredTheEvent]);
Then you then need to listen to the event inside the controller using a reference to the window:
yourWindow.on("myCustomEvent",this.myCustomEventHandler[,scope]);
There are also ways to listen to events without having a reference to the event triggering element.
Edit- Regarding your comment:
If you need to do stuff on afterlayout regarding elements inside the window, then i would do it inside the window. If you want to do stuff to elements that are not defined inside the window, then let the controller, that instantiated the elements, handle it. In ExtJS 5 there is something called a ViewController. I don't know if it's already in 4.2. You could utilize the ViewController to split the stuff that happens in your window. Here is a link on that:
https://www.sencha.com/blog/using-viewcontrollers-in-ext-js-5/
EDIT 2:
If you don't want custom events, just listen with the controller to the events the window already throws on important occasions.
I'm playing a bit with Backbone.js and Backbone.Marionette and I would like to know what's the difference between trigger and triggerMethod.
In particular, is there any rule of thumb to decide when use the former or the latter?
In my mind events, for example, are useful to communicate between a DOM element and its view.
triggerMethod is used in Marionette to update in cascade different components, e.g. a layout calls the show method to its children (children respond to onShow). So, for me its the same as calling a direct method on it. Is this true?
What about trigger?
Thanks you in advance.
There isn't a huge difference, and it simply depends on what you want to do...
trigger will trigger an event
triggerMethod will trigger an event AND call a corresponding method according to naming convention (see https://marionettejs.com/docs/v2.1.0/marionette.functions.html#marionettetriggermethod)
Obviously, if you only want to trigger an event, you'd use trigger. But using trigger you also create a "home made" triggerMethod implementation: trigger the event, then have a listener that will call the function you want.
So what about triggerMethod ? As mentioned above, it will trigger an event and call a method. So if your only objective is to call the method in the first place, there isn't necessarily a need for using triggerMethod.
So why would one use triggerMethod at all? Because it gives you "hooks" to add functionality with event listeners. In my book on Marionette, for example, there is a triggerMethod call in https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/edit/edit_controller.js#L24 to display error messages on a form. The same could be achieved by simply calling
view.onFormDataInvalid(contact.validationError);
But as mentioned above, triggerMethod give us a "hook" for later use. For example, let's say I want to add logging of user errors: I can simply add a listener to my view:
initialize: function(){
this.on("form:data:invalid", function(validationError){
// log error here
}
}
This additonal functionality can be added without impacting the rest of the code, because we've used triggerMethod instead of a direct method call. In addition, it will be easier to test later (small tests, with single point of failure):
test that "form:data:invalid" event is triggered when a user enters incorrect info
test that when "form:data:invalid" event is triggered, error gets logged
etc.
trigger(name)
Part of Backbone.js
Triggers event using the name passed in
triggerMethod(name)
Part of Marionnete.js
Does everything trigger(name) does
Also calls methods using a predefined naming convention.
eg. triggerMethod('foo') will call onFoo()
eg. triggerMethod('foo:bar') will call onFooBar()
I have a view that defines several events like this:
events: {
'click .js-icon-remove': 'removeFilter',
'change .select-control': 'updateFilters',
'click #btn_search': 'requestSearch',
'click #btn_add_search': 'requestSaveSearch'
}
The three click events are on buttons or links while the change event is on a dropdown. When the view first renders, all events work fine. However, when I go to a different page and then come back to this view, the dropdown event is lost. If I reload the browser, things work fine again. They even work fine multiple times, not just the first time. It's just when I re-render the view that the event binding is lost.
I have tried changing the event from a change to a click, or from the class to the element (select rather than .select-control). All things will work the first time the view is rendered and that's it.
This is getting into how event binding works in Backbone and the DOM.
When a view renders, and it's $el is added to the DOM, backbone binds the events automatically. However, once you remove the view's $el from the dom, those events will then need to be re-bound when the $el is added back into the dom. This is what your delegateEvents() method is doing.
A best practice when working in backbone is to make views disposable, eg don't keep them around after they are removed from the DOM. When you navigate away from the view, and remove it from the dom, then navigate back, you should re-instantiate the view, render it, and add it to the dom again. You can keep you models and collections around, so you don't have refetch data, but views work better when they are either left in the dom or are recreated each time they are added/ removed.
I highly recommend Marionette.js, and specifically it's region class. It saves you a lot of boilerplate code when it comes to view handling.
Basically, I am trying to check if the previous page on every route was equal to one specific page on my site. If it is, then I want to keep the person on that view, otherwise, they can proceed to the other view.
Is there a way in a Backbone.js router, to fire off a global event that will fire before the route callback is executed?
Or, is there a way to have a catch all route that does some checking then forwards to the appropriate route.
I think overridding Backbone.History.navigate and only invoking the superclass method conditionally might work. You could also add an additional event handler for window.hashChange and window.pushState events wired up before the backbone history, and prevent the default propagation of those events if your criteria match.
When I select an element on my page, I am requesting Json and firing off animations for page transitions using goog.fx.dom.FadeInAndShow and goog.fx.dom.FadeOutAndHide.
If one were to click on an element, and then quickly clicks on a different element, the request is canceled and the data from the most recent click is populated, however, the animations are not quite finished from the previous click, which cause the incoming information the be effected by the prior animations END.
I'm wondering if there is something like getAnimations on the component that would have the animations and then be able to .destroy() them before they finish.
If your animations are tied to a goog.ui.Component (or any other object that extends goog.Disposable), then after creating each animation, you could register the animation with the component via the component/disposable's registerDisposable() method.
That way, you could dispose the component (by invoking its dispose() method), which would invoke dispose() on all Disposables registered with it. Looking at goog.fx.Animation, when dispose() is invoked, its stop() method is invoked, so I believe this should work.
You could also just create a goog.Disposable for the purpose of registering animations on it and disposing all of them from one place. That said, if you register a lot of animations and don't call dispose until long after they are needed, you will have a memory leak because this will prevent the Animations from being garbage collected.