jquery data selector

Solution 1:

At the moment I'm selecting like this:

$('a[data-attribute=true]')

Which seems to work just fine, but it would be nice if jQuery was able to select by that attribute without the 'data-' prefix.

I haven't tested this with data added to elements via jQuery dynamically, so that could be the downfall of this method.

Solution 2:

I've created a new data selector that should enable you to do nested querying and AND conditions. Usage:

$('a:data(category==music,artist.name==Madonna)');

The pattern is:

:data( {namespace} [{operator} {check}]  )

"operator" and "check" are optional. So, if you only have :data(a.b.c) it will simply check for the truthiness of a.b.c.

You can see the available operators in the code below. Amongst them is ~= which allows regex testing:

$('a:data(category~=^mus..$,artist.name~=^M.+a$)');

I've tested it with a few variations and it seems to work quite well. I'll probably add this as a Github repo soon (with a full test suite), so keep a look out!

The code:

(function(){

    var matcher = /\s*(?:((?:(?:\\\.|[^.,])+\.?)+)\s*([!~><=]=|[><])\s*("|')?((?:\\\3|.)*?)\3|(.+?))\s*(?:,|$)/g;

    function resolve(element, data) {

        data = data.match(/(?:\\\.|[^.])+(?=\.|$)/g);

        var cur = jQuery.data(element)[data.shift()];

        while (cur && data[0]) {
            cur = cur[data.shift()];
        }

        return cur || undefined;

    }

    jQuery.expr[':'].data = function(el, i, match) {

        matcher.lastIndex = 0;

        var expr = match[3],
            m,
            check, val,
            allMatch = null,
            foundMatch = false;

        while (m = matcher.exec(expr)) {

            check = m[4];
            val = resolve(el, m[1] || m[5]);

            switch (m[2]) {
                case '==': foundMatch = val == check; break;
                case '!=': foundMatch = val != check; break;
                case '<=': foundMatch = val <= check; break;
                case '>=': foundMatch = val >= check; break;
                case '~=': foundMatch = RegExp(check).test(val); break;
                case '>': foundMatch = val > check; break;
                case '<': foundMatch = val < check; break;
                default: if (m[5]) foundMatch = !!val;
            }

            allMatch = allMatch === null ? foundMatch : allMatch && foundMatch;

        }

        return allMatch;

    };

}());

Solution 3:

You can also use a simple filtering function without any plugins. This is not exactly what you want but the result is the same:

$('a').data("user", {name: {first:"Tom",last:"Smith"},username: "tomsmith"});

$('a').filter(function() {
    return $(this).data('user') && $(this).data('user').name.first === "Tom";
});

Solution 4:

I want to warn you that $('a[data-attribute=true]') doesn't work, as per Ashley's reply, if you attached data to a DOM element via the data() function.

It works as you'd expect if you added an actual data-attr in your HTML, but jQuery stores the data in memory, so the results you'd get from $('a[data-attribute=true]') would not be correct.

You'll need to use the data plugin http://code.google.com/p/jquerypluginsblog/, use Dmitri's filter solution, or do a $.each over all the elements and check .data() iteratively

Solution 5:

There's a :data() filter plugin that does just this :)

Some examples based on your question:

$('a:data("category=music")')
$('a:data("user.name.first=Tom")');
$('a:data("category=music"):data("artist.name=Madonna")');
//jQuery supports multiple of any selector to restrict further, 
//just chain with no space in-between for this effect

The performance isn't going to be extremely great compared to what's possible, selecting from $._cache and grabbing the corresponding elements is by far the fastest, but a lot more round-about and not very "jQuery-ey" in terms of how you get to stuff (you usually come in from the element side). Of th top of my head, I'm not sure this is fastest anyway since the process of going from unique Id to element is convoluted in itself, in terms of performance.

The comparison selector you mentioned will be best to do in a .filter(), there's no built-in support for this in the plugin, though you could add it in without a lot of trouble.