Tuesday, February 21, 2012

Add global Link using Delegate Control

This article will give a simple example "or remember" on how you can use the Delegate controls to change Layout or simple add a global link in all SharePoint Sites without changing the Masterpages.
When i had 9 year old i was learning on how to make a newspaper article and teach me i should always answer the following questions:
  • What?
  • Who?
  • Where?
  • When?
  • Why?
  • How?
The most 6 important questions you must ask yourself every time one person request you something that i incorporate when i am working with SharePoint.

Who?
Delegate Control already exist from older versions of SharePoint and can be very usefull when we want to create global Action that have impact in one or more SharePoint Sites.
There are a lot of articles talking on how we can use Delegate control "example" and don't wan't to repeat and make more "Social 2.0 noise...".

I already see a lot of article explaining on how easy is to Create/change global controls but you need to be carefull and think what are the consequence of this changes,
By default some of this delegates control already have features associated and if you want to change you will need to be carefull to don't deactivate or remove the OOB actions associated, for example if you change or add features in the delegate control "GlobalSiteLink3,GlobalSiteLink2"

If you access the Element file "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\SocialRibbonControl\SocialDelegateControl.xml" you can see this control already have actions associated and if you change or add new control you can broke the normal behavior of SharePoint Sites.

Content of "DelegateControl.xml":
<Control Id="GlobalSiteLink3" Sequence="100"
  ControlClass="Microsoft.SharePoint.Portal.WebControls.SocialNotificationControl"
  ControlAssembly="Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
 <Control Id="GlobalSiteLink3-mini" Sequence="100"
  ControlClass="Microsoft.SharePoint.Portal.WebControls.SocialNotificationMiniControl"
  ControlAssembly="Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
 <Control Id="GlobalSiteLink2" Sequence="100" ControlSrc="~/_controltemplates/socialdata.ascx" />
 <Control Id="GlobalSiteLink2-mini" Sequence="100"
  ControlClass="Microsoft.SharePoint.Portal.WebControls.SocialNavigationControl"
  ControlAssembly="Microsoft.SharePoint.Portal, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />

But for this article i want to make focus in the "AdditionalPageHead" control.
This control is somehow more special then others because one property "AllowMultipleControls".

