NickDJM / accessible-menu

A JavaScript library to help you generate WAI-ARIA accessible menus in the DOM.
ISC License
35 stars 7 forks source link

Expanding Current Location #89

Closed michaeltlombardi closed 3 years ago

michaeltlombardi commented 3 years ago

Hullo! So opening caveat, I'm a competent infrastructure/backend developer whose javascript and frontend are both much less polished.

That out of the way, I'm trying to use accessible-menu to build accessible disclosure navigation menus for both a site (with multiple "books") and the books via hugo. templating.

For site navigation, this is less of an issue, but for navigation within a book, it would be extremely useful to be able to apply some automatic styling to indicate where in the book's navigation you currently are - possibly just by defaulting that section to expanded?

I've been introspecting on the initialized AccessibleMenu.DisclosureMenu object but can't seem to figure out a way to discover the appropriate elements and toggle them. If I tab through things and introspect, I can use foo.currentMenuItem.menuElements.toggle.expand() but I'm not sure I understand how to do this programmatically on page load.

Is this a wild anti-pattern, to pre-expand a disclosure menu to the current page? If not, I'd love to understand how to do this with accessible-menu.

Thanks in advance (and it's okay if you don't have time/interest in answering this! πŸ’œ)

NickDJM commented 3 years ago

Hey @michaeltlombardi

Interestingly enough I've been thinking about a very similar process for books in Drupal!

While adding something into accessible-menu proper would probably not be worth it (pretty unique case), you can easily just overwrite a function in accessible-menu after you've included it on the page.

I haven't tested anything yet myself, but my current idea is to rewrite the initialize() function for menuToggles so that it would actually look for either the openClass or perhaps aria-expanded="true" on the toggle in the DOM (which would be initially rendered by Drupal). If found, it would then trigger expand() on itself.

My advise would be to try something similar.


Whenever I end up implementing this in my Drupal instance I can post here with how I actually went about doing it.

michaeltlombardi commented 3 years ago

Thanks! I can try giving this override a shot. My javascript is pretty weak but that makes sense to me! If I find a functional solution I'll post it here myself. 😊

michaeltlombardi commented 3 years ago

Hmmm, trying to do so using the CDN source and banging my head on things, will keep trying but if you've got a handy-dandy pseudo-code example of how to overwrite this, I'm πŸ‘€

Extant script in my site footer:

  <script src="https://cdn.jsdelivr.net/npm/accessible-menu/dist/accessibleMenu.js"></script>
  <script>
    const navs = document.querySelectorAll("nav");
    const menuSettings = {
      menuItemSelector: ".menu-item",
      submenuItemSelector: ".menu-item.dropdown",
      submenuToggleSelector: ".dropdown-toggle",
      submenuSelector: ".menu.dropdown",
    };
    const whitelistNavs = ["site-nav", "game-nav", "blog-nav"]

    navs.forEach(nav => {
      const menuElement = nav.querySelector(".menu");

      if (whitelistNavs.includes(nav.id)) {
        const controllerElement = nav.querySelector(".menu-toggle");
        const menu = new AccessibleMenu.Menubar({
          menuElement,
          ...menuSettings,
          controllerElement,
          containerElement: nav
        });
      }
    });
  </script>
michaeltlombardi commented 3 years ago

Ended up with a lazy work around:

  <script src="https://cdn.jsdelivr.net/npm/accessible-menu/dist/accessibleMenu.js"></script>
  <script>
    const navs = document.querySelectorAll("nav");
    const menuSettings = {
      menuItemSelector: ".menu-item",
      submenuItemSelector: ".menu-item.dropdown",
      submenuToggleSelector: ".dropdown-toggle",
      submenuSelector: ".menu.dropdown",
    };
    const whitelistNavs = ["site-nav", "game-nav", "blog-nav"]

    function openMenuItem(element) {
      element.classList.remove("hide");
      element.classList.add("show");
      element.setAttribute("aria-expanded", true);
    }

    navs.forEach(nav => {
      const menuElement = nav.querySelector(".menu");

      if (whitelistNavs.includes(nav.id)) {
        const controllerElement = nav.querySelector(".menu-toggle");
        <!-- const lists = nav.querySelectorAll(ul[role=menubar]) -->
        const menu = new AccessibleMenu.Menubar({
          menuElement,
          ...menuSettings,
          controllerElement,
          containerElement: nav
        });
        const currentTreeElements = nav.querySelectorAll("ul.current");
        for (let element of currentTreeElements) {
          openMenuItem(element);
        }
      }
    });
  </script>

