Построение таблицы с данными JSON (улучшение кода)

У меня есть объект JSON, как показано ниже, и я конвертирую JSON в HTML с помощью ниже оберточной функции

Порция, полученная из JSON:

var data = { "Column Headers" : [ // Hierarchy is not limited to two levels, it is n level
    [  "Column1" , ["Column1 SubColumn 1", "Column1 SubColumn 2"] ], 
    [  "Column2" , ["Column2 SubColumn 1", "Column1 SubColumn 2"] ],
    [  "Column3" , ["Column3 SubColumn 1", "Column1 SubColumn 2"] ]  
],
"Columns under subColumns" : ["I am column 1", "I am column 2"],
"Data for Table" :[
    { "name": ["Group 1","Sub Group 1"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]},
    { "name": ["Group 1","Sub Group 2"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]},
    { "name": ["Group 2","Sub Group 1"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]},
    { "name": ["Group 2","Sub Group 2"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]}
], // here the hierarchy is not limited to two sub groups.. it could be any number..
"row group headers" : ["Group 1 Header", "Sub group Header"]
}

Он должен быть скомпилирован в HTML как в этой скрипте http://jsfiddle.net/RwdWq/

И вот код, который я написал

 var render = function(data){
    var formattedData = {};
    function returnRowsData( obj ) {
      return obj["Data for Table"];
    }
    function returnColsData(obj) {
      return obj["Column Headers"];
    }
    function rowLabels(obj) {
      return obj["row group headers"];
    }
    function bottomColLabels(obj) {
      return obj["Columns under subColumns"];
    }
    function simplifyCols(obj) {
      var reform = {
        table : {}
      }, bottomLabs = bottomColLabels(data);
      var y = 0;
      for(var i = 0, l = obj.length; i < l; i++){
        var key = obj[i];
        key.push(bottomLabs);
        for (var j = 0, m = key.length; j < m; j++) {
           var colspan = 1;
           for (var k = j + 1; k < m; k++) {
              colspan *= key[k].length; 
           }
           reform.table[j] = reform.table[j] || [];
           if (j == 0) {
            y += colspan;
           }
           reform.table[j].push({
             span : colspan,
             data : key[j]
           });
          }
        }
        reform.count = y;
        return reform;
     }
     var formatted = simplifyCols( returnColsData( data[0]) ) || {};
     var cols = formatted.table;
     //console.log(cols);
     formattedData.cols = cols;
     var keys = Object.keys(cols).sort(function(a, b){
         return a - b;
     });
     var table = document.createElement('table');
     for (var i = 0, l = keys.length - 1; i < l; i++) {
        var keyData = cols[keys[i]], tr = document.createElement('tr');
        if (i == 0) {
            var rLs = rowLabels(data[0]);
            for (var rL = 0; rL < rLs.length; rL++) {
              var td = document.createElement('th');
              td.innerHTML = rLs[rL];
                td.rowSpan = keys.length;
                td.className = "rowLabel";
                tr.appendChild(td);
            }
        }
        table.appendChild(tr);
        for (var j = 0, m = keyData.length; j < m; j++) {
            var eleData = keyData[j].data;
            if(eleData instanceof Array){
             for (var k = 0, n = eleData.length; k < n; k++) {
               var td = document.createElement('td');
               td.innerHTML = eleData[k];
               td.colSpan = keyData[j].span;
               td.className = "colHeaders";
               tr.appendChild(td);
              }
             }else{
               var td = document.createElement('td');
               td.innerHTML = keyData[j].data;
               td.colSpan = keyData[j].span;
               td.className = "colHeaders";
               tr.appendChild(td);
             }
        }
        table.appendChild(tr);
     }
     var tr = document.createElement('tr');
     var noOfbottomLabs = formatted.count ?  formatted.count / bottomLabs.length : bottomLabs.length;
                for (var i = 1; i <= noOfbottomLabs; i++) {
                    for (var j = 0; j < bottomLabs.length; j++) {
                        var td = document.createElement('td');
                        td.innerHTML = bottomLabs[j];
                        td.className = "bottomLabs";
                        tr.appendChild(td);
                    }
                }
                table.appendChild(tr);
                function setToValue(obj, value, path) {
                    var parent = obj;
                    for (var i = 0; i < path.length - 1; i += 1) {
                        parent[path[i]] = parent[path[i]] || {}
                        parent = parent[path[i]];
                    }
                    parent[path[path.length-1]] = value;
                }   
                var rowsData = returnRowsData(data), tempRows = {}, tempArr = {};
                for (var i = 0, l = rowsData.length; i < l ; i++) {
                    var names = rowsData[i].name, _data  = rowsData[i].data;
                    setToValue(tempRows, _data, names);
                }
                var similiar = {};
                for (var ele = 0, lent = rowsData.length; ele < lent; ele++) {
                    var curD = rowsData[ele], tr = document.createElement('tr');
                    for (var i = 0; i < curD.name.length; i++) {
                        var td = document.createElement('td');
                        td.innerHTML = curD.name[i] || "-";
                        td.setAttribute('val', curD.name[i]);
                        td.className = "rowHeader";
                        similiar[curD.name[i]] = 0;
                        tr.appendChild(td);
                    }
                    var merg = [];
                    merg = [].concat.apply( merg, curD.data);
                    for (var i = 0; i < merg.length; i++) {
                        var td = document.createElement('td');
                        td.innerHTML = merg[i] || "-";
                        td.className = "tdData";
                        tr.appendChild(td);
                    }
                    table.appendChild(tr);
                    console.log(merg);
                }
                document.body.appendChild(table);
                for (var text in similiar) {
                    var elements = document.querySelectorAll('[val="' + text + '"]');
                    elements[0].rowSpan = elements.length;
                    for (var j = 1; j < elements.length; j++) {
                        var v = elements[j];
                        v.parentNode.removeChild(v);
                    }
                }
        }

