icon [脱jQuery]ネイティブでDrag&Dropやtableソート (2019/06/17)
テーブルソートは、jQuery(tablednd.js)で比較的簡単に実装できる。
tablednd はマウスイベントを駆使して実装している感じだが、ここではHTML5のDND(Drag & Dropイベント)を利用して実装してみる。

HTML5ドラッグ&ドロップの仕様確認

まずは仕様を確認
HTML Living Standard:Drag and drop
ネイティブ HTML5 ドラッグ&ドロップ

サンプル&ライブラリ

tableの並び替え&アップデートスクリプトのライブラリ(PwJS)を作成してみた。

Github

PwJSライブラリ*PwTableDND.js, PwNode.jsを利用
html_samples/sotrable_table.html

色々と制御する必要があるので、ここでは主要部分だけ記述
*並び替えの保持やAPI更新は記述なし

ドラッグ&ドロップの実装

「dragstart」「dragover」「drop」などのDNDイベントを登録して、Element取得・操作を行う
        //各イベントハンドラ
        function handleDragStart(event) {
            //ここにドラッグアイテムの取得処理
            event.target.style.opacity = '0.4';  //透明度を有効
            event.stopPropagation();
        }
        function handleDrag(event) {
        }
        function handleDragEnter(event) {
        }
        function handleDragOver(event) {
            event.dataTransfer.dropEffect = 'move'; //仕様でサポートしている dropEffect
            event.preventDefault();  //伝播を止める
            return false;
        }
        function handleDragLeave(event) {
            
        }
        function handleDrop(event) {
            //ここにターゲットアイテムの取得処理
            event.preventDefault();  //伝播を止める
            event.stopPropagation();  //リンクキャンセル
        }
        function handleDragEnd(event) {
            event.target.style.opacity = '' //透明度を無効
        }
        //各イベントの登録
        document.addEventListener('dragstart', handleDragStart, false);
        document.addEventListener('drag', handleDrag, false);
        document.addEventListener('dragenter', handleDragEnter, false)
        document.addEventListener('dragover', handleDragOver, false);
        document.addEventListener('dragleave', handleDragLeave, false);
        document.addEventListener('drop', handleDrop, false);
        document.addEventListener('dragend', handleDragEnd, false);
伝播しないように preventDefault(), ドラッグでリンク誤動作しないように stopPropagation() を実行しているが、実際に実行してみるとよくわかる。

ドラッグ&ドロップで行を移動

並び順のデータID「row-id」属性を持った「tr」で構成
「tbody」内の「tr」を入れ替えるようなスクリプトを作成する
  <table id="sortable-table">
    <thead>
      <tr>
        <th>City</th>
      </tr>
    </thead>
    <tbody>
      <tr row-id="1">
        <td>Tokyo</td>
      </tr>
      <tr row-id="2">
        <td>Osaka</td>
      </tr>
      <tr row-id="3">
        <td>Nagoya</td>
      </tr>
      <tr row-id="4">
        <td>Kobe</td>
      </tr>
    </tbody>
  </table>

「draggable = true」でドラッグ可能&ソート属性を生成

querySelectorAll() などでElement や HTMLCollection を取得して、各行の属性を動的に設定
HTML5では「draggable = true」でドラッグ可能になる
       ....

    var _this = this;
    this.table_id = 'sortable-table';
    this.row_id_column = 'row-id';
    this.body_selector = '';
    this.tr_selector = '';
    this.sortable_tr_selector = '';
        ....

    this.enableDrag = function() {
        _this.loadSelectorColumn();
        if (!this.table_id) return;
        var row_id;
        [].forEach.call(this.getElements(), function(element, index) {
            _this.before_rows.push(element);
            row_node = PwNode.byElement(element);
            if (row_id = row_node.attr(_this.row_id_column)) {
                row_node.setAttr('id', pw_row_id_column + row_id);
                row_node.setAttr('order', index + 1);
                row_node.setAttr('draggable', true);
                if (!pw_app.isIE()) {
                    row_node.setAttr('ondragstart', "event.dataTransfer.setData('text/plain', null)");
                }
            }
        });
    }
    this.loadSelectorColumn = function() {
        _this.body_selector = '#' + _this.table_id + ' tbody';
        _this.tr_selector = '#' + _this.table_id + ' tr';
        _this.sortable_tr_selector = _this.body_selector + ' tr';
    }
    this.getElements = function() {
        let elements = document.querySelectorAll(_this.sortable_tr_selector);
        return elements;
    }
FirefoxではHTMLタグに「ondragstart="event.dataTransfer.setData('text/plain', null)"」を指定しておかないと、何故か動作しなかった。
*pw_app.isIE() はライブラリのIE判別関数

DNDイベント処理

dropイベントで、Drag Element, Drop Elementの取得・入れ替えを行う
        function handleDragStart(event) {
            let tr = event.target.closest('tr');
            if (tr) _this.drag_item = event.target.closest('tr');
            event.target.style.opacity = '0.4';
            event.stopPropagation();
        }
        function handleDrop(event) {
            var tr = event.target.closest('tr');
            if (tr && _this.drag_item != tr) {
                _this.target_item = tr;
            }
            event.target.style.opacity = '1.0'
            let row_id = _this.drag_item.getAttribute(_this.row_id_column);
            if (row_id && _this.target_item && _this.drag_item != _this.target_item) {
                var tbody = PwNode.byQuery(_this.body_selector).first();
                let drag_order = _this.drag_item.getAttribute('order');
                let target_order = _this.target_item.getAttribute('order');
                if (drag_order > target_order) {
                    tbody.insertBefore(_this.drag_item, _this.target_item);
                } else if (drag_order < target_order) {
                    tbody.insertBefore(_this.drag_item, _this.target_item.nextElementSibling);
                }
                //テーブル並び替え後の処理を記述
                //並び順管理、tr.order を振り直す etc..
            }
            event.preventDefault();
            event.stopPropagation();
        }
event.target.closest() で「tr」を取得
「tbody」内で insertBefore() を利用し、並び順を考慮しつつ Element を書き換える
色々な方法があると思うが、上記は「tr」の order が並び替え後に順番を振り直す事を前提にしている