javascriptselectors-api

How to best filter (descendent) elements by both name and class name?


I need to get all DOM elements within DOM element myRootElement, that satisfy the following conditions:

  1. have one of several element names (e.g. div or blockquote)
  2. have one of several class names (e.g. foo or bar)

If it were just condition 1, I know I can write:

let elementsWithRightName = myRootElement.querySelectorAll("div, blockquote");

and then I can filter the result, like so:

let isOfNiceClass = (elem) => ["foo", "bar"].includes(elem.className);
let elementsOfInterest = [...elementsWithRightName].filter(isOfNiceClass);

but is there a better/most elegant/idiomatic way to apply both filters?


Solution

  • Use .matches():

    classNames.some(c => elem.matches(`.${c}`))
    

    Or, with the new-ish :is()/:where():

    const tags = ['div', 'blockquote'];
    const classes = ['foo', 'bar', 'baz'];
    
    const selector = `:is(${tags.join(', ')}):is(${classes.map(c => '.' + c).join(', ')})`;
    // ':is(div, blockquote):is(.foo, .bar, .baz)'
    const elementsOfInterest = myRootElement.querySelectorAll(selector);
    

    For more complex cases, it depends. However, I'd say a CSS selector is probably the most efficient way to search for elements: browsers optimize it under the hood so we don't have to. I'm not sure about XPath though.