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 :-}
Other Posts you might also like