Обрабатывайте ячейки с помощью рядов при скрытии строк таблицы - программирование
Подтвердить что ты не робот

Обрабатывайте ячейки с помощью рядов при скрытии строк таблицы

У меня есть таблица содержащая ячейки с атрибутами rowspan, я хотел бы:

  • Всякий раз, когда tr скрыт, таблица будет правильно упорядочиваться
  • Всякий раз, когда символ tr отображается снова, он будет восстановлен в исходное состояние.

Итак, если у вас есть таблица, подобная этому, нажатие на X не должно разрушать макет. и нажмите кнопку come back, чтобы восстановить исходный макет.

(попробуйте удалить все строки снизу вверх, а затем восстановить их справа налево, это желаемый поток)

У меня были некоторые полурешения, но все они кажутся слишком сложными, и я уверен, что есть хороший способ справиться с этим.

4b9b3361

Ответ 1

OK Я действительно долгое время занимался этим вопросом, поэтому здесь идет...

Для тех из вас, кто просто хочет увидеть рабочее решение, нажмите здесь

Обновление:. Я изменил метод вычисления визуальных столбцов для перебора по таблице и создания двумерного массива, чтобы увидеть старый метод, который использовал метод jQuery offset(), нажмите здесь. Код короче, но более дорогостоящий.

Проблема возникает, потому что, когда мы скрываем строку, в то время как мы хотим, чтобы все ячейки были скрыты, мы хотим, чтобы псевдо-ячейки - то есть ячейки, которые, как представляется, находятся в следующих строках из-за ячеек rowspan атрибут - сохраняться. Чтобы обойти это, всякий раз, когда мы сталкиваемся с скрытой ячейкой с помощью rowspan, мы пытаемся переместить ее вниз по следующей видимой строке (уменьшая ее значение rowspan по мере продвижения). С нашей исходной ячейкой или ее клоном мы затем повторяем таблицу снова для каждой строки, которая будет содержать псевдо-ячейку, а если строка скрыта, мы снова уменьшаем rowspan. (Чтобы понять, почему, посмотрите на рабочий пример и обратите внимание, что когда синяя строка скрыта, красная ячейка 9 rowspan должна быть уменьшена с 2 на 1, иначе она будет нажимать зеленый 9 вправо).

С учетом этого мы должны применять следующую функцию, когда строки показаны/скрыты:

function calculate_rowspans() {
  // Remove all temporary cells
  $(".tmp").remove();

  // We don't care about the last row
  // If it hidden, it cells can't go anywhere else
  $("tr").not(":last").each(function() {
    var $tr = $(this);

    // Iterate over all non-tmp cells with a rowspan    
    $("td[rowspan]:not(.tmp)", $tr).each(function() {
      $td = $(this);
      var $rows_down = $tr;
      var new_rowspan = 1;

      // If the cell is visible then we don't need to create a copy
      if($td.is(":visible")) {
        // Traverse down the table given the rowspan
        for(var i = 0; i < $td.data("rowspan") - 1; i ++) {

          $rows_down = $rows_down.next();
          // If our cell row is visible then it can have a rowspan
          if($rows_down.is(":visible")) {
            new_rowspan ++;
          }
        }
        // Set our rowspan value
        $td.attr("rowspan", new_rowspan);   
      }
      else {
        // We'll normally create a copy, unless all of the rows
        // that the cell would cover are hidden
        var $copy = false;
        // Iterate down over all rows the cell would normally cover
        for(var i = 0; i < $td.data("rowspan") - 1; i ++) {
          $rows_down = $rows_down.next();
          // We only consider visible rows
          if($rows_down.is(":visible")) {
            // If first visible row, create a copy

            if(!$copy) {
              $copy = $td.clone(true).addClass("tmp");
              // You could do this 1000 better ways, using classes e.g
              $copy.css({
                "background-color": $td.parent().css("background-color")
              });
              // Insert the copy where the original would normally be
              // by positioning it relative to it columns data value 
              var $before = $("td", $rows_down).filter(function() {
                return $(this).data("column") > $copy.data("column");
              });
              if($before.length) $before.eq(0).before($copy);
              else $(".delete-cell", $rows_down).before($copy);
            }
            // For all other visible rows, increment the rowspan
            else new_rowspan ++;
          }
        }
        // If we made a copy then set the rowspan value
        if(copy) copy.attr("rowspan", new_rowspan);
      }
    });
  });
}

