JavaScript | 1 日 120 分 × 7 日アプリ学習:ミニ業務アプリ(総合)

Web APP JavaScript
スポンサーリンク

3日目のゴールと今日のテーマ

3日目のテーマは
「実際の画面(HTML)とクラス設計を“きれいに”つなぐこと」です。

同じログイン/一覧 → 詳細 → 編集/権限制御でも、
「とりあえず動く書き方」と
「あとから直しやすい書き方」はまったく別物です。

今日は特に、

画面の要素(DOM)をどこで持つか
イベント(クリック・入力)をどこで受けるか
クラスと DOM をどう結びつけるか

を、ミニ業務アプリの流れに沿って具体的に見ていきます。


UI とロジックをつなぐ“橋”をどう設計するか

画面ごとにバラバラに書くと何が起きるか

ありがちなパターンはこうです。

ログイン画面の JS が Auth を直接触る
一覧画面の JS が CustomerRepository を直接触る
編集画面の JS が Permission を直接触る

一見シンプルですが、
画面が増えるたびに「どこから何を呼んでいるか」が散らばり、
仕様変更のたびに全ファイルを探し回ることになります。

2日目で作った AppController は、
まさにこれを防ぐための“司令塔”でした。

3日目では、
この AppController を「DOM と結びつける」役割まで広げます。


AppController に“UI の入口”を渡す設計

HTML 側のざっくりイメージ

まず、最低限こんな HTML を想像してください。

<div id="screen-login">
  <input id="login-user-id" />
  <input id="login-password" type="password" />
  <button id="login-button">ログイン</button>
</div>

<div id="screen-list" style="display:none">
  <div id="user-info"></div>
  <table id="customer-table"></table>
</div>

<div id="screen-detail" style="display:none">
  <div id="detail-content"></div>
  <button id="detail-edit-button">編集</button>
</div>

<div id="screen-edit" style="display:none">
  <input id="edit-name" />
  <input id="edit-email" />
  <button id="edit-save-button">保存</button>
</div>

これを、AppController に“まとめて渡す”イメージで設計します。

AppController に DOM を注入する

class AppController {
  #auth;
  #customerRepo;

  #screenLogin;
  #screenList;
  #screenDetail;
  #screenEdit;

  #loginUserIdInput;
  #loginPasswordInput;
  #loginButton;

  #userInfo;
  #customerTable;

  #detailContent;
  #detailEditButton;

  #editNameInput;
  #editEmailInput;
  #editSaveButton;

  #currentDetailId;

  constructor(dom) {
    this.#auth = new Auth();
    this.#customerRepo = new CustomerRepository();

    this.#screenLogin = dom.screenLogin;
    this.#screenList = dom.screenList;
    this.#screenDetail = dom.screenDetail;
    this.#screenEdit = dom.screenEdit;

    this.#loginUserIdInput = dom.loginUserIdInput;
    this.#loginPasswordInput = dom.loginPasswordInput;
    this.#loginButton = dom.loginButton;

    this.#userInfo = dom.userInfo;
    this.#customerTable = dom.customerTable;

    this.#detailContent = dom.detailContent;
    this.#detailEditButton = dom.detailEditButton;

    this.#editNameInput = dom.editNameInput;
    this.#editEmailInput = dom.editEmailInput;
    this.#editSaveButton = dom.editSaveButton;

    this.#setupEvents();
  }

  start() {
    this.#showLogin();
  }
}
JavaScript

ここでのポイントは、

DOM をグローバルにベタ書きせず、
「AppController のコンストラクタに“依存物”として渡す」
という形にしていることです。

これだけで、
テストしやすさ・保守性が一段上がります。


イベントを“Controller の中だけ”に閉じ込める

#setupEvents で UI とロジックを結びつける

  #setupEvents() {
    this.#loginButton.addEventListener("click", () => {
      const userId = this.#loginUserIdInput.value;
      const password = this.#loginPasswordInput.value;
      this.#attemptLogin(userId, password);
    });

    this.#detailEditButton.addEventListener("click", () => {
      if (this.#currentDetailId != null) {
        this.#showEdit(this.#currentDetailId);
      }
    });

    this.#editSaveButton.addEventListener("click", () => {
      if (this.#currentDetailId != null) {
        const name = this.#editNameInput.value;
        const email = this.#editEmailInput.value;
        this.#saveCustomer(this.#currentDetailId, name, email);
      }
    });
  }
JavaScript

ここでの設計ポイントは、

DOM のイベントリスナーは
すべて AppController の private メソッドに流している
ということです。

画面のあちこちで
Auth や Repository を直接触らない。
「UI → Controller → ドメイン」という流れを守る。

これが、実務で効く“保守性の土台”になります。


画面の切り替えを“状態遷移”として整理する