И в настоящее время он работает следующим образом http://jsfiddle.net/RwdWq/3/

Как я могу решить эту проблему, и страница несколько раз мертва, если данные большие. Помогите мне улучшить производительность, и я хочу проверить возможность ее создания без таблицы.

Ответ 1

Вот мое решение на основе jQuery:

<!DOCTYPE html>
<html>
    <head>
      <title>json2html</title>
      <style>
        #target     { display: none }
        td          { text-align: center; border: 1px solid gray; }
        .colHeaders { }
        .rowLabel   { }
        .bottomLabs { }
        .rowHeader  { }
        .tdData     { }
      </style>
      <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
      <script type="text/javascript">
      var json2html = (function () {
          var json2html = function(json) {
              this.data = json;
              this.rgh  = json["row group headers"];
              this.ch   = json["Column Headers"];
              this.dft  = json["Data for Table"];
              this.cus  = json["Columns under subColumns"];
              this.sc   = this.cus.length;

              var depth = 0;
              for (var i in this.ch) {
                  depth = Math.max(depth, this.ch[i].length);
              }
              this.depth = depth;
          }

          function repeat(pattern, count) {
              var result = pattern;
              while (--count > 0) {
                  result += pattern;
              }
              return result;
          }

          function join(data, td) {
              try {
                  return td + data.join('</td>' + td) + '</td>';
              } catch (e) {
                  return td + data + '</td>';
              }
          }

          function renderSubHeader(data, index, sc) {
              var html = '';
              $.each(data, function() {
                  var cs = sc;
                  for (var i = index + 1; i < this.length; i++) {
                      cs *= this[i].length;
                  }
                  var value = (typeof this[index] != 'undefined') ? this[index] : '';
                  var cell = join(value, '<td class="colHeaders"' + ((cs > 1) ? ' colspan="'+cs+'">' : '>'));
                  if (index > 1) {
                      for (var i = index - 1; i > 0; i--) {
                          html += repeat(cell, this[i].length);
                      }
                  } else {
                      html += cell;
                  }
              });
              return(html);
          }

          function renderHeader(data) {
              var html = '<tr>';
              html += join(data.rgh, '<th rowspan="'+(data.depth + 1)+'" class="rowLabel">');
              html += renderSubHeader(data.ch, 0, data.sc);
              html += '</tr>';
              for (var index = 1; index < data.depth; index++) {
                  html += '<tr>';
                  html += renderSubHeader(data.ch, index, data.sc);
                  html += '</tr>';
              };
              return html;
          }

          function renderColHeader(data) {
              var html = '<tr>';
              $.each(data.dft[0].data, function(index) {
                  html += join(data.cus, '<td class="bottomLabs">');
              });
              return html+'</tr>';
          }

          function renderData(data) {
              var html = '';
              $.each(data.dft, function(nr) {
                  html += '<tr>';
                  html += join(this.name, '<td class="rowHeader">');
                  $.each(this.data, function() {
                      html += join(this, '<td class="tdData">');
                  });
                  html += '</tr>';
              });
              return html;
          }

          function mergeCells(cells, attr) {
              var rs = 1;
              var old = null;
              cells.each(function() {
                  if (old == null) {
                      old = $(this);
                      rs = 1;
                  } else {
                      if ($(this).text() == old.text()) {
                          rs++;
                          $(this).remove();
                      } else {
                          if (rs > 1) {
                              old.attr(attr, rs);
                              rs = 1;
                          }
                          old = $(this);
                      }
                  }
              });
              if (rs > 1) {
                  old.attr(attr, rs);
              }
          }

          json2html.prototype.renderTable = function(thead, tbody) {
              var startTime = new Date();
              thead.html(
                  renderHeader(this) + 
                  renderColHeader(this)
              );
              tbody.html(renderData(this));
              for (var i = this.rgh.length; i > 0; i--) {
                  mergeCells($('td:nth-child('+i+')', tbody), 'rowspan');
              };
              var endTime = new Date();
              console.log('renderTable('+this.dft.length+' rows): ' + (endTime - startTime) + 'ms');
          }

          return json2html;
      })();

      //==================================================================================================

      var data1 = {
          "Column Headers": [
              ["Column1", ["Column1 SubColumn 1", "Column1 SubColumn 2"] ], 
              ["Column2", ["Column2 SubColumn 1", "Column1 SubColumn 2"] ],
              ["Column3", ["Column3 SubColumn 1", "Column1 SubColumn 2"] ]
          ],
          "Columns under subColumns": [
              "I am column 1", 
              "I am column 2"
          ],
          "Data for Table": [
              { "name": ["Group 1","Sub Group 1"], "data": [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]] },
              { "name": ["Group 1","Sub Group 2"], "data": [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]] },
              { "name": ["Group 1","Sub Group 2"], "data": [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]] },
              { "name": ["Group 1","Sub Group 2"], "data": [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]] },
              { "name": ["Group 2","Sub Group 1"], "data": [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]] },
              { "name": ["Group 2","Sub Group 2"], "data": [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]] }
          ],
          "row group headers": [
              "Group 1 Header", 
              "Sub group Header"
          ]
      };

      var data2 = { 
          "Column Headers" : [
              [ "Column1", ["Column1 SubColumn 1", "Column1 SubColumn 2", "Column1 SubColumn 3"], ["abc", "Hello1"] ], 
              [ "Column2", ["Column2 SubColumn 1", "Column2 SubColumn 2", "Column2 SubColumn 3"], ["def", "Hello2"] ],
              [ "Column3", ["Column3 SubColumn 1", "Column3 SubColumn 2", "Column3 SubColumn 3"], ["ghi", "Hello3"] ]  
          ],
          "Columns under subColumns": [
              "I am column 1", 
              "I am column 2"
          ],
          "Data for Table": [
              { "name": ["Group 1","Sub Group 1", "abc"], "data": [[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20]] },
              { "name": ["Group 1","Sub Group 1", "def"], "data": [[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20]] },
              { "name": ["Group 2","Sub Group 1", "ghi"], "data": [[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20]] },
              { "name": ["Group 2","Sub Group 2", "jkl"], "data": [[0,1],[1,2],[45,20],[0,86],[1,2],[45,20],[0,1],[1,2],[45,20],[0,86],[1,2],[45,20],[0,1],[1,2],[45,20],[0,86],[1,2],[45,20]] }
          ], 
          "row group headers": [
              "Group 1 Header", 
              "Sub group Header",
              "abc"
          ]
      };

      var data3 = { 
          "Column Headers" : [
              [ "Column1", ["Column1 SubColumn 1", "Column1 SubColumn 2", "Column1 SubColumn 3"], ["abc", "Hello1"] ], 
              [ "Column2", ["Column2 SubColumn 1", "Column2 SubColumn 2", "Column2 SubColumn 3"] ],
              [ "Column3", ["Column3 SubColumn 1", "Column3 SubColumn 2", "Column3 SubColumn 3"], ["ghi"] ]  
          ],
          "Columns under subColumns": [
              "I am column 1", 
              "I am column 2"
          ],
          "Data for Table": [
              { "name": ["Group 1"], "data": [[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20]] },
              { "name": ["Group 1"], "data": [[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20]] },
              { "name": ["Group 2"], "data": [[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20],[0,1],[1,2],[45,20],[0, 1],[1,2],[45,20]] },
              { "name": ["Group 2"], "data": [[0,1],[1,2],[45,20],[0,86],[1,2],[45,20],[0,1],[1,2],[45,20],[0,86],[1,2],[45,20]] }
          ], 
          "row group headers": [
              "Group 1 Header", 
          ]
      };

      $(function () {
          var data = [data1, data2, data3];
          $('#dataset').change(function() {
              $('#target').hide();
              if (this.value != '') {
                  $('#target thead').empty();
                  $('#target tbody').empty();
                  var html = new json2html(data[this.value]);
                  html.renderTable($('#target thead'), $('#target tbody'));
                  $('#target').fadeIn('slow');
              }
          });
      });
      </script>    
    </head>
    <body>
      <label for="dataset">Choose a dataset</label>
      <select id="dataset">
          <option value=""></option>
          <option value="0">Data 1</option>
          <option value="1">Data 2</option>
          <option value="2">Data 3</option>
      </select>          
      <table id="target">
        <thead>
        </thead>
        <tbody>
        </tbody>
      </table>
    </body>

