9

I am trying to create a custom Tree Grid component in that based on my business use case I'm in need to create every row as a component, and every column as a component. Below is the structure of my component

table container component

<template>
   <table>
    <thead>
    </thead>
    <tbody>
     <template for:each={datas} for:item="d">
            <c-child key={d.Id} data={d}></c-child>
       </template>
    </tbody>
  </table>

</template>

Child component

<template>
  <td>{data.Name}</td>
  <td>{data.age}</td>
  <td>{data.num}</td>
  <td>{data.Email}</td>
          <template for:each={data.childrens} for:item="d">
                <c-child key={d.Id} data={d}></c-child>
           </template>
</template>

Child.css

:host {
  display: table-row;
}

Here My Question is when I try to build a row component recursively for the tree structure . Since My row component doesn't contain <tr> I cannot able to render recursively because every custom element(LWC component) includes a tag with the component name in HTML Lightning Web Component for table rows and cellsbased on this link every row component behaves like a row when applying display: table-row;

So My nested tree structure looks like when I try to build the row component recursively, So my host component behaves like single tr

<c-row>
   <td></td>
   <td></td>
   <c-row>
   <c-row>
</c-row>

Here is a Playground link that I described my issue with a simple example.

UPDATE :

Below is the screenshot of UI I am trying to build

Example Tree Grid

  • Any reason not using lightning-tree or lightning-tree-grid? – Atlas Can Dec 27 '19 at 6:54
  • We need inline edit feature in tree grid and some other business use cases which cannot be covered in tree grid so we planned to build custom grid component – vignesh Dec 27 '19 at 9:06
  • can you explain exactly what UI you are trying to render? any screenshot of that? – rahul gawale Dec 27 '19 at 10:15
  • I just want to render like slds tree grid lightningdesignsystem.com/components/tree-grid and I provided the playground URL above it describes the issue which I am facing' – vignesh Dec 27 '19 at 12:26
  • @rahulgawale Here is the screenshot of UI Iam trying to build drive.google.com/file/d/1r7Wh_vADwBbD11BI4NaZwz1N95DQ0tMI/… – vignesh Dec 27 '19 at 13:17
3
+50

When I want to rebuild/extend some of the functionality for LWC I usually get styling from SLDS. You can check markup here for tree-grid.

Salesforce flattens data and uses aria-expanded, aria-level for styling. I would suggest the same. Here is the script to flatten data. I also updated playground with simple styling for Id

UPDATE: Created separate playground to not override changes.

UPDATE Added expand/hide.

Here is sample row markup with some code.

// rendered.js
let data = [
    { Name:'st1', Id:1, age:'2', num:'342222222', Email:'v@gmail.com' },
    { Name:'st1', age:'2', Id:2, num:'342222222', Email:'v@gmail.com', children:[
        { Name:'st1child', Id:6, age:'2', num:'342222222', Email:'v@gmail.com' },
        { Name:'st1child', Id:7, age:'2', num:'342222222', Email:'v@gmail.com' }, ]
    },
    { Name:'st1', age:'2', Id:3, num:'342222222', Email:'v@gmail.com' },
    { Name:'st1', age:'2', Id:4, num:'342222222', Email:'v@gmail.com' },
    { Name:'st1', age:'2', Id:5, num:'342222222', Email:'v@gmail.com' },
];
const isExpandedDefault = false;
const flatIt = (data, level) => data.reduce((acc, row) => [...acc, Object.assign(row, {
    level,
    expanded: isExpandedDefault,
}), ...flatIt(row.children || [], level + 1)], []);
data = flatIt(data, 1);
<!-- parent.html -->
<template>
   <table class="slds-tree">
       <thead>
           <th>Name</th>

           <th>Age</th>

           <th>Num</th>

           <th>Email</th>
       </thead>
       <tbody>
           <template for:each={datas} for:item="d">
                <c-child key={d.Id} data={d}></c-child>
           </template>
       </tbody>

   </table>
</template>
// Child.js
export default class Child extends LightningElement {
    @track _data;

    renderedCallback() {

    }

    @api
    set data(value) {
        this._data = value;
        this.ariaExpanded = false;
        this.ariaLevel = value.level;
    }

    get data() {
        return this._data;
    }
}
| improve this answer | |
  • I think this is not really solving the issue since it is forcing to change the data structure from a tree to a list of elements. Is there a way to maintain the tree structure and still render the children data? Thanks! – Ismael Slimane May 13 at 14:02
  • @IsmaelSlimane you can add this transformation on the getter, so you both store you tree structure and list display – ytiq May 13 at 15:54
