Posted on 29. March 2013

Asynchronous loading of JavaScript Web Resources after U12/POLARIS

We all know that UR12/POLARIS was a monumental release for Dynamics CRM what with the new Process Forms and Cross Browser support, but also included were some performance optimisations. One such improvement was a change to the way JavaScript Web Resources are loaded on forms so that they load and execute asynchronously rather than in the order that they were added to the form. The drawback with this optimisation is that it can cause the '..is undefined' script error if you have scripts that depend on other scripts being loaded first.

This post describes the loading behaviour and some possible solutions.

How did it work before UR12/POLARIS?

In my example I have 3 scripts, each dependant on the last. Mscorlib.js is the Script# system library that is needed before any other libraries can be loaded. I'm not talking about code that runs in the 'onload' event of a form but global code that is run after the script has downloaded used to define the prototypes of the objects that are used by the onload code.

In the Client.js, I might have some Script# generated code that requires a core Script# (ss.IEnumerable) type that is defined in a different script file.

Xrm.Sdk.DataCollectionOfEntity.registerClass('Xrm.Sdk.DataCollectionOfEntity', null, ss.IEnumerable);

For this code to run, the mscorlib.js must be loaded and executed first. The same applies if you are using jQuery and jQuery-UI.

The form definition used to have each script added in order so that they would be added to the 'head' section of the page as follows:

<head>
..
<script src=”/%7B635001685810000000%7D/WebResources/fdocs_/js/mscorlib.js” type=”text/javascript”></script>
<script src= /%7B635001685810000000%7D/WebResources/fdocs_/js/Xrm.js” type=”text/javascript”></script>
<script src=” /%7B635001685810000000%7D/WebResources/fdocs_/js/Client.js” type=”text/javascript”></script>
..

