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.

Thursday, 7 January 2010

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.

Wednesday, 16 December 2009

ECM platform enhancements - Content Organizer, List Throttling, Enterprise Content Types etc.

This is part 2 of my write-up on my ‘ECM Enhancements in SharePoint 2010’ talk which I did recently. You can find previous parts here:

Part 1: Managed Metadata in SharePoint 2010 – a key ECM enhancement
Part 1.5: Managed Metadata in SharePoint 2010 - some notes on the "why"

Having spent some time on the taxonomy/metadata piece in the last articles, this post will delve into other ECM enhancements. ECM is quite a broad topic (Document Management/Collaboration/Records Management/Web Content Management), but it’s amazing just how much of the new stuff you can’t cover in such a talk – Sandboxed Solutions, BCS, Service Applications, API improvements etc. are all only tenuously linked to ECM, so didn’t get coverage.

Here’s what I think of as some key generic ‘ECM platform’ enhancements:

  • Scalability
  • Enterprise Content Types
  • User experience
  • Taxonomy/metadata (as covered in earlier posts)
    • Navigation by metadata
  • Content Organizer
  • Document Sets
  • Tagging

No doubt you might think of others as being a big deal too, but that’s a good list for starters. In this article we’ll look at some of these items in detail, but for others I’ll point you to other articles which have good coverage on those topics.

Scalability/list throttling

Although us architects/developers think that we know a lot about scaling SharePoint these days, consider that in the 2007 release a common cause of Out Of Memory exceptions in large farms was use of the Content Query Web Part on site home pages, left to default settings. Of course this usage is perfectly natural – CQWP is about rolling-up content after all, but what compounded the problem is that some site templates (e.g. publishing portal) added this to the home page for you, thus triggering the problem without explicit configuration. In large sites this would result in a significant query which put significant load on the servers, and the built-in caching didn’t fully mitigate it.

In SharePoint 2010, the query-throttling feature is designed to prevent this problem – this will cut off large queries once a threshold has been passed and the full results will not be returned, thus safeguarding stability. The limits are set at the web application level, and it’s possible to specify a window when the governor will not kick in (aka ‘happy hour’):

ListThrottleSettings

If the query originated from custom code, the query will not complete and an SPQueryThrottledException will be raised:

SPQueryThrottledException

This of course, is a good thing - developers just need to ensure their 2010 code catches this exception type and presents a pretty message on the page, rather than show a Yellow Screen of Death. Interestingly, when a user encounters a similar scenario in a regular SharePoint list view, the experience is somewhat more sophisticated as some results are returned (up to the point where the threshold is crossed), and an equivalent ‘signal’ is passed to the user in the form of a message:

ThrottledListResults

As an aside, I notice at this point I’ve been redirected to a URL which contains all the parameters needed to display the “permitted” subset of data:

http://cob.collab.dev/Lists/LargeList/ShowEverything.aspx#ServerFilter=FilterField1=ID-FilterValue1=3752-FilterOp1=Geq-OverrideScope=RecursiveAll-FallbackLimit=3752-ProcessQStringToCAML=1 

I haven’t yet worked out if it’s possible to use this “show partial results” facility in custom code – certainly it would be nice in some scenarios. It sounds feasible as in the list view request, the server has worked out what parameters constitute an acceptable query (ID >= 3752) and (it seems) has redirected the user to a new request which contains the filter parameters. I’d be interested to hear if anyone has noticed how this can be done with code – I haven’t dug too deep, but the most obvious place to communicate this info back (the SPQueryThrottledException) doesn’t have anything.

Enterprise Content Types

In 2007, maintaining content types and site columns across an enterprise deployment effectively became a technical problem – the business could not take care of this without help. Changes they made would only be local to a site collection and code/scripts were required to roll out updates more globally. With Enterprise Content Types in SharePoint 2010, this common pain point is eased – effectively the model is a hub and spoke-type arrangement where a ‘master site’ is nominated, and then a service application/set of timer jobs takes care of synchronizing the changes to other sites which are hooked up to the same Managed Metadata service app as the hub. I decided not to demo this in my talk in the end, partly because it doesn’t really make the world’s most scintillating demo. However, that’s not to downplay the significance of this feature – I think this goes a long way to making SharePoint more manageable in the enterprise. Others have covered this well, a couple of good write-ups are:

