Thursday, November 23, 2017

Dynamics CRM, form context without using Xrm.Page, can use with React

If you program in javascript/typescript for forms programming, you know that to access content on the page, you need to use the Xrm.Page object. However, in v9+, Xrm.Page is deprecated. The advice is to obtain the form context off the execution context. The execution context is what is provided when you add a callback handler to an onSave or onChange type event.

If you program with web resources, you know that there is not a supported way to obtain the form context. At best, you typically access it via window.parent.Xrm.Page. That works, but its deprecated. What’s a safer way to obtain the form context?

One way is to setup a simple system of access that relies only on the Form.onLoad handler and publishing the value to a well-know location. A onLoad handler is added in the form editor. Since onLoad is called at a different time then when your web resource may be loaded, you need to setup a simple promise to obtain the form context from any web resource by publishing the form context a well known location that is accesible to all web resources. The best well-known location is on the toplevel window, but for the description below, we add the form context one level up from where the form onLoad handlder is called. This location is also accessible from web resources. Client form scripts and web resources are loaded at the same child level with a common parent iframe.

Here’s how you do it:

  • Setup a function to be called with Form.onLoad.
  • In that function, setup a promise that can be accessed on the onLoad script’s window.parent.
  • In the web resource, access widow.parent. and use a then clause on the promise.

Here’s some code that you would load and attach to the onLoad handler:

/**
 * Captures the form context and stores it in
 * a well known location for other components,
 * especially Web Resources.
 *
 * Since form scripts and web resources live
 * in a hierarchy of iframes, ensure that
 * we embed that knowledge here, once.
 *
 * Note that MS documents are incomplete about
 * the context and its validness after a callback
 * function exits.
 *
 * Usage: arrange to have onLoad called
 * as a form's onload handler.
 */

/** Attachment point for the callback. Object has "Deferred" type. */
function Deferred() {
    return defer(Object.create(Deferred.prototype))
}

/** Add resolve, reject, promise to an object. */
function defer(deferred) {
    deferred.promise = new Promise(function(resolve, reject) {
        deferred.resolve = resolve
        deferred.reject = reject
    })
    return deferred
}

const p = Deferred()

/**
 * Arrange to have this function called
 * with the form's OnLoad event. This is 
 * the only way to guarantee that we obtain
 * a valid form context without going through
 * the deprecated Xrm.Page.
 * 
 * This form assumes that form scripts load
 * into a frame hierarchy that is one below
 * a parent that webresources can also access.
 */
export function onLoad(ctx: any): void {
    p.resolve(ctx.getFormContext())
}

/** 
 * Attach our promise one level up so other frames can find it.
 * To reach the promised land, call FormContextP() to obtain
 * the promise. Use Promise.race (or equivalent) to timeout
 * waiting.
 */
// @ts-ignore
window.parent.FormContextP = p.promise

A web resource would then access window.parent.FormCotextP from its code. There is no other way to pass objects between frame levels in an HTML document. The only thing you have used in the above is that the form script and web resouces are loaded as siblings, however, you can remove that assumption by posting the promise object to the topmost window, if you want.

Assuming you are using react, you could do:

class MyComponent extends React.Component<..,...> {
...
    public componentDidMount(): void {
        const p = (window.parent as any).FormContextP
        if (p)
            p.then(fctx => {
                this.setState({ formContext: (fctx as Xrm.PageContext) })
            })
    }
...
}

I’ve created a highly re-usable EntityForm that captures this and other Dynamics form related information and passes it to the child component. It makes it easy to access key “context” and other information that is needed. Note that in the above, we capture the form context and stick it into state, we could also provide this as “context” to child components by setting up some context methods in the class.

Monday, November 20, 2017

React, Redux, Typescript + Dynamics (Xrm, Crm) Client Programming

I've been assembling notes on react, dynamics and front end programming.

You may find them useful. They are in a state of continuous edit.

gitbook link

Thursday, November 9, 2017

Dynamics, Web API, FetchXml, generating your missing paging cookie

If you use fetchxml with the latest web api, you may be surprised that sometimes you do not get the paging cookie back when your results are > 5000 records. There are alot of articles on the web about using the paging cookie once you do have it.

How do you make sure you get your cookie?

Some people suggest just sticking the paging number into the fetchxml is the answer, but that causes thrash on the server (even if its in the cloud) if you have alot of results to page through.

<fetch page="20" ...>
...
</fetch>

That’s not great as its possible that the server may need to keep running the same query and tossing aside results–maybe a cache will save the day, but maybe not.

The real answer to always generate a paging cookie is contained on MSDN.

You need to add the right odata annotation to have the paging cookie generated.

...code to generate the request
content += 'Prefer: odata.include-annotations="Microsoft.Dynamics.CRM.*"\n'
...

I have not seen this mentioned anywhere so far, so I thought I would write this up. Note that you can use the fully specified annotation Microsoft.Dynamics.CRM.fetchxmlpagingcookie but the .* version picks any other CRM specific annotation that may be out there so I use the .* version vs the specific one. The OData spec has alot of notes on annotations and how to add and remove them. It’s worth a read of course. Don’t forget to add your other annotations e.g. FormattedValues.

There are many API libraries out there that are quite poor in that they do not allow you to easily batch request your fetchxml if your fetchxml string length is too large for the URL variety. Be aware of you what tools you use and their limitations. There is still a URL length limitation in batch requests but it is much larger than the URL limitation. You’ll still need to chunk your fetchxml somehow if you are retrieving, for example, something that requires a large list of values in a condition clause.