apex:actionStatusで作る待機表示の実装
この記事は2023年に書いたものです。当時はjQueryのblockUIプラグインを静的リソースに登録して待機表示を作っていました。いまはVisualforce標準のapex:actionStatusだけで足ります。現行の書き方を本文に、当時の実装は別の節に分けました。Lightning Experienceでの注意点も加えています。
この記事の読み方
最初に書いた時点では、jQueryとblockUIプラグインを静的リソースに登録し、JavaScriptで画面をふさぐ実装でした。いまは同じことがVisualforce標準のapex:actionStatusだけでできます。外部ライブラリも静的リソースも要りません。
そこでこの記事は、次のように分けています。
- 本文の手順は、
apex:actionStatusで組む現行の書き方です - 「当時の記録」の節は、2023年当時にjQueryのblockUIで組んでいたときの記録です。いまの手順では使いません
シナリオ
カスタムオブジェクトの詳細画面に「メール送信」ボタンを置き、押すと指定したメールアドレスへメールを送ります。送信には数百ミリ秒からの待ち時間があるため、送信が終わるまで画面をふさぐローディング表示を出し、連打による二重送信を防ぎます。
いまから作るなら、まず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:actionStatus | AJAX更新要求の状態を表示するコンポーネント。要求は「実行中」か「完了」のどちらか | 待機表示そのもの |
apex:actionStatusのid | actionStatusをページ内の他のコンポーネントから参照できるようにする識別子 | sendStatusという名前を付ける |
apex:actionFunctionのstatus | AJAX更新要求の状態を表示する、関連付けられたコンポーネントのID | sendStatusを指して連動させる |
apex:actionFunctionのreRender | アクションメソッドの結果がクライアントへ返ったときに再描画されるコンポーネントのID | 結果メッセージの入れ物だけを描き直す |
apex:actionFunctionのoncomplete | AJAX更新要求がクライアント側で完了したときに実行される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の中に入れているのはこのためです。
ボタンを作成してページレイアウトに配置する
詳細ページボタンを作成する
対象オブジェクトのボタンとリンクから新規作成します。表示の種類は詳細ページボタン、内容のソースはVisualforceページを選び、上で作ったページを指定します。「現在のウィンドウにサイドバー付きで表示」はClassicのサイドバーを前提にした設定です。
ページレイアウトへ配置する
対象オブジェクトのページレイアウトを編集モードで開き、左のパレットで「ボタン」を選び、作成したボタンを下の「カスタムボタン」欄へドラッグして保存します。
Lightning Experienceで開いて確認する
URLまたはVisualforceを内容のソースとするカスタムボタンはLightning Experienceでも使えますが、表示のされ方はClassicと異なります。実機のレコード詳細画面で確認します。
動作確認
確認は、レコードを作るところから通しで行います。送信されるメールの宛先はコントローラに直接書いているので、テスト用のアドレスになっているかを先に見てください。
確認用のレコードを作る
対象のカスタムオブジェクトで新規レコードを作ります。名前は「メール送信テスト」のように、後から見て確認用と分かるものにします。
レコードの詳細画面を開く
保存したレコードの詳細画面を開き、上部のボタン列に「メール送信ボタン」が出ていることを確認します。出ていなければ、ページレイアウトのカスタムボタン欄への配置が漏れています。
メール送信ボタンを押す
Visualforceページへ遷移し、ページを開いた時点で
doSend()が呼ばれて送信処理が始まります。ローディング表示を確認する
送信が終わるまで、
apex:actionStatusのstart facetが画面を覆うことを確認します。ここで画面が覆われていなければ、apex:actionFunctionのstatus属性とidの対応を見直します。完了のメッセージを確認する
送信が終わると待機表示が消え、結果メッセージのアラートが出ます。OKを押すと、コントローラの
PageReferenceによって元のレコード画面へ戻ります。届いたメールを確認する
宛先の受信箱を開き、コントローラで組み立てた件名と本文のまま届いているかを確かめます。失敗していれば「送信に失敗しました。」が出るので、待機表示が出たまま止まることはありません。
当時の記録:jQueryのblockUIを静的リソースに登録していた
⚠️ この節は2023年当時の実装の記録です。上の手順では使いません。
当時は待機表示を、jQuery本体とblockUIプラグインの2つのJavaScriptファイルで作っていました。Salesforceのページから外部のJavaScriptライブラリを使うときは、ファイルを静的リソースとして組織の中に置き、$Resourceで参照するのが基本の形です。そこで、この2つを別々の静的リソースとして登録していました。
| 当時の静的リソース | MIMEタイプ | サイズ | 役割 |
|---|---|---|---|
Jquery | text/javascript | 88,145バイト | jQuery本体 |
blockui | text/javascript | 20,584バイト | 画面をふさぐblockUIプラグイン |
記事の最後の「参考:当時の画面」に載せたローディング表示のスクリーンショットは、このblockUIで作っていたときのものです。灰色の半透明の膜と、黒い枠の白い箱に赤い文字、という見た目は当時の実装によるものです。apex:actionStatusとCSSで組み直したいまの実装では、見た目は自分で書いたCSSのとおりになります。画面をふさいでいる間は操作できない、という挙動だけが同じです。
静的リソースの登録手順そのものは、当時から変わっていません。
ライブラリのファイルを用意する
配布元からJavaScriptファイルを取得します。著作権表示とライセンス条件の行は削らずに残します。
静的リソースを新規作成する
設定のクイック検索で「静的リソース」を開き、「新規」を押します。名前は英数字とアンダースコアだけで、先頭は英字、組織内で一意にします。説明は任意です。
ファイルを選んでキャッシュコントロールを決める
ローカルのファイルを選び、キャッシュコントロールをPrivate(認証済みユーザごとのキャッシュ)かPublic(共有キャッシュ)から選んで保存します。当時の画面ではどちらも「公開」、つまりPublicでした。
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の画面で確かめてください |
| iframe | URLをiframeで表示するとLightning Experienceでエラーになることがある | 待機表示のオーバーレイが覆う範囲が上記のとおり変わります |
| iPad Safari | Lightning 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を静的リソースに登録していました。登録手順自体はいまも同じですが、この用途では標準コンポーネントで足ります
参考:当時の画面
記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。