1

I'm building a grid component that will allow the user to do multiple row grouping.

The original data I'm working on is as such example for stock items:

let stock = [
    { order: "200", type: "production", qty: 200, item: "IT282" },
    { order: "200", type: "production", qty: 90, item: "IT283" },
    { order: "200", type: "customer", qty: 80, item: "IT102" },
    { order: "200", type: "production", qty: 110, item: "IT283" },
    { order: "200", type: "customer", qty: 130, item: "IT102" },
    { order: "200", type: "production", qty: 45, item: "IT233" },
    { order: "200", type: "stock", qty: 30, item: "IT282" },
    { order: "210", type: "production", qty: 300, item: "IT282" },
    { order: "210", type: "production", qty: 190, item: "IT283" },
    { order: "210", type: "customer", qty: 180, item: "IT102" },
    { order: "210", type: "production", qty: 210, item: "IT283" },
    { order: "210", type: "customer", qty: 230, item: "IT102" },
    { order: "210", type: "production", qty: 145, item: "IT233" },
    { order: "210", type: "stock", qty: 130, item: "IT282" }
];

What I need to accomplish is to be able to group that data using multiple fields in different orders, like the following results:

let result = groupBy(stock, ["order"]);

[
    {
        field: "order",
        value: "200",
        rows: [
            { order: "200", type: "production", qty: 200, item: "IT282" },
            { order: "200", type: "production", qty: 90, item: "IT283" },
            { order: "200", type: "customer", qty: 80, item: "IT102" },
            { order: "200", type: "production", qty: 110, item: "IT283" },
            { order: "200", type: "customer", qty: 130, item: "IT102" },
            { order: "200", type: "production", qty: 45, item: "IT233" },
            { order: "200", type: "stock", qty: 30, item: "IT282" }
        ]
    },
    {
        field: "order",
        value: "210",
        rows: [
            { order: "210", type: "production", qty: 300, item: "IT282" },
            { order: "210", type: "production", qty: 190, item: "IT283" },
            { order: "210", type: "customer", qty: 180, item: "IT102" },
            { order: "210", type: "production", qty: 210, item: "IT283" },
            { order: "210", type: "customer", qty: 230, item: "IT102" },
            { order: "210", type: "production", qty: 145, item: "IT233" },
            { order: "210", type: "stock", qty: 130, item: "IT282" }
        ]
    }
];

let result = groupBy(stock, ["item"]);

[
    {
        field: "item",
        value: "IT282",
        rows: [
            { order: "200", type: "production", qty: 200, item: "IT282" },
            { order: "200", type: "stock", qty: 30, item: "IT282" },
            { order: "210", type: "production", qty: 300, item: "IT282" },
            { order: "210", type: "stock", qty: 130, item: "IT282" }
        ]
    },
    {
        field: "item",
        value: "IT283",
        rows: [
            { order: "200", type: "production", qty: 90, item: "IT283" },
            { order: "200", type: "production", qty: 110, item: "IT283" },
            { order: "210", type: "production", qty: 190, item: "IT283" },
            { order: "210", type: "production", qty: 210, item: "IT283" }
        ]
    },
    {
        field: "item",
        value: "IT102",
        rows: [
            { order: "200", type: "customer", qty: 80, item: "IT102" },
            { order: "200", type: "customer", qty: 130, item: "IT102" },
            { order: "210", type: "customer", qty: 180, item: "IT102" },
            { order: "210", type: "customer", qty: 230, item: "IT102" }
        ]
    },
    {
        field: "item",
        value: "IT233",
        rows: [
            { order: "200", type: "production", qty: 45, item: "IT233" },
            { order: "210", type: "production", qty: 145, item: "IT233" }
        ]
    }
];

let result = groupBy(stock, ["order", "item"]);

