keep js function inside jquery object - javascript

I have a menu class loading data from a received json file.
in the constructor I build the menu, so I have a for loop with this (extracted part) js:
for (var i = 0; i<data.length; i++)
{
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.click(function()
{
if($('.blockingFrame').length == 0)//pas de blocking
{
data[i].action()
}
});
}
Now, obviously this doesn't work because on runtime data[i] doesn't exist anymore...
data[i].action contains a valid js function.
This works, but doesn't contains the condition..:
for (var i = 0; i<data.length; i++)
{
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.click(data[i].action);
}
So I thought I could store this action inside the jquery object and call it like this, but it doesn't work:
for (var i = 0; i<data.length; i++)
{
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.action = data[i].action;
btn.click(function()
{
if($('.blockingFrame').length == 0)//pas de blocking
{
$(this).action();
}
});
}
A partial solution I came u with, was to store the action in another event, like dblclick, and trigger a dblclick inside the condition, but this seams ugly.
Any idea how to do this?

for loops don't work properly with closures. Consider using iterator methods instead:
$.each(data, function(index, elem) {
var btn = $('<div>'+elem.label+'</div>').appendTo(object.container);
btn.click(function()
{
if($('.blockingFrame').length == 0)//pas de blocking
{
elem.action()
}
});
}
Iterators are usually more elegant and compact than for loops, especially when you have them nested.
The reason why your last snippet doesn't work if that btn = $(...) is a temporary jquery object and disappears once you leave the scope, with everything you have assigned to it. When later a click handler is being invoked, you create a new jquery object via $(this), which doesn't carry your changes from the previous step. If you want to keep any data attached permanently to an element, use the data method - but in this case there's no need for that.

Use an immediately-executing function to create a closure that holds i.
for (var i = 0; i<data.length; i++) {
var btn = $('<div>'+data[i].label+'</div>').appendTo(object.container);
btn.click(function(i) {
return function() {
if($('.blockingFrame').length == 0)//pas de blocking {
data[i].action();
}
}(i));
}

Related

Event listener with anonymous function (typeError , toggle undefined)

I am new to Javascript.
I have two identical tables laid side by side. I would like to have a mirror effect. case 1 works fine with no anonymous function.
However, there seems to be some problem in my case 2 Javascript code which is vital for my project
CASE 1
var table1td = document.querySelectorAll("#table1 td");
var table2td = document.querySelectorAll("#table2 td");
for(var i=0; i<table2td.length; i++)
{
table2td[i].addEventListener("click",_click);
}
function _click() {
this.classList.toggle("_changecolor");
}
CASE 2
var table1td = document.querySelectorAll("#table1 td");
var table2td = document.querySelectorAll("#table2 td");
for(var i=0; i<table2td.length; i++)
{
table2td[i].addEventListener("click", function(){_click(i)});
}
function _click(index) {
this.classList.toggle("_changecolor");
table2td[9-index].classList.toggle("_changecolor");
}
(no changes in HTML,CSS code)
There are two problems with your code:
You can't use this inside _click because it won't be your element. The element that fired the event will be bound to the anonymous function passed to addEventListener, not to _click (which, depeneding on the rest of your code, will either be bound to undefined or the global object window).
You can fix that by explicitly setting the this value when you call _click from within the anonymous function using Function#call or Function#apply:
for(var i = 0; i < table2td.length; i++) {
table2td[i].addEventListener("click", function() {
_click.call(this); // passing the current this (of the anonymous function) to _click as we invoke it
});
}
function _click() {
this.classList.toggle("_changecolor"); // using this is OK
}
You can't use the indexes due to this famous problem.
A quick fix will be to use let (which respects the block scope) instead of var (which doesn't):
for(let i = 0; i < table2td.length; i++) { // using let instead of var
table2td[i].addEventListener("click", function() { _click(i); });
}
function _click(index) { // using index is also OK
table2td[index].classList.toggle("_changecolor");
}

For loop inside function (newbie)

I would like to have some variables that my for loop uses inside a function scope (not global).
I tried to wrap the for loop inside a function like this but it results in console error:
function() {
var data = livingroomTableData;
for(var i = data[0]; i < data[1]; i++) {
var elemvalue = data[2] + format(i) + ".png";
livingroomTableArray[i] = elemvalue;
}
}
I would like the data variable to have the values of livingroomTableData only inside this for loop (not globally). In other loops I will input a different variable into the data variable.
Oh yes, and as you can probably tell, I'm a total newbie. :S
There is only function scope in javascript, block scope does not exist, so you can't let the variable only inside the for loop. What you could do is to create a function scope.
Code example:
(function(livingroomTableData) {
var data = livingroomTableData;
//... the rest code
})(livingroomTableData);
The big problem is this line:
for(var i = data[0]; i < data[1]; i++) {
That means, starting with i as the first element of the array, do the code in the loop, incrementing i by one at the end of each run until i is not less than the second element of data.
I'd rewrite it to show you a working version, but its not clear what you actually want to do.
function() {
for(var i = 0; i < livingroomTableData.length; i++) {
var data = livingroomTableData[i];
//your code here...
}
}

Can you attach a handler function with arguments to an onclick event for a number of elements in a loop?

I am trying to set an onclick event for thumbnails that are dynamically populated from a database. I need to set the function to handle an argument, which is the id of the bigger picture the thumbnail represents. The code I have now sets all the thumbnails to point to #18. If you see in the for-loop, it is supposed to die at 17:
for (var i = 0; i < 18; i++) {
document.getElementById('tat' + i).onclick = function() { display(i); };
}
(My thumbnail <img />s all have id="tat0", id="tat1", id="tat2", id="tat3" etc.)
(display() loads the larger pic that the thumbnail represents into a separate element)
Each thumbnail gets this onclick function, so I know the for loop is accessing each one by its ID properly (stepping through for each i) so why are all the display(i) being assigned to 18? Can you assign an onclick function to handle parameters?
You need a closure function to generate your handlers.
function genHandler( param ) {
return function() {
// use all params in here
display( param );
}
}
and then assign your events similarly
for (var i = 0; i < 18; i++) {
document.getElementById('tat' + i).onclick = genHandler( i );
}
It might also work, if you just add 'i' as a parameter to your function.
Wrapping your onclick handler in a function will create a closure that carrys the current scope with it.
for (var i = 0; i < 18; i++) {
document.getElementById('tat' + i).onclick = (function(a) {
return (function() {
display(a);
});
})(i);
}​

Speed test: while() cycle vs. jQuery's each() function

I have an array of objects called targets and I want to execute a function on each of those objects. The first method:
targets.each(function() {
if (needScrollbars($(this))) {
wrap($(this), id);
id = id + 1;
}
});
This method gives execution speed of ~125ms. The second method is:
var i=0;
while (targets[i] != undefined) {
if (needScrollbars($(this))) {
wrap($(this), id);
id = id + 1;
}
i = i+1;
}
This second method takes whopping 1385ms to execute and I get my head around that. Does anyone have any idea why a bare bones cycle runs slower than a function which I'm only guessing that's doing (just guessing) a whole lot more than a simple cycle?
Thank you.
They are totally different. The this in the first example is the current target, in the second example this is the "external" this. You should change the second example as:
var i=0;
while (targets[i] != undefined) {
var cur = $(targets[i]);
if (needScrollbars(cur)) {
wrap(cur, id);
id = id + 1;
}
i = i+1;
}
The relevant quote
More importantly, the callback is fired in the context of the current DOM element, so the keyword this refers to the element.
But I don't know why you haven't written as:
for (var i = 0; i < targets.length; i++)
{
var cur = $(targets[i]);
if (needScrollbars(cur)) {
wrap(cur, id);
id = id + 1;
}
}
And in the end the each "method" is easier to comprehend (for me).
Your second method is not functionally equivalent to the first one.
Why? Because it uses this, making it a closure on the global scope. Of course the second method is slower: it continuously shells out jQuery objects made out of global scope. Try that benchmark again with:
var i=0;
while (targets[i] !== undefined) {
var o = $(targets[i]);
if (needScrollbars(o)) {
wrap(o, id);
id++;
}
i++;
}

Handling onclick events

I have a list which contains links . I am using this code to access them:
function initAll() {
var allLinks = document.getElementById("nav").getElementsByTagName("a");
for (var i=0; i< allLinks.length; i++) {
allLinks[i].onmouseover = showPreview;
allLinks[i].onmouseout = function() {
document.getElementById("previewWin").style.visibility = "hidden";
allLinks[i].onclick=mainProcess;
}
}
}
function mainProcess(evt){
alert(this.value);
false;
}
This is not the exact code, what I am trying to do is that I need to identify link is clicked and perform some function on the basis of link clicked. I don't know where code needs to be modified... Page is giving error on the allLinks[i].onclick=mainProcess(this); line.
Now the problem is that I don't know how I should handle all the three events?
1) You're setting the onclick property of each of the links to be the value returned by mainProcess() - which always returns false. So, in effect, you're writing allLinks[i].onclick = false;
2) When you define an event handler directly, the argument that gets passed to it when the event fires, is the event object - not the element it was fired on.
To figure out the element, you can either look in the event object, or (since the handler has been added to the element itself) simply use this, as that will refer to the link element
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}
You do need to pass this to mainProcess(link). As stated in http://www.quirksmode.org/js/events_tradmod.html "No parentheses!" and "this" chapters. Check it out, there's an example there too. Should be everything you need.
Try changing to this:
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].onclick = mainProcess;
}
function mainProcess(event) {
{
alert(this.value);
return false;
}

Categories