JavaScriptで複雑なHTMLをわかりやすく構築する方法

Web開発

お疲れ様です。すぺきよです。

今回はJavaScriptのお話です。

皆さんは、JavaScriptでHTMLを構築する際にどうしていますか、いくつか方法が考えられると思います。

標準のJavaScriptとして、MDNではdocument.createElement()で必要な要素をどんどん生成し、Element.append()などを使って要素を挿入していくことでHTMLを構築していきます。

しかし、この方法は直感的でなくて読みづらいし、保守性もめちゃくちゃ悪いです。正直めんどくさい!

そこで、私がよく使っている、JavaScriptでHTMLを直感的に、わかりやすく構築する方法を紹介します。

直感的にHTMLを構築する方法

やり方は意外とシンプルです。JSXっぽいことをします。

手順1.HTMLを静的な文字列で記述する

通常通り、HTMLファイルに記述するような感じで、JavaScript上にHTML文字列でテンプレートHTMLを書きます。

例えばブログの記事一覧を表示するためのカードを構築するHTMLを記述するときは以下のようにヒアドキュメントを利用します。

const template_html = `
<div class="blog_article">
    <div class="eye_catch"><img></div>
    <div class="post_date"></div>
    <div class="post_title"></div>
    <div class="post_body"></div>
    <div class="post_to_detail"><a>続きを読む</a></div>
    <div class="author">
        <div class="author_icon"><img></div>
        <div class="author_name"></div>
    </div>
</div>`.replace(/> *\n? +</g, "><");

この時、テスト目的などでなければ、要素内には固定値やデフォルト値以外のデータを含めない方が容量が少なくなって良いです。

また、各要素には、後で値の設定時にアクセスするためにわかりやすいclass名を指定します。(インデックス番号でもアクセス可能ですが、読みやすさと保守のしやすさからこちらの方が良いと思います。)

そして、最後に読みやすくするために配置しているいらない改行やインデント用の空白を「.replace(/> *\n? +</g, “><“);」で取り除いてHTMLを綺麗にします。

これをしないと、要素間に変な空白が開いたり、表示がずれたりといらない苦労をすることになります。

よくある、タグ間にコメントの <!– –>を挟むと表示がなぜかうまくいく問題ですね。

ここでコードが短くなるからと、直接ユーザーの入力値などが含まれている可能性のある変数値を${hoge}などを使ってヒアドキュメント内に絶対に含めてはいけません。

Note:ヒアドキュメント内に偏数値を入れてはいけない理由

変数値を含めてしまうとXSS脆弱性の発生につながるリスクがあります。

静的な文字列として用意することが重要です。

たとえ、絶対に定数であるとわかりきっている変数であっても、入れてはダメです。

あとからXSS脆弱性が含まれていないか見直すときに、全てのコードを全部チェックし直す羽目になります。

後で説明する方法でデータを挿入すればXSS脆弱性は発生しないため、見直しも簡単ですし、ルールを守っている限りはチェックする必要もありません。(念のためした方がいいとは思いますが、それでも簡単です)

手順2.適切な親要素を一つ作る

次に、適切な親要素を「document.createElement()」で作ります。

作りたい要素がliのときはolやulを、trであればtbodyやtable、optionであればinputですね。

適切な親要素が決められていない場合はdivで大丈夫でしょう。

今回のブログ記事の一覧で言うとdivで十分なので、以下のようにします。

let parent_elem = document.createElement('div');

手順3.作った親要素にHTMLを挿入し、要素化する

次に手順2で作った親要素に、テンプレートHTMLをHTMLとして挿入し、HTMLを要素化します。

このとき、innerTextではなく、innerHTMLを利用します。

innerTextを使うと、HTMLとして記述した文字列がそのまま、ただの文字列として解釈されて挿入されるのでHTML要素になりません。

parent_elem.innerHTML = template_html;

手順4.各要素に必要なデータを挿入する

テンプレートHTMLを要素化したら、いよいよユーザー入力値を含むデータを挿入していきます。

その際に、親要素に対して、querySelectorとinnerTextを駆使します。ここでは説明用として静的な文字列にしていますが、WebAPIでもらってきたJSONのデータをここに当てはめても問題ありません。

    parent_elem.querySelector(".post_date").innerText = "2024.01.01";
    parent_elem.querySelector(".post_title").innerText = "タイトル";
    parent_elem.querySelector(".post_body").innerText = "投稿内容本文";
    parent_elem.querySelector(".post_to_detail>a").href = "/article?id=1";
    parent_elem.querySelector(".eye_catch>img").src = "https://placehold.jp/300x200.png";
    parent_elem.querySelector(".author_icon>img").src = "https://placehold.jp/80x80.png";
    parent_elem.querySelector(".author_name").innerText = "投稿者";

ここでquerySelectorとinnerTextを何度も書くのが面倒ということで、関数化するかどうかはその人の好みですね。

私はパッと見て何をやっているか分かり易いこちらの書き方の方が好きです。

コードを減らすことを目的に関数を噛ませてしまうと、その関数の使い方を追加で覚えなくてはいけなくなり、可読性を下げることになるのではないかと、私は考えているためですね。

手順5.画面に反映する

変数内の要素でHTMLを要素化し、データを埋め込んだだけでは、画面に表示されないため、適切な場所に埋め込み直します。

親要素をから欲しい要素のみを取り出し、受け込みたい部分に移動します。

まずは以下のように親要素から必要な部分を取り出し、親要素から取り出したものを除去するためにremoveします。

    const elem = parent_elem.children[0];
    parent_elem.remove(elem);

親要素から必要な部分の除去のためのremove部分は必要ないかもしれませんが、なんとなく残したままだと気持ちが悪い(メモリリークしそう)な気がするので除去します。

最後に、今回作成した要素をWebページ上に反映します。

    const feed_element = document.querySelector("#feed");
    feed_element.appendChild(elem);

今回紹介したコードのサンプル全体

最後に、今回ここまで紹介したサンプルコードの全体は以下の通りです。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="index.js"></script>
</head>
<body>
    <div class="container">
        <div id="feed">
        </div>
    </div>
</body>
</html>

index.js

window.addEventListener("load",() =>
{
    const template_html = `
    <div class="blog_card">
        <div class="eye_catch"><img></div>
        <div class="post_date"></div>
        <div class="post_title"></div>
        <div class="post_body"></div>
        <div class="post_to_detail"><a>続きを読む</a></div>
        <div class="author">
            <div class="author_icon"><img></div>
            <div class="author_name"></div>
        </div>
    </div>`.replace(/> *\n? +</g, "><");

    const parent_elem = document.createElement("div");
    parent_elem.innerHTML = template_html;

    parent_elem.querySelector(".post_date").innerText = "2024.01.01";
    parent_elem.querySelector(".post_title").innerText = "タイトル";
    parent_elem.querySelector(".post_body").innerText = "投稿内容本文";
    parent_elem.querySelector(".post_to_detail>a").href = "/article?id=1";
    parent_elem.querySelector(".eye_catch>img").src = "https://placehold.jp/300x200.png";
    parent_elem.querySelector(".author_icon>img").src = "https://placehold.jp/80x80.png";
    parent_elem.querySelector(".author_name").innerText = "投稿者";

    const elem = parent_elem.children[0];
    parent_elem.remove(elem);

    const feed_element = document.querySelector("#feed");
    feed_element.appendChild(elem);
});

さいごに

今回は説明のためにわかりやすいよう、シンプルな書き方にしました。

実際の実装はもう少し複雑で、HTMLのパーツごとにクラス化したり、もう少しルール化したりしています。

それでは良きJavaScriptライフを。

コメント

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