The key limitation of the standard HTML Text Control
If you have ever used the HTML Text Control in Power Apps, you might already be aware of the great flexibility it provides in terms of being able to apply styling (such as size, colour and alignment) to elements of an HTML document.
You can see a great example of the use of this control in the Community Showcase With Dale Morrison, which is available for free in the Collab365 Academy.
As Dale pointed out in the session, the key limitation with applying styling in an HTML text control in Power Apps is that you can only use inline styling to achieve it, rather than making use of the more powerful cascading style sheets (CSS) method of styling.
A brief introduction to styling HTML text
Inline styling is achieved by adding in a style attribute into every html tag you want to style, for example, to emphasise certain paragraphs, you could do this:
<p>A normal paragraph.</p>
<p style="color: red; font-size: 1.2rem; font-weight: bold; text-decoration: underline;">A highly emphasised paragraph!</p>
Here's How It Looks
While this is flexible, you can see how the job of writing and maintaining the inline styles could quickly escalate into a complex and repetitive task, creating an HTML document that is difficult to understand and update. You could replace the example above with the following HTML document:
<html>
<head>
<style>
p.emphasis {
color: red;
font-size: 1.2rem;
font-weight: bold;
text-decoration: underline;
}
</style>
</head>
<body>
<p>A normal paragraph.</p>
<p class="emphasis">A highly emphasised paragraph!</p>
</body>
</html>
This looks the same when viewed in a browser, but adding more emphasised paragraphs would be less repetitive as you only need to apply a single class to a paragraph, rather than four style attributes. The same styles are then applied to any HTML element matching the CSS selector (in this case, a paragraph with the emphasis tag) in accordance with the details in the CSS stored in the style element.
Styling with CSS is also easier to update as you only need to update the style definition once to change all the emphasised paragraphs. You can even store your CSS in a separate file, rather than a style element in the same document, and use that separate file to apply a consistent style across many HTML text documents. This is done via a link element instead.
In addition, CSS supports applying styles to things known as pseudo-elements and pseudo-classes (think of these as parts and states of elements, respectively, rather than the elements themselves). This is not possible by using inline styles and gives access to more styling functionality. Unfortunately, the standard HTML Text Control does not support CSS!
The Solution: A PCF Control
If something can’t be done in Power Apps using the standard controls, it is often possible to build your own control in the form of a code component (if you have the necessary development skills or are willing to learn them!) using the Power Apps Component Framework (PCF).
Many people have done this already and shared their custom controls via the PCF Gallery. You may be able to find a solution to an existing problem and there may be no need to build your own! Collab365 Coach Connor Deasey shared a demonstration on how to install an iFrame PCF Component into your solution as part of the Weekly Digest events in the Collab365 Academy.
This was a challenge for me, as I had never built a PCF component before and am not a professional developer. Thankfully, Microsoft provide plenty of guidance on how to build a PCF Component.
However, I still didn’t know if the end result would be achievable. Luckily, I stumbled across a blog entry, which discusses loading external CSS files in a PCF Control. So, I thought I’d try the similar approach of inserting a style element rather than a link element into the HTML document.
Building The Component
To build a PCF component, I followed Microsoft's tutorial for creating a first component. I’m not going to regurgitate the article here but if you want any more guidance on the process you can watch Collab365 Coach Mark Jones run through it as part of his recent video on building real-time Power Apps with Signal R.
My journey in building the PCF component differed only from the sample component in the following material respects:
- I called the component HTMLControl
- I included two properties
- HTML String, a text property via which the Power App will provide to the component the HTML to render in the component; and
- CSS String, a text property via which the Power App will provide to the component the CSS to use to style the HTML elements rendered in the component
- I append an HTMLDivElement to the PCF component’s standard container
- I append an HTMLStyleElement to the existing head tag
- The component then populates the innerHTML of these new elements on the initialisation of and any updates to the component.
To name the component, I changed the initial terminal command to:
pac pcf init --namespace HTMLControlNamespace --name HTMLControl --template field --run-npm-install
My properties in the manifest file looked like this:
<property name="HtmlString" display-name-key="HTML String" description-key="A text string containing the HTML to display in the control." of-type="Multiple" usage="bound" required="false" />
<property name="CssString" display-name-key="CSS String" description-key="A text string containing the CSS to style the HTML displayed in the control." of-type="Multiple" usage="bound" required="false" />
I chose Multiple as the property type so that the string can be multiline and contain up to 1,048,576 characters – which is hopefully enough!
In the index.ts file, my init method ended up as follows:
private _container: HTMLDivElement;
private _css: HTMLStyleElement;
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @param container If a control is marked control-type='standard', it will receive an empty div element within which it can render its content.
*/
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement): void
{
this._css = document.createElement('style');
this._css.setAttribute('id','CssContainer');
this._css.innerHTML = context.parameters.CssString.raw ?? '';
document.getElementsByTagName("head")[0].appendChild(this._css);
this._container = document.createElement('div');
this._container.setAttribute('id','HtmlContainer');
this._container.innerHTML = context.parameters.HtmlString.raw ?? '';
container.appendChild(this._container);
}
Note: I added an id to each new element, in case I needed in future to refer to it in CSS or to easily find it in a debugger.
The updateView method finally just replicates the two lines from the above init method that take the input properties and add them to the new elements:
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
*/
public updateView(context: ComponentFramework.Context<IInputs>): void
{
this._css.innerHTML = context.parameters.CssString.raw ?? '';
this._container.innerHTML = context.parameters.HtmlString.raw ?? '';
}
Note that the ?? '' part (the nullish coalescing operator) in both functions above means that if the properties are left blank, an empty string is used instead. This is to avoid an error when the TypeScript code is converted to JavaScript, which complains that a null value cannot be assigned to the innerHTML properties.
Using the component
With the component built, I followed the Microsoft Guide to enabling PCF components and adding them to my environment and Power App, and was pleased to see it worked!
I noticed that with longer HTML documents, the component expanded its height past the limits of the screen, rather than allowing the user to scroll down the document. To solve this, I placed the component within a vertical container and set the Vertical Overflow property of the container to Scroll.
Here are a couple of examples that I set up by creating a gallery of HTML and CSS snippets, then linking the HTML String and CSS String properties of my component each to the relevant property of the selected item in the gallery. This way you do not need a separate control for each document you want to display. These examples show a couple of things that would be either extremely tedious or impossible to achieve using only inline styles.
Example 1: Paragraph formatting
In this example, paragraphs are coloured in an alternating fashion automatically using the nth-child pseudo-class. Imagine having done each colour in inline styles and then someone comes along with a new paragraph – you’d have to recolour every following paragraph again!
In addition, the first line of the first paragraph in each section is emphasised slightly, using a combination of the next-sibling combinator and the first-line pseudo-element. This is impossible to effect with inline styles in a responsive app as you never know where the end of the line might fall in advance.
Example 2: Responsive elements
In this example, the background colour of the PCF control container is set based on the size of the browser window, using the screen and min-width media query. I also used the id selector #HtmlContainer to refer to the new div element appended to the PCF control by the init method of my code.
Large Browser Window
Small Browser Window
In practice, rather than changing the colour, you’d more likely make changes to the font size or display elements in a different layout, or hide certain elements.
This is a rough and ready attempt at building a PCF component and would like to hear your feedback and suggestions on improvements. Join the discussion over at the Collab365 Academy.