This property give the ability to add multiple controls without Deactivating/overwrite existing OOB controls, giving more flexibility injecting ECMAScript and users/custom Controls.

 Some Out of the Box Delegate Controls:







  • AdditionalPageHead - Delegate controlo allocated in top site - AllowMultipleControls
  • GlobalSiteLink0
  • GlobalSiteLink1
  • GlobalSiteLink2
  • SmallSearchInputBox
  • TopNavigationDataSource
  • PublishingConsole
  • QuickLaunchDataSource

  • What?
     I want to create a global link in top left of Welcome Control to be accesible in all SharePoint Sites.


    For this change was used the Internet explorer developer Tool to detect how the Welcome Control was added and inject some Javascript to make the change using  firebug.


    After the Script is created, make a litle test in the current SharePoint Page, edit the Page and add Form Web Part and inject the following Javascript:

    <script type="text/javascript">
        ExecuteOrDelayUntilScriptLoaded(LoadLink, "sp.js");
    
        function LoadLink() {
            var myMenuContainer = document.getElementById('RibbonContainer-TabRowRight').children[2].children[0];
            var newSpan = document.createElement("span");
    
            newSpan.innerHTML = '<a class="ms-menu-a"  style="cursor:pointer;white-space:nowrap;"   href="#" onclick="window.location=\'http://www.sharepointpt.org\';return false;"><span>SharePoint PT</span></a>';
            newSpan.className = 'ms-SPLink ms-SpLinkButtonInActive ms-welcomeMenu';
    
            myMenuContainer.insertBefore(newSpan, myMenuContainer.children[1]);
        }
        
    
    </script>
    

    When?
    Load javascript after the page is loaded because the javascript needs to indentify the HTML ID "RibbonContainer-TabRowRight".
    ExecuteOrDelayUntilScriptLoaded(LoadLink, "sp.js");

    Why?
    I use Delegate Control to dont change Masterpages for all Sites...

    Where?
    To response this request Globaly was created a Visual Studio 2010 SharePoint Solution with Feature, since this solution need to be deployed globaly is used a Farm Solution.
    We can define differents scopes where this Feature can be deployed "Farm, WebApplication, Site, Web", but if you install/activate with Scope "Farm" the Central Administration will also have the Support Link, i can recomend "Farm, WebApplication Scope"  depending what you want to manage.

    Create a User control where will have your code...
    [Hive]CONTROLTEMPLATES\CustomHeader\CustomHeader.ascx

    Content of CustomHeader.ascx "You can also include Server Side code to add multiple actions"

    <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
    <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="CustomHeader.ascx.cs" Inherits="CustomHeader.CONTROLTEMPLATES.CustomHeader.CustomHeader" %>
    <script type="text/javascript">
        ExecuteOrDelayUntilScriptLoaded(LoadLink, "sp.js");
    
        function LoadLink() {
            var myMenuContainer = document.getElementById('RibbonContainer-TabRowRight').children[2].children[0];
            var newSpan = document.createElement("span");
    
            newSpan.innerHTML = '<a class="ms-menu-a"  style="cursor:pointer;white-space:nowrap;"   href="#" onclick="window.location=\'https://www.sharepointpt.org\';return false;"><span>SharePoint PT</span></a>';
            newSpan.className = 'ms-SPLink ms-SpLinkButtonInActive ms-welcomeMenu';
    
            myMenuContainer.insertBefore(newSpan, myMenuContainer.children[1]);
        }
        
    
    </script>

    Elements File
    Reference to User control used to add custom control or inject Javascript.






    <?xml version="1.0" encoding="utf-8"?> 
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Control Id="AdditionalPageHead" Sequence="100" ControlSrc="~/_CONTROLTEMPLATES/CustomHeader/CustomHeader.ascx"></Control> 
    </Elements> 

    After deployed the Solution the link will be visible in top right of the SharePoint Site.




    The "How?" is the process taken to response all other questions.

    Support links:
    Delegate Control
    http://msdn.microsoft.com/en-us/library/ms463169.aspx
    How to: Customize a Delegate Control
    http://msdn.microsoft.com/en-us/library/ms470880.aspx
    AllowMultipleControls
    http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webcontrols.delegatecontrol.aspx
    Replace SharePoint 2010 Search Box using Delegate Control
    http://msmvps.com/blogs/sundar_narasiman/archive/2011/02/19/sharepoint-2010-delegate-control-to-replace-smallsearchinputbox.aspx
    Using the Delegate Control
    http://www.sharepointnutsandbolts.com/2007/06/using-delegate-control.html
    Useful Delegate Controls in Windows SharePoint Services 3.0
    http://www.wictorwilen.se/Post/Useful-Delegate-Controls-in-Windows-SharePoint-Services-30.aspx

    PS: This script was only tested with Team Site Templates, you will need validate the other templates or change the script to have the same behavior.

    ---------------------------------------------------------------------------------------------------
    Updated 05.03.2012

    The code is updated ttrying to response all Templates

    <script type="text/javascript">
        ExecuteOrDelayUntilScriptLoaded(LoadLink, "sp.js");
        function LoadLink() {
    function getByClass (className, parent) {
      parent || (parent=document);
      var descendants=parent.getElementsByTagName('*'), i=-1, e, result=[];
      while (e=descendants[++i]) {
        ((' '+(e['class']||e.className)+' ').indexOf(' '+className+' ') > -1) && result.push(e);
      }
      return result;
    }
            var myMenuContainer = getByClass ( 'ms-welcomeMenu' )[0].parentNode ;
            var newSpan = document.createElement("span");
            newSpan.innerHTML = '<a class="ms-menu-a"  style="cursor:pointer;white-space:nowrap;"   href="#" onclick="window.location=\'http://www.sharepointpt.org\';return false;"><span>SharePoint PT</span></a>';
            newSpan.className = 'ms-SPLink ms-SpLinkButtonInActive ms-welcomeMenu';
            myMenuContainer.insertBefore(newSpan, myMenuContainer.children[1]);
        }
    </script>
    <noscript>
            Your browser does not support JavaScript!
    </noscript>

    ---------------------------------------------------------------------------------------------------
    Code updated 08-05-2012 (improved efficiency in load)


    typeof (ExecuteOrDelayUntilBodyLoaded) != "undefined" && ExecuteOrDelayUntilBodyLoaded(function () {
            var welcomeMenuContainer, linkSpan;

            //build link markup
            linkSpan = document.createElement('span');
            linkSpan.className = 'ms-SPLink ms-SpLinkButtonInActive ms-welcomeMenu';
            linkSpan.innerHTML = '<a class="ms-menu-a" style="cursor:pointer; white-space:nowrap;" href="#" onclick="window.location=\'http://www.sharepointpt.org\'; return false;"><span>SharePoint PT</span></a>';

            //inject link markup
            welcomeMenuContainer = GetElementByClassName(window.document, 'ms-welcomeMenu').parentNode;
            welcomeMenuContainer.insertBefore(linkSpan, welcomeMenuContainer.children[1]);
        });

    Thank you Marco Oechslin. :)
    Link to Project.

    Hope this help,
    Kind regards