Using window.postMessage to interact with IFrame in CRM Form

This is similar to my earlier post (https://dreamingincrm.com/2015/10/27/using-sessionstorage-to-interact-with-iframe-in-crm-forms/) that discussed using sessionStorage events to interact with IFrame and the IFrame to interact with the ClientApiWrapper. ClientApiWrapper is the IFrame that hosts the form scripts. Here is the code I used.

IFrame

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Form IFrame</title>
	<script>
	window.addEventListener('message', function(e) {
		if(e.origin != parent.Xrm.Page.context.getClientUrl()) return;
		document.getElementById('messageFromParent').textContent = 'Parent says "' + e.data+'"';	 
	});
		
	document.addEventListener('DOMContentLoaded' , function() {
		document.getElementById("buttons").addEventListener("click", function(e){
			var url = parent.Xrm.Page.context.getClientUrl();
			var fieldName = document.getElementById("crmFieldName").value;
			switch(e.target.id){
				case "enableField":
					parent.postMessage(['setFieldEditability', [fieldName,false]],url);
					break;
				case "disableField":
					parent.postMessage(['setFieldEditability', [fieldName,true]],url);
					break;
				case "hideField":
					parent.postMessage(['setFieldVisibility', [fieldName,false]],url);
					break;
				case "showField":
					parent.postMessage(['setFieldVisibility', [fieldName,true]],url);
					break;
			}
		});
	});
	</script>
</head>
<body>
	<p id="messageFromParent"></p><br><br>
	<input type="text" id="crmFieldName"><br><br>
	<div id="buttons">
	<button id="enableField">Enable Field</button>
	<button id="disableField">Disable Field</button>
	<button id="hideField">Hide Field</button>
	<button id="showField">Show Field</button>
	</div>	
</body>
</html>

Form Script

(function(){
var CVN = window.CVN || {};
var childChannel;
parent.addEventListener('message', function(e) {
if(e.origin != Xrm.Page.context.getClientUrl()) return;
childChannel = e.source;
CVN[e.data[0]].apply(null,e.data[1]);
}, false);

CVN.onSave = function(){
};

CVN.setFieldEditability = function(fieldName, isDisabled){
Xrm.Page.getControl(fieldName).setDisabled(isDisabled);
if(childChannel) childChannel.postMessage(fieldName+' has been '+(isDisabled ? 'disabled' : 'enabled'),Xrm.Page.context.getClientUrl());
};

CVN.setFieldVisibility = function(fieldName, isVisible){
Xrm.Page.getControl(fieldName).setVisible(isVisible)
if(childChannel) childChannel.postMessage(fieldName+' has been '+(isVisible ? 'hidden' : 'unhidden'),Xrm.Page.context.getClientUrl());
};

CVN.onLoad = function(){
};

window.CVN = CVN;
})();

Screenshot

Form

The eventhandler for message has to be attached to the parent for messages originating from both the form script (ClientApiWrapper) and the HTML webresource embedded into the form.

I envision the use of this technique primarily for form script -> html webresource communication, and common functions that are called by both form script and html webresource.

Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

Advertisements

Gotcha: Plugin Running Async

Generally when you want run a set of tasks that are not realtime, you would do this via Workflow. On the contrary when you want something to happen straightaway, you would do this using a Plugin. Before CRM2015, there was only one type of workflow. Starting with CRM2015 a workflow can be realtime or async. A plugin also can be run asynchronously or synchronously. To summarise quickly:

  1. A realtime workflow is similar to a plugin
  2. An asynchronous plugin is similar to a workflow

Take this particular scenario: You have a plugin that runs synchronously on the Update of a certain entity. Later down the track, you decide you want this plugin to run async, as the set of tasks performed by the plugin doesn’t really have to be realtime and you want to improve the performance of the core operation.

In this scenario, you have to be mindful of one particular plugin behaviour: transaction. When an exception is thrown inside a plugin, the core operation won’t succeed as the exception in the plugin will cause the transaction to rollback. When you change the Execution Mode to “Asynchronous”, you will still see that the plugin has failed in System Jobs area, but the transaction won’t be rolled back.

For eg. take this simple code:

using Microsoft.Xrm.Sdk;
using System;

namespace CrmExperimentPlugin
{
    public class CreateContactPlugin : IPlugin
    {
        #region Secure/Unsecure Configuration Setup
        private string _secureConfig = null;
        private string _unsecureConfig = null;

        public CreateContactPlugin(string unsecureConfig, string secureConfig)
        {
            _secureConfig = secureConfig;
            _unsecureConfig = unsecureConfig;
        }
        #endregion
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);
            Entity entity = (Entity)context.InputParameters["Target"];

            Entity contact = new Entity("contact");
            contact["firstname"] = "Max";
            contact["lastname"] = "Power";
            service.Create(contact);

            throw new InvalidPluginExecutionException("Exception raised after contact create");
        }
    }
}

Assuming that the plugin is registered post-create on a particular entity and Execution Mode is “Synchronous”, there will be no contact called “Max Power” at the end of the plugin execution. But if the same plugin is registered as post-create and asynchronous, there will be a contact called “Max Power” as the exception doesn’t rollback the transaction.

If you reference Event Execution Pipeline in msdn, it says this about the plugin transaction:

Stages 20 and 40 are guaranteed to be part of the database transaction while stage 10 may be part of the transaction.

But this is not entirely true, as it also depends on the Execution Mode.

tl;dr; Register plugins to run sychronously, if you need the transaction to rollback on plugin exception and need to validate certain criteria for the core operation to succeed. If you don’t need these features, use a workflow.