Adding ribbon items into existing tabs/groups (ribbon customization part 2)

This is the second article in my series on ribbon customization. Last time we looked in detail at creating custom tabs, and in the course of that also looked at how to create "groups" on a tab, and also how to add controls. We mentioned that the container hierarchy is ribbon > tab > group > controls, and showed how to create all of those in my example (which also used SP.UI.Status/SP.UI.Notify in the Client OM). The XML there was fairly extensive, but in this post we’ll see things are somewhat simpler if you don’t need to create an entire tab and instead only need to add something into an existing area of the ribbon. Here we cover the two main scenarios:

  • Adding a group to an existing tab
  • Adding controls to an existing group on an existing tab

Also, the last post covered a lot of ground on creating new tabs etc. but I didn’t get to cover something which I wanted to, so we’ll mop that up here:

  • Creating contextual tabs

Summary of approach – customizing existing ribbon areas

In order to slot your customizations into existing places, the following approach is used (I’ve broken it down, but in reality you’ll probably do some of this cross-referencing automatically once you understand the relationship between various chunks of ribbon XML and the result):

  1. Identify the location you wish to add your customization(s) toDefault Ribbon Customization Locations has a granular list, but I find it easier to first identify just the tab you’re shooting for from the list below (taken from CMDUI.XML):
    • Ribbon.Read
    • Ribbon.BDCAdmin
    • Ribbon.DocLibListFormEdit
    • Ribbon.ListForm.Display
    • Ribbon.ListForm.Edit
    • Ribbon.PostListForm.Edit
    • Ribbon.SvcApp
    • Ribbon.Solution
    • Ribbon.UsageReport
    • Ribbon.WikiPageTab
    • Ribbon.PublishTab
    • Ribbon.WebPartPage
    • Ribbon.WebApp
    • Ribbon.SiteCollections
    • Ribbon.CustomCommands      (note that these are just ‘standard’ tabs – read on for tabs in contextual groups)
  2. Find the declaration in CMDUI.XML for this tab, by searching on the string ID.
  3. If you haven’t already, find the ribbon location in the SharePoint UI – compare with the XML so you know what you’re working with.
  4. Stop, do not pass go without collecting the following information:
    1. Full ‘Location’ value of where you are targeting, i.e. the Location of the tab (if you’re adding a group) or group (if you’re directly adding controls)
    2. The ‘Sequence’ number you want to use – determined by looking at the Sequence numbers of the surrounding elements and working out where exactly you want to put your customization. As you’d expect, Sequence is a left-to-right representation of how things come out on the page.
    3. If adding a group, the ‘GroupTemplate’ used by an existing group with similar controls.
    4. The ‘TemplateAlias’ used by a control which is the same type (e.g. button) and size (e.g. large) as the control you are adding.
  5. Use these values when generating the XML for your customization.

So with that process in mind, let’s look at the XML needed for the scenarios I listed.

Adding a group to an existing tab:

In this example I’m provisioning a single button (lifted from last week’s example) into my new group, which is then being added to the wiki page editing tab (‘Ribbon.WikiPageTab’) in CMDUI.XML. Even if you only have a single control, groups are still very useful as they visually ‘categorize’ your button so it’s function is separated from it’s neighbors. Here I picked a Sequence of ‘15’ to nestle in between the ‘Ribbon.WikiPageTab.EditAndCheckout’ group (10) and ‘Ribbon.WikiPageTab.Manage’ (20):

Ribbon.WikiPageTab

Here’s the XML used to get this – you’ll notice there’s much less of it compared to last week’s ‘full ribbon’ example:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:     Id="COB.SharePoint.Ribbon.NewGroupInExistingTab"
   5:     Location="CommandUI.Ribbon">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition
   9:           Location="Ribbon.WikiPageTab.Scaling._children">
  10:           <MaxSize
  11:             Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.MaxSize"
  12:             Sequence="15"
  13:             GroupId="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup"
  14:             Size="Large" />
  15:         </CommandUIDefinition>
  16:         <CommandUIDefinition
  17:           Location="Ribbon.WikiPageTab.Groups._children">
  18:           <Group
  19:             Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup"
  20:             Sequence="15"
  21:             Description="Used to demo adding a group"
  22:             Title="Chris's custom group"
  23:             Template="Ribbon.Templates.Flexible">
  24:             <Controls Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.Controls">
  25:               <Button
  26:                 Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.NotifyHello"
  27:                 Command="COB.NewGroupInExistingTab.Command.Notify"
  28:                 Sequence="15" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
  29:                 Description="Uses the notification area to display a message."
  30:                 LabelText="Notify hello"
  31:                 TemplateAlias="o1" />
  32:             </Controls>
  33:           </Group>
  34:         </CommandUIDefinition>
  35:       </CommandUIDefinitions>
  36:       <CommandUIHandlers>
  37:         <CommandUIHandler
  38:           Command="COB.NewGroupInExistingTab.Command.Notify"
  39:           CommandAction="javascript:
  40:             SP.UI.Notify.addNotification('Hello from the notification area'); 
  41:           " />
  42:       </CommandUIHandlers>
  43:     </CommandUIExtension>
  44:   </CustomAction>
  45: </Elements>

Points of note:

  • The Location of ‘Ribbon.WikiPageTab.Groups._children’ on the main CommandUIDefinition tells the framework I am adding to the groups collection to the ‘Ribbon.WikiPageTab’ tab. This makes sense as I am adding a group.
  • When adding a group, you must decide whether to use an out-of-the-box GroupTemplate (defines how controls are laid out), or whether you will supply the definition. In this sample I’m using ‘Ribbon.Templates.Flexible’ which is suitable for simple layouts like this (one button!), whereas in the last article I showed creating a custom GroupTemplate. It’s worth spending some time in CMDUI.XML looking at the wide range of existing generic templates before creating your own, but by the same token sometimes it might be simpler just to create one, rather than find an existing one which matches the controls you’re trying to lay out. It soon becomes clear how the XML works here though – the analogy I used last week is using HTML to define a table.
    • Remember that the ‘TemplateAlias’ on your controls must match one defined somewhere in the GroupTemplate – in my example above, I’m using a TemplateAlias of ‘o1’ which I know is defined in ‘Ribbon.Templates.Flexible’
  • A ‘MaxSize’ definition must be supplied to the appropriate Scaling collection (i.e. ‘Ribbon.WikiPageTab.Scaling._children’ in this case), though it seems a ‘Scale’ definition is optional (e.g. if you want different controls to be used when less space is available)

Adding controls to an existing group:

If all we need to do is add a control or two to an existing group on an existing tab, things are simpler still. All we need to do is define the control/specify where it goes (CommandUIDefinition) and add the JavaScript behaviour (a CommandUIHandler element for simple cases – the approach for complex cases is discussed later in the series). For this example I’m adding to ‘Ribbon.ListForm.Display.Manage.Controls._children’ – this is DispForm.aspx (the list item display form), and is a good reminder that ribbons exist in application pages and can be customized there too:

Ribbon.ListForm.Display.Manage

..and here’s the relevant XML:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:    Id="COB.SharePoint.Ribbon.NewControlInExistingGroup"
   5:    Location="CommandUI.Ribbon" Sequence="20">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition Location="Ribbon.ListForm.Display.Manage.Controls._children">
   9:           <Button Id="COB.SharePoint.Ribbon.NewControlInExistingGroup.Notify"
  10:                   Command="COB.Command.NewControlInExistingGroup.Notify"
  11:                   Sequence="5" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
  12:                   Description="Uses the notification area to display a message."

  13:                   LabelText="Notify hello"
  14:                   TemplateAlias="o1"/>
  15:         </CommandUIDefinition>
  16:       </CommandUIDefinitions>
  17:       <CommandUIHandlers>
  18:         <CommandUIHandler
  19:           Command="COB.Command.NewControlInExistingGroup.Notify"
  20:           CommandAction="javascript:
  21:           
  22:           SP.UI.Notify.addNotification('Hello from the notification area'); 
  23:           " />
  24:       </CommandUIHandlers>    
  25:     </CommandUIExtension>
  26:   </CustomAction>
  27: </Elements>

Points of note:

  • Scaling instructions such as ‘MaxSize’ are not required, nor is a ‘Group’ or ‘GroupTemplate’ – since all these apply to groups, which we are not creating
  • As mentioned above, ensure that the ‘TemplateAlias’ on your controls matches one defined somewhere in the GroupTemplate for the parent group. If you’re using one of the ‘Flexible’ templates, ‘o1’ gives a large button whilst ‘o2’ gives a medium one. Always check the XML for the target group though, there could be differences.

The ‘Custom Commands’ tab

Before rushing off to create your new tab, consider that SharePoint 2010 already provides a home which may be suitable for your customization, for list pages at least – the Custom Commands tab. It only appears on list pages and contains just one lonely button under normal circumstances. This tab is actually the post-upgrade home for any SharePoint 2007 CustomActions you had – assuming you don’t introduce changes during your upgrade process, any CustomActions which didn’t target the ECB will end up here. In any case, it could help avoid tab proliferation so don’t forget it when building your customizations:

