Implementing a post back button in a SharePoint 2010 Web Part with contextual ribbon

There are quite a few articles that talks about implementing a contextual ribbon button that does a post back in SharePoint 2010.  But despite quite a few tries, I’ve not been able to get hold of a complete walk-through on it.  I am posting this one which I got working recently.  Note: I am not trying to explain the ribbon infrastructure here as there’s plenty of references on it on the internet, but I am simply showing the quick way to get post back button control working in a contextual ribbon in SharePoint 2010 web part.

First step is to create a VS 2010 project that will hold our SPIs (SharePoint Project Items).  For this contextual ribbon with a button that does a post back, we need to have a web part & a page component (and optionally images).  For the purpose of this walk-through, I’ll create a new projects in VS 2010 by choosing the “Empty SharePoint Project” template and then choosing to deploy it as a farm solution.

Once the project is created, I right-click the project name and choose Add and then New Item.  Now I choose “Web Part” item template, provide a name for it and Add it to my project.  I also add a reference to “Microsoft.Web.CommandUI.dll”.  I then include using statements for System.Xml, Microsoft.Web.CommandUI & System.Collections.Generic namespaces.  Finally, I derive my web part class from IWebPartPageComponentProvider & IPostBackEventHandler, which already should be deriving from System.Web.UI.WebControls.WebParts.WebPart.  The result should look like this:

image

In the next step, I describe the contextual ribbon.  Description here is: What components the ribbon has (groups, tabs, controls)?  What are their identifiers (IDs)?  What are their commands (the way to interact with them and what should happen when user interacts with them)?  Usually, this can be done with a ribbon elements file, but I am going to stub the ribbon definition out through a local variable inside my web part.  Here’s the variable defining the ribbon.

 private string demoContextualTab = @"
            <ContextualGroup Color=""Magenta"" 
                             Command=""CustomContextualTab.EnableContextualGroup"" 
                             Id=""Ribbon.CustomContextualTabGroup"" 
                             Title=""Custom Contextual Tab Group"" 
                             Sequence=""502"" 
                             ContextualGroupId=""CustomContextualTabGroup"">
                <Tab 
                    Id=""Ribbon.CustomTabExample"" 
                    Title=""My Custom Tab"" 
                    Description=""This holds my custom commands!"" 
                    Command=""CustomContextualTab.EnableCustomTab"" 
                    Sequence=""501"">
                    <Scaling
                        Id=""Ribbon.CustomTabExample.Scaling"">
                        <MaxSize
                            Id=""Ribbon.CustomTabExample.MaxSize"" 
                            GroupId=""Ribbon.CustomTabExample.CustomGroupExample"" 
                            Size=""OneLargeTwoMedium""/>
                        <Scale 
                            Id=""Ribbon.CustomTabExample.Scaling.CustomTabScaling""
                            GroupId=""Ribbon.CustomTabExample.CustomGroupExample"" 
                            Size=""OneLargeTwoMedium"" />
                        </Scaling>
                    <Groups Id=""Ribbon.CustomTabExample.Groups"">
                        <Group 
                            Id=""Ribbon.CustomTabExample.CustomGroupExample"" 
                            Description=""This is a custom group!"" 
                            Title=""Custom Group"" 
                            Command=""CustomContextualTab.EnableCustomGroup"" 
                            Sequence=""52"" 
                            Template=""Ribbon.Templates.CustomTemplateExample"">
                            <Controls 
                                Id=""Ribbon.CustomTabExample.CustomGroupExample.Controls"">
                                <Button 
                                    Id=""Ribbon.CustomTabExample.CustomGroupExample.HelloWorld"" 
                                    Command=""CustomContextualTab.HelloWorldCommand"" 
                                    Sequence=""15"" 
                                    Image32by32=""/_layouts/images/radio_blue_32.png""
                                    Description=""Says hello to the World!"" 
                                    LabelText=""Hello, World!"" 
                                    TemplateAlias=""cust1""/>
                                <Button 
                                    Id=""Ribbon.CustomTabExample.CustomGroupExample.GoodbyeWorld"" 
                                    Command=""CustomContextualTab.GoodbyeWorldCommand"" 
                                    Sequence=""17"" 
                                    Image16by16=""/_layouts/images/HelloWorld.png""
                                    Description=""Says good-bye to the World!"" 
                                    LabelText=""Good-bye, World!"" 
                                    TemplateAlias=""cust2""/>
                            </Controls>
                        </Group>
                    </Groups>
                </Tab>
            </ContextualGroup>";

Here’s the variable that holds the template definition of the ribbon.

 private string demoContextualTabTemplate = @"
            <GroupTemplate Id=""Ribbon.Templates.CustomTemplateExample"">
                <Layout 
                    Title=""OneLargeTwoMedium"" LayoutTitle=""OneLargeTwoMedium"">
                <Section Alignment=""Top"" Type=""OneRow"">
                    <Row>
                        <ControlRef DisplayMode=""Large"" TemplateAlias=""cust1"" />
                    </Row>
                </Section>
                <Section Alignment=""Top"" Type=""TwoRow"">
                    <Row>
                        <ControlRef DisplayMode=""Medium"" TemplateAlias=""cust2"" />
                    </Row>
                    <Row>
                        <ControlRef DisplayMode=""Medium"" TemplateAlias=""cust3"" />
                    </Row>
                </Section>
                </Layout>
            </GroupTemplate>";

