Target all instances of an attribute with one function, then add event listener?

Hi.

I’ve got two sub menus that I want to expand using javascript.

My code is working but I have to write two functions and and two html aria attributes:

HTML:

<ul class="nav-level1">
        <!-- NOTE: aria-expanded -->
	<li class="clients"><a href="#" aria-expanded="false">Clients</a>
		<ul class="nav-level2">
			<li>www</li>
			<li><a href="#">frankfit</a></li>
			<li><a href="#">squad studio</a></li>
			<li><a href="#">nb illustration</a></li>
			<li><a href="#">milou mazou</a></li>
			<li><a href="#">not the norm</a></li>
			<li><a href="#">12pm</a></li>
			<li><a href="#">academy</a></li>
			<li><a href="#">ellery class</a></li>
			<li><a href="#">nonogram</a></li>
		</ul>
	</li>
        <!-- NOTE: aria-open -->
	<li class="contact"><a href="#" aria-open="false">Contact</a>
		<ul class="nav-level2">
			<li>
				<address>
					<span><a href="#">hello@okpeach.co.uk</a></span>
					<span>+44(0)7432 013 442</span>
					<span>44 The Bowery, N4 3PN, UK</span>
				</address>
			</li>
		</ul>
	</li>
</ul>

JAVASCRIPT:

// NOTE that querySelector = a[aria-expanded]
const clientList = document.querySelector('a[aria-expanded]');
function toggleClientNav({ target }) {
	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	clientList.setAttribute('aria-expanded', !expanded);
}
clientList.addEventListener('click', toggleClientNav);

// NOTE that querySelector = a[aria-open]
const contactInfo = document.querySelector('a[aria-open]');
function toggleContactNav({ target }) {
	const open = target.getAttribute('aria-open') === 'true' || false;
	contactInfo.setAttribute('aria-open', !open);
}
contactInfo.addEventListener('click', toggleContactNav); 

What I first tried was:

HTML

<ul class="nav-level1">
        <!-- NOTE: aria-expanded !! -->
	<li class="clients"><a href="#" aria-expanded="false">Clients</a>
		<ul class="nav-level2">
			<li>www</li>
			<li><a href="#">frankfit</a></li>
			<li><a href="#">squad studio</a></li>
			<li><a href="#">nb illustration</a></li>
			<li><a href="#">milou mazou</a></li>
			<li><a href="#">not the norm</a></li>
			<li><a href="#">12pm</a></li>
			<li><a href="#">academy</a></li>
			<li><a href="#">ellery class</a></li>
			<li><a href="#">nonogram</a></li>
		</ul>
	</li>
        <!-- NOTE: also aria-expanded !! -->
	<li class="contact"><a href="#" aria-open="false">Contact</a>
		<ul class="nav-level2">
			<li>
				<address>
					<span><a href="#">hello@okpeach.co.uk</a></span>
					<span>+44(0)7432 013 442</span>
					<span>44 The Bowery, N4 3PN, UK</span>
				</address>
			</li>
		</ul>
	</li>
</ul>

JAVASCRIPT

// NOTE that querySelector = a[aria-expanded]
const subMenus = document.querySelector('a[aria-expanded]');
function toggleSubNav({ target }) {
	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	subMenus.setAttribute('aria-expanded', !expanded);
}
subMenus.addEventListener('click', toggleSubNav);

The result of this was that only the first menu worked.

So I tried document.querySelectorAll...

const subMenus = document.querySelectorAll('a[aria-expanded]');
function toggleSubNav({ target }) {
	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	subMenus.setAttribute('aria-expanded', !expanded);
}
subMenus.addEventListener('click', toggleSubNav);

and got this console message: querySelectorAll is not a function.

Is there a way to target all instances of an attribute with one function, or is the way I’ve done it correct?

(What I don’t want to happen is that clicking on one of the links opens both menus!).

from what I see,

you only have one <a> link that has aria-expanded attribute and none of your other <a> tags has the same attribute.

Scroll down to my second example - there I’ve used one function and aria-expanded on both links.

Sorry, you’re right - I posted erroneous code - will post it again…

Ok - here’s the code again… (simplified)

Using this code only one menu works (the first one):

HTML

<ul class="nav-level1">
	<li class="clients"><a href="#" aria-expanded="false">Clients</a>
		<ul class="nav-level2">
			<li>Sub menu item</li>
			<li>Sub menu item</li>
		</ul>
	</li>
	<li class="contact"><a href="#" aria-expanded="false">Contact</a>
		<ul class="nav-level2">
			<li>Sub menu item</li>
			<li>Sub menu item</li>
		</ul>
	</li>
</ul>

JAVASCRIPT

const subMenus = document.querySelector('a[aria-expanded]');
function toggleSubNav({ target }) {
	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	subMenus.setAttribute('aria-expanded', !expanded);
}
subMenus.addEventListener('click', toggleSubNav);

So I had to resort to this:

HTML

<ul class="nav-level1">
	<li class="clients"><a href="#" aria-expanded="false">Clients</a>
		<ul class="nav-level2">
			<li>Sub menu item</li>
			<li>Sub menu item</li>
		</ul>
	</li>
	<li class="contact"><a href="#" aria-open="false">Contact</a>
		<ul class="nav-level2">
			<li>Sub menu item</li>
			<li>Sub menu item</li>
		</ul>
	</li>
</ul>

JAVASCRIPT

const clientList = document.querySelector('a[aria-expanded]');
function toggleClientNav({ target }) {
	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	clientList.setAttribute('aria-expanded', !expanded);
}
clientList.addEventListener('click', toggleClientNav);

const contactInfo = document.querySelector('a[aria-open]');
function toggleContactNav({ target }) {
	const open = target.getAttribute('aria-open') === 'true' || false;
	contactInfo.setAttribute('aria-open', !open);
}
contactInfo.addEventListener('click', toggleContactNav); 

Is there any way of being able to use ‘aria-expanded’ on both anchors and writing only one piece of javascript?

You could do something like:

<ul class="nav-level1">
	<li class="clients"><a href="#" aria-expanded="false">Clients</a>
		<ul class="nav-level2">
			<li>Sub menu item</li>
			<li>Sub menu item</li>
		</ul>
	</li>
	<li class="contact"><a href="#" aria-expanded="false">Contact</a>
		<ul class="nav-level2">
			<li>Sub menu item</li>
			<li>Sub menu item</li>
		</ul>
	</li>
</ul>
const subMenus = document.querySelectorAll('a[aria-expanded]');
function toggleSubNav({ target }) {
	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	target.setAttribute('aria-expanded', !expanded);
}
subMenus.forEach(menu => {
  menu.addEventListener('click', toggleSubNav);
});

OR you could do something like the following if you were going to have many sub menus.

const menu = document.querySelector('.nav-level1');
function toggleSubNav({ target }) {
  if (target.nodeName === 'A') {
  	const expanded = target.getAttribute('aria-expanded') === 'true' || false;
	target.setAttribute('aria-expanded', !expanded);
  }
}
menu.addEventListener('click', toggleSubNav);

Thanks! Both solutions worked :slight_smile:

What is the difference between the two? Is .nodeName more efficient?

Thanks again!

With the second, there is only a single event listener added. In the first, if you had 10 submenus, then 10 event listeners would be created. Event listeners are “expensive”, so anytime you have more than a few, you can use the second method reduce the number of event listeners.

Got it - very useful :slight_smile: