How to filter elements returned by QuerySelectorAll

Viewed 35296

I'm working on a javascript library, and I use this function to match elements:

$ = function (a)
{
    var x;
    if (typeof a !== "string" || typeof a === "undefined"){ return a;}
    
    //Pick the quickest method for each kind of selector
    if(a.match(/^#([\w\-]+$)/))
    {
        return document.getElementById(a.split('#')[1]);
    }
    else if(a.match(/^([\w\-]+)$/))
    {
        x = document.getElementsByTagName(a);
    }
    else
    {
        x = document.querySelectorAll(a);
    }
    
    //Return the single object if applicable
    return (x.length === 1) ? x[0] : x;
};

There are occasions where I would want to filter the result of this function, like pick out a div span, or a #id div or some other fairly simple selector.

How can I filter these results? Can I create a document fragment, and use the querySelectorAll method on that fragment, or do I have to resort to manual string manipulation?

I only care about modern browsers and IE8+.

If you want to look at the rest of my library, it's here: https://github.com/timw4mail/kis-js

Edit:

To clarify, I want to be able to do something like $_(selector).children(other_selector) and return the children elements matching that selector.

Edit:

So here's my potential solution to the simplest selectors:

tag_reg = /^([\w\-]+)$/;
id_reg = /#([\w\-]+$)/;
class_reg = /\.([\w\-]+)$/;

function _sel_filter(filter, curr_sel)
{
    var i,
        len = curr_sel.length,
        matches = [];
    
    if(typeof filter !== "string")
    {
        return filter;
    }

    //Filter by tag
    if(filter.match(tag_reg))
    {
        for(i=0;i<len;i++)
        {
            if(curr_sell[i].tagName.toLowerCase() == filter.toLowerCase())
            {
                matches.push(curr_sel[i]);
            }
        }
    }
    else if(filter.match(class_reg))
    {
        for(i=0;i<len;i++)
        {
            if(curr_sel[i].classList.contains(filter))
            {
                matches.push(curr_sel[i]);
            }
        }
    }
    else if(filter.match(id_reg))
    {
        return document.getElementById(filter);
    }
    else
    {
        console.log(filter+" is not a valid filter");
    }
    
    return (matches.length === 1) ? matches[0] : matches;
    
}

It takes a tag like div, an id, or a class selector, and returns the matching elements with the curr_sel argument.

I don't want to have to resort to a full selector engine, so is there a better way?

4 Answers

Note: NodeList is not a genuine array, that is to say it doesn't have the array methods like slice, some, map etc. To convert it into an array, try Array.from(nodeList).

ref: https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll

for example:

let highlightedItems = Array.from(userList.querySelectorAll(".highlighted"));

highlightedItems.filter((item) => {
 //...
})

Most concise way in 2019 is with spread syntax ... plus an array literal [...], which work great with iterable objects like the NodeList returned by querySelectorAll:

[...document.querySelectorAll(".myClass")].filter(el=>{/*your code here*/})

Related