Monday, May 10, 2021

HTML Tables and Message Formatting

 I wrestled with what to label this one but decided to just keep it simple.  There isn't a great and straightforward way to create message templates within PowerApps currently and may find yourself struggling w/ HTML vs. Adaptive Cards depending upon the types of messages.  However, this at least will jumpstart your ability to create more standardized HTML Tables to include in your messages w/ minimal customization on your part.

The thing I almost named this post was "A ForAll() and Concat() Use Case".  Then I remembered that I prefer simpler applied examples as starting points for larger concepts and renamed it.  Both of these functions annoy the heck out of me.  Not to mention that some of Microsoft's code on the ForAll() function simply is non-functional if copy/pasta'd into your own application.

So the problem I continue to be plagued with is a "best practice" for standardized messaging inside an organization.  I'm not just talking about the basics we're going to solve for here, but the larger "noise" issue within an organization.  

While I'm not going to solve the larger issue here, this at least will let you stop mucking about w/ formatting HTML Tables within PowerApps to let you quickly generate them from a Collection (2 actually) so you don't have to spend time messing around with it.

Setting up your Message

Peep the following:

ClearCollect( //This represents all of the headers along the top of the Table
    tableHeaders,
    [
        "Provider",
        "Specialties"
    ]
);
ClearCollect( //This is the data that will ultimately go into the Table
    itemsInTable,
    {
        provider: "MD Smith",
        specialties: "Something"
    },
    {
        provider: "MD Jones",
        specialties: "Something, Something Else"
    }
);
ClearCollect(  //Start assembling the HTML Table w/ the headers first
    htmlTableRows,
    {
        Value: "<tr>" & Concat(
            tableHeaders,
            "<th style='border:1px solid #dddddd;
  text-align: left;
  padding: 8px;'>" & Value & "</th>"
        ) & "</tr>"
    }
);
ForAll( //Add rows to the table one by one to alternate the background color
    itemsInTable,
    Collect(
        htmlTableRows,
        {
            Value: If( //If there is an odd # of rows in the Collection then change the background color
                Mod(
                    CountRows(htmlTableRows),
                    2
                ) = 1,
                "<tr style='background-color: #E0FFFF;'>",
                "<tr>"
            ) & "<td style='border:1px solid #dddddd;
  text-align: left;
  padding: 8px;'>" & ThisRecord.provider & "</td><td style='border:1px solid #dddddd;
  text-align: left;
  padding: 8px;'>" & ThisRecord.specialties & "</td></tr>"}
    )
);
Set( //Merge everything into a single HTML string value
    htmlTable,
   " <table>" & Concat(
        htmlTableRows,
        Value,
        ""
    ) & "</table>"
);

I've broken out the creation of the HTML into a few distinct areas in this code:
  1. Create a Collection of the Headers
  2. Create a Collection of what goes into the Table (although you probably will already have this created in your app
  3. Create a Collection of all the Rows in the Table (including headers)
  4. Shove all of this into a simple String value containing everything
Items 1,2, and 4 are fairly straightforward.  But 3 can/will trip you up if only just because of dealing w/ inline HTML formatting vs. more common Style sections within HTML documents.

Also note, I'm using the Concat() statement and ForAll() statements here in how I interpret they are actually intended.  While both are commonly able to replace each other, there are some distinct situations where one is notably better than the other.

Formatting your message in practice

I highly recommend that anyone thinking about doing an HTML message use the Rich Text Editor control in PowerApps to view/edit their message until it is close to what they want.  That at least will allow you to get angry at only one company (i.e. Microsoft) for when it doesn't work.  Once you've got your layout kind of where you want it, then assign another HTML Text field to display the value of what you've created within the Rich Text Editor control.  I've found that the Rich Text Editor does not reliably display HTML "sometimes" that the HTML Text control does.  

Your mileage may vary.

Once you have your formatting, then you'll need to edit the above code to reflect how you want things to look.  

Returning to my code snippet above, I've created a simple application to execute the above code and populate two fields: rawHTML and htmlVisualized.



The left-hand field (rawHTML) is a standard Label (plain text) that shows the actual HTML that is generated when the button is pressed.  The right-hand field (htmlVisualized) is an HTML Text field so it displays what we're hoping our users will see (perhaps in Teams, Outlook, etc.).  

NOTE: The background on each field is just changed by the Fill() property on the control vs. using HTML

If we edit my code above to include more lines in our Table, then they will alternate between a "clear" background and a pale blue (or green - I dunno as I'm partially colorblind).


This is what most people would expect when seeing a table of data w/ some mild variation between rows.  

How it is done

You might burn a lot of brain cells trying to figure out a best way to alternate rows.  You might go looking for ways to index items by function, or manually add an Index column to your collection.  However, if you use the ForAll() function, then it operates on each item in a Collection one at a time.  So if we take those values and append them to a different Collection (one at a time), then we're essentially creating a ready-made counter by using the CountRows() function on the 2nd Collection.

Here's the relevant snippet of my code from above:

If(//If there is an odd # of rows in the Collection then change the background color
                Mod(
                    CountRows(htmlTableRows),
                    2
                ) = 1,
                "<tr style='background-color: #E0FFFF;'>",
                "<tr>"
            )

What we're doing here is that we're using the Mod() function to detect even/odd rows.  In my example above, since we start by adding the Table Headers to the htmlTableRows Collection, when we get to the first row of data, it resolves to an odd number of rows and therefore makes the background a different color.


Could I have done this using a Sequence() function?  Definitely.  If you're so smart then go do it that way and stop wasting your time learning things from me!  :D

So how do I apply this to my own code?

I've (hopefully) made this fairly straightforward in that you can easily change this to fit your needs.  

Change the blue text to reflect your own table headers and rows/contents (again, you might even already have your Collection built for the contents).

Change the green text to reflect the style you want to apply to your Table overall as well as to the odd/even rows.

But how do I put all of this into my larger HTML document/message?

That is where the Substitute() function comes in.

If you have a larger document that you want a placeholder for a Table or List, etc., then just put in a "tag" that is unique and use Substitute() to replace that text w/ what you've created above.  

For example, if I had an HTML document held inside a variable named myHTMLDocument, and it included the tag [[myHTMLTable]] within it, then the following would use the output from above (the variable htmlTable) to replace that tag w/ the contents of our table we generated:

Substitute(myHTMLDocument, "[[myHTMLTable]]", htmlTable)

This outputs a merged document of the original myHTMLDocument and the context of your htmlTable variable from above.

Bonus: Bulleted or Unordered List

One area that people also struggle with is simply taking values from a Collection and merging them into a proper list.  The code is crazy simple.  If we start w/ a Collection:

ClearCollect(
    myList,
    [
        "Red",
        "Green",
        "Blue"
    ]
);

We can turn this into the HTML for an Unordered List via the following:

Set(
    unorderedListHTML,
    "<ul>" & Concat(
        myList,
        "<li>" & Value & "</li>"
    ) & "</ul>"
)

Which when dropped into an HTML control would give us:
  • Red
  • Green
  • Blue


Final Thoughts

I hope that Microsoft gets their messaging formatting figured out and includes that within some form of messaging queue for noise reduction.  However, I'm sure it will also all get wrapped up into CDS/Dataverse and will add a layer of management to the overall effort that will likely limit initial uptake.  (I'm looking at you Company Communicator)

If you find yourself having trouble w/ formatting them just hop onto a more generic HTML editor online and try that out.  However, keep in mind that every Email client can treat HTML messages differently.  Don't get too crazy w/ your formatting or you'll wind up w/ some very crappy-looking messages.

 

No comments:

Post a Comment

Because some d-bag is throwing 'bot posts at my blog I've turned on full Moderation. If your comment doesn't show up immediately then that's why.