3

I want to add a CSS class name to the next sibling of an element as soon as I click on it to get a simple show/hide effect

I have the following html output and I don't have element-ID's.

<div class="news-message">
  <div class="message-head"> Headline of Message 1 </div>
  <div class="message-content"> Here comes the content of message 1 </div>
</div>
<div class="news-message">
  <div class="message-head"> Headline of Message 2 </div>
  <div class="message-content"> Here comes the content of message 2 </div>
</div>
<div class="news-message">
  <div class="message-head"> Headline of Message 3 </div>
  <div class="message-content"> Here comes the content of message 3 </div>
</div>

    

Now I want to hide the message-content and just display the headline (easy to realize with display: none).

As soon as I click on the headline, I want the content of that specific message to be displayed.

So my idea was to add a CSS class "visible" to the DIV "message-content" by click on message-head and remove it by another click.

So I added a "onClick="changeClass()" to the "pm-head" Element and the following javascript

function changeClass() {
  var hidecontent = document.querySelectorAll('.message-content');
  var i;
  for (i = 0; i < hidecontent.length; i++) {
    hidecontent[i].classList.toggle('visible');      }    }

but this adds the class visible to all "message-content" divs on that page while I just need it to be added to the next sibling of the "pm-head" element on which I have clicked.

And I can´t use jquery or other frameworks/libraries on this one.

Sorry, I am new to javascript, probably an easy to answer question.

Thanks John

Sanjeev Kumar
  • 3,113
  • 5
  • 36
  • 77
John982
  • 41
  • 1
  • 1
  • 6
  • https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling – Daniel Lizik Sep 26 '16 at 12:29
  • 1
    Rather an element method https://developer.mozilla.org/en-US/docs/Web/API/NonDocumentTypeChildNode/nextElementSibling than a node method. – Teemu Sep 26 '16 at 12:33
  • thanks for the hint. I understand that I need to use "nextSibling" but how do I select the element in question? The links at developer.mozilla.org use element-ID´s as well while I only have repeating classes and no ID´s. – John982 Sep 26 '16 at 12:40

3 Answers3

7

nextSibling is the answer you seek. Without testing it myself, but this should work for you:

function changeClass(e) {
    e.target.nextSibling.classList.toggle("visible");
}
Stefan Vilbrandt
  • 282
  • 3
  • 11
  • 2
    Thanks for your help, but if I use your code I get an "this.nextSibling is undefined" error.... – John982 Sep 26 '16 at 12:39
  • i edited my answer, now it should work. Otherwise i do a naked handstand -.- – Stefan Vilbrandt Sep 26 '16 at 13:08
  • thanks, but still doesn´t work for me...but very well possible that it´s me and not your code... Found a way that i´t works... function changeClass(myElem) { myElem.nextElementSibling.classList.toggle("visible"); } – John982 Sep 26 '16 at 13:24
  • Ok, now you got me confused. Used nextSibling already a couple of times and it always worked for me. Well, at least you have found a solutions. – Stefan Vilbrandt Sep 26 '16 at 13:27
  • 5
    Just an observe: `node.nextSibling` can also return a text node, which naturally has no `classList`. When an element is needed, it's better to use `element.nextElementSibling`, which won't give you text nodes. – Teemu Sep 26 '16 at 14:40
  • Ah, thx you very much for this info. Will remember it for my next time. Seems i was just lucky until now that there was never an textnode. – Stefan Vilbrandt Sep 26 '16 at 14:48
1

You could use this code, which requires an extra class hidden to be defined, and to be added to your HTML for each of the message-content tags:

Array.from(document.querySelectorAll('.news-message'), function (elem) {
    elem.addEventListener('click', function hideContent(e) {
        e.currentTarget.querySelector('.message-content').classList.toggle('hidden');
    });
});

Array.from(document.querySelectorAll('.news-message'), function (elem) {
    elem.addEventListener('click', function hideContent(e) {
        e.currentTarget.querySelector('.message-content').classList.toggle('hidden');
    });
});
.hidden { display:none; }
<div class="news-message">
  <div class="message-head">
    Headline of Message 1
    </div>
   <div class="message-content hidden">
    Here comes the content of message 1
   </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 2
    </div>
   <div class="message-content hidden">
    Here comes the content of message 2
   </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 3
    </div>
   <div class="message-content hidden">
    Here comes the content of message 3
   </div>
</div>

Note that although you could use nextSibling, this uses another approach: it captures the click on the news-message element, which is the parent, then locates the message-content element, and finally toggles the hidden class on it.

By capturing the click event on the parent level, you can also toggle the content's display by clicking on the content itself. This can be useful. If you don't want this behaviour then change the code to this:

