Salesforce のリストビューボタンで使うApex の書き方で知らなかったこと


これまでリストビューのアクションボタンで利用するスクリプトを書いたことがなかったので勉強になったことをまとめておきます。

今回はリストビューボタンを押すと、VisualforcePageが立ち上がり、そこで意思決定をすると、Apexがレコードを処理してくれるという機能です。具体的に言うと、レコードのマージ機能がリード/取引先責任者/取引先のみにしか提供されておらず、カスタムオブジェクトのマージは自分で実装するしか無いということで作ったという流れです。

こういうビューリストの右上に設置するボタンですね。

リストにチェックを入れてボタンを押すと、重複チェックページへと遷移します。

このような画面で、それぞれのレコードで相違がある項目だけを表示し、ラジオボタンで選択させる。
左下のマージボタンを押すとマージ処理が走るという具合です。

チェックされたレコードを取得するには?

LnestID_merge.cls
public with sharing class LnestID_merge {
    public LnestID_merge(ApexPages.StandardSetController controller) {
        List<Duplicate_LnestIDs__c> duplicates = controller.getSelected();
        this.selectedDuplicates = [
            SELECT 
                Id, 
                LnestID__c
            FROM Duplicate_LnestIDs__c WHERE Id IN :(new Map<Id, Duplicate_LnestIDs__c>(duplicates)).keySet()];

こんな感じに書きます。
controller.getSelected()とやると、チェックが入った当該レコードの IDのみ を返してくれます。
そのため、そのIDリストを用いて、本体のレコードを取得するという流れになります。
そこからあとはよしなにという感じですね。

apex:selectRadio は使わなかった

最初に貼った画像の様に、ラジオボタンでどの情報を使うかを選択させるのですが、ラジオボタンといえばapex:selectRadioだろということで仕様を見た結果これは使いませんでした。

<apex:selectRadio value="{!country}">
	<apex:selectOptions value="{!items}"/>
</apex:selectRadio><p/>

こんなイメージで使うのですが、使いづらいですね。
今回でいうと、テーブルの

の中に選択肢を一つずつ埋めたかったのですが、これが出来ないので諦めました。

そんなわけで、VisualforcePage側では、<apex:repeat 系のタグは使わずに、Apexクラス側で生成したテーブルを

<apex:outputText value="{!table_string}" escape="false" />

こんな形で表示する方法を選びました。
それに伴いapex:commandButtonも使っていません。選択した情報の取得方法が分からなかったので。
ボタンクリックでjs呼び出してパラメータ送信する形にしました。

テストをどう書けば良いんだろうか

新しい実装を行った時に一番困るのはテストの書き方ですね。
チェックボックスにチェックを入れて、それを受け取るっていうのの書き方がわからないぞ…と思っていたのですが簡単でした。

lidmerge.cls
    ApexPages.StandardSetController stdSetController = new ApexPages.StandardSetController(dup_list);
    stdSetController.setSelected(dup_list);

dup_listには、リストビューで表示しているオブジェクトのレコードリストが入っています。
これをStandardSetControllerにわたすのですが、それだけでは不足していて
setSelected()を使ってセットする必要があるようです。

jsでパラメータをApexにわたす方法

Visualforceページで選択した情報をjsを使ってApexにわたす必要があります。これをどうすればよいかという話。
今回は、パラメータに乗せる情報が動的に変わります。情報を変更したい部分のみの情報を送るからですね。

Visualforce.page
        <apex:actionFunction name="lidMergeAction" action="{!lidMergeExecute}" rerender="mergeButton" >
            <apex:param name="params" value="params" />
        </apex:actionFunction>  

Visualforceページ側にはparamsのみを設定し、この中に全てのパラメータを詰めて送信することにしました。
マージボタンを押すと、lidMerge()が発火するようになっており、その中で上記のapex:actionFunctionであるlidMergeActionが呼ばれています。

merge.js
        function lidMerge(){
            params = JSON.stringify(params)
            lidMergeAction(params)
        }

paramsはjs側で作った連想配列で、これをJSON.stringifyを使ってテキスト化して渡します。
それをApexクラス側で受け取るには

lidmerge.cls
    public PageReference lidMergeExecute(){
        Savepoint sp = Database.setSavepoint();
        params = ApexPages.CurrentPage().getParameters().get('params'));
        Map<String, Object> json_params = (Map<String, Object>)JSON.deserializeUntyped(params);
        system.debug('ids : ' + json_params.get('ids'));

こんな形でPageReference型のクラスの中で
ApexPages.CurrentPage().getParameters().get('params'))
これを呼びます。
取得できたparamsをMapに入れるためにJSON.deserializeUntypedを行います。
あとは、値を取り出して使うだけでOKです。

カスタム項目は書き込み可能か

更新を行うに当たり、書き込めない項目を更新しようとするとエラーが出てしまいます。
そのため、editableかどうかを判定しなくてはなりません。

lidmerge.cls
                Map<String, Schema.SObjectField> M = Schema.SObjectType.LnestID__c.fields.getMap();
                for(String key : columns){
                    Schema.SObjectField field = M.get(key);
                    Schema.DisplayType fldType = field.getDescribe().getType();
                    Boolean is_editable = field.getDescribe().isUpdateable();

上記のような書き方でis_editableを取得しました。

今回の発見は以上でした。十分色々なことをやってきたと思っていましたが、意外と知らないことがありますね。