Monday, 15 February 2010

Customize the ribbon programmatically from web parts and field controls

In this article series:

  1. Customizing the ribbon – creating tabs, groups and controls
  2. Adding ribbon items into existing tabs/groups
  3. Ribbon customizations - dropdown controls, Client Object Model and JavaScript Page Components
  4. Customize the ribbon programmatically from web parts and field controls (this post)

In contrast to my earlier posts, this article isn’t a detailed walkthrough – rather, it is a compendium of techniques and information which should help you implement ribbon customizations for web parts, custom field controls or indeed, any other kind of page control. At the time of writing I haven’t really seen a great deal written on this, aside from a couple of resources I’ll mention. I would have loved to have written detailed walkthroughs here, but alas I can’t because a) Microsoft have recently published some good info for one of the scenarios, so I’d prefer to point you to that, b) To run through all the scenarios here in depth would take weeks and c) Because if I don’t stop writing about the ribbon soon I’ll still be on this topic this time next year, and frankly I have a  book chapter to write on a completely different area of SharePoint 2010 which I need to crack on with! So we’ll look at each requirement and I’ll discuss what I think are the key techniques you’d use along with some observations from me.

Note that due to the general lack of documentation so far on this topic (and the fact I haven’t bottomed everything out in all the scenarios), a couple of things here are speculation rather than hard fact. I’ll make these items clear, and will endeavour to come back to this article and add updates as new information emerges. 

Before we dive in, remember that if you’re abiding by ribbon design principles you should most likely be working with a ContextualGroup (discussed towards the end of Adding ribbon items into existing tabs/groups) – this is the container to use for ribbon elements which are only relevant depending on what the user is doing (shown visible but not active here):

ContextualGroup

If this isn’t what you want, note that things are probably easier if you just need to add some controls on an existing tab or group which get activated under certain circumstances. In this case you can just supply some JavaScript to the ‘EnabledScript’ attribute of your controls – I showed this in my Notifications/Status demo in Customizing the ribbon (part 1) – creating tabs, groups and controls. The rest of this article focuses on how you might get a contextual group (see image above) to show in different scenarios.

 Adding ribbon items from a web part

In SharePoint 2010 the web part framework now has special provision for ribbon customizations, which means a couple of things are taken care of for you. Microsoft have now published some guidance on this scenario in the form of an article on the SharePoint Developer Documentation blog - How to Create a Web Part with a Contextual Tab. There’s a lot of golden info in there, but I’ll distil the main points here:

  • Using server-side code, the ‘RegisterDataExtension’ method of SPRibbon can be used to pass XML to the ribbon framework.
    • This is an alternative to the fully declarative approach using the CustomAction element. As far as I can tell, either technique can be used for ribbon customizations specific to a certain control only (as opposed to ribbon customizations for say, all lists of a specific type where CustomAction is the way to go).
  • Your web part needs to implement the new IWebPartPageComponentProvider interface and it’s WebPartContextualInfo property
    • This allows you to specify information about the associated ribbon customization(s) and the ID of the associated page component (which we discussed last time) for your custom bits. This allows the ribbon framework to ‘link’ your web part with your ribbon changes – meaning that certain things are taken care of for you e.g. firing commands only when your web part has focus on the page (if you specified this behaviour by use of ‘getFocusedCommands’ in your page component). Without this you would have to write JavaScript code to manually handle page events for controls emitted by your web part e.g. the click event for a textbox.

Adding ribbon items from a field control

Like web parts, I think (speculating here) there is special provision in the framework for ribbon customizations from a field control. Consider that, like web parts, field controls should typically only show their contextual ribbon options when they have focus - since this would be the case for all field controls, it makes sense to me that Microsoft might abstract some handling around this for us. If not, you would be responsible for writing JavaScript to detect when the user clicked into your field so that you could show your ContextualGroup.

Digging around, I notice that all SharePoint field controls have 4 new properties (since they are implemented on one of the base classes, Microsoft.SharePoint.WebControls.FormComponent):

  • RibbonTabCommand
  • RibbonContextualGroupCommand
  • RibbonGroupCommand
  • RibbonCommand