CustomCommands 

Creating new contextual tabs

So this section doesn’t quite fit with the theme of this article (adding items to existing areas of the SharePoint ribbon), but I vote we conveniently gloss over that fact. Anyway, in addition to regular tabs, you’ll have noticed that the ribbon also displays many contextual tabs which only appear when relevant. In fact, these are really contextual groups which can contain any number of tabs – perhaps the most common are the ‘List Tools’ (blue, shown in the last screenshot) and ‘Library Tools’ which appear in lists and libraries respectively. If we have custom ribbon controls which we only want to present conditionally, a contextual group could be a good design choice since it would fit well with existing ribbon semantics. They also look rather sexy. As you might expect, we can amend existing contextual groups or create our own:

ContextualGroup

To do this, simply wrap your Tab element(s) in a ‘ContextualGroup’ element (notice the CustomAction element has no RegistrationId/RegistrationType attributes, meaning it is scoped globally) and write some code which runs when the group should be shown. First the declaration of the ContextualGroup and Tab:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:    Id="COB.SharePoint.Ribbon.ContextualTab"
   5:    Location="CommandUI.Ribbon">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition Location="Ribbon.ContextualTabs._children">
   9:           <ContextualGroup Id="COB.SharePoint.Ribbon.ContextualGroup" Sequence="50" Color="Orange" Command="COBContextualGroupCommand" ContextualGroupId="COB.Contextual" Title="Chris's Contextual Group">
  10:             <Tab Id="COB.SharePoint.Ribbon.ContextualTab" Title="Chris's custom tab" Description="Groups and controls will go in here" Sequence="501">
  11:               <!-- Add Scaling, Groups, GroupTemplates (optional), CommandUI Handlers etc. in here as per creating a normal tab -->
  12:             </Tab>
  13:           </ContextualGroup>
  14:         </CommandUIDefinition>
  15:       </CommandUIDefinitions>
  16:     </CommandUIExtension>
  17:   </CustomAction>
  18: </Elements>

Here I’m writing server-side code (e.g. from a web part, custom field control, or other custom control), and all you need to do is call SPRibbon.MakeTabAvailable() and SPRibbon.MakeContextualGroupInitiallyVisible(), the latter assuming you want your group to be visible immediately when the page loads:

   1: protected override void OnPreRender(EventArgs e)
   2: {
   3:     SPRibbon currentRibbon = SPRibbon.GetCurrent(this.Page);
   4:     currentRibbon.MakeTabAvailable("COB.SharePoint.Ribbon.ContextualTab");
   5:     currentRibbon.MakeContextualGroupInitiallyVisible("COB.SharePoint.Ribbon.ContextualGroup", string.Empty);
   6:     
   7:     base.OnPreRender(e);
   8: }

In the absence of documentation, equivalent client-side code isn’t immediately obvious but I’ll dig around and update this post when I find it.

In terms of updating an existing contextual group or child tab, the following are defined in CMDUI.XML (contextual groups at level 1, child tabs at level 2), and you can probably identify some of them with things you’ve seen on your SharePoint 2010 travels:

  • Ribbon.EditingTools
    • Ribbon.EditingTools.CPEditTab
    • Ribbon.EditingTools.CPInsert
  • Ribbon.Image
    • Ribbon.Image.Image
  • Ribbon.LibraryContextualGroup
    • Ribbon.Document
    • Ribbon.Library
  • Ribbon.ListContextualGroup
    • Ribbon.ListItem
    • Ribbon.List
  • Ribbon.Link
    • Ribbon.Link.Link
  • Ribbon.Table
    • Ribbon.Table.Layout
    • Ribbon.Table.Design
  • Ribbon.WebPartInsert
    • Ribbon.WebPartInsert.Tab
  • Ribbon.WebPartCtx
    • Ribbon.WebPartOption
  • Ribbon.Calendar
    • Ribbon.Calendar.Events
    • Ribbon.Calendar.Calendar
  • Ribbon.PermissionContextualGroup
    • Ribbon.Permission

Summary

The ribbon is a key building block for SharePoint 2010 solutions, and anywhere you see a ribbon tab it can be customized. This means list pages, the page editing experience, particular application pages, Central Administration and many other locations can be targeted in the same way – identify the ID of the location from CMDUI.XML, then use this in the XML which declares your ribbon customization. In addition to adding items to existing tabs and groups, we also looked at contextual groups, and showed how to conditionally display them with code.

