Adding onClick event in a for loop - javascript

I am a beginner in Javascript. What I am trying to do is when a user clicks on "Click to start loop", the first <li> will be 1. The second time the user clicks it, it will be 2, and the third time, it will be 3. After the third click, the loop will break.
The issue with my code is that it always displays the number 3 instead of starting from 1 and going all the way to 3.
function myFunction() {
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
li = document.createElement("li")
ul.appendChild(li)
for (let i = 1; i <= 3; i++){
li.innerText = i
}
}
<p id="demo" onclick="myFunction()">Click to start loop</p>

It is because, there is only one 'li' element created before loop starts and at the end of loop, it is just updating the final innterText.
You can fix it by moving li creation code to loop
function myFunction() {
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
<--- from here
for (let i = 1; i <= 3; i++){
li = document.createElement("li") <--- to here
ul.appendChild(li)
li.innerText = i
}
}

You just have to save the current loop value in some place:
let i = 1;
function myFunction() {
// Check for i value
if (i === 4) return;
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
li = document.createElement("li")
ul.appendChild(li)
// Update the i value
li.innerText = i++;
}
<p id="demo" onclick="myFunction()">Click to start loop</p>

One solution could be to create a global variable with initial value set to 1 and increase it every time there is a click on your <p> tag.
I have implemented the same using the global variable counter.
<p id="demo" onclick="myFunction()">Click to start loop</p>
<script>
var counter = 1;
function myFunction() {
if(counter === 4){
return;
}
demo = document.getElementById("demo");
ul = document.createElement("ul");
demo.appendChild(ul);
li = document.createElement("li");
ul.appendChild(li);
li.innerText = counter;
counter++;
}
</script>

I think you should put li = document.createElement("li") inside of the loops
index.js
function myFunction(status) {
demo = document.getElementById("demo")
ul = document.createElement("ul")
demo.appendChild(ul)
for (let i=1; i<=3; i++) {
li = document.createElement("li")
li.innerText = i
ul.appendChild(li)
}
}

This is unclear, are you looking for javascript function generator ?
const
ul_List = document.body.appendChild( document.createElement('ul') )
, generator = (function* ()
{
for (let i = 0; (++i < 4);)
{
ul_List.appendChild( document.createElement('li') ).textContent = i
yield
}
})()
<p id="demo" onclick="generator.next()" >Click to 3 times loop</p>

Counters
A function that deals with an incrementing variable (aka counter) usually declares or defines it as a number outside of a loop then iterates the variable with a ++ or += operator within the loop. But would a loop within an event handler that increments a number by +1 per click make much sense? So forget about iterations based on a single run of a function/event handler.
The next problem is that once the function/event handler is done, the counter is garbage collected (deleted from memory), so on the next click it is back to what it was initially (usually 0) -- so you need the user to click a HTML element, increase a number by one, and increase it by one per click thereafter. There a few ways to keep the counter's last value:
HTML/DOM
Store the last value in a HTML form control by [value]
let counter = 0;
counter++;
document.forms[0].count.value = counter;
....
<input id='count' type='hidden' value='0'><!--value will be '1'-->
Store the last value in any other type of HTML element by [data-*]
or text content
document.querySelector('.count').dataset.idx = counter;
....
<nav class='count' data-idx='1'></nav>
document.querySelector('aside').textContent = counter;
....
<aside>2</aside>
Keep in mind any value stored in HTML is converted into a string so when getting the variable counter value you must convert it back into a real number:
parseInt(document.forms[0].count.value);
parseFloat(document.querySelector('.count').dataset.idx);
Number(document.querySelector('aside').textContent);
Closures
A better way IMO is to deal with variable scope. If you noticed in the previous code, let and const are used instead of var. The reason for this is scope.
Function Scope: If var was used, then all variables would be influenced by anything inside or the immediate outside of the function it is in. If completely outside of all functions then it is global (much more susceptible to side effects and buggy behavior).
Block Scope: let and const scope is block which means they can only be accessed within the brackets they are located in:
var G = `any function, method, expression, etc can affect it or be affected
by it`;
function clickHandler(e) {
var L = `vulnerable to anything within the function and immediately
outside the function`;
if (e.target.matches('button')) {
let A,
const B = `only affects or get affected only by things within the
brackets`;
var C = `even buried deep within a function it will be hoisted to be
accessible to everything within this function`;
}
function addItem(node, count) {
/* Although a function defined within another function it cannot access
A or B*/
}
}
Wrapping a function/event handler in another function in order to provide an isolated scope and an environment wherein variables can exist past runtime and avoid garbage collection is a closure. The following examples are closures #1 is simple and #2 is more refined (user can edit each list item directly). Go here for details on scope and closures.
Example 1
function init() {
const ul = document.querySelector('ul');
let i = 0;
function clickHandler(e) {
i++;
addItem(this, i);
}
ul.onclick = clickHandler;
};
function addItem(list, counter) {
const li = document.createElement('li');
list.append(li);
li.textContent = counter;
};
init();
<ul>Click to Add Item</ul>
Example 2
function init() {
const ui = document.forms[0];
let counter = 1;
function addItem(event) {
const clicked = event.target;
if (clicked.matches('legend')) {
const list = clicked.nextElementSibling;
let marker = `${counter++}`.padStart(2, '0');
list.insertAdjacentHTML('beforeEnd', `<li contenteditable data-idx="${marker}"></li>`);
}
}
ui.onclick = addItem;
};
init();
form {
font: 1.5ch/1 Consolas;
}
legend {
font-size: large;
font-weight: bold;
user-select: none;
cursor: pointer;
}
legend::after {
content: 'Click to Add Item';
font-size: small;
}
ul {
list-style: none;
}
li {
margin: 5px 11px 5px 9px;
padding: 4px 8px;
border-bottom: 1px solid #980;
}
li::marker {
display: list-item;
content: attr(data-idx)'.';
margin-bottom: -2px;
}
<form>
<fieldset>
<legend>List<br></legend>
<ul></ul>
</fieldset>
</form>