I’m surmising that these control properties would be set declaratively in the hosting page and are designed to match up with commands specified in an accompanying page component - this would enable you to run client-side code similar to my sample last time, perhaps to initialize data for your ribbon controls and so on. However, when I try to implement these commands, my page component’s ‘handleCommand’ method never receives these commands. So either I’m doing something wrong or this theory is incorrect. In which case, not to worry ribbon customizations for field controls should still be entirely possible, there will just be more work to do. Read on.

Using server-side code to show ribbon items

Thinking outside of any ‘framework support’ for where our ribbon customizations are targeted at, we can always write server-side or client-side code to show a contextual group. In fact, I already showed the server-side code for this in Adding ribbon items into existing tabs/groups (ribbon customization part 2):

protected override void OnPreRender(EventArgs e)
{
    SPRibbon currentRibbon = SPRibbon.GetCurrent(this.Page);
    currentRibbon.MakeTabAvailable("COB.SharePoint.Ribbon.ContextualTab");
    currentRibbon.MakeContextualGroupInitiallyVisible("COB.SharePoint.Ribbon.ContextualGroup", string.Empty);
    
    base.OnPreRender(e);
}

This is probably only appropriate if your stuff is contextual-ish – an application page would be a good example of this. In this example all we care about is that the user is on our page, then we can show our options. It doesn’t matter which control has focus, effectively our options are OK to show by default when the page loads. However, if you need page-level ‘contextuality’ (I may have just invented that word by the way - use it in a sentence to your boss today) then most likely you’ll be wanting to use JavaScript to show your contextual group when the user is doing something specific on the page e.g. editing a certain field. You’d then be looking to some client-side code to detect this and respond accordingly.  

Using client-side code to show ribbon items

So, final scenario - what if you need to show ribbon items from something which isn’t a web part or field control (or like me you couldn’t get there with anything in the field control framework which may or may not be designed to help), and it has to be done client-side to be fully contextual? Well, disappointingly I’m drawing another blank here so far – I’d love to hear from anyone who knows the answer to this. In case you’re doing ribbon development and are interested, here’s a summary of my journey:

  • Checked SP.Ribbon namespace in Client OM
  • Spent many happy hours in the debugger and various out-of-the-box JavaScript files, looking for examples of where the core product does this (e.g. calendar, rich text editor to name a couple)
  • Found some interesting methods on the CUI.Ribbon object, such as showContextualGroup(‘foo’) and selectTabByCommand(‘bar’)
    • Noted that across the entire SharePoint root, these methods are only called by the RTE, not globally across the SharePoint codebase
    • Noted that the RTE code gets a CUI.Ribbon instance from a property (RTE.RichTextEditorComponent.$3b() – N.B. most of the JS is obfuscated or machine-generated* down here)
  • Tried to use SP.Ribbon.PageManager.get_instance().get_ribbon() (used elsewhere in the OOTB codebase) to get me a CUI.Ribbon instance, however this gave me a null
  • Tried to use the ‘_ribbon’ page level variable, however this appears not to be of type CUI.Ribbon as the debugger shows it does not have the methods I’m trying to call
  • Tried a couple of other things which I’ve forgotten now

Needless to say, I’d love to hear what I’m missing on this. If nothing else, hopefully MS will release some more information soon which will shed some light on how to handle this scenario.

Summary

This post doesn’t claim to have all the answers, but it might serve as a “leg up" if you’re trying to build any of these scenarios now. I’m hoping that the lack of deep information in this area is a reflection on the fact that RTM is still some time away, and that ribbon dev will get some love in the SDK between now and then. The key scenarios I discussed here are displaying custom ribbon elements from web parts and field controls, but also the more generic cases of displaying customizations with server or client-side code.

Feel free to leave a comment if you can plug some of the gaps in my coverage here.

Friday, 5 February 2010

Ribbon customizations - dropdown controls, Client Object Model and JavaScript Page Components

