This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I have an un-ordered HTML list of images. Each Line (li) element has its own id:
<ul id="Demo3">
<li id="li1"><img src="images/FT/HopkinsPrairie.jpg" /></li>
<li id="li2"><img src="images/FT/PineForest.jpg" /></li>
<li id="li3"><img src="images/FT/2011GroupThumb.jpg" /></li>
<li id="li4"><img src="images/FT/JuniperSpringsSwimming.jpg" /></li>
<li id="li5"><img src="images/FT/HappyHourThumb.jpg" /></li>
<li id="li6"><img src="images/FT/HappyHourThumb.jpg" /></li>
</ul>
When I click each image I would like a larger version of the image to appear, sort of like this:
<li id="li2"><img src="images/FT/PineForest.jpg" /></li>
Unfortunately the CSS class associated with #Demo3 does a lot of manipulation to the class associated with all #li elements, so I have to add the anchor in programmatically.
I try doing it like this:
i = 1;
while ($("#li" + i).length > 0) { // If the li id exists
myLink = "images/FT/Page" + i + ".jpg"; // create a link
var element = document.getElementById("li" + i); // get the element
element.onclick = function () { // create an onclick for this element
var win = window.open(myLink, '_blank');
win.focus();
}
i++;
}
This does not work! For whatever reason, each image gets the same onclick as the last one in the list.
Does anyone know what I'm doing wrong or how to solve this so that each image contained within each li element gets its own onclick?
Any other suggestions?
Another Approach, Use Wildcard selectors:
$("[id^=li").each(function(index, value) {
value.click(function () {
var id = parseInt(value.id.substring(2)); // strip "li", convert to int.
var win = window.open("images/FT/Page" + id + ".jpg", '_blank');
win.focus();
});
});
or simply
$("[id^=li").click(function (event) {
var id = parseInt(this.id.substring(2)); // strip "li", convert to int.
var win = window.open("images/FT/Page" + id + ".jpg", '_blank');
win.focus();
});
This does not work! For whatever reason, each image gets the same onclick as the last one in the list.
The direct solution for you is this:
while ($("#li" + i).length > 0) { // If the li id exists
(function(i) { // <---- THIS LINE
var myLink = "images/FT/Page" + i + ".jpg"; // create a link
var element = document.getElementById("li" + i); // get the element
element.onclick = function () { // create an onclick for this element
var win = window.open(myLink, '_blank');
win.focus();
}
i++;
})(i); // <---- and THIS LINE
}
The reason is, myLink was being captured in your onclick closure. Your loop went a number of times, each time changing myLink and creating a closure on myLink. When the onclick triggered, the myLink value is the last value it had at the end of the loop - not at the time the closure was created.
The approach above, using an IIFE, "fixes" the value of i, and creates a separate variable myLink in each iteration for the onclick handler to close over.
The alternative approach by OneWay does this by directly using another closure as the callback of each. I'd use his solution in your code, and this answer to help you understand why your original behaved as it did.
Related
This question already has answers here:
Is it possible to append to innerHTML without destroying descendants' event listeners?
(13 answers)
Closed 6 years ago.
I have a function for adding buttons to a page.
var count = 0;
function createOnclickFunction(number)
{
return function()
{
alert("This is button number " + number);
}
}
function addButton(data)
{
var newbutton = "..." //creates string from data
var element = document.getElementById("mydiv");
var children = element.children;
element.innerHTML = newbutton + element.innerHTML;
var currentCount = count;
children[0].onclick = createOnclickFunction(currentCount)
count++;
}
It basically creates a button into the html based off some data.
Every time I add a button, I would like it to be added to the start of div #mydiv, and since newbutton is also not an Element, but a String, I have to modify the innerHtml to add it to the start of #mydiv.
Afterwards, I must get the element (to add an onclick), by getting the first child of #mydiv.
However, after adding a second button to my page, the first button onclick no longer works.
If I modify my code to only add one button, everything works fine.
So now, only the top button (the latest added button), can be clicked.
How can I fix this?
I've also tried to use element.firstChild instead of element.children[0].
Thanks in advance everyone!
EDIT:
Here is a jsfiddle: ( as you can see the only button that works is stackoverflow )
https://jsfiddle.net/7c7gtL26/
It seems you misunderstood the problem. The problem is that you are overwriting innerHTML in order to insert contents.
Never use innerHTML to insert contents. It will remove all the internal data, like event listeners. This is what happens here.
If you want to prepend some HTML string, use insertAdjacentHTML:
var count = 0;
function createOnclickFunction(number) {
return function() {
alert("This is button number " + number);
}
}
function addButton(data) {
var newbutton = "<button>Click me</button>" //creates string from data
var element = document.getElementById("mydiv");
element.insertAdjacentHTML('afterbegin', newbutton);
var children = element.children;
children[0].onclick = createOnclickFunction(count)
count++;
}
addButton();
addButton();
addButton();
<div id="mydiv"></div>
I'm having some trouble with jQuery in Meteor - I'm just trying to learn so I hope someone could help.
So when #addButton is clicked it will append the div to the .formField and each div created on click will have an unique class, eg formField[1], formField[2] etc
The trouble is when the button is clicked instead of just changing the name of the div only, the div is also added 50 times. I know how dumb it sounds as its a loop, but how would I loop only the div's class on click so each have a different name?
My code is below:
Template.form.events({
'click #addButton': function(event) {
var i;
for (i = 0; i < 50; i++) {
$(".formField").append('<div class="formField['+i+']">.....</div>');
}
return false;
If I understand what you are doing here you don't need a loop. You just need a variable to increment every time the button is clicked. Take your append out of the loop and instead on click increment your variable by one then call an append. No loop necessary.
var i = 0;
Template.form.events({
'click #addButton': function(event) {
i += 1;
$(".formField").append('<div class="formField['+i+']">.....</div>');
}
});
return false;
Do it like this, (i.e. by creating a closure), click run to verify
var uuid = 0;
$('#addButton').on('click', function (event) {
uuid = uuid + 1;
$(".formField").append('<div class="formField[' + uuid + ']">Form' + uuid + '</div>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="formField"></div>
<input type="button" value="Add New" id="addButton"></input>
I have a question about "onclick" function in JavaScript. Here I have a div "InfoBar"
<div id="InfoBar"><br>
and two for loop
var src = new Array();
for(var i = 0; i < 2; i++){
src.push("el1","el2");
}
for(var j = 0; j < 2; j++){
doesFileExist(src[j]);
}
and a doesFileExist() and klick function
function klick(el){
alert(el)
}
function doesFileExist(urlToFile){
document.getElementById('InfoBar').innerHTML += '<br>' + '<a id="css" onclick="klick(urlToFile)" href="#" title="'+urlToFile+'">' + "link1 : " + urlToFile + '</a>';
}
now I've added a "onclick" function in "a href".
if I click on "link1:el1", I want to display as alert "urlToFile" string.
But I doesn't work.
In "a href" title="'+urlToFile+'" it works perfect, but in "onclick" doesn't work.
Can anyone help me?
Thanks in advance.
You are generating an attribute. That gets converted back into a function but the scope is broken.
Don't use intrinsic event attributes.
Minimise use of globals
Avoid generating HTML by mashing strings together (at best it is hard to read, at worst you get this sort of issue)
Use standard DOM:
var container = document.getElementById('InfoBar');
container.innerHTML = ""; // Delete any existing content
container.appendChild(document.createElement('br'));
var anchor = document.createElement('a');
anchor.setAttribute('id', 'css'); // You are running this function is a loop and creating duplicate ids. Use a class instead.
anchor.addEventListener('click', function (event) {
klick(urlToFile); // the local variable urlToFile is still in scope
});
anchor.setAttribute('href', '#'); // Why are you linking to the top of the page? Use a <button>
anchor.setAttribute('title', urlToFile);
anchor.appendChild(document.createTextNode("link1 : " + urToFile));
container.appendChild(anchor);
Event handles assigned this way won't work. You have to use JavaScript event handles. Means, you must create a new 'a' element, then bind a click event to it, and then append it as a child to the parent node. All this stuff is very good described on the web out there.
The goal is to have info from a link in #list and have it create a new link in #list3.
Here is the link http://jsfiddle.net/4y5V6/24/
Is there a way to make it where once you click a link it will push it to #list3 and make the link look like this twitch.tv/chat/embed?channel=(channelname)&popout_chat=true ?
Thank you for your time!!
HTML:
<div id="navi">
<ul>
<li>Stream Selection:</li>
<li>SC2<ul id="list"><li><ul></ul></li></ul></li>
<li>League<ul id="list2"><li><ul></ul></li></ul></li>
<li>Chat<ul id="list3"><li><ul></ul></li></ul></li>
</ul>
</div>
<iframe id="iframe1" name="iframe1" type="text/html" src="" frameBorder="0" height="200px" width="300px">
</iframe>
Current function that populates the list.
// first set of streams SC2
$.getJSON("https://api.twitch.tv/kraken/search/streams?q=starcraft&limit=18&&type=top&callback=?", function (data) {
var temp = "";
$.each(data.streams, function (index, item) {
temp = temp + "<li><a target='iframe1' href='http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + item.channel.name + "'>" + item.channel.display_name + "</a></li>";
});
$("#list ul ").html(temp);
});
// next set of streams LoL
$.getJSON("https://api.twitch.tv/kraken/search/streams?q=League%20of%20Legends&limit=18&&type=top&callback=?", function (data) {
var temp = "";
$.each(data.streams, function (index, item) {
temp = temp + "<li><a target='iframe1' href='http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + item.channel.name + "'>" + item.channel.display_name + "</a></li>";
});
$("#list2 ul ").html(temp);
});
$('#navi').on('click', '#list a', function(e) {
var x = $(this).prop('href'),
y = x.slice(x.indexOf('channel')+8),
z = 'twitch.tv/chat/embed?channel=('+y+')&popout_chat=true',
clone = $(this).clone().attr('href', z);
$('#list3 ul').append($('<li />', { html: clone }));
});
Fork
Alternate version: No duplicates.
Adding ability to open new chat link in new smaller window
jsFiddle only copies list 1 to list 3
jsFiddle Copies from both list 1 and 2 to list 3
// The following call is jQuery's way of assigning events to both "Static"
// && "Dynamic" Elements. This way, even if the element is loaded after
// page load, it still gets the event callback.
// Keep in mind, you do need either a static parent (such as below, I use
// `$('#navi')`, since I assume this element is always present on your page
// load) |OR| you can use `$(document)`. The later is often recommended
// against, as you could run into "slowed down response" issues or naming
// issues. Personally, I use `$(document)` all the time and haven't had a
// complaint yet.
// Then you simply assign your event(s) to your "Selectors"
// For just list 1 that would be
// .on('click', '#list a'
// For list 1, and 2 that would be
// .on('click', '#list a, #list2 a'
// See Also: http://api.jquery.com/on/
$('#navi').on('click', '#list a, #list2 a', function(e) {
var x = $(this).prop('href'), // get href of <a> clicked
y = x.slice(x.indexOf('channel')+8), // extract name
z = 'twitch.tv/chat/embed?channel=('+y+')&popout_chat=true', // create new href link
clone = $(this).clone().attr('href', z), // clone this <a> and replace href with new one created
item = $('<li />', { class: y, html: clone }); // create new <li> making our cloned <a> it's innerHTML
if (!$('#list3 ul .' + y).length) $('#list3 ul').append(item); // check <li> item doesn't already exist and add to list
}) // Thanks to jQuery "chaining", I don't have to "recall" our static parent
.on('click', '#list3 a', function(e) {
e.preventDefault();
var lnk = $(this).attr('href');
// The following is basic JavaScript and simply opens a new window
// simply adjust width and height based on PIXELS
// see more window.open options #: http://www.w3schools.com/jsref/met_win_open.asp
// See Also: http://stackoverflow.com/questions/tagged/javascript+window+open
window.open(lnk, "myWindowName", "width=800, height=600");
})
I'm writing a logging function for automation unit test.
html elements structure:
<li>
<strong>
<ol>
<li>
<strong>
<ol>
I want to make it when I click on the <strong>, the <ol> which under same <li> be toggle.
code:
for(var i = 0, l = logSuites.length; i < l; i++) {
var suite = logSuites[i];
$("#tests").append('<li><strong>....</strong></li>');
var suiteSl = $('#tests > li:nth-child(' + (i + 1) + ')');
var caseli = "<li>...</li>...";
...
suiteSl.append('<ol style="display:none">' + caseli + '</ol>');
var caseLiSl = suiteSl.children('ol');
var toggleCases = function() {
caseLiSl.toggle();
};
suiteSl.children('strong').bind('click', toggleCases);
}
the problem is everytime the event being triggered, the last <ol> be toggled.. The variable caseLiSl in function toggleCases is always the last time it settled in the for loop
That’s a more common problem. We use closures in a loop (for or while) and it always keep the last value of the increment.
Answer can be found in this question: Doesn't JavaScript support closures with local variables?
I think you should do
suiteSl.children('strong').bind('click', function(){
$(this).children('ol').toggle();
});
in this way the this variable refers to the clicked element and you toggle only it's direct <ol> children
This is again the usual closure in a loop problem.
You probably should read this: http://www.mennovanslooten.nl/blog/post/62