Tabulator CSVを読み込んでテーブルのデータにする

 

 

 

Tabulatorのドキュメントを読んでみると、JSONの他、CSVファイルもインポートしてテーブルのデータとして使えるので、試してみました。
列タイトル、カラムのフォーマットなどに拘らなければ少ない記述でかんたんに出来ることがわかりました。

< コード例 >


// HTML //
<div id="apidocs_body" class="card-body">
  <table id="usedata-table"></table>
</div>

// CSVファイル読込 自作jQueryラッパークラス //
const ajax = new MyAjax("document/DataSources.csv", {}, "text")
const csvData = ajax.exec()

// ++ Tabulatorで表示 ++ //
const table = new Tabulator("#usedata-table", {
  data: csvData,
  importFormat: "csv",
  autoColumns: true,
})

< データ例 >

種別,ソース,方法,サーバーPG,有料
AIRPORT メイン,Multi,MyAPI,PHP,
AIRPORT 滑走路,AeroDataBox,MyAPI,PHP,
AIRPORT ルート明細,AeroDataBox,MyAPI,Java,  *
"AIRPORT 地図 (空港, 滑走路)",国土交通省 MyData,MyAPI,Java,
AIRLINE メイン,Multi,MyAPI,PHP,
AIRLINE 機材別合計,Multi,MyAPI,Java,
AIRLINE 機材明細,Multi,MyAPI,PHP,
AIRLINE 機材登録履歴,AeroDataBox,Direct,,  *
AIRLINE 機材写真,PLANESPOTTERS,Direct,,
AIRLINE 地図 (機材カレント),AirLabs,Direct,,  *
AIRCRAFT メイン,Multi,MyAPI,PHP,
AIRCRAFT 検索項目,MyDB,,,
AIRCRAFT 機材指定オペレータ別機数,Multi,MyAPI,PHP,
AIRCRAFT 機材写真,PLANESPOTTERS,Direct,,
// == 以下省略 == //

< 画面例 >
何もセットしなければ、デフォルトで1行目をカラムヘッダーにしてくれます。

JSON.stringifyを使うとオブジェクトが空になる問題を解決

3日前まで正常に変換されてたのが、不思議なことにJSON.stringifyでサイズの大きい連想配列が空になってしまい、しばらく悩んでたところ、新規に作った連想配列にコピーしてそれを使えば解決しました。
ここで教えて頂きました。

< コード例 >



    // == 省略 == //

    let envmap = {"######": {}}
    let newObj = {};

    if (key !== "" && val !== "") {
      envmap = this.readLSMap()   // ローカルストレージから読込
      delete envmap["######"]
      
      
      envmap[key] = val
      console.log(envmap)   // <=== ???? [] 

      // 空になるので別の連想配列を生成して追加 //
      Object.keys(envmap).forEach(function (key) {
        newObj[key] = envmap[key];
      });

      console.log(JSON.stringify(newObj))    // 空でなくなる

      // == 省略 == //

vanillaSelectBox を使ってみる

今までマルチセレクトについては、jQueryのMultipleSelect を使ってましたが、Bootstrap4で使うと表示が崩れる(多分CSSを変えれば直ると思いますが、調べる時間がないので)、VanillaSelectBoxを使ってみました。
ここで教えて頂きました。

< コード例 >

selectのプロパティに multiple を追加


 /**
   * 
   * @returns 空港クラス選択セレクトタグ
   * multiple.select
   */
  getClassTag (classes) {

    const CLASSES = ["1", "2", "3", "4", "5"]
    
    let html = `<select id='classsel' class='custom-select' multiple>`
    //html += "<option value='0'>ALL</option>"
    CLASSES.forEach((tmp, j) => {

      const seled = classes.includes(tmp) ? " selected" : ""

      html += `<option value="${tmp}" ${seled}>${tmp}</option>`
    })
    html += "</select>"
    return html

  }

マルチプルセレクトの設定


// マルチプルセレクト by vanillaSelectBox //
    let gpsel = new vanillaSelectBox("#groupsel", {
      maxWidth: 600,
      maxHeight: 600,
      minWidth: -1,
      //selected: classes + ""   <== これは出来ないので先にselectのoptionにセット
    })

選択項目取得は、val()で配列がとれます。


// アドミンユーザーフィルター値配列取得 //
    let filtervals = []
    if (isAdmin) {
      filtervals = $("#groupsel").val()
    }

Tabulator レコード削除

 

 

 

Tabulatorのドキュメントを見てレコード削除の処理を作ってみました。

< コード例 >

カラムスの左端にチェックボックスの列を配置


"rowSelection",
          hozAlign: "center", headerSort: false,
          cellClick: function (e, cell) {
            cell.getRow().toggleSelect()
          }
        },

