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:
function Deferred() {
return defer(Object.create(Deferred.prototype))
}
function defer(deferred) {
deferred.promise = new Promise(function(resolve, reject) {
deferred.resolve = resolve
deferred.reject = reject
})
return deferred
}
const p = Deferred()
export function onLoad(ctx: any): void {
p.resolve(ctx.getFormContext())
}
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.