Using async call to enable a custom ribbon button

1 Feb

It is not always appropriate to enable custom buttons on the ribbon all the time. Sometimes one or multiple conditions have to be met to enable the button. As showed in one of my previous posts ‘A better user experience with the dialog framework and notifications‘ the attribute EnabledScript of the CommandUIHandler can be used to test when to enable or disable the button. The check in the example used in the previous post is quite simple and checks if one or more items are selected in the list.     

To perform other, less simplistic, checks, it is likely you have to use asynchronous calls when working with ECMA script. Working with async calls in the ribbon is a bit different than working with synchronous calls and this is what this post is about.     

Functionality

Suppose you want to display a button on the ribbon on a default Tasks list. The Tasks list contains a column named Status. The button will only be enabled when exactly one item is selected in the list and when this selected item has a Status, a column/field value, not equal to Completed.     

The example will focus on the asynchronous part of the solution, not on creating a button on the ribbon.     

Solution

First of all a function is called from the EnabledScript attribute of CommandUIHandler.

<CommandUIHandler Command="TaskCompletedCommand"
            CommandAction="javascript:alert('Not implemented.');"
            EnabledScript="javascript:enableTaskNotCompletedButton();">
        </CommandUIHandler>

In this function a check is performed to make sure exactly one item is selected in the list.
After this check we have to get the Status field value of the selected item. Based on this value we enable or disable the button on the ribbon. When the Status field value is equal to Completed the button has to be disabled otherwise enabled. The return value of the function has to be false to disable a button, true to enable.

The enableTaskNotCompletedButton function looks like this at the moment(will be adjusted during the post!):

function enableTaskNotCompletedButton() {
    var result = false;//default return value of the function which disables the button
    var selectedItems = SP.ListOperation.Selection.getSelectedItems();
    //check if exactly one item is selected
    if (CountDictionary(selectedItems) == 1) {
        if (this.itemId != selectedItems[0]['id']) {
            this.itemId = selectedItems[0]['id'];
            GetTaskStatus(this.itemId);
        }
        else if (this.StatusValue == "Completed") {
            result = false; //can be omitted, just for readibility
        }
    }
    return result;
}

To get the value of the Status field of the selected item an asynchronous call has to be made by the Client Object model. This call loads the value of the Status field in a variable so a check on this value can be performed.
Since this is an async call the ribbon doesn’t know when this function completes and is not going to wait for it to complete. Therefor you have to tell the ribbon explicitly that the async function is completed and that is has to come back to see if the button has to be enabled or disabled.

Here the RefreshCommandUI method comes in which can be found in the Core.js. This function causes the Ribbon to refresh and EnableScript functions get called on the ribbon buttons.
By calling this method the function in the EnabledScript attribute is called again.

The following code is used for this example.

function GetTaskStatus(itemId) {
    var clientContext = SP.ClientContext.get_current();
    var currentList = clientContext.get_web().get_lists().getById(SP.ListOperation.Selection.getSelectedList());
    this.singleItem = currentList.getItemById(itemId);
 

    clientContext.load(this.singleItem, 'Status');
    clientContext.executeQueryAsync(Function.createDelegate(this, this.OnSucceeded), Function.createDelegate(this, this.OnFailed));
}
 

function OnSucceeded() {
    this.StatusValue = this.singleItem.get_item('Status');
    RefreshCommandUI();
}
 

function OnFailed(sender, args) {
    alert('Error occurred: ' + args.get_message());
}

    

When programming the functions needed you have to keep in mind the function defined in EnabledScript fires twice.
The first time it is the ‘initial time’, the second time is when calling the RefreshCommandUI method.     

By keeping this in mind you have to keep track of a few things in the code, which can be used when it comes back on the second fire of the function.     

The first item to keep track of is the item id. When selecting an item, deselecting and selecting the same item again, you don’t want the code to check the Status field value again, because you know that value already.
The most important value you want to keep track of is the Status field value of the selected item. The first time the function fires and the async call is made the Status field value is stored. Then the RefreshCommandUI is called
and fires the function again. This is the time you want to know the previous set Status field value, because this is the time to return true or false to enable or disable the button.     

With this knowledge the definite enableTaskNotCompletedButton function:

function enableTaskNotCompletedButton() {
    var result = false;//default return value of the function which disables the button
 

    var selectedItems = SP.ListOperation.Selection.getSelectedItems();
    //check if exactly one item is selected
    if (CountDictionary(selectedItems) == 1) {
        if (this.itemId != selectedItems[0]['id']) {
            this.itemId = selectedItems[0]['id'];
            GetTaskStatus(this.itemId);
        }
        else if (this.StatusValue == "Completed") {
            result = false; //can be omitted, just for readibility
        }
        //when RefreshCommandUI() makes the EnabledScript fire again, none of the above checks are valid
        //when the Status is not equal to Completed and the stored itemId is the same as the selected item id
        else if (this.StatusValue != "Completed" && this.itemId == selectedItems[0]['id']) {
            result = true;
        }
    }
 

    return result;
}

Conclusion

When using async calls to check if a button has to be enabled in the ribbon you have to tell the ribbon when the async method is finished to check if the status of the button has to be updated.
The RefreshCommandUI method helps you out with asynchronous calls. Besides this method a variable has to be set to keep track of the status(enable or disable) of the button.

12 Replies to “Using async call to enable a custom ribbon button

  1. Thanks for the great post — it helped me quite a bit. However I am hoping you have some suggestions that will enable me to take it one step further.

    I have a custom list, and only want to enable the custom action button when the current user is not the person who created the list item. I am able to retrieve the ‘Author’ (‘Created By’) field, which comes back as a generic Object. I am not sure the Type of the under lying object (FieldUser, FieldUserValue, or something else?). I would like to get either the Author’s name of LoginId. Can you suggest how I might accomplish this?

    Thanks

  2. Hi,

    The Author field actually is a Person field, which is a lookup field.
    If you approach the field with this in mind the code to get the name is:

    this.singleItem.get_item('Author').get_lookupValue();
    

    Regards, Anita

  3. Hi Anita,
    Thanks for post. Can we make another asynchronous call at the end of the first one.
    I’m getting an exception like “Cannot Complete this Action, Please try later”.
    Is this Possible ?

    Thanks in Advance

  4. Hi Anita, thank you for sharing this, it has helped me a great deal. I am also trying a second query (on a different list) at about the same time as the query on the current list, and I get the same exception as Raja, and I cannot seem to find the cause.

    Maybe we can come to a solution together? If it’s ok I’ll send you an email with the code.

    Thanks

  5. Hi Anita. It works for one button, but doesn’t work for the next button (when I add another button with different condition).
    Thanks

  6. Hi Dan,

    Thanks for pointing this out, I only tested it with one button. What solution did you use with multiple buttons?

    Regards, Anita

  7. Anita, I add 2 ribbon button. All with the same code as you did. The first one, is the same as you did, the second one is just different in function names and field (not ‘Status’). Nothing special.
    But only the first one is work, the second one doesn’t work (always in disable state).
    Thanks

  8. Hello! Good example!

    I am searching how to get the id of the selected webpart when i click in my ribbon button, do you know a similar method for the selected webparts in edit mode?

    Thanks!

  9. Thanks for the sharing.

    I guess that this post is a bit old but I have an issue with your code.

    this.StatusValue is undefined in the “main” function…

    Maybe I don’t understand it very well but I would appreciate your help if you can

    Regards, Jordan

Comments are closed.