How to unbind an external event when a controller/$scope is destroyed? - events

Supposed I have a controller for a page that is being rendered into an ng-view.
This controller binds to some events of an external source (such as an application-wide message bus) to update its model. This basically works very easy:
function MyController ($scope) {
$scope.bar = '…';
externalSource.on('foo', function (data) {
$scope.$apply(function () {
$scope.bar = data.bar;
});
});
}
The problem is: How do I unbind the controller from the external source once the view it it is associated with is not longer shown?
Is there something such as a dispose event or something similar?
Or is my approach completely wrong, and I should deal with something like that somehow else? If so, how?

To execute event unbind when controller's scope is got destroyed use:
$scope.$on('$destroy', function () { /* Unbind code here */ });
See Scope docs for more info

Use $routeChangeStart or $routeChangeSuccess events:
function MyController ($scope) {
$scope.bar = '…';
externalSource.on('foo', function (data) {
$scope.$apply(function () {
$scope.bar = data.bar;
});
});
$scope.$on('$routeChangeStart', function(next, current){
// unregister listener
// externalSource.off ....
});
}
... or $destroy event:
$scope.$on('$destroy', function(){
// unregister listener
// externalSource.off ....
});

Related

emberjs: how to trigger a custom event in a View

I would like to turn a primitive event (a click) into a semantic event, like "deleteTodo"
This is described here, but not how to implement :(
I have the following code:
App.TodoView = Em.View.extend({
click: function(e) {
this.trigger("deleteTodo");
}
});
App.Router.map(function(match) {
match('/').to('index');
});
App.IndexRoute = Ember.Route.extend({
deleteTodo: function(e) {
// this code is never executed :(
}
}) ;
After I perform the 'click', I see that the TodoView click function is called, but not the deleteTodo function from the IndexRoute. Any suggestions what might go wrong here ?
CHeers
You can use this.get("controller").send("deleteTodo"). This will send a message to the controller, if the controller doesn't handle deleteTodo it will bubble to the router and be handled there.
click: function(e) {
this.get('controller').send("deleteTodo");
}
In your router you will also need to define the event:
events: {
doStuff: function(e) {
alert("Do stuff") ;
}
}
http://jsfiddle.net/9Xasr/7/
I would typically do record deletion in the controller. Seems like putting that in a router event would not be ideal.

How to use events and event handlers inside a jquery plugin?

I'm triyng to build a simple animation jQuery-plugin. The main idea is to take an element and manipulate it in some way repeatedly in a fixed intervall which would be the fps of the animation.
I wanted to accomplish this through events. Instead of using loops like for() or while() I want to repeat certain actions through triggering events. The idea behind this: I eventualy want to be able to call multiple actions on certain events, like starting a second animation when the first is done, or even starting it when one animation-sequence is on a certain frame.
Now I tried the following (very simplified version of the plugin):
(function($) {
$.fn.animation = function() {
obj = this;
pause = 1000 / 12; //-> 12fps
function setup(o) {
o.doSomething().trigger('allSetUp');
}
function doStep(o, dt) {
o.doSomething().delay(dt).trigger('stepDone');
}
function sequenceFinished(o) {
o.trigger('startOver');
}
function checkProgress(o) {
o.on({
'allSetup': function(event) {
console.log(event); //check event
doStep(o, pause);
},
'stepDone': function(event) {
console.log(event); //check event
doStep(o, pause);
},
'startOver': function(event) {
console.log(event); //check event
resetAll(o);
}
});
}
function resetAll(o) {
/*<-
reset stuff here
->*/
//then start over again
setup(o);
}
return this.each(function() {
setup(obj);
checkProgress(obj);
});
};
})(jQuery);
Then i call the animation like this:
$(document).ready(function() {
$('#object').animation();
});
And then – nothing happens. No events get fired. My question: why? Is it not possible to use events like this inside of a jQuery plugin? Do I have to trigger them 'manualy' in $(document).ready() (what I would not prefer, because it would be a completely different thing – controling the animation from outside the plugin. Instead I would like to use the events inside the plugin to have a certain level of 'self-control' inside the plugin).
I feel like I'm missing some fundamental thing about custom events (note: I'm still quite new to this) and how to use them...
Thx for any help.
SOLUTION:
The event handling and triggering actually works, I just had to call the checkProgress function first:
Instead of
return this.each(function() {
setup(obj);
checkProgress(obj);
});
I had to do this:
return this.each(function() {
checkProgress(obj);
setup(obj);
});
So the event listening function has to be called before any event gets triggered, what of course makes perfect sense...
You need set event on your DOM model for instance:
$('#foo').bind('custom', function(event, param1, param2) {
alert('My trigger')
});
$('#foo').on('click', function(){ $(this).trigger('custom');});​
You DOM element should know when he should fire your trigger.
Please note that in your plugin you don't call any internal function - ONLY DECLARATION

