JQuery for loop stuck at last index [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
jQuery Looping and Attaching Click Events
(4 answers)
Closed 9 years ago.
I have function process_row that appends tags to html, and those tags are chained to a function upon clicked. (in this case, simply alert(i), its position in the result array).
But however, upon being clicked, the newly generated alerts the length of the entire result array. I have tried many, many changes to try and make it work, but it doesn't.
Strange thou, fab_div.attr("id", result_data[0]); works fine !! In Chrome inspect element the id tags are displayed as they are, but the click function points everything to the last element in the array.
for example, if I do, fab_div.click(function () { alert(result_data[0]) });, I get the name of the LAST element in the array, doesn't matter which element was clicked.
can anyone please explain to me... WHY??
I think it may have something to do with $("<div>") where JQuery thinks it's the same div that it's assigning to. Is there any way around this? The 's are generated dynamically and I would not want to let PHP do the echoing. Plus the content may be updated realtime.
Example dataset :
Smith_Jones#Smith#Jones#janet_Moore#Janet#Moore#Andrew_Wilson#Andrew#Wilson
After many, many changes, still not working:
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>");
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
fab_div.append(fab_text)
// fab_div.click(function () { alert(i) });
// ^ not working, try appending list of id's to id_list
id_list.push(result_data[0])
$('#ls_admin').append(fab_div)
}
for(j = 0; j < id_list.length; j++){
$('#' + id_list[j]).click(function () { alert(j) })
}
}
}
Original Attempt:
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>").append(fab_text).click(function () { alert(i) });
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
$('#ls_admin').append(fab_div)
}
}
}

If you must use an alert, then you can encapsulate the click handler in a self executing function and pass the index to it. Like,
(function (index) {
fab_div.click(function () {
alert(index);
});
})(i);
Although, this is not a clean way to do it. Otherwise, if you are looking to just manipulate the div element is any way, then adding any method directly will also work. Like,
fab_div.click(function () {
alert($(this).attr('id'));
});
You can refer a jsFiddle here

Wonky Solution, but it worked! Haha! Big thanks to Kevin B.
function process_row(data){
result_array = data.split("#");
if(result_array.length > 0){
result_data =result_array[0].split("#");
for(i = 0; i < result_array.length; i++){
result_data =result_array[i].split("#");
var fab_text = result_data[1] + " " + result_data[2]
var fab_div = $("<div>").append(fab_text);
fab_div.addClass('scroll_tap');
fab_div.attr("id", result_data[0]);
$('#ls_admin').append(fab_div)
}
$("#ls_admin").children(this).each(function( index ) {
$(this).append($(this).click(function () { alert($(this).text()) }));
});
}
}

Related

How do you make an IIFE remember **each** instance of a variable when dealing with event listeners?

I'm working with a list of images that may change in number, so fixed IDs and event listeners are not practical. The below code produces the correct number of buttons with the correct IDs, but only the last one has a functional event listener.
for (var i = 0; i < amount; i++) {
!function(index) {
if (items[index].classList.contains('current')) {
document.getElementById('selectButtons').innerHTML += '<button id=\"bitems' + index + '\"> ⬤ <span class=\"offscreen\">Item ' + i + '</span></button>';
}
else
{
document.getElementById('selectButtons').innerHTML += '<button id=\"bitems' + index + '\"> ◯ <span class=\"offscreen\">Item ' + i + '</span></button>';
}
document.getElementById('bitems' + index).addEventListener("click", function(ev) {
alert("clicked");
});
}(i);
}
Apparently the IIFE is not storing the individual variables like it is supposed to, but I can't figure out why. After all, that is the entire purpose of an IIFE within a loop.
Any help would be greatly appreciated.
IIFE is working fine. Actually every time you update the innerHTML for selectButtons, the DOM is recreated, and all the events attached to it are gone!
Instead of updating the innerHTML in each iteration, you can append the buttons to it instead like:
for (var i = 0; i < 10; i++) {
!function(index) {
var button = document.createElement("BUTTON");
var t = document.createTextNode("Button" + index);
button.appendChild(t);
document.getElementById('selectButtons').appendChild(button);
button.addEventListener("click", function(ev) {
alert("clicked +" +index);
});
}(i);
}
Please do add the conditions around it that you need.
Every time you do innerHTML += you are replacing the entire HTML, which removes any previously installed event handlers. This is one perfectly good reason not to treat HTML as a bunch of strings that you innerHTML onto the page. Instead of strings, think in terms of elements, as in another answer. Then you also don't need to use IDs as a poor man's "variable name" to reference elements; you can just use the element itself.
You don't need a clumsy IIFE. That's what let is for.
Here's a cleaned-up version of your code:
var buttons = document.getElementById('selectButtons');
for (let i = 0; i < amount; i++) {
var current = items[i].classList.contains('current');
var button = document.createElement("BUTTON");
var bullet = document.createTextNode(current ? '◯' : '⬤')
var span = document.createElement('span');
van spanText = `Item ${i}`;
span.className = 'offscreen';
span.appendChild(spanText);
button.appendChild(bullet);
button.appendChild(span);
buttons.appendChild(button);
button.addEventListener('click', () => alert(`clicked ${i}`));
}
If you want to save a line or two, you could take advantage of the fact that appendChild returns the appended child, and chain:
buttons.appendChild(button).appendChild(span).appendChild(spanText);
If you're going to be doing a lot of this, it would be best to create some tiny utility routines:
function createElementWithText(tag, text) {
var b = document.createElement(tag);
var t = document.createTextNode(text);
b.appendChild(t);
return b;
}
function button(text) { return createElementWithText('button', text); }
function span(text) { return createElementWithText('span', text); }
Now you can write your code more concisely as:
var buttons = document.getElementById('selectButtons');
for (let i = 0; i < amount; i++) {
var current = items[i].classList.contains('current');
var button = button(current ? '◯' : '⬤');
var span = span(`Item ${i}`);
span.className = 'offscreen';
buttons.appendChild(button).appendChild(span);
button.addEventListener('click', () => alert(`clicked ${i}`));
}
Actually, it would moderately preferable to create a document fragment, add all the buttons to it in advance, then insert it into the DOM a single time.
However, in practice, you would be better off using some kind of templating language, in which you could write something like:
<div id="selectButtons">
{{for i upto amount}}
<button {{listen 'click' clicked}}>
{{if items[i] hasClass 'current'}}◯{{else}}⬤{{endIf}}
<span class="offscreen">Index {{i}}</span>
</button>
{{endFor}}
</div>
It's beyond the scope of this answer to recommend a particular templating language. There are many good ones out there, such as Mustache, that google can help you find, with a search such as "javascript templating languages".