どの画面を表示するかを一元管理する

  #hideAllScreens() {
    this.#screenLogin.style.display = "none";
    this.#screenList.style.display = "none";
    this.#screenDetail.style.display = "none";
    this.#screenEdit.style.display = "none";
  }

  #showLogin() {
    this.#hideAllScreens();
    this.#screenLogin.style.display = "block";
  }

  #showList() {
    const user = this.#auth.getCurrentUser();
    if (!Permission.canViewList(user)) {
      this.#showLogin();
      return;
    }

    this.#hideAllScreens();
    this.#screenList.style.display = "block";

    this.#renderUserInfo(user);
    this.#renderCustomerTable();
  }

  #showDetail(id) {
    const user = this.#auth.getCurrentUser();
    if (!Permission.canViewDetail(user)) {
      this.#showLogin();
      return;
    }

    const customer = this.#customerRepo.findById(id);
    if (!customer) {
      this.#showList();
      return;
    }

    this.#currentDetailId = id;

    this.#hideAllScreens();
    this.#screenDetail.style.display = "block";

    this.#renderDetail(customer);
  }

  #showEdit(id) {
    const user = this.#auth.getCurrentUser();
    if (!Permission.canEdit(user)) {
      this.#showDetail(id);
      return;
    }

    const customer = this.#customerRepo.findById(id);
    if (!customer) {
      this.#showList();
      return;
    }

    this.#currentDetailId = id;

    this.#hideAllScreens();
    this.#screenEdit.style.display = "block";

    this.#editNameInput.value = customer.getName();
    this.#editEmailInput.value = customer.getEmail();
  }
JavaScript

ここでの重要な感覚は、

画面の表示・非表示
権限チェック
次にどこへ遷移するか

これらを全部 AppController に集めていることです。

画面側(HTML)は
「表示されれば描画されるだけ」
「ボタンを押せば Controller に通知するだけ」
という“従う側”に徹します。


一覧の描画と「行クリック → 詳細」の流れ

一覧テーブルを Controller から描画する

  #renderCustomerTable() {
    this.#customerTable.innerHTML = "";

    const customers = this.#customerRepo.findAll();

    customers.forEach(customer => {
      const tr = document.createElement("tr");

      const tdId = document.createElement("td");
      tdId.textContent = String(customer.getId());
      tr.appendChild(tdId);

      const tdName = document.createElement("td");
      tdName.textContent = customer.getName();
      tr.appendChild(tdName);

      tr.addEventListener("click", () => {
        this.#showDetail(customer.getId());
      });

      this.#customerTable.appendChild(tr);
    });
  }
JavaScript

ここでの責務分離はこうです。

顧客データをどう持つか → Customer
顧客一覧をどう取得するか → CustomerRepository
一覧をどう表示するか → AppController(UI 層)
行クリックでどこへ遷移するか → AppController

Repository は DOM を知らない。
Customer は DOM を知らない。
DOM を知っているのは Controller だけ。

この構造が、
「設計力」と「保守性」のど真ん中です。


編集の保存と権限制御を“二重で守る”

保存処理の流れ

  #saveCustomer(id, newName, newEmail) {
    const user = this.#auth.getCurrentUser();
    if (!Permission.canEdit(user)) {
      alert("編集権限がありません");
      this.#showDetail(id);
      return;
    }

    const customer = this.#customerRepo.findById(id);
    if (!customer) {
      this.#showList();
      return;
    }

    customer.setName(newName);
    customer.setEmail(newEmail);

    this.#customerRepo.save(customer);

    this.#showDetail(id);
  }
JavaScript

ここでの重要ポイントは、

UI で「編集ボタンを隠す」だけでなく、
保存処理の中でも Permission.canEdit を必ずチェックしていることです。

UI での制御は「親切」
Controller での制御は「安全」

業務アプリでは、この二重チェックが当たり前になります。


3日目でいちばん深掘りしてほしいこと

今日の本質を一言で言うと、

「DOM とクラスを直結させず、必ず Controller を“ハブ”にする」
です。

Auth/User/Customer/CustomerRepository/Permission
これらは一切 DOM を知らない。

DOM を知っているのは AppController だけ。
イベントも画面遷移も描画も、Controller 経由で行う。

この構造を守るだけで、

画面デザインを変えてもドメインはそのまま
権限ルールを変えても UI は最小限の修正で済む
保存方法を変えても Controller と Repository だけ触ればいい

という“実務的な保守性”が手に入ります。

もし余裕があれば、

ログアウトボタンを追加してみる
一般ユーザーでログインしたときに編集ボタンを非表示にする
一覧に「メールアドレス」列を追加してみる

などを、必ず
「Controller 経由で」「権限チェックを Permission に集約して」
というルールを守りながら手を動かしてみてください。

それが、設計力を“手の感覚”に落とし込む一番の近道です。

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