How To Make Dialogs For Model-Driven Apps Using Custom Pages

This week the Custom Pages were released to public preview: here the official blog.

This is a huge step towards having only one app type; not having to decide which App type to choose. But it also means, that we finally get the low-code possibilities right inside the model-driven apps. Aaaand.. we don’t have to pay an additional license, because it’s not a new app, but only a page inside the model-driven app. Now we’ll be able to accomplish a lot of scenarios we were struggling before. One of them is how to make a Dialog in model-driven apps.

How to make a Custom Page

First of all, let’s make a model-driven app. The best way to start is from a Solution.

We’ll use the new maker-portal, using the https://make.preview.powerapps.com. We’ll make a model-driven app and edit it using the new maker experience:

There we create a new Page of type Custom:

There we can create a new page, or choose an existing:

Choosing “Add” will navigate us to the page designer, where we have a very similar experience with a Canvas Page designer.

We can also add an existing Custom Page to the App:

Editing a Custom Page

The process of editing a CustomPage can be started through the App designer (like above). An existing Custom Page can be also edited from the Solution, directly choosing the Page.

Since we are making a page for a model-driven app, we need to consider making it responsive, just like the rest of the app. That’s why I’ve used a vertical container, and inside it, four horizontal containers, for each control or button row.

The purpose for my dialog is to generate a task for an account, allowing me to choose some input data (like subject, priority or due date). This dialog is just an example of what we can do with Custom Pages. Later we can extend the example, and autogenerate tasks for all selected accounts from a list. Of course, there might be a lot of interesting requirements, including choosing records from some other tables or even combine with PCFs. Everything is possible now.

Important: In order to try out the App, the page must be published, and after that the app must also be published . These two steps need to be made after every change in the page.

How to open a Custom Page as a Dialog

Let’s suppose that we have the CustomPage done, and we want to open it from inside an Account Form. For that I’ll use the SDK Xrm.Navigation.NavigateTo functionality (for now I’ll use the console, but this can be executed from the form scripting or from a ribbon button)

Here is the code used:

Xrm.Navigation.navigateTo({ 
   pageType: "custom", 
   name: "diana_dianamicsdialogpage_19356", 
   entityName: "account", 
   recordId: Xrm.Page.data.entity.getId()
}, {
   target:2, 
   width: 700, 
   height: 400
})
.then(console.log)
.catch(console.error)

There are two important parameters here:

  • “pageType”: “custom”. The navigateTo has a new pageType now. Before CustomPages we could use the pageType with the values: “entitylist”, “entityrecord”, “webresource”, “dashboard”. Now the docs include also the “custom” type.
  • name: “diana_dianamicsdialogpage_19356”. The name of the CustomPage can be found in the customizations:

Tipp: I wasn’t able to copy the name from there. In order to copy it, I’ve used the Solution Layer. There we can find the name and copy it

Please note that Xrm.Page.* is deprecated and shouldn’t be used in production. This is just a fast console snippet for demo reasons. If you need an example to open a dialog from the new commanding (inside a form) please scroll down to “Calling the Custom Page Dialog from the Ribbon (Commanding)”

I could have used the same dialog by opening it on the side. I only need to pass the “position: 2” as navigationOptions, together with target and width. But for my very few fields, is not the best experience:

Implementing the action

When I want to open a dialog, I need to do something with the data. I am not aware of a way to pass the data from the dialog back to the opener function. So the generation of the task will take place directly inside the CustomPage. Using low-code.

Grabing the recordId

First we need to be able to read the recordId, the accountId where the dialog was opened. For that we go in App, OnStart and grab there the Param “recordId” and save it in a variable: RecordId.

I’ve struggled for a while to get the Lookup for the account. The RecordId variable was looking good, but the RecordItem was always null. Until, I’ve noticed that I need to remove the curly brackets before calling the GUID function. In case you know a better way, please let me know.

