JavaScript | 1 日 120 分 × 7 日アプリ学習:モーダルウィンドウ管理

Web APP JavaScript
スポンサーリンク

モーダル管理を“プロ品質”へ近づける 5日目のテーマ

5日目は、1〜4日目で作ってきた Modal クラスModalManager をさらに実践的に強化し、
「アプリ全体で安心して使えるモーダル管理」を完成形に近づける回です。

今日の学習ポイントは次の3つです。

  • クラス設計を“拡張しやすい構造”に進化させる
  • UI 状態制御を「外部から操作しやすい API」として整理する
  • イベント伝播を応用し、複数モーダル・複雑な UI でも誤作動しない仕組みを作る

実装としては、
開く / 閉じる、背景クリック、ESCキー対応を
外部コードから安全に呼び出せる形 に整えます。


モーダルを「外部から操作しやすい API」にする

4日目までの課題

Modal クラスは内部的には良い設計になってきましたが、
外部から使うときに次のような問題が残ります。

  • modal.open() を呼ぶだけでは「開く前の準備」ができない
  • modal.close() を呼ぶだけでは「閉じた後の処理」ができない
  • モーダルごとに違う動きをしたいときにコードが増える

そこで、5日目では
「外部から自由に振る舞いを差し込める API」
を作ります。


コールバックを「イベント風」に整理する

4日目までのコールバック

new Modal(root, {
  onOpen: () => {},
  onClose: () => {}
});
JavaScript

これは悪くありませんが、
実務ではもっと柔軟な仕組みが必要になります。

5日目の改善:イベント風 API

modal.on("open", () => {
  console.log("モーダルが開いた");
});

modal.on("close", () => {
  console.log("モーダルが閉じた");
});
JavaScript

実装例

class Modal {
  constructor(root) {
    this.root = root;
    this.events = {};
  }

  on(eventName, handler) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(handler);
  }

  emit(eventName) {
    const handlers = this.events[eventName] || [];
    handlers.forEach(fn => fn());
  }

  open() {
    // 開く処理…
    this.emit("open");
  }

  close() {
    // 閉じる処理…
    this.emit("close");
  }
}
JavaScript

深掘りポイント:イベント API は“拡張性の核”

この仕組みがあると、

  • モーダルごとに違う動きを簡単に追加できる
  • クラス本体を改造せずに機能追加できる
  • 外部コードがモーダルの状態変化に反応できる

という、実務で非常に重要なメリットが生まれます。


UI 状態制御を「外部から見える状態」として整理する

4日目までの状態

this.state = "opened";
JavaScript

これは内部的には良いですが、
外部から状態を知りたいこともあります。

例:
「すでに開いているなら open() を呼ばない」
「閉じているときだけ close() を呼ぶ」

5日目の改善:getter を用意する

get isOpened() {
  return this.state === "opened";
}
JavaScript

外部からの利用例

if (!modal.isOpened) {
  modal.open();
}
JavaScript

深掘りポイント:状態を“読み取り専用”にするのが安全

外部から
modal.state = "closing"
のように書けてしまうと、
UI が壊れる原因になります。

getter を使うことで、
状態は読めるが書けない
という安全な設計になります。


背景クリック制御を「複雑な UI に耐える形」にする

4日目の contains() 判定

if (!this.content.contains(event.target)) {
  this.close();
}
JavaScript

これは非常に強力ですが、
5日目ではさらに一歩進めて
「クリックを許可する領域」を複数持てるようにします。

クリック許可領域を登録できるようにする

this.safeAreas = [this.content];
JavaScript

外部から追加できるようにする

modal.addSafeArea(someElement);
JavaScript

実装例

addSafeArea(el) {
  this.safeAreas.push(el);
}

handleRootClick(event) {
  const isSafe = this.safeAreas.some(el => el.contains(event.target));
  if (!isSafe) {
    this.close();
  }
}
JavaScript

深掘りポイント:safeAreas は“UI の自由度”を高める

モーダル内に

  • タブ
  • ドロップダウン
  • スクロール領域
  • 動的に追加される要素

などがある場合、
「ここは背景扱いにしたくない」という領域が増えます。

safeAreas を導入すると、
モーダルの構造が複雑になっても誤作動しない
という強力な仕組みになります。


ESC キー対応を「優先順位付き」にする

4日目までの ESC 対応

ModalManager がスタックの一番上を閉じる形でした。

5日目の改善:ESC を無効化できるようにする

例:
「削除確認モーダルは ESC で閉じられないようにしたい」

実装例

constructor(root, options = {}) {
  this.disableEsc = options.disableEsc || false;
}

handleKeydown(event) {
  if (event.key === "Escape" && !this.disableEsc) {
    this.close();
  }
}
JavaScript

深掘りポイント:UI の“意図”をコードで表現する

  • 間違って閉じてほしくないモーダル
  • ユーザーに必ず選択させたいモーダル
  • 重要な確認ダイアログ

こういったケースでは ESC を無効化する必要があります。

disableEsc は
UI の意図をコードで表現するための重要なフラグ
になります。


5日目のまとめ

今日あなたがやったことを整理するとこうなります。

  • モーダルにイベント API(on / emit)を導入して拡張性を高めた
  • 状態を getter で公開し、安全に外部から参照できるようにした
  • safeAreas を導入して、複雑な UI でも背景クリック誤作動を防いだ
  • ESC キー対応を「無効化できる」形にして UI の意図を表現した

どれも、実務でモーダルを扱うときに必ず必要になるテクニックです。

タイトルとURLをコピーしました