わんすけに聞いてみる EXCELっぽいHTMLテーブル Tableのセルにエクセルっぽいアドレスを振って矢印キー移動してみる。

Tableのセルにエクセルっぽいアドレスを振って矢印キー移動してみる。

前回の続きから、選択中のセルを矢印キーで移動しようと思ったら

セルの相対位置がわかるアドレスがないといけないなーと思いまして

まさにエクセルの様なセルアドレスを振ってみようじゃないかと考えました。

 

 

HTMLのTableセルにアドレスを振る。

まず、editable-tableクラスを持つtableタグのセルのindexで何行目・何列目を取得します。

      var row = $('.editable-table').find('tr').index(tr);
      var col = $(tr).find('td').index(this);
      var idx = editable_table.BuildAddress(row, col);

ほんで、このrowとcolを使ってセルアドレスを作ってみる訳ですね。

  BuildAddress: function (row, col) {
    var adrs = "";
    var col_idx = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    limit = col_idx.length;
    digit = col;
    do{
      adrs = (adrs.length)?col_idx[digit%limit-1] + adrs: col_idx[digit % limit];
      digit = Math.floor(digit / limit);
    }while (digit > 0);
    return  adrs + row;
  },

エクセル風にしようとするとアルファベットのカラム識別しが若干アレですが、

関数とか使えるようにしたら、きっと見やすくなるはず、、、と思って我慢して書きました。

ほんで、生成したアドレスと行番号・列番号は連想配列にして補完しておくっと。

この時にセルの属性にもアドレスを割り振っておいてー

セルの中身(value)と表示内容(html)を別々で管理して、あとで関数の機能で使い分けようという寸法な訳だ。

      $(this).attr("data-address", idx);
      editable_table_val[idx] = { value: $td.html(), row: row, col: col };

アドレスと行番号・列番号のマップが手に入ったから、

相対位置でセル移動できる関数を用意する。
  Offset: function (row, col) {
    var adrs = $('.selected').attr("data-address");
    var cell = editable_table_val[adrs];
    var nextAdrs = editable_table.BuildAddress(cell.row+row, cell.col+col);
    if (editable_table_val[nextAdrs]){
      editable_table.ClearSelectionCells();
      $("td[data-address='" + nextAdrs + "']").toggleClass("selected", true);
    }
  },

現在のアクティブセルのアドレスから行番号・列番号でオフセットして移動先アドレスを作って

そのアドレスがあれば、そこに選択セルを移動するっと。。。

ほんで、矢印キーのキーイベントにこのOffsetをセットしてみると。

      $(document).keydown(function(e){
        var cursor = function() { return ($("#edit_target").length) ? false : ($(".selected").length) ? true : false; }

        switch (e.keyCode) {
          case 27: // Esc キー
            var adrs = $("#edit_target").attr("data-address");
            if (adrs == undefined) { return }
            $("#edit_target").remove();
            $("td[data-address='" + adrs + "']").toggleClass("editing", false);
            editable_table.Recalculation();
            break;
          case  37: // left キー
            if (cursor()) { editable_table.Offset(0,-1) }
            break;
          case  38: // up キー
            if (cursor()) { editable_table.Offset(-1,0) }
            break;
          case  39: // right キー
            if (cursor()) { editable_table.Offset(0,1) }
            break;
          case  40: // down キー
            if (cursor()) { editable_table.Offset(1,0) }
            break;
        }
      });

うん。なんとなく選択したセルが矢印キーで移動できるようになりました。

今回生成したセルのアドレスを駆使して関数の実装とかやりたい訳なんですが、

怖いのは、セル指定の循環参照で起こる無限ループですかね。

エクセルでよくポップアップ見る、「循環参照の検出」について次回は考えてみることにします。

 

今日の成果の全体像
$(function (){
  editable_table.Initalize();
});

