Skip to content

PowerApps Grid Control – How to make Cell Renderers more Generic

The Power Apps grid control is a new and improved version for the data grid control for Power Platform model-driven applications. It introduces a modern data grid user experience and a range of noteworthy features, including the ability to perform inline editing and seamless infinite scrolling of the grid records, amongst others.

Simply implement the Power Apps grid control component on your applications data grids (table views or form subgrids) to unlock up its capabilities.

Configure a Power Apps grid control

Power Apps grid control

Moreover, as a developer, one of the most appealing aspects of the new grid control is the ability to customize the look and feel of the grid columns using specialized PCF (PowerApps Component Framework) controls known as Cell Renderers.

Although, like my fellow MVP Diana Birkelback explained in her great post on the subject, the current architecture of cell renderers lacks the ability to inject parameters to the control (like you can do with a normal PCF control) to make them more generic and reusable.

In this post I will show how I was able to attain a certain degree of genericity by making the cell renderer aware of their root table (entity) and how to obtain runtime information about lookup fields present in the underlying grid data.

The end-result can be appreciated in my latest community PCF project, the RecordImage Cell Renderer.

The control turns a plain out-of-the box grid like this one.

Into a visually appealing grid as seen below.

Please check the source code for more in-depth info

🔗drivardxrm/RecordImage.CellRenderer: Cell renderer for PowerApps Grid (github.com)
🔗RecordImage Cell Renderer | PCF Gallery

What is the PowerApps Grid Control

Before diving in the cell renderer specific stuff, I think that the PowerApps Grid control deserves a bit of attention as it will become the default experience for model-driven grids in the near future.

This control will eventually replace all read-only and editable grids in model-driven apps.

🔗Power Apps grid control – Power Apps | Microsoft Learn

official Microsoft documentation

The PowerApps Grid control acts as a one-stop shop where you can define the behavior and the look of your apps grids. The official documentation is a great resource to get a better understanding of all the features included with the control, but here are some of my favorites.

Switch from Read-Only to Editable grid

Finish are the times when you needed to have a different controls for editable and for read-only grids. With this new control, simply configure the ‘Enable editing‘ parameter of the control at design time and it will be rendered accordingly.

Enable editing parameter

Infinite scrolling by default

One aspect that always bugged me with classic data grids within modern-driven apps is the pagination system.

Especially for grids that contains a lot of data, navigating within the grid can quickly become a tedious task. Users find themselves constantly scrolling up and down, and if they can’t locate what they need, they’re forced to click through previous and next pages.

The PowerApps Grid control have infinite scrolling enabled by default, allowing seamless navigation of lengthy records lists without context switching.

Simply toggle the ‘Enable Pagination‘ setting to ‘Yes’ to revert to the classic rendering style.

Enable pagination parameter

Enters the Cell Renderers

From a developer perspective, the PowerApps Grid control truly shines by allowing the injection of a Customizer control (or cell renderer) on top of the grid. Just enter the name of the desired cell renderer in the ‘Customizer control‘ parameter. (Format : {publisher prefix}_{namespace}.{control name})

Customizer control parameter

It gives a way to customize the look and feel of specific cells in a grid without having to recreate your own data grid custom control from scratch. This approach allows you to piggyback on the out-of-the-box control and add your own customization on top of it, giving you the best of both worlds.

Head to the official documentation to get you started on cell customizer development process.

🔗Customize the editable grid control - Power Apps | Microsoft Learn
🔗Customized editable grid - Power Apps | Microsoft Learn

Also, don't forget to get a look at these posts from Diana Birkelback 

🔗Power Apps Grid Control – First Glimpse to the Cell Renderer and Editors – Dianamics PCF Lady (wordpress.com)
🔗Fetch Related Records with Power Apps Grid Customizer Control – Dianamics PCF Lady (wordpress.com)

