Accordion II – JS

Note: This is an advanced version of Accordion, you should complete that question first before attempting this question.

In Accordion, we built a functional accordion component that can expand/collapse each section’s contents. However, building good U…


This content originally appeared on DEV Community and was authored by MING

Note: This is an advanced version of Accordion, you should complete that question first before attempting this question.

In Accordion, we built a functional accordion component that can expand/collapse each section's contents. However, building good UI components goes beyond functionality and we have to ensure our components have great accessibility as well by adding the right ARIA roles, states, and properties to the DOM elements.

Requirements
The ARIA Authoring Practices Guide has a long list of guidelines for the ARIA roles, states, and properties to add to the various elements of an accordion. We should implement the following (improvised) guidelines for this question:

  • The title of each accordion header is contained in a <button> element.

  • If the accordion panel associated with an accordion header is visible, the header button element has aria-expanded set to true. If the panel is not visible, aria-expanded is set to false.

  • The accordion header button element has aria-controls set to the ID of the element containing the accordion panel content.

  • Each element that serves as a container for panel content has role region and aria-labelledby with a value that refers to the button that controls display of the panel.

The skeleton code uses the solution of Accordion, but you are free to use your own solution as a starting point.

Solution

We'll build on top of Accordion's solution. Other than adding the right ARIA roles and states, which is straightforward, we also need to link the accordion headers with the corresponding accordion contents/panels. Hence we create two functions, getAccordionHeaderId and getAccordionPanelId to do this.

  • getAccordionHeaderId generates a unique ID string to use as the value of the id attribute of the header element. This ID will be used as the value of the aria-labelledby attribute of the corresponding accordion panel.
  • getAccordionPanelId generates a unique ID string to use as the value of the id attribute of accordion panel. This ID will be used as the value of the aria-controls attribute of the corresponding accordion header.

Since there can be multiple accordion component instances on the page and we cannot guarantee that the accordion item values will be globally unique, each accordion instance needs to have a unique identifier. We can create a newID function that generates a unique ID which under the hood is a globally-incrementing integer. The final ID string will be a concatenation of the accordion instance's ID, the item value, and whether it's an accordion header or panel.

Test Cases

  • Inspect the rendered HTML to see that the right attributes were added to the DOM.
  • You can go a step further by using accessibility testing tools like axe to validate the a11y of the elements.

Accessibility
By using a <button> for the section titles, we have enabled the basic keyboard interactions necessary to achieve sufficient accessibility for accordion components. However, there are some useful optional keyboard interactions to add, which will be covered in Accordion III.

HTML:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div id="accordion" class="accordion"></div>
    <!-- 下面是函数创建后的sing item DOM样例: -->
    <!-- 
      <div>
        <div>
          HTML
          <span
            aria-hidden="true"
            class="accordion-icon"></span>
        </div>
        <div>
          The HyperText Markup Language or HTML is the
          standard markup language for documents designed to
          be displayed in a web browser.
        </div>
      </div>

    -->
    <script src="index.js"></script>
  </body>
</html>

JS:

const data = {
  sections: [
    {
      value: 'html',
      title: 'HTML',
      contents:
        'The HyperText Markup Language or HTML is the standard markup language for documents designed to be displayed in a web browser.',
    },
    {
      value: 'css',
      title: 'CSS',
      contents:
        'Cascading Style Sheets is a style sheet language used for describing the presentation of a document written in a markup language such as HTML or XML.',
    },
    {
      value: 'javascript',
      title: 'JavaScript',
      contents:
        'JavaScript, often abbreviated as JS, is a programming language that is one of the core technologies of the World Wide Web, alongside HTML and CSS.',
    },
  ],
};

