Tagged: custom ribbon button

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: