Creating a generic function to show or hide elements in JavaScript

Throughout the development process of a new website it can be easy to duplicate code that performs the exact same, or very similar functionality time after time. The reasons why aren’t the point of this article, instead here’s one solution to one such piece of functionality where it’s easy to find yourself duplicating code that fundamentally achieves the same goal.

Note: The examples below largely utilises jQuery, however this solution can be converted to vanilla JavaScript.

The Problem

Whether it’s drop down menus or tabbed content, a lot of websites show content that is hidden on initial load when the user clicks on a link or button. Though this can be achieved solely in CSS (more on this later in the article), in most cases JavaScript is often relied upon to add such functionality in a more user friendly and visually appealing way.

Here’s a basic example…

<a href="#" class="menu-toggle">Menu</a>
<nav class="the-menu">Links Here</nav>

<script>
$('.menu-toggle').click(function(){
    $('.the-menu').show();
});
</script>

Simple, right? But as soon as we add non-generic class names the likelihood that this can be reused is limited. Not only this but the show() function is a rather basic way of revealing hidden content.

A generic show/hide function

Instead of relying on specific class names or even basic jQuery functions to show content we instead utilise generic data-* attributes as well as CSS animation to ensure smooth transitions.

The HTML

<a href="#menu" class="menu-toggle" data-showhide>Menu</a>

<nav class="the-navigation" id="menu" data-docclose>
    Navigation links go here
</nav>

The JavaScript/jQuery

// 1. Rather than binding directly to the click action of the links, we use the on function so that 
// even elements injected via JavaScript can use the same code without rebinding the event.
// 2. You may choose to use a class, but we've adopted the use of data-* attributes to indicate 
// what link should open another element on the page 

$('body').on('click','[data-showhide]',function(ev){

    ev.preventDefault(); // Stop the link from completing it's default function
    ev.stopPropagation(); // Stop the element's parent containers events firing

    // Get the element to show/hide based on the href attribute of the clicked element
    var target = $($(this).attr('href'));

    // close (clicking element that opens target)
    // by using CSS to animate the hidden element, we determine if it's visible based on the 
    // existence of the slide-down class
    if(target.hasClass('slide-down')){
      hideTarget(target,true);

    // open/slide-down
    }else{
      hideAll(ev); // hide all elements opened by JS
      target.addClass('slide-down'); // adding the slide-down class triggers CSS animation to open
    }

  });


  // hide/close single object
  function hideTarget(obj,slideup){

    var animation_time = 500;

    // slide up open object
    if(slideup){

      $(obj).addClass('slide-up'); // adding a slide-up class triggers an alternative CSS animation
      // using setTimeOut ensures we remove the slide-up class only after the animation is completed, 
      // therefore its important the animation_time variable matches the length of the animation 
      // in the CSS in milliseconds
      setTimeout(function(){
        $(obj).removeClass('slide-up');
      },animation_time);

    }

    // remove the slide-down class as it's no longer needed and would only cause issues if the 
    // user clicks the link again
    $(obj).removeClass('slide-down');

  }


  // hide/close all JavaScript opened elements
  // we use data-* attributes again to determine any elements that have been opened with 
  // JavaScript that we'd expect to be hidden if any other similar element is opened in the same way
  function hideAll(ev){

    // get all element defined to close on document click
    $('[data-docclose]').each(function(){

      // if the original clicked link isn't this element or a child of this element
      if(!$(this).is(ev.target) && $(this).has(ev.target).length === 0){

        // if uses CSS animation
        if($(this).hasClass('slide-down')){
          hideTarget(this);

        // else just hide
        }else if($(this).is(':visible') && $(this).css('max-height') != '0px'){ // i.e. doesn't use slide-down
          $(this).hide();
        }

      }

    });
  }


  // if anywhere external to the elements revealed with JavaScript are clicked, hide everything 
  // (remember it checks to see if the click is on or within itself)
  $(document).click(function(ev){
    hideAll(ev);
  });

The CSS/SCSS

.slide-down    {animation:slideDown 1.5s forwards;}
.slide-up      {animation:slideUp 0.5s forwards;}

@keyframes slideDown{
    from {overflow:hidden;}
    to   {max-height:800px; overflow:auto;}
}

@keyframes slideUp{
    from {max-height:800px; overflow:auto;} // so long as your container doesn't need to be larger than max-height you want, any number is suitable here
    to   {max-height:0; overflow:hidden;}
}

.the-navigation   {max-height:0; overflow-x:auto;
  &:target        {display:block; max-height:800px;} // ensures this can be opened without JavaScript
}

Progressive Enhancement

As you may also notice, from the CSS, that we include code so that the link will display the hidden element without JavaScript at all. We do so through the use of the :target pseudo class. With so many websites relying on JavaScript and a number of websites displaying only a blank page if JavaScript is disabled or hasn’t loaded it’s important for us that our websites adopt Progressive Enhancement so that basic functionality is available to as many users as possible.

Over to you…

In some cases having a reusable function like this might be overkill. You’re unlikely to need such a function if you’re only likely to use it once or twice, but certainly as you start dealing with larger websites it’s important to try to minimise the amount of code you write and be smart about writing code that works in a wider set of circumstances. Maybe you’ll use our version, or remix it to your specific needs/standards/likes, but in reality we just hope it helps you optimise your JavasScript on future projects.


We'd love to hear from you!

If you think Bronco has the skills to take your business forward then what are you waiting for?

Get in Touch Today!

Discussion

Add a Comment

Get in touch