Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

No Empty Selector in jQuery


:P
On this page:

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 :-}

Posted in jQuery  

The Voices of Reason


 

Ariel
October 04, 2008

# re: No Empty Selector in jQuery

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.

Rick Strahl
October 04, 2008

# re: No Empty Selector in jQuery

@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...

Funka!
October 06, 2008

# re: No Empty Selector in jQuery

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

Dave
October 06, 2008

# re: No Empty Selector in jQuery

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.

Dave
October 06, 2008

# re: No Empty Selector in jQuery

Duh, I meant:

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

Otherwise you're back to the original problem!

Learning Remix, Fall 2008
October 28, 2008

# Issues with jQuery

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...

Patrick
December 09, 2008

# re: No Empty Selector in jQuery

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.

eduardo
February 15, 2010

# re: No Empty Selector in jQuery

I used this:

el = jQuery('#empty').not('#empty');

Rick Strahl
March 30, 2010

# re: No Empty Selector in jQuery

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:

$([])

user135711
April 15, 2018

# re: No Empty Selector in jQuery

@Dave's comments are great. One correction (I think) is that using $(this).parent().hide() would hide the handler or el depending on where div is appended, while the original intended to always hide the el which closable was called on. Right?


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