Related

html input create li element how to set an id for the element

I'm new with HTML & JS and I face the following problem:
I have an input in html that creates a new li Element (in combination with JS); is it possible to give every newly-created li element its own id? For example to delete an specific element?
For Example:
<li id="one"> .. </li>
<li id="two"> .. </li>
So far it creates only <li> ... </li>
I think it can be done with a for loop, but I have no idea how to use it in my case.
See my JS code below:
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var AddButton = document.getElementById("AddButton");
var ul = document.querySelector("ul");
var li = document.createElement("li");
li.appendChild(document.createTextNode(Input.value));
ul.appendChild(li);
Input.value = "";
I tried to insert a for loop into my code, but after that it doesn't add any elements.
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var AddButton = document.getElementById("AddButton");
var ul = document.querySelector("ul");
var li = document.createElement("li");
for (var i = 0; i < li.length; i++)
li[i].id = 'abc-' + i;
li.appendChild(document.createTextNode(Input.value));
ul.appendChild(li);
Input.value = "";
Your for loop needs curly braces to work properly:
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var AddButton = document.getElementById("AddButton");
var ul = document.querySelector("ul");
var li = document.createElement("li");
for (var i = 0; i < li.length; i++) {
abcElements[i].id = 'abc-' + i;
li.appendChild(document.createTextNode(Inputfield.value));
ul.appendChild(li);
}
Inputfield.value = "";
}
Otherwise only the immediate line after the for statement will run as part of the loop.
There also appeared to be a typo - you had Input instead of Inputfield? But I notice there are some other variables used here which are not defined, so I assume some extra code was omitted?
You could count the number of elements inside the <ul> and use that as id for the <li>:
var AddButton = document.getElementById("AddButton");
AddButton.addEventListener("click", NewEntry);
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var ul = document.querySelector("ul");
var li = document.createElement("li");
li.id = ul.childElementCount;
li.appendChild(document.createTextNode(Inputfield.value));
ul.appendChild(li);
Inputfield.value = "";
console.log(li);
}
<input type="text" id="Inputfield" />
<button id="AddButton">Add</button>
<ul></ul>
One simple means by which this could be accomplished is as follows, with explanatory comments in the JavaScript:
// a generator function, to generate a constantly increasing counter:
const generator = function*() {
// the initial value of the counter:
let i = 0;
// this loop causes the generator to return a number infinitely
// though while the loop is infinite it yields one value and then
// pauses until the next call; this is one instance where an
// infinite loop is deliberate and not a problem:
while (true) {
// increments the value of i, and then returns it to the
// calling context:
yield ++i;
}
},
// assigning a reference to the generator:
counter = generator(),
// a simple Arrow function that takes two arguments:
// tag: String, the element-type to create, and
// props: an Object of element-properties and values to
// assign to the created element:
create = (tag, props) => Object.assign(document.createElement(tag), props),
// a reference to the various elements to which event-listeners are attached:
list = document.querySelector('#list'),
form = document.querySelector('form'),
input = document.querySelector('input'),
button = document.querySelector('#add'),
// the addNew function, written as an Arrow function:
addNew = () => {
// caching the source of the text:
const inputField = document.querySelector('input');
// here we take the value, trim it to remove leading and trailing
// white-space, and then retrieve its length; if there is a zero
// length (so either nothing was entered, or only whitespace) then
// we return here:
if (inputField.value.trim().length === 0) {
return false;
}
// otherwise we cache a reference to the element to which the
// created <li> element will be appended:
const ul = document.querySelector('#list'),
// we call the create() function, here creating an <li>
// element, and passing in an object of properties and
// values:
li = create('li', {
// we set the 'id' - using a template literal string - to
// the string of 'abc-' plus next-value of the counter:
id: `abc-${counter.next().value}`,
// and set the text-content of the element to the
// trimmed value of the <input>:
textContent: inputField.value.trim()
}),
// here we create a <button>,
deleteButton = create('button', {
// with its className set to 'delete':
className: 'delete',
// its text-content set to 'delete task':
textContent: 'delete task',
// and its type set to 'button' in order to
// prevent the <button> submitting the <form>:
type: 'button'
});
// we append the created <button> to the created <li>:
li.append(deleteButton);
// we append the <li> to the <ul>:
ul.append(li);
// and reset the value of the <input> to an empty string:
inputField.value = '';
};
// here we prevent form submission:
form.addEventListener('submit', (e) => e.preventDefault());
// here we bind an anonymous function as the event-handler of the
// 'keyup' event on the <input> element:
input.addEventListener('keyup', (evt) => {
// if the, or an, 'Enter' key is pressed (note that evt.keyCode is
// almost entirely deprecated now, but is here for backwards
// compatibility, it can and possibly should be removed):
if (evt.key === 'Enter' || evt.keyCode === 13) {
// here we call the addNew() function:
addNew();
}
});
// we bind the addNew() function - note the deliberate lack of
// parentheses - as the event-handler for the 'click' event:
button.addEventListener('click', addNew);
// because we're adding the delete <button> elements we delegate the
// event-handling to the closest ancestor element present in the DOM
// on page-load, here that's the #list element. Here we bind the
// anonymous function as the event-handler for the 'click' event
// which bubbles to the <ul>:
list.addEventListener('click', (evt) => {
// if the element on which the click event fired has an ancestor
// .delete element (therefore it was fired on, or in, the .delete
// <button>):
if (evt.target.closest('.delete')) {
// we navigate from the event-target to the closest <li>
// ancestor element and use Element.remove() to remove that
// element from the document:
evt.target.closest('li').remove();
}
})
*,
::before,
::after {
box-sizing: border-box;
font-family: system-ui;
font-size: 16px;
margin: 0;
padding: 0;
}
main {
inline-size: clamp(15em, 80vw, 1000px);
margin-inline: auto;
}
h2 {
font-size: 1.4em;
text-align: center;
}
fieldset {
display: grid;
padding: 0.5em;
}
legend {
border-inline: 1px solid currentColor;
border-radius: 0.25em;
margin-inline: 1em;
padding-inline: 0.5em;
}
label {
display: flex;
gap: 1em;
justify-content: space-between;
margin-block: 0.5em;
}
label input[type=text] {
flex-grow: 1;
}
#list {
border: 1px solid currentColor;
display: grid;
gap: 0.5em;
list-style-type: none;
margin-block: 1em;
margin-inline: auto;
padding: 0.5em;
}
#list:empty::before {
color: hsl(0deg 30% 30% / 0.8);
content: "A beautiful, quiet day of reflection...";
font-style: italic;
}
#list li {
background-color: lavender;
display: grid;
padding: 0.25em;
}
#list li:nth-child(odd) {
background-color: lightcyan;
}
li[id]::before {
content: "#" attr(id) ": ";
}
.delete {
justify-self: end;
}
<main>
<section>
<h2>To do list</h2>
<form action="#" method="post">
<fieldset>
<legend>New task</legend>
<label>
<span class="labelText">What do you want to do today?</span>
<input type="text">
</label>
<button id="add" type="button">Add task to list</button>
</fieldset>
</form>
<ul id="list"></ul>
</section>
</main>
JS Fiddle demo.
It's worth noting that there is one potential issue using a generator function to generate a counter for an id property; the counter will only ever increase, so if you have three elements abc-1, abc-2, abc-3 and then delete those, and create another three elements those elements will continue from abc-4. So the id will be independent of the number of created elements. This may be a benefit in that a duplicate value can't be created, so there's little chance of a duplicate id being created by a generator.
References:
Arrow functions.
document.createElement().
document.querySelector().
Element.append().
Element.remove().
EventTarget.addEventListener().
Generator functions (function*(){...}).
Object.assign().
String.prototype.trim().
Template literals.
while loop.

