Sample code: Asynchronously checking if a Ribbon command is available


While extending SharePoint 2010 Ribbon with new functionality one of the common things you do is to check whether a command should be enabled or not. Since the checking function expects you to return a value it can get tricky if your custom checking operation is asynchronous. Find out how to asynchronously check if a Ribbon command is available.

Last year fellow SharePoint MVP – Andrew Connell posted a great tip about how you can asynchronously check if a command should be available or not. According to his approach you should return a safe default first (which in most cases is false), perform your asynchronous check and then refresh the UI with your real value.

While working on one of our SharePoint 2010 WCM products recently I had to apply this concept twice: once for checking if a button should be enabled and the other time for checking if a toggle button should be on or off. Below you will find sample code from the Page Component for both scenarios.

For the purpose of this article I have created the following custom Ribbon command:

<CustomAction Id="Mavention.SharePoint.Labs.MyCustomAction"
              Location="CommandUI.Ribbon"
              Sequence="10">
  <CommandUIExtension>
    <CommandUIDefinitions>
      <CommandUIDefinition Location="Ribbon.WikiPageTab.PubPageActions.Controls._children">
        <ToggleButton 
          Id="Ribbon.WikiPageTab.PubPageActions.MaventionMyCustomAction"
          Alt="My Custom Action" 
          Command="Mavention.SharePoint.Labs.MyCustomAction"
          QueryCommand="Mavention.SharePoint.Labs.MyCustomActionQuery"
          LabelText="My Custom Action" 
          Sequence="40" 
          TemplateAlias="o2" 
          Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-112" Image16by16Left="-16" 
          ToolTipTitle="My Custom Action" 
          ToolTipDescription="My Custom Action."/>
      </CommandUIDefinition>
    </CommandUIDefinitions>
  </CommandUIExtension>
</CustomAction>

Asynchronously checking if a button should be enabled

The code snippet below presents you with a solution for asynchronously checking if a command should be available or not. The check is based on the value of a property in the Property Bag of the current Web.

Type.registerNamespace('Mavention.SharePoint.Labs.MyCustomAction.PageComponent');

