ServiceWise Developers’ Blog – Dependent Picklist in Lightning Component

Welcome to our new Developers Blog! Our R&D team here at ServiceWise is dedicated to finding unorthodox and groundbreaking coding solutions for customers. So, we thought – why not share these solutions with the world?

For our first blog post, we chose to present to you a solution for a dependent Picklist in Lightning component. The purpose of this solution was to create a dependent Picklist and present it in a custom UI in Lightning, according to the customer’s request.

Why did we need to create a custom solution? The customer had two picklists – Country (controlling list) and Region (dependent). For each picklist entry of the controlling list, Salesforce saves a property name – “valid for” – which is a string in Base64 encoding. It looks like that – gAAA. In order to transfer this string to bytes, we had to go through the following process:
Base64 -> binary -> bitwise operation -> bytes

Example:
[table id=2 /]

Functions to manipulate the Base64 string (the main function is base64ToBits)

// base64Chars hold the base 64 chars
	public static final String base64Chars = '' +
                        'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
			'abcdefghijklmnopqrstuvwxyz' +
			'0123456789+/'
	  // Convert decimal to binary representation 
         //Remainder (0 or 1).
	// these, in reverse order, are the binary.
	
      public static String decimalToBinary(Integer val) {
		String bits = '';
		while (val > 0) {
			Integer remainder = Math.mod(val, 2);
			val = Integer.valueOf(Math.floor(val / 2));
			bits = String.valueOf(remainder) + bits;
		}
		return bits;
	}//decimalToBinary
	// Convert a base64 token into a binary/bits representation
	// e.g. 'gAAA' => '1000000	00000 0000 00000'
	public static String base64ToBits(String validFor) {
		if (String.isEmpty(validFor)) return '';

		String validForBits = '';

		for (Integer i = 0; i < validFor.length(); i++) {
			String thisChar = validFor.mid(i, 1);
			Integer val = base64Chars.indexOf(thisChar);
			String bits = decimalToBinary(val).leftPad(6, '0');
			validForBits += bits;
		}

		return validForBits;
	}//base64ToBits

In order to find the controlling picklist entry that corresponds with an entry in the dependent picklists, we go over the bits of the “valid for” property of the dependent entry picklist from left to right. Each bit corresponds with an option in the index of the bit. (1 = it corresponds with the dependent picklist entry[i], 0 = it doesn’t correspond with it). However, Salesforce doesn’t reveal the “valid for” information in the APEX classes, but in the API calls. So, to overcome this problem we created a wrapper class:

public class PicklistEntryWrapper {
		public String active {get; set;}
		public String defaultValue {get; set;}
		public String label {get; set;}
		public String value {get; set;}
		public String validFor {get; set;}
	}

How did we do that?
First of all, note that all the functions that take care of the bitwise operation are handled in a class named “Bitset” (including the wrapper class).
Full Bitset class code:

Full Bitset class code  
public class Bitset  {

	public class PicklistEntryWrapper {
		public String active {get; set;}
		public String defaultValue {get; set;}
		public String label {get; set;}
		public String value {get; set;}
		public String validFor {get; set;}
	}
	// base64Chars holde the base 64 chars
	public static final String base64Chars = '' +
			'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
			'abcdefghijklmnopqrstuvwxyz' +
			'0123456789+/';

	//convert picklist entery to wreper
	public static List<PicklistEntryWrapper> wrapPicklistEntries(List<Schema.PicklistEntry> PLEs) {
		return (List<PicklistEntryWrapper>)
			JSON.deserialize(JSON.serialize(PLEs), List<PicklistEntryWrapper>.class);
	}//wrapPicklistEntries