JS fiddle здесь.   

Ответ 2

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

1. Ваша структура json ужасна. Я предлагаю вам немного изменить его. Это мое:

EDIT:. Как Bergi, моя структура точно не определяет порядок ячеек в заголовках. Так лучше.

var data = {

    "Column Headers" : {
        "Column1" : { 
            "Column1 SubColumn 1" : ["I am column 1", "I am column 2"], 
            "Column1 SubColumn 2" : ["I am column 1", "I am column 2"]
        },
        "Column2" : {
            "Column2 SubColumn 1": ["I am column 1", "I am column 2"], 
            "Column2 SubColumn 2" : ["I am column 1", "I am column 2"]
        },
        "Column3" : {
            "Column3 SubColumn 1": ["I am column 1", "I am column 2"], 
            "Column3 SubColumn 2": ["I am column 1", "I am column 2"]
        } 
    },

    "Row Headers" : {
        "Column1" : ["Column1 SubColumn 1", "Column1 SubColumn 2"],
        "Column2" : ["Column2 SubColumn 1", "Column1 SubColumn 2"],
        "Column3" : ["Column3 SubColumn 1", "Column1 SubColumn 2"] 
    },

    "Data for Table" : [
    [0, 1, 1, 2, 45, 20, 0, 1, 1, 2, 45, 20],
    [0, 1, 1, 2, 45, 20, 0, 1, 1, 2, 45, 20],
    [0, 1, 1, 2, 45, 20, 0, 1, 1, 2, 45, 20],
    [0, 1, 1, 2, 45, 20, 0, 1, 1, 2, 45, 20]
    ]
}

Он по-прежнему не идеален, но, по крайней мере, он читается человеком.

2. Алгоритм разбора Json

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

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

function parseJson() { ... } 

function parseHeader() { ... } 

function parseRows() { ... } 

function renderColumnsHeaders() { ... }

function renderRow() { ... }

Также вы можете сделать это как объект. Затем вы можете легко добавлять различные функции, такие как сортировка, фильтрация или другие материалы.

var MyDataTable = (function () { // this is namespace

    var MyDataTable = function(json) { // this is object constructor, it will probably just loads data
        this.data = json;
    }

    // these functions are visible only inside namespace
    function parseJson() { ... } 

    function parseHeader() { ... } 

    function parseRows() { ... } 

    function renderColumnsHeaders() { ... }

    function renderRow() { ... }

    MyDataTable.prototype.renderTable = function (where) { ... } // this will render whole table

    MyDataTable.prototype.filterTableBy = function () { ... } // filter that you can implement later


return MyDataTable; // returns constructor
})();


var data = { ... } // some data

var firstTable = new MyDataTable(data);
firstTable.renderTable(someDivOrAnyElementYouWant);

Код, такой как almoast profi;) Прост в обслуживании, легко расширяется, легко сделать плагин из него;)

3. Улучшение производительности рендеринга таблицы

