Login or Sign up

Remove links that point to the current page with jQuery

Posted by: skyl on Feb. 9, 2010

I hate links that go to the current page with no change. I can refresh my browser without clicking one of those. For instance, if you go to http://skyl.org/ you can click the skyl.org logo, it reloads the same page. What good is that? It can be even stupider of you have auto-generated links and text that doesn't know about its current url. So, we want to remove these dumb non-links. This task is pretty convenient (if still not perfectly solved by me) using jquery selectors!

// href ends with the current location
$('a[href$='+window.location.pathname+']').each( function(){
    $(this).replaceWith($(this).html());
});

That's okay (not really, but let's pretend). But, if we run that on a page with a link in the nav that has some style, we might break our layout. We can use the .not() function which jquery provides:

// href ends with the current location but anchor not in nav
$('a[href$='+window.location.pathname+']')
        .not('#subnav a').each( function(){
    $(this).replaceWith($(this).html());
});

window.location.pathname does not include any querystring and the selectors do not take into account a querystring. So, if an href attribute has a querystring, the anchor tag will not be selected and removed. Further, if the last part of the href's querystring matches the current location, it will be erroneously selected. For instance, I am now at /log/edit/41/ which would be the {{ request.path }} in the Django template. The logout link on this site will always attempt to return you to the current page by using the next argument in the querystring of the link. On this page, it renders to /account/logout/?next=/log/edit/41/. As you can see, the end of that href is the same as my current path. So, we would select that link and remove it which is not what we want to do. To get the full pathname, we want to use window.location.pathname + window.location.search.substring(), the full path with querystring of the current page. This is similar (identical in result?) to Django's request.get_full_path() method. So, our final case is to match all anchor's whose href attribute ends exactly with our full path. After all, we can assume that there might be something meaningful going on if we link to the same place (window.location.pathname) that has a different querystring (window.location.search.substring()). This method neglects to babysit the trailing slash, however. So, we leave it to the rest of the code and markup to be consistent in this regard.

// href ends with current location full path
$('a[href$='+window.location.pathname+window.location.search.substring()+']')
        .not('#subnav a').not('#right_tabs a').each( function(){
    $(this).replaceWith($(this).html());
});

Oh no! More bad logic. If the current location is just /, every link with a trailing slash is removed! We can't effectively use the href$= to search that the href ends with attributes of the current location. We are left to catch that the href exactly matches either the full path with protocol, domain, path and querystring or the absolute url made of the path and querystring. So, we change the script to read:

// href matches the path+querystring
$('a[href='+window.location.pathname+window.location.search.substring()+']')
        .not('#subnav a').not('#right_tabs a').each( function(){
    $(this).replaceWith($(this).html());
});

// href matches the fqdn + path + querystring (window.location.href)
$('a[href='+window.location.href+']')
        .not('#subnav a').not('#right_tabs a').each( function(){
    $(this).replaceWith($(this).html());
});

Surely jquery has a way to combine those into one that will be more elegant and efficient. Surely indeed, we can use .filter(). The basic idea is that we can replace $('a[foo=bar]') with $('a').filter('[foo=bar]'). We can also do two attributes such that if either match the element will be selected like $('a').filter('[b=c],[d=e]'). This will select anchor elements that have a b attr that equals c or a d attribute that equals e. Now are script is down to one statement:

$('a').filter(
    '[href='+window.location.href+'],[href='
    +window.location.pathname+window.location.search.substring()+
    ']'
).each( function(){
    $(this).replaceWith($(this).html());
});

Man, I wish I could figure out a more elegant way to format that selector string as in Python. We can look here. That's a task for another post though:

<inimino> skyl: in mine you'd do expandTemplate({s1:'foo',s2:'bar'})("$s1$ is the new $s2$")
<inimino> skyl: but you can make a printf-style API if you prefer

One potentially cool feature would be to mirror the rules for the anchor tags with a css class so that we could wrap <span class='a'></span> around the html where we are removing the anchor tags so that we could modify all of the relevant tags (without the .not()) but leave the important style bits the same. We don't have time for that right now either. So, we hope that the above is sufficient.

Comments on This Post:

Please Login (or Sign Up) to leave a comment