LoginSignup
2
3

More than 5 years have passed since last update.

intra-martのスクリプト開発でimuiPageDialogのbeforeclose時に確認ダイアログを表示する

Last updated at Posted at 2018-07-09

対象となる開発形態

  • intra-mart AccelPlatform
    • スクリプト開発

やりたいこと

imuiPageDialogで内容が更新されているのにページダイアログを閉じようとした時、beforecloseイベントで確認ダイアログを表示し、OKなら閉じる/キャンセルなら閉じないようにする。

問題点

  1. beforecloseイベントでページダイアログ内の入力内容変更チェックを行いたいため、呼び出し元ページでイベント設定ができない。
  2. 確認ダイアログはimuiMessageDialogを利用するが、非同期で動作する(確認ダイアログが表示されても、ボタン押下を待たずに次の処理に進んでfunctionが終了してしまう)ため同期的に動かす仕組みが必要になる。
  3. 同期的に動かした場合、ボタン押下で再度ページダイアログのbeforecloseイベントが発動するため、処理がループしてしまう。

解決策

beforecloseイベントをページダイアログ側でバインドする

ページダイアログ側でbeforecloseイベントをバインドするには、対象のimuiPageDialogに対してon('imuipagedialogbeforeclose', function(event, ui) {})を実行します。
ただし、ページダイアログ側では呼び出し元で何というIDを指定したか分からないため、呼び出し元からリクエストパラメータとして受け取って指定します。

sample.html
<imart type="imuiButton" id="dialogButton" value="ボタン" class="imui-medium-button" />

<script type="text/javascript">
   $(function() {
      // ページダイアログID
      var pageDialogId = 'samplePageDialog';

      // ページダイアログ呼び出し
      $('<div></div>').attr('id', pageDialogId)
                      .appendTo(document.body)
                      .imuiPageDialog({
            title: 'ページダイアログ',
            url: 'pageDialog',
            parameter: {
               'imui-theme-builder-module': 'notheme',
               pageDialogId: pageDialogId
            },
            position: [650, 300],
            modal: true,
            height: 750,
            width: 465,
            close: function(event, ui) {
               // imuiPageDialogIdを作成したdivを削除
               $('#' + pageDialogId).remove();
            }
         });
      });
   });
</script>
pageDialog.html
<imart type="hidden"
   pageDialogId=$pageDialogId
/>

<imart type="imuiTextbox" id=textbox1" value="text1" />

<script type="text/javascript">
   $(function() {
      // 引数で渡されたページダイアログID
      var pageDialogId = $('input[name="pageDialogId"]').val();

      // ページダイアログのbeforeCloseイベントをバインド
      $('#' + pageDialogId).on('imuipagedialogbeforeclose', function(event, ui) {
         // imuiMessageDialog表示
         var dialogId = 'dialog_saveConfirmDialog';
         $('<div></div>').text('入力内容を保存しますか?')
                         .attr('id', dialogId)
                         .imuiMessageDialog({
            iconType: 'info',
            title: '確認',
            verticalAlign: 'top',
            modal: true,
            buttons: [
               {
                  'id': 'save',
                  'text': '保存',
                  'click': function() {
                     save();
                     $('#' + dialogId).imuiMessageDialog('close');
                     return true;
                  }
               },
               {
                  'id': 'unsave',
                  'text': '破棄',
                  'click': function() {
                     $('#' + dialogId).imuiMessageDialog('close');
                     return true;
                  }
               },
               {
                  'id': 'cancel',
                  'text': 'キャンセル',
                  'click': function() {
                     $('#' + dialogId).imuiMessageDialog('close');
                     return false;
                  }
               }
            ],
            close: function(event, ui) {
               $('#' + dialogId).remove();
            }
         });
      });
   });
</script>
pageDialog.js
// リクエストパラメータのページダイアログID
var $pageDialogId;

function init(request) {
   $pageDialogId = request.pageDialogId;
}

なお、上記のソースではimuiMessageDialogの同期処理対応ができていないため、実行すると確認ダイアログが表示された瞬間にページダイアログが閉じてしまいます。

確認ダイアログを同期的に動作させる

imuiMessageDialogを同期的に動作させるため、処理をdeferredを使って書き換えます。
ついでに、保存処理についてもdeferredを使用するようにしています。

