If you are ever writing an element ID that contains a number, as opposed to a totally unique name, you should re-think how you're using them. In fact, if you want to take a totalitarian mental shortcut, just never use IDs (at least, until you're very experienced and understand their pros/cons). Most JavaScript frameworks allow you to easily query for all elements that use a certain class, and even if you aren't using a framework, it's not so hard to loop through the results of a selector. That said, it's possible the main difficulty in this instance is associating a dropdown button with dropdown content. If the HTML is up for modifying, here's a loose way that I would do it.
HTML:
<div class="dropdown">
<button type="button" class="openDropdown">Open</button>
<div class="dropdownContent">
</div>
</div>
CSS:
.dropdownContent:not(.visible) {
display: none;
}
JS:
var dropdowns = document.querySelectorAll('.dropdown');
for (var i = 0; i < dropdowns.length; i++) {
var dropdown = dropdowns[i];
var content = dropdown.querySelector('.dropdownContent');
dropdown.querySelector('.openDropdown').addEventListener('click', function() {
content.classList.toggle('visible');
}
}
Part of the reason I used classes as opposed to direct styling is it's easier to toggle; but also, the other options for display:
properties can often conflict with a developer's attempt to show/hide it. inline-block
is very useful, but has previously been messed up for me because of code that was showing/hiding an element and overrode that.
EDIT: RoToRa found a bug in the above code - the closure generation for the addEventListener
block will preserve the variable content
, but that variable will change before the loop actually finishes, so in the end it will only apply to the last instance. (You won't notice the issue if you only have one dropdown) Here's my attempt at a fix: Note the arguments to the listener, and the .bind
at the end.
var dropdowns = document.querySelectorAll('.dropdown');
for (var i = 0; i < dropdowns.length; i++) {
var dropdown = dropdowns[i];
var content = dropdown.querySelector('.dropdownContent');
dropdown.querySelector('.openDropdown').addEventListener('click', function(content, evt) {
content.classList.toggle('visible');
}.bind(null, content);
}
Closures are very convenient automatic ways to preserve a variable with asynchronous programming, but bind
is a more manual way that treats it as an argument. Another option would have been to turn the entire inside of the loop into a function, and run it immediately (creating a separate closure context that will exist once for each loop run)