嵌套数组与平面表的乘积

时间:2022-06-29 15:26:42

I have nested objects, depth of objects can be N, each object can have X properties as well as X objects nested to it.

我有嵌套的对象,对象的深度可以是N,每个对象都可以有X属性以及嵌套的X对象。

Problem

问题

1) I am having trouble guessing what column headers will be added to my table until the whole loop is completed. As you can see in my first row, I only had 1 column "Name" but in 2nd row I had nested objects which added more column headers to table.

1)在整个循环完成之前,我很难猜测将会添加什么列标题。正如您在第一行中看到的,我只有一个列“Name”,但是在第2行我有嵌套对象,它向表添加了更多的列标题。

2) I want to make it work with jQuery datatable which doesn't work with above scenario, as all rows doesn't have same number of columns

2)我想让它与jQuery datatable一起工作,这与上面的场景不兼容,因为所有的行都没有相同的列数。

3) I don't want to hard code anything as JSON data can be anything.

3)我不想硬编码任何东西,因为JSON数据可以是任何东西。

Here is the fiddle: https://jsfiddle.net/jwf347a4/8/

这是小提琴:https://jsfiddle.net/jwf347a4/8/

Please note that JSON is dynamic, syntax will remain same but number of objects and properties + number of child objects can vary, so no hard coding... thanks

请注意,JSON是动态的,语法将保持不变,但是对象和属性的数量和子对象的数量可以变化,所以没有硬编码……谢谢

Code

代码

var resultFromWebService = {
  //"odata.metadata": "http://localhost:21088/WcfDataService1.svc/$metadata#Categories&$select=Name,Products,Products/Currency/Symbol",
  "value": [{
    "Products": [],
    "Name": "Meat"
  }, {
    "Products": [{
      "Currency": {
        "ID": 47,
        "Num": 826,
        "Code": "GBP",
        "Description": "United Kingdom Pound",
        "DigitsAfterDecimal": 2,
        "Symbol": "\u00a3",
        "Hide": null,
        "Priority": 1
      },
      "ID": 2425,
      "Name": "303783",
      "ExpiryDate": "2014-02-22T00:00:00",
      "CurrencyID": 47,
      "Sales": "0.00000000000000000000",
      "PreTaxProfitOnProduct": null,
      "Assets": "0.30000000000000000000",
      "BarCode": null,
      "Worth": "0.20000000000000000000",
      "MarketValue": null
    }],
    "Name": "Produce & Vegetable"
  }]
};


var createBody = true;
var $table = $('<table id="myTable" class="defaultResultsFormatting"/>');
var $thead = $('<thead />');
var $tbody = $('<tbody />');
var $headRow = $('<tr/>');
var $parentCells = null;
var columnHeaders = [];
$table.append($thead);
var $resultContainer = createResultsTable(resultFromWebService);
$("#resultTableContainer").append($resultContainer);

//$('#myTable').dataTable();

