Changing inner values in a list of items - javascript

Let say I have a HTML code:
<li>
<button class="add">+</button>
<span class="amount">0</span>
<button class="substract">-</button>
</li>
<li>
<button class="add">+</button>
<span class="amount">0</span>
<button class="substract">-</button>
</li>
and so on...
and JS:
function addAmount(el) {
let amount = document.querySelector(".amount");
let addAmount = parseInt(amount.innerHTML);
if (el.classList.contains("add")) {
addAmount = addAmount + 1;
amount.innerHTML = addAmount;
}
return addAmount;
}
document.addEventListener("click", e => {
addAmount(e.target);
});
This code works only for first li element. I would like to know how I can obtain a code in which every button from each li element is responsible for only one element (one button adds value for one li element).

You shouldn't be searching the document at all. You can use document.querySelectorAll(".amount") to get a list of all the amount fields, but you need to figure out which index to use.
Instead, you can use previousElementSibling or nextElementSibling to get the element before or after the button you clicked on.
function addAmount(el) {
let amountElement, adjustAmount
if (el.classList.contains("add")) {
adjustAmount = 1;
amountElement = el.nextElementSibling;
} else {
adjustAmount = -1;
amountElement = el.previousElementSibling;
}
amountElement.innerText = parseInt(amountElement.innerText) + adjustAmount;
}
document.addEventListener("click", e => {
addAmount(e.target);
});
<li>
<button class="add">+</button>
<span class="amount">0</span>
<button class="substract">-</button>
</li>
<li>
<button class="add">+</button>
<span class="amount">0</span>
<button class="substract">-</button>
</li>

Related

Classlist validation for unique selected item

So I'm trying to create a chat application like messenger.
When I press the button, a new conversation should be started. I want to add a list item in my overview bar on the left but there can only be one selected, and that one has the 'history-item-selected' classname. So every new convo gets that classname, while the others ones get another classname to change it's appearance but it won't work.
const newConvoButton = document.getElementById("newmessage");
const addNewConvo = (e) => {
e.preventDefault();
const myMessages = document.getElementById('history');
let newListItem = document.createElement('li');
newListItem.textContent = "user " + Math.floor(Math.random(2 - 100) * 100);
myMessages.appendChild(newListItem);
if (newListItem.classList = 'history-item-selected') {
newListItem.classList.add('history-item-selected');
} else {
newListItem.classList.add('history-item')
};
};
newConvoButton.addEventListener('click', addNewConvo);
<main>
<div id="top">
<span>
<h2>My conversations</h2>
</span>
<button type="submit" id="newmessage">+</button>
</div>
<div id="messagecontainer">
<ul id="history"></ul>
<id id="chatscreen">
<ul id="messages">
<li>yolo</li>
</ul>
<div id="messagebottom">
<input type="text" placeholder="Start met typen" size="28" height="auto"> <button type="submit">Send</button>
</div>
</id>
</div>
</main>
There is a "tautological" way to do what you are trying to do.
var addNewConvo = (e)=> {
var nodes = document.querySelectorAll(".history-item-selected");
nodes.forEach(function(elem) {
this.classList.remove("history-item-selected");
});
e.target.classList.add("history-item-selected");
}
In your CSS, you should have something like
.history-item {
/*Styles for history-item*/
}
.history-item.history-item-selected {
/*Styles for elements with both */
}

How do you write a for loop that chooses the first button in an array?

How can I add a for-loop to just pick the first buttons of the div where the .mybuttons class is located?
var mapNumber;
const mybuttons = document.querySelectorAll('.mybuttons button');
mybuttons.forEach(mybutton => {
mybutton.addEventListener('click', processClick);
});
function processClick() {
window.mapNumber = this.id; // the id of the clicked button
}
<div class="mybuttons">
<button id="One">One</button>
<button id="Two">Two</button>
<button id="Three">Three</button>
<button id="Four">Four</button>
<button id="Five">Five</button>
</div>

Getting to the next button chain