How do I attach placeholder to the button through JS' DOM?

I am trying to figure out how to attach button 'Delete' to the each list element making so in advance that I will be able to make more buttons for additional list elements later.But when I create buttons,they appear without the text,just tiny rectangle box near the text.I wanted to fix through command 'document.getElementByClassName[list_number + "_button"].placeholder = "Delete",but I got an error even earlier trying to attach classnames to the buttons:
Uncaught TypeError: Cannot read property 'classList' of undefined
at addButton (script.js:74)
at script.js:82
But what's strange is that this error shows only at the [1] list object,not the the [0].For some reason with [0] object everything goes OK,although I didn't succeed in attaching name to it.I thought that the problem laid in list numeration,because the first button is actually "Send',but when I changed the value of var list_number = 0 from 0 to 1,it only got worse and gave an error right away.
How do I attach text in the buttons so they will look normal?
Note:the commands related to the buttons are at the end,everything earlier are command to add new elements to the list trhough input and make the elements line-through
CODE
var button = document.getElementById("button");
var modify_list = document.getElementById("userinput");
var ul = document.querySelector("ul");
var li = document.querySelectorAll("li");
var all_buttons = document.querySelectorAll("button");
var i = 0; //Attach classes to the li
while (li.length > i) {
li[i].classList.add(i);
li[i].classList.add('done');
li[i].classList.add('cursor');
i++
}
//Toggle the line-through function(later we will cal the function that will toggle once more when clicked on element of the list.
var n = 0
while (li.length > n) {
li[n].classList.toggle("done");
n++
}
//Command to add new elements to the list and make line-thorugh when clicked.
function inputLength() {
return modify_list.value.length;
}
function addToTheList() {
var li = document.createElement("li");
li.appendChild(document.createTextNode(modify_list.value));
ul.appendChild(li);
modify_list.value = '';
}
function addAfterClick() {
if (inputLength() === 0) {
alert("Please,don\'t enter the empty list");
} else {
addToTheList();
}
}
function addAfterEnter(key) {
if (key.keyCode === 13 && inputLength() > 0) {
addToTheList();
}
}
button.addEventListener("click", addAfterClick);
modify_list.addEventListener("keypress", addAfterEnter);
function toggle(number) {
li[number].classList.toggle("done");
}
ul.addEventListener("click", whenClicked);
function whenClicked(event) {
var li_number = event.target.className[0];
//In JS it doesn't matter in some occasions if it's a string or number,I suppouse.
// var li_number = Number(li_number_string);
// console.log(li_number);
toggle(li_number);
}
// Create buttons and their functions
function addButton(list_number) {
var button = document.createElement("button");
li[list_number].appendChild(button); //append button near the respective li objects
all_buttons[list_number].classList.add(list_number + "_button") //create class for the button
console.log(all_buttons[list_number].classList);
// document.getElementByClassName[list_number + "_button"].placeholder = "Delete"
}
var list_number = 0 // because under number 0 we have "Send" button
while (li.length > list_number) {
addButton(list_number);
list_number++;
}
// console.log(list_number);
.done {
color: red;
text-decoration: line-through;
}
.cursor {
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<title>DOM</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h1>What plans do I have till the end of the summer?</h1>
<p>They are:</p>
<input type="text" name="add activities" id="userinput" placeholder="add activities">
<button id="button">Send</button>
<ul>
<li>Learn German</li>
<li>Learn Japanese</li>
<li>Learn Java Script</li>
<li>Physical activities</li>
</ul>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
The text you want to be displayed on a programatically generated HTMLButtonElement can be set using it's .innerText property. The .placeholder property serves a different purpose.
Let's take a closer look at your addButton function:
function addButton(list_number){
var button = document.createElement("button");
li[list_number].appendChild(button); //append button near the respective li objects
all_buttons[list_number].classList.add(list_number + "_button") //create class for the button
console.log(all_buttons[list_number].classList);
// document.getElementByClassName[list_number + "_button"].placeholder = "Delete"
}
The first two lines are okay. Trouble starts here:
all_buttons[list_number].classList.add(list_number + "_button");
all_buttons is a HTML collection of buttons you initialized before you started adding dynamically generated button elements to the DOM thus it just contains the buttons set up via HTML. That means this array is outdated and would need to be updated every time you add or remove buttons.
Furthermore you don't need to use that array at all if you want to manipulate properties of your freshly generated button - you can directly access it using the variable button. I'd also recommend giving those buttons an unique id, so you can reference them later on and give it a click event listener for example. Also since there is already a global variable named button you should give the variable inside the function a different name e.g. localButton.
Here's an example:
var button = document.getElementById("button");
var modify_list = document.getElementById("userinput");
var ul = document.querySelector("ul");
var li = document.querySelectorAll("li");
var all_buttons = document.querySelectorAll("button");
var i = 0; //Attach classes to the li
while (li.length > i) {
li[i].classList.add(i);
li[i].classList.add('done');
li[i].classList.add('cursor');
i++
}
//Toggle the line-through function(later we will cal the function that will toggle once more when clicked on element of the list.
var n = 0;
while (li.length > n) {
li[n].classList.toggle("done");
n++;
}
//Command to add new elements to the list and make line-thorugh when clicked.
function inputLength() {
return modify_list.value.length;
}
function addToTheList() {
var li = document.createElement("li");
li.appendChild(document.createTextNode(modify_list.value));
ul.appendChild(li);
modify_list.value = '';
}
function addAfterClick() {
if (inputLength() === 0) {
alert("Please,don\'t enter the empty list");
} else {
addToTheList();
}
}
function addAfterEnter(key) {
if (key.keyCode === 13 && inputLength() > 0) {
addToTheList();
}
}
button.addEventListener("click", addAfterClick);
modify_list.addEventListener("keypress", addAfterEnter);
function toggle(number) {
li[number].classList.toggle("done");
}
ul.addEventListener("click", whenClicked);
function whenClicked(event) {
var li_number = event.target.className[0];
//In JS it doesn't matter in some occasions if it's a string or number,I suppouse.
// var li_number = Number(li_number_string);
// console.log(li_number);
toggle(li_number);
}
// Create buttons and their functions
function addButton(list_number) {
var localButton = document.createElement("button");
localButton.innerText = "Delete";
localButton.id = "myButton" + list_number;
li[list_number].appendChild(localButton);
}
var list_number = 0 // because under number 0 we have "Send" button
while (li.length > list_number) {
addButton(list_number);
list_number++;
}
.done {
color: red;
text-decoration: line-through;
}
.cursor {
cursor: pointer;
}
<h1>What plans do I have till the end of the summer?</h1>
<p>They are:</p>
<input type="text" name="add activities" id="userinput" placeholder="add activities">
<button id="button">Send</button>
<ul>
<li>Learn German</li>
<li>Learn Japanese</li>
<li>Learn Java Script</li>
<li>Physical activities</li>
</ul>

Javascript onclick event on getElementsByClassName

I have a svg map and I am putting that into object and I am trying to create all path with id clickable.
For that I am doing this to get svg object :
let a = document.getElementById("biharsvg")
And I am putting that into svg doc like this:
var svgDoc = a.contentDocument;
And now I am getting all the values of certain class using this:
let de = svgDoc.getElementsByClassName("fil0");
I can also get the attribute id value using this:
var i;
for (i = 0; i < de.length; i++) {
var j = de[i].getAttribute("id");
console.log(j);
}
I want to add a click event on each attribute id and get the value when I am doing this:
var i;
for (i = 0; i < de.length; i++) {
var j = de[i].getAttribute("id");
console.log(j);
svgDoc.getElementById(j).onclick = function() {
modal.style.display = "block";
console.log(this.getAttribute("id"));
}
}
This is working fine and I am getting all the values but in jquery I can use this:
$(de).click(function(){
alert(this.getAttribute("id"));
});
Is there any way I can use something like this in javascript without loop. My question is what is the best possible way to make this work in javascript.
The javascript version for jQuery's
$(de).click(function(){
alert(this.getAttribute("id"));
});
would be something like
Array.from(de).forEach( function(el){
el.addEventListener('click', function() {
alert(this.getAttribute("id"));
// or "this.id" should work too
});
});
To be noted, when doing $(de).click(function(){...} with jQuery, it also loops, internally.
And as commented, with arrow functions you could shorten the code even more
Array.from(de).forEach(el => el.addEventListener('click', function () {...}))
var de = document.querySelectorAll('span');
Array.from(de).forEach(el => el.addEventListener('click', function () {
alert(this.id);
}))
span {
display: inline-block;
padding: 20px;
margin: 0 5px;
border: 1px dotted black;
}
span::after {
content: attr(id)
}
<span id="nr1">click </span>
<span id="nr2">click </span>
<span id="nr3">click </span>
Updated based on a comment.
The main difference between your existing loop and the above is, the above is more efficient, with a cleaner/shorter code.
In your original loop
var i;
for (i = 0; i < de.length; i++) {
var j = de[i].getAttribute("id");
svgDoc.getElementById(j).onclick = function() {
modal.style.display = "block";
console.log(this.getAttribute("id"));
}
}
you iterate through the element array de, get its id and then make a new call using getElementById to get the element you already have.
With the kept syntax/logic, your existing code could been simplified to something like this
for (var i = 0; i < de.length; i++) {
de.onclick = function() {
modal.style.display = "block";
console.log(this.getAttribute("id"));
}
}
I just want to show an alternative way, pointed out by CBroe, where a click on the document is checked for its event target:
let de = Array.from(mylist.getElementsByClassName("fil0"));
document.addEventListener('click', function(e) {
const el = e.target;
if (de.indexOf(el) < 0) return false;
alert(el.innerHTML);
});
<ul id="mylist">
<li>one</li>
<li class="fil0">two*</li>
<li class="fil0">three*</li>
</ul>
<p>* has click event assigned indirectly</p>
This has the additional benefit that it only uses a single handler function, and if the indexOf() condition is changed to something like classList.contains(), it will even work for elements that don't exist yet.
I find for..of to be the most convenient and readable syntax:
for (const element of svgDoc.getElementsByClassName("fil0")) {
console.log(element.id);
}

JavaScript increase button increases only once

I am a beginner in JS and working at a shopping cart. I have several products which are rendered in the page with ES6 template strings. Everything is working so far, you can add items to the basket and the basket and total update correctly. The only part I am having trouble with is the increase/decrease buttons: they only work once, if you click again the amount printed in the console stays the same.
I did find other SO post related to increment/decrement functions but the button keeps working only once, so I reckon the problem is related to something else in the code that I am overlooking.
Please see the code below:
this is the shopping cart that will be rendered
// select ul
const shoppingCart = document.querySelector('.cart-items');
// create a li item inside ul
let billContainer = document.createElement('li');
// attach an event listener to every li
billContainer.classList.add('list');
// create the markup for every item added to the cart
for(let j = 0; j < basket.length; j++){
const billMarkup = `
<p class="prodName">${basket[j].name}</p>
<div class="button-wrapper">
<button type="button" name="increase" class="increase">+</button>
<span class="quantity">${basket[j].quantity}</span>
<button type="button" name="decrease" class="decrease">-</button>
</div>
<p class="totPrice">£${basket[j].price}</p>
`;
// add the markup to the DOM
billContainer.innerHTML = billMarkup;
shoppingCart.appendChild(billContainer);
}
and this is the increase/decrease functionality (the event listener for the buttons is attached to their parent 'li'):
// attach an event listener to every li
const li = document.querySelectorAll('.list');
li.forEach( liItem => liItem.addEventListener('click', updateBill));
// add or remove items on click
function updateBill(e){
if(e.target.nodeName === 'BUTTON'){
// current value in the basket
let value = parseInt(this.querySelector('.quantity').textContent);
// if the user clicks on 'increase' button
if(e.target.name === 'increase'){
value++;
console.log(value);
// if the user clicks on 'decrease' button
} else if(e.target.name === 'decrease'){
value < 1 ? value = 1 : '';
value--;
console.log(value);
}
}
}
Thanks!
Problem
Plus/minus buttons inc/decremented only once then wouldn't go any further.
Explanation
Once a value has changed, it is just a number in a variable floating in the console since that is the last statement that has anything to do with the value. So only the initial change is successful but when the buttons are clicked for the second time, the function goes back to span.quantity and gets the value that's never been updated from the last click.
Solution
The easiest way to resolve the problem at hand is to update the value of span.quantity:
if (e.target.name === 'increase') {
value++;
console.log(value);
} else if (e.target.name === 'decrease') {
value--;
value = value < 1 ? 1 : value;
console.log(value);
} else {
return false;
}
this.querySelector('.quantity').textContent = value;
Because you didn't provide a functional nor a copyable demo, I didn't bother to test it nor did I attempt to spot check your code. It's less effort to rewrite the source and resolve the problem and maybe prevent problems in the future.
Demo Highlights
The Demo uses a different API for referencing form controls and alternate methods and properties that are better versions of ones that are more commonly used. Event Delegation is used. Array methods might've been overkill but I like using them. Below are line item references to the Demo, unfortunately Stack Snippets don't have line numbers. The Plunker - index.html and README.md can be read together with line numbers.
HTMLFormControlsCollection
52 Declare the <form>,
53 Referencing ALL form controls,
92-95 Create very short references to each form control,
96-99 Create references to their values and convert them to numbers,
102-103, 109-110 Simple and short expressions,
122 A grand total value
Template Literals
75-83 Improved the layout for the list items by using semantic elements. Each element is assigned a unique #id,
92-94 Flexible referencing of #ids originating from the results of 89 and 90.
Array Methods
90-91 By planning a specific naming strategy: abc-0, split('-').pop() returns the number end of an id and split('-').shift() returns the letters before the dash,
113-120 Collecting all .prc;
map() returns an array of price totals;
reduce() returns the total sum;
Event Delegation/Event Object
52 Reference the <form>,
54 Register the <form> to click events. This is the only EventListener needed, it will work for all of its children/descendants,
88-91, 100 Reference origin of event with the Event.target property and not only determine the clicked element but others as well like siblings, parents/ancestors, and children/descendants.
Miscellaneous
56-71 It looks like the basket is an array of objects? Didn't see it in the OP so I had to guess. Removed the basket[j].quantity property because it makes more sense that each item is initially a quantity of 1.
84 insertAdjacentHTML() is innerHTML on steroids.
Plunker
Demo
<!DOCTYPE html>
<html>
<head>
<style>
html,
body {
font: 400 16px/1.1 Consolas;
}
legend {
font-size: 1.3rem;
}
output,
input {
display: inline-block;
text-align: center;
}
[id^=qty] {
width: 1.5ch;
}
[id^=prc] {
min-width: 9ch;
}
[id^=prc]::before {
content: "= £";
}
[id^=bas]::before {
content: " x £";
}
#cart+label {
display: inline-block;
margin: 10px 0 0 40%;
}
#total::before {
content: " £";
}
</style>
</head>
<body>
<form id='cart'></form>
<label>Total:
<output id='total' form='cart'>0.00</output>
</label>
<script>
var cart = document.forms.cart;
var x = cart.elements;
cart.addEventListener('click', updateBill, false);
var basket = [{
name: "thing0",
price: 1.99
}, {
name: "thing1",
price: 12.99
}, {
name: "thing2",
price: 21.59
}, {
name: "thing3",
price: 0.09
}, {
name: "thing4",
price: 5.99
}];
for (let j = 0; j < basket.length; j++) {
var details = `
<fieldset id="item-${j}">
<legend>${basket[j].name}</legend>
<button id="inc-${j}" type="button">+</button>
<output id="qty-${j}">1</output>
<button id="dec-${j}" type="button">-</button>
<output id="bas-${j}">${basket[j].price}</output>
<output id="prc-${j}" class="prc">${basket[j].price}</output>
</fieldset>
`;
cart.insertAdjacentHTML('beforeend', details);
}
function updateBill(e) {
if (e.target.type === 'button') {
var ID = e.target.parentElement.id;
var idx = ID.split('-').pop();
var dir = e.target.id.split('-').shift();
var qty = x.namedItem(`qty-${idx}`);
var bas = x.namedItem(`bas-${idx}`);
var prc = x.namedItem(`prc-${idx}`);
var sum = x.total;
var quantity = parseInt(qty.value, 10);
var base = parseFloat(bas.value).toFixed(2);
var price = parseFloat(prc.value).toFixed(2);
var total = parseFloat(sum.value).toFixed(2);
if (dir === "inc") {
quantity++;
qty.value = quantity;
prc.value = quantity * base;
} else {
quantity--;
if (quantity <= 0) {
quantity = 1;
}
qty.value = quantity;
prc.value = quantity * base;
}
}
var prices = Array.from(document.querySelectorAll('.prc'));
var numbers = prices.map(function(dig, idx) {
return parseFloat(dig.value);
});
var grandTotal = numbers.reduce(function(acc, cur) {
return acc + cur;
}, 0);
x.total.value = grandTotal.toFixed(2);
}
</script>
</body>
</html>