In this article series:

  1. Customizing the ribbon – creating tabs, groups and controls
  2. Adding ribbon items into existing tabs/groups
  3. Ribbon customizations - dropdown controls, Client Object Model and JavaScript Page Components (this post)
  4. Customize the ribbon programmatically from web parts and field controls

Once you understand how to get your customizations into the right place in the ribbon, you may find you want to go beyond simply adding buttons and make use of other controls such as dropdowns, checkboxes, flyout anchors and so on. This type of ribbon development is fairly involved, but as with so many things in SharePoint development, once you’ve done it the first time you know the overall “template” for subsequent occasions - hopefully what I’m showing here is a good starting point for quite a few advanced things you might want to do. The key is that a JavaScript “page component” is generally required in addition to the declarative XML we’ve seen in my earlier posts.

[Beta sidenote] At the time of writing (Feb 2010, still in beta), after some time on this I’m forming the opinion that ribbon development could be one of the more complex development areas in SP2010. Maybe some stellar documentation from Microsoft could change this, but right now there are many dark corners which are quite difficult to understand – currently there is practically no coverage of much of this stuff in the SDK (and very little anywhere really), so unless you have inside information it’s mainly blood, sweat and tears all the way. I’ve mentioned this to the PM in the Product Group (Elisabeth Olson), and it sounds like more MS guidance is on the way soon, so let’s hope.

The sample

My sample shows the use of a custom dropdown in the ribbon – I’ll go into more detail later, but the concepts I’m showing here can be used for many controls in the ribbon, not just a dropdown. So if what you’re wanting to do doesn’t specifically involve a dropdown, I’d suggest reading on anyway as this technique is still probably what you will use.

When clicked the first time, it uses the Client Object Model to fetch a collection of lists in the current web, then presents them as options in the dropdown:

CustomDropdownInRibbon

CustomDropdownInRibbonExpanded

When an item is selected, a simple JavaScript alert is raised with the name of the selected list, though a real-life implementation would of course do something more useful with the value. The goal here is to illustrate how to work with ribbon controls other than buttons, and also how to write code behind them – once you can do this, you’ll be able to build a wide range of solutions.  

One key thing to note – it IS possible to add items to a dropdown or other control entirely in XML. I’m choosing to use a JavaScript page component to illustrate what happens when you need “code-behind” e.g. to iterate all the lists in a web in my case.

What’s required – summary

  1. Declarative XML to provision the ribbon controls
  2. JavaScript “page component”, typically declared in external .js file
  3. Addition of JavaScript to page (using whatever technique is most appropriate to the scope of your ribbon customization – code in a web part/delegate control which is added to AdditionalPageHead, etc.). This will:
    1. Link the external .js file
    2. Ensure core dependent .js files are loaded e.g. SP.js, CUI.js
    3. Call into the initialization function within our page component – this registers our component with the ribbon framework and ensures our component gets added to the page.

1. Declarative XML

I used the following XML – here I’m actually showing a cut-down extract which is just for the group containing my controls. Really it’s just the ‘Controls’ section which is the most interesting bit, the surroundings would depend on whether you are wanting to create a new tab or add the items into an existing tab/group, see my previous articles for those details.

Key points of note are the Command, PopulateQueryCommand, and QueryCommand attributes on the dropdown – these will link into our JavaScript page component:

<Group
  Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup"
  Description="Contains advanced ribbon controls"
  Title="Page component sample"
  Sequence="53"
  Template="Ribbon.Templates.COB.OneLargeExample">
  <Controls Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Controls">
    <Label Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Label" 
           ForId="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown" 
           Command="LabelCommand"
           LabelText="Select list:"
           Sequence="16" 
           TemplateAlias="c1"/>
    <DropDown
      Id="COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown"
      Sequence="17"
      Command="COB.PageComponent.Command.DoAction"
      PopulateDynamically="true"
      PopulateOnlyOnce="true"
      PopulateQueryCommand="COB.PageComponent.Command.PopulateDropDown"
      QueryCommand="COB.PageComponent.Command.QueryDoAction"
      Width="75px"
      TemplateAlias="c2" />
  </Controls>
</Group>

2. JavaScript page component