Since I'm using hugo to generate the pages, it was trivial to mark the current node and ancestor menus with the current class.

NickDJM commented 3 years ago

@michaeltlombardi I don't have a Drupal book example yet, but I do have an example where I overwrite the expand/collapse functions for toggles to work with Bootstrap 3 (Disclaimer: this is super hacky, but I think its a similar method to what you would need to do).


  /**
   * Override's an AccessibleMenu's default expand/collapse functions
   * allowing it to work natively with Bootstrap 3.
   *
   * @param {AccessibleMenu.DisclosureMenu} menu - The menu to alter.
   */
  function overrideToggling(menu) {
    menu.menuElements.submenuToggles.forEach(toggle => {
      toggle.expand = () => {
        toggle.dom.toggle.setAttribute("aria-expanded", "true");

        // Add the open class
        if (toggle.openClass !== "") {
          if (typeof toggle.openClass === "string") {
            toggle.elements.controlledMenu.dom.menu.classList.add(
              toggle.openClass
            );
            toggle.domElements.parent.classList.add(toggle.openClass);
          } else if (Array.isArray(toggle.openClass)) {
            toggle.openClass.forEach(value => {
              toggle.elements.controlledMenu.dom.menu.classList.add(value);
              toggle.domElements.parent.classList.add(value);
            });
          }
        }

        // Remove the close class.
        if (toggle.closeClass !== "") {
          if (typeof toggle.closeClass === "string") {
            toggle.elements.controlledMenu.dom.menu.classList.remove(
              toggle.closeClass
            );
            toggle.domElements.parent.classList.remove(toggle.closeClass);
          } else if (Array.isArray(toggle.closeClass)) {
            toggle.closeClass.forEach(value => {
              toggle.elements.controlledMenu.dom.menu.classList.remove(value);
              toggle.domElements.parent.classList.remove(value);
            });
          }
        }
      };

      toggle.collapse = () => {
        toggle.dom.toggle.setAttribute("aria-expanded", "false");

        // Add the close class
        if (toggle.closeClass !== "") {
          if (typeof toggle.closeClass === "string") {
            toggle.elements.controlledMenu.dom.menu.classList.add(
              toggle.closeClass
            );
            toggle.domElements.parent.classList.add(toggle.closeClass);
          } else if (Array.isArray(toggle.closeClass)) {
            toggle.closeClass.forEach(value => {
              toggle.elements.controlledMenu.dom.menu.classList.add(value);
              toggle.domElements.parent.classList.add(value);
            });
          }
        }

        // Remove the open class.
        if (toggle.openClass !== "") {
          if (typeof toggle.openClass === "string") {
            toggle.elements.controlledMenu.dom.menu.classList.remove(
              toggle.openClass
            );
            toggle.domElements.parent.classList.remove(toggle.openClass);
          } else if (Array.isArray(toggle.openClass)) {
            toggle.openClass.forEach(value => {
              toggle.elements.controlledMenu.dom.menu.classList.remove(value);
              toggle.domElements.parent.classList.remove(value);
            });
          }
        }
      };
    });
  }

In my case I just passed it an already-initialized DisclosureMenu to loop through.

You would need to use a similar method to replace the initialize function of the toggles and then re-initialize them once you made the edit.

NickDJM commented 3 years ago

Closing as "resolved" since I believe we have a solution here.

I unfortunately haven't had time to implement this for Drupal books yet, but with the release of v3.0.0 my approach will most likely change to writing a custom subclass that will look for a flag to "auto open" a menu toggle on initialization.