Javascript: Create Read More / Read Less Functionality for Blog Posts

Posted this earlier, but marked a reply as an “Answer” before realizing a limitation that would not work with my code. Reposting my question.

New to JS. I am creating a blog from scratch, and trying to implement a Read More / Read Less button. I was able to get this working on one blog post, but it will be problematic obviously if I try to add the same classes to other blog posts, because then the function won’t be sure which objects to act on.

The closest I came to getting it working was this solution provided by another user:

function readMoreFunction(buttonIndex) {
    var dots = document.getElementsByClassName("dots")[buttonIndex];
    var contentText = document.getElementsByClassName("content")[buttonIndex];
    var btnText = document.getElementsByClassName("buttonReadMore")[buttonIndex];
    
    if (dots.style.display === "none") {
      dots.style.display = "inline";
      btnText.innerHTML = "Read More";
      contentText.style.display = "none";
    } else {
      dots.style.display = "none";
      btnText.innerHTML = "Read Less";
      contentText.style.display = "inline";
    }
  }
.content{
  display: none;
}
<p><strong>Blog Title</strong></p>

<p>Here is an example of the short snippet I want to show by default. <span class="dots">...</span></p>
<span class="content">
<p>Here is the longer text I want to show after the user clicks Read More.</p>
</span>
<button onclick="readMoreFunction(0)" class="buttonReadMore">Read More</button>

<p><strong>Blog Title 2</strong></p>

<p>Here is another short snippet I want to show by default for the second blog post. <span class="dots">...</span></p>
<span class="content">
<p>Here is the longer text I want to show after the user clicks Read More on the second blog post.</p>
</span>
<button onclick="readMoreFunction(1)" class="buttonReadMore">Read More</button>

Basically, when the user clicks “Read More”, the ellipses disappear, the hidden content is now showing, and the button changes to “Read Less”. When they click “Read Less”, the ellipses will be inserted back into the shorter snippet portion, the full content is hidden again, and the button changes back to “Read More.”

The problem is, we are passing through an index number that will find the first instance of the “dots” class on the page when we pass through readMoreFunction(0), the second instance of the “dots” class when we pass through readMoreFunction(1), etc. So you’ll find that if you switch the (0) and the (1) in the HTML above, the code will no longer work properly. Clicking the “Blog 1” Read More button will try to expand “Blog 2”, because it’s looking for the second instance of the “dots” class.

This would mean that I need to update the index number being passed through on all of my blog posts every time I add a new one to the top, so that the most recent (top) post is now (0) and the second post is now (1), on through the hundreds of blog posts I may have, which is unreasonable.

How can I write a function to make the “Read More / Read Less” button run independently for all of the different blog posts? I tried to pass through this in the readMoreFunction, but couldn’t figure out how to reference a previous element with a specific id or class name (so that it would grab the most previous span tag that had the id “dots”, etc). I could only figure out how to reference the most immediate previous element with this.previousElementSibling;

Can you guys help point me in the right direction?

Answer

If you are free to edit the html markup, it is recommended to enclose each blog content in a parent element.

By searching the class name starting from the parent element, each element corresponding to the button can be uniquely identified.

function readMoreFunction(el) {
    var parent = el.closest(".wrapper")
    var dots = parent.querySelector(".dots");
    var contentText = parent.querySelector(".content");
    var btnText = parent.querySelector(".buttonReadMore");
    
    if (dots.style.display === "none") {
      dots.style.display = "inline";
      btnText.innerHTML = "Read More";
      contentText.style.display = "none";
    } else {
      dots.style.display = "none";
      btnText.innerHTML = "Read Less";
      contentText.style.display = "inline";
    }
  }
.content{
  display: none;
}
<div class="wrapper">
<p><strong>Blog Title</strong></p>

<p>Here is an example of the short snippet I want to show by default. <span class="dots">...</span></p>
<span class="content">
<p>Here is the longer text I want to show after the user clicks Read More.</p>
</span>
<button onclick="readMoreFunction(this)" class="buttonReadMore">Read More</button>
</div>

<div class="wrapper">
<p><strong>Blog Title 2</strong></p>

<p>Here is another short snippet I want to show by default for the second blog post. <span class="dots">...</span></p>
<span class="content">
<p>Here is the longer text I want to show after the user clicks Read More on the second blog post.</p>
</span>
<button onclick="readMoreFunction(this)" class="buttonReadMore">Read More</button>
</div>