Creating a Dynamic Text Interpolator in PowerFx ??

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.

No alt text provided for this image


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:

  • Present text with replacement tags
  • Present values
  • Replace tags with values when presenting to the user.
  • User can edit the text and add replacement tags, which can be interpreted by the app and rendered correctly.

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:

  • MatchAll() - Will use our Regular Expression to find our tags
  • ForAll() - Will iterate through all our results to find our tags and do something
  • Coalesce() - Which will help us do the text substitution
  • Concat() - Is going to put everything back together

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.

No alt text provided for this image

That. Looks. Perfect ??

And this isn't just matching whole words, it's also handling punctuation as well:

No alt text provided for this image

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:

  • It finds all words with trailing spaces
  • It finds words encapsulated in #'s, and removes any trailing spaces
  • The trailing spaces from the words encapsulated in #'s are added as a match after the tags
  • Punctuation is added as it's own match.


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:

No alt text provided for this image

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:

No alt text provided for this image

Implementation Tips

Looking to put this into action? Look at these tips below:

  • Similar to variable naming conventions, give your tags meaningful names to help your users understand what they will display.
  • Define in advance what fields you want to present to users to be able to tag. Go beyond the basics and include extras, for example; the current date.
  • Make sure your users know what tags they can use. Display a list of them whilst they're editing scripts.
  • You can store the scripts in practically any data source, it may be worth storing a list of tags in a table for reference.
  • Will this be administered from a Model Driven App? Consider using a Custom Page to modify the scripts so you can implement the code above.


Next Time

  • We'll implement this with full editing capability in a Dataverse table with custom page.
  • We'll make this a function of a component in a library, so we can easily reuse the functionality.


Thanks for reading this article, I hope this helps you implement this functionality in your future apps.

Geert van Raaij

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!

Craig White

Microsoft MVP, Power Platform Architect

1 年

This is an awesome article mate, best one yet!

要查看或添加评论,请登录

Mike Gowland的更多文章

  • Documenting Components ??

    Documenting Components ??

    I've already covered a lot of aspects in my previous articles on how to add properties seamlessly to components, how to…

    4 条评论
  • Modern Theming is coming! Are your components ready???

    Modern Theming is coming! Are your components ready???

    I recently gave a session at the Microsoft Cloud (South Coast) User Group where I talked about Component Libraries and…

  • Components - Eliminate "Property Fatigue": Use a Properties Record!

    Components - Eliminate "Property Fatigue": Use a Properties Record!

    In my first ever article, I mentioned one of my component tricks of using a Properties record instead of individual…

    5 条评论
  • Using Events in Components ??

    Using Events in Components ??

    Events; one of the new Property Types of Component Libraries, are infact the new name for Behaviors. If you've used…

    3 条评论
  • Component Actions, what are they?!

    Component Actions, what are they?!

    In a previous article, we took a look at one of the new property types of components called Functions. Today we're…

    9 条评论
  • Components - Let's Make a Function!?

    Components - Let's Make a Function!?

    Continuing with this series on Components and Component Libraries, this article will go over how to write re-usable…

    2 条评论
  • Component Libraries - Lego-Blocking Canvas Apps ??

    Component Libraries - Lego-Blocking Canvas Apps ??

    If there's one thing I hate doing, it's the same job twice. Whether it's using an AutoHotKey Script to start my…

    2 条评论

社区洞察

其他会员也浏览了