12

以下のような JSON オブジェクトがあり、以下のラッパー関数を使用して JSON を HTML に変換しています

JSON から取得した部分:

var data = { "Column Headers" : [ // Hierarchy is not limited to two levels, it is n level
    [  "Column1" , ["Column1's SubColumn 1", "Column1's SubColumn 2"] ], 
    [  "Column2" , ["Column2's SubColumn 1", "Column1's SubColumn 2"] ],
    [  "Column3" , ["Column3's SubColumn 1", "Column1's 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"]
}

このフィドルhttp://jsfiddle.net/RwdWq/のように HTML にコンパイルする必要があります。

そして、ここに私が書いたコードがあります

 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/のように機能しています

この問題を解決するにはどうすればよいですか。データが大きい場合、ページが時々死んでしまいます。パフォーマンスの改善を手伝ってください。テーブルなしで作成する可能性を確認したいです。

4

5 に答える 5

15

私はあなたの問題を解決する方法を知っています。これには複数の解決策があります。あなたのコードはかなり長く、私のソリューションはすべて、あなたが書いたほとんどすべてを書き直す必要があるため、最も重要な部分だけをここに記載します。まずはひとまず。

1.あなたのjson構造はひどいです。少し変えることをお勧めします。これは私のものです:

編集: Bergiが言ったように、私の構造はヘッダー内のセルの順序を正確に定義していません。この点ではあなたの方が優れています。

var data = {

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

    "Row Headers" : {
        "Column1" : ["Column1's SubColumn 1", "Column1's SubColumn 2"],
        "Column2" : ["Column2's SubColumn 1", "Column1's SubColumn 2"],
        "Column3" : ["Column3's SubColumn 1", "Column1's 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名前空間またはコンストラクターである可能性のある関数 render から始めました。次に、多くの小さな関数を作成し、それらの後にアルゴリズムの長い部分を作成しました。

アルゴリズムをより多くの関数に分割し、すべての関数を次のような主要なポイントに設定してみてください。

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);

そのようなコードはほぼプロフィです;)維持しやすく、拡張しやすく、プラグインを簡単に作成できます;)

3. テーブルのレンダリング パフォーマンスを改善する

テーブルのレンダリングが非常に遅いのはなぜですか? まあ、それはおそらくテーブルのためではなく、一度に含めようとしている大きなhtmlのためです。JavaScript を使用して DOM 要素を作成するか、DOM 要素を直接 html に書き込むかは問題ではありません。多くの要素がある場合、レンダリングには常に時間がかかります。

残念ながら、html レンダリングは同期的です。これは、機能が完了するまですべてが凍結されることを意味します。たとえば、アニメーションや ajax と同じようには機能しません (理由から「非同期 JavaScript および XML」と呼ばれます)。

あなたが持っている最初のオプションは、ajaxを使用してテーブルを段階的にロードすることです。一度にロードできる行数に制限を設定します。次に: * ajax を呼び出す * ajax から json を取得する * 解析する * 行を取得してレンダリングする * 繰り返す すると、ブラウザはフリーズしません。レンダリングは引き続き同期されますが、2 つのレンダリング呼び出しの間に「ウィンドウ」が存在するため、さまざまなイベントやその他の処理がここで行われます。短いスパイクが何度も発生しても、1 回の長いスパイクと同じようにブラウザがフリーズすることはありません。

別のオプションは、ユーザーの位置に応じてデータをロードすることです。ユーザーは一度にすべてのデータを表示できません。これは最初のオプションと非常に似ており、「無限スクロール」と呼ばれます。FacebookやGoogleの写真ですでにそれを見ることができます...これにはjqueryへのプラグインがあります。

最後のオプションは usesetIntervalまたはsetTimeoutnative javascript functionsです。この手法は、ajax を使用しない場合を除いて、多くの短いスパイクでも機能します。最初にすべてのデータを JavaScript でロードします。次に、これらの関数を使用して段階的にレンダリングします。これらの関数は非同期で動作するため、うまく機能します。しかし、それにはもう少し深い理解が必要です。ここに非同期呼び出しの例があります。虹のように色を変える div がありますが、アニメーションではありません: http://windkiller.g6.cz/async/async-html-render.html

編集:

テーブルなしで作る可能性

テーブルの代わりに div 要素またはリストを使用することができます。お勧めしません。テーブルは大規模なデータ用であり、ブラウザーの開発者はテーブルのパフォーマンスを向上させるために最善を尽くしています。テーブルが遅いという推測を聞いたり読んだりしたことがあるかもしれません。彼らは遅かったが、これらの時代は過ぎ去った。したがって、この場合はテーブルに固執してください。

于 2013-06-26T13:15:50.993 に答える
2

更新 1

私は John Resig のマイクロ テンプレート エンジンを試してみましたが、問題が多すぎることがわかりました。そのため、この問題にはアンダースコアが適していると思います。

更新 2

参照: https://stackoverflow.com/questions/17298292/building-the-table-html-from-json-data-with-js-template/17353525#17353525

これは、他の回答で更新されたデータに対して機能するはずのフィドルですが、この質問の将来の読者のためにここに提供します(また、スタックオーバーフローではコードなしで回答にフィドルを含めることは許可されていないため、ここにも貼り付けます):

アンダースコアとほんの少しの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>'s 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>'s 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's SubColumn 1", "Column1's SubColumn 2", "Column1's SubColumn 3"], ["abc", "Hello", "A"], ["t1","t2"] ], 
    [  "Column2" , ["Column2's SubColumn 1", "Column2's SubColumn 2", "Column2's SubColumn 3"], ["abc", "Hello", "A"], ["t1","t2"] ],
    [  "Column3" , ["Column3's SubColumn 1", "Column3's SubColumn 2", "Column3's 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/

于 2013-06-27T22:08:12.293 に答える
1

個人的なベンチマークと、オンラインで入手できる多くのテスト ケースから、DOM 操作がコードのパフォーマンスに影響を与えていることはわかっています (データのすべての単一ノードで 1 つの DOM 作成と約 3 つの他の DOM 操作を実行しています)。

また、それほどではありませんが、JavaScript では不変である多くの文字列の処理/連結が、このコードの要因になっています。たとえば、一度に 1,000 個の数値の配列を作成し、 を使用して.join(' ') 書き出すと、最初の 100 万個の整数を書き込むジョブのパフォーマンスが約 40 倍向上します (一度に 1 つずつ書き出す場合と比べて)。

上記の私のコメントを見て、この問題に取り組む価値があると思うかどうか教えてください。(このような問題は私にとっては楽しいものですが、既存の回答に基本的に満足している場合は、それを行うほど楽しいものではありません。)

于 2013-06-27T23:33:35.357 に答える
-2

たとえば、jQueryトラバース関数を使用してJSONをトラバースするか、純粋なjavascriptを使用してから、EJSを使用できます。
EJS の代わりに、まったく異なるPUREを使用できますが、より強力に見えます。

于 2013-06-27T16:22:02.093 に答える