How for loop work with JavaScript EventListner - javascript

I have a html document where has 3 button and one h1 element. I have tried to change the h1 element content by clicking in each button with EventListener. And i have done this too. But i dont understand how foor loop work there. How exact button working when i am click on them?
//JavaScript Code
var len = document.querySelectorAll(".mybutton").length
for (let i = 0; i < len; i++) {
var doucemnts = document.querySelectorAll("button")[i]
doucemnts.addEventListener("click", function(){
var text = this.innerHTML
document.querySelector("h1").innerHTML= "You Have Selected " + text
console.log(i)
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>You have selected no button</h1>
<button class="mybutton">Button 1</button>
<button class="mybutton">Button 2</button>
<button class="mybutton">Button 3</button>
<script src="index.js">
</script>
</body>
</html>
Can Anyone Tell me How For loop worked there?

For in the nodelist attach an event listener that calls a function when the button is clicked. The function updates the h1 with the button text.
Your code can be improved slightly by caching all the elements up front, and then using those references in the loop (a for/of loop is a little easier to parse) instead.
// Cache the elements
const buttons = document.querySelectorAll('.mybutton');
const heading = document.querySelector('h1');
// Loop over `buttons`
for (const button of buttons) {
// For every button add a click listener
button.addEventListener('click', function() {
// Assign the button textContent to a new variable
const text = this.textContent;
// Use that variable to update the textContent
// of the heading textContent
heading.textContent = 'You Have Selected ' + text;
});
}
<h1>You Have Selected No Button</h1>
<button class="mybutton">Button 1</button>
<button class="mybutton">Button 2</button>
<button class="mybutton">Button 3</button>
You could even use event delegation. It attaches one listener to a parent element which captures events from its child elements as they "bubble up" the DOM.
// Cache the elements, and a partial string
const heading = document.querySelector('h1');
const buttons = document.querySelector('.buttons');
const tmpl = 'You Have Selected';
// Add an event listener to the buttons container
buttons.addEventListener('click', handleClick);
function handleClick(e) {
// If the child element that fired the
// click event is a button
if (e.target.matches('button')) {
// Construct the new string, and assign it
// to the heading textContent
const text = `${tmpl} ${e.target.textContent}`;
heading.textContent = text;
}
}
<h1>You Have Selected No Button</h1>
<section class="buttons">
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
</section>
Additional information
matches
Template/string literals

In JS when manipulating DOM elements, it's better to query elements once at start of your code then reuse those variables. Another important thing to consider is to use const/let instead of old var keyword. Here's your updated code based on that and might make more sense now:
const h1 = document.querySelector('h1');
const buttons = document.querySelectorAll('.mybutton');
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", function() {
const text = this.innerHTML;
h1.innerHTML = "You Have Selected " + text;
});
}
This code is basically doing the same, but instead of querying for elements on each iteration, it's done once before starting loop. Then loop over the buttons one by one and add click event to each one of them.
You can also use for..of it might be easier to understand. Here's the code updated to use for..of:
const h1 = document.querySelector('h1');
const buttons = document.querySelectorAll('.mybutton');
for (const button of buttons) {
button.addEventListener('click', function() {
const text = this.innerHTML;
h1.innerHTML = 'You Have Selected ' + text;
});
}
Update: The main thing to understand in both code snippets is const text = this.innerHTML. This line of code is using the this keyword to get the HTML content of each button. this keyword will be pointing to button triggered the event, so based on that you're getting text displayed on that button using innerHTML property and concatenate that to content of h1. In short, getting text in clicked button and append to h1 content.
I have removed console.log(i) as it refers to variable i that is valid only while add the event listener callback only. It'll show an error because when event callback function is called there will be no for loop and i won't be defined.

Related

Some of Icon click listeners work and some not in a strange way in JavaScript

