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.
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.
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.
<a href="#menu" class="menu-toggle" data-showhide>Menu</a>
<nav class="the-navigation" id="menu" data-docclose>
Navigation links go here
</nav>
// 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);
});
.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
}
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.
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.
Like what you’ve read, then why not tell others about it... they might enjoy it too
If you think Bronco has the skills to take your business forward then what are you waiting for?
Get in Touch Today!
Discussion