Next time – going beyond simple button customizations with JavaScript page components

Customizing the ribbon (part 1) – creating tabs, groups and controls

Some good posts are starting to appear on SharePoint 2010 ribbon customization now, and over the next couple of articles I want to cover some key things you might want to do with the ribbon when developing your solutions. Broadly speaking, the plan for this series is:

  1. Creating tabs, groups and controls (this post)
  2. Adding items into existing ribbon tabs and groups
  3. JavaScript considerations when working with the ribbon
  4. Developing JavaScript page components for the ribbon
  5. Working with the ribbon programatically e.g. from web parts and field controls

Diving straight in then, some ribbon fundamentals:

  • Ribbon elements must be defined in declarative XML with the CustomAction tag – even if you will actually use code to manipulate them (e.g. make visible, enable/disable etc.)
  • The “control hierarchy” is ribbon > tab > group > controls – we’ll explore all these in this post
  • Individual buttons/controls do not appear and disappear on the ribbon. This is a key ribbon principle, to avoid the “I’m sure this button was here yesterday!” effect – instead, depending on the context:
    • Entire tabs can be shown/hidden
    • Individual controls can be enabled/disabled
  • It’s not possible to add custom controls to the ribbon e.g. a custom .ascx/server control. The list of controls defined by the ribbon can be found here in the MSDN docs, and includes things like Button, Checkbox, Color Picker, Combo Box, Dropdown, Textbox, Toggle Button etc, but also some funky ones like Spinner, Split Button and Flyout Anchor (definitions of these exotic varieties can be found in the documentation). Flyout Anchor is particularly interesting as it takes XML as a datasource and can be used to build interesting “pickers” e.g. with images – I’ll hopefully cover this in detail in a future post
  • The definitions for the out-of-the-box ribbon elements are split across several files in the SharePoint root, with TEMPLATE\GLOBAL\XML\CMDUI.XML being the main one. You will likely spend significant time in this file looking for examples similar to what you’re building.

It’s also worth giving special consideration to how JavaScript plays with the ribbon – it’s used frequently since much happens on the client. Depending on the scope you need for your JavaScript (e.g. every page vs. a couple) and the complexity of what you’re doing, JavaScript can be supplied in a few ways:

  • By embedding it into your declarative XML (via a separate CustomAction with a new ‘Location=”ScriptLink”’ attribute) – this post uses this approach, though later in the series I’ll show the next option
  • By deploying a custom .js file which contains some object-oriented JavaScript. This is the approach used for more complex customizations, where you need to create an object which is the client-side “page component” in addition to your XML. The page component supplies the implementation for how your custom ribbon elements should handle various events (“commands”) . This object needs to be derived from the existing CUI.Page.Component object defined in CUI.js. As with any JavaScript file, you then have a couple of options for referencing it on your page.

In this post we’ll show adding JavaScript the first way, though later in the series I’ll show the use of a page component.

Example – creating a new custom tab

This is a fairly in-depth example, since by necessity it also covers creating custom groups and controls too. Also, to kill two birds with one stone, I thought it would be good to look at the new ‘notifications’ and ‘status’ frameworks in the Client Object Model for passing messages back to the user in your SharePoint app. First we’ll walk through what my custom tab looks like, then what it actually does. I have a tab titled “Chris’s custom tab” with 3 groups (“Notification messages”, “Add status messages” and “Remove status messages”) each with some buttons of different sizes and images in them:

CustomRibbonTab

Clicking the ‘Notify hello’ button adds a transient message in the notifications area (fades in from right and stays for 5 seconds by default):

SP.UI.Notify

Clicking the ‘Info status’ button shows a status message this time, in the default color (this remains on screen until removed with another API call):

SP.UI.Status_Info

Clicking the ‘Warning status’ button shows a status message of a different color to indicate severity, I chose red: 

SP.UI.Status_Warning

You might also have noticed the ‘remove status’ buttons have become enabled when a status message is present – such client-side checks can be done by linking a ‘CommandUIHandler’ with an ‘EnabledScript’ attribute, as we’re about to see.

