Last time out, we built the foundation of a Power Apps dynamic form. This article will extend the form with some bespoke validation and cover how to save & retrieve data. I’ll also include building a separate experience for admins/line-of-business workers to manage the questions database.
All references in this article will assume you’ve seen part 1. If you’ve landed here at part 2 without first seeing part 1, you can find that here.
Table of Contents
ToggleValidation - field level
One thing that gets levelled at Power Apps dynamic forms is lack of validation. A form control has a lot of this baked in for us so how can we replicate?
My goal here is to highlight some of the ways in which we can provide the validation needed. We have to be a bit creative and work with the gallery that’s acting as our form. That means we’ll likely have a single label control that will act as the validation alert for any response, to any question.
This is quite custom and relatively Power Fx heavy so expect lots of code snippets! It’s not perfect by any means, but by the same token it’s also pretty cool what you can do with some imagination. Let’s get into some examples.
A common thing to validate in data entry are email addresses. Is the address entered in the correct format? Does it have all the right symbols in the right places?
“Easy, we can use the IsMatch function, then the predefined Match.Email pattern, right?”
You’d think, but it’s not always accurate. Microsoft have documented all predefined patterns (email included) here. However, I highly recommend you check out this article by Sancho Harker as to why Match.Email doesn’t quite work as you’d hope, and to use a Regex expression instead.
I read somewhere on my travels that the Regex syntax looks like a cat walking across your keyboard. Couldn’t agree more. But, it’s very very useful and especially for canvas apps & validating strings.
Config
A reminder that my Power Apps dynamic form is leveraged in a flexible height gallery. I’ve purposely added a ringfenced text input control to capture an email address, as it needs to run Power Fx that doesn’t apply to any other text-based response.
In the OnChange property of the control, we can extend the logic. We want to capture the users response and store it in the ResponseText column of the underlying collection, but also capture the output of the validation. We have another column in the collection for this, called ResponseError.
When the collection is built (again, see part 1), the ResponseError column defaults to ‘false’ for all questions. We want to keep this as false if the user provides a correctly formatted email address. However, we want to update this to true if the email address submitted isn’t in the correct format.
Here’s what the Power Fx looks like in the OnChange property for our email-based text input control:
With(
{
/*
Does text match pattern for an email address
ResponseError column wants true for errors, so need to reverse
the output of the IsMatch
*/
tvEmailFormat: !IsMatch(
Self.Text,
"(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*)@{1}(?:([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9]{2,})"
)
},
UpdateIf(
colUserResponse,
// Capture response and whether it's passed the above check
questionorder = ThisItem.questionorder,
{
ResponseText: Self.Text,
ResponseError: tvEmailFormat
}
)
);
We can see below that the ResponseError column will update to true if the format doesn’t match:
Later on in this article, we can count the total number of rows in the collection where ResponseError equals true. We can ensure our users can’t submit any data until addressing these.
Pattern match
Another validation requirement may be to ensure what the user has entered follows a particular pattern.
My example for the Power Apps dynamic form is getting a new car insurance quotation. One of the key pieces of information will be the registration number of the car. UK registrations can change slightly in format but for this example, lets say we need to match the pattern of the image below:
We want to ensure the user adds in the registration in that format, which is:
Letter, Letter, Number, Number, Space, Letter, Letter, Letter.
Config
For this example, I’ve purposely added the bespoke Power Fx to the main text input control in the gallery:
If you have a single control with lots of If statements for different validation, maybe it can get a bit chaotic. If there is a bespoke need for validation, I’ll tend to dedicate a separate control for that column so I can isolate the Power Fx. There’s no right or wrong way of doing these kind of things, just preference. That said, a single control with LOTS of If statements might get a bit unruly.
In our example, the OnChange property of the main text input control already has the UpdateIf function running. This captures the users response to any question that requires a free text response:
/*
Save user response to question against relevant
row in collection. Ensure response is saved in
relevant response column (Text, Number or Date)
*/
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseText: Self.Text}
);
This single text input control in our gallery could be for any question. So to validate one specific question & response, we’d need to add some additional code to the OnChange property. This will only run if the question is the one specified. Much like the email validation, the ResponseError column will capture the output of the format of the vehicle registration that the user enters:
// If question relates to Vehicle reg, perform matching check
If(
ThisItem.question = "What's the registration of your vehicle?",
With(
{
/*
Determine if Registration entered matches the specified pattern
ResponseError column wants trye for errors, so need to reverse
the output of the IsMatch
*/
tvRegFormatCheck: !IsMatch(
Self.Text,
Match.Letter & Match.Letter & Match.Digit & Match.Digit & Match.Space & Match.Letter & Match.Letter & Match.Letter
)
},
// Update ResponseError column with true or false
UpdateIf(
colUserResponse,
questionorder = ThisItem.questionorder,
{ResponseError: tvRegFormatCheck}
)
)
)
Whilst this provides the required validation, note that the If statement at the top is looking for hardcoded text. What if someone changes the wording of the question? This validation would break. It might be better to find the specific line ID / unique identifier of the question instead.
This is another reason why, if there’s some bespoke validation needed, I’ll use a dedicated control (like with the Email validation) and only show that control for the specific question. None the less, we can choose to use the single control & layer the OnChange with lots of If / Switch logic if you’d prefer. I wanted to show both approaches so you can determine the pros and cons for your scenarios.
Notifying the user of errors
If you try to submit data in a form control and there’s errors, you’ll see some red text appear under the relevant field. This will give you a custom message (that uses the Validate function) relevant to the question. You’ll see a dedicated label control for each column you add to a form control:
Ok, so how do we replicate this in our Power Apps dynamic form?
Remember we are using a gallery for our form, so we can add a single control that will show or hide multiple times. I’ve added a label control to the gallery. This only needs to show if the ResponseError column for the question is true, so we can set the Visible property of the label to reflect that:
/*
Only shows if boolean value in ResponseError column is true, as this
indicates an issue with the users response to the relevant question
*/
ThisItem.ResponseError
Config
The Text property of the label is where we want to write our custom messages. These will be displayed to the user real-time, as will be triggered by any relevant OnChange event.
In the below snippet, we are catering for the following validations:
1- Person is under the legal driving age.
2- Person is over the legal driving age (this isn’t actually a thing, I just did it for demo purposes).
3- Email address entered isn’t in the correct format.
4- Car registration isn’t in the correct format.
If(
// Is calculated age below the legal driving limit
ThisItem.question = "Age" && Value(ThisItem.ResponseNumber) < ThisItem.minlength,
"Person is too young to drive in the UK.",
// Is calculated age above the legal driving limit
ThisItem.question = "Age" && Value(ThisItem.ResponseNumber) > ThisItem.maxlength,
"Person is too old to drive in the UK.",
// If email address entered isn't in the correct format
ThisItem.answertype = "Email" && !IsMatch(
ThisItem.ResponseText,
"(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*)@{1}(?:([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9]{2,})"
),
"Email address provided isn't in the correct format.",
// If vehicle registration entered isn't in the correct format
ThisItem.question = "What's the registration of your vehicle?" && !IsMatch(
ThisItem.ResponseText,
Match.Letter & Match.Letter & Match.Digit & Match.Digit &
Match.Space & Match.Letter & Match.Letter & Match.Letter
),
"Unrecognised UK car registration format."
)
Here is our Power Apps dynamic form. The single label control appears multiple times, each with its own custom error message when the ResponseError column for the relevant question is true:
As I mentioned earlier, building logic with hard coded text values might be risky. Maybe individual label controls housing each granular bit of logic might be better? You could even store the error messages within the underlying Question Data. I do like the FULL control on what logic to check and what’s presented; I’ve covered email & matching specific patterns but could also build in other checks for different patterns, number thresholds and more.
Form controls do a lot of this for us automatically, but those larger form scenarios aren’t great for the number of controls & limited customisations available. With Power Apps dynamic forms, we can cater for those bigger scenarios by reducing the number of controls and making a performant experience. Unfortunately, the error logic then needs to be custom, but it CAN be done.
I suspect it can also be done more elegantly too. If you’ve any suggestions, please leave a comment below or reach out!
Validation - high level
In the examples above, we looked at field level validation. We can also ensure high level validation takes place to ensure incomplete, or inaccurate data is saved.
In our app, there’s a button for users to submit their response:
This button should only be clickable if our custom requirements have been met, which are:
1- all mandatory fields are completed.
2- any choice based questions aren’t on the default of ‘Please select’.
3- there aren’t any questions where the ResponseError column equals true.
For requirement 1, we have x3 response fields in the collection, one for dates, one for numbers and another for text. We need to check if the field is mandatory but all of these response fields are empty. Requirement 2 could be captured in no 1, but it’s there to be on the safe side. Requirement 3 supports the item-level validation covered earlier in this article.
Here’s those requirements represented in the DisplayMode property of the Save Quote button:
If(
Or(
// Question is mandatory but no response entered
CountRows(
Filter(
colUserResponse,
questionmandatory = "Yes"
&& IsBlank(ResponseDate)
&& IsBlank(ResponseNumber)
&& IsBlank(ResponseText))) > 0,
// Response is Choice but no response selected
CountRows(
Filter(
colUserResponse,
answertype = "Choice" && (ResponseText = "Please select" || IsBlank(ResponseText)
))
),
// At least one error currently present
CountRows(
Filter(
colUserResponse,
ResponseError
)) > 0
),
DisplayMode.Disabled,
DisplayMode.Edit
)
You can run as many checks here as you need, the ones captured above are just examples – albeit ones that are likely to be most common.
Saving a response
In part 1, I covered a table called User Responses where I’ll be saving the data. This is a very simple table to store the name of the person generating the quote, their email address and the responses to all the questions.
For the latter, we are going to convert the underlying collection into JSON and store that in a multiple line of text column. This data will also include all the question config (ie the order, what response type each question is, min & max number ranges). For more information on the JSON technique, check out this article on different ways to patch multiple rows of data. It’s very powerful and what I default to for most Power Apps dynamic form builds.
To save a brand new response, we’ll use the patch function to create a single row. The collection with all the questions & answers is converted into JSON and stored in the multiple line of text column:
// Create new row in User Response tbl
Patch(
'User Responses',
Defaults('User Responses'),
{
// Unique response ID
responseid: First(colUserResponse).ResponseID,
// Get first & surname of person
respondername:
$"{LookUp(colUserResponse, question = "First name").ResponseText} {LookUp(colUserResponse, question = "Surname").ResponseText}",
// Their email address
responderemail:
LookUp(colUserResponse,question = "Email address").ResponseText,
// convert all questions & responses to JSON
responsetext: JSON(colUserResponse,JSONFormat.IndentFour)
}
)
Editing a Response
I’ve added a gallery to the app. This will show at a high-level all previous submissions:
To edit a response, we need to convert the JSON string back into the collection used in the gallery. When a user clicks on the edit icon next to an entry, the ParseJSON function needs to do the work. The Dataverse row ID of the record also needs capturing as we’ll need it for patching the changes back to the User Response table.
Here’s what the Power Fx looks like in the OnSelect property of the edit icon:
// Store response id of selected record
UpdateContext({cvRecordID: ThisItem.'User Response'});
// Rebuild colUserResponse collection using previously saved JSON string
ClearCollect(
colUserResponse,
ForAll(
ParseJSON(ThisItem.responseText),
{
section: Text(ThisRecord.section),
question: Text(ThisRecord.question),
questionorder: Value(ThisRecord.questionorder),
questionmandatory: Text(ThisRecord.questionmandatory),
questionstate: Text(ThisRecord.questionstate),
answertype: Text(ThisRecord.answertype),
minlength: Value(ThisRecord.minlength),
maxlength: Value(ThisRecord.maxlength),
choicesets: Text(ThisRecord.choicesets),
ResponseID: Text(ThisRecord.ResponseID),
ResponseText: Text(ThisRecord.ResponseText),
ResponseNumber: Text(ThisRecord.ResponseNumber),
ResponseDate: Text(ThisRecord.ResponseDate)
}
)
);
The response is then ready to edit, using the same gallery, functionality & validation previously configured:
Any changes will need to be saved. We’ll use the patch function again, but this time target the specific row in the User Response table to update. This is why we captured the unique row ID as a context variable:
// Update new row in User Response tbl
Patch(
'User Responses',
// Specific row to update
{'User Response': cvRecordID},
{
// Get first & surname of person
respondername:
$"{LookUp(colUserResponse, question = "First name").ResponseText} {LookUp(colUserResponse, question = "Surname").ResponseText}",
// Their email address
responderemail:
LookUp(colUserResponse,question = "Email address").ResponseText,
// convert all questions & responses to JSON
responsetext: JSON(colUserResponse,JSONFormat.IndentFour)
}
)
The admin experience
The final part of our Power Apps dynamic form experience to cover is the ‘admin’ experience. By that, I mean a dedicated area where our carefully selected colleagues can manage the Question Data table. We’d like to provide them some easy abilities to:
1- add new questions.
2- edit existing questions.
3- change the display order of questions.
I’ve worked on a number of large builds where this can take the shape of a custom page, a model-driven app or a canvas app. I’ll use a canvas for my example. This is all optional too; you can completely negate this and stick to performing all those actions in your development environment for the solution. This is simply a value-add, to make small tweaks easily accessible without having to edit the app or adding columns to a data source.
The logic is mostly simple and uses a form control! In this instance, the Question Data table will remain static in terms of columns. We are also capturing a small amount of data that’s unlikely to grow in size.
Add/update questions
For the most part, I’m using typical functionality that comes with a form control.
In the OnSelect property of the plus icon, the form will be ready to accept a new entry:
// Form mode = new to accept a new record
NewForm(frmMyFormControl)
In the OnSelect property of the edit icon for each row in the left-hand gallery, the form will be ready to edit the selected entry:
// Form mode = edit to edit an existing entry
EditForm(frmMyFormControl)
The OnSelect property of the ‘Save Question’ button will save the form – either to capture a new question or save edits to an existing one:
// Submit form
SubmitForm(frmMyFormControl)
The slightly more complex part is providing ability to reorder questions. If we want to move a question down, we need to automatically move the question below up by one to compensate. If we want to move a question up, the question above needs to go down by one to compensate.
Changing question order
This is a bit of fun to round off this two-part blog series. I have actually delivered this a couple of times too so this will serve as documenting it for my own memory too.
Rather than selecting an item and changing the order via a form control, we can do so with arrows. We can configure the OnSelect properties of the arrows to handle movement of rows. This will update the integer values in the Question Order column of our question data.
If we update First Name from question 1 to question 2, we also need to update Middle Name from 2 to 1. We can get creative with Power Fx to make sure a single click of an arrow with update both rows at the same time.
As you can see from the image above, we can also show or hide the arrows when needed. We don’t want the UP icon to show for the top line, else users might change the number to 0 or even a minus number. So we can set the Visible property of that icon to:
ThisItem.QuestionOrder > 1
Similarly, we can add the following to the DOWN icon to ensure it’s not visible on the last row:
ThisItem.QuestionOrder < Max('Question Data', QuestionOrder)
The OnSelect properties for each icon is a little more in-depth. For the DOWN icon, we want the question to go backwards, so a 2 needs to become 1. At the same time, the previous row then needs to move forwards so a 1 then becomes a 2.
For this, I’ll use the With function to store the two Dataverse row ID’s that I need, then Patch both rows at the same time with their new values for the Question Order column:
With(
{
// Get unique row id of current and previous row
tvCurrentRow: ThisItem.'Question Data',
tvPreviousRow: LookUp('Question Data',QuestionOrder = ThisItem.QuestionOrder - 1).'Question Data'
},
Concurrent(
// Decrement current row by 1
Patch(
'Question Data',
{'Question Data': tvCurrentRow},
{QuestionOrder: ThisItem.QuestionOrder - 1}
),
// Increment previous row by 1
Patch(
'Question Data',
{'Question Data': tvPreviousRow},
{QuestionOrder: ThisItem.QuestionOrder}
)
)
)
The DOWN icon also needs similar logic for its OnSelect property. Moving a question down means the current it’s Question Order value needs to increase by 1. That also means the next question in the list needs to move back a place.
With(
{
// Get unique row id of current row and next row
tvCurrentRow: ThisItem.'Question Data',
tvNextRow: LookUp('Question Data',QuestionOrder = ThisItem.QuestionOrder + 1).'Question Data'
},
Concurrent(
// Decrement next row by 1
Patch(
'Question Data',
{'Question Data': tvNextRow},
{QuestionOrder: ThisItem.QuestionOrder}
),
// Increment current row by 1
Patch(
'Question Data',
{'Question Data': tvCurrentRow},
{QuestionOrder: ThisItem.QuestionOrder + 1}
)
)
)
This is the end goal:
The primary purpose for things like this is to enable easy changes to config without having to edit or republish the app. Yes, validation for a new column would have to be written in, but simple changes to wording or order can be done outside of the design studio.
Conclusion
Power Apps Dynamic forms is a concept commonly used in recent years, especially for canvas apps on top of SharePoint. It enables us to transpose lists with 100+ columns into a smaller, sleeker & more manageable approach.
They won’t fit every scenario you face, but either will using the native form control or manually adding controls to a screen. The purpose of the two articles has been to display the art of the possible. The important thing is to know all the tools & techniques at your disposal, so you can make the most informed choice to support data capture requirements.
That brings a close to the majority of my Power Apps dynamic forms logic, end to end. I appreciate some of the concepts might be difficult to digest, so have made a solution available for you to download and play with. It’s unmanaged, meaning you can open in edit mode to see the underlying configuration. If you have any suggestions for improvements, please let me know!
If you liked this article and want to receive more helpful tips about Power Platform every week, don’t forget to subscribe or follow me on socials 😊
I was not able to find the attached sample file
Hi Adolfo,
Thanks for letting me know. They’ve recently migrated the Power Platform forums and it must have removed attached files.
I’ve updated the link in the last paragraph of the post to point directly to the PP samples instead:
https://adoption.microsoft.com/en-us/sample-solution-gallery/sample/pnp-powerplatform-samples-dynamic-forms/
You should be able to download the solution example from there!
Nice Post! Thank,
Hi, this is a fantastic resource that I have been following to build a dynamic input for >100 questions.
One question – is it possible to add a checkbox style control allowing multiple inputs?
Hi Dan, a checkbox is simply a true or false output, if you wanted multiple then might be better to use a radio control?