Mavention.SharePoint.Labs.MyCustomAction.PageComponent = function () {
    Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initializeBase(this);
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initialize = function () {
    SP.SOD.executeOrDelayUntilScriptLoaded(Function.createDelegate(null, Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initializePageComponent), 'SP.Ribbon.js');
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initializePageComponent = function () {
    var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
    if (null !== ribbonPageManager) {
        ribbonPageManager.addPageComponent(Mavention.SharePoint.Labs.MyCustomAction.PageComponent.instance);
    }
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.prototype = {
    init: function () {
        this.enabledStatusChecked = false;
        this.isEnabled = false;
    },
    getFocusedCommands: function () {
        return [];
    },
    getGlobalCommands: function () {
        return ['Mavention.SharePoint.Labs.MyCustomAction'];
    },
    canHandleCommand: function (commandId) {
        if (commandId === 'Mavention.SharePoint.Labs.MyCustomAction') {
            if (!this.enabledStatusChecked) {
                this.checkIsEnabled();
            }

            return this.isEnabled;
        }
        else {
            return false;
        }
    },
    handleCommand: function (commandId, properties, sequence) {
        // handle button commands
    },
    isFocusable: function () {
        return true;
    },
    receiveFocus: function () {
        return true;
    },
    yieldFocus: function () {
        return true;
    },
    checkIsEnabled: function () {
        this.enabledStatusChecked = true;

        var context = SP.ClientContext.get_current();
        var properties = context.get_web().get_allProperties();
        context.load(properties);
        context.executeQueryAsync(Function.createDelegate(this, function() {
            this.isEnabled = properties.get_item('MaventionMyProperty') == 1;
            RefreshCommandUI();
        }), Function.createDelegate(this, function() {
            // handle error
        }));
    }
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.registerClass('Mavention.SharePoint.Labs.MyCustomAction.PageComponent', CUI.Page.PageComponent);
Mavention.SharePoint.Labs.MyCustomAction.PageComponent.instance = new Mavention.SharePoint.Labs.MyCustomAction.PageComponent();

SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs('mavention.sharepoint.labs.mycustomaction.pagecomponent.js');

The biggest part of this code is just the standard framework for creating custom Page Components – nothing special here. The code that really matters is located within the canHandleCommand handler (line 29) and in the checkIsEnabled function (line 53).

As you can see the first thing we do, is to return a safe default which is false in our scenario. Just before we return the value we call the function that will asynchronously check if the button should be enabled or not. To prevent getting into an infinite loop (the canHandleCommand function will be called after refreshing the UI again) we call the checkIsEnabled function only if it hasn’t been called before. We follow the track of our calls using the enabledStatusChecked variable.

Within our checker function we first retrieve the properties of the current Web. Once the properties have been loaded we can check if our property is set and depending on the value we set the isEnabled flag to true or false. Once our check is done we can call the RefreshCommandUI function to refresh the UI. After that the canHandleCommand handler will be called again. Since our check has already run, the enabledStatusChecked variable is set to true and the check will not be executed again.

If the MaventionMyProperty property hasn’t been set or if its value is different than 1 the command will remain disabled.

‘My Custom Command’ button disabled on the Ribbon

If you however set the MaventionMyProperty property to 1 and refresh the page, the command will become available.

‘My Custom Command’ button enabled on the Ribbon

Asynchronously checking if a toggle button should be on or off

When working with toggle buttons you might find yourself in a scenario when you might need to check the status of the button based on some data stored in SharePoint. As you know retrieving data using the ECMA Script OM is asynchronous so you cannot directly set the status of a toggle button. Instead you should use an approach similar to checking if the command is available. First you should set a safe default (on or off depending on what the button does) and once you have run the check set its state to the right value.

The following code snippet contains a sample code that asynchronously checks the state of a toggle button. The status is being set using once again a value of a property from the Property Bag of the current Web.

Type.registerNamespace('Mavention.SharePoint.Labs.MyCustomAction.PageComponent');

Mavention.SharePoint.Labs.MyCustomAction.PageComponent = function () {
    Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initializeBase(this);
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initialize = function () {
    SP.SOD.executeOrDelayUntilScriptLoaded(Function.createDelegate(null, Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initializePageComponent), 'SP.Ribbon.js');
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.initializePageComponent = function () {
    var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
    if (null !== ribbonPageManager) {
        ribbonPageManager.addPageComponent(Mavention.SharePoint.Labs.MyCustomAction.PageComponent.instance);
    }
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.prototype = {
    init: function () {
        this.toggleStatusChecked = false;
        this.isOn = false;
    },
    getFocusedCommands: function () {
        return [];
    },
    getGlobalCommands: function () {
        return ['Mavention.SharePoint.Labs.MyCustomAction', 'Mavention.SharePoint.Labs.MyCustomActionQuery'];
    },
    canHandleCommand: function (commandId) {
        if (commandId === 'Mavention.SharePoint.Labs.MyCustomAction' ||
            commandId === 'Mavention.SharePoint.Labs.MyCustomActionQuery') {
            return true;
        }
        else {
            return false;
        }
    },
    handleCommand: function (commandId, properties, sequence) {
        if (commandId === 'Mavention.SharePoint.Labs.MyCustomActionQuery') {
            if (!this.toggleStatusChecked) {
                this.checkIsOn();
            }

            properties.On = this.isOn;
        }
    },
    isFocusable: function () {
        return true;
    },
    receiveFocus: function () {
        return true;
    },
    yieldFocus: function () {
        return true;
    },
    checkIsOn: function () {
        this.toggleStatusChecked = true;

        var context = SP.ClientContext.get_current();
        var properties = context.get_web().get_allProperties();
        context.load(properties);
        context.executeQueryAsync(Function.createDelegate(this, function () {
            this.isOn = properties.get_item('MaventionMyProperty') == 1;
            RefreshCommandUI();
        }), Function.createDelegate(this, function () {
            // handle error
        }));
    }
}

Mavention.SharePoint.Labs.MyCustomAction.PageComponent.registerClass('Mavention.SharePoint.Labs.MyCustomAction.PageComponent', CUI.Page.PageComponent);
Mavention.SharePoint.Labs.MyCustomAction.PageComponent.instance = new Mavention.SharePoint.Labs.MyCustomAction.PageComponent();

SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs('mavention.sharepoint.labs.mycustomaction.pagecomponent.js');

Once again the biggest part of the script is responsible for registering the custom Page Component. The code that really matters is located in the handleCommand handler (line 38) and in the checkIsOn function (line 56).

To be able to check the status of a toggle button you have to register a Query Command with the Toggle Button (see the sample Toggle Button that I’ve created for this scenario). The next step is to handle the query command in the handleCommand handler. Comparable to the previous scenario, we first need to check if the check has already been run to prevent falling into an infinite loop. If the check hasn’t been run, we call the checkIsOn function to check the status of the toggle button. In the meanwhile we set the status to a safe default value until we have real data available to really determine the status of the button.

As is the previous scenario, if the MaventionMyProperty property hasn’t been set or if its value is different than 1, the toggle button will be off:

‘My Custom Command’ toggle button set to off

If you however set the MaventionMyProperty property to 1 and refresh the page, the toggle button will be on:

‘My Custom Command’ toggle button active

And this is all you have to know to asynchronously check if your command is available and what its status is. Although the code isn’t that complex once you understand it, it consists of a few pieces you need to have right, to get things done which might be challenging if you’re not that experience with extending Ribbon and using SharePoint 2010 ECMA Script OM.

Download my sample project (10KB, ZIP)

Technorati Tags: SharePoint 2010,Ribbon

Others found also helpful: