Monday 18 January 2010

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

[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 (this post)
  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
  5. ** Oct 2011 - Download the code samples in this series (and more) ** 

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. 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
    • [Sept 2011 – although SharePoint itself adheres to this guideline, with customization it *is* possible to completely hide ribbon items (though I’d still say this is not recommended due to the inconsistency aspect). Essentially you need to deploy XML which ‘blanks out’ the definition – see Removing a Button from the Server Ribbon on MSDN for more details.]
  • 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). FlyoutAnchor 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 (N.B. although these screenshots are from the SP2010 beta timeframe, this sample still looks the same):

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" 
   6:       RegistrationType="List" 
   7:       RegistrationId="101"> 
   8:     <CommandUIExtension> 
   9:       <CommandUIDefinitions> 
  10:         <CommandUIDefinition Location="Ribbon.Tabs._children"> 
  11:           <Tab Id="COB.SharePoint.Ribbon.CustomTab" 
  12:                Title="Chris's custom tab" 
  13:                Description="Groups and controls will go in here" 
  14:                Sequence="550"> 
  15:             <Scaling Id="COB.SharePoint.Ribbon.CustomTab.Scaling"> 
  16:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.MaxSize" 
  17:                        GroupId="COB.SharePoint.Ribbon.CustomTab.NotificationGroup" 
  18:                        Size="OneLarge"/> 
  19:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.MaxSize" 
  20:                        GroupId="COB.SharePoint.Ribbon.CustomTab.StatusGroup" 
  21:                        Size="TwoMedium"/> 
  22:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.MaxSize" 
  23:                        GroupId="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup" 
  24:                        Size="TwoLarge"/> 
  25:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Scaling.CustomTabScaling" 
  26:                      GroupId="COB.SharePoint.Ribbon.CustomTab.NotificationGroup" 
  27:                      Size="OneLarge" /> 
  28:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.Scaling.CustomTabScaling" 
  29:                      GroupId="COB.SharePoint.Ribbon.CustomTab.StatusGroup" 
  30:                      Size="TwoMedium" /> 
  31:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.Scaling.CustomTabScaling" 
  32:                      GroupId="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup" 
  33:                      Size="TwoLarge" /> 
  34:             </Scaling> 
  35:             <Groups Id="COB.SharePoint.Ribbon.CustomTab.Groups"> 
  36:               <Group 
  37:                 Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup" 
  38:                 Description="Contains notification items" 
  39:                 Title="Notification messages" 
  40:                 Sequence="10" 
  41:                 Template="Ribbon.Templates.OneLargeExample"> 
  42:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Controls"> 
  43:                   <Button 
  44:                     Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Notify" 
  45:                     Command="COB.Command.Notify" 
  46:                     Sequence="10" 
  47:                     Image16by16="/_layouts/images/NoteBoard_16x16.png" 
  48:                     Image32by32="/_layouts/images/NoteBoard_32x32.png" 
  49:                     Description="Uses the notification area to display a message." 
  50:                     LabelText="Notify hello" 
  51:                     TemplateAlias="cust1"/> 
  52:                 </Controls> 
  53:               </Group> 
  54:               <Group 
  55:                 Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup" 
  56:                 Description="Contains 'add status' items" 
  57:                 Title="Add status messages" 
  58:                 Sequence="20" 
  59:                 Template="Ribbon.Templates.TwoMediumExample"> 
  60:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.Controls"> 
  61:                   <Button 
  62:                     Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.AddStatusInfo" 
  63:                     Command="COB.Command.AddStatusInfo" 
  64:                     Sequence="10" 
  65:                     Image16by16="/_layouts/images/info16by16.gif" 
  66:                     Image32by32="/_layouts/images/info16by16.gif" 
  67:                     Description="Uses the status bar to display an info message." 
  68:                     LabelText="Info status" 
  69:                     TemplateAlias="cust2"/> 
  70:                   <Button 
  71:                     Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.AddStatusWarning" 
  72:                     Command="COB.Command.AddStatusWarn" 
  73:                     Sequence="20" 
  74:                     Image16by16="/_layouts/images/warning16by16.gif" 
  75:                     Image32by32="/_layouts/images/warning32by32.gif" 
  76:                     Description="Uses the status bar to display a warning message." 
  77:                     LabelText="Warning status" 
  78:                     TemplateAlias="cust3"/> 
  79:                 </Controls> 
  80:               </Group> 
  81:               <Group 
  82:                   Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup" 
  83:                   Description="Contains 'remove status' items" 
  84:                   Title="Remove status messages" 
  85:                   Sequence="30" 
  86:                   Template="Ribbon.Templates.TwoLargeExample"> 
  87:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.Controls"> 
  88:                   <Button 
  89:                     Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.RemoveLastStatusButton" 
  90:                     Command="COB.Command.RemoveLastStatus" 
  91:                     Sequence="10" 
  92:                     Image16by16="/_layouts/images/warning16by16.gif" 
  93:                     Image32by32="/_layouts/images/CRIT_32.GIF" 
  94:                     Description="Removes the last message from the status bar." 
  95:                     LabelText="Remove last status message" 
  96:                     TemplateAlias="cust4"/> 
  97:                   <Button 
  98:                     Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.RemoveAllStatusButton" 
  99:                     Command="COB.Command.RemoveAllStatus" 
 100:                     Sequence="20" 
 101:                     Image16by16="/_layouts/images/warning16by16.gif" 
 102:                     Image32by32="/_layouts/images/CRIT_32.GIF" 
 103:                     Description="Removes all messages from the status bar." 
 104:                     LabelText="Remove all status messages" 
 105:                     TemplateAlias="cust5"/> 
 106:                 </Controls> 
 107:               </Group> 
 108:             </Groups> 
 109:           </Tab> 
 110:         </CommandUIDefinition> 
 111:         <CommandUIDefinition Location="Ribbon.Templates._children"> 
 112:           <GroupTemplate Id="Ribbon.Templates.OneLargeExample"> 
 113:             <Layout Title="OneLarge" LayoutTitle="OneLarge"> 
 114:               <Section Alignment="Top" Type="OneRow"> 
 115:                 <Row> 
 116:                   <ControlRef DisplayMode="Large" TemplateAlias="cust1" /> 
 117:                 </Row> 
 118:               </Section> 
 119:             </Layout> 
 120:           </GroupTemplate> 
 121:         </CommandUIDefinition> 
 122:         <CommandUIDefinition Location="Ribbon.Templates._children"> 
 123:           <GroupTemplate Id="Ribbon.Templates.TwoMediumExample"> 
 124:             <Layout Title="TwoMedium" LayoutTitle="TwoMedium"> 
 125:               <Section Alignment="Top" Type="TwoRow"> 
 126:                 <Row> 
 127:                   <ControlRef DisplayMode="Medium" TemplateAlias="cust2" /> 
 128:                 </Row> 
 129:                 <Row> 
 130:                   <ControlRef DisplayMode="Medium" TemplateAlias="cust3" /> 
 131:                 </Row> 
 132:               </Section> 
 133:             </Layout> 
 134:           </GroupTemplate> 
 135:         </CommandUIDefinition> 
 136:         <CommandUIDefinition Location="Ribbon.Templates._children"> 
 137:           <GroupTemplate Id="Ribbon.Templates.TwoLargeExample"> 
 138:             <Layout Title="TwoLarge" LayoutTitle="TwoLarge"> 
 139:               <Section Alignment="Top" Type="OneRow"> 
 140:                 <Row> 
 141:                   <ControlRef DisplayMode="Large" TemplateAlias="cust4" /> 
 142:                   <ControlRef DisplayMode="Large" TemplateAlias="cust5" /> 
 143:                 </Row> 
 144:               </Section> 
 145:             </Layout> 
 146:           </GroupTemplate> 
 147:         </CommandUIDefinition> 
 148:       </CommandUIDefinitions> 
 149:       <CommandUIHandlers> 
 150:         <CommandUIHandler 
 151:             Command="COB.Command.Notify" 
 152:             CommandAction="javascript: var notificationId = SP.UI.Notify.addNotification('Hello from the notification area'); " /> 
 153:         <CommandUIHandler 
 154:             Command="COB.Command.AddStatusInfo" 
 155:             CommandAction="javascript:                    
 156:               var statusId = SP.UI.Status.addStatus('Quite important status message');         
 157:               visibleStatusIds.push(statusId); 
 158:               enableRemoveStatusButton(); 
 159:               RefreshCommandUI();" /> 
 160:         <CommandUIHandler 
 161:             Command="COB.Command.AddStatusWarn" 
 162:             CommandAction="javascript: 
 163:               var warnStatusId = SP.UI.Status.addStatus('Very important status message'); 
 164:               SP.UI.Status.setStatusPriColor(warnStatusId, 'red');   
 165:               visibleStatusIds.push(warnStatusId); 
 166:               enableRemoveStatusButton(); 
 167:               RefreshCommandUI(); " /> 
 168:         <CommandUIHandler 
 169:             Command="COB.Command.RemoveLastStatus" 
 170:             EnabledScript="javascript: enableRemoveStatusButton();" 
 171:             CommandAction="javascript:             
 172:               SP.UI.Status.removeStatus(visibleStatusIds[visibleStatusIds.length - 1]);   
 173:               visibleStatusIds.pop(); 
 174:               enableRemoveStatusButton(); 
 175:               RefreshCommandUI();" /> 
 176:         <CommandUIHandler 
 177:             Command="COB.Command.RemoveAllStatus" 
 178:             EnabledScript="javascript: enableRemoveStatusButton();" 
 179:             CommandAction="javascript:           
 180:               SP.UI.Status.removeAllStatus(true);        
 181:               visibleStatusIds.length = 0; 
 182:               enableRemoveStatusButton(); 
 183:               RefreshCommandUI();" /> 
 184:       </CommandUIHandlers> 
 185:     </CommandUIExtension> 
 186:   </CustomAction> 
 187:   <CustomAction 
 188:     Id="COB.Command.RemoveLastStatus.CheckEnable" Location="ScriptLink" 
 189:     ScriptBlock="          
 190:       var visibleStatusIds = [];                           
 191:       function enableRemoveStatusButton()  {   
 192:           return (visibleStatusIds.length > 0);        
 193:       }" /> 
 194: </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 template IDs) – 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, or define a contextual tab which is shown only under specific circumstances.
    • 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! [Sept 2011 – RegistrationType=”ContentType also works.] 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 – typically this would be a ‘contextual’ tab. 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 the groups for the ‘Ribbon.Library’ tab, shown below (where exactly will depend on the “Sequence” number of the group):
      Ribbon.Library.Groups
  • Tab:
    • The “Sequence” attribute decides where to place my tab amongst the existing ones (and in general defines where an item should slot in compared to it’s siblings). Out-of-the-box values are generally multiples of 10, occasionally 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 (usually 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). This is an extremely cool and useful feature of the SharePoint ribbon. [Sept 2011 – note that these elements should be sequenced MaxSize, MaxSize, Scale, Scale (as mentioned in the comments below). This changed from beta to RTM, and is now reflected in my code sample above.]
  • 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. [Sept 2011 – by the way, I agree with Andrew Connell’s assertion that although you can avoid having to define GroupTemplates by using ones defined by Microsoft, this should be avoided since the template may not always be available.]
  • 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) – remember however, that this is just one way to provide the JavaScript – the other way is to use a page component (as described in the 3rd article in this series). Using this approach, 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 – this is effectively the page component approach, 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.

