Tabulator topCalc ビルトイン関数編

 

 

 

TabulatorのtopCalcプロパティを使えば集計値の表示が出来ます。
ビルトインで用意されたプロパティを使ってみました。
公式ドキュメントはこちら
sum, avg, count, maximum, minmum, concatenate が使えます。、

< コード例 >

sumとcount


        // 機材数 //
        {
          topCalc: "sum",
          title: "Fleet",
          field: "fleet_size",
          hozAlign: "right",
          width: 46,
          formatterParams: {thousand: ","},
          formatter: function (cell, formatterParams, onRendered) {
            if (cell.getValue() <= 0) {
              return ""
            }
            else {
              return numeral(cell.getValue()).format("#,###")
            }
          },
          //width: 50,
          sorter: "number",
          cssClass: "cell_num_onlytext_navy"
        },
        
        // 旅客貨物定時国際タイプ記号列挙 //
        { topCalc: "count",
          title: "Type", field: "type", hozAlign: "center", width: 50
        },

< 画面例 >

Tabulator topCalc カスタム自作関数編

 

 

 

Tabulatorで集計行の表示が、topCalc で簡単に出来て使ってましたが、ビルトインのプロパティにない集計が必要になり作ってみました。
公式ドキュメントはこちら

Group byした結果のアイテム数
メジアン
0や空白を除外した集計
などです。

< コード例 >
実は別関数にしたところ、うまく動かないので、カラムのプロパティに記述してます。

0以下除外平均


topCalc: function (values, dtoata, calcParams) {

  // 0 (データは-1)を除外した平均 //
  let cnt = 0
  let sum = 0
  values.forEach(function (value) {
  
    /*console.log("--- value")
     console.log(value)
     console.log("--- data")
     console.log(data)*/
  
    let val = value === -1 ? 0 : value
  
    if (val > 0) {
      cnt++;
      sum = Number(sum) + Number(val)
    }
  })
  //alert(sum + ":" + cnt)
  
  if (sum > 0 && cnt > 0) {
    const res = Math.round(sum / cnt * 10) / 10
    return res;
  }
  else {
    return 0
  }

}

0以下除外メジアン


topCalc: function (values, data, calcParams) {

  // 0 -1を除外したメジアン //
  let targets = []
  values.forEach((value) => {

    console.log(value)
    //let val = value.replaceAll(",", "")

    let val = value < 0 ? 0 : value
    val = parseInt(val)

    if (val > 0) {
      targets.push(val)
    }
  })
  console.log(targets)

  const half = Math.floor(targets.length / 2);

  if (targets.length % 2) {
    return targets[half];
  }
  else {
    return (targets[half - 1] + targets[half]) / 2;
  }

}

Group by アイテム数


topCalc: function (values, data, calcParams) {

  let manuCnts = {}
  values.forEach((tmp) => {

    manuCnts[tmp] = (manuCnts[tmp] || 0) + 1;

  })
  console.log("--- manufac")
  console.log(manuCnts)

  return Object.keys(manuCnts).length + " Manufactures"
}

< 画面例 >

Tabulator addRow()で行を追加した時の位置が上と下があるので

Tabulator addRow()で行を追加した時、原因はわかりませんが、既存の表の一番上に挿入されたり、一番下に追加されたりするので、戸惑いそうなので、上下線を入れて強調させてみました。

< コード例 >
CSS


.border-bottom-red1 {
    border-bottom: solid 1px red
}

.border-bottom-red2 {
    border-bottom: solid 2px red
}
.border-top-red1 {
    border-top: solid 1px red
}

.border-top-red2 {
    border-top: solid 2px red
}

Javascript


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

    // table行追加 //
    table.addRow({NM_GROUP: userGroup, DT_DEP: TODAY_FORMAT, ID_USER: userId, NO_FLIGHT: "NH____",
      IS_NOTIFY: 0, DEP_IATA: "", ARV_IATA: ""}, true)
    //table.selectRow([-1]);

    $("#savearow").removeClass("dsp_none")
    $("#savearow").show()
    $("#cancelarow").removeClass("dsp_none")
    $("#cancelarow").show()
    
    // 行を強調 //
    // NH____の行を特定 //
    const rows = table.getRows()
    rows.forEach((row) => {
      const noflight = row.getData().NO_FLIGHT
      if (noflight === "NH____") {
        
        const elm = row.getElement()
        $(elm).addClass("border-bottom-red1")
        $(elm).addClass("border-top-red1")
        return
      }
    })

    // == 以下省略 == //


  })

