[Vuejs]-Flat array restructure to a tree array

1👍

You could group with another array.

let data = [{ KlasCode: "1S RD BJ GS ma-d", LESDatum: "12/12/20", LESID: "1", ModuleID: "1050", Moduleomschrijving: "Realisaties blouse/jurk", ParticipationLetterCode: "X" }, { KlasCode: "1S RD BJ GS ma-d", LESDatum: "11/11/20", LESID: "2", ModuleID: "1050", Moduleomschrijving: "Realisaties blouse/jurk", ParticipationLetterCode: "X" }, { KlasCode: "1S RD BJ GS ma-d", LESDatum: "1/1/20", LESID: "3", ModuleID: "1050", Moduleomschrijving: "Realisaties blouse/jurk", ParticipationLetterCode: "Y" }, { KlasCode: "2S RD BJ RR ma-d", LESDatum: "5/12/20", LESID: "4", ModuleID: "1051", Moduleomschrijving: "Realisaties shirts", ParticipationLetterCode: "Z" }, { KlasCode: "2S RD BJ RR ma-d", LESDatum: "6/11/20", LESID: "4", ModuleID: "1051", Moduleomschrijving: "Realisaties shirts", ParticipationLetterCode: "Z" }],
    groups = [['KlasCode'], ['Moduleomschrijving', 'ModuleID'], ['ParticipationLetterCode'], ['LESDatum']],
    result = data
        .reduce((r, o) => {
            groups.reduce((p, [key, ...levelKeys]) => {
                let taskName = o[key],
                    temp = (p.subtasks = p.subtasks || []).find(q => q.taskName === taskName);
                if (!temp) {
                    let moreProps = levelKeys.reduce((t, k) => ({ ...t, [k]: o[k] }), {});
                    p.subtasks.push(temp = { ...moreProps, taskName });
                }
                return temp;
            }, r)
            return r;
        }, { subtasks: [] })
        .subtasks;

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

1👍

Create a class Task and let it manage its subtasks.
Use the reducer on the original array in combination with the Task class to create your desired structure.
Check the stackblitz link for the solution
https://stackblitz.com/edit/js-vvxkve

class Task {
    constructor ( taskName ) {
        this.taskName = taskName;
        this.subtasks = [];
    }
    addSubTask ( options ) {
        const ModuleID = options['ModuleID'] || null;
        const taskName = options['Moduleomschrijving'] || null;
        const participationLetterCode = options['ParticipationLetterCode'] || null;
        const subTask = this.subtasks.find ( s => s.ModuleID === ModuleID );
        const subTaksL2 = {
            taskName: options['LESDatum'] || null,
            LESID: options['LESID'] || null
        } 
        if ( !subTask ) {
            subTask = {
                ModuleID,
                taskName,
                subtasks: [{
                    taskName: participationLetterCode,
                    subtasks: [ subTaksL2 ]
                }]
            }
            this.subtasks.push ( subTask );
        } else {
            let subTaskL1 = subTask.subtasks.find ( s => s.taskName === participationLetterCode );
            if ( !subTaskL1 ) {
                subTaskL1 = {
                    taskName: participationLetterCode,
                    subtasks: []
                }
                subTask.subtasks.push ( subTaskL1 );
            } 
            subTaskL1.subtasks.push ( subTaksL2 );
        }
    }
}

let participations = [{
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "12/12/20",
    LESID: "1",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "X"
  }, {
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "11/11/20",
    LESID: "2",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "X",
  },
  {
    KlasCode: "1S RD BJ GS ma-d",
    LESDatum: "1/1/20",
    LESID: "3",
    ModuleID: "1050",
    Moduleomschrijving:"Realisaties blouse/jurk",
    ParticipationLetterCode: "Y"
  },
  {
    KlasCode: "2S RD BJ RR ma-d",
    LESDatum: "5/12/20",
    LESID: "4",
    ModuleID: "1051",
    Moduleomschrijving:"Realisaties shirts",
    ParticipationLetterCode: "Z"
  },
  {
    KlasCode: "2S RD BJ RR ma-d",
    LESDatum: "6/11/20",
    LESID: "4",
    ModuleID: "1051",
    Moduleomschrijving:"Realisaties shirts",
    ParticipationLetterCode: "Z"
  }
];

