Visualforceのレコード保存処理時、標準バリデーションエラーを回避して任意のactionを実行させる


目的・目標

  • Visualforceのページメッセージを処理ごとに毎回クリアしたい
  • 確認画面、完了画面のないVisualforceページで編集画面のみでapex:pageMessagesを利用して視覚的に更新が完了・エラーした事を伝えたい
  • 処理実行前にVisualforce画面のapex:pageMessagesをクリアして任意の処理後に再度、完了・エラーを表示させたい

Before(保存1の処理)

Visualforceコード

<apex:pageBlockButtons location="bottom">
  <apex:commandButton value="保存1" action="{!save}" reRender="container" status="spinnerStatus"/>
</apex:pageBlockButtons>

動作画面

  • 保存ボタンを押下したときロード画面が表示されているだけで更新されたのが認識しづらい

Try(保存2の処理)

Visualforceコード

<apex:pageBlockButtons location="bottom">
  <apex:actionFunction name="save1B" action="{!clearVfMsg}" oncomplete="return false;" reRender="container" />
  <apex:commandButton value="保存2" onclick="save1B();" action="{!save}" reRender="container" status="spinnerStatus"/>
</apex:pageBlockButtons>

動作画面

改善

  • immediate="true"apex:actionFunctionにつけることで保存ボタン押下時にデータ型、桁数(Salesforce標準)、必須項目(Salesforce標準)のバリデーションエラーが発生した場合にメッセージクリア処理が実行されないことを回避しました

問題

  • メッセージクリア前にロード画面が入ってしまう
  • ボタンを連打するとapex:pageMessagesがバグで表示されない場合がある(キャプション 4回目の保存ボタン押下時)

After(保存3の処理)

Visualforceコード

<apex:pageBlockButtons location="bottom">
  <apex:actionFunction name="save1C" action="{!clearVfMsg}" oncomplete="save2C();" reRender="container" immediate="true"/>
  <apex:actionFunction name="save2C" action="{!save}" reRender="container" status="spinnerStatus"/>
  <apex:commandButton value="保存3" onclick="save1C(); return false;"/>
</apex:pageBlockButtons>

動作画面

改善

  • メッセージクリア後にロード画面が表示される
  • ボタンを連打してもapex:pageMessagesにバグが発生しない

学習したこと

  • immediate="true"apex:actionFunctionにつけることで保存ボタン押下時にデータ型、桁数(Salesforce標準)、必須項目(Salesforce標準)のバリデーションエラーが発生する場合でも任意の処理を先に実行させる事ができる
  • apex:actionFunctiononcompleteを利用して処理をメソッドチェーンさせることができる
  • メソッドチェーンさせることで任意の処理でロード画面などを実行させることができる

サンプルコード

Visualforceページ
<apex:page controller="VfTestCtrl" title="取引先責任者 編集" lightningStylesheets="true">
  <style>
    .spinnerBg{
        width: 100%;
        height: 100%;
        position: absolute;
        background-color: #000;
        opacity: 0.2;
        z-index: 999999;
    }
    .spinner{
        width: 100%;
        height: 100%;
        position: absolute;
        background-image: url("/img/loading32.gif");
        background-size: 16px;
        background-repeat: no-repeat;
        background-attachment: fixed;
        background-position: center;
        z-index: 9999999;
        opacity: 1;
    }
  </style>

  <apex:actionStatus id="spinnerStatus">
      <apex:facet name="start">
          <div class="spinnerBg"/>
          <div class="spinner"/>
      </apex:facet>
  </apex:actionStatus>

  <apex:form>
    <apex:outputPanel id="container">
      <apex:pageBlock title="取引先責任者 編集">
        <div style="min-height: 120px">
          <apex:pageMessages/>
        </div>
        <apex:pageBlockSection columns="1">
          <apex:inputField value="{!con.TestNum__c}"/>
        </apex:pageBlockSection>
        <apex:pageBlockButtons location="bottom">
          <apex:commandButton value="保存1" action="{!save}" reRender="container" status="spinnerStatus"/>
        </apex:pageBlockButtons>
        <apex:pageBlockButtons location="bottom">
          <apex:actionFunction name="save1B" action="{!clearVfMsg}" oncomplete="return false;" reRender="container" />
          <apex:commandButton value="保存2" onclick="save1B();" action="{!save}" reRender="container" status="spinnerStatus"/>
        </apex:pageBlockButtons>
        <apex:pageBlockButtons location="bottom">
          <apex:actionFunction name="save1C" action="{!clearVfMsg}" oncomplete="save2C();" reRender="container" immediate="true"/>
          <apex:actionFunction name="save2C" action="{!save}" reRender="container" status="spinnerStatus"/>
          <apex:commandButton value="保存3" onclick="save1C(); return false;"/>
        </apex:pageBlockButtons>
      </apex:pageBlock>
    </apex:outputPanel>
  </apex:form>
</apex:page>
VFコントローラ
public without sharing class VfTestCtrl {

    public Contact con { get; set; }
    private Id conId;

    /** 画面表示メッセージ */
    private static final String CONTACT_NOT_EXIST_MSG       = '取引先責任者が存在しません。';
    private static final String CONTACT_UPDATE_COMPLETE_MSG = '取引先責任者の更新が完了しました。';


    public VfTestCtrl() {
        this.conId = ApexPages.currentPage().getParameters().get('Id');

        List<Contact> conList = queryContact(conId);
        if (conList.isEmpty()) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, CONTACT_NOT_EXIST_MSG));
            return;
        }
        this.con = conList[0];
    }


    /** VFメッセージクリア処理 */
    public void clearVfMsg() {
        ApexPages.getMessages().clear();
    }


    /** 保存処理 */
    public void save() {
        List<Contact> conList = queryContact(conId);
        if (conList.isEmpty()) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, CONTACT_NOT_EXIST_MSG));
            return;
        }

        update con;
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.CONFIRM, CONTACT_UPDATE_COMPLETE_MSG));
        return;
    }


    /** 対象の取引先責任者を取得 */
    private List<Contact> queryContact(Id contactId) {
        return [
            SELECT
                Id,
                TestNum__c
            FROM
                Contact
            WHERE
                Id = :contactId
        ];
    }
}