So what XML is required to get that? Well before you scroll through, note that I’ve taken the complex route with some of the declarations so that my example is as informative as possible – most samples I’ve seen so far simply add a button or two in a single group and don’t specify the “’group template” details which determines how the controls in the group get laid out. This is fine for a button or two as you can just reference an out-of-the-box group template, but if you want to do anything different you’re a bit stuck so hopefully this is good documentation:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:    Id="COB.SharePoint.Ribbon.CustomTab"
   5:    Location="CommandUI.Ribbon" RegistrationType="List" RegistrationId="101">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition Location="Ribbon.Tabs._children">
   9:           <Tab Id="COB.SharePoint.Ribbon.CustomTab" Title="Chris's custom tab" Description="Groups and controls will go in here" Sequence="501">
  10:             <Scaling Id="COB.SharePoint.Ribbon.CustomTab.Scaling">
  11:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.MaxSize"
  12:                        GroupId="COB.SharePoint.Ribbon.CustomTab.NotificationGroup"
  13:                        Size="OneLarge"/>
  14:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Scaling.CustomTabScaling"
  15:                      GroupId="COB.SharePoint.Ribbon.CustomTab.NotificationGroup"
  16:                      Size="OneLarge" />
  17:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.MaxSize"
  18:                       GroupId="COB.SharePoint.Ribbon.CustomTab.StatusGroup"
  19:                       Size="TwoMedium"/>
  20:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.Scaling.CustomTabScaling"
  21:                      GroupId="COB.SharePoint.Ribbon.CustomTab.StatusGroup"
  22:                      Size="TwoMedium" />
  23:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.MaxSize"
  24:                       GroupId="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup"
  25:                       Size="TwoLarge"/>
  26:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.Scaling.CustomTabScaling"
  27:                      GroupId="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup"
  28:                      Size="TwoLarge" />
  29:             </Scaling>
  30:             <Groups Id="COB.SharePoint.Ribbon.CustomTab.Groups">
  31:               <Group
  32:                 Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup"
  33:                 Description="Contains notification items"
  34:                 Title="Notification messages"
  35:                 Sequence="52"
  36:                 Template="Ribbon.Templates.OneLargeExample">
  37:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Controls">
  38:                   <Button
  39:                     Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Notify"
  40:                     Command="COB.Command.Notify"
  41:                     Sequence="15" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
  42:                     Description="Uses the notification area to display a message."
  43:                     LabelText="Notify hello"
  44:                     TemplateAlias="cust1"/>
  45:                 </Controls>
  46:               </Group>
  47:               <Group
  48:                 Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup"
  49:                 Description="Contains 'add status' items"
  50:                 Title="Add status messages"
  51:                 Sequence="49"
  52:                 Template="Ribbon.Templates.TwoMediumExample">
  53:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.Controls">
  54:                   <Button
  55:                    Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.AddStatusInfo"
  56:                    Command="COB.Command.AddStatusInfo"
  57:                    Sequence="17" Image16by16="/_layouts/images/info16by16.gif" Image32by32="/_layouts/images/info16by16.gif"
  58:                    Description="Uses the status bar to display an info message."
  59:                    LabelText="Info status"
  60:                    TemplateAlias="cust2"/>
  61:                   <Button
  62:                     Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.AddStatusWarning"
  63:                     Command="COB.Command.AddStatusWarning"
  64:                     Sequence="17" Image16by16="/_layouts/images/warning16by16.gif" Image32by32="/_layouts/images/warning32by32.gif"
  65:                     Description="Uses the status bar to display a warning message."
  66:                     LabelText="Warning status"
  67:                     TemplateAlias="cust3"/>
  68:                 </Controls>
  69:               </Group>
  70:               <Group
  71:                 Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup"
  72:                 Description="Contains 'remove status' items"
  73:                 Title="Remove status messages"
  74:                 Sequence="52"
  75:                 Template="Ribbon.Templates.TwoLargeExample">
  76:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.Controls">
  77:                   <Button
  78:                     Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.RemoveLastStatusButton"
  79:                     Command="COB.Command.RemoveLastStatus"
  80:                     Sequence="15" Image16by16="/_layouts/images/warning16by16.gif" Image32by32="/_layouts/images/CRIT_32.GIF"
  81:                     Description="Removes the last message from the status bar."
  82:                     LabelText="Remove last status message" 
  83:                     TemplateAlias="cust4"/>
  84:                   <Button
  85:                     Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.RemoveAllStatusButton"
  86:                     Command="COB.Command.RemoveAllStatus"
  87:                     Sequence="15" Image16by16="/_layouts/images/warning16by16.gif" Image32by32="/_layouts/images/CRIT_32.GIF"
  88:                     Description="Removes all messages from the status bar."
  89:                     LabelText="Remove all status messages"
  90:                     TemplateAlias="cust5"/>
  91:                 </Controls>
  92:               </Group>
  93:             </Groups>
  94:           </Tab>
  95:         </CommandUIDefinition>
  96:         <CommandUIDefinition Location="Ribbon.Templates._children">
  97:           <GroupTemplate Id="Ribbon.Templates.OneLargeExample">
  98:             <Layout Title="OneLarge" LayoutTitle="OneLarge">
  99:               <Section Alignment="Top" Type="OneRow">
 100:                 <Row>
 101:                   <ControlRef DisplayMode="Large" TemplateAlias="cust1" />
 102:                 </Row>
 103:               </Section>
 104:             </Layout>
 105:           </GroupTemplate>
 106:         </CommandUIDefinition><CommandUIDefinition Location="Ribbon.Templates._children">
 107:           <GroupTemplate Id="Ribbon.Templates.TwoMediumExample">
 108:             <Layout Title="TwoMedium" LayoutTitle="TwoMedium">
 109:               <Section Alignment="Top" Type="TwoRow">
 110:                 <Row>
 111:                   <ControlRef DisplayMode="Medium" TemplateAlias="cust2" />
 112:                 </Row>
 113:                 <Row>
 114:                   <ControlRef DisplayMode="Medium" TemplateAlias="cust3" />
 115:                 </Row>
 116:               </Section>
 117:             </Layout>
 118:           </GroupTemplate>
 119:         </CommandUIDefinition>
 120:         <CommandUIDefinition Location="Ribbon.Templates._children">
 121:           <GroupTemplate Id="Ribbon.Templates.TwoLargeExample">
 122:             <Layout Title="TwoLarge" LayoutTitle="TwoLarge">
 123:               <Section Alignment="Top" Type="OneRow">
 124:                 <Row>
 125:                   <ControlRef DisplayMode="Large" TemplateAlias="cust4" />
 126:                   <ControlRef DisplayMode="Large" TemplateAlias="cust5" />
 127:                 </Row>
 128:               </Section>
 129:             </Layout>
 130:           </GroupTemplate>
 131:         </CommandUIDefinition>
 132:       </CommandUIDefinitions>
 133:       <CommandUIHandlers>
 134:         <CommandUIHandler
 135:           Command="COB.Command.Notify"
 136:           CommandAction="javascript:
 137:           
 138:           var notificationId = SP.UI.Notify.addNotification('Hello from the notification area'); 
 139:           " />
 140:         <CommandUIHandler
 141:           Command="COB.Command.AddStatusInfo"
 142:           CommandAction="javascript:
 143:           
 144:           var statusId = SP.UI.Status.addStatus('Quite important status message');
 145:           latestId = statusId;
 146:           enableRemoveStatusButton();
 147:           " />
 148:         <CommandUIHandler
 149:           Command="COB.Command.AddStatusWarning"
 150:           CommandAction="javascript:
 151:           
 152:           var statusId = SP.UI.Status.addStatus('Very important status message');
 153:           SP.UI.Status.setStatusPriColor(statusId, 'red');
 154:           latestId = statusId;
 155:           enableRemoveStatusButton();
 156:           " />
 157:         <CommandUIHandler
 158:           Command="COB.Command.RemoveLastStatus" EnabledScript="javascript:enableRemoveStatusButton();"
 159:           CommandAction="javascript:
 160:           
 161:           SP.UI.Status.removeStatus(latestId);
 162:           latestId = '';
 163:           enableRemoveStatusButton();" />
 164:         <CommandUIHandler
 165:           Command="COB.Command.RemoveAllStatus" EnabledScript="javascript:enableRemoveStatusButton();"
 166:           CommandAction="javascript:
 167:           
 168:           SP.UI.Status.removeAllStatus(true);
 169:           latestId = '';
 170:           enableRemoveStatusButton();" />
 171:       </CommandUIHandlers>
 172:     </CommandUIExtension>
 173:   </CustomAction>
 174:   <CustomAction Id="COB.Command.RemoveLastStatus.CheckEnable" Location="ScriptLink"
 175:              ScriptBlock="
 176:                 var latestId = '';
 177:                           
 178:                 function enableRemoveStatusButton() 
 179:                 { 
 180:                   if (latestId == '')
 181:                   {
 182:                     return false;
 183:                   }
 184:                   else
 185:                   {
 186:                     return true;
 187:                   }
 188:                 }"
 189:                 />
 190: </Elements>