Следующая, действительно сложная часть вопроса - это вычисление, при котором индекс помещает копии ячеек внутри строки. Обратите внимание, что в примере синяя ячейка 2 имеет фактический индекс в своей строке 0, т.е. Это первая действительная ячейка внутри строки, однако мы можем видеть, что визуально она находится в столбце 2 (с индексом 0).

Я взял подход к вычислению этого только один раз, как только документ будет загружен. Затем я сохраняю это значение как атрибут данных ячейки, чтобы я мог поместить его копию в нужное место (у меня было много моментов Эврика на этом и сделано много страниц заметок!). Чтобы сделать этот расчет, я закончил создание двумерного массива matrix, который отслеживает все используемые визуальные столбцы. В то же время я сохраняю исходное значение ячейки rowspan, так как это изменится со скрытием/показом строк:

function get_cell_data() {
    var matrix = [];  
    $("tr").each(function(i) {
        var $cells_in_row = $("td", this);
        // If doesn't exist, create array for row
        if(!matrix[i]) matrix[i] = [];
        $cells_in_row.each(function(j) {
            // CALCULATE VISUAL COLUMN
            // Store progress in matrix
            var column = next_column(matrix[i]);
            // Store it in data to use later
            $(this).data("column", column);
            // Consume this space
            matrix[i][column] = "x";
            // If the cell has a rowspan, consume space across
            // Other rows by iterating down
            if($(this).attr("rowspan")) {
                // Store rowspan in data, so it not lost
                var rowspan = parseInt($(this).attr("rowspan"));
                $(this).data("rowspan", rowspan);
                for(var x = 1; x < rowspan; x++) {
                    // If this row doesn't yet exist, create it
                    if(!matrix[i+x]) matrix[i+x] = [];
                    matrix[i+x][column] = "x";
                }
            }
        });
    });

    // Calculate the next empty column in our array
    // Note that our array will be sparse at times, and
    // so we need to fill the first empty index or push to end
    function next_column(ar) {
        for(var next = 0; next < ar.length; next ++) {
            if(!ar[next]) return next;
        }
        return next;
    }
}

Затем просто примените это на загрузку страницы:

$(document).ready(function() {
    get_cell_data();
});

(Примечание: в то время как код здесь длиннее моего jQuery .offset(), скорее всего, он быстрее вычисляется. Пожалуйста, поправьте меня, если я ошибаюсь).

Ответ 2

Рабочее решение - http://codepen.io/jmarroyave/pen/eLkst

Это в основном то же самое решение, которое я представил ранее, я просто изменил способ получения индекса столбца, чтобы удалить ограничение jquery.position, и сделал некоторый рефакторинг для кода.

function layoutInitialize(tableId){
  var layout = String();
  var maxCols, maxRows, pos, i, rowspan, idx, xy;

  maxCols = $(tableId + ' tr').first().children().length;
  maxRows = $(tableId + ' tr').length;

  // Initialize the layout matrix
  for(i = 0; i < (maxCols * maxRows); i++){
    layout += '?';
  }

  // Initialize cell data
  $(tableId + ' td').each(function() {
    $(this).addClass($(this).parent().attr('color_class'));
    rowspan = 1;
    if($(this).attr('rowspan')){
      rowspan = $(this).attr("rowspan");  
      $(this).data("rowspan", rowspan);  
    }

    // Look for the next position available
    idx = layout.indexOf('?');
    pos = {x:idx % maxCols, y:Math.floor(idx / maxCols)}; 
    // store the column index in the cell for future reposition
    $(this).data('column', pos.x);
    for(i = 0; i < rowspan; i++){
      // Mark this position as not available
      xy = (maxCols * pos.y) + pos.x
      layout = layout.substr(0, xy + (i * maxCols)) + 'X' + layout.substr(xy + (i * maxCols)  + 1);
    }
  });   

}

Решение: с jquery.position() - http://codepen.io/jmarroyave/pen/rftdy

Это альтернативное решение, оно предполагает, что первая строка содержит всю информацию о количестве столбцов таблицы и позиции каждого из них.

Этот aproach имеет ограничение на то, что код инициализации должен вызываться, когда таблица видна, потому что она зависит от видимой позиции столбцов.

Если это не проблема, надейтесь, что это сработает для вас

