Export (0) Print
Expand All
Information
The topic you requested is included in another documentation set. For convenience, it's displayed below. Choose Switch to see the topic in its original location.

Asynchronous Ribbon Enable Rules and Executing Workflows from the Ribbon with Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online

Applies To: Microsoft Dynamics CRM 2011, Microsoft Dynamics CRM Online

Jim Daly
Microsoft Corporation

September, 2012

Summary

This article describes a scenario where a ribbon control executes a workflow process. The ribbon control uses asynchronous logic to determine when the control is enabled.

The download package RibbonAsynchronousEnableRulesandExecutingWorkflows.zip includes a Visual Studio 2010 solution and a Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online managed solution to illustrate the concepts presented in this article.

Applies To

Introduction

In Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online, you can configure a control in the ribbon to execute a workflow process using JavaScript and the SOAP endpoint for web resources. To do this, create a <CommandDefinition> element that specifies a JavaScript function. When you configure a control in the ribbon to execute a workflow you should only enable the control when it is valid to execute the workflow. Use the <EnableRule> element to set whether the control is enabled. Whether a control should be enabled to execute a workflow often requires checking the status of records in the system using a web service call, typically using the REST endpoint for web resources.

Ribbon enable rules should never be configured to use long-running processes. Because JavaScript runs in a single thread in the browser, any long-running process could cause the browser to stop responding. The recommended procedure is to use an asynchronous strategy where the enable rule can return a definite response immediately to disable the control while also initiating an asynchronous process that can enable the control after data is retrieved from the web service.

Table of Contents

Requirements

This sample uses a custom entity named SDK Sample Item that supports activities. This entity has a custom ribbon tab named Item Completion, which is only displayed after the record is saved. This tab has a group that contains two buttons: Create Task and Complete Task. By default, only the Create Task button is enabled as shown in the following screenshot.

Showing the Item Completion ribbon tab in the form

The goal of this sample is to create a single task for each SDK Sample Item record by using the Create Task button. After this task is created, the Create Task button becomes disabled and the Complete Task button is enabled. When the Complete Task button is pressed, a workflow is executed that includes a Change Status step that sets the task Status to Completed. After the Complete Task button is pressed it is disabled so that the workflow can only be executed one time.

Components

This section describes the components for this scenario.

Custom Entity

The SDK Sample Item custom entity is configured to have the properties in the following table.

 

Name Value

Display Name

SDK Sample Item

Plural Name

SDK Sample Items

Name (Logical Name)

sample_sdksampleitem

Description

A custom entity created for the Ribbon: Asynchronous Enable Rules and Executing Workflows SDK Sample

Areas that display this entity

No areas are selected.

Options for Entity

Only Activities are selected.

Workflow

The SDK Sample Disable Task workflow is configured to have the properties in the following table.

 

Name Value Notes

Process Name

SDK Sample Disable Task

Activate As

Process

Available to Run

As an on-demand process

This allows for execution of the workflow using the ExecuteWorkflowRequest Class and the Execute method.

Entity

Task

This workflow deactivates task records.

Category

Workflow

Scope

Organization

Start when

Do not select an event.

Web Resources

This sample uses the following web resources.

sample_/Scripts/SDK.ItemCompletion.js

This JavaScript library contains the functions that are used by ribbon actions and enable rules. The following table summarizes the functions defined in the SDK.ItemCompletion namespace.

 

Function Description

SDK.ItemCompletion.errorHandler

Alerts the user of an error. This function is used as a callback function for web service calls.

SDK.ItemCompletion.getTaskName

Returns the name of the task. The task name will be “Complete “+< the name of the SDK Sample Item record>.

This function also specifies the name if it hasn't been set.

SDK.ItemCompletion.canCreateTask

Returns a SDK.ItemCompletion.isTaskCreateable Boolean object that specifies whether the task can be created or not. This object is used in a custom ribbon enable rule.

This function also refreshes the ribbon.

SDK.ItemCompletion.canCompleteTask

Returns a SDK.ItemCompletion.isTaskCompleteable Boolean object that specifies whether an open task exists that the workflow can be executed on. This object is used in a custom ribbon enable rule.

This function also refreshes the ribbon.

SDK.ItemCompletion.createTask

Creates a task in Microsoft Dynamics CRM using the REST endpoint. It sets SDK.ItemCompletion.isTaskCreateable equal to false and refreshes the ribbon to disable the Create Task button.

This function uses SDK.ItemCompletion.getTaskName to specify the name of the task and sets the description field to “Do what’s necessary”.

SDK.ItemCompletion.completeTask

Calls SDK.ItemCompletion.executeCompleteWorkflow to execute the workflow on any task records associated with the SDK Sample Item record.

It sets SDK.ItemCompletion.isTaskCompleteable equal to false and refreshes the ribbon to disable the Complete Task button.

SDK.ItemCompletion.retrieveOpenTasks

Retrieves tasks that are open, are related to the item, and have the specified subject value that was set using the pattern used by SDK.ItemCompletion.getTaskName.