The resulting load pattern would be something like:

  • The script load/execution would be approximately:

    1. Mscrolib.js downloads and then executes
    2. Xrm.jsd downloads and then executes after mscrolib.js has completed executing since it follows it in the <HEAD> section of the page
    3. Client.js downloads and then executes after Xrm.js has completed executing since again it is added to the <HEAD> section in this order.
    4. The On Load event code then runs when all the scripts have loaded and finished executing

     

     

    What changes with the UR12/POLARIS update?

    Scripts are no longer in the <HEAD> section but are loaded asynchronously via the 'loadScriptAdv' function:

    loadScriptAdv('\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fmscorlib.js', '\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fmscorlib.js', false);
    loadScriptAdv('\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fXrm.js', '\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fXrm.js', false);
    loadScriptAdv('\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fClient.js', '\x2f\x257B635001707050003339\x257D\x2fWebResources\x2ffdocs_\x2fjs\x2fClient.js', false);
    

    The new asynchronous load behaviour results in the OnLoad being run after a smaller wait time since each script can execute without waiting for the preceding ones. This is a good performance gain but causes scripts to fail if the dependant scripts are not loaded at the point of execution.

  • The interesting thing is that this wasn't immediately apparent or was only an intermittent problem because once the scripts are loaded into the browser cache, the execution would usually be in the correct order. I can consistenly reproduce the issue by clearing down the browser cache and then disabling all caching.

    Ribbon JavaScript

    It's been a common technique to add dependant libraries to Ribbon Commands by adding them as Command Actions with a function of 'isNaN'

    <Actions>
    <JavaScriptFunction Library=”$webresource:mscorlib_crm.js” FunctionName=”isNaN”/>
    <JavaScriptFunction Library=”$webresource:xrm.js” FunctionName=”isNaN”/>
    <JavaScriptFunction Library=”$webresource:RibbonCommands.js” FunctionName=”someCommand”/>
    </Actions>
    
    

    It seems that the same issue exists with these libraries since they are loaded in an asynchronous fashion.

    Solutions

    Unfortunately, any solution that uses depending scripts in this way has to be updated – and like with all things, the right solution depends on your specific case.

    1) All script in a single library

    By far the simplest solution is to put all of your scripts into a single file in the correct order.

    I didn't' settle for single script option for the following reasons:

    1. A single script is harder to maintain
    2. Common libraries can't be shared between solutions
    3. All code must be downloaded on first use, rather than only downloading what is required at the time.

    2) RequireJs or HeadJs

    There are some good JavaScript loading libraries out there such as RequiresJs or HeadJs. These libraries allow you to dynamically load dependant script before you execute code that needs them.
    Gayan has an example of this technique on his blog. Maarten also has a good tutorial on his blog.

    I didn't settle for the RequireJs/HeadJs option for the following reasons:

    1. It requires you to manually set the Cache key to ensure that scripts are not downloaded every time they are needed
    2. It completely bi-passes the Dynamics CRM script loading mechanism in an 'unsupported' way
    3. There is some possibility that a backward compatibility option may be added into a future release (I'm ever the optimist!). Adopting this approach would make it harder to revert back to the standard script registration approach.

    3) Manually wait for required libraries to load

    The approach I took was to build a simple wait function to wrap code in that prevented execution until the required scripts had loaded. Each time a script completes, it adds its name to a semaphore array that is waited on by other libraries. The result is much the same as before UR12:

  • The difference here is that the scripts start executing as soon as they are loaded, but then wait using setTimeout until the required scripts have completed. The code will work on pre-UR12 systems as well, but because the wait is unnecessary, the code will simply execute the script without checking dependancies. setTimeout is used to release control to other scripts because Javascript is single-threaded.

    The Code

    Each script that has dependencies must be wrapped in the following:

    waitForScripts("client",["mscorlib", "xrm",],
    function () {
    //Original Code goes here
    });
    

    The first parameter provides the name of the current script, and the array is the list of scripts that must be loaded first.

    So in the Xrm.js library, you would wrap it in:

    waitForScripts("xrm",["mscorlib"],
    function () {
    //Original Code goes here
    });
    

    Each script must also include the waitForScript function at the bottom:

    function waitForScripts(name, scriptNames, callback) {
        var hasLoaded = false;
        window._loadedScripts = window._loadedScripts || [];
        function checkScripts() {
            var allLoaded = true;
            for (var i = 0; i < scriptNames.length; i++) {
                var hasLoaded = true;
                var script = scriptNames[i];
                switch (script) {
                    case "mscorlib":
                        hasLoaded = typeof (window.ss) != "undefined";
                        break;
                    case "jquery":
                        hasLoaded = typeof (window.jQuery) != "undefined";
                        break;
                    default:
                        hasLoaded = window._loadedScripts[script];
                        break;
                }
                allLoaded = allLoaded && hasLoaded;
                if (!allLoaded) {
                    setTimeout(checkScripts, 10);
                    break;
                }
            }
            if (allLoaded) {
                callback();
                window._loadedScripts[name] = true;
            }
        }
        // Only check for async loading of scripts if later than UR12/POLARIS
        if (typeof(APPLICATION_FULL_VERSION)!='undefined' && parseFloat(APPLICATION_FULL_VERSION.replace('5.0.',''))>9690.2835) {
    	setTimeout(checkScripts, 0);
        }
        else {
    	callback();
            window._loadedScripts[name] = true;
        }
    }

    Script# 'Script.template'

    I almost exclusively use Script# in my Dynamics CRM projects. What made this solution really work well for me was that I just include the code in the Script.template file to wrap the '#include[as-is] "%code%"'. The beauty is that I can now forget it's there and it just gets minified along with the rest of the code when deployed.

    This issue has affected a number of people I know, and I'm sure it'll start to become more of an issue as people start to upgrade to the latest Rollup. If you have any suggestions/comments, please let me know.

    The code was originally posted by me in the MSDN forums:

     http://social.msdn.microsoft.com/Forums/en-US/crmdevelopment/thread/fdca4779-e866-4e51-bab9-97a159f9cd37

    @ScottDurow

    Posted on 19. February 2013

    App/Command Bar Workbench for Dynamics CRM 2013

    The present roadmap for Dynamics CRM has the next version (Dynamics CRM 2013 possibly) sporting the new 'modern' UI throughout. An automatic upgrade path from Dynamics CRM 2011 forms customisations and ribbons will be provided - and it's likely to use a similar RibbonXml like schema under the hood. With this in mind I've done some re-imagining of what the Ribbon Workbench might look like by the end of the year.

    Introducing the App Bar Workbench for Dynamics CRM 2013

  •  

    Exciting times are ahead :)

    @ScottDurow

    Posted on 2. February 2013

    Adding Auto Refresh to Dashboards

    This post shows you how to set up a CRM2011 Dashboard 'auto-refresh' feature using the Ribbon Workbench in 10 easy steps (well…11 if you count clicking publish at the end!).

    We will add an auto refresh function to the 'EnableRule' of the Dashboard refresh button that schedules a refresh using the 'setTimeout' function. The EnableRule is called when the dashboard page is first displayed to check if the Refresh Button should be enabled. We schedule the refresh, and then return true to ensure the button is still enabled. This technique can also be used to add JavaScript to areas of CRM 2011 that are not form based.

    Let's get started:

    1) Create a new solution and add the Application Ribbon to it (Client Extensions->Add Existing->Application Ribbon).

    2) Create a new web-resource named 'RefreshRibbon.js' (or something similar)

    Add the following JavaScript to it:

    var AUTO_REFRESH_INTERVAL=30000;
    /// Schedule the first refresh if we havn't already
    function autoRefreshDashboards() {
        var topWindow = window.top;
        if (typeof (topWindow['refreshRegistered']) === 'undefined') {
            window.setTimeout(autoRefreshTimeoutCallback, AUTO_REFRESH_INTERVAL);
            topWindow['refreshRegistered'] = true;
        }
        return true;
    }
    // Refresh the dashboards and schedule the next refresh
    function autoRefreshTimeoutCallback() {
        try {
            Mscrm.DashboardRibbonActions.refreshDashboardPage();
            window.setTimeout(autoRefreshTimeoutCallback, 30000);
        }
        catch ($e1) {
            // Perhaps the dashboards are no longer loaded. and we are on a different page
        }
    }
    
    

    Note: You can change the refresh interval using the 'AUTO_REFRESH_INTERVAL' value which is in milliseconds (30000=30 seconds)

    Your solutions should now look something like:

  • 3) Open the Ribbon workbench, and load up your new solution.

    4) Select the 'Application Ribbon' in the 'Entities' panel if not already, then select the 'Dashboards' tab in the Design surface (be sure to select 'Dashboards' and not 'Dashboard')

    5) Right click on the 'Refresh All' button and click 'Customise Command' (Not Customise Button)

  • 6) Locate the 'Mscrm.DashboardTools.RefreshCommand' command in the 'Commands' section of the 'Solution Elements' panel. Right click, and select 'Edit Enable Rules'

    7) In the Enable Rules dialog click '+Add New' and then select 'Add Step', before selecting 'Custom JavaScript Rule'

    8) In the Enable Rule properties, set:

    FunctionName: 'autoRefreshDashboards'

    Library : Click the lookup button and select the 'RefreshRibbon.js' (If you don't see it, then you forgot to add the javascript webresource you created above to the solution you loaded into the Ribbon Workbench)

    9) Click OK, and OK again.

    10) In the 'Solution Elements' panel, expand the 'Enable Rules' and select 'Mscrm.IsDashboardSelected'. In the properties panel, set 'IsCore' = True.

    This ensures that this rule is not customised in our solution since we only need to customise the Command to add the new enable rule.

    11) Click Publish Solution

    And you're done! This will work both in the Web Browser and the Outlook client. It is important that you remember that auto-refreshing dashboards could place more load on your server if lots of users leave the dashboards open all day!

    Until next time…

    @ScottDurow

    Posted on 22. November 2012

    Add a Run Dialog Short-Cut Ribbon Button

     

    As I've mentioned before, users are always asking about making solutions 'less-clicky' - a common request is to provide a button to run a frequently used dialog rather than having to search for it each time in the lookup window.

    The following solution shows you how to do this quickly using the Ribbon Workbench for CRM2011.

    http://ribbonworkbench.uservoice.com/knowledgebase/articles/140652-create-a-run-dialog-short-cut-ribbon-button

     

    Happy Ribbon Customising!

    @ScottDurow

    Posted on 26. October 2012

    No-Code Workflow Shortcut Ribbon Button

    Users are always asking about making solutions 'less-clicky' - a common request is to provide a button to run a frequently used workflow rather than having to use the look up dialog.

    The following solution shows you how to do this without writing a single line of code!

    http://ribbonworkbench.uservoice.com/knowledgebase/articles/132235-create-a-workflow-short-cut-ribbon-button-no-code

    Homepage button

    Of course it is using the Ribbon Workbench for CRM2011. If you've not already, download it for free now!

    Posted on 6. September 2012

    O Ribbon, Ribbon! wherefore art thou Ribbon?

    Recently, I had to trouble shoot an issue where the Ribbon was not being displayed for a particular entity. When navigating away from the form, the following error was given by CRM 2011:

    <CrmScriptErrorReport>
      <ReportVersion>1.0</ReportVersion>
      <ScriptErrorDetails>
       <Message>Unable to get value of the property 'attrs': object is null or undefined</Message>
       <Line>1</Line>
       <URL>/_static/_controls/ribbon/ribbon.js?ver=-658901612</URL>
       <PageURL>/main.aspx</PageURL>
       <Function></Function>
       <CallStack>
       </CallStack>
      </ScriptErrorDetails>
    

    It turns out that this was because of a FlyoutAnchor/SplitButton with no PopulateQueryCommand or Menu Sections added. 

    The most recent version of the Ribbon Workbench now has a validation rule that checks you have either of these before publishing.

     

    Posted on 21. July 2012

    How to move a button from one Ribbon tab to another

    If you've ever tried to move a ribbon button from one tab to another, you'll know it involves some hefty Ribbon Xml gymnastics.

    The Ribbon Workbench makes this customisation really simple:

    http://ribbonworkbench.uservoice.com/knowledgebase/articles/96993-move-a-button-from-one-tab-to-another

     

    Posted on 24. March 2012

    Rename a standard CRM2011 Tab

    Occasionally the built-in names for Ribbon tabs need to be changed. Using the Ribbon Workbench for CRM2011 makes this very easy.

    1. Create a solution with the entity you want to change.

    2. Open the Ribbon Workbench for CRM2011 and select your solution.

    3. Find the tab you want to change. If you want to customise the Form Tabs, you'll need to select the 'Form' value in the drop down in the top right.

    If you wanted to change the 'Add' tab on the Account form, you would add the 'Account' entity to the solution, and select the 'Form' tab in the workbench:

    4. Select the 'Add' tab and use the 'right click' menu to 'Customise Tab'. This will mark each element within the tab as customised so you are free to rename/edit/add to.


    5. Select the 'Add' tab and in the right hand properties, rename the TabTitle to whatever you want:

    6. Click Publish Solution, and you will then see your tab renamed:

    The Ribbon Workbench for CRM 2011 is a free download.

    If you find an issue with the Ribbon Workbench for Dynamics CRM 2011 or have any suggestions,please send it here.

    Happy Ribbon Customising!

     

    Posted on 23. March 2012

    Make sure ribbon buttons appear in Outlook

    There are some marked differences in the way that the Ribbon is handled between Outlook and the Web Client of Dynamics CRM.

    Mandatory DisplayRule and EnableRule

    In the Web Client you can create buttons that refer to a command with just an action. If you want to make the button appear in Outlook, you need to have both a display rule and en enable rule.

    If you don't add at least one of each, you will not see your button because Outlook is failing to evaluate the rules. To see the reason why buttons are not showing up, you need to turn on Developer Debug messages in Outlook :

    (Options->Advanced->Show add-in user interface errors)

    Restart outlook and you will get the following error message:

    An exception occurred while calling function "HrExplorerServerButtonGetVisible"

    To correct this, using the Ribbon Workbench, the minimum you should have is a Display Rule and an Enable Rule both added to your Command. Each rule should have an OrEnableRule that detects either the Web or Outlook Client.

    This gives the following Ribbon Xml:

    <CommandDefinitions>
        <CommandDefinition Id="new.ApplicationRibbon.Command0.Command">
          <EnableRules>
            <EnableRule Id="new.ApplicationRibbon.EnableRule0.EnableRule" />
          </EnableRules>
          <DisplayRules>
            <DisplayRule Id="new.ApplicationRibbon.DisplayRule0.DisplayRule" />
          </DisplayRules>
          <Actions>
            <Url Address="about:blank" WinMode="0" />
          </Actions>
        </CommandDefinition>
      </CommandDefinitions>
      <RuleDefinitions>
        <TabDisplayRules />
        <DisplayRules>
          <DisplayRule Id="new.ApplicationRibbon.DisplayRule0.DisplayRule">
            <OrRule>
              <Or>
                <CrmClientTypeRule Type="Outlook" />
              </Or>
              <Or>
                <CrmClientTypeRule Type="Web" />
              </Or>
            </OrRule>
          </DisplayRule>
        </DisplayRules>
        <EnableRules>
          <EnableRule Id="new.ApplicationRibbon.EnableRule0.EnableRule">
            <OrRule>
              <Or>
                <CrmClientTypeRule Type="Web" />
              </Or>
              <Or>
                <CrmClientTypeRule Type="Outlook" />
              </Or>
            </OrRule>
          </EnableRule>
        </EnableRules>
      </RuleDefinitions>
    

    Actually it doesn't matter what rules you use, provided there is at least one and it evaluates as true for both Outlook and the Web Client.

    Unique ID's

    In the Web Client, each Homepage ribbon is loaded on the fly within a browser window. In Outlook, all of these ribbons are loaded on start up. If you don't have unique identifiers for all CustomActions and Buttons, you will receive the error:

    Error Found in Custom UI XML of "Microsoft Dynamics CRM"
    "The ID CrmOrg.<GUID>.<Button ID>' is duplicated"

    This issue was originally described by http://blog.sonomapartners.com/2011/08/ribbon-errors-in-outlook-add-in-for-dynamics-crm-2011.html

    The most recent version of the Ribbon Workbench for Dynamics CRM 2011, ensures that when you add a button to a group on a tab that is duplicated for more than one entity (i.e. The Application Ribbon tabs), it add '{!EntityLogicalName}' into the ID, so that no two ID will be the same.

    E.g.

    <CustomAction Id="new.ApplicationRibbon.{!EntityLogicalName}.Button12.Button.CustomAction" Location="Mscrm.HomepageGrid.{!EntityLogicalName}.MainTab.Management.Controls._children" Sequence="45">
          <CommandUIDefinition>
            <Button Id="new.ApplicationRibbon.{!EntityLogicalName}.Button12.Button" LabelText="$LocLabels:new.ApplicationRibbon.Button12.Button.LabelText" Sequence="45" TemplateAlias="o2" />
          </CommandUIDefinition>
        </CustomAction>
    

    Thanks to Peter Hörnell for his help with this one.

    Posted on 26. February 2012

    Ribbon Scaling De-mystified

    If you want to add a custom group or tab to a CRM2011 ribbon, you're going to need to understand the scaling mechanism. Following the SDK tutorials and other sources around the internet will give you a basic template, but it only starts to become clear when you see it in action. The Ribbon Workbench for CRM2011 allows you to define your own scaling and quickly see how your controls will behave when the window is re-sized without waiting for it to be uploaded and published first. For this reason it makes the Ribbon Workbench a great tool to use to get a better understanding of how it all works.

    Ribbon Scaling is basically a way of ensuring that the user experience is as the best it can be when working with windows of different widths. In the old-days of toolbars, if you could fit all the tools into your window widths you had a simple overflow drop down where the rest would appear. This wasn't very helpful when the most commonly used tools were always on the right hand side, because you always had to use the drop down overflow. To overcome this, the ribbon allows full control over how each group changes with the available space meaning that you can make less commonly used buttons shrink, hide the label before the more important ones.

    This article is an attempt to de-mystify the rather confusing description of thise mechanism in the SDK (http://msdn.microsoft.com/en-us/library/gg309453.aspx) by using the Ribbon Workbench to show:

    • Adding a custom tab to the application ribbon (Dashboard page)
    • Adding 3 custom groups
    • Defining scaling so that the groups shrink when the window is resized.
    • Changing the template for a group

    Before you continue, go ahead and install the Ribbon Workbench if you haven't already.

    1. Adding a custom tab to Dashboard page

    The Ribbon Workbench uses solutions to define the ribbon customisations, so since we are adding a tab to the dashboard area, we need to create a solution and add the Application Ribbon from 'Client Extensions' as shown here:

    Now we open the ribbon workbench and select our solution. To add the custom tab, drag a new tab from the toolbox onto the application ribbon.

    Every tab requires a 'TabDisplayRule' to define when and where the Tab is visible. The default TabDisplayRule created by the Ribbon Workbench is defined just to show that tab on the HomePageGrid. In order to make our custom tab only visible in the dashboards area, we first delete the pre-created EntityRule, and then add a PageRule with the Address: /dashboards/dashboard.aspx. This tells the Ribbon that the tab should only be shown when the Url of the current page matches the address provided, which in our case is the dashboards area.

    So to re-cap: Delete the pre-created EntityRule:

    Add a new Rule to the Tab Display Rule:

    Finally, select 'Add Rule' and select 'PageRule'. Select the new Page Rule, and enter the Address: /dashboards/dashboard.aspx

    2. Adding three groups to the New Tab

    To really see how the Ribbon scaling behaviour works we need to make our ribbon tab big enough so that it does not fit to a window width by adding some more groups and buttons. Drag another 2 group to the tab, and then add 2 buttons to each layout section o1 and isv. I added some 16x16 and 32x32 images by including them in the solution.

    At this point I should explain what the layout sections are. Each tab is divided into groups as you know but then each group is sub-divided into layout sections. These are not visible directly to the user when viewed in Dynamics CRM, but are used to control how the tab groups scale when the window is resized. Each group is assigned a Template and this Template in turn defines layout sections and their behaviour when scaling. 

    The default template used is 'MSCRM.Template.3'. It defines two Layout Sections with the names 'o1' and 'isv'. You can see the Layout Section names in the Ribbon Workbench to the bottom-left of the dotted divider. 

    Using the Ribbon Workbench you don't really need to worry about the xml, but it sometimes help seing what the template definition xml looks like:

    <GroupTemplate Id="Mscrm.Templates.3">
              <Layout Title="Large">
                <OverflowSection Type="OneRow" TemplateAlias="o1" DisplayMode="Large" />
                <OverflowSection Type="OneRow" TemplateAlias="isv" DisplayMode="Large" />
              </Layout>
              <Layout Title="Medium">
                <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Medium" />
                <OverflowSection Type="ThreeRow" TemplateAlias="isv" DisplayMode="Medium" />
              </Layout>
              <Layout Title="Small">
                <OverflowSection Type="ThreeRow" TemplateAlias="o1" DisplayMode="Small" />
                <OverflowSection Type="ThreeRow" TemplateAlias="isv" DisplayMode="Small" />
              </Layout>
              <Layout Title="Popup" LayoutTitle="Large" />
    </GroupTemplate>

    You can see each Layout Section name is called it's TemplateAlias. This is where ribbon controls are positioned in each Layout Section, but setting the Buttons TemplateAlias location.

    You will notice that the same TemplateAlias appears more than once in the Template. This is because the Template also defines how the Layout Sections will change size when the Ribbon is scaled using the 'DisplayMode' property. It does this by defining different layouts that each have a 'Title'. In each Layout, the Layout Sections are listed and the Type and DisplayMode are changed. So inside the Mscrm.Template.3 you can see that the 'Large' layout has both 'o1' and 'isv' in the 'Large' mode and as 'OneRow' - this results in 32x32 buttons being shown. There is a 'Small' Layout that has 'o1' and 'isv' in 'Small' DisplayMode with a Type of 'ThreeRow' – this makes them appear as 16x16 buttons with no label text. The only difference between this and the 'Medium' Layout is that the DisplayMode is 'Medium' which shows the Label Text alongside the 16x16 button.

    The last Layout is the 'Popup' layout where all group buttons are rendered as a single popup button. Are you with me so far? If not, just take another read of the above, and examine the GroupTemplate Xml to see how it's structured. 

    3. Managing Scaling

    The important thing to understand at this stage is that at any one time each group can only assigned a single Layout from the chosen template. The Layout is selected via the Tab Scaling settings. In the ribbon workbench, the Tab Scales are shown as the items below the main ribbon button area and define the order in which the Layouts are selected as the window width is shrunk or expanded. The starting Layout is selected by taking the left most Tab Scale for each group. In fact these are special 'Max Scales' that define the Layout to use first and are shaded blue in the Ribbon Workbench. As the window width is shrunk down, every time the ribbon width is too big to fit, the next Tab Scale is picked, and the corresponding Layout is used to make the ribbon shrink in width. Each Tab Scale item shows the name of the group it applied to on top, and the Layout that is selected underneath.

    When you first drag a Tab onto the ribbon, the Ribbon Workbench creates only a Max and a Popup scale. This means that when you shrink the ribbon, it will simply start picking the 'Popup' Layout for each group in the order they are specified.

    To see this in action, select the 'Group 1 Popup' and you will see that Group 1 converts to a popup group. If then you select the 'Group2 Popup', in turn 'Group 2' 'shrinks' to a popup. By selecting the Tab Scale from left to right is simulating the user shrinking the window width.

    I added some images and published the solution to see this happening 'for real'. Here is the window using the Max Scale items:

    And now the window is shrunk so that the first Popup layout is selected.

    This is quite an extream way of scaling a ribbon, so normally we would want to change this by defining some intermediary scale steps so that we make button smaller and hide their labels to make them smaller. To do this, drag a 'Tab Scale' item from the toolbox onto the Scale portion of the ribbon between 'Group 1 Large' and 'Group 1 Popup'. Select the new item, and then in the properties, change the Size to 'Medium'. This should give you the following:

    When the user now shrinks the ribbon, it will first change Group 1 to its 'Medium' layout, and then finally to its 'Popup' layout. By adding more Tab Scales and changing their order, you have complete control over the sequence in which groups change their size.

    If you now drag another Tab Scale after the 'Group 1 Medium', select it and change the Group property dropdown to 'Group 2' and the Size property dropdown to 'Small' you will have:

    If you publish this the ribbon will scale as the window is shrunk by:
    First changing Group 1 to Medium
        then Group 2 to Small
            then Group 1 to Popup
                then Group 2 to Popup
                    and finally Group 3 to Popup.
    The great thing about the Ribbon Workbench is you can try this out by simply clicking on the Tab Scale Items from left to right to simulate the window being shrunk.

    4. Change the template

    The default Template.3 is not always going to suit. There are other templates to choose from that CRM 2011 uses in the Standard Ribbons. You can see what these templates are like and use them by clicking on a Group and changing the 'Template' property dropdown list. If you click on Group 3 and select the Template Mscrm.Templates.Flexible.4, you will see the following message.

    This message is warning you that the current Tab Scale items for the selected group have a Layout(s) selected that are not contained in the newly chosen Template. If you select Ok, then each Layout that is not found will be re-assigned to the first layout in the selected template. You will now see:

    You can see here that the new template selected contains many more Layout sections than before, and there are more Layout Sizes to choose from. If you select a Tab Scale Item for the group, and use the Size drop down you can see all of the available Layouts.

    Have a play with this by selecting the different Layout Sizes and see the effect on the buttons. 

    The resulting RibbonXml is as follows:

    <RibbonDiffXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <CustomActions>
        <CustomAction Id="demo.Homepage.applicationribbon.tab26.CustomAction" Location="Mscrm.Tabs._children" Sequence="255">
          <CommandUIDefinition>
            <Tab Command="demo.Homepage.applicationribbon.tab26.DisplayRule" Id="demo.Homepage.applicationribbon.tab26" Sequence="255" Title="$LocLabels:demo.Homepage.applicationribbon.tab26.Title">
              <Scaling Id="demo.Homepage.applicationribbon.tab26.Scaling">
                <MaxSize GroupId="demo.Homepage.applicationribbon.tab26.Group0" Id="demo.Homepage.applicationribbon.tab26.MaxSize.0" Sequence="10" Size="Large" />
                <MaxSize GroupId="demo.Homepage.applicationribbon.tab26.Group1" Id="demo.Homepage.applicationribbon.tab26.MaxSize.2" Sequence="15" Size="Large" />
                <MaxSize GroupId="demo.Homepage.applicationribbon.tab26.Group2" Id="demo.Homepage.applicationribbon.tab26.MaxSize.4" Sequence="17" Size="Large" />
                <Scale GroupId="demo.Homepage.applicationribbon.tab26.Group0" Id="demo.Homepage.applicationribbon.tab26.Scale.6" Sequence="18" Size="Medium" />
                <Scale GroupId="demo.Homepage.applicationribbon.tab26.Group1" Id="demo.Homepage.applicationribbon.tab26.Scale.7" Sequence="19" Size="Small" />
                <Scale GroupId="demo.Homepage.applicationribbon.tab26.Group0" Id="demo.Homepage.applicationribbon.tab26.Scale.1" Sequence="20" Size="Popup" />
                <Scale GroupId="demo.Homepage.applicationribbon.tab26.Group1" Id="demo.Homepage.applicationribbon.tab26.Scale.3" Sequence="30" Size="Popup" />
                <Scale GroupId="demo.Homepage.applicationribbon.tab26.Group2" Id="demo.Homepage.applicationribbon.tab26.Scale.5" Sequence="40" Size="Popup" />
              </Scaling>
              <Groups Id="demo.Homepage.applicationribbon.tab26.Groups">
                <Group Id="demo.Homepage.applicationribbon.tab26.Group0" Sequence="10" Template="Mscrm.Templates.3" Title="$LocLabels:demo.Homepage.applicationribbon.tab26.Group0.Title">
                  <Controls Id="demo.Homepage.applicationribbon.tab26.Group0.Controls">
                    <Button Id="demo.ApplicationRibbon.Button1.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button1.Button.LabelText" Sequence="10" TemplateAlias="o1" />
                    <Button Id="demo.ApplicationRibbon.Button2.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button2.Button.LabelText" Sequence="10" TemplateAlias="isv" />
                    <Button Id="demo.ApplicationRibbon.Button8.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button8.Button.LabelText" Sequence="20" TemplateAlias="o1" />
                    <Button Id="demo.ApplicationRibbon.Button12.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button12.Button.LabelText" Sequence="20" TemplateAlias="isv" />
                  </Controls>
                </Group>
                <Group Id="demo.Homepage.applicationribbon.tab26.Group1" Sequence="20" Template="Mscrm.Templates.3" Title="$LocLabels:demo.Homepage.applicationribbon.tab26.Group1.Title">
                  <Controls Id="demo.Homepage.applicationribbon.tab26.Group1.Controls">
                    <Button Id="demo.ApplicationRibbon.Button3.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button3.Button.LabelText" Sequence="10" TemplateAlias="o1" />
                    <Button Id="demo.ApplicationRibbon.Button4.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button4.Button.LabelText" Sequence="10" TemplateAlias="isv" />
                    <Button Id="demo.ApplicationRibbon.Button7.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button7.Button.LabelText" Sequence="20" TemplateAlias="o1" />
                    <Button Id="demo.ApplicationRibbon.Button11.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button11.Button.LabelText" Sequence="20" TemplateAlias="isv" />
                  </Controls>
                </Group>
                <Group Id="demo.Homepage.applicationribbon.tab26.Group2" Sequence="30" Template="Mscrm.Templates.3" Title="$LocLabels:demo.Homepage.applicationribbon.tab26.Group2.Title">
                  <Controls Id="demo.Homepage.applicationribbon.tab26.Group2.Controls">
                    <Button Id="demo.ApplicationRibbon.Button5.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button5.Button.LabelText" Sequence="10" TemplateAlias="o1" />
                    <Button Id="demo.ApplicationRibbon.Button6.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button6.Button.LabelText" Sequence="10" TemplateAlias="isv" />
                    <Button Id="demo.ApplicationRibbon.Button9.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button9.Button.LabelText" Sequence="20" TemplateAlias="o1" />
                    <Button Id="demo.ApplicationRibbon.Button10.Button" Image32by32="$webresource:new_Image32.png" Image16by16="$webresource:new_Image16.png" LabelText="$LocLabels:demo.ApplicationRibbon.Button10.Button.LabelText" Sequence="20" TemplateAlias="isv" />
                  </Controls>
                </Group>
              </Groups>
            </Tab>
          </CommandUIDefinition>
        </CustomAction>
      </CustomActions>
      <Templates>
        <RibbonTemplates Id="Mscrm.Templates" />
      </Templates>
      <CommandDefinitions />
      <RuleDefinitions>
        <TabDisplayRules>
          <TabDisplayRule TabCommand="demo.Homepage.applicationribbon.tab26.DisplayRule">
            <PageRule Address="/dashboards/dashboard.aspx" />
          </TabDisplayRule>
        </TabDisplayRules>
        <DisplayRules />
        <EnableRules />
      </RuleDefinitions>
      <LocLabels>
        <LocLabel Id="demo.ApplicationRibbon.Button1.Button.LabelText">
          <Titles>
            <Title description="1" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button2.Button.LabelText">
          <Titles>
            <Title description="3" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button8.Button.LabelText">
          <Titles>
            <Title description="2" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button12.Button.LabelText">
          <Titles>
            <Title description="4" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.Homepage.applicationribbon.tab26.Group0.Title">
          <Titles>
            <Title description="Group 1" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button3.Button.LabelText">
          <Titles>
            <Title description="5" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button4.Button.LabelText">
          <Titles>
            <Title description="7" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button7.Button.LabelText">
          <Titles>
            <Title description="6" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button11.Button.LabelText">
          <Titles>
            <Title description="8" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.Homepage.applicationribbon.tab26.Group1.Title">
          <Titles>
            <Title description="Group 2" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button5.Button.LabelText">
          <Titles>
            <Title description="9" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button6.Button.LabelText">
          <Titles>
            <Title description="12" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button9.Button.LabelText">
          <Titles>
            <Title description="10" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.ApplicationRibbon.Button10.Button.LabelText">
          <Titles>
            <Title description="13" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.Homepage.applicationribbon.tab26.Group2.Title">
          <Titles>
            <Title description="Group 3" languagecode="1033" />
          </Titles>
        </LocLabel>
        <LocLabel Id="demo.Homepage.applicationribbon.tab26.Title">
          <Titles>
            <Title description="New Tab" languagecode="1033" />
          </Titles>
        </LocLabel>
      </LocLabels>
    </RibbonDiffXml>

    I've attempted here to shown that it's really easy to create custom tabs groups with the Ribbon Workbench, quickly change the Template for each Group and then add Tab Scale Items to control the scaling behaviour. I think the most important point is that development time is greatly reduced because you can play with how the ribbon will scale without waiting for customisations to be uploaded and published first.

    The Ribbon Workbench for CRM 2011 is a free download.

    If you find an issue with the Ribbon Workbench for Dynamics CRM 2011 or have any suggestions, please send it here.

    Happy Ribbon Customising!