Is there any existing OnDestroy/OnDispose event in JavaScript or are there any known custom implementations in plain JS or Mootools? Let's say I want to call console.log('bye') when an element gets destroyed/removed from the DOM. Something similar to this jQuery solution
whereas you can do this, it's not practical to do so.
first - destroy - the event fill fire with the context of the element that is being destroyed, at which point during the event cb, it will get removed and GCd, potentially.
second, IE6,7,8 where Element prototype is read-only and elements get the methods added to them locally via the $/document.id - means that the decorated methods need to be loaded before anything is accessed in the DOM.
third, this won't actually fire if say, el.parentNode.innerHTML = '' or they get removed via raw js / alternative ways. it's not a true watcher in that sense, just traps 2 methods.
http://jsfiddle.net/5YYyb/1/
(function(){
// old methods
var destroy = Element.prototype.destroy,
dispose = Element.prototype.dispose;
// redefine them and fire the events before calling old protos.
[Element, Elements].invoke('implement', {
destroy: function(){
this.fireEvent('destroy');
return destroy.apply(this, arguments);
},
dispose: function(){
this.fireEvent('dispose');
return dispose.apply(this, arguments);
}
});
}());
var foo = document.getElement('.foo');
foo.addEvents({
dispose: function(){
alert('we are not in the dom now');
}
});
foo.dispose();
Related
I'm making a shooting game in which I need to update the state of bullets by binding them to the 'tick' event, however when calling the remove method to remove them from the 'tick' event it does not removes it. After creating a new instance this keeps getting updated instead of the one that was binded.
The methods 'add'/'remove' are used to bind/unbind the methods from the 'tick' event
class window.Stage
stage = undefined
counter = 0
fps = 60
add: (element) =>
element.id = counter++
stage.addChildAt(element.view, element.id)
element.listener = createjs.Ticker.on("tick", element.update)
remove: (element) =>
createjs.Ticker.off("tick", element.listener) # Not removing!
stage.removeChildAt(element.id)
update: () =>
stage.update()
This is how I'm calling the remove method in the Game class
run: () =>
if #gun? && !#gun.alive
#stage.remove(#gun)
#gun = undefined
if #player.shooting() && !#gun?
#gun = #player.shoot() # Ticker keeps updating new instance
#stage.add(#gun)
for bunker in #bunkers
if #gun? && bunker.gotShot(#gun)
#gun.alive = false
This is how bullets are created
class window.Player
shoot: =>
new Gun(#name, #x, #y - radius, false)
If there's any tutorial to better undestand how to correctly use listerners a link will be very much appreciated, thanks is advance.
The off() method requires you pass the method closure generated by calling on(), and not the original method that is passed. This is because the on() method generates a closure to maintain scope -- whereas addEventListener will not scope methods for you, requiring you to bind them yourself, or use global or anonymous handlers.
Make sure to store off the closure, and pass that instead. I am not familiar with the syntax in your example, so here is a vanilla JS example:
var listener = element.on("tick", handler, this);
element.off("tick", listener);
Note that the 3rd parameter is the scope the method should be called in, and if you don't pass it, it still generates a closure, and fires it in the element's scope instead of anonymously. There are also some other nice features of the on() approach, such as the "fire once" and event.remove() functionality.
You can always stick with the addEventListener/removeEventListener methods if you would prefer the same behaviour as DOM level 3 events.
On the setClickEvents funtion in the below javascript object when I try to use the this element inside the addEventListener it doesn't work, I have to use tabUI, the object itself.
I'm not sure if it's a good practice what I'm doing but it works.
What can I do for improving it?
var tabUI = {
leftTab: undefined,
rightTab: undefined,
setTabs: function(leftTabId, rightTabId) {
this.leftTab = document.getElementById(leftTabId);
this.rightTab = document.getElementById(rightTabId);
},
setClickEvents: function() {
this.leftTab.addEventListener('click', function() {
// This works
tabUI.leftTab.classList.add('tab_selected');
tabUI.rightTab.classList.remove('tab_selected');
// This doesn't work
this.leftTab.classList.add('tab_selected');
this.rightTab.classList.remove('tab_selected');
});
this.rightTab.addEventListener('click', function() {
tabUI.leftTab.classList.remove('tab_selected');
tabUI.rightTab.classList.add('tab_selected');
});
},
}
this inside the event handler is the element on which the event has occurred.
You can solve your problem using any of the following two ways. I'd recommend you to use the second way, so that you can access the elements on which event has occurred.
bind context
Changing the context of the function regardless of how it'll be called.
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
this.leftTab.addEventListener('click', function() {
// This works
tabUI.leftTab.classList.add('tab_selected');
tabUI.rightTab.classList.remove('tab_selected');
// This also works here
this.leftTab.classList.add('tab_selected');
this.rightTab.classList.remove('tab_selected');
}.bind(this));
// ^^^^^^^^^^
Cache context/this
Cache the previous context and then, that can be used in the event handler.
var that = this; // Cache this here
// ^^^^^^^^^^^^^
this.leftTab.addEventListener('click', function() {
// This works
tabUI.leftTab.classList.add('tab_selected');
tabUI.rightTab.classList.remove('tab_selected');
// That works here
that.leftTab.classList.add('tab_selected');
// ^^^
that.rightTab.classList.remove('tab_selected');
// ^^^
});
The this object isn't tabUI, as you seem to have noticed. What it is is the element the event is running on: event handlers have their context set to the element they run on.
See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#The_value_of_this_within_the_handler
What's the best way of unbinding event handlers in the destroy method of a plain JS plugin? The following (non working) code shall demonstrate what I mean:
var myPlugin = (function(){
function myPlugin(selector){
var elems = document.querySelectorAll(selector);
for (var i=0; i<elems.length; i++) {
function _handler(){ console.log('Hello'); }
elems[i].addEventListener("click", _handler);
}
this.destroy = function(){
document.removeEventListener("click", _handler);
};
}
return myPlugin;
})();
So, I iterate over a set of elements and do something with them, including attaching an event handler function. The problem: In plain JS, I need a reference to the original handler in order to remove it when the plugin instance gets destroyed.
This snippet naturally cannot work, because the event handler function is written over and over again with each selected element.
One way of handling this: Creating functions with a dynamic/unique name, as described here: Creating functions dynamically in JS.
The function needs to be globally set on the window object. Then, I just need to remember the name (e.g. by using a data attribute on the selected element) and with that, it's possible to unbind the event later on.
However, this approach is clumsy and I run into issues on IE8, when using such function with attachEvent. Is there a better way or any best practice for that?
This may be a bit abstract but I'm trying to get my head round JavaScript closures etc. Take the following code:
function MyObj() {
var me = this;
this.foo = function(bar) {
// Do something with 'bar'
}
// Set up lots of local variables etc.
// ....
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
$(document).on('click.myobj', 'input.special', function() {
// Do something with the <input> that triggered the click event
me.foo(this);
});
});
}
var myObj = new MyObj();
The anonymous function passed to that is bound to the click event creates a closure that references me. What I want to know is whether it's better to do something like this instead (to avoid the closure):
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
(function(localMe) {
$(document).on('click.myobj', 'input.special', function() {
// Do something with the <input> that triggered the click event
localMe.foo(this);
});
})(me);
});
Is this a better approach, or am I being overly paranoid about creating a closure? Alternatively, is there a "third way"?
EDIT
Additionally, would it be better to do something like this:
$(window).load(function() {
// Add a delegated click handler to specific <input> elements
$(document).on('click.myobj', 'input.special', {localMe : me}, function(event) {
// Do something with the <input> that triggered the click event
event.data.localMe.foo(this);
});
});
The latter is (AFAIK) more efficient, but probably not measurably so unless used in a tight loop.
The reason is that all variable dereferencing must follow the scope chain. In the latter case, the variable localMe can be found in the anonymous function's parameter list.
In the former case, the variable isn't found there, but in the outer scope. This traversal up the scope chain takes extra time.
Anonymous functions are massively used in javascript now (as arguments and as an immediate function for scopes/closures). There's no performance problem with that.
But you can have a problem of code reading maybe. Because when you see a variable, you must check where the variable is from. But no big deal here.
And in your second example, you still have a closure "break". Because in your anonymous function in the click, you use the localMe variable. And the localMe is an argument of a fonction outside of your fonction.
// Here, 'me' is a direct local variable.
$(window).load(function() {
// Here, we are in an anonymous fonction, so 'me' is not a direct variable anymore. But you still can access it.
// Add a delegated click handler to specific <input> elements
(function(localMe) {
// Here, 'localMe' is a direct local variable.
$(document).on('click.myobj', 'input.special', function() {
// We are in an anonymous function, so 'localMe' is not a direct variable anymore.
// Do something with the <input> that triggered the click event
localMe.foo(this);
});
})(me);
});
If you really want to avoid a closure "break", you should bind your function to your object. But note that not every browser support the bind method on functions.
You will always create a closure if you bind the event from the constructor. In fact, you even need the closure to preserve the reference to your instance. However, you might do something like this:
function MyObj() {
this.foo = function(bar) {
// Do something with 'bar'
}
// Set up lots of local variables etc.
// ....
}
var myObj = new MyObj();
$(function() {
$(document).on('click.myobj', 'input.special', function() {
myObj.foo(this);
});
});
If you do only create a singleton instance of your constructor, it won't matter anyway.
I would probably do it this way:
var bind = function( fn, me ) { return function() { return fn.apply(me, arguments); }; },
Object = (function() {
function Object() {
this.handler = bind(this.handler, this);
// Add a delegated click handler to specific <input> elements.
$(document).on("click.myobj", "input.special", this.handler);
}
Object.prototype.foo = function( bar ) {
// Do something with "bar".
};
Object.prototype.handler = function( event ) {
// Do something with the <input> that triggered the click even.
return this.foo(event.currentTarget);
};
return Object;
})();
var obj = new Object();
This skips the uses of closures and iifes, using .apply instead. Not sure if it is more efficient or not, but it is another option.
I am developing an add-on for Firefox (3.6.*). in the following code notify called from inside init works fine, but I get an error saying this.notify is not a function when it is called from within onPageLoad. Why is that?
Also when I change the call to myextobj.notify('title', 'msg'), it works. The same is true for accessing variables. So, what is the difference between this and the object name as a prefix?
var myextobj = {
init: function() {
this.notify('init', 'We are inside init');
...
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
},
onPageLoad: function(aEvent) {
this.notify('onPageLoad', 'We are inside onPageLoad');
...
},
notify: function (title, text) {
Components.classes['#mozilla.org/alerts-service;1'].
getService(Components.interfaces.nsIAlertsService).
showAlertNotification(null, title, text, false, '', null);
}
};
window.addEventListener("load", function() { myextobj.init(); }, false);
When you do this:
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
you just add the function that is hold in onPageLoad as event handler. The connection to the object is lost and this will refer to the global object when executed.
Just create an anonymous function as you do for the load event:
var that = this; // capture reference to object
appcontent.addEventListener("DOMContentLoaded", function(event) {
that.onPageLoad(event);
// myextobj.onPageLoad(event); should also work in this case
}, true);
Remember that functions are first class objects in JavaScript, they can be passed around like any other value. Functions have no reference to an object they are defined on, because they don't belong to that object. They are just another kind of data.
To which object this refers to in a function is decided upon execution and depends on the context the function is executed in. If you call obj.func() then the context is obj, but if you assign the function to another variable before like var a = obj.func (that is wat you do with adding the event handler (in a way)) and then call a(), this will refer to the global object (which is window most of the time).
When onPageLoad is called for the event, 'this' would not be referring to your myextobj. Because it wasn't called in the context of your object myextobj.
The way I deal with this is, by having all member functions of an object using the following convention.
var myObj = {
.....
counter: 0,
.....
myFunction: function () {
var t = myObj;
t.myOtherFunc();
},
....
myOtherFunc: function() {
var t = myObj;
t.counter++;
}
};
See how, I'm aliasing myObj as t, to save on typing and making my intent of using this clear.
Now you can call your methods safely from any context without worrying about what this would be referring to. Unless you really want the standard behavior; in that case, you may like to look at the call and apply methods. This link might help: Function.apply and Function.call in JavaScript
You may also want to look at a recent addition to JavaScript (would be available in FireFox 4): the bind method: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
Another link, which directly addresses your problem: https://developer.mozilla.org/en/DOM/element.addEventListener#The_value_of_this_within_the_handler
The other way to add an event listener without losing track of this is to pass this itself as the event listener. However you are limited in that the function is always called handleEvent, so it's less useful if you have many listeners (unless they are all for different events, in which case you can switch on the event's type).