Last active
October 1, 2024 17:55
-
-
Save boxfoot/4166342 to your computer and use it in GitHub Desktop.
Handle cases where one dependent option can be used for multiple controlling options
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Apex doesn't expose dependent picklist info directly, but it's possible to expose. | |
* Approach: | |
* * Schema.PicklistEntry doesn't expose validFor tokens, but they are there, and can be accessed by serializing to JSON | |
* (and then for convenience, deserializing back into an Apex POJO) | |
* * validFor tokens are converted from base64 representations (e.g. gAAA) to binary (100000000000000000000) | |
* each character corresponds to 6 bits, determined by normal base64 encoding rules. | |
* * The binary bits correspond to controlling values that are active - e.g. in the example above, this dependent option | |
* is available for the first controlling field only. | |
* | |
* by Benj Kamm, 2017 | |
* CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0/us/) | |
*/ | |
public class HL_FieldDescribeUtil { | |
public static Map<String, List<String>> getDependentOptionsImpl(Schema.SObjectField theField, Schema.SObjectField ctrlField) { | |
// validFor property cannot be accessed via a method or a property, | |
// so we need to serialize the PicklistEntry object and then deserialize into a wrapper. | |
List<Schema.PicklistEntry> contrEntries = ctrlField.getDescribe().getPicklistValues(); | |
List<PicklistEntryWrapper> depEntries = | |
HL_FieldDescribeUtil.wrapPicklistEntries(theField.getDescribe().getPicklistValues()); | |
// Set up the return container - Map<ControllingValue, List<DependentValues>> | |
Map<String, List<String>> objResults = new Map<String, List<String>>(); | |
List<String> controllingValues = new List<String>(); | |
for (Schema.PicklistEntry ple : contrEntries) { | |
String label = ple.getLabel(); | |
objResults.put(label, new List<String>()); | |
controllingValues.add(label); | |
} | |
for (PicklistEntryWrapper plew : depEntries) { | |
String label = plew.label; | |
String validForBits = 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(label); | |
} | |
} | |
} | |
return objResults; | |
} | |
// 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; | |
} | |
// Convert a base64 token into a binary/bits representation | |
// e.g. 'gAAA' => '100000000000000000000' | |
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; | |
} | |
private static final String base64Chars = '' + | |
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + | |
'abcdefghijklmnopqrstuvwxyz' + | |
'0123456789+/'; | |
private static List<PicklistEntryWrapper> wrapPicklistEntries(List<Schema.PicklistEntry> PLEs) { | |
return (List<PicklistEntryWrapper>) | |
JSON.deserialize(JSON.serialize(PLEs), List<PicklistEntryWrapper>.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;} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* getDependentPicklistOptions | |
* by Benj Kamm, 2012 | |
* (inspired by http://iwritecrappycode.wordpress.com/2012/02/23/dependent-picklists-in-salesforce-without-metadata-api-or-visualforce/) | |
* CC BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0/us/) | |
* | |
* Build an Object in which keys are valid options for the controlling field | |
* and values are lists of valid options for the dependent field. | |
* | |
* Method: dependent PickListEntry.validFor provides a base64 encoded | |
* string. After decoding, each of the bits (reading L to R) | |
* corresponds to the picklist values for the controlling field. | |
*/ | |
function getDependentOptions (objName, ctrlFieldName, depFieldName) { | |
// Isolate the Describe info for the relevant fields | |
var objDesc = sforce.connection.describeSObject(objName); | |
var ctrlFieldDesc, depFieldDesc; | |
var found = 0; | |
for (var i=0; i<objDesc.fields.length; i++) { | |
var f = objDesc.fields[i]; | |
if (f.name == ctrlFieldName) { | |
ctrlFieldDesc = f; | |
found++; | |
} else if (f.name == depFieldName) { | |
depFieldDesc = f; | |
found++; | |
} | |
if (found==2) break; | |
} | |
// Set up return object | |
var dependentOptions = {}; | |
var ctrlValues = ctrlFieldDesc.picklistValues; | |
for (var i=0; i<ctrlValues.length; i++) { | |
dependentOptions[ctrlValues[i].label] = []; | |
} | |
var base64 = new sforce.Base64Binary(""); | |
function testBit (validFor, pos) { | |
var byteToCheck = Math.floor(pos/8); | |
var bit = 7 - (pos % 8); | |
return ((Math.pow(2, bit) & validFor.charCodeAt(byteToCheck)) >> bit) == 1; | |
} | |
// For each dependent value, check whether it is valid for each controlling value | |
var depValues = depFieldDesc.picklistValues; | |
for (var i=0; i<depValues.length; i++) { | |
var thisOption = depValues[i]; | |
var validForDec = base64.decode(thisOption.validFor); | |
for (var ctrlValue=0; ctrlValue<ctrlValues.length; ctrlValue++) { | |
if (testBit(validForDec, ctrlValue)) { | |
dependentOptions[ctrlValues[ctrlValue].label].push(thisOption.label); | |
} | |
} | |
} | |
return dependentOptions; | |
} | |
var OBJ_NAME = 'Custom_Object__c'; | |
var CTRL_FIELD_NAME = "Controlling_Field__c"; | |
var DEP_FIELD_NAME = "Dependent_Field__c"; | |
var options = getDependentOptions(OBJ_NAME, CTRL_FIELD_NAME, DEP_FIELD_NAME); | |
console.debug(options); | |
Hi @fahey252 I want to tweak your code. In my version I assume method may be used several time, so I added memorization. Another tune, is to use integers as bitwise arrays, since bitwise operation are very fast.
const base64CharacterSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const memory = new Map();
function base64EncodingToBinaryBits(value) {
if (memory.has(value)) return memory.get(value);
var v = value
.split('')
.map(character => base64CharacterSet
.indexOf(character)
.toString(2)
.padStart(6, '0')
.split('')
.reverse()
.join('')
)
.reverse()
.join('');
v = parseInt(v, 2);
memory.set(value, v);
return v;
}
function isValidFor(validFor, i) {
return base64EncodingToBinaryBits(validFor) & 1 << i;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For JavaScript, this may be helpful:
Helper functions:
Tests for helpers: