Apex

apex:actionStatusで作る待機表示の実装

この記事は2023年に書いたものです。当時はjQueryのblockUIプラグインを静的リソースに登録して待機表示を作っていました。いまはVisualforce標準のapex:actionStatusだけで足ります。現行の書き方を本文に、当時の実装は別の節に分けました。Lightning Experienceでの注意点も加えています。

2023.02.06

この記事の読み方

最初に書いた時点では、jQueryとblockUIプラグインを静的リソースに登録し、JavaScriptで画面をふさぐ実装でした。いまは同じことがVisualforce標準のapex:actionStatusだけでできます。外部ライブラリも静的リソースも要りません。

そこでこの記事は、次のように分けています。

  • 本文の手順は、apex:actionStatusで組む現行の書き方です
  • 「当時の記録」の節は、2023年当時にjQueryのblockUIで組んでいたときの記録です。いまの手順では使いません
記事中のスクリーンショットは、当時のSalesforce Classicの画面です。現在の画面とは見た目が異なります。⚠️ 当時の画面を載せている箇所は、キャプションでその旨を書いています。

シナリオ

カスタムオブジェクトの詳細画面に「メール送信」ボタンを置き、押すと指定したメールアドレスへメールを送ります。送信には数百ミリ秒からの待ち時間があるため、送信が終わるまで画面をふさぐローディング表示を出し、連打による二重送信を防ぎます。

いまから作るなら、まずLWCを検討する

先に前提を書きます。Salesforce公式のVisualforce開発者ガイドは、新規開発についてVisualforceよりLightning Experienceのローコードツールとlightning web componentを勧めています。理由として、より応答のよいユーザ体験、最新のWCAG(Webコンテンツアクセシビリティガイドライン)への対応、複数の画面サイズへの最適化のしやすさ、性能の4点を挙げています。

一方で、Visualforce自体はLightning ExperienceとSalesforce Classicの両方で引き続き使えます。公式ドキュメントに廃止の予告は見当たりませんでした。この記事は「すでにあるVisualforceページに待機表示を足す」話として読んでください。ゼロから画面を作るなら、まずLWCで組めないかを先に検討する順番になります。

Apexコントローラ

送信処理と、送信後に元のレコード画面へ戻る処理を1つのコントローラにまとめます。

public with sharing class MailSendVFController {
    public String resultMsg { get; set; }
    private testObject1__c newObjBackup;

    public MailSendVFController(ApexPages.StandardController controller) {
        this.resultMsg = '';
        this.newObjBackup = (testObject1__c) controller.getRecord();
    }

    public void sendMessage() {
        String subject = '【メール配信】';
        String plainTextBody = '各位\n\nテスト用メールをお送りいたします。\n宜しくお願い致します。\n';
        List<String> sendTo = new List<String>{ 'xxxxxxxx@example.com' };
        try {
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            mail.setToAddresses(sendTo);
            mail.setSubject(subject);
            mail.setPlainTextBody(plainTextBody);
            mail.setReplyTo('xxxxxxxx@example.com');
            Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ mail });
            this.resultMsg = 'メール送信されました。';
        } catch (System.EmailException emlEx) {
            this.resultMsg = '送信に失敗しました。';
            System.debug('emlEx:' + emlEx);
        }
    }

    public PageReference changePage() {
        return new PageReference('/' + this.newObjBackup.Id);
    }
}

メール送信と、送信後の画面遷移を持つコントローラです。標準コントローラの拡張として使います。

当時の記事のコードは、例外発生時にresultMsgを設定していませんでした。これだと送信に失敗したとき、待機表示が終わらないまま止まって見えます。catch節でも必ずメッセージを設定するよう直しています。

Visualforceページの実装

待機表示には外部のjQueryライブラリを使わず、Visualforce標準のapex:actionStatusを使います。開始時に表示するfacetを用意し、CSSだけでオーバーレイを作れます。ページをレンダリング・実行するAPIのバージョンはapiVersion属性で指定するため、現行の67.0を明示します。

<apex:page standardController="testObject1__c" extensions="MailSendVFController" apiVersion="67.0">
    <style>
        .loadingOverlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.6); color: #fff;
            display: flex; align-items: center; justify-content: center;
            z-index: 9999;
        }
    </style>
    <apex:form>
        <apex:actionFunction name="doSend" action="{!sendMessage}"
            status="sendStatus" reRender="resultHolder" oncomplete="afterSend();" />
        <apex:actionFunction name="doChangePage" action="{!changePage}" />
        <apex:actionStatus id="sendStatus">
            <apex:facet name="start">
                <div class="loadingOverlay">処理しています。しばらくお待ちください。</div>
            </apex:facet>
        </apex:actionStatus>
        <apex:outputPanel id="resultHolder">
            <apex:outputText id="resultText" value="{!resultMsg}" style="display:none" />
        </apex:outputPanel>
    </apex:form>
    <script>
        function afterSend() {
            var msg = document.getElementById('{!$Component.resultHolder.resultText}').innerText;
            if (msg) { alert(msg); }
            doChangePage();
        }
        doSend();
    </script>
</apex:page>

apiVersionを67.0に指定しています。ページを開いた瞬間にdoSend()を呼び、送信中はapex:actionStatusのオーバーレイを表示します。画面遷移は引き続きコントローラのPageReferenceで行います。

3つのタグがどうつながっているか

公式のコンポーネントリファレンスの定義で並べると、次の関係になります。

タグ・属性公式の説明この実装での役割
apex:actionStatusAJAX更新要求の状態を表示するコンポーネント。要求は「実行中」か「完了」のどちらか待機表示そのもの
apex:actionStatusのidactionStatusをページ内の他のコンポーネントから参照できるようにする識別子sendStatusという名前を付ける
apex:actionFunctionのstatusAJAX更新要求の状態を表示する、関連付けられたコンポーネントのIDsendStatusを指して連動させる
apex:actionFunctionのreRenderアクションメソッドの結果がクライアントへ返ったときに再描画されるコンポーネントのID結果メッセージの入れ物だけを描き直す
apex:actionFunctionのoncompleteAJAX更新要求がクライアント側で完了したときに実行されるJavaScript結果を読んでアラートを出し、画面を戻す

AJAX処理が始まるとstartfacetが表示され、終わると自動的に隠れます。停止時に別の表示を出したいならstopfacetを足します。facetはstartText/stopText属性の代わりに使うもので、文字だけでよければstartText、自由なマークアップを置きたいならfacetという使い分けです。見た目だけ差し替えたいならstartStyleClassにCSSクラスを渡す書き方もあります。

⚠️ apex:actionStatusのfor属性は、apex:actionFunctionとつなぐための属性ではありません。公式の定義は「状態表示の対象となるapex:actionRegionコンポーネントのID」です。apex:actionRegionは、AJAX要求が起きたときにサーバ側で処理する範囲を区切るコンポーネントで、再描画する範囲を決めるものではありません。再描画の範囲はreRender側で指定します。今回のように送信ボタンが1つだけのページではapex:actionRegionは使いません。

なおapex:actionFunctionはapex:formの子要素として置く必要があります。コード例でapex:formの中に入れているのはこのためです。

ボタンを作成してページレイアウトに配置する

  1. 詳細ページボタンを作成する

    対象オブジェクトのボタンとリンクから新規作成します。表示の種類は詳細ページボタン、内容のソースはVisualforceページを選び、上で作ったページを指定します。「現在のウィンドウにサイドバー付きで表示」はClassicのサイドバーを前提にした設定です。

  2. ページレイアウトへ配置する

    対象オブジェクトのページレイアウトを編集モードで開き、左のパレットで「ボタン」を選び、作成したボタンを下の「カスタムボタン」欄へドラッグして保存します。

  3. Lightning Experienceで開いて確認する

    URLまたはVisualforceを内容のソースとするカスタムボタンはLightning Experienceでも使えますが、表示のされ方はClassicと異なります。実機のレコード詳細画面で確認します。

カスタムボタンの詳細画面。表示の種類は詳細ページボタン、内容のソースはVisualforceページ
⚠️ 当時のSalesforce Classicの画面です。設定する項目はいまも同じです。表示の種類が「詳細ページボタン」、内容のソースが「Visualforce ページ」になっているかを確かめてください。作成者と更新者の行は伏せています。
ページレイアウトの編集画面。左のパレットでボタンを選び、下のカスタムボタン欄にメール送信ボタンが配置されている
⚠️ 当時のSalesforce Classicの画面です。左のパレットで「ボタン」を選び、下の「カスタムボタン」欄に「メール送信ボタン」が入っているかを見てください。

動作確認

確認は、レコードを作るところから通しで行います。送信されるメールの宛先はコントローラに直接書いているので、テスト用のアドレスになっているかを先に見てください。

  1. 確認用のレコードを作る

    対象のカスタムオブジェクトで新規レコードを作ります。名前は「メール送信テスト」のように、後から見て確認用と分かるものにします。

  2. レコードの詳細画面を開く

    保存したレコードの詳細画面を開き、上部のボタン列に「メール送信ボタン」が出ていることを確認します。出ていなければ、ページレイアウトのカスタムボタン欄への配置が漏れています。

  3. メール送信ボタンを押す

    Visualforceページへ遷移し、ページを開いた時点でdoSend()が呼ばれて送信処理が始まります。

  4. ローディング表示を確認する

    送信が終わるまで、apex:actionStatusのstart facetが画面を覆うことを確認します。ここで画面が覆われていなければ、apex:actionFunctionのstatus属性とidの対応を見直します。

  5. 完了のメッセージを確認する

    送信が終わると待機表示が消え、結果メッセージのアラートが出ます。OKを押すと、コントローラのPageReferenceによって元のレコード画面へ戻ります。

  6. 届いたメールを確認する

    宛先の受信箱を開き、コントローラで組み立てた件名と本文のまま届いているかを確かめます。失敗していれば「送信に失敗しました。」が出るので、待機表示が出たまま止まることはありません。

