前回からのつづき

前回、IndexedDBの基本動作の確認について説明を行ってきました。今回は、その続きとなります。

今回は、データ検索テストプログラムを中心に説明したいと思います。


データ検索テストプログラムによる検索動作の確認

下記のボタンを押下することで、テストプログラムの起動が可能です。

👉 本記事のテストプログラムを動かすことで、「IndexedDBの動き」が体感できます。


テストプログラムの概要(かんたん説明)

このプログラムが保存するデータは、売り上げのオブジェクトです。

例)

{
  id: 1,
  hizuke: 2026/05/01,
  hinmoku: "えんぴつ",
  kingaku: 1000
}

表示画面には、大きく分けて3つのエリアが存在します。

  • テーブルエリア
  • 検索項目エリア
  • 検索条件エリア

テーブルエリア

IndexedDBに格納されているデータが一覧で表示されます。(最大10件)

検索項目エリア

検索対象とする項目を選択するものです。

検索条件エリア

値を入力することにより、一致検索、前方一致検索、下限検索、上限検索、範囲検索が可能です。検索されたデータは、テーブルエリアに反映されます。

全検索ボタン押下により、全件表示に戻ります。

構造はいたってシンプルです。

操作手順

手順①:本頁の「テストプログラム起動」を押す

テストプログラム起動 👉 起動時に、現在登録されているデータがテーブルエリアに表示されます。

手順②:一致検索

検索項目エリアにて、検索対象の項目を選択 検索条件エリアの1つ目の入力欄に値を入力し、「一致検索」ボタン押下 👉 入力欄への入力後、「一致検索」ボタンが押せるようになります。

手順③:前方一致検索

検索項目エリアにて、検索対象の項目を選択 検索条件エリアの1つ目の入力欄に値を入力し、「前方一致検索」ボタン押下 👉 入力欄への入力後、「前方一致検索」ボタンが押せるようになります。ただし、検索対象項目が文字列の場合のみ使用可能となります。

手順④:下限検索

検索項目エリアにて、検索対象の項目を選択 検索条件エリアの2つ目の入力欄に値を入力し、「下限検索」ボタン押下 👉 入力欄への入力後、「下限検索」ボタンが押せるようになります。 👉 「閾値含む」のチェックを外すことで、入力した値を含まない検索が可能です。

手順⑤:上限検索

検索項目エリアにて、検索対象の項目を選択 検索条件エリアの3つ目の入力欄に値を入力し、「上限検索」ボタン押下 👉 入力欄への入力後、「上限検索」ボタンが押せるようになります。 👉 「閾値含む」のチェックを外すことで、入力した値を含まない検索が可能です。

手順⑥:範囲検索

検索項目エリアにて、検索対象の項目を選択 検索条件エリアの2つ目と3つ目の入力欄の両方に値を入力し、「範囲検索」ボタン押下 👉 両方の入力欄への入力後、「範囲検索」ボタンが押せるようになります。 👉 「閾値含む」のチェックを外すことで、入力した値を含まない検索が可能です。

手順⑦:全検索

「全検索」ボタン押下 👉 登録されている全てのデータが読み込まれます。


動作確認を実施する

このテストで確認したいこと

このテストでは、次のことを確認していきたいと思います:

  • IndexedDBのデータストアへの初期データ登録の動き
  • Key値による一致検索の動き
  • Key値による下限検索の動き
  • Key値による上限検索の動き
  • Key値による範囲検索の動き
  • Indexによる一致検索の動き
  • Indexによる前方一致検索の動き
  • Indexによる下限検索の動き
  • Indexによる上限検索の動き
  • Indexによる範囲検索の動き

実際の操作手順(ここが重要)

  1. 手順:テストプログラムの起動。 初期データ10件が登録され、テーブルエリアに表示されることを確認します。