Set(RecordId, Substitute(Substitute(Param("recordId"), "{", ""), "}",""));
Set(RecordItem, 
 LookUp(Accounts, Account = GUID(RecordId))
 )

Actually the reason I need the account lookup, is because i must use a “Patch”, where the account will be used as a “Regarding”. If you know a way to make the “Patch” containing the Regarding directly using the RecordId, so without using the Lookup function, please, please let me know,

The buttons

For the “Cancel” button I only need to navigate back.

The “Generate” button need to make a “Patch”, in order to create the task.

Patch(Tasks, {Subject: InputControlSubject.Value, Regarding: RecordItem, 'Due Date': InputControlDatePicker.Value });
Notify("Task created");
//Navigate(RecordItem);
Back()

For now I’ve only passed the subject, the due date and the regarding account. I show a notification (which unfortunately is shown only inside the dialog, which closes automatically, so we basically don’t see it), and then I close the dialog by using “Back()”.

In case the dialog closes, and you don’t see the task generated in your account timeline, you could try to use

Navigate(RecordItem)

instead of the “Back()” command. This will reopen the underlying record, so it forces it to do a full refresh.

Calling the Custom Page Dialog from the Ribbon (Commanding)

To call the Custom Page from the new (Ribbon) Commading, we need some similar javascript code, based on Xrm.Navigation.navigateTo. For now we don’t have a low-code way to navigate modal-dialogs.

The way we can use javascript to open Custom Pages is documented now in the docs.

The only detail we need to adapt, is how to pass the id to the custom page. So first you need define a javascript file, and pass the needed parameters. If we are on the form, we need to pass the PrimaryControl.

I have the library “diana_opendialog.js”, and my function is called openCustomPage. I pass the PrimaryControl to my function. And the javascript should look something like this (notice the pageContext parameter, and using it fpor entityName and recordId):

function openCustomPage(pageContext){
   Xrm.Navigation.navigateTo({ 
      pageType: "custom",  
      name: "diana_custompagespcfpage_e9ab3",  
      entityName: pageContext.data.entity.getEntityName(),  
      recordId: pageContext.data.entity.getId()
   }, {
   target:2, 
   width: 700, 
   height:400}
   )
  .then(console.log).catch(console.error);
}

[Edit] Getting rid of the “Leave this page?” warning

To close the dialog you can use “Back()” or “Navigate()” functions. In some cases, Back() is enough, but it seems that the data is not always refreshed on the form. If you try to use “Navigate”, that will work, but you will get that “Leave this page?” dialog (which you don’t want to see):

Thanks to Thomas van der Waard‘s comment on this blog post, we have a solution for this now:

  • inside the CustomPage use “Back()” to close the dialog
  • change the scripts for opening the dialog, and refresh the form after the dialog is closed.

It should look like this:

function openCustomPage(pageContext){
   Xrm.Navigation.navigateTo({ 
      pageType: "custom",  
      name: "diana_custompagespcfpage_e9ab3",  
      entityName: pageContext.data.entity.getEntityName(),  
      recordId: pageContext.data.entity.getId()
   }, {
   target:2, 
   width: 700, 
   height:400}
   )
.then(function(){
    pageContext.data.refresh();
})
.catch(console.error);
}

We use here the data.refresh sdk. Depending on the content you need to refresh, you could need to use other sdk refresh methods.

Conslusion

I have on older blog where I has happy to find a workarround to transform form into dialogs. Now we can forget all that tricky ways. Finally, we have a way to design dialogs provided by the platform.

But using the CustomPages as dialogs is only the tip of the iceberg. The Converged App will offer so much flexibility. I’m really looking forward to apply the new possibilities. A game changer!

The Custom Pages docs have a lot more details: have a look here.

23 thoughts on “How To Make Dialogs For Model-Driven Apps Using Custom Pages

