jquery for loop parsing variable in a eq() - javascript

I am having a problem with JQuery at the moment where I am trying to parse a integer variable in a for loop in a eq() function. The problem I am having is when I try append the class ".episode-l" (needs to be a class as there are multiple of these and are displayed with a getjson) in a certain location with the eq() function but it wont display with a variable, I need it as a variable as i need to increment it each loop. There are other posts like this but I have looked at them and none of them work. Here is the code:
for( var i = 0; i < filmnamevar.length; i++)
{
$.getJSON('http://api.themoviedb.org/3/search/movie?query='+ filmnamevar[i] +'&api_key=81c50c197b83129dd4fc387ca6c8c323',function(dataa){
$('.episode-l').eq(i).append('<div class="rating">'+ dataa.results['0'].vote_average +'</div>');
console.log(i);
});
}

It is a common problem associated with the usage of a closure in a loop.
Assuming filimnamevar is a array, since you are using jQuery you can use $.each()(instead of using an IIFE function as given in the below links but the principle behind the problem is the same)
$.each(filmnamevar, function (i, val) {
$.getJSON('http://api.themoviedb.org/3/search/movie?query=' + val + '&api_key=81c50c197b83129dd4fc387ca6c8c323', function (dataa) {
$('.episode-l').eq(i).append('<div class="rating">' + dataa.results['0'].vote_average + '</div>');
console.log(i);
});
})
Read
Creating closures in loops: A common mistake
Javascript closure inside loops - simple practical example

Related

Adding a listener to an array of elements, need unique arguments for function on each item

I have a bit of HTML generated by PHP in the format of:
<div class=zoomButton>
<input type=hidden name=zoomURL value=*different per instance*>
</div>
I am trying to attach a listener (imageZoom(event, url)) to each of the class "zoomButton" elements, but call it with different arguments for each instance.
i.e.
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
var zoomURL = zoomButtonArray[i].children[0].value;
zoomButtonArray[i].addEventListener("mousedown", function(){imageZoom(event,zoomURL);});
}
however it seems that zoomURL is always the value of the very last element. How can I change my code/approach so that the argument passed to the listener is the correct one, and not the last one in the "zoomButtonArray" array?
Thanks
You need to wrap the event listener in a closure:
function makeEventListenerForZoomURL(zoomURL) {
return function(event) {
imageZoom(event, zoomURL);
}
}
var zoomButtonArray = document.getElementsByClassName('zoomButton');
for (i=0; i<zoomButtonArray.length; i++)
{
zoomButtonArray[i].addEventListener(
"mousedown",
makeEventListenerForZoomURL(zoomButtonArray[i].children[0].value)
);
}
This can also be simplified using the ECMAScript5 forEach:
var zoomButtonArray = document.getElementsByClassName('zoomButton');
zoomButtonArray = Array.prototype.slice.call(zoomButtonArray, 0);
zoomButtonArray.forEach(function(node) {
node.addEventListener("mousedown", function(event) {
imageZoom(event node.children[0].value);
});
});
The reason is that each time the for loop executes a new function is created, this new scope references the variable i but i changes each time the loop iterates. So by the time the event listener runs it looks at the value of i only to find that it is the last value when the for loop ended. By using a closure described above the scope created is unique to each iteration of the loop so that when the event listener finally executes the value of the wrapped variable (zoomURL or node in the examples above) will not have changed.
Here is a good article explaining closures in for loops: http://trephine.org/t/index.php?title=JavaScript_loop_closures
I think you are missing quotes around attributes. I just added quotes and the tested at jsFiddle (Fiddle link in comments) and it's working see to console in developer tool. it is iterating through each element as desired. Console screen shot

finding the class of .click function

i have a string objects of books which i have got from a JSON objects. This Book object has three Key value pairs, Title,Author and URL. I use a for loop to read each object and just put the title of the object as a button on the html page. But when the button is clicked i want the URL of the book to be alerted. As i read the objects i make Books objects and push it into an array for later use. but i am not able to Use .Click() method the URL is not right. Please see the code for better understanding. :-)
for (i = 0; i < 6; i++) //I know that there is only 65 Books..jsonString.books.lenght is not working.
{
var title = jsonString.books[i].title;
var classname = title.replace(/\s/g, "");
var author = jsonString.books[i].author;
var URL = jsonString.books[i].url;
var htmlString = '<div class="' + classname + '"><input type="button" value="' + title + '"></div>';
$(htmlString).appendTo(attachPoint).click(function () {
loadBook(URL);
});
OneBook = new Book(author, title, URL);
arr.push(OneBook);
}
attachpoint is a reference in the html file that i got from
var attachpoint=document.querySelector('.buttonAttachPoint');
So in the above code the URL that i get on clicking is always the last object in the jsonString. this is happening coz of the for loop. So is there a way i can get to class name of the Div that has onclick or the title of the button so that i can get the URL from the array of objects i created? Or is there an easier way. Also could any one point out why "jsonString.books.lenght" is not working? Thanks in advance.:-) all the help much appreciated. :-)
Creating a closure using an immediately invoked function expression should do the trick. Just replace this:
$(htmlString).appendTo(attachPoint).click(function () {
loadBook(URL);
});
with this:
(function(URL) {
$(htmlString).appendTo(attachPoint).click(function () {
loadBook(URL);
});
})(URL);
URL inside the scope of that anonymous function will have the value passed to it, which will be the correct value for that iteration of the for loop.
In ECMAScript, variables are scoped to their function, rather than any block.
The functions you are binding to click have a closure over URL in the context of the loop as a whole, not over URL in the context of the loop iteration.
This means that whenever any of the functions are invoked, URL will have the last value that the loop sets it to.
You need to freeze the value of URL for each loop. One way to do this is to have a function elsewhere that takes URL as an argument, and returns a function that closes over it, thus:
function getBookLoader(url) {
return function (){
loadBook(url);
};
}
You can then replace your ... .click line with the following:
$(htmlString).appendTo(attachPoint).click(getBookLoader(URL))
To answer the question in the title, the target property of an event contains the object to which the event was dispatched, and the currentTarget property contains the object whose listeners are currently being evaluated. currentTargetshould be the div in question.