👉 IndexedDBのオープン、データ保存、読込を確認します。

  • indexedDB.open()
  • IDBOpenDBRequest.onupgradeneeded
  • IDBOpenDBRequest.onsuccess
  • IDBDatabase.createObjectStore()
  • IDBDatabase.transaction()
  • IDBTransaction.objectStore()
  • IDBObjectStore.createIndex()
  • IDBObjectStore.openCursor()
  • IDBObjectStore.add()
  • IDBCursor.continue()
  1. 手順:IDによる一致検索を行う。 指定されたIDのデータのみが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.only()による一致検索の動きを確認します

  1. 手順:IDによる下限検索を行う。 指定されたID以降のデータが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.lowerBound()による下限検索の動きを確認します

  1. 手順:IDによる上限検索を行う。 指定されたID以前のデータが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.upperBound()による上限検索の動きを確認します

  1. 手順:IDによる範囲検索を行う。 指定されたID範囲のデータが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.bound()による範囲検索の動きを確認します

  1. 手順:ID以外の項目による一致検索を行う。 指定された項目のデータのみが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.only()による一致検索の動きを確認します

  1. 手順:ID以外の項目による前方一致検索を行う。 指定された項目のデータと前方一致するデータのみが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.bound()による前方一致検索の動きを確認します

  1. 手順:ID以外の項目による下限検索を行う。 指定された項目以降のデータが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.lowerBound()による下限検索の動きを確認します

  1. 手順:ID以外の項目による上限検索を行う。 指定された項目以前のデータが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.upperBound()による上限検索の動きを確認します

  1. 手順:ID以外の項目による範囲検索を行う。 指定された項目範囲のデータが読み込まれ、テーブルエリアに表示されることを確認します。

👉 IDBKeyRange.bound()による範囲検索の動きを確認します

テスト結果まとめ

  • IndexedDBの初期データ登録は、IDBOpenDBRequest.onupgradeneeded内で、IDBObjectStore.add()やIDBObjectStore.put()が使用可能
  • IDBOpenDBRequest.onupgradeneededの中では、暗黙的に「versionchange トランザクション」が開いているため、新たなトランザクションの開始は不要
  • 初期データの件数が多い場合(1000 件以上)は、 データ量が多すぎてトランザクションが閉じることを防ぐ為、IDBOpenDBRequest.onupgradeneededの中ではなく、IDBOpenDBRequest.onsuccess内で、分割登録の検討が必要
  • IndexedDBの検索は、指定項目の「一致検索」「前方一致検索」「下限検索」「上限検索」「範囲検索」が基本
  • Key以外の項目による検索は、IDBObjectStore.createIndex()で、事前にインデックスの作成が必須
  • 「複合条件検索(ORの使用など)」は基本的にできない
  • 「前方一致検索」は、範囲検索の応用で文字列の場合のみ実現可能

テストプログラムのコード

下記に、本テストで使用したプログラムのコードを記します。

1. HTML(index.html)

注)前回までのものとは、別物です。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Session Storage Test</title>
  <link rel="stylesheet" href="./style.css">
  <script type="text/javascript" src="./script.js"></script>
  <script type="text/javascript" src="./table_class.js"></script>
</head>
<body>
  <div id="table-area"></div>
  <fieldset class="field">
    <legend>[ 検索項目 ]</legend>
    <div>
      <label>
        <input type="radio" id="fid" name="field" value="id" checked>ID
      </label>
      <label>
        <input type="radio" id="fhizuke" name="field" value="hizuke">日付
      </label>
      <label>
        <input type="radio" id="fhinmoku" name="field" value="hinmoku">品目
      </label>
      <label>
        <input type="radio" id="fkingaku" name="field" value="kingaku">金額
      </label>
    </div>
  </fieldset>
  <fieldset class="condition">
    <legend>[ 検索条件 ]</legend>
    <div>
      <input type="number" class="value" id="input-value" disabled><br>
      <input type="number" class="bound" id="input-lower-value" disabled> ~ <input type="number" class="bound" id="input-upper-value" disabled><br>
      <label><input type="checkbox" id="input-lower-threshold" checked disabled> 閾値含む      </label>
      <label><input type="checkbox" id="input-upper-threshold" checked disabled> 閾値含む</label>
    </div>
    <div class="search-button">
      <button id="btn-only" disabled>一致検索</button>
      <button id="btn-prefix-match" disabled>前方一致検索</button><br>
      <button id="btn-lower" disabled>下限検索</button>
      <button id="btn-upper" disabled>上限検索</button>
      <button id="btn-bound" disabled>範囲検索</button>
    </div>
  </fieldset>
  <p>
    <button id="btn-all" disabled>全検索</button>
  </p>
  <p>
    <div id="popup-status"></div>
  </p>
