How to Work with PCF Dataset Control using Fluent UI’s Detail List

By | July 13, 2020

Introduction

With the introduction of PowerApps Component Framework, Microsoft has provided a way to replace OOB grid and Entity fields with custom controls.

These controls are reusable and can be designed in any form using web development technologies (HTML, CSS, Javascript) to show the CRM records like showing records in a form of Kanban Board, Sentiment analysis control, and many more.

Apart from basic web development technologies (HTML, CSS, Javascript), we can also use various modern frameworks and libraries which expands the scope of development implementation.

In this blog, we will see the basic of PCF (PowerApps Component Framework) dataset control i.e. replace OOB grid with our custom PCF grid and also the offerings which are provided to us in PCF context.

To keep dataset designing simple and easy to understand we will be using the React Fluent UI (previously known as React Fabric UI) framework’s “DetailsList – Basic “ control in our example.

It is not necessary that you have to use “DetailsList – Basic“ control or any other react designing framework. You can also use simple HTML, JavaScript, and CSS for your dataset grid as the designing is based on developers.

Prerequisites

PCF Overview

Step 1:  Creating a PCF project.

Follow the following link for creating the boilerplate React project.

Before coding let us see some of the important aspects of PCF control.

When you create a PCF project you will see there are four methods i.e. init, updateView, getOutputs, destroy which are already present in the index.ts file and a couple of them have parameters as shown in the below screenshot.

We will walk through some of them which we will be using in our details list.

PCF Dataset Control

Context: In the init and the updateView view you will get the function parameter as a context object.

It provides all the properties and methods available in the PowerApps Component Framework i.e. Webapi methods, Utility, Navigation, Device Info, Client Info, etc.

Container:  Container variable is used to append our custom grid over OOB grid.

Now the question arises on how to get and use CRM data in our control?

How to get CRM data?

By default, in a context variable we get all the columns and record info that is available/can be seen in the OOB grid as a parameter. To access/use it we need to use attributes that are provided in the context variable. Given below are the few commonly used attributes in PCF dataset control.

Columns information:

Context.parameters.sampledataset  – This attribute will give you all the OOB grid information like columns, page records, paging info, etc.

Below is a screenshot of the context properties which you can check while debugging.

PCF Dataset Control

Context.parameters.sampledataset.columns – It will give us an array of objects which contain all the column information including Logical name, Display name datatype of the column.

PCF Dataset Control

Context.parameter.sampleDatset.records – As you can see in the below screenshot, it will give all the page records value which was in OOB column views.

If the default paging size is 50 then you will get 50 records that are present on the page. Similarly, if the paging size is 100 you will get 100 records using context.parameter.sampleDatset.records-

PCF Dataset Control

Code Implementation

For using “DetailsList – Basic“ control we have to just pass parameters (props) in a certain format which will then load our custom grid.

Here is the code for loading custom grid (DetailsList – Basic) –

Index.ts

1. Add the below import in your index.ts file. You don’t need to install react and react-dom since it would be already installed when you create a project.

import * as React from ‘react’;

import * as ReactDOM from ‘react-dom’;

//React component which we will create

import { DetailsListGrid } from “./components/DetailsListGrid”;

2. Declare a global variable which will store the default CRM grid object

private _container: any

3. In the init function, we will initialize the global variable with the default CRM grid object.

public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {

this._container = container

}

4. In updateView, we will write all the logic to append our custom grid over default CRM’s OOB grid.

public updateView(context: ComponentFramework.Context<IInputs>): void {

let functionName: string = “updateView”;

// all columns which are on views(Eg Active account)

let columnsOnView = context.parameters.sampleDataSet.columns;

let mappedcolumns = this.mapCRMColumnsToDetailsListColmns(columnsOnView);

let pageRows = this.getAllPageRecords(columnsOnView, context.parameters.sampleDataSet)

try {

this.renderDatasetGrid(context, mappedcolumns, pageRows)

} catch (error) {

console.log(functionName + ” + error);

}

}

/**

* Render DetailsListGrid

*/

public renderDatasetGrid(context: ComponentFramework.Context<IInputs>, mappedcolumns: any, pageRows: any) {

let functionName = ‘renderDatasetGrid’;

let appProps: any

try {

// props to be passed to component.

appProps = {

mappedcolumns: mappedcolumns, // formatted columns for  details list

pageRows: pageRows, // page records value

pcfContext: context // pcf context

};

ReactDOM.render(React.createElement(DetailsListGrid, appProps), this._container);

} catch (error) {

console.log(functionName + ” + error);

}

}

/**

* Get all page record column value.

* @param columnsOnView

* @param gridParam

*/

public getAllPageRecords(columnsOnView: DataSetInterfaces.Column[],

gridParam: DataSet) {

let functionName = ‘loadPagingRecords’;

let pagingDataRows: any = [];

let currentPageRecordsID = gridParam.sortedRecordIds;

try {

for (const pointer in currentPageRecordsID) {

pagingDataRows[pointer] = {}

pagingDataRows[pointer][“key”] = currentPageRecordsID[pointer];

columnsOnView.forEach((columnItem: any, index) => {

pagingDataRows[pointer][columnItem.name] = gridParam.records[currentPageRecordsID[pointer]].getFormattedValue(columnItem.name);

});

}

} catch (error) {

console.log(functionName + ” + error);

}

return pagingDataRows;

}