Инициализация

  // Initialize cell data
  $('td').each(function() {
    $(this).addClass($(this).parent().attr('color_class'));
    $(this).data('posx', $(this).position().left);
    if($(this).attr('rowspan')){
      $(this).data("rowspan", $(this).attr("rowspan"));  
    }
  });

UPDATE В соответствии с этим сообщением для обеспечения видимости таблицы можно управлять с помощью

  $('table').show();
  // Initialize cell data
  $('td').each(function() {
    $(this).addClass($(this).parent().attr('color_class'));
    $(this).data('posx', $(this).position().left);
    if($(this).attr('rowspan')){
      $(this).data("rowspan", $(this).attr("rowspan"));  
    }
  });
  $('table').hide();

Как сказал Ян, основной проблемой для решения этой проблемы является вычисление положения ячеек при слиянии скрытых с видимыми строками.

Я попытался понять, как браузер реализует эту функциональность и как с ней работать. Затем, глядя в DOM, я искал что-то вроде columnVisiblePosition, и я нашел атрибуты позиции и сделал это.

 function getColumnVisiblePostion($firstRow, $cell){
  var tdsFirstRow = $firstRow.children();
  for(var i = 0; i < tdsFirstRow.length; i++){
    if($(tdsFirstRow[i]).data('posx') == $cell.data('posx')){
      return i;
    }
  }
}

Код js

$(document).ready(function () {
  add_delete_buttons();

  $(window).on("tr_gone", function (e, tr) {
    add_come_back_button(tr);
  });

  // Initialize cell data
  $('td').each(function() {
    $(this).addClass($(this).parent().attr('color_class'));
    $(this).data('posx', $(this).position().left);
    if($(this).attr('rowspan')){
      $(this).data("rowspan", $(this).attr("rowspan"));  
    }
  });
});

function calculate_max_rowspans() {
  // Remove all temporary cells
  $(".tmp").remove();

  // Get all rows
  var trs = $('tr'), tds, tdsTarget,
      $tr, $trTarget, $td, $trFirst,
      cellPos, cellTargetPos, i;

  // Get the first row, this is the layout reference
  $trFirst = $('tr').first();

  // Iterate through all rows
  for(var rowIdx = 0; rowIdx < trs.length; rowIdx++){
    $tr = $(trs[rowIdx]);
    $trTarget = $(trs[rowIdx+1]);
    tds = $tr.children();

    // For each cell in row
    for(cellIdx = 0; cellIdx < tds.length; cellIdx++){
      $td = $(tds[cellIdx]);
      // Find which one has a rowspan
      if($td.data('rowspan')){
        var rowspan = Number($td.data('rowspan'));

        // Evaluate how the rowspan should be display in the current state
        // verify if the cell with rowspan has some hidden rows
        for(i = rowIdx; i < (rowIdx + Number($td.data('rowspan'))); i++){
          if(!$(trs[i]).is(':visible')){
            rowspan--;
          }
        }

        $td.attr('rowspan', rowspan);

        // if the cell doesn't have rows hidden within, evaluate the next cell
        if(rowspan == $td.data('rowspan')) continue;

        // If this row is hidden copy the values to the next row
        if(!$tr.is(':visible') && rowspan > 0) {
          $clone = $td.clone();
          // right now, the script doesn't care about copying data, 
          // but here is the place to implement it
          $clone.data('rowspan', $td.data('rowspan') - 1);
          $clone.data('posx', $td.data('posx'));
          $clone.attr('rowspan',  rowspan);
          $clone.addClass('tmp');

          // Insert the temp node in the correct position
          // Get the current cell position
          cellPos = getColumnVisiblePostion($trFirst, $td);

          // if  is the last just append it
          if(cellPos == $trFirst.children().length - 1){
            $trTarget.append($clone);
          }
          // Otherwise, insert it before its closer sibling
          else {
            tdsTarget = $trTarget.children();
            for(i = 0; i < tdsTarget.length; i++){
              cellTargetPos = getColumnVisiblePostion($trFirst, $(tdsTarget[i]));
              if(cellPos < cellTargetPos){
                $(tdsTarget[i]).before($clone);  
                break;
              }
            }                
          }          
        }
      } 
    }

    // remove tmp nodes from the previous row 
    if(rowIdx > 0){
      $tr = $(trs[rowIdx-1]);
      if(!$tr.is(':visible')){
        $tr.children(".tmp").remove();  
      }

    } 
  }
}

