素のJavascript(Vanilla)だけでフロントエンド開発(04:API+SELECT編)

このページでは、ReactやVueではなく、Vanillaと呼ばれる素のJavascriptのみで、フロントエンド開発が可能であるか?を解説します。今回はAPIを利用してSELECTタグにOPTIONを追加します。

今回は、前回…

の続きになります。

目次

今回の課題のゴールは?

さて、いよいよ、APIを利用してHTMLをアレンジメントしていきます。

今回はSELECTタグのOPTIONに、APIから取得した値を追加していきます。

いきなり、難しくなってしまうかもしれませんが、頑張って、ついてきてください!

OPTIONタグを追加する必要ってあるの?

一般的な入力画面では、必ずと言っていいほど、何らかの選択項目を入力する必要があります。

例えばアンケート画面であれば、年齢層や職業・職種、都道府県など。

その際に用いるタグ(または属性)は次のどれかになります。

  • チェックボックス
    未選択でも良い場合
    複数選択する可能性がある場合(複数回答可など)
  • ラジオボタン
    いづれかひとつを必ず選択しなければならない場合
  • セレクト(ボックス)
    ラジオボタンと同様だが、項目数が多い場合に使われる
    選択しなくても良い場合もある

この内、ラジオボタンやチェックボックスは、フラグや区分に属する項目であり、画面上にベタ書きされることがほどんとで、動的に項目が増えたり減ったりすことは、まず少ないと思います。

一方、セレクト(ボックス)はコード管理されていることが多いため、DBから都度セットしたいといったニーズが多々あります。

その場合でのプログラミング例を考えていきましょう。

APIを使用したプログラム例

プログラム例

今回はいきなり結論的なHTMLを貼り付けます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<script src="js/vanilla-front-end.js"></script>
<script>
async function setInitData() {

  // API実行
  let result = await fetch("api_mock/select.json", {
      method: "GET"
  });

  // JSONを取り出す
  let jsonData = await result.json();

  //console.log(jsonData.result);

  // selectのoptionを追加する
  addSelectOption(jsonData.result, true);

} // function

// DOMコンテンツのロード完了時に実行
window.addEventListener('DOMContentLoaded', (ex) => {
  // SELECTタグのJSONを取得
  setInitData();
});
</script>
</head>
<body>

開始時間<select name="hoge1" class="sel_time_kbn"></select><br />
終了時間<select name="hoge2" class="sel_time_kbn"></select><br />
<br />
元号1<select name="foo1" class="sel_gengo"></select><br />
元号2<select name="foo2" class="sel_gengo">
  <option value="X">ここには追加しません</option>
  <option value="M">明治</option>
  <option value="T">大正</option>
</select><br />

<template id="bar">
  テンプレート内の選択項目<select name="hoge3" class="sel_time_kbn"></select>
</template>

</body>
</html>
function addSelectOption(jr, guidance=true) {

  //console.log(jr);
  // 連想配列でループ
  Object.entries(jr).forEach(function ([key, opt]) {
    //console.log(key);
    //console.log(opt);

    // そのselect項目に対応したclassを取得し配列化する
    let cls = Array.from(document.getElementsByClassName('sel_' + key));
    //console.log(cls);
    // selectタグが複数存在することを想定し、ループする
    cls.forEach(function (cl) {
      //console.log(cl);
      // すでにoptionが存在している場合は、スルーする
      //console.log(cl.length);
      if (cl.length > 0) return; // このreturnはaddSelectOptionのリターンではなくcontinueと同じ意味
      // 「選択してください」を追加する
      if (guidance) {
        let option = document.createElement("option");
        option.text = '選択してください';
        option.value = '-';
        cl.appendChild(option);
      }
      // optionタグでループする
      opt.forEach(function (op) {
        // optionタグを追加する
        let option = document.createElement("option");
        option.text = op.text;
        option.value = op.value;
        cl.appendChild(option);
      }); // opt.forEach(function (op) {
    }); // cls.forEach(function (cl) {
  }); // Object.entries(jr).forEach(function ([key, opt]) {
  
} // function

例題作成時にデバッグに使用したconsoleは、そのままにしております。
適宜、コメントを外して中を覗いてみてください。

さらに、同一ディレクトリ内に「api_mock」を作成し、以下のJSONファイルを追加します。

{
  "result" : {
    "time_kbn" : [
      {"value" : "AM" , "text" : "午前"},
      {"value" : "PM" , "text" : "午後"}
    ],
    "gengo" : [
      {"value" : "S" , "text" : "昭和"},
      {"value" : "H" , "text" : "平成"},
      {"value" : "R" , "text" : "令和"}
  ]
  }
}

必要性に応じ、適宜、値や行数を変えてみてください。

JSONファイルを変更してもキャッシュとして残っている場合があります。ブラウザのF12からNetwork>select.jsonを右クリック>Clear browser cacheを実行してみてください。
(一般的なJavascriptやCSSといった静的なファイルの症状と一緒です。これがモックではなくサーバーサイドフレームワークとやり取りをする段階で、APIのみこの症状は解決します。)

実行方法

http://localhost/~(ユーザー名)/test_api_select.html

にて、SELECTにOPTIONが追加されていればOKです。

プログラムの説明

プログラムのざっくり説明

