DIALOGの2

消え行く showModalDialog の代わりに DIALOG が使えそうではある。 が、昨日の形だと過去の資産をそのまま置き換える訳にはいかなくて、結構手を入れなきゃいけない。 汎用性を持たせるためにも、元の形に近づけたい。 つまりこんな感じで使いたい。

returnValue = showModalDialog( url, object, options );

ということで実際にできるのかちょっと試してみた。 その結果。

動きは昨日のと同じ。 [Show DIALOG] ボタンをクリックするとダイアログをモーダル表示する。 左右を選択して [OK] をクリックすると、ダイアログを閉じて選択結果を反映する。

左:
右:

以下、ソースコードと若干の説明。

まずはダイアログの本体。

function showModalDialog2( url, obj, opt ) { const dialog = ( () => { const _iframe = document.createElement( "iframe" ); _iframe.src = url; _iframe.style.border = "0"; const _dialog = document.createElement( "dialog" ); _dialog.appendChild( _iframe ); const _opt = JSON.parse( `{${ opt .replace( /\s*([:;])\s*/g, '$1' ) .replace( /([^:;]+)/g, '"$1"' ) .replace( /;/g, ',' ) .replace( /,$/, '' ) }}` ); _dialog.style.width = _opt.width; _dialog.style.height = _opt.height; document.querySelector( "body" ).appendChild( _dialog ); return _dialog; } )(); window.myDialog = {}; window.myDialog.close = returnValue => { window.myDialog.returnValue = returnValue; dialog.close(); }; window.myDialog.obj = obj; return new Promise( resolve => { dialog.showModal(); dialog.addEventListener( 'close', () => { resolve( window.myDialog.returnValue ); } ); } ) }

IE11 が使えなくなった時に備えてだから同じ名前でもよかったのだが、なんとなく showModalDialog2 と微妙に名前を変えた。

呼び出し元の body に dialog を置き、その中に iFrame を置き、第1引数で渡された url を適用する。

第3引数の opt は表示関係の指定。 一般的なスタイル指定と同じ形式で指定することにした。 なので頑張ればもっと色々指定できるようになるのだが、ここでは最小実装で横と縦の大きさのみに限定。 しかも設定されることが前提。

ダイアログの配置は中央固定。 見た目をウインドウっぽくして、ドラッグやリサイズができると便利な気もするが、今回はスルー。

スコープを超えてやり取りするために window に myDialog を持たせている。 この御時世にグローバルオブジェクトはどうかと思うが、背に腹は代えられない。 ダイアログを閉じる処理もここに置いて、ダイアログの中の画面から呼び出せるようにしている。

そんなこんな準備をして、最終的にダイアログを閉じるイベントで resolve される Promise を返す。

次に、これを呼び出す側のコード。

async function showDialog() { const returnValue = await showModalDialog2( "./dialogContent.html", null, "width:300px;height:150px;" ); if ( returnValue ) { document.getElementById( "dspL" ).innerText = returnValue.L; document.getElementById( "dspR" ).innerText = returnValue.R; } } window.onload = () => { document.getElementById( "btn1" ).addEventListener( 'click', showDialog ); }

ポイントは一つだけ。 showModalDialog2 を await で待つこと。 showModalDialog2 は Promise を返し、これは閉じることで resolve される。 つまりダイアログが閉じられるまでここで待つことになるのだな。

最後に url で指定される、ダイアログの中身。

<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8" /> <title>Dialog Content</title> <script> function doOK() { window.parent.myDialog.close( { "L" : document.getElementById( "chkL" ).checked , "R" : document.getElementById( "chkR" ).checked , } ); } function doCancel() { window.parent.myDialog.close(); } </script> </head> <body> <div> 選べ </div> <div> <label> <input type="checkbox" id="chkL" /> 左 </label> <label> <input type="checkbox" id="chkR" /> 右 </label> </div> <div> <button id="btnOK" onclick="doOK()">OK</button> <button id="btnCancel" onclick="doCancel()">Cancel</button> </div> </body> </html>

これもポイントは一つだけ。 呼び出し元とのやり取りに window.parent.window.myDialog を使うこと。 閉じる時の close() もそうだし、呼び出し側から第2引数で指定されたオブジェクトを使うなら、ここから持ってくる。 今回は使っていないが。

…とまあ、だいたいこんな感じ。

だいたい showModalDialog と言って良いと思うが、ちょっと気持ち悪くもある。 ダイアログを閉じる時の window.parent.window.myDialog.close とか。

でもこうしないと、ダイアログを閉じる時にエラーになるんだよな。 「自分で表示したものじゃないから閉じれない」 と、多分セキュリティ上の理由で。 セキュリティ上の理由でブロックしたのをこの程度の工夫で突破されて良いのか、これはこれで疑問だったりするが。

モジュールにしていないのも同じ理由。 スコープの壁が邪魔だったから。 いや、邪魔とか言ってちゃ駄目なんだろうけどさ。

あと、ボタンクリックの度にダイアログを生成してるのも、どうかと思う。 dialog は一度生成したら使い回すようにして、 iFrame の中身だけリフレッシュした方がいいだろう。

まあ、そんなのは実際にこれを使うとなった時に考えればいいか。 とりあえず showModalDialog っぽくできたことで、今日は良しとしよう。