I am working on a to-do list application. I create a li item and put 2 icons and a p tag in it. One of the icons is edit and it works quite well, I replace an input with the p tag and it is fine but the problem is that my check icons on the left side work half way. If I add the li items one by one, the check icons work very well but when I add 5 or 10 items and then try to check the icons, a few of them works and the others do not. I have tried replacing i tags with span tags and no result. It is like every second li tag blocks the former one. I need help, I would appreciate any.
I'll add below the only the icons which don't work.
const DONE = document.getElementsByClassName('far fa-circle');
const LINE = document.getElementsByClassName('list-points');
const EDIT = document.getElementsByClassName('far fa-edit');
const CONTAINER = document.getElementById("actual-container");
const BUTTON = document.getElementById("list-adder");
BUTTON.addEventListener('click', nameList);
function nameList() {
const item1 = document.createElement("i");
item1.className = "far fa-circle";
const paraph1 = document.createElement("p");
paraph1.className = "list-points";
paraph1.innerText = "Fresh again!";
const item2 = document.createElement("i");
item2.className = "far fa-edit";
const myList = document.createElement("li");
myList.appendChild(item1);
myList.appendChild(paraph1);
myList.appendChild(item2);
CONTAINER.appendChild(myList);
for (let i = 0; i < DONE.length; i++) {
DONE[i].addEventListener('click', function() {
DONE[i].classList.toggle('fa-times-circle');
})
}
}
<head>
<title>Debug</title>
<script src="https://kit.fontawesome.com/ae444f90db.js" crossorigin="anonymous"></script>
</head>
<body>
<div>
<ul id="actual-container"></ul>
</div>
<button id="list-adder">ME</button>
</body>
The error is within the assignment of your click-handlers.
You do
for (let i = 0; i < DONE.length; i++) {
DONE[i].addEventListener('click', function() {
DONE[i].classList.toggle('fa-times-circle');
});
}
This will add a toggling event handler to all elements that you keep in DONE and has this behaviour.
Click button: create elements A, assign click handler to A
(A has a single handler) works
Click button: create elements B, assign click handler to A and B
(A has two handlers) does not work
(B has a single handler) works
Click button: create elements C, assign click handler to A and B and C
(A has three handlers) works
(B has two handlers) does not work
(C has a single handler) works
Because you are using toggle in your click handler, the handlers are "canceling" each other because toggling something twice will leave you in the initial state.
I guess you are not aware of the fact that getElementByClassName returns a live list, so that the elements of your variable DONE are changed when you add new elements to the DOM. I was not aware of this either, so thank you for your question :)
See here for a better explanation: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
In your code it should be enough to add the handler just to the one element you create (item1 is the icon element in your code):
item1.addEventListener('click', function() {
item1.classList.toggle('fa-times-circle');
});

How to delete text box along with button in JavaScript

I have a button when user clicks the button it create the text box along with remove button
but all the text boxes created with same id how we can delete the text box when clicks respective remove button
here My Code:
<body>
<button type="button" id="URLbtn" onclick="Createinput()"> + Add URL</button>
<div id="TextAreaBtn"></div>
<script>
function Createinput() {
var newdiv=document.createElement("div");
newdiv.id="test"
var Inputele=document.createElement("input");
Inputele.type="text";
Inputele.id="URLtxt"
newdiv.appendChild(btnele);
var btnele=document.createElement("button");
btnele.id="rmvbtn"
btnele.type="button"
btnele.innerHTML="-"
btnele.onclick=RemoveUrlBox()
newdiv.appendChild(btnele);
var element = document.getElementById("TextAreaBtn");
element.appendChild(newdiv);
}
function RemoveUrlBox() {}
</script>
</body>
i am getting following output
if user click 2 remove button only remove the second textbox and button
You need to select the wrapping div. Easiest way is to use remove() and use closest. No need to use the id..... You also need to remember ids need to be unique.
function createInput() {
var newDiv = document.createElement("div");
newDiv.className = 'group';
var inputElem = document.createElement("input");
inputElem.type = "text";
newDiv.appendChild(inputElem);
var btnElem = document.createElement("button");
btnElem.type = "button";
btnElem.textContent = "-";
btnElem.addEventListener("click", removeUrlBox);
newDiv.appendChild(btnElem);
var element = document.getElementById("TextAreaBtn");
element.appendChild(newDiv);
}
function removeUrlBox() {
this.closest('.group').remove();
}
<button type="button" id="URLbtn" onclick="createInput()"> + Add URL</button>
<div id="TextAreaBtn"></div>
This should do the trick:
const txtarea=document.getElementById('TextAreaBtn');
document.getElementById('URLbtn').onclick=()=>txtarea.innerHTML+=
'<div><input type="text" class="URLtxt"><button class="rmvbtn">-</button></div>';
txtarea.onclick=ev=>ev.target.className==="rmvbtn"&&ev.target.parentNode.remove()
<button type="button" id="URLbtn"> + Add URL</button>
<div id="TextAreaBtn"></div>
I replaced your id attributes with class attributes, as these don't need to be unique.
I reduced your script by using innerHTML instead of laboriously putting elements together with createElement(). This is a matter of opinion as both methods have their advantages.
I also used delegated event listener attachment for the removal buttons. This way you can get away with a single event listener on div.TextAreaBtn. The attached funcion will only trigger any action if the clicked element has class "rmvbtn".
Change
btnele.onclick=RemoveUrlBox()
to
btnele.addEventListener('click', function (e) {
// `this` is the button that was clicked no matter about the id
// `this.parentNode` is the div you want to remove
const nodeToRemove = this.parentNode;
nodeToRemove.parentNode.removeChild(nodeToRemove);
});

