Get "this" element ID in jQuery live/delegate with timeout - javascript

I am trying to get the ID of an element bound with a jQuery delegate() function. I want to pass the element's ID to another function. The ID returned is always "undefined" and I'm not sure why. Here is my code:
$(document).ready(function() {
var timeout = undefined;
$('body').delegate(
'#tab-form input[type="text"]',
'keypress',
function(){
if(timeout != undefined) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
timeout = undefined;
alert($(this).attr('id'));
}, 500);
}
);
});
And my markup:
<form id="tab-form">
<input type="text" value="" id="Tab_name" name="Tab[name]">
<input type="text" value="" id="Tab_text" name="Tab[text]">
</form>
Making a keypress in the text input pops up a JS alert that says "undefined", instead of "Tab_name" or "Tab_text" like I imagined it would.
My initial Googling around leads me to believe that the reason for the attr('id') being undefined is that "this" is not actually a single DOM element, but is an array of the elements that delegate() is attached to. I have not been able to figure out how to access the current bound element's DOM object in the jQuery object array.
Any help is much appreciated!

It's because this isn't what you want it to be in that anonymous function, it's window. There are a few ways to solve this, for example using $.proxy(), like this:
timeout = setTimeout($.proxy(function() {
timeout = undefined;
alert(this.id);
}, this), 500);

The context is window because window owns setTimeout. Just cache it:
$(document).ready(function() {
var timeout = undefined;
$('body').delegate(
'#tab-form input[type="text"]',
'keypress',
function(){
var el = this;
if(timeout != undefined) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
timeout = undefined;
alert($(el).attr('id'));
}, 500);
}
);
});

meder already pointed out the reason for the behavior. You might also pass in the event object and use target:
$(document).ready(function() {
var timeout = undefined;
$('body').delegate(
'#tab-form input[type="text"]',
'keypress',
function(event){
if(timeout != undefined) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
timeout = undefined;
alert($(event.target).attr('id'));
}, 500);
}
);
});
Sidenote: using .delegate() on the document.body does not make sense at all. You could just bind those events with .live() to your elements.

Since the other options are spoken for, I'll give the closure option. :o)
(function( th ) {
timeout = setTimeout(function() {
timeout = undefined;
alert(th.id);
}, 500);
})( this );
EDIT: To explain what is happening, basically you're creating a function, calling the function and passing in this as the argument to the function all at the same time.
Think of it this way:
// Create a new function that accepts one argument
function myFunc( th ) {
timeout = setTimeout(function() {
timeout = undefined;
alert(th.id);
}, 500);
}
// Immediately call the function, and pass "this" in to it
myFunc( this );
This is the exact same thing. Take this, wrap the entire function in (), make it anonymous by removing the name of the function myFunc, and then take the execution operator ( this ) from the function call, and place it directly after the function.
When you do that, you're essentially calling the function just after you've created it. It is just an easy way of immediately executing an unnamed (anonymous) function.

Related

How are callbacks fired here?

Simple question,
How can I call this as a simple and basic function instead of a window.resize event?
$( window ).resize( debouncer( function ( e ) {
// do stuff
$('body').append('<p>done</p>');
} ) );
On window resizing I append a <p> element - However the debouncer function below, seems to be called first which debounces the window-resizing.
The question is much simpler than that, how do I call the debouncer as a function instead of calling it on window.resize? Does this has something to do with callbacks?
E.g I want to call a function in my code instead of on window-resize.
I figured I could remove , $( window ).resize and replace it with a function, e.g var greet = function and then call greet() to no avail.
This seems really basic but I don't get how functions are called between the debouncer, how it is called and how the debounced-code gets called.
The debouncer function that gets called on window.resize
(should be irrelevant, but I include it anyway):
function debouncer( func , timeout ) {
var timeoutID , slice = Array.prototype.slice , timeout = timeout || 200;
return function () {
var scope = this , args = arguments;
clearTimeout( timeoutID );
timeoutID = setTimeout( function () {
func.apply( scope , slice.call( args ) );
} , timeout );
}
}
Your debouncer function returns function. I think what you are trying to achieve, is call that function from code. If that is the case you can do as following:
var timeout = 100;
function debouncer_func( e ) {
// do stuff
$('body').append('<p>done</p>');
}
debouncer(debouncer_func, timeout)();
You can also do this as below:
debouncer(function(e){
// do stuff
$('body').append('<p>done</p>');
}, 100)();
Basically here your debouncer function is returning a function. So debouncer(args)() will call your returned function from debouncer.

