🎉

メモアプリを改善してみた(dialog + Promise編)

に公開

はじめに

以前作ったメモアプリを改善してみました。元のコードではユーザーからの入力をprompt()で受け取って画面に表示していました。

次のステップアップとして、dialog要素とPromiseを使って作り直してみることにしました。

この記事では、その改善過程をまとめます。

元のコード(prompt版)

元のコードはこちら。

コード全文は以下です。

<h1>メモアプリ</h1>
<button id="addMemoBtn">メモを追加</button>
<ul id="memo_list"></ul>

<script>
const btn = document.getElementById("addMemoBtn");
const memo_list = document.getElementById("memo_list");

btn.addEventListener("click", () => {
    const input = prompt("入力してください");
    
    const newElem = document.createElement("li");
    newElem.textContent = input;
    memo_list.appendChild(newElem);
});
</script>

改良版(dialog + Promise版)

同じ機能をdialog要素とPromiseで実装した結果です。

以下、改良版のコード全文です。

<h1>メモアプリ</h1>
<button id="addMemoBtn">メモを追加</button>
<ul id="memo_list"></ul>

<dialog id="inputDialog">
    <form method="dialog">
        <label for="memoInput">入力してください:</label>
        <input type="text" id="memoInput">
        <div>
            <button type="submit" value="ok">OK</button>
            <button type="submit" value="cancel">キャンセル</button>
        </div>
    </form>
</dialog>

<script>
const btn = document.getElementById("addMemoBtn");
const memo_list = document.getElementById("memo_list");
const inputDialog = document.getElementById("inputDialog");
const memoInput = document.getElementById("memoInput");

btn.addEventListener("click", async () => {
    memoInput.value = "";
    inputDialog.showModal();
    
    const input = await new Promise((resolve) => {
        inputDialog.onclose = () => {
            if (inputDialog.returnValue === "ok") {
                resolve(memoInput.value);
            } else {
                resolve(null);
            }
        };
    });
    
    if (!input || input.trim() === "") return;
    
    const newElem = document.createElement("li");
    newElem.textContent = input;
    memo_list.appendChild(newElem);
});
</script>

コード解説

1. dialogを表示する

memoInput.value = "";
inputDialog.showModal();
  • memoInput.value = ""で前回の入力をクリア
  • showModal()でdialogをモーダル表示(背景が暗くなり、他の操作ができなくなる)

2. dialogが閉じるまで待つ(重要!)

const input = await new Promise((resolve) => {
    inputDialog.onclose = () => {
        if (inputDialog.returnValue === "ok") {
            resolve(memoInput.value);
        } else {
            resolve(null);
        }
    };
});

ここが一番重要な部分

まず、Promise(プロミス)について。

Promiseとは?

Promiseは「将来の結果を約束するオブジェクト」

今回の場合:

  1. ユーザーが入力中 → 結果はまだ分からない
  2. OKまたはキャンセルを押す → 結果が確定
  3. 確定した結果を返す

awaitとは?

awaitは「Promiseの結果が出るまで待つ」命令。

const input = await new Promise(...);

この1行で「ユーザーがOKかキャンセルを押すまで待つ」ことができます。まるでprompt()のように、上から順番に処理が進むように書けるのがポイントです。

各部分の役割

  • new Promise((resolve) => {...}):「これから結果を返すよ」という約束を作る
  • inputDialog.onclose:dialogが閉じた時に実行される処理
  • inputDialog.returnValue:押されたボタンのvalue属性("ok" または "cancel")
  • resolve(memoInput.value):入力値を結果として返す(「resolve」は「解決する、確定させる」という意味)
  • resolve(null):キャンセル時はnullを返す

3. 入力チェック

if (!input || input.trim() === "") return;

以下の場合は何もせず終了します:

  • キャンセルされた場合(inputnull
  • 空文字の場合(スペースだけの入力も除外)

4. メモをリストに追加

const newElem = document.createElement("li");
newElem.textContent = input;
memo_list.appendChild(newElem);

ここは元のコードと同じです。入力値を<li>要素に入れてリストに追加しています。

補足:非同期処理について

「Promise」や「await」を理解するには、非同期処理を知っておく必要があります。

プログラムには2種類の処理があります:

同期処理: 順番に1つずつ実行される

console.log("1番目");
console.log("2番目");
console.log("3番目");
// 結果: 1→2→3 の順番

非同期処理: 待ち時間がある処理を後回しにして、他の作業を先に進める

console.log("1番目");
setTimeout(() => {
    console.log("2番目(3秒後)");
}, 3000);
console.log("3番目");
// 結果: 1→3→2 の順番(3は2を待たない)

身近な例で説明すると:

レストランでの注文シーン。

  • 同期処理:注文した料理が来るまで、その場で立って待つ。何もできない。
  • 非同期処理:注文したら席に座って、料理ができるまで他のことができる。料理ができたら呼ばれる。

今回のコードでは:

  • prompt():同期処理(入力するまで全ての処理が止まる)
  • dialog + Promise:非同期処理をawaitで同期的に扱う(入力を待つが、書き方は同期的)

awaitを使うことで、非同期処理でもprompt()のように書けるのがポイントです。

今回の改善のメリットとデメリット

メリット ✨

  • 見た目を自由にカスタマイズできる(CSSでスタイル変更可能)
  • 複数の入力欄やボタンを追加できる

デメリット 🤔

  • コード量が増えるprompt()は1行だが、こちらは10行以上。簡単な用途には過剰かも)

今回学んだこと

  • dialog要素を使えば見た目をカスタマイズできる入力画面を作れる
  • Promiseawaitで、非同期処理を同期的に書ける
  • 最初は複雑に見えたが、理解できると応用が効きそう

Discussion