Salesforce Lightning Components: Creating a Select Box/Tree Component

Need a user to select an item from a 2-tier hierarchy? Typically, you’d either use a lightning:select with embedded optgroup tags, or use the new lightning:tree component. But, for optimal code reuse, consider building a single component that enables a developer to toggle the input mechanism that they think is the most appropriate for their use-case. For instance, while lightning:tree is great for desktop and tablet apps, it’s not appropriate for mobile phones, or for input forms.

fwb1

lightning:tree (left)

IMG_2170

lightning:select (in Salesforce Mobile)

The following component enables a user to either select from a list or a tree, depending on the value of the “mode” attribute.

<aura:component controller="BeerContacts" access="global" extends="c:Base">

<aura:attribute 
 name="mode" 
 type="string" 
 access="global" 
 default="select" />

<aura:attribute 
  name="beers"       
  access="private"       
  type="Object[]"      
  description="hierarchical data to display" />

 <aura:attribute       
  name="selectedBeerId"       
  access="public"       
  type="Id"       
  default=""       
  description="User selection"/>

 <aura:attribute       
  name="labelPrefix"       
  access="public"       
  type="String"       
  default="Friends with "      
  description="Text prefix for select options"/>

 <aura:handler    
  name="change"         
  value="{!v.selectedBeerId}"         
  action="{!c.onBeerChange}" />

 <aura:handler         
  name="init"         
  value="{!this}"         
  action="{!c.doInit}" />

 <!-- fire event when a selection is made -->
 <aura:registerEvent       
  name="onBeerSelected"       
  type="c:FWB_BeerSelected"/>

 <aura:if isTrue="{!v.mode == 'select'}">
   <div>
    <lightning:select     
     name="beerId"           
     label="Select Beer"      
     variant="label-hidden"      
     value="{!v.selectedBeerId}">

    <option value="">Select Beer</option>
    <aura:iteration items="{!v.beers}" var="thisBeerGroup">
        <optgroup label="{!thisBeerGroup.label}">
        <aura:iteration items="{!thisBeerGroup.items}"               
                        var="thisBeer">

            <option value="{!thisBeer.name}">
             {!v.labelPrefix}{!thisBeer.label}
            </option>
        </aura:iteration>
        </optgroup>
     </aura:iteration>
   </lightning:select>
  </div>
  <aura:set attribute="else">
    <lightning:tree items="{!v.beers}"                               
       header=""                               
       onselect="{!c.onTreeSelect}"/>
  </aura:set>
 </aura:if>
</aura:component>

While you can’t prevent a user from selecting a category in lightning:tree, you can provide them with guidance that they need to select a leaf node as illustrated by the component’s controller onTreeSelect function:

({
    doInit : function(component, event, helper) {
        helper.onInit(component,event,helper);
    },
    onBeerChange: function(component,event,helper) {
        var compEvent = component.getEvent("onBeerSelected");
        compEvent.setParam("beerId",component.get('v.selectedBeerId'));
        compEvent.fire();
    },
    onTreeSelect: function(component,event,helper) {
        var compEvent = component.getEvent("onBeerSelected");
        var selectedName = event.getParam('name');
        if (selectedName.indexOf('type:') == -1) {
            compEvent.setParam("beerId",selectedName);
            compEvent.fire();
        } else {
            var toastEvent = $A.get("e.force:showToast");
            if (toastEvent) {
                toastEvent.setParams({
                    "type" : "warning",
                    "message": "Please select a beer, not a category."
                });
                toastEvent.fire();
            }
        }
    }
})

And finally, you can use the same data structure to populate both the select box and tree control, as outlined by the following helper method, which is invoked by the controller’s doInit() method:

({
    onInit : function(component,event,helper) {
        helper.runApex(
            component,
            "c.getBeers",
            function(response) {
                var result = [];
                var groupName = "";
                var thisGroup = {items : []};
                for (var i=0; i<response.length; i++) {
                  if (groupName != response[i].Type__c) {                          
                        if (thisGroup.items.length > 0) {
                            result.push(thisGroup);
                        }
                        groupName = response[i].Type__c;
                        thisGroup = {
                            label: response[i].Type__c,
                            name: 'type:' + response[i].Type__c,
                            expanded : true,
                            items : []
                        };
                    }
                    thisGroup.items.push({
                        name: response[i].Id,
                        label: response[i].Name
                    });

                }
                component.set('v.beers',result);
            }
        );
    }
})

For reference, the getBeers() Apex method simply retrieves a category and beer name from a custom object. Note that the data is sorted by the fields that we are going to use for grouping in both the tree and select box:

public with sharing class BeerContacts {

    @AuraEnabled
    public static List<Beer__c> getBeers() {
      return [
         select Id,Name,Type__c,Country__c
         from Beer__c
         order by Type__c,Name
      ];
    }
}

Happy coding!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s