Spine javascript unable to unbind proxied callbacks - javascript

I am using spine javascript library for a standalone javascript application.
I heavily use the publisher/subscriber model and bind proxied callbacks to spine models.
When I try to unbind a proxied callback, it doesnt unbind it. This happens only with proxied callbacks.
A demo code
var Listener = Spine.Model.sub({
onChange : function(){this.log("Hooray!!")},
log:function(msg){console.log("Rxed event"+msg);},
bind: function(){SomeModel.bind("onChange",this.proxy(this.onChange));},
unBind:function(){SomeModel.unbind("onChange",this.proxy(this.onChange));}
});
var listener = new Listener();
when listener.bind() is called it binds it correctly and callbacks are correct.
but when listener.unBind() is called, the unbind doesnt happen.
If the callback was not proxied, it works but I need the correct context for the callback and so I need to proxy it.
Any help would be greatly appreciated.

You should do Spine.Class.sub since you don't need a model.
Secondly, change the unbind to: SomeModel.unbind("onChange");.
This way all callbacks are cleared. But you are right ... if you wrap the unbind with or without the proxy call it won't work. So I hope you don't have a need to unbind specific callbacks.
I made a jsfiddle to easily test it:
http://jsfiddle.net/SpoBo/cmUmT/2/
edit:
http://jsfiddle.net/SpoBo/cmUmT/3/
By using underscore's bindAll functionality it works. I guess the proxy does something odd which prevents the unbind function from successfully comparing callbacks.
http://jsfiddle.net/SpoBo/cmUmT/7/
It's recommended to use CoffeeScript though to write Spine and CS has a solution of itself for this problem. Just use the 'fat arrows'. However, if you can't use CS you can just use the code that CS generates. I solved it this way in version 7.
Basically, it creates a __bind function which returns a proxied function from a closure.
After that you override the original function with the proxied function.
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }
someObject.onChange = __bind(someObject.onChange, someObject);
And now onChange will always be called from the context of someObject. Pretty cool stuff! But check the jsFiddle for a working demo.

Related

Javascript: addEventListener(), removeEventListener() and bind()

I want to add and event listener, I want the function the event listener calls to be bound to the calling scope and I want to be able to remove the listener at some arbitrary date in the future.
The obvious thing doesn't work:
function Thing(){
this.thingINeed = "important!"
}
// the function that does the thing.
Thing.prototype.handlerFunction = function(e){
console.log(this.thingINeed)
e.preventDefault;
}
// do the binding.
window.document.body.addEventListener('click', this.handlerFunction.bind());
// sometime later...this is my best guess. The event listener remains active.
window.removeEventListener('click', this.handlerFunction.bind());
// this also doesn't work:
window.removeEventListener('click', this.handlerFunction);
So I flogged together some code that does work:
function Thing(){
this.thingINeed = "important!"
}
Thing.prototype.handlerFunction = function(e){
console.log(this.thingINeed);
e.preventDefault;
}
// Where the 'magic' happens...
this.boundHandlerFunction = this.handlerFunction.bind(this);
window.document.body.addEventListener('click', this.boundHandlerFunction);
// sometime later...
window.removeEventListener('click', this.boundHandlerFunction);
MDN goes into some detail on matching event listeners with removal, but it doesn't mention .bind() and I can't find any examples of other people doing it this way. The code isn't exactly obvious without extensive commenting.
Is there a better way?
jQuery event listeners can be named, which makes them really easy to remove, but that isn't possible with vanilla?
Thanks.
The issue boils down to - Function.prototype.bind returns a new function. It works when you set the bound function to a variable and use it in both addEventListener and removeEventListener because both are referencing the same function. The first block of code does not work because they are referencing different functions. Here is a contrived example:
function foo () {}
// does not work because foo.bind() returns a new function each time
// these functions are not the same object
document.addEventListener('click', foo.bind())
document.removeEventListener('click', foo.bind())
//does work because both reference the same function
var boundFoo = foo.bind()
document.addEventListener('click', boundFoo)
document.removeEventListener('click', boundFoo)
I can't speak much to how jQuery handles events under the hood, but there is no getting around this behavior in vanilla JS.

Understanding specific $scope.$apply(fn) implementation

What does the following function wrapped by scope.$apply do? I can't seem to find the answer to this, but I see examples where it is used in directives.
scope.$apply(function() {
fn(scope, {
$event: evt
})
});
The closest explanation I could find implies that this might be used when the event that you want to respond to is not handled by Angular directives. Here is the explanation I am referring to.
If someone could provide the intended use of this pattern and what it means, it would be appreciated.
EDIT 1
I should have seen this earlier. Must need more sleep.
Since my example is not a complete working one. It makes a lot more sense after looking at the referenced explanation carefully.
The fn(scope, {$event: evt}) call is invoking the parsed reference to a custom function via the directive parsed in the following line:
var fn = $parse(tAttrs.myContextmenu);
So the target function implementation is capturing a specific event via the directive and then suppressing it.
So I guess this is useful when you do not want to clutter directives with controller specific functions and maybe fire a different event in response to another event then let a controller handle it.
scope.$apply is used to manually trigger Angular's digest cycle for any async events that happen outside of Angular's execution context.
One such async event is element.on("click", function(e){...}) (or any other event related captured with .on), but could also be other async function outside of Angular context.
The second part is an invocation of the "$parsed" expression. It accepts a scope as a parameter and a map of "local" variables, such as {$event: evt}. The intent is every similar to what scope: "&" is doing - but without creating an isolate scope. For example, if the expression is:
<my-directive p="doSomething(foo)">
then, if doSomething(foo) is $parsed, the caller can supply the value of foo:
var parsedFn = $parse(attrs.p);
parsedFn(scope, {foo: 5})'
This will cause the invocation of doSomething(5)
Posting an answer to this since it makes sense to me (see my edit).
The fn(scope, {$event: evt}) call is invoking the parsed reference to a custom function via the directive parsed in the following line:
var fn = $parse(tAttrs.myContextmenu);
So the target function implementation is capturing a specific event via the directive and then suppressing it.
So I guess this is useful when you do not want to clutter directives with controller specific functions and maybe fire a different event in response to another event then let a controller handle it.

