# PowerGrid

## Purpose

Packed with features and customization options, the lowcodera PowerGrid takes presenting, interacting and editing your tabular data in Power Apps to a new level.

## Binding Data

You can bind data to the PowerGrid using the **items** property. Simply select your data source like you would with any out of the box control like the Gallery or Data Grid.

## PowerGrid Properties

<table data-header-hidden><thead><tr><th width="198.33333333333331">Property</th><th>Description</th><th>Type<select><option value="f1d23c58a407493bb41d433c34710260" label="Input" color="blue"></option><option value="f2f01f27df394bf2aa4d9f168669df40" label="Output" color="blue"></option></select></th></tr></thead><tbody><tr><td>Theme</td><td>Styles the grid with one of several themes.</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Auto-Size</td><td><p>Size the grid columns with three different options</p><p><br>Auto-Size All – auto-sizes columns based on cell content and column name lengths</p><p><br>Auto-Size All (Skip Header) – auto-sizes columns based on cell content and ignores headers</p><p> </p><p>Size to Fit – auto-sizes the grid content to fit the width of the component</p><p> </p></td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Row Drag</td><td>Enables or disables the ability for end users to re-order rows by dragging rows</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Grouped Rows</td><td>Enables or disables the ability for end users to use the row grouping function. The row grouping feature allows the data to be grouped by a column or hierarchy of columns.</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Use Custom Grouping</td><td>Boolean to determine if rows should be grouped based on the column schema definition instead of what the end user does as run-time</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Columns Sidebar</td><td>Enables or disables the ability for end users to see the columns sidebar. The columns sidebar allows users to select which columns to show in the grid at run-time</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Filters Sidebar</td><td>Enables or disables the ability for end users to see the Filters sidebar. The filters sidebar provides advanced data filtering capability for users at run-time</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Column Search</td><td>Enables or disables the in-column search function for users</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Use Dataset API</td><td>Enables or disables use of the Dataset API feature. The Dataset API is new feature from Microsoft and simplifies how data is saved back to a data source. There are currently <a href="#limitations-known-issues">limitations and known issues</a> with this approach which is why it is not always suitable for <a href="#saving-data">saving data back to your data source</a></td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Hide Toolbar</td><td>Boolean to hide or show the PowerGrid toolbar which is above the grid.</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>items</td><td>Used to bind to your data source</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>ColumnsDataSet_Items</td><td>Used to define <a href="#columns-dataset">column schema</a> in JSON so that advanced grid column features can be used.</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Load All Rows</td><td>Preloads all rows instead of loading data on scroll</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Font Size</td><td>Sets the font size of the grid content</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Row Height</td><td>sets the height of rows for the grid</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>Set Filter</td><td>definition of the pre-defined filter settings to apply to the grid</td><td><span data-option="f1d23c58a407493bb41d433c34710260">Input</span></td></tr><tr><td>filter</td><td>JSON output of the filters currently applied to the grid. Can be copied and pasted into Set Filter input property for setting predefined filters</td><td><span data-option="f2f01f27df394bf2aa4d9f168669df40">Output</span></td></tr><tr><td>onSelectedItems</td><td>Returns a collection of the items selected on the grid</td><td><span data-option="f2f01f27df394bf2aa4d9f168669df40">Output</span></td></tr><tr><td>onEventType</td><td>Outputs the current event which has been triggered on the grid.  </td><td><span data-option="f2f01f27df394bf2aa4d9f168669df40">Output</span></td></tr><tr><td>actionButtonRowId</td><td>Returns the id of the row from where the user invoked an action button</td><td><span data-option="f2f01f27df394bf2aa4d9f168669df40">Output</span></td></tr></tbody></table>

## Columns Dataset

&#x20;The items property allows you to bind the grid to your data source and render it. In addition to this, there is some advanced functionality available in the PowerGrid which is unlocked through using the columns dataset property which is defined in JSON. Key attributes for our column dataset schema are documented below

&#x20;