Почему рендеринг таблицы так медленный? Ну, это, вероятно, не из-за таблицы, а из-за большого html, который вы пытаетесь включить в один раз. Не имеет значения, используете ли вы javascript для создания элементов DOM или если вы пишете элементы DOM непосредственно в html. Это всегда занимает некоторое время, чтобы сделать это, если есть много элементов.

К сожалению, рендеринг html является синхронным. Это означает, что все заморожено, пока ваша функция не будет выполнена. Он не работает так же, как, например, анимация или ajax (он называется "Асинхронный JavaScript и XML" для резонанса).

Первый параметр, который у вас есть, - это использование ajax и загрузка таблицы шаг за шагом. Установите некоторое ограничение на количество строк, которое вы можете загрузить сразу. Затем: * call ajax * получить json из ajax * разобрать * получать линии и отображать их * повторение Тогда ваш браузер не замерзнет. Рендеринг будет по-прежнему синхронным, но между двумя вызовами рендеринга будет "окно", поэтому здесь происходят различные события и другие вещи. Просто много коротких шипов не затормозит ваш браузер так же, как один длинный шип.

Другая опция - это данные загрузки в зависимости от позиции пользователя. Пользователь не может видеть сразу все данные. Это очень похоже на первый вариант и называется "бесконечным прокруткой". Вы уже могли видеть, что на facebook, картинах Google... для этого есть плагин для jquery.

Последний вариант - это использование setInterval или setTimeout собственных функций JavaScript. Этот метод также работает с большим количеством коротких шипов, за исключением того, что вы не используете ajax. Сначала загружаете все данные в javascript. Затем вы выполняете их шаг за шагом с помощью этих функций. Поскольку эти функции работают асинхронно, он работает хорошо. Но нужно немного глубже понять. У меня есть пример асинхронного вызова. Есть divs, которые меняют цвета, как радуга, но это не анимация: http://windkiller.g6.cz/async/async-html-render.html

EDIT:

Положительность делает его без таблицы

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

Ответ 3

ОБНОВЛЕНИЕ 1

Я попробовал механизм микро-шаблонов John Resig и нашел слишком много проблем с ним, поэтому я чувствую, что подчеркивание является хорошим выбором для этой проблемы.

ОБНОВЛЕНИЕ 2

Смотрите: https://stackoverflow.com/questions/17298292/building-the-table-html-from-json-data-with-js-template/17353525#17353525

Вот скрипка, которая должна работать для ваших данных, обновленная в другом ответе, но предоставляющая здесь будущих читателей этого вопроса (и stackoverflow не позволяет отвечать содержать скрипку без кода, поэтому я вставляю сюда как а):

Использование подчеркивания и крошечного фрагмента jQuery, но, вероятно, недостаточно, чтобы гарантировать его, если вы еще не используете jQuery (легко заменить):

http://jsfiddle.net/Wzpde/8/

