Monday, 25 January 2010

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

[This post updated September 2011 with fixes + extra info – see text for details]

In this article series:

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

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 Location="Ribbon.Templates._children">
   9:           <GroupTemplate Id="Ribbon.Templates.NewGroupInExistingTab.OneLargeExample">
  10:             <Layout Title="NewGroupInExistingTabOneLarge" LayoutTitle="NewGroupInExistingTabOneLarge">
  11:               <Section Alignment="Top" Type="OneRow">
  12:                 <Row>
  13:                   <ControlRef DisplayMode="Large" TemplateAlias="Button1" />
  14:                 </Row>
  15:               </Section>
  16:             </Layout>
  17:           </GroupTemplate>
  18:         </CommandUIDefinition>
  19:         <CommandUIDefinition Location="Ribbon.WikiPageTab.Scaling._children">
  20:           <MaxSize         
  21:             Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.MaxSize"         
  22:             Sequence="15"         
  23:             GroupId="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup"            
  24:             Size="NewGroupInExistingTabOneLarge" />
  25:         </CommandUIDefinition>
  26:         <CommandUIDefinition Location="Ribbon.WikiPageTab.Groups._children">
  27:           <Group        
  28:             Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup"       
  29:             Sequence="15"     
  30:             Description="Used to demo adding a group"      
  31:             Title="Chris's custom group!"   
  32:             Template="Ribbon.Templates.NewGroupInExistingTab.OneLargeExample">
  33:             <Controls Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.Controls">
  34:               <Button          
  35:                 Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.NotifyHello"     
  36:                 Command="COB.NewGroupInExistingTab.Command.Notify"   
  37:                 Sequence="10"
  38:                 Image16by16="/_layouts/images/NoteBoard_16x16.png" 
  39:                 Image32by32="/_layouts/images/NoteBoard_32x32.png"     
  40:                 Description="Uses the notification area to display a message."  
  41:                 LabelText="Notify hello"               
  42:                 TemplateAlias="Button1" />
  43:             </Controls>
  44:           </Group>
  45:         </CommandUIDefinition>
  46:       </CommandUIDefinitions>
  47:       <CommandUIHandlers>
  48:         <CommandUIHandler       
  49:           Command="COB.NewGroupInExistingTab.Command.Notify"      
  50:           CommandAction="javascript:  SP.UI.Notify.addNotification('Hello from the notification area'); " />
  51:       </CommandUIHandlers>
  52:     </CommandUIExtension>
  53:   </CustomAction>
  54: </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. [Sept 2011 – I agree with Andrew Connell’s statement that it is better to always define your own GroupTemplate. This is because an OOTB template may not always be made available by the framework when you expect it to be.] 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) [Sept 2011 – in fact, ‘Scale’ is invalid here. Specify MaxSize only.]

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:



<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
   Id="COB.SharePoint.Ribbon.NewControlInExistingGroup"
   Location="CommandUI.Ribbon" Sequence="20">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.ListForm.Display.Manage.Controls._children">
          <Button Id="COB.SharePoint.Ribbon.NewControlInExistingGroup.Notify"
                  Command="COB.Command.NewControlInExistingGroup.Notify"
                  Sequence="5" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
                  Description="Uses the notification area to display a message."
                  LabelText="Notify hello"
                  TemplateAlias="o1"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
          Command="COB.Command.NewControlInExistingGroup.Notify"
          CommandAction="javascript:
          
          SP.UI.Notify.addNotification('Hello from the notification area'); 
          " />
      </CommandUIHandlers>    
    </CommandUIExtension>
  </CustomAction>
</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. [Sept 2011 – see earlier comment; always define your own GroupTemplate.] 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:


<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
   Id="COB.SharePoint.Ribbon.ContextualTab"
   Location="CommandUI.Ribbon">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.ContextualTabs._children">
          <ContextualGroup Id="COB.SharePoint.Ribbon.ContextualGroup" Sequence="50" Color="Orange" Command="COBContextualGroupCommand" ContextualGroupId="COB.Contextual" Title="Chris's Contextual Group">
            <Tab Id="COB.SharePoint.Ribbon.ContextualTab" Title="Chris's custom tab" Description="Groups and controls will go in here" Sequence="501">
            <!-- Add Scaling, Groups, GroupTemplates, CommandUIHandlers etc. in here as per creating a normal tab -->
            </Tab>
          </ContextualGroup>
        </CommandUIDefinition>
      </CommandUIDefinitions>
    </CommandUIExtension>
  </CustomAction>
