Creating a Dynamic Text Interpolator in PowerFx ??
Edit 11/07/2023 - It was brought to my attention that an error existed in the example code for the interpolator which used an incomplete version of the Regular Expression statement. This has now been rectified ??
I'm in the process of doing a write up on Component Actions which follows my previous article on Functions, but I wanted to take a different steer today and discuss a solution I put together.
You may or may not already know about String Inteprolation in Canvas Apps. It's a way to mask variables and values within a text value itself. It's really handy to help make your code more readable; for example you could use:
$"{gvString}, {gvText}, {gvLike}, {gvThis}"
where you would otherwise use:
Concatenate("String","Text","Like","This")
or
"String" & "Text" & "Like" & "This"
Whilst this is great for text that's static in your app, you may come across requirements where you'd like an administrator to be able to edit this text without having to edit the app itself, which isn't a difficult problem in of itself until they also need to be able to refer to values in your app.
I had this very challenge recently, and this is how I solved it!
Planning
A problem like this requires some planning to ascertain how this will work exactly. We want to be able to do the following:
The first thing we would need to ascertain is what is our replacement tag? This is going to be an identifiable piece of text we can look for in our text block to replace with a value. I settled for #Tag# in this instance.
So let's think about some of the approaches we could take.
We have a block of text that looks like this:
"Hello #FirstName# #Surname#"
And a Table of Tags like this:
Table(
? ? {
? ? ? ? Tag: "#FirstName#",?
? ? ? ? Value: "Mike"},
....
)
So we'll try and replace any #tags# in the text with the corresponding Values in the Table.
And with this, we could try the following:
Trying Find() and Replace()
We could try the Find() function
Find("#FirstName#",txtInputString)
But we'd need to test every tag:
Concat(
ForAll(
? ? colTabs,
? ? Find(
ThisRecord.Tag,
txtInputString)),
ThisRecord.Value)
So Find() will return the Starting Locations as Numbers of these tags, we'd need to somehow replace them with the tag value.
But how do we iterate over every Find() value, run a Replace() and combine everything back together? What if our replacement text exceeds the length of the replacement tag? Would it overwrite our text? Will the code get too long and unmaintainable?
Let's try another approach:
Enter Regular Expression and MatchAll()
领英推荐
We can use Regular Expression with the MatchAll() function to describe the text we're looking for. I personally use regex101.com to write and; ultimately, diganose my Regular Expressions as I find it quick and easy to see where it's going wrong, and see useful information on characters I can use. It's super powerful and definietly a weapon to have in your arsenal.
We're going to combine a few functions here to do what we want:
The regular expression we're going to use is as follows:
(<[^>]+>|#\S+#|\S+(?:\s|$)|[.!,]\s*|\s)
And if this looks like a load of hieroglyphics to you, you're not alone! You can use Regex101.com to find out what this expession actually does (A full explaination is out of the scope of this article), and we can also test it's functionality out.
That. Looks. Perfect ??
And this isn't just matching whole words, it's also handling punctuation as well:
It also plays nice with HTML tags, for those using HTMLText Controls ??
Oh, and normal #HashTags are left as is too ??
The basis of what it does is:
The Tags Table
We mustn't forget that this solution will be useless unless we declare and use that Tags table. We can delcare a collection in our app like the following:
ClearCollect(
colTabs,
Table(
{
Tag: "#FirstName#",?
Value: gvUser.givenName
},
{
Tag: "#Surname#",?
Value: gvUser.surName
},
{
Tag: "#JobTitle#",?
Value: gvUser.jobTitle
},
{
Tag: "#Today#",
Value: Today()
}))
This Collection will need to be referenced when we use the formula below.
Let's get Matching!
So, combining this expression with MatchAll() will give us a table of words in our script. This is good as it breaks our problem down:
So now we have our text block broken down, and our replacement tags in a state that we can just do a direct replacement, we can reconstruct the above table, replacing tags as we go in a ForAll() statement by using LookUp() to match the FullMatch value in our table of tags, and Coalesce() to handle instances where our tags don't match.
Concat(
? ? ForAll(
? ? ? ? MatchAll(
? ? ? ? ? ? TextInput1.Text, //Get all matches on our RegEx
? ? ? ? ? ? "(<[^>]+>|#\S+#|\S+(?:\s|$)|[.!,]\s*|\s)") As Elements,
? ? ? ? Coalesce(? ? ? ? ? ? ? ?//Coalesce will either replace the tag if
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //it's found, or just insert the text we're evaluating
? ? ? ? LookUp(
? ? ? ? ? ? ? ? tags,
? ? ? ? ? ? ? ? ThisRecord.Tag = Elements.FullMatch
? ? ? ? ? ? ).Value,
? ? ? ? ? ? Elements.FullMatch
? ? ? ? )
? ? ),
? ? ThisRecord.Value? ? ? ?//Finally let's concat the resulting table from the ForAll statement,?
? ? ? ? ? ? ? ? ? ? ? ? ? ? //without any seperators
)? //Output as Text)
And when added into a Function, the end result should look something like this:
Implementation Tips
Looking to put this into action? Look at these tips below:
Next Time
Thanks for reading this article, I hope this helps you implement this functionality in your future apps.
Senior Technical Solutions Architect & Evangelist Power Platform & Dynamics 365 @ Fellowmind
1 年Thanks for sharing Mike. Actually we had about the same requirement some weeks ago and I managed to get it working without using regular expression. This is what I did. I put the input text with the placeholders (tags) in a collection which has always only one record with one column (named: Value) containing the text. In this example I gave the collection the name '_inputTextWithPlaceholders'. For the purpose of simplicity I also created a collection containing the key value pair list. This contains multiple records with the tag name in the Tag-column and the value of that tag in the Value-column. In this example I dynamically fill the values from the selected record from a persons gallery. Then with a combination of ForAll, Patch and Substitute, I replace the tags with the values. Works just as simple I think. So you see, several roads lead to Rome ??
Love it when I come across articles like this and find new and interesting ways to solve issues my apps!
Microsoft MVP, Power Platform Architect
1 年This is an awesome article mate, best one yet!