0

I think there are are two ways to achieve what you want:

  1. Don't change underlying data structure but change UI markup handle data structure.
  2. Change underlying data structure but keep UI markup simple.

I chose option 2 and was able to get this output:

Link to playground: https://developer.salesforce.com/docs/component-library/tools/playground/KG-dO5AL/2/edit

enter image description here

app.html:

<template>
  <table>
    <thead>
        <th>Name</th>
    </thead>
    <tbody>
        <template for:each={tableData} for:item="rowData">
            <tr key={rowData.Id} class={rowData.rowStyle}>
                <td>
                    <span style={rowData.nameStyle}>
                        <lightning-icon icon-name={rowData.iconName} 
                        size=x-small 
                        onclick={showOrHideChildrenRows} 
                        data-rowid={rowData.Id}
                        data-expanded="false">
                        </lightning-icon>
                        {rowData.Name}
                    </span>
                </td>
            </tr>
        </template>
    </tbody>
  </table>
</template>

app.js:

import { LightningElement, track } from "lwc";

export default class App extends LightningElement {
    @track
    tableData =[
                {
                    Name: "United States",
                    Id: 1,
                    iconName: "utility:chevronright",
                    parentId : null,
                    rowStyle : "",
                    nameStyle : ""
                },
                {
                    Name: "Massachusetts",
                    Id: 2,
                    iconName: "utility:chevronright",
                    parentId: 1,
                    rowStyle : "hide",
                    nameStyle : "margin-left:10px;"
                },
                {
                    Name: "Boston",
                    Id: 3,
                    iconName: "",
                    parentId: 2,
                    rowStyle : "hide",
                    nameStyle : "margin-left:20px;"
                },              
                {
                    Name: "New York",
                    Id: 4,
                    iconName: "",
                    parentId : 1,
                    rowStyle : "hide",
                    nameStyle : "margin-left:10px;"
                },
                {
                    Name: "Vatican City",
                    Id : 5,
                    iconName: "",
                    parentId : null,
                    rowStyle : "",
                    nameStyle : ""
                },
                {
                    Name: "Canada",
                    Id : 6,
                    iconName: "utility:chevronright",
                    parentId : null,
                    rowStyle : "",
                    nameStyle : ""
                },
                {
                    Name: "Ontario",
                    Id : 7,
                    iconName: "",
                    parentId : 6,
                    rowStyle : "hide",
                    nameStyle : "margin-left:10px;"
                },
                {
                    Name: "Alberta",
                    Id : 8,
                    iconName: "",
                    parentId : 6,
                    rowStyle : "hide",
                    nameStyle : "margin-left:10px;"
                }
            ];

    showOrHideChildrenRows(event){
        let rowId = event.target.dataset.rowid;
        let isExpanded = event.target.dataset.expanded;
        event.target.iconName = JSON.parse(isExpanded) ? "utility:chevronright": "utility:chevrondown";
        event.target.dataset.expanded = JSON.stringify(!JSON.parse(isExpanded));

        this.tableData = this.tableData.map((obj) => {
            if(obj.parentId == rowId &&  !JSON.parse(isExpanded)){
                obj.rowStyle = "";
            }
            if(obj.parentId == rowId && JSON.parse(isExpanded)){
                obj.rowStyle = "hide";
            }
            return obj;
        });
        console.log(this.tableData);

    }

}

Each element in the data structure has these special attributes:

  • parentId - to relate each element to its parent element.
  • rowStyle - to hide or show children elements.
  • nameStyle - to apply proper indentation to parent elements.

EDIT:

As per ytiq's comment I added the logic on how to flatten the data given in the question. here is the updated playground

Note that the logic to flatten the original data structure may need to be changed based on factors like:

  1. how many levels deep the original data structure is going to be
  2. Is Id going to be unique across entire hierarchy.
| improve this answer | |
  • You hard-coded styling, so you didn't solve the issue for properly displaying the underlying data – ytiq Dec 29 '19 at 17:41
  • @ytiq we can solve that while flattening the data. My point was to show that we don’t need to work with the same data structure that we are getting from server. I will try to update logic on how to flatten the data structure. – javanoob Dec 29 '19 at 18:12
  • @ytiq check my upddated playground. I think this is what you referring to in your previous comment. – javanoob Dec 29 '19 at 22:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.