55 comments:

Steve said...

I think the complexity of the ribbon UI is going to be one of the major challenges of SP2010 for end users. It's one of the few risk factors for the success of this release. Great to see some of the leaders of the SharePoint community jumping on this issue early, providing solutions and options. Great content!

Peter Kneale said...

Great post, im trying to build some custom ribbon buttons that will allow us to insert links to our document management system. any chance you could remove the line numbers from your xml files (or just make them available for download). It would make experimenting with them a lot easier :)
I used your other article on Styles and Markup Styles and it worked perfectly - saved me a heap of time.
Thanks for the great blog.

Chris O'Brien said...

@Peter Kneale,

Good point, I guess my samples are 'optimized for reading' ;) I'll try and do something about the line numbers over the next day or two.

Chris.

Furqan Shaikh said...

Great introductory articles on ribbon customization.
I am working on SP 2010 beta version. element has only one element unlike your XML which has Groups as well. Am i missing something? Any help is appreciated.

Thanks,
Furqan

Chris O'Brien said...

@Furqan,

Don't forget you only need a 'Groups' element if you are defining new groups. If you are targeting an existing group (as per the 2nd article in my series - Adding ribbon items into existing tabs/groups), you wouldn't have a 'Groups' element.

Does that make sense?

Chris.

Toni Frankola said...

