質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.33%

JavaScript フォームで入力された値をJSONへ変換し、確認画面で表示したい

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,224

score 4

前提

フォームで入力された値をJSONへ変換し、確認画面で表示するプログラムを作成しました。
JSONへ変換する理由は、データ保存のためJSON形式でAPIサーバへリクエストするためです。

質問

エラーなく動作しておりますが、コメント「フォームの値をJSONへ変換」の箇所が非効率です。
クラス属性順にフォームの値を取得していることが原因と思います。
効率的な考え方等、教えていただければと思います。

参考

JavaScript フォームの確認画面を作成したい」の続きの質問です。

ソースコード

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h3>Document</h3>
    <div id="work">
        <p>入力画面</p>
        <form>
            <p class="text">Q1.TEXT: <input type="text" name="text1" value="hoge" /></p>
            <p class="radio">Q2.RADIO:
                <input type="radio" name="radio1" value="A" checked />A
                <input type="radio" name="radio1" value="B" />B</p>
            <p class="text">Q3.TEXT: <input type="text" name="text2" value="fuga" /></p>
            <p class="radio">Q4.RADIO:
                <input type="radio" name="radio2" value="A" />A
                <input type="radio" name="radio2" value="B" checked />B
                <input type="radio" name="radio2" value="C" />C</p>
            <input id="button" type="button" value="確認" />
        </form>
    </div>

    <script>
        // イベントを非同期で取得する (非同期コンテキストで element.observe() でイベントを待つ)
        HTMLElement.prototype.observe = function (type) {
            return new Promise(resolve => this.addEventListener(type, resolve, { once: true }));
        };

        (async () => {
            while (true) {
                // ボタンクリックを待つ
                await document.getElementById("button").observe("click");

                // ワークエリアを取得
                const workArea = document.getElementById("work");

                // ワークエリアをバックアップ
                const backup = workArea.cloneNode(true);

                // ワークエリアのフォームの値を取得
                let formValue = [];
                const countText = document.getElementsByClassName("text").length
                for (let i = 1; i < countText + 1; i++) {
                    formValue.push(document.getElementsByName(`text${i}`)[0].value);
                }
                const countRadio = document.getElementsByClassName("radio").length
                for (let i = 1; i < countRadio + 1; i++) {
                    const data = document.getElementsByName(`radio${i}`);
                    for (var j = 0; j < data.length; j++) {
                        if (data[j].checked) {
                            formValue.push(data[j].value);
                            break;
                        }
                    }
                }

                // フォームの値をJSONへ変換
                let formJson = new Object();
                formJson.Q1 = formValue[0];
                formJson.Q2 = formValue[2];
                formJson.Q3 = formValue[1];
                formJson.Q4 = formValue[3];
                console.log(formJson);

                // ワークエリアを結果で書き換える
                let html = "";
                for (let i = 1; i < 5; i++) {
                    html += `<p>Q${i}.` + formJson[`Q${i}`] + "</p>"
                }
                workArea.innerHTML = `
                    <p>確認画面</p>
                    ${html}
                    <input id="back" type="button" value="戻る">
                    <input id="send" type="button" value="送信">
                `;

                // 送信ボタンがクリックされたら
                document.getElementById("send").addEventListener("click", function () {
                    console.log("APIサーバへリクエスト");
                });

                // 戻るボタンのクリックを待つ
                await document.getElementById("back").observe("click");

                // ワークエリアを戻す
                document.body.replaceChild(backup, workArea);
            }
        })();
    </script>
</body>

</html>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

