2

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

morganF
  • 117
  • 9
  • 2
    Darn near upvoted for the first line alone. So often people seem to full of themselves, nice to see someone with humility. – T.J. Crowder Jul 29 '17 at 14:05

4 Answers4

1

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...

Run

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Not sure where it goes wrong but when I try to run this code I get no show. I assume I should do;
    Show NEW VERSION
    ?? ...(((I am testing this on Firefox 52.2.1 (32-bit) and Chrome 49.0.2623.112 - both not the most recent)))
    – morganF Jul 29 '17 at 14:44
  • @morganF Oh no! Actually its because "myDivA" !== "myDIVA" ... Im sorry for that :/ – Jonas Wilms Jul 29 '17 at 14:51
  • Yeah I just noticed. Reading the small print :-) – morganF Jul 29 '17 at 14:52
  • So now it works - showing one, hiding all others. Sweet! But is it possible to call more than one to show? Like: show 1 and 3 and hide the rest? – morganF Jul 29 '17 at 14:55
  • @t.j.crowder cause some genious made the *text tools* hide on touch devices... ( im writing all markup manually :/ ) – Jonas Wilms Jul 29 '17 at 14:56
  • @morganF shure. Pass an array of elements to show, then use toShowArray.includes(id) instead of idShow===id – Jonas Wilms Jul 29 '17 at 14:57
  • Sorry. That's above my head. Not sure how much of your code I should reuse - and how... The following doesn't work: var groupArray = ['myDIVA','myDIVB'].map(id=>[id,document.getElementById(id)]); function showTwoAndHideRest(showId){ groupArray.forEach(function([id,el]){ el.style.display=toShowArray.includes(id)?'block':'none'; }); } And my clicker:
    Show Two and Hide rest
    – morganF Jul 29 '17 at 15:23
  • @Jonas OK. I'll post a new question being more specific - using your model as basis :-) – morganF Jul 29 '17 at 16:27
1

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).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • @Jonasw: Yeah, just caught that adding a live example with proper event handling. :-) Thanks. – T.J. Crowder Jul 29 '17 at 14:14
  • And couldnt you leave away the nodeList to Array cconversion? – Jonas Wilms Jul 29 '17 at 14:20
  • @Jonasw: I don't see any reason to. If we're going to use it like an array, making an array once seems reasonable. Otherwise we have to use `.call` every time we `forEach` it. – T.J. Crowder Jul 29 '17 at 14:23
  • @Jonasw: Would need shimming on all versions of IE, including IE11. (But then, that's not hard...) But I'm really glad it's finally been added to the standard. – T.J. Crowder Jul 29 '17 at 14:34
0

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>
RyanZim
  • 6,609
  • 1
  • 27
  • 43
  • querySelectorAll returns a NodeList, which already has a forEach method... – Jonas Wilms Jul 29 '17 at 14:19
  • 1
    There is a non-standard `forEach`, but it's not supported in IE: https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach – RyanZim Jul 29 '17 at 14:27
  • @RyanZim Actually, if you look at the end of that article, it cites standards. Nice to see they've added that to the standard! And one could easily shim it on IE8+. But yeah, the minor detail that **no** version of IE supports it (what with having been released before it was added) is an issue that shouldn't be left out when recommending it (`@` Jonas). – T.J. Crowder Jul 29 '17 at 14:32
  • @t.j. im not yet coding for the real world, but rather doing some small projects. So i dont care on how many devices the stuff i write runs, but maybe i should chamge my scope of view somewhen... – Jonas Wilms Jul 29 '17 at 14:36
  • 1
    @Jonasw: Definitely when recommending what to use to others, yeah. :-) – T.J. Crowder Jul 29 '17 at 14:37
  • Can I ask if this solution is only able to show one and hide the rest? I would like to be able to do: show#1,4,8... and hide#2,3,5,6,7,9.... That's why I was hoping for a simpler grouping thing... – morganF Jul 29 '17 at 14:49
  • 1
    @morganF As it's currently written, yes, it only shows one. I'm not sure exactly how you want multi-show to work. Perhaps post a new question detailing your requirements? – RyanZim Jul 29 '17 at 14:52
0

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>
guest271314
  • 1
  • 15
  • 104
  • 177