addEventListener mouseenter not - javascript

I would like to find out how can I go (on click) through every element with class children. If children element has additional class selected I would like to set counter to += 1.
After All I have plan to print message on same click: You have either 0,1,2,3 elements selected with class .SELECTED
Can you help me on that?
const children = document.querySelectorAll('.children');
const selected = document.getElementsByClassName('selected');
const button = document.getElementById('bttn');
let counter = 0;
for (let i = 0; i < children.length; i++) {
children[i].addEventListener('click', () => {
if (children[i].classList.contains('selected')) {
children[i].classList.remove('selected')
} else {
children[i].classList.add('selected')
}
button.addEventListener('click', () => {
for (u = 0; u < children[i]; u++) {
if (children[i].classList == 'selected') {
counter++;
}
}
console.log(counter);
})
})
}
#div {
display: inline-flex;
flex-direction: column;
}
.children {
cursor: pointer;
}
.selected {
background-color: royalblue;
}
<div id="div">
<span class='children selected'>children1</span>
<span class='children'>children2</span>
<span class='children'>children3</span>
</div>
<p><input id="bttn" type='button' value='klik'></p>

You can make your loops a little easier to read using a forEach loop and referencing objects with event.target. Rather than testing to see if an element contains a class just to add/remove it, use classList.toggle().
e.target.classList.toggle('selected');
To get the total number of children with class selected you can simply do this one liner:
counter = document.querySelectorAll('.children.selected').length
const children = document.querySelectorAll('.children');
const selected = document.getElementsByClassName('selected');
const button = document.getElementById('bttn');
let counter = 0;
children.forEach(el => {
el.addEventListener('click', (e) => {
e.target.classList.toggle('selected');
})
})
button.addEventListener('click', () => {
counter = document.querySelectorAll('.children.selected').length;
console.log(counter);
})
#div {
display: inline-flex;
flex-direction: column;
}
.children {
cursor: pointer;
}
.selected {
background-color: royalblue;
}
<div id="div">
<span class='children selected'>children1</span>
<span class='children'>children2</span>
<span class='children'>children3</span>
</div>
<p><input id="bttn" type='button' value='klik'></p>

I think the following more or less does what you are hoping to accomplish(?). The total reported should vary between 0 and max number of .children elements - the counter therefore needs to be reset when the button is clicked and the count re-calculated.
/*
initial variables for counter,
click event and class names
*/
let counter=0;
let pcn='children';
let cn='selected';
let evt='click';
/*
shorthand utility methods
*/
const d=document;
const qa=(n,e)=>n.querySelectorAll(e);
const q=(n,e)=>n.querySelector(e);
/*
Find all nodes with class `children`
and iterate through collection.
Add a `click` handler to each and
toggle the active class `selected`
when clicked.
Increase the counter accordingly.
*/
let col=qa(d, '.' + pcn );
col.forEach( n=>{
n.addEventListener(evt,e=>{
n.classList.toggle( cn );
counter += n.classList.contains( cn ) ? 1 : 0;
});
});
/*
Assign a `click` evnt handler to each button ( assumed more than one )
- reset the counter when clicked and restart the counter.
*/
qa(d,'input[type="button"]').forEach(n=>{
n.addEventListener(evt,e=>{
counter=0;
col.forEach( c=>{
if( c.classList.contains( cn ) )counter++;
});
console.info( counter );
});
});
div {
display: inline-flex;
flex-direction: column;
}
.children {
cursor: pointer;
}
.selected {
background-color: royalblue;
}
<div>
<span class='children selected'>children1</span>
<span class='children'>children2</span>
<span class='children'>children3</span>
</div>
<input type='button' value='klik' />

Related

How to style a singular v-for item in vue, based on a condition?