I will not go in every details of the implementation of a cell customizer but here are the key points.

  • In the init method of the customizer PCF control, an EventName parameter will be received and will serve as a reference to the datagrid (this is supplied at runtime by the platform, you don’t need to do anything).
  • You then need to instantiate a PAOneGridCustomizer that will contain the business logic in the form of a cellRendererOverrides.
  • Bind that customizer to the grid instance (EventName) by invoking a fireEvent.

The cellRendererOverrides implementation will contain code that will fire every time a cell of the grid is rendered. The goal here is to intercept the columns of interest and carry out the desired custom rendering.

Note that every type of columns can have their own renderers so you need to define different overrides for each column types. Also, you have access to the column name so you can filter out the unwanted columns.

However, as previously mentioned, despite Cell Renderers being constructed using the PCF control framework, a notable limitation is the inability to supply any runtime parameters to a specific instance of the control, as you would with a standard PCF control. This limitation presents challenges in creating generic cell renderers that are reusable across the system.

Let’s explore ways to alleviate some of these challenges using my latest community PCF control.

The RecordImage Cell Renderer

The story behind the RecordImage Cell renderer is to leverage the use of individual record image (primary image) and showcase these images within data grids that surface these records.

Image defined in individual record

Enabling record images is a straightforward process that is often underestimated in model-driven application projects. It can introduce a layer of depth and visual appeal to your application.

To enable record image on any table, create an image column and configure the column as the Primary Image of the table.

Inside a data grid, link to records can come in two flavors, either by the primary name column or related data lookups columns contained in the view.

Images rendered in data grid by the RecordImage Cell Renderer

Under the hood of the cell customizer, whenever a cell of interest rendering is fired, a mini-React application (RecordImageCellApp) will be returned to override the out-of-the-box rendering. To ensure accurate rendering of the image, the app relies on two crucial pieces of information passed as props (parameters).

  • The logical name of the table (entityname)
  • The ID of the individual record (recordid)

Here is how I was able to retrieve them.

Get Entity Reference for Primary Name column

Since the primary name column is of type ‘Text‘, the first step is to determine if the cell that is being rendered is actually the PrimaryName column of the displayed view and skip the rendering for all other Text type columns. Fortunately the platforms gives us this information in the rendererParams object (colDefs[columnIndex].isPrimary)

Knowing that, it’s also easy to get the ID of the row’s record using the [RECID] property of the rowData object.

To get the table name, it’s a bit more tricky. First, we need to determine what is the type of grid that is being rendered. The grid can either be rendered as a main entity view or as a subgrid in a form that shows related records.

Grid rendered as main entity view
Grid rendered as a subgrid in a form

I found a way to get that information by inspecting the factory._customControlProperties.pageType that is buried inside the the context object of the PCF control. Note that the context object needs to be casted as an any because these properties are not exposed in the official type definition of the context.

(context as any).factory._customControlProperties.pageType

As seen below, the pageType value will be ‘EntityList‘ if the datagrid is rendered on a main entity view and ‘EditForm‘ when rendered as a subgrid on a form

Now depending on the pageType value the name of the table can be infered accordingly.

const entityname = pageType == 'EntityList' ?  
            (pcfContextService.context as any).page.entityTypeName :
            (pcfContextService.context as any).factory._customControlProperties.descriptor.Parameters.TargetEntityType 

Main Entity view

Subgrid on a form

Get Entity Reference for Lookup columns

The second technique I want to show is how to get the entity reference information about lookup columns referenced inside the grids underlying view.

The information about a particular lookup value (table name and id of the record) can be found in the rowData object received by the cell renderer rendererParams. The only challenge is that the property name inside the rowData object is dynamic and correspond to the name of the column inside the view.

Lookup info in rowData object

Using the  keyof typeof obj technique, we can get the lookup info (entity name and guid) by dynamically accessing the rowData object property that equals to the column name of the lookup. The code below speaks fort itself.

