Page tab highlighted on the SharePoint 2010 Ribbon.
SharePoint 2010 ships with the new Ribbon framework that allows you to easily extend the Ribbon with new functionality. All Ribbon extensions consist of the UI and behavior, which in simple scenarios can be specified declaratively but in more advanced scenarios would rely on a Page Component. Most samples, that show using Page Components, suggest registering them using Delegate Controls. Unfortunately this makes it impossible to use Page Component in Sandboxed Solutions which don’t support Delegate Controls. So are we forced to use the limited declarative approach or is there a solution that would allow us to register custom Page Components within Sandboxed Solutions?

CommandUIHandler vs. Page Component

If you’re interested in extending the SharePoint 2010 Ribbon with some new functionality you have to take care for two things. First of all you have to register your control: no matter if it’s a custom tab, group or a button. The next step is to associate some behavior with your control. You can do this the simple way using a CommandUIHandler or a more complex way using a Page Component. Andrew Connell wrote recently an article about advantages of using custom Page Components. As the background case Andrew uses extending the Ribbon from code – something you are very likely to do if the extension applies to a specific piece of the solution only.

A little different approach on using Page Components has been discussed by Chris O’Brien, while he creates globally available extensions. In his example Chris uses a Delegate Control to register the Page Component across the whole Site Collection. As you might’ve heard by now, Delegate Controls are not supported in Sandboxed Solutions. So does this mean that if you’re writing a globally available Ribbon extension you cannot use Page Components or is there a different way to register the behavior than using a Delegate Control?

Registering Page Components using JavaScript

Recently I’ve created a few Web Content Management-oriented Ribbon extensions. In many situations the extension itself would easily fit into the capabilities available for Sandboxed Solutions. Unfortunately, because I needed to use Page Components, I had to fall back to Delegate Controls and change the Solution to a Farm Solution. Not convinced about the Delegate Control being the only way to register a Page Component, I’ve done some experiments.

What is exactly the challenge with registering Page Components?

Registering a Page Component means nothing more than calling its initialize function. While it might seem trivial at first it isn’t and here is why…

Page Components rely on some resources which are being dynamically loaded by SharePoint. Those are the SP.js, CUI.js and the SP.Ribbon.js files but also the MicrosoftAjax.js file. All of those are being loaded on demand to improve the User Experience. For you – as a Ribbon-extension developer – it means that you cannot just assume that those files have already been loaded and have to check it and sometimes even load them yourself.

When using a Delegate Control you can do all the checks as easy as adding two ScriptLink controls just before initiating the Page Component:

<SharePoint:ScriptLink Name="CUI.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink1" />
<SharePoint:ScriptLink Name="/_layouts/Mavention/Mavention.SharePoint.Ribbon.PageComponent.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink2" />
<script type="text/javascript">
//<![CDATA[
    function initRibbon() {
        Mavention.SharePoint.Ribbon.PageComponent.initialize();
    }

    SP.SOD.executeOrDelayUntilScriptLoaded(initRibbon, 'Mavention.SharePoint.Ribbon.PageComponent.js');
//]]>
</script>

But how to achieve the same if the only thing available to you is the CustomAction element?

Registering Page Components in Sandboxed Solutions

It turns out that you can register a Page Component using the following JavaScript snippet:

SP.SOD.executeOrDelayUntilScriptLoaded(function () {
    SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        var ctx = SP.ClientContext.get_current();
        var site = ctx.get_site();
        ctx.load(site);
        ctx.executeQueryAsync(Function.createDelegate(this, function (sender, args) {
            var pageComponentScriptUrl = SP.Utilities.UrlBuilder.urlCombine(site.get_url(), "Style Library/Mavention/Mavention.SharePoint.Ribbon.PageComponent.js");
            SP.SOD.registerSod('mavention.sharepoint.ribbon.pagecomponent.js', pageComponentScriptUrl);
            LoadSodByKey('mavention.sharepoint.ribbon.pagecomponent.js', function () {
                Mavention.SharePoint.Ribbon.PageComponent.initialize();
            });
        }));
    }, "cui.js");
}, "sp.js");

The first thing that I mentioned was the dependency of the Page Components on SP.js and CUI.js files. We can solve this by using the SP.SOD.executeOrDelayUntilScriptLoaded function (1, 2). Since both files are loaded by SharePoint itself all we have to do is wait until they have been loaded. The dependency on the MicrosoftAjax.js file is being handled by SharePoint itself, as its required by the SP.js file.

The next step is to load our custom Page Component. As mentioned before this can be done not earlier than after the SP.js and MicrosoftAjax.js files have been loaded and this is exactly why using a CustomAction is not an option. Another challenge is getting a correct URL of the JavaScript file that contains the definition of our Page Component. Since the file has been deployed to the Style Library or any other Document Library in the root site, we need the URL of the current Site Collection to prepend the part of the URL that we know (3-6).

Once we have the Site Collection URL we can build up the URL of the script containing the definition of our Page Component. While you could do that manually, it’s probably easier and more reliable to use the SharePoint ECMA script URL Builder (7). Using that URL we can register our script to be loaded dynamically (8). The next step is to actually load the file. Unfortunately I haven’t found a method to do this that would be a part of the public SP ECMA API, and had to use the internal LoadSodByKey function. The LoadSodByKey function takes two arguments. The first is the key of a script previously registered using the SP.SOD.registerSod function.

Important: The key of the registered script must be lower case. Although it doesn’t matter which case you use while registering the script in the SP.SOD.registerSod function, internally it’s being converted to lower case. Unfortunately the LoadSodByKey function doesn’t automatically convert the key to lower case so if you use casing other than lower case your script won’t be loaded.

Finally, once the custom Page Component has been loaded, we can move on and initialize it calling the initialize function.

Summary

Using Page Components for implementing behavior of custom Ribbon extensions gives you more control and allows you to support advanced scenarios. Although it seems that the only way for using Page Components for globally available extensions is by using Delegate Controls, you can use a pure JavaScript-based approach to achieve the same. This allows you to use Page Components in Sandboxed Solutions.

Technorati Tags: