Customizing Ribbons and Command Bars


Adding/Modifying Ribbon and Command Bar Buttons

The Ribbon Workbench tool makes the task of editing CRM ribbons and command bars fairly easy (compared with manually editing XML).
http://www.develop1.net/public/page/Ribbon-Workbench-for-Dynamics-CRM-2011.aspx

Adding a Command Bar Button to a Form that Calls a JavaScript Function (Ribbon Workbench)

Here's a quick step-by-step guide for creating a ribbon ("command bar") button using the Ribbon Workbench tool. For more complete instructions with screenshots, click here.
  1. Create a Solution and add the entity that will provide the custom command button. Don't include "dependencies" in the solution; keep the contents of the solution as small as possible to make editing with Ribbon Workbench faster. Export the solution so that you have a version of the entity before making ribbon changes.
  2. Within the same solution, create (or add existing) a Script Web Resource with a function that the command bar button will call when clicked.
  3. If the script that the ribbon button uses needs to interact with the form (e.g., to set a field value, hide/show controls) then add the script web resource to the Form Libraries for the form where the command bar button will be shown. Publish customizations.
  4. Load the Ribbon Workbench tool
  5. Start by creating a command under the Commands node (Solution Elements tab). Recommendation: Set the command id in the form of "publisher_prefix.entity.CommandPurpose.Command". For example, a command for publisher "Contoso Corp." for the account entity that sets default field values might have the id contoso.account.SetFieldDefaults.Command.
  6. Right-click the command node you just created and select "Edit Actions". Add a JavaScript Function Action. Enter the function name (remember the namespace if you use one in your JavaScript code) and select the library.
  7. Drag a Button control from the Toolbox to the location where you want the button to reside on the form's command bar. In the Properties section, set the Command to the command you just created. Set the Id to something like contoso.account.SetFieldDefaults.Button. Also, provide a value for the fields in the Labels section. Typically, you should set AltText and ToolTipTitleText to the same value as LabelTextText (the caption of the button). Likewise, set Description and ToolTipDescriptionText to the same value. You can use the HTML break (BR) tag in the description fields to create paragraphs/whitespace. It's also a good idea to reuse or create an image and specify the image path in the Image fields.
  8. See the CRM SDK for topic "Define ribbon commands" to learn about how to hide/show the command button based on various conditions.
  9. Click the Publish button at the top of the Ribbon Workbench page. The publish process will take a minute or two (or three). Keep the Ribbon Workbench open in case you need to make further changes.
  10. Go to an entity record where your new button should be (you might need to clear your browser cache first) and try out your command.
  11. If you need to make changes to the button, go back to the Ribbon Workbench, make changes, publish, and repeat as necessary.

Sample (rendered) custom ribbon button

Here's an example of a new "Open Wiki Page" command button added for an entity form:


external image Button.jpg

Manually adding a custom button to the Dashboard Ribbon

This section contains instructions and sample XML for adding a button to the Dashboard ribbon. The scenario for this might be that you want a command button visible for multiple dashboards.

High-level steps: Export “Application Ribbons” from CRM, modify the exported customizations.xml with custom button details and import the revised solution.
Here are specific steps:
  1. Create a solution in CRM and add component “Application Ribbons”.
  2. Export the solution. Keep the original zip file. Make a copy of the zip to use to re-import the modified ribbon definition back into CRM.
  3. Extract the copied zip and open customizations.xml in a text editor.
  4. Run ExportRibbonXml.exe (included with the CRM SDK) to export ribbon xml. This will export applicationRibbon.xml. You will use applicationRibbon.xml as a reference; you will not modify applicationRibbon.xml.
  5. In customizations.xml, add nodes to RibbonDiffXml to define the custom button. See the blog link below for an example. Add nodes CustomAction, CommandUIDefinition, CommandDefinition, LocLabel, etc.
  6. Update the copied solution zip file with the modified customizations.xml file and import the zip file (solution) back into CRM.
  7. Publish all customizations.

The image below shows a custom "Projects" button that appears for all dashboards.
wiki_custom_dashboard_button.png
See this blog for a related example: http://www.resultondemand.nl/support/sdk/81b2066f-76c0-4e85-af36-0ef42222a470.htm

The image below shows the before and after of the changes necessary to create the "Projects" button in customizations.xml. The text on the left is the original customizations.xml and on the right are the entries that create the dashboard button. The text "MSCRMRocks.Wiki" is used as a namespace for various components. You can use whatever you like for the Id's, but it's best to decide on a naming convention and use it for other ribbon customizations to maintain consistency.

wiki_custom_dashboard_button_xml_02.png


Adding a "Flyout Menu" to the Command Bar with Dynamic Commands

CRM makes it possible to add custom flyout (expanding) menus to the Command Bar. You can also dynamically render commands (buttons) so that only the appropriate buttons are visible based on values on the form or other conditions such as the user's role.

In this example, the requirement was to allow the user to create, extend the expiration or remove an FTP account for the contact. In the screenshot, only the extend and remove options are shown because the contact already has an active FTP account.
wiki_commandbar_flyout.png

Design Notes:
  • It's not possible to disable (gray out) a Command Bar button; a button is either shown or hidden, rendered or not rendered.
  • Of course, it is possible to provide a flyout menu and a fixed set of commands on the Command Bar. This can all be done using the Ribbon Workbench. For this example, though, the Ribbon Workbench does not have the feature set to fully create the dynamically rendered commands. You can use the Ribbon Workbench to create the XML you need but you'll need a custom Web Resource to add the Button controls.

To create functionality similar to what's shown above, the first step is to create a Solution in CRM and add the entity to it that will show the custom buttons. Then create a JavaScript Web Resource to the solution and add the functions that your custom buttons will call. In the JavaScript Web Resource source code provided below, the functions created first include FtpCreate, FtpExtend and FtpRemove. Creating the Web Resource and functions allow you to provide these function references as Command nodes in Ribbon Workbench.

After creating the web resource, add it to the form(s) you want to customize and publish. You can now load Ribbon Workbench and load the solution you just created. (Note: Having just one entity in a solution will allow Ribbon Workbench to save, load and publish faster than with a solution that has lots of other artifacts in it.)

You will use Ribbon Workbench mainly to generate the XML for the flyout menu, the command buttons and the CommandDefinition nodes. It's beyond the scope of this article to provide step-by-step guidance on using the Ribbon Workbench but there are several examples available online. Essentially, though, the recommendation is to first get the static buttons working with the help of Ribbon Workbench. This is to make sure that your flyout menu appears and all of the buttons appear and are working (or at least are calling alert("hello world") or something).

Once you have a working flyout menu and command buttons, you will no longer need to use the Ribbon Workbench tool. From here, you will export the CRM solution that contains the entity that you're customizing (in this example, it's the Contact entity), make a backup of it, expand the zip file, and open the customizations.xml file in a text editor. Find the RibbonDiffXml node and compare it to the example shown below. You must set the PopulateDynamically property of the FlyoutAnchor to true and specify a command for the PopulateQueryCommand attribute. The PopulateQueryCommand attribute's value is the Id for a CommandDefinition. In the sample RibbonDiffXml below, the value is set to contoso.contact.FtpAutomation.Command.PopulateFlyOut, which, itself, has a JavaScriptFunction node that specifies the function in your Web Resource to call to dynamically populate the flyout menu.

Another step while editing the RibbonDiffXml node is to cut (remove) the Menu node in its entirety and paste that text to a different file. This is necessary because you will be dynamically rendering the Menu and child Button controls, so that part of the FlyoutAnchor definition needs to be removed. You should now have just the FlyoutAnchor in your RibbonDiffXml.

Lastly, in the RibbonDiffXml, create a CommandDefinition for the command that will be called as specified in the PopulateQueryCommand attribute. In the sample RibbonDiffXml below, the CommandDefinition manually added has the Id "contoso.contact.FtpAutomation.Command.PopulateFlyOut".

Once the RibbonDiffXml is ready, save the fie, add it back into the solution zip file and import the solution.

The last step is to finish the JavaScript code. See the sample below. The function "getFtpFlyOutMenu" is what provides the dynamically-rendered Button controls for the FlyoutAnchor. You'll see that Button controls are added based on field values on the form.

RibbonDiffXml example in customizations.xml (exported from a Solution)

<RibbonDiffXml>
  <CustomActions>
    <CustomAction Id="contoso.contact.FtpAutomation.FlyOut.CustomAction" Location="Mscrm.Form.contact.MainTab.Collaborate.Controls._children" Sequence="1020">
      <CommandUIDefinition>
        <FlyoutAnchor Alt="$LocLabels:contoso.contact.FtpAutomation.FlyOut.Alt" Id="contoso.contact.FtpAutomation.FlyOut" Command="Mscrm.Enabled"
                      LabelText="$LocLabels:contoso.contact.FtpAutomation.FlyOut.LabelText"
                      PopulateDynamically="true" PopulateQueryCommand="contoso.contact.FtpAutomation.Command.PopulateFlyOut"
                      Sequence="1020" TemplateAlias="o2" ToolTipTitle="$LocLabels:contoso.contact.FtpAutomation.FlyOut.ToolTipTitle"
                      ToolTipDescription="$LocLabels:contoso.contact.FtpAutomation.FlyOut.ToolTipDescription"></FlyoutAnchor>
      </CommandUIDefinition>
    </CustomAction>
  </CustomActions>
  <Templates>
    <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
  </Templates>
  <CommandDefinitions>
    <CommandDefinition Id="contoso.contact.FtpAutomation.Command.PopulateFlyOut">
      <EnableRules />
      <DisplayRules />
      <Actions>
        <JavaScriptFunction FunctionName="Contoso.FTP.GetFtpCommands" Library="$webresource:contoso_/Entities/Contact/ContactFormFtpAutomation.js">
          <CrmParameter Value="CommandProperties" />
        </JavaScriptFunction>
      </Actions>
    </CommandDefinition>
    <CommandDefinition Id="contoso.contact.FtpAutomation.Command.CreateFtpAccount">
      <EnableRules />
      <DisplayRules />
      <Actions>
        <JavaScriptFunction FunctionName="Contoso.FTP.FtpCreate" Library="$webresource:contoso_/Entities/Contact/ContactFormFtpAutomation.js" />
      </Actions>
    </CommandDefinition>
    <CommandDefinition Id="contoso.contact.FtpAutomation.Command.ExtendFtpAccountExpiration">
      <EnableRules />
      <DisplayRules />
      <Actions>
        <JavaScriptFunction FunctionName="Contoso.FTP.FtpExtend" Library="$webresource:contoso_/Entities/Contact/ContactFormFtpAutomation.js" />
      </Actions>
    </CommandDefinition>
    <CommandDefinition Id="contoso.contact.FtpAutomation.Command.RemoveFtpAccount">
      <EnableRules />
      <DisplayRules />
      <Actions>
        <JavaScriptFunction FunctionName="Contoso.FTP.FtpRemove" Library="$webresource:contoso_/Entities/Contact/ContactFormFtpAutomation.js" />
      </Actions>
    </CommandDefinition>
  </CommandDefinitions>
...


JavaScript Web Resource source code