More onkeyof typeof obj here :
🔗Dynamically access an Object's Property in TypeScript | bobbyhadz
["Lookup"]: (props: CellRendererProps, rendererParams: GetRendererParams) => {             
        const {columnIndex, colDefs, rowData } = rendererParams;         
        const columnName = colDefs[columnIndex].name;
        
        type ObjectKey = keyof typeof rowData;
        const columnNameProperty = columnName as ObjectKey;
        
        const lookup = rowData?.[columnNameProperty] as any
        if(lookup == null){
            return null
        }
        const lookupentity = lookup.etn
        const lookupid = lookup.id.guid

The table name can be derived from the lookup.etn and the record id can be found in lookup.id.guid.

Get the PrimaryImage column name from Metadata

Having access to the table name either from the main table or from a a lookup, the next challenge is to get the name of the primary image column of these tables so we can retrieve the individual record images further on.

To achieve this, its a matter of making a metadata call on the table name using the getEntityMetadata function exposed by the ComponentFramework.Context object. Within the metadata call’s response, we can conveniently extract the value of the PrimaryImageAttribute, which holds the logical name of the primary image column.

primaryImageAttribute from metadata call

Get the record image

Finally, to retrieve the image data, all is needed is to make a webAPI.retrieveRecord call to query the table (entityname) and get the primaryimage attribute of the current cell record (recordid)

Given that the content of the primaryimage field is stored in base64 within the Dataverse table, the function shown below will produce a string that is appropriately formatted and suitable for injection into an HTML <img> element’s src attribute. This string will be structured as follows: data:image/jpeg;base64,{primaryimage_data}.

async getRecordImage (entityname:string, recordid:string, primaryimage:string) : Promise<string> {
    
    let record = await this.context.webAPI.retrieveRecord(entityname,recordid,`?$select=${primaryimage}`)

    return  record?.[primaryimage]
            ? `data:image/jpeg;base64,${record?.[primaryimage]}`
            : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='  //1 px transparent  https://png-pixel.com/
  }

In my implementation I have wrapped the result of the function in a custom hook, but you can see the resulting image data being injected in a FluentUI Image object src attribute.

Clickable link

To preserve the standard functionality of Primary Name and lookup columns, it’s essential to wrap the text of the cell in a clickable link that redirects the user to the record expressed by the cell.

This can be achieve by using the ComponentFramework.Context navigation.openForm function and binding the resulting promise to the OnClick event of a link tag. Here i’m using FluentUI Link object.

async openRecord (entityname:string, recordid:string):Promise<ComponentFramework.NavigationApi.OpenFormSuccessResponse> {
    return this.context.navigation.openForm(
      {
        entityName: entityname,
        entityId: recordid
      }
    )
  }

What about performance and caching?

As you can imagine, having code that can run on any cell of a data grid needs to execute lightning fast.

That’s why for my implementation I opt-in for a PCF Virtual control. This flavor of PCF control uses the React and Fluent provided by the PowerApps runtime and are known to be smaller in size and execute faster than regular PCF control.

See official docs :
🔗React controls & platform libraries (Preview) - Power Apps | Microsoft Learn

Additionally, given that a substantial portion of the metadata and Dataverse WebApi calls might be the same across a grid rendering, it would be a counterproductive to re-query the same data over and over again.

For this, I’m using the awesome React Query package to graciously handle the asynchronous state management and caching of the app.

Learning how to effectively use React Query is beyond the scope of this blog post and will probably be the subject of future writings. I highly recommend that Check the source code to get a grasp of its usage.

Takeaway

Exploring Cell Renderers for the PowerApps Grid control has been an enjoyable learning experience. They offer unprecedented ways to enhance the user experience of data grids.

While it might take a bit of practice to master their implementation, the payoff is well worth it, as we have seen with the RecordImage Cell Renderer.

I hope that the framework will evolve overtime to remove some of the current limitation and I can’t wait to see what kind of renderers will emerge from the community.

Happy coding!

Links

Photo by Fayette Reynolds M.S.

Published inBlog

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *