Показать сообщение отдельно
  #28 (permalink)  
Старый 06.08.2012, 04:07
Аватар для Дзен-трансгуманист
√₋̅₁̅
Отправить личное сообщение для Дзен-трансгуманист Посмотреть профиль Найти все сообщения от Дзен-трансгуманист
 
Регистрация: 18.06.2012
Сообщений: 385

Сообщение от 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 = "&nbsp;";

      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 всякий раз происходят именно по текущему смещению колонки/строки с нажатой кнопкой относительно начала не-кнопочных ячеек.
__________________

Гейзенберг, возможно, читал этот тред.

Последний раз редактировалось Дзен-трансгуманист, 08.08.2012 в 03:10.
Ответить с цитированием