evan
evan

Reputation: 83

How to display data with multiple arrays as an html table with angular.js?

Assuming data structured like:

[
 {name: 'first', data1: [1,2,3], data2: [4,5]},
 {name: 'second', data1: [9,8,7], data2: [6,6,4,5]}, 
 ...
]

(Note data1 and data2 are not fixed lengths and not necessarily the same length).

I'd like to create an html table like:

<table>
 <thead>
   <th> Name </th>
   <th> Data 1 </th>
   <th> Data 2 </th>
 </thead>
 <tbody>
  <tr>
    <td rowspan="4">first</td>
    <td>1</td>
    <td>4</td>
  </tr>
  <tr>   
    <td>2</td>
    <td>5</td>
  </tr>
  <tr>   
    <td>3</td>
    <td>&nbsp;</td>
  </tr>
  <tr>   
    <td>&nbsp;</td>
    <td>&nbsp;</td>
  </tr>

  <tr>
    <td rowspan="5">first</td>
    <td>9</td>
    <td>6</td>
  </tr>
  <tr>   
    <td>8</td>
    <td>6</td>
  </tr>
  <tr>   
    <td>7</td>
    <td>5</td>
  </tr>
  <tr>   
    <td>&nbsp;</td>
    <td>5</td>
  </tr>
  <tr>   
    <td>&nbsp;</td>
    <td>&nbsp;</td>
  </tr>
 </tbody>
<table>

Notice that in the display there is at least one extra empty cell after the last bit of data in each column. I'd like that cell, the first empty cell for each of data1 and data2, to have an ng-click handler which if clicked would show a input in that column instead of %nbsp; where one could enter a new value.

What's the best way to accomplish this?

Currently I can almost create the above structure by having a second variable on the scope which watches the first and when the first changes it flattens the data so the above data would be stored in another variable as:

[ { name: 'first', rowsNeeded: '4', data: [ [1,4], [2,5], [3, undefined], [undefined, undefined]], ... ]

and then rendering it like

 <tbody ng-repeat="item in flattenedData">
    <tr ng-repeat="data in item.data">
        <td ng-if="$first" rowspan="{{data.rowsNeeded}}">{{data.name}}</td>
        <td>{{data[0] == undefined ? '&nbsp;' : macroData[0] }}</td>
        <td>{{data[1] == undefined ? '&nbsp;' : macroData[1] }}</td>
    </tr>
</tbody>

But I'm wondering a few things. One, do I really need to have a second variable using a watch statement to create the table or is there a cleaner way? Two, I'm not sure how I'd add the click event so on the first 'undefined' in each column I could add an instead of '&nbsp'. Lastly, is it really necessary to have a different tbody tag for each row?

Thanks for your help!!

Upvotes: 3

Views: 3680

Answers (1)

The general idea is to extract data formatting into filters. From there on, I'd combine everything into directive to simplyfy table component usage. This example (JSBin) shows only filters and template, non-optimal, but working:

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th ng-repeat="key in datas[0]|dataKeys">
                Data {{$index+1}}
            </th>
        </tr>
    </thead>
    <tbody ng-repeat="set in datas">
        <tr ng-repeat="row in (set|setMaxLength|range)">
            <td
              rowspan="{{(set|setMaxLength)+1}}"
              ng-if="!$index"
            >
                {{set.name}}
            </td>
            <td ng-repeat="data in (set|setDatas)">
                {{data[$parent.$index]}}
            </td>
        </tr>
        <tr>
            <td
              ng-repeat="data in (set|setDatas)"
              ng-click="editing = true"
            >
                <span ng-if="!editing">&nbsp;</span>
                <form
                  ng-submit="data[data.length] = input.value"
                  name="input"
                >
                    <input
                      type="text"
                      ng-if="editing"
                      ng-model="input.value"
                      autofocus
                    >
                </form>
            </td>
        </tr>
    </tbody>
</table>
angular
.module("app", [])
.controller("ctrl", function($scope) {
    $scope.datas = [{
        name: 'first',
        data1: [1, 2, 3],
        data2: [4, 5]
    }, {
        name: 'second',
        data1: [9, 8, 7],
        data2: [6, 6, 4, 5]
    }];
})
.filter("range", function() {
    return function(length) {
        return Array.apply(0, Array(length))
        .map(function(_, i) {
            return i;
        })
    }
})
.filter("dataKeys", function() {
    return function(set) {
        return Object.keys(set)
        .filter(function(key) {
            return key.indexOf("data") !== -1;
        })
    }
})
.filter("setDatas", function($filter) {
    return function(set) {
        return $filter("dataKeys")(set)
        .map(function(key) {
            return set[key]
        });
    }
})
.filter("setMaxLength", function($filter) {
    return function(set) {
        return $filter("dataKeys")(set)
        .map(function(key) {
            return set[key].length;
        })
        .sort().pop();
    }
})

Upvotes: 3

Related Questions