Tagged: custom actions

Enabling custom ribbon buttons dynamically based on multiple selected item values using RefreshCommandUI and JSOM

Updated 12/23/2014: Thanks to Chris Bell for pointing out some errors in the JavaScript in this post. The script and sample code below have been updated to reflect these corrections.

In this post, I will show you how to use the built-in RefreshCommandUI() method to update the enabled status of a custom ribbon button based on field values in multiple selected list items. In my searching, I have only found examples that changed the enabled status of a custom ribbon button based on a single selected item (I even blogged a very simple example of this here).

For an exhaustive treatment of how to add a custom button to the server ribbon, check out Walkthrough: Adding a Button to the Server Ribbon on MSDN. Our focus in this post is on the CommandUIHandler element of the XML necessary to declare this button, which contains an EnabledScript attribute that contains JavaScript that must return true or false–true if the button should be enabled, false to disable it.

In this example, we set our EnabledScript to “javascript:EnableFinishedButton();” which enables us to declare this function in an external JavaScript file (that we can load via a ScriptLink Custom Action). As an added bonus, this makes our script MUCH easier to locate and debug using our browser’s built-in JavaScript debugging tools! In this example, our EnableFinishedButton function will return true to enable our “Mark As Finished” ribbon button ONLY when ALL selected items in the list (one or more) have a value of “In Progress” in the “Status” field. This example should be easy to adapt to suit your needs.

For completeness, here is the XML necessary to declare the button and associated CommandUIHandler, as well as load the external JavaScript file containing the custom functions we need. While custom ribbon buttons can be deployed in SharePoint-hosted apps, I have packaged this as a farm solution so that I may specify JavaScript functions defined in an external file (loaded via a ScriptLink custom action) for the CommandAction and EnabledScript attributes. As an aside, I strongly encourage you to read Sean McDonough’s post on Custom Ribbon Button Image Limitations with SharePoint 2013 Apps to learn about some interesting (and frustrating!) limitations that forced me to go the farm solution route.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="DannyJessee.TestCustomAction"
                Location="CommandUI.Ribbon"
                RegistrationId="100"
                RegistrationType="List">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.ListItem.Actions.Controls._children">
          <Button Id="DannyJessee.MarkItemsFinishedButton"
                  Command="cmdMarkItemsFinished"
                  Image16by16="/_layouts/images/kpinormallarge-0.gif"
                  Image32by32="/_layouts/images/kpinormallarge-0.gif"
                  LabelText="Mark as Finished"
                  TemplateAlias="o2" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler Command="cmdMarkItemsFinished"
                          CommandAction="javascript:MarkItemsFinished();"
                          EnabledScript="javascript:EnableFinishedButton();">
        </CommandUIHandler>
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
  <CustomAction Id="DannyJessee.TestScript"
                Location="ScriptLink"
                ScriptSrc="/_layouts/15/CustomRibbonButton/CustomAction.js" />
</Elements>

Here are the contents of CustomAction.js, which I am provisioning to the LAYOUTS folder:

function EnableFinishedButton() {
    var context = SP.ClientContext.get_current();
    var list;
    var selectedItems = SP.ListOperation.Selection.getSelectedItems(context);
    var totalSelectedItems = CountDictionary(selectedItems);

    if (totalSelectedItems > 0) {
        var web = context.get_web();
        context.load(web);
        var listId = SP.ListOperation.Selection.getSelectedList();
        list = web.get_lists().getById(listId);

        // We will use this variable to determine whether EnableFinishedButton() is being called directly or by RefreshCommandUI()
        var hadToMakeCall = false;

        if (typeof this.itemRows == "undefined" || this.itemRows.length != totalSelectedItems) {
            // This will be true if this is the first time an item has been selected in the list OR if the selected items have changed, forcing the need to check again
            hadToMakeCall = true;
            GetItemsStatus();
        }

        // If we just issued the async call, do not enable the button yet
        if (hadToMakeCall) {
            return false;
        }
        else {
            // Once the call has returned, set the enabled status based on the returned value
            return this._can_be_enabled;
        }
    }
    else {
        this.itemRows = undefined;
        return false;
    }

    function GetItemsStatus() {
        // Store the selected list items in an array where their values can be checked
        itemRows = [];

        for (i in selectedItems) {
            itemRows[i] = list.getItemById(selectedItems[i].id);
            context.load(itemRows[i]);
        }

        context.executeQueryAsync(Function.createDelegate(this, onGetItemsSuccess), Function.createDelegate(this, onGetItemsQueryFailed));
    }

    function onGetItemsSuccess() {
        this._can_be_enabled = true;

        // Iterate through all selected items. If one is false, the value of _can_be_enabled will be false and the button will not be enabled
        for (i in itemRows) {
            this._can_be_enabled = this._can_be_enabled && itemRows[i].get_item("Status") == "In Progress";
        }

        // Now we can call the EnabledScript function again
        RefreshCommandUI();
    }

    function onGetItemsQueryFailed(sender, args) {
        alert(args.get_message());
    }
}

