Javascript addEventListener inside a loop - javascript

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.

Related

addEventListener getting ignored

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)

Why my Chrome extension event doesn't work?

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;
}

Input box value vanishes when adding a new one

I'm working on a web app where I need to add a number of input boxes one after the other in order to get commands from the user. I add them using JavaScript to a div with a unique ID to each. The problem I have is once I press enter and the JavaScript function is called to add the next one, the previous input box empties out, and I don't know why.
Here is sample code:
var i = 0;
add_input();
function add_input() {
i++;
document.getElementById('main').innerHTML += "<p>> <input type='text' style='width:90%' id='input" + i + "' onkeypress='press_key(event, this)'></p>";
document.getElementById('input' + i).focus();
}
function press_key(e, t) {
if (e.keyCode == 13) {
add_input();
}
}
<div id='main'></div>
innerHTML will override all existing content and replace them with new ones. You should create a new input element and use insertNode instead.
The addition assignment operator will add the right hand value to the left hand value and then assign the resultant value to the left hand side.
For a quick example:
x += y;
// is equivalent to
x = x + y;
In your code you are basically taking the existing HTML, adding a new chunk of HTML and then assigning that new HTML to the original element replacing the existing HTML. Since the value is not set in the HTML but stored in the DOM it is lost as soon as you assign new HTML to the element (which is when the browser renders it to the DOM replacing the previous DOM).
You could use insertNode as mentioned above or set the HTML attribute to store the value first as the below example shows. However note that this solution is purely to show why the values are disappearing. Doing it this way has an issue that if any of the previous input values are changed only the original value for those inputs would be preserved.
var i = 0;
add_input();
function add_input() {
var curInput = document.getElementById('input' + i);
if (curInput) {
curInput.setAttribute('value', curInput.value);
}
++i;
document.getElementById('main').innerHTML += "<p>> <input type='text' style='width:90%' id='input" + i + "' onkeypress='press_key(event, this)'></p>";
document.getElementById('input' + i).focus();
}
function press_key(e, t) {
if (e.keyCode == 13) {
add_input();
}
}
<div id='main'></div>
innerHTML overwrites all html from the selected element including any user/javascript actions performed on the given html. Thus your input values will be erased with the new html. You are going to want to create an element and then use appendChild. This will maintain the state of your current html elements.
var i = 0;
function add_input()
{
i++;
var input = document.createElement('input');
input.onkeypress=press_key;
input.id = 'input' + i;
document.body.appendChild(input);
input.focus();
}
function press_key(e)
{
//`t` argument is no longer used. Use `this` instead.
if (e.keyCode == 13)
{
add_input();
}
}
<html>
<head>
<script>
</script>
</head>
<body onload='add_input()'>
<div id='main'>
</div>
</body>
</html>
As stated above, your other values disappear due to the inner workings of "innerHTML". In fact, when you do string.innerHTML += string it will replace the HTML for it (meaning what was there before is totally gone and is de-facto replaced with fresh new HTML).
What you want to use is probably appendChild().
With little rewriting I have managed to make your code work:
http://jsfiddle.net/gs1s0fsx/
var i = 0;
function add_input() {
i++;
var main = document.getElementById('main'),
p = document.createElement("p"),
arrow = document.createTextNode('>'),
el = document.createElement('input');
el.type = "text";
el.style = "width:90%";
el.id = "input" + i;
el.addEventListener("keypress", press_key);
main.appendChild(p);
main.appendChild(arrow);
main.appendChild(el);
el.focus();
}
function press_key(e, t) {
if (e.keyCode == 13) {
add_input();
}
}
add
<div id='main'></div>
Hope this helps.

Javascript onclick parameter

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.

Javascript only last event listener works

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/

Categories