Wednesday, January 6, 2010

SharePoint 2010 Ribbon Customization - server-side command handling

In the previous post we’ve discussed the way to customize SharePoint 2010 Ribbon bar by deploying its xml description with SharePoint feature. Using this approach it is possible to deploy all necessary controls (buttons, checkboxes, dropdowns etc.), control containers (groups and tabs) and JavaScript command handlers. But sometimes it is necessary to generate a JavaScript command handler code dynamically or to handle ribbon actions on the server side. For this purpose the SharePoint ribbon allows user to create command handlers programmatically.

Here is the example:

var commands = new List<IRibbonCommand>();
commands.Add(new SPRibbonCommand(
                "Your_Command_ID",
                "alert('this is server generated command')"));

When you have created a command, you should register it with help of SPRibbonScriptManager class. It has protected method RegisterInitializeFunction, so it is necessary to use reflection to invoke it:

var manager = new SPRibbonScriptManager();
var methodInfo = typeof(SPRibbonScriptManager).GetMethod(
    "RegisterInitializeFunction",
    BindingFlags.Instance | BindingFlags.NonPublic);
methodInfo.Invoke(
    manager,
    new object[]
    {
        Page,
        "InitPageComponent",
        "/_layouts/INC/RibbonCustomization/PageComponent.js",
        false,
        "RibbonCustomization.PageComponent.initialize()"
    });

This method is used to register a JavaScript class for managing server-created command handlers. It should implement the appropriate interface for registering ribbon commands. Here how it should look like:


function ULS_SP() {
    if (ULS_SP.caller) {
        ULS_SP.caller.ULSTeamName = "Windows SharePoint Services 4";
        ULS_SP.caller.ULSFileName = "/_layouts/INC/RibbonCustomization/PageComponent.js";
    }
}

Type.registerNamespace('RibbonCustomization');

// RibbonApp Page Component
RibbonCustomization.PageComponent = function () {
    ULS_SP();
    RibbonCustomization.PageComponent.initializeBase(this);
}
RibbonCustomization.PageComponent.initialize = function () {
    ULS_SP();
    ExecuteOrDelayUntilScriptLoaded(
        Function.createDelegate(
            null,
            RibbonCustomization.PageComponent.initializePageComponent),
        'SP.Ribbon.js');
}
RibbonCustomization.PageComponent.initializePageComponent = function () {
    ULS_SP();
    var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
    if (null !== ribbonPageManager) {
        ribbonPageManager.addPageComponent(RibbonCustomization.PageComponent.instance);
        ribbonPageManager
            .get_focusManager()
            .requestFocusForComponent(RibbonCustomization.PageComponent.instance);
    }
}
RibbonCustomization.PageComponent.refreshRibbonStatus = function () {
    SP.Ribbon.PageManager
        .get_instance()
        .get_commandDispatcher()
        .executeCommand(Commands.CommandIds.ApplicationStateChanged, null);
}
RibbonCustomization.PageComponent.prototype = {
    getFocusedCommands: function () {
        ULS_SP();
        return [];
    },
    getGlobalCommands: function () {
        ULS_SP();
        return getGlobalCommands();
    },
    isFocusable: function () {
        ULS_SP();
        return true;
    },
    receiveFocus: function () {
        ULS_SP();
        return true;
    },
    yieldFocus: function () {
        ULS_SP();
        return true;
    },
    canHandleCommand: function (commandId) {
        ULS_SP();
        return commandEnabled(commandId);
    },
    handleCommand: function (commandId, properties, sequence) {
        ULS_SP();
        return handleCommand(commandId, properties, sequence);
    }
}

// Register classes
RibbonCustomization.PageComponent.registerClass(
    'RibbonCustomization.PageComponent',
    CUI.Page.PageComponent);
RibbonCustomization.PageComponent.instance = new RibbonCustomization.PageComponent();

// Notify waiting jobs
NotifyScriptLoadedAndExecuteWaitingJobs(
    "/_layouts/INC/RibbonCustomization/PageComponent.js");

The last thing you should do to enable server-registered commands on your page is registering three client functions:

manager.RegisterGetCommandsFunction(Page, "getGlobalCommands", commands);
manager.RegisterCommandEnabledFunction(Page, "commandEnabled", commands);
manager.RegisterHandleCommandFunction(Page, "handleCommand", commands);

If you are creating your own page, it might be necessary to link SP.Runtime.js and SP.js
JavaScript files. You can do this in the following way:

ScriptLink.RegisterScriptAfterUI(Page, "SP.Runtime.js", false, true);
ScriptLink.RegisterScriptAfterUI(Page, "SP.js", false, true);

This is enough to enable the handling of your server-registered commands for the ribbon controls.

Registering server-side ribbon command handlers

In order to handle ribbon command on the server you should create and register an instance of SPRibbonPostBackCommand command class. Here is the example:

commands.Add(new SPRibbonPostBackCommand(
                "ServerCommandID",
                this,
                "ServerCmd",
                null));

In order to use the server command handling from the web part it has to implement IPostBackEventHandler interface. Then you can perform all necessary server actions in RaisePostBackEvent method. You can determine which command was run by eventArgument method argument.

The source of sample project you can find here