Can an EventListener be removed by nullifying its callback function?

I was wondering if an event listener can be removed by nullifying its callback function?
Simplified Example:
var somefunction = function() {
// some code
}
window.addEventListener(eventType, somefunction, false);
Now, will setting somefunction = null; remove the above EventListener, or will it simply turn it into a zombie EventListener?
The actual code is used in a Firefox (overlay-type) Addon and I was contemplating alternative methods of (automatically) removing EventListeners on unload event, other than the obvious:
window.removeEventListener(eventType, somefunction, false);
Update: Please note that this is part of a code for a Firefox addon. The eventType in this instance is 'popupshowing' which can not be nullified as it would disrupt browser functions.
Thank you in advance for your help
Looking forward to (alternative) suggestions
removeEventListener is the way to go.
Also, you don't actually nullify the function by setting some variable to null. The variable has assigned just a reference (to a non-POD object, like a function). To illustrate this, consider the following:
var a = function() { alert("called"); };
setTimeout(a, 1000); // Will still alert!
var b = a;
a = null; // What you called `nullify`....
console.log(this.a, "a" in this); // null, true
delete this.a; // Actually remove the property from the global scope.
// `a` is really dead at this point!
b(); // will still alert, too.
If you want to avoid some removeEventListener calls, I'd use some helper function(s):
let { addEventListenerUnload, removeEventListenerUnload } = (function() {
let tracked = [];
addEventListener("unload", function removeTracked() {
removeEventListener("unload", removeTracked);
for (let t of tracked) {
try {
removeEventListener(t.type, t.fn, t.capture);
}
catch (ex) {}
}
tracked.length = 0;
});
return {
addEventListenerUnload: function(type, fn, capture) {
addEventListener(type, fn, capture);
tracked.push({type: type, fn: fn, capture: capture});
},
removeEventListenerUnload: function(type, fn, capture) {
tracked = tracked.filter(e => e.type != type || e.fn != fn || e.capture != capture);
removeEventListener(type, fn, capture);
}
};
})();
(Includes some ECMA-6 stuff that Firefox supports, but which you could easily convert. Also, removeEventListenerUnload might not be needed at all, so you might omit it. Also, when using this in overlay script make sure to give it unique names to avoid clashes with other code).
i dont think setting callback function to null will remove eventlistener, you'll still have eventlistener attached, you could use removeEventListener or set the eventType to null, like:
window.eventType = null;

setTimeout() problems on IE 9

I have a simple js structure like this :
var Waiting = (function () {
function Waiting() {
this.timer;
}
Waiting.prototype.show = function () {
var self = this;
clearTimeout( self.timer );
self.timer = setTimeout( function(){ self.hideLogo(); },3000);
}
Waiting.prototype.hideLogo = function () {
console.log("ok i passed timeout");
};
return Waiting;
})();
As expected, I get the "ok i passed timeout" log on every browser the first time I execute the show function (which called the hideLogo one). The problem appears in IE9 when I called for the second time the show function. This time, the hideLogo function is never called (log never appears in IE console). I tried a lot of things without any success.
If anyone as an idea...
When you're using setTimeout, the function that is being called looses the context: in other words this doesn't post to the instance on which the method is called anymore. You're using self to cancel this issue out, but self is, itself, an iffy word (as in reserved keyword). Perhaps use that, and use an IIFE in the setTimeout call:
this.timer = setTimeout((function (that)
{
return function()
{
clearTimeout(that.timer);//perhaps clear timeout here?
that.hideLogo.apply(that,[]);//double dutch, the apply _shouldn't_ be required
};
}(this)), 3000);
At first glance, that's the only thing I can see that might be the issue with your code: the clearTimeout call shouldn't be an issue, but I like to call it at the end of the timeout itself, and the self ambiguity thing. Let me know if this changes anything for you!
I am not really sure how you'd call show the second time with the code provided, maybe you create a new Waiting()?
Here is what worked for IE8
var Waiting=(function () {
function Waiting() {
this.timer;
}
Waiting.prototype.show = function () {
var self = this;
console.log("will clear pref timeout");
clearTimeout( self.timer );
self.timer = setTimeout(
function(){
self.hideLogo();
},30);
}
Waiting.prototype.hideLogo = function () {
console.log("ok i passed timeout");
};
return new Waiting();
})();
// shows only one time
Waiting.show();
Waiting.show();
// next one will show because it lets the prefious one
// finish without clearing the pref timeout.
setTimeout(function(){
Waiting.show();
},1000);
Try:
setTimeout( function(){
clearTimeout( that.timer );
that.hideLogo();
},3000);
Worked for me on IE and Chrome. IE is very behind on everything.