function createResultsTable(data, title) {
  if (data) { // && data.length > 0) {
    if (createBody) {
      $thead.append($headRow);
      $table.append($tbody);
      createBody = false;
    }
    if (data.length > 0) {
      addColumnHeaders(data[0]);
      $.each(data, function(index, e) {
        populateTable(e);
      });
    } else {
      addColumnHeaders(data);
      populateTable(data);
    }
  } else {
    this.noResults();
  }

  function addColumnHeaders(result) {
    for (var property in result) {
      var type = typeof result[property];
      if (type === 'string' || type === 'number' || type === 'boolean' || result[property] instanceof Date || !result[property]) {
        var mainEntityName = result.__metadata ? result.__metadata.type.split(".").splice(-1)[0] + "." : "";
        mainEntityName = (title ? title + "." : mainEntityName);
        var cName = mainEntityName + property;
        if ($.inArray(cName, columnHeaders) < 0) {
          $headRow.append($('<th />', {
            text: cName
          }));
          columnHeaders.push(cName);
          console.log("columnHeader:" + cName);
        }
      }
    }
  }

  function populateTable(data) {
    var $bodyRow = null;
    if ($parentCells) {
      $bodyRow = $parentCells.clone();
    } else {
      $bodyRow = $('<tr/>');
    }

    var expandedChildResults = [];
    $.each(data, function(index, property) {
      var type = typeof property;
      if (type === 'string' || type === 'number' || type === 'boolean') {
        $bodyRow.append($('<td />', {
          text: property
        }));
      } else if (property instanceof Date) { // DataJS returns the dates as objects and not as strings.
        $bodyRow.append($('<td />', {
          text: property.toDateString()
        }));
      } else if (!property) {
        $bodyRow.append('<td />');
      } else if (typeof property === 'object' && property.results && index !== '__metadata') {
        expandedChildResults.push({
          key: property.results,
          value: index
        });
      } else if (typeof property === 'object' && index !== '__metadata' && index !== '__deferred' && !isPropertyAnObjectWithJustSingle__deferred(property)) {
        expandedChildResults.push({
          key: property,
          value: index
        });
      }
    });

    if (expandedChildResults.length > 0) {
      $.each(expandedChildResults, function(index, childObject) {
        $parentCells = $bodyRow;
        createResultsTable(childObject.key, childObject.value);
      });
      $parentCells = null;
    } else
      $tbody.append($bodyRow);
      console.log($bodyRow);
  }

  function isPropertyAnObjectWithJustSingle__deferred(property) {
    var keys;
    return typeof property === 'object' && // test if it's and object
      (keys = Object.keys(property)).length === 1 && // test if it has just sibgle property
      keys[0] === '__deferred'; // test if that property is '__deferred'
  }

  return $table;
};

Biggest Problem / Summary

最大的问题/总结

The problem where I stuck is that I am not sure how to add empty cells to rows once whole table object is built, otherwise jQuery datable won't like it.

我遇到的问题是,一旦构建了整个表对象,我不确定如何向行添加空单元格,否则jQuery datable不会喜欢它。

Update

更新

Current answer doesn't works when I have more then 1 nested child arrays and so on...

当我有更多的嵌套的子数组时,当前的答案不会起作用。

https://jsfiddle.net/1tsu6xt9/46/

https://jsfiddle.net/1tsu6xt9/46/

As you can see there is only 1 row, I want it to show 3, like I have in this fiddle,

你们可以看到只有一行,我想让它显示3,就像我在这把小提琴里的,

https://jsfiddle.net/jwf347a4/24/

https://jsfiddle.net/jwf347a4/24/

2 个解决方案

#1


2  

I propose keeping track of which columns already exist, and using that to detect when you need to inject a new column or skip over unused columns.

我建议跟踪哪些列已经存在,并在需要注入新列或跳过未使用的列时使用它来检测。

Live Demo