[
    {
        field: "order",
        value: "200",
        rows: [
            {
                field: "item",
                value: "IT282",
                rows: [
                    {
                        order: "200",
                        type: "production",
                        qty: 200,
                        item: "IT282"
                    },
                    { order: "200", type: "stock", qty: 30, item: "IT282" },
                    {
                        order: "210",
                        type: "production",
                        qty: 300,
                        item: "IT282"
                    }
                ]
            },
            {
                field: "item",
                value: "IT283",
                rows: [
                    {
                        order: "200",
                        type: "production",
                        qty: 90,
                        item: "IT283"
                    },
                    {
                        order: "200",
                        type: "production",
                        qty: 110,
                        item: "IT283"
                    }
                ]
            },
            {
                field: "item",
                value: "IT102",
                rows: [
                    { order: "200", type: "customer", qty: 80, item: "IT102" },
                    { order: "200", type: "customer", qty: 130, item: "IT102" }
                ]
            },
            {
                field: "item",
                value: "IT233",
                rows: [
                    {
                        order: "200",
                        type: "production",
                        qty: 45,
                        item: "IT233"
                    }
                ]
            }
        ]
    },
    {
        field: "order",
        value: "210",
        rows: [
            {
                field: "item",
                value: "IT282",
                rows: [{ order: "210", type: "stock", qty: 130, item: "IT282" }]
            },
            {
                field: "item",
                value: "IT283",
                rows: [
                    {
                        order: "210",
                        type: "production",
                        qty: 190,
                        item: "IT283"
                    },
                    {
                        order: "210",
                        type: "production",
                        qty: 210,
                        item: "IT283"
                    }
                ]
            },
            {
                field: "item",
                value: "IT102",
                rows: [
                    { order: "210", type: "customer", qty: 180, item: "IT102" },
                    { order: "210", type: "customer", qty: 230, item: "IT102" }
                ]
            },
            {
                field: "item",
                value: "IT233",
                rows: [
                    {
                        order: "210",
                        type: "production",
                        qty: 145,
                        item: "IT233"
                    }
                ]
            }
        ]
    }
];

let result = groupBy(stock, ["item", "order"]);

[
    {
        field: "item",
        value: "IT282",
        rows: [
            {
                field: "order",
                value: "200",
                rows: [
                    {
                        order: "200",
                        type: "production",
                        qty: 200,
                        item: "IT282"
                    },
                    { order: "200", type: "stock", qty: 30, item: "IT282" }
                ]
            },
            {
                field: "order",
                value: "210",
                rows: [
                    {
                        order: "210",
                        type: "production",
                        qty: 300,
                        item: "IT282"
                    },
                    { order: "210", type: "stock", qty: 130, item: "IT282" }
                ]
            }
        ]
    },
    {
        field: "item",
        value: "IT283",
        rows: [
            {
                field: "order",
                value: "200",
                rows: [
                    {
                        order: "200",
                        type: "production",
                        qty: 90,
                        item: "IT283"
                    },
                    {
                        order: "200",
                        type: "production",
                        qty: 110,
                        item: "IT283"
                    }
                ]
            },
            {
                field: "order",
                value: "210",
                rows: [
                    {
                        order: "210",
                        type: "production",
                        qty: 190,
                        item: "IT283"
                    },
                    {
                        order: "210",
                        type: "production",
                        qty: 210,
                        item: "IT283"
                    }
                ]
            }
        ]
    },
    {
        field: "item",
        value: "IT102",
        rows: [
            {
                field: "order",
                value: "200",
                rows: [
                    { order: "200", type: "customer", qty: 80, item: "IT102" },
                    { order: "200", type: "customer", qty: 130, item: "IT102" }
                ]
            },
            {
                field: "order",
                value: "210",
                rows: [
                    { order: "210", type: "customer", qty: 180, item: "IT102" },
                    { order: "210", type: "customer", qty: 230, item: "IT102" }
                ]
            }
        ]
    },
    {
        field: "item",
        value: "IT233",
        rows: [
            {
                field: "order",
                value: "200",
                rows: [
                    { order: "200", type: "production", qty: 45, item: "IT233" }
                ]
            },
            {
                field: "order",
                value: "210",
                rows: [
                    {
                        order: "210",
                        type: "production",
                        qty: 145,
                        item: "IT233"
                    }
                ]
            }
        ]
    }
];

