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.
function idWithoutBrackets(id: string): string { if (id.slice(0, 1) === "{") { return id.slice(1, -1); } else { return id; } }
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.
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; }
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.
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); }); }
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.
You can also subscribe and get new blog posts emailed to you directly.
very cool – will this same js code work for opening custom pages from the main grid? for multiple selected rows?