Some key points, following the XML sequence:



  • CustomAction:

    • Notice I have two CustomAction elements – one for the ribbon elements, the other for some JavaScript I want to use with my custom elements. This is the approach mentioned earlier where the JavaScript is effectively embedded in your XML [sidenote: you don’t have  to be doing ribbon customization to leverage this approach – this use of CustomAction is a new way of providing JavaScript to the page, just be aware it will be added for every page in the Feature scope (e.g. site/web) and you have no control over where in the page it will be injected. It does give you the ability to take away your JavaScript via Feature deactivation though, which could be useful for many scenarios).

      • The Location attribute of CustomAction for ribbon elements should always be “CommandUI.Ribbon”
      • The Location attribute of CustomAction for script is a new value, “ScriptLink”

    • My ribbon tab is scoped to document libraries only – this is courtesy of the RegistrationType=”List” RegistrationId=”101″ attributes (which is exactly what you did when targeting a CustomAction to doc libs in SharePoint 2007, no change there)
    • When targeting a list in this way, RegistrationId refers to the list template ID (e.g. generic list = 100. document library = 101 etc. – here’s a full list of list template IDs for 2007, there could be a couple of new ones in 2010) – it is not possible to declaratively target a list by e.g. GUID or URL. So consider that this could drive you to create a list template when you otherwise might not have.
    • Other options for the RegistrationType continue to be “ContentType”, “ProgID” and “FileType”, but I’m pretty sure only “List” can be used for ribbon elements, but I’ve not tested that yet so I reserve the right to be wrong! If you want a different scope level, you would omit the RegistrationType and RegistrationId attributes and use code such as SPRibbon.MakeTabAvailable() to conditionally show the ribbon. More on this later in the series when I show how to add ribbon customizations for a web part or custom field control.

  • CommandUIDefinition:

    • Another element you might have multiple of – one for the main customization definition, one for each of the “GroupTemplate” elements being provisioned (more on this later).  For the main one, the “Location” attribute here is crucially important as this specifies where the customization should appear. My value of “Ribbon.Tabs._children” indicates I’m adding something into the “Ribbon.Tabs” collection defined by SharePoint (typically in CMDUI.XML) – “_children” is a convention used when adding to  many collections in the ribbon architecture. We’ll look at how to add new groups and controls into an existing group in the next article, but as a quick example, adding a group into “Ribbon.Library.Groups._children” would make your group appear somewhere in here (depending on the “Sequence” value assigned on the definition for the group):

      Ribbon.Library.Groups

  •  Tab:

    • The “Sequence” attribute decides where to place my tab amongst the existing ones. Out-of-the-box values are generally multiples of 10, sometimes of 5, so your sequence values should avoid such numbers to avoid conflict. Generally you’ll need to find the declaration of the surrounding elements near where you are targeting (in CMDUI.XML) to find the appropriate number.

  • Scaling (and children):

    • This section defines how your elements should behave when the window is resized and there’s not enough room. You need a “MaxSize” and “Scale” element for each Group you define. These define the size and layout the element(s) should be at when at “max”, and also what to change to when the window is smaller – effectively you can provide multiple layouts for your controls depending on the window size (e.g. prioritising the important buttons).

  • Group:

    • This is the “section on the ribbon tab” which is the container for your controls – in the small image above, an example of a Group is ‘View Format’ which contains the two leftmost buttons. Key things here are the “Sequence” (same deal as elsewhere) and the “Template” – this is a reference to a “GroupTemplate” element (which we’ll come onto shortly). In essence, this is the link which will tell the ribbon framework how to lay out the controls in this group.

  • Controls:

    • Fairly obvious, this is the parent node for any controls you want to add e.g. buttons, dropdowns etc etc. Note that each of your controls in here must have a “TemplateAlias” attribute – this tells the framework exactly where to place the individual control within the GroupTemplate which is referenced.
    • Controls expose various commands, via attributes – a Button simply has a “Command” attribute which fires when clicked, whereas a Dropdown has additional ones such as “PopulateQueryCommand” and “QueryCommand”. These link to “CommandUIHandler” elements or code defined in a JavaScript page component.

  • GroupTemplate:

    • Similar to defining say, a HTML table, this section provides the actual layout of the controls, alignments, control sizes etc. Each control which is being declared needs a corresponding “ControlRef” element which will be matched to the control on the “TemplateAlias” value.

  • CommandUIHandler:

    • This is the where you get to define the JavaScript which executes when the basic “Command” is fired for a control (e.g. a button is clicked). The command name must match that defined on the Control element, and the “CommandAction” attribute contains the script for the basic command. You can also use the “EnabledScript” attribute to add some script which decides whether the control should be enabled or not – this is how my ‘’remove status’ buttons are only enabled when there is a message to remove.
    • Since all the JavaScript gets added to the same page, as you’ll see in my sample it is possible to declare variables which get used by other JavaScript provisioned by a different CommandUIHandler – again though, whilst the sequence is deterministic you cannot control where your script gets added into the overall page (at the start of the <body> tag), so if you need your code to run when the DOM is complete you’d have to take steps to get your code called at the appropriate time – more on this later in the series.

