Building Custom Javascript Dropdown Menus

It's quite amazing to see how much time can speed up if you're thrown into a new environment. One month ago, I moved from Hamburg to Amsterdam. After putting my life into boxes, finding time for selftaughtjs has been challenging. Finding a place to live as well as a reliable internet connection have been higher up on my list of priorities. Now that things have calmed down a little, I'm finally able to continue this blog with more frequent updates. I got the idea for this post while working on a recent project for my portfolio. Dropdown menus are some of the basic staples of websites these days and appear in many different variations. There are a lot of libraries and frameworks such as Twitter's Bootstrap, Zurb's Foundation or jQuery UI which offer Javascript poweredcs dropdowns out of the box. Those dropdowns have proven cross-browser functionality and are highly reliable. Not surprisingly many developers rely on these external libraries to create dropdown menus in their applications or websites.

So why would we want to look into re-inventing the wheel and write our own dropdown menu? Let me quote the second book of Harry Potter here:

"Never trust anything that can think for itself if you can't see where it keeps its brain."

Understanding what happens below the hood of a framework will allow you to fix bugs, add features and modify behaviour and appearance much easier. You should never have to use a library or a framework because you can't get the job done by yourself. It should be a matter of convenience to use it, not a dependency. There is a huge debate in the Javascript community between Javascript purists arguing that people are relying too much on frameworks and those who say that not using battle-proven libraries in production is insanity. Both sides have valid arguments. However, from a pure learning perspective, writing native Javascript will without a doubt help you to become a better developer.

So, after this little prep-talk, let's get our hands dirty and write some code. Our outcome should look like this dropdown button below:

To begin, let's create an html file and add some elements to display our dropdown menu.

<div class='dropdown' id='icecream-dropdown'>  
  <div class='dropdown-button'>Please Select</div>
  <span class='triangle'>&#9660;</span>
  <ul class='dropdown-selection'>
    <li>Vanilla</li>
    <li>Chocolate</li>
    <li>Strawberry</li>
    <li>Banana</li>
  </ul>
</div>  

The only thing that might catch your eye in there is the span with the class triangle. The content is an HTML-Entity which will produce a nice downwards facing triangle (▼) for us. This often used icon on the right of a dropdown button helps the user to recognize dropdowns.

The whole thing looks a bit plain, so let's add some CSS styling for our classes. In a first step, we add some shape and color to the button.

.dropdown {
  display: inline-block;
  position: relative;
  font-size: 16px;
  font-family: "Arial";
}
.dropdown-button {
  background: #3498db;
  min-width: 100px;
  color: #fff;
  letter-spacing: 0.025rem;
  box-sizing: border-box;
  padding: 10px 30px 10px 20px;
  position: relative;
  cursor: pointer;
  transition: background .3s ease;
}
.dropdown-button:hover {
  background: #2980b9;
  transition: background .3s ease;
}
.triangle {
  font-size: 50%;
  position: absolute;
  right: 10px;
  top: -2px;
  bottom: 0;
  margin-top: auto;
  margin-bottom: auto;
  height: 5px;
  color: #fff;
}

First of all we're giving our entire dropdown the display:inline-block property to make sure it'll be usable next to labels or in menus with multiple buttons. I've added some styles to the button to make it look a bit more interesting. If you're familiar with css attributes, this shouldn't be too hard to follow. Adding position:relative to the correct elements is quite important as it'll determine the positioning and size of our dropdown list, which we'll give an absolute position. The list still needs some redesign so let's add the classes to our css sheet:

.dropdown ul {
  padding: 0;
  list-style: none;
  box-shadow: 0px 2px 6px 0 rgba(0,0,0,0.2);
  position: absolute;
  left: 0;
  margin-top: 2px;
  top: 100%;
  min-width: 90%;
}
.dropdown li {
  background: #fff;
  padding: 8px 10px 8px 15px;
  box-sizing: border-box;
  cursor: pointer;
  transition: background .2s ease;
}
.dropdown li:hover {
  background: #f6f6f6;
  transition: background .2s ease;
}

Important here is the absolute positioning of our list. We've then positioned it a 100% of the button's size from the top - placing it just below our dropdown button (with a slight margin of 2px). It's nice to add a subtle drop shadow on the menu so it'll have a higher contrast to the rest of the UI you have going on in your application / website. Other than that it's pretty straightforward. I've removed the natural style of the list by changing the list-style attribute and reduced the default margin / padding of the element.

At this point our dropdown actually starts to look like something. Below is our current state:

Now it's time to add some interactivity. At first, we will remove our dropdown list from the realm of visibility by adding visibility:hidden to our styling of ul.

.dropdown ul {
  visibility: hidden;
  padding: 0;
  list-style: none;
  box-shadow: 0px 2px 6px 0 rgba(0,0,0,0.2);
  position: absolute;
  left: 0;
  margin-top: 2px;
  top: 100%;
  min-width: 90%;
}

Remember that icecream-dropdown id we've added to the dropdown div at the very beginning? Now it's time to add an event listener in Javascript.

document.getElementById('icecream-dropdown').addEventListener('click',function(){

});

in theory, we could've also added a mouseover listener, if we wanted the dropdown button to react to a mouseover rather than a click. Anyway, let's continue by making the list visible on every click. What we want to do is check for all the children of the div with the class dropdown. If we find a child that has the class dropdown-selection, we want to add an inline style of visibility:visible, which will overwrite the hidden attribute we've set in the class.

