Reputation: 19
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 !!!
Upvotes: -1
Views: 157
Reputation: 13432
Regarding both of my above comments ...
@jeromebg ...
link_value
is an invalid attribute name. Please consider making use of adata-*
global attribute and the DOM element's relateddataset
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>
Upvotes: 0
Reputation: 1142
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.
Upvotes: 2