/**

* Convert our columns in a format which is accepted by DetailsList grid

* @param columnsOnView columns available on views

*/

public mapCRMColumnsToDetailsListColmns(columnsOnView: any): any {

let functionName = ‘mapCRMColumnsToDetailsListColmns’;

let mappedColumn = []

try {

// loop thorugh all columns

for (const pointer in columnsOnView) {

mappedColumn.push({

key: pointer,

name: columnsOnView[pointer].displayName,

fieildName: columnsOnView[pointer].name,

minWidth: 150,

maxWidth: 200,

isResizable: true,

onColumnClick: () => {

alert(`Column ${columnsOnView[pointer].displayName} clicked`);

},

data: “string”,

onRender: (item: any) => {

return React.createElement(‘span’, null, item[columnsOnView[pointer].name])

}

 

})

}

} catch (error) {

console.log(functionName + ” + error);

}

 

return mappedColumn;

}

5. We will remove the control from DOM when it is not required which will be handled by the destroy function that is invoked automatically.

public destroy(): void {

ReactDOM.unmountComponentAtNode(this._container);

}

}

DetailsListGrid.tsx (Create this React component with name)

Create a file with extension .tsx in your project e.g. DetailsListGrid.tsx and copy the below code:

1. Install React fluent UI library using integrated terminal – npm i office-ui-fabric-react @fluentui/react

import * as React from “react”;

import {

DetailsList,

DetailsListLayoutMode,

Selection

} from “office-ui-fabric-react/lib/DetailsList”;

import { MarqueeSelection } from “office-ui-fabric-react/lib/MarqueeSelection”;

import { Fabric } from “office-ui-fabric-react/lib/Fabric”;

export interface IDetailsListBasicExampleItem {

key: number;

name: string;

value: number;

}

export interface IDetailsListBasicExampleState {

items: any;

}

export class DetailsListGrid extends React.Component<

any,

IDetailsListBasicExampleState

> {

private _selection: Selection;

private _allItems: any = this.props.pageRows;

private _columns: any = this.props.mappedcolumns;

private _pcfContext = this.props.pcfContext;

private _allSelectedCards: any = [];

constructor(props: {}) {

super(props);

this._selection = new Selection({

onSelectionChanged: () => {

// @ts-ignore

this.onRowSelection(this._selection._anchoredIndex);

}

});

// Populate with items for demos.

this.state = {

items: this._allItems

};

}

public render(): JSX.Element {

const { items } = this.state;

return (

<Fabric>

<MarqueeSelection selection={this._selection}>

<DetailsList

items={items}

columns={this._columns}

setKey=”set”

layoutMode={DetailsListLayoutMode.justified}

selection={this._selection}

selectionPreservedOnEmptyClick={true}

ariaLabelForSelectionColumn=”Toggle selection”

ariaLabelForSelectAllCheckbox=”Toggle selection for all items”

checkButtonAriaLabel=”Row checkbox”

onItemInvoked={this._onItemInvoked}

/>

</MarqueeSelection>

</Fabric>

);

}

/**

* Function to change the ribbon bar of CRM.

*/

private onRowSelection = (rowIndex: number) => {

let functionName: string = “onRowSelection”;

let selectedRowId: string;

let selectedCardIndex: number;

try {

selectedRowId = this.props.pageRows[rowIndex].key;

// check if selected row is alrady seelected

selectedCardIndex = this._allSelectedCards.findIndex((element: any) => {

return element == selectedRowId;

});

// if card is already clicked remove card id

if (selectedCardIndex >= 0) {

this._allSelectedCards.splice(selectedCardIndex, 1);

} else {

// store all selected card in array

this._allSelectedCards.push(selectedRowId);

}

// update ribbon bar

this._pcfContext.parameters.sampleDataSet.setSelectedRecordIds(

this._allSelectedCards

);

} catch (error) {

console.log(functionName + “” + error);

}

};

/**

* Call function to open Entity record

*/

private _onItemInvoked = (item: IDetailsListBasicExampleItem): void => {

// function to open entity record

this.openEntityRecord(item.key);

};

/**

* Open selected entity record

* @param event

*/

private openEntityRecord(recordID: any): void {

let functionName: string = “onCardDoubleClick”;

try {

if (recordID != null || recordID != undefined) {

let entityreference = this._pcfContext.parameters.sampleDataSet.records[

recordID

].getNamedReference();

let entityFormOptions = {

entityName: entityreference.LogicalName,

entityId: entityreference.id

};

/** Using navigation method */

this._pcfContext.navigation

.openForm(entityFormOptions)

.then((success: any) => {

console.log(success);

})

.catch((error: any) => {

console.log(error);

});

}

} catch (error) {

console.log(functionName + “” + error);

}

}

}

Final Component

After deploying your control and configuring it on the entity you will see the OOB Grid has been replaced with our own React Fluent UI Detail List Grid.

PCF Dataset Control

Conclusion

As illustrated above, you can now easily work with PCF Dataset Control using Fluent UI’s Detail List.

Leave a Reply

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