views:

701

answers:

13

Was recently using some code along the lines of

$("#divMenuContainer:visible").hide("explode");

However after some time spent trying to get it to work I realized my selector was referencing a div that didnt exist.

The result of the query was simply that it didn’t execute.

Obviously this is by design, could anyone explain the logic of why this design choice was made rather than raise some sort of exception?

Not trying to criticise just trying to understand.

+3  A: 

It's part of jQuerys (actually John Resigs I guess) philosophy about the library.

It trys to be "kind" to you and your customers, that in turn means, it'll throw exceptions pretty rarely
(like really rarely)

But like always, you can easily extend it like:

(function(_jQuery){
    jQuery = function(){
        var ret = _jQuery.apply(this, arguments);
        if(!ret.length) throw new Error('empty selector');
        return ret;
    };
}(jQuery));

But like Nick says in a comment, most times this is not a desired behavior. If you want to have it for some reason anyway, a code snippet like the above should do it.

jAndy
With such a development philosophy, there should imperatively be two modes, one that is lenient (production) and one that throws early and often (for debugging). Otherwise, how can a sane person do any debugging? I don’t know jQuery at all – maybe such a mode exists?
Konrad Rudolph
@Konrad: Just put the above code into a file 'debug.js' and only include it in your source during dev. This sort of thing is a common pattern.
thenduks
@Konrad: an unmatched selector isn't, in and of itself, an error. It's not like, say, HTML where there's a (reasonably) strict standard and lenient browsers that don't force compliance - empty sets are a useful and relied-upon behavior in many (if not most) programs. If your program will malfunction if a selector isn't matched, then the onus is on *you* to verify that it is.
Shog9
+2  A: 

because $("#thisdivdoesntexist") in jQuery still returns an 'empty' jQuery Object and all jQuery objects have their methods, so no error.

this is actually a good thing. If this were to throw an error, you would need some extra checks in a lot of cases before doing something, meaning you'd have a lot of 'overload' code. although it would not be bad practice to check if it exists before calling a method on the object, when the selector returns nothing, not all javascript will halt (which is what would happen if it'd throw an error)

therefore you can use selectors globally, even if some pages don't have the selector, without really having to worry about that.

Stefanvds
Yes, `$('something')` always returns a jQuery object, in the case of `$("#thisdivdoesntexist")`, the object has 0 elements.
Rocket
anyways, of course this is a call to `jQuerys constructor function`, but this probably should optionally throw a warning at least.
jAndy
how would you throw a warning?
Stefanvds
A: 

I think that it is probably to do with the fact that your selector:

$("#divMenuContainer:visible")

Under the covers returns a jQuery object, containing a number of possible matches. The Hide function is then performed on each of these. I imagine not throwing an exception in this case makes a certain kind of sense, as you have a list with zero entries, rather than getting a null back.

Paddy
+3  A: 

A selector that doesn't refer to any elements is still a legal selector, and it may be intentional. It may be that a given selector will sometimes return elements, and you would want to be able to use such a selector without a chance to throw runtime errors.

recursive
+5  A: 

jQuery() will always return a jQuery object, for one to prevent errors, but more importantly:

So you can write responsive code.

do X if Y is present

If Y is not present, X does not compute.

This means you can have a global init cross pages and just init plugins wether they find something or not.

$(function(){
    // does nothing if the page contains no element with className 'accordion'
    $('.accordion').implementAccordion();
    // usually on a single page, but we can add it to a global js file nontheless.
    $('.validate-form').implementFormValidator();
});

Though ofcourse, some plugins are really poorly written and will throw an error.

BGerrissen
A: 

It just makes sense to me... If your selector doesn't match any element, you can still call all the normal jQuery prototype functions without generating errors! The worst case, you end up with an 'empty' jQuery set, applying your changes to no elements.

gnarf
A: 

Normally I only continue if the element(s) exist by doing something along the lines of:

var aThing = $("#myElement");
if(aThing.length){
    //my code here
}
Chris Barr
+31  A: 

There are a few good reasons here, "chainability" is the main drive, the ability to write very terse code by chaining has to throw no errors to work seemlessly, for example:

$("#divMenuContainer:visible").hide("explode").add("#another").fadeIn();

Each object in the chain, even if it references no DOM elements may have more added later, or let's take another example:

$("#divMenuContainer:visible").live("click", function() { ... });

In this case we don't care about any of the elements the selector found, we care about the selector itself. Here's another:

$("#divMenuContainer:visible").find(".child").hide("explode").end().fadeOut();

Even if there are no children, we may want to hop back in the chain afterwards, continuing to use the .prevObject reference to go back up the chain.