var editable_table_val = {};
var editable_table = {
  Initalize: function () {
    $('.editable-table').find('td').each(function(){
      var $td = $(this);
      var tr = $(this).parent();
      var row = $('.editable-table').find('tr').index(tr);
      var col = $(tr).find('td').index(this);
      var idx = editable_table.BuildAddress(row, col);
      $(this).attr("data-address", idx);
      editable_table_val[idx] = { value: $td.html(), row: row, col: col };

      $td.click(function () {
        var $this = $(this);
        if ($this.hasClass("selected")) {
            $this.trigger("dblclick");
        } else {
          editable_table.ClearSelectionCells();
          $this.toggleClass("selected", true);
        }
      });

      $td.dblclick(function () {
        var $this = $(this);
        if ($this.hasClass("editing")) { return }
        if ($this.attr("lock") != undefined) { editable_table.CellLock(this); return }
        var adrs = $this.attr("data-address");
        var cell_val = editable_table_val[adrs].value;
        editable_table.ClearSelectionCells();
        var input = $("<textarea />").attr("id", "edit_target").attr("data-address", adrs).addClass("cell-edit").text(cell_val);
        $this.toggleClass("selected",true).html("").append(input);
        $this.addClass("editing");
        $("#edit_target").focus().blur(function (){
           var adrs = $(this).attr("data-address");
           var edit_val = $(this).text().toString();
           var rollback = editable_table_val[adrs].value;
           editable_table_val[adrs].value = edit_val;
           $(this).remove();
           $("td[data-address='" + adrs + "']").toggleClass("editing", false);
           editable_table.Recalculation();
         });
      });
    });

    $(document).keydown(function(e){
      var cursor = function() { return ($("#edit_target").length) ? false : ($(".selected").length) ? true : false; }

      switch (e.keyCode) {
        case 27: // Esc キー
          var adrs = $("#edit_target").attr("data-address");
          if (adrs == undefined) { return }
          $("#edit_target").remove();
          $("td[data-address='" + adrs + "']").toggleClass("editing", false);
          editable_table.Recalculation();
          break;
        case  37: // left キー
          if (cursor()) { editable_table.Offset(0,-1) }
          break;
        case  38: // up キー
          if (cursor()) { editable_table.Offset(-1,0) }
          break;
        case  39: // right キー
          if (cursor()) { editable_table.Offset(0,1) }
          break;
        case  40: // down キー
          if (cursor()) { editable_table.Offset(1,0) }
          break;
      }
    });
  },
  BuildAddress: function (row, col) {
    var adrs = "";
    var col_idx = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    limit = col_idx.length;
    digit = col;
    do{
      adrs = (adrs.length)?col_idx[digit%limit-1] + adrs: col_idx[digit % limit];
      digit = Math.floor(digit / limit);
    }while (digit > 0);
    return  adrs + row;
  },
  ClearSelectionCells: function () {
    $('.editable-table').find('td').each(function(){
      $(this).toggleClass("selected", false);
    });
  },
  Offset: function (row, col) {
    var adrs = $('.selected').attr("data-address");
    var cell = editable_table_val[adrs];
    var nextAdrs = editable_table.BuildAddress(cell.row+row, cell.col+col);
    if (editable_table_val[nextAdrs]){
      editable_table.ClearSelectionCells();
      $("td[data-address='" + nextAdrs + "']").toggleClass("selected", true);
    }
  },
  Recalculation: function() {
    for (idx in editable_table_val) {
      $("td[data-address='" + idx + "']").html(editable_table_val[idx].value);
    }
  },
  CellLock: function(cell) {
    var msg = "このセルは変更できません。";
    var $tooltip = $("<span />").addClass("tooltip").append($("<span />").addClass("tooltip__body").text(msg));
    $('body').append($tooltip);
    var $cell = $(cell);
    var cell_offset = $cell.offset();
    var cell_size = { width: $cell.outerWidth(), height: $cell.outerHeight() };
    var tip_size = { width: $tooltip.outerWidth(), height: $tooltip.outerHeight() };
    $tooltip.css({ top: cell_offset.top - tip_size.height,
                  left: cell_offset.left + cell_size.width / 2 - tip_size.width / 2});
    $cell.mouseout(function (){ $('.tooltip').fadeOut(2000, function() {$(this).remove(); })});
  }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

Related Post

編集したTableの値に循環参照が発生していないかチェックしてみる。編集したTableの値に循環参照が発生していないかチェックしてみる。

さて、今回は編集可能にしたTableに関数を実装していく前に循環参照を検出しようというところ。 編集中のセルからフォーカスアウトした時に発火するイベントにチェックを仕込みます。 循環参照のチェックを開始する部分 $("# […]