User experience

Put simply, the new wiki-style page editing experience will have a huge impact on collaboration sites. The ability for users to place rich text, images and even web parts exactly where they like on a page (as opposed to having to deal with web part zones and the Content Editor web part) will make a lot of end users happy:

PageEditing

Interestingly if you don’t want the new page editing experience in team sites, you’ll probably need to do some customization work. It is possible to create web part pages in the new team site template (and even store them in the ‘Site Pages’ library alongside wiki pages), but the user experience isn’t obvious and the menu options aren’t right there in front of the user – you have to pass by the ‘New Page’ menu item and head to ‘More Options’ > ‘Web Part Page’ and specify which library to store it in. So if you did want the ‘old-style’ page editing, you’d probably either use a custom site definition or at least add something to the Site Actions menu or ribbon if sticking to the 2010 team site template. In any case, the wiki page editing functionality is defined by the actual field control used rather than the field type – it seems there’s a fair amount of behind-the-scenes jiggery-pokery which goes on with hidden fields, update panels and a new EmbeddedFormField control (in team sites at least, publishing sites continue to use the RichHtmlField control), but the upshot is that anywhere the new Rich Text Editor is used will display the new editing experience.

I know for a fact that the site owners on the collaboration roll-out I’m currently working on would welcome this with open arms. Consequently this is a significant for advance for SharePoint as an ECM platform, and I can see it being a key driver for some organizations to upgrade to 2010 for their collab sites.

Finally, as alluded to just now, publishing sites also get the new RTE – which as an aside, means things like the wiki-syntax for linking pages can be used there too.

Content Organizer

You may have already seen AC talk about this – in essence the Content Organizer allows you to add certain types of content (e.g. documents, pages, rich media files but NOT list items) to one bucket (known as the ‘Drop-Off Library’), and then let some rules define where it should actually end up within your site. A timer job handles the actual processing of the rules. Typically you’ll define rules based on metadata, so continuing to use my electrical goods example, here I’m creating a rule which moves documents with a ‘Screen type’ value of ‘Plasma’ to a library called ‘Television specifications’:

ContentOrganizerRules

I’m also specifying that subfolders should be created within the document library, such that I’ll end up with a set of folders for each of the ‘Screen type’ values encountered when documents are uploaded. In general, the way rule definition works is you first specify the content type the rule applies to, then the UI allows you to select columns on that content type to use as metadata filters. The metadata bit is actually optional though – your rule could simply be based on content type, with no further sub-filter based on metadata values.

If you’re like me, you may have wondered how the Content Organizer copes with certain scenarios – so here are a few findings:

  • What happens if users don’t go to the ‘Drop-Off Library’ to add documents there? Surely the rules won’t run otherwise? To ensure all uploads do indeed use the routing rules, you can specify that users should be redirected to the Drop-Off Library when they upload – this is pretty seamless to the user, they just see a small message in the dialog indicating their file will be routed:

    ContentOrganizerMessage
  • What happens if the user doesn’t have permissions to write to the library specified in the rule? The user will get an Access Denied, and the document gets cleared from the Drop-Off Library. I’d be interested to know if the ‘rule managers’ are notified in such a scenario (I didn’t have SMTP set up at the time of testing) – this is the group who can be configured to be notified when a document is uploaded which doesn’t match any rules, but I’m not yet sure if they get notified for other cases like this.
  • When specifying a rule to route items to another site’s Content Organizer, what happens if the rules there redirect back to this one? Somewhat tongue in cheek this one, but hey, it’s good to know what happens! The answer is the document stays in the Drop-Off Library, and as with any such routing failure, if configured the rule managers are notified after the specified wait period is over.
  • What does the user see if no rules are matched? As well as the notification to the rule managers, the user sees a message to inform them their document won’t be being routed anywhere just yet:

    RuleNotMatched