document.getElementById('icecream-dropdown').addEventListener('click',function(){  
  //looping over all the children of our dropdown
  for (var i = 0;i<this.children.length;i++){
    //checking if one of the children is our dropdown-selection
    if (this.children[i].classList.contains('dropdown-selection')){
        //only then we add the inline style 
        this.children[i].style.visibility = 'visible';
        }
  }
});

Three takeaways here:

  • We can refer to the element to which we are listening by using this within the function.
  • It is possible access the children of an element by using element.children - the datatype is a DOM-Collection, which works similar to arrays with a few limitations.
  • We can check if an elment has a certain class by using the contains() method of the classList property like element.classLisst.contains('dropdown-selection').

Now we can open our dropdown by clicking on the button - neat. However, we need it to close again if the user is clicking anywhere else but on our dropdown button. Now this is where things get a bit more tricky. First of all we need a function on our entire browser window that will listen for clicks anywhere.

window.onclick = function(event){

}

As you see, this function receives an event from the click, that has quite a few useful properties. We can use the event.target property to see exactly on which div a user clicked.

window.onclick = function(event){  
  if (!event.target.classList.contains('dropdown-button')){
    //???
  }
}

Now this listener will fire our action whenever the user clicks anywhere but the dropdown button. If we wouldn't include this condition, we'd hide our element on every click, even on the button, making all other code useless. Now we simply need to change the visibility of our list to hidden. But how do we find our list in the DOM again? We could potentially search in the entire DOM tree or traverse the children of our div with the id icecream-dropdown. However, that'd be quite heavy on performance. What we can do instead is to create a variable that will store two pieces of data. the id of the current dropdown as well as the list element itself. We will save this data once we add the visibility changes on the first click. Our entire code at this point looks like this:

var activeDropdown = {};  
document.getElementById('icecream-dropdown').addEventListener('click',function(){  
  for (var i = 0;i<this.children.length;i++){
    if (this.children[i].classList.contains('dropdown-selection')){
        //saving the data into our object, so we can recall it easily
        activeDropdown.id = this.id;
        activeDropdown.element = this.children[i];
        this.children[i].style.visibility = 'visible';
        }
  }
});

window.onclick = function(event){  
  if (!event.target.classList.contains('dropdown-button')){
    activeDropdown.element.style.visibility = 'hidden';
  }
}

Sweet! Our dropdown is working. However, there is one thing we haven't considered yet. What if you have multiple dropdowns on your site. If you add another dropdown element to your code and add another event listener like we have done for our icecream dropdown, everything breaks. This is why our activeDropdown object also has an id property. We save the unique id of our dropdown to it and make sure that before opening the new dropdown, we close any previous ids that might be open.
It's a single line of code at the beginning of our event listener:

document.getElementById('icecream-dropdown').addEventListener('click',function(){  
  //checking if we are clicking on a new dropdown button
  if (activeDropdown.id && activeDropdown.id !== event.target.id) {
    activeDropdown.element.style.visibility = 'hidden';
  }
  for (var i = 0;i<this.children.length;i++){
    if (this.children[i].classList.contains('dropdown-selection')){
        activeDropdown.id = this.id;
        activeDropdown.element = this.children[i];
        this.children[i].style.visibility = 'visible';
        }
  }
});

Now look at both of those beautifully working dropdown buttons below.

We can add a few more features to our buttons. First of all we might want to keep the input of the user saved in the button after a selection has been made. For this we add a button property to our activeDropdown object in which we save the button element at the time of the click. Then if a LI element is clicked, we take the innerHTML value and assign it to our button.

var activeDropdown = {};  
document.getElementById('icecream-dropdown').addEventListener('click',function(){  
  if (activeDropdown.id && activeDropdown.id !== event.target.id) {
    activeDropdown.element.style.visibility = 'hidden';
  }
  //checking if a list element was clicked, changing the inner button value
  if (event.target.tagName === 'LI') {
    activeDropdown.button.innerHTML = event.target.innerHTML;
  }
  for (var i = 0;i<this.children.length;i++){
    if (this.children[i].classList.contains('dropdown-selection')){
        activeDropdown.id = this.id;
        activeDropdown.element = this.children[i];
        this.children[i].style.visibility = 'visible';
     }
    //adding the dropdown-button to our object
    else if (this.children[i].classList.contains('dropdown-button')){
      activeDropdown.button = this.children[i];
    }
  }
});

window.onclick = function(event){  
  if (!event.target.classList.contains('dropdown-button')){
    activeDropdown.element.style.visibility = 'hidden';
  }
}

Very nice. Now those button are changing their size after the first selection.. we might not want that. You can fix this by adding a single linge of CSS to our .dropdown-button class and set the min-width value to something a bit higher such as 160px. Now we can go on and on to add more features, animation and so forth. I've made some more tweaks to my buttons and these are their final versions. I'm not setting the style of visibility directly anymore but adding and removing a class that also creates the animation through css transitions:

I've also made the entire project code available on codepen:

See the Pen Custom Javascript Dropdown Buttons by Tobias (@Tbgse) on CodePen.

Despite seeming trivial, even the most simple things can take hours of work if you approach them without any helpful libraries. It's up to you if you'd rather use your own implementation or go with a more standardized. Did you know you can build dropdown menus entirely without Javascript using only CSS? The next blog post will show how we can achieve a similar button with this approach.

Further down the rabbit hole