Chris,

this is awesome article, very helpful!

I run into some problems with the code, maybe cause I am using beta2?

1) I had to reorder sub-elements of the group element element and order them: MaxSize-MaxSize-MaxSize-Scale-Scale-Scale
2) Also the Remove status buttons are note enabled after status is posted, they stay in disable state all the time.

Chris O'Brien said...

@Toni,

Thanks for the kind words. Not sure why you had a couple of problems, though now I actually can't remember whether I wrote this sample on beta 2 or RC so could be that.

Suggest making sure you're clearing the browser cache after each change though - sometimes I was thrown for a loop and was thinking for a while a certain set of markup was wrong, turned out it was the caching fooling me again and when I cleared the cache things started working. A definite gotcha when working with the ribbon :)

Chris.

Jason Lochan said...

Thanks for pointing me to TEMPLATE\GLOBAL\XML\CMDUI.XML! Huge help

Andi Fandrich said...

Does anyone know if it is possible to add ribbon commands for a specific custom list template only?
I developed a custom feature with a custom list template and tried to apply a ribbon customization just for lists based on this template, but to no avail. Tried with RegistrationId=myID and tried with a custom content type. Ribbon customization for default list templates (Custom List etc.) works well.

My first thought was that this is a problem with the Beta, but it doesn't work in RTM as well. Any thoughts?

