【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.