はじめに
クリックしてダイアログを表示して処理する場合で、window.openを使用して対応する場合のメモ。
現状の画面(親ウィンドウ)から、新たにウィンドウを開いてその値を取得する手順です。
window.openメソッド
-
親ウィンドウ は、
window.open
を使って子ウィンドウを開き、postMessage
を使って子ウィンドウから送られてきたデータを受け取ります。 -
子ウィンドウ は、
window.opener.postMessage
を使って親ウィンドウに選択結果を送信し、送信後に自分自身を閉じます。
親ウィンドウ (index.html
側のスクリプト)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Window Open Example</title>
</head>
<body>
<h1>親ウィンドウ</h1>
<button id="openDialogBtn">モーダルを開く</button>
<p id="result">選択された結果: なし</p>
<script>
function openModalDialog(url) {
const width = 500;
const height = 400;
const left = (window.screen.width - width) / 2;
const top = (window.screen.height - height) / 2;
return new Promise((resolve, reject) => {
// ウィンドウを開く
let dialogWindow = window.open(
url,
'modalDialog',
`width=${width},height=${height},left=${left},top=${top}`
);
// ウィンドウがブロックされている場合
if (!dialogWindow) {
reject(new Error('ポップアップがブロックされました'));
return;
}
// メッセージイベントのリスナーを追加(子ウィンドウからのメッセージを受信)
window.addEventListener('message', function(event) {
// オリジンを確認して、信頼できるソースからのメッセージのみ処理する
if (event.origin !== window.location.origin) {
return;
}
// メッセージを解決する
resolve(event.data);
});
// ウィンドウが閉じられたことを定期的にチェック
let interval = setInterval(() => {
if (dialogWindow && dialogWindow.closed) {
clearInterval(interval);
}
}, 500);
});
}
// ボタンのクリックイベントでモーダルを開く
document.getElementById('openDialogBtn').addEventListener('click', () => {
openModalDialog('child.html')
.then((result) => {
document.getElementById('result').innerText = `選択された結果: ${result}`;
})
.catch((error) => {
alert(error.message);
});
});
</script>
</body>
</html>
子ウィンドウ (child.html
側のスクリプト)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>子ウィンドウ</title>
</head>
<body>
<h1>子ウィンドウ</h1>
<p>何か選択してください:</p>
<button id="option1">選択肢1</button>
<button id="option2">選択肢2</button>
<script>
function sendResult(result) {
// 親ウィンドウにメッセージを送信
window.opener.postMessage(result, window.location.origin);
}
document.getElementById('option1').addEventListener('click', () => {
sendResult('選択肢1');
window.close(); // ウィンドウを閉じる
});
document.getElementById('option2').addEventListener('click', () => {
sendResult('選択肢2');
window.close(); // ウィンドウを閉じる
});
</script>
</body>
</html>
以下、親ウィンドウと子ウィンドウの詳細な解説です。
親ウィンドウ (index.html
側)
概要
index.html
では、ボタンをクリックすると window.open
を使って新しいウィンドウ(子ウィンドウ)を開き、Promise
を使用して子ウィンドウのデータ(選択された結果)を受け取る処理を実装しています。
スクリプトの流れ
-
openModalDialog
関数:-
window.open
を使って新しいウィンドウ(子ウィンドウ)を開きます。ここで、ウィンドウのサイズや位置を指定できます。第2引数'modalDialog'
はウィンドウの名前です。この名前で後からウィンドウを特定できます。 -
Promise
を使って、非同期的に子ウィンドウからのデータを受け取ります。
-
-
window.open
:- 新しいウィンドウを開くための関数です。第1引数にウィンドウで表示する URL(
child.html
)を指定し、第3引数でウィンドウのサイズや位置を指定しています。これで新しい子ウィンドウが開かれます。
- 新しいウィンドウを開くための関数です。第1引数にウィンドウで表示する URL(
-
ポップアップブロックの確認:
-
dialogWindow
がnull
の場合は、ブラウザのポップアップブロッカーによってウィンドウの生成がブロックされたと考えられます。その場合はPromise
をreject
してエラーメッセージを表示します。
-
-
メッセージイベントリスナー:
- 子ウィンドウから送信された
postMessage
を受信するため、message
イベントをリッスンしています。 -
event.origin
の確認: セキュリティ上の理由で、メッセージが同じオリジン(URLのスキーム、ホスト、ポートが同一)から送信されているか確認しています。 - 正しいオリジンからメッセージが送信されていれば、
resolve
を使ってPromise
を完了させ、子ウィンドウからのデータを親ウィンドウで受け取ります。
- 子ウィンドウから送信された
-
ウィンドウの閉じられた状態の確認:
- 定期的に
dialogWindow.closed
をチェックして、子ウィンドウが閉じられたかどうか確認しています。この部分では、特にデータの受け渡しに影響はありませんが、ウィンドウが閉じられているかを監視しています。
- 定期的に
-
ボタンのクリックイベント:
- ボタンをクリックすると
openModalDialog
を呼び出し、子ウィンドウを開きます。子ウィンドウで選択肢がクリックされ、データが渡されると、親ウィンドウに結果が表示されます。
- ボタンをクリックすると
子ウィンドウ (child.html
側)
概要
子ウィンドウでは、選択肢を選んだ結果を親ウィンドウに送信し、その後自分自身(子ウィンドウ)を閉じるという処理を行います。
スクリプトの流れ
-
sendResult
関数:- 親ウィンドウにデータを送信するための関数です。選択された結果(
"選択肢1"
または"選択肢2"
)をwindow.opener.postMessage
を使って親ウィンドウに送信します。
- 親ウィンドウにデータを送信するための関数です。選択された結果(
-
window.opener.postMessage(result, window.location.origin)
:-
window.opener
は、親ウィンドウを参照するオブジェクトです。postMessage
メソッドを使って、親ウィンドウにデータを送信します。 -
result
は選択された結果の値です("選択肢1"
や"選択肢2"
)。 -
window.location.origin
はメッセージが送信されるオリジン(プロトコル、ドメイン、ポート)を指定します。これにより、同じオリジン内でメッセージを送信できるようになります。
-
-
window.close()
:- データを親ウィンドウに送信した後、
window.close()
で子ウィンドウを閉じます。
- データを親ウィンドウに送信した後、
-
選択肢のクリックイベント:
- ボタンがクリックされると、それぞれのボタンに対応する結果(
"選択肢1"
または"選択肢2"
)をsendResult
関数を通じて親ウィンドウに送信し、その後子ウィンドウを閉じます。
- ボタンがクリックされると、それぞれのボタンに対応する結果(
Promiseについて
子ウィンドウの値を取得するのに、子ウィンドウが閉じるのを確認してから親ウィンドウでその値を取得しますが、その時にPromise
を使用しています、その使用に関しもう少し深堀します。
Promise
は、JavaScript で非同期処理を扱うためのオブジェクトです。Promise
は、非同期処理が「成功」または「失敗」したときに、その結果を処理するための方法を提供します。
Promise
の基本構造
Promise
は次のような構造を持っています。
new Promise((resolve, reject) => {
// 非同期処理を実行
if (成功) {
resolve(結果); // 成功時
} else {
reject(エラー); // 失敗時
}
});
-
resolve
: 非同期処理が成功した場合に呼び出され、成功結果を呼び出し元に渡します。 -
reject
: 非同期処理が失敗した場合に呼び出され、エラーを呼び出し元に渡します。
Promise
を使うことで、非同期処理の結果がいつ完了するか分からない場合でも、完了した際に処理を続けられるようにすることができます。
親ウィンドウの Promise
使用について
親ウィンドウのコードでは、window.open
で子ウィンドウを開き、そのウィンドウでユーザーが何らかの操作をしたときに結果を親ウィンドウに返すという非同期処理を行っています。この場合、Promise
を使うことで、子ウィンドウの処理が完了した時点で親ウィンドウに結果を返すことができます。
Promise
の流れ
親ウィンドウの openModalDialog
関数では、次のように Promise
を使用しています。
openModalDialog
関数での Promise
使用
function openModalDialog(url) {
return new Promise((resolve, reject) => {
// 非同期処理: 子ウィンドウを開く
let dialogWindow = window.open(url, 'modalDialog', `width=500,height=400`);
// ポップアップがブロックされた場合、Promiseをrejectで終了
if (!dialogWindow) {
reject(new Error('ポップアップがブロックされました'));
return;
}
// 子ウィンドウからのメッセージを待ち受ける
window.addEventListener('message', function(event) {
if (event.origin === window.location.origin) {
// メッセージを受け取ったらPromiseをresolveして終了
resolve(event.data);
}
});
});
}
この関数の詳細な流れを見ていきます。
1. new Promise
を返す
openModalDialog
関数は Promise
を返します。これにより、非同期処理(子ウィンドウを開き、その結果を受け取るまでの処理)を待って処理を行えるようになります。
2. 子ウィンドウを開く (window.open
)
まず window.open
を使って子ウィンドウを開きます。ここで新しいウィンドウが開かれますが、この時点ではまだ子ウィンドウでユーザーが何を選択するかが分かりません。これが非同期処理の一つの例です。
-
同期処理と非同期処理の違い:
- 同期処理の場合、処理が終わるまで次の行のコードは実行されません。
- 非同期処理の場合、次の行のコードはすぐに実行されますが、結果が得られるのは後になります。この後処理を待つために
Promise
を使います。
3. ポップアップがブロックされているかの確認
ポップアップがブロックされている場合、window.open
は null
を返します。この場合、reject
メソッドを呼び出し、Promise
を失敗(エラー)として処理します。これにより、親ウィンドウでエラーをキャッチでき、適切なエラーメッセージ(ポップアップがブロックされた)を表示できます。
4. メッセージの受信 (postMessage
)
Promise
内で window.addEventListener('message')
を使って、子ウィンドウからの postMessage
を待ち受けます。
-
postMessage
でメッセージが受信されると、resolve
が呼び出され、Promise
が成功として完了します。 - このとき、子ウィンドウから送信されたデータ(
event.data
)が親ウィンドウに渡されます。
5. Promise
の結果処理 (then
と catch
)
この Promise
を返すことによって、親ウィンドウで子ウィンドウからの結果を待って次の処理を行うことができるようになります。
openModalDialog('child.html')
.then((result) => {
// 子ウィンドウからのメッセージを表示
document.getElementById('result').innerText = `選択された結果: ${result}`;
})
.catch((error) => {
// ポップアップがブロックされたなどのエラー処理
alert(error.message);
});
-
then
:Promise
が成功した場合(resolve
された場合)、この部分の処理が実行されます。ここでは、子ウィンドウから受け取ったデータ(result
)を親ウィンドウに表示しています。 -
catch
:Promise
が失敗した場合(reject
された場合)、エラーメッセージを表示します。たとえば、ポップアップがブロックされたときにこの部分が実行されます。
Promise
の利点
Promise
を使うことで、非同期処理の結果(この場合は子ウィンドウでの選択結果)を簡単に扱うことができます。Promise
がなければ、複雑なコールバック関数を使う必要があり、コードが読みづらくなることがありますが、Promise
を使うと非同期処理を順序立てて記述することができます。
-
非同期処理の管理: 非同期処理が成功するか失敗するかを、
resolve
とreject
で明確に管理できる。 - 可読性: コールバック関数を使わず、順序立てて処理を記述できるため、コードの可読性が向上する。
まとめ
この Promise
の使い方によって、親ウィンドウと子ウィンドウの間の非同期な通信をスムーズに管理できます。親ウィンドウでは子ウィンドウが完了するのを待ってから次の処理を行い、エラーが発生した場合には適切に対処できるようになっています。
window.addEventListener('message', ...)について
window.addEventListener('message', ...)について、わかりにくいかもしれないのでもう少し説明を加えます。
window.addEventListener('message', ...)
を使って、子ウィンドウから送信されたデータを親ウィンドウで受信する仕組みを実装しています。この message
イベントリスナーは、子ウィンドウから postMessage
によって送られてくるメッセージを待ち受け、受信したメッセージを処理するためのものです。
仕組みの流れ
-
子ウィンドウ (
child.html
) が親ウィンドウにデータを送信:
子ウィンドウ側で、window.opener.postMessage(result, window.location.origin)
を使用して親ウィンドウにメッセージを送信します。window.opener.postMessage(result, window.location.origin);
-
result
は子ウィンドウで選択されたデータです(例:'選択肢1'
や'選択肢2'
)。 -
window.location.origin
は、親ウィンドウに送信する際のオリジン(プロトコル、ホスト、ポートの組み合わせ)を指定します。これにより、セキュリティを強化し、異なるオリジンからのデータ送信を防ぎます。
-
-
親ウィンドウ (
index.html
) がデータを受信:
親ウィンドウ側では、window.addEventListener('message', ...)
を使用して、子ウィンドウから送られてくるメッセージを受信します。window.addEventListener('message', function(event) { // オリジンの確認(セキュリティ対策) if (event.origin === window.location.origin) { // 子ウィンドウからのメッセージを受け取ったら処理を実行 resolve(event.data); // Promiseを成功として終了し、データを親ウィンドウで処理 } });
この
message
イベントリスナーが、子ウィンドウから送信されたメッセージを受け取り、その内容をevent.data
を通じて処理します。-
event.origin
の確認: 受信したメッセージの送信元(オリジン)が、親ウィンドウのオリジンと一致しているか確認します。これは、他のサイトから送信されたメッセージを防ぐためのセキュリティ対策です。 -
resolve(event.data)
: メッセージが正常に受信されたら、Promise
のresolve
関数を呼び出して、親ウィンドウで処理が完了します。このresolve
によって、メッセージ内容(event.data
)が親ウィンドウに渡されます。
-
なぜ message
を使用するのか?
postMessage
と message
イベントリスナーの組み合わせは、異なるウィンドウ(親ウィンドウと子ウィンドウ、または異なるタブ)間で安全にデータを送受信するための標準的な方法です。
-
異なるウィンドウ間のデータ通信:
通常、ウィンドウやフレーム間で直接データをやり取りすることはできませんが、postMessage
を使用すると、クロスオリジン(異なるプロトコルやドメイン)であっても、セキュリティを保ちながらデータを送信できます。 -
セキュリティ:
postMessage
はセキュリティ機構を備えており、送信先のオリジンを指定することで、信頼できる送信元からのメッセージだけを受信することができます。この仕組みを使うことで、他のサイトからの不正なメッセージを無視できるため、安全にデータをやり取りすることができます。
まとめ
- 親ウィンドウは
window.addEventListener('message', ...)
を使って、子ウィンドウからのpostMessage
を待ち受けます。 - 子ウィンドウは
window.opener.postMessage(result, window.location.origin)
で親ウィンドウにデータを送信します。 - 親ウィンドウは、受信したメッセージの
event.data
を使って処理を行い、子ウィンドウからのデータを受け取ります。
この message
イベントリスナーを使うことで、親ウィンドウと子ウィンドウ間の通信を安全かつ非同期で行うことができます。
上記プログラムの動作について
デスクトップで今回のプログラムを動作させると、file://
プロトコルでファイルを直接開くことになり、ブラウザのセキュリティポリシーにより postMessage
の送信が制限されます。postMessage
を使用するには、ファイルをローカルサーバー経由で開く必要があります。たとえば、ローカル環境で簡単にウェブサーバーを起動できる方法があります。
ローカルサーバーのセットアップ方法
1. Python を使用した簡単な HTTP サーバーのセットアップ
Python がインストールされている場合、次のコマンドで簡単な HTTP サーバーを立ち上げられます。
-
Python 3 系の場合:
python -m http.server 8000
-
Python 2 系の場合:
python -m SimpleHTTPServer 8000
これにより、現在のディレクトリをウェブサーバーとして動作させ、ブラウザから http://localhost:8000
でアクセスできます。
2. Node.js の http-server
モジュールを使用した方法
Node.js がインストールされている場合、http-server
をインストールして使用することもできます。
npm install -g http-server
その後、プロジェクトフォルダで次のコマンドを実行します。
http-server
これにより、http://localhost:8080
などでサーバーが起動し、ブラウザからファイルにアクセスできるようになります。
アクセス方法
ローカルサーバーを起動した後、index.html
と child.html
に http://localhost:8000/index.html
のようにブラウザでアクセスして動作を確認してください。
例: Python サーバー使用時
- ターミナルで
python -m http.server 8000
を実行。 - ブラウザで
http://localhost:8000/index.html
にアクセス。 -
postMessage
によるデータ通信がfile://
ではなく、HTTP(S) プロトコルを使用するので、エラーが解消されます。
これにより、セキュリティエラーを回避し、postMessage
を使用した親子ウィンドウ間の通信が正常に行えるようになります。
参考