対象となる開発形態
- intra-mart AccelPlatform
- スクリプト開発
やりたいこと
imuiPageDialog
で内容が更新されているのにページダイアログを閉じようとした時、beforeclose
イベントで確認ダイアログを表示し、OKなら閉じる/キャンセルなら閉じないようにする。
問題点
-
beforeclose
イベントでページダイアログ内の入力内容変更チェックを行いたいため、呼び出し元ページでイベント設定ができない。 - 確認ダイアログは
imuiMessageDialog
を利用するが、非同期で動作する(確認ダイアログが表示されても、ボタン押下を待たずに次の処理に進んでfunctionが終了してしまう)ため同期的に動かす仕組みが必要になる。 - 同期的に動かした場合、ボタン押下で再度ページダイアログの
beforeclose
イベントが発動するため、処理がループしてしまう。
解決策
beforecloseイベントをページダイアログ側でバインドする
ページダイアログ側でbeforeclose
イベントをバインドするには、対象のimuiPageDialog
に対してon('imuipagedialogbeforeclose', function(event, ui) {})
を実行します。
ただし、ページダイアログ側では呼び出し元で何というIDを指定したか分からないため、呼び出し元からリクエストパラメータとして受け取って指定します。
<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>
<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>
// リクエストパラメータのページダイアログID
var $pageDialogId;
function init(request) {
$pageDialogId = request.pageDialogId;
}
なお、上記のソースではimuiMessageDialog
の同期処理対応ができていないため、実行すると確認ダイアログが表示された瞬間にページダイアログが閉じてしまいます。
確認ダイアログを同期的に動作させる
imuiMessageDialog
を同期的に動作させるため、処理をdeferred
を使って書き換えます。
ついでに、保存処理についてもdeferred
を使用するようにしています。
<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>
// リクエストパラメータのページダイアログID
var $pageDialogId;
function init(request) {
$pageDialogId = request.pageDialogId;
}
書き換えてもやはり確認ダイアログ表示と同時にページダイアログは閉じてしまいます。
処理は待機してボタン押下と同時にpromise
オブジェクトのfunctionは実行されるのですが、beforeclose
イベント自体はそのまま終了してしまうからです。
同期処理にした意味がない……。
変更状態を監視して同期処理の結果から処理を実行させる
deferred
で同期的に処理が実行できる所までは解決できました。
ここからは多少力技ですが、mutationObserver
を利用して確認ダイアログ押下状態を監視、deferredの同期処理で押下状態を変更することでMutationObserver
に検知させることにしました。
先頭の<imart type="hidden">
タグに、確認ダイアログ押下状態を格納するconfirmStatus
を追加し、mutationObserver
の監視対象に指定しています。
属性監視なので監視タイプはattributes
、フィルタでvalue
属性のみ監視するよう設定しました。
<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>
// リクエストパラメータのページダイアログID
var $pageDialogId;
function init(request) {
$pageDialogId = request.pageDialogId;
}
1回目のbeforeclose
イベントでは確認ダイアログ押下状態は初期値です。ここから状態はwait
に変化し、mutationObserver
が監視を開始します。
1回目のbeforeclose
イベントは、false
を返却してこれで終了。
状態を変化させるトリガーとなるのは、確認ダイアログのボタンを押下です。押下したボタンに応じて、beforeclose
イベント内にあるconfirmPromise
のpromise.done()
/promise.fail()
いずれかが発動して、状態がclose
/cancel
に変化します。
この変更をmutationObserver
が捕捉するとコールバック関数checkConfirmStatus()
が動作し、close
に変化していたら再度ページダイアログのclose
イベントを発動します(1回目のclose
イベントはbeforeclose
イベントでfalse
を返しているので動作していない)。
ここで2回目のbeforeclose
イベントが動作するのですが、この時状態はclose
となっているので、他の処理は一切行わずtrue
を返却するだけです。これにより、2回close
イベントが動いても正しくクローズできるという仕組みです。
cancel
に変化していたら確認ダイアログを閉じて状態を初期状態に戻してやれば、再びclose
イベントの発動待ち状態になります。
サンプルコード
という訳で、できあがったコードは以下となりました。
input
の変更をチェックして、変更されている場合のみ確認ダイアログを出すようにしてあります。
<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>
<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>
// リクエストパラメータのページダイアログID
var $pageDialogId;
function init(request) {
$pageDialogId = request.pageDialogId;
}