participations = participations.reduce ( ( acc, cval ) => {
      const taskName = cval['KlasCode'] || null;
      let node = acc.find ( a => a.taskName === taskName );
      if ( !node ) {
          node = new Task ( taskName );
          acc.push ( node );
      }
      node.addSubTask ( cval );
      return acc;
}, []);

1👍

This was a very interesting problem to work on.

I like the overall idea of the answer from Nina Scholz, but I really wanted a more generic version. What I came up with is a function that’s configured with something like the following:

[
  {_children: 'subtasks', taskName: 'KlasCode'},
  {_children: 'subtasks', taskName: 'Moduleomschrijving', ModuleID: 'ModuleID'},
  {_children: 'subtasks', taskName: 'ParticipationLetterCode'},
  {taskName: 'LESDatum'},
]

(See below the code for how I might alter this configuration if I spent more time on this problem.)

This says that the outer level of the output gets a property named taskName from the KlasCode property, grouping on all values that match, and names it array of children subtasks. Those children get taskName from Moduleomschrijving and ModuleID from ModuleID, also naming its children subtasks, and so forth. The final node transforms the name LESDatum to taskName, but has no children to descend into. All remaining names are kept intact. I’ve made the assumtion that Moduleomschrijving and ModuleID are always in sync. If this is not true, then I may be missing something important.

The implementation depends on two helper functions:

  • groupBy turns an array into an object with keys the result of your custom function and their values arrays of those original element that generate the key.
  • omit creates a copy of an object with the given keys missing.

Such functions are available in many utility libraries. We also have two main functions.

  • nestGroup: takes one of those configuration objects and an array of objects, doing the key transfomation, property renaming, and child nesting. This is a useful function in its own right, useful if you only have one level of nesting.

  • nestGroups: calls nestGroup using the first level supplied and recursively calling nestGroups with the remaining configuration levels on the array of children. It bottoms out when there are no levels remaining, and just returns the array intact.

Finally, that last function is curried, so we can create a reusable function that embeds our configuration and just takes the array as a parameter. This might or might not be useful for the OP, but I can see it helpful in other places. We take advantage of this by calling

const nestParticipations = nestGroups (config)
// ... later
const tree = nestParticipations (participations)

But we could also just do

const tree = nestGroups (config) (participations)

You can see it in action here:

const groupBy = (fn) => (xs) => 
  xs .reduce((a, x) => ({... a, [fn(x)]: [... (a [fn (x)] || []), x]}), {})

const omit = (keys) => (obj) => 
  Object .fromEntries (Object .entries (obj) .filter (([k, v]) => !keys.includes(k)))

const nestGroup = (level) => {
  const {_children, ...rest} = level
  const keys = Object .values (rest)
  const pairs = Object .entries (rest)
  return (xs) => 
    Object .values (groupBy (x => keys .map (k => x [k]) .join ('|')) (xs))
      .map (group => ({
        ... (Object .assign (... (pairs .map (([k, v]) => ({[k]: group [0] [v] }))))),
        ... (_children ? {[_children]: group .map (omit (keys))} : {... omit (keys) (group [0])})
      }))
}

const nestGroups = ([level = undefined, ... levels]) => (xs) =>
  level == undefined
    ? xs
    : nestGroup (level) (xs)  
        .map (({[level._children]: childGroup, ... rest}) => ({
            ... rest, 
            ... (childGroup ? {[level._children]: nestGroups (levels) (childGroup)} : {})
        })) 

const config = [
  {_children: 'subtasks', taskName: 'KlasCode'},
  {_children: 'subtasks', taskName: 'Moduleomschrijving', ModuleID: 'ModuleID'},
  {_children: 'subtasks', taskName: 'ParticipationLetterCode'},
  {taskName: 'LESDatum'},
]

const nestParticipations = nestGroups (config)

let participations = [{ KlasCode: "1S RD BJ GS ma-d", LESDatum: "12/12/20", LESID: "1", ModuleID: "1050", Moduleomschrijving:"Realisaties blouse/jurk", ParticipationLetterCode: "X" }, { KlasCode: "1S RD BJ GS ma-d", LESDatum: "11/11/20", LESID: "2", ModuleID: "1050", Moduleomschrijving:"Realisaties blouse/jurk", ParticipationLetterCode: "X" }, { KlasCode: "1S RD BJ GS ma-d", LESDatum: "1/1/20", LESID: "3", ModuleID: "1050", Moduleomschrijving:"Realisaties blouse/jurk", ParticipationLetterCode: "Y" }, { KlasCode: "2S RD BJ RR ma-d", LESDatum: "5/12/20", LESID: "4", ModuleID: "1051", Moduleomschrijving:"Realisaties shirts", ParticipationLetterCode: "Z" }, { KlasCode: "2S RD BJ RR ma-d", LESDatum: "6/11/20", LESID: "4", ModuleID: "1051", Moduleomschrijving:"Realisaties shirts", ParticipationLetterCode: "Z" } ]

console .log (
  nestParticipations (participations)
)
.as-console-wrapper {min-height: 100% !important; top: 0}

If I wanted to spend more time on this, I think I would break this apart a bit further, and I would probably use a configuration more like this:

[
  { children: 'subtasks', matchOn: [ 'KlasCode' ], rename: { KlasCode: 'taskName' } },
  {
    children: 'subtasks', 
    matchOn: [ 'Moduleomschrijving', 'ModuleID' ], 
    rename: { Moduleomschrijving: 'taskName' }
  },
  {
    children: 'subtasks', 
    matchOn: [ 'ParticipationLetterCode' ],
    rename: { ParticipationLetterCode: 'taskName' }
  },
  { rename: {LESDatum, 'taskName'} }
]

That is left as an exercise for the reader…

1👍

We have checked your Array Structure, In order to convert FlatArray to TreeGrid Arrary structure, it is necessary to define Mapping Field to form parent-child hierarchy. So we suggest you to use to define Field (as like given below ParentId) to form TreeGrid structure.

While using Self-Referential Data binding (Flat Data )in TreeGrid component it is necessary to define the IdMapping and ParentIdMapping property for the hierarchy relations.
Refer to the code example:-

let treeGridObj: TreeGrid = new TreeGrid({
dataSource: participations,
idMapping: ‘LESID’,
parentIdMapping: ‘ParentId’,
allowPaging: true,
treeColumnIndex: 1,
columns: [
{ field: ‘LESID’, headerText: ‘Task ID’, width: 90, textAlign: ‘Right’ },
{ field: ‘ParticipationLetterCode’, headerText: ‘Task Name’, width: 180 },
. . .
]
});

Refer to the below TreeGrid DataSourceArray Structure:-

let participations = [{
KlasCode: “1S RD BJ GS ma-d”,
LESDatum: “12/12/20”,
LESID: 1,
ModuleID: “1050”,
Moduleomschrijving: “Realisaties blouse/jurk”,
ParticipationLetterCode: “X”,
ParentId = null

    }, { 
        KlasCode: "1S RD BJ GS ma-d", 
        LESDatum: "11/11/20", 
        LESID: 2, 
        ModuleID: "1050", 
        Moduleomschrijving: "Realisaties blouse/jurk", 
        ParticipationLetterCode: "X", 
        ParentId = 1                               //  Here ParentId(ParentIdMapping) value with 1 has been grouped under LESID(IdMapping) with Value 1 
    }, 
    { 
        KlasCode: "1S RD BJ GS ma-d", 
        LESDatum: "1/1/20", 
        LESID: 3, 
        ModuleID: "1050", 
        Moduleomschrijving: "Realisaties blouse/jurk", 
        ParticipationLetterCode: "Y", 
        ParentId = 1 
    }, 
    { 
        KlasCode: "2S RD BJ RR ma-d", 
        LESDatum: "5/12/20", 
        LESID: 4, 
        ModuleID: "1051", 
        Moduleomschrijving: "Realisaties shirts", 
        ParticipationLetterCode: "Z", 
        ParentId = null 
    }, 
    { 
        KlasCode: "2S RD BJ RR ma-d", 
        LESDatum: "6/11/20", 
        LESID: 5, 
        ModuleID: "1051", 
        Moduleomschrijving: "Realisaties shirts", 
        ParticipationLetterCode: "Z", 
        ParentId = 4 
    } 
    ] 

ID Field: This field contains unique values used to identify nodes. Its name is assigned to the idMapping property.
Parent ID Field: This field contains values that indicate parent nodes. Its name is assigned to the parentIdMapping property.

Refer to the documentation and Demo Link:-
https://ej2.syncfusion.com/demos/#/material/tree-grid/selfreference.html
https://ej2.syncfusion.com/documentation/treegrid/data-binding/#self-referential-data-binding-flat-data

Please get back to us if you need any further assistance

Regards,
Farveen sulthana T

Leave a comment