Using loop to bind click event over several elements not working in javascript/jquery

I have a map of messages
say:
var Mapping = {
"notnow": 2,
"expensive": 3,
"not_worth_it": 4
}
i have a bunch of html elements (lets say divs with the same name)
so
<div id="notnow"></div>
,etc
now i want to attach a click handler to each of them,
i run a loop as shown below
function setThemUp(){
for(var item in Mapping)
{
$("#" + item).bind('click', function () {
Apply(Mapping[item]); });
}
}
But for some reason all of them seem to get bound to "not_worth_it":4. Not to their respective values.
I'm using Jquery 1.5.
Can someone please explain why this might be happening?
My guess is that instead of the Mapping[item] being resolved to their values, it's being passed as a reference or something, that's why since the value of item eventually points to "not worth it" all of them call the function with that value itself. Any way in which i could overcome them.
Hard coding each of them as
$("#notnow").bind('click', function () {
Apply(Mapping["notnow"]); });
$("#expensive").bind('click', function () {
Apply(Mapping["expensive"]); });
$("#not_worth_it").bind('click', function () {
Apply(Mapping["not_worth_it"]); });
does work, but i would prefer an elegant solution with a loop.
Answer
i went with the closure solution
function setThemUp(){
for(var item in Mapping)
{
$("#" + item).bind('click', (function () {
return function(temp) {
Apply(Mapping[temp]); };
})(item));
}
}
Reasons being , this was more of a why the loop didn't work rather than about optimization of the jquery , since this was afterall a representative example, not my actual code, and this was an elegant solution to that problem.
This is a classic case of scope issues: you're referencing the variable item in each of your bound handlers. The variable item changes, though -> it's being assigned all properties of the Mapping object literal, the last one being not_worth_it.
Creating a closure might help, to preserve the state of item for each callback:
for(var item in Mapping)
{
$("#" + item).bind('click', (function(currentItem)
{//IIFE, pass item as argument---------/
return function ()
{//return function, \/ access to closure scope
Apply(Mapping[currentItem]);
};
}(item)););
}
But this seems to be somewhat overkill, why not simply delegate the event, and use Mapping[$(this).attr('id')]?
I would suggest moving to this form:
Add the class mapped to your mapping divs.
HTML
<div id="notnow" class="mapped"></div>
JS
function setThemUp(){
$('.mapped').bind('click', function () {
Apply(Mapping[this.id]);
});
}
The problem is you need to write:
for (var item in Mapping)
rather than foreach.

How to copy a variable in JavaScript?

I have this JavaScript code:
for (var idx in data) {
var row = $("<tr></tr>");
row.click(function() { alert(idx); });
table.append(row);
}
So I'm looking through an array, dynamically creating rows (the part where I create the cells is omitted as it's not important). Important is that I create a new function which encloses the idx variable.
However, idx is only a reference, so at the end of the loop, all rows have the same function and all alert the same value.
One way I solve this at the moment is by doing this:
function GetRowClickFunction(idx){
return function() { alert(idx); }
}
and in the calling code I call
row.click(GetRowClickFunction(idx));
This works, but is somewhat ugly. I wonder if there is a better way to just copy the current value of idx inside the loop?
While the problem itself is not jQuery specific (it's related to JavaScript closures/scope), I use jQuery and hence a jQuery-only solution is okay if it works.
You could put the function in your loop:
for (var idx in data) {
(function(idx) {
var row = $("<tr></tr>");
row.click(function() { alert(idx); });
table.append(row);
})(idx);
}
Now, the real advice I'd like to give you is to stop doing your event binding like that and to start using the jQuery "live" or "delegate" APIs. That way you can set up a single handler for all the rows in the table. Give each row the "idx" value as an "id" or a "class" element or something so that you can pull it out in the handler. (Or I guess you could stash it in the "data" expando.)
Check out jquery's data() method:
http://api.jquery.com/jQuery.data/
Description: Store arbitrary data associated with the specified element.

Javascript function: nothing executes after a for(x in y) loop?

I can't seem to find the answer to this anywhere...
I have a function that needs to set the bg color of two tables. Only the first table in the function is being affected. Is only one for loop allowed in a function? ...or is the 1st for loop maybe never exiting?
I can pretty much work around this by creating multiple functions but I really want to understand why this behaves the way it does!
Thanks!
Here is my simplified code:
function setColor()
{
//This works
var t1rows=document.getElementById("table1").getElementsByTagName("tr");
var x;
for (x in t1rows)
{
t1rows[x].style.backgroundColor='yellow';
}
//this one does not work
var t2rows=document.getElementById("table2").getElementsByTagName("tr");
var y;
for (y in t2rows)
{
t2rows[y].style.backgroundColor='yellow';
}
}
getElementsByTagName() returns a NodeList object, and for-in will iterate its properties which you don't want. You want to use a straight for loop:
for(var i=0; i < t2rows.length; i++) {
t2rows[i].style.backgroundColor='yellow';
}
You're not getting to the second loop because the first is failing when it tries to access a non-existent style property on one of the NodeList's members.

Categories