function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
kyle.moseleykyle.moseley 

Lazy Load of Lightning Tree

I am working on developing a Lighthing Tree that has upwards of 60,000 records with various branches to the tree and can have up to 5 depths to the tree. 
I can easily render the first two tiers in the tree. I can also handle an on click action to pass back to the server and query the child records of the selected component. 
What I need is to be able to put those child records into the tree underneath the selected node. 

TestContainer.app 
<aura:application extends="force:slds">
    <c:ProductTreeView /> 
</aura:application>

ProductTreeView.cmp
<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="ProductTreeController" >
	<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <aura:attribute name="items" type="Object"/>
    <aura:attribute name="expanded" type="Boolean" default="false" />
    
<div class="slds-box slds-p-around_small"> 
    <lightning:card title="Product Search" iconName="standard:product">
            <aura:set attribute="actions">
            	<lightning:button label="Refresh List" onclick="{!c.refreshList}"/>
                <lightning:button label="Create Special Item"/>
        	</aura:set>
             
            <lightning:tree items="{! v.items }" header="Product Hierarchy" onselect="{!c.handleSelect}" />

            <!--Lightning Spinner-->
    		<div>
        		<lightning:spinner alternativeText="Processing.." title="Processing.." aura:id="spnr" variant="brand" size="large" />
    		</div>  
    </lightning:card>
</div>    
</aura:component>

ProductTreeViewController.js
({
    doInit: function (component, event, helper) {
        var spinner = component.find("spnr");
        var action = component.get('c.getProductTree');
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
                component.set('v.items', response.getReturnValue());
                //hide spinner after getting data
                $A.util.toggleClass(spinner, "slds-hide");
            }else{
                $A.util.toggleClass(spinner, "slds-hide");
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    },
    handleSelect: function (component, event, helper) {
        //return name of selected tree item
        var items = component.get('v.items');
        var Branch = items.length - 3;
        console.log(items);
        var Parentid = event.getParam('name');
        var action = component.get('c.loadChildren');
	    action.setParams({"ParentId": Parentid});
        
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
               	var newitems = response.getReturnValue();
                items[Branch].items.push(newitems);              
                component.set('v.items', items());             
            }else{
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    },
    
     refreshList: function (component, event, helper) {
         
        var action = component.get('c.getProductTree');
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
                component.set('v.items', response.getReturnValue());          
            }else{
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
     }
})

ProductTreeController.cls
public class ProductTreeController {

    @AuraEnabled
    public static List<item> getProductTree(){
        
        List<item> items = new List<item>();
        List<Product_Hierarchy__c> tier1 = new List<Product_Hierarchy__c>();
        
        tier1 = [SELECT id, DisplayName__c, Product__c, (Select id, DisplayName__c, Product__c FROM Hierarchy_Parent__r) FROM Product_Hierarchy__c WHERE ParentId__c = NULL];
        
        
        for(Product_Hierarchy__c t1: tier1){

            
            List<item> tier2 = new List<item>();
            for (Product_Hierarchy__c t2 : t1.Hierarchy_Parent__r){
                
                item tier2item = new item(t2.DisplayName__c, String.valueof(t2.Id), false, null);
                tier2.add(tier2item);
            }
           
            
            item tier1item = new item(t1.DisplayName__c, String.valueof(t1.Id), false, tier2);
            items.add(tier1item);
        }
                
        return items;
    
    }
    
    @AuraEnabled
    public static List<item> loadChildren(String ParentId){
        List<item> items = new List<item>();
        List<Product_Hierarchy__c> children = new List<Product_Hierarchy__c>();
        
        children = [SELECT id, DisplayName__c, Product__c FROM Product_Hierarchy__c WHERE ParentId__c =: ParentId];
        
        for(Product_Hierarchy__c c: children){
            item childitem = new item(c.DisplayName__c, String.valueof(c.id), true, null);
                items.add(childitem);
        }
        
        return items;
    }
    
    
    
    public class item{
        @AuraEnabled
        public String label {get; set;}
        @AuraEnabled
        public String name {get; set;}
        @AuraEnabled
        public Boolean expanded {get; set;}
        @AuraEnabled
        public List<item> items {get; set;}
         
        public item(String label, String name, Boolean expanded, List<item> items){
            this.label = label;
            this.name = name;
            this.expanded = expanded;
            this.items = items;
        }
    }    
}

I am trying to PUSH the new queried records to the existing "Items" that is generated in the function handleSelect, but I get errors. Can anyone provide me with examples on how to do a lazy load like this OR how to identify the specific NODE that was selected so I can add the items to that section of the existing "Items" 

We have built this functionallity using JS and VF for classic and need to reproduce this in LEX. Due to the large ammount of data, I cannot load all the records at the start and have to go with a lazy load scenario. 

Any help will be greatly appreciated!! 

 
Best Answer chosen by kyle.moseley
Alain CabonAlain Cabon
Hi Kyle,

This solution is better and simpler.

The key of the nodes are always the "name".

mapitems contains all the nodes (key = name).
 