I'm trying to create a chain of buttons:
First options;
- Button 1
- Button 2
IF chosen Button 1:
- Button 1a
- Button 1b
IF chosen Button 1a:
- Button 1aa
- Button 1ab
IF chosen Button 1b:
- Button 1ba
- Button 1bb
And so on.. same goes for Button 2.
Thus far I got this but my .js is not working out for me.
I tried it in two ways.
WAY 1:
HTML (onclick="nextPush" is going to change in way 2)
<div class="buttons1-2">
<button id="btn1" class="btn btn1" onclick="buttonPushed(this)">Button 1</button>
<button id="btn2" class="btn btn2" onclick="buttonPushed(this)">Button 2</button>
</div>
<div class="buttons1a-b">
<button id="btn1a" class="btn btn1a" onclick="nextPush(this)">Button 1a</button>
<button id="btn1b" class="btn btn1b" onclick="nextPush(this)">Button 1b</button>
</div>
<div class="buttons2a-b">
<button id="btn2a" class="btn btn2a">Button 2a</button>
<button id="btn2b" class="btn btn2b">Button 2b</button>
</div>
<div class="buttons1aa-ab">
<button id="btn1aa" class="btn btn1a">Button 1aa</button>
<button id="btn1ab" class="btn btn1b">Button 1ab</button>
</div>
<div class="buttons1ba-bb">
<button id="btn1ba" class="btn btn2a">Button 1ba</button>
<button id="btn1bb" class="btn btn2b">Button 1bb</button>
</div>
WAY 1: .JS
function buttonPushed(btn) {
var replacewith = "buttons1a-b";
if (btn.id == "btn2") {
replacewith = "buttons2a-b";
}
function nextPush(btn) {
var replacewith = "buttons1aa-ab";
if (btn.id == "btn1b") {
replacewith = "buttons1ba-bb";
}
var allChildren = document.getElementsByClassName('buttons')[0].children;
for (var i = 0; i < allChildren.length; i++) {
var child = allChildren[i];
if (child.className != replacewith) {
child.style.display = "none";
} else {
child.style.display = "inline";
}
}
}
WAY 2: HTML (notice the onclick="nextPush" is gone)
<div class="buttons1-2">
<button id="btn1" class="btn btn1" onclick="buttonPushed(this)">Button 1</button>
<button id="btn2" class="btn btn2" onclick="buttonPushed(this)">Button 2</button>
</div>
<div class="buttons1a-b">
<button id="btn1a" class="btn btn1a" onclick="buttonPushed(this)">Button 1a</button>
<button id="btn1b" class="btn btn1b" onclick="buttonPushed(this)">Button 1b</button>
</div>
<div class="buttons2a-b">
<button id="btn2a" class="btn btn2a">Button 2a</button>
<button id="btn2b" class="btn btn2b">Button 2b</button>
</div>
<div class="buttons1aa-ab">
<button id="btn1aa" class="btn btn1a">Button 1aa</button>
<button id="btn1ab" class="btn btn1b">Button 1ab</button>
</div>
<div class="buttons1ba-bb">
<button id="btn1ba" class="btn btn2a">Button 1ba</button>
<button id="btn1bb" class="btn btn2b">Button 1bb</button>
</div>
WAY 2 .JS
function buttonPushed(btn) {
/* btn = Id: btn1, btn2, btn1a or btn1b */
let replacewith = "buttons1a-b";
if (btn.id == "btn2") {
replacewith = "buttons2a-b";
}
else if (btn.id == "btn1a") {
replacewith = "buttons1aa-ab";
}
else if (btn.id == "btn1b") {
replacewith = "buttons1ba-bb";
}
}
let allChildren = document.getElementsByClassName('buttons')[0].children;
for (let i = 0; i < allChildren.length; i++) {
let child = allChildren[i];
if (child.className != replacewith) {
child.style.display = "none";
} else {
child.style.display = "inline";
}
}
.CSS for BOTH WAYS:
.buttons1a-b {
display: none;
}
.buttons2a-b {
display: none;
}
.buttons1aa-ab {
display: none;
}
.buttons1ba-bb {
display: none;
}
Sorry for the long post, hope you can help me out :) If you know a better way to do this, please also do let me know.
Building on your example, and the one from Michael, you could also use another approach of declaring what div you want displayed by attaching an attribute to the button, and then add an event listener to all buttons with that attribute. This makes the HTML slightly smaller and more declarative, and makes it easier to switch what element you want to display next instead of relying on a particular schema of id's.
(function(document) {
// get all buttons that have the attribute data-next
const buttons = document.querySelectorAll('[data-next]');
for (const item of buttons) {
// get references to the parent item and next item to hide/show
const parentId = item.getAttribute('data-parent');
const parent = document.querySelector(`#${parentId}`);
const nextDivId = item.getAttribute('data-next');
const nextDiv = document.querySelector(`#${nextDivId}`);
if (!nextDiv) {
console.error('could not find next div for button ', item);
}
// attach an event listener for click that toggles visibility of the above elements
item.addEventListener('click', function() {
nextDiv.classList.toggle('hidden');
parent.classList.toggle('hidden');
});
}
})(document);
.hidden {
display: none;
}
<div id="base">
<button data-next="option-a" data-parent="base">Option A</button>
<button data-next="option-b" data-parent="base">Option B</button>
</div>
<div id="option-a" class="hidden">
<p>Option A</p>
</div>
<div id="option-b" class="hidden">
<p>Option B</p>
</div>
If you want to add new buttons dynamically (or change what your next items should be) you will need to attach the event listener when you create your other buttons. For instance, you can do something like the following:
(function(document) {
function onButtonClicked(event) {
const item = event.target;
// get references to the next item to show
const nextDivId = item.getAttribute('data-next');
const nextDiv = document.querySelector(`#${nextDivId}`);
if (!nextDiv) {
console.error('could not find next div for button ', item);
}
// The function toggle on classList either removes a class if it exists
// or adds it if it does not exist in the list of classes on the element
nextDiv.classList.toggle('hidden');
// check if container has an attribute for loading next buttons lazily
const lazyLoadLevel = nextDiv.getAttribute('data-level');
// if we found the attribute, load the contents
if (lazyLoadLevel) {
// cast lazyLoadedLevel to an integer (with +) since getAttribute returns a string
loadLevel(+lazyLoadLevel, nextDiv);
// since we have populated the container we can remove the attribute so that elements do not get added again
nextDiv.removeAttribute('data-level');
}
// get references to the parent item to hide
const parentId = item.getAttribute('data-parent');
const parent = document.querySelector(`#${parentId}`);
if (parent) {
parent.classList.toggle('hidden');
}
}
function addButton(parent, nextElementId, text) {
const newItem = document.createElement('button');
newItem.setAttribute('data-next', nextElementId);
newItem.setAttribute('data-parent', parent.getAttribute('id'));
newItem.textContent = text;
newItem.addEventListener('click', onButtonClicked);
parent.appendChild(newItem);
}
function loadLevel(level, container) {
switch (level) {
// depending on level you can define other buttons to add here
case 2:
{
addButton(container, 'option-a', 'Goto option a');
break;
}
}
}
// get all *existing* buttons that have the attribute data-next
// this is run once when the script loads, and will not attach listeners to dynamically created buttons
const buttons = document.querySelectorAll('[data-next]');
for (const item of buttons) {
// attach an event listener for click that toggles visibility of parent and next elements
// notice that we pass a reference to onButtonClicked. Even though it is a function we shouldn't call it *here*
item.addEventListener('click', onButtonClicked);
}
})(document);
.hidden {
display: none;
}
<div id="base">
<button data-next="option-a" data-parent="base">Option A</button>
<button data-next="option-b" data-parent="base">Option B</button>
</div>
<div id="option-a" class="hidden">
<p>Option A</p>
<button data-next="option-b" data-parent="option-a">Option B</button>
</div>
<div id="option-b" class="hidden" data-level="2">
<p>Option B. The contents of this div is loaded lazily based on the value of the attribute data-level</p>
</div>
At first, I was thinking this should be done entirely dynamically -- where the next container of buttons is created and inserted into the DOM when the button is clicked. But judging by your current attempts, it seems like you want to have all the buttons hardcoded into the source, hidden with CSS, and shown with DOM during the click event. Here is one way you can achieve that:
function handleButtonClick(button) {
const clickedID = button.id.substring(3);
const nextDiv = document.getElementById("buttons" + clickedID);
if (nextDiv) {
nextDiv.style.display = "block";
}
}
.hidden {
display: none;
}
<div id="buttons">
<button id="btn1" onclick="handleButtonClick(this)">Button 1</button>
<button id="btn2" onclick="handleButtonClick(this)">Button 2</button>
</div>
<div id="buttons1" class="hidden">
<button id="btn1a" onclick="handleButtonClick(this)">Button 1a</button>
<button id="btn1b" onclick="handleButtonClick(this)">Button 1b</button>
</div>
<div id="buttons2" class="hidden">
<button id="btn2a" onclick="handleButtonClick(this)">Button 2a</button>
<button id="btn2b" onclick="handleButtonClick(this)">Button 2b</button>
</div>
<div id="buttons1a" class="hidden">
<button id="btn1aa" onclick="handleButtonClick(this)">Button 1aa</button>
<button id="btn1ab" onclick="handleButtonClick(this)">Button 1ab</button>
</div>
<div id="buttons1b" class="hidden">
<button id="btn1ba" onclick="handleButtonClick(this)">Button 1ba</button>
<button id="btn1bb" onclick="handleButtonClick(this)">Button 1bb</button>
</div>
<div id="buttons2a" class="hidden">
<button id="btn2aa" onclick="handleButtonClick(this)">Button 2aa</button>
<button id="btn2ab" onclick="handleButtonClick(this)">Button 2ab</button>
</div>
<div id="buttons2b" class="hidden">
<button id="btn2ba" onclick="handleButtonClick(this)">Button 2ba</button>
<button id="btn2bb" onclick="handleButtonClick(this)">Button 21bb</button>
</div>
This just identifies which button was clicked, and uses that information to determine the next div to show, until there are no more divs that correspond to the one that was clicked.

