how to chain selectors with OR condition (alternative result set if main is empty)
Asked Answered
B

5

6

What I have now:

var result = $('selector1');
if (result.length == 0) result = $('selector2');

but this defeats chaining.

Question is - how do I get same result with JQuery chaining?

I can't use $('selector1, selector2'), because this would always select result sets for both selectors, while I need results of selector2 only if there are no matching elements for selector1.

Blythebm answered 25/10, 2011 at 16:38 Comment(0)
D
3

This behavior in some places is called "coalescing". Here's a generic jQuery plugin that does it for you (editing after great feedback, see the comments).

// The namespace function
jQuery.coalesce = function(selectors){

    var out;

    $.each(selectors, function(i, v){
        var el = jQuery(v);
        if (el.length) {
           out = el;
           return false;
        }
    });

    return out || jQuery();
};

// The jQuery plugin
jQuery.fn.coalesce = function(){
    return jQuery.coalesce(this.selector.split(",")); //a little brittle
};

So, in a world where #foo doesn't exist, and a and div do, if you do:

jQuery.coalesce(["#foo", "a", "div"])

That returns jQuery("a") if #foo doesn't exist, or jQuery("#foo") if #foo does exist.

If you require using it in the middle of the chain, you can use $("#foo, a, div").coalesce(), but its vulnerable to commans within the selectors themselves.

Dynameter answered 25/10, 2011 at 17:5 Comment(14)
Why efficiency might be a problem here? The only "extra" I see is split() call, but I believe this is heavily optimized down to native code in browser code, and isn't really something to worry about.Blythebm
If you don't care about traversing the DOM twice, then why not simply ( $('selector1').length ? $('selector1') : $('selector2') )?Federalize
@Blythebm the efficiency issue is that jQuery will query the DOM once with the full selector, and then this plugin will re-query each selector until it finds one a matching set.Dynameter
This code traverses DOM only once if selector1 is not empty, and twice if selector1 is empty while selector2 is not (so does my code in question, so its best case we can get here). Code with ternary would parse it two and three times respectively for the same result.Blythebm
@yahelc - Why don't you repackage this as a standalone function on the jQuery namesapce, so that you only have to query the DOM once?Federalize
Just did :) The reason you might want it on the prototype is that you might want to use it in the middle of a chain. If you're not using it in the middle of a chain, jQuery.coalesce is preferable, as it will be much more performant.Dynameter
What if I try this: jQuery.coalesce('#foo, a[title="My god, it breaks!"], div')? I think you should pass in an array of selectors, rather than a comma separated one.Federalize
@yahelc yes you're right I see now, - so it has to be repackaged as a standalone function as Joseph suggestsBlythebm
@JosephSilber yep this will also break split(), there should be more robust solution..Blythebm
@yahelc - You could also add it to the prototype, so that it returns $.coalesce(this.selector). That way, you'll get the best of both worlds.Federalize
Great feedback all around. Joseph, wouldn't we still need to use split on the selector if we use $.coalesce within $.fn.coalesce, since its now expecting an array of selectors?Dynameter
@yahelc - Sure. I wrote that before you changed it to take an array. And, as you noted, it'll still be vulnerable to commas within the selectors themselves.Federalize
Hm. Might be mitigatable if jQuery exposes their selector parsing function to the global object so we could access it; I'm not sure if they do.Dynameter
+1 I took the liberty of editing your answer. I f you don't like it, feel free to revert.Federalize
F
0

If you must, this would do it:

$(function(){
  var r = $('selector1');
  if (r.length == 0) r = $('selector2');
  return r;
}());

However, I don't think chaining is being defeated in your example. Chaining is not the be-all-end-all and putting in a tortured selection like the one above serves more to obfuscate the code than simplify.

Fabriane answered 25/10, 2011 at 16:47 Comment(0)
F
0
var result = $('selector1');
( result.length ? result : $('selector2') ).css('color', 'red');
Federalize answered 25/10, 2011 at 16:58 Comment(0)
A
0

I don't believe you can do this exactly how you want with just pure jQuery but you may be able to get some of the functionality you are looking for by using jQuery's extended selectors, which allow you to select based on negation and boolean operators. Check out http://api.jquery.com/category/selectors/jquery-selector-extensions/.

Avi answered 25/10, 2011 at 18:0 Comment(0)
T
0

Personally I like very much the "presence" idiom from Ruby on Rails, so I have added this to my JS set up:

jQuery.fn.presence = function presence() {
    return this.length !== 0 ? this : null;
};

and now use this in my code:

const ul = el.parents('UL').first().presence() ?? el.siblings('UL').first()

(If you can't afford to use ??, || would do just as well here.)

Taliped answered 26/4, 2021 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.