// this function calculates the position of a column 
// based on the visible position
function getColumnVisiblePostion($firstRow, $cell){
  var tdsFirstRow = $firstRow.children();
  for(var i = 0; i < tdsFirstRow.length; i++){
    if($(tdsFirstRow[i]).data('posx') == $cell.data('posx')){
      return i;
    }
  }
}

function add_delete_buttons() {
  var $all_rows = $("tr");
  $all_rows.each(function () {
    // TR to remove
    var $tr = $(this);
    var delete_btn = $("<button>").text("x");
    delete_btn.on("click", function () {
      $tr.hide();
      calculate_max_rowspans();
      $(window).trigger("tr_gone", $tr);
    });
    var delete_cell = $("<td>");
    delete_cell.append(delete_btn);
    $(this).append(delete_cell);
  });
}

function add_come_back_button(tr) {
  var $tr = $(tr);
  var come_back_btn = $("<button>").text("come back " + $tr.attr("color_class"));
  come_back_btn.css({"background": $(tr).css("background")});
  come_back_btn.on("click", function () {
    $tr.show();
    come_back_btn.remove();
    calculate_max_rowspans();
  });
  $("table").before(come_back_btn);
}

Если у вас есть какие-либо вопросы или комментарии, дайте мне знать.

Ответ 3

Я предполагаю, что вы хотите, чтобы строки смещались вверх, когда вы скрываете строку, но вы не хотите, чтобы ячейки сдвигались влево.

Вот что я получил http://codepen.io/anon/pen/prDcK

Я добавил два правила CSS:

#come_back_container{height: 30px;}
td[rowspan='0']{background-color: white;}

Вот html, который я использовал:

<div id="come_back_container"></div>
<table id="dynamic_table" cellpadding=7></table>
<table id="dynamic_table2" cellpadding=7>
  <tr style="background-color: red">
    <td rowspan="5">a</td>
    <td rowspan="1">b</td>
    <td rowspan="5">c</td>
    <td rowspan="1">d</td>
    <td rowspan="2">e</td>
  </tr>
  <tr style="background-color: grey">
    <td rowspan="0">f</td>
    <td rowspan="1">g</td>
    <td rowspan="0">h</td>
    <td rowspan="1">i</td>
    <td rowspan="0">j</td>
  </tr>
  <tr style="background-color: blue">
    <td rowspan="0">k</td>
    <td rowspan="1">l</td>
    <td rowspan="0">m</td>
    <td rowspan="1">n</td>
    <td rowspan="1">o</td>
  </tr>
  <tr style="background-color: yellow">
    <td rowspan="0">p</td>
    <td rowspan="1">q</td>
    <td rowspan="0">r</td>
    <td rowspan="1">s</td>
    <td rowspan="2">t</td>
  </tr>
  <tr style="background-color: green">
    <td rowspan="0">u</td>
    <td rowspan="1">v</td>
    <td rowspan="0">w</td>
    <td rowspan="1">x</td>
    <td rowspan="0">y</td>
  </tr>
</table>

Первое правило - просто сохранить верхний край таблицы в одном и том же месте. Второе правило состоит в том, чтобы сделать ячейки пустыми, смешавшись с фоном, поэтому измените их соответствующим образом.

Наконец, вот js:

$(function () {
  //firstTable()

  var myTb2 = new dynamicTable();
  myTb2.createFromElement( $("#dynamic_table2") );
  myTb2.drawTable()

  $(window).on("tr_hide", function (e,data){
    var tbl = data.ctx,
        rowIndex = data.idx;
    tbl.hideRow.call(tbl, rowIndex);
  })
  $(window).on("tr_show", function (e,data){
    var tbl = data.ctx,
        rowIndex = data.idx;
    tbl.showRow.call(tbl, rowIndex);
  })
})

function dynamicTableItem(){
  this.height = null;
  this.content = null;
}

function dynamicTableRow(){
  this.color = null;
  this.items = []
  this.show = true

  this.setNumColumns = function(numCols){
    for(var i=0;i<numCols;i++){
      var item = new dynamicTableItem(); 
      item.height = 0;
      this.items.push(item)
    }
  }

  this.addItem = function(index, height, content){
    var item = new dynamicTableItem();
    item.height = height;
    item.content = content;
    if(index>=this.items.length){ console.error("index out of range",index); }
    this.items[index] = item;
  }
}