| **Attribute**         | **Description**                                                                                                                             |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| DisplayName           | Sets the display name of the column                                                                                                         |
| IsKey                 | Boolean to define if the column is the primary key. Necessary when the API dataset method is not used to save records.                      |
| visible               | Boolean to define whether the column is visible or not on the grid                                                                          |
| editable              | Boolean to determine if a column is editable or not                                                                                         |
| DataType              | <p>Defines the column Data Type. Supported values are:<br>String</p><p>Number<br>Date</p><p>Choices<br>html<br>rating<br>actionButton</p>   |
| DataFormat            | Allows advanced data formatting expressions to be applied. See [data formatting](#data-formatting) for examples.                            |
| ConditionalFormatting | Allows in-cell conditional formatting rules to specified for the column. See [Conditional Formatting](#conditional-formatting) for examples |
| Choices               | Used to provide a JSON data schema for any column defined with a datatype of Choices                                                        |
| rowGroupIndex         | Integer to define the column grouping order for this column                                                                                 |
| showCheckbox          | Boolean to determine if a checkbox is shown against the column                                                                              |

### Example Schema

```csharp
Table(
    {
        fieldName:"ID",
        displayName:"ID",
        isKey:true        
    },
    {
        fieldName:"Title",
        displayName:"Work Order"        
    },
    {
        fieldName:"District",
        displayName:"District"
    },
    {
        fieldName:"Service",
        displayName:"Service"        
    },
    {
        fieldName:"LeadTech",
        displayName:"LeadTech"                
    },    
    {
        fieldName:"WorkDate",
        displayName:"Work Date",                
        dataType:"date",//datatype for the value eg. string,number
        dataFormat:"DD-MMM-YYYY" //Custom date format         
    },
    {
        fieldName:"Payment",
        displayName:"Payment",
        dataType:"choices",
        choices:ForAll([
            {
                key:"COD",
                value:"C.O.D."
            },
            {
                key:"account",
                value:"Account"
            }
        ], {Value: ThisRecord})  
    },
    {
        fieldName:"LbrHrs",
        displayName:"Labor Hours",
        dataType:"number",        
        dataFormat:"{{value:d1}}"// data format mechanism to format the number value eg. 100.0
    },
    {
        fieldName:"Techs",
        displayName:"Techs",       
        dataType:"number",
        conditionalFormat: //Display background-color of the value based on two or more conditions 
        ForAll([
            {
                condition: "value == 1",
                ifTrue: {backgroundColor: "red"}                
            },
            {            
                condition: "value == 2",
                ifTrue: {backgroundColor: "blue"}                
            },
            {
                condition: "value == 3",
                ifTrue: {backgroundColor: "green"}                
            }
        ], {Value: ThisRecord})      
    },
    {
        fieldName:"TotalPayable",
        displayName:"Total Cost",
        dataType:"number",
        dataFormat:"{{value:d2}}",//Set the data format of the value eg. 100.25
        dataAlign: "right"//Set the alignment of the value eg. left,center,right        
    }
)
```

## Saving Data

There are two approaches for saving data back to your data source- using the dataset API or by patching records via the OnChange Event. The Dataset API is simpler to use, however it is a new feature from Microsoft and there are some [limitations](#limitations-known-issues) which means it is not suitable for all situations.

### Using the Dataset API

To use this method for saving data, make sure the “**Use Dataset API**” property is set to **true** and the **Hide Toolbar** property is set to **false**.

As users make changes to column values, the toolbar will show a count of unsaved row changes. Users need to press the **save** button in the toolbar to commit changes back to the underlying data source.

### Saving via the OnChange Event

Before using this method:

1. Set the **IsKey** attribute to **true** for whichever the unique ID column is in your [Columns Dataset](#columns-dataset).
2. Set the “**Use Dataset API**” property to **false**
3. Set the **“Hide Toolbar**” property to true.

This method requires PowerFX to be inserted into the **OnChange** event of the PowerGrid. There are 3 sections to the PowerFX code which are documented in the example code snippet below.

* Generating a collection of changed rows
* Generating a collection of changed values from the changed rows
* Patching the changed values back to the data source

See below for an example code snippet for saving data via the OnChange Event:

```csharp
// 1.  Parsing changed rows into a collection
Clear(updatedRowsCol);
Clear(updatedItemsCol);

ForAll(
    Split(Self.onUpdatedItems,"</Row>"),
    Collect(updatedRowsCol,Result)
);

// 2. Parsing the ID, ColumnName and new value for each changed row

ForAll(
    updatedRowsCol,
    Collect(
        updatedItemsCol,
        {
            id: First(Split(ThisRecord.Value,"</>")).Result,
            fieldName: First(LastN(Split(ThisRecord.Value,"</>"),2)).Result,
            value: Last(LastN(Split(ThisRecord.Value,"</>"),2)).Result
        }
    )
);

// 3. Save the final data - Patch Updates

ForAll(
    updatedItemsCol As UpdatedItems,
    Switch(
        UpdatedItems.fieldName,
        "field_LeadTech",
        Patch(
            colWorkOrders,
            LookUp(
                colWorkOrders,
                ID = Value(UpdatedItems.id)
            ),
            {LeadTech: UpdatedItems.value}
        ),
        "field_ReqDate",
        Patch(
            colWorkOrders,
            LookUp(
                colWorkOrders,
                ID = Value(UpdatedItems.id)
            ),
            {
                ReqDate: DateValue(
                    UpdatedItems.value,
                    "en-GB"
                )
            }
        ),
        "field_Service",
        Patch(
            colWorkOrders,
            LookUp(
                colWorkOrders,
                ID = Value(UpdatedItems.id)
            ),
            {Service: UpdatedItems.value}
        ),
        "field_District",
        Patch(
            colWorkOrders,
            LookUp(
                colWorkOrders,
                ID = Value(UpdatedItems.id)
            ),
            {District: UpdatedItems.value}
        ),
        "field_LbrHrs",
        Patch(
            colWorkOrders,
            LookUp(
                colWorkOrders,
                ID = Value(UpdatedItems.id)
            ),
            {LbrHrs: Value(UpdatedItems.value)}
        )
    )
);
```

## Copy and Pasting Data

The grid supports copying and pasting data into it from data sources like Excel. The **Ctrl + V** keyboard shortcut will need to be used to perform the paste operation.&#x20;

When data is pasted into the grid, there is a **pastedData** EventType which is exposed. This should be used in the OnChange event to process the pasted data. The pasted data is held in the onUpdatedItems output property, as per when any data is updated in the grid.

You will need to handle the two scenarios of when data is pasted into existing records and when pasted data is creating new rows. When the pasted data is creating a new row on the grid, the id attribute for that row of onUpdatedItems will be set to "NEW\_ROW".

See the example PowerFx code below for how the OnChnage event of the grid can be used to process regular updates to the grid as well as from Copy and Paste operations.#

<pre class="language-csharp"><code class="lang-csharp">    Clear(updatedRowsCol);
    Clear(updatedItemsCol);
<strong>
</strong><strong>If(
</strong>    Self.onEventType = "pastedData",

// 1.  Parsing pasted rows into a collection
ForAll(
        Split(
            Self.onUpdatedItems,
            "&#x3C;/Row>"
        ),
        Collect(
            updatedRowsCol,
            Result
        )
    );
    
// 2. Parsing the ID, ColumnName and new value for each pasted row
ForAll(
        updatedRowsCol,
        Collect(
            updatedItemsCol,
            {
                id: First(
                    Split(
                        ThisRecord.Value,
                        "&#x3C;/>"
                    )
                ).Result,
                fieldName: First(
                    LastN(
                        Split(
                            ThisRecord.Value,
                            "&#x3C;/>"
                        ),
                        2
                    )
                ).Result,
                value: Last(
                    LastN(
                        Split(
                            ThisRecord.Value,
                            "&#x3C;/>"
                        ),
                        2
                    )
                ).Result
            }
        )
    );
    
// Patch any new records from the paste operation. New rows hav an id value of NEW_ROW
ForAll(
        Filter(
            updatedItemsCol,
            id = "NEW_ROW"
        ) As UpdatedItems,
        Switch(
            UpdatedItems.fieldName,
            "Payment",
            Patch(
                colltestData,
                Defaults(colltestData),
                {Payment: UpdatedItems.value}
            ),
            "Title",
            Patch(
                colltestData,
                Defaults(colltestData),
                {Title: UpdatedItems.value}
            ),
            "JoinDate",
            Patch(
                colltestData,
                Defaults(colltestData),
                {JoinDate: DateValue(UpdatedItems.value)}
            )
        )
    );
    
//Patch Updates for pasted rows which already exist
ForAll(
        updatedItemsCol As UpdatedItems,
        Switch(
            UpdatedItems.fieldName,
            "Payment",
            Patch(
                colltestData,
                LookUp(
                    colltestData,
                    ID = Value(UpdatedItems.id)
                ),
                {Payment: UpdatedItems.value}
            ),
            "Title",
            Patch(
                colltestData,
                LookUp(
                    colltestData,
                    ID = Value(UpdatedItems.id)
                ),
                {Title: UpdatedItems.value}
            ),
            "JoinDate",
            Patch(
                colltestData,
                LookUp(
                    colltestData,
                    ID = Value(UpdatedItems.id)
                ),
                {JoinDate: DateValue(UpdatedItems.value)}
            )
        )
    ),
    
//Handle regular updates to rows

    
// 1.  Parsing chnaged rows into a collection
ForAll(
        Split(
            Self.onUpdatedItems,
            "&#x3C;/Row>"
        ),
        Collect(
            updatedRowsCol,
            Result
        )
    );
    
// 2. Parsing the ID, ColumnName and new value for updated row
ForAll(
        updatedRowsCol,
        Collect(
            updatedItemsCol,
            {
                id: First(
                    Split(
                        ThisRecord.Value,
                        "&#x3C;/>"
                    )
                ).Result,
                fieldName: First(
                    LastN(
                        Split(
                            ThisRecord.Value,
                            "&#x3C;/>"
                        ),
                        2
                    )
                ).Result,
                value: Last(
                    LastN(
                        Split(
                            ThisRecord.Value,
                            "&#x3C;/>"
                        ),
                        2
                    )
                ).Result
            }
        )
    );
    
    
//Patch Updates for chnaged rows
ForAll(
        updatedItemsCol As UpdatedItems,
        Switch(
            UpdatedItems.fieldName,
            "Payment",
            Patch(
                colltestData,
                LookUp(
                    colltestData,
                    ID = Value(UpdatedItems.id)
                ),
                {Payment: UpdatedItems.value}
            ),
            "Title",
            Patch(
                colltestData,
                LookUp(
                    colltestData,
                    ID = Value(UpdatedItems.id)
                ),
                {Title: UpdatedItems.value}
            ),
            "JoinDate",
            Patch(
                colltestData,
                LookUp(
                    colltestData,
                    ID = Value(UpdatedItems.id)
                ),
                {JoinDate: DateValue(UpdatedItems.value)}
            )
        )
    )
);


</code></pre>

&#x20;&#x20;

## Conditional Formatting

The columns schema can be used to define cell based rules for conditional formatting. The example code snippet below shows how the background color of a cell can conditionally be defined

```csharp
    {
        fieldName:"AccountStatus",
        displayName:"Account Status",
        dataType:"number",
        conditionalFormat: ForAll([
            {
                condition: "value == 1",
                ifTrue: {backgroundColor: "red"}                
            },
            {            
                condition: "value == 2",
                ifTrue: {backgroundColor: "blue"}                
            },
            {
                condition: "value == 3",
                ifTrue: {backgroundColor: "green"}                
            }
        ] ,{Value: ThisRecord})   
    }

```

### Supported operators

#### Relational operators

* \>&#x20;
* <&#x20;
* \>=
* <=
* \===
* <> or !== or !=

#### Logical operators

* && or And
* || or Or
* ! or Not
* ‘value’ or ‘x’ is used for the content in the cell
* Schema for conditional formatting JSON (conditionalFormat in column definition of the grid)

{% hint style="info" %}
When there are overlapping regions then the later style takes the precedence
{% endhint %}

&#x20;

Any [CSS attributes](https://www.tutorialrepublic.com/css-reference/css3-properties.php) can be used inside the ‘ifTrue’ and ‘ifFalse’. Also use camelCase for the CSS attributes.&#x20;

## Data Formatting

Cell values can be formatted in the PowerGrid by specifying the appropriate expression in DataFormat attribute of the JSON column schema.

### **Decimal places**

Use the expression below, where the number after d denotes the number of decimal places. The example below formats the numbers to 2 decimal places.\
\
dataFormat: "{{value:d2}}"

### **Thousand separators**

A thousand separator can be specified by extending the decimal formatting expression with “:t”. The example below formats the numbes to 0 decimal places, with a thousand separator

&#x20;dataFormat: "{{value:d2:t}}"

### **Prefixes**

Values can also be prefixed into the expression e.g. Currency Symbol. The example below formats the number to 0 decimal places, with a thousand separator and a £ currency symbol

dataFormat: "£{{value:d2:t}}"

### **Dates**

When the DataType is Date, various data formatting expressions can be used to format the date.

dataFormat:"DD-MM-YYYY"

## Action Buttons

Action buttons allow you to define in-line interactive buttons within the grid. You can have one or more buttons per row and style each one independently. Importantly, you can also assign an event to a button and define PowerFx expressions to trigger when the user clicks on it.

### **Adding buttons**

Action buttons can be defined in the Column Schema by setting the DataType attribute for a column to be “actionButton”

<details>

<summary>Sample button definition</summary>

```
        fieldName: "button_column1",
        visible: true,
        displayName: "",
        editable: false,
        dataType: "actionButton",
        actionButtonProps: [
            {
                event: "viewProfile",
                id: "viewProfile",
                variant: "Outline",
                tooltip: "View Profile",
                name: "view",
                disabled: false,
                icon: "eye",
                fontColor: "green",
                backgroundColor: "transparent",
                borderColor: "transparent",
                width: 30,
                height: 30,
                fontSize: 12,
                borderRadius: 10,
                margin: 0
            }
        ]

```

</details>

### Action button attributes&#x20;

| event           | Name of the event which can be used in the OnChange event formula                 |
| --------------- | --------------------------------------------------------------------------------- |
| id              | Unique of the event. Comes into use when conditional visibility rules are applied |
| variant         | The style of the button. “Outline” or “Default”                                   |
| tooltip         | A tooltip description which will be displayed on hover                            |
| name            | Text to appear on the button                                                      |
| disabled        | Boolean to denote whether button can be clicked or not                            |
| icon            | FontAweseome icon reference                                                       |
| fontColor       | Color of text ad icon                                                             |
| backgroundColor | Background color of button                                                        |
| borderColor     | Border color of button                                                            |
| width           | Width of button in px                                                             |
| height          | Height of button in px                                                            |
| fontSize        | Size of text and icon                                                             |
| borderRadius    | Radius of the button in px                                                        |
| margin          | Margin between buttons                                                            |

### Handling user interaction

When a user clicks on an action button the PowerGrid onEventType property outputs the name of the event, as defined in the column definition. The actionButtonRowId output property also returns the id of the row the user has interacted with.&#x20;

To handle the interaction, follow these steps:

1. Make sure you have defined a unique name for the action button in the **event** attribute
2. In the OnChange of the PowerGrid, add a PowerFx expression for when the onEventType is equal to the name of your event.&#x20;
3. Use the actionButtonRowId output to perform your action against the appropriate row in your data source
4. See below for example, where the event has been called viewProfile

```csharp
Switch(
    Self.onEventType,
    "viewProfile",
    Set(
        VarPlayerProfileID,
        Self.actionButtonRowId
    );
    Navigate('Player Profile')
)
```

## Setting Predefined Filters

The PowerGrid supports scenarios where you need the grid prefiltered. To enable this simply populate the Set Filter input property with the appropriate JSON expressio&#x6E;**.**

{% hint style="info" %}
To save you having to manually generate the JSON for the filter query, the easiest way of setting a predefined filter is to first of all apply the filter condition(s) to the grid as an end user. Then copy the string generated in the output property **filter** and paste this into the **set filter** input property

{% endhint %}

<details>

<summary>Example filter</summary>

```
{
  "Nationality": {
    "filterType": "multi",
    "filterModels": [
      null,
      {
        "values": [
          "Argentina",
          "Austria",
          "Belgium"
        ],
        "filterType": "set"
      }
    ]
  }
}
```

</details>

## Using Pre-set Grouping

It's possible to have predefined column grouping on the grid. To enable this follow thee steps

1. Turn on the **Use Custom Grouping** property in the grid (set to true)
2. In the column schema, for each column you want to group by, set the rowGroupIndex attribute. This should be a number from 0 onwards for each column which will be grouped.  See example below.

```csharp
{
    fieldName: "name",
    displayName: "name", 
},
{
    fieldName: "country",
    displayName: "category",
    rowGroupIndex: 0
},
{
    fieldName: "Language",
    displayName: "Language",
    rowGroupIndex: 1
}
```

## Limitations/Known issues

* Saving decimal data is currently not supported by the new dataset API
* Saving data to SQL data source is currently not supported the new dataset API
* Under certain scenarios, changes on the grid are not reflected in real-time even though they are saved to the data source

&#x20;
