Board Anything with SLDS and Lightning Components
Introduction
One of the features I really like in the Lightning Experience is the Opportunity Board (or Opportunity Kanban as it will be known in Spring 16). Not so much the ability to drag and drop to change the stage of an individual opportunity, although that’s very useful for the BrightGen Sales team, but more the visualisation of which state a number of records are in.
This seemed to me something that would useful for a wide range of records - anything with a status-like field really, which lead me to create Board Anything - a Lightning component that retrieves records for a particular SObject type, groups them by the value in a configurable status field and displays them as a table of Salesforce Lighting Design System board tiles - Leads for example:
Whatever, Where’s The Code
The Board Lightning Component
The board contents are displayed via a Lightning Component. In order for this component to be able to handle any type of sobject, there are a few attributes to configure its behaviour:
<aura:attribute name="SObjectType" type="String" default="Case" /> <aura:attribute name="StageValueField" type="String" default="Status" /> <aura:attribute name="StageConfigField" type="String" /> <aura:attribute name="FieldNames" type="String" default="CaseNumber,Subject" /> <aura:attribute name="ExcludeValues" type="String" default="Closed" />
These attributes influence the component as follows:
- SObjectType - the type of SObject to display on the board
- StageValueField - the field containing the stage (or status) values
- StageConfigField - a picklist field containing the values to display on the board - use this if your StageValueField is a free text field to limit the stages that are displayed on the board
- FieldNames - comma separated list of fields to display in each tile - the first is used as the title field for the tile
- ExcludeValues - stage values that should be excluded from the board
Note that the defaults will result in a board of open cases being displayed as follows:
The component also defines design attributes for each of these attributes, so that you can configure a board via the Lightning Builder.
The component does most of its work on a list of wrapper classes. First it iterates the list to output the stage headings, which will be in the order returned by the Schema Describe methods for the stage value field or stage config field, depending on which is defined:
<aura:iteration items="{!v.Stages}" var="stage"> <th style="{!'width:' + v.ColumnWidth + '%'}"> <h3 class="slds-section-title--divider slds-text-align--center">{!stage.stageName}</h3> </th> </aura:iteration>
and then iterates the list again, displaying the tiles for each record in each stage:
<aura:iteration items="{!v.Stages}" var="stage"> <td style="{!'width:' + v.ColumnWidth + '%; vertical-align:top'}"> <aura:iteration items="{!stage.sobjects}" var="sobject"> <ul class="slds-list--vertical slds-has-cards--space has-selections"> <li class="slds-list__item slds-m-around--x-small"> <div class="slds-tile slds-tile--board"> <p class="slds-tile__title slds-truncate slds-text-heading--medium">{!sobject.titleField.fieldValue}</p> <div class="slds-tile__detail"> <aura:iteration items="{!sobject.fields}" var="field"> <p class="slds-truncate">{!field.fieldName} : {!field.fieldValue}</p> </aura:iteration> </div> </div> </li> </ul> </aura:iteration> </td> </aura:iteration>
The Apex Controller
Most of the heavy lifting is done by the Apex controller, which gets the available stage values for the stage value field or stage config field:
if ( (null==stageConfigField)) { stageConfigField=stageValueField; } Map<String, String> excludeValuesMap=new Map<String, String>(); if (null!=excludeValues) { for (String excludeValue : excludeValues.split(',')) { excludeValuesMap.put(excludeValue, excludeValue); } } Map<String, BB_LTG_BoardStage> stagesByName=new Map<String, BB_LTG_BoardStage>(); Map<String, Schema.SObjectField> fieldMap = Schema.getGlobalDescribe().get(sobjectType). getDescribe().fields.getMap(); Schema.DescribeFieldResult fieldRes= fieldMap.get(stageConfigField).getDescribe(); List<Schema.PicklistEntry> ples = fieldRes.getPicklistValues(); for (Schema.PicklistEntry ple : ples) { String stageName=ple.GetLabel(); if (null==excludeValuesMap.get(stageName)) { BB_LTG_BoardStage stg=new BB_LTG_BoardStage(); stg.stageName=ple.GetLabel(); stagesByName.put(stg.stageName, stg); stages.add(stg); } }
Note that BB_LTG_BoardStage wrapper classes are created here - later these will be fleshed out with the records and their details.
It then generates a dynamic SOQL query to retrieve the named fields:
String queryStr='select Id,' + String.escapeSingleQuotes(stageValueField) + ', ' + String.escapeSingleQuotes(fieldNames) + ' from ' + String.escapeSingleQuotes(sobjectType); List<SObject> sobjects=Database.query(queryStr);
and finally iterates the results of the query, grouping the records by stage and using dynamic DML to retrieve the field values:
List<String> fieldNamesList=fieldNames.split(','); for (SObject sobj : sobjects) { String value=String.valueOf(sobj.get(stageValueField)); BB_LTG_BoardStage stg=stagesByName.get(value); if (null!=stg) { BB_LTG_BoardStage.StageSObject sso=new BB_LTG_BoardStage.StageSObject(); Integer idx=0; for (String fieldName : fieldNamesList) { fieldName=fieldName.trim(); fieldRes= fieldMap.get(fieldName).getDescribe(); BB_LTG_BoardStage.StageSObjectField ssoField= new BB_LTG_BoardStage.StageSObjectField(fieldRes.getLabel(), sobj.get(fieldName)); if (0==idx) { sso.titleField=ssoField; } else { sso.fields.add(ssoField); } idx++; } stg.sobjects.add(sso); } }
What About the Example Leads Board?
The example Leads board is built using Lighting Out for Visualforce. As you may remember from my previous blog post on this, first I need to create an app that references the component, in this case BBSObjectBoardOutApp:
<aura:application access="GLOBAL" extends="ltng:outApp"> <aura:dependency resource="c:BBSObjectBoard" /> </aura:application>
And then I have a Visualforce page (BBLeadBoard) that uses the app to instantiate the component and sets up the attributes to configure the board for leads:
<apex:page sidebar="false" showHeader="false" standardStylesheets="false"> <apex:includeScript value="/lightning/lightning.out.js" /> <div id="lightning"/> <script> $Lightning.use("c:BBSObjectBoardOutApp", function() { $Lightning.createComponent("c:BBSObjectBoard", { 'SObjectType': 'Lead', 'StageValueField' : 'Status', 'FieldNames' : 'Company, FirstName, LastName, LeadSource', 'ExcludeValues': 'Converted', }, "lightning", function(cmp) { // no further setup required - yet! }); }); </script> </apex:page>
That’s All There Is?
No, there’s the wrapper class, a JavaScript controller and helper, but there’s nothing particularly exciting about those so they are available from my BBLDS samples repository at:
https://github.com/keirbowden/BBLDS
This also contains an unmanaged package so you can just install this into your dev org and try out the Visualforce page - check out the README for more information.
Anything Else I Need to Know
Yes.
- As this is an unmanaged package there are test classes - you’re welcome.
- The styling of the headings could be improved
- Errors are written to the debug log and swallowed server side - I did this as otherwise the Lightning Builder displays exceptions all over the place when you try to update the attributes. Its not ideal, so feel free to change it!
- No drag and drop support to change the stage/status value - while this makes sense for opportunities, it doesn’t for things like cases, so I used that as justification not to do it.
Related Posts
- Responsive Design with the Lighting Design System
- Lightning Design System - Edit Parent and Child Records
- Lightning, Visualforce and the DOM
- Lightning Components and JavaScript Libraries
- Lighting Components and CSS Media Queries
- Lightning Component Events
- Lightning Components and Unobtrusive JavaScript
- Lightning Components and Custom Apex Classes
- LDS Activity Timeline, Lightning Components and Visualforce
Hey Keir,
ReplyDeleteI saw a new tag added, apex:includeLightning.
Can it be used in place of the apex:includescript lightning.out.js?
Yeah - it looks like they've removed the need for us to know what the JavaScript is.
ReplyDeleteHey Bob,
ReplyDeleteDo you know if replicating the opportunity kanban board Drag and Drop functionality can be done with vanilla Lightning/Aura? or would you have to use another JS library like Dragula?
Thanks,
Nathan
Hai Bob, how to retrieve the picklist values in lightning components.
ReplyDeleteThanks......
Hi, Bob
ReplyDeleteI stucked on that how to retrieve the picklist values in lightning component ???
Thanks
Hi Keir,
ReplyDeleteAs always an excellent post with awesome supporting code! Thanks for all you do for the dev community.
Mike
Great post, Keir! Hopefully the Kanban view becomes more standard across Lightning, across more objects, and with the ability to customise the field to summarise. (For example, the sum of Lead age in the kanban column header would be great. Imagine it even displaying a trending chart of sum of time open for each stage.... I think I've just talked myself into committing something to your git project! =D )
ReplyDelete