Sunday, April 9, 2017

Form notifications in Dynamics CRM

Form Notifications have been covered elsewhere already. When you are on a form, there is an area right below the title area that form messages are displayed.

The messages can be errors, information or warnings. In your web resources, you can add messages to the notification areas using Xrm.Page.ui.setNotification.

The picture below does not have  messages:


If a message is generated:
 
You can create your own messages in JS as mentioned above:


There is also an undocumented function, setFormHtmlNotification, that allows you to set the formatting of the message. While its not official API, its been around for awhile and saves you from having to use something like notifyjs. So if you use  

Xrm.Page.ui.setFormHtmlNotification("<h1>Wow! <button type='button'>Push Me For More!</button></h1>", 'INFO', "100")

then you get:

Don't forget to add some styling via inline styles. It's not stable API of course, but it helps. Hacking into notifications otherwise is a bit non-portable which is why notifyjs exist. Styling can also be a bit awkward.

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.