Add yours

  1. Does it allow to send back the information to parent page. In my last project, I used Widnow.sessionStorage object to store the info and pushing back to the parent page.

    1. Unfortunatelly until now there is no supported way to pass data back. One approach could be to execute all the code using low-code or PCFs inside the CustomPage. After that we can use “Back()” or one of the Navigation methods, which would refresh the opener window-context.
      There is also a workarround for the case where the CustomPage is saving some data somewhere. In that case we can read that data from the Dataverse, after the dialog was closed, and get that way a kind of response.

      In my opinion the sessionStorage approach is not really safe. One problem is that there could be more browser tabs opened, which shares the same sessionStorage, where the same CustomPage might be opened. So there must be a safe way to know which sessionStorage key was set for this instance of the CustomPage.
      Also, the PCF docs says not to rely on sessionStoarage. I use it for caching purposes, but if it’s not available, the application should still work, so I wouldn’t rely on it. Here the docs: https://docs.microsoft.com/en-us/powerapps/developer/component-framework/code-components-best-practices?WT.mc_id=BA-MVP-5004107#avoid-use-of-web-storage-objects
      I really hope Microsoft will provide in the future a way to pass the data back from the dialog.

  2. This is a great article, but you say “Later we can extend the example, and autogenerate tasks for all selected accounts from a list.”

    That’s exactly what I need to do, but I can’t see any way of passing multiple record IDs into my custom page, only a single record ID. Do you think this is one for the future or is there a way to do it now?

    1. Hi Len,
      Unfortunately right now it doesn’t really work., but I’m pretty sure this is on the roadmap.

      Until we get a better way to pass more parameters, you could use the workaround Mehdi showed us (instead if recordId pass a JSON as a string with whatever parameres you need): https://xrmtricks.com/2021/10/25/how-to-pass-an-object-from-a-model-driven-to-a-custom-page/
      Of yourse, passing the list with recordIds instead of JSON should work too. The trick is to use the parameter “recordId”.

      1. After writing I tried something and it actually did what I wanted. I created a button using the new Command Bar editor (preview) and created a parameter for SelectedRecordIds – this adds multiple Ids as a comma separated string. In my custom page I then set that as a variable on start, and have a button that calls an instant flow, passing in the list of IDs. The flow then iterates through them and does a dataverse update on each record.

    2. Thank you for this article Dianna , very informative.

      I’ve also worked out the same as Len, to build a solution for resolving multiple cases. This really does open up lots of possibilities.

  3. I wish there was a way to launch the dialog canvas app based upon a screen condition, for example, if a checkbox yes/no was = to yes the dialog would pop up. I feel like having to always tie it to a button limits the use cases.

      1. Hi Dian, Thank you so much for your feedback. I did exactly as you recommended and it works perfect. I am relatively new to Dynamics and Model apps and mostly come from the canvas app camp so the scripting and javascript side is new to me however I see so much potential as these two platforms continue to converge. Thanks again!

  4. Hi Diana, Curious if you have ever tried passing more than one parameter to the canvas app page? I need a secondary param to pass to the canvas app startscreen providing screen navigation. I can certainly get this value by performing a lookup on the main ID param however that defeats the purpose of the benefits of the new startscreen functionality within the canvas app which provides great performance benefits.

  5. Hey Diana! Thank you for sharing this great article! However, I have a question, maybe you can help me out? When using the `Navigate(RecordItem)` a new confirmation dialog appears to ensure I’m leaving the page. Could this be avoided?
    https://imgur.com/a/ARnNMAx

    1. Hi Thomas,
      I saw that problem too.
      In case you can use Back() instead of Navigate() you won’t get that message. But in case you need the navigate, I’m npt sure if there is a solution.
      Have you tried to save the page/form, before you open the dialog?

      1. I found a solution.

        I added a refresh in the JS on the success callback of the navigateTo function:

        Xrm.Navigation.navigateTo(pageInput, navigationOptions)
        .then(
        function () {
        Xrm.Page.data.refresh();
        }
        ).catch(
        function (error) {
        // Handle error
        }
        );

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Website Powered by WordPress.com.

Up ↑

%d bloggers like this: