Power Apps Portals

Custom Per-Partner Branding in Power Pages – Part 2: Liquid and CSS

Our goal here is to present contacts from different partners logging into the portal with personalised branding specific to their account / the partner they work for. We’ll drive this from custom fields on the Account table.

Another common use for this might be presenting different branding per sub-brand within a parent company’s portal.

Here are some examples of this in effect… I’ve switched my parent account between Sony Music:

Example of custom styling for contact from Sony Records logged into partner portal

…and Atlantic Records:

Example of custom styling for contact from Atlantic Records logged into partner portal

What we’ll learn in this session:

  • How to retrieve Images columns with liquid
  • Use conditions in liquid to check for custom branding on the logged in portal user (contact)’s account
  • Make your CSS dynamics with liquid
  • Provide fallbacks with liquid’s default filter

The partner portal (as well as Custom and Customer Self-Service) use the following Content Snippets for branding:

  • Navbar Left
  • Mobile Header

These each do much the same thing – providing the header styling (whether you have that set to use text only, or an image). Navbar provides this at medium resolutions and above and then (as the name suggests), the Mobile Header is used for low resolutions e.g. on mobile devices.

Note – I wanted to focus on the Power Pages-specific skills here; retrieving our data with liquid and applying the branding. In preparation for this, I’ve made some customisations to the Account table; adding columns for logo, primary colour and secondary colour. You can download the managed solutions here:https://github.com/rwilson504/PCFControls/releases/latest/download/ColorPicker_managed.zip

https://francomusso.com/wp-content/uploads/2022/11/PartnerBranding_2022_11_12_1454_managed.zip

If you’re interested in how to create the logo and colour columns yourself, see part 1 of this series

You may be wondering, “what about storage costs, isn’t expensive to have a load of images stored in the database?”. The good news is that Dataverse image columns actually use Azure Blob Storage so no issue.

Styling the navbar with our primary and secondary colours

We’ll need somewhere to store our CSS. We need this to load after the usual theme CSS so that it’s not superseded.

For this, we can use (or create) the Head/Bottom content snippet.

Note: for a list of useful Site Settings and Content Snippets, see MVP and friend of mine, Oleksandr Olashyn’s excellent resource at https://github.com/OOlashyn/PowerAppsPortalSiteSettingsAndSnippets . His blog is a fantastic resource for Power Pages / Power Apps Portals also: https://www.dancingwithcrm.com

For the impatient skimmers among you (that’s usually me!), here’s my final code for the Head/Bottom content snippet. Then if you’re still here, we’ll go into what it all means…

{%- if user.parentcustomerid -%}
    {%- assign account = entities['account'][user.parentcustomerid.id] -%}
    {%- if account.musdyn_portalbrandingprimarycolour or account.musdyn_portalbrandingsecondarycolour -%}
        <style>
        .navbar.navbar-inverse.navbar-static-top {
            background: {{ account.musdyn_portalbrandingprimarycolour | default: '#000' }} !important;
        }

        .navbar-static-top.navbar .menu-bar > .navbar-nav > li > a,
        .navbar-static-top.navbar .menu-bar > .navbar-nav > li > .dropdown-menu > li > a,
        .navbar .navbar-icon span {
            color: {{ account.musdyn_portalbrandingsecondarycolour | default: 'red' }} !important;
        }
        </style>
    {% endif %}
{% endif %}

Now for the how…

In the portal management app, select Content Snippets from the left nav menu.

Search for Head/Bottom

If you get a result, open it for editing. If not, create new.

Here’s some CSS to get us started:

        <style>
        .navbar.navbar-inverse.navbar-static-top {
            background: #000 !important;
        }

        .navbar-static-top.navbar .menu-bar > .navbar-nav > li > a,
        .navbar-static-top.navbar .menu-bar > .navbar-nav > li > .dropdown-menu > li > a,
        .navbar .navbar-icon span {
            color: #FFF !important;
        }
        </style>

Getting the data

First step – access the user’s account record

Our partner will be an Account record related to the logged in portal contact via the Company Name (aka parent Customer) column.

We don’t need a query to get the portal user’s contact record. It’s always available to us in liquid via the user object.

We can use this in various ways…

To check if the visitor is a logged in user (contact), like so:

{% if user %} whatever {% endif %}

To check whether the user has a specific web role, like so:

{% if user.roles contains 'Premium' %} whatever e.g. show members-only content {% endif %}

Or the opposite (if the user does not have a specific web role):

{% unless user.roles contains 'Premium' %} whatever e.g show subscription special offers {% endunless %}

Or we can access columns from the contact record using dot notation and the logical / database name of the column e.g.

{{ user.firstname }}

would output the portal users’ first name.

Nice and easy BUT more complex column types such as lookups and choice columns (aka option sets) require us to be a little more specific…

For Lookup columns, liquid gives us an object made up of the GUID and the name of the record

To access the GUID, we need to use columnlogicalname.id e.g.

parentcustomerid.id

To access the name of the record, we use columnlogicalname.name e.g.

parentcustomerid.name

For Choice, Status and Status Reason fields, liquid provides an object containing the numeric value and label for each item

To access the numeric value, we use columnlogicalname.value e.g.

statuscode.value

To access the label, we use columnlogicalname.label e.g.

statuscode.label

Here, I need the GUID of the contact’s account. The logical name for the lookup column I need is parentcustomerid. Therefore to get the GUID from this lookup via the user object, I need:

user.parentcustomerid.id

So we’ve got the GUID of the record, now to retrieve the record…

The entities object makes it nice and easy… one line of code to both retrieve the record and store it in a variable.

The entities object requires 2 pieces of information from us:

  • The logical name of the table (account in our example)
  • The GUID of the row (record) you wish to retrieve from that table