ざっくりですが、このようなことを行っています。

  • JSONの”result”でループし、連想配列(例題では”time_kbn”や”gengo”)を取得します
  • getElementsByClassName()にて、”sel_” + 連想配列でclassを検索し、ループします
    • そのelements(=SELECTタグ)にOPTIONを追加していきます

HTML側のclassも”sel_time_kbn”といったように、返された名称管理コード的なものと併せておくと、簡単に置き換えることが出来るでしょう。
“sel_time_kbn”を持つSELECTタグが2つ以上ある場合にも、対象のSELECTタグ直下にOPTIONを追加します。

addSelectOption()の処理解説

addSelectOption()について、もう少し詳しく説明します。

まず、JSONの連想配列”result”でループを行います。
Javascriptの進化過程で、いろんな方法があると思うのですが、

Object.entries(jsonのresult).forEach(function ([key, opt]) {

で、key(例題では、time_kbnとgengoといった値)と、配列opt(valueとkeyの連想配列が同梱されたもの)が取得できます。

次にkeyを元に、

let cls = Array.from(document.getElementsByClassName('sel_' + key));
cls.forEach(function (cl) {

「sel_xxxx」といった配列を持つclass属性の配列を取得します。
で、こちらでループします。
ループの初っ端で「選択してください」OPTIONを追加します。

さらに、

opt.forEach(function (op) {
  // optionタグを追加する
  let option = document.createElement("option");
  option.text = op.text;
  option.value = op.value;
  cl.appendChild(option);

JSONの”time_kbn”や”gengo”にぶら下がる配列をもとに、OPTIONタグを追加していきます。

guidanceをfalseにすると…

// selectのoptionを追加する
addSelectOption(jsonData.result, true);

truefalseにした場合、「選択してください」は表示されなくなります。
まあ、オマケ機能ですね。

APIには欲しいコードをリクエストするパラメータを付ける

今回はモックを使ったシンプルな例(パラメータなし)ですが、これが実際のシステムとなると、名称テーブルの項目も膨大になり、何も考えずそのまま作成すると、無条件にすべてのコードが露わになり、コード管理が丸見えになってしまいます

ですので、必要な選択項目のみリスト形式(カンマ区切り)で渡し、その項目だけのJSONを返したほうが賢明でしょうね。

今回の例では、以下のようなパラメータになると思います(prmは適切な値に変えてください)。

http://localhost/~(ユーザー名)/api_mock/(APIプログラム名)?prm=time_kbn,gengo

APIのパラメータに「all」、あるいはパラメータなしの場合は全項目…といった裏機能を付けたがるかもしれませんが、セキュリティ上問題だと思うので、避けましょう
(不正なパラメータはさり気なくログに残すと良いかも。)

参考:API側のセキュリティ対策は?

参考までに、API側の一般的なセキュリティ対策をリストアップしておきます。

  • Cookieにあるハッシュ値をチェックする
    (パラメータあるいはヘッダー・BODY等で受け取らなくても、サーバーサイドで勝手に確認できるかも)
  • (必要ならば)第2ハッシュを先に発行し、それとの比較を行う
    (主にDB更新が伴うであろうPOST系のチェックなので)値を返すだけなら、そこまで必要か?考えて
  • (前項のように)パラメータの不正チェックを行う

こんなところでしょうか。
不正があった場合は、ログインに飛ぶなり、しれっとログに書くなり、何らかのことはやっておいたほうが良いかもしれませんね。

TEMPLATE内のSELECTタグは要注意

TEMPLATE内のclassにはアクセスできない

上手く行ったように思えますが、F12でElementsを覗いてみると、謎の<template>なる非表示欄があることが、わかります。
ソースを確認すると確かに「テンプレート内の選択項目」といったものが<template>と</template>で囲まれていますよね?

で、それをよく見ると、OPTIONが追加されていません。
classは一致するのにも関わらずです。

こちらのTEMPALTEタグは、このTEMPLATEは任意の場所にコピペ出来るのです。
ただ、ドキュメント対象外となるので、classなどで、直接アクセスすることは出来ません。

TEMPALTEは主に明細行追加や一覧画面に使用されます。
気になる方は、参考記事を御覧ください。

参考までに解決策は…

こちらのロジックは、明細行追加で使用されます。

クローンをコピペする段階でclassが認識出来るようになりますので、そこで先程のロジックを再実行します。

ただし、何も考えずに実行すると、すべてのclassに対し再追加してしまう不具合が発生するかもしれません。

それを回避するには、すでに設定したSELECTタグのclassを逐一除去するようにaddSelectOption()を改造するか、addSelectOption()のclassをidに変更した版を別途作成する等が考えられますが、実は、例題では、すでにOPTIONタグが設定されている場合は追加せず、ループ内の次の項目へスキップするようなロジックが挿入されています

// すでにoptionが存在している場合は、スルーする
//console.log(cl.length);
if (cl.length > 0) return; // このreturnはaddSelectOptionのリターンではなくcontinueと同じ意味

これにより、「元号2」欄のOPTIONが追加されていないことがわかります。

それでも、一度設定したSELECTタグのclassを除去する方がお好みでしたら、そのように改造しても構いません。

最後に

だんだん難しくなってきましたね!

今回もfunctionをHTMLの中にベタ書きしましたが、実際に利用する場合は、外部のJSソースにまとめましょう。

次回は、そのSELECTの初期値をセットするようにします。
さらにINPUTやTEXTAREA、SPANなどにも初期値をセットします。

それでは、また。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA

目次