Power Pages Multistep forms Deep Dive (Part 2)
In the last blogpost I explained how to get started with multi-step forms in Power Pages, in this blogpost I will continue explaining the details of working with multi-step forms.
Working with Power Pages is in some ways similar to working with Model Driven Apps, you will have forms and subgrids within those forms where you can create, edit and delete records, so I will start by explaining how to add subgrids to a Multi Step form.
Adding a Subgrid
As I explained in the last blogpost one of the first steps when working with Power Pages is to create the forms in your Dataverse solution, and a recommendation is to set a prefix in the name like ‘Web Form – ’ because then you will easily recognise the froms of the Power Pages Management Model Driven App.
So after creating the form you can add a sub-grid as shown in the following image:
In my case I have added two sub-grids, one to show the last year’s data and one to show the current year’s data.
So after that you can go to your multi-step form, open the step where you want to add those two sub-grids, go to the Metadata tab and click on ‘New Multi-step Form Metadata’:
Then you select subgrid in the type of control and then you select the subgrid you added in your table form:
Then you can save the record and that would be it, but you can also add the create, edit or delete buttons to this sub-grid, but note that when adding those buttons you will have to specify which basic form will be used to create a record, which basic form will be used to edit a record:
And since we don’t have them yet, we can save the metadata record for now and come back later.
Now let’s create the basic forms.
To better clarify what a basic form is, think of the forms you use in Model Driven Apps, those are data-driven forms that allow users to add new records or update existing records in Dataverse, well in Power Pages we have Basic Forms to do exactly the same function.
Creating a Basic Form
In the Power Pages Management Model Driven App sitemap you have an option called Basic Forms, go there and click on new, you will have to set the name of the basic form, but the most important thing is that you have to select the table, then the form of that table and finally the tab of that form (this last one is optional).
You will have to set whether the form is for insert or for edit:
Click save and that is for the basic insert form.
When you create an Edit Basic Form, it is more or less the same with a difference that you have to set the Mode as Edit and you have to set the Record ID Parameter Name as id:
Click save and that’s it for the edit form.
You can now go back to your MultiStep form, open the step where you added the subgrid and you can now specify for the create button which basic form you are using to create and for the edit button which Basic Form you are using to edit:
Testing the Subgrid and Basic Form
Before testing, remember to purge the cache in the admin centre, after that go to your Power Pages website, start advancing in the Multistep form and when you reach the step with the sub-grid you will see a Create button, click there and a pop-up form will appear to create the record:
Web Template to retrieve Data
The next step I consider essential in any Power Pages project, which is to retrieve data to be used in the web pages, this data can be anything like configuration records or historical data or data to automatically fill in some fields in a basic form or multi-step form.
In this case I am retrieving data from the last fiscal year because I want to make a comparison of currency amounts between last year and current year, so if the result of the comparison is 10% above or below I want to show a pop-up asking for a comment so the user has to explain why the difference.
So in the sitemap there is an option called Web Templates, go there and click on new, set a name for the web template:
After that you can go to Power Pages Studio and click in Edit Code button, so Visual Studio Code will opened to edit the code:
In Visual Studio Code go to web templates and select the web template you just created, there you can add FetchXML between the Liquid tags:
This is very useful because you can use the preview find your model driven app to build the search you want and then export the FetchXML file or you could even use the FetchXML Builder of the XRM Toolbox to build even more complex queries.
The next thing is to iterate the result of the FetchXML, in this case it is a single record that I am retrieving, so the idea is inside the for loop, create html div that will contain the values of each retrieved field and mark them as hidden:
In the image above you can see that I’m also using Liquid to access each field I specified in the FetchXML.
Now we are going to call the web template from the web page, so go to your web page, open the content page:
Add JavaScript code to a Basic Form
Now the final step is to add the JavaScript code that will do the comparison with last year data, for that I have to mention that when the user is creating a new record in the subgrid, a new form will appear (which is the basic creation form) and it is in that form that I want to do the comparison, so Whitin Visual Studio Code, let’s go to that basic form and add the code:
Here is the code:
// Reusable function to set
up comparison and modal logic
function setupComparison(inputId, labelId, lastYearDivId) {
const inputField = document.getElementById(inputId);
const labelElement = document.getElementById(labelId);
const lastYearDiv = parent.document.getElementById(lastYearDivId);
// Ensure
last year value is available
const lastYearValue = lastYearDiv ? parseFloat(lastYearDiv.textContent.replace(/,/g, ”)) : null;
if (lastYearValue === null || isNaN(lastYearValue)) {
console.error(`Last year
value for ${lastYearDivId} is invalid or not found.`);
return;
}
// Get the
updated value
const currentValue = parseFloat(inputField.value.replace(/,/g, ”));
// Calculate
the percentage difference
const percentageDifference = ((currentValue – lastYearValue) / lastYearValue) * 100;
// Check if
the difference exceeds 10% (above or below)
if (Math.abs(percentageDifference) > 10) {
showModal(percentageDifference, labelElement);
}
}
// Function to set the
comment
function SetComment(percentageDifference, userComment, labelElement)
{
var commentTextarea = document.getElementById(“mac_comment”);
// Get the
current innerHTML content
const currentContent = commentTextarea.value;
// Get the
current date
const currentDate = new Date().toLocaleString();
// Get the
label content
const labelContent = labelElement ? labelElement.textContent : “Unknown Label”;
// Append the
new content
const newContent = `${currentContent}${currentDate} – ${labelContent} – Percentage Difference: ${percentageDifference.toFixed(2)}% – Comment: ${userComment}`.trim();
// Update the
textarea content
commentTextarea.value = newContent;
// Optionally
trigger an onchange event
commentTextarea.dispatchEvent(new Event(“change”));
}
// Function to create and
show the modal dialog
function showModal(percentageDifference, labelElement) {
// Create the
overlay
const overlay = document.createElement(“div”);
overlay.id = “modalOverlay”;
overlay.style.position = “fixed”;
overlay.style.top = “0”;
overlay.style.left = “0”;
overlay.style.width = “100%”;
overlay.style.height = “100%”;
overlay.style.backgroundColor = “rgba(0,
0, 0, 0.5)”; // Transparent gray
overlay.style.zIndex = “999”;
// Create the
modal structure
const modal = document.createElement(“div”);
modal.id = “commentModal”;
modal.style.position = “fixed”;
modal.style.top = “50%”;
modal.style.left = “50%”;
modal.style.transform = “translate(-50%,
-50%)”;
modal.style.zIndex = “1000”;
modal.style.backgroundColor = “#fff”;
modal.style.padding = “20px”;
modal.style.boxShadow = “0 0
10px rgba(0,0,0,0.5)”;
modal.style.borderRadius = “8px”;
modal.style.width = “400px”;
// Add modal
content
modal.innerHTML = `
<h3>Provide a Comment</h3>
<p>The current value deviates by more than 10% compared to
last year’s value. Please provide a comment:</p>
<textarea id=”commentTextArea” rows=”4″
cols=”40″ placeholder=”Enter your comment
here”></textarea>
<br><br>
<button id=”submitComment”>Submit</button>
`;
// Append
overlay and modal to the body
document.body.appendChild(overlay);
document.body.appendChild(modal);
// Add event
listener for the Submit button
document.getElementById(“submitComment”).addEventListener(“click”, function () {
const userComment = document.getElementById(“commentTextArea”).value;
if (userComment.trim()) {
// Call the SetComment function
SetComment(percentageDifference, userComment, labelElement);
// Close the modal and overlay
document.body.removeChild(modal);
document.body.removeChild(overlay);
} else {
alert(“Please enter a
comment.”);
}
});
}
// Helper function to check
if a last year value is valid and call setupComparison
function trySetupComparison(inputId, labelId, lastYearDivId) {
const lastYearDiv = parent.document.getElementById(lastYearDivId);
const lastYearValue = lastYearDiv ? lastYearDiv.textContent.trim() : null;
if (lastYearValue) {
setupComparison(inputId, labelId, lastYearDivId);
} else {
console.warn(`Skipping
setup for ${lastYearDivId} as it has no value.`);
}
}
// Function to attach the
event listeners and handle value changes
function initializeComparisonSetup() {
const fields = [
{
inputId: “mac_field1”,
labelId: “mac_field1_label”,
lastYearDivId: “lastyear_mac_field1”
},
{
inputId: “mac_field2”,
labelId: “mac_field2_label”,
lastYearDivId: “lastyear_mac_field2”
},
{
inputId: “mac_field3”,
labelId: “mac_field3_label”,
lastYearDivId: “lastyear_mac_field3”
},
{
inputId: “mac_field4”,
labelId: “mac_field4_label”,
lastYearDivId: “lastyear_mac_field4”
},
{
inputId: “mac_field5”,
labelId: “mac_field5_label”,
lastYearDivId: “lastyear_mac_field5”
},
{
inputId: “mac_field6”,
labelId: “mac_field6_label”,
lastYearDivId: “lastyear_mac_field6”
},
{
inputId: “mac_field7”,
labelId: “mac_field7_label”,
lastYearDivId: “lastyear_mac_field7”
}
];
// Attach
change event listeners to each input field
fields.forEach(field => {
const inputElement = document.getElementById(field.inputId);
if (inputElement) {
inputElement.addEventListener(“change”, function () {
trySetupComparison(field.inputId, field.labelId, field.lastYearDivId);
});
} else {
console.warn(`Input field with ID ‘${field.inputId}‘ not found.`);
}
});
}
initializeComparisonSetup();
It all starts with the initializeComparisonSetup, which will take all the basic form fields and all the hidden divs retrieved by the web template and then add an event listener to each field of the basic form so that when the user changes any of these fields the code will do the comparison and if the result is 10% less or more it will show a pop-up asking for a comment, before testing don’t forget to purge the cache:
In the image above I entered an amount of 3 while last year’s amount was much higher than that, which is why I got the pop-up window asking for the message:
And if you continue with the rest of the fields, the comments will be added to the previous ones.
This is for now, in my next blogpost I will explain how to configure the web roles so that users can see only the data of their department and others can see all the data, stay tuned.
Have a nice day!


Hello there! Welcome to my blog!
My name is Wilmer Alcivar and I’m a Power Platform fan
I’d love to share my knowledge, so please do not hesitate to connect!





