-
Notifications
You must be signed in to change notification settings - Fork 77
Description
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
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>