My group function would receive any array of items and group any number of array fields in any order.

As I want an ES6 based function, I'm today using the following groupBy function from that link that works for a single pass, but I'm having difficulties nesting then together.

Here is the code I'm working on:

  groupBy = (rows, groups) => {
    if (groups.length === 0) return rows;

    return this.groupByField(rows, groups, 0);
  };

  groupByField = (rows, groups, index) => {
    if (index >= groups.length) return rows;

    let grouped = this.groupRows(rows, groups[index]);

    index++;
    let ret = [];

    grouped.rows.map(row => {
      ret.push(this.groupByField(row.rows, groups, index));
    });

    grouped.rows = ret;
    return grouped;
  };

  groupRows = (rows, field) => {
    return rows.reduce(function(groups, x) {
      let val = helper.getValueByFieldNameString(x.data, field);

      let found = groups.find(item => {
        return item.groupedValue === val;
      });

      if (!found) {
        let rows = [];
        rows.push(x);

        groups.push({
          groupedField: field,
          groupedValue: val,
          rows: rows
        });
      } else {
        found.rows.push(x);
      }

      return groups;
    }, []);
  };

Seems that the recursivity is not working properly.

halfer
  • 19,824
  • 17
  • 99
  • 186
Mendes
  • 17,489
  • 35
  • 150
  • 263

1 Answers1

4

I think you can just make a function that groups an array using reduce and Object.values. Then after you group the array, if you have more fields to group by, call the function on each child row array. For example:

let stock = [{ order: "200", type: "production", qty: 200, item: "IT282" },{ order: "200", type: "production", qty: 90, item: "IT283" },{ order: "200", type: "customer", qty: 80, item: "IT102" },{ order: "200", type: "production", qty: 110, item: "IT283" },{ order: "200", type: "customer", qty: 130, item: "IT102" },{ order: "200", type: "production", qty: 45, item: "IT233" },{ order: "200", type: "stock", qty: 30, item: "IT282" },{ order: "210", type: "production", qty: 300, item: "IT282" },{ order: "210", type: "production", qty: 190, item: "IT283" },{ order: "210", type: "customer", qty: 180, item: "IT102" },{ order: "210", type: "production", qty: 210, item: "IT283" },{ order: "210", type: "customer", qty: 230, item: "IT102" },{ order: "210", type: "production", qty: 145, item: "IT233" },{ order: "210", type: "stock", qty: 130, item: "IT282" }];

function groupBy(arr, fields) {
  let field = fields[0]               // one field at a time
  if (!field) return arr              
  let retArr = Object.values(
     arr.reduce((obj, current) => {
        if (!obj[current[field]]) obj[current[field]] = {field: field, value: current[field],rows: []}
        obj[current[field]].rows.push(current)
        return obj
     }, {}))
  
  // recurse for each child's rows if there are remaining fields
  if (fields.length){
      retArr.forEach(obj => {
          obj.count = obj.rows.length
          obj.rows = groupBy(obj.rows, fields.slice(1))
      })
  }
  return retArr
}

let result = groupBy(stock, ["order", "item"]);
console.log(result)
Mark
  • 90,562
  • 7
  • 108
  • 148
  • Thanks Mark for the post. I'm trying your posted code and for now it is working fine. I'm not stuck on buiding my table from the results.. How can I know, when rendering the rows, which colums to do rowSpan and for how many rows? – Mendes Oct 24 '18 at 23:10
  • Hi @Mendes, there was actually a bug in that code, which I think I fixed. I also edited it to add a `count` to each item which will be the total of all descendants to a node, which is just the row length before recursing. I *think* that's the count you're after. – Mark Oct 25 '18 at 00:27
  • 2
    Very nice piece of code. Thanks @Mark for the version and attending my extra requirement.... Well done. – Mendes Oct 25 '18 at 22:54
  • one more question. How can I add to each row the recursive fields that it grouped ? In example, the last example should show on each row that it is grouped by `field` and `order`, as well as the number of elements. This is necessary to do colSpan's when building a result table using html. Thanks for helping! – Mendes Oct 25 '18 at 23:53