</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:

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);
}

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. [Sept 2011 – it seems that contextual tabs/groups can only be made available by server-side code, and note also that SPRibbon is not available in the sandbox.]

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

24 comments:

Jeremy said...

Great article Chris. Can you please explain how you would go about implementing Javascript that would activate a ribbon and enable/disable certain buttons. For instance, I would like a link in my custom webpart to activate the ribbon. Also, I would like to click anywhere in the webpart and activate the ribbon. One thing I notice is that the OOB webparts have an onclick event on the div, I am guessing that needs to be implemented somehow.

Chris O'Brien said...

Good question, and I think this could be a fairly common scenario. There are a few elements to this, and I'm afraid I haven't personally bottomed out all of them yet - but that's my aim during this series.

A couple of pointers in the meantime:

- the built-in get_webPartIsSelected() function may help you with the click event
- if it's enabling/disabling the buttons you wish to do, you may be able to combine the above function with code in the EnabledScript attribute attached to the command for your button. This would allow you to enable the buttons only when your web part is in focus.

I haven't found a convenient client-side method to show a ribbon or contextual group yet (i.e. a client-side equivalent to the server-side code in this post), but I'm sure there is one as OOTB contextual groups appear without postbacks. I'll be sure to write it down when I find it!

Thanks,

Chris.

Peter Kneale said...

Wow, great work. Im about to start this myself and your doco is better than that from MS!

Nirav said...

In the Display form example, i tried
javascript:alert('{ListId}')

expecting to prompt me current list id, and it always prompts 'null'

isn't {ListId} and other tokens replaced at runtime ?

When same script was added from Sharepoint Designer as custom action (instead as from custom feature) in display form, it worked fine.

I guess when we are adding custom actions using feature it's have context as null.

Any idea why this happens ?.
I just want to redirect to other page using custom action with listid as url param.

Chris O'Brien said...

@Nirav,

It could be that the token replacement doesn't happen within the CustomAction framework - I could certainly believe that.

I think you'll need to write some JavaScript using SP.ClientContext to get the URL of the list to redirect to. My other article which shows how to use JavaScript page components with the ribbon may help.

Good luck,

Chris.

Hypheroth said...