pageDialog.html
<imart type="hidden"
   pageDialogId=$pageDialogId
/>

<imart type="imuiTextbox" id=textbox1" value="text1" />

<script type="text/javascript">
   // 名前空間
   if (typeof pageDialog === 'undefined') {
      var pageDialog = {}; 
   }

   // 引数で渡されたページダイアログID
   pageDialog.pageDialogId = $('input[name="pageDialogId"]').val();

   $(function() {
      // ページダイアログのbeforeCloseイベントをバインド
      $('#' + pageDialog.pageDialogId).on('imuipagedialogbeforeclose', function(event, ui) {
         var confirmPromise = changeConfirm();
         confirmPromise.done(function() {
            return true;
         });
         confirmPromise.fail(function() {
            return false;
         });
      });
   });

   function changeConfirm() {
      // deferredオブジェクト
      var confirmDeferred = new $.Deferred();

      // imuiMessageDialog表示
      var dialogId = 'dialog_saveConfirmDialog';
      $('<div></div>').text('入力内容を保存しますか?')
                      .attr('id', dialogId)
                      .imuiMessageDialog({
         iconType: 'info',
         title: '確認',
         verticalAlign: 'top',
         modal: true,
         buttons: [
            {
               'id': 'save',
               'text': '保存',
               'click': function() {
                  var savePromise = save();
                  savePromise.done(function() {
                     imuiShowSuccessMessage('保存しました。');
                     $('#' + dialogId).imuiMessageDialog('close');
                     // 保存成功時はページダイアログを閉じる
                     confirmDeferred.resolve();
                  });
                  savePromise.fail(function() {
                     imuiShowErrorMessage('保存に失敗しました。');
                     $('#' + dialogId).imuiMessageDialog('close');
                     // 保存失敗時はページダイアログを閉じない
                     confirmDeferred.reject();
                  });
               }
            },
            {
               'id': 'unsave',
               'text': '破棄',
               'click': function() {
                  imuiShowSuccessMessage('変更を破棄しました。');
                  $('#' + dialogId).imuiMessageDialog('close');
                  // 変更破棄時はページダイアログを閉じる
                  confirmDeferred.resolve();
               }
            },
            {
               'id': 'cancel',
               'text': 'キャンセル',
               'click': function() {
                  $('#' + dialogId).imuiMessageDialog('close');
                  // キャンセル時はページダイアログを閉じない
                  confirmDeferred.reject();
               }
            }
         ],
         close: function(event, ui) {
            $('#' + dialogId).remove();
         }
      });
      return confirmDeferred.promise();
   }

   function save() {
      // deferredオブジェクト
      var saveDeferred = new $.Deferred();

      // 保存処理
      $.ajax({
         type: 'POST',
         url: 'save',
         async: false,
         data: {data: $('#textbox1').val()},
         success: function(res, dataType) {
            imuiShowSuccessMessage('保存しました。');
            saveDeferred.resolve();
         },
         error: function(res, textStatus, xhr) {
            imuiShowErrorMessage(res.responseText);
            saveDeferred.reject();
         }
      });
      return saveDeferred.promise();
   }
</script>
pageDialog.js
// リクエストパラメータのページダイアログID
var $pageDialogId;

function init(request) {
   $pageDialogId = request.pageDialogId;
}

書き換えてもやはり確認ダイアログ表示と同時にページダイアログは閉じてしまいます。
処理は待機してボタン押下と同時にpromiseオブジェクトのfunctionは実行されるのですが、beforecloseイベント自体はそのまま終了してしまうからです。
同期処理にした意味がない……。

変更状態を監視して同期処理の結果から処理を実行させる

deferredで同期的に処理が実行できる所までは解決できました。
ここからは多少力技ですが、mutationObserverを利用して確認ダイアログ押下状態を監視、deferredの同期処理で押下状態を変更することでMutationObserverに検知させることにしました。
先頭の<imart type="hidden">タグに、確認ダイアログ押下状態を格納するconfirmStatusを追加し、mutationObserverの監視対象に指定しています。
属性監視なので監視タイプはattributes、フィルタでvalue属性のみ監視するよう設定しました。

pageDialog.html
<imart type="hidden"
   pageDialogId=$pageDialogId
   confirmStatus=""
/>