<!-- BEGIN: Underscore Template Definition. -->
<script type="text/template" class="template">    
    <!-- collect some info, better to do outside of temmplate,
         but to keep with the idea of doing it all in a template -->
    <% 
    //////
    // Collect the header data into the hdrs variable
    ////

    var colsUnderSubCols = data["Columns under subColumns"]
    var hdrs = [[]]; // Initialize the hdrs array as an array with one array element.

    // Walk through each column header
    _.each(data["Column Headers"], function(tophdr, tind){ 
        var n=1;

        // First take the first row of headers, since they are specified in a different format
        // than subsequent headers (they are specified as strings, not arrays).

        hdrs[0].push(tophdr.slice(0, 1));

        // Next, take the subsequent arrays that represent the rest of the sub-columns.
        // Note that these do not include the final columns, as those are also specified
        // differently due to the awkward JSON structure and are tacked on at the very end.

        var otherhdrs = tophdr.slice(1, tophdr.length);

        // walk through each of the array-based sub-columns

        for(var m=0; m<otherhdrs.length; m++){

            // As we are building the header columns by name, we need to iterate over the
            // over the number of headers that will be in the row representing this depth
            // of sub-column, so we aggregate the multiplicative value of each:

            n *= otherhdrs[m].length;

            // using the final number of columns for this depth, we can determine the 
            // column name by the remainder of our iteration index divided by the length
            // of unique column names at our current column header depth.  

            for(var i=0; i<n; i++){

                // Initialize this container of headers for this depth if it is not 
                // yet initialized:

                if(!hdrs[m+1]) hdrs[m+1] = [];

                // i represents the iteration index into the total number of column headers
                // at the current column header depth, and otherheaders[m] is the array of
                // unique column header names at this depth, so taking the modulo allows us
                // the correct column name:

                hdrs[m+1].push(otherhdrs[m][i%otherhdrs[m].length]);
            }
        }
    });

    // Finally, the last set of column is not specified like either the first column or the 
    // sub-columns in the JSON structure provided, so we tack those on last.
    // They can be tacked on by iterating over the last sub-header array with a nested 
    // iteration of these final column names:

    var finalhdrs = [];
    for(var i=0; i<hdrs[hdrs.length-1].length; i++){
        for(var j=0; j<colsUnderSubCols.length; j++){
            finalhdrs.push(colsUnderSubCols[j]);
        }
    }

    // Push them as the last set of header names:

    hdrs.push(finalhdrs);

    //////
    // Collect group rowspan information into a groupspan variable
    ////

    var dft = data["Data for Table"];
    groupspan = [];

    // Each row is going to have some <td> applied, but it depends on whether or not
    // we previously saw the group name, so that can be done as a look-behind so-to-speak:

    for(var m=1; m<dft.length; m++){

        //Initialize the look-behind groupspan arrays if not previously initialized:
        // This is certainly an opportunity for some optimization.  But
        // as it is can provide a baseline for functionality testing:

        if(! groupspan[m-1]){ 
            groupspan[m-1] = [];
            _.each(dft[m-1].name,function(item,ind){ groupspan[m-1][ind] = 1; });
        }

        //Initialize the groupspan arrays if not previously initialized:
        // This is certainly an opportunity for some optimization.  But
        // as it is can provide a baseline for functionality testing:

        if(! groupspan[m]){
            groupspan[m] = [];
            _.each(dft[m].name,function(item,ind){ groupspan[m][ind] = 1; });
        }

        //Now that initialization of the look-behind goupspan info and the
        // groupspan info has taken place, we can walk through them and
        // build out some information that lets the rowspans be built later.

        //Provided a 3-deep group configuration and 4 rows of data, we wind up
        // with a structure like the following:
        //   [ [ sp1, sp2, sp3 ], [ sp4, sp5, sp6 ], [ sp7, sp8, sp9 ], [ sp10, sp11, sp12 ] ]
        // If an sp* above is == 0, then we skip putting in a <td> 
        // Otherwise, we prepend a <td> with the rowspan set to the sp* variable
        // So essentially, we are collecting whether or not a <td> should be prepended
        // to the row, and if so,  what its rowspan should be:

        for(var n=0; n<dft[m].name.length; n++){
            if(!!dft[m-1] && dft[m].name[n] === dft[m-1].name[n]){
                groupspan[m-1][n]++;
                groupspan[m][n] = 0;
            }else{
                break;
            }
        }
    }

    %>

