I have to make a real simple task but I cannot figure it out.
I have some cards that the user can select with a radio button. I want to higlight the selected card when the user click on the relative input radio.
I don't understand how can I select the closest class of the selected radio.
My HTML looks like this:
<div class="box">
<div class="box-content">
<input type="radio" name="box-input">
<label>Label One</label>
</div>
</div>
<div class="box">
<div class="box-content">
<input type="radio" name="box-input">
<label>Some Two</label>
</div>
</div>
And so on...
<button onclick="submit()">Submit</button>
If I do like this:
let boxes = document.querySelectAll('input');
const submit = () => {
for (let i=0;i<boxes.length;i++) {
boxes[i].checked? this.closest('.box').classList.add('selected'): console.log('nothing is selected')
}
}
It says that this.closest is undefined, and it works only if the user click on the submit button.
What I want to do is just add a class to div .box when the radio input is selected, and remove it when change the state to unselected.
I'd like also to avoid the inline HTML "onclick" if possible.
Please pure javascript only
EDIT
With the suggestion of #somethinghere I added onchange="change(this)" to each input radio and I change my script in this way:
const change = el => {
el.checked?el.closest('.box').classList.add('selected'):el.closest('.box').classList.remove('selected')
;
It works, it adds the class selected when I click on a input radio. But if I click on another input, then the class selected is not removed.
Suggestions?
Added code to change the style of closest class when input radio is selected
var radioAll = document.querySelectorAll('input');
for(var i = 0; i < radioAll.length; i++)
{
radioAll[i].onclick = function()
{
//remove selected class from all box classs
var boxElems = document.querySelectorAll(".box");
[].forEach.call(boxElems, function(el) {
el.classList.remove("selected");
});
if(this.checked)
{
this.closest('.box').classList.add('selected');
}
};
}
.selected{
background-color: coral;
}
<div class="box">
<div class="box-content">
<input type="radio" name="box-input">
<label>Label One</label>
</div>
</div>
<div class="box">
<div class="box-content">
<input type="radio" name="box-input">
<label>Some Two</label>
</div>
</div>
And so on...
<button onclick="submit()">Submit</button>
While you've already accepted an answer, I thought I'd add an alternative approach:
// get a reference to the <button> element; here you only have the one <button>,
// so document.querySelector() will suffice (as it returns either the first
// Node that matches the supplied selector or null):
let submitButton = document.querySelector('button'),
inputs = document.querySelectorAll('input');
// here we use the same Arrow function syntax, which does not - by design - get
// its own 'this' reference:
const submit = () => {
// since we have the <input> elements already we use that, along with the
// NodeList.prototype.forEach() method:
inputs.forEach(
// here 'input' is a reference to the current <input> element of the
// NodeList of <input> elements over which we're iterating.
// we use Element.closest() to find the relevant '.box' element, and
// use the Element.classList API to toggle the 'hasSelected'
// class-name based on the supplied 'switch', the 'input.checked'; if
// 'input.checked' is true the class-name is added to the '.box', if
// 'input.checked' is false the class-name is removed (if the class-name
// is already present, or not-present, when it's added or removed no
// error is thrown and it presents no problem):
(input) => input.closest('.box').classList.toggle('hasSelected', input.checked)
)
}
// using the EventTarget.addEventListener() method, in place of the obtrusive
// 'onclick' in-line event-handling; here we bind the submit() function
// (note the deliberate lack of parentheses) as the event-handler for the
// 'click' event:
submitButton.addEventListener('click', submit);
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
font-size: 1rem;
line-height: 1.5;
}
body>div {
display: flex;
justify-content: space-between;
width: 50vw;
margin: 1em auto;
}
div.box {
display: grid;
grid-template-columns: 1fr 30px;
grid-gap: 0 10px;
border: 2px solid transparent;
padding: 0.5em;
border-radius: 1em;
}
div.box.hasSelected {
border-color: limegreen;
}
div.box.hasSelected::after {
display: contents;
content: '✓';
font-weight: bold;
color: limegreen;
}
<div>
<div class="box">
<div class="box-content">
<label><input type="radio" name="box-input">
Label One</label>
</div>
</div>
<div class="box">
<div class="box-content">
<label><input type="radio" name="box-input">
Some Two</label>
</div>
</div>
<button>Submit</button>
</div>
JS Fiddle demo.
References:
Arrow functions.
Document.querySelector().
Element.classList API.
Element.closest().
EventTarget.addEventListener().
NodeList.prototype.forEach().
Related
If the "slick-initialized" div tag doesn't exist within a parent, then I want the parent ID (product recommender-recipe) to display none. Right now this is what I have set up:
HTML is set up like this:
<div id="product-recommender-recipe">
<div class="slick-initialized">
</div>
</div>
My JS so far. If length is 0, then have the parent ID display none. :
var productTemplate = document.getElementsByClassName("#product-recommender-recipe > .slick-initialized")
if (productTemplate.length === 0){
document.getElementById("product-recommender-recipe").style.display = "none";
}
Do I have this set up properly?
You can hide #product-recommender-recipe and check if .slick-initialized exists than show using just CSS.
it is working perfectly.
#product-recommender-recipe {
padding: 50px;
background-color: red;
display: none;
}
#product-recommender-recipe:has(.slick-initialized) {
display: block;
}
<!-- hidden if slick-initialized not exist -->
<div id="product-recommender-recipe">
<!-- <div class="slick-initialized"></div> -->
</div>
<br/>
<!-- visible if slick-initialized exist -->
<div id="product-recommender-recipe">
<div class="slick-initialized"></div>
</div>
You are pretty close. You have two mistakes in your implementation.
The first one is that you used getElementByClassName when in fact you are using an ID as your selector. Thus you should have used querySelector.
The second one is that you overused your selector. You have selected your parent div and placed it in a var so that you can reference it again.
Here is my implementation:
var productTemplate = document.querySelector("#product-recommender-recipe")
if (!productTemplate.querySelector('.slick-initialized')) {
productTemplate.style.display = none;
}
#product-recommender-recipe {
background: red;
width: 50px;
height: 50px;
}
<div id="product-recommender-recipe">
<div class="slick-initialized"></div>
</div>
getElementsByClassName expects a single class name – not a selector. If you want to use a selector, use querySelector or querySelectorAll. querySelector returns null if the element doesn't exists in the DOM.
const element = document.querySelector(".slick-initialized");
if(element === null) {
document.querySelector("#product-recommender-recipe").style.display = "none";
}
I am building a site and one of the components requires different text to be displayed in the same spot when you hover over different blocks. I am using jQuery to accomplish this and changing the html, however I am noticing since the text is different sizes it pushes down the div to allocate more room for the text.
Is it possible to keep the text transparent or something so the colour and html is changed at the same time, to give the illusion it is popping in?
Please see code below:
$(".stats-text-1").hover(
function() {
$(".stats-text").html(
"Our client’s monetary milestones are driven by our social tactics and digital marketing."
);
},
function() {
$(".stats-text").html(" ");
}
);
$(".stats-text-2").hover(
function() {
$(".stats-text").html(
"Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses."
);
},
function() {
$(".stats-text").html(" ");
}
);
$(".stats-text-3").hover(
function() {
$(".stats-text").html(
"Our clients currently see a minimum average of 5.4 times return on ad spend."
);
},
function() {
$(".stats-text").html(" ");
}
);
.stats-1 {
font-size: 12vw;
font-weight: bold;
color: rgba(255, 255, 255, 0.83);
}
.stats-2 {
font-size: 2vw;
font-weight: bold;
color: #f2f2f2;
}
.stats-3 {
font-size: 2vw;
color: rgba(255, 255, 255, 0.6);
}
.stats-text {
padding-top: 1rem;
font-size: 2vw;
text-align: left;
color: #fff6f4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="row">
<div class="col-1"> </div>
<div class="col-11 card-2-title">BEEN THERE, DONE THAT.</div>
<div class="card-2-title-mobile">BEEN THERE, DONE THAT.</div>
</div>
</div>
<div class="row stats-border">
<div class="col-1"> </div>
<div class="col-3 stats stats-text-1">
<h1 class="stats-1" style="text-align: center">11</h1>
<h2 class="stats-2" style="text-align: center">Million</h2>
<h3 class="stats-3" style="text-align: center">
Revenue Generated
</h3>
</div>
<div class="col-spec24"> </div>
<div class="col-3 stats stats-text-2">
<h1 class="stats-1" style="text-align: center">9</h1>
<h2 class="stats-2" style="text-align: center">Years</h2>
<h3 class="stats-3" style="text-align: center">
In The Making
</h3>
</div>
<div class="col-spec24"> </div>
<div class="col-3 stats stats-text-3">
<h1 class="stats-1" style="text-align: center">6</h1>
<h2 class="stats-2" style="text-align: center">Times</h2>
<h3 class="stats-3" style="text-align: center">
Return On Ad Spend
</h3>
</div>
<div class="col-1"> </div>
</div>
<div class="card-2-desktop" style="padding-bottom: 15vw">
<div class="row">
<div class="col-1"> </div>
<div class="col-7 stats-text" id="statsText"></div>
<div class="col-4"> </div>
</div>
</div>
The way I've adressed the problem is based on using CSS Grid to create the initial size of the element in which you're showing your messages, and also adding the messages to the HTML, rather than replacing text.
While you could – of course – establish the sizes of the elements by positioning the content off-screen to inform the necessary sizing, and then animate to those dimensions before showing the message on-screen, that's more work than feels necessary.
My suggested approach is below, with explanatory comments in the code itself:
// using the '.stats' selector to obtain a jQuery Object containing
// all of the elements with that class-name in the document,
// we then use the attr() method to set the custom data-index attribute
// for later use:
$('.stats').attr('data-index', function(i) {
return i + 1;
// rather than the hover() method we use the on() method instead to handle
// both 'mouseenter' and 'mouseleave' events, and we pass the Event Object,
// as 'evt', into the anonymous function:
}).on('mouseenter mouseleave', function(evt) {
// here we use jQuery's data() method to retrieve the value of the
// data-index custom-attribute:
let index = $(this).data('index');
// here we retrieve the .message element which has the same
// data-index attribute and attribute-value, which is also within
// a .marketing element:
$(`.marketing .message[data-index="${index}"]`)
// we then use the toggleClass() method to add, or remove,
// the 'visible' class to the relevant .message element
// depending on whether the assessment returns true or false;
// if the evt.type is exactly-equal to 'mouseenter' the
// assessment returns Boolean true, and the class is added;
// otherwise Boolean false is returned and the class is
// removed:
.toggleClass('visible', evt.type === 'mouseenter');
});
$('input').on('input', function(){
$('main').css('--textSize',`${$(this).val()}rem`)
}).change();
/* a basic CSS reset to ensure that all elements
are sized in similar ways: */
*,
::before,
::after {
box-sizing: border-box;
font-size: 1rem;
font-weight: normal;
line-height: 1.5;
margin: 0;
padding: 0;
}
/* defining CSS Grid as the layout of
the <main> element: */
main {
display: grid;
/* defining three equal-width columns, each
of one fractional-unit of the available
space: */
grid-template-columns: repeat(3, 1fr);
margin: 0.5em auto;
width: 90vw;
}
/* I removed the inline <style> attribute from the
various elements, since it made the HTML noisier
than I'd like (adjust to taste of course): */
.stats > :is(h1, h2, h3) {
text-align: center;
}
/* I assumed that the messages should be full-width,
so here I defined the .marketing element should
start in the first track and end in the last: */
.marketing {
grid-column: 1 / -1;
}
/* again, using CSS Grid for the element that holds the
marketing messages: */
.marketing > div {
display: grid;
/* defining a single named area in which the marketing
claims should appear: */
grid-template-areas: "claims";
}
.marketing > div > .message {
/* here we position all of the .message elements into
the same grid area; which allows the largest grid-item
to define the size of that grid area: */
grid-area: claims;
/* effectively hiding the elements, and centring the text: */
opacity: 0;
pointer-events: none;
text-align: center;
user-select: none;
z-index: -1;
}
/* this is the 'background' element against which the .message
will be displayed, this can be easily adjusted or the
.message elements themselves can have their own background: */
.marketing > div > .mask {
background: linear-gradient(135deg, lime, #ffaf);
grid-area: claims;
}
/* when the 'visible' class-name is added to the .message elements
this CSS promotes their visibility, by raising their opacity to
1 (fully visible), raising their z-index above the background
and re-enabling pointer events and user-selection: */
.marketing > div > .message.visible {
opacity: 1;
pointer-events: auto;
user-select: auto;
z-index: 2;
}
.message:nth-child(2) {
font-size: var(--textSize, inherit);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- this is absolutely irrelevant to the demo, but does demonstrate how the
grid size automatically adjusts to the size of the largest element -->
<label>Adjust text-size of the second <code>message</code> element to:
<input type="number"
min="0.5"
max="20"
step="0.5"
value="1" /></label>
<!-- using the <main> element as a wrapping block for the posted content; -->
<main>
<!-- I removed the 'stats-text-n' class-name, since that would seem
to be more use as an id (given its role in uniquely identifying
a specific element, and also because that makes your code
inherently non-reusable; whereas each element has a 'stats' class-
name which allows us to generalise the JavaScript -->
<div class="col-3 stats">
<h1 class="stats-1">11</h1>
<h2 class="stats-2">Million</h2>
<h3 class="stats-3">Revenue Generated</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">9</h1>
<h2 class="stats-2">Years</h2>
<h3 class="stats-3">In The Making</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">6</h1>
<h2 class="stats-2">Times</h2>
<h3 class="stats-3">Return On Ad Spend</h3>
</div>
<!-- here I added the 'marketing' class-name, since the 'card-2-desktop' seems
as though it may be a product of a framework -->
<div class="card-2-desktop marketing">
<div class="row">
<!-- these messages were taken from your jQuery code, and placed inside of
the '.row' element, along with a custom data-* attribute which indicates
which of the '.stats' elements it refers to: -->
<div class="message" data-index="1">Our client’s monetary milestones are driven by our social tactics and digital marketing.</div>
<div class="message" data-index="2">Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses.</div>
<div class="message" data-index="3">Our clients currently see a minimum average of 5.4 times return on ad spend.</div>
<!-- an element to act as the background of the other elements, this is
entirely optional and largely irrelevant -->
<div class="mask"></div>
</div>
</div>
</main>
JS Fiddle demo.
Of course, anything that can be accomplished in jQuery can also be achieved in native JavaScript; again, explanatory notes are in the comments of the code below:
// we use Array.from() to convert the NodeList returned by
// document.querySelectorAll() into an Array, in order to
// use Array methods later:
const messages = Array.from(
// here we retrieve all .message elements within a .marketing
// element:
document.querySelectorAll('.marketing .message')
),
// defining the toggle function, using Arrow syntax, and passing
// the Event Object ('evt') into the function:
toggle = (evt) => {
// we use 'currentTarget' property of the Event Object to find
// the element to which the event-handler was bound, as opposed
// to the 'target' property which simply returns the element
// upon which the event was initially fired; from that element
// we retrieve the data-index attribute-value:
let index = evt.currentTarget.dataset.index,
// here we filter the Array of .message elements to find the
// element(s) matching the the supplied filter, using an
// Arrow function to pass the current Array-element into
// the function body:
message = messages.filter(
// here we're looking to retain elements whose data-index
// attribute-value matches that of the .stats element
// upon which the event-handler was triggered:
(msg) => msg.dataset.index === index
);
// Array.prototype.filter() returns an Array, so here we use
// Array.prototype.forEach() to iterate through that Array:
message.forEach(
// here we toggle the 'visible' class-name on the retained
// .message elements, if the Event-type (evt.type) is exactly
// equal to 'mouseenter' the assessment returns Boolean true,
// and the class-name is added; otherwise Boolean false is
// returned and the class-name is removed (this generates no
// error if the class-name addition or removal would match
// the existing state):
(msg) => msg.classList.toggle('visible', evt.type === 'mouseenter')
);
};
// here we retrieve all elements matching the supplied CSS selector,
// and use NodeList.prototype.forEach() to iterate over that NoseList:
document.querySelectorAll('.stats').forEach(
// here we pass in a reference to the current Node of the NodeList
// (stat) and the index of that Node in the NodeList (i):
(stat, i) => {
// here we set the data-index attribute to be equal to the index
// plus 1 (to match the 1-based index in the HTML attributes I
// added):
stat.dataset.index = i + 1;
// and then bind the toggle() function - note the deliberate
// omission of the parentheses in the below code - as the
// event-handler for both the 'mouseenter' and 'mouseeout'
// events:
stat.addEventListener('mouseenter', toggle);
stat.addEventListener('mouseleave', toggle);
});
// again, largely irrelevant to the demo but demonstrates how the font-size
// determines the grid-area size to avoid size jumps between 'empty' and
// 'populated':
document.querySelector('input').addEventListener('input', (evt) =>
document.querySelectorAll('.marketing')
.forEach(
(el) => el.style.setProperty(
'--textSize',
`${evt.currentTarget.value}rem`)
)
);
*,
::before,
::after {
box-sizing: border-box;
font-size: 1rem;
font-weight: normal;
line-height: 1.5;
margin: 0;
padding: 0;
}
main {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0.5em auto;
width: 90vw;
}
.stats > :is(h1, h2, h3) {
text-align: center;
}
.marketing {
grid-column: 1 / -1;
}
.marketing > div {
display: grid;
grid-template-areas: "claims";
}
.marketing > div > .message {
grid-area: claims;
opacity: 0;
pointer-events: none;
text-align: center;
user-select: none;
z-index: -1;
}
.marketing > div > .mask {
background: linear-gradient(135deg, lime, #ffaf);
grid-area: claims;
}
.marketing > div > .message.visible {
opacity: 1;
pointer-events: auto;
user-select: auto;
z-index: 2;
}
.message:nth-child(2) {
font-size: var(--textSize, inherit);
}
<label>Adjust text-size of the second <code>message</code> element to:
<input type="number" min="0.5" max="20" step="0.5" value="1" /></label>
<main>
<div class="col-3 stats">
<h1 class="stats-1">11</h1>
<h2 class="stats-2">Million</h2>
<h3 class="stats-3">Revenue Generated</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">9</h1>
<h2 class="stats-2">Years</h2>
<h3 class="stats-3">In The Making</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">6</h1>
<h2 class="stats-2">Times</h2>
<h3 class="stats-3">Return On Ad Spend</h3>
</div>
<div class="card-2-desktop marketing">
<div class="row">
<div class="message" data-index="1">Our client’s monetary milestones are driven by our social tactics and digital marketing.</div>
<div class="message" data-index="2">Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses.</div>
<div class="message" data-index="3">Our clients currently see a minimum average of 5.4 times return on ad spend.</div>
<div class="mask"></div>
</div>
</div>
</main>
JS Fiddle demo.
References:
CSS:
Attribute-selectors.
:is().
JavaScript:
Arrow functions.
Array.from().
Array.prototype.filter().
document.querySelectorAll().
Element.classList API.
EventTarget.addEventListener().
HTMLElement.dataset.
NodeList.prototype.forEach().
Template literals.
jQuery:
attr().
change().
data().
on().
toggleClass().
val().
I have div question and one for each answer:
<div id="question"></div>
<div id="a"></div>
<div id="b"></div>
<div id="c"></div>
<div id="d"></div>
<div id="e"></div>
when user click in one div I'd to 'select' it adding a css background.
So, I did:
var divA = document.getElementById('a');
divA.addEventListener('click', function () {
remove();
divA.classList.add("select");
},false);
var divB = document.getElementById('b');
divB.addEventListener('click', function () {
remove();
divB.classList.add("select");
},false);
... same thing to C, D and E.
I'd like to not repeat the code for each div select. any ideas how to avoid all this divA, divB, divC, divD, divE functions and add all in one?
my remove function:
function remove(){
document.getElementById('a').classList.remove("select");
document.getElementById('b').classList.remove("select");
document.getElementById('c').classList.remove("select");
document.getElementById('d').classList.remove("select");
document.getElementById('e').classList.remove("select");
}
You need to do the following steps:
Use a same class for each option. Like I user "answer" in below code.
Then use querySelectorAll() to select all the elements in an array.
Loop through array and add event listener to each item.
In the function passed to event listener. First unselect all the elements.
Then select the clicked element.
I have added a little styling from myself.
Note: You can also remove the id from the elements the code will still work.
const answers = document.querySelectorAll('.answer');
answers.forEach(elm => {
elm.addEventListener('click', e =>{
//removing all the previous selections
answers.forEach(x => x.classList.remove('select'))
//selecting the current one
e.target.classList.add('select');
})
})
.answer{
padding:15px;
font-size: 30px;
text-align: center;
background: lightgray;
border-bottom:1px solid black;
font-family: cursive;
}
.select{
background: rgb(245, 245, 245)
}
.answer:hover{
background: rgb(245, 245, 245);
cursor:pointer;
}
<div id="question"></div>
<div class="answer" id="a">A</div>
<div class="answer" id="b">B</div>
<div class="answer" id="c">C</div>
<div class="answer" id="d">D</div>
<div class="answer" id="e">E</div>
I am developing a website which has a few filter buttons which are grouped into several groups. I am trying to find a way to set the class of one of these buttons to "filter-set" while all other buttons in the group are set to "not-set".
Each button is a DIV with a unique ID.
i have some bloated code where each button has its own function and sets the associated buttons to "not-set" but this seems inefficient and im sure there's a better way!
Bloated code example:
function setClassR(){
document.getElementById('filter_rare').className= 'filter-set';
document.getElementById('filter_common').className= 'not-set';
document.getElementById("filter_occasional").className = 'not-set';
}
function setClassC(){
document.getElementById('filter_rare').className= 'not-set';
document.getElementById('filter_common').className= 'filter-set';
document.getElementById("filter_occasional").className = 'not-set';
}
function setClassO(){
document.getElementById('filter_rare').className= 'not-set';
document.getElementById('filter_common').className= 'not-set';
document.getElementById("filter_occasional").className = 'filter-set';
}
I would like to be able to have a function for each group of filters which when run using an onClick=function() sets the clicked button to "filter-set" and all others to "not-set"
I have tried the following code but it doesnt appear to run:
function setClassSeas(rareClass, commonClass, occClass) {
setClass("filter_rare", rareClass);
setClass("filter_common", commonClass);
setClass("filter_occ", occClass);
}
function setClass(IDName, displayValue) {
var items = document.getElementById(IDName);
for (var i=0; i < items.length; i++) {
items[i].className = (displayValue? "filter-set" : "not-set");
}
}
UPDATE///
HTML Code for the Divs acting as buttons:
<div id="filter_rare" title="Rare"
class="not-set"
onclick="chosenFrequency('frequency=Rare'); setClassR();"></div>
<div id="filter_common" title="Common"
class="not-set"
onclick="chosenFrequency('frequency=Common'); setClassC();"></div>
<div id="filter_occasional" title="Occasional"
class="not-set"
onclick="chosenFrequency('frequency=Occasional'); setClassO();"></div>
If every button has a class, say filter-button, then you can address all buttons at once.
In modern development you should attach an event handler instead of using inline onclick handlers.
With all buttons having a common class you can find them all at once. I'm changing your buttons to look like this, adding the "filter-button" class and removing the onclick handler:
<div id="filter_rare" title="Rare"
class="filter-button not-set">Rare</div>
(I've put text in the div just to simplify this demonstration)
Now collect all the filter buttons:
let filters = document.querySelectorAll('div.filter-button');
This gets you a NodeList of elements (kind of like an Array but not one) You'll want to attach an onclick event handler to each of the buttons. To do this you can use the NodeList.forEach() call.
filters.forEach(node => node.addEventListener('click', someFunction));
In the function that gets called when you click a button, you want to clear any filter-set class that's currently set, put back the original not-set class, then set the filter-set class only on the button that was clicked. This will look something like this:
function someFunction(event) {
// again, use forEach to do the same thing to each filter button
filters.forEach( function(node) {
node.classList.remove('filter-set');
node.classList.add('not-set');
} );
// now add the 'filter-set' class on the button that was clicked
event.target.classList.add('filter-set');
}
The good thing about using classList instead of just doing className="something" is that classList can add/remove classes while leaving other classes alone; doing className="something" wipes out all the classes that are present and replaces them with "something".
Putting that all together, and using an anonymous function instead of named function gives this snippet:
let filters = document.querySelectorAll('div.filter-button');
filters.forEach(node => node.addEventListener('click',
function(event) {
console.log(event.target);
filters.forEach(function(node) {
node.classList.remove('filter-set');
node.classList.add('not-set');
});
event.target.classList.add('filter-set');
}));
/* Make these look like buttons; put a green border on them */
.filter-button {
min-height: 2ex;
max-width: 12em;
padding: .25em;
margin: .7em .3em;
background-color: lightgreen;
border: 2px solid green;
border-radius: 4px;
}
/* use a Red border on any button that has "filter-set" */
.filter-button.filter-set {
border: 2px solid red;
}
/* limit the height of the stack-snippet console */
div.as-console-wrapper {
max-height: 2.5em;
}
<div id="filter_rare" title="Rare"
class="filter-button not-set">Rare</div>
<div id="filter_common" title="Common"
class="filter-button not-set">Common</div>
<div id="filter_occasional" title="Occasional"
class="filter-button not-set">Occasional</div>
Using the class not-set is really redundant — you could just have no extra class on buttons by default and it would simplify things a little. Buttons would have the class(es) filter-button or filter-button filter-set.
Change your setClass function according to this. Hope it will work. document.getElementById() function will always return a single element (not a list of elements). Even if you have multiple elements having the same ID this function will always return the first element having the given ID. Do not forget to call your setClassSeas() function from html.
function setClassSeas(rareClass, commonClass, occClass) {
setClass("filter_rare", rareClass);
setClass("filter_common", commonClass);
setClass("filter_occ", occClass);
}
function setClass(IDName, displayValue) {
var item = document.getElementById(IDName);
item.className = displayValue ? "filter-set" : "not-set";
}
<div id="filter_rare" title="Rare" class="not-set"
onclick="chosenFrequency('frequency=Rare'); setClassSeas(true, false, false);"></div>
<div id="filter_common" title="Common" class="not-set"
onclick="chosenFrequency('frequency=Common'); setClassSeas(false, true, false);"></div>
<div id="filter_occasional" title="Occasional" class="not-set"
onclick="chosenFrequency('frequency=Occasional'); setClassSeas(false, false, true);"></div>
Here is another (as in "alternative") way to do it (but with jQuery, oh no!)
$('body').click(function(e) {
let $clicked = $(e.target);
console.log("Clicked " + $clicked.attr('id'))
if ($clicked.hasClass('filter')) {
let $filters = $clicked.closest('.filter-group').find('.filter');
let unset = $clicked.hasClass('set');
$filters.toggleClass('not-set', true);
$filters.toggleClass('set', false);
if (!unset) {
$clicked.toggleClass('not-set', false);
$clicked.toggleClass('set', true);
}
}
})
button.filter.not-set {
background: white;
}
button.filter.set {
color: white;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="filter-group">
<button id="filter_rare" class="filter not-set">
filter_rare
</button>
<button id="filter_common" class="filter not-set">
filter_common
</button>
<button id="filter_occasional" class="filter not-set">
filter_occasional
</button>
</div>
<div class="filter-group">
<button id="filter_one" class="filter not-set">
filter_one
</button>
<button id="filter_two" class="filter not-set">
filter_two
</button>
<button id="filter_three" class="filter not-set">
filter_three
</button>
</div>
i have an input Element that Get Name of Helper but Two Element has same id
it is incorrect for Edit this problem,
i want to Add Parent Div Id to Child Input.how can i Append ParentId to ChildId
<div class="wrapper-merge-input">
<div id="Stb" class="col-md-6 ">
<label class="control-label">StartDate</label>
#page.Html.Raw(fromHtml)
</div>
<div id="Edb" class="col-md-6 ">
<label class="control-label">EndDate</label>
#page.Html.Raw(toHtml)
</div>
</div>
</div>
*And *
<input type="text" dir="ltr" class="form-control"
name="#name" id="#name"
value="#value"
onclick="DatePicker.Show(this,'#today');" />
for example #name+Edb
The code below shows how you can use .attr("id") to get an element's id. In order to travel up one level to the parent DOM element you should use .parent().
You can also use .attr("id", "new-id") to set an attribute value. This can take strings or variables (as in the code below).
By placing the update code into a function you can call it on page load, after a click or any other event. I have made it run after a click of the button so you can see the id change.
I added some basic styling to make the demo a bit nicer to use.
Let me know if you wanted something else.
WARNING you will likely want to make the updateID() function more specific, so it does not act on every input on the page.
// Click event for button to demonstrate change
$("#startUpdate").click(function() {
updateID();
});
function updateID() {
// Cycle through each input - WARNING - you will want to make this more selective
$("input").each(function() {
// Update id of input from it's own name and it's immediate parent's id
$(this).attr("id", $(this).attr("name") + "-" + $(this).parent().attr("id"));
// Print message to console to demonstrate id
console.log("new id = " + $(this).attr("id"));
});
}
.wrapper-merge-input {
border: 1px solid black;
border-radius: 5px;
padding: 8px;
margin: 20px;
}
#Edb {
padding-top: 10px;
}
#startUpdate {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper-merge-input">
<div id="Stb" class="col-md-6 ">
<label class="control-label">StartDate</label>
<input name="example1" id="example1" value="example1" onclick="DatePicker.Show(this,'#today');">
</div>
<div id="Edb" class="col-md-6 ">
<label class="control-label">EndDate</label>
<input name="example2" id="example2" value="example1" onclick="DatePicker.Show(this,'#today');">
</div>
</div>
<button id="startUpdate">Update IDs</button>