(function() {
    "use strict";

    var activeColumns = [],
        injectedColumns = [];

    var testData = [{
        "Products": [],
        "ID": 999,
        "Name": "Grocery"
    }, {
        "Products": [],
        "Name": "Meat"
    }, {
        "Products": [{
            "Currency": {
                "ID": 47,
                "Num": 826,
                "Code": "GBP",
                "Description": "United Kingdom Pound",
                "DigitsAfterDecimal": 2,
                "Symbol": "\u00a3",
                "Hide": null,
                "Priority": 1
            },
            "ID": 2425,
            "Name": "303783",
            "ExpiryDate": "2014-02-22T00:00:00",
            "CurrencyID": 47,
            "Sales": "0.00000000000000000000",
            "PreTaxProfitOnProduct": null,
            "Assets": "0.30000000000000000000",
            "BarCode": null,
            "Worth": "0.20000000000000000000",
            "MarketValue": null
        }],
        "Name": "Produce & Vegetable"
    }];

    var rows = testData.reduce(function(rows, row) {
        return appendRow(rows, flattenRecord({}, row));
    }, []);

    injectedColumns.forEach(function(ic) {
        rows.slice(0, ic.end).forEach(function(row) {
            var tds = row.find('td'),
                adjacentTo = ic.idx < tds.length ? tds[ic.idx] : null;
            if (adjacentTo) {
                $('<td/>', {
                    insertBefore: adjacentTo
                });
            } else {
                $('<td/>', {
                    appendTo: row
                });
            }
        });
    });

    var table = $('<table/>', {
        append: rows,
        appendTo: 'body'
    });


    var headingRow = $('<tr/>', {
        prependTo: table
    });

    activeColumns.forEach(function(name) {
        $('<th/>', {
            text: name,
            appendTo: headingRow
        });
    });

    function flattenRecord(result, rec) {
        return Object.keys(rec).reduce(function(result, key) {
            var value = rec[key];
            if (value && typeof value === 'object')
                flattenRecord(result, value);
            else
                result[key] = value;
            return result;
        }, result);
    }

    function injectColumn(key, idx, end) {
        injectedColumns.push({
            key: key,
            idx: idx,
            end: end
        });
    }

    function appendRow(rows, obj) {
        var inputColumns = Object.keys(obj),
            tr = $('<tr/>'),
            outputCol;

        outputCol = 0;
        inputColumns.forEach(function(key) {
            var value = obj[key],
                cell,
                activeSrch = activeColumns.indexOf(key),
                emptyCells = activeSrch >= 0 ? activeSrch - outputCol : 0,
                i;
            for (i = 0; i < emptyCells; ++i, ++outputCol) {
                $('<td/>', {
                    appendTo: tr
                });
            }

            if (activeColumns[outputCol] !== key) {
                activeColumns.splice(outputCol, 0, key);
                injectColumn(key, outputCol, rows.length);
            }
            cell = $('<td/>', {
                text: value,
                appendTo: tr
            });
            ++outputCol;
        });

        rows.push(tr);

        return rows;
    }
}());

How it works

First it flattens the records to get rid of the nesting.

首先,它使记录变平,以去除嵌套。

Then, for each column, see if that column already exists.

然后,对于每一列,查看该列是否已经存在。

If that column exists, see if we are skipping over columns. If so, we skip forward that many and insert that many empty cells. If not, we remember which row we are on so we can insert a cell in all previous rows, at the appropriate index.

如果该列存在,请查看是否跳过列。如果是这样,我们就跳过这么多,然后插入这么多空单元格。如果没有,我们还记得我们在哪个行,这样我们就可以在所有前面的行中插入一个单元格,在适当的索引中。

#2


1  

Solution: (Working Fiddle) Once you have built your table call another function which will complete the table by adding missing cells into the rows

解决方案:(工作小提琴)一旦您构建了表,调用另一个函数,它将通过向行中添加丢失的单元格来完成表

This logic must help.

这个逻辑必须帮助。

function RestructureTheDynamicTable(){
 var $table = $('#myTable');
 var maxColumns = $table.find('thead tr th').length;    //find the total columns required

  $table.find('tbody tr').each(function(){
    var thisRowColumnCount = $(this).find('td').length;
    var extraTds = "";
    if(maxColumns > thisRowColumnCount){    //if this row doesn't have the required number of columns lets add them
      for(var i=0;i < maxColumns - thisRowColumnCount;i++){
        extraTds += "<td></td>";
      }
      $(this).append(extraTds);
    }    
  });
}

Call this function after you have completed building the table. After the table completes restructuring then you can apply the Datatables plugin

在完成构建表之后调用这个函数。在完成重组之后,您可以应用Datatables插件。

#1


2  

I propose keeping track of which columns already exist, and using that to detect when you need to inject a new column or skip over unused columns.

我建议跟踪哪些列已经存在,并在需要注入新列或跳过未使用的列时使用它来检测。

Live Demo