testObject1の編集画面。testObject1名の欄にメール送信テストと入力されている
⚠️ 当時の画面です。確認用のレコードを作るところです。名前は後から見て確認用と分かるものにします。所有者の欄は伏せています。
testObject1のレコード詳細画面。上部のボタン列にメール送信ボタンが並んでいる
⚠️ 当時のSalesforce Classicでのレコード詳細画面です。上部のボタン列に「メール送信ボタン」が出ていれば配置は成功です。所有者と、作成者・最終更新者の行は伏せています。
ブラウザの確認ダイアログ。メール送信されました、というメッセージとOKボタンが出ている
送信が終わったときに出るメッセージです。OKを押すと元のレコード画面へ戻ります。組織のホスト名の行は伏せています。
受信したメールの本文。各位、テスト用メールをお送りいたします、宜しくお願い致しますと書かれている
実際に届いたメールの本文です。コントローラで組み立てた本文どおりに届いているかを確かめてください。

当時の記録:jQueryのblockUIを静的リソースに登録していた

⚠️ この節は2023年当時の実装の記録です。上の手順では使いません。

当時は待機表示を、jQuery本体とblockUIプラグインの2つのJavaScriptファイルで作っていました。Salesforceのページから外部のJavaScriptライブラリを使うときは、ファイルを静的リソースとして組織の中に置き、$Resourceで参照するのが基本の形です。そこで、この2つを別々の静的リソースとして登録していました。

当時の静的リソースMIMEタイプサイズ役割
Jquerytext/javascript88,145バイトjQuery本体
blockuitext/javascript20,584バイト画面をふさぐblockUIプラグイン

記事の最後の「参考:当時の画面」に載せたローディング表示のスクリーンショットは、このblockUIで作っていたときのものです。灰色の半透明の膜と、黒い枠の白い箱に赤い文字、という見た目は当時の実装によるものです。apex:actionStatusとCSSで組み直したいまの実装では、見た目は自分で書いたCSSのとおりになります。画面をふさいでいる間は操作できない、という挙動だけが同じです。

静的リソースの登録手順そのものは、当時から変わっていません。

  1. ライブラリのファイルを用意する

    配布元からJavaScriptファイルを取得します。著作権表示とライセンス条件の行は削らずに残します。

  2. 静的リソースを新規作成する

    設定のクイック検索で「静的リソース」を開き、「新規」を押します。名前は英数字とアンダースコアだけで、先頭は英字、組織内で一意にします。説明は任意です。

  3. ファイルを選んでキャッシュコントロールを決める

    ローカルのファイルを選び、キャッシュコントロールをPrivate(認証済みユーザごとのキャッシュ)かPublic(共有キャッシュ)から選んで保存します。当時の画面ではどちらも「公開」、つまりPublicでした。

  4. Visualforceページから読み込む

    <apex:includeScript value="{!$Resource.Jquery}" />のように、$Resourceグローバル変数へリソース名を渡して参照します。IDを直書きしません。

MIMEタイプは入力する欄ではなく、アップロードしたファイルから決まって詳細画面に表示されます。上の表のtext/javascriptもそうして付いた値です。容量の上限は1ファイル5MB、組織全体で250MBです。

なぜいまは使わないのか

待機表示のためだけにjQueryとblockUIを入れると、2つの静的リソースを組織内で保守し続けることになります。ライブラリの更新やライセンス表記の管理も付いてきます。apex:actionStatusはVisualforceの標準コンポーネントで同じことができるので、この記事の用途では外部ライブラリを足す理由がありません。QRコード生成のように標準コンポーネントで代わりが利かない機能を使うときだけ、静的リソースの出番になります。

Lightning Experienceでの注意点

公式ヘルプは、Visualforceページについて「まずLightning Experienceでテストしてください。ほとんどはそのまま動きます」と案内しています。そのうえで、この実装に関わる点を挙げます。

Lightning Experience上のVisualforceページは、iframeの中に読み込まれます。Salesforce公式の開発者ブログは「Lightning Experienceでホストされるvisualforceページはiframeの中に読み込まれる」と明記しており、Visualforceページ(visual.force.com)とLightningの画面(lightning.force.com)は別のドメインから読み込まれるとしています。

⚠️ つまりposition: fixedのオーバーレイが覆うのは、そのiframeの内側だけです。Lightning Experienceのヘッダやナビゲーションまでふさぐことはできません。連打による二重送信を防ぐという目的(ボタンのあるページ自体をふさぐ)は満たせますが、「画面全体が暗くなる」という当時のClassicの見え方とは違います。