Unknown said...

Thanks for the great post. I am using your code as a basis but have found that none of the images load when I deploy and test the ribbon. Has anyone else run into this? Is there an easy solution? Also is there an easy way to have the ribbon always displayed no matter which part of the site a user goes to?

Thanks,
Mark

Victor said...

Does this approach still allow the SP 2010 permissions to be applied by default to any new tabs or buttons ?

Chris O'Brien said...

@Victor,

I'm not sure I fully understand the question, but the implementor/developer is responsible for implementing any security-trimming of ribbon controls. As an example, for a button the EnabledScript attribute is used to point to some JavaScript which you write to decide whether the button should be enabled or not for the current user.

HTH,

Chris.

Shai Petel said...

Hi Chris!

Great post, only you have 2 bugs in the code you posted:

1. Your enable script will not get fired after adding status messages. You have to call "RefreshCommandUI();" in order to get SharePoint to reevaluate the button status.
Actually, you should call it instead of calling enableRemoveStatusButton();

2. Second one is more of a logical issue, the "latestId" should actually store an array of id's, so that once you remove latest one it will still have the prev. messages in your array.

Apart from that - Thanks for your great post. Really liked reading it.

Chris O'Brien said...

@Shai,

Useful feedback, thanks! On the 2nd point I guess I wasn't too worried about supporting multiple messages as it's not really a lifelike demo (and I wanted to keep it simple), but otherwise yes I agree.

I am definitely interested on the 1st point - I'm assuming RefreshCommandUI() would trigger the EnabledScript for *all* current ribbon elements, not just one? If so, I wonder - is it a good pattern to use this when you know only one element has changed? I know it's on the client, but still it's more processing than necessary no?

Or is the bug something bigger than my understanding?

Good discussion, thanks :)

Chris.

Shai Petel said...

Hi Chris,

The issue is that you want to click a ribbon button (add status message) and in that event you change the flag for enabling other ribbon buttons.

Thing is, the ribbon does not re-evaluate the button status in that time.

So, in the RTM build that i have the buttons stayed disabled all the time, unless I trigget the update (like by clicking on a web part).

The refresh function does not run on all of the ribbon, it does 2 things:
1. Show / Hide contextual tabs
2. Enable / Disable controls only within the current active tab

but I don't see anyway to make it more efficiant, as you can never know who else might be listening (other controls perhaps by another developer)

Chris O'Brien said...

@Shai,

Oh wow - the buttons were definitely behaving as expected when I did the development, but since that was in January 2010 this was obviously not on RTM. I guess something changed between releases.

In which case I'm even more grateful for your correction - I guess we conclude RefreshCommandUI() is required in these situations then.

Thanks,

Chris.

Luis Esteban Valencia said...

I have a warning on the xml file:

Warning 1 The element 'Scaling' in namespace 'http://schemas.microsoft.com/sharepoint/' has invalid child element 'MaxSize' in namespace 'http://schemas.microsoft.com/sharepoint/'. List of possible elements expected: 'Scale, LowScaleWarning' in namespace 'http://schemas.microsoft.com/sharepoint/'. K:\Users\Administrator\Documents\Visual Studio 2010\Projects\SlnSharepoint2010\EmptySharePointProject1\CustomRibbonElements\Elements.xml 19 16 EmptySharePointProject1

Any idea ?

Thank you

Chris O'Brien said...

@Luis,

