I'm noticing that this references something else inside a function that I added as event listener. I read this informative resource and a few questions on stackoverflow but I don't know how to apply it to my case (I'm quite new to the "oop" and the module pattern in javascript so I'm a bit lost).
Here is my little module:
var myModule = myModule || ( function() {
// Adds event listener for all browsers
// see http://stackoverflow.com/a/6348597
function addEvent( element, event, listener ) {
// IE < 9 has only attachElement
// IE >= 9 has addEventListener
if ( element.addEventListener ) {
return element.addEventListener( event, listener, false );
} else if ( element.attachElement ) {
return element.attachElement( "on" + event, listener );
}
}
return {
init: function() {
// Add event listeners
addEvent(
document.getElementById( "myElementId" ),
"click",
this.processMyElement
);
addEvent(
document.getElementById( "myOtherElementId" ),
"click",
this.processMyOtherElement
);
},
hideElementById: function( elementId ) {
document.getElementById( elementId ).style.display = "none";
},
showElementById: function( elementId ) {
document.getElementById( elementId ).style.display = "block";
},
processMyElement: function() {
this.hideElementById( "myElementId" );
this.showElementById( "myOtherElementId" );
},
processMyOtherElement: function() {
// Do something else...
}
};
}() );
The thing is that this which I use to call hideElementById in processMyElement is referencing to the element I added an eventListener to, and not to the current object.
I tried a few things without success:
removing the return in addEvent,
using var that = this as a private property of the module (placed in the module before the addEvent definition) and using that in processMyElement
using apply in the init method but it (obviously) calls processMyElement when adding the listener to the element
Could anyone help me with this? I tried a few things but I cannot see how to do it better...
PS: I try to build testable code, that's why I had those hideElementById and showElementById methods, in order to separate various functionalities (that may be quite clumsy actually but that's where I am ATM...).
There are (more than) a couple of common ways to get the correct this binding. For example, you can use a closure:
var that = this;
addEvent(
document.getElementById( "myOtherElementId" ),
"click",
function () {
that.processMyOtherElement();
}
);
Or you could use bind:
addEvent(
document.getElementById( "myOtherElementId" ),
"click",
this.processMyOtherElement.bind(this)
);
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
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.
Which one you use would depend on other factors.
Related
Can somebody tell how to "unbind" an anonymous function?
In jQuery it's capable to do that, but how can I implement this Functionality in my own script.
This is the scenario:
The following code attach a onclick event to the Div which have someDivId as ID, now when you click the DIV, it's showing 'clicked!'.
var a = document.getElementById('someDivId');
bindEvent(a,'click',function(){alert('clicked!');});
That's all great, the problem is how to "un-attach" the Function to the DIV if the function is anonymous or how to "un-attach" all attached events to the 'a' Element?
unBind(a,'click'); //Not necessarily the given params, it's just an example.
This is the code for bindEvent Method:
function bindEvent (el,evtType,fn){
if ( el.attachEvent ) {
el['e'+evtType+fn] = fn;
el[evtType+fn] = function(){
fn.call(el,window.event);
}
el.attachEvent( 'on'+evtType, el[evtType+fn] );
} else {
el.addEventListener( evtType, fn, false );
}
}
Finally, and after hours of Test&Errors i have found a solution, maybe it's not the best or most efficient but... IT WORKS! (Tested on IE9, Firefox 12, Chrome 18)
First all I'v create two cross-browser and auxiliary addEvent() and removeEvent() methods. (Idea taken from Jquery's source code!)
HELPERS.removeEvent = document.removeEventListener ?
function( type, handle,el ) {
if ( el.removeEventListener ) {
//W3C Standard
el.removeEventListener( type, handle, true );
}
} :
function( type, handle,el ) {
if ( el.detachEvent ) {
//The IE way
el.detachEvent( 'on'+type, el[type+handle] );
el[type+handle] = null;
}
};
HELPERS.addEvent = document.addEventListener ?
function( type, handle,el ) {
if ( el.addEventListener ) {
//W3C Standard
el.addEventListener( type, handle, true );
}
} :
function( type, handle,el ) {
if ( el.attachEvent ) {
//The IE way
el['e'+type+handle] = handle;
el[type+handle] = function(){
handle.call(el,window.event);
};
el.attachEvent( 'on'+type, el[type+handle] );
}
}
Also we need some kind of 'container' to store the attached events to elements, like this:
HELPERS.EVTS = {};
And finally the two callable and exposed to the users Methods:
The next one to add an Event(event) and associate this Event to a Method (handler) for a specific Element (el).
function bindEvent(event, handler,el) {
if(!(el in HELPERS.EVT)) {
// HELPERS.EVT stores references to nodes
HELPERS.EVT[el] = {};
}
if(!(event in HELPERS.EVT[el])) {
// each entry contains another entry for each event type
HELPERS.EVT[el][event] = [];
}
// capture reference
HELPERS.EVT[el][event].push([handler, true]);
//Finally call the aux. Method
HELPERS.addEvent(event,handler,el);
return;
}
Lastly the method that un-attach every pre-attached events (event) for an specific Element (el)
function removeAllEvent(event,el) {
if(el in HELPERS.EVT) {
var handlers = HELPERS.EVT[el];
if(event in handlers) {
var eventHandlers = handlers[event];
for(var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
HELPERS.removeEvent(event,handler[0],el);
}
}
}
return;
}
By the way, to call this methods you must do the following:
Capture a DOM Node
var a = document.getElementById('some_id');
Call the method 'bindEvent()' with the corresponding parameters.
bindEvent('click',function(){alert('say hi');},a);
And to de-attach it:
removeAllEvent('click',a);
That's all, hope will be useful for somebody one day.
Personally (and I know this isn't the "best" way, as it does require me to think about what I'm doing), I like to just use the on* event properties of the element I'm working with.
This has the convenient upside of being able to quickly and easily detach events.
var a = document.getElementById('someDivId');
a.onclick = function() {alert("Clicked!");};
// later...
a.onclick = null;
However, you do have to be careful with this because if you try to add a second event handler it will overwrite the first. Keep that in mind and you should be all fine.
I'm not sure if you can unbind an anonymous function attached via javascript. If possible you can simple remove the element from the DOM and recreate it. This will get rid of any event handlers previously attached.
JavaScript provides no list of event listeners attached to a node.
You can remove all event listeners of a node but using the Node.cloneNode method, see here: https://developer.mozilla.org/En/DOM/Node.cloneNode
This clones the node (obviously) but it does not clone the event listeners attached to it.
You could also just bind empty functions as event listeners:
function noop() {}
bindEvent(myElement, "click", noop);
This is from jquery's source:
jQuery.removeEvent = document.removeEventListener ?
function( elem, type, handle ) {
if ( elem.removeEventListener ) {
elem.removeEventListener( type, handle, false );
}
} :
function( elem, type, handle ) {
if ( elem.detachEvent ) {
elem.detachEvent( "on" + type, handle );
}
};
How do you manage namespace for a custom JavaScript library depends on jQuery?
Do you create your own namespace, say foo and add your objects there? e.g. foo.myClass, foo.myFunction
Or do you add your objects to jQuery's namespace? e.g. jQuery.myClass, jQuery.myFunction
Which is the more common practice and why?
This article discusses writing jQuery plugins/libraries in excruciating detail.
What NOT to do:
(function( $ ){
$.fn.tooltip = function( options ) { // THIS };
$.fn.tooltipShow = function( ) { // IS };
$.fn.tooltipHide = function( ) { // BAD };
$.fn.tooltipUpdate = function( content ) { // !!! };
})( jQuery );
What to do:
(function( $ ){
var methods = {
init : function( options ) { // THIS },
show : function( ) { // IS },
hide : function( ) { // GOOD },
update : function( content ) { // !!! }
};
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
I also wrote a blog post last year about various methods for namespacing in JavaScript (non-jQuery related).
It would depend on what the library does.
If you're extending the functionality of instances of jQuery objects, you'd use jQuery.fn as was described very nicely by #David Titarenco in his answer.
If you're creating utilities that are meant to be seen as additions to those provided in window.jQuery, then I don't see a problem with using that namespace (as long as you're careful with naming).
If it is really its own separate library that is not meant to be seen as an extension of jQuery, yet relies on functionality from jQuery, then definitely use your own namespace.
I have some problems trying to figure out what is wrong with my object design.
var comment = function(){
var textarea = null;
$(document).ready( init );
function init()
{
$('.reply').click( comment.reply_to );
this.textarea = $('.commentbox textarea');
console.log( this.textarea ); // properly shows the textarea element
console.log( textarea ); // outputs null
}
function set_text( the_text )
{
console.log( textarea ); // outputs null
console.log( this.textarea ); // outputs undefined
textarea.val( the_text );
}
return {
reply_to: function()
{
console.log( this ); // outputs the element who fired the event
set_text( 'a test text' ); // properly gets called.
}
};
}();
When document is fully loaded, init() is automatically called and initializes the object. I must note that the textarea member properly points to the desired element.
A click event is attached to a "reply" button, so reply_to() function is called whenever user clicks on it.
So, this is what I don't understand:
* When using "this" is safe? Using it from reply_to() it is not, as it seems like the context is set to the caller element.
* Why I can call "set_text()" from reply_to, but I cannot access the "textarea" member?
* How I should do to access "textarea" member from reply_to() (which is an event callback)?
Since inside those handlers the context will change, it's easiest to keep a reference to the context you want, I personally prefer self. Here's one alternative format:
var comment = function(){
this.textarea = null;
var self = this;
$(document).ready( init );
function init()
{
$('.reply').click( reply_to );
self.textarea = $('.commentbox textarea');
}
function set_text( the_text )
{
self.textarea.val( the_text );
}
function reply_to() {
set_text( 'a test text' );
}
}();
You can test it here. Admittedly though I'm not really sure what you're trying to accomplish though. You are trying to return the reply_to function, but bind it yourself inside the init() ready handler...so you can either bind it immediately (like above), or change it up and return what you want to bind elsewhere.
How do I create a custom event class similar to ActionScript? What I mean by that is a class that I can use to fire off my own events, send the necessary data.
I don't want to use third-party libraries like YUI or jQuery to do it. My goal is to be able to send a event that looks like this:
document.addEventListener("customEvent", eventHandler, false);
function eventHandler(e){
alert(e.para1);
}
document.dispatchEvent(new CustomEvent("customEvent", para1, para2));
Please no third-party library solutions.
A method that worked for me was to call document.createEvent(), init it and dispatch it with window.dispatchEvent().
var event = document.createEvent("Event");
event.initEvent("customEvent", true, true);
event.customData = getYourCustomData();
window.dispatchEvent(event);
I'm a little late to the party here, but was searching for the same thing. I'm not keen on the first answer (above) because it relies upon the document to manage the custom event. This is dangerous because it's global and could potentially conflict with another script should that script coincidentally rely on the same custom event.
The best solution I've found is here:
Nicholas C. Zakas - Custom Events in Javascript
Unfortunately, since javascript doesn't support inheritance keywords, it's a bit messy with prototyping, but it definitely keeps things tidy.
This is straightforward when using DOM elements to broker the events.
Given an element:
var element = document.querySelector('div#rocket');
For a client to subscribe:
element.addEventListener('liftoff', function(e)
{
console.log('We have liftoff!');
});
Then to dispatch/raise/fire the event, use this code:
element.dispatch(new Event('liftoff'));
This by John Resig:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent( 'on'+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
}
More at his blog post at http://ejohn.org/projects/flexible-javascript-events/.
I was just thinking of assigning a supported handler to a new namespace i.e. a reference to a supported event. The code below works (paste it in console of Chrome) but you can write it in a better format, and you should have additional helper methods (that can redefine themselves as well), for xBrowser support, and for sniffing support types (which after you've detected which path to use, you'll have the function redefine itself. I hope what I have below helps.
var de = document.documentElement || document.getElementsByTagName[0];
function all(){ console.log('all') };
var customEventForSomeSpecificElement = function customEventForSomeSpecificElement() {
return ({
customEvent: function() {
if ('onclick' in de ) {
return 'click';
}
},
init: function(){ return this.customEvent(); }
}).init();
}();
de.addEventListener(customEventForSomeSpecificElement, all, false);
It's not so hard actually - there isn't so many event definitions, only three versions. The first one is the corect one (addEventListener), then there's the IE way (attachEvent) and then there's the compatibility way for older browser (element.onevent = function)
So a complete event handling solution would look something like this:
setEvent = function(element, eventName, handler){
if('addEventListener' in element){
//W3
element.addEventListener(eventName,handler,false);
}else if('attachEvent' in elm){
//IE
elm.attachEvent('on'+eventName,handler)
}else{
// compatibility
elm['on'+eventName] = handler;
}
}
and to clear events:
clearEvent = function(element, eventName, handler){
if('removeEventListener' in element){
//W3
element.removeEventListener(eventName,handler,false);
}else if('detachEvent' in elm){
//IE
elm.detachEvent('on'+eventName,handler)
}else{
// compatibility
elm['on'+eventName] = null;
}
}
and an example:
setEvent(document, "click", function(){alert('hello world!');});
clearEvent(document, "click", function(){alert('hello world!');});
This is not really a complete example though since the compatibility handler always overwrites the previous events (it's not appending actions, it's overwriting) so you probably would like to check if a handler is already set and then save it into some temporary variable and fire it inside the event handler function.
i am using jquery and doing something like this
DOM:
<div id="parent"></div>
JS:
var _doSomeThing = function()
{
//some codes
}
$(function()
{
// appending div and binding methods to span
$('#parent').append('<span>1</span>');
$('#parent').append('<span>2</span>');
$('#parent span').bind('click', _doSomeThing);
});
function _clearDiv()
{
//clear div
$('#parent').html('');
}
//sometime in future, call clear div
_clearDiv();
Now my question is, do binding events to DOM and later just removing the elements from DOM leads to memory leakage?
If yes, how to solve this problem?
the jQuery html method attempts to prevent memory leaks by removing event handlers for any elements that are deleted as a result of calling .html('') on a jQuery object.
From the 1.4.2 source
html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;
}
// See if we can take a shortcut and just use innerHTML
// THE RELEVANT PART
else if ( typeof value === "string" && !rnocache.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
value = value.replace(rxhtmlTag, fcloseTag);
try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}
// If using innerHTML throws an exception, use the fallback method
}
catch(e) {
this.empty().append( value );
}
}
else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery(this), old = self.html();
self.empty().append(function(){
return value.call( this, i, old );
});
});
}
else {
this.empty().append( value );
}
return this;
}
We can see that the jQuery.cleanData() function is called. Here is the source for that
cleanData: function( elems ) {
var data, id, cache = jQuery.cache,
special = jQuery.event.special,
deleteExpando = jQuery.support.deleteExpando;
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
id = elem[ jQuery.expando ];
if ( id ) {
data = cache[ id ];
if ( data.events ) {
for ( var type in data.events ) {
if ( special[ type ] ) {
jQuery.event.remove( elem, type );
} else {
removeEvent( elem, type, data.handle );
}
}
}
if ( deleteExpando ) {
delete elem[ jQuery.expando ];
} else if ( elem.removeAttribute ) {
elem.removeAttribute( jQuery.expando );
}
delete cache[ id ];
}
}
}
This looks in the jQuery.cache object for any event type properties on the events object property of the data object relating to each element that will be deleted when calling .html('') and removes them.
To basically explain how the standard event binding works, when a function is bound as a handler to an event raised on an element using jQuery, a data object is added as a property to the jQuery.cache object. This data object contains an events property object that will have a property created on it with a name matching the event type to which you wish to bind the event handler function. this property will contain an array of functions that should be called when the event is raised on the element, so the event handler function is added to this array. If this is the first event handler function for the event type and element in question, the jQuery.event.handle function with a call to apply (using the element as the context such that this in the function execution context will refer to the element) is registered with the browser using addEventListener/attachEvent.
When an event is raised, the jQuery.event.handle function will call all of the functions in the array on the property of the events property object of the data object matching the event type and the element on which the event was raised.
So in summary, html('') shouldn't cause memory leaks as a number of defensive measures are in place to prevent them.
Yes, because jQuery maintains a list of the attached event handlers to make unhooking them easier and in order to explicitly unhook them for you when the page is unloaded (which works around a more serious memory leak in IE). (So does Prototype, can't speak for other libs.) The solution is to unhook them before removing the elements (either directly, or via empty).
Can't comment on the leakage problem but you could simply use .empty() instead of .html(''). That way you'd clean the innerHTML and remove any bound event handlers.
You can always use $('#parent span').unbind(); just to be sure
Since you're constantly referring to $('#parent'), you should create a reference to that object in the global scope so that jQuery isn't constantly looking for the object on each request. Doing this, you're essentially caching the reference to the object, which will cut down on memory usage tremendously.
_parent = $('#parent');
...
function(){ _parent.append('<span>1</span>'); }
Edit: I picked up this tip from this article on jQuery Performance Rules