SDK.ItemCompletion.executeCompleteWorkflow

Defines an ExecuteWorkflowRequest message to call the SDK Disable Task workflow and uses the SDK.ItemCompletion.executeSOAPRequest function by using the SOAP endpoint for web resources to execute a workflow that completes a task. This function is used by the SDK.ItemCompletion.completeTask function and has a hard-coded reference to a specific workflow ID value.

SDK.ItemCompletion.executeSOAPRequest

Executes messages using SOAP. This function is used by the SDK.ItemCompletion.executeCompleteWorkflow function.

SDK.ItemCompletion.xmlEncode

A utility function that is used to encode parameters passed within the SOAP XML when using the SOAP endpoint for web resources.

These methods are implemented in the following JavaScript library.


if (typeof SDK === 'undefined') {
 var SDK = {};
}

SDK.ItemCompletion = {};


SDK.ItemCompletion.errorHandler = function (error) {
 /// <summary>
 /// Alerts the user of an error.  Used as a callback function for webservice calls.
 /// </summary>
 /// <param name='error'>an Error object</param>
 alert(error.message);
};


SDK.ItemCompletion.getTaskName = function () {
 /// <summary>
 /// Returns the name of the task.  Builds the name if it hasn't been built.
 /// </summary>
 /// <returns>a string - the name of the task</returns>
 if (typeof SDK.ItemCompletion.taskName === 'undefined') {
  SDK.ItemCompletion.taskName = 'Complete ' +
            Xrm.Page.getAttribute('sample_name').getValue();
 }

 return SDK.ItemCompletion.taskName;
};

SDK.ItemCompletion.canCreateTask = function () {
 /// <summary>
 /// Returns whether the task can be created or not.  Used in a custom ribbon enable rule.
 /// </summary>
 /// <returns>true if the task can be created, false otherwise</returns>
 var itemId,
        taskName,
        entities = [],
        i;

 // Check to make sure that we are on the form before continuing with the check.
 if (Xrm.Page.data === null) return false;

 // Set the isTaskCreateable state variable if this is the first time the ribbon
 // enable rule is being checked.
 if (typeof SDK.ItemCompletion.isTaskCreateable === 'undefined') {
  // Set the isTaskCreateable property to false in order to return it immediately
  // since the query for tasks could take some time to complete.
  SDK.ItemCompletion.isTaskCreateable = false;

  // Asynchronously search for a specific task that is related to this item.  If there
  // are any, then we do not want to allow creation of another task from the ribbon.
  taskName = SDK.ItemCompletion.getTaskName();
  itemId = Xrm.Page.data.entity.getId();
  SDK.REST.retrieveMultipleRecords("Task",
            "$select=ActivityId&$filter=RegardingObjectId/Id eq guid'" + itemId +
            "' and Subject eq '" + taskName.replace(/\'/g, "\'\'") + "'",
            function (results) {
             for (i = 0; i < results.length; i += 1) {
              entities.push(results[i]);
             }
            },
            SDK.ItemCompletion.errorHandler,
            function () {
             // Enable the ribbon button only if there are no tasks related to this
             // item.
             SDK.ItemCompletion.isTaskCreateable = entities.length === 0;
             Xrm.Page.ui.refreshRibbon();
            });
 }

 return SDK.ItemCompletion.isTaskCreateable;
};


SDK.ItemCompletion.canCompleteTask = function () {
 /// <summary>
 /// Returns whether the task can be completed or not.  Used in a custom ribbon enable
 /// rule.
 /// </summary>
 /// <returns>true if the task can be completed, false otherwise</returns>
 var taskName,
        itemId;
 // Check to make sure we're on the form before continuing.
 if (Xrm.Page.data === null) return false;

 // Set the isTaskCompleteable variable if this is the first time that this is being
 // checked.
 if (typeof SDK.ItemCompletion.isTaskCompleteable === 'undefined') {
  // Set the isTaskCompleteable variable to false and then query to see if it is
  // completeable or not.
  SDK.ItemCompletion.isTaskCompleteable = false;

  // Asynchronously query for a specific open task that can be completed.
  taskName = SDK.ItemCompletion.getTaskName();
  itemId = Xrm.Page.data.entity.getId();
  SDK.ItemCompletion.retrieveOpenTasks(itemId, taskName,
            function (entities) {
             // Enable the ribbon button and refresh the ribbon if there were some
             // tasks to complete.
             SDK.ItemCompletion.isTaskCompleteable = entities.length > 0;
             Xrm.Page.ui.refreshRibbon();
            });
 }

 return SDK.ItemCompletion.isTaskCompleteable;
};


SDK.ItemCompletion.createTask = function () {
 /// <summary>
 /// Creates a task in CRM using the REST endpoint.
 /// </summary>
 // Disable the button so that we don't allow creating more than one task.
 SDK.ItemCompletion.isTaskCreateable = false;
 Xrm.Page.ui.refreshRibbon();

 // Create the task for completing the item.
 var taskName = SDK.ItemCompletion.getTaskName();

 var taskToCreate = {
  Subject: taskName,
  Description: "Do what's necessary.",
  RegardingObjectId: {
   Id: Xrm.Page.data.entity.getId(),
   LogicalName: "sample_sdksampleitem"
  }
 };
 SDK.REST.createRecord(taskToCreate,
        "Task",
        function (createdRecord) {
         SDK.ItemCompletion.isTaskCompleteable = true;
         Xrm.Page.ui.refreshRibbon();
        },
        function (error) {
         SDK.ItemCompletion.isTaskCreateable = true;
         SDK.ItemCompletion.isTaskCompleteable = false;
         Xrm.Page.ui.refreshRibbon();
         SDK.ItemCompletion.errorHandler(error);
        }
        );
};


SDK.ItemCompletion.completeTask = function () {
 /// <summary>
 /// Marks the task as complete.  Used in a ribbon custom action.
 /// </summary>
 // Disable the ribbon button since it should not be pressed more than once.
 SDK.ItemCompletion.isTaskCompleteable = false;
 Xrm.Page.ui.refreshRibbon();

 // Retrieve all of the open tasks that have the name of the task we care about (there
 // should only be one).
 SDK.ItemCompletion.retrieveOpenTasks(Xrm.Page.data.entity.getId(),
        SDK.ItemCompletion.getTaskName(),
        function (tasks) {
         var i;
         // Iterate through the open tasks, firing the workflow for each one (there
         // should only be one).
         for (i = 0; i < tasks.length; i += 1) {
          SDK.ItemCompletion.executeCompleteWorkflow(tasks[i]);
         }
        });
};


SDK.ItemCompletion.retrieveOpenTasks = function (itemId, taskName, callback) {
 /// <summary>
 /// Retrieves tasks that are open, are related to the item, and have the specified subject
 /// (taskName).
 /// </summary>
 var entities = [],
        i;
 SDK.REST.retrieveMultipleRecords("Task",
        "$select=ActivityId&$filter=RegardingObjectId/Id eq guid'" + itemId + "'" +
        " and Subject eq '" + taskName.replace(/\'/g, "\'\'") + "' and StateCode/Value eq 0",
        function (results) {
         for (i = 0; i < results.length; i += 1) {
          entities.push(results[i]);
         }
        },
        SDK.ItemCompletion.errorHandler,
        function () {
         callback(entities);
        });
};

SDK.ItemCompletion.executeCompleteWorkflow = function (task) {
 /// <summary>
 /// Uses the SOAP endpoint to execute a workflow that completes a task.  Used by a
 /// ribbon custom action.
 /// </summary>
 /// <param name='task'>the task that will be marked complete by the workflow</param>
 // This workflow ID will need to be changed if you created your own workflow.    
 var WORKFLOW_ID = 'b199b292-ee19-45a5-9e44-2ec199251aa1',
        executeWorkflowXml;
 // Most of this XML was generated by the soaplogger included in the SDK sample found
 // in samplecode\cs\client\soaplogger.
 executeWorkflowXml =
        ['<request i:type="b:ExecuteWorkflowRequest" xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.microsoft.com/crm/2011/Contracts">',
            '<a:Parameters xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">',
                '<a:KeyValuePairOfstringanyType>',
                    '<c:key>EntityId</c:key>',
                    '<c:value i:type="d:guid" xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/">',
                        SDK.ItemCompletion.xmlEncode(task.ActivityId),
                    '</c:value>',
                '</a:KeyValuePairOfstringanyType>',
                '<a:KeyValuePairOfstringanyType>',
                    '<c:key>WorkflowId</c:key>',
                    '<c:value i:type="d:guid" xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/">',
                        SDK.ItemCompletion.xmlEncode(WORKFLOW_ID),
                    '</c:value>',
                '</a:KeyValuePairOfstringanyType>',
            '</a:Parameters>',
                '<a:RequestId i:nil="true" />',
            '<a:RequestName>ExecuteWorkflow</a:RequestName>',
        '</request>'].join('');

 SDK.ItemCompletion.executeSOAPRequest(executeWorkflowXml, "Execute",
        function (responseXml) {
         //We do not need the systemJobId, but the following code can be used to capture the value
         var systemJobId;
         if (typeof responseXml.selectSingleNode != "undefined") {
          //For Internet Explorer 
          systemJobId = responseXml.selectSingleNode(
                's:Envelope/s:Body/ExecuteResponse/ExecuteResult/a:Results/a:KeyValuePairOfstringanyType/c:value').text;
         }
         else {
          //Using the XPathEvaluator for Chrome or Firefox
          var _NSResolver = function (prefix) {
           var ns = {
            "s": "http://schemas.xmlsoap.org/soap/envelope/",
            "a": "http://schemas.microsoft.com/xrm/2011/Contracts",
            "i": "http://www.w3.org/2001/XMLSchema-instance",
            "b": "http://schemas.microsoft.com/crm/2011/Contracts",
            "c": "http://schemas.datacontract.org/2004/07/System.Collections.Generic",
            "d": "http://schemas.microsoft.com/2003/10/Serialization/",
            "ns": "http://schemas.microsoft.com/xrm/2011/Contracts/Services"
           };
           return ns[prefix] || null;
          }
          var xpathExpr = "s:Envelope/s:Body/ns:ExecuteResponse/ns:ExecuteResult/a:Results/a:KeyValuePairOfstringanyType/c:value";
          var xpe = new XPathEvaluator();
          var xPathNode = xpe.evaluate(xpathExpr, responseXml, _NSResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
          var result = (xPathNode != null) ? xPathNode.singleNodeValue : null;
          systemJobId = result.textContent;
         }
        },
        SDK.ItemCompletion.errorHandler);
};


SDK.ItemCompletion.executeSOAPRequest = function (body, requestType, successCallback, errorCallback) {
 /// <summary>
 /// Executes a SOAP request.
 /// </summary>


 function _getError(faultXml) {
  /// <summary>
  /// Parses the XML of a SOAP response for an error.
  /// </summary>
  var errorMessage = "Unknown Error (Unable to parse the fault)";
  if (typeof faultXml == "object") {
   try {
    var bodyNode = faultXml.firstChild.firstChild;
    //Retrieve the fault node
    for (var i = 0; i < bodyNode.childNodes.length; i++) {
     var node = bodyNode.childNodes[i];

     //NOTE: This comparison does not handle the case where the XML namespace changes
     if ("s:Fault" == node.nodeName) {
      for (var j = 0; j < node.childNodes.length; j++) {
       var faultStringNode = node.childNodes[j];
       if ("faultstring" == faultStringNode.nodeName) {
        if (typeof faultStringNode.text != "undefined") {
         errorMessage = faultStringNode.text;
        }
        else {
         errorMessage = faultStringNode.textContent;
        }
        break;
       }
      }
      break;
     }
    }
   }
   catch (e) { }
  }
  return new Error(errorMessage);
 }


 function _handleResponse(req, successCallback, errorCallback) {
  /// <summary>
  /// Handles a SOAP response appropriately based on whether the request succeeded or
  /// failed.
  /// </summary>
  if (req.readyState == 4) {
   if (req.status == 200) {
    if (successCallback != null)
    { successCallback(req.responseXML); }
   }
   else {
    errorCallback(_getError(req.responseXML));
   }
  }
 }

 var request =
            ["<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">",
                "<s:Body>",
                    "<", requestType, " xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\"",
                        " xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">",
                        body,
                    "</", requestType, ">",
                "</s:Body>",
                "</s:Envelope>"
            ].join("");

 var req = new XMLHttpRequest();
 req.open("POST", Xrm.Page.context.getServerUrl() + "/XRMServices/2011/Organization.svc/web", true);
 // Responses will return XML. It isn't possible to return JSON.
 req.setRequestHeader("Accept", "application/xml, text/xml, */*");
 req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
 req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/" + requestType);

 req.onreadystatechange = function () {
  _handleResponse(req, successCallback, errorCallback);
 };
 req.send(request);
};

SDK.ItemCompletion.xmlEncode = function (strInput) {
 var c;
 var XmlEncode = '';

 if (strInput == null) {
  return null;
 }
 if (strInput == '') {
  return '';
 }

 for (var cnt = 0; cnt < strInput.length; cnt++) {
  c = strInput.charCodeAt(cnt);

  if (((c > 96) && (c < 123)) ||
			((c > 64) && (c < 91)) ||
			(c == 32) ||
			((c > 47) && (c < 58)) ||
			(c == 46) ||
			(c == 44) ||
			(c == 45) ||
			(c == 95)) {
   XmlEncode = XmlEncode + String.fromCharCode(c);
  }
  else {
   XmlEncode = XmlEncode + '&#' + c + ';';
  }
 }

 return XmlEncode;
};

sample_/Configuration.html

The sample_/Configuration.html web resource provides only a user interface and instructions to verify that the sample works. It is included as the configuration pages for the Ribbon Asynchronous EnableRules and Executing Workflows managed solution. The following image shows the content of this HTML web resource. View the source HTML in \WebResourceSourceFiles\WebResourceSourceFiles\Configuration.html.

The Content of the Configuration page

Other Web Resources

The web resources in the following table are included in the solution to support the functionality used in the sample.

 

Type Name Description

JScript

sample_/Scripts/SDK.REST.js

Functions in this JavaScript library are used to make web service calls to the REST endpoint. It is used by sample_/Scripts/SDK.ItemCompletion.js. This is the same JavaScript library introduced in the Sample: Create, Retrieve, Update and Delete Using the REST Endpoint with JavaScript. To view the code for this library, see sample_/Scripts/SDK.REST.js.

JScript

sample_/Scripts/SDK.Configuration.js

A JavaScript library used by sample_/Configuration.html. It includes a function to start the sample by opening a new SDK Sample Item entity form. View the source code in \WebResourceSourceFiles\WebResourceSourceFiles\Scripts\SDK.Configuration.js.

Image

sample_/Images/customAction.png

An image that is used in sample_/Configuration.html.

Image

sample_/Images/task16complete.png

A small icon that is used in the ribbon Complete Task button.

Image

sample_/Images/task16create.png

A small icon that is used in the ribbon Create Task button.

Image

sample_/Images/task32complete.png

A large icon that is used in the ribbon Complete Task button.

Image

sample_/Images/task32create.png

A large icon that is used in the ribbon Create Task button.

CSS

sample_/Styles/crmStyles.css

This CSS file is used by sample_/Configuration.html. View the source code at \WebResourceSourceFiles\WebResourceSourceFiles\Styles\crmStyles.css.

CSS

sample_/Styles/startPage.css

This CSS file includes styles specific to sample_/Configuration.html. View the source code at \WebResourceSourceFiles\WebResourceSourceFiles\Styles\startPage.css.

Ribbon

The following table describes the major elements for this ribbon tab definition.

 

Element Element ID Description

<CustomAction>

Sample.Form.sample_sdksampleitem.CompletionTab.CustomAction

Defines the custom tab that contains the group and button controls for the sample.

<CommandDefinition>

Sample.Form.sample_sdksampleitem.CompletionTab

Defines when the custom tab is enabled and displayed.

<CommandDefinition>

Sample.Form.sample_sdksampleitem.TaskGroup

Defines when the group in the tab is enabled and displayed.

<CommandDefinition>

Sample.Form.sample_sdksampleitem.CompleteTask

Used by the Complete Task button. This element calls the SDK.ItemCompletion.completeTask function in sample_/Scripts/SDK.ItemCompletion.js to execute the SDK Sample Disable Task workflow.

<CommandDefinition>

Sample.Form.sample_sdksampleitem.CreateTask

Used by the Create Task button, which calls the SDK.ItemCompletion.createTask function in sample_/Scripts/SDK.ItemCompletion.js to create a task related to the SDK Sample Item record.

<EnableRule>

Sample.Form.sample_sdksampleitem.CanCreateTask

Used by the Create Task button. This element defines a <CustomRule> that uses the SDK.ItemCompletion.canCreateTask function in sample_/Scripts/SDK.ItemCompletion.js.

This function queries Microsoft Dynamics CRM for tasks related to the SDK Sample Item record. If no tasks are found, the button is enabled.

<EnableRule>

Sample.Form.sample_sdksampleitem.CanCompleteTask

Used by the Complete Task button. This element defines a <CustomRule> that uses the SDK.ItemCompletion.canCompleteTask function in sample_/Scripts/SDK.ItemCompletion.js to query CRM for active tasks with a certain name. If one is found, the button is enabled.

These elements are defined in the following XML.


<RibbonDiffXml>
 <CustomActions>
  <CustomAction Id="Sample.Form.sample_sdksampleitem.CompletionTab.CustomAction"
                Location="Mscrm.Tabs._children"
                Sequence="40">
   <CommandUIDefinition>
    <Tab Id="Sample.Form.sample_sdksampleitem.CompletionTab"
         Command="Sample.Form.sample_sdksampleitem.CompletionTab"
         Title="$LocLabels:Sample.sample_sdksampleitem.CompletionTab.Title"
         Description="$LocLabels:Sample.sample_sdksampleitem.CompletionTab.Description"
         Sequence="40">
     <Scaling Id="Sample.Form.sample_sdksampleitem.CompletionTab.Scaling">
      <MaxSize Id="Sample.Form.sample_sdksampleitem.CompletionTab.TaskGroup.MaxSize"
               GroupId="Sample.Form.sample_sdksampleitem.CompletionTab.TaskGroup"
               Sequence="10"
               Size="LargeMedium" />
     </Scaling>
     <Groups Id="Sample.Form.sample_sdksampleitem.CompletionTab.Groups">
      <Group Id="Sample.Form.sample_sdksampleitem.CompletionTab.TaskGroup"
             Command="Sample.Form.sample_sdksampleitem.TaskGroup"
             Sequence="10"
             Title="$LocLabels:Sample.sample_sdksampleitem.CompletionTab.TaskGroup.Title"
             Template="Mscrm.Templates.3.3">
       <Controls Id="Sample.Form.sample_sdksampleitem.CompletionTab.TaskGroup.Controls">
        <Button Id="Sample.Form.sample_sdksampleitem.CompletionTab.TaskGroup.CreateTask"
                ToolTipTitle="$LocLabels:Sample.sample_sdksampleitem.CreateTask.LabelText"
                ToolTipDescription="$LocLabels:Sample.sample_sdksampleitem.CreateTask.ToolTipDescription"
                Command="Sample.Form.sample_sdksampleitem.CreateTask"
                Sequence="10"
                LabelText="$LocLabels:Sample.sample_sdksampleitem.CreateTask.LabelText"
                Alt="$LocLabels:Sample.sample_sdksampleitem.CreateTask.LabelText"
                Image16by16="$webresource:sample_/Images/task16create.png"
                Image32by32="$webresource:sample_/Images/task32create.png"
                TemplateAlias="o1" />
        <Button Id="Sample.Form.sample_sdksampleitem.CompletionTab.TaskGroup.CompleteTask"
                ToolTipTitle="$LocLabels:Sample.sample_sdksampleitem.CompleteTask.LabelText"
                ToolTipDescription="$LocLabels:Sample.sample_sdksampleitem.CompleteTask.ToolTipDescription"
                Command="Sample.Form.sample_sdksampleitem.CompleteTask"
                Sequence="10"
                LabelText="$LocLabels:Sample.sample_sdksampleitem.CompleteTask.LabelText"
                Alt="$LocLabels:Sample.sample_sdksampleitem.CompleteTask.LabelText"
                Image16by16="$webresource:sample_/Images/task16complete.png"
                Image32by32="$webresource:sample_/Images/task32complete.png"
                TemplateAlias="o1" />
       </Controls>
      </Group>
     </Groups>
    </Tab>
   </CommandUIDefinition>
  </CustomAction>
 </CustomActions>
 <Templates>
  <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
 </Templates>
 <CommandDefinitions>
  <CommandDefinition Id="Sample.Form.sample_sdksampleitem.TaskGroup">
   <EnableRules>
    <EnableRule Id="Mscrm.Enabled" />
   </EnableRules>
   <DisplayRules />
   <Actions />
  </CommandDefinition>
  <CommandDefinition Id="Sample.Form.sample_sdksampleitem.CompleteTask">
   <EnableRules>
    <EnableRule Id="Sample.Form.sample_sdksampleitem.CanCompleteTask" />
   </EnableRules>
   <DisplayRules />
   <Actions>
    <!-- isNaN is used to load necessary libraries without actually executing
                anything interesting - it will just return true. -->
    <JavaScriptFunction Library="$webresource:sample_/Scripts/json2.js"
                        FunctionName="isNaN" />
    <JavaScriptFunction Library="$webresource:sample_/Scripts/SDK.REST.js"
                        FunctionName="isNaN" />
    <JavaScriptFunction Library="$webresource:sample_/Scripts/SDK.ItemCompletion.js"
                        FunctionName="SDK.ItemCompletion.completeTask" />
   </Actions>
  </CommandDefinition>
  <CommandDefinition Id="Sample.Form.sample_sdksampleitem.CompletionTab">
   <EnableRules>
    <EnableRule Id="Mscrm.Enabled" />
   </EnableRules>
   <DisplayRules>
    <DisplayRule Id="Sample.Form.IsExisting" />
   </DisplayRules>
   <Actions />
  </CommandDefinition>
  <CommandDefinition Id="Sample.Form.sample_sdksampleitem.CreateTask">
   <EnableRules>
    <EnableRule Id="Sample.Form.sample_sdksampleitem.CanCreateTask" />
   </EnableRules>
   <DisplayRules />
   <Actions>
    <!-- isNaN is used to load necessary libraries without actually executing
                anything interesting - it will just return true. -->
    <JavaScriptFunction Library="$webresource:sample_/Scripts/json2.js"
                        FunctionName="isNaN" />
    <JavaScriptFunction Library="$webresource:sample_/Scripts/SDK.REST.js"
                        FunctionName="isNaN" />
    <JavaScriptFunction Library="$webresource:sample_/Scripts/SDK.ItemCompletion.js"
                        FunctionName="SDK.ItemCompletion.createTask" />
   </Actions>
  </CommandDefinition>
 </CommandDefinitions>
 <RuleDefinitions>
  <TabDisplayRules>
   <TabDisplayRule TabCommand="Sample.Form.sample_sdksampleitem.CompletionTab">
    <EntityRule EntityName="sample_sdksampleitem"
                Context="Form"
                AppliesTo="PrimaryEntity" />
   </TabDisplayRule>
  </TabDisplayRules>
  <DisplayRules>
   <DisplayRule Id="Sample.Form.IsExisting">
    <FormStateRule State="Existing" />
   </DisplayRule>
  </DisplayRules>
  <EnableRules>
   <EnableRule Id="Sample.Form.sample_sdksampleitem.CanCreateTask">
    <CustomRule Library="$webresource:sample_/Scripts/json2.js"
                FunctionName="isNaN" />
    <CustomRule Library="$webresource:sample_/Scripts/SDK.REST.js"
                FunctionName="isNaN" />
    <CustomRule Library="$webresource:sample_/Scripts/SDK.ItemCompletion.js"
                FunctionName="SDK.ItemCompletion.canCreateTask" />
   </EnableRule>
   <EnableRule Id="Sample.Form.sample_sdksampleitem.CanCompleteTask">
    <CustomRule Library="$webresource:sample_/Scripts/json2.js"
                FunctionName="isNaN" />
    <CustomRule Library="$webresource:sample_/Scripts/SDK.REST.js"
                FunctionName="isNaN" />
    <CustomRule Library="$webresource:sample_/Scripts/SDK.ItemCompletion.js"
                FunctionName="SDK.ItemCompletion.canCompleteTask" />
   </EnableRule>
  </EnableRules>
 </RuleDefinitions>
 <LocLabels>
  <LocLabel Id="Sample.sample_sdksampleitem.CompletionTab.Description">
   <Titles>
    <Title languagecode="1033"
           description="Create and complete activities to complete this item." />
   </Titles>
  </LocLabel>
  <LocLabel Id="Sample.sample_sdksampleitem.CreateTask.ToolTipDescription">
   <Titles>
    <Title languagecode="1033"
           description="Create a task for completing this item." />
   </Titles>
  </LocLabel>
  <LocLabel Id="Sample.sample_sdksampleitem.CreateTask.LabelText">
   <Titles>
    <Title languagecode="1033"
           description="Create Task" />
   </Titles>
  </LocLabel>
  <LocLabel Id="Sample.sample_sdksampleitem.CompletionTab.Title">
   <Titles>
    <Title languagecode="1033"
           description="Item Completion" />
   </Titles>
  </LocLabel>
  <LocLabel Id="Sample.sample_sdksampleitem.CompleteTask.ToolTipDescription">
   <Titles>
    <Title languagecode="1033"
           description="Complete the task for completing this item." />
   </Titles>
  </LocLabel>
  <LocLabel Id="Sample.sample_sdksampleitem.CompleteTask.LabelText">
   <Titles>
    <Title languagecode="1033"
           description="Complete Task" />
   </Titles>
  </LocLabel>
  <LocLabel Id="Sample.sample_sdksampleitem.CompletionTab.TaskGroup.Title">
   <Titles>
    <Title languagecode="1033"
           description="Task" />
   </Titles>
  </LocLabel>
 </LocLabels>
</RibbonDiffXml>

Description of Tasks

There are several tasks that are performed in this sample.

  • Control when the Item Completion tab is displayed.

  • Control when the Create Task button is enabled.

  • Control when the Complete Task button is enabled.

  • Create a task record when the Create Task button is pressed.

  • Execute the SDK Sample Disable Task workflow when the Complete Task button is pressed.

All of these tasks are controlled by using <CommandDefinition> elements defined in the RibbonDiffXml and functions defined in the sample_/Scripts/SDK.ItemCompletion.js JavaScript library.

Display Item Completion Tab

The tab uses the Sample.Form.sample_sdksampleitem.CompletionTab command, which uses the Sample.Form.IsExisting display rule. This display rule uses the <FormStateRule> configured to use Status=”Existing” so that the Item Completion tab is only displayed after a record is saved and the user can edit it.The tab also uses a <TabDisplayRule> with an <EntityRule> so that it only appears on the SDK Sample Item entity form.

Enable Create Task Button

The Create Task button uses the Sample.Form.sample_sdksampleitem.CreateTask command, which uses the Sample.Form.sample_sdksampleitem.CanCreateTask enable rule. This enable rule uses the SDK.ItemCompletion.canCreateTask function.

This enable rule also uses additional <CustomRule> definitions to load the sample_/Scripts/json2.js and sample_/Scripts/SDK.REST.js libraries. These libraries are required for actions that query data. These custom rules ensure these libraries are loaded by specifying the JavaScript isNaN function, which always returns true when no parameter is passed to it. By calling this function in the custom rule, you can make sure these libraries get loaded with the ribbon.

The SDK.ItemCompletion.canCreateTask function looks for an SDK.ItemCompletion.isTaskCreateable Boolean object. If the object exists, it returns the value to control whether to enable the button.

If the SDK.ItemCompletion.isTaskCreateable object does not exist, it creates it and sets the value to false. The function immediately returns this value so that the button is disabled.

At the same time, this function uses the SDK.REST.retrieveMultipleRecords method to initiate an asynchronous request to retrieve any task records that are related to the current record and have a Subject value that matches a specific name.

In the successCallback parameter of SDK.REST.retrieveMultipleRecords, an anonymous function adds any found records into an array.

In the OnComplete parameter of SDK.REST.retrieveMultipleRecords, an anonymous function tests the number of any records added to the array. If no records are found, the SDK.ItemCompletion.isTaskCreateable value is set to true. Then, the Xrm.Page.ui.refreshRibbon method is used to refresh the ribbon and display the button based on the current value of SDK.ItemCompletion.isTaskCreateable.

Enable Complete Task button

The Complete Task button uses the Sample.Form.sample_sdksampleitem.CompleteTask command, which uses the Sample.Form.sample_sdksampleitem.CanCompleteTask<EnableRule>. This enable rule uses the SDK.ItemCompletion.canCompleteTask function.

This enable rule also uses additional <CustomRule> definitions to load the sample_/Scripts/json2.js and sample_/Scripts/SDK.REST.js libraries. These libraries are required for actions that query data. These custom rules ensure these libraries are loaded by specifying the JavaScript isNaN function, which always returns true when no parameter is passed to it. By calling this function in the custom rule you can make sure these libraries get loaded with the ribbon.

The SDK.ItemCompletion.canCompleteTask function looks for an SDK.ItemCompletion.isTaskCompleteable Boolean object. If the object exists it returns the value to control whether to enable the button.

If the SDK.ItemCompletion.isTaskCompleteable object does not exist, it creates it and sets the value to false. The function immediately returns this value so that the button is disabled.

At the same time, this function uses the SDK.ItemCompletion.retrieveOpenTasks method to initiate an asynchronous request using SDK.REST.retrieveMultipleRecords method to retrieve any open task records that are related to the current record and have a Subject value that matches a specific name.

In the callback parameter of SDK.ItemCompletion.retrieveOpenTasks, an anonymous function tests the number of any records returned. If no records are found, the SDK.ItemCompletion.isTaskCompleteable value is set to false. Then, the Xrm.Page.ui.refreshRibbon method is used to refresh the ribbon and display the button based on the current value of SDK.ItemCompletion.isTaskCompleteable.

Create a Task Record

The Create Task button uses the Sample.Form.sample_sdksampleitem.CreateTask command. This command has three <JavaScriptFunction> elements configured as actions.

The first two make sure that the sample_/Scripts/json2.js and sample_/Scripts/SDK.REST.js JavaScript libraries are loaded by calling the isNaN function.

The third function is SDK.ItemCompletion.createTask. This function sets the SDK.ItemCompletion.isTaskCreateable Boolean object to false and refreshes the ribbon so that the button is disabled. This prevents the user from clicking it twice and creating more than one task.

Next, this function defines a task to create using the SDK.ItemCompletion.getTaskName function to specify a standard Subject value for the task. It then uses the SDK.REST.createRecord function to asynchronously create the task record.

In the SDK.REST.createRecord successCallback parameter an anonymous function sets SDK.ItemCompletion.isTaskCompleteable to true and refreshes the ribbon. This enables the Complete Task button.

In the SDK.REST.createRecorderrorCallback parameter, an anonymous function handles the situation where the request fails, sets SDK.ItemCompletion.isTaskCreateable to true and SDK.ItemCompletion.isTaskCompleteable to false, and then refreshes the ribbon. This lets the user try again and ensures that the Complete Task button is not enabled. Finally, the errorCallback function calls the SDK.ItemCompletion.errorHandler function to display information about the error.

Execute the SDK Sample Disable Task Workflow

The Complete Task button uses the Sample.Form.sample_sdksampleitem.CompleteTask command. This command has three <JavaScriptFunction> elements configured as actions. The first two functions make sure that the sample_/Scripts/json2.js and sample_/Scripts/SDK.REST.js JavaScript libraries are loaded by calling the isNaN function.

The third function is SDK.ItemCompletion.completeTask. This function sets the SDK.ItemCompletion.isTaskCompleteable Boolean object to false and refreshes the ribbon so that the button is disabled. This prevents the user from clicking it twice.

Next, this function uses SDK.ItemCompletion.retrieveOpenTasks to retrieve any open tasks related to the record with the expected task name using SDK.REST.retrieveMultipleRecords. In the SDK.ItemCompletion.retrieveOpenTaskscallback parameter the SDK.ItemCompletion.executeCompleteWorkflow function is called for each task retrieved.

It is expected that there should only be one task retrieved, but the function accepts that more than one could be returned and executes the workflow on any matching open tasks.

SDK.ItemCompletion.executeCompleteWorkflow defines a request using the SOAP endpoint for web resources to execute the SDK Sample Disable Task workflow. The workflow is identified by the ID value. The XML is composed using the SOAP Logger utility described in the SDK topic: Walkthrough: Use the SOAP Endpoint for Web Resources with JScript. The SDK.ItemCompletion.executeCompleteWorkflow function passes the XML for the request to SDK.ItemCompletion.executeSOAPRequest. This function is a generic function designed to process any SOAP request. It passes the response back to SDK.ItemCompletion.executeCompleteWorkflow so that the systemjob ID value can be captured. In this example this systemjob ID value is not used.

Conclusion

 

This article described a scenario where a workflow is executed using a ribbon button and a strategy to make sure that that buttons in the ribbon are only enabled when conditions depending on data in the system indicate the controls can be used. Because the responsiveness of the application depends on enable rules returning a value immediately, and asynchronous strategy is used to disable any controls until data can be retrieved to verify that controls should be enabled. After the results of the asynchronous request are returned the ribbon is refreshed so that the controls can be enabled.

This sample also shows how to use the ExecuteWorkflowRequest class to execute a workflow from a ribbon command using the SOAP endpoint for web resources and JavaScript.

Send comments about this article to Microsoft.

Community Additions

Show:
© 2014 Microsoft