create and add "li" element after click - javascript

In an exercise, I have created a new element li after each click of my button.
It doesn't work. In the console it reports "li is not defined".
What did I do wrong?
My code:
const btn = document.querySelector("button");
let number = 1;
const ul = document.querySelector("ul");
const fcn = function() {
const li = document.createElement('li');
li.textContent = number;
}
if (number % 3 == 0) {
li.classList.add('big');
}
ul.appendChild(li);
number += 2;
btn.addEventListener("click", fcn);

try this:
const btn = document.querySelector("button");
let number = 1;
const ul = document.querySelector("ul");
const fcn = function () {
const li = document.createElement('li');
li.textContent = number;
if (number % 3 == 0) {
li.classList.add('big');
}
ul.appendChild(li);
number += 2;
}
btn.addEventListener("click", fcn);

This problem is related to closures in javascript. li is not accessible outside function fcn.
following code should solve your problem.
const btn = document.querySelector("button");
let number = 0;
const ul = document.querySelector("ul");
const fcn = function () {
number += 1;
const li = document.createElement('li');
li.textContent = number;
ul.appendChild(li);
if (number % 3 == 0) {
li.classList.add('big');
}
}
btn.addEventListener("click", fcn);

li is defined within anonymous function.
When you define a variable/constant within function it's local to that function and can't be accessed outside it. It has function level scope.
To fix your code either define li outside of function at global scope or move the piece of code that use it within function itself as follows :
const btn =
document.querySelector("button");
let number = 1;
const ul =
document.querySelector("ul");
const fcn = function () {
const li =
document.createElement('li');
li.textContent = number;
if(number % 3 == 0){
li.classList.add('big');
}
ul.appendChild(li);
number += 2;
}
btn.addEventListener("click", fcn);

Related

Trying to create a button with an li and it's not working

I'm trying to make a grocery list app using an array similar to a todo list. I've got it working with an add button, and had a remove button that would remove an item from the list. But now I'm trying to make it so that a remove button is created with each li so that each grocery item can be selectively removed. I gave it a shot but I'm not quite sure what I've done wrong here.
let addButton = document.getElementById('add-button');
addButton.addEventListener('click', add);
let addInput = document.getElementById('add-input');
//let removeButton = document.getElementById('remove-button');
//removeButton.addEventListener('click', remove);
let groceryList = [
]
function add() {
groceryInput = addInput.value;
groceryList.push(groceryInput);
addInput.value = '';
displayGroceries();
}
function remove(event) {
let position = event.currentTarget.id;
groceryList.splice(position, 1);
displayGroceries();
}
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = '';
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('li');
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
}
let removeButton = document.createElement('button');
removeButton.innerText = "Remove";
removeButton.addEventListener('click', remove);
removeButton.id = i;
groceryLi.appendChild(removeButton);
}
<div class="container">
<h1>Grocery List</h1>
<input id="add-input" placeholder="Add Groceries" autocomplete="off">
<button id="add-button">Add</button>
<!--<button id="remove-button">Remove</button>-->
<div>
<ul id="grocery-ul"></ul>
</div>
Its not working as groceryLi.appendChild(removeButton) you are calling outside for loop.
You have defined groceryLi using let and let have a block scope.
Moving code to add button inside resolves issue
Find fixed method for displayGroceries as follows
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = "";
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement("li");
groceryLi.innerHTML = groceryList[i];
let removeButton = document.createElement("button");
removeButton.innerText = "Remove";
removeButton.addEventListener("click", remove);
removeButton.id = i;
groceryLi.appendChild(removeButton);
groceryUl.appendChild(groceryLi);
}
}
In your example you are calling on the incrementing value of i outside of the scope of its loop.
You can create the button using the same method you are using to create your list item tag, then add the button to the UL element tag using .insertAdjacentElement('beforeend', removeBtn).
Then you can use a removeEl function that looks at the event.target parentNode and firstChild --> li that will contain the grocery item and its remove button to both remove the element from the DOM and the array.
function removeEl(event) {
event.target.parentNode.remove(event.target)
if (groceryList.includes(event.target.parentNode.firstChild.textContent)) {
let k = groceryList.indexOf(event.target.parentNode.firstChild.textContent);
if (k !== -1) {
groceryList.splice(k, 1);
}
}
displayGroceries();
}
//and the for loop that creates the new elements in displayGroceries()
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('LI');
let removeBtn = document.createElement('BUTTON');
groceryUl.classList.add('flex-display')
removeBtn.textContent = `remove ${groceryList[i]}`;
removeBtn.setAttribute('onclick', `removeEl(event)`)
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
groceryLi.insertAdjacentElement('beforeend', removeBtn);
}
let addButton = document.getElementById('add-button');
addButton.addEventListener('click', add);
let addInput = document.getElementById('add-input');
//let removeButton = document.getElementById('remove-button');
//removeButton.addEventListener('click', remove);
let groceryList = [
]
function add() {
groceryInput = addInput.value;
groceryList.push(groceryInput);
addInput.value = '';
displayGroceries();
}
function removeEl(event) {
//this targets the LI element, parent of the button
event.target.parentNode.remove(event.target)
//event.target.parentNode.firstChild.textContent -> the grocery item
if (groceryList.includes(event.target.parentNode.firstChild.textContent)) {
// get the index
let k = groceryList.indexOf(event.target.parentNode.firstChild.textContent);
if (k !== -1) {
groceryList.splice(k, 1);
}
}
displayGroceries();
}
function displayGroceries() {
let groceryUl = document.getElementById('grocery-ul');
groceryUl.innerHTML = '';
for (let i = 0; i < groceryList.length; i++) {
let groceryLi = document.createElement('LI');
let removeBtn = document.createElement('BUTTON');
groceryUl.classList.add('flex-display')
removeBtn.textContent = `remove ${groceryList[i]}`;
removeBtn.setAttribute('onclick', `removeEl(event)`)
groceryLi.innerHTML = groceryList[i];
groceryUl.appendChild(groceryLi);
groceryLi.insertAdjacentElement('beforeend', removeBtn);
}
}
.flex-display {
display: flex;
align-items: start;
flex-direction: column;
}
li {
list-style: none;
}
button {
margin-left: 1rem;
}
<div class="container">
<h1>Grocery List</h1>
<input id="add-input" placeholder="Add Groceries" autocomplete="off">
<button id="add-button">Add</button>
<!--<button id="remove-button">Remove</button>-->
<div>
<ul id="grocery-ul">
</ul>
</div>

