forcedotcom / salesforcedx-templates

Salesforce Templates Node Library
BSD 3-Clause "New" or "Revised" License
80 stars 57 forks source link

Inconsistent behavior in Salesforce LWC Hierarchy Tree Grid selection and state #591

Open rafasegat opened 4 months ago

rafasegat commented 4 months ago

Full post here: https://salesforce.stackexchange.com/questions/424214/inconsistent-behavior-in-salesforce-lwc-hierarchy-tree-grid-selection-and-state

I'm working on a LWC Dashboard Analytics component - https://developer.salesforce.com/docs/atlas.en-us.bi_dev_guide_lwc_in_db.meta/bi_dev_guide_lwc_in_db/bi_lwc_in_db_reference.htm

image

I'm encountering an unexpected behavior in a Salesforce LWC component, and I'm not sure if it's a bug or an issue with my code. Here's the scenario I'm observing:

Scenario:

1 - Page loads user is on "Tab 1"

2 - User clicks "Current Tab"

Console.log:

this.selection = []

this.getStateStepFilter = ['item1', 'item2', 'item3', 'item4'] // Unexpected, should be []

3 - User selects Item 1 and click "Update": works nice

this.selection = ['item1']

this.getStateStepFilter = ['item1']

4 - User clicks on "Tab 2"

5 - User clicks back on "Current Tab"

this.selection = ['item1', 'item2', 'item3', 'item4'] // Unexpected, should be ['item1']

this.getStateStepFilter = ['item1', 'item2', 'item3', 'item4'] // Unexpected, should be ['item1']

-- End

Additionally, when refreshing the page, the values seem to be set inconsistently.

Questions:

Is this a known Salesforce bug? Could this be an issue with my code? Has anyone else experienced similar behavior with Salesforce tab components?

import { LightningElement, api, track, wire } from 'lwc'; 
import saveUserSelection from '@salesforce/apex/HierarchyTreeGridUserSelection.saveUserSelection';
import apex_getUserSelection from '@salesforce/apex/HierarchyTreeGridUserSelection.getUserSelection';
import USER_ID from '@salesforce/user/Id';
import { refreshApex } from '@salesforce/apex';

export default class HierarchyTreeGrid_DEV extends LightningElement {
  @api selection;
  @api setSelection;
  @api refresh;
  @api getState;
  @api setState;
  @api metadata;
  @track selectedRows = [];
  @track selectValuesText = "All";

  isInitialized = false;

  @api stateChangedCallback(_, newState) {
    const stateStepFilter = this.getStateStepFilter(newState);
    const selectedRows = stateStepFilter || this.selection.map((e) => e.Id);
    if(selectedRows?.length) {
      console.log("stateChangedCallback stateStepFilter", JSON.parse(JSON.stringify(stateStepFilter)));
      console.log("stateChangedCallback selection", JSON.parse(JSON.stringify(this.selection)));
      console.log("stateChangedCallback selectedRows", JSON.parse(JSON.stringify(selectedRows || '{}')));
    } 
  }

