The new Custom Page component can be opened as a dialog in a model driven app. This feature will allow app makers to quickly create intuitive dialogs without code with a rendering that is close to the standard model driven app’s dialogs, since it uses Fluent UI controls that are also used by Model Driven Apps.

In today’s post, we’ll see how we can pass an object from a Custom page opened as a dialog to the parent Model Driven App.

To learn how to build this type of dialog, how to open and close them, please refer to this blog written by DIANA BIRKELBACH ~ PCFLADY

The problem:

To close a dialog, the Back function is called in a custom page. The Back function closes the current page and returns to the last model-driven app or custom page in the model-driven app.

Based on the investigations I’ve done, we can’t currently return data from a custom page to the parent context, which is the Model Driven App. In fact, the PowerFx Back() function does not accept a parameter that returns an object to the parent context.

Possible solution:

To share the data between the custom page and the model driven app, I propose to use the Widnow.sessionStorage object. Indeed, this standard object allows us to store data on the browser and each tab of the browser has its own sessionStorage.

You can refer to the following documentation for more information about the sessionStorage object.

Save an object from the Custom page to the browser’s sessionStorage:

In order to pass the data from the Custom Page to the browser’s sessionStorage, we have to use some code. The use of JavaScript code is not supported for Canvas apps. However, we have the possibility to use PCF controls which will do the trick for this scenario.

The PCF I used contains two inputs:

  • Data: this input represents the data to be stored on the sessionStorage.
  • sessionStorageKey: this input represents the key used when writing and reading from the sessionStorage.
xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="MEA" constructor="ValueTurner" version="1.0.21" display-name-key="ValueTurner" description-key="ValueTurner description" control-type="standard" >
<external-service-usage enabled="false">
external-service-usage>
<property name="data" display-name-key="data" description-key="data" of-type="Object" usage="input" required="true" />
<property name="sessionStorageKey" display-name-key="sessionStorageKey" description-key="sessionStorageKey" of-type="SingleLine.Text" usage="input" required="true" />
<resources>
<code path="index.ts" order="1"/>
resources>
control>
manifest>

If you notice, the Data input is of type Object. This type of input is not yet documented by Microsoft. I took the opportunity to play with it during the development of my PCF control. Of course, we can use a text input to stay in the standard.

To activate this type of input, simply go to the file node_modules/pcf-scripts/featureflags.json. Then look for the line “pcfObjectType” and change “off” to “on”.


EDIT:

I would like to thank Cathal Noonan for proposing an alternative way to override the pcfObjectType value without manually changing the node_modules files

There’s another way to set the value to “on” without needing to modify files inside the node_modules folder…

If you create a file called “featureconfig.json” next to the control’s package.json, you can set the properties that you want to override

In this case, the contents of the file would be

{
“pcfObjectType”: “on”
}

The benefit of this approach is that there are no manual steps after cloning the source code


The logic of the control is as follows:

  • During the initialization, it clears the sessionStorage based on the key used.
  • When the Data input is updated, the control checks if the data object contains the result property which should be equal to true. If yes, it stores the Data object on the sessionStorage.
import { IInputs, IOutputs } from "./generated/ManifestTypes";
export class ValueTurner implements ComponentFramework.StandardControl<IInputs, IOutputs> {
private context_: ComponentFramework.Context<IInputs>
constructor() {
}
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @param container If a control is marked control-type='standard', it will receive an empty div element within which it can render its content.
*/
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement): void {
this.context_ = context;
sessionStorage.removeItem(this.context_.parameters.sessionStorageKey.raw!);
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
*/
public updateView(context: ComponentFramework.Context<IInputs>): void {
let props = this.context_.parameters.data.raw;
if (props.result == true) {
sessionStorage.setItem(this.context_.parameters.sessionStorageKey.raw!, JSON.stringify(props));
}
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
*/
public getOutputs(): IOutputs {
return {};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void {
}
}
view raw index.ts hosted with ❤ by GitHub

The control is used on the custom in the following way:

  • The PCF has no rendering, it can be considered as a renderless component.
  • The “data” input receives Data object.
  • The sessionStorageKey input takes the string “dialogKey”.

The Data object is initialized when the canvas apps is started:

Once the “Confirm” Button is clicked:

Once the “Cancel” Button is clicked:

The PCF’s source code: https://github.com/melamriD365/CanvasApps-Controls/tree/main/ValueTurner
Unmanaged solution: https://github.com/melamriD365/CanvasApps-Controls/releases

Get the shared object from sessionStorage in the Model Driven App:

The custom page opened as a dialog is done with the function Xrm.Navigation.navigateTo(). We can retrieve the Data object from the sessionStorage on the successCallback function using the same key “dialogKey”.

function openDialog(exectionContext) {
var dialogParameters = {
pageType: "custom",
name: "new_dialogsample_bb2dc",
};
var navigationOptions = {
target: 2,//use 1 if you want to open page inline or 2 to open it as dialog
width: 800, // value specified in pixel
height: 420,
position: 1,//1 to locate dialog in center and 2 to locate it on the side,
}
Xrm.Navigation.navigateTo(dialogParameters, navigationOptions).then(
function () {
let data = JSON.parse(sessionStorage.getItem("dialogKey"));
console.log(data);
// define notification object
var notification =
{
type: 1,
level: 1, //error
message: data.context
}
Xrm.App.addGlobalNotification(notification).then(
function success(result) {
console.log("Notification created with ID: " + result);
// perform other operations as required on notification display
},
function (error) {
console.log(error.message);
// handle error conditions
}
);
},
function (e) {
//put your error handler here
});
}
view raw AccountForm.js hosted with ❤ by GitHub

DEMO


Hope it helps…