I have a feeling this is so obvious I’ll be ashamed when (if) solved – but I just can’t make it work.
I have a html+javascript page with multiple items all needing to be shown or hidden via user-clicks.
So I have x amount of div’s like:
<div id="myDIVA" style="display:none;">blabla 1</div>
<div id="myDIVB" style="display:none;">blabla 2</div>
<div id="myDIVC" style="display:none;">blabla 3</div>
and they are hidden and/or shown by clicking div’s:
<div onClick="myClickFunctionA()">Show only ONE</div>
<div onClick="myClickFunctionB()">Show only TWO</div>
<div onClick="myClickFunctionC()">Show only THREE</div>
Simple script goes:
<script>
var a = document.getElementById('myDIVA');
var b = document.getElementById('myDIVB');
var c = document.getElementById('myDIVC');
function myClickFunctionA() {
a.style.display = 'block';
b.style.display = 'none';
c.style.display = 'none';
}
function myClickFunctionB() {
a.style.display = 'none';
b.style.display = 'block';
c.style.display = 'none';
}
</script>
What I would like to do is be able to group the vars so I could have a function like:
function myClickFunctionA() {
a.style.display = 'block';
b + c.style.display = 'none';
}
I have a lot of div’s! So this may seem silly but it would make much sense to me to be able to “group” vars for easier editing.
So I hope someone is up for an easy solution 🙂
Thanks
You can define a common class for each of the elements where click
event is expected using .querySelectorAll()
with selector ".myClickFunction"
, iterate the NodeList
using for
loop; set Element.dataset
of each element to the current index of iteration; attach click
handler to the element; again use .querySelectorAll()
with selector "[id^myDIV]"
to select all elements where id
begins with "myDIV"
; at click
event set each "myDIV"
.style
to "display:none"
; set "myDIV"
at index Element.dataset
.style
to "display:block"
.
for (var i = 0, el = document.querySelectorAll(".myClickFunction")
; i < el.length; i++) {
el[i].dataset.index = i;
el[i].onclick = function(event) {
for (var n = 0, el_ = document.querySelectorAll("[id^=myDIV]")
; n < el_.length; n++) {
el_[n].style.display = "none"
}
document.querySelectorAll("[id^=myDIV]")[event.target.dataset.index]
.style.display = "block"
}
}
<div id="myDIVA" style="display:none;">blabla 1</div>
<div id="myDIVB" style="display:none;">blabla 2</div>
<div id="myDIVC" style="display:none;">blabla 3</div>
and they are hidden and/or shown by clicking div's:
<div class="myClickFunction">Show only ONE</div>
<div class="myClickFunction">Show only TWO</div>
<div class="myClickFunction">Show only THREE</div>
I assume you don’t want to have a separate function for every div
. And you don’t need to. 🙂 You can have a single function that handles the clicks, and works with an array of divs.
First, let’s get an array of those divs:
var divs = Array.prototype.slice.call(document.querySelectorAll("[id^=myDIV]"));
(It would be better if we could give them a common class. That grabs them by the start of their IDs.)
Then, let’s have the clickable divs say which div they relate to:
<div onClick="myClickFunction('myDIVA')">Show only ONE</div>
Then, let’s have a single function that handles clicks:
function myClickFunction(id) {
divs.forEach(function(div) {
div.style.display = div.id === id ? "block" : "none";
});
}
(See compatibility notes at the end.)
However, using onxyz
-attribute-style event handlers is generally a bad idea. They require that the functions you use be globals, for one thing.
Instead, let’s hook them up dynamically, and use a data-*
attribute to say which target they relate to:
// Scoping function to avoid globals
(function() {
var divs = Array.prototype.slice.call(document.querySelectorAll(".showable"));
Array.prototype.forEach.call(document.querySelectorAll("div[data-show]"), function(dshow) {
dshow.addEventListener("click", myClickFunction, false);
});
function myClickFunction() {
var idToShow = this.getAttribute("data-show");
divs.forEach(function(div) {
div.style.display = div.id === idToShow ? "block" : "none";
});
}
})();
<div data-show="myDIVA">Show A</div>
<div data-show="myDIVB">Show B</div>
<div data-show="myDIVC">Show C</div>
<div class="showable" id="myDIVA">A</div>
<div class="showable" id="myDIVB" style="display: none">B</div>
<div class="showable" id="myDIVC" style="display: none">C</div>
Now we have no global functions, and the markup defines the relationship between the elements.
Compatibility notes:
If you have to support obsolete browsers that don’t have addEventListener
(like IE8 — and sadly many do), this answer has a cross-browser hookEvent
function you an use.
Note that if you’re supporting IE8, you’ll also have to use a for
loop rather than Array.prototype.forEach
, since IE8 doesn’t have it and you can’t reasonably shim it (because IE8 also doesn’t support adding a non-enumerable property to a JavaScript object).
Here’s a solution using addEventListener
that removes the need for onclick
attributes:
// Select the buttons and items
var buttons = Array.prototype.slice.call(document.querySelectorAll('.showhidebutton'))
var items = Array.prototype.slice.call(document.querySelectorAll('.showhideitem'))
// Add a click eventListener for each button
buttons.forEach(function(button, i) {
button.addEventListener('click', function() {
// When a button is clicked, loop through the items
items.forEach(function(item, j) {
// If this is the item that should be shown, show it
if (i === j) item.style.display = 'block'
// Otherwise, hide it
else item.style.display = 'none'
})
})
})
// This code hides all but item 1 on pageload
// can remove this code if you don't want that
items.forEach(function(item, j) {
// Arrays are zero-indexed in JS, so the first item is 0
if (i === 0) return
// Hide all the other items
else item.style.display = 'none'
})
<div class="showhideitem">blabla 1</div>
<div class="showhideitem">blabla 2</div>
<div class="showhideitem">blabla 3</div>
<div class="showhidebutton">Show only ONE</div>
<div class="showhidebutton">Show only TWO</div>
<div class="showhidebutton">Show only THREE</div>
Probably a good usecase for an array:
var elemsById = ["myDIVA","myDIVB","myDIVC"]
.map(id=>[id,document.getElementById(id)]);
So whenever we want to show one of these elements, we iterate over all of them, and hide/show them, depending on their id:
function showThisAndHideRest(showId){
elemsById.forEach(function([id,el]){
el.style.display=id===showId?"block":"none";
});
}
So you can do:
showThisAndHideRest("myDIVA");
Note: the upper uses some cool ES6 features, which are not yet supported by all browsers. And theres actually no need to store id in the array, as its already part of el (el.id), but i just wanted to use some parameter destructuring…