Tag Archives: 2010

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:

Which SharePoint Front End Server Am I Hitting?

If you have ever worked in a network load balanced (NLB) environment with multiple SharePoint front end servers, you have no doubt had at least one occasion where only some of your users were seeing some sort of strange behavior. Perhaps some users receive a generic error message when accessing a page and others do not. Whatever the reason, it never hurts to be able to have users give you just a little bit more information to help you in the troubleshooting process.

Of course, you can always dig through the ULS log files on each front end server to look for any anomalies or to match up correlation ID values associated with generic error messages. But wouldn’t it be great if your end user could tell you that he or she is hitting front end #3 so you don’t have to waste time searching through the logs on front ends #1 and #2 first?

One potential solution to this problem (and there are no doubt more elegant ways to solve it) are to create multiple images such as the ones below:

Save each image with exactly the same name (such as frontend.jpg) and place it in the TEMPLATE\IMAGES directory of the SharePoint root on each front end (make sure you match up the correct image with each front end server!) Then, when something goes wrong for one user but not another, you can ask him/her to visit the following URL:

http://yourcompany.com/_layouts/images/frontend.jpg

Based on the number that appears, you will know exactly which front end that user is hitting!

I hope this information is useful to you. You could extend this by creating a custom URL action called “Front End Check” that allows certain users to hit the front end image link directly without having to remember it. Feel free to leave your thoughts and suggestions in the comments!