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.