skip to Main Content

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 !!!

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search