-1

Sorry if my question is really for newbie level...

I have a 3 level list to build a menu, but I don't want to use href. I would like to use a specific attribute instead and call a javascript function with it value.

Here is the markup of my example-list:

<ul class="menu"> <li><a href="#" link_value="item 1">Firts level Menu item 1</a></li> <ul> <li><a href="#" link_value="item 2-1">Second level Menu item 1</a></li> <ul> <li><a href="#" link_value="item 3-2-1">Third level Menu item 1</a></li> <li><a href="#" link_value="item 3-2-2">Third level Menu item 2</a></li> <li><a href="#" link_value="item 3-2-3">Third level Menu item 3</a></li> </ul> <li><a href="#" link_value="item 2-2">Second level Menu item 2</a></li> <li><a href="#" link_value="item 2-3">Second level Menu item 3</a></li> </ul> <li><a href="#" link_value="item 2">Firts level Menu item 2</a></li> <li><a href="#" link_value="item 3">Firts level Menu item 3</a></li> 
How could I run a javascript function to set onclick event for all a items to have something like
<a onclick="my_fynction()"> function my_fonction() {do_something(link_value)} 

and also add an 'active' class to the active element and his parent levels and remove the active class from the others ?

Hope I have been clear enough ? Many thanks !!!

7
  • 1
    Select all of them with querySelectorAll, then use addEventListener to bind you function to that event on that item? Commented Feb 6, 2024 at 13:08
  • 1
    FYI, your HTML is invalid. You can't have a ul as a direct child of a ul. If you want a nested list, you have to put your inner ul inside a li. Commented Feb 6, 2024 at 13:09
  • 1
    See: developer.mozilla.org/en-US/docs/Learn/JavaScript/… Commented Feb 6, 2024 at 13:10
  • Another option is make a delegated event on your .menu class. developer.mozilla.org/en-US/docs/Learn/JavaScript/… mmm, just noticed MDN's hash links don't work in Chrome, but seem fine in Firefox.. Commented Feb 6, 2024 at 13:11
  • @jeromebg ... link_value is an invalid attribute name. Please consider making use of a data-* global attribute and the DOM element's related dataset property instead ... something like e.g ... <a href="#" data-value="item 3"/> where each value then can be read like this ... linkElement.dataset.value. Commented Feb 6, 2024 at 15:26

2 Answers 2

2

You can listen to clicks on ul.menu and put your event logic only on this element.

document.querySelector('ul.menu').addEventListener('click', event => { // event.target is clicked element if(event.target.tagName!=="A") return; // prevent default behaviour; redirecting event.preventDefault(); // your code... }) 

But you'd need to be careful if you put <span> for example inside <a>. You'd need to put CSS pointer-events: none; on these elements.

Sign up to request clarification or add additional context in comments.

2 Comments

An easier option, rather than pointer-events, just do -> if (!event.target.closest('a')) return; instead. Or even more specific -> if (!event.target.closest('[link_value]')) return;
@Keith Yeah, that's an option as well.
0

Regarding both of my above comments ...

@jeromebg ... link_value is an invalid attribute name. Please consider making use of a data-* global attribute and the DOM element's related dataset property instead ... something like e.g ... <a href="#" data-value="item 3"/> where each value then can be read like this ... linkElement.dataset.value.

@jeromebg ... the nested <ul/> markup is broken/invalid as well ... please fix it.

... and after having fixed the markup, one should make use of, as already suggested but not explicitly named, event-delegation.

The latter does not only mean listening at a common (outer) root node, it also means targeting the element/s of interest which, like the root-node, might have child element-nodes as well.

Thus one always has to query the element/s one is interested in. One mostly does achieve the result by utilizing the closest method of the event's target element.

And regarding another of the OP's requirements ...

... also add an 'active' class to the active element and his parent levels and remove the active class from the others ?

... the handler function would pass the currently identified link-element to a custom implemented function, thus forwarding such a special task and not taking care of such stuff by itself.

A function which marks the current menu item has to do following ...

  • identifying the menu-item node ... done by ...

    const menuItem = elmLink.closest('li'); 
  • identifying the menu-root node ... done by ...

    const menuRoot = menuItem.closest('ul.menu'); 
  • choosing the right selector for querying any list-item node which has a link-element (e.g. 'li:has(a[href])') and removing the intended class-name (and/or e.g. aria attributes) from each queried element.

  • adding the intended class-name (and/or e.g. aria attributes) to the list-item which has been identified as current.

It is not necessary to mark any list-item involved in another list-item's current state as current as well. The visual representation of such a state can be easily achieved by utilizing a functional CSS pseudo-class like :has()

function markCurrentMenuItem(elmLink) { const menuItem = elmLink.closest('li'); const menuRoot = menuItem.closest('ul.menu'); menuRoot .querySelectorAll('li:has(a[href])') .forEach(elm => { // elm.classList.remove('active'); elm.removeAttribute('aria-current'); }); // menuItem.classList.add('active'); menuItem.setAttribute('aria-current', 'true'); } document .querySelector('ul.menu') .addEventListener('click', evt => { const elmLink = evt.target.closest('a[href][data-value]') if (elmLink) { evt.preventDefault(); markCurrentMenuItem(elmLink); console.log({ value: elmLink.dataset.value }); } })
body { margin: 0; } ul { list-style: none; margin: 0; padding: 0 0 0 20px; } li { padding: 2px 0 2px 0; } a span { display: inline-block; padding: 0 20px; background-color: #f2ffa7; } /* li.active, */li[aria-current="true"] a > span { background-color: #b1d000; } /* ul:has(> li.active), */ul:has(> li[aria-current="true"]) { background-color: #bee8ff; } .as-console-wrapper { left: auto!important; width: 50%; min-height: 100%; }
<ul class="menu" role="menu" aria-label="Main Menu"> <li> <a href="#" data-value="item 1"> <span>First level Menu item 1</span> </a> </li> <li> <ul role="menu" aria-label="Second Level Menu"> <li> <a href="#" data-value="item 2-1"> <span>Second level Menu item 1</span> </a> </li> <li> <ul role="menu" aria-label="Third Level Menu"> <li> <a href="#" data-value="item 3-2-1"> <span>Third level Menu item 1</span> </a> </li> <li> <a href="#" data-value="item 3-2-2"> <span>Third level Menu item 2</span> </a> </li> <li> <a href="#" data-value="item 3-2-3"> <span>Third level Menu item 3</span> </a> </li> </ul> </li> <li> <a href="#" data-value="item 2-2"> <span>Second level Menu item 2</span> </a> </li> <li> <a href="#" data-value="item 2-3"> <span>Second level Menu item 3</span> </a> </li> </ul> </li> <li> <a href="#" data-value="item 2"> <span>First level Menu item 2</span> </a> </li> <li> <a href="#" data-value="item 3"> <span>First level Menu item 3</span> </a> </li> </ul>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.