4

While working on a script recently, I came across a peculiar nuance of how Sizzle works with the href attribute. Specifically, using an attribute selector on an href, Sizzle will use the actual attribute value:

// Will not find <a href="index.html">...
$('a[href="http://www.example.com/index.html"]')

Sizzle uses .getAttribute() instead of elem.href (or more precisely, elem['href'], as Sizzle does in most cases); elem.href would provide the fully-qualified URL.

To understand this a bit more, I created a fiddle to try different forms of URLs. In the process of testing, I discovered the (obvious) "solution" of setting the href equal to itself:

$('a').each(function(){
    this.href = this.href;
});

Which, unsurprisingly, updates the element to reflect the fully-qualified URL that this.href provides. There are other ways I've found that work (any that update the href attribute of an element), but they just port the above approach to other forms, or involve something like .filter() (demo):

var matches = $('a').filter(function(){
    var window_location = 'http://example.com/services.html';
    return window_location == this.href;
});

The reason I say this is that doing el.href = el.href before selecting is a workaround in a sense (and one I don't think is a great alternative). For instance, running a check on a set of elements to find if any contain a matching link to the current URL (or another URL) is simpler to do if you can:

$links.not('[href="' + window.location.href + '"]')

Is there a way to do this without having to resort to "updating" the attributes, or writing additional code to manage the check? Is there a way I've overlooked that doesn't involve modifying how Sizzle works ^?

^ Note: Modifying the actual source code would be a (bad) idea compared to just adding an expression:

$.extend($.expr[':'], {
    url: function(elem, i, match){
        return elem.href === match[3];
    }
});

var $matched = $('a:url(http://test.com/index.html)');

http://jsfiddle.net/yt2RU/

(And as an aside, are there other attributes with similar untypical behavior in Sizzle?)

Community
  • 1
  • 1
Jared Farrish
  • 48,585
  • 17
  • 95
  • 104
  • 1
    It's more efficient (and more legible, IMHO) to just use `.filter()` instead, so if you know it doesn't work like that, just don't use it for this particular case. (And no, I don't think there's any other way. Attribute selector are attribute selectors, not property selectors.) – Ry- Feb 10 '12 at 02:49
  • minitech, my question is whether I'm overlooking something. I don't know Sizzle well enough to know if I am right or not, or that using `.filter()` would be faster. – Jared Farrish Feb 10 '12 at 02:52
  • 1
    Okay. (I'm pretty sure `filter` is faster though - a simple tag selector like `a` can be optimized to `getElementsByTagName`.) – Ry- Feb 10 '12 at 02:54
  • @minitech - Also, another reason I asked the question is because when I was looking around for an answer, I couldn't find one about this specific problem I ran into. If this is right (there's no way to get it to work with fully-qualified URLs), that's fine. Hopefully it might help someone else having the same problem I did. – Jared Farrish Feb 10 '12 at 02:55
  • @minitech - And that's a good point about attribute selectors vs. property selectors. My particular issue was that I needed to block functionality on a menu depending on the `href` value and the URL, so `getElementsByTagName()` wouldn't necessarily replace a selector in that case, unless I'm not understanding the comment. – Jared Farrish Feb 10 '12 at 03:06
  • 1
    Sizzle is consistent with native browser `querySelector()` and `querySelectorAll()` methods. Updating sizzle won't help for more current browsers which use native methods. http://jsfiddle.net/gilly3/Zg2Ve/ – gilly3 Feb 10 '12 at 03:21
  • @gilly3 - I've never used those, so that's good to know. A [custom expression](http://jsfiddle.net/yt2RU/) could be added I suppose. – Jared Farrish Feb 10 '12 at 04:11
  • 1
    If you've used jQuery, you've used them, albeit indirectly. Sizzle is jQuery's fallback selector engine for browsers that don't support `querySelectorAll()` natively - so, really only IE7. – gilly3 Feb 10 '12 at 05:23
  • @gilly3 - Ok, your comment makes a lot more sense now. – Jared Farrish Feb 10 '12 at 12:43
  • @gilly3 - If either you or minitech want to add an answer with your response, I don't mind deleting my answer and marking of yours as the answer (and an upvote). My answer came from your comments, so I think it's only fair. – Jared Farrish Mar 07 '12 at 01:08
  • @minitech - If either you or gilly3 want to add an answer with your response, I don't mind deleting my answer and marking of yours as the answer (and an upvote). My answer came from your comments, so I think it's only fair. – Jared Farrish Mar 07 '12 at 01:08
  • @JaredFarrish: Nah. Your answer is original. – Ry- Mar 07 '12 at 01:36

1 Answers1

3

I believe I've got the answer to this question...

Use either an expression:

jQuery.extend(jQuery.expr[':'], {
    url: function(elem, i, match){
        return elem.href === match[3];
    }
});

var $matched = jQuery('a:url(http://test.com/index.html)');

http://jsfiddle.net/yt2RU/

Or, as it was noted in the comments, $.filter() could be used:

var $matched = $('a').filter(function(){
    if (this.href == 'http://test.com/index.html') {
        return true;
    }
    return false;
});

http://jsfiddle.net/dMuyj/

And that jQuery only falls back to Sizzle if querySelector() and querySelectorAll() are not available natively, and that both of these work the same as jQuery's selector engine (not surprising).

The sum total is, you either need an expression or a filter if you desire this type of selector, and use elem.href for the comparison, not getAttribute().

Jared Farrish
  • 48,585
  • 17
  • 95
  • 104