Remove class if id's the correct ID

Looking to remove a class if a certain button is clicked.
<div class="slide-container">
<section class="about" id="slide-0">
<div class="menu-total">
<nav class="nav">
<button class="nav_link home" onclick="slideTo('slide-2')">HOME</button>
<button class="nav_link about" onclick="slideTo('slide-0')">ABOUT</button>
<button class="nav_link fun-stuff" onclick="slideTo('slide-1')">FUN STUFF</button>
<button class="nav_link professional" onclick="slideTo('slide-3')">PROFESSIONAL</button>
<button class="nav_link contact" onclick="slideTo('slide-4')">CONTACT</button>
</nav>
<div class="hamburger">
<span class="hamburger__patty"></span>
<span class="hamburger__patty"></span>
<span class="hamburger__patty"></span>
</div>
</div>
The one I want to remove the class on is the HOME button. So "slideTo('slide-2)". If it's clicked on the others then the class is kept. I believe someone is either wrong with my loop or not getting the ID correctly of the items/
function slideTo(slideId) {
const slide = document.getElementById(slideId);
slide.scrollIntoView({
behavior: 'smooth'
})
// above this line works fine
let nonHome = document.querySelectorAll('.slide-container section');
let nonHomeID = document.getElementById('slide-2');
var i;
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
// i believe it's somewhere here it is wrong
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
} else{
nonHomeID.classList.remove("nav-visibility");
}
}
}, 1000)
}
If you can use jquery library, you can write in the HTML:
<button class="nav_link" data-value="home">HOME</button>
...
and then in the JS code:
$(".nav_link").on("click", function() {
var valueClicked = $(this).data("value"); // Get the data-value clicked
$(".nav_link").each(function() { // Loop through all elements of the class 'nav-link'
var v = $(this).data("value");
if (v == valueClicked) {
$(this).removeClass("nav-visibility");
} else {
$(this).addClass("nav-visibility");
}
)
}
Not much simpler, but the HTML is cleaner.
Simpler version if it is not required to browse through all buttons at each button click:
$(".nav_link").on("click", function() {
var valueClicked = $(this).data("value"); // The value of the button clicked by the user
if (valueClicked == "home") {
$(this).removeClass("nav-visibility");
console.log('remove')
} else { $(this).addClass("nav-visibility");
console.log('add')
}
});

JavaScript function to search elements

I have a search bar, and an unordered list.
<input onkeyup="Cautare()" type='text' id='userInput' placeholder='Cautare...'>
<button class="Buton" id='Trimitere' onclick="TrimitereWikipedia()">Cautare Wikipedia</button>
<br />
<ul id="myUL">
<li>ISTORIA OPTICII</li>
<li>PRINCIPIILE OPTICII GEOMETRICE</li>
</ul>
With this JS function (Cautare()):
function Cautare() {
var input, filter, ul, li, a, i;
input = document.getElementById("userInput");
filter = input.value.toUpperCase();
ul = document.getElementById("myUL");
li = ul.getElementsByTagName("li");
for (i = 0; i < li.length; i++) {
a = li[i].getElementsByTagName("a")[0];
if (a.innerHTML.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
}}
So this function basically searches what's inside the <a> tags from <li>
I wanted to make this script kinda search what's inside of the content by doing this:
add <p style="display: none">content</p>: so the word 'content' would be another 'keyword' which you can't see;
declaring p variable in the function
p = li[i].getElementsByTagName("p")[0];, under a = li[i].getElementsByTagName("a")[0];
add in the if statement: || p.innerHTML.toUpperCase().indexOf(filter) > -1
So when I put the word "content" in the search bar the first list item should appear, but nothing is happening and in fact all of this breaks the function.
How could I edit the function so I can finish what I want to do? I am not very experienced with JavaScript.
Option 1: data attributes
You can use data attributes to store that additional keyword. Something like <a href="Istoria_Opticii.html" data-keyword="content"> and then you can get that keyword using element.dataset.keyword.
Note that if you want to select those a elements, and you hold reference to their parent, all you need to do is ask for firstChild. And even better, just select them right away using #myUL > li > a pattern combined with querySelectorAll.
Also note that you should use textContent instead of innerHTML if you need to get only the text.
function Cautare() {
const inputValue = document.getElementById('userInput').value.toUpperCase();
const items = [...document.querySelectorAll('#myUL > li > a')];
items.forEach(item => {
if (item.textContent.toUpperCase().includes(inputValue) ||
item.dataset.keyword.toUpperCase().includes(inputValue)) {
item.parentElement.style.display = '';
} else {
item.parentElement.style.display = 'none';
}
});
}
<input onkeyup="Cautare()" type='text' id='userInput' placeholder='Cautare...'>
<!-- <button class="Buton" id='Trimitere' onclick="TrimitereWikipedia()">Cautare Wikipedia</button> -->
<br />
<ul id="myUL">
<li>ISTORIA OPTICII</li>
<li>PRINCIPIILE OPTICII GEOMETRICE</li>
</ul>
Now, if you start typing word content the first li element will stay visible (second will stay visible if you start typing somethingelse).
Option 2: hidden elements
You can use hidden elements the way you have described if you want to. Snippet below shows how, using the hidden p element the way that you have mentioned.
function Cautare() {
const inputValue = document.getElementById('userInput').value.toUpperCase();
const items = [...document.querySelectorAll('#myUL > li')];
items.forEach(item => {
const aElem = item.querySelector('a');
const pElem = item.querySelector('p');
if (aElem.textContent.toUpperCase().includes(inputValue) ||
pElem.textContent.toUpperCase().includes(inputValue)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
}
<input onkeyup="Cautare()" type='text' id='userInput' placeholder='Cautare...'>
<!-- <button class="Buton" id='Trimitere' onclick="TrimitereWikipedia()">Cautare Wikipedia</button> -->
<br />
<ul id="myUL">
<li>
<p style="display: none">content</p>
ISTORIA OPTICII
</li>
<li>
<p style="display: none">somethingElse</p>
PRINCIPIILE OPTICII GEOMETRICE
</li>
</ul>
Option 3: Map/WeakMap
Another option would be to create a map that would hold individual li elements as keys and keywords associated with them as values.
function Cautare() {
const inputValue = document.getElementById('userInput').value.toUpperCase();
const items = [...document.querySelectorAll('li')];
const keywords = new WeakMap([[items[0], 'content'], [items[1], 'somethingElse']]);
items.forEach(item => {
if (item.firstChild.textContent.toUpperCase().includes(inputValue) ||
keywords.get(item).toUpperCase().includes(inputValue)) {
item.style.display = '';
} else {
item.style.display = 'none';
}
});
}
<input onkeyup="Cautare()" type='text' id='userInput' placeholder='Cautare...'>
<!-- <button class="Buton" id='Trimitere' onclick="TrimitereWikipedia()">Cautare Wikipedia</button> -->
<br />
<ul id="myUL">
<li>ISTORIA OPTICII</li>
<li>PRINCIPIILE OPTICII GEOMETRICE</li>
</ul>
This approach can be simplified even more if you store both keyword and text content of a given item in the map. In such case, you need to check only one condition.
function Cautare() {
const inputValue = document.getElementById('userInput').value.toUpperCase();
const items = [...document.querySelectorAll('li')];
const keywords = new WeakMap([[items[0], 'content, ISTORIA OPTICII'],
[items[1], 'somethingElse, PRINCIPIILE OPTICII GEOMETRICE']]);
items.forEach(item => {
keywords.get(item).toUpperCase().includes(inputValue) ?
item.style.display = '' :
item.style.display = 'none';
});
}
<input onkeyup="Cautare()" type='text' id='userInput' placeholder='Cautare...'>
<!-- <button class="Buton" id='Trimitere' onclick="TrimitereWikipedia()">Cautare Wikipedia</button> -->
<br />
<ul id="myUL">
<li>ISTORIA OPTICII</li>
<li>PRINCIPIILE OPTICII GEOMETRICE</li>
</ul>

Categories