I am making a multiple choice quiz game and want what the user clicked to change to either red or green depending on if that answer is correct or incorrect. I made a variable called selected which is what the user pressed- this does correctly update. I have also got all of the v-for items to change to the same colour depending on if the answer is correct or not, I only need help separating it so that only one of the v-for things change colour.
Here is my relative HTML:
<button type="button" class="btn" class="answer" v-for="option in options" #click="checkAnswer(option)" #click="selected = option" :style="{backgroundColor: colour}">
{{option}}<br/>
</button>
<button type="button" #click="getQ" #click="shuffle(options)" class="btn button next">Next</button>
Here is the relative JS:
let colour = Vue.ref('');
let selected = Vue.ref('');
let options = Vue.ref([correctAnswer, incorrectAnswerOne, incorrectAnswerTwo, incorrectAnswerThree]);
// Methods
let shuffle = function(options) {
let num = options.length, t, raInt;
//while there are remaining elements to shuffle
while (num) {
//choose random
raInt = Math.floor(Math.random() * num--);
//swap with current element
t = options[num];
options[num] = options[raInt];
options[raInt] = t;
}
return options;
};
let checkAnswer = function(clicked) {
console.log(clicked.value);
console.log(correctAnswer.value);
if (clicked.value == correctAnswer.value) { // checks if the button that was clicked is the same as the answers value
this.result = "Correct!"; //if it does, result changes to Correct!
this.colour = "green";
} else {
this.result = "Incorrect!"; //if the answer is incorrect, result changes to Incorrect!
this.colour = "red";
};
};
And here is some CSS:
.answer {
width: 100%;
background-color: #dbdbdb;
padding: 4% 2%;
margin: 1 0;
}
.answer:hover {
background-color: #c2c2c2
}
I haven’t really tried that much. I’m not sure what to try. In a different project I did style a different div based on what other div was selected, but I am not sure how to change just one part of a v-for, or if it is even possible.
Thanks in advance
You can set condition for showing color style:
const { ref, computed } = Vue
const app = Vue.createApp({
setup() {
let correctAnswer = 3
let incorrectAnswerOne = 1
let incorrectAnswerTwo = 2
let incorrectAnswerThree = 4
let colour = ref('');
let selected = ref('');
let options = ref([correctAnswer, incorrectAnswerOne, incorrectAnswerTwo, incorrectAnswerThree]);
let shuffled = ref([])
let shuffle = (array) => {
selected.value = null
let currentIndex = array.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
shuffled.value = array;
};
shuffle(options.value)
let checkAnswer = function(clicked) {
// 👇 set selected
selected.value = clicked
if (clicked == correctAnswer) {
this.result = "Correct!";
this.colour = "green";
} else {
this.result = "Incorrect!";
this.colour = "red";
};
};
return { colour, selected, options, shuffle, checkAnswer, shuffled }
},
})
app.mount('#demo')
.answer {
width: 100%;
background-color: #dbdbdb;
padding: .5em 2em;
margin: 1 0;
}
.answer:hover {
background-color: #c2c2c2
}
.btns {
display: flex;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="btns">
<div v-for="(option, i) in shuffled" :key="i" >
<!-- 👇 condition -->
<button class="answer" #click="checkAnswer(option)" :style="selected == option && {backgroundColor: colour}">
{{option}}<br/>
</button>
</div>
</div>
<button type="button" #click="getQ, shuffle(options)" class="btn button next">Next</button>
</div>

Add clicks when differents div are clicked

Im working on a project and i have basically some troubles with things for my website.
This one is a bit hard for me, i have some ideas but i dont know how to do them in my javascript code.
I have 98 divs (it's a calendar where you can select everyday differents hours to book slots).
There is a Summary (kinda same thing on commercial website) which i want that it says how many slots you selected. But the problem is that i have like I said 98div so i wanna do it in one function.
On the slots you want to book, you can click on it (it selects it) and if you click on it again it deselects it.
I want that you can select as many slots that you want, and the summary shows how many you selected then you can go to next step.
Here is my code if you guys have some ideas !
function x1(e) {
var target = e.target,
count = +target.dataset.count;
target.style.backgroundColor = count === 1 ? "#707070" : 'black';
target.dataset.count = count === 1 ? 0 : 1;
target.innerHTML = count === 1 ? '' : 'réserver';
target.classList.toggle('Resatxt');
target.classList.toggle('unselectable');
}
Actually this code is for the selection of the slots (it's going background black when you clicl on it, and then back to the normal color when you deselect it).
But i think i can do what i want with this.
I thinked about incrementing +1 when we click on the div but the problem that i dont know how to figure it out is when you deselect it you have to do -1 but im a bit lost.
I tried to be clear but ik that is not really.
If you guys have some ideas, go for it.
Thanks a lot
it's nice to see that your joining the programming community. I hope I understood you correctly and made a simple and minimal example to present you how can you achieve what you want. This is just an idea, don't take it too serious and write your own logic to handle the functionality!
const divs = 98;
const list = document.querySelector("#list");
const selectedList = document.querySelector("#selectedList");
let selected = [];
let elementsAdded = 1;
const onSelectDiv = (element) => {
const elementCopy = element.cloneNode(true);
elementCopy.id += "-copy";
selected = [
...selected,
{
id: elementsAdded,
elementId: element.id
}
];
elementsAdded += 1;
selectedList.appendChild(elementCopy);
};
const onClick = (e) => {
if (e.target.className.includes("selected")) {
e.target.classList.remove("selected");
elementsAdded -= 1;
const elementToDelete = selected.findIndex(
(x) => e.target.id === x.elementId
);
selectedList.removeChild(selectedList.childNodes[elementToDelete + 1]);
selected = selected.filter((x) => x.elementId !== e.target.id);
return;
}
onSelectDiv(e.target);
e.target.className += " selected";
};
for (let i = 0; i < divs; i++) {
const div = document.createElement("div");
div.innerHTML += i;
div.className += "div";
div.id = i;
div.addEventListener("click", function (event) {
onClick(event);
});
list.appendChild(div);
}
.view {
display: flex;
flex-direction: 'column';
}
#list {
display: flex;
width: 400px;
max-width: 500px;
flex-wrap: wrap;
}
.div {
padding: 5px;
background-color: black;
cursor: pointer;
color: white;
border-radius: 10px;
margin: 10px;
}
.selected {
background-color: red;
color: white;
}
<div class="view">
<div>
<p>Elements:</p>
<div id="list">
</div>
</div>
<div>
<p>Selected:</p>
<div id="selectedList">
</div>
</div>
</div>

How to increase, decrease and reset the counter in dynamic web application

Here we have counter application to increase, decrease and reset the counter by using HTML, CSS, and JavaScript, but I can't getting to do how we can increase the counter and decrease the counter and reset the counter.
When the HTML button element with the id decreaseBtn is clicked,
If the count is odd then decrease the counter value by -1 as shown in the image.
If the count is even then decrease the counter value by -2 as shown in the image.
When the HTML button element with the id increaseBtn is clicked,
If the count is odd then increase the counter value by 10 as shown in the image.
If the count is even then increase the counter value by 5 as shown in the image.
When the HTML button element with the id resetBtn is clicked,
Set counter value as 0.
The Output image is Counter app
let counterElement = document.getElementById("counterValue");
function onIncrement() {
let previousCounterValue = counterElement.textContent;
let updatedCounterValue = parseInt(previousCounterValue) + 1;
if (updatedCounterValue > 0) {
counterElement.style.color = "black";
}
else if (updatedCounterValue < 0) {
counterElement.style.color = "black";
}
else {
counterElement.style.color = "black";
}
counterElement.textContent = updatedCounterValue;
}
function onDecrement() {
let previousCounterValue = counterElement.textContent;
let updatedCounterValue = parseInt(previousCounterValue) - 1;
if (updatedCounterValue > 0) {
counterElement.style.color = "black";
}
else if (updatedCounterValue < 0) {
counterElement.style.color = "black";
}
else {
counterElement.style.color = "black";
}
counterElement.textContent = updatedCounterValue;
}
function onReset() {
let counterValue = 0;
counterElement.textContent = counterValue;
counterElement.style.color = "black";
}
#import url("https://fonts.googleapis.com/css2?family=Bree+Serif&family=Caveat:wght#400;700&family=Lobster&family=Monoton&family=Open+Sans:ital,wght#0,400;0,700;1,400;1,700&family=Playfair+Display+SC:ital,wght#0,400;0,700;1,700&family=Playfair+Display:ital,wght#0,400;0,700;1,700&family=Roboto:ital,wght#0,400;0,700;1,400;1,700&family=Source+Sans+Pro:ital,wght#0,400;0,700;1,700&family=Work+Sans:ital,wght#0,400;0,700;1,700&display=swap");
.counter-value {
font-size: 36px;
font-weight: 900;
}
.button {
color: #ffffff;
background-color: #0967d2;
font-size: 14px;
border-width: 0;
border-radius: 4px;
padding: 10px;
}
<!DOCTYPE html>
<html>
<head> </head>
<body>
<p id="counterValue" class="counter-value">0</p>
<button id="decreaseBtn" class="button" onclick="onDecrement()">DECREASE</button>
<button id="resetBtn" class="button" onclick="onReset()">RESET</button>
<button id="increaseBtn" class="button" onclick="onIncrement()">INCREASE</button>
</body>
</html>
Expected Output is
Counter app
I am new to js so may not be best solution.
let count = 0;
const counter = document.getElementById("counterValue");
function isEven(num) {
return num % 2 ? false : true;
}
function onDecrement() {
if (isEven(count)) {
count = count - 2;
} else {
count = count - 1;
}
counter.textContent = count;
}
function onReset() {
count = 0;
counter.textContent = count;
}
function onIncrement() {
if (isEven(count)) {
count = count + 5;
} else {
count = count + 10;
}
counter.textContent = count;
}
.counter-value {
font-size: 36px;
font-weight: 900;
}
.button {
color: #ffffff;
background-color: #0967d2;
font-size: 14px;
border-width: 0;
border-radius: 4px;
padding: 10px;
}
<!DOCTYPE html>
<html>
<head> </head>
<body>
<p id="counterValue" class="counter-value">0</p>
<button id="decreaseBtn" class="button" onclick="onDecrement()">DECREASE</button>
<button id="resetBtn" class="button" onclick="onReset()">RESET</button>
<button id="increaseBtn" class="button" onclick="onIncrement()">INCREASE</button>
</body>
</html>
The other users in the comments have explained about how to check whether the count is odd or even.
I wanted to add an answer that approaches the code from a slightly different angle, and which I often find very useful.
Instead of picking up the textContent of the counter element and parsing it to a number we maintain one number variable.
We give each button a data-type attribute, and a button container one handler function that is called immeditately.
The reason for this is so we can use a technique called a closure - a function returned from another function but maintains references to the variables that were declared before it was returned.
We can intitialise count.
We return the closure.
When any button is clicked the event bubbles up the DOM and is captured by the listener in the container (this is called event delegation.
We check the data type. We update count based on the type and whether the count is odd or even.
Finally we update the counter element text.
// Cache the elements
const counterElement = document.querySelector('#counterValue');
const container = document.querySelector('#container');
// Add a listener to the container
container.addEventListener('click', handleClick(), false);
// Small function to check whether the
// count is odd or even
function isEven(count) {
return count % 2 === 0;
}
// Initialise the count variable
function handleClick(count = 0) {
// Return a new function (the closure)
// as the function that will be called when
// the buttons are clicked
return function(e) {
// Get the button type from the clicked button
const { type } = e.target.dataset;
// Now just use `switch` to update the count
switch(type) {
case 'decrease': {
if (isEven(count)) {
count = count - 2;
} else {
count = count - 1;
}
break;
}
case 'increase': {
if (isEven(count)) {
count = count + 5;
} else {
count = count + 10;
}
break;
}
default:
case 'reset': count = 0; break;
}
// Finally update the counter element
counterElement.textContent = count;
}
}
.counter-value {font-size: 36px; font-weight: 900; }
.button {color: #ffffff; background-color: #0967d2; font-size: 14px; border-width: 0; border-radius: 4px; padding: 10px;}
<p id="counterValue" class="counter-value">0</p>
<div id="container">
<button data-type="decrease" class="button">DECREASE</button>
<button data-type="reset" class="button">RESET</button>
<button data-type="increase" class="button">INCREASE</button>
</div>

Why do I have to click twice to get links in search results menu to load page?

QUESTION:
Why do I have to click twice to get links in search results menu to load page?
See here:
Type in Staff, or Blog in the filter field. You have to click on each link twice to get the page to load?
https://startech-enterprises.github.io/docs/data-integration-and-etl/branches-and-loops-local.html
I'm trying to get to this behaviour (i/e just one click):
https://learn.microsoft.com/en-us/dotnet/csharp/linq/
NOTE
The code in the link above has now been updated, based on the answers given below
CODE
JS I'm using
/**
* Search Filter
*/
"use strict";
(function searchFilter() {
eventListeners();
// Add Event Listerns
function eventListeners(){
document.getElementById('searchFilter').addEventListener('keyup', searchQuery);
document.getElementById('searchFilter').addEventListener('focusout', searchQuery);
document.getElementById('searchFilter').addEventListener('focusin', searchQuery);
};
function searchQuery(){
// Declare variables
let input, filter, ul_toc, li_toc, ul_suggestions, li_suggestion, a1, a2, a3, i, j, k, txtValue, txtValue2, txtValue3, link;
input = document.getElementById('searchFilter');
filter = input.value.toUpperCase();
ul_toc = document.getElementsByClassName("toc")[0];
li_toc = ul_toc.getElementsByClassName("none");
ul_suggestions = document.getElementsByClassName("searchFilter-suggestions")[0];
// Check whether input is empty. If so hide UL Element
if (filter === "") {
ul_suggestions.classList.add("is-hidden")
};
// Check whether input is not empty. If so show UL Element
if (filter !== "") {
ul_suggestions.classList.remove("is-hidden")
};
// Check whether input is not active. If so hide UL Element
if (input !== document.activeElement) {
setTimeout(function(){
ul_suggestions.classList.add("is-hidden");
}, 2000);
};
// Check whether input is active. If so show UL Element
if (input === document.activeElement) {
ul_suggestions.classList.remove("is-hidden")
};
// Keep emptying UL on each keyup event, or when input element is not active
ul_suggestions.innerHTML = "";
let df = new DocumentFragment();
// Run search query so long as filter is not an empty string
if(filter !== ""){
// Loop through all list items, and update document fragment for those that match the search query
for (i = 0; i < li_toc.length; i++) {
a1 = li_toc[i].getElementsByTagName("a")[0];
txtValue = a1.textContent || a1.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
// Start creating internal HTML
li_suggestion = document.createElement('li');
li_suggestion.classList.add("searchFilter-suggestion");
// Parent span element
let span = document.createElement("SPAN");
span.className = ("is-block is-size-7 has-padding-left-small has-padding-right-small");
link = document.createElement('a');
link.href = a1.href;
span.appendChild(link);
// Child 1 span element
let span2 = document.createElement("SPAN");
span2.className = ("is-block has-overflow-ellipsis-tablet");
span2.textContent = txtValue;
// Child 2 span element
let span3 = document.createElement("SPAN");
span3.className = ("is-block has-text-subtle has-overflow-ellipsis is-size-8 has-line-height-reset has-padding-bottom-extra-small");
j = 0;
let immediateParent = li_toc[i].parentElement;
let correctParent = li_toc[i].parentElement;
// Get top most level of branch --> Set as Node 1
while(true){
if (immediateParent.parentElement.classList.contains('toc')) break;
immediateParent = immediateParent.parentElement;
j++;
};
if (j == 0){
a2 = li_toc[i].getElementsByTagName("a")[0];
}
else {
k = 0;
for ( k = 0; k < j - 1; k++) {
correctParent = correctParent.parentElement;
};
a2 = previousByClass(correctParent, "treeitem");
a2 = child_by_selector(a2, "tree-expander")
}
txtValue2 = a2.textContent || a2.innerText;
txtValue2 = document.createTextNode(txtValue2);
// Insert Chevron Right --> Set as Node 2
let span4 = document.createElement("SPAN");
span4.className = ("has-padding-right-extra-small has-padding-left-extra-small");
span4.innerHTML = '&nbsp&#9002&nbsp';
span4.setAttribute("style", "font-size: 0.70rem; font-weight: bold");
// Get second-top most level of branch --> Set as Node 3
correctParent = li_toc[i].parentElement;
switch (j) {
case 0:
a3 = "";
break;
case 1:
a3 = li_toc[i].getElementsByTagName("a")[0];
default: {
k = 0;
for ( k = 0; k < j - 2; k++) {
correctParent = correctParent.parentElement;
};
a3 = previousByClass(correctParent, "treeitem");
a3 = child_by_selector(a3, "tree-expander")
}
}
if (a3 != ""){
txtValue3 = a3.textContent || a3.innerText;
txtValue3 = document.createTextNode(txtValue3);
span3.appendChild(txtValue2);
span3.appendChild(span4);
span3.appendChild(txtValue3);
} else {
span3.appendChild(txtValue2);
}
span.firstChild.appendChild(span2);
span.firstChild.appendChild(span3);
li_suggestion.appendChild(span);
df.appendChild(li_suggestion)
}
}
// Output HTML, and remove is-hidden class
ul_suggestions.appendChild(df);
}
}
})();
// WAIT TILL DOCUMENT HAS LOADED BEFORE INITIATING FUNCTIONS
document.addEventListener('DOMContentLoaded', searchFilter);
CSS I'm using:
/* Search Filter */
.filter-icon{
display: inline-block;
height:0.9rem;
width: 1.0rem;
text-transform: none;
text-align: center;
}
.searchFilter {
display: inline-block;
position: relative;
}
.searchFilter-input {
padding-right: 26px;
}
.searchFilter-suggestions {
list-style-type: none;
z-index: 1;
position: absolute;
max-height: 18rem;
min-width: 100%;
max-width: 100%;
padding: 0;
margin: 2px 0 0 !important;
cursor: default;
box-shadow: 0 1.6px 3.6px 0 rgba(0,0,0,0.132),0 .3px .9px 0 rgba(0,0,0,0.108);
border: 1px solid #e3e3e3;
background-color: white;
}
#media screen and (min-width: 768px), print {
.searchFilter-suggestions {
max-width: 500px;
}
}
.searchFilter-suggestion {
display: block;
border: 1px solid transparent;
}
.searchFilter-suggestion a {
color: rgb(23, 23, 22);
text-decoration: none;
}
.searchFilter-suggestion:hover{
background-color: #f2f2f2;;
border: 1px solid rgba(0,0,0,0);
}
.is-hidden {
display: none !important;
}
Relevant portion of HTML with UL that loads the search results:
(The search results document fragment generated by the JS gets loaded in the ul, with the class, searchFilter-suggestions)
form class = "has-margin-bottom-small" action="javascript:" role="search">
<label class="visually-hidden">Search</label>
<div class="searchFilter is-block">
<div class="control has-icons-left">
<input id="searchFilter" class="searchFilter-input input control has-icons-left is-full-width is-small" role="combobox" maxlength="100" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="Filter by title" type="text">
<span class="icon is-small is-left">
<img src="/../docs/assets/images/filter.png" class="filter-icon">
</span>
</div>
<ul class="searchFilter-suggestions is-vertically-scrollable is-hidden"></ul>
</div>
</form>
I think the best solution is to remove the focus listeners temporarily.
It should work using this:
(function searchFilter() {
let input = document.getElementById('searchFilter');
let suggestions = document.getElementsByClassName("searchFilter-suggestions")[0];
eventListeners();
// Add Event Listerns
function eventListeners() {
input.addEventListener('keyup', searchQuery);
suggestions.addEventListener("mouseenter", () => removeInputFocusListeners());
suggestions.addEventListener("mouseleave", () => addInputFocusListeners());
};
function addInputFocusListeners() {
input.addEventListener('focusout', searchQuery);
input.addEventListener('focusin', searchQuery);
}
function removeInputFocusListeners() {
input.removeEventListener('focusout', searchQuery);
input.removeEventListener('focusin', searchQuery);
}
...

Is there a way to add class to all elements

I browsed thru various similar questions, but I found nowhere real help on my problem. There are some Jquery examples, but none of them weren't applicable, since I want to do it by vanilla JavaScript only.
The Problem
I have rating widget which I want to build like every time I click on "p" element, eventListener adds class "good" on all p elements before and on the clicked one, and remove class good on all that are coming after the clicked one.
My Thoughts
I tried to select all the "p" elements and iterate over them by using a for loop to add class before and on clicked element, and to leave it unchanged or remove after, but somewhere I am doing it wrong, and it adds class only on clicked element not on all previous ones.
My Code
function rating () {
var paragraph = document.getElementsByTagName('p');
for (var i = 0; i < paragraph.length; i++) {
paragraph[i].addEventListener('click', function(e) {
e.target.className='good';
})
}
}
rating();
Result
Expected result is that every p element before the clicked one, including clicked one should be having class good after click and all the ones that are after the clicked one, should have no classes.
Actual Result: Only clicked element is having class good.
Using Array#forEach , Element#nextElementSibling and Element#previousElementSibling
General logic behind this is to loop through all the previous and next sibling elements. For each element, add or remove the class .good until there are no more siblings to handle.
const ps = document.querySelectorAll('p');
ps.forEach(p => {
p.addEventListener("click", function() {
let next = this.nextElementSibling;
let prev = this;
while(prev !== null){
prev.classList.add("good");
prev = prev.previousElementSibling;
}
while(next !== null){
next.classList.remove("good");
next = next.nextElementSibling;
}
})
})
p.good {
background-color: red;
}
p.good::after {
content: ".good"
}
p {
background-color: lightgrey;
}
<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>
Alternative:
Using Array#slice to get the previous and next group of p's.
const ps = document.querySelectorAll('p');
ps.forEach((p,i,list) => {
p.addEventListener("click", function() {
const arr = [...list];
const previous = arr.slice(0,i+1);
const next = arr.slice(i+1);
previous.forEach(pre=>{
pre.classList.add("good");
})
next.forEach(nex=>{
nex.classList.remove("good");
});
})
})
p.good {
background-color: red;
}
p.good::after {
content: ".good"
}
p {
background-color: lightgrey;
}
<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>
function setup() {
var paragraph = document.querySelectorAll("p");
for (p of paragraph) {
p.onclick = (event) => {
let index = Array.from(paragraph).indexOf(event.target);
[].forEach.call(paragraph, function(el) {
el.classList.remove("good");
});
for (let i = 0; i <= index; i++) {
paragraph[i].classList.add("good");
}
}
}
}
//Example case
document.body.innerHTML = `
<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>`;
setup();
The key ingredient is to use Node.compareDocumentPosition() to find out if an element precedes or follows another element:
var paragraphs;
function handleParagraphClick(e) {
this.classList.add('good');
paragraphs.forEach((paragraph) => {
if (paragraph === this) {
return;
}
const bitmask = this.compareDocumentPosition(paragraph);
if (bitmask & Node.DOCUMENT_POSITION_FOLLOWING) {
paragraph.classList.remove('good');
}
if (bitmask & Node.DOCUMENT_POSITION_PRECEDING) {
paragraph.classList.add('good');
}
});
}
function setup() {
paragraphs = [...document.getElementsByTagName('p')];
paragraphs.forEach((paragraph) => {
paragraph.addEventListener('click', handleParagraphClick);
});
}
setup();
#rating { display: flex; }
p { font-size: 32px; cursor: default; }
p:hover { background-color: #f0f0f0; }
.good { color: orange; }
<div id='rating'>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
<p>*</p>
</div>`

Categories