We can use our user.parentcustomerid.id from earlier

To be able to access that record and actually do something with it, we need it in a variable. That we can do with assign.

So, altogether that’s:

{% assign account = entities['account'][ user.parentcustomerid.id] %}

Note – if you have more complex needs e.g. to retrieve a row/record where you don’t have a GUID or a direct relationship with the user object, see here for liquid’s FetchXML tag

Now we can access columns from that account record using dot notation, much like we did with the user object.

Tip – here’s an easy way to get the logical name of a column:Navigate to that column in your Dataverse solution

Click the ellipsis (3 dots) next to the column name and select Advanced > Tools > Copy logical name

Screenshot of how to get a column's logical name. Select the ellipsis next to the column name and then select Advanced, Tools, Copy logical name

…and the same applies to tables:

Navigate to that table in your Dataverse solution

Click the ellipsis (3 dots) next to the table name and select Advanced > Tools > Copy logical name

Screenshot of how to table's logical name. Select the ellipsis next to the table name and then select Advanced, Tools, Copy logical name

Note – In real world, the account table is often used for many other things as well as partners. In that case, you may wish to consider the account type column to ensure the current portal user’s account is indeed a partner

To get started, lets just ‘print’ the primary and secondary colour values on the screen

{{ account.musdyn_portalbrandingprimarycolour }}
{{ account.musdyn_portalbrandingsecondarycolour }}

Not exactly what we want just yet but later on, we’ll pass these values into out CSS to control how things are styled; applying the primary colour as the background and the secondary colour as the font colour.

Next, let’s drop our primary & secondary colours into the CSS. For this, all we need to do is replace the hardcoded hex values in the sample above with liquid to output our text fields.
Let’s provide a fallback for when no primary or secondary colour have been selected. We could use an if condition. However, we’ve used that already and I’m all about expanding your liquid knowledge / toolset so we’re going to use a different approach: liquid’s default filter.

Liquid’s default filter – graceful degradation

Filters in liquid can do all kinds of magical things to transform our output. (I’ll post on this in future with many real world examples, far too many to cover here. If you’re interested, check out https://learn.microsoft.com/en-us/power-apps/maker/portals/liquid/liquid-filters in the meantime)

To add a filter, we type a pipe | character followed by the name of the filter we wish to use

Some filters require parameters e.g. in our example, that’s our fallback value.

For this, we need a : colon after the filter name, followed by the value we want to pass to that parameter.

As the name suggests, the default filter returns a default value for dynamic content (columns / variables) with no assigned value i.e. null.

{{ account.musdyn_portalbrandingprimarycolour | default: '#000' }}

Here’s the CSS populated with our dynamic values, with defaults, wrapped in conditions to check whether:

  • The user has a parent account (is associated with a partner)
  • The account has either a primary colour, secondary colour or both specified

Conditionally outputting the Logo

Again, for the impatient skimmers, here’s the final code for my Navbar Left content snippet:

{%- if user.parentcustomerid -%}
    {%- assign account = entities['account'][user.parentcustomerid.id] -%}
    {%- if account.musdyn_portalbrandinglogoid -%}
        <a href="~/" tile="home"><img class="desktop-logo" style="width: auto; height: 55px; margin-top: -1em;"
        src="~/Image/download.aspx?entity=account&attribute=musdyn_portalbrandinglogo&id={{ user.parentcustomerid.id }}&Full=true" 
        alt="{{ account.name }} logo"></a>
    {%- endif -%}
    {%- else -%}
        <a href="~/" tile="home"><img style="width: 120px; margin-top: -5px;" class="desktop-logo" src="~/songify-logo-coloured.svg" alt="Songify logo"></a>
{% endif %}

We’ll store this code in the content snippet called Navbar Left

Open the Portal Management app and select Content Snippets from the left nav menu

Search for Navbar Left and open the record for editing

The Navbar Left content snippet in the portal management app. Type is set to HTML

Outputting the image is slightly trickier. We need to use a specific URL format containing:

The logical name of the table we’re getting the image from

The logical name of the image column we want to retrieve

The GUID of the row (record) we want the image from

Here’s a completed example taken from https://learn.microsoft.com/en-us/power-apps/maker/portals/configure/image-column :

yourportalurl.com/Image/download.aspx?entity=contact&attribute=entityimage&id=cb059a4a-b1a6-ec11-9840-00224829604e

By default, this will get you a low resolution copy of te image. To get the full resolution, uncropped image, add the Full=true parameter like so:

https://songify.powerappsportals.com/Image/download.aspx?entity=account&attribute=musdyn_portalbrandinglogo&id={{ user.parentcustomerid.id }}&Full=true

We can build this URL dynamically using liquid, like so:

https://songify.powerappsportals.com/Image/download.aspx?entity=account&attribute=musdyn_portalbrandinglogo&id={{ user.parentcustomerid.id }}&Full=true

That’ should be looking pretty good so far but what about records where no logo has been uploaded? We don’t want to show the nasty ‘broken  image’ icon.

To work around this, we’ll first check whether the record has an image, using if

We have a helper column for this. It’s the column’s logical name followed by id.

{% if account.musdyn_portalbrandinglogoid %}

Output the image

{% endif %}

Note – for graceful degradation, you could provide an alternative image as default

What we’ll focus on is how to make that dynamic using liquid.

First, let’s replace the placeholder image with our logo. Just cut the link and paste it over the img tag.

Catch / limitation / gotcha

Dataverse Image columns (and File columns for that matter) don’t support SVGs. Due to their tiny file size and infinite scalability, these would’ve been my first choice. Instead I’ll opt for PNGs with transparent backgrounds.

Franco Musso

You may also like

Leave a reply

Your email address will not be published.