JavaScript onKeyUp closures with timeout

Im trying to assign an onKeyUp event to all inputs within a form, using closures. the array fields contains all the names of the fields that require the event assigned to them. The array ajaxFields contains the names of the fields (from the array fields) that requires ajax validation.
function createEvents(fields,ajaxFields) {
for(var x=0;x<fields.length;x++) {
$('input[name='+fields[x]+']').keyup(function(field) {
//assign an onKeyUp event
return function() {
//some code using variable 'field' and array 'ajaxFields'
}(fields[x]));
}
}
I would like the onKeyUp function to be executed a second after the user has finished typing in that field, insted of every time the key is up (onKeyUp). this would save up a lot of processing space, not to mention the ajax calls. So far im using this:
clearTimeout(timer);
timer = setTimeout('validate()' ,1000);
You might have noticed that the function validate() doesn't exist, and thats because I dont know how to wrap the closures inside a named function, and im not even sure if I should...
So how do I do that?
EDIT: here is a current fiddle
You can (and should) pass functions to setTimeout instead of strings.
clearTimeout(timer);
timer = setTimeout(function(){
// your code here
}, 1000);
So, in your keyup, try something like this:
$('input[name='+fields[x]+']').keyup(function(field) {
//assign an onKeyUp event
return function() {
var that = this,
$this = $(this);
clearTimeout($this.data('timeout'));
$this.data('timeout', setTimeout(function(){
//some code using variable 'field' and array 'ajaxFields'
// "this" will not be your element in here, make sure to use "that" (or "$this")
}, 1000));
};
}(fields[x]));
I save the timeout in $this.data, so that each element can have its own timeout, instead of using a global variable.
Updated Demo: http://jsfiddle.net/Z43Bq/3/
This is what your code should look like:
var timer;
$(document).ready(function() {
var fields = $('.field');
var ajaxFields = $('.ajax-field');
createEvents(fields, ajaxFields);
});
function createEvents(fields,ajaxFields) {
// Use jQuery's "foreach" method
$(fields).each(function(inx, field) {
// Bind the listener here
$(field).keyup(function(ev) {
// Clear timeout if necessary
if (timer != null) clearTimeout(timer);
// Set the timeout
timer = setTimeout(function() {
// Your code should here
console.log('Fields: ', fields, '\nAjax Fields: ', ajaxFields, '\nTHE field: ', field);
}, 1000);
});
});
}
Also checkout the fiddle for the working code: http://jsfiddle.net/BLyhE/

JS Object, how to access objects from within myself

So, i see no reason why this isn't working but i am at a wall and frustrated. Why can't i call this.myself from within the wyr.message.close function? Maybe my understanding of this is scewed but i was sure this is referring to the object itself, not the function.
this.myself is undefined
Code:
wyr.message = {
myself: $('.message'),
init: function() {
if(this.myself.is(':visible')){
setTimeout(this.close, 5000);
}
},
close: function(){
this.myself.fadeOut(1200,function(){
this.myself.remove();
});
}
}
wyr.message = {
myself: $('.message'),
init: function() {
var self = this;
if(this.myself.is(':visible')){
setTimeout(function(){
self.close();
}, 5000);
}
},
close: function(){
this.myself.fadeOut(1200,function(){
$(this).remove();
});
}
}
The issue is context. Within the callback function passed to fadeOut, this is bound to the element being worked on by jQuery, not to the wyr.message object.
EDIT:
There's also an issue with the init method. SetTimeout will bind the value of this to the global (window) object - so we save a reference to the this we want and use that to invoke the close method.
You could also look into Function.prototype.bind, but it's not supported in older browsers.
First, javascript object literal property expressions are evaluated at the time you create the object instance. myself will be whatever that selector grabs at that time, which is probably nothing. The myself property needs to be a function if you want it to return the value of $('.message') at the time of invocation. As a consequence you'll need to change all uses to function calls as well.
Second, during the execution of the setTimeout callback, this is bound to the window object, so you need to qualify it appropriately:
wyr.message = {
myself: function() { return $('.message'); },
init: function() {
if(this.myself().is(':visible')){
setTimeout(this.close, 5000);
}
},
close: function(){
message.myself().fadeOut(1200,function(){
$(this).remove();
});
}
};
(Note, this will fade out and remove everything matching the selector when the timeout fires.)

Categories