【问题标题】:CSS Grid, ng-repeat and unknown dataCSS Grid、ng-repeat 和未知数据
【发布时间】:2018-08-06 12:10:46
【问题描述】:

这就是问题所在。该代码以多维数组格式从 Web 服务接收数据。行数和列数以及这些列的名称(键)在运行时之前也是未知的。我需要以类似表格的结构显示该数据的网格。我们想使用 CSS Grid(没有表格)来显示这些数据。

例如,假设以下数据是从 Web 服务中检索到的。

                var data = [
                    { id: 1, first: 'John', last: 'Doe' },
                    { id: 2, first: 'Jane', last: 'Davis' },
                    { id: 3, first: 'Steve', last: 'Smith' }
                ];
                vm.data = data;

现在,我需要以 CSS 网格格式显示它。首先,我定义样式中包含“display: grid”的包含 DIV,这样我们就有了一个 CSS Grid。

<div style="display: grid;">...</div>

所需的 HTML 需要类似于以下内容。 (请注意,DIV 的实际顺序对我来说无关紧要,只要 grid-row 和 grid-column 值正确即可)。

<div style="display: grid;">
  <div style="grid-row: 1; grid-column: 1;">id</div>
  <div style="grid-row: 1; grid-column: 2;">first</div>
  <div style="grid-row: 1; grid-column: 3;">last</div>
  <div style="grid-row: 2; grid-column: 1;">1</div>
  <div style="grid-row: 2; grid-column: 2;">John</div>
  <div style="grid-row: 2; grid-column: 3;">Doe</div>
  <div style="grid-row: 3; grid-column: 1;">2</div>
  <div style="grid-row: 3; grid-column: 2;">John</div>
  <div style="grid-row: 3; grid-column: 3;">Doe</div>
  <div style="grid-row: 4; grid-column: 1;">3</div>
  <div style="grid-row: 4; grid-column: 2;">John</div>
  <div style="grid-row: 4; grid-column: 3;">Doe</div>
</div>

该网站使用 AngularJS,因此一种选择是使用 ng-repeat 来生成 HTML。问题是,ng-repeat 真的是用来处理一维数据的。但是,我们可以使用 $index (例如)提取一些额外的数据。

例如,为了生成 DIV 来呈现列标题(在这种情况下,我们将使用键名)我可以这样做:

HTML:

<div style="display: grid; grid-template-columns: repeat({{ vm.reportHeaders.length }}, 1fr); grid-gap: 10px;">
    <div ng-repeat="(key, value) in reportingCtrl.data[0]" style="grid-column: {{ $index }}; grid-row: 1;">{{ key }}
    </div>
</div>

后面的代码:

var reportHeaders = [];
for (var property in data[0]) {
    if (data[0].hasOwnProperty(property)) {
       reportHeaders.push(property);
    }
}
vm.reportHeaders = reportHeaders;

到目前为止一切顺利,但现在我们来看看数据,这就是我卡住的地方。

在我看来,我需要:

  1. 逐行遍历数据以访问行索引
  2. 逐行逐列循环,以获得正确的列索引和要显示的数据
  3. 生成一个 DIV 标记,在“grid-column”和“grid-row”样式属性中呈现正确的行/列数据,以及行/列的正确数据。

我不知道该怎么做。

想法

  • 我看到了对 ng-repeat-start/end 的引用,这在这里可能会有所帮助,但这似乎仍然无法解决不知道涉及多少列的问题?
  • 我可以将表标题数据添加到代码隐藏中的数据表中,如果这样可以让生活更轻松吗?
  • 也许有一种方法可以在 ng-repeat 中创建计数器,而不是使用 $index?
  • 我可以在代码隐藏中生成 HTML,然后将其显示或添加到 DOM,但这不是我的首选。
  • 我对想法持开放态度?也许我离树太近了,看不到森林?