<imart type="imuiTextbox" id=textbox1" value="text1" />

<script type="text/javascript">
   // 名前空間
   if (typeof pageDialog === 'undefined') {
      var pageDialog = {}; 
   }

   // 引数で渡されたページダイアログID
   pageDialog.pageDialogId = $('input[name="pageDialogId"]').val();

   // 確認ダイアログのボタン押下状態監視
   pageDialog.observer = new MutationObserver(checkConfirmStatus);

   $(function() {
      // ページダイアログのbeforeCloseイベントをバインド
      $('#' + pageDialog.pageDialogId).on('imuipagedialogbeforeclose', function(event, ui) {
         if ($('input[name="confirmStatus"]').val() === '') {
            // 確認ダイアログのボタンが押下されていない場合、状態をwaitにして確認ダイアログを表示する
            $('input[name="confirmStatus"]').val('wait');
            var confirmPromise = changeConfirm();
            confirmPromise.done(function() {
               // promiseがresolveになった場合(更新成功/破棄時)、状態をcloseにする
               $('input[name="confirmStatus"]').val('close');
            });
            confirmPromise.fail(function() {
               // promiseがrejectになった場合(更新失敗/キャンセル時)、状態をcancelにする
               $('input[name="confirmStatus"]').val('cancel');
            });
         }

         switch ($('input[name="confirmStatus"]').val()) {
         case 'wait':
            // 状態がwaitの場合、MutationObserverの監視を開始してfalseを返却(ページダイアログを閉じない)
            pageDialog.observer.observe($('input[name="confirmStatus"]')[0], {attributes: true, attributeFilter: ['value']});
            return false;
         case 'close':
            // 状態がcloseの場合、trueを返却(ページダイアログを閉じる)
            return true;
         case 'cancel':
            // 状態がcancelの場合、falseを返却(ページダイアログを閉じない)
            return false;
         }
      });
   });

   function checkConfirmStatus() {
      switch ($('input[name="confirmStatus"]').val()) {
      case 'wait':
         // 状態がwaitの場合、何もしない
         break;
      case 'close':
         // 状態がcloseに変化した場合、監視を停止して再度ページダイアログをcloseする
         pageDialog.observer.disconnect();
         $('#' + pageDialog.pageDialogId).imuiPageDialog('close');
         break;
      case 'cancel':
         // 状態がcancelに変化した場合、監視を停止して状態を初期状態に戻す
         pageDialog.observer.disconnect();
         $('input[name="confirmStatus"]').val('');
         break;
      }
   }

   function changeConfirm() {
      // deferredオブジェクト
      var confirmDeferred = new $.Deferred();

      // imuiMessageDialog表示
      var dialogId = 'dialog_saveConfirmDialog';
      $('<div></div>').text('入力内容を保存しますか?')
                      .attr('id', dialogId)
                      .imuiMessageDialog({
         iconType: 'info',
         title: '確認',
         verticalAlign: 'top',
         modal: true,
         buttons: [
            {
               'id': 'save',
               'text': '保存',
               'click': function() {
                  var savePromise = save();
                  savePromise.done(function() {
                     imuiShowSuccessMessage('保存しました。');
                     $('#' + dialogId).imuiMessageDialog('close');
                     // 保存成功時はページダイアログを閉じる
                     confirmDeferred.resolve();
                  });
                  savePromise.fail(function() {
                     imuiShowErrorMessage('保存に失敗しました。');
                     $('#' + dialogId).imuiMessageDialog('close');
                     // 保存失敗時はページダイアログを閉じない
                     confirmDeferred.reject();
                  });
               }
            },
            {
               'id': 'unsave',
               'text': '破棄',
               'click': function() {
                  imuiShowSuccessMessage('変更を破棄しました。');
                  $('#' + dialogId).imuiMessageDialog('close');
                  // 変更破棄時はページダイアログを閉じる
                  confirmDeferred.resolve();
               }
            },
            {
               'id': 'cancel',
               'text': 'キャンセル',
               'click': function() {
                  $('#' + dialogId).imuiMessageDialog('close');
                  // キャンセル時はページダイアログを閉じない
                  confirmDeferred.reject();
               }
            }
         ],
         close: function(event, ui) {
            $('#' + dialogId).remove();
         }
      });
      return confirmDeferred.promise();
   }

   function save() {
      // deferredオブジェクト
      var saveDeferred = new $.Deferred();

      // 保存処理
      $.ajax({
         type: 'POST',
         url: 'save',
         async: false,
         data: {data: $('#textbox1').val()},
         success: function(res, dataType) {
            imuiShowSuccessMessage('保存しました。');
            saveDeferred.resolve();
         },
         error: function(res, textStatus, xhr) {
            imuiShowErrorMessage(res.responseText);
            saveDeferred.reject();
         }
      });
      return saveDeferred.promise();
   }