	// Convert decimal to binary representation (alas, Apex has no native method :-(
	//    eg. 4 => '100', 19 => '10011', etc.
	// Method: Divide by 2 repeatedly until 0. At each step note the remainder (0 or 1).
	// These, in reverse order, are the binary.
	public static String decimalToBinary(Integer val) {
		String bits = '';
		while (val > 0) {
			Integer remainder = Math.mod(val, 2);
			val = Integer.valueOf(Math.floor(val / 2));
			bits = String.valueOf(remainder) + bits;
		}
		return bits;
	}//decimalToBinary

	// Convert a base64 token into a binary/bits representation
	// e.g. 'gAAA' => '1000000	00000 0000 00000'
	public static String base64ToBits(String validFor) {
		if (String.isEmpty(validFor)) return '';

		String validForBits = '';

		for (Integer i = 0; i < validFor.length(); i++) {
			String thisChar = validFor.mid(i, 1);
			Integer val = base64Chars.indexOf(thisChar);
			String bits = decimalToBinary(val).leftPad(6, '0');
			validForBits += bits;
		}

		return validForBits;
	}//base64ToBits

}//Bitset

Let’s break it down to steps:

  1. We’ve got two fields – dependent and non-dependent. Here’s an example with the Country and Region fields.
    Schema.DescribeFieldResult fieldResultCountry = User.Countrycode.getDescribe();//get country code 
    List<Schema.PicklistEntry> contrEntries = fieldResultCountry.getPicklistValues();//parse country code to list
    
  2. We created a map with a key type “string” => The map holds the controlling picklist entry value (in our example – Country), and the value the “string” list type => that will hold the dependent picklist entry values (in our example – Region). Eventually we’ll get a map with the key “Country” and value list of regions.
    Map<String,List<String>> objResults = new Map<String,List<String>>();
    
  3. We created a picklist from the wrapper class that we made and inserted into it the entries from the dependent picklist, after we serialized and de-serialized them.
    //convert picklist entry to wrapper
    public static List<PicklistEntryWrapper> wrapPicklistEntries(List<Schema.PicklistEntry> PLEs) {
    return (List<PicklistEntryWrapper>)JSON.deserialize(JSON.serialize(PLEs), List<PicklistEntryWrapper>.class);
    }//wrapPicklistEntries
    
    List<Bitset.PicklistEntryWrapper> depEntries = Bitset.wrapPicklistEntries(fieldResultState.getPicklistValues());
    
  4. We went over all of the picklist entries in the controlling picklist and inserted their values to a string list – “controlling values”. We inserted each value as a key to the map we created in step 2 and gave it the value “new list”. Now we’ll have a map with Country as keys and empty lists as value, and a list that holds the Country values.
    list<String> controllingValues = new List<String>();
    		
    		for (Schema.PicklistEntry ple : contrEntries) {
    			String valueStr = ple.getValue();
    			objResults.put(valueStr, new List<String>());
    			controllingValues.add(valueStr);
    	}
    
  5. We went over the dependent entry picklist in the wrapper list from step 2. We kept the value of each entry. In the same time, each “valid for” field in the different values is being converted into bits.
  6. We went over the bits in the string we’ve created. If the bit = 1 we added the value to the dependent list as a controlling value, to the map from step 2.
    for (Bitset.PicklistEntryWrapper plew : depEntries) {
    	String value = plew.value;
    	String validForBits = Bitset.base64ToBits(plew.validFor);
    	for (Integer i = 0; i < validForBits.length(); i++) {
    // For each bit, in order: if it's a 1, add this label to the dependent list for the corresponding controlling value
    		   String bit = validForBits.mid(i, 1);
    	   if (bit == '1') {
    			     objResults.get(controllingValues.get(i)).add(value);
    	   }
    	 }
           }</li>
    
    
  7. We ended up with a map that holds the Country field and a list of Regions!
  8. Full code of Bitset:
    public class Bitset  {
    
    	public class PicklistEntryWrapper {
    		public String active {get; set;}
    		public String defaultValue {get; set;}
    		public String label {get; set;}
    		public String value {get; set;}
    		public String validFor {get; set;}
    	}
    	// base64Chars holde the base 64 chars
    	public static final String base64Chars = '' +
    			'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
    			'abcdefghijklmnopqrstuvwxyz' +
    			'0123456789+/';
    
    	//convert picklist entery to wreper
    	public static List<PicklistEntryWrapper> wrapPicklistEntries(List<Schema.PicklistEntry> PLEs) {
    		return (List<PicklistEntryWrapper>)
    			JSON.deserialize(JSON.serialize(PLEs), List<PicklistEntryWrapper>.class);
    	}//wrapPicklistEntries
    
    	// Convert decimal to binary representation (alas, Apex has no native method :-(
    	//    eg. 4 => '100', 19 => '10011', etc.
    	// Method: Divide by 2 repeatedly until 0. At each step note the remainder (0 or 1).
    	// These, in reverse order, are the binary.
    	public static String decimalToBinary(Integer val) {
    		String bits = '';
    		while (val > 0) {
    			Integer remainder = Math.mod(val, 2);
    			val = Integer.valueOf(Math.floor(val / 2));
    			bits = String.valueOf(remainder) + bits;
    		}
    		return bits;
    	}//decimalToBinary
    
    	// Convert a base64 token into a binary/bits representation
    	// e.g. 'gAAA' => '1000000	00000 0000 00000'
    	public static String base64ToBits(String validFor) {
    		if (String.isEmpty(validFor)) return '';
    
    		String validForBits = '';
    
    		for (Integer i = 0; i < validFor.length(); i++) {
    			String thisChar = validFor.mid(i, 1);
    			Integer val = base64Chars.indexOf(thisChar);
    			String bits = decimalToBinary(val).leftPad(6, '0');
    			validForBits += bits;
    		}
    
    		return validForBits;
    	}//base64ToBits
    
    }//Bitset
    

    Full code of the function that we used in the Lightning component (we will pass that function the desired country code to get the list of depended regions)

    @AuraEnabled global static List<String> getRegonPiklistValuse(String country){
    
    Map<String,List<String>> objResults = new Map<String,List<String>>();
    /* get all country codes */
    List<String>countryCodes = new List<String>();//list that holds the country code
    
    Schema.DescribeFieldResult fieldResultCountry = User.Countrycode.getDescribe();//get country code 
    List<Schema.PicklistEntry> contrEntries = fieldResultCountry.getPicklistValues();//parse country code to list
    
    		
    		/*get all state codes */
    List<String>stateCodes = new List<String>();//list that holds the state code
    Schema.DescribeFieldResult fieldResultState = User.statecode.getDescribe();//get state code 
    List<Bitset.PicklistEntryWrapper> depEntries = Bitset.wrapPicklistEntries(fieldResultState.getPicklistValues());
    
    List<String> controllingValues = new List<String>();
    		
    for (Schema.PicklistEntry ple : contrEntries) {
    	String valueStr = ple.getValue();
    	objResults.put(valueStr, new List<String>());
    	controllingValues.add(valueStr);
    }
    
    for (Bitset.PicklistEntryWrapper plew : depEntries) {
    	String value = plew.value;
    	String validForBits = Bitset.base64ToBits(plew.validFor);
    	for (Integer i = 0; i < validForBits.length(); i++) {
    // For each bit, in order: if it's a 1, add this label to the dependent     list for the corresponding controlling value
    		String bit = validForBits.mid(i, 1);
    		if (bit == '1') {
    								 objResults.get(controllingValues.get(i)).add(value);
    		}
    	}
    }
    return objResults.get(country);
    	 
    }//countryStateCodeStract
    
    

    We hope this post taught you something new, and showed you how creative and innovative you can get with code! See you next time on our blog, with another unprecedented solution from the ServiceWise R&D team!

    Let us know in the comments if there's a specific subject you'd like us to write about, and if you found this post helpful.

    If you'd like to hear more about our solutions and services, don't hesitate to contact us.