A Lightning Component grid implementation that is meant to jumpstart your development of a custom grid.
This implementation is biased towards a scenario where you have some large number of records in your database and you need a table to surface some of those records at a time. You expect search, filtering, sorting, paginating all to work not just in the browser but to also consider the other records back in the database.
- Driven by a data store
- Frontend data source: serve data from Javascript
- Backend data source: serve data from Apex (can be standard objects, custom objects, cMDTs, external objects...anything Apex can touch)
- Pagination
- Search
- Filtering
- Sorting
This grid component implementation is intentionally more hands-on than something like lightning:datatable or other implementations we've seen.
It does not assume your record shape in the database is exactly the same shape you are going to surface in your UI. It does not assume what the markup for each column is going to be based on the database field type. You are in the driver seat.
Consequently, this is a more verbose implementation and is not a good fit if you don't need the additional level of access and customization.
Our desire was to build a table that would support arbitrary column markup in a syntax that would be intuitive for developers. Especially important for us was combining column definitions with row cell markup in the same syntax.
Here is the sample markup from the built-in "Accounts Demo" table:
<aura:component implements="force:appHostable" description="An example of how you can use the Grid component to fetch and filter/sort/page server data.">
<!--Content-->
<lightning:card title="Accounts Demo" class="slds-m-bottom_medium slds-p-horizontal_small">
<p>This is an example of how to use the Grid component. Below you will see Accounts in this org.</p>
<p>To see the code involved, look at the Lightning component AccountsDemo.cmp and its corresponding Apex support class AccountsDemo.cls.</p>
</lightning:card>
<c:Grid>
<aura:set attribute="startingFilters">
<c:GridFilter fieldName="Type" value="Channel Partner / Reseller" />
</aura:set>
<aura:set attribute="dataSource">
<c:ApexDataSource className="AccountsDemo" />
</aura:set>
<c:Column fieldName="Id" label="Account Id">
{#record.Id}
</c:Column>
<c:Column fieldName="AccountNumber" label="Account Number" sortable="true">
{#record.AccountNumber}
</c:Column>
<c:Column fieldName="Name" label="Account Name" sortable="true">
{#record.Name}
</c:Column>
<c:Column fieldName="Type" label="Account Type" sortable="true">
{#record.Type}
</c:Column>
<c:Column fieldName="NumberOfEmployees" label="Employees" sortable="true">
<lightning:formattedNumber value="{#record.NumberOfEmployees}" />
</c:Column>
<c:Column fieldName="AnnualRevenue" label="Annual Revenue" sortable="true">
<lightning:formattedNumber value="{#record.AnnualRevenue}" style="currency" />
</c:Column>
<c:Column fieldName="Industry" label="Industry" sortable="true">
{#record.Industry}
</c:Column>
</c:Grid>
</aura:component>
In addition to the built-in filtering that a user can do, you are able to do two additional things in your markup as you set up a Grid component.
A "starting" filter looks like a normal filter a User would have selected, but it is already in place when the Grid is rendered for the first time. Use this when the user is arriving at the Grid with some initial state already in place. Perhaps you show them a list of choices somewhere, and clicking a choice brings them the Grid, already pre-filtered for that choice.
You can define zero or more filters for the startingFilters
attribute.
Syntax:
<c:Grid>
<aura:set attribute="startingFilters">
<c:GridFilter fieldName="Type" value="Channel Partner / Reseller" />
</aura:set>
The fieldName
attribute should match one of your c:Column
definitions.
Hidden Filters
Hidden filters are set up pretty much identically to starting filters, except:
- They are not visible to the user
- They cannot be canceled by the user
Use these when you want to add a filter to the Grid that you know you're going to want in place. This saves you the trouble of having to write a new data provider to exclude whatever you are filtering.
You can define zero or more filters for the hiddenFilters
attribute.
Syntax:
<c:Grid>
<aura:set attribute="hiddenFilters">
<c:GridFilter fieldName="Type" value="Channel Partner / Reseller" />
</aura:set>
Data is served to the Grid by your specified "data source":
<c:Grid>
<aura:set attribute="dataSource">
<c:ApexDataSource className="AccountsDemo" />
</aura:set>
A data source is any Aura component that implements the c:GridDataSource
interface:
c:GridDataSource interface:
<aura:interface description="Implement this interface to be usable as a data provider for the Grid component">
<aura:method name="fetchRecords">
<aura:attribute name="context" type="Map" />
<aura:attribute name="callback" type="Function" />
</aura:method>
</aura:interface>
Grid doesn't care where you get data. It just cares that you implement this single function, and that you know how to interpret the context object that is passed to this function.
If you want to handle data provisioning entirely in Javascript, go right ahead. If you'd like to have the data come from the server, we've included some prebuilt elements to help you do that.
Included in this repository is a data provider that knows how to talk to Apex to fetch data. Supporting classes exist on the backend to make this really easy.
The provided ApexDataSource Aura component talks to the GridController Apex class.
GridController expects to instantiate some class you've built that implements the GridData Apex interface.
We've already written two implementations of GridData. Extend whichever you like:
- SObjectGridData - knows how to work with SObjects
- CustomMetadataGridData - knows how to work with cMDT records
To use the built-in ApexDataSource data provider, simply pass it the namespace and className of your implementation of GridData. Here's that sample code again:
<c:Grid>
<aura:set attribute="dataSource">
<c:ApexDataSource className="AccountsDemo" />
</aura:set>
To understand how setting className
= "AccountsDemo" is going to get you data, just have a look at AccountsDemo.
This component is available as a self-contained SFDX project. If you would like to run the example implementation, clone the repository then set up a new scratch org by running:
sfdx force:org:create -f config/project-scratch-def.json -a grid
This builds a scratch org named "grid". Now run:
sfdx force:source:push -u grid
This pushes the project into that new scratch org. Next run:
sfdx force:data:bulk:upsert -s Account -f data/FakeSalesforceAccounts.csv -i Id -u grid
This pushes some fake Account rows into the new org that will be used by the demo.
Finally, run:
sfdx force:org:open -u grid -p /lightning/n/Accounts_Demo
This will open your new scratch org in a browser window.
This component is not hardened for security. For example, the example AccountsDemo apex controller will happily violate FLS and CRUD permissions to retrieve fields to display.
If you use this component, think through the specific security requirements for your implementation carefully.