Steve Peschka has a great series of 3 in-depth articles on Content Organizer, and also you can quickly understand a lot about defining rules from the screenshots in AC’s article.

Document Sets

Briefly, Document Sets allow multiple items to be treated as one in terms of workflow, approval, versioning and so on. A classic use case might be a proposal which consists of several documents – however they are bundled up and given to a client all together, meaning they need to be treated almost as a ‘release’ in coding terms. There are some useful features in here, like the fact you can set default values on columns, which individual documents will inherit when added to the set. Also, each Document Set gets a “home page”, meaning content (including web parts) can be added to provide an entrance on to the information.

Liam has a good write-up here - “SharePoint 2010 User Experience – Document Sets

Tagging

Tagging in SharePoint 2010 is a deep area so I can’t do it justice here, and it reaches into the “social” arena as well as the Managed Metadata framework I’ve previously discussed. Some highlights are the fact that the social bookmarking feature (“Tags and notes”) allows any item inside or outside SharePoint to have your tags and notes added, and that your recent tagging activity is summarized in your activity feed on your MySite (à la Facebook tagging).

Christian Glessner has a write-up here - Managing Metadata in SharePoint 2010

Summary

SharePoint 2010 contains a raft of ECM enhancements, and any one could be a killer feature for your organization. The “ECM platform” features discussed here are relevant whether you are using SharePoint for collaboration/document management, WCM, Records Management or all three. Many of the criticisms of the 2007 release have been addressed, and right now it looks like SharePoint 2010 will be an even bigger hit for ECM than it’s predecessor.

Tuesday, 8 December 2009

Common compilation error for SharePoint 2010 VS projects

This post is a quick public service announcement for a beta 2 issue which I think a lot of SharePoint developers are about to hit. I can’t take any credit whatsoever for the fix, I’m simply playing the messenger here – purely because I notice that whilst the information is in the public domain now, search engine users will probably not find it. Since I’ve hit this a few times already (including when writing code for my Managed Metadata demo), I figure it’s worthy of more attention.

The extremely useful SharePoint 2010 Beta Release Known Issues post over on the SharePoint Developer Documentation Team Blog has the info in one of the ‘development issues’ points:

  • Some assemblies, such as Microsoft.SharePoint.Publishing, appear in some cases to have a dependency on an incorrect version of the System.Web.DataVisualization assembly. The incorrect reference causes build failures. If you see this problem, add a reference to the correct version of System.Web.DataVisualization on your system. If you installation is on the C drive, that assembly will be located here:

C:\Program Files (x86)\Microsoft  Chart Controls\Assemblies\System.Web.DataVisualization.dll

To expand on this, this is a known bug which causes a design-time only error, since the correct version will be loaded by .Net at runtime – so it will stop you compiling, but once you’ve got past that you’re good. When I hit the issue the fix above didn’t seem to work for me, but an alternative fix of adding this path into a specific registry key did – however, when I went back to test again adding the reference did solve my problem, so I’m assuming I did something wrong first time. Certainly this approach is far preferable to editing the registry on every dev VM you have, so I won’t publish those details.

At least the following assemblies seem to be affected:

  • Microsoft.Office.Server.Search
  • Microsoft.SharePoint.Taxonomy
  • Microsoft.SharePoint.Publishing

I tried using NDepend to generate a full list, but for some reason the trial version isn’t finding dependencies which I think exist. In any case, if you discover additional assemblies with this issue, please leave a comment.

Finally, to help folks searching for this on the interweb here are some of the errors you’re probably searching on:

The primary reference "Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the framework assembly "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or retarget your application to a framework version which contains "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

The primary reference "Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the framework assembly "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or retarget your application to a framework version which contains "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

The primary reference "Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the framework assembly "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or retarget your application to a framework version which contains "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

Again, thank the guys behind the SharePoint Developer Documentation Team Blog and the Product Group folks for this info, not me.