Next, I add a DelayScript string variable to register our custom page component that’ll back our ribbon controls.  This will specially have the web part’s page component ID registered such that the contextual tabs and its controls are only available when the web part is in focus.

 public string DelayScript
        {
            get
            {
                string webPartPageComponentId = SPRibbon.GetWebPartPageComponentId(this);
                return @"
                        <script type=""text/javascript"">
                        //<![CDATA[
                        function _addCustomPageComponent()
                        {
                            var _customPageComponent = new PostbackContextualDemo.PostbackPageComponent('" + webPartPageComponentId + @"');
                            SP.Ribbon.PageManager.get_instance().addPageComponent(_customPageComponent);
                        }
 
                        function _registerCustomPageComponent()
                        {
                            SP.SOD.registerSod(""PostbackContextualDemo.js"", ""\/_layouts\/PostbackContextualDemo.js"");
                            SP.SOD.executeFunc(""PostbackContextualDemo.js"", ""PostbackContextualDemo.PostbackPageComponent"", _addCustomPageComponent);
                        }
                        SP.SOD.executeOrDelayUntilScriptLoaded(_registerCustomPageComponent, ""sp.ribbon.js"");
                        //]]>
                        </script>";
            }
        }

Then, I create an “AddContextualTab” method, which will parse through the variables holding our ribbon and ribbon template definitions and register it with the ribbon framework.

 

 private void AddContextualTab()
        {
            Microsoft.Web.CommandUI.Ribbon ribbon = SPRibbon.GetCurrent(this.Page);
            XmlDocument ribbonExtensions = new XmlDocument();
            ribbonExtensions.LoadXml(this.demoContextualTab);
            ribbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.ContextualTabs._children");
            ribbonExtensions.LoadXml(this.demoContextualTabTemplate);
            ribbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.Templates._children");
            ribbon.MakeTabAvailable("Ribbon.CustomTabExample");
            ribbon.MakeContextualGroupInitiallyVisible("Ribbon.CustomTabExample.CustomGroupExample", string.Empty);
            ribbon.NormalizeContextualGroup("Ribbon.CustomTabExample.CustomGroupExample", string.Empty);
        }

Then, I pass the web part contextual information by implementing the IWebPartPageComponentProvider interface.

 WebPartContextualInfo IWebPartPageComponentProvider.WebPartContextualInfo
        {
            get
            {
                WebPartContextualInfo info = new WebPartContextualInfo();
                WebPartRibbonContextualGroup contextualGroup = new WebPartRibbonContextualGroup();
                WebPartRibbonTab ribbonTab = new WebPartRibbonTab();

                contextualGroup.Id = "Ribbon.CustomContextualTabGroup";
                contextualGroup.Command = "CustomContextualTab.EnableContextualGroup";
                contextualGroup.VisibilityContext = "CustomContextualTab.CustomVisibilityContext";

                ribbonTab.Id = "Ribbon.CustomTabExample";
                ribbonTab.VisibilityContext = "CustomContextualTab.CustomVisibilityContext";

                info.ContextualGroups.Add(contextualGroup);
                info.Tabs.Add(ribbonTab);
                info.PageComponentId = SPRibbon.GetWebPartPageComponentId(this);

                return info;
            }
        }

Now, I override the “OnPreRender” method of the web part.  What I do here is to call the “AddContextualTab” method.  And register our custom page component’s JS file so that it’s loaded before the contents of the web part are initialized.  We also initialize the commands that we are interested in trapping and register command handlers.

 protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            this.AddContextualTab();

            string script = @"function _registerPostBackPageComponent() {
                            var postBackPageComponent = new PostbackContextualDemo.PostbackPageComponent();
                            SP.Ribbon.PageManager.get_instance().addPageComponent(postBackPageComponent);
                        }
                         ExecuteOrDelayUntilScriptLoaded(_registerPostBackPageComponent, ""PostbackContextualDemo.js"");";

            ScriptLink.RegisterScriptAfterUI(Page, "CUI.js", false, true);
            ScriptLink.RegisterScriptAfterUI(Page, "SP.Ribbon.js", false, true);
            ScriptLink.RegisterScriptAfterUI(Page, "PostbackContextualDemo.js", false, true);
            Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "postbackpagecomponent", script, true);

            SPRibbonScriptManager ribbonScript = new SPRibbonScriptManager();

            List<IRibbonCommand> globalCommands = new List<IRibbonCommand>();
            globalCommands.Add(new SPRibbonCommand("CustomContextualTab.EnableCustomTab"));
            globalCommands.Add(new SPRibbonCommand("CustomContextualTab.EnableCustomGroup"));
            globalCommands.Add(new SPRibbonCommand("CustomContextualTab.GoodbyeWorldCommand"));
            globalCommands.Add(new SPRibbonPostBackCommand("CustomContextualTab.HelloWorldCommand", this));
            ribbonScript.RegisterHandleCommandFunction(this, "handleCommand", globalCommands);
        }

