【Salesforce】Apexで連動選択リストを取得する
Salesforceの選択リストには、他の選択リストと連動した「連動選択リスト」という仕組みがあります。
その連動選択リストをApexコード内で取得したのですが、少し複雑だったのでメモです。
とりあえず、選択リストの項目を取得するとすればスキーマですよね。
連動する選択リスト側のスキーマを取得すれば終わりだと思いましたが、そういうわけではないようです。
選択リストのスキーマの中にvalidForという項目があるようなのですが、これが連動関係を示す項目のようです。
しかし、この値をApex内では取得することができないようです。
少し調べてみると、ゼイの人も取れないゼイって言ってました。
http://blog.livedoor.jp/volvic_beer/archives/52382942.html
もう少し調べてみると、スキーマをJSONに変換してvalidForの値を取得している人がいました。
海外の方が作ったもののようですね。
https://developer.salesforce.com/forums/?id=906F0000000BIVqIAO
ソースコードを読んでみると、独自のクラスを作って処理を行っているようです。
これらからヒントを得て、自分でも作ってみることにしました。
/**
* 連動選択リストのMapを作成する
* Map<親の値, Map<子の値, 子のラベル>>
*/
public class DependentPickList{
/**
* 連動選択リストのMapを作成する
* @param parentEntryList 親選択リストのスキーマ
* @param childEntryList 子選択リストのスキーマ
* @return 作成したMap 作成したMap
*/
public static Map<String, Map<String, String>> createSelectListMap(List<Schema.PicklistEntry> parentEntryList, List<Schema.PicklistEntry> childEntryList){
List<Object> jsonStringList;
Map<String, Map<String, String>> validForMap;
Map<String, Integer> hexMap;
Integer parentIndex;
JSONGenerator gen;
String jsonString;
// hex=>intのMapを作成する
hexMap = DependentPickList.createHexMap();
// スキーマの状態では取得できない項目があるため、JSONに変換する
gen = JSON.createGenerator(true);
gen.writeObject(childEntryList);
jsonString = gen.getAsString();
// JSON文字列のリストに変換する
jsonStringList = (List<Object>)JSON.deserializeUntyped(jsonString);
// 親選択リストのindexを初期化する
parentIndex = 0;
// Mapを作成する
validForMap = new Map<String, Map<String, String>>();
// 空白時の値を設定する
validForMap.put('', new Map<String, String>());
for(Schema.PicklistEntry parentEntry : parentEntryList){
String parentValue;
Integer testBit;
// テスト用BITを作成する
testBit = 8 >> (Math.mod(parentIndex, 4));
// 親項目リストの値を取得する
parentValue = parentEntry.getValue();
// Mapに追加する
validForMap.put(parentValue, new Map<String, String>());
// 連動項目でループする
for(Object obj : jsonStringList){
Map<String, Object> schemaMap;
String validFor;
Blob b;
String hexB;
Integer bitIndex;
String targetBit;
Integer bitArray;
// スキーマのMapを作成する
schemaMap = (Map<String, Object>)obj;
// 連動項目のビットセットを取得する
validFor = String.valueOf(schemaMap.get('validFor'));
// 存在しない場合は処理を行わない
if(String.isEmpty(validFor)){
continue;
}
// 紐付けByte列を取得する
b = EncodingUtil.base64Decode(validFor);
// hexを文字列にする
hexB = EncodingUtil.convertToHex(b);
// hexBから親選択リストに対応するBITを含む文字のindexを取得する
bitIndex = parentIndex / 4;
// 文字を取得する
targetBit = hexB.substring(bitIndex, bitIndex + 1);
// 対象の文字が0である場合は処理を行わない
if('0'.equals(targetBit)){
continue;
}
// 文字を数値にする
bitArray = hexMap.get(targetBit);
// 対応するBITが一致する場合、TRUE
if((bitArray & testBit) != 0){
String childValue = String.valueOf(schemaMap.get('value'));
String childLabel = String.valueOf(schemaMap.get('label'));
validForMap.get(parentValue).put(childValue, childLabel);
}
}
// 次のindexへ
parentIndex++;
}
return validForMap;
}
/**
* 0~fの値を数値で返すMapを作成する
*/
private static Map<String, Integer> createHexMap(){
Map<String, Integer> hexMap;
hexMap = new Map<String, Integer>();
hexMap.put('0', 0);
hexMap.put('1', 1);
hexMap.put('2', 2);
hexMap.put('3', 3);
hexMap.put('4', 4);
hexMap.put('5', 5);
hexMap.put('6', 6);
hexMap.put('7', 7);
hexMap.put('8', 8);
hexMap.put('9', 9);
hexMap.put('A', 10);
hexMap.put('B', 11);
hexMap.put('C', 12);
hexMap.put('D', 13);
hexMap.put('E', 14);
hexMap.put('F', 15);
hexMap.put('a', 10);
hexMap.put('b', 11);
hexMap.put('c', 12);
hexMap.put('d', 13);
hexMap.put('e', 14);
hexMap.put('f', 15);
return hexMap;
}
}
// ***** SAMPLE *****
// Map<String, Map<String, String>> dependentSelectMap;
// List<Schema.PicklistEntry> parentEntryList;
// List<Schema.PicklistEntry> childEntryList;
//
// // 親選択リストのスキーマを取得する
// parentEntryList = Account.ParentField__c.getDescribe().getPicklistValues();
// // 子選択リストのスキーマを取得する
// childEntryList = Account.ChildField__c.getDescribe().getPicklistValues();
//
// // Mapを作成する
// dependentSelectMap = DependentPickList.createSelectListMap(parentEntryList, childEntryList);
// ***** SAMPLE *****
ちょっと汚いソースコードになってしまいました。
まず、選択リストのスキーマにある「validFor」の仕組みについて書きます。
一番上のURLにあるように、「validFor」の値はバイトの値なんだそうです。
ビットの列が並んでいて、左から順に親項目の選択リストに連動しているそうです。
たとえば、子項目の選択リストのとある値の「validFor」の一番左のビット列が1の場合、親項目の最初の値の場合に子項目が使用可能になります。
一番上のURLを例にすると
| 親項目 | 子項目 | validFor(Hex) | validFor |
|---|---|---|---|
| コーヒー | カフェインなし | 80 | 1000 0000 |
| レギュラー | 80 | 1000 0000 | |
| 紅茶 | カモミール | 40 | 0100 0000 |
| アールグレイ | 40 | 0100 0000 | |
| イングリッシュブレックファスト | 40 | 0100 0000 |
親項目が「コーヒー」の場合に使用可能な「カフェインなし」と「レギュラー」のvalidForの最初のビットは1となります。
また、親項目が「紅茶」の場合に使用可能な「カモミール」「アールグレイ」「イングリッシュブレックファスト」のvalidForの2つ目のビットが1となります。
ちなみに、どちらでも使用可能な項目があった場合の「validFor」は、1番目と2番目のビットの両方が1となります。
Apexで取得できない値である「validFor」ですが、取得できないだけでプロパティとしては持っているそうです。
そのため、スキーマごとJSONの状態に変換して文字列として取得しています。
上のURLの猛者の人もそのように取得しています。
// スキーマの状態では取得できない項目があるため、JSONに変換する gen = JSON.createGenerator(true); gen.writeObject(childEntryList); jsonString = gen.getAsString(); // JSON文字列のリストに変換する jsonStringList = (List<Object>)JSON.deserializeUntyped(jsonString);
上にも書いたように、連動関係を持っている「validFor」の値はバイトの値となっています。
それを文字列として取得しているため、Base64の値に変換されていますね。
ゼイの人はVisualforce内の処理でBase64をデコードしています。
// 紐付けByte列を取得する b = EncodingUtil.base64Decode(validFor);
Apexコード内ではbyteの値を触ることが出来ませんが、ビットシフトのシフト演算や論理演算は出来ます。
そのためにはIntegerの型に変換する必要があるため、デコードしたBase64(Blob)をHexの値にしています。
// hexを文字列にする hexB = EncodingUtil.convertToHex(b);
先ほどの例で書くと、コーヒーの場合に有効な値の「validFor」は「800000」となりました。
また、紅茶の場合に有効な値の「validFor」は「400000」となりました。
どちらも有効な場合は「A00000」となります。
実際に出力すると分かりやすいですね。
ここまでくると、あとは対応するHexと親項目のindexを対応させるだけです。
Hexにはしましたが、実際のバイト列は「1100 0000 0000 0000 0000 0000」となっています。
4bitずつ処理する必要があるため、indexを4で割った際の値にあるHexが対応する値になりますね。
さらに、bitの演算はIntegerで行うため、MapからIntegerの値を取得しています。
// hexBから親選択リストに対応するBITを含む文字のindexを取得する bitIndex = parentIndex / 4; // 文字を取得する targetBit = hexB.substring(bitIndex, bitIndex + 1); // 文字を数値にする bitArray = hexMap.get(targetBit);
取得したHexの文字と対応する、判定用のHexの文字を作成します。
// テスト用BITを作成する testBit = 8 >> (Math.mod(i, 4));
2つのbitで論理演算を行い、1であれば連動して表示される項目となります。
// 対応するBITが一致する場合、TRUE
if((bitArray & testBit) != 0){
String childValue = String.valueOf(schemaMap.get('value'));
String childLabel = String.valueOf(schemaMap.get('label'));
validForMap.get(parentLabel).put(childValue, childLabel);
}
出力されたMapの値を使ってVisualforce側で動作をつけてやれば、連動選択リストを実装できます。
選択リストの項目が大量にあると、処理に時間がかかるので注意が必要です。
少し長くなってしまいましたが、実際に作ってみると楽しかったです。
これを作って遊んでいたら数時間が経過していたことは内緒です。
No comments.