Search code examples
javascripthtmlfunctionelement

Show or hide multiple forms with javascript


I am trying to create a piece of javascript code that will only hide and show each form on the page that corresponds with its id property. So imagine having 4 buttons visible on the page and the form elements are hidden by default, once a single button with its matching id to the form is clicked it will only toggle that form. Here is the code i have tried

function _(x) {
  return document.getElementById(x);
}

const catBtn = _("catBtn");
const blogBtn = _("blogBtn");
const videoBtn = _("VideoBtn");
const prodBtn = _("prodBtn");

const catForm = _("catForm");
const blogForm = _("blogForm");
const videoForm = _("VideoForm");
const prodForm = _("prodForm");

document.addEventListener('click', function(e) {
  if (e.target.id === catBtn) {
    // catForm.classList.toggle("showHide");
    console.log("you clicked the cat btn");
  } else if (e.target.id === blogBtn) {
    // blogForm.classList.toggle("showHide");
    console.log("you clicked the blog btn");
  } else if (e.target.id === videoBtn) {
    // videoForm.classList.toggle("showHide");
    console.log("you clicked the video btn");
  } else if (e.target.id === prodForm) {
    // prodForm.classList.toggle("showHide");
    console.log("you clicked the product btn");
  }
})
<div class="new__btn">
  <button id="catBtn">New Category</button>
</div>
<div class="new__btn">
  <button id="blogBtn">New Blog</button>
</div>
<div class="new__btn">
  <button id="videoBtn">New Video</button>
</div>
<div class="new__btn">
  <button id="prodBtn">New Product</button>
</div>

<div id="catForm">
  Hide or show this div for the categories
  <div class="panel">
    <div class="panel__head">Panel Head</div>
    <div class="panel__body">Panel Body</div>
    <div class="panel__footer">Panel Footer</div>
  </div>
</div>

<div id="blogForm">
  Hide or show this div for the blogs
  <div class="panel">
    <div class="panel__head">Panel Head</div>
    <div class="panel__body">Panel Body</div>
    <div class="panel__footer">Panel Footer</div>
  </div>
</div>

<div>
  <div id="videoForm">
    Hide or show this div for the videos
    <div class="panel">
      <div class="panel__head">Panel Head</div>
      <div class="panel__body">Panel Body</div>
      <div class="panel__footer">Panel Footer</div>
    </div>
  </div>

  <div id="prodForm">
    Hide or show this div for the products
    <div class="panel">
      <div class="panel__head">Panel Head</div>
      <div class="panel__body">Panel Body</div>
      <div class="panel__footer">Panel Footer</div>
    </div>
  </div>


Solution

  • If you wrap your buttons and forms in containing elements, and add data attributes to both the buttons and forms you can use event delegation. This allows you to attach one listener to the buttons container which will catch events from its child elements as they "bubble up" the DOM.

    The handler can then check that it was a button that was clicked, extract its data id (which will correspond to an identical data id on a form), and use that id in a selector to target that form.

    Then it's just a matter of using CSS to toggle the "show" class.

    As you can see from the example you can use the same method to toggle an active class to the clicked button too.

    // Cache the forms and buttons containers
    const forms = document.querySelector('.forms');
    const buttons = document.querySelector('.buttons');
    
    // Add one listener to the buttons container
    buttons.addEventListener('click', handleButton);
    
    // Utility helper to remove classes from a set of elements
    function removeClasses(context, selector, classname) {
      context.querySelectorAll(selector).forEach(el => {
        el.classList.remove(classname);
      });
    }
    
    function handleButton(e) {
      
      // If the clicked element that the listener catches
      // is a button
      if (e.target.matches('button')) {
        
        // Destructure its id from its dataset
        const { id } = e.target.dataset;
    
        // Create a selector using a template string which
        // will find a form with a matching data-id attribute
        const selector = `.form[data-id="${id}"]`;
    
        // Find the corresponding form in the forms container
        const form = forms.querySelector(selector);
    
        // If the button is active, remove the active
        // class, and remove the show class from the form
        if (e.target.classList.contains('active')) {
          e.target.classList.remove('active');
          form.classList.remove('show');
    
        // Otherwise remove all the active classes from the buttons,
        // and all the show classes from the forms, and then add the
        // active class to the button, and the show class to the
        // selected form.
        } else {
          removeClasses(buttons, 'button', 'active');
          removeClasses(forms, '.form', 'show');
          e.target.classList.add('active');
          form.classList.add('show');
        }
    
      }
    
    }
    .forms { margin-top: 1em; }
    .form { display: none; border: 1px solid lightgray; padding: 0.5em; }
    .form { margin-top: 0.5em; }
    .show { display: block; }
    button:hover { cursor: pointer; }
    button:hover:not(.active) { background-color: #fffff0; }
    .active { background-color: #ffff00; }
    <div class="buttons">
      <button type="button" data-id="cat">New Category</button>
      <button type="button" data-id="blog">New Blog</button>
      <button type="button" data-id="video">New Video</button>
      <button type="button" data-id="prod">New Product</button>
    </div>
    
    <section class="forms">
      <div class="form" data-id="cat">
        Hide or show this div for the categories
        <div class="panel">
          <div class="panel__head">Panel Head</div>
          <div class="panel__body">Panel Body</div>
          <div class="panel__footer">Panel Footer</div>
        </div>
      </div>
      <div class="form" data-id="blog">
        Hide or show this div for the blogs
        <div class="panel">
          <div class="panel__head">Panel Head</div>
          <div class="panel__body">Panel Body</div>
          <div class="panel__footer">Panel Footer</div>
        </div>
      </div>
      <div>
        <div class="form" data-id="video">
          Hide or show this div for the videos
          <div class="panel">
            <div class="panel__head">Panel Head</div>
            <div class="panel__body">Panel Body</div>
            <div class="panel__footer">Panel Footer</div>
          </div>
        </div>
      </div>
      <div class="form" data-id="prod">
        Hide or show this div for the products
        <div class="panel">
          <div class="panel__head">Panel Head</div>
          <div class="panel__body">Panel Body</div>
          <div class="panel__footer">Panel Footer</div>
        </div>
      </div>
    </section>

    Additional documenation