xhr.send() does not trigger ajaxComplete/ajaxSuccess, but $.get does. Why?

Imagine I have these three functions:
$( document ).ajaxSuccess(function( event, xhr, settings ) {
console.log('ajaxSuccess fired');
});
function doCall1 () {
var xhr = new XMLHttpRequest();
xhr.open('get', 'someurl.com', true);
xhr.send();
}
function doCall2 (){
$.get('someurl.com');
}
When I call doCall1, ajaxSuccess does not fire. However, when I call doCall2, ajaxSuccess does fire.
I understand this is related to the fact jQuery is in fact using a callback function to various methods of $.ajax() rather than looking at onreadystatechange / readyState of the XMLHttpRequest (correct me if I'm off the mark), but I still don't completely understand.
Can someone give me a quick explanation or direct me to some docs that'd get me the rest of the way to getting this?
As others have noted, ajaxSuccess and ajaxComplete are non-native events added by jQuery. If for some reason you wanted interop between jQuery and vanilla js, you could dispatch the events yourself (with the caveat that browser support for CustomEvent is limited):
var event = new CustomEvent("ajaxComplete");
document.dispatchEvent(event);
Thats because the ajaxSuccess and ajaxComplete methods are part of jQuery and they are only triggered by the jQuery functions.
XMLHttpRequest is a native javascript object and its not related with jQuery.
The code for triggering ajaxSuccess/ajaxComplete are written in the end of $.ajax(), $.get(), $.post(), etc. The above events are related to jQuery and the $.ajax(), $.get(), $.post() functions are jQuery functions.
The XHR is native JavaScript and is no way related to jQuery.
the one thing is the jQuery object having deferrers and the other one a native JavaScript object, where you have to attach a callback yourself and may forward it to the ajaxSuccess of jQuery (jQ wraps the native XMLRequest object and does that).

How do I remove a specific event callback from an HTML element with jquery?

I have an element in a webpage which has several callbacks on it
// First callback
$("#element").click(fn1);
// Second callback
$("#element").click(fn2);
// Definitions
function fn1(){console.log("1");}
function fn2(){console.log("2");}
Is there a way to remove only fn2 from the list of callbacks triggered by jQuery.
I know I could add an 'if' inside the function and some global variable, but that's not what I'm looking for.
The second parameter in the unbind function specifies the handler to unbind.
$("#element").unbind("click", fn2);
Working Example: http://jsfiddle.net/k73Nx/
Interesting that nobody mentioned namespaces yet. Is there a reason for that?
When attaching your event, you can namespace it. Instead of $(elem).on('click', fn) you would add a namespace to the click event. $(elem).on('click.namespaced', fn)
When unbindung, you can then unbind that exact event, using the namespace as well.
$(elem).off('click.namespaced')
This is most practical when you're defining your event function inline.
One more thing you can do with namespaces is to unbind all event types within a namespae with just a single call: $(elem).off('.namespaced')
Be careful with your syntax here, other answers are very loose with theirs.
If you use:
$('#element').on('click',function() {
//callback code
});
Then you must use:
$('#element').off('click');
You cannot use
$('body').off('click','#element',function() { });
or
$(document).off('click','#element',function() { });
because you originally bound your event to #element, not to document or body.
Use unbind: http://api.jquery.com/unbind/
Example:
$(document).unbind('click', fn2);
use .off
$("#element").off("click",fn2);
working fiddle

Events on DOM elements not yet created, in Javascript?

I have a Javascript module the following Javascript:
EntryController = function$entry(args) {
MainView();
$('#target').click(function() {
alert('Handler called!');
});
}
MainView() has a callback that creates the #target button. Because of the callback the code will pick up and run through the rest of the code $('#target') ... before #target is created. If this is the case the event is never hooked up to the #target. If I put a breakpoint at $('#target') that'll give the callback enough time to return and build the #target, when I press play everything works as expected.
What's the best way to deal with this? I would like all events to take place in the controller so it can choose which view to send it to.
I was thinking about placing the entire $('#target').click ... inside MainView() and instead of alert('Handler called!'); I'd put a references to EntryController.TargetEventRaise(), but that started to look a bit like steady code. What's the best way to approach this?
You're looking for jQuery's live event handlers, which will handle an event on every element that matches the selector, no matter when the element was created.
For example:
$('#target').live('click', function() {
alert('Handler called!');
});
Alternatively, you could make the MainView function itself take a callback, and add the handler in the callback. You could then call the callback in MainView inside of its callback.

Categories