This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I want to add a delete option for a cookie of each img element inside for-loop. the problem is that all ids are always from the last object.
what I've tried:
function listCookies() {
var theCookies = document.cookie.split(';');
var aString = '';
for (var i = 1; i <= theCookies.length; i++) {
(function (i) {
if (theCookies[i - 1].indexOf("pauseCookie-") >= 0) {
cookieArray = theCookies[i - 1].split("=");
theCookie = $.trim(cookieArray[0]);
cookieInfo = $.cookie($.trim(theCookie));
cookieInfo = cookieInfo.split("^");
...
htmlCode = "<div id='pauseDiv-" + theCookie + "'><a href='" + cookiePath + "'>" + cookieTitle + "</a> (" + pauseInfo + ") </div><br />";
$("#cookiesContent").append(htmlCode);
header = document.createElement('img');
header.id = 'delImg' + theCookie;
header.style = 'cursor:pointer';
header.src = delImage;
header.onclick = function () {
alert("#pauseDiv-" + header.id);
//$.removeCookie(theCookie,{path:'/'});
$("#pauseDiv-" + theCookie).html("<div class=\"pauseDivResponse\">Deleted</div>");
}
document.getElementById("pauseDiv-" + theCookie).appendChild(header);
}
})(i);
}
}
listCookies();
alert("#pauseDiv-" + header.id); always prints the last id from all img elements I created.
This is not directly related to closures, but is to do with your variable header being in the global scope. When you declare a new variable, you should use the var keyword, otherwise it is assumed to be a property of the global object (window).
If you enable strict mode (by adding "use strict"; to the top of your code, or the top of the function), you will be told about these sorts of mistake.
To be clear, you need to change
header = document.createElement('img');
to
var header = document.createElement('img');
(and should do the same for all your variables)
Related
I am creating a 'photos' page on a website. It uses PHP to retrieve the filenames in a directory, and then attempts to create divs (with images in them) programmatically with javascript. However, when I try to create 'w3-third' divs, edit the innerHTML so that it embeds an image, and (the problematic step) add them to the 'w3-row' div, it removes the existing children. Hence, there is only one image per row.
I have been looking for alternate code / solutions, but the element.appendChild() function seems to be the only method; I have tried element.children.push(), but element.children is an [HTMLCollection] which (I guess) is read-only.
$.getJSON("content/photospage/get_filenames.php", function(data){
var photoFileNames = data;
console.log(photoFileNames.length + " images to display.");
var photosDiv = document.getElementById("divPhotos");
for(var i = 0; i < photoFileNames.length; i += 3){
console.log("Loop! i=" + i);
var newRow = document.createElement("div");
newRow.classList.add("w3-row");
newRow.classList.add("w3-padding-8")
var newImg1 = newImg2 = newImg3 = document.createElement("div");
newImg1.classList.add("w3-third")
newImg2.classList.add("w3-third")
newImg3.classList.add("w3-third")
newImg1.innerHTML = "<img src='" + dir + photoFileNames[i] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(newImg1);
console.log("displayed img " + (i))
if(i+1 < photoFileNames.length){
newImg2.innerHTML = "<img src='" + dir + photoFileNames[i+1] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(newImg2);
console.log("displayed img " + (i+1))
}
if(i+2 < photoFileNames.length){
newImg3.innerHTML = "<img src='" + dir + photoFileNames[i+2] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(newImg3);
console.log("displayed img " + (i+2))
}
console.log(newRow.children);
photosDiv.appendChild(newRow);
}
The html element that exists by default:
<div class="w3-container w3-content w3-center w3-padding-32 " id="divPhotos">
</div>
Sorry for the large amount of code above. Thanks for any assistance, and I'm happy to clarify anything that I failed to mention. :)
Also, I am aware that the code is clunky and inefficient, so let me know if you pick up on anything I could do better. Thanks again! :)
With
var newImg1 = newImg2 = newImg3 = document.createElement("div");
you've created one object (an HTMLDivElement) in memory, which 3 variable names (newImg1, newImg2, newImg3) refer to. You do not have 3 separate elements. When you call appendChild with one of the elements, you remove it from wherever it previously existed in the DOM.
Since you want separate elements, you should do so explicitly:
var newImg1 = document.createElement("div");
var newImg2 = document.createElement("div");
var newImg3 = document.createElement("div");
You could make the code less repetitive by using another for loop instead of creating separate standalone elements:
for (let j = 0; j < 3; j++) {
const thisIndex = i + j;
if (thisIndex >= photoFileNames.length) {
break;
}
const img = document.createElement("div");
img.innerHTML = "<img src='" + dir + photoFileNames[thisIndex] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(img);
}
I have looked at other Questions of this type and none of them solved my problem. I am having this JavaScript code:
var count1;
for (count1 = 1; count1 < 11; count1++) {
var article = res.articles[count1]
var ImageURL = res.articles[count1].urlToImage
$('#showNews').append('<div id="' + count1 + '" class="article"><div class="overlayart"><div class="art"><h3>' + article.title + '</h3 <p>' + article.description + '<br><br><button onclick="divLoad()">Follow Link</button></p></div></div></div>');
$("#" + count1).css('background-image', 'url(' + ImageURL + ')');
x = article.url;
}
function divLoad() {
alert(article.url);
};
Basically there are 10 items with different articles. Scopes of variables are all correct. I can see the links of the items when I console log them in the loop. I want to alert each URL whenever each Item is clicked for a respected button. But when I click that I get the error:
Uncaught ReferenceError: divLoad is not defined
at HTMLButtonElement.onclick
Am I missing something?
EDIT [My Full Code]:
var x;
function divLoad() {
alert(x);
};
$(document).ready(function(){
var url = 'https://newsapi.org/v2/top-headlines?country='+country+'&apiKey=MYAPIKEY';
$.getJSON(url).then(function(res){
//console.log(res)
var count1;
for(count1 = 1; count1 < 11; count1++){
var article = res.articles[count1]
var ImageURL = res.articles[count1].urlToImage
x = article.url;
$('#showNews').append('<div id="'+count1+'" class="article"><div class="overlayart"><div class="art"><h3>'+article.title+'</h3><p>'+article.description+'<br><br><button onclick="divLoad()">Follow Link</button></p></div></div></div>');
$("#"+count1).css('background-image','url(' + ImageURL + ')');
}
});
The problem is that you're always referencing one single, global variable. That variable (x) will only ever hold the last value it was set to in your for loop.
Instead, we can append the articles and give each one a data attribute - that way, we can associate each element with a specific article URL.
function divLoad(url) {
alert(url);
};
$(document).ready(function() {
var url = 'https://newsapi.org/v2/top-headlines?country=' + "test" + '&apiKey=MYAPIKEY';
$.getJSON(url).then(function(res) {
for (let count1 = 1; count1 < 11; count1++) {
let article = res.articles[count1];
$('#showNews').append('<div id="' + count1 + '" class="article"><div class="overlayart"><div class="art"><h3>' + article.title + '</h3><p>' + article.description + '<br><br><button class="article-btn">Follow Link</button></p></div></div></div>');
$("#" + count1)
.css("background-image", "url('" + article.urlToImage + "'")
.attr("data-url", article.url); //Associate the URL to the element
}
});
$("#showNews").on("click", ".article-btn", function() {
var url = $(this).closest(".article").attr("data-url"); //Get the associated URL
divLoad(url);
});
});
If you inspect the <div class="article"> now, you'll see each one has a data-url attribute that holds its URL.
This is the code that I currently have, one problem that is happening is I cannot use test() because presets[index].name and value are not visible outside of their function scope, how should I declare my array of objects in the global scope in order for me to be able to access these two variables in other functions?
var presets = [];
var index;
function CreatePresetArray(AMib, AVar) {
var parentpresetStringOID = snmp.getOID(AMib, AVar);
var presetStringOID = parentpresetStringOID;
parentpresetStringOID = parentpresetStringOID.substring(0, parentpresetStringOID.length - 2);
log.error("parentpresetStringOID is " + parentpresetStringOID);
var presetswitches = {};
for (var i = 1; i < 41; i++) {
presets.push(presetswitches);
try {
log.error("presetStringOID before getNextVB= " + presetStringOID);
vb = snmp.getNextVB(presetStringOID);
presetStringOID = vb.oid;
log.error("presetStringOID after getnextVB= " + presetStringOID);
var presetStringVal = snmp.get(presetStringOID);
log.error("presetStringVal= " + presetStringVal);
index = i - 1;
presets[index].name = presetStringOID;
presets[index].value = presetStringVal;
log.error("preset array's OID at position [" + index + "] is" + presets[index].name + " and the value stored is " + presets[index].value);
//log.error("presets Array value ["+index+"] = "+presets[index].configs);
if (presetStringOID.indexOf(parentpresetStringOID) != 0) {
break;
}
} catch (ie) {
log.error("couldn't load preset array " + index);
};
};
}
CreatePresetArray(presetMib, "presetString");
function test() {
for (i = 1; i < 41; i++) {
log.error("test" + presets[index].name + " " + presets[index].value);
};
}
test();
The for loop in your function test iterates over i but uses index inside the loop. Perhaps you meant to use
for (i = 0; i < 40; i++) { // 1 lower as you were using `index = i - 1` before
log.error("test" + presets[i].name + " " + presets[i].value);
}
Re-wrote your code. I don't think I made that much by way of change. If this doesn't clear up your problem, consider: Is the catch happening each iteration? Is the problem actually coming from a different method which is only visible here? Also, consider logging the whole presets Array when debugging to see what it looks like.
var presets = [];
function CreatePresetArray(AMib, AVar) {
var parentPresetOID, presetOID, presetValue, preset, vb, i;
parentPresetOID = snmp.getOID(AMib, AVar);
presetOID = parentPresetOID; // initial
parentPresetOID = parentPresetOID.substring(0, parentPresetOID.length - 2);
log.error("parentPresetOID is " + parentPresetOID);
presets = []; // empty array in case not already empty
for (i = 0; i < 40; ++i) {
try {
preset = {}; // new object
// new presetOID
vb = snmp.getNextVB(presetOID);
presetOID = vb.oid;
log.error("presetOID after getnextVB= " + presetOID);
// new value
presetValue = snmp.get(presetOID);
log.error("presetValue= " + presetValue);
// append data to object
preset.name = presetOID;
preset.value = presetValue;
// append object to array
presets.push(preset);
// more logging
log.error(
"preset array's OID at position [" + i + "]" +
" is" + presets[i].name + " and " +
"the value stored is " + presets[i].value
);
if (presetOID.indexOf(parentPresetOID) !== 0) {
break;
}
} catch (ie) {
log.error("couldn't load preset array " + i);
if (presets.length !== i + 1) { // enter dummy for failed item
presets.push(null);
}
}
}
}
Two options come to mind immediately:
you could pass the preset array as a argument to test().
You could put both CreatePresetArray() and test() inside a wrapper function and declare preset array at the top of your wrapper. That would give them both access to the variable.
It's generally considered Bad Form to declare globals if it can be avoided. Pollutes the namespace.
I'm trying to clone to divs in and append them to to other divs (their parents). I'm using clonenode for this but it doesn't seem to work. It clones the div in the first function and appends it to the parent of the div in the second function! Not sure what I'm doing wrong.
Here's the code (*EDIT:*var added):
function cloneQ() {
//Cloning questions and shit
cloneQ.id = (cloneQ.id || 0) + 1;
var question = document.getElementById("question");
var clone = question.cloneNode(true);
var numberOfQuestions = $('.question').length;
var id = "questioncon" + cloneQ.id;
clone.id = id;
question.parentNode.appendChild(clone);
var inid = "question" + cloneQ.id;
var optionid = "optionsdiv" + cloneQ.id;
$('#' + id + ' ' + '.' + 'questionin').attr('id', inid);
$('#' + id + ' ' + '.' + 'options').attr('id', optionid);
$('#' + id + ' h2').html('Question ' + cloneQ.id);
//Question Cloned
}
function cloneforPrint() {
cloneforPrint.id = (cloneforPrint.id || 0) + 1;
var questionprint = document.getElementById("questionprint");
var cloneprint = questionprint.cloneNode(true);
var printid = "questionprint" + cloneforPrint.id;
cloneprint.id = printid;
questionprint.parentNode.appendChild(clone);
var printinid = "thequestionprint" + cloneforPrint.id;
$('#' + printid + ' ' + '.' + 'thequestionprint').attr('id', printinid);
}
LIVE here: http://bit.ly/R8hB2m
Edit : Global vars are the problem.
You aren't putting var in front of your variables, making them global. The cloneForPrint function is picking up vars defined in cloneQ.
Init all the variables properly and you'll get some errors indicating where the problems are.
CloneQ is indeed appending to questions parent, but cloneForPrint then moves it somewhere else.
-- Old answer --
There's not enough here to work out what the problem is. My 1st guess is that the question element has the same parent as the questionprint element.
Based on the code given, cloneQ should definitely append to questions parent. So to give the appearance you've specified the DOM probably doesn't look like what you expect.
Given:
// Positions the bars relative to scale
this.gDrawBars = function() {
// Go through all the bars
for (i = 0; i < this.gData.length; i++) {
// Check part of it is within range
if (this.gDisplayFrom < this.gData[i][2] || this.gDisplayTo > this.gData[i][1]) {
// Is the entire bar showing
var isEntireBarInRange = (this.gDisplayFrom < this.gData[i][2] && this.gDisplayTo > this.gData[i][1]);
var div = document.createElement('div');
div.id = "gBar" + i;
div.className = 'gBar';
div.innerHTML = this.gData[i][0];
var self = this;
div.onmouseover = function() {
gBarHighlight(this, this.gData[i][1], this.gData[i][2]);
};
div.onmouseout = function() {
gBarUnHighlight(this, this.gData[i][1], this.gData[i][2]);
};
this.gContainer.appendChild(div);
//this.gContainer.innerHTML += "<div id=\"gBar" + i + "\" class=\"gBar\" onmouseover=\"gBarHighlight(this, '" + this.gData[i][1] + "', '" + this.gData[i][2] + "')\" onmouseout=\"gBarUnHighlight(this, '" + this.gData[i][1] + "', '" + this.gData[i][2] + "')\">" + this.gData[i][0] + "</div>";
The commented line at the bottom works fine, but I'm trying to change it to add these functions dynamically. It needs to pass this.gData[i][1] into the functions but it can't, because the i value has no meaning outside the loop.
How can I get around this? IE, make the function recognise it's being passed a value to use and not a reference.
You need to retain the value of i in a new execution context.
Place the code that assigns the handlers into a named function, and call that in the loop, passing i as an argument.
Place this function before the for loop:
function setupMouseOverOut( el, i ){
el.onmouseover = function() {
gBarHighlight(this, this.gData[i][1], this.gData[i][2]);
};
el.onmouseout = function() {
gBarUnHighlight(this, this.gData[i][1], this.gData[i][2]);
};
}
...then call it in the for loop:
setupMouseOverOut( div, i );
This way the value of i that you passed out of the for loop is retained in the new execution context of the setupMouseOverOut() function call, and the new functions you set as handlers will refer to that local variable.
It's not a function, it's an event. You need to add it as an event to the element:
div.addEventListener('mouseover', function() {
// ...
});
Note that when you do it this way you don't have that 'on' word there.