(function () {
    if (typeof (Contoso) == "undefined")
        Contoso = {};
 
    Contoso.FTP =
    {
        FORM_TYPE_CREATE: 1,
        FORM_TYPE_UPDATE: 2,
 
        FTP_FIELD_ISACTIVE: "contoso_ftpaccountisactive",
        FTP_FIELD_STATUS: "contoso_ftpstatus",
 
        FTP_STATUS_NONE: 378630000,
        FTP_STATUS_CREATE_INITIATED: 378630001,
        FTP_STATUS_EXTEND_INITIATED: 378630005,
        FTP_STATUS_DELETE_INITIATED: 378630002,
        FTP_STATUS_ACTIVE: 378630003,
        FTP_STATUS_REMOVED: 378630004,
        FTP_STATUS_CREATE_FAILED: 378630006,
        FTP_STATUS_EXTEND_FAILED: 378630007,
        FTP_STATUS_DELETE_FAILED: 378630008,
 
        // Called from the Command Bar (ribbon)
        GetFtpCommands: function (CommandProperties) {
            var menuXml = me.getFtpFlyOutMenu();
            CommandProperties["PopulationXML"] = menuXml;
        },
 
        getFtpFlyOutMenu: function () {
            var menuXml = '<Menu Id="contoso.contact.FtpAutomation.FlyOut.Menu">' +
                      '<MenuSection Id="contoso.contact.FtpAutomation.MenuSection" Title="Select Command" Sequence="10" DisplayMode="Menu16">' +
                      '<Controls Id="contoso.contact.FtpAutomation.Section.Controls">';
 
            var createButtonXml = '<Button Alt="Create" Command="contoso.contact.FtpAutomation.Command.CreateFtpAccount" Description="Create an FTP account for the contact" Id="contoso.contact.FtpAutomation.Button.Create" LabelText="Create" Sequence="12" ToolTipTitle="Create" ToolTipDescription="Create an FTP account for the contact" />';
 
            var extendButtonXml = '<Button Alt="Extend Expiration" Command="contoso.contact.FtpAutomation.Command.ExtendFtpAccountExpiration" Description="Extend the expiration date for the FTP account" Id="contoso.contact.FtpAutomation.Button.ExtendExpiration" LabelText="Extend Expiration" Sequence="15" ToolTipTitle="Extend Expiration" ToolTipDescription="Extend the expiration date for the FTP account" />';
 
            var removeButtonXml = '<Button Alt="Remove" Command="contoso.contact.FtpAutomation.Command.RemoveFtpAccount" Description="Remove (delete) the FTP account for the contact" Id="contoso.contact.FtpAutomation.Button.Remove" LabelText="Remove" Sequence="25" ToolTipTitle="Remove" ToolTipDescription="Remove (delete) the FTP account for the contact" />';
 
            if (me.showCreateButton()) {
                menuXml += createButtonXml;
            }
 
            if (me.showExtendButton()) {
                menuXml += extendButtonXml;
            }
 
            if (me.showRemoveButton()) {
                menuXml += removeButtonXml;
            }
 
            menuXml += '</Controls></MenuSection></Menu>';
 
            return menuXml;
        },
 
        FtpCreate: function () {
            me.initiateFtpAction(me.FTP_STATUS_CREATE_INITIATED);
        },
 
        FtpExtend: function () {
            me.initiateFtpAction(me.FTP_STATUS_EXTEND_INITIATED);
        },
 
        FtpRemove: function () {
            Xrm.Utility.confirmDialog(
                "Remove the FTP account for this contact?\n\nClick OK to continue with the removal or click Cancel.",
                    function () { me.initiateFtpAction(me.FTP_STATUS_DELETE_INITIATED); },
                    function () { return; }
            );
        },
 
        initiateFtpAction: function (ftpStatusToSet) {
            Xrm.Page.getAttribute(me.FTP_FIELD_STATUS).setValue(ftpStatusToSet);
            Xrm.Page.getAttribute(me.FTP_FIELD_STATUS).setSubmitMode("always");
            Xrm.Page.data.save().then(
            function () {
                Xrm.Utility.alertDialog("The FTP create request has been initiated. Please refresh this record to see the results.");
            },
            function (error) {
                Xrm.Utility.alertDialog("There was a problem performing the last action. Please try again.");
            });
            Xrm.Page.getAttribute(me.FTP_FIELD_STATUS).setSubmitMode("never");
        },
 
        isFtpActive: function () {
            var active = Xrm.Page.getAttribute(me.FTP_FIELD_ISACTIVE).getValue();
            if (active == null) {
                active = false;
            }
            return active;
        },
 
        getFtpStatus: function () {
            var ftpStatus = Xrm.Page.getAttribute(me.FTP_FIELD_STATUS).getValue();
            if (ftpStatus == null) {
                ftpStatus = me.FTP_STATUS_NONE;
            }
            return ftpStatus;
        },
 
        showCreateButton: function () {
            var formType = Xrm.Page.ui.getFormType();
            var showButton = false;
            if (formType == me.FORM_TYPE_UPDATE) {
                if (!me.isFtpActive()) {
                    var ftpStatus = me.getFtpStatus();
                    if (ftpStatus == me.FTP_STATUS_NONE || ftpStatus == me.FTP_STATUS_REMOVED || ftpStatus == me.FTP_STATUS_CREATE_FAILED) {
                        showButton = true;
                    }
                }
            }
 
            return showButton;
        },
 
        showExtendButton: function () {
            var formType = Xrm.Page.ui.getFormType();
            var showButton = false;
            if (formType == me.FORM_TYPE_UPDATE) {
                if (me.isFtpActive()) {
                    var ftpStatus = me.getFtpStatus();
                    if (ftpStatus == me.FTP_STATUS_ACTIVE || ftpStatus == me.FTP_STATUS_EXTEND_FAILED || ftpStatus == me.FTP_STATUS_DELETE_FAILED
                        || ftpStatus == me.FTP_STATUS_EXTEND_INITIATED || ftpStatus == me.FTP_STATUS_DELETE_INITIATED) {
                        showButton = true;
                    }
                }
            }
 
            return showButton;
        },
 
        showRemoveButton: function () {
            var formType = Xrm.Page.ui.getFormType();
            var showButton = false;
            if (formType == me.FORM_TYPE_UPDATE) {
                if (me.isFtpActive()) {
                    var ftpStatus = me.getFtpStatus();
                    if (ftpStatus == me.FTP_STATUS_ACTIVE || ftpStatus == me.FTP_STATUS_EXTEND_FAILED || ftpStatus == me.FTP_STATUS_DELETE_FAILED                        || ftpStatus == me.FTP_STATUS_DELETE_INITIATED) {
                        showButton = true;
                    }
                }
            }
 
            return showButton;
        },
    };
 
    var me = Contoso.FTP;
 
}).call(this);
 


.