Quick Tip: Dynamically add an icon for external links
read 57 commentsA common feature I've seen on “web 2.0” sites and wikis is the "external link" icon:
. While I'm not crazy about the idea of sticking these little images all over the HTML, they're a great candidate for using progressive enhancement. In our case, we can use jQuery to add the images pretty easily.
Test the hostname
To identify the external links, we test for the location.hostname against the link's hostname, which will be represented by this.hostname once we have the selector in place, and make sure the two don't match. We should also test for the mere existence of this.hostname to avoid problems or false positives with "mailto" links. our tests will look like this: this.hostname && location.hostname !== this.hostname.
Use the filter function
Now let's wrap that test in a filter function. For our example, we'll test all links inside an "extlinks" element that match the above test. Here is what it looks like:
A $(document).ready() is wrapped around the script so that it will execute when the DOM has loaded. Line 4 shows the insertion of the image after each of the external links.
Demo
Here is a little demo using the above code. Of the three links, only the second points to a different site. We should see the external-link icon next to it.
So, that's it. Short and sweet.
















Personally I prefer doing this with pure CSS (using background images and padding with a special class, which is what MediaWiki [for example] does), but JavaScript is another way to go, I suppose.
@Voyagerfan, using css is great if all browsers could support a[href^=http].
Is this also possible for set the favicon on the externel link?
If you wanted to implement a CSS version then this is simply a case of doing the following:
And the CSS would go something like:
@Voyagerfan5761: the only way to do this consistently across browsers without using JavaScript is to add a class to the link manually or write some sort of server-side script that will do it for you. Doing it manually isn't always practicable, especially for content-managed sites. But if you have total control of the site's HTML, and you're vigilant about adding those classes, then pure CSS is probably the way to go.
@James Chan: in a content-managed situation, the
a[href^=http], while certainly a good start, isn't completely reliable. What's to stop someone from adding the fully qualified URL to a same-site link? If the attribute selector were bulletproof, it would certainly be preferable to the filter function I recommend in the post.@Ben: yep, that's another way to do it for sure. Not "pure CSS" as Voyagerfan5761 was suggesting, but definitely a nice option. If your links have text decoration, the underline will extend underneath the icon, so it might not be a viable option, depending on the look you're trying to achieve.
@Frank: sure, it's possible. Google is your friend here. First result for "external link" favicon is for a jQuery external link favicon plugin.
I played around with this task a while back and came up with an exhaustive, js-based solution which accounts for accessibility and additional usability, with a built in new window toggler.
More recently, I added a jQuery-based version.
So, fwiw…
Flag & toggle external links with jQuery
It hasn't been touched for quite a while, so may not be taking advantage of recent improvements in the jQuery library.
There's also the straight css way. Doesn't work in old browsers:
a[rel="external"]{
background:url(images/external.png) top right no-repeat;
padding: 12px 0 0 0;
}
Of course that means you'd have to add the rel to your links. But I'm a fan of rel, and you could add the rel with jquery if you wish.
In a perfect world.
I like the adding a class part you mention in comment #4. Gives me a lot more control. It does not work in IE6/7 though for me.
Karl,
Will calling after('') for each external link result in a new image download for each link? I could see this being an expensive process for a site with 10-15 external links on it, or even more for that matter. I wonder if generating that image and storing it in a var outside of the filter call would be a speedier solution?
Thanks for the article. Pax,
- Stan
@Karl Swedberg: I use the CSS suggested by Ben for links that have :hover text-decoration set, and the underline doesn't go past the end of the text. Is the behavior of that different in browsers like IE7 (which I don't have)?
@Jethro Larson: The rel method works, but if you're adding something using JavaScript it won't show up to robots, so really you're using the same method as I do (and what Ben suggested), just with a different attribute of the <a> tag.
@Bill Posters: Looks like an interesting idea there. I'll have to remember that in case I ever want or need that behavior for a future site. Thanks!
@Voyagerfan5761: hmm. Could be that my recollection was faulty. Regardless, if you're using JavaScript to display the icon, does it really matter if you're using a CSS background image or inserting the image into the DOM? As I already said, there isn't a pure CSS way (that I know of) to show the icons without adding a class manually or having some server-side script take care of it—not cross-browser anyway.
Seems to me that for accessibility reasons, it might actually be preferable to insert the image rather than use a background image, because then screen readers can read something like "(external)" from the
altattribute.Of course, I could be wrong about all this. Or, it could be just a matter of taste. I'm interested in hearing your reasons for preferring the CSS background image method.
@Karl Swedberg: I tend to prefer doing things without JavaScript whenever possible. If I can make something work in a browser whose user has disabled JS, why do it in a way that would deny that user the experience? People do browse without JS (though I can't understand why ;-), and I want them to get as much of the experience as possible.
With regards to accessibility: At least on the main site I work on, having a screen reader read out "(external)" isn't important, because the site uses external links that are clearly external from the context.
I think it's a matter of personal preference and site-specific reasoning. Your preferred method of doing things would make perfect sense to me if the site involved was different. For example, I could see how it could be a good thing to do here on Learning jQuery because it would provide a practical example of jQuery code.
@Voyagerfan5761: Yes, I prefer doing things without JavaScript whenever possible, too. But, as I've noted more than once now, it's not possible to do this with pure CSS in a way that IE6 will understand, unless you manually add a class (or perhaps use some server-side logic).
You say that you use the CSS suggested by Ben. That's great. But Ben is applying that class with JavaScript.
It should be pretty obvious from my posts here as well as from my books and my suggestions on the jQuery discussion list that I don't advocate using JavaScript for presentation when a cross-browser, pure-CSS solution is available.
Still, do you agree that there are times when pure CSS is insufficient for this task? If not, show me the pure CSS selector that will add a background image to all external links on a page — keeping in mind that many sites are content-managed by less-technical users who won't be adding a class manually.
@Karl Swedberg: Yes, there are times when pure CSS is insufficient for a lot of things. This is only one of them. I'm just saying that I haven't found any situations in which my method has proven to be inadequate. Not to say that such situations don't exist. ;-)
Re your views being obvious, I honestly haven't been following the comments here much, nor do I regularly read the jQuery mailing list. I'm also sorry to say that I haven't bought any Web development books in over a year, and the last ones were on the subject of PHP. So I didn't know of your similar preferences. But I do now. :-)
great tip! muy bien!
I want to not display the external image if the link or tag contian the tag of image (img)
how do you do for make it?
sorry for my bad english...
Hi Mte90, you just need to make the selector more specific. So, instead of, for example,
$('#extlinks a'), you could do something like,$('#extlinks a:not(:has(img))'). Hope that helps.Thanks!
It's perfect!
Hey! This is basically awesome, thanks. One thing I'm wondering about, though, is how easy it would be to add target="_blank" inside the <a> tag as well as the image after it.
I know very little JavaScript, and even less jQuery, but it seems to me that if there is a .after possibility, there must be some sort of .inside equivalent. Trouble is, "after" isn't a word you can just search for in Google. Does anybody here know how to make this happen?
Cheers!
Matt
Hi Matt!
Use the
.attr()method:.after( ... ).attr('target', '_blank');One other thing, Matt -- even though I'm not crazy about the idea of forcing a new window to appear, you can certainly do it without the
target="_blank". Something like this should work:Thanks a lot, Karl! Yeah, I know I know, user-customizability, etc. The website I work on is not my own though, and I have to do what I'm told. We're in the lengthy process of manually adding
target="_blank"to every external link, so this solution is a lot better, thank you!Hey Matt! Glad the solution is going to work for you. I definitely understand the bind you are in with external constraints determining what you do with the site. I just felt the need to put in that note about forcing a new window to avoid the wrath of others who might decry such an outrage.
Karl - great tip!
I'm using this on a web site I've built successfully using your method. One thing- how would you make it so the icon is part of the link so when a user hovers over the icon or the link they are both clickable? Just like is done on Wikipedia and MediaWiki? Thanks in advance.
Hi James,
You would do it the same way, except instead of using
.after(), you would use.append():That's great Karl, couldn't have been simpler. Thanks for that simple tip. However, is there a way to make it so the icon is not underlined. I should have stated better how I have my links setup. text-decoration is underline by default and on hover text-decoration is none. Was kinda hoping I could not have the icons underlined at all. I want them just hanging out there as an afterthought, no decoration.
Hi James,
I suppose the best way, in that case, is to simply set a class on the link and then use CSS to position the icon as a background image. So, for the jQuery:
And for the CSS, something like this should do:
Karl,
Great tip, but I found an issue with non-standard ports and Safari (mac)...
I am using mampstack (bitnami.org) for my development server, and it runs apache on port 8080, so I connect to my page using http://localhost:8080.
Using this as my test HTML:
<p><a href="http://www.yahoo.com">Yahoo</a></p>
<p><a href="/test">Test</a></p>
The first link should have class="external" added, but in Safari (mac) I found that both links were getting the class added. With a few alert statements, I found that in Safari (mac)
this.hostnamereturns localhost:8080 andlocation.hostnamereturns localhost.Obviously, this makes the script flag the second link as external when it shouldn't.
Note, this is only an issue in Safari (mac) (FF and IE are fine) and ONLY if you specify a port in the URL
To fix it, I made this change:
return this.hostname && this.hostname !== location.hostname;becomes:
return this.hostname && (this.hostname).split(":")[0] !== (location.hostname).split(":")[0];This way the script only looks at the hostname and discards the port.
Just thought I'd share this in case anyone else had the same problem, and see if anyone has a different approach.
Josh,
Thanks a lot for that tip. Looks like another edge case I'll have to take into account.
Okay, so my question would be: How do you do this, but apply it to only text links. In other words, make it ignore links that might be wrapped around images.
Hi Michael,
You can specify that in the selector. Something like this should work:
Thanks, that seems to have gotten me partway there, but it doesn't seem to be ignoring images quite right. I have:
Any thoughts on what I'm doing wrong here?
Nevermind, I got it!, it was:
$(this).not(":has(img)").addClass('psuExternalLink');Thanks for the great tip.
Personally I think i will implement and use this icon only when a external link is opened in a new window. As mentioned this isn't liked by many people, so an icon to show the use of this action would be very helpful.
Where the hell do I put the .js code? I can't get this to work...
Nazim, how about asking a bit more politely next time?
You can put the .js code in the <head> in a separate file and then include that file in a <script> tag. You'll always need to include the jQuery core file first. Here is an example (you'll have to change the paths/file names to match the ones particular to your server):
Hi Karl, sorry about that... Thanks for the response! I've done what you said, but still can't get it to work..Do I neeed to wrap the link in a class as well? Cheers.
I just put a separate demo page up for you. Just view source to see the markup.
Hi Karl,
I am using your very useful script on one site and I wonder know how to add between anchor and external image non breakable space in order to not divide it in end of the line.
I have tried this
but it doesn't work.
Could you please help? I am not so familiar with javascript.
Instead of putting a space there, non-breaking or otherwise, you could just add margin-left to the image. But still, since the image is another inline element, I'm not sure how you can prevent it from breaking to a new line. Anyone else have any ideas?
Hi Karl, thanks a mill for that! I totally understand it now, and am loving it! Cheers!
Late reply, I know, but you could include
white-space: nowrap;with your a.external style, with the caveat that long links could break your design somewhere.
Another option
http://snahta.blogspot.com/2009/07/links-list-external-link-in-new-windows.html
Karl - From a useability POV - as a visitor, I don't really care if a link is "external" or "internal". (It seems a very author-centric feature and the graphic just makes reading text more difficult).
However, I can see using the icon to differentiate between opening links in a "new window" (i.e., clicking the linked word opens the link in the same window and clicking the icon opens the link in a new window).
This behavior might be differentiated upon internal/external links, but there remains some situations in which you'd want to defeat that, so the method would need to have some HTML override (certain class name or rel value, perhaps).
Not being a jQuery pro, how difficult would it be to construct such an animal?
(Of course, not everyone has JS enabled, but it seems the method degrades nicely to "normal link behavior").
Cheers,
-stk
I was always curious about this. Now I see how easy it is. Thank you guys, will tell it to friends who asked before...
Hi Karl, great tip, thx.
I got in trouble wince in my web page it doesn't work. Is there any reason about that?
You can see the page here: http://www.4sct.net/piloti_wb.php
Look at the web link close to tartar username
Hi Frederico,
Yes, the problem is that you are using the same selector that I used in my demonstration:
$('#extlinks a'). Since your links do not have a containing element with id="extlinks" it isn't matching anything. You might want to try$('.pilot a')instead.Got it working fine now :D. Thanks a lot for help and feedback.
Hello, not sure if you still reading this post, but I will get a chance...
I would like to use your code for my website. However, Im not really familiar with the jQuery yet.
Here is my problem. I have some external link that I do not want anchor next to them. I would like to use a CLASS inside the A tag to identifiate them... ex :
<a href=www.externalsite.com class="noAnchor">LinkHere</a>
I also do not want to have the anchor on the Image link.
And I want to add _Blank to those link
Here is the code I tought that would work...
$(document).ready(function() { $('a:not((.nojqueryanchor)|(:not(:has(img)))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});The part with ":not(:has(img)) work perfectly... but When i want to add a 2nd condition, this do not work anymore...
this code work perfectly...
$(document).ready(function() { $('a:not(:has(img))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});but it add the icon on EVERY link on my website... and instead of putting a class inside all link that I want an icon... i would like to add a class inside all link that i dont want and icon..
any clues ? thanks
I think I got the solution. This look like working perfectly. Maybe there is a shortcut.
$(document).ready(function() { $('a:not(.nojqueryanchor):not(:has(img))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});$(document).ready(function() { $('a:not(.nojqueryanchor):not(:has(img))').filter(function() { return this.hostname && this.hostname !== location.hostname; }).after(' ').attr('target', '_blank');});This is my latest code. Unfortunatly, im getting an error about "has" that is malformed, in the Firefox's console.
Do i need to add something ? like....
:not(":has(img)")... or:not(":has("img")")Thanks
this is the error message i got.
Warning: Unknown pseudo-class or pseudo-element 'has'. Malformed simple selector as negation pseudo-class argument 'has'.
On some forum, peoples are saying this :
it's not an error, it's just a warning.
And this particular warning tells us that Firefox is not aware of
jQuery selectors, at least not of the :gt() selector, but, as long as
you know that it is a valid selector (and it works), you should just
ignore the warning....
Is there something we can do for this ? or just wait for the jQuery 1.5 to solve that issue ? or maybe next version of FireFox...
Thanks
My recommendation is to take the advice from the people on that forum: ignore those Firefox warnings. They're meaningless within the context of JavaScript.
@Karl - thanks for the non-image-containing-link selector. Tried every syntax EXCEPT that one ;)
Hi Karl, another great post from you! keep up with the good work.. I have a question regarding this post, lets say I have a subdomain of help.whatever.com and I don't want the external link icon to appear on links of my main domain which could be http://www.whatever.com I want them both to be consider as one domain.. How can I do that? Any recommendation?
Cheers,
E.K
Hi there,
You could write a little function that strips the subdomain before comparing. The function could look something like this:
Then your return statement would look like this:
Works as expected, thanks a lot!!