function MarkItemsFinished() {
    var context = SP.ClientContext.get_current();
    var web = context.get_web();
    var selectedItems = SP.ListOperation.Selection.getSelectedItems(context);
    var listId = SP.ListOperation.Selection.getSelectedList();
    var list = web.get_lists().getById(listId);
    var i;
    for (i in selectedItems) {
        // Update the "Status" field of each selected item to have a value of "Finished"
        var listItem = list.getItemById(selectedItems[i].id);
        listItem.set_item("Status", "Finished");
        listItem.update();
    }
    context.executeQueryAsync(Function.createDelegate(this, onUpdateItemsSuccess), Function.createDelegate(this, onUpdateItemsFailed));

    function onUpdateItemsSuccess() {
        alert("Items updated!");
    }

    function onUpdateItemsFailed() {
        alert(args.get_message());
    }
}

Once deployed, we navigate to our custom list. As long as the items we select have a value of “In Progress” in the “Status” field, the custom button remains enabled:

markasfinished1

As soon as we select an item that does not have a value of “In Progress” in the “Status” field, the button is no longer enabled:

markasfinished2

Download the sample code for this project:

JavaScript to Conditionally Enable a SharePoint Custom Ribbon Button

During my session on getting started with apps for SharePoint 2013, I demonstrate how to use the “extension” app shape to define a UI custom action that displays a button in the ribbon. When clicked, this button allows the user to view a Bing traffic map centered at the location specified in the selected list item. This is done using a CustomAction in the same declarative manner as it was done in SharePoint 2010, using the following XML:

customaction1

Installing the app adds a button labeled “Plot on Bing Traffic Map” to the “Items” tab on all custom lists in the host web where the app is installed:

button0

In my CommandAction of the CommandUIHandler for the CommandUIExtension I define to create the button, I specify that clicking the button will take the user to the following URL:

~appWebUrl/Pages/BingTrafficMap.html?{StandardTokens}&ListID={ListId}&ItemID={SelectedItemId}

Note the use of the SelectedItemId token in the URL above. This means my BingTrafficMap.html page can rightly expect to have an ItemID parameter in its query string, which can then be parsed to get the list item and read its properties using JavaScript.

But what if the user hasn’t selected any items? Or multiple items? Since I can only center a map on one single location, I need to have more control over when this button is enabled. In this case, I only want the button to be enabled when exactly one item in the list is selected. (By default, any ribbon buttons you create through custom actions will always be enabled, regardless of whether or not any items are selected.)

As it turns out, there is an EnabledScript attribute of the CommandUIHandler that will enable or disable the button depending on whether the function returns true or false. I was able to adapt this code from Tomasz Rabiński to add the following logic to my CommandUIHandler:

customaction2

Note that the function EnableDisable–which I can define and then call within the EnabledScript attribute–returns true only if the number of selected items equals 1.

Now my button behaves exactly as I would expect.

No items selected (disabled):

button1

One item selected (enabled):

button2

More than one item selected (disabled):

button3

I have updated my demo code to include this change. You can download this code here.