I have a loop that adds elements to a dropdown.
The elements get added, and I want to add an event that happens when the user clicks on one of the elements.
So I am adding an event listener like this.
for (var i = 0; cities.length > i; i++) {
if (cities[i].toLowerCase().indexOf(value.toLowerCase()) !== -1 && citiesList <= 10) {
citiesList ++;
dropdown.classList.add('show');
dropdown.innerHTML += '<a class="dropdown-item" id="dropdown_item-' + i + '" href="#">' + cities[i] + '</a>';
var item = document.getElementById('dropdown_item-' + i);
item.addEventListener("click", function(){ console.log("test") });
console.log(item);
}
}
But when I click on the elements the event does not fire even though console.log logs the item correctly and when I add the listener manually through the console it works. I tried using a timeout function but to no prevail.
When you += with the innerHTML of a container, the container's contents get completely re-parsed from their HTML markup alone. Anything else (such as event listeners not in the HTML markup) will be lost. Use insertAdjacentHTML instead:
The insertAdjacentHTML() method of the Element interface parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. It does not reparse the element it is being used on, and thus it does not corrupt the existing elements inside that element. This avoids the extra step of serialization, making it much faster than direct innerHTML manipulation.
dropdown.insertAdjacentHTML('beforeend', '<a class="dropdown-item" id="dropdown_item-' + i + '" href="#">' + cities[i] + '</a>');
That said, it looks like you may be giving the element an ID just so that you can select it and subsequently attach a listener to it. Dynamic IDs are a pretty bad idea - if the reason for the ID, it would be better to create the element using createElement instead:
const a = document.createElement('a');
a.className = "dropdown-item";
a.textContent = cities[i];
a.addEventListener("click", function() {
console.log("test")
});
dropdown.appendChild(a);
Keep in mind that i will equal to cities.length after the end of the loop, given your current code. If you reference i inside the listener callback, if you want it to reference the i used for that particular iteration, you need a block-scoped i instead: change
for (var i = 0; cities.length > i; i++) {
to
for (let i = 0; cities.length > i; i++) {
(or, even better, use Array.prototype.forEach or something like that instead of manually messing with indicies)
Related
I'm trying to create a chrome extension. I had a problem with the affectation of event for the new element that i append to the dom of site with content. Js
If I add an event to an element' 'for example class' exist already in the page, it works correctly. Just for my new appended element((in the code iadded a button ,the event is just an alert to test))
function tst() {
myclass = $("._3hg-._42ft");
myclass = myclass.not(".supp");
myclass.addClass("supp");
var patt = /https:\/\/(.)*\.facebook\.com\/(.)*\/(posts|photos|videos)\/(\w|\.|\d)*/g;
for (i = 0; i < myclass.length; i++) {
result = patt.exec(myclass[i]);
myclass.append('<button class="fact" id=' + result[0] + ' style="position: absolute;">fact</button>');
};
/* this is a simple event*/
/***********************/
$(".fact").on('click', function() {
alert("no event work ");
});
Making somewhat broad assumption here in my answer that it is JavaScript/jQuery related and is NOT an extension...or is so still in that context.
You need to attach the event to the container here perhaps for the dynamically created elements. Lots of global stuff, suggested to not do that, updated there.
Appends a lot of buttons perhaps? might need to only hit DOM once but left as-is in this isolated function.
function tst() {
let myclass = $("._3hg-._42ft")
.not(".supp");
myclass.addClass("supp");
//let result = {};
var patt = /https:\/\/(.)*\.facebook\.com\/(.)*\/(posts|photos|videos)\/(\w|\.|\d)*/g;
var i = 0; //avoid global
for (i; i < myclass.length; i++) {
// broad assumption of the returned value from patt.exec() here
// not even sure why it needs an id, have a class, use for css
let result = patt.exec(myclass[i]);
myclass.append('<button class="fact" id="' + result[0] + '">fact</button>');
}
/* attache event to pre-existing element */
/***********************/
myclass.on('click', ".fact", function() {
alert("event works");
});
}
button.fact {
position: absolute;
}
Using a 'for' loop, let's say I want to make 4 different 'p' elements in the body with 4 different IDs. I approached the problem as follows:
for (var i = 1; i <= 4; i++) {
$("body").append(document.createElement("p"));
$("p").attr('id', 'paragraph' + i);
}
This was a very silly mistake on my part, heres why:
Every time the loop increments, it creates another p element, which is what I want it to do. However, it assigns ALL the p elements to the latest value of 'i'. So when the loop is executed all of of p elements would have a value of four.
My question is: Is there a way to assign the 'p' element's ID to the current value of 'i' when the 'p' element is appended?
To be clear, this is my goal:
<p id="paragraph1"></p>
<p id="paragraph2"></p>
<p id="paragraph3"></p>
<p id="paragraph4"></p>
But doing it through jQuery without editing the actual HTML file.
Create element and set attribute first
for (var i = 1; i <= 4; i++) {
var p= document.createElement("p");
p.setAttribute("id", "paragraph" + i);
$("body").append(p);
}
Here you go with the solution https://jsfiddle.net/7snh10zz/
for (var i = 1; i <= 4; i++) {
$("body").append("<p id='paragraph" + i + "'>Paragraph " + i + "</p>");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
One more way to do it https://jsfiddle.net/7snh10zz/2/
for (var i = 1; i <= 4; i++) {
$("body").append(document.createElement("p"));
$("p:last-child").attr('id', 'paragraph' + i).text("Paragraph " + i);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Instead of using document.createElement(), use jQuery:
$("body").append($("<p/>", { id: "paragraph" + i }));
When you create an element with jQuery by passing in the HTML fragment for the element you want, you can add an object that provides properties for the new object. That can include a wide variety of things, including native DOM stuff as well as things like jQuery event handlers. For example, you can add text to your paragraph:
$("body").append($("<p/>", {
id: "paragraph" + i,
text: "Hello I am paragraph number " + i
}));
Your code mean,$("p") select all created elements and give the id.
In 1st iteration,your body has one p element with id paragraph1.
In 2nd iteration, your body has two p elements with ids paragraph2.It override the first <p> tag's id.
In 3rd iteration, your body has three p elements and your code select these three elements and give id of paragraph3 to all tags.
In these way, the last iteration cause your body to have four p elements with ids of paragraph4.
You can fix by using this code.
function appendText() {
for (var i = 1; i <= 4; i++) {
var txt = "<p id='paragraph"+i+"'></p>";
$("body").append(txt);
}
}
it's selector problem
$("p:last").attr('id', 'paragraph' + i);
will select last p html tags and give it the current i value
check this link
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.
It's very difficult for me to show you my code, as it's all over the place, but what I'm trying to do is this:
I am injecting html code into the DOM in a function buy using .innerHTML, I wish to add a click event to an icon that is being injected in this step, as at this moment in time I know its id. So after I've injected it I write:
document.getElementById(product.id+"x").addEventListener("click", removeItem);
product.id is created above and this element is a 'X' button, that when clicked will be removed from the screen.
The trouble is, this code is run many times as there are many items to be displayed on the screen. And when finished, only the last even made fires when the 'X' button is pressed.
Any suggestions?
EDIT:
I am unable to use jquery in this project.
Here is my code:
function createHTML(targetID, product) {
var target = document.getElementById(targetID);
total = (parseFloat(total) + parseFloat(product.price)).toFixed(2);;
target.innerHTML += '<article class="item" id="'+product.id+'"><img class="item_img" src="../'+product.image+'" width=100 height=100><h1 class="item_name">'+product.name+'</h1><p class="item_description">'+product.desc+'</p><h1 class="item_quantity">Quantity: '+product.quantity+'</h1><h1 class="item_price">£'+product.price+'</h1><i id="'+product.id+'x" class="fa fa-times"></i></article>';
document.getElementById(product.id+"x").addEventListener("click", removeItem, true);
}
So you're adding new elements to a container by overwriting the innerHTML or appending to it using +=. This is your problem. When you overwrite the innerHTML or append to it, you are destroying and recreating all elements within it and this causes them to lose any bound event handlers (ie your click handler).
This fiddle reproduces your problem. Only the last button has a click handler.
The solution is to build DOM elements using document.createElement() and use appendChild() or similar to append them, instead of creating/appending raw HTML. This way, your previous elements event handlers will remain intact.
This Fiddle uses DOM nodes instead of raw HTML and all buttons have a click handler.
Example fix:
var container = document.getElementById("container");
var elem;
function clicky(){
alert("clicked");
}
for(var i=0; i<4; i++){
elem = document.createElement('button');
elem.id = "btn_" + i;
elem.appendChild(document.createTextNode('Click'));
elem.addEventListener("click", clicky);
container.appendChild(elem);
}
I quess you do something like that
//Place where you add elements.
var container = document.body;
you create element and add listener to that element(button):
var button = '<button id="btn1x">Button 1</button>';
container.innerHTML += button;
//product.id = 'btn1';
document.getElementById(product.id+"x").addEventListener("click", removeItem);
and then you add in the same way new elements and add for them event listeners before next element will be generated.
If my quess is right, then your problem is that you replace whole content of container so previous event listens are lost.
stringVariable += 'abc' is the same as stringVariable = stringVariable + 'abc'. Because of that you overwrite html.
You should create elements from functions, not from string as you do now.
var button = document.createElement('button');
button.id = product.id + 'x';
button.innerText = 'Button 1'; // Title of button.
//Add button to container.
container.appendChild(button);
//Add event listener to created button.
button.addEventListener('click', myFunc);
UPDATE:
There are a way to parse your string to element.
First create container where will be set inner html from string, then get from that temp container first element (or more elements, depends from your html string), then add them to container and add to these elements listeners.
DEMO: http://jsfiddle.net/3cD4G/1/
HTML:
<div id="container">
</div>
Javascript:
var container = document.getElementById("container");
function clicky(){
alert("clicked");
}
var tempContainer = document.createElement('div');
for(var i=0; i<4; i++){
//Create your element as string.
var strElem = "<button type='button' id='btn_" + i + "'>Click</button>";
//Add that string to temp container (his html will be replaced, not added).
tempContainer.innerHTML = strElem.trim();//Trim function used to prevent empty textnodes before element.
//Get element from temp container.
var button = tempContainer.children[0];
//Empty tempContainer for better security (But about which security I'm talking in JavaScript in string element generation :) )
tempContainer.innerHTML = '';
//Add your button to container.
container.appendChild(button);
//Add event listener to button:
//document.getElementById("btn_" + i).onclick = clicky;
//Better way to add event listener:
button.addEventListener('click', clicky);
}
DEMO: http://jsfiddle.net/3cD4G/1/
I'm trying to add an event listener to some elements which I generate in the loop. I must use div.lastChild - although in this example it's pretty stupid. But that's just demonstration:
<div id="someArea">
</div>
<script type="text/javascript">
var func = function() {
alert('Callback works');
}
var func1 = function() {
alert('Test');
}
var func2 = function() {
alert('Test2');
}
var div = document.getElementById("someArea");
var callbacks = [func, func1, func2];
for(var i = 0; i <= 2; i++) {
div.innerHTML += '<input type="button" value="' + i + '" />';
(function(i) {
console.log(div.lastChild);
div.lastChild.addEventListener('click', callbacks[i], false);
}(i));
}
</script>
The event works only for the last button. What am I missing here?
Why it's not working.
When you do this...
div.innerHTML += '<input type="button" value="' + i + '" />';
...in every iteration of the loop you're destroying the old DOM nodes inside div (and therefore their handlers), and recreating new nodes (but no handlers). The destruction includes the input elements that were added in previous iterations.
That's why only the last one works, since after that point, you've assigned the handler to the last element, but the other ones are brand new and untouched.
A solution.
Instead of treating the DOM as though it was a string of HTML markup, consider using DOM methods for element creation...
for(var i = 0; i <= 2; i++) {
var input = document.createElement('input');
input.type = 'button';
input.value = i;
input.addEventListener('click', callbacks[i], false);
div.appendChild(input);
}
Notice that I removed the immediately invoked function. For your code, it was unnecessary since i is being evaluated in the loop instead of later in the handler.
The DOM is not HTML, but .innerHTML makes you think it is.
It's important to understand innerHTML. When working with the DOM, there is no HTML markup. So when you get the .innerHTML, the DOM is being analyzed, and a new HTML string is created.
When you assign to .innerHTML, you're destroying all the current content, and replacing it with new nodes created from the HTML string.
So when you do...
div.innerHTML += '<input...>'
...you're first creating the new HTML string from the current content, then concatenating the new HTML content to the string, then destroying the old nodes and creating new ones from the new string.
This is terribly inefficient, and as you've already seen, it destroys any data associated with the original elements, including handlers.