項目公式の記載この実装への影響
window.locationでの遷移Lightning Experienceと互換性がないと明記この実装は使っていません。画面遷移はコントローラのPageReferenceです
静的URLでのリンクLightning Experienceは静的URLでのSalesforceリソースへのリンクをサポートしない。URLFORで組み立てる詳細画面へ戻すPageReferenceが期待どおりに動くかは、Lightning Experienceの画面で確かめてください
iframeURLをiframeで表示するとLightning Experienceでエラーになることがある待機表示のオーバーレイが覆う範囲が上記のとおり変わります
iPad SafariLightning ExperienceのiPad SafariではVisualforceページとカスタムiframeはサポート対象外iPadでの利用は想定から外してください

ここで間違えやすい

間違い何が起きるか
apex:actionStatusのidとapex:actionFunctionのstatus属性を対応させ忘れる待機表示が出ないまま処理が終わります
apex:actionStatusのfor属性でapex:actionFunctionとつなごうとするforはapex:actionRegionのIDを指す属性です。連動させるのはapex:actionFunction側のstatus属性です
apex:actionFunctionをapex:formの外に置くapex:actionFunctionはapex:formの子要素である必要があります
例外時にresultMsgを設定しない送信に失敗しても画面がそのまま止まって見えます。catch節でも必ずメッセージを設定します
Lightning Experienceでも画面全体がふさがれると思い込むVisualforceページはiframeの中に読み込まれます。オーバーレイが覆うのはその内側だけです
詳細ページボタンの動作設定をClassic基準のまま考える「現在のウィンドウにサイドバー付きで表示」はClassicのサイドバーを前提にした設定です。実機で表示を確認します
待機表示のために外部ライブラリを静的リソースへ足す標準のapex:actionStatusで足ります。保守する対象が増えるだけになります
apiVersionを指定しないどのAPIバージョンでページが動くか読み手に分かりません。現行のバージョンを明示します

確認した環境

  • 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで確認しています
  • Visualforce開発者ガイドのapex:actionStatus・apex:actionRegion・apex:actionFunction・apex:pageの各コンポーネントリファレンスで、属性の定義とfacetの扱いを確認しています
  • Visualforce開発者ガイド「Why Should I Use Lightning Web Components instead of Visualforce?」で、新規開発にlightning web componentを勧める記載を確認しています
  • Salesforceヘルプ「Define Static Resources」「Using Static Resources」で、静的リソースの作成手順と容量の上限(1ファイル5MB、組織合計250MB)を確認しています
  • Salesforceヘルプ「Prepare Your Visualforce Pages for Lightning Experience」で、window.locationと静的URLの扱いを確認しています
  • Lightning Experience上のVisualforceページがiframeに読み込まれる点は、Salesforce公式の開発者ブログ「Communicating between Lightning Components and Visualforce Pages」で確認しています

まとめ

  • 待機表示はapex:actionStatusで組みます。jQueryや外部ライブラリの静的リソースは不要です
  • apex:actionStatusにidを付け、apex:actionFunctionのstatus属性から指して連動させます。for属性はapex:actionRegion用なので混同しません
  • Salesforce公式は新規開発にlightning web componentを勧めています。Visualforceは引き続き使えますが、ゼロから作るならまずLWCを検討してください
  • Lightning ExperienceではVisualforceページがiframeの中に読み込まれます。オーバーレイが覆うのはその内側だけです
  • 例外発生時もresultMsgを必ず設定し、待機表示が止まらなくなるのを防ぎます
  • 当時はjQueryのblockUIを静的リソースに登録していました。登録手順自体はいまも同じですが、この用途では標準コンポーネントで足ります

参考:当時の画面

記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。

画面全体が灰色の膜で覆われ、中央の黒い枠の箱に処理しています、しばらくお待ち下さいと赤い文字で出ている
これは当時jQueryのblockUIで作っていたときの見た目で、apex:actionStatusとCSSで組み直したいまの実装では、自分で書いたCSSのとおりの見た目になります。ふさがれている間は操作できない、という挙動だけが同じです。
静的リソースJqueryの詳細画面。MIMEタイプはtext/javascript、キャッシュコントロールは公開、サイズは88,145バイト
当時登録していたjQuery本体の静的リソースです。MIMEタイプは入力欄ではなく、アップロードしたファイルから決まります。作成者と最終更新者の行は伏せています。
静的リソースblockuiの詳細画面。MIMEタイプはtext/javascript、キャッシュコントロールは公開、サイズは20,584バイト
blockUIプラグイン側の静的リソースです。この2つを$Resourceで読み込んで画面をふさいでいました。作成者と最終更新者の行は伏せています。

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

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