Handsontable コンテキストメニューでページ遷移させる。

コンテキストメニューが実行する callback 関数の引数

key contextmenu の items で定義したキー名称
selection focusしているセルの開始から終わりまでの row と col
clickEvent マウスイベント
console.log("start (" + selection[0].start.row+ ","+selection[0].start.col + ")" );
console.log("end   (" + selection[0].end.row+ ","+selection[0].end.col + ")" );

で確認すればわかるが、selection がとても便利で、

Handsontable コンテキストメニューで、フォーカス選択した行の任意の列の値を
ページ遷移パラメータにすることができる。
グリッドを選択して、選択行の状況に沿った次のページを表示する場合など、
簡単にこれが使えるはずだ。

サンプル

var hot = new Handsontable(document.getElementById("table"), {
    data: Handsontable.helper.createSpreadsheetData(1000, 1000),
    width: '90%',
    height: 320,
    rowHeaders: true,
    colHeaders: true,
    contextMenu: { items:{
        "goPage": {
            name: 'ページ遷移',
            callback: function(key, selection, clickEvent){
                    // 選択開始位置の行の2列目の値を遷移先ページパラメータにする
                    var value = hot.getDataAtCell(selection[0].start.row, 1);
                    location.href = './etcPage?param='+value;
                },
            },
        },
    },
    licenseKey: 'non-commercial-and-evaluation'
});

(参考)
新しい独自のコンテキストメニューを列固定をつくる - Oboe吹きプログラマの黙示録

新しい独自のコンテキストメニューを列固定をつくる

Excel の Window枠の固定のように、Handsontable では、列インデックスを指定して
指定インデックスより左側を固定にするオプション fixedColumnsLeft がある。

現在選択しているセルに対して、この fixedColumnsLeft の値をセットするように
コンテキストメニューを作れば、便利である。当然、解除も用意する。

コンテキストメニューが実行する callback が、以下の引数で呼ぶことを考慮すれば良い

key contextmenu の items で定義したキー名称
selection focusしているセルの開始から終わりまでの row と col
clickEvent マウスイベント

selection は、
selection[0].start.row と  selection[0].start.col で、選択開始セルの行と列番号
selection[0].end.row と selection[0].end.col で選択終了セルの行と列番号

初期表示は、fixedColumnsLeft = 0 であることを利用して、以下のようにする。

contextMenu: {
    items:{
        'row_above':  { name: '1行挿入',  },
        'remove_row': { name: '行削除', disabled: function(){ return hot.countRows() < 2; }  },
        "hsep": "---------",
        'undo': { name: '戻る' },
        'fixcolumn' : {
            name: function(){
                if ( hot.getSettings().fixedColumnsLeft==0){
                    return "列固定";
                }else{
                    return "列固定解除";
                }
            },
            callback: function(key, selection, clickEvent){
                if ( hot.getSettings().fixedColumnsLeft==0){
                    hot.getSettings().fixedColumnsLeft = selection[0].start.col;
                }else{
                    hot.getSettings().fixedColumnsLeft = 0;
                }
                hot.render();
            },
        },
    },
},

列固定後は、コンテキストメニュー表示が「列固定解除」になるように制御する。

解決、Handsontable で ClockPicker による時刻入力

自分で作っていて気に入らなかった
oboe2uran.hatenablog.com

これを解決してくれたブログ記事を見つけました。
【Handsontable】セル内に時刻入力(ClockPicker)の表示 - Qiita

非常に助かり、感謝です。

そのまま、コピーで使わせてもらいたいと思います。
外だしの JS ソースで使いまわします。
handson_timepicker.js

/**
 * handson_timepicker.js
 */