Hope you found this useful. Next time we’ll take a quick look at adding items to existing ribbon locations, before moving onto working with JavaScript and page components etc.

Editing .cmp files to fix lookup field issues

I ran into an interesting (well, it’s all relative) content deployment issue the other day, which I’m pretty sure will apply to both SharePoint 2007 and SharePoint 2010. In preparation for some SharePoint training I was delivering at my current client, I wanted to move some real data from production into the training environment to make the training more realistic. To do this, I used my Content Deployment Wizard tool, which uses SharePoint’s Content Deployment API to export content into a .cmp file. (Quick background – the tool does exactly the same thing as ‘STSADM –o export’ and out-of-the-box content deployment, but allows more control. A .cmp file is actually just a renamed cab file i.e. a compressed collection of files, similar to a .wsp). However, when importing the .cmp file containing my sites/documents etc., the operation failed with the following error:

The element ‘FieldTemplate’ in namespace ‘urn:deployment-manifest-schema’ has invalid child element ‘Field’ in namespace ‘http://schemas.microsoft.com/sharepoint/’. List of possible elements expected: ‘Field’ in namespace ‘urn:deployment-manifest-schema’.

So clearly we have a problem with a field somewhere, and it’s an issue I was vaguely aware of – cross-web lookup fields deployed with a Feature break content deployment. Michael Nemtsev discusses the issue here, saying “There are several samples how to deploy lookup fields via feature (http://www.sharepointnutsandbolts.com/2007/04/feature-to-create-lookup-fields-on.html) but all of them are not suitable for the Content Deployment Jobs. Because you will get the exception…”

Oops that’s a link to an old article of mine. So effectively *my* code to create lookup fields doesn’t work with *my* content deployment tool – don’t you just love it when that happens?! However, I actually have a clear conscience because I know both utilities are doing valid things using only supported SharePoint APIs – this is simply one of those unfortunate SharePoint things. As Michael says, all of the cross-web lookup field samples would have this issue. So what can we do about it?


For fields yet to be created

In this scenario my recommendation would be to use the technique Michael suggests in his post, which is to strip out the extraneous namespace at the end of our code which creates the lookup.

For fields which are already in use (i.e. the problem I ran into)

If your lookup fields have already been deployed, then you have 2 options:

  • develop and test a script to retrospectively find and fix the issue across your web/site collection/farm/whatever scope you need
  • fix the issue in the .cmp file you were trying to import in the first place, so this particular import will succeed

Clearly your decision might depend on how much content deployment you want to do to the site/web/document library or list which has the problem. If you’re anticipating doing it all the time, you should fix the underlying issue. If, as in my scenario, you just need to get an ad-hoc import to succeed, here’s how..

Hacking the .cmp file

The process is effectively to fix-up the .cmp file, then rebuild it with the updated files. I noticed an unanswered question on Stack Overflow about this process, so clearly it’s something that can occasionally arise. Of course even in these WSPBuilder/SP2010 tools days, all SharePoint devs should know you can use makecab.exe with a .ddf file to build a cab file – but what happens when you have hundreds of files? That’s hundreds of lines you’ll need in your .ddf file? Certainly you could write some code to generate it for you, but chances are you’re looking for a quick solution. 

The first process I came up with was:

  1. Rename .cmp file to .cab, extract files to other directory.
  2. Fix-up files (more detail shortly).
  3. Use WSPBuilder to generate the .ddf file from files on filesystem – edit this to ensure paths are correct.
  4. Use makecab.exe + the generated .ddf file to build the .cab file.
  5. Rename extension to .cmp.

However, a slightly better way is to use Cab File Maker, like this:

  1. Rename .cmp file to .cab, extract files to other directory.
  2. Fix-up files – to do this, edit manifest.xml, remove all instances of following string – “xmlns=http://schemas.microsoft.com/sharepoint/
  3. Open Cab File Maker 2.0, drag files in and set options for where to generate ddf file and cmp file, like so:

    CabFileMaker
  4. Voila – your cmp file is now ready to go and should import successfully.