タイトル行に+アイコンと-アイコンを配置


  // タイトル行HTML 編集ボタン //
  const TITLE_HTML = `${userGroup} Booked flight records X <span id="reccnt"></span>

    <span id="savearow" class="float-right mr-2 curpo tippyspan editbutton dsp_none" 
    data-toggle="tooltip" data-placement="bottom"
    title="Save a row">
    <img src="Img/save_64_darkgray.png" width="20"/>
    </span>

    <span id="cancelarow" class="float-right mr-2 curpo tippyspan editbutton dsp_none" 
    data-toggle="tooltip" data-placement="bottom"
    title="Cancel edit">
    <img src="Img/batsubutton_64_darkgray.png" width="20"/>    
    </span>

    <span id="addarow" class="float-right mr-2 curpo editbutton tippyspan" 
    data-toggle="tooltip" data-placement="bottom"
    title="Add a row">
    <i class="fa fa-plus"></i></span>

    <span id="delarow" class="float-right mr-2 curpo editbutton tippyspan" 
    data-toggle="tooltip" data-placement="bottom"
    title="Delete a row">
    <i class="fa fa-minus"></i>
    `

  let isAddingNow = false

  // 列 //
  let COLUMNS = [
    {
      // テーブルタイトル //
      field: "",
      title: TITLE_HTML,

      columns: [
      // == 以下省略 == //

削除処理
サーバーで削除後、即検索してテーブルを更新してますので、実はTabulatorでの行削除はありません。


  // @@ テーブルタイトル右側 -アイコンを押した時 @@ //
  $(document).on("click", "#delarow", function (e) {
    //alert ("minus button clicked")

    // チェックを入れてる行 //
    const selectedData = table.getSelectedData()
    console.log(selectedData)
    
    // チェックを入れてる行のテーブルID //
    const delIds = []
    selectedData.forEach((row) => {
      delIds.push(row.ID_BOOK)
    })

    // チェックしてる行がある場合 //
    if (delIds.length > 0) {

      // ここに確認を入れる //
      $("#modal_delcfg_body").text("Are you sure you want to delete ??")

      // 削除確認 //
      $("#modal_delcfg").modal("show")

      $(".delexec").off("click")

      // @@ OKボタンを押した時 @@ //
      $(".delexec").on("click", function (e) {

        // -C 削除実行 //
        const djax = new MyAjax("update/DeleteBookRecordsByIds.php", {ids: delIds + ""})
        const dres = djax.exec()
        if (dres["result"] === "Success") {

          // 処理結果通知 //
          const msg = `${dres["affected"]} records(s) was deleted.`
          toastr.success(msg)

          // テーブル更新 //
          // 検索ボタンクリックを発生 //
          $("#search_button").trigger("click")

        }
        else {
          toastr.error("Update error occured !!")
        }

      })
    }
    else {
      toastr.warning("No row selected. Nothing will be done.")
    }

  })

サーバー側

<?php
/**
 * aviation BookedList マルチ行データ削除
 * DeleteBookRecordsByIds.php
 *
 * 2023-01-06 tanulator aviation用
 * 
 */

header("Access-Control-Allow-Origin:*");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header("Content-type: application/json");

ini_set('default_charset', 'UTF-8');
ini_set('display_errors', 0);
//ini_set("memory_limit", "200M");
ini_set("memory_limit", "-1");

date_default_timezone_set('Asia/Tokyo');

/* * **************************************************** */

$SQL = <<<EOM
DELETE FROM h_book
WHERE ID IN (##IDS##)
EOM;

// == リクエストパラメータ == //
$ids = $_POST["ids"] ? $_POST["ids"] : $_GET["ids"];

$res = array();
try {

    // 接続定義 //
    $connect_db = "mysql:dbname=aviation;host=localhost;charset=utf8;";
    $connect_user = '??????';
    $connect_passwd = '??????????';

    //データベース接続 //
    $dbh = new PDO(
        $connect_db,
        $connect_user,
        $connect_passwd
    );

    $idstxt = str_replace(",", "','", $ids);
    $idstxt = "'" . $idstxt . "'";

    $sql = str_replace("##IDS##", $idstxt, $SQL);
    //echo $sql;

    // 削除実行 //
    $delcnt = $dbh->exec($sql);

    $res["result"] = "Success";
    $res["affected"] = $delcnt;

} catch (PDOException $e) {
    //var_dump($e->getMessage());
    $res["result"] = "Fail";
    $res["affected"] = -1;
}

// 切断
$dbh = null;

echo json_encode($res, JSON_NUMERIC_CHECK);

?>

< 画面例 >

Tabulator フィルター

 

 

 

 

 

ログインユーザーの登録レコードを表示するテーブルで、アドミンユーザーの場合、全グループのデータが表示されるので、Tabulatorのフィルターを使ってユーザー別にフィルターできるようにしました。

< コード例 >

アドミンユーザーの場合、マルチセレクトでグループを選べるセレクトタグを配置します。


// アドミンユーザーグループ名フィルター用 //
  if (isAdmin) {

    const uajax = new MyAjax("include/GetGroupNames.php", {})
    const groups = uajax.exec()
    
    // マルチセレクトタグ //
    const st = new SelectTag()
    const html = st.getGroups(groups, groups)
    //console.log(html)

    $(".groupsel").removeClass("dsp_none")
    $("#group_select").html(html)
    // マルチプルセレクト by vanillaSelectBox //
    let gpsel = new vanillaSelectBox("#groupsel", {
      maxWidth: 600,
      maxHeight: 600,
      minWidth: -1,
      //selected: cntrys + ""   <== これは出来ないので先にselectのoptionにセット
    })


  }

検索処理後、Tabulatorのフィルターを使って選んだグループ名で絞込みます。


    // アドミンユーザーフィルター値配列取得 //
    let filtervals = []
    if (isAdmin) {
      filtervals = $("#groupsel").val()
      //alert(filtervals)
    }


    // -C ブックリスト検索 (グループ名は指定しない) //
    const reqUserGroup = userGroup === "Admin" ? "%%" : userGroup
    const initajax = new MyAjax("include/GetBookList.php", {group: reqUserGroup, mindt: mindt, maxdt: maxdt})
    const seracheddata = initajax.exec()["data"]
    console.log(seracheddata)
    // グリッドの更新 //
    table.setData(seracheddata) // localなのでURLは要らぬ
    
    // アドミンユーザーフィルターの実行 //
    if (isAdmin) {
      table.setFilter("NM_GROUP", "in", filtervals);
    }
    // タイトルのレコード数  //
    $("#reccnt").text(table.getDataCount("active"))

< 画面例 >

初期設定 全グループ
グループ指定

Tabulator インライン編集と更新

先日まで、主にリードオンリーのデータを扱ってましたが、編集、更新が必要なデータを扱う必要があってドキュメントを見ながら、作ってみました。

< コード例・日付 >

カラムの設定で、editorとcellEditedを記述


 // 出発日 //
{
  title: "Dep Date",
  field: "DT_DEP",
  width: 90,
  hozAlign: "center", editor: "date", editorParams: {
    mask: "yyyy-MM-dd", // ????? 
    min: "2023-01-01", // the minimum allowed value for the date picker
    max: "2030-12-31", // the maximum allowed value for the date picker
    format: "yyyy-MM-dd", // the format of the date value stored in the cell
    elementAttributes: {
      title: "slide bar to choose option" // custom tooltip
    }
  },
  cellEdited: function (cell) {
    //cell - cell component
    
    // 追加の場合は何もしない //
    if (isAddingNow) {
      return false
    }

    const inpedData = cell.getData()
    const inpedDataJSON = JSON.stringify(cell.getData())

    // 行データ //
    console.log("--- inpedDataJSON (row)")
    console.log(inpedDataJSON)

    // データ列名 field //
    const fieldName = cell.getField()
    console.log("--- cell.getField()")
    console.log(fieldName)

    // cellの値 //
    const cellval = cell.getData()[fieldName]
    console.log("--- cellval")
    console.log(cellval)

    // テーブルのid //
    const tableId = cell.getData()["ID_BOOK"]


    //alert(inpedData + " に変わったよ\n" + inpedDataJSON)
    // サーバーに送信して保存する 他の列でも編集があるので、関数で行う //
    const updtres = postACellData(fieldName, cellval, tableId)
    if (updtres === "Success") {
      toastr.success("Date was updated")
    }
    else {
      toastr.error("Update error occured !!")
    }
  }
},

/**
   * セルフィールド値更新
   * @param {type} fieldname
   * @param {type} value
   * @param {type} id
   * @returns {.ajax@call;exec.result|res.result}
   */
  const postACellData = (fieldname, value, id) => {

    // -C セル値更新リクエスト
    const ajax = new MyAjax("update/UpdateABookField.php", {
      field: fieldname,
      value: value,
      id: id
    })
    const res = ajax.exec()
    return res["result"]

  }

サーバー側

<?php

/**
 * aviation BookedList セル値更新
 * UpdateABookField.php
 *
 * 2023-01-06 tanulator aviation用
 * 
 */

header("Access-Control-Allow-Origin:*");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header("Content-type: application/json");

ini_set('default_charset', 'UTF-8');
ini_set('display_errors', 0);
//ini_set("memory_limit", "200M");
ini_set("memory_limit", "-1");

date_default_timezone_set('Asia/Tokyo');

/* * **************************************************** */

$SQL = <<<EOM
UPDATE h_book
SET ##FIELD## = :VALUE
WHERE ID = :ID
EOM;

// == リクエストパラメータ == //
$field = $_POST["field"] ? $_POST["field"] : $_GET["field"];
$value = $_POST["value"] ? $_POST["value"] : $_GET["value"];
$id = $_POST["id"] ? $_POST["id"] : $_GET["id"];

if (!$field) {
    $field = "DT_DEP"; 
}
if (!$value) {
    $value = "2023-01-01"; 
}
if (!$id) {
    $id = 296; 
}

/*echo $field."\n";
echo $value."\n";
echo $id."\n";*/

$res = array();
try {

    // 接続定義 //
    $connect_db = "mysql:dbname=aviation;host=localhost;charset=utf8;";
    $connect_user = '????';
    $connect_passwd = '????????';

    //データベース接続 //
    $dbh = new PDO(
        $connect_db,
        $connect_user,
        $connect_passwd
    );

    $sql = str_replace("##FIELD##", $field, $SQL);
    //echo $sql;

    // パラメータセット //
    $stmt = $dbh->prepare($sql);
    //$stmt->bindvalue(':FIELD', $field, PDO::PARAM_STR);
    $stmt->bindvalue(':VALUE', $value, PDO::PARAM_STR);
    $stmt->bindvalue(':ID', $id, PDO::PARAM_INT);
    $stmt->execute();

    $res["result"] = "Success";

} catch (PDOException $e) {
    //var_dump($e->getMessage());
    $res["result"] = "Fail";
}

// 切断
$dbh = null;

echo json_encode($res);

?>

< 画面例 >

Tabulator でツールチップ

 

 

 

Tabulatorのツールチップ表示につきましては、最初よくドキュメントを読まないで試した時、エラーになったりして表示できなく、それがプチとらうまとなって、DOMエレメント操作でtippy.jsのそれを使ってました。
ところ、今度はtippyが動作しなくなったので、Tabulatorのドキュメントを読み直して試したところできましたので備忘録しておきます。

< コード例 >


// オペレータロゴ //
{
    title: "|#",
    field: "opeflag",
    width: 26,
    hozAlign: "center",
    formatter: function (cell, formatterParams, onRendered) {

        const ope = cell.getRow().getData().NO_FLIGHT.slice(0, 2).toLowerCase()
        //console .log(ope)
        
        // 非可視にした他の列の値を参照 //
        const opename = cell.getRow().getData().NM_AIRLINE
        const icao = cell.getRow().getData().CD_ICAO
        const callsign = cell.getRow().getData().NM_CALLSIGN
        
        // 他の列の値を画像の親スパンの属性にセットする //
        const img = `<span iata="${ope.toUpperCase()}"
        opename="${opename}" icao="${icao}" callsign="${callsign}">
        <img src="Img/airline/airline_${ope}_square.png" width="20"
        onerror="this.style.display='none'"/>
        </span>`
        return img
    },
    tooltip: true,      // trueにしてツールチップを表示可能にする
    
    // ツールチップが見える時 //
    tooltip: function (e, cell, onRendered) {
        //e - mouseover event
        //cell - cell component
        //onRendered - onRendered callback registration function
        
        // エレメントを生成 //
        var el = document.createElement("div")
        el.style.backgroundColor = "black"
        el.style.color = "white"
        
        // formatterでセットした属性 //
        const cellelm = cell.getElement()
        const opename = $(cellelm).children("span").attr("opename")
        const icao = $(cellelm).children("span").attr("icao")
        const callsign = $(cellelm).children("span").attr("callsign")
        
        // HTMLにしてリターン //
        $(el).html(`${icao} : ${callsign}<br>${opename}`)

        return el
    },

},

< 画面例 >

フライト番号からIATAがわかるので、ICAOとコールサインとエアライン名をポップアップ

 

 

 

 

 

FlightradarのAPiでとれたカレントステータスをポップアップ

糸川英夫・老化

人間は老化する動物だ。
だから絶えず過去の出来事や覚えた知識を反復、反芻することが大切。
それがイメージを膨らませ、年老いてもクリエーティブな仕事をする源。

糸川 英夫の名言