Multi Vitamin & Mineral

Multi Vitamin & Mineral です。プログラムに関することを書いております。

「カスタムデータ属性(data-*)」と「script タグへの JSON 埋め込み」のススメ【JavaScript で扱うデータを HTML に埋める方法2選】

f:id:hiranoon:20210305000546p:plain

ブラウザに表示されているデータは HTML 上にあり、 JavaScript から取得できるます。これはいいんです。が、ブラウザに表示されないが、JavaScript で扱いたいデータがあった場合は HTML のどこに書くのがよいのでしょう? というのが今回のテーマです。

方法はいくつかあるのですが、私が良さそうと思った2つの方法を書いています。

結論

前者は有名だと思っていますが、後者はあまり見かけない話かなと思います。前者を知っている人は後者だけでも見てってもらえればと思います。

今回取り上げる問題

問題が分かりにくいかと思うので、具体例をあげます。分かる人はどうぞ飛ばして(→次へ)。

表示データ=取得したいデータ(問題のないパターン)

まずは特に問題のない通常のパターンから。

以下のような HTML があるとします。

<div>しょうゆ:税抜<span id="price">200</span></div>

JavaScript で税込価格を計算します。

const price = document.getElementById('price').textContent; // "200" を取得
console.log('税込' + Math.round(price * 1.1) + '円'); // "price * 1.1" で税込価格
// => "税込220円"

document.getElementById('price').textContent で目的の 200 を取得できました。完。( ちなみに、 Math.round(...) は小数点以下の丸めです。)

表示データ≠取得したいデータ(問題のあるパターン)

ですが、取得したいデータが表示されていない場合もあるでしょう。

<div>しょうゆ:お値段秘密! カートに追加してからのお楽しみ!</div>

んなサイトあるかい! って感じの例ですが、要するに表示しないけれども裏でデータを持っておきたい場合です。この場合のデータは「税抜価格」の 200 です。この 200 を HTML のどこかに埋めておいて、 JavaScript から取得したい!

その1 : カスタムデータ属性(data-*)の利用(対応要素がある場合)

今回の解決策の1つ目。まずは条件として対応するタグ(要素)がある場合です。

この場合は HTML の仕様上用意されている「カスタムデータ属性( data-* )」を使うのがスジでしょう。

カスタムデータ属性(data-*)と利用例

以下、「カスタムデータ属性( data-* )」の例です。

<div id="item" 
  data-price="200"
  data-longname="本仕込み丸大豆醤油"
  data-shortname="しょうゆ">
しょうゆ:お値段秘密! カートに追加してからのお楽しみ!
</div>

「カスタムデータ属性( data-* )」は、「特定の要素(今回の例では <div id="item"> )に関連付いたデータ」を定義できるのが特徴です。

「属性」というくらいなので、 idclass のようにタグの中に プロパティ名="プロパティ値" の形式で埋め込めます。ルールは先頭を data- にすることで、その後ろは任意です。必要なデータを自由に好きなだけ追加できます

JavaScript からのアクセス

JavaScript からは [要素].dataset.[data-以下の部分] でアクセスできる仕組みになっています。

const item = document.getElementById('item'); // <div> を取得
console.log('税込' + Math.round(item.dataset.price * 1.1) + '円');
// => "税込220円"

item.dataset がカスタムデータ属性を指しています。 item.dataset.price でカスタムデータ属性の中のひとつの data-price へアクセスしています。

このように、標準の JavaScript でアクセスできる専用の方法(というか専用のプロパティ( dataset ))が用意されているというわけです。

場面にもよりますが、 <span id="price">200</span>円 のように頑張って表示データと関連させるよりずっと扱いやすいと思います。

jQuery からのアクセス

jQuery を利用していた場合は以下の方法を使いましょう。

const item = $("#item"); // <div> を取得
console.log('税込' + Math.round(item.data('price') * 1.1) + '円');
// => "税込220円"

jQuery からは [要素].data('[data-以下の部分]') でアクセスできる仕組みになっています。

カスタムデータ属性( data-* )のまとめ

「カスタムデータ属性( data-* )」は、

  • 特定の要素に関連付いたデータを定義できます。
  • 必要なデータを自由に好きなだけ追加できます
  • ルール
    • 先頭は必ず data- になります。
    • data- 以下の部分は任意ですので好きな名前を使えます。
  • アクセス方法
    • JavaScript からは [要素].dataset.[data-以下の部分] でアクセスできます
    • jQuery からは [要素].data('[data-以下の部分]') でアクセスできます

(参考)MDN の解説ページ

MDN の解説ページも一応掲載しておきます。

developer.mozilla.org

↑こちらは「カスタムデータ属性( data-* )」の利用例。当該記事以外の情報としては、 CSS からのアクセスについても触れられています

ただ、 CSS からは通常の属性としてアクセスするだけです。「カスタムデータ属性( data-* )」に特化した話ではありませんし、また、 CSS から利用する頻度は少なそうなので当該記事には書きませんでした。

developer.mozilla.org

↑こちらは「カスタムデータ属性( data-* )」の技術仕様。詳しく知っておきたい人向け。

