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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | // 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:
1 2 3 4 5 6 7 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 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:
- We’ve got two fields – dependent and non-dependent. Here’s an example with the Country and Region fields.
12
Schema.DescribeFieldResult fieldResultCountry = User.Countrycode.getDescribe();
//get country code
List<Schema.PicklistEntry> contrEntries = fieldResultCountry.getPicklistValues();
//parse country code to list
- 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.
1
Map<String,List<String>> objResults =
new
Map<String,List<String>>();
- 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.
123456
//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());
- 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.
1234567
list<String> controllingValues =
new
List<String>();
for
(Schema.PicklistEntry ple : contrEntries) {
String valueStr = ple.getValue();
objResults.put(valueStr,
new
List<String>());
controllingValues.add(valueStr);
}
- 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.
- 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.
1234567891011
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>
- We ended up with a map that holds the Country field and a list of Regions!
- Full code of Bitset:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
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)
12345678910111213141516171819202122232425262728293031323334353637@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.