This is the complex bit, the first time at least. We are effectively writing object-oriented JavaScript which contains a class which powers our ribbon control. Consider JavaScript such as this the ‘template’ to use for page components, where you’ll modify the actual implementation bits each time. I’ve commented some key points, suggest having a scroll through and then we’ll walk through the highlights:

Type.registerNamespace('COB.SharePoint.Ribbon.PageComponent');
 
COB.SharePoint.Ribbon.PageComponent = function () {
    COB.SharePoint.Ribbon.PageComponent.initializeBase(this);
}
 
// the initialize function needs to be called by some script added to the page elsewhere - in the end, it does the important work 
// of calling PageManager.addPageComponent()..
COB.SharePoint.Ribbon.PageComponent.initialize = function () {
    ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, COB.SharePoint.Ribbon.PageComponent.initializePageComponent), 'SP.Ribbon.js');
}
COB.SharePoint.Ribbon.PageComponent.initializePageComponent = function() {
    
    var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
    if (null !== ribbonPageManager) {
        ribbonPageManager.addPageComponent(COB.SharePoint.Ribbon.PageComponent.instance);
    }
}
 
COB.SharePoint.Ribbon.PageComponent.prototype = {
    init: function () { },
 
    getFocusedCommands: function () {
        return ['COB.PageComponent.Command.FieldControl.GroupCommand', 'COB.PageComponent.Command.FieldControl.TabCommand', 'COB.PageComponent.Command.FieldControl.ContextualGroupCommand', 'COB.PageComponent.Command.FieldControl.RibbonCommand'];
    },
 
    getGlobalCommands: function () {
        return ['COB.PageComponent.Command.DoAction', 'COB.PageComponent.Command.PopulateDropDown', 'COB.PageComponent.Command.QueryDoAction'];
    },
 
    canHandleCommand: function (commandId) {
        if ((commandId === 'COB.PageComponent.Command.DoAction') ||
            (commandId === 'COB.PageComponent.Command.PopulateDropDown') || (commandId === 'COB.PageComponent.Command.QueryDoAction')) {
            return true;        
        }
        else {
            return false;
        }
    },
 
    handleCommand: function (commandId, properties, sequence) {
        if (commandId === 'COB.PageComponent.Command.FieldControl.GroupCommand') {
            alert("COB.PageComponent.Command.FieldControl.GroupCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.FieldControl.TabCommand') {
            alert("COB.PageComponent.Command.FieldControl.TabCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.FieldControl.ContextualGroupCommand') {
            alert("COB.PageComponent.Command.FieldControl.ContextualGroupCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.FieldControl.RibbonCommand') {
            alert("COB.PageComponent.Command.FieldControl.RibbonCommand fired");
        }
        if (commandId === 'COB.PageComponent.Command.QueryDoAction') {
            // this command executes as soon as tab is requested, so do initialization here ready for if our dropdown gets requested..
            loadCurrentWebLists();
        }
        if (commandId === 'COB.PageComponent.Command.PopulateDropDown') {
            // actually build the dropdown contents by setting the PopulationXML property to a value with the expected format. We have to deal with possible 
            // timing issues/dependency on core SharePoint JS code with an ExecuteOrDelay..
            ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, getDropdownItemsXml), 'SP.js');
 
            properties.PopulationXML = getDropdownItemsXml();
        }
        if (commandId === 'COB.PageComponent.Command.DoAction') {
            // here we're using the SourceControlId to detect the selected item, but more normally each item would have a unique commandId (rather than 'DoAction'). 
            // However this isn't possible in this case since each item is a list in the current web, and this can change..
            var selectedItem = properties.SourceControlId.toString();
            var listName = selectedItem.substring(selectedItem.lastIndexOf('.') + 1);
            alert("You selected the list: " + listName);
        }
    },
 
    isFocusable: function () {
        return true;
    },
 
    receiveFocus: function () {
        return true;
    },
 
    yieldFocus: function () {
        return true;
    }
}
 
// **** BEGIN: helper code specific to this sample ****
 
// some global variables which we'll use with the async processing..
var lists = null;
var querySucceeded = false;
 
// use the Client Object Model to fetch the lists in the current site..        
function loadCurrentWebLists() {
    var clientContext = new SP.ClientContext.get_current();
    var web = clientContext.get_web();
    this.lists = web.get_lists();
 
    clientContext.load(lists);
    clientContext.executeQueryAsync(
           Function.createDelegate(this, this.onQuerySucceeded),
           Function.createDelegate(this, this.onQueryFailed));
}
 
function onQuerySucceeded() {
    querySucceeded = true;
}
 
function onQueryFailed(sender, args) {
    querySucceeded = false;
}
 
function getDropdownItemsXml() {
    var sb = new Sys.StringBuilder();
    sb.append('<Menu Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu\'>');
    sb.append('<MenuSection DisplayMode=\'Menu\' Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu.Manage\'>');
    sb.append('<Controls Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu.Manage.Controls\'>');
    if (querySucceeded)
    {
        var listEnumerator = lists.getEnumerator();
 
        while (listEnumerator.moveNext()) {
            var oList = listEnumerator.get_current();
            
            sb.append('<Button');
            sb.append(' Id=\'COB.SharePoint.Ribbon.WithPageComponent.PCNotificationGroup.Dropdown.Menu.Manage.');
            sb.append(oList.get_title());
            sb.append('\'');
            sb.append(' Command=\'');
            sb.append('COB.PageComponent.Command.DoAction');
            sb.append('\'');
            sb.append(' LabelText=\'');
            sb.append(SP.Utilities.HttpUtility.htmlEncode(oList.get_title()));
            sb.append('\'');
            sb.append('/>');
        }
    }
    sb.append('</Controls>');
    sb.append('</MenuSection>');
    sb.append('</Menu>');
    return sb.toString();
}
  
// **** END: helper code specific to this sample ****
 
COB.SharePoint.Ribbon.PageComponent.registerClass('COB.SharePoint.Ribbon.PageComponent', CUI.Page.PageComponent);
COB.SharePoint.Ribbon.PageComponent.instance = new COB.SharePoint.Ribbon.PageComponent();
 
NotifyScriptLoadedAndExecuteWaitingJobs("COB.SharePoint.Ribbon.PageComponent.js");
 
  • The ‘initialize’ function is typically responsible for calling ‘addPageComponent’ on the ribbon PageManager (but not before SP.Ribbon.js has loaded)
  • The commands referenced in the JS are those specified in the control XML e.g. for my dropdown
    • The ‘getFocusedCommands’ function returns an array of commands which should execute when my control has focus
    • The ‘getGlobalCommands’ function returns an array of commands which should execute regardless of focus
    • We need to list the commands which can be handled in the ‘canHandleCommand’ function, and provide the actual implementation for each of these in ‘handleCommand’
  • Note the following crucial points about the various commands used:
    • PopulateQueryCommand – used to build the list of items in the control. This is where I’m using the Client Object Model (ECMAScript version) to fetch the lists for the current web.
    • QueryCommand – called when the parent container (e.g. tab) is activated. Remember the ribbon is all about “script on demand” (lazy loading), so I’m choosing this as a better place to do my initialization work of the actual Client OM request – more on this later.
    • Command – called when the user actually selects an item
  • IMPORTANT – these commands apply to lots of ribbon controls other than dropdowns e.g. FlyoutAnchor, SplitButton, ToggleButton, TextBox, Checkbox, Spinner, etc. This is why this information is relevant even if it’s not specifically a dropdown control you’re working with.
  • The key to populating controls which take collections is to use the ‘properties’ object passed to handleCommand, using either: ul>
  • properties.PopulationXML
  • properties.PopulationJSON
  • I’m using properties.PopulationXML to supply the items which should appear in my dropdown, and the format required is:
       1: <Menu Id="">
       2:   <MenuSection Id="">
       3:     <Controls Id="">
       4:       <Button Command="" Id="" LabelText="" />
       5:       ..a 'Button' element here for each item in the collection..
       6:     </Controls>
       7:   </MenuSection>
       8: </Menu>
    I haven’t yet seen an example of how to use the .PopulationJSON property, so don’t know the exact names to use in the JSON.
  • There are some interesting facets to combining the Client OM with the ribbon JS framework – effectively the async model used means the result of your method call may not be ready by the time the ribbon framework needs it (it happens on a different request after all). I’ll explain how I dealt with this in my example towards the end of this article.

3. Page-level JavaScript

The final element is the JavaScript you need to add to the page to call into the page component. In my example I’m happy for this JavaScript to be added to every page in my site (since that’s the scope of my ribbon customization), so I used a delegate control in AdditionalPageHead to add a custom user control, the body of which looks like this: 

<SharePoint:ScriptLink Name="sp.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink1" />
<SharePoint:ScriptLink Name="CUI.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink3" />
<SharePoint:ScriptLink Name="/_layouts/COB.SharePoint.Demos.Ribbon/COB.SharePoint.Ribbon.PageComponent.js" LoadAfterUI="true" OnDemand="false" Localizable="false" runat="server" ID="ScriptLink2" />
    <script type="text/javascript">
     
        //<![CDATA[
            function initCOBRibbon() {
                COB.SharePoint.Ribbon.PageComponent.initialize();
            }
     
            ExecuteOrDelayUntilScriptLoaded(initCOBRibbon, 'COB.SharePoint.Ribbon.PageComponent.js');
    //    
    //]]>
</script>

The important things here are that we ensure required system JS files are loaded with the ScriptLink tag, do the same for our JS file, then call the .initialize() function of our page component.

So those the component pieces for complex controls in the ribbon! A wide variety of ribbon customizations should be possible by tailoring this information/sample code as needed (remember ‘handleCommand’ is the key implementation hook), and I definitely think that starting from such a template is the way to go.

Appendix - considerations for using the Client Object Model in the ribbon

When working with the ribbon it quickly becomes apparent that if the Client Object Model didn’t exist, things would be much trickier – they are a natural pairing for many requirements. Despite this, some challenges arise – consider that a control (e.g. dropdown) will have it’s items collection populated as late as possible if ‘PopulateDynamically’ is set to true (generally a good idea) i.e. when the dropdown is actually clicked to select an item! This is because the ribbon is designed around a “script on demand” model (you’ll often see “SOD” references in Microsoft’s JavaScript) – this ensures only the required JavaScript is downloaded to the client, and no more. This solves the issue where on SharePoint 2007 WCM sites, we would suppress the core.js file for anonymous users because it was big and not required for these users. Anyway, when the dropdown is clicked, at this point the ribbon framework calls ‘handleCommand’ with the command specified for the ‘PopulateQueryCommand’ value. If you run your Client OM code here it’s no good, since you won’t get the result there and then due to the async model – the result will be provided to the callback function, long after ‘handleCommand’ has completed, so the end result is your control will be empty.

Consequently, you need to do the actual processing before the ‘PopulateQueryCommand’ is called. You could choose to do as soon as the page loads, but in most cases this could be inefficient – what if the user doesn’t come near your ribbon control on this page load? In this case we would have incurred some client-side processing and an extra request to the server which was completely unnecessary – on a high-traffic page, this could be bad news. Without any documentation it’s hard to be sure at this stage, but it seems the ‘QueryCommand’ is a good place to put such Client OM code – this seems to be called when the parent container (e.g. tab) is made visible (at which point there’s now a chance the user could use our control). In my code I have the actual Client OM query run here and store the result in page-level variable - this is then picked up and iterated for the ‘PopulateQueryCommand’. By the time the script runs this command to populate the control, the query has already executed and has the data ready – happy days. I’ll be interested to see what emerges in this area to see whether this is the expected pattern or, well, if I’ve got things completely wrong.

Summary

Complex ribbon customizations are likely to require a JavaScript page component - in general page components are somewhat complex (partly because of the current lack of documentation perhaps), but once you have a suitable template, subsequent implementations should be easier. If you need to use Client Object Model code, beware of the “async/lifecycle issue” discussed here and ensure your code has data ready for the ribbon framework.