<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="ProductTreeController" >
	<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <aura:attribute name="items" type="Object"/>
    <aura:attribute name="expanded" type="Boolean" default="false" />    
    <aura:attribute name="mapitems" type="Map"  default="{}" />
    
<div class="slds-box slds-p-around_small"> 
    <lightning:card title="Product Search" iconName="standard:product">
            <aura:set attribute="actions">
            	<lightning:button label="Refresh List" onclick="{!c.refreshList}"/>
                <lightning:button label="Create Special Item"/>
        	</aura:set>
             
            <lightning:tree items="{! v.items }" header="Product Hierarchy" onselect="{!c.handleSelect}" />

            <!--Lightning Spinner-->
    		<div>
        		<lightning:spinner alternativeText="Processing.." title="Processing.." aura:id="spnr" variant="brand" size="large" />
    		</div>  
    </lightning:card>
</div>    
</aura:component>
Controller JS :
({
    doInit: function (component, event, helper) {
        var spinner = component.find("spnr");
        var action = component.get('c.getProductTree');      
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
                component.set('v.items', response.getReturnValue());
                helper.loadingMap(component.get('v.mapitems'),component.get('v.items'));
                //hide spinner after getting data
                $A.util.toggleClass(spinner, "slds-hide");
            }else{
                $A.util.toggleClass(spinner, "slds-hide");
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    },
    handleSelect: function (component, event, helper) {      
        console.log('name:' + event.getParam('name'));       
        var myitems = component.get('v.items');        
        var Parentid = event.getParam('name');    
        var mapitems = component.get("v.mapitems");
        var selectedItem = mapitems[Parentid];
        var action = component.get('c.loadChildren');
        action.setParams({"ParentId": Parentid});
        action.setCallback(this, function(response){
            var state = response.getState();           
            if(state === 'SUCCESS'){
                var newItems = response.getReturnValue(); 
                helper.loadingMap(mapitems,newItems);
                console.log('return selectedItem:' + JSON.stringify(selectedItem));
                if (selectedItem) {
                    for (let myitem of newItems) {
                        selectedItem.items.push(myitem); 
                    }
                    component.set('v.items', myitems);  
                }              
            }else{
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    },
    refreshList: function (component, event, helper) {   
        var action = component.get('c.getProductTree');
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
                component.set('v.items', response.getReturnValue());          
            }else{
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    }
})

Helper:
({
    loadingMap: function (myMap,myItems) {       
        function recurs(myItems) {          
            for (let i=0;i< myItems.length;i++) {    
                myMap[myItems[i].name] = myItems[i];           
            }            
            for (let i=0;i< myItems.length;i++) { 
                if(myItems[i].items && myItems[i].items.length >0) {
                    recurs(myItems[i].items);
                }
            }            
        } 
        recurs(myItems);
    }
})

Controller APEX:
public class ProductTreeController {
    
    @AuraEnabled
    public static List<item> getProductTree(){       
        List<item> items = new List<item>();        
        for (integer i=1;i<10;i++) {
            List<item> tier2 = new List<item>();        
            for (integer j=1;j<10;j++) {
                item tier2item = new item('node-label-'+i+j, 'node-name-'+i+j, false, new List<item>());
                tier2.add(tier2item);
            }  
            item tier1item = new item('node-label-'+i, 'node-name-'+i, false, tier2);
            items.add(tier1item);
        }            
        return items; 
    }
    
    @AuraEnabled
    public static List<item> loadChildren(String ParentId){
        List<item> items = new List<item>();
        
        for (integer i=1;i<10;i++) {
            item tier1item = new item('node-'+parentId+'-'+i, 'node-name-'+parentId+'-'+i, false, new List<item>());
            items.add(tier1item);
        }  
        return items;
    }
    
    public class item{
        @AuraEnabled
        public String label {get; set;}
        @AuraEnabled
        public String name {get; set;}
        @AuraEnabled
        public Boolean expanded {get; set;}
        @AuraEnabled
        public List<item> items {get; set;}
        
        public item(String label, String name, Boolean expanded, List<item> items){
            this.label = label;
            this.name = name;
            this.expanded = expanded;
            this.items = items;
        }
    }    
}


User-added image


Alain
 

All Answers

Alain CabonAlain Cabon
Hi Kile,

public static List<item> loadChildren(String ParentId)

but the nodes can just be added one by one.
 
var newItems = response.getReturnValue();
newItems.forEach(function(myitem) {
    myitems[Branch].items.push(myitem); 
});                 
component.set('v.items', myitems);
kyle.moseleykyle.moseley
Alain, 

This was SUPER helpful! 

And it adds them to the screen. My issue now is that it is adding them tot he bottom branch. I am not sure how to add the children to the branch that was selected. 

Before Select:
User-added image

After Select: 
User-added image

I know it is going to be something within the Branch Variable but I don't know how to get the specific one that the select came from. using event.GetParam doesn't seem to do it. Do you have any suggestions on that regard? 
Alain CabonAlain Cabon
Hi Kyle,

This solution is better and simpler.

The key of the nodes are always the "name".

mapitems contains all the nodes (key = name).
 
<aura:component implements="flexipage:availableForAllPageTypes" access="global" controller="ProductTreeController" >
	<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <aura:attribute name="items" type="Object"/>
    <aura:attribute name="expanded" type="Boolean" default="false" />    
    <aura:attribute name="mapitems" type="Map"  default="{}" />
    
<div class="slds-box slds-p-around_small"> 
    <lightning:card title="Product Search" iconName="standard:product">
            <aura:set attribute="actions">
            	<lightning:button label="Refresh List" onclick="{!c.refreshList}"/>
                <lightning:button label="Create Special Item"/>
        	</aura:set>
             
            <lightning:tree items="{! v.items }" header="Product Hierarchy" onselect="{!c.handleSelect}" />

            <!--Lightning Spinner-->
    		<div>
        		<lightning:spinner alternativeText="Processing.." title="Processing.." aura:id="spnr" variant="brand" size="large" />
    		</div>  
    </lightning:card>
</div>    
</aura:component>
Controller JS :
({
    doInit: function (component, event, helper) {
        var spinner = component.find("spnr");
        var action = component.get('c.getProductTree');      
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
                component.set('v.items', response.getReturnValue());
                helper.loadingMap(component.get('v.mapitems'),component.get('v.items'));
                //hide spinner after getting data
                $A.util.toggleClass(spinner, "slds-hide");
            }else{
                $A.util.toggleClass(spinner, "slds-hide");
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    },
    handleSelect: function (component, event, helper) {      
        console.log('name:' + event.getParam('name'));       
        var myitems = component.get('v.items');        
        var Parentid = event.getParam('name');    
        var mapitems = component.get("v.mapitems");
        var selectedItem = mapitems[Parentid];
        var action = component.get('c.loadChildren');
        action.setParams({"ParentId": Parentid});
        action.setCallback(this, function(response){
            var state = response.getState();           
            if(state === 'SUCCESS'){
                var newItems = response.getReturnValue(); 
                helper.loadingMap(mapitems,newItems);
                console.log('return selectedItem:' + JSON.stringify(selectedItem));
                if (selectedItem) {
                    for (let myitem of newItems) {
                        selectedItem.items.push(myitem); 
                    }
                    component.set('v.items', myitems);  
                }              
            }else{
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    },
    refreshList: function (component, event, helper) {   
        var action = component.get('c.getProductTree');
        action.setCallback(this, function(response){
            var state = response.getState();
            if(state === 'SUCCESS'){
                component.set('v.items', response.getReturnValue());          
            }else{
                alert('ERROR');
            }
        });
        $A.enqueueAction(action);
    }
})

Helper:
({
    loadingMap: function (myMap,myItems) {       
        function recurs(myItems) {          
            for (let i=0;i< myItems.length;i++) {    
                myMap[myItems[i].name] = myItems[i];           
            }            
            for (let i=0;i< myItems.length;i++) { 
                if(myItems[i].items && myItems[i].items.length >0) {
                    recurs(myItems[i].items);
                }
            }            
        } 
        recurs(myItems);
    }
})

Controller APEX:
public class ProductTreeController {
    
    @AuraEnabled
    public static List<item> getProductTree(){       
        List<item> items = new List<item>();        
        for (integer i=1;i<10;i++) {
            List<item> tier2 = new List<item>();        
            for (integer j=1;j<10;j++) {
                item tier2item = new item('node-label-'+i+j, 'node-name-'+i+j, false, new List<item>());
                tier2.add(tier2item);
            }  
            item tier1item = new item('node-label-'+i, 'node-name-'+i, false, tier2);
            items.add(tier1item);
        }            
        return items; 
    }
    
    @AuraEnabled
    public static List<item> loadChildren(String ParentId){
        List<item> items = new List<item>();
        
        for (integer i=1;i<10;i++) {
            item tier1item = new item('node-'+parentId+'-'+i, 'node-name-'+parentId+'-'+i, false, new List<item>());
            items.add(tier1item);
        }  
        return items;
    }
    
    public class item{
        @AuraEnabled
        public String label {get; set;}
        @AuraEnabled
        public String name {get; set;}
        @AuraEnabled
        public Boolean expanded {get; set;}
        @AuraEnabled
        public List<item> items {get; set;}
        
        public item(String label, String name, Boolean expanded, List<item> items){
            this.label = label;
            this.name = name;
            this.expanded = expanded;
            this.items = items;
        }
    }    
}


User-added image


Alain
 
This was selected as the best answer
Alain CabonAlain Cabon

This new loadingMap function (Helper) is simpler and probably sufficient.
 
({
    loadingMap: function (myMap,myItems) {       
        function recurs(myItems) {          
            for (let i=0;i< myItems.length;i++) {    
                myMap[myItems[i].name] = myItems[i]; 
                if(myItems[i].items && myItems[i].items.length >0) {
                    recurs(myItems[i].items);
                }
            }                       
        } 
        recurs(myItems);
    }
})

The previous function was better for a search (with a break) but here we want to load all the nodes.