</script>
pageDialog.js
// リクエストパラメータのページダイアログID
var $pageDialogId;

function init(request) {
   $pageDialogId = request.pageDialogId;
}

1回目のbeforecloseイベントでは確認ダイアログ押下状態は初期値です。ここから状態はwaitに変化し、mutationObserverが監視を開始します。
1回目のbeforecloseイベントは、falseを返却してこれで終了。
状態を変化させるトリガーとなるのは、確認ダイアログのボタンを押下です。押下したボタンに応じて、beforecloseイベント内にあるconfirmPromisepromise.done()/promise.fail()いずれかが発動して、状態がclose/cancelに変化します。
この変更をmutationObserverが捕捉するとコールバック関数checkConfirmStatus()が動作し、closeに変化していたら再度ページダイアログのcloseイベントを発動します(1回目のcloseイベントはbeforecloseイベントでfalseを返しているので動作していない)。
ここで2回目のbeforecloseイベントが動作するのですが、この時状態はcloseとなっているので、他の処理は一切行わずtrueを返却するだけです。これにより、2回closeイベントが動いても正しくクローズできるという仕組みです。
cancelに変化していたら確認ダイアログを閉じて状態を初期状態に戻してやれば、再びcloseイベントの発動待ち状態になります。

サンプルコード

という訳で、できあがったコードは以下となりました。
inputの変更をチェックして、変更されている場合のみ確認ダイアログを出すようにしてあります。

sample.html
<imart type="imuiButton" id="dialogButton" value="ボタン" class="imui-medium-button" />

<script type="text/javascript">
   $(function() {
      $('#dialogButton').on('click', function() {
         // ページダイアログID
         var pageDialogId = 'samplePageDialog';

         // ページダイアログ呼び出し
         $('<div></div>').attr('id', pageDialogId)
                         .appendTo(document.body)
                         .imuiPageDialog({
            title: 'ページダイアログ',
            url: 'pageDialog',
            parameter: {
               'imui-theme-builder-module': 'notheme',
               pageDialogId: pageDialogId
            },
            position: [650, 300],
            modal: true,
            height: 750,
            width: 465,
            close: function(event, ui) {
               // imuiPageDialogIdを作成したdivを削除
               $('#' + pageDialogId).remove();
            }
         });
      });
   });
</script>
pageDialog.html
<imart type="hidden"
   pageDialogId=$pageDialogId
   confirmStatus=""
/>

<imart type="imuiTextbox" id=textbox1" value="text1" />

