Dataverse Use Async Promise in ribbon enable rule
When creating a button/command we can use custom enable rule to control the visibility of the button.
Normally, enable rule is a function should return true or false to make the button show or hide. For example:
function EnableRule()
{
const value = Xrm.Page.getAttribute("column1").getValue();
return value === "Active";
}
Custom rules that do not return a value quickly can affect the performance of the ribbon. The command bar is only visible until all custom enable rules are evaluated (return true/false).
In case of we have some buttons and each button has complex logic to determine a button is show or hide, then the command bar is blocked, user can not see and click
Let see an example
We have two buttons "Async button 1" and "Aync button 2". Each button need to retrieve record before evaluating the enable rule.
// Sync mode
function asyncButtonEnableRule(executeContext) {
debugger;
var globalContext = Xrm.Utility.getGlobalContext();
var clientUrl = globalContext.getClientUrl();
console.log(new Date, 'Retrieving account');
var request = new XMLHttpRequest();
request.open('GET', encodeURI(clientUrl + '/api/data/v9.1/accounts(93dc4816-535d-eb11-a812-0022481631b1)'), false); // Sync mode
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
request.send();
var account = JSON.parse(request.responseText);
console.log(new Date,'Retrieved account');
return request.status === 200 && account['name'] != 'XXX';
}
function asyncButtonEnableRule2(executeContext) {
debugger;
return asyncButtonEnableRule(executeContext);
}
In this case, the command bar is hidden until 2 complex rules are evaluated one by one
In the console, we can see the enable rules were executed one by one.
This is not a good idea when we have several buttons like this since the command bar will be hidden until all rules are evaluated
Solution:
We should return a Promise rather than boolean for asynchronous rule evaluation. The promise must resolve true or false to show or hide button
The new enable rule will be like this
// Async mode
function asyncButtonEnableRule(executeContext) {
debugger;
var globalContext = Xrm.Utility.getGlobalContext();
var clientUrl = globalContext.getClientUrl();
console.log(new Date, 'Retrieving account');
var request = new XMLHttpRequest();
request.open('GET', encodeURI(clientUrl + '/api/data/v9.1/accounts(93dc4816-535d-eb11-a812-0022481631b1)'), true); // Async mode
request.setRequestHeader("Accept", "application/json");
request.setRequestHeader("Content-Type", "application/json; charset=utf-8");
request.setRequestHeader("OData-MaxVersion", "4.0");
request.setRequestHeader("OData-Version", "4.0");
return new Promise(function (resolve, reject) {
request.onload = function (e) {
if (request.readyState === 4) {
console.log(new Date, 'Retrieved account');
if (request.status === 200) {
var account = JSON.parse(request.responseText);
resolve(account['name'] != 'XXX');
} else {
reject(request.statusText);
}
}
};
request.onerror = function (e) {
reject(request.statusText);
};
request.send();
});
}
function asyncButtonEnableRule2(executeContext) {
debugger;
return asyncButtonEnableRule(executeContext);
}
In this new version (async), 2 enable rules in 2 buttons are evaluated at the same time.
Alternatively, we can use Xrm.WebApi when CRUD CRM record
Then the new code will be like this:
function asyncButtonEnableRule(executeContext) {
debugger;
console.log(new Date, 'Retrieving account');
return new Promise(function (resolve, reject) {
Xrm.WebApi.retrieveRecord('account', '93dc4816-535d-eb11-a812-0022481631b1').then(
function (accountInfo) {
console.log(new Date, 'Retrieved account');
resolve(accountInfo['name'] != "XXX");
},
function (error) {
reject(error.message);
}
);
});
}
function asyncButtonEnableRule2(executeContext) {
debugger;
console.log(new Date, 'Retrieving account in asyncButtonEnableRule2');
return asyncButtonEnableRule(executeContext);
}
Conclusion
- Button enable rule should return a Promise if possible, especially when retrieving data from server
- Promises-based rules will only work on Unified Interface, so they cannot be used if classic Web Client is still being used
- If the promise does not resolve within 10 seconds, the rule will resolve with a false value.
- When we reject in Promise, the buttons are still visible. In this case, you can consider resolve(false) instead of "reject"
Dataverse Use Async Promise in ribbon enable rule
Comments
thanks!
this was a big help~ calling a custom api from a ribbon, so using a Promise allowed me to ensure completion!