Home > Mobile >  Multiple read more / read less sections are not working on a page
Multiple read more / read less sections are not working on a page

Time:12-09

I have multiple read more / read less sections for a page. If I use one read more read / less section on the page, that is working fine, if I use multiple sections on the page then only one section is working, others are not working. I need multiple sections to work on the same page.

const content = document.querySelector(".content-inner");
const contentFull = document.querySelector(".content-full");
const more = document.querySelector(".read-more");
let open = false;

if (more) {
  more.addEventListener("click", (e) => {
    if (open) {
      content.removeAttribute("style");
      e.target.innerText = "click here";
      open = false;
    } else {
      content.style.maxHeight = `${contentFull.clientHeight}px`;
      e.target.innerText = "read less";
      open = true;
    }
  });
}
.read-more {
  display: inline-block;
  margin-top: 10px;
  font-weight: 400;
  cursor: pointer;
  font-size: 14px;
}
<div >
  <div >
    <div >
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    </div>
  </div>
  <span >click here</span>
</div>

CodePudding user response:

This is a task to do with event delegation. Click events (like the most of the events) are bubbling up to document from the clicked element, and you can detect events on their way up.

The example below shows you how to utilize event delegation. The code is reusable, it can be used on any page with almost any element structure, you can also keep your own class names for the elements, they are passed to the function. This snippet can also be used on dynamic pages, it works without need to make any changes to JS if you add or remove expandable elements to/from the containers.

function toggleContent(options) {
  const {container, expandable, expandee, triggerer, autoClose} = options,
    contents = document.querySelectorAll(container),
    buttonText = ['Read more', 'Read less'];
  let current = null; // Keeps book of the currently open expandee

  function toggle(e) {
    const button = e.target;
    if (!button.matches(triggerer)) {return;} // Quit, an irrelevant element clicked 
    const togglee = button.closest(expandable).querySelector(expandee);
    if (!togglee) {return;} // Quit, the element to expand was not found
    if (autoClose && current && button !== current) {
      // If autoClose is on, closes the current expandee
      toggle({target: current});
    }
    const state =  togglee.classList.toggle('visible');
    button.textContent = buttonText[state];
    current = state ? button : null;
  }

  // Add click listeners to all elements containing expandables
  contents.forEach(cont => cont.addEventListener('click', toggle));
}

// Activate ContentToggler
toggleContent({
  container: '.expandables-container', // Selector for the elements containing expandable elements
  expandable: '.expandable',   // Selector for expandable elements
  expandee: '.expandee',       // Selector for the elements to expand
  triggerer: '.toggle-button', // Selector for the element triggering expansion
  autoClose: true              // Indicates whether the expanded element is closed when a new element is expanded (optional)
});
.expandable {
  border: 1px solid #000;
  margin-bottom: 1em;
}

.expandable .expandee {
  display: none;
}

.expandee.visible {
  display: block;
}
<div >
  <div >
    <p>Always visible content.</p>
    <div >
      <p>More content.</p>
    </div>
    <button >Read more</button>
  </div>
  <div >
    <p>Toggle button can be placed above the expandee element.</p>
    <button >Read more</button>
    <div >
      <p>More content.</p>
    </div>
  </div>

  <p>Elements can be placed between expandables.</p>

  <div >
    <p>Always visible content.</p>
    <div >
      <p>More content.</p>
    </div>
    <p>Elements can be placed between expandable and toggle button.</p>
    <button >Read more</button>
  </div>
  <div >
    <p>The only requirement is, that the toggle button and expandee are descendants of expandable.</p>
    <div >
      <p>And the button must not be placed inside expandee.</p>
    </div>
    <button >Read more</button>
  </div>
</div>

The idea is, that all the expandables are wrapped in an element. The click listener is attached to that wrapper, and only a single click listener per wrapper is needed. You can also add multiple wrappers to the page.

The "widget" is activated by calling toggleContent function. Selectors for the wrappers, the expandable elements, the expandee elements and the toggle buttons are passed in an object. The fifth parameter controls the automatic closing, if you want to keep only a single expanded element visible at the time, pass true, when passing false, toggle buttons will work independently from each other.

You can test the code at jsFiddle. To see how flexible the snippet is, you can check also this jsFiddle, it uses the same "widget", but with very different markup (a minor change to the widget is, that you can define the texts of the buttons in the options object too).

If you need more control over the toggleable content, I'd recommend OOP based approach. This OOP example wraps the code in the example in this answer in a class, and provides some usecases of how to control the content. A small change is, that you don't have to pass the selector for the expandee, it's defined directly in the stylesheet. When you'll get wind of how OOP works (or you already know), you can easily add methods to the class, ex. for hiding/showing all the content etc.

CodePudding user response:

You need to select the relative selection to perform this operation as the class are repetitive. Here the toggleReadMore function will attach to every read more class and perform the height manipulation action on the content inner class.

var prevOpened = null;
function toggleReadMore(e) {
  if(prevOpened !== null && e.target !== prevOpened){
     prevOpened.click();
  }
  // get the current section parent. This will make the selection on other class items easier. 
  var contentSection = e.target.closest('.content');
  var contentInner = contentSection.querySelector('.content-inner');

  if (e.target.textContent === "read less") {
    contentInner.removeAttribute("style");
    e.target.innerText = "click here";
    prevClicked = null;
    return;
  }
  contentInner.style.maxHeight = `${contentSection.querySelector('.content-full').clientHeight}px`;
  e.target.innerText = "read less";
  prevOpened = e.target;
}

var readMoreSections = document.querySelectorAll(".read-more");
readMoreSections.forEach(function(sections) {
  sections.addEventListener("click", toggleReadMore);
})
.read-more {
  display: inline-block;
  margin-top: 10px;
  font-weight: 400;
  cursor: pointer;
  font-size: 14px;
}

.content-inner {
  max-height: 20px;
  overflow: hidden;
}
<div >
  <div >
    <div >
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    </div>
  </div>
  <span >click here</span>
</div>


<div >
  <div >
    <div >
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
    </div>
  </div>
  <span >click here</span>
</div>

Edit: This has been updated to open only latest clicked item.

  • Related