Friday, May 23, 2014

How to Create Global Custom Ribbon using ScriptLink without MDS issues

This article explains how you can create a Custom Ribbon to be accessed in all Out of the Box Ribbon Location using ScripLlink Custom Action  and don’t change any Page/Masterpage/File out of the box, no server code, no Declarative Code, No Visual Studio Solution and will manage the Minimal Download Strategy (MDS)  to include the Ribbon file to the SharePoint Site, everything will be done by a JavaScript File, simple like that.

PS: i am aware of the MakeTabAvailable but this method is for Server side, this demo is for Client Side "JavaScript" to include Office 365 environments.

PS: there is a new version about this topic in the following link
http://aaclage.blogspot.ch/2015/07/headerfooter-with-breadcrumb-and-global.html

The challenge here wasn't to create the Ribbon itself, there is a lot of code on how to create Ribbon using SharePoint JSOM, 
Here is a article examplethe challenge was on how to do it with MDS (Minimal Download Strategy).
The problem when you have the MDS approach, some of our assumptions has to be change:
  • There isn't a real redirection of page, 
    • Then there isn't a postback associated
  • The scripts are loaded in the first page other pages with MDS will have this files preloaded from "_layouts/15/start.aspx#/" this makes the page load faster, 
    • "SP Context will be hard to get".
  • When the users change pages, the URL query parameter is changed but not the main page  "/_layouts/15/start.aspx#/Shared%20Documents/Forms/AllItems.aspx"
    • Ajax calls to change specific zones and becomes hard to understand the changes in the page and associate the correct Context.

Here some nice articles talking about this Topic "MDS" and how you should work with your Javascript and include the "RegisterModuleInit" Method in specific areas of SharePoint to work correctly with MDS, but this is not what i want....
What i want is to make a simple reference to a JavaScript File and it's done. Don't need to change WebParts, no CSR  override Files, add file to MasterPage, that is to complicated for some people and to many steps, i like the 2 way approach.
  • Developer makes the code and add JavaScript File in Document Library
  • Site Administrator add Link to JavaScript using Scriptlink with one click in the SharePoint and done, it's shared in all pages of the SharePoint Site, everything in a very easy way.
    • The process that some Developers make to deploy their solutions can be very complicated with multiple clicks and confusing descriptions .Some Site administrators are just Business Persons, they don't need to be SharePoint functionality experts.....
Here are some articles talking about MDS i can recommend you to read:


Here was the challenge to create a Custom Ribbon to be accessed in all Ribbons Locations:
  • Need to include Custom JavaScript to be loaded in all SharePoint pages and include Custom Ribbon if there is a Ribbon Location in the Page.
  • Minimal Download Strategy don't allow postback "everything is the same page and is managed by url parameters" with this approach we lose control of context, then needs to include in the JavaScript a specific Listener onhashchange to get the events when the url is changed and include the JavaScript for the Custom Ribbon.
The image below shows the final result of this solution: 



The following Code inject 2 Main Methods:

  • LoadMDSGlobalRibbon()
    • Include Javascript code in the SharePoint and Page to add a Listener when the MDS is activated and his url is changed then the Custom Ribbon is injected everytime the DOM Object Are Loaded
  •  LoadGlobalRibbon()
    • This script is execute when the Page is loaded ensures the SP.Ribbon.JS file is added to include the Custom Ribbon in the Page. 

Create the JavaScript file named "GlobalRibbon.js" with the following code: 

//Load the Global Ribbon Method without MDS
LoadGlobalRibbon();

//This injected code that includes a listener for url parameters, when the MDS is Activated.
$('body').append('<script>window.onhashchange = locationHashChanged;function locationHashChanged(){LoadMDSGlobalRibbon();}</script>');

//Load all DOM OBJECTS and Load Global Ribbon in the Page
function LoadMDSGlobalRibbon() {
    $(window).load(function () {
        LoadRibbon();
    });
}
//Load Content of Page and Load Global Ribbon in the Page
function LoadGlobalRibbon() {
    $(document).ready(function () {
        LoadRibbon();
    });
}
//Validate Ribbon Context in the Page
function LoadRibbon() {
    SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        try {
            var pm = SP.Ribbon.PageManager.get_instance();
            pm.add_ribbonInited(function () {
                ribbonTab();
            });
            var ribbon = null;
            try {
                ribbon = pm.get_ribbon();
            }
            catch (e) { }
            if (!ribbon) {
                if (typeof (_ribbonStartInit) == "function")
                    _ribbonStartInit(_ribbon.initialTabId, falsenull);
            }
            else {
                ribbonTab();
            }
        } catch (e)
        { }
    }, "sp.ribbon.js");
}
//include the Ribbon in the Page
function ribbonTab() {
    var Ribbonhtml = "";
    Ribbonhtml += "<div><div><a href='#' onclick=\"alert('Option Selected!')\" ><img src='/_layouts/images/NoteBoard_32x32.png' /></a><br/>Ribbon Example</div></div>";

    var ribbon = SP.Ribbon.PageManager.get_instance().get_ribbon();
    if (ribbon) {
        var tab = new CUI.Tab(ribbon, 'GlobalRibbon.Tab''Option''Option''GlobalRibbon.Tab.Command'false''null);
        ribbon.addChildAtIndex(tab, 1);
        var group = new CUI.Group(ribbon, 'GlobalRibbon.Tab.Group''Custom Ribbon''Global Ribbon Example''GlobalRibbon.Group.Command'null);
        tab.addChild(group);
    }
    SelectRibbonTab('GlobalRibbon.Tab'true);
    $("span:contains('Custom Ribbon')").prev("span").html(Ribbonhtml);
    SelectRibbonTab('Ribbon.Read'true);

}

Add the created file in the SharePoint Library "Site Assets"



Include the Global Script to your SharePoint Site, for this task i used my SharePoint App "Manage User Custom Action" to include using ECMAscrpt JSOM the ScriptLink in the Site.



If you like to use Visual Studio and Declarative code you can use the option "Generate Declarative XML" to create the XML for your SharePoint Feature as shown.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="Global Ribbon" Title="Global Ribbon" Description="" Location="ScriptLink"ScriptSrc="~Site/SiteAssets/GlobalRibbon.js" Sequence="2" > </CustomAction>
</Elements>

After the Reference is made a new Option Appears in your Ribbon, you can access to your SharePoint page and you will see the MDS is activated and the Option Ribbon is loaded.


When you select another page the URL Parameters are changed and the Custom Ribbon appears in the Out of the Box Ribbons Locations



To validate if everything was working correctly, deactivate the the MDS Feature and access to a SharePoint Page.


After access the SharePoint page you will be able to see the Url change, it's not calling "_layouts/15/start.aspx#/" but the correct Page "/sitesPages/DevHome.aspx" and the Ribbon Load correctly.



As you can see it's very easy to add a Global Ribbon using a Scriptlink and avoid possible issues with MDS, this was not tested fully but a PoC on how we can have nice workarounds without creating SharePoint Solutions WSP or extreme XML Customization .

Here the Video on how to configure this Global Ribbon in Office365



Hope you like this article, 
Kind regards, 
Andre Lage
Post a Comment