その2 : script タグへの JSON 埋め込み(対応要素がない場合)

今回の解決策の2つ目。

条件として対応するタグ(要素)がない場合です。タグ(要素)がないのでその1の「カスタムデータ属性( data-* )」が使いたくても使えないパターンです。

script タグに JSON を埋め込む利用例

「script タグに JSON を埋め込めばいいじゃん」というタイトル通りの方法です。

こんな感じで使います。

<script id="user-info" type="application/json">
{
  "username": "hogehoge",
  "premiumUser": true
}
</script>

対応するタグがないので、<script> タグを作ります。 <script> タグはブラウザでは表示されません。 type="application/json" として、 JSON データであることを明示するとよいでしょう。(これはなくても動きますが)

JavaScript からは、 id で取得します。

const userInfo = JSON.parse(document.getElementById('user-info').text);
if (userInfo.premiumUser) {
  console.log(userInfo.username + 'はプレミアムユーザーです!');
}

id で取得した要素の中身を JSON.parse(...) でオブジェクトに変換しています。 userInfo はオブジェクトです。

オブジェクトの要素は . でアクセスできるので、 userInfo.premiumUsertrue が、 userInfo.username"hogehoge" が取得できます。

(参考)MDN の解説ページ

MDN には、「こういう使い方もできるよ」という例として掲載されていました。

developer.mozilla.org

<script>: スクリプト要素」の解説ページなのですが、正直こういうページを敢えて見ることはほぼないので、今回の情報は見つけにくかったですね。地味な場所に書かれていましたが、中々よい方法だと思いましたので拡散したいです。

(おまけ)避けたいパターン

ここからはおまけです。

私が様々なプロジェクトで見てきた実際のコードで、避けてもらいたいと思ったパターンです。これらを使うなら、先に紹介した2つの方法のどちらかを選択いただきたいと思ってます。

input タグの hidden を使う

こういうやつです。

<input type="hidden" id="price" value="200" />

<input type="hidden"> はブラウザ上で表示されないので、これを活用しようという方法です。

冷静に考えると(冷静にならなくとも)、通常 <input type="hidden"><form> の中に入れて、サーバー側にデータを送信する(submit する)用途で使うものです。サーバー側にも送信するならいいんですが、送信しないのにこれを使うのは違うかな、と思います。

サーバーサイドエンジニアが使いがちな方法で、何度も見てきました。ブラウザに見せないデータを <input type="hidden"> で送信することはよくあるので、それをフロントにも転用させてしまったパターンです。まあ問題なく動いちゃうんですけどね。

display: none で見えない意味無しタグを用意する

ちょっとタイトル分かりにくいですが、こんな感じ。

<div id="price">200</div>
<style>
#price {
  display: none;
}
</style>

<div> などの「ブラウザに表示される意味を持つタグ」を使い、 CSS の display: none; により「ムリヤリ非表示にしてやろう」という作戦です。

<input type="hidden"> よりも気持ちは分かる、が、本来はブラウザに表示するタグを別用途で使ってるんですよね。今回あげた2つの方法の方が、本来の用途に従っているという意味でスッキリ解決できるかと思います。

JavaScript のコードに埋め込む

ゴクマレに見かけます。レアです。

<div>しょうゆ:税抜<%= getPrice() %></div> <!-- Java コード getPrice() を実行 -->
<script>
const price = <%= getPrice() %>; // Java コード getPrice() を実行
console.log('税込' + Math.round(price * 1.1) + '円');
</script>

注目は3行目の const price = <%= getPrice() %>; の部分です。 Java の JSP を例にしていますが、 PHP でも Ruby でもありえる話です。

サーバーサイド側の言語にて、 HTML に値を埋め込むことはあります。通常は <div>しょうゆ:<%= getPrice() %>円</div> と使います。( getPrice( ) は別途作成している前提。)ここまではOK。

JavaScript から HTML の値を取得する方法が分からなくて(or 思いつかなくて)、 JavaScript のソースコード中に、 const price = <%= getPrice() %>; のようにして変数や定数に直接値を埋め込んだのでしょう。 JavaScript を HTML (JSP) と別ファイルにできなくなりますし、結構キッツイです。

これは、いろいろな手法を知らないため、知恵を絞って考えた方法なのだと思います。(そこは偉い!)先人がすでに考えた方法がありますので、それを知っていただいて、データは HTML に埋め込み JavaScript はそれを扱うっていう原則に従ってコードを書けるようになるといいかなと思います。いや、初心者のころって知識は断片的になっちゃうんで気持ちは分かるんですよね。知ったらこんな方法使わんわーってなると思いますので、知識を仕入れてもらえればと思います。

あとがき

私、 <script type="application/json"> の中に JSON データを定義する方法、知らなかったんですね。知って「便利じゃん!」と思って、知らせたく、拡散したくて、そのために記事を書きました。

尚、こちらのはてなブログ用のパーツでも利用しています。パーツ用の設定値を埋め込む方法に JSON を利用しました。

multimineral-tech.com

当該記事では、2つの方法チョイスして書きましたが、他によりいい方法があったならすみません。。。(教えていただきたいです。。。)