how to subtract a number on the second button click?

function createElement(){
const input = document.getElementById("input");
const ul = document.getElementById("ul");
const li = document.createElement("li");
const div = document.createElement("div");
div.innerText = input.value;
const btn = document.createElement("button");
btn.innerText = "add 2";
li.appendChild(div);
li.appendChild(btn);
ul.appendChild(li);
btn.onclick = () => {
let num = parseInt(div.innerText);
num = num + 2;
const roundednum = num.toFixed(2);
div.innerText = roundednum;
btnCLicked = true;
}
}
document.getElementById("btn").addEventListener("click", createElement);
<input type="number" id="input">
<ul id="ul"></ul>
<button id="btn">click me</button>
i have this simple code above that creates some elements, and it will add 2 to the number in the div when button inside the <li> is clicked, which works perfectly
now i want to subtract 2 from the number when button is clicked the second time, so first click will add 2, second click will subtract 2
so i come up with this code which uses a boolean to add and subtract the number which works
let btnCLicked = false;
function createElement(){
const input = document.getElementById("input");
const ul = document.getElementById("ul");
const li = document.createElement("li");
const div = document.createElement("div");
div.innerText = input.value;
const btn = document.createElement("button");
btn.innerText = "add 2";
li.appendChild(div);
li.appendChild(btn);
ul.appendChild(li);
btn.onclick = () => {
if (btnCLicked === false) {
let num = parseInt(div.innerText);
num = num + 2;
const roundednum = num.toFixed(2);
div.innerText = roundednum;
btnCLicked = true;
}else if (btnCLicked === true) {
let num = parseInt(div.innerText);
num = num - 2;
const roundednum = num.toFixed(2);
div.innerText = roundednum;
btnCLicked = false;
}
}
}
document.getElementById("btn").addEventListener("click", createElement);
<input type="number" id="input">
<ul id="ul"></ul>
<button id="btn">click me</button>
but now the problem is that when i create an <li>, and click the button inside to add 2 to the number, btnClicked is now set to true.
so when i create another element and click the button for the first time it will subtract the number because btnClicked is true, which is what i dont want, i want the first click to add and subtract on the second click
how do i solve that problem? im thinking about something like making the boolean only inside each li so each li has an own boolean and it will not effect other elements but idk lol i have no other idea
Move the let btnCLicked = false; into the function. Each invocation of createElement() will create a [closure`]1 since it's child function (click handler) is using variables from parents scope:
const input = document.getElementById("input");
const ul = document.getElementById("ul");
function createElement() {
let btnCLicked = false;
const li = document.createElement("li");
const div = document.createElement("div");
const btn = document.createElement("button");
div.innerText = input.value || 0;
btn.innerText = "add 2";
li.appendChild(div);
li.appendChild(btn);
ul.appendChild(li);
btn.onclick = () => {
let num = Number(div.innerText);
if (btnCLicked === false) {
num += 2;
} else {
num -= 2;
}
div.innerText = num;
btnCLicked = !btnCLicked;
}
}
document.getElementById("btn").addEventListener("click", createElement);
<input type="number" id="input">
<ul id="ul"></ul>
<button id="btn">click me</button>
Another solution: use event delegation and use a data-attribute to track the last action (add or subtract):
document.addEventListener("click", handle);
function createElement() {
document.querySelector("#ul").insertAdjacentHTML("beforeend", `
<li>
<div>${(+document.querySelector("#input").value).toFixed(2)}</div>
<button data-action="add">2</button>
</li>`);
}
function handle(evt) {
const origin = evt.target;
if (origin.id === "btn") {
return createElement();
}
if (origin.dataset.action) {
return addOrSubtract2(origin);
}
}
function addOrSubtract2(fromBttn) {
const parentLi = fromBttn.closest("li");
const add = fromBttn.dataset.action === "add";
const num = +(parentLi.querySelector("div").textContent) + (add ? 2 : -2);
fromBttn.dataset.action = add ? "subtract" : "add";
parentLi.querySelector("div").textContent = num.toFixed(2);
}
[data-action]:before {
content: attr(data-action)' ';
}
<input type="number" id="input" value=0 step="0.1">
<button id="btn">create new</button>
<ul id="ul"></ul>

Is this javasScript sorting function correct?

I'm a javaScript begginer. This function works and allows me to sorting list by ascendant and descendant by clicking in a button but I would like to know if it's properly written or maybe it can be simplified or written differently. Thanks in advance.
index.html
<p id="sort">Sort</p>
<ul>
<li>Phosphorus</li>
<li>Polonium</li>
<li>Radium</li>
<li>Fluorine</li>
<li>Iron</li>
<li>Hydrogen</li>
<li>Germanium</li>
<li>Mercury</li>
<li>Actinium</li>
<li>Barium</li>
<li>Calcium</li>
<li>Cadmium</li>
</ul>
<script src="main.js"></script>
main.js
const sortButton = document.querySelector("#sort");
let counter = 0;
eventListeners();
function eventListeners() {
sortButton.addEventListener("click", sortList);
}
function sortList() {
const elementsList = new Array();
const elements = document.querySelectorAll("ul li");
for (const element of elements) {
elementsList.push(element.textContent);
}
const ul = document.querySelector("ul");
ul.innerHTML = "";
if (counter % 2 === 0) {
const orderedList = elementsList.sort();
for (let i = 0; i < orderedList.length; i++) {
const li = document.createElement("li");
li.innerHTML = orderedList[i];
ul.appendChild(li);
}
counter++;
} else {
const orderedList = elementsList.reverse();
for (let i = 0; i < orderedList.length; i++) {
const li = document.createElement("li");
li.innerHTML = orderedList[i];
ul.appendChild(li);
}
counter++;
}
}
you don't have to clear the innerHTML of the ul, nor even ever use innerHTML
element.appendChild will move existing elements - as below
const sortButton = document.querySelector("#sort");
let counter = 0;
eventListeners();
function eventListeners() {
sortButton.addEventListener("click", sortList);
}
function sortList() {
const elementsList = Array.from(document.querySelectorAll("ul li"));
const ul = document.querySelector("ul");
elementsList.sort(({textContent:a}, {textContent:b})=>(counter%2?-1:1)*a.localeCompare(b))
.forEach(el => ul.appendChild(el));
counter++;
}
<p id="sort">Sort</p>
<ul>
<li>Phosphorus</li>
<li>Polonium</li>
<li>Radium</li>
<li>Fluorine</li>
<li>Iron</li>
<li>Hydrogen</li>
<li>Germanium</li>
<li>Mercury</li>
<li>Actinium</li>
<li>Barium</li>
<li>Calcium</li>
<li>Cadmium</li>
</ul>
In Es2015 (i.e. old school javascirpt) - this function looks like:
function sortList() {
var elementsList = Array.from(document.querySelectorAll("ul li"));
var ul = document.querySelector("ul");
elementsList.sort(function (_ref, _ref2) {
var a = _ref.textContent;
var b = _ref2.textContent;
return (counter % 2 ? -1 : 1) * a.localeCompare(b);
}).forEach(function (el) {
return ul.appendChild(el);
});
counter++;
}

Card from Javascript into HTML, there must be a better way (working)

There must be a better, shorter way to generate many cards from javascript into HTML.
This is the format to follow, it's working but can it be better?????
span{color: red;}
<div id="mycard"></div>
var dateSpan = document.createElement('span')
var ul = document.createElement('ul')
var ol = document.createElement('ol')
var li = document.createElement('li');
var li2 = document.createElement('li')
dateSpan.innerHTML = '#3500';
li.textContent = 'Title of card '
li2.textContent = '"Small description"'
li.appendChild(dateSpan);
li.appendChild(ul);
ul.appendChild(li2);
ol.appendChild(li);
var app = document.querySelector('#mycard');
app.appendChild(ol)
Note: Yeah, you can add a '<b/r>' but the "Small description" should be stylish later on... :)
Create a function to generate html content and then call that function as many time as you want.
For example
function generateList(title, description){
var htmlVal = `<li>${title}<br>${description}</li>`;
return htmlVal;
}
Then call the function however you like and append it to the element.
document.getElementById("myCard") += generateList("Title of Card #3500","Small description");
Where in your html there's an element with id "myCard"
You can create a class cardMaker and create instances with a for loop:
class cardMaker {
constructor(n) {
var dateSpan = document.createElement('span')
var ul = document.createElement('ul')
var ol = document.createElement('ol')
var li = document.createElement('li');
var li2 = document.createElement('li')
dateSpan.innerHTML = '#3500' + n;
li.textContent = 'Title of card '
li2.textContent = '"Small description"'
li.appendChild(dateSpan);
li.appendChild(ul);
ul.appendChild(li2);
ol.appendChild(li);
var app = document.querySelector('#mycard');
app.appendChild(ol)
}
}
let cards = [];
for (let i = 0; i < 10; i++) {
cards[i] = new cardMaker(i);
}

Create span element in li with Javascript

I'm working on a to-do list project and when creating a new li I would like it to start with a span containing a "X". I wrote the code below, but instead of a span I get "[object HTMLSpanElement]". Anybody knows how to fix this? Thank you!
var enterItem = document.querySelectorAll("input");
var todoList = document.getElementById("todo-list");
for (var i = 0; i < enterItem.length; i++) {
enterItem[i].addEventListener("keypress", function(key) {
if(key.which === 13){
var newLi = document.createElement("li");
var span = document.createElement("span");
var newItem = this.value;
span.textContent = "X";
newLi.appendChild(document.createTextNode(span + " " + newItem));
todoList.appendChild(newLi);
this.value = "";
}
});
}
You are trying to add an html element in a textNode so it triggers the toString of the element
You need
const todoList = document.getElementById("todo-list");
document.getElementById("inputContainer").addEventListener("keypress", function(e) {
const tgt = e.target;
if (tgt.type === "text" && e.which === 13) {
let newLi = document.createElement("li");
let span = document.createElement("span");
span.classList.add("remove");
let newItem = tgt.value;
span.textContent = "X";
newLi.appendChild(span)
newLi.appendChild(document.createTextNode(" " + newItem));
todoList.appendChild(newLi);
tgt.value = "";
}
});
document.getElementById("todo-list").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains("remove")) {
tgt.closest("li").remove();
}
})
<div id="inputContainer">
<input type="text" />
</div>
<ul id="todo-list"></ul>

Categories

Resources