ExtJS 4.1 Call One Controller From Another

Note: I'm a total ignoramus regarding javascript.
I've broken my ExtJS 4.1 MVC app out into several controllers like:
/app/controller/Auth
| |Quiz
| |Result
| |Blah...
|model/...
I want to respond to an "event", not a DOM Event, rather a Ext.form.action.Submit.success event by calling functions in both my Auth and Quiz controllers. The summarized code for the first part is here:
// File: app/controller/Auth.js
attemptLogin : function() {
var form = Ext.ComponentQuery.query('#loginpanel')[0].form;
if (form.isValid()) {
form.submit({
success : function(form, action) {
// THIS IS THE FUNCTION FROM THE CURRENT CONTROLLER
Assessor.controller.Auth.prototype.finishLogin();
// THIS IS THE FUNCTION FROM THE OTHER CONTROLLER
Assessor.controller.Quiz.prototype.setupAssessment();
},
This works but feels wrong. Is there a proper way to do this? It seems like I should fire a unique event that is listened to by both controllers, but I can't understand how to do that with Ext.Event. Any guidance?
Thanks! I'm really grateful for all the great ideas and advice.
It makes sense to me to fire a custom event from the form and simply listen to it in both your controllers, like what you said here:
It seems like I should fire a unique event that is listened to by both
controllers
// File: app/controller/Auth.js
attemptLogin : function() {
var form = Ext.ComponentQuery.down('#loginpanel').form;
if (form.isValid()) {
form.submit({
success : function(form, action) {
// fire the event from the form panel
form.owner.fireEvent('loginsuccess', form.owner);
},
Then in each of your controllers you can listen to it with Controller#control, like this:
Ext.define('YourApp.controller.Auth', {
extend: 'Ext.app.Controller',
init: function() {
var me = this;
me.control({
'#loginpanel': {
loginsuccess: me.someHandler
}
});
},
someHandler: function(form) {
//whatever needs to be done
console.log(form);
}
}
And then add the same thing to your Quiz controller:
Ext.define('YourApp.controller.Quiz', {
extend: 'Ext.app.Controller',
init: function() {
var me = this;
me.control({
'#loginpanel': {
loginsuccess: me.someOtherHandler
}
});
},
someOtherHandler: function(form) {
//whatever needs to be done
console.log(form);
}
}
I've used this approach successfully in 4.1.0 and 4.1.1
It really should be
Assessor.controller.Auth.prototype.finishLogin.apply(this, arguments)
or something along these lines (in order to have a correct this reference that points to the 'owner' of the method, the controller object)
However, why do you use this unorthodox way to call the current controller's method. Just set the scope for the success callback, then call this.finishLogin().
form.submit({
success : function(form, action) {
// THIS IS THE FUNCTION FROM THE CURRENT CONTROLLER
this.finishLogin();
...
},
scope: this
});
Also, you can retrieve another controller instance using Controller#getController.
this.getController('Assessor.controller.quiz').setupAssignment();
Then, if your controller methods are not depending on each other, you could make them both listen to the same event.
Another solution is to fire a custom event once the login is finished. You could do that on the application object
this.application.fireEvent('logincomplete');
and in your controller's init method:
this.application.mon('logincomplete', this.setupAssignment, this);
Please note that you cannot listen to those events via Controller#control - see Alexander Tokarev's blog post for a patch to Ext to achieve this.
There is no standard way to fire events between controllers, but it's possible with some custom hacks. See my recent blog post.
I have also been looking for this and all you need is Asanda.app.getController('quiz').setupAssignment();, where Asanda is the name of your app
You should use a MessageBus if you have to send events between controllers:
Ext.define('MyApp.utils.MessageBus', {
extend : 'Ext.util.Observable'
});
store the message bus in a global var
MsgBus = Ext.create('MyApp.utils.MessageBus');
Where you have to send events:
MsgBus.fireEvent('eventName',eventArg_1,eventArg_2);
Where you have to receive events:
MsgBus.on('eventName', functionHandler,scope); //scope is not mandatory
...
functionHandler:function(eventArg_1,eventArg_2){
...
//do whatever you want
...
}

Backbone.js - event trigger not work after rendering other views

There's a addPost function in my router. I don't want to re-create the postAddView every time the function is invoked:
addPost: function () {
var that = this;
if (!this.postAddView) {
this.postAddView = new PostAddView({
model: new Post()
});
this.postAddView.on('back', function () {
that.navigate('#/post/list', { trigger: true });
});
}
this.elms['page-content'].html(this.postAddView.render().el);
}
Here's the PostAddView:
PostAddView = backbone.View.extend({
events: {
'click #post-add-back': 'back'
}
, back: function (e) {
e.preventDefault();
this.trigger('back');
}
});
The first time the postAddView is rendered, the event trigger works well. However, after rendering other views to page-content and render postAddView back, the event trigger won't be trigger anymore. The following version of addPost works well, though.
addPost: function () {
var that = this, view;
view = new PostAddView({
model: new Post()
});
this.elms['page-content'].html(view.render().el);
view.on('back', function () {
delete view;
that.navigate('#/post/list', { trigger: true });
});
}
Somewhere you are calling jQuery's remove and that
In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed.
so the delegate call that Backbone uses to bind events to your postAddView.el will be lost. Then, when you re-add your postAddView.el, there are is no delegate attached anymore and no events are triggered. Note that Backbone.View's standard remove method calls jQuery's remove; a few other things in jQuery, just as empty will do similar things to event handlers. So the actual function call that is killing your delegate could be hidden deep inside something else.
You could try calling delegateEvents manually:
this.elms['page-content'].html(this.postAddView.render().el);
this.postAddView.delegateEvents();
or better, just throw the view away and create a new one every time you need it. Your view objects should be pretty light weight so creating new ones should be cheap and a lot less hassle than trying to keep track of the existing views by hand.
If you really want to reuse the current DOM and View you do not need to set again and again the element as you are doing, everything that you call .html() you are destroying the DOM of the View and generating again and losing events. Also I prefer always to add the "el" in the DOM before render the View. I will have your function in this way:
addPost: function () {
if (!this.postAddView) {
this.postAddView = new PostAddView({
model: new Post()
});
this.postAddView.on('back', this.onBack);
this.elms['page-content'].html(this.postAddView.el);
}
this.postAddView.render();
},
onBack : function () {
this.navigate('#/post/list', { trigger: true });
}
I'm not fan of the use of local variables to refer to "this". If all of your Views uses _.bindAll(this) in the initialize method you could bind your events to your view and could use this(check how I transformed onBack).
With my code there is not a need to manually call this.delegateEvents()

Live jQuery events not firing for dynamic element

Why are none of the live (or dead) events I bind to a dynamic element firing?
(function ($) {
$.fn.myPlugin = function () {
var $filterBox = $("<input type='text'>").live("click", function () {
alert("Clicked");
});
this.before($filterBox); // insert into DOM before current element
return this; // keep chain
};
})(jQuery);
I am calling myPlugin on several <select> elements. I thought it would work without the Live plugin if I bound it before adding the element to the DOM, but not even the live events are firing. Is it because my element has no ID?
Edit:
The following does not work either:
var $filterBox = $("<input type='text'>").bind("click", function () {
alert("Clicked");
});
.live() works off a selector (since it checks the target against the selector at the time the event happens), you can't attach it directly to an element...you should just use .click() in these cases:
(function ($) {
$.fn.myPlugin = function () {
var $filterBox = $("<input type='text'>").click(function () {
alert("Clicked");
});
this.before($filterBox); // insert into DOM before current element
return this; // keep chain
};
})(jQuery);
You can try it out here, or a bit shorter with .insertBefore():
(function ($) {
$.fn.myPlugin = function () {
$("<input type='text'>").click(function () {
alert("Clicked");
}).insertBefore(this);
return this;
};
})(jQuery);
You can test it here.
The live method works with selectors, not detached elements.
You can handle the normal (non-live) click event, and it should work fine.
Why not just bind it? http://jsfiddle.net/9WvpA/
Can it be just because "<input type='text'>" is not a valid HTML? You have not closed your tag. However, I am not sure whether jQuery is unable to close it for you.
Solved by not using global variables that replaced each other, and iterating over each element in question with this.each(...):
(function ($) {
$.fn.myPlugin = function () {
return this.each(function () {
// do stuff
});
};
})(jQuery);

Resources