window.showModalDialogを移行した話


ようやく移行しました

Google Chromeからwindow.showModalDialogが使えなくなって久しいのですが、
アプリ利用ユーザの標準ブラウザがIEになっているので長らく棚上げしていました。

ChromeからshowModalDialogが完全になくなったのが2015年5月、FireFoxからも2017年9月になくなり、OperaはとっくにChromiumでEdgeもChromiumベースになろうととしていて、Callbackベースで移行したいなと考えていて方法を思いつきました。

今まで私のshowModalDialogの使い方と、それを踏襲した実装方法を順を追って書いていきます。

今までのshowModalDailogの使いかた

子画面から値を返すのは例えば

window.opener.document.main.elementId.value = 'xxx';

などと書いたら戻せるのですが、子画面側が値をセットするエレメントIDを知る必要があり子画面の汎用性がなくなるため、window.returnValue を使う方法を使ってました。
window.returnValue を使えば予め必要となる値をJSONにして返し、呼び出し元が必要な値をJSONから引き出して使えばよく親画面、子画面を疎結合にできます。

親画面(parent.html)

<input id="cd">
<input id="name">
<input id="dept">
<input type="button" value="社員"
    onclick="
        var json = window.showModalDialog('child.html');
        if(json) { //子画面が×で閉じられたらnull
            document.getElementById('cd').value = json.cd;
            document.getElementById('name').value = json.name;
            document.getElementById('dept').value = json.dept;
            //他にも age,email,tel とかjsonに入ってる

            somefunc(); //値が選ばれたタイミングでやりたいことがあれば書く
        }
    "/>

子画面(child.html)


<input type="button" value="山田さんを選択"
    onclick="
        window.returnValue = {
            cd: '22102',
            name: '山田太郎',
            kana: 'ヤマダタロウ',
            dept: '総務',
            age: '26',
            tel: '09011112222',
            email: '[email protected]',
            position: '一般'
            //必要そうな値を予めJSONに詰めて返す
        };
        window.close();
    "/>

代替案 window.open を使った callbackの実装

コードを見ての通りこれまでのやり方のいいところは、子画面で値が選ばれたタイミングでやりたい処理が行えることです。モーダルにして他の処理をブロックしたいわけではないのですが、子画面で値を選んで自画面で必要な処理を行う一連の処理を、同期的に記述できるが魅力でした。それに、他の画面からも child.html を変更することなく呼び出せます。

window.showModalDialog が使えないなか子画面で値選択のタイミングで処理をかけるには 指定したCallback を呼び出してほしい。しかし、子画面はどのコールバックを呼んだらいいかわからないし、コールバックに命名規則を設けると parent.html に複数コールバックを持てなくなる(入力欄ごとに子画面がある場合など変数名が重複する可能性あり)のです。

そこで「値選んだらこのコールバックを呼んでね」window.open の第2引数 windowName を使って渡したのがこんな実装です

親画面(parent.html)

<input id="cd">
<input id="name">
<input id="dept">
<input type="button" value="社員"
    onclick="
        var callback_id = 'callback_xxxx'; //適当にIDをふる
        window[callback_id] = function(json) { //windowにコールバックを登録
            document.getElementById('cd').value = json.cd;
            document.getElementById('name').value = json.name;
            document.getElementById('dept').value = json.dept;
            callfunc(); //値が選ばれたタイミングでやりたいことがあれば書く
        }

        //第2引数でcallbackのID名を渡す。子画面側では window.name として取得できる。
        window.open('child.html', callback_id, 'width=700,height=500,statusbar=yes');
    "/>

子画面(child.html)


<input type="button" value="山田さんを選択"
    onclick="
        var callback_id = window.name; //window.nameに第2引数で指定した値が入っている
        var json = {
            cd: '22102',
            name: '山田太郎',
            kana: 'ヤマダタロウ',
            dept: '総務',
            age: '26',
            tel: '09011112222',
            email: '[email protected]',
            position: '一般'
            //必要そうな値を予めJSONに詰めて返す
        };
        window.opener[callback_id](json); //指定されていたコールバックを呼び出す
        window.close();
    "/>

これで無事、子画面にコールバック関数を指定でき、値を汎用的に戻すことができました。
Chrome, IE10, IE11, Edge, Safari, FireFox で動作OKでした。

まとめ

  1. コールバック関数を親画面のwindowオブジェクト(など)に登録。
  2. window.open第2引数でコールバック関数名を渡す
  3. 子画面はwindow.nameでコールバック関数名を受け取る。
  4. 子画面はwindow.opener[window.name](xxx)で親画面のコールバックを呼んで値を返す。

この方法の欠点は、他の操作をブロックしないので本当の意味ではモーダルではない点ですが、逆に子画面を開きながら別の操作もできるのでメリットは大きいです。
多少冗長ですが、子画面Open と 子画面Close 処理を関数にまとめてしまえば問題もないでしょう。

もし同じような実装をしていて、Chromeに対応させたい方がおられたら参考にして下さい。