A loop within a loop - Javascript

I have a problem with a loop inside a loop.
By selecting the number and clicking the "Generate boxes" button it generates boxes with numbers from 1 to 49.
If you click the first time everything works fine.
But if you add more boxes it once again adds those 49 numbers to the already existing boxes. That's the problem. I only want to generate new boxes with numbers from 1 to 49.
This is the code:
function kasterl() {
$(".plunder").click(function() {
var wert = $( "#wieviel option:selected").text();
MyInt = parseInt(wert);
createKaesten(MyInt);
});
}
function createKaesten(zahl) {
var gesamt = $(".kasten").length+1;
var numberOf = $(".kasten").length;
for(var i=1; i<=zahl; i++) {
$(".rahmen").append("<div class='kasten nr"+ gesamt +"'><ul></ul></div>");
}
for(var n=1; n<=49; n++) {
$(".kasten ul").append("<li class='nummer'>" + n + "</li>");
}
}
And here you can test it: link for testing
As you have found, $(".kasten ul").append(...) will append to all elements that matched the ".kasten ul" selector.
You said you had a problem with a "loop within a loop", but your current code doesn't in fact nest the loops. Following is a solution that actually does nest the loops:
function createKaesten(zahl) {
var gesamt = $(".kasten").length + 1;
var numberOf = $(".kasten").length;
var newUL;
for (var i = 1; i <= zahl; i++) {
newUL = $("<ul></ul>");
for (var n = 1; n <= 5; n++) {
newUL.append("<li class='nummer'>" + n + "</li>");
}
$("<div class='kasten nr" + gesamt + "'></div>").append(newUL).appendTo(".rahmen");
}
}
$("button").click(function() {
createKaesten(3);
});
li { display: inline-block; padding: 0 10px; }
.kasten { border: thin black solid; margin: 2px; padding: 0px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button>Test</button>
<div class="rahmen"></div>
The outer loop creates a new, empty UL, then the inner loop appends the new LI items to that UL, then we create a DIV, append the new UL to it, then append the DIV to the .rahmen" container.
(Note that for demo purposes each click of the button only adds 3 x 5 items, rather than something x 49 items, and I've styled the LIs to go across the page horizontally so that it's easier to see what's happening. Click "Run code snippet" to try it out.)
Note in the code that you use the function $().append()
Appending does just that - it appends new content to the end of existing content. Append will be executed every time you click generate.
Edit: I added a codepen to illustrate this. Hit each button multiple times to see the difference.

Categories

Resources