はじめに
クリックしてダイアログを表示して処理する場合で、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 を使用した親子ウィンドウ間の通信が正常に行えるようになります。
参考