Note: “PostbackContextualDemo.js” will be implemented shortly.

Then I override the Render() and RenderContents() methods of my web part to make it visually appealing Smile

 protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            writer.Write("Ribbon Post back Demo");
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            base.RenderContents(writer);
        }

And then I implement the RaisePostBackEvent() of the IPostBackEventHandler to handle the post back event from the ribbon control.

 void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
        {
            SPRibbonPostBackEvent postBackEvent = SPRibbonPostBackCommand.DeserializePostBackEvent(eventArgument);
            if (null != postBackEvent)
            {
                if (postBackEvent.Id == "CustomContextualTab.HelloWorldCommand")
                {
                    HelloWorldCommand_Click();
                }
            }
        }

        protected void HelloWorldCommand_Click()
        {
            SPWeb web = SPControl.GetContextWeb(HttpContext.Current);
            SPList list = web.Lists["Announcements"];
            SPListItem newItem = list.AddItem();
            newItem["Title"] = "RibbonPostbackDemo Item - [" + DateTime.Now.ToString() + "]";
            newItem.Update();
        }

I specifically track the “CustomContextualTab.HelloWorldCommand” of the button control in my contextual ribbon and invoke another local method that’ll simply add an item to the announcements list.  Note: Look at the “demoContextualTab” variable declaration above to understand how we are tracking this event ID.

Now, I implement the page component java script file.  I do this by right-clicking on my project and adding a new SharePoint item “Module”.  I remove the “Elements.xml” and “Sample.txt” file.  I right-click on this Module node again and a java script file named “PostbackContextualDemo.js” (Note: This is the same name I’ve registered in my web part code).  Important: Once we have this java script file, switch to the properties of this JS file and change the “Deployment Type” property to “RootFile” and change the “Path” property (expand “Deployment Location” property) to “Template\Layouts\”.

image

Below is the complete code of my page component.  To have a good understanding of page component, please go through Developing Page Components for the Server Ribbon.

 Type.registerNamespace('PostbackContextualDemo');

var _webPartPageComponentId;
PostbackContextualDemo.PostbackPageComponent = function PostbackContextualDemo_PostbackPageComponent(webPartPcId) {
    this._webPartPageComponentId = webPartPcId;
    PostbackContextualDemo.PostbackPageComponent.initializeBase(this);
}

PostbackContextualDemo.PostbackPageComponent.prototype = {

    init: function PostbackContextualDemo_PostbackPageComponent$init() { },

    getFocusedCommands: function PostbackContextualDemo_PostbackPageComponent$getFocusedCommands() {
        return [];
    },

    receiveFocus: function () {
        return true;
    },

    yieldFocus: function () {
        return true;
    },

    getGlobalCommands: function PostbackContextualDemo_PostbackPageComponent$getGlobalCommands() {
        return ['CustomContextualTab.EnableCustomTab', 'CustomContextualTab.EnableCustomGroup', 'CustomContextualTab.HelloWorldCommand', 'CustomContextualTab.GoodbyeWorldCommand'];
    },

    isFocusable: function PostbackContextualDemo_PostbackPageComponent$isFocusable() {
        return true;
    },

    canHandleCommand: function PostbackContextualDemo_PostbackPageComponent$canHandleCommand(commandId) {
        if (commandId == 'CustomContextualTab.EnableCustomTab') {
            return true;
        }
        if (commandId == 'CustomContextualTab.EnableCustomGroup') {
            return true;
        }
        if (commandId == 'CustomContextualTab.HelloWorldCommand') {
            return true;
        }
        if (commandId == 'CustomContextualTab.GoodbyeWorldCommand') {
            return true;
        }
    },

    handleCommand: function PostbackContextualDemo_PostbackPageComponent$handleCommand(commandId, properties, sequence) {

        if (commandId === 'CustomContextualTab.HelloWorldCommand') {
            return handleCommand(commandId, properties, sequence);
        }
        if (commandId === 'CustomContextualTab.GoodbyeWorldCommand') {
            alert('Good-bye, world!');
        }
    },

    refreshRibbon: function () {
        SP.Ribbon.PageManager.get_instance().get_commandDispatcher().executeCommand(Commands.CommandIds.ApplicationStateChanged);
    },

    getId: function PostbackContextualDemo_PostbackPageComponent$getId() {
        return this._webPartPageComponentId;
    }
}

PostbackContextualDemo.PostbackPageComponent.registerClass('PostbackContextualDemo.PostbackPageComponent', CUI.Page.PageComponent);
if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) != "undefined") NotifyScriptLoadedAndExecuteWaitingJobs("PostbackContextualDemo.js");

Ok, now I am done! My completed project looks like this:

image

That’s it! Just deploy this solution to a SharePoint 2010 site, load up the “RibbonPostbackDemo” web part and see the ribbon button’s post back in action!

image

Here's the complete solution for you to download and try: RibbonPostbackDemo

Just open the solution in VS 2010, change the Site Url property to reflect your SharePoint site and deploy.

HTH!