</body>
</html>

2. CSS(style.css)

注)前回までのものとは、別物です。

body {
  padding: 1rem;
  background-color: rgb(253, 191, 76);
  text-align: center;
  overflow: hidden;
}
#table-area {
  height: 12rem;
  background-color: white;
  overflow-y: auto;
}
#table-area table {
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid;
}
#table-area thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  border: 1px solid;
  background-color: skyblue;
  font-weight: bold;
  height: 2rem; 
}
#table-area tbody td {
  border: 1px solid;
  background-color: white;
  height: 2rem; 
}
#table-area tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 1;
  border: 1px solid;
  background-color: lightgray;
  font-weight: bold;
  height: 2rem; 
}
#table-area .id {
  width: 20rem;
}
#table-area .hizuke {
  width: 30rem;
}
#table-area .hinmoku {
  width: 50rem;
}
#table-area .kingaku {
  width: 30rem;
}
.condition {
  display: flex;
  justify-content: center;
  gap: 1rem;
}
.search-button {
  text-align: left;
}
.value {
  width: 19.5rem;
}
.bound {
  width: 8rem;
}
#popup-status {
    font-size: 1.25rem;
    font-weight: bold;
    color: red;
    margin-bottom: 1rem;
}

3. JavaScript(本体:script.js)

注)前回までのものとは、別物です。