How to have a dynamic variable in a function without linking it to a changing variable [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
sorry for the title but I'm not sure what the correct terminology is for this issue. I am trying to dynamically generate a table of clickable elements using for loops in JavaScript. Each of the elements, when clicked, should trigger the same function but with different parameters. In my code I am setting the onClick function up like so:
elementArray[i].onClick = function() { clickFunction(i) };
However, when I do this the clickFunction is just taking whatever value i is currently set to, not what it was when I set the onClick function, which is what I want. Any ideas?
As I got your question you want a table having clickable element calling to same function with diff param. [Let me know if it's not your question]
So taking i (index or row no.) would be param to that function.
HTML :
<table id='myTable'></table>
Javascript :
for(i = 9; i >= 0; i--) {
var table = document.getElementById("myTable");
// Create an empty <tr> element and add it to the 1st position of the table:
var row = table.insertRow(0);
// Insert new cells (<td> elements) at the 1st and 2nd position of the "new" <tr> element:
var cell = row.insertCell(0);
// Add some text to the new cells:
cell.innerHTML = "<input type='button' onclick='callIt("+i+")'";
}
function callIt(index) {
alert(index);
}
I am considering that you want button to click.
Hope it will help you.
more you can get on w3schools
Here's a version of #Bhojendra Nepal's "duplicate question" applied to your question. With this HTML:
<div class="element-0">0</div>
<div class="element-1">1</div>
<div class="element-2">2</div>
And this script:
var elementArray = [];
var creatFunc = function($this,i){
return function() {
console.log(i);
$this.html(i + 1);
if (i>1) i++;
};
};
for (var i = 0; i < 3; i++) {
elementArray[i] = $('.element-' + i);
elementArray[i].click( creatFunc( elementArray[i] , i) );
}
by creating the functions in a separate function, we've isolated the variable. I added "if (i>1) i++;" to show how the new function keeps its own variable and can increment it (keep clicking on the third div to see the number keep increasing).
here's a jsfiddle:
https://jsfiddle.net/mckinleymedia/1cksb8ky/
I hope this helps.
Oh, I added jQuery to this. I'm not positive how to get this working in vanilla javascript off hand.

Simplifying a javascript function with repeated similar lines (with a loop?)

Okay, I hope you don't all facepalm when you see this - I'm still finding my way around javascript.
I am putting together an RSVP form for a wedding website.
I want the guests to be able to add their names to the RSVP form, but only have as many fields showing as required. To this end, after each name field, there is a link to click, which will, when clicked, show a name field for the next guest.
The code below works... but I am sure it can be tidier.
I have tried to insert a for() loop into the code in several different ways, I can see that the for() loop increments correctly to the last value - but when it does so, it leaves only the last addEventListener in place. I can only assume, that I should be using a different kind of loop - or a different approach entirely.
How should I tidy up the following?
<script>
function showNextGuest(i) {
document.getElementsByTagName(\'fieldset\')[i].style.display = \'block\';
}
function initiateShowNextGuest() {
document.getElementsByTagName('fieldset')[0].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(1);},false);
document.getElementsByTagName('fieldset')[1].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(2);},false);
document.getElementsByTagName('fieldset')[2].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(3);},false);
document.getElementsByTagName('fieldset')[3].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(4);},false);
document.getElementsByTagName('fieldset')[4].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(5);},false);
}
window.onload = initiateShowNextGuest();
</script>
Your intuition is right - a for loop could indeed simplify it and so could a query selector:
var fieldsSet = document.querySelectorAll("fieldset"); // get all the field sets
var fieldss = [].slice.call(asSet); // convert the html selection to a JS array.
fields.map(function(field){
return field.querySelector("a"); // get the first link for the field
}).forEach(function(link, i){
// bind the event with the right index.
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
This can be shortened to:
var links = document.querySelectorAll("fieldset a:first-of-type");
[].forEach.call(links, function(link, i){
link.addEventListener("click", showNextGuest.bind(null, i+1), false);
});
function nextGuest () {
for(var i = 0; i < 5; i++){
document.getElementsByTagName('fieldset')[i]
.getElementsByTagName('a')[0]
.addEventListener('click',function(){
showNextGuest(parseInt(i + 1));
}, false);
}
}
Benjamin's answer above is the best given, so I have accepted it.
Nevertheless, for the sake of completeness, I wanted to show the (simpler, if less elegant) solution I used in the end, so that future readers can compare and contrast between the code in the question and the code below:
<script>
var initiateShowNextGuest = [];
function showNextGuest(j) {
document.getElementsByTagName('fieldset')[j].style.display = 'block';
}
function initiateShowNextGuestFunction(i) {
return function() {
var j = i + 1;
document.getElementsByTagName('fieldset')[i].getElementsByTagName('a')[0].addEventListener('click',function(){showNextGuest(j);},false);
};
}
function initiateShowNextGuests() {
for (var i = 0; i < 5; i++) {
initiateShowNextGuest[i] = initiateShowNextGuestFunction(i);
initiateShowNextGuest[i]();
}
}
window.onload = initiateShowNextGuests();
</script>
In summary, the function initiateShowNextGuests() loops through (and then executes) initiateShowNextGuestFunction(i) 5 times, setting up the 5 anonymous functions which are manually written out in the code in the original question, while avoiding the closure-loop problem.

for loop inside jquery function

I am trying to repeat something inside a jquery function. I tried a for loop, but it seems it doesnt like the syntax.
for instance i have the variable
var number = 2;
now i have
$('tr').html('<td id="'+number+'"></td>');
what i want to do is loop from 0 to number (0,1,2) so that in the end i end up having 3 .
Thanks
There is probably a better way, but this should work.
var loops = [1,2,3];
$.each(loops, function(index, val) {
$('tr').html('<td id="myCell' + index + '"></td>');
});
This should also work (regular JS):
var i;
for(i=0; i<3; i++) {
$('tr').html('<td id="myCell' + i + '"></td>');
}
Note how i prefixed id with the word 'myCell', to ensure XHTML compliancy. (thanks to #Peter Ajtai for pointing that out).
EDIT
I just noticed another problem - you're using the .html function to add the cells. But .html replaces the entire html of the matched element. So you'll only ever end up with the last cell. :)
You're probably looking for the .append function:
$('tr').append('<td id="myCell' + i + '"></td>');
EDIT 2 -- moved the double quote before myCell rather than after.
Heres an option using an anonymous function.
$('TR').html(
function(){
var content='';
for (var i=0; i<=2; i++ ){
content=content+'<td id="id_'+i+'"></td>';
}
return content;
}
)
This works for me:
loop.forEach((amount) => {
// your code
}

Passing values to onclick [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
If I create a whole lot of HTML elements using a loop, like
for (i= 1; i < 100; i++) {
var my_element = document.createElement ("td");
row.appendChild (my_element);
my_element.onclick = function () {my_function (i));
}
then when the element is clicked, the value of i passed to my_function is always 100, regardless of what number element is calling it. I have worked around this by using
my_element.id = "something"+i;
my_element.onclick = function (e) {my_function (e.target.id)};
(For Internet Explorer, the target needs to be srcElement, apparently.) I am curious to know whether there is any way to create the function without having to add the ID to the element like this.
The value of i changes with each iteration of the loop. You need a closure to capture the value of i:
(function(i) {
my_element.onclick = function () {my_function (i)};
}(i))
If you write a function which builds you a handler function, you can use the new scope which that gives you to ensure that you get the number you want. For example:
function BuildHandler (i) { return function () { alert(i); };
for (i= 1; i < 100; i++) {
var my_element = document.createElement ("td");
row.appendChild (my_element);
my_element.onclick = BuildHandler(i);
}
if I were you I will use Jquery (or prototype or whatever js frameworks that available)
on each elements you should add attributes like myid for example so that when you did on click you can retrive it.
for(i=1; i ++ ; i<100){
var myelement = "<td myid='something"+i+"' class='myTD'></td>" ;
row.append(myelement);
}
....
$(document).ready(function(){
$('.myTD').click(function(){
var id = $(this).attr('myid');
my_function(id);
});
});
I did this trick on my web app :)

Categories

Resources