Sunday, April 2, 2017

Lifecycle for CRM web resources and accessing form data

Dynamics CRM allows you to add your own web resources to a form. The web resource is HTML that can display widgets that add to the form's interface and the user's experience. The web resource often displays information related to the entity being edited in the form.

The web resource is loaded into its own iframe. This means that the javascript that runs will use a different javascript execution context than the main form. This is a standard technique for isolating CSS and javascript effects from the toplevel page.

Under Form Properties, for the overall form, you can load a javascript library and call an initialization function on it.  You also have the option of passing an execution context to the "init" function. The execution context mentioned here is different than Xrm. Xrm, Microsoft's global variable, allows you access to both the general context, via .context, and the form's context via Xrm.Page. However, running javascript by using Form Properties is a capability separate from Web Resources.

There are two key issues you face when you load a web resource:
  • How do you get the "context" information that allows you to make data calls or perform processing based on the entity being edited. You can pass web query parameters to the loaded frame, but there is no equivalent "pass the Xrm execution context" switch to pass the object like with the javascript init function in Form properties.
  • How do you communicate between tabs, even if those tabs are running in their own iframes?

For the first issue, there is of course, the MSDN page here that shows how to get the ClientGlobalContext by including in a special <script> ClientGlobalContext.js.aspx. This only provides the equivalent of Xrm.Page.context and not Xrm.Page as you can get in the Form Properties' execution context. You have to make sure that the relative directory path matches the hierarchical embedding of the web resource. If the web resource name contains slashes, to fake a directory structure, you have to make sure the '../../and so on' prefix before ClientGlobalContext.js.apx matches the level.

How do you get enough access to context information to properly obtain critical information? You have a few choices:
  • Access parent.Xrm which should exist since the iframe is one below the actual entity frame. However, given asynchronous loading, the Page and global context may not have been initialized onto that object yet. You may need to use callbacks to sequence your access.
  • Pass the critical values, such as the entityid or other information, through the query parameters then parse the document.location.search value.
  • Use both with the query parameters having higher precedent than the parent.Xrm object.
You do have an option of passing in the entity id, language id and org id and name into the web resource via query parameters. With these, you can use the web data API and the context brought into scope via ClientGlobalContext to access data directly in the CRM database. But, if you want to connect to the main form's attributes and controls, for example to hook into control events, the context that you get from ClientGlobalContext.js.aspx does not help you as its the general context, not the form context.

So you can manipulate entity data that you get via the web data APIs but it may not be enough.

You probably need to access the toplevel Xrm.Page to get attributes and controls. Hence, you almost always wind up referencing parent.Xrm to obtain the form context at parent.Xrm.Page. You often make a big assumption that you are one level below the toplevel and hence only need one "parent." versus "parent.parent." But this assumption that may break in the future or if your webresource is used differently than you envisioned.

Many plugins that provide web resources actually use both ClientGlobalContext.js.apx, query parameters passed to the Web Resource as well as parent.Xrm.Page (e.g. parent.Xrm.Page.data.entity.getId()).

On the second, issue, communicating between iframes, there are no Microsoft XRM provided solutions. You can use standard javascript techniques such as global variables to setup a pub/sub bus, use a variable to exchange state/callbacks or other similar mechanisms. You are a bit on your own. You might think that you can use a javascript resource that loads via Form Properties to setup a toplevel communication mechanism, but it turns out that these resources also are loaded into a sub-frame, one level below the toplevel. Again, you can make some assumptions and perform some load-time checks to setup your environment. Just recognize that things may break some day.

1 comment:

  1. I am also pondering about this topic for quite a long time. My best hope is, that one day in the not-too-far future, custom webresource applications will be treated as first-class citizens in CRM web client land. Both, in terms of how well they are integrated (supported access to extensive API) and performance-wise. Especially the latter is as of today most critical part to me -- custom webresources (especially embedded webresource containers) have a totally limited lifetime, which lasts only as long as a visual form context is active. The improvements done by MS with Turbo Forms is heading into right direction, but it was only the first step (only internal artifacts affected).

    Given that typical webresource apps have lots of assets (f.e. js, css, images, fonts asf.) which have to be parsed/executed/rendered over and over again, everytime the user opens an entity form, this is highly wasteful and results in poor overall performances. Sure, I found means to circumvent this, but unsupported (while nevertheless stable). I hope that MS will one day provide kind of a shared execution context for custom webresource code which persists across multiple form/page load events. Maybe not-yet-official CCK (Custom Control Kit when I recap correctly) will give us some insight to the future of CRM client UI strategy -- without deepy nested throw-away iframe myriads.

    ReplyDelete