Kevin Nugent Kevin Nugent - 1 month ago 8
Javascript Question

Mutually Exclusive Data Attribute Selectors

Is it possible to select elements based on mutually exclusive data attributes, for example: I'd like to

.show()
any
div
s with data attribute of
country="united-kingdom"
and also with
type="partner"
OR
"director"
? Something like:

$('.post[data-country="united-kingdom"]&[data-type="partner,director"]').show();


or

$('.post[data-country="united-kingdom"]&[data-type="partner"]or[data-type="director"]').show();

Answer

I'd like to .show() any divs with data attribute of country="united-kingdom" and also with type="partner" OR "director"?

Then you want a selector group:

$('.post[data-country="united-kingdom"][data-type="partner"], .post[data-country="united-kingdom"][data-type="director"]').show();

That says: Match any element which

  • Has class post, has data-country set to united-kingdom, and has data-type set to partner

    or

  • Has class post, has data-country set to united-kingdom, and has data-type set to director

The "or" part comes from the ,, which is what makes it a selector group rather than a single selector.


In a comment, you've said:

The user might select ten or more of each taxonomy term which requires generating loads of permutations of this conditional.

In that case, you may be better off with filter:

var countries = ["united-kingdom"];  // This would be created from inputs
var types = ["partner", "director"]; // This too

then

var results = $(".post[data-country][data-type]").filter(function() {
    var $this = $(this);
    return countries.indexOf($this.attr("data-country") != -1 &&
           types.indexOf($this.attr("data-type") != -1;
});

In ES2016 or above, you could use Array#includes — which gives you a simple boolean — instead of Array#indexOf which you have to check against -1; and there'a polyfill you can use in ES2015 and earlier...:

var results = $(".post[data-country][data-type]").filter(function() {
    var $this = $(this);
    return countries.includes($this.attr("data-country") &&
           types.includes($this.attr("data-type");
});

This can even be taken further:

var criteria = {};
// From inputs, ...
criteria["country"] = ["united-kingdom"];
criteria["type"] = ["parter", "director"];

then

var keys = Object.keys(criteria);
var selector= ".post" + keys.map(function(key) {
    return "[data-" + key + "]";
}).join();
var results = $(selector).filter(function() {
    var $this = $(this);
    return keys.every(function(key) {
        return criteria[key].includes($this.attr("data-" + key));
    });
});

And as long as we're thinking about ES2015 and ES2016:

const keys = Object.keys(criteria);
const results = $(selector).filter(() => {
    const $this = $(this);
    return keys.every(key => criteria[key].includes($this.attr("data-" + key)));
});

or if you really want to go crazy:

const keys = Object.keys(criteria);
const results = $(selector).filter(() =>
    keys.every(key => criteria[key].includes(this.getAttribute("data-" + key)))
);