(function() {
    "use strict";

    var activeColumns = [],
        injectedColumns = [];

    var testData = [{
        "Products": [],
        "ID": 999,
        "Name": "Grocery"
    }, {
        "Products": [],
        "Name": "Meat"
    }, {
        "Products": [{
            "Currency": {
                "ID": 47,
                "Num": 826,
                "Code": "GBP",
                "Description": "United Kingdom Pound",
                "DigitsAfterDecimal": 2,
                "Symbol": "\u00a3",
                "Hide": null,
                "Priority": 1
            },
            "ID": 2425,
            "Name": "303783",
            "ExpiryDate": "2014-02-22T00:00:00",
            "CurrencyID": 47,
            "Sales": "0.00000000000000000000",
            "PreTaxProfitOnProduct": null,
            "Assets": "0.30000000000000000000",
            "BarCode": null,
            "Worth": "0.20000000000000000000",
            "MarketValue": null
        }],
        "Name": "Produce & Vegetable"
    }];

    var rows = testData.reduce(function(rows, row) {
        return appendRow(rows, flattenRecord({}, row));
    }, []);

    injectedColumns.forEach(function(ic) {
        rows.slice(0, ic.end).forEach(function(row) {
            var tds = row.find('td'),
                adjacentTo = ic.idx < tds.length ? tds[ic.idx] : null;
            if (adjacentTo) {
                $('<td/>', {
                    insertBefore: adjacentTo
                });
            } else {
                $('<td/>', {
                    appendTo: row
                });
            }
        });
    });

    var table = $('<table/>', {
        append: rows,
        appendTo: 'body'
    });


    var headingRow = $('<tr/>', {
        prependTo: table
    });

    activeColumns.forEach(function(name) {
        $('<th/>', {
            text: name,
            appendTo: headingRow
        });
    });

    function flattenRecord(result, rec) {
        return Object.keys(rec).reduce(function(result, key) {
            var value = rec[key];
            if (value && typeof value === 'object')
                flattenRecord(result, value);
            else
                result[key] = value;
            return result;
        }, result);
    }

    function injectColumn(key, idx, end) {
        injectedColumns.push({
            key: key,
            idx: idx,
            end: end
        });
    }

    function appendRow(rows, obj) {
        var inputColumns = Object.keys(obj),
            tr = $('<tr/>'),
            outputCol;

        outputCol = 0;
        inputColumns.forEach(function(key) {
            var value = obj[key],
                cell,
                activeSrch = activeColumns.indexOf(key),
                emptyCells = activeSrch >= 0 ? activeSrch - outputCol : 0,
                i;
            for (i = 0; i < emptyCells; ++i, ++outputCol) {
                $('<td/>', {
                    appendTo: tr
                });
            }

            if (activeColumns[outputCol] !== key) {
                activeColumns.splice(outputCol, 0, key);
                injectColumn(key, outputCol, rows.length);
            }
            cell = $('<td/>', {
                text: value,
                appendTo: tr
            });
            ++outputCol;
        });

        rows.push(tr);

        return rows;
    }
}());

How it works

First it flattens the records to get rid of the nesting.

首先,它使记录变平,以去除嵌套。

Then, for each column, see if that column already exists.

然后,对于每一列,查看该列是否已经存在。

If that column exists, see if we are skipping over columns. If so, we skip forward that many and insert that many empty cells. If not, we remember which row we are on so we can insert a cell in all previous rows, at the appropriate index.

如果该列存在,请查看是否跳过列。如果是这样,我们就跳过这么多,然后插入这么多空单元格。如果没有,我们还记得我们在哪个行,这样我们就可以在所有前面的行中插入一个单元格,在适当的索引中。

#2


1  

Solution: (Working Fiddle) Once you have built your table call another function which will complete the table by adding missing cells into the rows

解决方案:(工作小提琴)一旦您构建了表,调用另一个函数,它将通过向行中添加丢失的单元格来完成表

This logic must help.

这个逻辑必须帮助。

function RestructureTheDynamicTable(){
 var $table = $('#myTable');
 var maxColumns = $table.find('thead tr th').length;    //find the total columns required

  $table.find('tbody tr').each(function(){
    var thisRowColumnCount = $(this).find('td').length;
    var extraTds = "";
    if(maxColumns > thisRowColumnCount){    //if this row doesn't have the required number of columns lets add them
      for(var i=0;i < maxColumns - thisRowColumnCount;i++){
        extraTds += "<td></td>";
      }
      $(this).append(extraTds);
    }    
  });
}

Call this function after you have completed building the table. After the table completes restructuring then you can apply the Datatables plugin

在完成构建表之后调用这个函数。在完成重组之后,您可以应用Datatables插件。