//
// IndexedDBの動作確認用JavaScriptコード
//
window.addEventListener("load", function() {

  // 表示テーブル作成
  const tableObj = new tableClass("table-area", [
    { name: "id", class: "id", type: "number", calc: "合計", content: "ID" },
    { name: "hizuke", class: "hizuke", type: "date", calc: "", content: "日付" },
    { name: "hinmoku", class: "hinmoku", type: "string", calc: "", content: "品目" },
    { name: "kingaku", class: "kingaku", type: "number", calc: "sum", content: "金額" }
  ]);

  // 画面要素取得
  const input_fields = document.getElementsByName("field");
  const input_value = document.getElementById("input-value");
  const input_lower_value = document.getElementById("input-lower-value");
  const input_upper_value = document.getElementById("input-upper-value");
  const input_lower_threshold = document.getElementById("input-lower-threshold");
  const input_upper_threshold = document.getElementById("input-upper-threshold");
  const btn_only = document.getElementById("btn-only");
  const btn_prefix_match = document.getElementById("btn-prefix-match");
  const btn_lower = document.getElementById("btn-lower");
  const btn_upper = document.getElementById("btn-upper");
  const btn_bound = document.getElementById("btn-bound");
  const btn_all = document.getElementById("btn-all");
  const status = document.getElementById("popup-status");

  // 固定値定義
  const MAX_COUNT = 10;
  const datas = [
    { id: 1, hizuke: new Date("2026-05-01"), hinmoku: "ノート", kingaku: 2000 },
    { id: 2, hizuke: new Date("2026-05-02"), hinmoku: "えんぴつ", kingaku: 1200 },
    { id: 3, hizuke: new Date("2026-05-02"), hinmoku: "消しゴム", kingaku: 500 },
    { id: 4, hizuke: new Date("2026-05-03"), hinmoku: "ボールペン", kingaku: 1500 },
    { id: 5, hizuke: new Date("2026-05-03"), hinmoku: "シャーペン", kingaku: 100 },
    { id: 6, hizuke: new Date("2026-05-04"), hinmoku: "定規", kingaku: 900 },
    { id: 7, hizuke: new Date("2026-05-04"), hinmoku: "コンパス", kingaku: 2500 },
    { id: 8, hizuke: new Date("2026-05-05"), hinmoku: "ノートA4", kingaku: 1000 },
    { id: 9, hizuke: new Date("2026-05-06"), hinmoku: "ノートB5横", kingaku: 6000 },
    { id: 10, hizuke: new Date("2026-05-07"), hinmoku: "ノートB5縦", kingaku: 2000 }
  ];

  // 変数定義
  let db = null;              // IndexedDBのIDBDatabase(DB接続)オブジェクト
  let threshold_open = false; // 閾値フラグ false:含まない/true:含む

  // 検索項目に従い、入力欄の種別変更
  input_fields.forEach(function (field) {
    field.addEventListener('click', function(event) {
      input_value.value = "";
      input_lower_value.value = "";
      input_upper_value.value = "";
      btn_only.disabled = true;
      btn_prefix_match.disabled = true;
      btn_lower.disabled = true;
      btn_upper.disabled = true;
      btn_bound.disabled = true;

      switch (field.value) {
        case "id" :
          input_value.type = "number";
          input_lower_value.type = "number";
          input_upper_value.type = "number";
          break;
        case "hizuke" :
          input_value.type = "date";
          input_lower_value.type = "date";
          input_upper_value.type = "date";
          break;
        case "hinmoku" :
          input_value.type = "text";
          input_lower_value.type = "text";
          input_upper_value.type = "text";
          break;
        case "kingaku" :
          input_value.type = "number";
          input_lower_value.type = "number";
          input_upper_value.type = "number";
          break;
        default :
          input_value.type = "text";
          input_lower_value.type = "text";
          input_upper_value.type = "text";
          break;
      }
    });
  });

  // 一致検索、前方一致検索の入力欄の変更を監視して、入力があれば検索ボタンを有効化
  ['keyup', 'change'].forEach(function (eventType) {
    input_value.addEventListener(eventType, function(event) {
      input_lower_value.value = "";
      input_upper_value.value = "";
      input_lower_threshold.disabled =true;
      input_upper_threshold.disabled =true;
      btn_lower.disabled = true;
      btn_upper.disabled = true;
      btn_bound.disabled = true;
      if (this.value) {
        btn_only.disabled = false;
        if (input_value.type == "text") btn_prefix_match.disabled = false;
      } else {
        btn_only.disabled = true;
        btn_prefix_match.disabled = true;
      }
      status.textContent = "";
    });
  });

  // 下限検索の入力欄の変更を監視して、入力があれば検索ボタンを有効化
  ['keyup', 'change'].forEach(function (eventType) {
    input_lower_value.addEventListener(eventType, function(event) {
      input_value.value = "";
      btn_only.disabled = true;
      btn_prefix_match.disabled = true;
      if (this.value) {
        input_lower_threshold.disabled =false;
        btn_lower.disabled = false;
        if (input_upper_value.value) btn_bound.disabled = false;
      } else {
        input_lower_threshold.disabled =true;
        btn_lower.disabled = true;
        btn_bound.disabled = true;
      }
      status.textContent = "";
    });
  });

  // 下限検索、上限検索、範囲検索の入力欄の変更を監視して、入力があれば検索ボタンを有効化
  ['keyup', 'change'].forEach(function (eventType) {
    input_upper_value.addEventListener(eventType, function(event) {
      input_value.value = "";
      btn_only.disabled = true;
      btn_prefix_match.disabled = true;
      if (this.value) {
        input_upper_threshold.disabled =false;
        btn_upper.disabled = false;
        if (input_lower_value.value) btn_bound.disabled = false;
      } else {
        input_upper_threshold.disabled =true;
        btn_upper.disabled = true;
        btn_bound.disabled = true;
      }
      status.textContent = "";
    });
  });

  // 一致検索、前方一致検索ボタン押下時に、IndexedDBから値を取得し、テーブルに表示
  [btn_only, btn_prefix_match].forEach(function (button) {
    button.addEventListener('click', function(event) {
      const tx = db.transaction('sales', 'readonly');
      const store = tx.objectStore('sales');

      tableObj.setHeader();

      const results = []

      function query(value) {
        switch (button.id) {
          case "btn-only" :
            return IDBKeyRange.only(value);
          case "btn-prefix-match" :
            return IDBKeyRange.bound(value, value+"\uffff");
          default :
            return IDBKeyRange.only(value);
        }
      }

      let request;
      input_fields.forEach((input_field) => {
        if (input_field.checked) {
          switch (input_field.value) {
            case "id" : 
              request = store.openCursor(query(Number(input_value.value)));
              break;
            case "hizuke" :
              request = store.index(input_field.value + "Index").openCursor(query(new Date(input_value.value)));
              break;
            case "hinmoku" :
              request = store.index(input_field.value + "Index").openCursor(query(input_value.value));
              break;
            case "kingaku" :
              request = store.index(input_field.value + "Index").openCursor(query(Number(input_value.value)));
              break;
            default :
              request = store.openCursor(query(0));
              break;
          }
        }
      });

      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (!cursor) {
          tableObj.setFooter();
          if (!results.length) status.textContent = "指定されたデータがありません。";
          return;
        }

        results.push(cursor.value)
        tableObj.setData(cursor.value);

        status.textContent = "完了"

        if (results.length < MAX_COUNT) {
          cursor.continue();
          return;
        }

        tableObj.setFooter();
      }

      request.onerror = () => {
        status.textContent = "エラー発生"
      }
    });
  });

  // 下限検索、上限検索、範囲検索ボタン押下時に、IndexedDBから値を取得し、テーブルに表示
  [btn_lower, btn_upper, btn_bound].forEach(function (button) {
    button.addEventListener('click', function(event) {
      const tx = db.transaction('sales', 'readonly');
      const store = tx.objectStore('sales');

      tableObj.setHeader();

      const results = []

      function query(lowerValue, upperValue, lowerOpen, upperOpen) {
        switch (button.id) {
          case "btn-lower" :
            return IDBKeyRange.lowerBound(lowerValue, lowerOpen);
          case "btn-upper" :
            return IDBKeyRange.upperBound(upperValue, upperOpen);
          case "btn-bound" :
            if (lowerValue == upperValue && lowerOpen != upperOpen) {
              return IDBKeyRange.only(0);
            } else if (lowerValue > upperValue) {
              return IDBKeyRange.only(0);
            } else {
              return IDBKeyRange.bound(lowerValue, upperValue, lowerOpen, upperOpen);
            }
          default :
            return IDBKeyRange.only(0);
        }
      }

      let request;
      input_fields.forEach((input_field) => {
        if (input_field.checked) {
          const lower_open = (input_lower_threshold.checked ? false : true);
          const upper_open = (input_upper_threshold.checked ? false : true);

          switch (input_field.value) {
            case "id" : 
              request = store.openCursor(query(Number(input_lower_value.value), Number(input_upper_value.value), lower_open, upper_open));
              break;
            case "hizuke" :
              request = store.index(input_field.value + "Index").openCursor(query(new Date(input_lower_value.value), new Date(input_upper_value.value), lower_open, upper_open));
              break;
            case "hinmoku" :
              request = store.index(input_field.value + "Index").openCursor(query(input_lower_value.value, input_upper_value.value, lower_open, upper_open));
              break;
            case "kingaku" :
              request = store.index(input_field.value + "Index").openCursor(query(Number(input_lower_value.value), Number(input_upper_value.value), lower_open, upper_open));
              break;
            default :
              request = store.openCursor(query(0, 0, 0, 0));
              break;
          }
        }
      });

      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (!cursor) {
          tableObj.setFooter();
          if (!results.length) status.textContent = "指定されたデータがありません。";
          return;
        }

        results.push(cursor.value)
        tableObj.setData(cursor.value);

        status.textContent = "完了"

        if (results.length < MAX_COUNT) {
          cursor.continue();
          return;
        }

        tableObj.setFooter();
      }

      request.onerror = () => {
        status.textContent = "エラー発生"
      }
    });
  });

  // 全検索ボタン押下時に、カスタムイベント発生
  btn_all.addEventListener('click', function(event) {
    document.dispatchEvent(new CustomEvent('reloadTable', {
      detail: { storename: 'sales' }
    }));
  });

  // カスタムイベント発生時、IndexedDBから全データ取得し、テーブルを再表示
  document.addEventListener('reloadTable', (e) => {
    const tx = db.transaction(e.detail.storename, 'readonly');
    const store = tx.objectStore(e.detail.storename);

    tableObj.setHeader();

    const results = []

    const request = store.openCursor();
    request.onsuccess = (event) => {
      const cursor = event.target.result;
      if (!cursor) {
        tableObj.setFooter();
        return;
      }

      results.push(cursor.value)
      tableObj.setData(cursor.value);

      if (results.length < MAX_COUNT) {
          cursor.continue();
          return;
      }

      tableObj.setFooter();
    }
  });

  // 初期処理
  status.textContent = "データベースオープン中";

  const request = indexedDB.open('SalesDatabase', 1);

  request.onupgradeneeded = (event) => {
    db = event.target.result;
    if (!db.objectStoreNames.contains("sales")) {
      const store = db.createObjectStore("sales", { keyPath: "id" });
      store.createIndex("hizukeIndex", "hizuke");
      store.createIndex("hinmokuIndex", "hinmoku");
      store.createIndex("kingakuIndex", "kingaku");
      datas.forEach(data => {
        store.add(data);
      });
    }
  };

  request.onsuccess = (event) => {
    db = event.target.result;
    document.dispatchEvent(new CustomEvent('reloadTable', {
      detail: { storename: 'sales' }
    }));
    input_value.disabled=false;
    input_lower_value.disabled=false;
    input_upper_value.disabled=false;
    btn_all.disabled=false;
    status.textContent = "";
  };
});