$(function(){
    const TimeEditor = Handsontable.editors.BaseEditor.prototype.extend();
    TimeEditor.prototype.init = function(){
        this.clock = document.createElement('input');
        this.clock.setAttribute('type', 'text');
        const that = this;
        this.clockInput = $(this.clock).clockpicker({
            placement: 'bottom',
            align: 'left',
            autoclose: true,
            afterShow: function(){
                $('.clockpicker-minutes').find('.clockpicker-tick').show();
                let choices = that.instance.getCellMeta(that.row, that.col).source;
                if (choices == undefined || choices.length == 0) return;
                $('.clockpicker-minutes').find('.clockpicker-tick').filter(function(index, element){
                    return !($.inArray($(element).text(), choices) != -1)
                }).hide();
            },
            afterDone: function(){
                let choices = that.instance.getCellMeta(that.row, that.col).source;
                if (choices != undefined && choices.length > 0) {
                    let selectedMinutes = that.clockInput.val().split(":")[1];
                    if ($.inArray(selectedMinutes, choices) == -1) {
                        let time = that.clockInput.val().split(":");
                        let hour = time[0];
                        let minutes = choices[0];
                        for(minutes of choices){
                            if(time[1] < minutes) break;
                        }
                        that.clockInput.val(hour + ':' + minutes)
                        that.clockInput.clockpicker('show').clockpicker('toggleView', 'minutes');
                        let $cpop = $('.clockpicker-popover');
                        $cpop.offset(locate);
                        return;
                    }
                }
                that.instance.setDataAtCell(that.row, that.col, that.clockInput.val());
            }
        });
    };
    TimeEditor.prototype.open = function(){
        let $td = $(this.TD);
        let offset = $td.offset();
        this.clockInput.clockpicker('show');
        $(document).off('click.clockpicker.cp1 focusin.clockpicker.cp1');
        let $cpop = $('.clockpicker-popover');
        locate = { top: offset.top + $td.height() + 10, left: offset.left };
        $cpop.offset(locate);
        $('.clockpicker-hours, .clockpicker-span-hours, .clockpicker-span-minutes')
.on('mousedown mouseup', function (event) {
            event.stopPropagation();
        });
        $('.clockpicker-minutes').on('mousedown', function(event){
            event.stopPropagation();
        });
    };
    TimeEditor.prototype.close = function(){
        this.clockInput.clockpicker('hide');
    };
    TimeEditor.prototype.getValue = function(){
        return $('.clockpicker-span-hours').text() + ':' + $('.clockpicker-span-minutes').text();
    };
    TimeEditor.prototype.setValue = function(newValue){
        this.clock.value = newValue;
    };
    TimeEditor.prototype.focus = function(){};
    Handsontable.editors.TimeEditor = TimeEditor;
    Handsontable.editors.registerEditor('time', TimeEditor);
});

これを <script src= で指定して、カラム定義を以下のようにします。
10分区切りや、15分区切り、カラム定義で定義して、
Handsontable インスタンスのセルのメタ情報として認識させるんですね。
なるほど。。。

columns:[
    {
        data: 'A',
        tyoe: 'text',
        renderer: 'autocomplete', editor: 'time'
    },
    {
        data: 'B',
        tyoe: 'text',
        renderer: 'autocomplete', editor: 'time',
        source: ['00','10','20','30','40','50']
    },
    {
        data: 'C',
        tyoe: 'text',
        renderer: 'autocomplete', editor: 'time',
        source: ['00','15','30','45']
    },
],

Handsontable でスクロールをしたときにグリッドの崩れを回避したい

たくさんの行、列が存在するグリッドはスクロール操作をした時に崩れることがあります。
この原因は、Handsontable ではなく、
そもそも、HTMLの表示をブラウザでは、DOM + CSS → CSSOM というCSSOM を作るわけですが、
このCSSOM を作る間、ブラウザがレンダリングをするのを止まってしまいます。
スクロール操作ではこれが遅延するものと追いつくものの差が出てきて崩れを生じます。

完全に解決する方法はないですが、回避できる可能性がある Handsontable のオプションがあります。

https://handsontable.com/docs/api/options/#viewportcolumnrenderingoffset

https://handsontable.com/docs/api/options/#viewportrowrenderingoffset

viewportColumnRenderingOffset と、viewportRowRenderingOffset 
グリッド周囲を描画する時の行と列の数、これがデフォルトでは、auto :自動計算される値になってます。
これを、表示するグリッドの状況に合わせて、数値で指定します。

viewportColumnRenderingOffset: 100,
viewportRowRenderingOffset: 100,

