Generic TS/JS to open a Custom Page

In the last few weeks, I have created TS/JS functions/snippets for several projects to open a custom Page from a ribbon button within a model-driven app. Since I am a lazy developer and like to make stuff reusable, I created a generic TS/JS to open a Custom Page. In this article, I will explain the script.

Sara Lagerquist wrote a companion blog post which explains in detail how this script can be used and configured in a model-driven app. Read more.

If you would like to get started with TypeScript for Dataverse make sure to take a look at my blog post “Setting up a TypeScript project for Dataverse“.

Background

Custom Pages are a great way to extend your model-driven App implementation. Use cases could be to collect additional data in a nice way (for example with a wizard-like multi-step experience) or start external processes and wait for their response (start a Power Automate flow).

To open such a custom page a small script is needed. This script would have to define which custom page to open as well as additional configurations such as open target (main page, dialog or side sialog), dialog size and others.

As I mentioned I am a bit lazy especially when it comes to repetitive tasks. Therefore I was more than happy when my dear colleague and friend Sara asked me to create a generic TS/JS to open custom Pages. We also have the script in an internal Template repository. With that either my colleagues can grab it themselves when they have this requirement in a project or I can fix it for them. Either way it is much faster than recreating basically the same function over an over again.

For those TypeScript snippets to work you would also need the @types/xrm npm package.

Helper Scripts

Before we dive into the actual script I have to explain some helper scripts I use.

You would need all 3 scripts in your JS file to make it work.

idWithoutBrackets

This function is one of our general helper scripts. We reuse it across different other generic functions (like the one to get an Environment Variable Value). In some places the ID we get for a record is placed within curly brackets. All this function does is strip away the curly brackets surrounding a GUID so that we don’t have to do it manually all the time.

TypeScript
function idWithoutBrackets(id: string): string {
if (id.slice(0, 1) === "{") {
return id.slice(1, -1);
} else {
return id;
}
}
JavaScript
function idWithoutBrackets(id) {
if (id.slice(0, 1) === "{") {
return id.slice(1, -1);
} else {
return id;
}
}

convertToSize

This is a function specifically and only used by the generic TS/JS to open a custom page. It takes an input string and transforms it to the correct TypeScript object the NavigationOptions expects. This could be either a number (the size in pixels) or a string (with either the size in % or px).

I decided to create such a function to be as generic as I could be. So that my colleagues (even the non-developers) can use all the configurations mentioned by the Microsoft docs.

TypeScript
function convertToSize(
input: number | string,
defaultSize: number,
): number | Xrm.Navigation.NavigationOptions.SizeValue | undefined {
let result: number | Xrm.Navigation.NavigationOptions.SizeValue | undefined = defaultSize;
if (typeof input === "number") {
if (input > 0) {
result = input;
}
} else {
const parsedInput = Number(input);
if (isNaN(parsedInput)) {
if (input.endsWith("px")) {
const numberPart = Number(input.substring(0, input.length - 2));
if (!isNaN(numberPart)) {
result = { value: numberPart, unit: "px" } as Xrm.Navigation.NavigationOptions.SizeValue;
}
} else if (input.endsWith("%")) {
const numberPart = Number(input.substring(0, input.length - 1));
if (!isNaN(numberPart)) {
result = { value: numberPart, unit: "%" } as Xrm.Navigation.NavigationOptions.SizeValue;
}
}
} else {
if (parsedInput > 0) {
result = parsedInput;
}
}
}
return result;
}
JavaScript
function convertToSize(input, defaultSize) {
let result = defaultSize;
if (typeof input === "number") {
if (input > 0) {
result = input;
}
}
else {
const parsedInput = Number(input);
if (isNaN(parsedInput)) {
if (input.endsWith("px")) {
const numberPart = Number(input.substring(0, input.length - 2));
if (!isNaN(numberPart)) {
result = { value: numberPart, unit: "px" };
}
}
else if (input.endsWith("%")) {
const numberPart = Number(input.substring(0, input.length - 1));
if (!isNaN(numberPart)) {
result = { value: numberPart, unit: "%" };
}
}
}
else {
if (parsedInput > 0) {
result = parsedInput;
}
}
}
return result;
}

Generic TS/JS to open a custom Page

Finally here is the generic TS/JS to open a custom page. It does take some input parameters (see next chapter for details) and opens the custom page accordingly. It also handles errors in a very rudimentary way. This is one area which could be improved on.

All you have to do is upload the JavaScript as a web resource and then use it within your ribbon button. As I mentioned Sara wrote an excellent companion blog post explaining just that.

TypeScript
export async function openCustomPage(
pageName: string,
title: string,
entityName = "",
id = "",
width: number | string = 600,
height: number | string = 400,
target: 1 | 2 = 2,
position: 1 | 2 = 1,
) {
const defaultWidth = 600;
const defaultHight = 400;
const recId = idWithoutBrackets(id);
const pageInput: Xrm.Navigation.CustomPage = {
pageType: "custom",
name: pageName,
entityName: entityName,
recordId: recId,
};
const navigationOptions: Xrm.Navigation.NavigationOptions = {
target: target,
width: convertToSize(width, defaultWidth),
height: convertToSize(height, defaultHight),
position: position,
title: title,
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions)
.then(function () {
// Called when page opens
return true;
})
.catch(function (error) {
// Handle error
console.log(error.message);
});
}
JavaScript
async function openCustomPage(pageName, title, entityName = "", id = "", width = 600, height = 400, target = 2, position = 1) {
const defaultWidth = 600;
const defaultHight = 400;
const recId = idWithoutBrackets(id);
const pageInput = {
pageType: "custom",
name: pageName,
entityName: entityName,
recordId: recId,
};
const navigationOptions = {
target: target,
width: convertToSize(width, defaultWidth),
height: convertToSize(height, defaultHight),
position: position,
title: title,
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions)
.then(function () {
// Called when page opens
return true;
})
.catch(function (error) {
// Handle error
console.log(error.message);
});
}

Inputs

The script has 8 input parameters which I try to explain shortly in this chapter. For more and detailed information about each and everyone of the input parameters head to Saras blog.

pageName

The schema name of the page to open.

title

The title the dialog should show.

entityName

The schema name of the table the custom page would need. Not always the custom page needs a table to work. But sometimes it would load additional data. Usually, that is the schema name of the table the button is created. It could also be some other table like a child.

id

The id of the row that the custom page would need.

width

Configuration on how wide the custom Page should be shown. Could either be a number or some value in % or px.

height

Configuration on how heigh the custom page should be sown. This is only used when the custom page is shown as a dialog (target = 2). Could either be a number or some value in % or px.

target

Defines the target of the custom page. Could be inline/main page (1) or dialog (2). This is optional and 2 is the default.

position

Specifies the position of the dialog. Could be either center (1) or far side (2). This is optional and 1 is the default.

Conclusion

With this script, it is very easy to open a custom page. This could be done by non-developers as well as developers.

I hope this helps. Feel free to reach out with any questions or feedback you might have.

This is just 1 of 61 articles. You can browse through all of them by going to the main page. Another possibility is to view the categories page to find more related content.
You can also subscribe and get new blog posts emailed to you directly.
Enter your email address to receive notifications of new posts by email.

Loading
One Comment

Add a Comment

Your email address will not be published. Required fields are marked *