I have a few elements on my webpage that share the class name "table-data". I am trying to loop through them and give each one of them a unique id, then I want all them to have the same onclick attribute. I was able to successfully give each one of them a unique id with pure JavaScript, but the part that I am having trouble with is giving them all the same onclick attribute. I tried with JavaScript and jQuery and failed using both.
Here is my code:
// this counter will be used to give each element a unique id
var counter = 0;
// x holds all elements with the class name "table-data"
var x = document.getElementsByClassName("table-data");
// loop through all the elements in the array x
for (var i = 0; i < x.length; i++) {
// give each element the name "idNum" plus whatever number the counter variable holds
var idName = 'idNum' + String(counter);
// set the id name to the variable defined above
x[i].setAttribute('id', idName);
// here is the part I am struggling with, this section is supposed to alert the id of the element when it is clicked
$('#' + idName).click(function () {
alert(idName);
});
// increment the counter
counter += 1;
The problem I am having is that all elements are showing the idName of the last element in the array "x". so if there are 5 elements in total and I run this code on them, they will all show the idName of the 5th element when clicked. I tried to alert() the idName outside of the onclick function and make it so the loop will alert the idName of each element as soon as it is assigned and that worked just fine. I would get five different ids for five different elements, so what is causing the problem inside the onclick function?
I also tried the following and it didn't work either:
x[i].setAttribute('onclick', 'call_alert(this.idName)');
function call_alert(clickedId) {
alert(clickedId);
}
I am very new to web programming so I am sorry if there are any obvious mistakes in my code.
You're making this much more complicated than your need to. You don't need to add an id to every element, you can simply bind on a class using the class(.) selector and then add the click binding all in one go:
$(document).ready(function() {
//match on the class
$('.table-data').click(function(){
//access the actual element clicked using $(this)
alert('clicked'+ $(this).text());
});
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
1
2
You won't need counter variable as you're already i:
//var counter = 0;
var x = document.getElementsByClassName("table-data");
for (var i = 0; i < x.length; i++) {
var idName = 'idNum' + String(i);
x[i].setAttribute('id', idName);
x[i].onclick = function() {
console.log(this.id);
}
//counter += 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="table-data">table1</div>
<div class="table-data">table2</div>
<div class="table-data">table3</div>
<div class="table-data">table4</div>
<div class="table-data">table5</div>
<div class="table-data">table6</div>
jQuery version:
$(".table-data").each((idx, element) => {
$(element).attr("id", "idNum" + String(idx));
$(element).on("click", () => {
console.log($(element).attr("id"));
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="table-data">table1</div>
<div class="table-data">table2</div>
<div class="table-data">table3</div>
<div class="table-data">table4</div>
<div class="table-data">table5</div>
<div class="table-data">table6</div>
Related
I am programmatically generating some HTML, and trying to add a listener for a change event the elements.
This works fine for the first object, but as soon as I add the second object the first one stops firing the event.
In the code example below you'll see the updateLabel function only fires for the last object created. I need it to fire for all of the objects.
I have tried with .onchange, and with an event listener, but get the same results.
Any help much appreciated.
<html>
<body>
<div id="main">
<!--this is where all the generated HTML goes -->
</div>
</body>
<script>
mainDomElement = document.querySelector("#main");
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count;
newHTML = '<input class="accordionLabel" type="text" id="' + labelId + '" value="' + labelId + '"/>'
currentHTML = mainDomElement.innerHTML
mainDomElement.innerHTML = newHTML + currentHTML
labelDomObj = document.querySelector('#' + labelId);
//labelDomObj.addEventListener("change", updateLabel);
labelDomObj.onchange = function(event){updateLabel(event)}
}
function updateLabel(event)
{
alert(event.target.value);
}
</script>
</html>
It may be best to take a different approach when creating and adding DOM elements. Try this.
for (var count = 0; count < 4; count++)
{
var labelId = 'Label' + count,
newHTML = document.createElement('input');
newHTML.type = 'text';
newHTML.value = labelId;
newHTML.id = labelId;
newHTML.addEventListener('onchange', updateLabel, false);
mainDomElement.appendChild(newHTML);
}
your code explanation
// this takes the main DOM element and stores a copy of it in the variable.
currentHTML = mainDomElement.innerHTML
/*
This is taking the innerHTML property of the main DOM element. It is then
trying to concatenate the newly created DOM to that stored in the mainDomElement
variable. I don’t think this is what you want.
*/
mainDomElement.innerHTML = newHTML + currentHTML
// this is trying to select an element from the DOM that does not exist yet.
labelDomObj = document.querySelector('#' + labelId);
// This is trying to add an event listener to an element that does not exist.
labelDomObj.onchange = function(event){updateLabel(event)}
You are also missing sim icons from your variable declarations.
This looks like an issue with closures, where initializing var count = 0 within the for loop ultimately results in only the last value getting bound to the event handler.
I believe moving the initialization outside of the loop will fix your issue. Also, ES6 introduced the let keyword that scopes the variable in the way you are expecting:
for (let count = 0; count < 4; count++) { }
See this excellent introduction to javascript closures for more information.
I am creating a form dynamically and therefore edit the form elements’ properties. When attempting to change the label, assigning an auto-generated id works fine but when changing this label using the generated id, the function or keyup() from jQuery keeps calling all the previously created label id(s). this means when i want to edit one label, it ends up editing every label.
HTML
<input type="text" id="change-label"><br><br>
<button id="add-button">add label</button>
<div id="add-label"></div>
JavaScript/jQuery
$('#add-button').click(function(){
var div = document.createElement('div');
var textLabel = document.createElement('label');
var labelNode = document.createTextNode('untitled');
textLabel.appendChild(labelNode);
textLabel.id = autoIdClosure();
$('#change-label').val('untitled');
div.appendChild(textLabel);
$('#add-label').append(div);
});
var autoIdClosure = (function(){
var counter = 0;
var labelId = "textInputLabel";
return function(){
counter += 1;
var id = labelId + counter;
editLabelWrapper(id)
return id;
}
})();
function editLabelWrapper(id){
function editLabel(){
var value = $(this).val();
$("#"+id).text(value);
}
$("#change-label").keyup(editLabel).keyup();
}
I’ve already found an alternative using onkeyup="$('#'+globaID).text($(this).val());", but I need to understand what I was doing wrong so I can learn from it.
JSFiddle
I think you are overthinking the matter...
Instead of using an unique id, rather use classes, makes it easier to handle.
So change <div id="add-label"></div> to <div class="add-label"></div>
Then what you want to do is, when a value is given in #change-label you want it in the last div.add-label.
So the function will become this:
$("#change-label").on('keyup', function() {
$('.add-label:last').text( $(this).val() );
});
Next what you want to do is bind a function to #add-button. Once it gets clicked, we want to add a new div.add-label after the last one. And empty the #change-label. You can do that by using this function:
$('#add-button').on('click', function() {
$('.add-label:last').after('<div class="add-label"></div>');
$('#change-label').val('');
});
Updated Fiddle
I've got multiple a elements, that are generated on the page view by a for loop that when they are clicked, send an ajax post to the database, to mark the item in the database by its id, that is equal to the value in the a element but my code is not working, anybody have any insight on why? Thanks.
<a href='#' value='(loaded from database)' id='markAsRead'>
var element = document.getElementById('markAsRead');
element.addEventListener('click', function() {
alert(this.value);
});
Anchors don't have values like inputs do, you should use a data-attribute
<a href='#' data-value='(loaded from database)' id='markAsRead'>
var element = document.getElementById('markAsRead');
element.addEventListener('click', function() {
alert(this.getAttribute("data-value"));
});
the id attribute must be unique.
if you want to have a group of controls with the same identifier you can use the name attribite.
if you look at the functions for dom manipulation:
document.getElementById() - Element (singular)
document.getElementsByName() - Elements (plural)
An example of adding a unique (depending on circumstances) id
for (var index = 0; index < 10; index++) {
var element = document.createElement("a");
element.setAttribute("data-value", "your value");
element.setAttribute("id", "markAsRead_" + index);
element.addEventListener('click', function() {
alert(this.getAttribute("data-value"));
});
document.getElementsByTagName("body")[0].appendChild(element);
}
I have a snippet of code that applies a highlighting effect to list items in a menu (due to the fact that the menu items are just POST), to give users feedback. I have created a second step to the menu and would like to apply it to any element with a class of .highlight. Can't get it to work though, here's my current code:
[deleted old code]
The obvious work-around is to create a new id (say, '#highlighter2) and just copy and paste the code. But I'm curious if there's a more efficient way to apply the effect to a class instead of ID?
UPDATE (here is my updated code):
The script above DOES work on the first ul. The second ul, which appears via jquery (perhaps that's the issue, it's initially set to hidden). Here's relevant HTML (sort of a lot to understand, but note the hidden second div. I think this might be the culprit. Like I said, first list works flawlessly, highlights and all. But the second list does nothing.)?
//Do something when the DOM is ready:
<script type="text/javascript">
$(document).ready(function() {
$('#foo li, #foo2 li').click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
//When a link in div is clicked, do something:
$('#selectCompany a').click(function() {
//Fade in second box:
//Get id from clicked link:
var id = $(this).attr('id');
$.ajax({
type: 'POST',
url: 'getFileInfo.php',
data: {'id': id},
success: function(msg){
//everything echoed in your PHP-File will be in the 'msg' variable:
$('#selectCompanyUser').html(msg)
$('#selectCompanyUser').fadeIn(400);
}
});
});
});
</script>
<div id="selectCompany" class="panelNormal">
<ul id="foo">
<?
// see if any rows were returned
if (mysql_num_rows($membersresult) > 0) {
// yes
// print them one after another
while($row = mysql_fetch_object($membersresult)) {
echo "<li>"."".$row->company.""."</li>";
}
}
else {
// no
// print status message
echo "No rows found!";
}
// free result set memory
mysql_free_result($membersresult);
// close connection
mysql_close($link);
?>
</ul>
</div>
<!-- Second Box: initially hidden with CSS "display: none;" -->
<div id="selectCompanyUser" class="panelNormal" style="display: none;">
<div class="splitter"></div>
</div>
You could just create #highlighter2 and make your code block into a function that takes the ID value and then just call it twice:
function hookupHighlight(id) {
var context = document.getElementById(id);
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
}
hookupHighlight("highliter1");
hookupHighlight("highliter2");
jQuery would make this easier in a lot of ways as that entire block would collapse to this:
$("#highlighter1 li, #highlighter2 li").click(function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
If any of the objects you want to click on are not initially present when you run this jQuery code, then you would have to use this instead:
$("#highlighter1 li, #highlighter2 li").live("click", function() {
// do ajax stuff
$(this).siblings('li').removeClass('highlight');
$(this).addClass('highlight');
});
change the replace in /highlight/ig, it works on http://jsfiddle.net/8RArn/
var context = document.getElementById('highlighter');
var items = context.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
items[i].addEventListener('click', function() {
// do AJAX stuff
// remove the "highlight" class from all list items
for (var j = 0; j < items.length; j++) {
var classname = items[j].className;
items[j].className = classname.replace(/highlight/ig, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
}, false);
}
So all those guys that are saying just use jQuery are handing out bad advice. It might be a quick fix for now, but its no replacement for actually learning Javascript.
There is a very powerful feature in Javascript called closures that will solve this problem for you in a jiffy:
var addTheListeners = function (its) {
var itemPtr;
var listener = function () {
// do AJAX stuff
// just need to visit one item now
if (itemPtr) {
var classname = itemPtr.className;
itemPtr.className = classname.replace(/\bhighlight\b/i, '');
}
// set the "highlight" class on the clicked item
this.className += ' highlight';
itemPtr = this;
}
for (var i = 0; i < its.length; i++) {
its[i].addEventListener ('click', listener, false);
}
}
and then:
var context = document.getElementById ('highlighter');
var items = context.getElementsByTagName ('li');
addTheListeners (items);
And you can call add the listeners for distinct sets of doc elements as many times as you want.
addTheListeners works by defining one var to store the list's currently selected item each time it is called and then all of the listener functions defined below it have shared access to this variable even after addTheListeners has returned (this is the closure part).
This code is also much more efficient than yours for two reasons:
You no longer iterate through all the items just to remove a class from one of them
You aren't defining functions inside of a for loop (you should never do this, not only for efficiency reasons but one day you are going to be tempted to use that i variable and its going to cause you some problems because of the closures thing I mentioned above)
how do i call a function to count the number of divs with an id of 'd1' after the page loads. right now i have it in my section but doesnt that execute the script before anything in the loads? because it works if i put the code below the div tags...
Firstly there should be at most one because IDs aren't meant to be repeated.
Second, in straight Javascript you can call getElementById() to verify it exists or getElementsByTagName() to loop through all the divs and count the number that match your criteria.
var elem = document.getElementById("d1");
if (elem) {
// it exists
}
or
var divs = document.getElementsByTagName("div");
var count = 0;
for (var i = 0; i < divs.length; i++) {
var div = divs[i];
if (div.id == "d1") {
count++;
}
}
But I can't guarantee the correct behaviour of this because like I said, IDs are meant to be unique and when they're not behaviour is undefined.
Use jQuery's document.ready() or hook up to the onLoad event.
well an ID should be unique so the answer should be one.
you can use <body onload='myFunc()'> to call a script once the DOM is loaded.
You need to have the function tied to the onload event, like so:
window.onload = function() {
var divElements = document.getElementById("d1");
var divCount = divElements.length;
alert(divCount);
};
For the record, you should only have one div with that ID, as having more than one is invalid and may cause problems.