Navigation

Monday 12 July 2010

RCWP Part 2 - Web Part with Ribbon Contextual-Tab

Also check out the other part of the series:
This follows on from Part 1, where we created a “Related Content Web Part” (RCWP) which would dynamically return search results based on the value of a field on the current page.

This post summarises the second-phase of that Web Part (which we started in session 4 of SPRetreat) to provide an easier method for the Content Editors to cherry-pick which field they wanted to use for the keyword search value. Seeing as me and my session 4 partner hadn’t done anything with Contextual Tabs before, we decided to give that a crack.

I’m not going to cover every single details of this. There are 2 fantastic articles:
Chris O’Brien (MVP) has a great 4-Part series titled “SharePoint 2010 Ribbon Customisation series” which is a must-read if you want to understand more about Ribbon development work.

There is also an excellent step-by-step guide on the Microsoft SharePoint Developer Documentation Blog called “How to create a Web Part with a contextual tab”.

In my example, I used the Microsoft blog’s example code, but there are plenty of good tutorials for this on the web. I’m not going to copy the same information in the blog article here, but the summarised core points are as follows:
  • Your Web Part must implement the Interface IWebPartPageComponentProvider(Microsoft.SharePoint.WebControls).
  • The Interface involves a WebPartContextualInfo property, which you use to register you “contextual” event
  • You will need a JS file to stored your “actions”
  • You will need XML configuration to register the definition of your “tab” and it’s controls.
So first off, we need to modify our web part to implement the Interface IWebPartPageComponentProvider:
public class RelatedContentWebPart : CoreResultsWebPart,
    IWebPartPageComponentProvider
{
    // omitted for clarity
}
Now, before we implement the Interface we need to setup a few methods and properties.
First up is the XML schema for both the Tab Template and the Tab itself. It’s a lot of XML so I’m not going to post it here, but I basically copied the XML provided on the MS Blog post, so you should find it easy enough to go and fetch it from there or just use my Full Source Code (in Part 3).

A snippet of the first part is below;
private string contextualTab = @"
   <ContextualGroup Color=""Magenta""
     Command=""CustomContextualTab.EnableContextualGroup""
     Id=""Ribbon.CustomContextualTabGroup""
     Title=""Related Content Web Part""
     Sequence=""502""
     ContextualGroupId=""CustomContextualTabGroup"">
          <Tab
              Id=""Ribbon.CustomTabExample""
              Title=""Filtering Settings""
              Description=""This holds my custom commands!""
// remainder omitted for clarity
You can see that we have created a string called “contextualTab” that references a “ContextualGroup”. This has a colour, as well as a unique ID that we need to create. The Title will appear in the ribbon as the “contextual” group, which will span across multiple tabs (if you have multiple tabs in the same “contextual group” of course!).

Note - I have changed the title and description of my Tab and the Contextual Group, but everything else is the same as the MS Blog!

We also have a standard Ribbon “Tab” declaration, with an Id value which we will need later) as well as the Tab Title and Description.

Further down in the XML is a reference to the "Buttons” that we are going to have on our tab.
(Note – the code below is an exact copy of the "MS Blog Article” code .. we will be changing this later on!)
<Button     Id=""Ribbon.CustomTabExample.
    CustomGroupExample.GoodbyeWorld""
    Command=""CustomContextualTab.GoodbyeWorldCommand""
    Sequence=""15""
    Description=""Show the Goodbye World text""
    LabelText=""Goodbye World!""
    TemplateAlias=""cust1""/>
Make a note of where these button controls are, because we’re going to need to modify this in Part 3 of this post!

There is also another string created called “contextualTabTemplate” which you can use for the layout configuration.
private string contextualTabTemplate = @"
          <GroupTemplate Id=""Ribbon.Templates.CustomTemplateExample"">
            <Layout
              Title=""OneLargeTwoMedium"" LayoutTitle=""OneLargeTwoMedium"">
              <Section Alignment=""Top"" Type=""OneRow"">
                <Row>
                  <ControlRef DisplayMode=""Large"" TemplateAlias=""cust1"" />
                </Row>
              </Section>
// remainder omitted for clarity
So … now we have all of our XML defined we need to create a JavaScript file. The MS Blog article includes a JS file called “CustomContextualTabPageComponent.js” which deploys directly to the 14\Template\Layouts\ folder.
This contains a whole load of JavaScript for the Tab, Group and Button items in your XML file.

Important Note – The Command and Enable scripts use in your XML are in this JS file. If you start changing names you will need to manually keep these files in check.. they are XML and JS files so no compile errors or warnings! … except this one ;)

Probably the most important section in this file is that which handles the “Command” event for your buttons:
handleCommand: function ContextualTabWebPart_CustomPageComponent
    $handleCommand(commandId, properties, sequence)
{
    if (commandId ===
        'CustomContextualTab.HelloWorldCommand) {
            alert('Hello, world!');
    }
    if (commandId ===
        'CustomContextualTab.GoodbyeWorldCommand') {
            alert('Good-bye, world!');
    }
}
Again, this code has been copied from the MS Blog, but keep a note of it .. we will be modifying this too in Part 3!

So .. we have our XML schema, and we have our JS file created, but we haven’t actually DONE anything with them yet.