There are dozens of distinct cases like this that show the benefits of the library being the way it is. As for the why, from interviews of John Resig, who is the creator of jQuery, he states that's just how it worked out. He was after code as terse as he could get it, and the chaining model is what came out of hat, it just happens to have a lot of benefits as well, the example above are just a few of those.

To be clear, I'm not saying every attribute of chaining is a good one, there are just many upsides to it.


Let's take this page as an example, what if we had something like this:

$(".comment").click(replyToFunction);

Should that fail because there aren't any comments yet? Well no not really, that's expected, I wouldn't want an error here...if the element exists do it, if not don't. My point is, at least in my experience, not throwing an error because of a missing element is tremendously more useful than throwing one.

The selector in your question, the #IDselector is a very special case where you expect only a single element, so maybe you could argue it should fail there...but then that wouldn't be consistent with other selectors, and you want a library to be consistent.

With pretty much any other selector you expect 0-many elements, so failing when you don't find any elements would be significantly less desirable in most situations, even more so in the cases like .live() above.

Nick Craver
The flipside of this coin is that, if you want to do something in the case where an element is not present, you're left with lots of checks for empty selector results. Of course, one does not want to do this. If an element on a page is missing while it shouldn't, the output of your backend is wrong. This can be caught by testing the output for certain conditions.
Cthulhu
@Cthulhu - If you want to check, `if(!$("selector").length) { }` is a pretty short way to do so :) You could also define a static method for something like this.
Nick Craver
It might be useful to mention the null object pattern.
Matt Ball
@Bears - For *some* examples it fits this pattern, for others, like `.live()` we don't even care about the DOM elements inside...it's not that the list is null or empty, we just don't care about it. So some things with chaining use it, some don't, sometimes they're using other properties (`.selector` and `.context` in this case, `.prevObject` in others).
Nick Craver
Fair enough - though I think `.live()` is primarily a special case.
Matt Ball
This discussion suggests to me that there ought to be a chainable way of checking the number of results. Perhaps `$("selector").require(1).hide()...`? I'm thinking of that throwing an exception if it matches less than 1 result, so you may also want ranges `require(1,3)`. I'd be tempted to make `require(0)` an alias for `require(0,0)` since it isn't useful by itself anyway, but that's a bad idea (think `require($n)`).
sfink
@sfink - Sure if you wanted that you could, it'd look like this: `jQuery.fn.require = ​function(num, num2) { if(this.length < num) $.error("At least " + num + " elements are required"); if(num2 };` You can test/play with it here: http://jsfiddle.net/nick_craver/VrXmc/ check the console for errors.
Nick Craver
+30  A: 

Think of it as a query, which it is. You are asking for all "records" (DOM elements) that match your criteria. The result is a set of zero records.

It then loops over your zero records and applies the action to them. :)

If you did the same thing with SQL, or an array, it would behave the same way in most languages. A collection of zero records is not an error state.

Matt Sherman
The lack of an object is as informative as its presence ;) Just need to design for this.
danp
@danp how profound!
Here Be Wolves
A: 

A good example is when you want to do something with all the checked checkboxes

$("input:checked")

...you don't know how many are checked. Could be any, all or none. It depends on the user.

So, instead of having to write code like

var checkedInputs = $("input:checked");
if (checkedInputs  && checkedInputs .length > 0) {
      checkedInputs .doStuff();
}

You can just

$("input:checked").doStuff();

And if they have made selections, great, stuff gets done. If not... no harm, no foul.

Chad
FYI - The first test in your `if()` statement will always evaluate to `true`, so you could simply do: `if(checkedInputs.length > 0) {...`. Or since any number greater than `0` equates to `true`, you can get it down to `if(checkedInputs.length) {...`. :o)
patrick dw
@patrick dw, yes, I know, in reality it always evaluates to true. But that is because jQuery always returns a valid jQuery object. If, the selector "bombed" and it instead was null, as the question alludes too, you would need to write code in that fashion.
Chad
A: 

i guess because it'll be used on the front-end - end users shouldn't see exceptions because a developer has written some bad code. it's probably generally safer to fail silently in this case.

PeterJ
+3  A: 

Its a matter of flexibility. Myself I'd like the same protections you ask for, you can always do it yourself. Use:

jQuery.fn.single = function() {
    if (this.length != 1) {
        throw new Error("Expected 1 matching element, found " + this.length);
    }

    return this;
};

and now use $("input:checked").single() with assurance that its either returning a single item or giving you an error.

Frank Schwieterman
A: 

I thought it was to be like CSS, meaning if you are trying to style an element that does not exist, you get no error.

John Isaacks