Array.from(document.querySelectorAll('.message-head'), function (elem) {
    elem.addEventListener('click', function hideContent(e) {
        e.currentTarget.parentNode.querySelector('.message-content')
                       .classList.toggle('hidden');
    });
});

Array.from(document.querySelectorAll('.message-head'), function (elem) {
    elem.addEventListener('click', function hideContent(e) {
        e.currentTarget.parentNode.querySelector('.message-content')
                       .classList.toggle('hidden');
    });
});
.hidden { display:none; }
<div class="news-message">
  <div class="message-head">
    Headline of Message 1
    </div>
   <div class="message-content hidden">
    Here comes the content of message 1
   </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 2
    </div>
   <div class="message-content hidden">
    Here comes the content of message 2
   </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 3
    </div>
   <div class="message-content hidden">
    Here comes the content of message 3
   </div>
</div>

This variation will only process clicks on the header, go to its parent node, and proceed from there as before.

trincot
  • 317,000
  • 35
  • 244
  • 286
1

I'd suggest, if you're able to use ES6:

// a named function to handle the toggling of visibility:
function toggleNextSibling(event) {

  // event.target is the element/node that triggered
  // the event:
  event.target
    // the nextElementSibling finds the next element
    // sibling of the clicked node:
    .nextElementSibling
    // we use Element.classList.toggle to add the
    // supplied class-name if it's not present, or
    // remove the supplied class-name if it is
    // present:
    .classList.toggle('shown');
}

// here we document.querySelectorAll() to retrieve a (non-live)
// NodeList of the elements of the document that match the
// supplied CSS selector, this NodeList is then passed to
// Array.from() to convert the Array-like NodeList into an
// Array:
Array.from(document.querySelectorAll('.message-head'))

  // as we have an Array we can then use Array.prototype.forEach()
  // to iterate over each element of the Array to perform actions
  // upon them:
  .forEach(

    // 'head' is a reference to the Array element of the Array
    // over which we're iterating, and upon each of those
    // Array elements we're then using addEventListener() to
    // bind a function (toggleNextSibling()) as the event-
    // handler for the 'click' event (but note the deliberate 
    // absence of parentheses following the function name):
    head => head.addEventListener('click', toggleNextSibling)
  );

function toggleNextSibling(event) {
  event.target.nextElementSibling.classList.toggle('shown');
}

Array.from(document.querySelectorAll('.message-head'))
  .forEach(head => head.addEventListener('click', toggleNextSibling));
.message-head {
  cursor: pointer;
  margin: 0.5em 0 0 0;
}
.news-message:first-child.message-head {
  margin-top: 0;
}
.message-content {
  display: none;
  text-indent: 1em;
  color: rebeccapurple;
}
.message-content.shown {
  display: block;
}
<div class="news-message">
  <div class="message-head">
    Headline of Message 1
  </div>
  <div class="message-content">
    Here comes the content of message 1
  </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 2
  </div>
  <div class="message-content">
    Here comes the content of message 2
  </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 3
  </div>
  <div class="message-content">
    Here comes the content of message 3
  </div>
</div>

Without ES6 the above can be translated to the following (albeit the function itself requires no changes):

// here we use Function.prototype.call(), to enable us to
// pass the NodeList to Array.prototype.slice(), which converts
// the NodeList to an Array:
Array.prototype.slice.call(document.querySelectorAll('.message-head'))

  // again, having an Array allows us to use Array methods, but
  // here we cannot use Arrow function expressions (as they were
  // not available until ES6), so instead we use a function expression:
  .forEach(function(head) {

    // 'head' refers to the current Array element of the Array
    // over which we're iterating, and we again use addEventListener
    // to bind the named function to the 'click' event:
    head.addEventListener('click', toggleNextSibling)
  });

function toggleNextSibling(event) {
  event.target.nextElementSibling.classList.toggle('shown');
}

Array.prototype.slice.call(document.querySelectorAll('.message-head'))
  .forEach(function(head) {
    head.addEventListener('click', toggleNextSibling)
  });
.message-head {
  cursor: pointer;
  margin: 0.5em 0 0 0;
}
.news-message:first-child.message-head {
  margin-top: 0;
}
.message-content {
  display: none;
  text-indent: 1em;
  color: rebeccapurple;
}
.message-content.shown {
  display: block;
}
<div class="news-message">
  <div class="message-head">
    Headline of Message 1
  </div>
  <div class="message-content">
    Here comes the content of message 1
  </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 2
  </div>
  <div class="message-content">
    Here comes the content of message 2
  </div>
</div>

<div class="news-message">
  <div class="message-head">
    Headline of Message 3
  </div>
  <div class="message-content">
    Here comes the content of message 3
  </div>
</div>
David Thomas
  • 249,100
  • 51
  • 377
  • 410