前回の続きから、選択中のセルを矢印キーで移動しようと思ったら
セルの相対位置がわかるアドレスがないといけないなーと思いまして
まさにエクセルの様なセルアドレスを振ってみようじゃないかと考えました。
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(); })});
}
}