< 画面例 >

Tabulator コンテキストメニュー

ここで教えていただきました。

< コード例 >

HTML

        <!-- 空港テーブル本体 -->
        <div id="aptable" class="main_table"></div>
        <!-- コンテキストメニュー -->
        <div id="aptable_contextmenu" class="contextmenu-wrapper" style="display: none;">
            <div class="contextmenu-contents apcntxtdiv">
              
                <!-- 出発ライブ -->
                <ul id="cntxt_livemap_ul_arv" class="curpo apcntxt">
                    <img id="cntxt_livemap_icon_arv" src="Img/arrival_48_green.png" width="18" />
                    <span id="cntxt_livemap_arv">Show arrival LiveMap</span>
                </ul>
                <!-- 到着ライブ -->
                <ul id="cntxt_livemap_ul_dep" class="curpo apcntxt">
                    <img id="cntxt_livemap_icon_dep" src="Img/departure_48_blue.png" width="18" />
                    <span id="cntxt_livemap_dep">Show departure LiveMap</span>
                </ul>
                
                <!-- 出発ステータスリンク -->
                <ul id="cntxt_status_ul_arv" class="curpo apcntxt">
                    <img id="cntxt_status_icon_arv" src="Img/arrival_48_green.png" width="18" />
                    <span id="cntxt_status_arv">Open arrival status</span>
                </ul>
                <!-- 到着ステータスリンク -->
                <ul id="cntxt_status_ul_dep" class="curpo apcntxt">
                    <img id="cntxt_status_icon_dep" src="Img/departure_48_blue.png" width="18" />
                    <span id="cntxt_status_dep">Open departure status</span>
                </ul>
                
                
                <!-- SkyVector -->
                <ul id="cntxt_skyvec_ul" class="curpo apcntxt">
                    <img id="cntxt_skyvec_icon" src="Img/skyvector.png" width="18" />
                    <span id="cntxt_skyvec">Open SkyVector</span>
                </ul>
                <!-- Airnav -->
                <ul id="cntxt_airnav_ul" class="curpo apcntxt">
                    <img id="cntxt_airnav_icon" src="Img/airnav.png" width="18" />
                    <span id="cntxt_airnav">Open AirNav</span>
                </ul>
                 <!-- FlightPlan -->
                 <ul id="cntxt_fplan_ul" class="curpo apcntxt">
                    <img id="cntxt_fplan_icon" src="Img/flightplan_icon.png" width="18" />
                    <span id="cntxt_fplan">Open FlightPlan</span>
                </ul>
                 <!-- LiveATC -->
                 <!--<ul id="cntxt_liveatc_ul" class="curpo apcntxt">
                    <img id="cntxt_liveatc_icon" src="Img/liveatc.png" width="18" />
                    <span id="cntxt_liveatc">Open LiveATC</span>
                </ul>-->

            </div>
        </div>

カラムのセルコンテキストイベントから自作メニューを呼ぶ


    // 行番号 //
    {
      title: 'No.',
      field: 'rowno',
      formatter: 'rownum',
      frozen: true,
      width: 16,
      hozAlign: 'right',
      cssClass: 'cell_num',
      cellContext: function (event, cell) {
        //event - the click event object
        //cell - cell component

        const row = cell.getRow()

        $(".apcntxtdiv").show()

        // 通常の右クリックメニューを停止 //
        event.preventDefault()
        // 代わりに自作のメニューを表示 //
        showNoContextMenu(row, event.pageX, event.pageY)
      }
    },

自作メニュー


  /**
   *  No列コンテキストメニュー
   * @param {type} row 行オブジェクト
   * @param {type} posx ページX位置
   * @param {type} posy ページY位置
   * @returns {undefined}
   */
  const showNoContextMenu = (row, posx, posy) => {

    const cm = document.getElementById("aptable_contextmenu")
    
    // 行のデータ //
    const icao = row.getData().cdIcao
    const iata = row.getData().cdIata
    const city = row.getData().nmCity
    const apname = row.getData().nmAirport

    // 非表示をやめて、クリックした位置に表示する
    cm.style.left = (eval(posx) + eval(20)) + 'px'
    cm.style.top = (eval(posy) + eval(10)) + 'px'
    cm.style.display = ''

    // メニューのテキストを列の値で変更 //
    $("#cntxt_livemap_arv").text(iata + " Show arrival LiveMap")
    $("#cntxt_livemap_dep").text(iata + " Show departure LiveMap")

    $("#cntxt_status_arv").text(iata + " Open arrival status")
    $("#cntxt_status_dep").text(iata + " Open departure status")

    $("#cntxt_skyvec").text(icao + " Open SkyVector")
    $("#cntxt_airnav").text(icao + " Open AirNav")
    $("#cntxt_fplan").text(icao + " Open FlightPlan")
    $("#cntxt_liveatc").text(icao + " Open LiveATC")


    $(".apcntxt").show()

    $(".apcntxt").off("click")

    // @@ アイテムをクリックした時 @@ //
    $(".apcntxt").on("click", function (e) {

      cm.style.display = 'none'

      const id = $(this).attr("id")

      // == id別の処理 省略 == //
      


    })

  }

