Contact   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

No Empty Selector in jQuery


One thing that bugs me about jQuery selectors is that if you pass a null or empty value into the jQuery constructor you end up with a selection of the document. In other words if you do any of the following:

$(null).length; 
$().length;
$("").length;

you end up with a 1 item jQuery object that contains the html document object.

Empty jQuery Selectors in Plug-ins

This has tripped me up on a few occasions, especially when creating plug-ins that rely on user provided selectors or jquery objects as input. For example if I have a plug-in definition and that plug-in includes references to an element or a group of elements I usually provide the ability provide a selector. I frequently use initialization code like this to handle parameter management:

$.fn.closable = function(options)
{
    var opt = { handle: null,
                closeHandler: null,
                cssClass: "closebox"
    };
    $.extend(opt,options);
    
    return this.each(function(i) {    
        var el = $(this);            
        var pos = el.css("position");
        if (!pos || pos=="static")
            el.css("position","relative");

        var h = $(opt.handle);
        if (h.length < 1)
           h = el;
        var div = $("<div></div>").addClass(opt.cssClass).click(function() { $(el).hide(); });
        h.append(div);                  
    });
}

If you’re new to plug-ins the $.fn object refers to the jQuery wrapped set implementation object. Any function attached to this object becomes part of the jQuery Wrapped Set so it effectively can act on the wrapped set like any of the native jQuery functions. Typically jQuery plug-ins are have an options parameter which tends to be passed in as an object map ( { property: value, property2: value }) which is similar to an anonymous type in .NET. This passed option parameter is then typically merged with a known object that holds the default state – in this case the private opt variable in the function with the effect that the passed values override the default values in the declared object.

I love using this particular pattern of parameter passing because it allows maximum flexibility with minimum effort for the consumer of the plug-in. A typical call may look like this then:

$(document).ready( function() {            
    $("#divDialog").closable( { handle: "#divDialog .dialogheader", cssClass="myCloseBox" };
});

So far so good. This all works great as long as the users passes in a handle explicitly. But if no handle is passed the above code fails in an unexpected way. You see the default value for the handle parameter is null. If the following code executes with null or empty the result ends up being the document:

var h = $(opt.handle); 
if (h.length < 1) 

Because null or empty returns the document the if block never executes and instead of my ‘default’ object I now get the document which will produce definitely incorrect results.

This makes the above code fail, because the handle (where the closebox icon is attached) turns out to be the document object which is of course incorrect. The intended behavior is that if no match is found the handle is set to the same as the parent element – the closable element in this case.

The workarounds for this are actually quite simple. You can simply check for document:

var h = $(opt.handle);
if (h.length < 1 || h.get(0) == document  )
   h = el;

Note that I using h.is( document ) didn’t work although that looks cleaner (the above should also be more efficient.

Another and maybe cleaner approach is to set a default value that won’t be matched:

var opt = { handle: "xx",…};

by specifying an invalid HTML element type you will end up with a 0 item jQuery object when the constructor returns. Or maybe a little more efficient for jQuery:

var opt = { handle: "body>xx",…};

so jQuery doesn’t have to parse the entire document but just the top level tree.

In standalone code, if you apply selectors that are passed as parameters or come from otherwise generic code, it’s also a good idea to always check for null or empty.

Once you know about this issue it’s not a big deal and it’s easy to work around and most likely this will only bite you if you build some sort of plug-in or API function with jQuery. But it’s easy to miss – it’s bitten me quite a few times.

There aren’t many things that I don’t like about jQuery, but this is one of the rare one. It’s an unfortunate API choice to have an empty value return the document – I suspect this was done for brevity for things like:

$().ready(function(){
  …       
 });

Small issue – one is hard pressed to find fault with jQuery :-}

Make Donation
Posted in jQuery  


Feedback for this Post

 
# re: No Empty Selector in jQuery
by Ariel October 04, 2008 @ 2:54pm
I wouldn't really recommend it, but it looks like you can get your desired behavior by commenting out the "selector = selector || document" line from jQuery.fn.init(). A better option might be to create a plugin that does the same.
# re: No Empty Selector in jQuery
by Rick Strahl October 04, 2008 @ 3:08pm
@Ariel - yes but if you know about the issue it's easy enough to work around so I'm not sure if a plug-in wouldn't be a bit of overkill...
# re: No Empty Selector in jQuery
by Funka! October 06, 2008 @ 2:45pm
Funny I should come across this today, after I was just looking for a similar solution last week. I found this interesting post from John Resig recommending that you can get an empty jQuery object via an empty array $([]) ... although I ended up doing something different and didn't need to use this. http://www.nabble.com/Re%3A-Creating-an-empty-jQuery-object-p9010479.html
# re: No Empty Selector in jQuery
by Dave October 06, 2008 @ 7:59pm
To continue Funka's line of thought, you could just change your selector to this:

var h = $(opt.handle || el);

If the user's opt.handle is non-empty but an invalid selector, e.g., "#missingDiv", then it won't default to el. If an invalid selector isn't considered a user input error and you want that to default to el, you could do this:

$("<div></div>")
.addClass(opt.cssClass)
.click(function() { el.hide(); })
.appendTo($(opt.handle)[0] || el);

That will pass .appendTo() either a DOM element (if opt.handle selected an element) or the default of el.

The original code had an extraneous $(el) in the click handler; el is already a jQuery object. Depending on how often the code is used, you might want to replace that with $(this).parent().hide() to eliminate the closure.

The general pattern to create a jQuery object that defaults to something if the user's element isn't there would look something like this:

$($(opt.myElement)[0] || defaultElement)

Where defaultElement is a selector string, DOM element, or jQuery object that you know identifies something good.
# re: No Empty Selector in jQuery
by Dave October 06, 2008 @ 8:03pm
Duh, I meant:

.appendTo($(opt.handle || el)[0] || el)

Otherwise you're back to the original problem!
# Issues with jQuery
by Learning Remix, Fall 2008 October 28, 2008 @ 2:13pm
After searching  to find  if there are any problems and  issues with jquery handling data. I found that it does not have any thing that is big and really matters. Given  the way it eases the burden on the developer...
# re: No Empty Selector in jQuery
by Patrick December 09, 2008 @ 5:37pm
We had a similar issue, we use to use
$('body>nofreakingwaythisexists')
with further study of the jQuery source we found the author does uses
$([])
this jQuery object has 0 members and fits the bill.
# re: No Empty Selector in jQuery
by eduardo February 15, 2010 @ 10:06pm
I used this:

el = jQuery('#empty').not('#empty');
# re: No Empty Selector in jQuery
by Rick Strahl March 30, 2010 @ 2:55am
jQuery 1.4 now returns an empty selection list for:

$()


which is a very welcome change, since it seems a heck of a lot more logical this way.

Pre 1.4 this works best:

$([])
 


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2014