Problem
Is there a way to simplify this JavaScript function any further? Having to input 4 variables just for a drop-down menu seems a bit ridiculous, and it is hard to keep track of the variables when I want to have more than 1 button.
<!DOCTYPE html>
<html>
<head>
<style>
.dropbtn:hover, .dropbtn:focus {
background-color: #3e8e41;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
}
.dropdown-content a {
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown a:hover {background-color: #f1f1f1}
.show {display:block;}
</style>
</head>
<body>
<h2>Clickable Dropdown</h2>
<p>Click on the button to open the dropdown menu.</p>
<div class="dropdown">
<button onclick="myFunction('dropdown-content','myDropdown','show','.dropbtn')" class="dropbtn">Dropdown</button>
<div id="myDropdown" class="dropdown-content">
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</div>
</div>
<script>
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction(dropDownClass,dropDownId,show,dropbtnClass) {
var dropdowns = document.getElementsByClassName(dropDownClass);
var i;
var openDropdown = dropdowns[i];
document.getElementById(dropDownId).classList.toggle(show);
// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches(dropbtnClass)) {
for (i = 0; i < dropdowns.length; i++) {
if (openDropdown.classList.contains(show)) {
openDropdown.classList.remove(show);
}
}
}
}
}
</script>
</body>
</html>
Solution
First, your code will be easier to read if you separate you JS and your HTML completely. You can add your onclick
event on your element this way:
var dropDowns = document.getElementsByClassName('dropdown');
for(var i = 0; i < dropDowns.length; i++){
dropDowns[i].addEventListener('click', function(e){
// e contains your click data
});
}
You might also want to isolate your code by wrapping it in an anonymous function to avoid leaking informations in the global object.
Then, you can deduce everything you’re currently passing as parameter from your context.
.dropbtn
: this is the button you clicked, which will be sent in your event data as e.target
dropdown-content
: as far as I can tell, this will always be the name of your class, so I’d put it in a variable inside your function
dropDowns[i].addEventListener('click', function(e){
var dropdownClass = 'dropdown-content';
});
myDropdown
: this represents the div inside the dropdown element containing the button clicked by the user, which can be written as
e.target.nextElementSibling
since the div is written just after your button
show
: again, a simple var in your function is enough
This gives us this:
// namespace variables
(function(){
// classes used in your HTML
var dropDownClass = 'dropdown-content';
var showClass = 'show';
var dropDowns = document.getElementsByClassName('dropdown');
// this function will be called on click
var open = function(e){
var openDropDown = e.target.nextElementSibling;
var dropdowns = document.getElementsByClassName(dropDownClass);
openDropDown.classList.toggle(showClass);
var closeDropDown = function(event) {
if (event.target === e.target) {
return;
}
if (openDropDown.classList.contains(showClass)) {
openDropDown.classList.remove(showClass);
}
window.removeEventListener('click', closeDropDown);
};
window.addEventListener('click', closeDropDown);
}
for(var i = 0; i < dropDowns.length; i++){
dropDowns[i].children[0].addEventListener('click', open);
}
}());
Now, you can remove the onclick event and the id attribute in your html and add more dropdowns.
Here is the corresponding Plunker: https://plnkr.co/edit/3mdULmI2QEcWmpDCzXQb?p=preview