Handsontableをセットする書き方、以下のようにこのオプションを書きます。

var hot = new Handsontable(document.getElementById("table"), {
        data: data,
        language: 'ja-JP',
        columns: // 列定義がある。
        autoColumnSize: true,
        autoRowSize: true,
        viewportColumnRenderingOffset: 100,
        viewportRowRenderingOffset: 100,

});

Handsontable フィルタを使用中かどうかを判断する

原始的な方法であるが思いつくのは、afterFilter イベントをフックして、
条件配列の数を記録しておくことだろう。

var conditionsStackLength = 0;

var hot = new Handsontable(document.getElementById("table"), {
        data: data,
        language: 'ja-JP',
        columns: // 列定義がある。。
        filters: true,
        dropdownMenu: ['filter_by_condition', 'filter_by_value', 'filter_action_bar'],
        
        /* フィルタセットのイベントHook */
        afterFilter:function(conditionsStack){
              conditionsStackLength = conditionsStack.length;
        },        
    });
    
// 何かボタンを押した時、、
$('#todo').click(function(){
    if (conditionsStackLength > 0){
        console.log("フィルタ使用中");
    }else{
        console.log("フィルタ未使用");
    }
    // hot.getData() などをする。。
});

Handsontable 必須チェックバリデーション

columns でセットする validator に、以下のように callbaclk を呼べば
空なら、エラーになる。

var hot = new Handsontable(document.getElementById("table"), {
    data:data,
    columns: [
      {  type:'text', 
         validator: function(value, callback){ callback(!!value); },       
      },
      {  type:'numeric',  
         validator: function(value, callback){ callback(!!value); },       
      },
   ],
));

これは、これは入力値が、空、value==null || value=='' の代わりに、
!value で代用するのを
さらに、! で反転してるだけである。
でも、数値型であれば、必須の制限なら正規表現で、

{  
       type:'numeric',  
       validator: /^[0-9]+$/,       
 },

でも良いはずだ。
type : 'text' も、、、

{  
       type:'text',  
       validator: /^.+$/,       
 },

でも良いはずだ。



    
    
  

Handsontable AutoFil の結果で処理を行う場合

afterAutofill イベントフックの処理を定義する。

例)

var data = [
    [ "a", 1,  ],
    [ "b", 2,  ],
    [ "c", 3,  ],
    [ "d",  ],
    [ "e",  ],
    [ "f",  ],
];
var hot = new Handsontable(document.getElementById("table"), {
    data:data,
    columns: [
        { type: 'text', },
        { type: 'numeric', },
        { type: 'text', },
    ],
    colHeaders: [ "A", "B", "C" ],
    copyPaste: true,
    autoColumnSize: true,
    manualColumnResize: true,
    afterAutofill: function(fillData, sourceRange, targetRange, direction){
        console.log("---- fillData : 対象データ");
        console.log(fillData);
        console.log("---- sourceRange : 対象データ範囲 ----");
        console.log("from  (" + sourceRange.from.row + ", "+ sourceRange.from.col + ")  "+
        "to   (" + sourceRange.to.row + ", "+ sourceRange.to.col + ")  "+
        "highlight (" + sourceRange.highlight.row + ", "+ sourceRange.highlight.col + ")");
        console.log("---- targetRange ; 更新データ範囲 ----");
        console.log("from  (" + targetRange.from.row + ", "+ targetRange.from.col + ")  "+
        "to   (" + targetRange.to.row + ", "+ targetRange.to.col + ")  "+
        "highlight (" + targetRange.highlight.row + ", "+ targetRange.highlight.col + ")");
        console.log("# direction = " + direction);
    },
    licenseKey: 'non-commercial-and-evaluation'
});

direction は、ドラッグ操作の方向を示し、

down 下にドラッグ
up 上にドラッグ
right 右にドラッグ
left 左にドラッグ

範囲選択
f:id:posturan:20210820113811j:plain
下にドラッグ
f:id:posturan:20210820113837j:plain

afterAutofill で、任意のセルを変更するなら、
Handsontable インスタンスメソッドの setDataAtCell( row, col, value ) を実行する