[SalesForce] HTML table with left side headers in LWC

I have below HTML

                <div>
                    <table class="slds-table slds-table_cell-buffer slds-table_bordered">
                        <thead>
                            <template for:each={pageDetailsHeaders} for:item="pageDetail">
                                <tr class="slds-line-height_reset" key={pageDetail}>
                                    <th scope="row">
                                        <div>
                                            {pageDetail}
                                        </div>
                                    </th>
                                </tr>
                            </template>
                        </thead>
                        <tbody>
                            <template for:each={pageDetailsData} for:item="pageDetail">
                                <tr key={pageDetail}>
                                    <td>
                                        <div>
                                            {pageDetail}
                                        </div>
                                    </td>
                                </tr>
                            </template>
                        </tbody>
                    </table>
                </div>

JS:

handlePageDetails() {
        let pageDetailsMap = new Map();
        this.showPageDetails = this.showPageDetails ? false : true;
        //if (this.showPageDetail === false) return;
        lineItemData.forEach(data => {
            let category = data.category ? data.category : 'Without Category';
            if (!pageDetailsMap.has(category)) {
                pageDetailsMap.set(category, 1);
            } else {
                let updatedValue = pageDetailsMap.get(category) + 1;
                pageDetailsMap.set(category, updatedValue);
            }
            if (!pageDetailsMap.has(data.testType)) {
                pageDetailsMap.set(data.testType, 1);
            } else {
                let updatedValue = pageDetailsMap.get(data.testType) + 1;
                pageDetailsMap.set(data.testType, updatedValue);
            }
        })
        pageDetailsMap.set('Total Products', lineItemData.length);
        pageDetailsMap.set('Total Visits', this.studyData.length);
        this.pageDetailsHeaders = Array.from(pageDetailsMap.keys());
        this.pageDetailsData = Array.from(pageDetailsMap.values());
        console.log(pageDetailsMap.entries());
    }

I tried to iterate map but didn't get any success so I converted keys and values in different array and then iterated on HTML and I am getting results as below

table image

table data and header should come side by side instead of all in one column, can anyone suggest any better approach to do the same.

Best Answer

Salesforce styles are expecting the th to be on the thead element to give the styles, and the problem you are facing is that thead rows are shown on the top.

You could try to play with css, but the second you have rows with more than one line, you might face some rendering issues.

My suggestion below is to have all the data inside the tbody, having the first cell as header (th) and the second as regular cell (td).

As per your last comment, I think that this could be closer to what you are looking for (note that you might have to edit some styles. I put one in-line so it looks closer to the standard, but I strongly recommend to use the .css file of the component, taking advantage of the fact that the header is in th. Feel free to copy the ones from SF thead>th)

    <div>
        <table class="slds-table slds-table_cell-buffer slds-table_bordered">
            <tbody>
                <template for:each={pageDetailsData} for:item="pageDetail">
                    <tr key={pageDetail.header}>
                        <th style="background-color: var(--lwc-tableColorBackgroundHeader,rgb(250, 250, 249))">
                            <div>
                                {pageDetail.header}
                            </div>
                        </th>
                        <td>
                            <div>
                                {pageDetail.value}
                            </div>
                        </td>
                    </tr>
                </template>
            </tbody>
        </table>
    </div>

.js ( I did not check if it compiles, but it gives you an idea. The idea is to have a list with two attributes, header and value

    handlePageDetails() {

        this.showPageDetails = this.showPageDetails ? false : true;
        //if (this.showPageDetail === false) return;

        // reduce goes over every element and passes an accumulator so you can keep track of your counters
        // I've used the operator || to give defaults to undefined variables (which saves you the trouble of all those "ifs"
        let pageDetailsMap = lineItemData.reduce((accumulator, data) => {

            let category = data.category || 'Without Category';

            accumulator.set(category, (accumulator.get(category) || 0) + 1);
            accumulator.set(data.testType, (accumulator.get(data.testType) || 0) + 1);

        }, new Map());

        pageDetailsMap.set('Total Products', lineItemData.length);
        pageDetailsMap.set('Total Visits', this.studyData.length);

        // .map iterates over all the items of the array replacing them by the returned value of the function. in this case the header gets replaced by a structure with a header field and a value field)
        this.pageDetailsData = Array.from(pageDetailsMap.keys()).map(key=>{
            return {
                header : key,
                value  : pageDetailsMap.get(key)
            };
        });
        console.log(pageDetailsMap.entries());
    }
Related Topic