Sunday, 20 March 2011

Opening Links in a new Window in SharePoint 2010 using jQuery live() method

The Problem
I recently encountered an interesting requirement from a customer who wanted a way of ensuring that hyperlinks to external URLs would be opened in a new window.  The difficulty was that they were making heavy use of the native SharePoint Links List which unfortunately does not provide this option, unlike the Summary Links Web Part.
The reasoning behind their usage of the Links List was that the majority of their content was being stored within Autonomy Documentum, along with them needing to be able to reference external web sites, but they were using SharePoint 2010 for their Corporate Intranet.  The Links List enabled them to use a standard mechanism for specifying metadata on their links to content, whether it was stored in Documentum or elsewhere, resulting in a more structured Information Architecture that would facilitate a better search and navigation experience.
I had a few ideas of how I would want to implement this functionality and ultimately the most flexible approach would involve the ability to choose whether to open in a new window on a per Link Item basis.  On the flip-side, there was a limited amount of time available and I was conscious of the fact that a large amount of content had already been created which would have to be updated should I go for the item-by-item method.
Below are the constraints that I set myself for implementing the solution:
·         Requires no modification to core SharePoint templates.
o   Modifying core schema.xml files is not supported and is definitely not best practice!
·         Makes use of the existing Links Content Type and List Template.
o   Especially since half of the content had been created already using the above.
·         Can be applied to all existing content with minimum effort
Based upon the above constraints, the best candidate for this functionality is to use JavaScript, particularly as we are talking about an Intranet so we don’t have to be worried about users having JavaScript disabled.
Initially, whichever method I used, I had intended to locate the <a> tag for the link and set the “target” attribute to “_blank”, which is a fairly standard method.  I did a bit of Google research and found the following post which summarised a few different options, of which the first was the most simple and elegant:
The author proposes the method of using JavaScript to analyse the target URL of the <a> tag and compare it to the current window Hostname.  If the hostname of the target is different from the current window, then update the <a> tag with the appropriate “target” attribute.
Unfortunately, elegant as the above solution is, it doesn’t cater for the heavily asynchronous nature of SharePoint 2010 as the code will executed once after the page has loaded but not after any asynchronous calls have been made that may update the DOM.  In particular, the Links that I wanted to manipulate were contained within a Grouped View of the SharePoint List View Web Part, which uses AJAX to populate the contents once the group is expanded.
The Answer
I spent some time researching the problem and investigated the possibility of updating the event handler that makes the AJAX call, however, this became quite messy, involving some “eval()” code which generally doesn’t sit well with most developers.
What I wanted was a mechanism that could do one of the following:
·         Raise an event when any AJAX call has been completed
o   Doesn’t appear to be available
·         Raise an event when the DOM has been modified
o   Not supported in IE
o   Refer to this post for an excellent comparison of supported events from the W3C specification: http://www.quirksmode.org/dom/events/index.html
·         Bind events to DOM objects that don’t exist yet (i.e. the <a> tags)
o   Surely this must be impossible?!! …
Low and behold, I found exactly what I was looking for in the fantastic jQuery library (for those of you who do web development but haven’t started using jQuery yet, THIS LIBRARY ROCKS!!!).  To use the jQuery library, download from the link below and store in a sensible location within SharePoint (such as a subfolder within the LAYOUTS directory) and modify your SharePoint Masterpage to reference the library.
jQuery contains a method called “.live()” which enables you to perform the 3rd mechanism in the list above.  Refer to the following article for a detailed explanation of how this works, but to summarise, it works by a process of event delegation.  This means that a specific DOM container can handle events of a specific type that bubble up from the container’s subtree, effectively allowing me to bind the “click” event to <a> tags that will be created asynchronously.
Bringing it all together
Now I had a workable mechanism, all I had to do was write the appropriate jQuery code.  What I came up with is shown below:
$(document).ready(function() {
     $(
"#MSO_ContentTable").find("table.ms-listviewtable").each(function() {
          $(
"tbody td.ms-vb2 a", $(this)).live('click'function() {
               
var thisDomain = window.location.hostname;
               
var theHref = $(this).attr("href");
        
               
if (theHref.indexOf(thisDomain) != -1 || theHref.indexOf("://"== -1) {
                    window.location.href 
= theHref;
               }
               
else {
                    window.open(theHref);
               }
        
               
return false;
          });
     });
});
There are a couple of points that I need to explain about the above:
1.   The above code assumes that a DOM element with an id of “MSO_ContentTable” exists on the page. 
a.    This should be the case if you have adapted the SharePoint Masterpage v4.master.
b.    If you have created a completely custom Masterpage then wrap a <div> with an id attribute around the content placeholder “PlaceHolderMain” and substitute $(“#MSO_ContentTable”) with $(“#MyCustomContainer”)
2.   Only Links within a List View Web Part are targeted by selecting the <table> tag with a class of “ms-listviewtable” applied.
3.   The <table> above will act as the context for the .live() method for each List View Web Part, ensuring that the “click” event does not bubble up any further than is required.
4.   The URL comparison allows relative URLs (i.e. relative to the current site) to be excluded.
In conclusion, jQuery is an extremely powerful, easy to use and relatively lightweight library compared to some other libraries (you know I’m talking about you ASP.NET AJAX!).  Once you get your head round it, it can dramatically reduce the amount of code that you need to write and greatly simplify the process of working with the DOM.  Without the .live() method, I would have struggled to achieve what should have been a fairly simple task and I think that this method may see more use within SharePoint 2010 considering the much wider usage of AJAX calls.

5 comments:

  1. I'm having a bit of trouble getting this to work...

    I already have my jquery file link on the master page as shown here:

    jquery-1.6.1.min.js" type="text/javascript"

    but where exactly do I insert the jquery code? Do I put it inside a .js file and have that linked to the master page as well or should this go somewhere else?

    Thanks so much

    ReplyDelete
  2. Ben - I have a need for this exact problem your attempting to address but I'm struggling to implement. I have downloaded and added jquery-1.5.2.min.js in the layouts directory (14 hive). I've referenced the file from my master page in the head tag.


    I have the following DOM element in my master page:

    MSO_ContentTable

    And I have added the following jQuery code just above the closing body tag in my master page:


    $(document).ready(function() {
    $("#MSO_ContentTable").find("table.ms-listviewtable").each(function() {
    $("tbody td.ms-vb2 a", $(this)).live('click', function() {
    var thisDomain = window.location.hostname;
    var theHref = $(this).attr("href");

    if (theHref.indexOf(thisDomain) != -1 || theHref.indexOf("://") == -1) {
    window.location.href = theHref;
    }
    else {
    window.open(theHref);
    }

    return false;
    });
    });
    });


    I've done a view source on the page and I'm finding the DOM elements in the jquery script above, but it's still not opening an external link in the list in a new window. Any suggestions would be greatly appreciated. Thanks in advance. -Ian

    ReplyDelete
  3. Hi there, sorry for not replying, haven't really been checking my blog for a while!

    Steve: Yes you should really create a separate .js file with the method, link it to masterpage, but make sure it is loaded after the main jQuery file otherwise it won't work.

    Anonymous: Can't really say what could be wrong, if it is still problem then you could send me your masterpage because it will depend on your DOM and how the jQuery is loaded.

    ReplyDelete
  4. Not being able to open links in a new window was a real issue for me. I have just spent some time implementing this but it was worth the time. It was just a matter of trial and error and some minor edits to your code.

    I first tried loading from Google (http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js) but that wouldn't work. So I downloaded the .js file and added it to my layouts area. Also remember to check in and approve that file.

    I then pasted your code just before the closing body tag. Here is a copy of the code:
    ...opening tag here... src="/_catalogs/masterpage/jquery-1.7.1.min.js" type="text/javascript"/>
    ...openingtag here... type="text/javascript">
    $(document).ready(function() {
    $("#MSO_ContentDiv").find("table.s4-wpTopTable").each(function() {
    $("tbody td div.link-item a", $(this)).live('click', function() {
    var thisDomain = window.location.hostname;
    var theHref = $(this).attr("href");

    if (theHref.indexOf(thisDomain) != -1 || theHref.indexOf("://") == -1) {
    window.location.href = theHref;
    }
    else {
    window.open(theHref);
    }

    return false;
    });
    });
    });
    ... closing tag here

    Note that I have edited the container ID name, table class and link class to match my page. Making those edits made it work. Remember to replace opening/closing tag comments above with actual tag.

    ReplyDelete
  5. The simple and easiest solution is: Create a Site column of "Hyperlink with formatting and constraints for publishing" type and add it in your list, which enables you to specify "Open in New Window" Option!

    There are few more possible solutions, including JavaScript, JQuery, SharePoint Designer, List Schema Edit to make SharePoint Link list open in new window at SharePointDiary.com -

    SharePoint Link list: Open in a New Window

    ReplyDelete