自己満足で回答します。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h3>Document</h3>
    <div id="work">
        <p>入力画面</p>
        <form>
            <p class="text">Q1.TEXT: <input type="text" name="text1" value="hoge" /></p>
            <p class="radio">Q2.RADIO:
                <input type="radio" name="radio1" value="A" checked />A
                <input type="radio" name="radio1" value="B" />B</p>
            <p class="text">Q3.TEXT: <input type="text" name="text2" value="fuga" /></p>
            <p class="radio">Q4.RADIO:
                <input type="radio" name="radio2" value="A" />A
                <input type="radio" name="radio2" value="B" checked />B
                <input type="radio" name="radio2" value="C" />C</p>
            <input id="button" type="button" value="確認" />
        </form>
    </div>

    <script>
        // イベントを非同期で取得する (非同期コンテキストで element.observe() でイベントを待つ)
        HTMLElement.prototype.observe = function (type) {
            return new Promise(resolve => this.addEventListener(type, resolve, { once: true }));
        };

        (async () => {
            while (true) {
                // ボタンクリックを待つ
                await document.getElementById("button").observe("click");

                // ワークエリアを取得
                const workArea = document.getElementById("work");

                // ワークエリアをバックアップ
                const backup = workArea.cloneNode(true);

                // // ワークエリアのフォームの値を取得
                // let formValue = [];
                // const countText = document.getElementsByClassName("text").length
                // for (let i = 1; i < countText + 1; i++) {
                //     formValue.push(document.getElementsByName(`text${i}`)[0].value);
                // }
                // const countRadio = document.getElementsByClassName("radio").length
                // for (let i = 1; i < countRadio + 1; i++) {
                //     const data = document.getElementsByName(`radio${i}`);
                //     for (var j = 0; j < data.length; j++) {
                //         if (data[j].checked) {
                //             formValue.push(data[j].value);
                //             break;
                //         }
                //     }
                // }

                // // フォームの値をJSONへ変換
                // let formJson = new Object();
                // formJson.Q1 = formValue[0];
                // formJson.Q2 = formValue[2];
                // formJson.Q3 = formValue[1];
                // formJson.Q4 = formValue[3];
                // console.log(formJson);

                // // ワークエリアを結果で書き換える
                // let html = "";
                // for (let i = 1; i < 5; i++) {
                //     html += `<p>Q${i}.` + formJson[`Q${i}`] + "</p>"
                // }

                const formJson = Array
                    .from(document.querySelectorAll(".text > input, .radio > input[checked]"))
                    .map(a => a.value)
                    .reduce((acc, cur, idx) => {
                        acc[`Q${idx + 1}`] = cur;
                        return acc;
                    }, Object.create(null));

                const html = Object
                    .entries(formJson)
                    .map(([key, value]) => `<p>${key}.${value}</p>`)
                    .join("");

                workArea.innerHTML = `
                    <p>確認画面</p>
                    ${html}
                    <input id="back" type="button" value="戻る">
                    <input id="send" type="button" value="送信">
                `;

                // // 送信ボタンがクリックされたら
                // document.getElementById("send").addEventListener("click", function () {
                //     console.log("APIサーバへリクエスト");
                // });

                // // 戻るボタンのクリックを待つ
                // await document.getElementById("back").observe("click");

                // 送信ボタンと戻るボタンのどちらかが押されたら戻る
                const clicked = await Promise.race([
                    document.getElementById("send").observe("click"),
                    document.getElementById("back").observe("click")
                ]);
                switch (clicked.target.value) {
                    case "送信":
                        console.log("APIサーバへリクエスト");
                        break;
                    case "戻る":
                        break;
                }

                // ワークエリアを戻す
                document.body.replaceChild(backup, workArea);
            }
        })();
    </script>
</body>

</html>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/09/26 07:29

    送信ボタンの方を addEventListener から observe() に変更していますが、これは Promise.race() のサンプルです。

    Promise.race() は複数の Promise のうち最も早く実行されたものを採用するもので、今回は送信ボタンと戻るボタンのどちらか早く押された方のイベントを返します。
    どちらが返されたかは戻り値の target プロパティを見ればわかります。今回は target プロパティにボタンが入っているので、その value つまりボタンのテキストで判別しています。

    このように Promise.race() を使うと、複数の非同期コンテクストを作らなくても、複数のイベントを同時に待つことができます。

    この時、押された方のボタンのイベントリスナーは {once: true} オプションによって削除されますが、押されなかった方のボタンのイベントリスナーは削除されないことに気を付けてください。
    コードによってはこれがメモリリークの原因となるので、https://gist.github.com/Zuishin/6c5ae7fc66fa31db175bb034878e096d のような方法を使って明示的な削除が必要になります。
    これは observe() 特有の問題ではなく、addEventListener を使っても同じ問題が生じます。

    ただし、今回の場合は画面を書き換えているため、イベントリスナーが関連付けられた HTML 要素ごとガベージコレクションの対象となるのでメモリリークの心配は必要ありません。

    キャンセル

  • 2020/09/26 07:37 編集

    また、画面を書き換えていない場合でも、押されたボタンのイベントリスナーはすべて削除されるので、どちらか片方のみ押し続けるようなことでもなければメモリリークの心配は要りません。
    例えば大量(数十万)のボタンがある場合などにメモリリークの心配をする必要があるので、その時には明示的な削除をこころがけましょう。
    もっとも、人間の押すボタンによるリークなどしれているので問題になることはないと思いますが。

    キャンセル

  • 2020/09/26 09:06

    丁寧なご回答ありがとうございました。
    当方環境ではradioボタンの方が取得できませんでしたが、Array.from()で効率よくできそうなので、調べてみます。

    キャンセル

  • 2020/09/26 09:13

    ".text > input, .radio > input[checked]"

    これが検索条件です。class="text" 直下の input と、class="radio" 直下の input で checked がついているものを検索しています。
    CSS のセレクタで指定しますので、それを調べてみてください。

    キャンセル

+1

JSON.stringify()

  • FormData は iterable なので、Array.from() で配列に変換可能です
  • JSON.stringify() で配列からJSONに変換可能です

従って、1行でJSONに変換するコードを書けます。

FormData

ただし、フォーム情報をそのままサーバに送信する目的なら、FormData をそのまま送信する方が、サーバでJSONデコードする工程が不要になる分、効率的だと思います。

Re: berry.js さん

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/09/26 09:08

    ご回答ありがとうございました。
    FormData をそのまま送信する方も検討してみたいと思います。

    キャンセル

0

それぞれの処理を関数に分けたらどうですか?

問題毎に答えを取得する関数
表示をリライトする関数
イベントセットする関数
回答送信処理関数
などなど...

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る