【问题讨论】:

    标签: css angularjs angularjs-ng-repeat css-grid


    【解决方案1】:

    您可以使用 CSS Grid 和 Vanilla JavaScript 做到这一点。

    这是一个 CodePen:CodePen

    我举了一个例子,你的 HTML 代码用蓝色边框中的 CSS 类名重构,然后是绿色边框中的 JavaScript 版本。

    代码sn-p:

    var data = [
      {
        id: 1,
        first: "John",
        last: "Doe"
      },
      {
        id: 2,
        first: "Jane",
        last: "Davis"
      },
      {
        id: 3,
        first: "Steve",
        last: "Smith"
      }
    ];
    
    function parseData() {
      var array = data.map(object => Object.values(object));
      return array; //this is an array of arrays
    }
    
    function outputData() {
      var data = parseData(); //this is an array of arrays
      var output_1 = document.getElementById("output-1");
      var output_2 = document.getElementById("output-2");
      var output_3 = document.getElementById("output-3");
    
      for (i = 0; i < data.length; i++) {
    
        var array1 = data[i][0];
        var array2 = data[i][1];
        var array3 = data[i][2];
        
        var node1 = document.createElement("div");
        var node2 = document.createElement("div");
        var node3 = document.createElement("div");
    
        var id_column = array1;
        var firstName = array2;
        var lastName = array3;
        
        var textnode1 = document.createTextNode(id_column);
        var textnode2 = document.createTextNode(firstName);
        var textnode3 = document.createTextNode(lastName);
    
        node1.appendChild(textnode1);
        node2.appendChild(textnode2);
        node3.appendChild(textnode3);
    
        output_1.appendChild(node1);
        output_2.appendChild(node2);
        output_3.appendChild(node3);
      }
    }
    outputData();
    .container {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      grid-template-rows: repeat(auto-fill, 1fr);
      grid-template-areas:
        "a b c"
        "d e f";
    }
    .one {
      grid-area: a;
    }
    .two {
      grid-area: b;
    }
    .three {
      grid-area: c;
    }
    .id {
      grid-area: d;
    }
    .first-name {
      grid-area: e;
    }
    .last-name {
      grid-area: f;
    }
    #blue-box {
      border: 3px solid blue;
    }
    #green-box {
      border: 3px solid green;
    }
    <p>
      Hardcoded HTML with CSS classnames:
    </p>
    
    <div class="container" id="blue-box">
      <div class="one">id</div>
      <div class="two">first</div>
      <div class="three">last</div>
      <div>1</div>
      <div>John</div>
      <div>Doe</div>
      <div>2</div>
      <div>John</div>
      <div>Doe</div>
      <div>3</div>
      <div>John</div>
      <div>Doe</div>
    </div>
    
    <p>
      Dynamically generated via Vanilla JavaScript:
    </p>
    
    <div class="container" id="green-box">
      <div class="one">id</div>
      <div class="two">first</div>
      <div class="three">last</div>
      <div id="output-1" class="id">
      </div>
      <div id="output-2" class="first-name">
      </div>
      <div id="output-3" class="last-name">
      </div>
    </div>

    【讨论】:

      【解决方案2】:

      这里有两种方法可以解决这个问题(Codepen Demo):-

      方法 1

      我们可以使用 hacky 方法来组合两个 ng-repeat 指令 - 一个用于数据数组中的所有项目,另一个用于项目中的所有键值对。

      对于标题生成,如果您希望对象键作为标题而不是自定义标题,一种方法是对数据数组的第一项的键值对使用 ng-repeat 并绑定键以获取标题。

      <div style="display:grid;">
          <div ng-repeat="(key, value) in vm.data[0]" style="grid-row:1; grid-col:{{$index+1}}">{{key}}</div>
          <div class="ng-hide" ng-repeat-start="(index, object) in vm.data"></div>
          <div ng-repeat-end ng-repeat="(key, value) in object" style="grid-row:{{index+2}}; grid-col:{{$index+1}}">{{value}}</div>
      </div>
      

      关键要点:-

      1. 使用的两个 ng-repeat 处于父子关系。 ng-repeat-start/ng-repeat-end 对是父级。
      2. vm.data 中的语法 (index, object) 允许我们将父 ng-repeat 项目索引存储在单独的变量中。或者,我们可以在这里使用 $parent.$index。对于子 ng-repeat,我们使用 $index 变量。
      3. 为什么行索引+2?索引是从 0 开始的,我们已经有第 1 行作为标题,因此循环将从第 2 行开始添加行。
      4. 我们需要支持一个额外的 div 来实现这个结构。因此,我们使用 ng-hide 来隐藏多余的 div。

      方法2

      我们可以将集合展平为原始数组并使用 ng-repeat 进行布局。

      <div style="display:grid;">
          <div ng-repeat="(index, key) in vm.keys" style="grid-row:1; grid-col:{{index+1}}">{{key}}</div>
          <div ng-repeat="(index, value) in vm.values" style="grid-row:{{vm.calculateRow(index)}}; grid-col:{{(index%vm.keys.length)+1}}">{{value}}</div>
      </div>
      

      在你的 JS 中:-

      vm.currentRow = 0;
      vm.keys = Object.keys(vm.data[0]);
      vm.values = vm.data.reduce((array, object) => [...array, ...Object.values(object)], []);
      vm.calculateRow = i => Math.floor(i/vm.data.length) + 2;
      

      关键要点:-

      1. 很难将原始对象绑定到 ng-click 等指令。
      2. 附加网格行值需要额外的性能密集型逻辑。

      注意事项

      1. 以下两种解决方案都依赖于迭代集合中对象的键/值对。不保证迭代顺序在不同浏览器之间保持一致。
      2. 我们假设每行(数组项)的列数(对象键)保持不变。

      在 CSS 方面

      CSS 网格非常灵活。在上述两种方法中,都不需要指定网格行规则,布局系统足够智能,可以确定何时创建新行。或者,不必在每个 div 上内联行数/列数,因为在获取数据后,您可以计算行数/列数,您还可以使用 grid-template 规则在包装网格上指定。

      例如:-

      <div style="display:grid; grid-template:repeat({{vm.rowCount}}, 1fr)/repeat({{vm.colCount}}, 1fr)"></div>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多