16 comments:

  1. Hi,
    Thanks for the post. It throws some light on ribbons. But, On trying the above code i.e. displaying a new tab in a custom application page, I encountered the below error stack.

    Although I have clearly given the 'Scaling' node under 'Tab' node of my new tab, but still it fails.

    if you encounter it or have any comments, please help !


    [ArgumentException: Ribbon node must have a subnode.]
    Microsoft.Web.CommandUI.Ribbon.CreateRenderContext(CUIDataSource uiproc) +2702
    Microsoft.Web.CommandUI.Ribbon.Render(HtmlTextWriter writer) +1360
    Microsoft.SharePoint.WebControls.SPRibbon.Render(HtmlTextWriter writer) +300
    System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +240
    System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) +253
    System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +87
    System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +53
    System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +240
    System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +42
    System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +240
    System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +240
    Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase.RenderChildren(HtmlTextWriter writer) +58
    System.Web.UI.Page.Render(HtmlTextWriter writer) +38
    Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase.Render(HtmlTextWriter writer) +58
    System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +4240

    ReplyDelete
  2. The tags got suppressed.

    ArgumentException: Ribbon 'Tab' node must have a 'Scaling' subnode

    ReplyDelete
  3. Hello Ravi,
    Your problem must be with the Scaling subnode. Be sure you've added two items from each group in the tab - MaxSize and Scale. The problem you have might be with incorrect "Size" attribute values in those nodes. Please check our "SharePoint 2010 Ribbon Customization - controls and commands deployment" post, especially notes for "Adding custom group to existing tab" section.

    ReplyDelete
  4. really great if you can attach sample project source of this post

    ReplyDelete
  5. Nirav,

    we've added a link to the "rough but working" project source code. Find it at the bottom of the post.

    ReplyDelete
  6. Nice post, got some insight on ribbon customation. Can we customize a command eg: For an infopath OOTB Save button, i want to add my custom action along with OOTB fuction, I guess there is no way of adding Custom XML via CMDUI.XML (since there is no element/action for Ribbon.Tabs.InfoPathHomeTab), when i see the FormServer.aspx, there is inline javascript defined for Ribbon.Tabs.InfoPathHomeTab.Save other actions... How can we hook our custom javascript methods to this?? Give some insight on customizing the Infopath Ribbon...

    Thanks

    ReplyDelete
  7. The link for source code doesn't work. Is it possible to get another one? :)

    ReplyDelete
  8. Mitko, the link is fixed. You'll have to remove .bin suffix from the end of file name.

    ReplyDelete
  9. I created custom tabs with DropDown (for WebPartPage) and with Button (for appliction page). Both comtrols have handler created via SPRibbonPostBackCommand.

    On page with DropDonw I encountered the below error stack:

    [NullReferenceException: Object reference not set to an instance of an object.]
    Microsoft.Web.CommandUI.RibbonRenderer.AllGroupsControlsTrimmed(DataNode xn, RibbonRenderContext rrc) +291
    Microsoft.Web.CommandUI.RibbonRenderer.RenderTabBody(DataNode xnTab, RibbonRenderContext rrc, HtmlTextWriter writer) +682
    Microsoft.Web.CommandUI.RibbonRenderer.RenderRibbon(DataNode xnData, RibbonRenderContext rrc, HtmlTextWriter writer) +4971
    Microsoft.Web.CommandUI.Ribbon.AppendRibbon(HtmlTextWriter writer, Boolean headerOnly, UInt32 initialTabScaleIndex, RibbonRenderContext rrc) +77
    Microsoft.Web.CommandUI.Ribbon.Render(HtmlTextWriter writer) +1393

    On page with Button I encountered the below error stack:

    [NullReferenceException: Object reference not set to an instance of an object.]
    Microsoft.Web.CommandUI.RibbonRenderer.RenderGroup(DataNode xnGroup, RibbonRenderContext rrc, HtmlTextWriter writer) +1080
    Microsoft.Web.CommandUI.RibbonRenderer.RenderTabBody(DataNode xnTab, RibbonRenderContext rrc, HtmlTextWriter writer) +703
    Microsoft.Web.CommandUI.RibbonRenderer.RenderRibbon(DataNode xnData, RibbonRenderContext rrc, HtmlTextWriter writer) +4971
    Microsoft.Web.CommandUI.Ribbon.AppendRibbon(HtmlTextWriter writer, Boolean headerOnly, UInt32 initialTabScaleIndex, RibbonRenderContext rrc) +77
    Microsoft.Web.CommandUI.Ribbon.Render(HtmlTextWriter writer) +1393


    If I use SPRibbonCommand then I have not that errors...

    Please, tell me wath can be wrong?

    ReplyDelete
  10. You say you don't get this error if you replace your server command SPRibbonPostBackCommand with SPRibbonCommand, correct? But the exception stack looks like you should have this error in both cases, can you confirm or reject your statement? This can give a hint where to search for error.
    Additionally, does your web part implement IPostbackEventHandler interface?

    ReplyDelete
  11. Not sure this is related but it is what burned me. and caused the NullReferenceException but you can't have comments in your CommandUIDefinition/Tab/Groups elements.xml file.
    http://www.avanadeblog.com/sharepointasg/2010/07/sharepoint-2010-woes-avoid-comments-in-your-customaction-xml-for-ribbon-customizations.html

    ReplyDelete
  12. for beginners like me need a lot of reading and searching for information on various blogs. and articles that you share a very nice and inspires me .

    cara menggugurkan kandungan
    obat aborsi
    tanda tanda kehamilan

    ReplyDelete