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 に集約して」
というルールを守りながら手を動かしてみてください。
それが、設計力を“手の感覚”に落とし込む一番の近道です。