<table>
    <% 
    //////
    // Now we can build out the table using the information we've gathered above:
    ////

    // For each header in the hdrs variable populated above:

    for(var i=0; i<hdrs.length; i++){
    %>
    <tr>
        <%

        // Be sure to put the group headers first, before the column headers:

        if(i===0){
            _.each( data["row group headers"], function( hdr ){ 
            %>
                <td rowspan="<%- hdrs.length %>" >
                     <%- hdr %>
                </td>
            <% 
            });
        }

        // Now put the column headers in:

        _.each( hdrs[i], function( hdr ){ %>
            <td colspan="<%- hdrs[hdrs.length-1].length/hdrs[i].length%>">
                <%- hdr %>
            </td>
        <% }); %>
    </tr>
    <%
    } 

    // Done with group and column headers, now for each data row
    // (but not the group titles, which get injected into the rows below
    //  with appropriate rowspan):

    for(var d=0; d<dft.length; d++){
        var dftdata = dft[d].data;
    %>
        <tr>
            <%

            // Using the groupspan information detailed above,
            // we can layer in the group titles with the appropriate
            // number of <td> and appropriate rowspan attributes:

            for(var i=0; i<groupspan[d].length;i++){
                if(groupspan[d][i] === 0){ 
                    continue;
                }else{
            %>
                <td rowspan="<%- groupspan[d][i] %>" >
                    <%- dft[d].name[i] %>
                </td>
            <%
                }
            }

            // All of the heavy lifting is done, now we can put the data into the table:

            _.each( dftdata, function( item ){  
                for(var di=0; di<data["Columns under subColumns"].length; di++){ 
                %>
                <td>
                    <%- item[di] %>
                </td>
                <%
                }
                }); %>
        </tr>
    <%
    }
    %>

</table>
</script>

Данные с вложенными столбцами и группами:

var data = { "Column Headers" : [ // Hierarchy is not limited to two levels, it is n level
    [  "Column1" , ["Column1 SubColumn 1", "Column1 SubColumn 2", "Column1 SubColumn 3"], ["abc", "Hello", "A"], ["t1","t2"] ], 
    [  "Column2" , ["Column2 SubColumn 1", "Column2 SubColumn 2", "Column2 SubColumn 3"], ["abc", "Hello", "A"], ["t1","t2"] ],
    [  "Column3" , ["Column3 SubColumn 1", "Column3 SubColumn 2", "Column3 SubColumn 3"], ["abc", "Hello", "A"], ["t1","t2"] ]  
],
"Columns under subColumns" : ["I am column 1", "I am column 2"],
"Data for Table" :[
    { "name": ["Group 1","Sub Group A", "SubSub1"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]},
    { "name": ["Group 1","Sub Group B", "SubSub2"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]},
    { "name": ["Group 2","Sub Group A", "SubSub3"], "data" : [[0,1],[1,2],[45,20],[0,1],[1,2],[45,20]]},
    { "name": ["Group 2","Sub Group B", "SubSub4"], "data" : [[0,1],[1,2],[45,20],[0,86],[1,2],[45,20]]}
], // here the hierarchy is not limited to two sub groups.. it could be any number..
"row group headers" : ["Group 1 Header", "Sub group Header 1", "Sub group Header 2"]
}

_.templateSettings.variable = "data";

// Grab the HTML out of our template tag and pre-compile it.
var template = _.template($( "script.template" ).html()); 
// Render the underscore template and inject it into the DOM
$("body").prepend(template(data));

ОБНОВЛЕНИЕ 3

Новая скрипка устраняет проблему с группами:

http://jsfiddle.net/Ju7xz/6/

Ответ 4

Я знаю из личных тестов и многих доступных для тестирования случаев, что манипуляция DOM влияет на производительность вашего кода (вы делаете одно создание DOM плюс три другие операции DOM на каждом отдельном node ваших данных.)

Кроме того, в меньшей степени обработка/конкатенация многих строк, которые неизменяемы в JavaScript, является фактором этого кода. Например, при создании массива из 1000 номеров за раз, а их запись с использованием .join(' ') увеличивает производительность примерно на 40 раз для задания записи первых миллионов целых чисел (по сравнению с их записью по одному).

См. мой комментарий выше и дайте мне знать, если вы считаете, что это стоит того, чтобы я попытался поработать над этой проблемой. (Такие проблемы мне нравятся, но не достаточно весело, чтобы сделать это, если вы уже в основном довольны существующими ответами.)

Ответ 5

Вы можете перемещать JSON с помощью функций jQuery, например, или использовать чистый javascript, затем вы можете использовать EJS.
В альтернативе EJS вы можете использовать PURE, что совсем другое, но выглядит более мощным.