Clone div and traverse to whats inside

I have a div with an input inside it, i want to clone this div on click with different id, and i want the id of the input inside it to change also, and i want to limit the number of clones,
var div = document.getElementById('clonedDiv'),
clone = div.cloneNode(true);
clone.id = "some_id";
document.body.appendChild(clone);
<div id="clonedDiv">
<p>Clone this div</p>
<input type="file" id="clonedInput">
</div>
<br>
<button type="button">Clone me</button>
how can i do that? here is my code:
I'd like to offer a native JS solution to your problem. It is rather straight forward and works in all modern browsers.
outerHTML is IE4+, see here
insertAdjacentHTML is IE4+, see here
const
sourceDiv = document.getElementById('clonedDiv'),
cloneTrigger = document.getElementById('make-clone'),
maxClones = 3;
let
clonesCreated = 0;
function makeClone() {
// Make sure there aren't too many clones created.
if (clonesCreated === maxClones) {
console.log('max clones reached');
return;
}
let
// outerHTML is llke innerHTML but includes the element itself.
clone = sourceDiv.outerHTML;
// Replace the two IDs for unique IDs
clone = clone.replace('clonedDiv', `clonedDiv_${clones}`);
clone = clone.replace('clonedInput', `clonedInput_${clones}`);
// insertAdjacentHTML is like innerHTML except your can tell where it should be inserted in relation to the element.
// In this case, add the clone before the button element.
cloneTrigger.insertAdjacentHTML('beforebegin', clone);
// Increase the number of clones created.
clonesCreated++;
}
cloneTrigger.addEventListener('click', makeClone);
<div id="clonedDiv">
<p>Clone this div</p>
<input type="file" id="clonedInput">
</div>
<br>
<button id="make-clone" type="button">Clone me</button>
Since you have tagged jQuery in the question, we can use it to greatly simplify things.
Bind a click event handler to the clone button, and we can use it to call a method, say clone(), that will handle all the logic of cloning
Define a global variable, say cloneCount, that stores how many clones have been created, so that we can generate unique IDs
Clone your target <div> element.
Modify all IDs in your target element and its children (use .add() to create a superset) by simply appending cloneCount
Append cloned element to the DOM
If you want to limit the number of clones, simply track cloneCount in the method. When it exceeds a certain threshold, return to exit the function.
Here is a proof-of-concept example:
var $div = $('#clonedDiv');
var cloneCount = 0,
maxCloneCount = 5;
var clone = function() {
// Stop execution if we have cloned max number of times
if (cloneCount >= maxCloneCount)
return;
// Construct clone
var $clone = $div.clone();
// Replace all IDs (of clone and its children) to make sure it is unique
$clone.add($clone.children()).attr('id', function() {
return this.id + '_' + cloneCount;
});
// Append to DOM
$('body').append($clone);
cloneCount++;
};
$('button').on('click', function(e) {
e.preventDefault();
clone();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="clonedDiv">
<p>Clone this div</p>
<input type="file" id="clonedInput">
</div>
<br>
<button type="button">Clone me</button>
To achieve expected result, use below option
var div = document.getElementById('clonedDiv');
var count = 0;
$('button').on('click',function(){
if(count <= 3){
$('#clonedDiv').clone().attr('id','cloneDiv'+count).appendTo('body');
}
count++;
})
https://codepen.io/nagasai/pen/JrjNmq
Restrict the number clones using count variable
Using ID attribute and count, different ids can be assigned to cloned div - cloneDiv
<!DOCTYPE html>
<html>
<head>
<script data-require="rxjs#4.0.6" data-semver="4.0.6" src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.6/rx.all.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div id = 'files'>
</div>
<br>
<button type="button" id = 'clonebtn'>Clone me</button>
</body>
<script type="text/javascript" charset="utf-8">
// Code goes here
window.onload = init;
function init() {
const observable = Rx.Observable.fromEvent($('#clonebtn'),'click');
observable
.take(4)
.map(1)
.scan((acc, curr) => acc + curr)
.subscribe(i=>$('#files').before(getFileTemplate(i)));
}
function getFileTemplate(i){
return `<div id='clonedDiv${i}'>
<p>Clone this div${i}</p>
<input type="file" id='clonedInput${i}'>
</div>`;
}
</script>
</html>

Identify cloned DOM element

I am using MediumEditor which a WYSIWYG editor using contenteditables. I need to assign different IDs to each element inside of the contentEditable, but (when you press Enter) the editor will clone a paragraph from an esiting one with all it's attributes. I am wondering if there is a way to identify the new <p> element from the one it was cloned from? The new element can be placed either before or after the existing one.
UPDATE:
Here's an example:
https://jsfiddle.net/wwoh7e62/
<div id="container">
<p id="myid" class="myclass" data-id="myid">some text</p>
</div>
<button onclick="doClone(); myFunc();">clone</button>
<script>
doClone = function() {
var container = document.getElementById('container');
var node = container.getElementsByTagName('p')[0].cloneNode(false);
node.innerHTML = 'cloned node';
container.appendChild(node);
}
myFunc = function () {
console.log('my func');
}
</script>
The code in doClone I don't have access to. My code should reside in the myFunc function.
While I was typing the fiddle I realized that the solution will probably be in attaching an event listener (which is not cloned) and the new node will be the one that does not have the event listener.
UPDATE:
ids that were assigned previously need to stay the same as thay are used to identify particular nodes.
You can try this :
Remove the id from "p"
<div id="container">
<p class="myclass" data-id="myid">some text</p>
</div>
<button onclick="doClone(); myFunc();">clone</button>
Then update them into your Func function :
var length = document.getElementById("container").getElementsByTagName("p").length;
for(var i=0; i < length; i++) {
document.getElementById("container").getElementsByTagName("p")[i].id = i;
}
Does this help ?

How can i use a single function to change the innerHTML of all the buttons in a webpage?

I have 9 buttons in my page. When ever I click on a button the inner HTML of the button should change. How can I achieve this using a single JavaScript function? i.e. What can I do so that the code detects different buttons in the HTML?
<html>
<script>
function changeHtml(){
alert("h");
element.innerHTML = "y"; /* I tried to achieve this by setting the "element" to "this"
i++ However I am getting an error innerHTML of null is undefined.*/
}
element.onclick = changeHtml;
</script>
<body>
<button id = "but1" ></button>
<button id = "but2" ></button>
<button id = "but3" ></button>
<button id = "but4" ></button>
<button id = "but5" ></button>
<button id = "but6" ></button>
<button id = "but7" ></button>
<button id = "but8" ></button>
<button id = "but9" ></button>
</body>
</html>
How should the code be modified so that when ever I click on a button, let it be the first or the sixth button the code should detect the button and therefore call the "changeHtml" function on that button?
You can simply get all the buttons and assign a click listener to each of them. To differentiate between them, you can use a custom data-* attribute on each button (if you don't want to rely on button's text only). You can access the attribute using the getAttribute method, so you can make conditional switches, etc.
See this example:
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
var b = buttons[i];
b.addEventListener('click', function(){
alert('button ' + this.getAttribute('data-num') + ' clicked!');
this.textContent = '-' + this.textContent + '-';
})
}
<button data-num="1">Button</button>
<button data-num="2">Button</button>
<button data-num="3">Button</button>
<button data-num="CRAZY">Crazy Button</button>
<button data-num="Where's Luigi?">Super Mario Button</button>
Select all the buttons and then loop them and assign the HTML you want, where .button is the class of each button. If you don't want to depend on classes you may use the tag method .getElementsByTagName.
var buttons = document.getElementsByClassName('button');
for(var i = 0; i < buttons.length; i++)
buttons[i].addEventListener('click', function(){
this.innerHTML = "Text changed!";
});
If you want one by one, you can do:
var buttons = document.getElementsByClassName('button');
button[0].addEventListener('click', function(){
this.innerHTML = "I am the first button"
});

Categories

Resources