(() => {
  // Encapsulate the ID generation so that it can only be read and is protected from external modification.
  const newID = (() => {
    let id = 0;
    return () => id++;
  })();
  const getAccordionHeaderId = (accordionId, value) =>
    accordionId + '-header-' + value;
  const getAccordionPanelId = (accordionId, value) =>
    accordionId + '-panel-' + value;



  function accordion($rootEl, { sections }) {
    const accordionId = `accordion-${newID()}`;

    function attachEvents() {
      // Use Event Delegation.
      $rootEl.addEventListener('click', (event) => {
        const target = event.target;
        if (
          target.tagName !== 'BUTTON' ||
          !target.classList.contains('accordion-item-title')
        ) {
          return;
        }

        const isExpanded = target.getAttribute('aria-expanded') === 'true'; //<--diff
        target.setAttribute('aria-expanded', !isExpanded); //<--diff

        // Find the icon and toggle the direction.
        const $icon = target.querySelector('.accordion-icon');
        $icon.classList.toggle('accordion-icon--rotated', !isExpanded); //<--diff

        //key point --->通过target.nextSibling找到内容,然后toggle内容的显示隐藏
        const $accordionContents = target.nextSibling;
        $accordionContents.hidden = !$accordionContents.hidden;
      });
    }

    function init() {
      const $accordionSections = document.createDocumentFragment();

      sections.forEach(({ value, title, contents }) => {
        const headerId = getAccordionHeaderId(accordionId, value); //<--diff
        const panelId = getAccordionPanelId(accordionId, value); //<--diff

        const $accordionSection = document.createElement('div');
        $accordionSection.classList.add('accordion-item');

        const $accordionTitleBtn = document.createElement('button');
        $accordionTitleBtn.classList.add('accordion-item-title');
        $accordionTitleBtn.type = 'button';
        $accordionTitleBtn.setAttribute('data-value', value);
        $accordionTitleBtn.id = headerId; //<--diff
        $accordionTitleBtn.setAttribute('aria-controls', panelId); //<--diff
        $accordionTitleBtn.setAttribute('aria-expanded', false); //<--diff

        const $accordionIcon = document.createElement('span');
        $accordionIcon.classList.add('accordion-icon');
        $accordionIcon.setAttribute('aria-hidden', 'true');

        $accordionTitleBtn.append(title, $accordionIcon);

        const $accordionSectionContents = document.createElement('div');
        $accordionSectionContents.classList.add('accordion-item-contents');
        $accordionSectionContents.hidden = true;
        $accordionSectionContents.textContent = contents;
        $accordionSectionContents.role = 'region'; //<--diff
        $accordionSectionContents.id = panelId; //<--diff
        $accordionSectionContents.setAttribute('aria-labelledby', headerId); //<--diff

        $accordionSection.append($accordionTitleBtn, $accordionSectionContents);
        $accordionSections.append($accordionSection);
      });

      $rootEl.appendChild($accordionSections);
    }

    init();
    attachEvents();
  }

  accordion(document.getElementById('accordion'), data);
})();


This content originally appeared on DEV Community and was authored by MING


Print Share Comment Cite Upload Translate Updates
APA

MING | Sciencx (2023-03-01T21:25:23+00:00) Accordion II – JS. Retrieved from https://www.scien.cx/2023/03/01/accordion-ii-js/

MLA
" » Accordion II – JS." MING | Sciencx - Wednesday March 1, 2023, https://www.scien.cx/2023/03/01/accordion-ii-js/
HARVARD
MING | Sciencx Wednesday March 1, 2023 » Accordion II – JS., viewed ,<https://www.scien.cx/2023/03/01/accordion-ii-js/>
VANCOUVER
MING | Sciencx - » Accordion II – JS. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2023/03/01/accordion-ii-js/
CHICAGO
" » Accordion II – JS." MING | Sciencx - Accessed . https://www.scien.cx/2023/03/01/accordion-ii-js/
IEEE
" » Accordion II – JS." MING | Sciencx [Online]. Available: https://www.scien.cx/2023/03/01/accordion-ii-js/. [Accessed: ]
rf:citation
» Accordion II – JS | MING | Sciencx | https://www.scien.cx/2023/03/01/accordion-ii-js/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.