Tabulator5.6 RangeSelect を使ってExcelのようなことをする

T

 

 

abbulatorのドキュメントを見てたところ、Ver5.6にアップデートされてるのがわかり、有償のテーブルでしか出来なかったExcelのようなレンジセレクトができることがわかり、早速使ってみました。

ドキュメントはこちらにあります。

< コード例 >

イベントも沢山あっていろいろカスタマイズできるようですが、とりあえずは以下のようなプロパティを追加すれば動きました。


    // ++ Tabulator ++ //
    const table = new Tabulator("#table_" + this.tp, {
    	
      selectableRange: true,
      selectableRangeColumns: true,
      selectableRangeRows: true,
     clipboardCopyRowRange:"range", //change default selector to selected

< 画面例 >

Shift, Ctrl も使えます。
Ctrl + C でクリップボードに選んだセルがコピー出来ました。

矩形選択

 

 

 

 

 

 

 

Ctrl + Cの後のクリップボードを貼り付けてみる

 

 

 

 

左端の行から選ぶと行選択になります

こんなことも出来ました。

Tabulator 通し行番号取得

 

 

 

TabulatorのFormatterのrownumで行番号が取得できますが、ページを変えると1に戻ってしまうので自作してみました。

< コード例 >

Formatter


// 行番号 //
        {
          // 各表の //
          title: "No.",
          field: "__",
          //frozen: true,
          width: 16,
          hozAlign: "right",
          cssClass: "cell_num",
          //formatter: "rownum",
          formatter: function (cell, formatterParams, onRendered) {

            // 反転させて強調 //
            const cellelm = cell.getElement()
            $(cellelm).on("mouseover", function (e) {
              $(this).addClass("text_reverse")

            })
            $(cellelm).on("mouseout", function (e) {
              $(this).removeClass("text_reverse")
            })

            // 通し行番号 //
            return new TableUtil().getRowNum(cell)


          },

テーブルユーティリティクラス


class TableUtil {
  
  // クリップボード設定 //
  static
          clipboardCopyConfig = {
            columnHeaders: true, //do not include column headers in clipboard output
            columnGroups: false, //do not include column groups in column headers for printed table
            rowGroups: false, //do not include row groups in clipboard output
            columnCalcs: true, //do not include column calculation rows in clipboard output
            dataTree: false, //do not include data tree in printed table
            formatCells: false //show raw cell values without formatter
          }
  
  /**
   * コンストラクタ
   * @returns {TableUtil}
   */
  constructor() {

  }

  /**
   * 通し行番取得
   * @param {type} cell
   * @returns {Number}
   */
  getRowNum(cell) {

    const tbl = cell.getTable()
    const page = tbl.getPage()
    const pageSize = tbl.getPageSize()
    //console.log("# page # " + page + ":" + pageSize)

    let num = (page - 1) * pageSize
    num += cell.getRow().getPosition(true)

    return num
  }

Javascript Fetch API を使う (ファイルアップロードとPOST)

< コード例 >

HTML


        <form id="photoupload_uploadform" enctype=”multipart/form-data”>
          <table>
            <tr>
              <td valign="top" class="td_title">Book ID</td>
              <td valign="top" class="td_input"><span id="photoupload_bookid"></td>
            </tr>
            
            <!--
            <tr>
              <td valign="top" class="td_title">Dep date</td>
              <td valign="top" class="td_input"><input id="photoinp_depdate" type="text" size="10" max="10" value=""></td>
            </tr>
            <tr>
              <td valign="top" class="td_title">Flight No.</td>
              <td valign="top" class="td_input"><input id="photoinp_flnum" type="text" size=12" max="12" value=""></td>
              </td> 
            </tr>
            -->

            <tr>
              <td valign="top" class="td_title">Reg No.</td>
              <td valign="top" class="td_input"><input id="photoinp_regno" type="text" size="12" max="12" value=""></td>
            </tr>

            <tr>
              <td valign="top" class="td_title">Airport</td>
              <td valign="top" class="td_input">
                <select id="photoupload_iatasel">
                  <option id="iatasel_from" value="from" selected></option>
                  <option id="iatasel_to" value="to"></option>
                </select>
                
                <span id="selapname" class="ml-1">..</span>
              </td>
               
            </tr>

            <tr>
              <td valign="top" class="td_title">Publish</td>
              <td valign="top" class="td_input">
                <select id="photoupload_publishsel">
                  <option value="yes" selected>Yes</option>
                  <option value="no">No</option>
                </select>
              </td>
              </td> 
            </tr>



            <tr>
              <td valign="top" class="td_title">Select</td>
              <td class="td_input" valign="top">
                <input id="photoupload_fileinput" type="file" accept="image/*" id="userfile" name="userfile">
              </td>  
            </tr>  

          </table>
        </form>  

Javascript


      // ここに確認を入れる //

      $("#marker_loading_booklist").removeClass("dsp_none")
      $("#marker_loading_booklist").show()
      
      // フォームの値を連想配列に入れる //
      const array = {
        userid: userid,
        bookid: $("#photoupload_bookid").text(),
        depdate: $("#photoinp_depdate").val(),
        flnum: $("#photoinp_flnum").val(),
        regno: $("#photoinp_regno").val(),
        airport: $("#photoupload_iatasel").val(),
        publish: $("#photoupload_publishsel").val()
      }

      const fd = new FormData($('#photoupload_uploadform').get(0));
      fd.append('json', JSON.stringify(array));

      // Fetch API //
      fetch("upload/UploadAPhoto.php",
              {
                method: "POST", body: fd
              }
      )
              // ** 応答時 ** // 
              .then((data) => data.text())
              // ** 応答後 ** //
              .then((res) => {


                $("#marker_loading_booklist").addClass("dsp_none")
                $("#marker_loading_booklist").hide()
                
                const array = JSON.parse(res)
                // 正常完了の場合 //
                if (array["result"] === "Success") {
                  toastr.success("Photo upload was completed")
                }
                // エラーの場合 //
                else {
                  toastr.error("Photo upload was FAILED.")
                }
                
                // 写真表示更新 //
                new BookListUserPhoto(rowdataThis).showPhoto()

              })


    })

Javascript Fetch API を使う (POST)

最近使い始めた Fetch APIでPOSTを試してみました。

< コード例 >
HTML


      <!-- 検索条件メニュー -->
      <div id="menudiv" class="container float-left ml-0 badge-dark rounded">

        <form id="formdiv" class="form-inline form-group ml-0 mr-1 mt-1 mb-1 p-1">

          <!-- 日付範囲選択 -->
          <div class="pl-0 pr-1"></div>
          <input type="text" id="mindtinput" value="" size="10" class="mr-1 form-control" />
          ~
          <input type="text" id="maxdtinput" value="" size="10" class="ml-1 mr-1 form-control" />

          <!-- 検索ボタン -->
          <button id="search_button" type="button" 
                  class="btn btn-primary  ml-1 config_button tippyspan" 
                  data-toggle="tooltip" data-placement="bottom" 
                  title="Execute search">
            <i class="fa fa-search"></i></button>


          <!-- ページネーション -->
          <div id="top_pagenator" class="ml-2 mt-0 pt-0">
            <span id="topp_first" class="mr-1 curpo topps tippyspan" title="Move to first page"><i class="fa fa-angle-double-left"></i></span>
            <span id="topp_prev" class="mr-1 curpo topps tippyspan" title="Move to previous page"><i class="fa fa-angle-left"></i></span>
            <span id="topp_next" class="mr-1 curpo topps tippyspan" title="Move to next page"><i class="fa fa-angle-right"></i></span>
            <span id="topp_last" class="mr-1 curpo topps tippyspan" title="Move to last page"><i class="fa fa-angle-double-right"></i></span>

            <span id="topp_position" class=""></span>
          </div>

          <span id="reccnt" class="ml-3 mr-1"></span> Records

        </form>

      </div>

Javascript



      // moment.jsで前日を取得 //
      const mm = moment()
      mm.add(BFCNT[tpThis] * -1, "day")
      const mindt = mm.format('YYYY-MM-DD')

      // inutに日付をセット //
      $("#mindtinput").val(mindt)
      $("#maxdtinput").val(TODAY_FORMAT)

      // フォームデータを作成 //
      const fd = new FormData();
      fd.set('tp', tpThis);
      fd.set('stdt', mindt);
      fd.set('endt', TODAY_FORMAT);

      // Fetch API //
      fetch("include/SelectLogStc.php",
              {
                method: "POST", body: fd
              }
      )
              // ** 応答時 ** //
              .then((data) => data.text())
              // ** 応答後 ** //
              .then((res) => {

                // JSON文字列を配列にする //
                const data = JSON.parse(res)
                console.log("-- data")
                console.log(data)
                
                // ** 応答後の処理をここに書く ** //

              })

< 画面例 >

Fetch APIでリクエストした応答結果をTabulatorのテーブルで表示

Javascript Fetch API を使う (GET)

最近使い始めました。

ここここで教えて頂きました。

最近まで、JQueryのAjaxから自サイトのPHPにリクエストして取得するのが普通でしたが、APIにキーが必要ない場合、隠蔽するものがないので、PHPを介する必要なしなので便利で少ないコードで済むのがメリットかと思います。

次はPOSTを試してみます。

< コード例 >


      // PlaneSpotter写真問い合わせ //
      const url = "https://api.planespotters.net/pub/photos/hex/" + acmodeS

      // fetch API //
      fetch(url)
              .then((data) => data.text())
              .then((res) => {
                console.log("-- PLANESPOTTERS")
                console.log(res)
                
                // 取得出来たら写真を表示 //
                if (res) {
                  const array = JSON.parse(res)["photos"][0]

                  $("#photo_bookside_img").attr("src", array["thumbnail_large"]["src"])
                  $("#photo_bookside_a").attr("href", array["link"])
                  $("#photo_bookside_figcap").text("@ " + array["photographer"])

                  $("#photo_bookside").show()
                }
                else {
                  $("#photo_bookside").hide()
                }

              })

Tabulator 列数を動的に変える

 

 

 

画像のサイズや列数を変えてTabulatorでサムネイル表示させる為、列数、列幅を動的設定してみました。

< コード例 >

ローカルストレージにあるユーザー設定読込


// == ユーザー設定読込 == //
    const envmap = new ConfigPhoto().readLSMap()
    const w = envmap["thumbwidth"]    // 列幅
    const colcnt = envmap["colcnt"]   // 列数

// サムネイル列 //
let COLUMNS = [
  {title: `検索結果 X <span id="cnt_photo"></span>
           `,
    columns: []
  }
]

// ++ 設定列数分の列を動的追加 ++ //
for (let j = 0; j < colcnt; j++) {

  // 画像列 //
  const coltmp = {
    title: "", field: "COL" + j, width: w, hozAlign: "center",
    formatter: cellFormat, contextMenu: cellContextMenu
  }
  COLUMNS[0]["columns"].push(coltmp);

  // 非表示列 //
  COLUMNS[0]["columns"].push({field: "COL" + j + "_COL_AVG", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_URL", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_PHOTOID", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_TITLE", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_PHOTOGRAPHER", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_POS", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_IDX", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_CATEG_KANJI", visible: false})
  COLUMNS[0]["columns"].push({field: "COL" + j + "_URL_ORIGINAL", visible: false})

}

// 列設定 //
const tablerows = this.getArrangedColumnsDataColCnt(data, colcnt)

// ++ Tabulator ++ //
let table = new Tabulator("#table_photos", {
  columns: COLUMNS,
  data: tablerows,
  //placeholder: '見つかりませんでした',

  //layout: 'fitColumns',
  //layout:"fitData",
  layout: "fitDataTable",

  rowHeight: Math.round(w * 3 / 4),
  headerVisible: false

})

列設定メソッド


  /**
   * テーブル用サムネイル列設定
   * 列数指定用 使用
   * 
   * @param {type} data 応答データ
   * @param {type} colcnt 列数
   * @returns {Array|Photos.getArrangedColumnsData.resdatas}
   */
  getArrangedColumnsDataColCnt(data, colcnt) {

// == ユーザー設定幅読込 == //
 const w = new ConfigPhoto().readLSMap()["thumbwidth"]

  let resdatas = []
  let row = {}
  data.forEach((tmp, i) => {

    const mod = i % colcnt;

    row["COL" + mod] = w <= 250 ? tmp["URL_SMALL"] : tmp["URL_MEDIUM"]
    row["COL" + mod + "_COL_AVG"] = tmp["COL_AVG"]
    row["COL" + mod + "_URL"] = tmp["URL"]
    row["COL" + mod + "_PHOTOID"] = tmp["PHOTOID"]
    row["COL" + mod + "_TITLE"] = tmp["TITLE"]
    row["COL" + mod + "_PHOTOGRAPHER"] = tmp["PHOTOGRAPHER"]
    row["COL" + mod + "_POS"] = tmp["POS"]
    row["COL" + mod + "_IDX"] = tmp["IDX"]
    row["COL" + mod + "_NM_CATEG_KANJI"] = tmp["NM_CATEG_KANJI"]
    row["COL" + mod + "_URL_ORIGINAL"] = tmp["URL_ORIGINAL"]

    if (i === data.length - 1) {
      resdatas.push(row)
    }

    else if (mod === colcnt - 1) {
      resdatas.push(row)
      row = {}
    }



  })
  return resdatas

}

< 画面例 >

幅250px 5列

幅100px 12列

幅60px 16列

Tabulator initialSort とソート内容表示

 

 

 

< コード例 >


// ロード時既定ソート列 リクエストパラメータはsortでMapのList //
      initialSort: [
        {column: "NM_FILE", dir: "asc"}, //sort by this first
                //{column:"TM_UPDATE", dir:"desc"}//then sort by this second
      ],

カラムの1行目にspanをセット


<span id="sorttxt" class="ml-2">ソート列:ソート順</span>

ソート後のイベント


  /**
     * ソート表示テキスト
     * @param {type} clms
     * @param {type} sorters
     * @returns {String}
     */
    const sortstsstr = (clms, sorters) => {
      
      let clmmap = {}
      clms.forEach(tmp => clmmap[tmp.field] = tmp.title)
      
      let res = ""
      sorters.forEach(st => 
        res += clmmap[st["field"]] + (st.dir === "asc" ? ":小さい順 " : "大きい順 ")
      )
      return res
      
    }
    
    // @@ ソートが完了した時 @@ //
    table.on("dataSorted", function (sorters, rows) {
      //sorters - array of the sorters currently applied
      //rows - array of row components in their new order
      
   
      // 初回ロード時も呼ばれる //
      $("#sorttxt").text(sortstsstr(COLUMNS[0]["columns"], sorters))
      
      
    });

< 画面例 >

Tabulator AJaxリクエストと表示

 

 

 

Tabulatorのテーブルのデータとして、今まで大容量、多いレコードのデータを読み込む必要がなかった為、Ajaxでリクエストして受信したデータを一旦ローカルの配列にして表示させてましたが、
この度、大量のデータでページ毎に読み込む必要があって試してみました。Tabulatorのドキュメントの通りにしてます。

詳しくはこちら

< コード例 >

Javascriptクライアント


// ++ Tabulator ++ //
    let table = new Tabulator('#table_files', {
      columns: COLUMNS,

      // ロード時既定ソート列 リクエストパラメータはsortでMapのList //
      initialSort: [
        {column: "NM_FILE", dir: "asc"}, //sort by this first
                //{column:"TM_UPDATE", dir:"desc"}//then sort by this second
      ],
      
      // ソートモードはサーバー側 //
      sortMode: "remote",

      ajaxURL: url, //ajax URL
      
      // AJaxのリクエストパラメータ //
      ajaxParams: {
        years: years, folders: folders, exts: exts, fnm: fnm, isin: isin,
        ischart: isChartShow ? "1" : "0"
      },
      ajaxConfig: "POST",
      index: "id",

      ajaxRequesting: function (url, params) {
        return true
      },
	  
      // == レスポンスの処理 == //
      // dataのキーを返す応答 + jqGridのuserDataの処理 //
      ajaxResponse: function (url, params, response) {
        //url - the URL of the request
        //params - the parameters passed with the request
        //response - the JSON object returned in the body of the response.

        console.log(response)

        // === ここにテーブル以外の処理を入れる === //

        return response
      },
      		
      // ==== 省略 ==== //
      		
})

PHPサーバー
データ配列のキーはTabulatorがデフォルトで使う “data”にします。
レスポンスにTabulatorがデフォルトで使う “last_page” を加えます


<?php

// == リクエストパラメータ == //

// Tabulatorが送るページインデックスとレコード数 //
// ページと行数 //
$page = $_POST["page"] ? $_POST["page"] : $_GET["page"];
$size = $_POST["size"] ? $_POST["size"] : $_GET["size"];

// Tabulatorが送るソート列と方向 //
// ソート //
$sorts = $_POST["sort"] ? $_POST["sort"] : $_GET["sort"];

$sort_column = $sorts[0]["field"];
$sort_order = $sorts[0]["dir"];


// レスポンス配列 //
 $resp = array(
      "data" => $response_array_limited,
      "sql" => $sql_files,             // 実行SQL
      "last_page" => $last_page,       // <== Tabulatorが使うデフォルトページ数
      "totalcount" => $totalcount,     // 合計レコード数
      "tmcost" => $entm - $sttm,
      "total_file_size" => $total_file_size,
      
      "sorts" => $sorts
  );

  echo json_encode($resp, JSON_UNESCAPED_UNICODE + JSON_UNESCAPED_SLASHES + JSON_NUMERIC_CHECK + JSON_UNESCAPED_LINE_TERMINATORS);


<?php

// SQL ソートとページング部分 //

ORDER BY
  ##SORT_COLUMN## ##SORT_ORDER##, NM_FOLDER ASC, NM_FILE ASC
LIMIT :STARTIDX, :PAGESIZE  

// ソートはSQL文を置換 //
$sql_files = str_replace("##SORT_COLUMN##", $sort_column, $sql_files);
$sql_files = str_replace("##SORT_ORDER##", $sort_order, $sql_files);

// ページングはパラメータを渡す //

// ページング用 //
$startidx = ($page - 1) * $size;
$endidx = $startidx + $size - 1;
$last_page = (int) ($totalcount / $size) + 1;

// 明細 パラメータセット 実行 //
$stmt = $dbh->prepare($sql_files);
$stmt->bindvalue(':STARTIDX', $param_startidx, PDO::PARAM_INT);
$stmt->bindvalue(':PAGESIZE', $param_size, PDO::PARAM_INT);
$stmt->execute();

chart.js 再描画でのエラーを回避


ここで教えて頂きました。

< コード例 >

チャートは4個を同時に表示
chart.jsの描画はクラスで行ってます。
4個のチャートオブジェクトは、staticな配列に入れてます。


static charts = [null, null, null, null] 

   // 前回消去 //
    if (typeof PieChart.charts[this.idx] !== 'undefined' &&  PieChart.charts[this.idx] !== null && PieChart.charts[this.idx]) {
      PieChart.charts[this.idx].destroy();
    }


    // == ドーナツチャート描画 == //
    PieChart.charts[this.idx] = new Chart(context, {
      //type: 'doughnut',
      //type: 'pie',
      type: chartType,
      data: {
        labels: labels,
        datasets: [{
            label: name,
            backgroundColor: ["#fa8072", "#00ff7f", "#00bfff", "#a9a9a9", "#f5f5f5", "#a7d4d4", "#E5E100"],
            data: counts
          }]
      },

      // プラグインをセット //
      plugins: [
        ChartDataLabels
      ],

      //color: "#ffffff",
      options: {
        responsive: false,
        maintainAspectRatio: false, // 2回目以降大きくなってしまうのを防止用

        legend: {
          labels: {
            // グローバルプロパティを上書 ?????? no affect //
            fontColor: 'white'
          }
        },

        // データラベルがアフェクトする用 //
        /*toltip: {
         enabled: false
         },*/

        plugins: {

          // データラベルがアフェクトする用 //
          toltip: {
            enabled: false
          },

          title: {
            display: true,
            text: name
          },

          // == データラベルプラグインの設定 == //
          datalabels: {
            color: '#294829',
            font: {
              weight: 'bold',
              size: 10, // <=== 小さ目
            },
            formatter: (value, ctx) => {

              let label = ctx.chart.data.labels[ctx.dataIndex];
              //return label + '\n' + value + '%';
              return label
            }
          }
          // == データラベルプラグインの設定終わり == //

        }
      }
    })

Tabulator カラムのグループヘッダー

 

 

 

ドキュメントはここ

< コード例 >

グループ化したいタイトルのカラムの配列を入れ子にします。


      // == Sizes == //
      {
        title: "Sizes",
        columns: [
          // GB Size //
          {title: 'Size (GB)', field: 'GB_SIZE', width: 50, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },

          // GB 前日//
          {title: 'Size (GB) P', field: 'GB_SIZE_BF1', width: 50, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },

          // MB Diff from 1 day before //
          {title: 'DiffS', field: 'DIFF_MB_SIZE', width: 60, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },
        ]
      },

      // == Counts == //
      {
        title: "Counts",
        columns: [

          // File count //
          {title: 'Count', field: 'CNT_FILE', width: 70, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },
          // File count previous //
          {title: 'Count P', field: 'CNT_FILE_BF1', width: 70, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          },

          // File count difference from previous date //
          {title: 'DiffC', field: 'DIFF_CNT_FILE', width: 60, hozAlign: 'right',
            formatter: function (cell, formatterParams, onRendered) {
              return numeral(cell.getValue()).format("#,###")
            }
          }
        ]
      },

< 画面例 >