Hi, I copied your code for adding a custom group to the site tab, but nothing shows up there after deployment :-(
Any ideas?

greetings
martin

Chris O'Brien said...

@Hypheroth,

Did you recycle the app pool and clear the browser cache? I found both of these to be necessary when doing ribbon development.

HTH,

Chris.

Markus said...

Chris, thanks for the great article!

I tried to show a contextual tab whenever I show my application page. To do so I added a user control to my page which registers and shows (MakeTabAvailable and MakeContextualGroupInitiallyVisible) the contextual tab in its OnPreRender method.

I managed to see the contextual tab in the ribbon, but whenever I click this tab to make it current, it vanishes immediately.

What am I missing?

Chris O'Brien said...

@Markus,

I haven't experienced this behaviour so not sure what to suggest - certainly you need to make these calls on every page load though, so if you're doing a postback in between you'd need to ensure this is happening.

Otherwise if no postbacks, are you getting any JavaScript errors?

Chris.

nik said...

@Chris, @Markus
I'm seeing a similar issue as Markus did.

The contextual tab shows up, but no controls within it. (I'm pretty certain the ribbon XML is valid - just a simple test button)

Clicking the tab has no effect.

@Chris - gr8 articles btw, really helpful in light of sparse doc from MS.

Could you post a working sample of getting a global contextual tab to show up using a custom field or application page. (I was able to get contextual tabs to work with web parts and IWebPartPageComponentProvider)? That would be greatly appreciated.

Markus said...

@Chris,

I do all my code in the OnPreRender method (of my user control) which is supposed to be called on postback as well, but obviously there is no postback event if I click any ribbon tab. After any tab click, my contextual tab vanishes. I don't get any JavaScript errors. I must be missing something, but what?
I could send you my code if you want to take a look at it.

Thanks,
Markus

Chris O'Brien said...

@Markus,

One thing that occurred to me is that I wonder if we are developing on different versions? If you are on the public beta and I did this work on a later build (99% sure I did), that could account for it.

If you can wait until after the SharePoint Evolutions conference I could spend more significant time trying to get to the bottom of it. Until then though, I'm afraid I'm flat out preparing demos for my talk :(

Sorry I can't be more helpful at the moment..

Chris.

Hornet said...

Hi Chris,

Great article, well written and precise.
However I'm experiencing the same problem than Marcus.
I'm on the RTM Version of SharePoint.
My Contextual Group and my tab is showing correctly and when i click on the tab everything disappear.
If i do a ribbon.NormalizeContextualGroup my tab is showing correctly O_o".
Kind of strange.

Chris O'Brien said...

@Hornet,

Ah, maybe that's the thing missing from my sample. Unfortunately I don't have access to the pre-release VM I was using to build these samples, but I'll try to verify ribbon.NormalizeContextualGroup very soon.

In the meantime, if someone else finds this is indeed the missing piece, would be great if you leave a comment to confirm.

Many thanks,

Chris.

JacobUT said...

Really informative article.

I did run into one issue. The remove status buttons never get enabled. I looked through the code and put some alerts. The EnabledScript code does not get fired again after the first evaluation when the page loads. Any idea what I am doing wrong?

Jake.

Chris O'Brien said...

@Jake,

This could be something to do with minor changes which MS made between the pre-release version I wrote these samples on and the RTM version. I think you now need to make an extra call (yourself) which previously wasn't necessary - unfortunately I can't remember the name (RefreshRibbon??) and haven't needed to do it on RTM yet.

Sorry I can't be more help. Can anyone else chip in?

Thanks,

Chris.

ddNils said...

@Cris, @Markus,
I am experiencing the exact same Problem as Markus did. I am working on a usual SP2010 (as it is used in these days). After a lot of problems I can now finally see my Tab (not the contents of it though). - The key here was to use your provided Sequences in ContextualGroup AND Tab (in Tab concat 1).
Back to my Problem: When clicking on the ContextualTab it just disappears. Tough NormalizeContextualGroup helps by showing the Tab constantly, it hides the Contextual Part, which I want to preserve...

I hope someone can help with this... or can point me in the right direction. I would greatly appreciate any help, thx.

Doug said...

Chris,
Great article. I can set up a custom button now! However, if I set it up and want to add to it by enhancing the code or changing the icon or group where a button is placed, it doesn't happen. I tried restarting the server and clearing cache, but no help. Can you lay out the steps to take for iterative development of button code?

Thanks.

Chris O'Brien said...

@Doug,

That's a good question - ribbon development can be pernickity. Here's the things I do once I've made a change in the iterative dev process:

- Redeploy the solution using VS2010, ensuring the Feature is being reactivated
- Ensure an app pool recycle happens (usually taken care of by the above, depending on your VS deployment configuration)
- Ensure the browser cache is either disabled (both the IE Developer Tools and Firebug for Firefox allow you to do this) or cleared

This last one is often the one that folks forget to do.

HTH,

Chris.

Darrell said...

Good walkthrough but I am having a problem when I try to add a button to the controls. I actually get the same error if I have any groups in the section or not. The wrror is:
Sys.ArgumentException: Cannot deserialize. The data does not correspond to valid JSON.
Parameter name: data
It won't let me post my code here sorry.

Chris O'Brien said...

@Darrell,

My suspicion is that there's a typo somewhere in your code. If you can't post the code as a comment here, suggest leaving another comment with your e-mail address (which I won't publish to the blog). I'll then get in touch with you via e-mail to see if we can work it out.

HTH,

Chris.

Paul Gorman said...

Great article. It really helped me move forward on customizing the SharePoint ribbon. I am having an issue with controls showing up in my custom group and I was wondering if you could help me out.

I have added a new group under the EditingTools.Insert contextual tab that shows when you edit a page. My group title shows just fine. I have a section defined under my group, in the XML, but the controls will not show up.

I tried posting the XML from my Elements file but it wouldn't let me. If it would help to look at it please give me your email and I will send it to you.

Thank you in advance.
Paul

Chris O'Brien said...

@Paul,

Sure, happy to take a look. Suggest leaving another comment with your e-mail address and I'll get in touch.

Thanks,

Chris.

Rodolfo Perez G said...

Hi Chris! Thanks for that post!
I would like to know, what can I do if I want to show my custom contextual group and tabs, when the user select a file in a document library?