So lets create a method to register our new Contextual Tab:
private void AddContextualTab()
        {
            //Gets the current instance of the ribbon on the page.
            Microsoft.Web.CommandUI.Ribbon ribbon = SPRibbon.GetCurrent(this.Page);
            //Prepares an XmlDocument object used to load the ribbon extensions.
            XmlDocument ribbonExtensions = new XmlDocument();
            //Load the contextual tab XML and register the ribbon extension.
            ribbonExtensions.LoadXml(this.contextualTab);
            ribbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.ContextualTabs._children");
            //Load the custom templates and register the ribbon extension.
            ribbonExtensions.LoadXml(this.contextualTabTemplate);
            ribbon.RegisterDataExtension(ribbonExtensions.FirstChild, "Ribbon.Templates._children");
        }
So .. this method is basically using the SPRibbon object to get a reference to the Ribbon on the current page. It is then loading in our XML schema to register a new contextual tab).
(Eventually we will end up calling this method from the PreRender() event).

Now we are going to need to register our JavaScript, and strangely enough we are going to do that with yet more JavaScript! (yes .. when you are working with the Ribbon JavaScript really is word of the day!)
public string DelayScript
        {
            get
            {
            string webPartPageComponentId = SPRibbon.GetWebPartPageComponentId(this);
            return @"
            <script type=""text/javascript"">
            //<![CDATA[
            function _addCustomPageComponent()
            {
                var _customPageComponent = new ContextualTabWebPart.CustomPageComponent('" + webPartPageComponentId + @"');
                SP.Ribbon.PageManager.get_instance().addPageComponent(_customPageComponent);
            }
            function _registerCustomPageComponent()
            {
                SP.SOD.registerSod(""CustomContextualTabPageComponent.js"", ""\/_layouts\/CustomContextualTabPageComponent.js"");
                SP.SOD.executeFunc(""CustomContextualTabPageComponent.js"", ""ContextualWebPart.CustomPageComponent"", _addCustomPageComponent);
            }
            SP.SOD.executeOrDelayUntilScriptLoaded(_registerCustomPageComponent, ""sp.ribbon.js"");
            //]]>
            </script>";
            }
        }
The really important part of this is the first line:
SPRibbon.GetWebPartPageComponentId(this);
This gets a unique reference for the instance of your Web Part, on the current page, in the context of the Ribbon. This is used to effectively identify your Web Part when someone clicks on it!
The other really important bit is the last line:
SP.SOD.executeOrDelayUntilScriptLoaded(_registerCustomPageComponent, ""sp.ribbon.js"");
This is the new “Script On Demand” (SOD) method which allows us to tell SharePoint not to try loading our JavaScript until the SP.Ribbon.JS has already been processed!
Once that has been done we need to implement our WebPartContextualInfo method:
public WebPartContextualInfo WebPartContextualInfo
        {
            get
            {
                // create objects for the contextual web part tab
                WebPartContextualInfo info = new WebPartContextualInfo();
                WebPartRibbonContextualGroup contextualGroup = new WebPartRibbonContextualGroup();
                WebPartRibbonTab ribbonTab = new WebPartRibbonTab();
                //Create the contextual group object and initialize its values.
                contextualGroup.Id = "Ribbon.CustomContextualTabGroup";
                contextualGroup.Command = "CustomContextualTab.EnableContextualGroup";
                contextualGroup.VisibilityContext = "CustomContextualTab.CustomVisibilityContext";
                //Create the tab object and initialize its values.
                ribbonTab.Id = "Ribbon.CustomTabExample";
                ribbonTab.VisibilityContext = "CustomContextualTab.CustomVisibilityContext";
                //Add the contextual group and tab to the WebPartContextualInfo.
                info.ContextualGroups.Add(contextualGroup);
                info.Tabs.Add(ribbonTab);
                // fetch dynamic component info for the current page's Ribbon control
                info.PageComponentId = SPRibbon.GetWebPartPageComponentId(this);
                return info;
            }
        }
Now lets take a quick look through some of this code first. The first thing you should notice is that there are 3 new classes provided in the OOB API specifically for providing contextual tabs (from Web Parts):
  • WebPartContextualInfo
  • WebPartRibbonContextualGroup
  • WebPartRibbonTab
These objects contains all of the core functionality that you will need to handle the interaction between selecting your Web Part in edit mode, and the Ribbon dynamically popping up your Tab when that happens.
The RibbonContextualGroup and RibbonTab objects then get given both an ID property and a VisibilityContext. These values refer to the same pointers in the XML schema definition for the Tab, Group and Controls that we are going to register (which we created earlier).
The final part of the puzzle is to add these to the Web Part’s load stack (in this case, the Pre-Render method):
protected override void OnPreRender(EventArgs e)
{
            base.OnPreRender(e);
            this.AddContextualTab();
            ClientScriptManager clientScript = this.Page.ClientScript;
            clientScript.RegisterClientScriptBlock(this.GetType(), "ContextualTabWebPart", this.DelayScript);
}
So, we are registering our contextual tab (using the AddContextualTab method we created earlier).
We then dynamically add a “lazy load” reference to our “DelayScript” string, which will register all of the JavaScript functions that we are using.

Build / Deploy / Test
There you have it … a contextual tab in edit mode!



Make sure you check back for:


  • RCWP Part 3 – Edit Web Part using a Ribbon modal dialog


  • (Full source code for the solution will be published in Part 3)

    No comments:

    Post a Comment

    This blog has been moved to www.martinhatch.com

    Note: only a member of this blog may post a comment.