  connectedCallback()  {
    const stateStepFilter = this.getStateStepFilter(newState);
    const selectedRows = stateStepFilter || this.selection.map((e) => e.Id);
    if(selectedRows?.length) {
      console.log("connectedCallback stateStepFilter", JSON.parse(JSON.stringify(stateStepFilter)));
      console.log("connectedCallbackselection", JSON.parse(JSON.stringify(this.selection)));
      console.log("connectedCallbackselectedRows", JSON.parse(JSON.stringify(selectedRows || '{}')));
  }

  getStateStepFilter(state) {
    const currentStep = Object.values(state?.state?.steps).find((value) => {
      const array1 = value?.metadata?.groups;
      const array2 = this.metadata?.groups;
      return (array1?.length === array2?.length) && array1.every((el, i) => {
        return el === array2[i]; 
      });
    });
    return currentStep?.values.map((item) => item[1]) || null;
  }

  setSelectedRows(selectedRows) {
    this.selectedRows = selectedRows;
    this.selectedRows?.length == 0 ? this.selectValuesText = "All" : this.selectValuesText = "Selected " + "(" + this.selectedRows?.length + ")";
  }

  wiredUserSelectionResult;
  @wire(apex_getUserSelection, { componentId: '$titleLabel', userId: USER_ID })
  wiredUserSelection(result) {
    this.wiredUserSelectionResult = result;
    if (result?.data?.length ) {
      try {
        const selectedRows = this.getStateStepFilter(this.getState());
        console.log("loaded from cache selectedRows(state) ", selectedRows)
        console.log("loaded from cache selection ", JSON.parse(JSON.stringify(this.selection || {})))
        const jsonData = JSON.parse(result?.data);
        console.log("loaded from cache jsonData", jsonData);
      } catch (e) {
        console.error('Error parsing selectedRows:', e);
      }
    } else if (result.error) {
      console.error('Error retrieving user selection:', result.error);
    }
  }

XML

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>59.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Hierarchy Tree Grid DEV</masterLabel>
    <targets>
        <target>analytics__Dashboard</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="analytics__Dashboard">
            <hasStep>true</hasStep>
            <property name="idColumn" type="Dimension" label="ID Column" description="Primary key." required="true" />
            <property name="parentIdColumn" type="Dimension" label="Parent ID Column" description="Self-reference to parent record." required="true" />
            <property name="labelColumn" type="Dimension" label="Label Column" description="Record label." required="true" />
            <property name="root" type="String" label="Root Node" />
            <property name="titleLabel" type="String" label="Title Label" required="true"/>
            <property name="filterDivCSS" type="String" label="Stylesheet for the filter Div" />
            <property name="titleLabelCSS" type="String" label="Title Label Stylesheet" description="Stylesheet for the title label" />
            <property name="selectedValueCSS" type="String" label="Selected Value Stylesheet" description="Stylesheet for the Selected Value" />
            <property name="treeGridCSS" type="String" label="Tree Grid Stylesheet" description="Stylesheet for the tree grid" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

<template>
    <div class="collapsible-filter-widget" tabindex="0" role="button" aria-haspopup="dialog" onclick={openPopover} onkeydown={onKeyDownHandler}>
        <div class="collapsible-filter-body" style={filterDivCSS} >
            <div class="dropdown-trigger">
                <div class="title" style={titleLabelCSS}>{titleLabel}</div>
                <div class="selected-values" style={selectedValueCSS}>{selectValuesText}</div>
                <div style="position:absolute;right:8px;top:30%;">
                    <lightning-icon icon-name="utility:chevrondown" alternative-text="Down Arrow" title="Down Arrow" size="xx-small" ></lightning-icon>
                </div>
            </div>
        </div>
    </div>
<!--- PopOver Component with a tree grid table -->
    <template if:true={isPopOverOpen}>
        <div class="overlay-popup" onclick={closePopover}></div>
        <section aria-describedby="dialog-body-id-110" aria-labelledby="dialog-heading-id-5" class="slds-popover slds-popover_prompt dropdown-menu" 
        role="dialog" onmouseleave={mouseOutHandler} onmouseenter={mouseEnterHandler} 
        style="outline: 0px; position: absolute; width: 100%; min-width: 250px; margin-top: 5px; margin-bottom: 12px;">
            <button class="slds-button slds-button_icon slds-button_icon-small slds-popover__close" title="Close dialog" onclick={closePopover}>
                <lightning-icon icon-name="utility:close" alternative-text="Close" title="Close" size="x-small" ></lightning-icon>
                <span class="slds-assistive-text">Close dialog</span>
            </button>
            <div class="slds-popover__body" id="dialog-body-id-110">
                <div class="slds-media">
                    <div class="slds-media__body">
                        <div onchange={handleSearch}>
                            <lightning-input
                                name="searchBox"
                                label="Search"
                                type="search"
                            ></lightning-input>
                        </div>
                        <div class="slds-scrollable" style={treeGridCSS} >
                            <lightning-tree-grid
                            columns={gridColumns}
                            data={dataJson}
                            key-field="recordId"
                            aria-label="name"
                            onrowselection={updateSelectedRows}
                            selected-rows={selectedRows}
                            ontoggle={handleRowToggle}
                            min-column-width="200"
                            wrap-text="true"
                            ></lightning-tree-grid>
                        </div>
                    </div>
                </div>
            </div>
                <footer class="slds-popover__footer">
                    <div class="slds-grid slds-grid_vertical-align-center">
                        <button class="slds-button slds-button_neutral slds-col_bump-right" onclick={selectAllHierarchyTree}>Select all</button>
                        <button class="slds-button slds-button_neutral slds-col_bump-right" onclick={deselectAllHierarchyTree}>Deselect all</button>
                        <lightning-button variant="brand" label="Update" title="Update" onclick={updateFacets} ></lightning-button>
                    </div>
                </footer>

          </section>
    </template>
</template>