4. JavaScript(テーブルクラス:table_class.js)

注)前回までのものとは、別物です。

//
// テーブル拡張クラス用JavaScriptコード
//
class tableClass {

  // コンストラクター(テーブルを生成するdivタグとテーブルの定義をうけとる)
  constructor(target, fields) {
    this.target = target;
    this.fields = fields;
    this.table = document.createElement("table");
    document.getElementById(target).append(this.table);
  }

  // テーブル定義に従い、テーブルのヘッダー部を作成
  setHeader() {
    this.table.innerHTML = "";
    this.thead = document.createElement("thead");
    this.tbody = document.createElement("tbody");
    this.tfoot = document.createElement("tfoot");
    this.table.appendChild(this.thead);
    this.table.appendChild(this.tbody);
    this.table.appendChild(this.tfoot);
    const trec = document.createElement('tr');
    this.fields.forEach((field) => {
      const theader = document.createElement('th');
      theader.classList.add(field.class);
      theader.textContent = field.content;
      trec.appendChild(theader);
    });
    this.thead.appendChild(trec);
  }

  // 1行分のデータを受け取り、テーブル定義に従ってテーブルのデータ部を作成
  setData(data) {
    const trec = document.createElement('tr');
    this.fields.forEach((field) => {
      const tdata = document.createElement('td');
      tdata.textContent = (field.type == "date" ? data[field.name].toLocaleDateString() : data[field.name]);
      trec.appendChild(tdata);
    });
    this.tbody.appendChild(trec);
  }