<script type="text/javascript">
   // 名前空間
   if (typeof pageDialog === 'undefined') {
      var pageDialog = {}; 
   }

   // 引数で渡されたページダイアログID
   pageDialog.pageDialogId = $('input[name="pageDialogId"]').val();

   // 確認ダイアログのボタン押下状態監視
   pageDialog.observer = new MutationObserver(checkConfirmStatus);

   // input要素の変更状態
   pageDialog.isChanged = false;

   $(function() {
      $('input:not([type="hidden"],[type="submit"],[type="button"],[type="reset"],[type="image"])').on('change', function() {
         // 入力可能なinput要素の内容が変更されたら変更状態をtrueにする
         pageDialog.isChanged = true;
      });

      // ページダイアログのbeforeCloseイベントをバインド
      $('#' + pageDialog.pageDialogId).on('imuipagedialogbeforeclose', function(event, ui) {
         if ($('input[name="confirmStatus"]').val() === '') {
            // 確認ダイアログのボタンが押下されていない場合、状態をwaitにして確認ダイアログを表示する
            $('input[name="confirmStatus"]').val('wait');
            var confirmPromise = changeConfirm();
            confirmPromise.done(function() {
               // promiseがresolveになった場合(更新成功/破棄時)、状態をcloseにする
               $('input[name="confirmStatus"]').val('close');
            });
            confirmPromise.fail(function() {
               // promiseがrejectになった場合(更新失敗/キャンセル時)、状態をcancelにする
               $('input[name="confirmStatus"]').val('cancel');
            });
         }

         switch ($('input[name="confirmStatus"]').val()) {
         case 'wait':
            // 状態がwaitの場合、MutationObserverの監視を開始してfalseを返却(ページダイアログを閉じない)
            pageDialog.observer.observe($('input[name="confirmStatus"]')[0], {attributes: true, attributeFilter: ['value']});
            return false;
         case 'close':
            // 状態がcloseの場合、trueを返却(ページダイアログを閉じる)
            return true;
         case 'cancel':
            // 状態がcancelの場合、falseを返却(ページダイアログを閉じない)
            return false;
         }
      });
   });

   function checkConfirmStatus() {
      switch ($('input[name="confirmStatus"]').val()) {
      case 'wait':
         // 状態がwaitの場合、何もしない
         break;
      case 'close':
         // 状態がcloseに変化した場合、監視を停止して再度ページダイアログをcloseする
         pageDialog.observer.disconnect();
         $('#' + pageDialog.pageDialogId).imuiPageDialog('close');
         break;
      case 'cancel':
         // 状態がcancelに変化した場合、監視を停止して状態を初期状態に戻す
         pageDialog.observer.disconnect();
         $('input[name="confirmStatus"]').val('');
         break;
      }
   }

   function changeConfirm() {
      // deferredオブジェクト
      var confirmDeferred = new $.Deferred();

      // inputに変更があった場合のみダイアログを表示
      if (pageDialog.isChanged) {
         // imuiMessageDialog表示
         var dialogId = 'dialog_saveConfirmDialog';
         $('<div></div>').text('入力内容を保存しますか?')
                         .attr('id', dialogId)
                         .imuiMessageDialog({
            iconType: 'info',
            title: '確認',
            verticalAlign: 'top',
            modal: true,
            buttons: [
               {
                  'id': 'save',
                  'text': '保存',
                  'click': function() {
                     var savePromise = save();
                     savePromise.done(function() {
                        imuiShowSuccessMessage('保存しました。');
                        $('#' + dialogId).imuiMessageDialog('close');
                        // 保存成功時はページダイアログを閉じる
                        confirmDeferred.resolve();
                     });
                     savePromise.fail(function() {
                        imuiShowErrorMessage('保存に失敗しました。');
                        $('#' + dialogId).imuiMessageDialog('close');
                        // 保存失敗時はページダイアログを閉じない
                        confirmDeferred.reject();
                     });
                  }
               },
               {
                  'id': 'unsave',
                  'text': '破棄',
                  'click': function() {
                     imuiShowSuccessMessage('変更を破棄しました。');
                     $('#' + dialogId).imuiMessageDialog('close');
                     // 変更破棄時はページダイアログを閉じる
                     confirmDeferred.resolve();
                  }
               },
               {
                  'id': 'cancel',
                  'text': 'キャンセル',
                  'click': function() {
                     $('#' + dialogId).imuiMessageDialog('close');
                     // キャンセル時はページダイアログを閉じない
                     confirmDeferred.reject();
                  }
               }
            ],
            close: function(event, ui) {
               $('#' + dialogId).remove();
            }
         });
      } else {
         // inputに変更がない場合はページダイアログを閉じる
         confirmDeferred.resolve();
      }
      return confirmDeferred.promise();
   }

   function save() {
      // deferredオブジェクト
      var saveDeferred = new $.Deferred();

      // 保存処理
      $.ajax({
         type: 'POST',
         url: 'save',
         async: false,
         data: {data: $('#textbox1').val()},
         success: function(res, dataType) {
            imuiShowSuccessMessage('保存しました。');
            saveDeferred.resolve();
         },
         error: function(res, textStatus, xhr) {
            imuiShowErrorMessage(res.responseText);
            saveDeferred.reject();
         }
      });
      return saveDeferred.promise();
   }
</script>
pageDialog.js
// リクエストパラメータのページダイアログID
var $pageDialogId;

function init(request) {
   $pageDialogId = request.pageDialogId;
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3