Opening new windows with JavaScript, version 1.2

Update: The technique described here is not ideal. Read why in New windows with JavaScript and the target attribute.

My script for opening new windows without using the target attribute became more popular than I thought it would. A lot of people commented on the post, Using JavaScript instead of target to open new windows, with suggestions for improvement.

A few days after the original post, I updated the script (see Opening new windows with JavaScript, version 1.1) , and now it's time for another update. The news is that the script now uses object literal notation for better portability and lets you choose which attribute-value pair (or pairs) should make links open in new windows.

Let's start by taking a look at the revised script, jstarget.js:

  1. /*
  2. JSTarget function by Roger Johansson,
  3. */
  4. var JSTarget = {
  5. init: function(att,val,warning) {
  6. if (document.getElementById && document.createElement && document.appendChild) {
  7. var strAtt = ((typeof att == 'undefined') || (att == null)) ? 'class' : att;
  8. var strVal = ((typeof val == 'undefined') || (val == null)) ? 'non-html' : val;
  9. var strWarning = ((typeof warning == 'undefined') || (warning == null)) ? ' (opens in a new window)' : warning;
  10. var oWarning;
  11. var arrLinks = document.getElementsByTagName('a');
  12. var oLink;
  13. var oRegExp = new RegExp("(^|\\s)" + strVal + "(\\s|$)");
  14. for (var i = 0; i < arrLinks.length; i++) {
  15. oLink = arrLinks[i];
  16. if ((strAtt == 'class') && (oRegExp.test(oLink.className)) || (oRegExp.test(oLink.getAttribute(strAtt)))) {
  17. oWarning = document.createElement("em");
  18. oWarning.appendChild( document.createTextNode(strWarning) );
  19. oLink.appendChild(oWarning);
  20. oLink.onclick = JSTarget.openWin;
  21. }
  22. oWarning = null;
  23. }
  24. }
  25. },
  26. openWin: function(e) {
  27. var event = (!e) ? window.event : e;
  28. if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return true;
  29. else {
  30. var oWin ='href'), '_blank');
  31. if (oWin) {
  32. if (oWin.focus) oWin.focus();
  33. return false;
  34. }
  35. oWin = null;
  36. return true;
  37. }
  38. },
  39. /*
  40. addEvent function from
  41. */
  42. addEvent: function(obj, type, fn) {
  43. if (obj.addEventListener)
  44. obj.addEventListener(type, fn, false);
  45. else if (obj.attachEvent) {
  46. obj["e"+type+fn] = fn;
  47. obj[type+fn] = function() {obj["e"+type+fn]( window.event );}
  48. obj.attachEvent("on"+type, obj[type+fn]);
  49. }
  50. }
  51. };
  52. JSTarget.addEvent(window, 'load', function(){JSTarget.init("rel","external"," (external website, opens in a new window)");});

I'm not going to explain every line of code in this script. Instead, here is a quick description of how to use it, along with a very simple demo page.

If you look at the last line of the script, you will notice that the JSTarget.init() function takes three parameters (all optional). This is what makes this script more flexible than the previous versions.

Several people commented that they would prefer using the rel attribute to indicate that a link should open in a new window. I do not agree with that, since the rel attribute describes the relationship between the current document and the one being linked to. If the link leads to a document on another site, using something like rel="external" would make sense. If the target document is on the same site though, I don't think it makes sense to label it as "external".

Anyway, you can now choose to use the rel attribute if you want to. To do so, call the init() function with two parameters; the name of the attribute to look for and the value that will trigger the script. You can also add a third parameter to change the text that warns the user that the link will open in a new window.

The default setup is to look for a class attribute that contains the value non-html, and insert the text " (opens in a new window)" at the end of the link. To use that, just initialise the script like this:

  1. JSTarget.addEvent(window, 'load', function(){JSTarget.init();});

To make the script look for rel attributes with the value external and insert the text " (external website, opens in a new window)", use the following:

  1. JSTarget.addEvent(window, 'load', function(){JSTarget.init("rel","external"," (external website, opens in a new window)");});

If you want to check for links with class="non-html" or rel="external", you can run the script twice, once with the default setup and once with custom parameters:

  1. JSTarget.addEvent(window, 'load', function(){JSTarget.init();});
  2. JSTarget.addEvent(window, 'load', function(){JSTarget.init("rel","external"," (external website, opens in a new window)");});

There will be problems if a link matches both cases, so avoid that situation (i.e. <a href="/" class="non-html" rel="external">).

I could have used variables inside the script to store the settings. However, since I see myself using this on CMS-driven sites I think being able to specify the parameters from the addEvent function makes it more flexible. In that situation I would move the addEvent() call out of the JS file and let the CMS write it to each page with the correct parameters (for instance using a different warning text for different languages).

As always, any suggestions for further improvement are welcome.

Update (2006-10-14): Ok, I realise now that I should have included a disclaimer about opening new windows since it's obvious that I cannot expect people to read the entire article, the previous articles, and my comments to get the whole story. So here goes.

Opening new windows should almost always be avoided. There are very few exceptions to that rule. However, sometimes it is beyond your control as a Web developer to enforce that. You can normally choose which DOCTYPE to use though. I prefer using a strict DOCTYPE, which does not allow the target attribute. And that is why I created this script. It can also be argued that opening new windows is behaviour, and as such should be moved to the behaviour layer (JavaScript) anyway.

Posted on October 12, 2006 in JavaScript