function dynamicTable(){
  this.element = null;
  this.numCols = null;
  this.rows = []

  this.addRow = function(color){
    var row = new dynamicTableRow();
    row.color = color;
    row.setNumColumns(this.numCols)
    var length = this.rows.push( row )
    return this.rows[length-1]
  }
  this.drawTable = function(){
    this.element.empty()

    var cols = [],
        rowElements = [];
    for(var i=0;i<this.numCols;i++){
      cols.push( [] )
    }

    for(var r=0; r<this.rows.length; r++){
      var row = this.rows[r]
      if(row.show){
        var $tr = $("<tr>"),
            delete_cell = $("<td>"),
            delete_btn = $("<button>").text("x")
        var data = {ctx: this, idx: r};
        delete_btn.on("click", data, function(e){
          $(window).trigger("tr_hide", e.data);
        })
        delete_cell.addClass("deleteCell");
        $tr.css( {"background": row.color} );

        delete_cell.append(delete_btn);
        $tr.append(delete_cell);
        this.element.append($tr);
        rowElements.push( $tr );

        for(var i=0; i<row.items.length; i++){
          cols[i].push( row.items[i] );
        }
      }
    }

    for(var c=0; c<cols.length; c++){
      var cellsFilled = 0;
      for(var r=0; r<cols[c].length; r++){
        var item = cols[c][r]
        var size = item.height;
        if(r>=cellsFilled){
          cellsFilled += (size>0 ? size : 1);
          var el = $("<td>").attr("rowspan",size);
          el.append(item.content);
          rowElements[r].children().last().before(el);
        }
      }
    }
  }

  this.hideRow = function(rowIndex){
    var row = this.rows[rowIndex]
    row.show = false; 

    var come_back_btn = $("<button>").text("come back");
    come_back_btn.css( {"background": row.color} );
    var data = {ctx:this, idx:rowIndex};
    come_back_btn.on("click", data, function(e){
      $(window).trigger("tr_show", e.data);
      $(this).remove();
    });
    $("#come_back_container").append(come_back_btn);

    this.drawTable();
  }

  this.showRow = function(rowIndex){
    this.rows[rowIndex].show = true;
    this.drawTable();
  }

  this.createFromElement = function(tbl){
    this.element = tbl;
    var tblBody = tbl.children().filter("tbody")
    var rows = tblBody.children().filter("tr")
    this.numCols = rows.length

    for(var r=0;r<rows.length;r++){
      var row = this.addRow( $(rows[r]).css("background-color") );
      var items = $(rows[r]).children().filter("td");

      for(var i=0;i<items.length;i++){
        var item = $(items[i]);
        var height = parseInt(item.attr("rowspan"));
        var contents = item.contents();
        row.addItem(i,height,contents);
      }
    }
    //console.log(this); 
  }

}

function firstTable(){
  var myTable = new dynamicTable();
  myTable.element = $("#dynamic_table");
  myTable.numCols = 5

  var red = myTable.addRow("red"); 
  red.addItem(0,5);
  red.addItem(1,1);
  red.addItem(2,5);
  red.addItem(3,1);
  red.addItem(4,2);

  var white = myTable.addRow("grey");
  //white.addItem(0,0);
  white.addItem(1,1);
  //white.addItem(2,0);
  white.addItem(3,1);
  //white.addItem(4,0);

  var blue = myTable.addRow("blue");
  //blue.addItem(0,3);  //try uncommenting this and removing red
  blue.addItem(1,1);
  //blue.addItem(2,0);
  blue.addItem(3,1);
  blue.addItem(4,1);

  var yellow = myTable.addRow("yellow");
  //yellow.addItem(0,0);
  yellow.addItem(1,1);
  //yellow.addItem(2,0);
  yellow.addItem(3,1);
  yellow.addItem(4,2);

  var green = myTable.addRow("green");
  //green.addItem(0,0);
  green.addItem(1,1);
  //green.addItem(2,0);
  green.addItem(3,1);
  //green.addItem(4,0);

  myTable.drawTable();
}

Я попытался использовать понятные имена переменных и методов, но если у вас есть квесты, просто спросите.

PS- Я знаю, что сейчас нет простого способа добавить контент в ячейки, но вы только просили исчезнуть строки.