Сообщение от Apollo_440
|
Это везде пашет, то есть на любых таблицах?
|
Помимо
описанных ранее ограничений, других затыков пока не обнаружил... Впрочем, пока не факт, что в остальном всё в порядке. Короче, сделал тест, пробуйте.
Да, и еще добавил сохранение карты ячеек в объект таблицы (хотя не уверен, что это правильный подход), то есть обход всей таблицы происходит только при первом вызове функции, а затем ссылки просто достаются из кэша по индексам. Только тут надо учесть, что после добавления новых
<tr>
и
<td>
и любого другого изменения структуры таблицы, обязательно нужно будет строить карту ячеек заново. Для этого при вызове надо указать 3-й аргумент
true
, либо самостоятельно удалить свойство
_cellMap
из объекта таблицы.
________________________________________
Update 07.08.2012
- Добавлена функция
deleteTableRow
для удаления строк.
- Добавлено утверждение аргументов с номерами строк/колонок.
- Общий код вынесен в отдельные функции в private scope.
- Добавлен фикс IE для теста: добавление функции
indexOf
в прототип Array
, если таковая в нем не определена. (Однако этого фикса пока оказалось недостаточно.)
- Тест обновлен в соответствии с текущим функционалом.
________________________________________
Update 08.08.2012
- Добавлен еще один фикс IE для теста:
<tr>
теперь добавляется в <table>
не напрямую, а через <tbody>
. (Теперь всё работает.)
- Исправлен баг в
deleteTableRow
с некорректным способом удаления строки из DOM (непосредственным родителем считался аргумент table
, хотя на практике им может оказаться промежуточный <thead>
, <tbody>
или <tfoot>
).
<style type="text/css">
table { border: 0px; border-spacing: 2px; }
tr { height: 24px; }
td { padding: 0px; }
</style>
<script type="text/javascript">
// ------------------------------ Here it starts ------------------------------
var deleteTableCol;
var deleteTableRow;
( function () {
function makeCellMap (table) {
var rows = table.rows, cells, cell;
var cx, cy, cw, ch, coff;
var marr = [], mx, my, mw = 0, mh = 0;
for (cy=0; cy<rows.length; cy++) {
cells = rows[cy].cells;
for (cx=0, coff=0; cx<cells.length; cx++, coff+=cw) {
cell = cells[cx];
cw = cell.colSpan;
ch = cell.rowSpan;
if (cw == 0 || ch == 0) { return false; }
while (mh < cy+ch) { marr[mh++] = []; }
while (marr[cy][coff] !== undefined) { coff++; }
for (my=cy; my<cy+ch; my++) {
for (mx=coff; mx<coff+cw; mx++) {
if (marr[my][mx] !== undefined) { return false; }
marr[my][mx] = cell;
}
}
if (coff+cw > mw) { mw = coff+cw; }
}
}
return { marr: marr, mw: mw, mh: mh };
}
var cacheProp = "_cellMap";
function getCellMap (table, invalidateCache) {
var cellMap;
if (invalidateCache && table[cacheProp] !== undefined) { delete table[cacheProp]; }
if (table[cacheProp] === undefined) {
cellMap = makeCellMap(table);
if (cellMap === false) { return false; }
table[cacheProp] = cellMap;
}
return table[cacheProp];
}
function assertOrdinal (value, caller) {
if (!isFinite(value) || value < 0 || value != Math.floor(value)) {
throw new Error("Bad ordinal argument in " + caller);
}
}
deleteTableCol = function (table, col, invalidateCache) {
assertOrdinal(col, "deleteTableCol");
var cellMap = getCellMap(table, invalidateCache);
if (cellMap === false) { return false; }
if (col >= cellMap.mw) { return true; }
var rows = table.rows, cell, prevCell;
var marr = cellMap.marr, mw = cellMap.mw, mh = cellMap.mh, my, cw;
for (my=0; my<mh; my++) {
cell = marr[my][col];
if (cell !== prevCell && cell !== undefined) {
cw = cell.colSpan;
if (cw > 1) { cell.colSpan = cw-1; }
else { rows[my].removeChild(cell); }
prevCell = cell;
}
marr[my].splice(col, 1);
}
cellMap.mw--;
return true;
}
deleteTableRow = function (table, row, invalidateCache) {
assertOrdinal(row, "deleteTableRow");
var cellMap = getCellMap(table, invalidateCache);
if (cellMap === false) { return false; }
if (row >= cellMap.mh) { return true; }
var trow = table.rows[row], trowNext = trow.nextSibling;
var mrow = cellMap.marr[row], mrowNext = cellMap.marr[row+1];
var mw = cellMap.mw, mh = cellMap.mh, mx, mxNext, ch;
var cell, prevCell, nextCell;
for (mx=0; mx<mw; mx++) {
cell = mrow[mx];
if (cell !== prevCell && cell !== undefined) {
ch = cell.rowSpan;
if (ch > 1) {
cell.rowSpan = ch-1;
if (cell.parentNode === trow) {
trow.removeChild(cell);
for (mxNext=mx+1; ; mxNext++) {
nextCell = mrowNext[mxNext];
if (nextCell !== cell && (nextCell === undefined || nextCell.parentNode === trowNext)) { break; }
}
if (nextCell !== undefined) { trowNext.insertBefore(cell, nextCell); }
else { trowNext.appendChild(cell); }
}
}
prevCell = cell;
}
}
trow.parentNode.removeChild(trow);
cellMap.marr.splice(row, 1);
cellMap.mh--;
return true;
}
})();
// ------------------------------ Here it ends ------------------------------
/*
* Fix for IE 8 and below
* Required for following test only
*
*/
if (!Array.prototype.hasOwnProperty("indexOf")) {
Array.prototype.indexOf = function (value) {
var i;
for (i=0; i<this.length; i++) { if (this[i] == value) { return i; } }
return -1;
}
}
function appendTestTable (cols, rows, maxCellW, maxCellH) {
var body = document.body, table, tbody, tr, td, killer;
var map = [], mx, my;
var cx, cy, cw, ch;
var hIndex = [], vIndex = [];
function rand (x) { return Math.floor(Math.random() * x); }
function randColor () {
return "#" + rand(16).toString(16) + rand(16).toString(16) + rand(16).toString(16);
}
function onDelete (fn, value, index1, index1Extra, index2, index2Extra) { return function () {
var off = index1.indexOf(value);
if (fn(table, off)) {
index1.splice(off, 1);
if (index1.length == index1Extra) {
if (table.getElementsByTagName("td").length == index2.length - index2Extra + 1) {
body.removeChild(table);
body.removeChild(killer);
}
else { killer.value = "Oh, shit, there's a bug somewhere!"; }
}
}
else { alert("Something gone wrong..."); }
}}
function createButton (caption, onclick) {
var button = document.createElement("input");
button.style.width = "24px";
button.type = "button";
button.value = caption;
button.onclick = onclick;
return button;
}
table = document.createElement("table");
tbody = document.createElement("tbody");
killer = document.createElement("input");
killer.style.margin = "12px 0px 0px 2px";
killer.type = "button";
killer.value = "Kill this table";
killer.onclick = function () {
body.removeChild(table);
body.removeChild(killer);
}
body.appendChild(killer);
for (my=0; my<rows; my++) { map[my] = []; vIndex[my] = my; }
for (my=0; my<rows; my++) {
tr = document.createElement("tr");
td = document.createElement("td");
td.appendChild(createButton(my, onDelete(deleteTableRow, my, vIndex, 0, hIndex, 1)));
tr.appendChild(td);
for (mx=0; mx<cols; mx+=cw) {
while (map[my][mx]) { mx++; }
if (mx == cols) { break; }
for (cw=1; cw<maxCellW && mx+cw<cols; cw++) { if (map[my][mx+cw]) { break; } }
cw = rand(cw) + 1;
ch = rand(rows-my > maxCellH? maxCellH: rows-my) + 1;
for (cy=0; cy<ch; cy++) {
for (cx=0; cx<cw; cx++) {
map[my+cy][mx+cx] = true;
}
}
td = document.createElement("td");
td.style.backgroundColor = randColor();
td.colSpan = cw;
td.rowSpan = ch;
td.innerHTML = " ";
tr.appendChild(td);
}
tbody.appendChild(tr);
}
tr = document.createElement("tr");
td = document.createElement("td");
tr.appendChild(td);
for (mx=0; mx<cols; hIndex[mx+1]=mx++) {
td = document.createElement("td");
td.appendChild(createButton(mx, onDelete(deleteTableCol, mx, hIndex, 1, vIndex, 0)));
tr.appendChild(td);
}
tbody.appendChild(tr);
table.appendChild(tbody);
body.appendChild(table);
}
function newTestTable () {
var i;
var args = document.getElementById("args").value.match(/[0-9]+/g);
if (args.length < 4) { return; }
for (i=0; i<4; i++) {
args[i] = parseInt(args[i], 10);
if (!isFinite(args[i]) || args[i] == 0 || args[i] > 1000) { return; }
}
if (args[0] * args[1] > 10000) { return; }
args.length = 4;
appendTestTable.apply(window, args);
}
</script>
<span>table cols, table rows, max colspan, max rowspan:</span>
<input id="args" type="text" value="20, 20, 6, 3">
<input type="button" value="Append table" onclick="newTestTable()">
<br>
NOTE: Хоть номера кнопок в процессе не меняются (они равны исходным индексам), но вызовы
deleteTableCol
и
deleteTableRow
всякий раз происходят именно по текущему смещению колонки/строки с нажатой кнопкой относительно начала не-кнопочных ячеек.