Hmm that's pretty weird - if you take a look in CMDUI.XML you'll see that Microsoft themselves use have many 'MaxSize' elements in their ribbon XML (I've just checked on RTM).

Could it be something with namespaces used in your XML? Are you developing on the RTM version?

Thanks,

Chris.

Luis Esteban Valencia said...

Hello Chris, as somebody else said on the comments I fixed this by changing order,
MaxSize,MaxSize,MaxSize, Scaling, Scaling, Scaling

Yes, I am using RTM

Chris O'Brien said...

@Luis,

Great stuff - that's certainly the sequence Microsoft use for their elements.

Chris.

ഇക്ബാല്‍ കാഞ്ഞിരമുക്ക് said...

Hi,

How to create new approval task content type using ribbons.

This is for project server 2010 workflow. There is a default project server workflow task form. Am looking for custome one including Approve/Reject and Send to initiator.

Can you help me?
Thanks in advance
Iqbalkmk

BeginningSP2010 said...

(can't believe my browser died as soon as I clicked on publish. please ignore if this is being double posted)
I'm awfully ashamed with myself for having to ask this but please do bear with me. I want to try out your example but I can't figure out where exactly I'm supposed to add this XML excerpt? Is it going into the cmdui.xml or am I supposed to create a new XML file? I would have thought the second one but if that's the case, how do I make SP load my XML file along with the cmdui.xml?
I know this is basic knowledge probably but if you could please give me a quick answer, I'd be indebted.
Thank you in advance and have a wonderful weekend.

Chris O'Brien said...

@BeginningSharePoint2010,

Actually that's a great beginner's question, apologies that the article doesn't deal with it. The XML will be in a new XML file - cmdui.xml is an "out-of-the-box" SharePoint file and like most of these, should never be modified.

To get your custom XML file to be 'known' by SharePoint, you have to create something called a Feature - this has a feature.xml file which contains several several 'elements', such as the CustomAction shown here to modify the ribbon. You can create a Feature easily in Visual Studio 2010 by right-clicking on the Features item and selecting 'New Feature'.

As you can probably tell, creating Features requires an element of development skills and (unless you're crazy and do it in Notepad) use of Visual Studio.

Hopefully that helps you out with some links and terms to search on for additional reading. Feel free to ask more questions though.

Thanks,

Chris.

BeginningSP2010 said...

Thank you so very much for your prompt answer! I can't believe how quickly you answered me! Thanks again! I'm digging in the SharePoint blogs like a maniac :) Lots of knowledge to digest but I'll get there one day :)

eswarachari said...

Hi chris

This is a great post for the SP 2010 beginers to jump into the Ribbon implementaion.

1.I Implemented the ribbon control given in the sample.
2.everything working fine, but the problem came after adding the pageviewer webpart to the page.
After adding the webpart all the buttons in the tab are getting disabled, which were in the enable mode prev.

pls get me if you have any solution

Thanks
Eshhwar

Henrik Andersson said...

@ShaiPetel

I have posted an example on how to disable and enable buttons based on a field value and permission.

Perhaps it can help you in the right direction?

Regards,
// Henrik

Martin said...

Chris,

first of all, thanks for a great blog serie. It helped me a lot!

Do you have any idea of how to extend the behavior of an existing (OOB) button on the ribbon?

I need to extend the 'Cancel Approval' button so that it cancels both the approval workflow and a custom workflow. Is this possible?

Regards,
Martin

Chris O'Brien said...

@Martin,

Good question. I think what you'd have to do in this case is hide the original button and put one of your own in there. I can't think of a way to extend the original functionality, outside of making changes to the original workflow.

You'd use HideCustomAction to remove the original button from the ribbon.

HTH,

Chris.

Martin said...

Thanks for the feedback! Unfortunately im not able to use the HideCustomAction element. This removes the button and this will cause an error (something inside SharePoint depends on the button to exist).

I've tried to remove it with CSS but the "big" button (the root of the menu or whatever it might be called) is still showing and I dont know how to change this.

I will redesign my solution so I dont have to do this...


Regards,
Martin

Anonymous said...

Great job Chris.
I am working on customizing the View Item ribbon of Calendar list. That is, I am required to move the "Export Event" from Custom Commands to any of the View groups.
To give more description: When you hit View Item a new window shows up with View and Custom Commands tabs. One of the project requirement is to move (or copy) the Export Event to View tab. Here I can create separate group on View tab. But I am not required to do this. I tried to get the location of this Ribbon Control. What is the location of this control and how do you customize this? Any help please?

Anonymous said...

Very good post!

I have a small problem though. I am a true beginner so this might be a stupid question.

I have created an XML-file in my project, and pasted your code. I have also created a feature but the feature does not "find" my XML-file so I can not add it to the feature.

techgeek said...

Hi Chris,

Awesome Post. It helped me a lot. I am fairly new to SharePoint 2010 & want to badly customize the ribbon. I get that the ribbon components are stored in that XML file in the GLOBAL folder. But if you want to customize the ribbon, where we need to upload the new XML or we need to change the CMDUI.XML itself. I read your entire post & this is the only thing which is not clear. Please can you include the whole process i.e. what we need to exactly do to customize the ribbon. Upload the new xml somewhere or modify the existing XML.

Thanks & Regards,
Panny

Chris O'Brien said...

@Panny,

No, you shouldn't modify CMDUI.XML - check out my answer to @BeginningSharePoint2010 above for how to go about using a 'Feature' to supply the custom XML to SharePoint.

Thanks,

Chris.

Chris O'Brien said...

@Anonymous,

You can use Feature Explorer in Visual Studio 2010 to control which elements files are referenced by a Feature.

HTH,

Chris.

Anonymous said...

Ignore my last message. I was able to get it working using the RefreshCommandUI. Should have read the feedback thread first.

Thanks,
Jake

Chris O'Brien said...

@Jake,

Glad to hear you got sorted - RefreshCommandUI() is indeed key in the RTM version of SP2010.

Cheers,

Chris.

P.S. Sorry that your original question seems to have been dropped by my blog host. (For other readers this was just a statement that the sample wasn't working as advertised, and as noted in these comments it's RefreshCommandUI() which is needed.)

Anonymous said...

I have an issue with the OOTB Ribbon Commands. I am creating a new Master Page. Nothing new here I have done literally hundreds of them. However, I now amd seeing a postback error. Realted to a few of the ribbon elements. Show Ribbon,Hide Ribbon and the edit page buttons. I have not added a new ribbon. First Time, I have seen this: Invalid postback or callback argument. I am at a loss to how this is being caused??

Unknown said...

Hello Chris, first of all great article. It was very helpful. But I have small problem I created custom group and button for ribbon in Library/Documents contextual tab and it is visible from sites in Library category i.e. shared documents site but it is not from any other site when Library contextual tab appear. I dont know if it is good idea to put my code in comment right here so I will not. But maybe you know what the problem might be?
Regards
Michael

Chris O'Brien said...

@Michael,

Sure - I'll try to help if you post the details here.

Thanks,

Chris.

Unknown said...

So basically I want to add new group to Library/Documents contextual tab. My custom action is defined like this:

http://pastebin.com/4FEUR5de

But this group is visible only from Library sites i.e. Shared Documents. I want it to be visible from every site page, everytime the Library contextual tab is shown. What should I change to make it visible with all Library contextual tab?

Regards
Michael

Unknown said...

Hello again, I already resolved issue: I removed RegistrationID and RegistrationType tags, performed iisreset and cleared browser cache and my group is visible from every place.

Regards
Michael

Chris O'Brien said...

@Michael,

Excellent, good to hear you got sorted.

Chris.

Ariel said...

Hi Chris, thanks for this great content.

I'm trying this out, as-is, just copy & paste, and when I navigate to a 101 doc lib, tabs spin showing a "Loading..." message.

Any idea?

Ariel said...

Nevermind, found the problem... missing a GroupTemplate. After re-adding that, working correctly. Great post Chris. You can simply ignore my prev question.

xzy2ob said...

Hi,

The "full lists of ids" does no longer exists:

http://simeonlobo.wordpress.com/2008/02/27/sharepoint-list-template-ids/http://simeonlobo.wordpress.com/2008/02/27/sharepoint-list-template-ids/http://simeonlobo.wordpress.com/2008/02/27/sharepoint-list-template-ids/

Where could it be found now?

Chris O'Brien said...

@All - the link to the list of template IDs has now been updated, as have many of the other points. Many thanks for the feedback!

Chris.

Anonymous said...

Great Work Chris.It helped me a lot in customizing the SP ribbon

Anonymous said...

me and my colleague are trying to simply learn how to add a custom group, thanks for adding 3 pages of xml right off the bat...

Anonymous said...

Excellent Post Man ,helped me a lot

Unknown said...

Awesomesauce of a post! Just a quick question on possibility of making a tab group always visible no matter what list/library you are on. Would prefer if only in elements.xml file, but if it requires code, that would be good to know! As always, your posts are quite useful!

Chris O'Brien said...

@evilgenius,

This can be accomplished by registering your customization by content type, and using 'Item' as the content type. So, on the CustomAction I think you'll need:

RegistrationType="ContentType" RegistrationId="0x01"

HTH,

Chris.

SPEvilGenius said...

Chris,

A little late! but yes, it all worked out! Now the loaded question! Is it possible to do all of this in pure javascript without declarative XML? For some clients there is no Designer or access to the HIVE!!

Regards,
Dan Walker
SPEvilGenius

Anonymous said...

Great post, as usual.
Thank you.

Unknown said...

Hi Chris, this is awesome post. Very helpful. Saved me loads of efforts. I searched for articles on ribbon customization for good two weeks, but found nothing that matched my requirements. Certainly you are a great asset to the share point community.

Jason Dunbar said...

Yep, six years later, people are still finding this useful.

Thanks - hope you're well :-)