  // テーブル定義に従い、テーブルのフッター部を作成
  setFooter() {
    const trec = document.createElement('tr');
    this.fields.forEach((field, columnIndex) => {
      const tdata = document.createElement('td');
      if (field.type == "number") {
        switch (field.calc) {
          case "sum" :
            const tds = document.querySelectorAll(`#${this.target} table tbody tr td:nth-child(${columnIndex + 1})`);
            let sum = 0;
            tds.forEach(td => {
              const value = Number(td.textContent.trim());
              if (!isNaN(value)) sum += value;
            });
            tdata.textContent = sum;
            break;
          default :
            tdata.textContent = field.calc;
            break;
        }
      } else {
         tdata.textContent = field.calc;
      }
      trec.appendChild(tdata);
    });
    this.tfoot.appendChild(trec);
  }

  // データを配列で受け取り、テーブル定義に従ってテーブルを作成
  resetDatas(datas) {
    this.setHeader();
    datas.forEach((data) => {
      const trec = document.createElement('tr');
      this.fields.forEach((field) => {
        const tdata = document.createElement('td');
        tdata.textContent = (field.type == "date" ? data[field.name].toLocaleDateString() : data[field.name]);
        trec.append(tdata);
      });
      this.table.appendChild(trec);
    });
    this.setFooter();
  }
}

(function (root, factory) {
    if (typeof module === 'object' && typeof module.exports === 'object') {
        // Node.js環境
        module.exports = factory();
    } else {
        // ブラウザ環境
        root.tableClass = factory();
    }
}(this, function () {
    return tableClass;
}));