2012年10月9日火曜日

親ウインドウから子ウインドウのメソッドを呼び出す

javascriptでアプリケーションを作る際,複数の画面間を行き来するのはよく有ることです.その際悩ましいこととして,どのようにデータを受け渡すかがあります.例えば親ウインドウが子ウインドウを呼び出して,子ウインドウの中にデータを挿入したいといったケースです.

一旦サーバーにデータを送って良いなら様々な構成をとることができますが,これをクライアントサイドのみ(サーバーからは静的なファイルの取得のみ)で行う場合どうすればよいでしょうか?

この問題を解決する方法としては主に次の二つの方法が考えられます.
  1. window.openの引数に渡すurl文字列のクエリ文字列にパラメータを仕込む.
    「?」以降にkey=value形式の文字列を挿入する方法です.子ウインドウではこの内容をスクリプトで取得し,画面に表示します.
  2. window.openで子ウインドウを呼び出した後,子ウインドウの持つメソッドを呼び出す.
上の方法は素朴な方法で,直感的にも判りやすいのですが,注意すべき点があります.挿入できる内容は文字列ですし,余りに長いデータは挿入出来ない場合があります.
※その代わり,ドメインの縛りを受けないメリットはあります.

そこで子ウインドウのメソッドを直接呼び出す方法を考えてみましょう.


素朴な実装


最も素朴に考えると次のようなコードとなるでしょう.

呼び出し元(main.htm)のスクリプト
var win = window.open("sub.htm");
win.setValue("sample code");

呼び出し先(sub.htm)のスクリプト
window.onload = function(){
 window.setValue = function(text){
  document.body.innerHTML = text;
 };
};

一見正しいように見えますが,正しく有りません.なぜかというと,window.openを呼び出した後,setValueが存在している保証が無いからです.子ウインドウのonloadイベントによる処理が完了しないと子ウインドウのもつsetValueメソッドは呼び出すことが出来ず,親からはundefinedとして扱われてしまうのです.

一般的なwindowsアプリケーションを触っているとwindow.openによるイベント処理が完了してから次の処理が行われる気がするのですが,htmlアプリケーションではその限りではないのです.


初期化の順番を考慮した実装


では,子ウインドウの初期化処理が完了した後にプログラムが実行されるように出来れば良いわけですから,次のように書き換えたらどうでしょうか?

呼び出し元(main.htm)のスクリプト(その2)
var win = window.open("sub.htm");
win.addEventListener("load", function(){
 win.setValue("sample code");
}, false);

子ウインドウでは初期化処理をwindow.onloadプロパティで行なっています.従ってそれよりも後に処理を実行するには,親ウインドウから子ウインドウのaddEventListenerメソッドに実行したい処理を登録すればいいはずです.

しかし実際に試してみると結果はあまり良く有りません.
  • chrome・・・正しく動作
  • firefox・・・エラー
  • opera・・・何も起きない
実はこの部分はブラウザに依存するデリケートな部分なようです.この方法を使って対処するには実際には次のようにする必要があります.

呼び出し元(main.htm)のスクリプト(その2')
var win = window.open("sub.htm");
setTimeout(function(){
 win.addEventListener("load", function(){
  win.setValue("sample code");
 }, false);
}, 0);

呼び出し先(sub.htm)のスクリプト(その2')
$(function(){
 window.setValue = function(text){
  document.body.innerHTML = text;
 };
});
  • jQueryを使ってloadイベントの前に確実に初期化が行われるようにする.
    →firefox対策
  • loadイベントの登録をsetTimeout関数で囲む
    →opera対策
これで一応動作するようになります.が,外部のモジュールが必要となるなどどうにもスマートでありません.もっとシンプルな方法は無いのでしょうか?


初期化が終わるのを待つ


この問題の根本は親ウインドウから子ウインドウのもつsetValueメソッドが見つからないのが問題なのです.このメソッドは少し待てば何れ使用可能になるはずです.と言う事は,メソッドの呼び出しを子ウインドウの初期化が終わるまで待てればいいはずです.では内容を書き換えてみましょう.

呼び出し元(main.htm)のスクリプト(その3)
var win = window.open("sub.htm");
tryCall();

function tryCall(){
 if(!win.setValue){
  setTimeout(tryCall, 100);
  return;
 }
 win.setValue("sample code");
}

呼び出し先(sub.htm)のスクリプト(その3)
window.onload = function(){
 function setValue(text){
  document.body.innerHTML = text;
 };
 window.setValue = setValue;
};
  • 呼び出し元ではsetTimeoutでsetValue関数がセットされることを待っています.
  • 呼び出し先では確実に初期化が終了してからsetValue関数を外部に公開するため,一旦ローカル関数で処理を定義し,初期化処理の最後にsetValue関数を公開しています.

この方法であればchrome,firefox,operaでも正しく動作するようです.
※とは言ってもこの他のブラウザで動作するかどうかは確認する必要はありますが・・・


2つめの方法はhttp://www.h2.dion.ne.jp/~defghi/svgMemo/svgMemo.htmで使っています.
3つめの方法はhttp://www.h2.dion.ne.jp/~defghi/makefonts/makefonts.htmで使っています.

0 件のコメント:

コメントを投稿