< 画面例 >

Tabulator テーブルの行IDを使えるようにするため

Tabulator で選んだ行などに対して処理を行いたい場合、一意に識別したい場合のIdの使い方です。

詳しくはこちら

< コード例 >

カラムに非表示のID列をセット

// 行ID //
{field: "id", visible: false},

行選択

// テーブル行選択 //
table.selectRow(0) //select row with id of 0

行Id取得

// 行ID取得 //
const rowidx = row.getIndex()

サーバー側

  // パラメータセット //
  $stmt = $dbh->prepare($SQL);
  $stmt->bindvalue(':MINDT', $mindt, PDO::PARAM_STR);
  $stmt->bindvalue(':MAXDT', $maxdt, PDO::PARAM_STR);
  $stmt->bindvalue(':GROUP', $group, PDO::PARAM_STR);
  $stmt->execute();

  $response = new stdClass();

  // 結果格納 //
  $i = 0;
  while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {

    $response->data[$i]["ID_BOOK"] = $row["ID_BOOK"];
    $response->data[$i]["NM_GROUP"] = $row["NM_GROUP"];
    
    // Tabulator行ID //
    $response->data[$i]["id"] = $i;

    // == 省略 == //

    $i++;
  }
} catch (PDOException $e) {
  var_dump($e->getMessage());
}

echo json_encode($response, JSON_NUMERIC_CHECK);


Tabulator topCalc 集計行

 

 

 

Tabulatorで集計行を配置してみました。
カラム設定の公式ドキュメントはこちら
Column Calcuculationのドキュメントはこちら

< コード例 >

カラムのプロパティのtopCalcで合計、平均、件数とかをセットします。


// 飛行中数 //
  {
    title: "Active",
    field: "active_count",
    width: 40,
    hozAlign: "right",
    topCalc: "sum",
    formatter: function (cell, formatterParams, onRendered) {
      //console.log(cell.getValue())

      if (cell.getValue() <= 0) {
        return ""
      }
      else {
        return cell.getValue()
      }
    }
  },
  {field: "id", visible: false}
]

< 画面例 >

件数列と合計列を上に配置

Tabulator tickCross のチェックボックス

 

 

 

TabulatorでtickCrossのチェックボックスを使ってみました。

ドキュメントはここここ
ここでも教えて頂きました。

< コード例 >

状態が変化した時、即サーバーに送信して保存しています。

カラムの設定


// 通知設定チェック //
{
  title: "N",
  field: "IS_NOTIFY",
  width: 20,
  hozAlign: "center",
  formatter: "tickCross",
  editor: "tickCross", editorParams: {
    trueValue: 1,     // Onの値
    falseValue: 0,    // Offの値
    tristate: false,  // 中立状態なし
    indeterminateValue: "n/a",
    elementAttributes: {
      maxlength: "10", //set the maximum character length of the input element to 10 characters
    }
  },

  cellEdited: function (cell) {
    //cell - cell component
    
    
    // 追加中の場合、何もしない //
    if (isAddingNow) {
      return false
    }
    
    // データ列名 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"]

    // サーバーに送信して保存する 他の列と共通の関数を使う //
    const updtres = postACellData(fieldName, cellval, tableId)
    
    if (updtres === "Success") {
      toastr.success("Notify check 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) => {
    
    console.log(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

/**
 * Tabulator セル値更新
 * 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,
  TM_UPDT = CURRENT_TIMESTAMP      
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; 
}

$res = array();
try {

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

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

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

    // パラメータセット //
    $stmt = $dbh->prepare($sql);
    $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 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行目をカラムヘッダーにしてくれます。

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);

?>

< 画面例 >