Apex

Visualforceで一覧から選択リストを一括更新する

Visualforceの一覧画面で複数の商談を選び、選択リストの値をまとめて更新する例です。StandardSetControllerの拡張クラスとカスタムリストボタンの設定を確認します。

2024.11.14

なぜ一覧から選択して更新するのか

複数の商談をまとめて確認しながら、選択リストの値だけ変えて保存したい場面があります。標準の一括編集画面では画面のレイアウトを自由に変えられないので、一覧で選んだレコードをVisualforceページへ渡し、Apexの拡張クラスで保存する方法を使います。

Visualforceページ

一覧ボタンから渡された商談を、選択リストつきの表で表示します。

<apex:page standardController="Opportunity" recordSetVar="records" lightningStyleSheets="true" extensions="UpdateOfSelectList">
    <apex:pageMessages id="pageMessages" />
    <apex:form>
        <apex:pageBlock title="選択リスト値の更新">
            <apex:pageBlockTable value="{!targetList}" var="target">
                <apex:column headerValue="商談Id"><apex:outputLink value="/{!target.id}" target="_blank">{!target.id}</apex:outputLink></apex:column>
                <apex:column headerValue="Delivery/Installation Status">
                    <apex:selectList value="{!target.deliveryInstallationStatus}" size="1" multiselect="false">
                        <apex:selectOptions value="{!deliveryInstallationStatus}" />
                    </apex:selectList>
                </apex:column>
            </apex:pageBlockTable>
            <apex:pageBlockButtons location="bottom">
                <apex:commandButton action="{!save}" value="保存" />
                <apex:commandButton action="{!close}" value="閉じる" />
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>

StandardSetControllerの拡張として動きます。標準の保存・閉じるボタンで完結します。

recordSetVar="records"を指定すると、コントローラの引数にApexPages.StandardSetControllerが渡ってきます。一覧で選んだレコードは、この中のgetSelected()で取得します。

Apex拡張クラス

選択されたレコードのIdを受け取り、選択リストの値だけ表示・更新します。

public with sharing class UpdateOfSelectList {
    public final String INFO_MSG = 'エラーです。ご確認ください。';
    public final String ERROR_MSG = '更新エラーです。ご確認ください。';
    public final Integer MAXNUM = 99;
    public List<TargetOpportunity> targetList { get; set; }

    public UpdateOfSelectList(ApexPages.StandardSetController controller) {
        List<Opportunity> selected = controller.getSelected();
        targetList = new List<TargetOpportunity>();
        searchTargetOpportunity(selected);
    }

    private void searchTargetOpportunity(List<Opportunity> selected) {
        List<Opportunity> oppList = [SELECT Id, DeliveryInstallationStatus__c FROM Opportunity
            WHERE Id IN :(new Map<Id, Opportunity>(selected)).keySet() ORDER BY Id ASC LIMIT 100];
        if (oppList.isEmpty() || oppList.size() > MAXNUM) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, INFO_MSG));
            return;
        }
        for (Opportunity opp : oppList) {
            TargetOpportunity target = new TargetOpportunity();
            target.id = opp.Id;
            target.deliveryInstallationStatus = opp.DeliveryInstallationStatus__c;
            targetList.add(target);
        }
    }

    public class TargetOpportunity {
        public String id { get; set; }
        public String deliveryInstallationStatus { get; set; }
    }

    public List<SelectOption> getDeliveryInstallationStatus() {
        List<SelectOption> options = new List<SelectOption>{ new SelectOption('', '--なし--') };
        for (Schema.PicklistEntry entry : Opportunity.DeliveryInstallationStatus__c.getDescribe().getPicklistValues()) {
            options.add(new SelectOption(entry.getValue(), entry.getLabel()));
        }
        return options;
    }

    public PageReference save() {
        Savepoint sp = Database.setSavepoint();
        try {
            List<Opportunity> updateList = new List<Opportunity>();
            for (TargetOpportunity target : targetList) {
                updateList.add(new Opportunity(Id = target.id, DeliveryInstallationStatus__c = target.deliveryInstallationStatus));
            }
            if (!updateList.isEmpty()) update updateList;
        } catch (DmlException e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, ERROR_MSG));
            Database.rollback(sp);
        }
        return null;
    }

    public PageReference close() {
        return null;
    }
}

getSelected()で選択済みのIdを取得し、あらためてSOQLで最新の値を取り直しています。

一覧で選んだレコードの値をそのまま画面に表示するのではなく、getSelected()で取れたIdだけを使い、選択リストの値はあらためてSOQLで取り直しています。一覧画面の表示は必ずしも最新とは限らないため、保存前の値は再取得したほうが安全です。

選択リストの選択肢を出す仕組み

apex:selectOptionsに渡すList<SelectOption>は、項目の選択リスト定義から作ります。Schema.PicklistEntryには有効な値だけが入るため、無効化した選択肢は選べません。

メソッド・プロパティ内容
getPicklistValues()その組織で有効な選択肢の一覧(PicklistEntryのリスト)
entry.getValue()保存される実際の値
entry.getLabel()画面に出すラベル

カスタムリストボタンを作る

このVisualforceページを、商談の一覧から呼び出せるようにします。

  1. ボタン、リンク、アクションを開く

    商談オブジェクトの管理設定で「ボタン、リンク、アクション」を開き、新規ボタンまたはリンクを作成します。

  2. 表示タイプをリストボタンにする

    表示の種類に「リストボタン」を選び、コンテンツソースに「Visualforceページ」を選び、作成したページを指定します。

  3. 一覧のボタン欄に追加する

    商談の検索レイアウトを開き、一覧ボタンの欄に作成したボタンを追加して保存します。

一覧ボタンから呼び出すVisualforceページは、recordSetVarを指定し、コントローラの引数にApexPages.StandardSetControllerを受け取る必要があります。standardControllerだけでは、選択したレコードの集合を受け取れません。

動作確認の流れ

  1. 商談レコードを作成する

    選択リスト値の更新を試すため、商談をいくつか作成します。

  2. 一覧でレコードを選び、ボタンを押す

    商談一覧で対象のレコードにチェックを入れ、作成したリストボタンを押します。

  3. 初期値を確認する

    Visualforceページが開き、選択したレコードぶんの行と、いまの選択リスト値が表示されます。

  4. 選択リストの値を変えて保存する

    行ごとに選択リストの値を変え、保存ボタンを押します。

  5. 商談側で反映を確認する

    商談の詳細画面を開き、選択リストの値が更新されていることを確認します。

ここで間違えやすい

間違い何が起きるか
standardControllerだけで一覧の選択を受けようとするrecordSetVarが無いと、コントローラの引数にStandardSetControllerが渡りません
選択件数の上限を確認せずに保存するこのコントローラはMAXNUM(99件)を超えるとメッセージを出すだけで処理を続けません。上限を超えたら分けて実行してください
一覧の値をそのまま保存に使う一覧の表示は最新とは限りません。保存前に対象レコードを再取得してから使うほうが安全です
無効化した選択リストの値を選ばせようとするgetPicklistValues()は有効な選択肢しか返さないため、無効値は選べません

確認した環境

  • 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ガイドで、StandardSetController・apex:selectList・カスタムリストボタンの作成手順を確認しています

まとめ

  • 一覧で選択した複数レコードは、recordSetVarを指定したVisualforceページとStandardSetControllerの拡張クラスで受け取ります
  • 選択リストの選択肢はSchema.PicklistEntryから作り、apex:selectListとapex:selectOptionsで画面に出します
  • 保存前の値は、一覧の表示に頼らずSOQLで取り直すと安全です
  • このVisualforceページを呼び出すには、商談オブジェクトにカスタムリストボタンを作り、一覧のボタン欄に追加します
  • 選択件数が上限(MAXNUM)を超えると、このコントローラは処理を続けずメッセージだけを出します
  • 無効化された選択リストの値は、getPicklistValues()に含まれないため選べません

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。