diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.html b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.html index f4f2624f7..fdc3e2ced 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.html +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.html @@ -15,6 +15,7 @@ disable-reordering={disableReordering} size={size} all-options={_options} + required={required} required-options={requiredOptions} selected-options={_selectedValues} use-object-value-as-output> diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js index d4c850a33..d9767ebe7 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js @@ -1,189 +1,625 @@ -import {LightningElement, api, track} from 'lwc'; -import {FlowAttributeChangeEvent} from "lightning/flowSupport"; +import { LightningElement, api, track, wire } from "lwc"; +import { FlowAttributeChangeEvent } from "lightning/flowSupport"; +import { getObjectInfo } from "lightning/uiObjectInfoApi"; +import getPicklistValues from "@salesforce/apex/usf3.FieldPickerController.getPicklistValues"; import { - defaults, - inputTypeToOutputAttributeName, - inputTypeToInputAttributeName -} from 'c/fsc_dualListBoxUtils'; + defaults, + inputTypeToOutputAttributeName, + inputTypeToInputAttributeName, +} from "c/fsc_dualListBoxUtils"; export default class dualListBoxFSC extends LightningElement { + @api label; + @api sourceLabel; + @api fieldLevelHelp; + @api selectedLabel; - @api label; - @api sourceLabel; - @api fieldLevelHelp; - @api selectedLabel; - - @api min; - @api max; - @api disableReordering; - @api size; - @api required; - @api requiredOptions; - @api useWhichObjectKeyForData = defaults.valueField; - @api useWhichObjectKeyForLabel = defaults.labelField; - @api useWhichObjectKeyForSort; - @api useObjectValueAsOutput = false; - - _allOptionsStringFormat; - @track selectedValuesStringFormat; - @track _options = []; - @track _selectedValues = []; - @track optionValues = {}; - - - set allOptionsStringFormat(value) { - - this._allOptionsStringFormat = value; - //TODO: ask if we need to have this as a separate list of types of output parameters - - this.selectedValuesStringFormat = value; - if (inputTypeToInputAttributeName[value] && this.optionValues[inputTypeToInputAttributeName[value]]) { - this._options = this.optionValues[inputTypeToInputAttributeName[value]]; - } - if (!this._selectedValues && inputTypeToOutputAttributeName[value] && this.optionValues[inputTypeToOutputAttributeName[value]]) { - this._selectedValues = this.optionValues[inputTypeToOutputAttributeName[value]]; - } - if (this._allOptionsStringFormat === defaults.csv && (!this._selectedValues || !this._selectedValues.length)) { - this._selectedValues = ''; + @api min; + @api max; + @api disableReordering; + @api size; + @api required; + @api requiredOptions; + @api useWhichObjectKeyForData = defaults.valueField; + @api useWhichObjectKeyForLabel = defaults.labelField; + @api useWhichObjectKeyForSort; + @api useObjectValueAsOutput = false; + + // New properties for picklist integration + @api objectApiName; + @api fieldApiName; + @api usePicklistValues = false; + + _allOptionsStringFormat; + @track selectedValuesStringFormat; + @track _options = []; + @track _selectedValues = []; + @track optionValues = {}; + @track picklistValues = []; + @track isLoading = false; + @track errorMessage; + @track _values = []; // Internal selected values storage + + get STORAGE_KEY() { + // Extract field name from fieldApiName (could be object or string) + let fieldName = "noField"; + if (this.fieldApiName) { + if (typeof this.fieldApiName === "string") { + try { + const parsed = JSON.parse(this.fieldApiName); + fieldName = parsed[0]?.name || parsed.name || "noField"; + } catch (e) { + fieldName = this.fieldApiName; } + } else if (this.fieldApiName.name) { + fieldName = this.fieldApiName.name; + } } - set allOptionsFieldDescriptorList(value) { - this.setOptions(inputTypeToInputAttributeName.object, value); - } + return `dualListBox:${this.objectApiName || "noObject"}:${fieldName}`; + } + + connectedCallback() { + console.log("connectedCallback called"); + console.log("Initial _selectedValues:", this._selectedValues); + console.log( + "Initial selectedOptionsPicklist:", + this.selectedOptionsPicklist + ); + this.restoreSelectedValues(); + } - set allOptionsStringCollection(value) { - this.setOptions(inputTypeToInputAttributeName.list, value); + disconnectedCallback() { + // Only clear storage if we're not in the middle of validation + // Validation might cause temporary disconnection, so we'll keep the storage + console.log("disconnectedCallback called - keeping storage for validation"); + // window.sessionStorage.removeItem(this.STORAGE_KEY); + } + + renderedCallback() { + // Restore values after re-render (e.g., after validation fails) + if (!this._selectedValues || this._selectedValues.length === 0) { + this.restoreSelectedValues(); } + } + + restoreSelectedValues() { + console.log("restoreSelectedValues called"); + console.log("STORAGE_KEY:", this.STORAGE_KEY); + console.log("selectedValuesStringFormat:", this.selectedValuesStringFormat); + console.log("allOptionsStringFormat:", this.allOptionsStringFormat); + console.log("Current _selectedValues:", this._selectedValues); + console.log( + "Current selectedOptionsPicklist:", + this.selectedOptionsPicklist + ); - set allOptionsStringCollectionLabels(value) { - this.setOptions(inputTypeToInputAttributeName.twoLists, value); + const cached = window.sessionStorage.getItem(this.STORAGE_KEY); + console.log("cached value:", cached); + + // Don't restore if we don't have the format set yet + if (!this.selectedValuesStringFormat) { + console.log( + "selectedValuesStringFormat not set yet, skipping restoration" + ); + return; } - set allOptionsCSV(value) { - this.setOptions(inputTypeToInputAttributeName.csv, value); + // Check if we already have values from Flow Builder + if (this._selectedValues && this._selectedValues.length > 0) { + console.log( + "Already have selected values from Flow Builder:", + this._selectedValues + ); + return; } - set selectedOptionsStringList(value) { - this.setOptions(inputTypeToOutputAttributeName.list, value); + // If _selectedValues is explicitly set to empty array, don't restore + if ( + Array.isArray(this._selectedValues) && + this._selectedValues.length === 0 + ) { + console.log("Values explicitly cleared - not restoring from storage"); + return; } - set selectedOptionsCSV(value) { - this.setOptions(inputTypeToOutputAttributeName.csv, value); + if (cached) { + try { + const selectedValues = JSON.parse(cached); + console.log("parsed selectedValues:", JSON.stringify(selectedValues)); + + if (selectedValues && selectedValues.length > 0) { + this._selectedValues = [...selectedValues]; + console.log( + "Restored selected values:", + JSON.stringify(this._selectedValues) + ); + + // Force a re-render by triggering a property change + this._selectedValues = [...this._selectedValues]; + + // Also dispatch to Flow to update the output + const outputAttribute = + inputTypeToOutputAttributeName[this.allOptionsStringFormat]; + let flowValue = this._selectedValues; + + if ( + this.allOptionsStringFormat === defaults.picklist || + this.allOptionsStringFormat === defaults.csv + ) { + flowValue = this._selectedValues.join(","); + } + + this.dispatchEvent( + new FlowAttributeChangeEvent(outputAttribute, flowValue) + ); + console.log( + "Dispatched restored values to Flow:", + JSON.stringify(flowValue) + ); + } + } catch (e) { + console.error("Error parsing cached values:", e); + } + } else { + console.log("No cached values found"); } + } + + saveSelectedValues() { + console.log("saveSelectedValues called"); + console.log("_selectedValues:", JSON.stringify(this._selectedValues)); + console.log("STORAGE_KEY:", this.STORAGE_KEY); - set selectedOptionsFieldDescriptorList(value) { - this.setOptions(inputTypeToOutputAttributeName.object, value); + if (this._selectedValues && this._selectedValues.length > 0) { + const valueToSave = JSON.stringify(this._selectedValues); + window.sessionStorage.setItem(this.STORAGE_KEY, valueToSave); + console.log( + "Saved selected values to sessionStorage:", + JSON.stringify(valueToSave) + ); + } else { + console.log("No values to save"); } + } - @api - get allOptionsStringFormat() { - return this._allOptionsStringFormat; + _pushToFlow() { + console.log("_pushToFlow called"); + console.log("_values:", this._values); + this._selectedValues = [...this._values]; + console.log("_selectedValues:", this._selectedValues); + const outputAttribute = + inputTypeToOutputAttributeName[this.allOptionsStringFormat]; + console.log("outputAttribute:", outputAttribute); + let value = this._values; + + // Convert to appropriate format for the output attribute + if ( + this.allOptionsStringFormat === defaults.picklist || + this.allOptionsStringFormat === defaults.csv + ) { + value = this._values.join(","); } + console.log("value to dispatch:", value); + + this.dispatchEvent(new FlowAttributeChangeEvent(outputAttribute, value)); + window.sessionStorage.setItem( + this.STORAGE_KEY, + JSON.stringify(this._values) + ); + console.log( + "Saved to sessionStorage:", + this.STORAGE_KEY, + JSON.stringify(this._values) + ); + } - @api - get allOptionsFieldDescriptorList() { - return this.getOptions(defaults.originalObject); + set allOptionsStringFormat(value) { + this._allOptionsStringFormat = value; + //TODO: ask if we need to have this as a separate list of types of output parameters + + this.selectedValuesStringFormat = value; + if ( + inputTypeToInputAttributeName[value] && + this.optionValues[inputTypeToInputAttributeName[value]] + ) { + this._options = this.optionValues[inputTypeToInputAttributeName[value]]; + } + if ( + !this._selectedValues && + inputTypeToOutputAttributeName[value] && + this.optionValues[inputTypeToOutputAttributeName[value]] + ) { + this._selectedValues = + this.optionValues[inputTypeToOutputAttributeName[value]]; } - @api - get allOptionsStringCollection() { - return this.getOptions(defaults.list); + // Handle picklist selected values specifically + if (value === defaults.picklist && this.selectedOptionsCSV) { + this._selectedValues = this.selectedOptionsCSV + .split(",") + .map((item) => item.trim()); } - @api - get allOptionsStringCollectionLabels() { - return this.getOptions(defaults.twoLists); + // Restore values from sessionStorage when format changes + const cached = window.sessionStorage.getItem(this.STORAGE_KEY); + if (cached) { + const fromCache = JSON.parse(cached); + if (fromCache && fromCache.length) { + this._values = [...fromCache]; + this._selectedValues = [...fromCache]; + } } + if ( + this._allOptionsStringFormat === defaults.csv && + (!this._selectedValues || !this._selectedValues.length) + ) { + this._selectedValues = ""; + } + } + + set allOptionsFieldDescriptorList(value) { + this.setOptions(inputTypeToInputAttributeName.object, value); + } + + set allOptionsStringCollection(value) { + this.setOptions(inputTypeToInputAttributeName.list, value); + } + + set allOptionsStringCollectionLabels(value) { + this.setOptions(inputTypeToInputAttributeName.twoLists, value); + } - @api - get allOptionsCSV() { - return this.getOptions(defaults.csv); + set allOptionsCSV(value) { + this.setOptions(inputTypeToInputAttributeName.csv, value); + } + + set selectedOptionsStringList(value) { + this.setOptions(inputTypeToOutputAttributeName.list, value); + } + + set selectedOptionsCSV(value) { + this.setOptions(inputTypeToOutputAttributeName.csv, value); + } + + set selectedOptionsPicklist(value) { + console.log("selectedOptionsPicklist setter called with:", value); + console.log("Value type:", typeof value); + console.log("Is array:", Array.isArray(value)); + + // For picklist format, value should be an array of selected values + if (Array.isArray(value)) { + this._selectedValues = [...value]; + console.log("Set picklist selected values:", this._selectedValues); + + // If values are cleared (empty array), clear sessionStorage too + if (value.length === 0) { + console.log("Values cleared - removing from sessionStorage"); + window.sessionStorage.removeItem(this.STORAGE_KEY); + } else { + // Save to sessionStorage + this.saveSelectedValues(); + } + } else if (typeof value === "string" && value.includes(",")) { + // Handle CSV string input for backward compatibility + const selectedArray = value.split(",").map((item) => item.trim()); + this._selectedValues = selectedArray; + console.log("Converted CSV string to picklist array:", selectedArray); + this.saveSelectedValues(); + } else if (value) { + // Handle single value + this._selectedValues = [value]; + console.log("Set single picklist value:", this._selectedValues); + this.saveSelectedValues(); + } else { + console.log("No picklist values provided - clearing selection"); + this._selectedValues = []; + // Clear sessionStorage when no values provided + window.sessionStorage.removeItem(this.STORAGE_KEY); } + } + + set selectedOptionsFieldDescriptorList(value) { + this.setOptions(inputTypeToOutputAttributeName.object, value); + } + + @api + get allOptionsStringFormat() { + return this._allOptionsStringFormat; + } + + @api + get allOptionsFieldDescriptorList() { + return this.getOptions(defaults.originalObject); + } - @api - get selectedOptionsStringList() { - return this.getValues(defaults.list); + @api + get allOptionsStringCollection() { + return this.getOptions(defaults.list); + } + + @api + get allOptionsStringCollectionLabels() { + return this.getOptions(defaults.twoLists); + } + + @api + get allOptionsCSV() { + return this.getOptions(defaults.csv); + } + + @api + get selectedOptionsStringList() { + return this.getValues(defaults.list); + } + + @api + get selectedOptionsCSV() { + return this.getValues(defaults.csv); + } + + @api + get selectedOptionsPicklist() { + return this.getValues(defaults.picklist); + } + + @api + get selectedOptionsFieldDescriptorList() { + return this.getValues(defaults.originalObject); + } + + get isDataSet() { + const result = + this.isPicklistMode || + (this.allOptionsStringFormat && + this.useWhichObjectKeyForData && + this.useWhichObjectKeyForLabel); + console.log("isDataSet:", result); + console.log("isPicklistMode:", this.isPicklistMode); + console.log("allOptionsStringFormat:", this.allOptionsStringFormat); + console.log("useWhichObjectKeyForData:", this.useWhichObjectKeyForData); + console.log("useWhichObjectKeyForLabel:", this.useWhichObjectKeyForLabel); + return result; + } + + get isPicklistMode() { + return !!( + this.usePicklistValues && + this.objectApiName && + this.fieldApiName + ); + } + + get fieldApiNameString() { + if (!this.fieldApiName) return null; + + try { + // If it's a JSON string, parse it + if (typeof this.fieldApiName === "string") { + const parsed = JSON.parse(this.fieldApiName); + // If it's an array, get the first field object + if (Array.isArray(parsed) && parsed.length > 0) { + return parsed[0].name; + } + // If it's a single field object + if (parsed && parsed.name) { + return parsed.name; + } + } + // If it's already an object, get the name directly + if (typeof this.fieldApiName === "object" && this.fieldApiName.name) { + return this.fieldApiName.name; + } + // If it's already a string field name, return as-is + return this.fieldApiName; + } catch (error) { + console.error("Error parsing fieldApiName:", error); + return this.fieldApiName; } + } + + // Wire to get object info for field validation + @wire(getObjectInfo, { objectApiName: "$objectApiName" }) + objectInfo; - @api - get selectedOptionsCSV() { - return this.getValues(defaults.csv); + // Wire to get picklist values + @wire(getPicklistValues, { + objectApiName: "$objectApiName", + fieldName: "$fieldApiNameString", + }) + wiredPicklistValues({ error, data }) { + if (data) { + console.log("picklistValues: " + JSON.stringify(data)); + this.picklistValues = data; + this.updateOptionsFromPicklist(); + } else if (error) { + console.error("Error loading picklist values:", JSON.stringify(error)); + this.errorMessage = + "Error loading picklist values: " + error.body.message; } + } - @api - get selectedOptionsFieldDescriptorList() { - return this.getValues(defaults.originalObject); + setOptions(optionName, optionValue) { + this.optionValues[optionName] = optionValue; + if ( + this._allOptionsStringFormat && + inputTypeToInputAttributeName[this._allOptionsStringFormat] === + optionName && + this._allOptionsStringFormat !== defaults.twoLists + ) { + this._options = optionValue; + } else if ( + this._allOptionsStringFormat && + inputTypeToOutputAttributeName[this._allOptionsStringFormat] === + optionName + ) { + this._selectedValues = optionValue; } + if ( + this._allOptionsStringFormat === defaults.twoLists && + this.optionValues[inputTypeToInputAttributeName.list] && + this.optionValues[inputTypeToInputAttributeName.twoLists] + ) { + this.setDualListOptions(); + } + } - get isDataSet() { - return this.allOptionsStringFormat && this.useWhichObjectKeyForData && this.useWhichObjectKeyForLabel; + setDualListOptions() { + this._options = []; + let values = this.optionValues[inputTypeToInputAttributeName.list]; + let labels = this.optionValues[inputTypeToInputAttributeName.twoLists]; + if (labels.length === values.length) { + for (let i = 0; i < values.length; i++) { + this._options.push({ label: labels[i], value: values[i] }); + } } + } - setOptions(optionName, optionValue) { - this.optionValues[optionName] = optionValue; - if (this._allOptionsStringFormat && inputTypeToInputAttributeName[this._allOptionsStringFormat] === optionName && this._allOptionsStringFormat !== defaults.twoLists) { - this._options = optionValue; - } else if (this._allOptionsStringFormat && inputTypeToOutputAttributeName[this._allOptionsStringFormat] === optionName) { - this._selectedValues = optionValue; - } - if (this._allOptionsStringFormat === defaults.twoLists && this.optionValues[inputTypeToInputAttributeName.list] && this.optionValues[inputTypeToInputAttributeName.twoLists]) { - this.setDualListOptions(); - } + updateOptionsFromPicklist() { + console.log("updateOptionsFromPicklist called"); + console.log("isPicklistMode:", this.isPicklistMode); + console.log("usePicklistValues:", JSON.stringify(this.usePicklistValues)); + console.log("objectApiName:", JSON.stringify(this.objectApiName)); + console.log("fieldApiName:", JSON.stringify(this.fieldApiName)); + console.log("picklistValues:", JSON.stringify(this.picklistValues)); + + if ( + this.isPicklistMode && + this.picklistValues && + this.picklistValues.length > 0 + ) { + this._options = this.picklistValues.map((picklistValue) => ({ + label: picklistValue.label, + value: picklistValue.value, + })); + + // Set the format to picklist for proper option processing + if (!this._allOptionsStringFormat) { + this._allOptionsStringFormat = defaults.picklist; + } + + console.log("Updated _options:", JSON.stringify(this._options)); + + // Restore values after options are loaded + this.restoreSelectedValues(); + } else { + console.log("updateOptionsFromPicklist: conditions not met"); } + } - setDualListOptions() { - this._options = []; - let values = this.optionValues[inputTypeToInputAttributeName.list]; - let labels = this.optionValues[inputTypeToInputAttributeName.twoLists]; - if (labels.length === values.length) { - for (let i = 0; i < values.length; i++) { - this._options.push({label: labels[i], value: values[i]}); - } - } + getValues(valueType) { + let listBox = this.template.querySelector( + "c-fsc_extended-base-dual-list-box" + ); + if (listBox) { + return listBox.getValues(valueType); } + } - getValues(valueType) { - let listBox = this.template.querySelector('c-fsc_extended-base-dual-list-box'); - if (listBox) { - return listBox.getValues(valueType); - } + getOptions(valueType) { + let listBox = this.template.querySelector( + "c-fsc_extended-base-dual-list-box" + ); + if (listBox) { + return listBox.getOptions(valueType); } + } - getOptions(valueType) { - let listBox = this.template.querySelector('c-fsc_extended-base-dual-list-box'); - if (listBox) { - return listBox.getOptions(valueType); - } + handleValueChanged(event) { + console.log("=== handleValueChanged called ==="); + console.log("event.detail.value:", JSON.stringify(event.detail.value)); + + let value = event.detail.value; + + // Convert to array format for selected values + if (Array.isArray(value)) { + this._selectedValues = [...value]; + } else if (typeof value === "string" && value.includes(",")) { + this._selectedValues = value.split(",").map((item) => item.trim()); + } else { + this._selectedValues = value ? [value] : []; } - handleValueChanged(event) { - this.dispatchFlowAttributeChangedEvent(inputTypeToOutputAttributeName[this.allOptionsStringFormat], event.detail.value); + console.log("Updated _selectedValues:", this._selectedValues); + + // Save to sessionStorage + this.saveSelectedValues(); + + // Dispatch to Flow + const outputAttribute = + inputTypeToOutputAttributeName[this.allOptionsStringFormat]; + let flowValue = this._selectedValues; + + if ( + this.allOptionsStringFormat === defaults.picklist || + this.allOptionsStringFormat === defaults.csv + ) { + flowValue = this._selectedValues.join(","); } - dispatchFlowAttributeChangedEvent(attributeName, attributeValue) { - console.log(attributeName, attributeValue); - const attributeChangeEvent = new FlowAttributeChangeEvent( - attributeName, - attributeValue - ); - this.dispatchEvent(attributeChangeEvent); + this.dispatchEvent( + new FlowAttributeChangeEvent(outputAttribute, flowValue) + ); + } + + dispatchFlowAttributeChangedEvent(attributeName, attributeValue) { + console.log(attributeName, attributeValue); + const attributeChangeEvent = new FlowAttributeChangeEvent( + attributeName, + attributeValue + ); + this.dispatchEvent(attributeChangeEvent); + } + + // Event handlers for picklist properties + handleObjectChange(event) { + this.objectApiName = event.detail.value; + this.fieldApiName = null; // Clear field when object changes + this.picklistValues = []; // Clear picklist values + this._options = []; // Clear options + this.dispatchFlowAttributeChangedEvent("objectApiName", this.objectApiName); + } + + handleFieldChange(event) { + this.fieldApiName = event.detail.value; + console.log("fieldApiName: " + JSON.stringify(this.fieldApiName)); + this.dispatchFlowAttributeChangedEvent("fieldApiName", this.fieldApiName); + } + + handleUsePicklistChange(event) { + this.usePicklistValues = event.detail.checked; + this.dispatchFlowAttributeChangedEvent( + "usePicklistValues", + this.usePicklistValues + ); + + if (!this.usePicklistValues) { + this.picklistValues = []; + this._options = []; } + } - @api - validate() { - console.log('entering validate'); - console.log("entering validate: required=" + this.required + " values=" + this._selectedValues); + @api + validate() { + console.log("entering validate"); + console.log( + "entering validate: required=" + + this.required + + " values=" + + JSON.stringify(this._selectedValues) + ); - if(this.required == true && (this._selectedValues == [] || this._selectedValues == '')) { - console.log('validate reporting false'); - return { - isValid: false, - errorMessage: 'At least one value must be selected.' - }; - } - else { - return { isValid: true }; - } + if ( + this.required == true && + (this._selectedValues == [] || this._selectedValues == "") + ) { + console.log("validate reporting false"); + return { + isValid: false, + errorMessage: "At least one value must be selected.", + }; + } else { + return { isValid: true }; } -} \ No newline at end of file + } +} diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js-meta.xml b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js-meta.xml index 12a8fb3b8..90c7fb944 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js-meta.xml +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBox3/fsc_dualListBox3.js-meta.xml @@ -1,6 +1,6 @@ - 47.0 + 64.0 Dual List Box v3 true Dual List Box v3 @@ -8,29 +8,35 @@ lightning__FlowScreen - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.html b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.html index 2bc5437a1..7ff73f8a4 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.html +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.html @@ -1,196 +1,234 @@ \ No newline at end of file diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js index 9a80f05f7..86a1b5c8f 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js @@ -1,231 +1,515 @@ -import {LightningElement, track, api} from 'lwc'; +import { LightningElement, track, api } from "lwc"; import { - defaults, inputTypeToOutputAttributeName, inputTypeToInputAttributeName -} from 'c/fsc_dualListBoxUtils'; + defaults, + inputTypeToOutputAttributeName, + inputTypeToInputAttributeName, +} from "c/fsc_dualListBoxUtils"; + +const DATA_TYPE = { + STRING: "String", + BOOLEAN: "Boolean", + NUMBER: "Number", + INTEGER: "Integer", +}; export default class DualListBoxCpe extends LightningElement { + @api automaticOutputVariables; + typeValue; + _builderContext = {}; + _values = []; + _flowVariables = []; + _typeMappings = []; + rendered; - _builderContext; - _values; - - @track inputValues = { - label: {value: null, valueDataType: null, isCollection: false, label: 'Master Label'}, - allOptionsStringFormat: {value: null, valueDataType: null, isCollection: false, label: 'Select datasource'}, - sourceLabel: {value: null, valueDataType: null, isCollection: false, label: 'Available Choices Label'}, - fieldLevelHelp: {value: null, valueDataType: null, isCollection: false, label: 'Add a \'None\' choice'}, - selectedLabel: {value: null, valueDataType: null, isCollection: false, label: 'Selected Chocies Label'}, - min: {value: null, valueDataType: null, isCollection: false, label: 'Min'}, - max: {value: null, valueDataType: null, isCollection: false, label: 'Max'}, - disableReordering: {value: null, valueDataType: null, isCollection: false, label: 'Disable Reordering'}, - size: { - value: null, - valueDataType: null, - isCollection: false, - label: 'Vertical Size of List Box (visible items)' - }, - required: {value: null, valueDataType: null, isCollection: false, label: 'Required'}, - requiredOptions: {value: null, valueDataType: null, isCollection: true, label: 'Datasource for Choice Icons:'}, - useWhichObjectKeyForData: { - value: null, - valueDataType: null, - isCollection: false, - label: 'Use Which Object Key For Data' - }, - useWhichObjectKeyForLabel: { - value: null, - valueDataType: null, - isCollection: false, - label: 'Use Which Object Key For Label' - }, - useWhichObjectKeyForSort: { - value: null, - valueDataType: null, - isCollection: false, - label: 'Use Which Object Key For Sort' - }, - useObjectValueAsOutput: {value: null, valueDataType: null, isCollection: false, label: 'Select Field'}, - allOptionsFieldDescriptorList: { - value: null, - valueDataType: null, - isCollection: true, - label: 'Field Descriptors' - }, - allOptionsStringCollection: {value: null, valueDataType: null, isCollection: true, label: 'Values'}, - allOptionsStringCollectionLabels: {value: null, valueDataType: null, isCollection: true, label: 'Labels'}, - allOptionsCSV: {value: null, valueDataType: null, isCollection: false, label: 'CSV String'}, - selectedOptionsStringList: {value: null, valueDataType: null, isCollection: true, label: 'Select List Values'}, - selectedOptionsCSV: {value: null, valueDataType: null, isCollection: false, label: 'Selected CSV Values'}, - selectedOptionsFieldDescriptorList: { - value: null, - valueDataType: null, - isCollection: true, - label: 'Select Field Descriptor Values' - } - }; - - selectDataSourceOptions = [ - { - label: 'CSV String', - value: defaults.csv, - allowedAttributes: [inputTypeToOutputAttributeName.csv, inputTypeToInputAttributeName.csv] - }, - { - label: 'Two String Collections', - value: defaults.twoLists, - allowedAttributes: [inputTypeToOutputAttributeName.list, inputTypeToInputAttributeName.list, inputTypeToInputAttributeName.twoLists] - }, - { - label: 'One String Collections', - value: defaults.list, - allowedAttributes: [inputTypeToOutputAttributeName.list, inputTypeToInputAttributeName.list] - }, - { - label: 'FieldDescriptor Collection (use with Get Field Information action)', - value: defaults.originalObject, - allowedAttributes: [inputTypeToOutputAttributeName.object, inputTypeToInputAttributeName.object] - } - ]; + @track inputValues = { + label: { + value: null, + valueDataType: null, + isCollection: false, + label: "Master Label", + }, + allOptionsStringFormat: { + value: null, + valueDataType: null, + isCollection: false, + label: "Select datasource", + }, + sourceLabel: { + value: null, + valueDataType: null, + isCollection: false, + label: "Available Choices Label", + }, + fieldLevelHelp: { + value: null, + valueDataType: null, + isCollection: false, + label: "Add a 'None' choice", + }, + selectedLabel: { + value: null, + valueDataType: null, + isCollection: false, + label: "Selected Chocies Label", + }, + min: { + value: null, + valueDataType: null, + isCollection: false, + label: "Min", + }, + max: { + value: null, + valueDataType: null, + isCollection: false, + label: "Max", + }, + disableReordering: { + value: null, + valueDataType: null, + isCollection: false, + label: "Disable Reordering", + }, + size: { + value: null, + valueDataType: null, + isCollection: false, + label: "Vertical Size of List Box (visible items)", + }, + required: { + value: null, + valueDataType: null, + isCollection: false, + label: "Required", + }, + requiredOptions: { + value: null, + valueDataType: null, + isCollection: true, + label: "Datasource for Choice Icons:", + }, + useWhichObjectKeyForData: { + value: null, + valueDataType: null, + isCollection: false, + label: "Use Which Object Key For Data", + }, + useWhichObjectKeyForLabel: { + value: null, + valueDataType: null, + isCollection: false, + label: "Use Which Object Key For Label", + }, + useWhichObjectKeyForSort: { + value: null, + valueDataType: null, + isCollection: false, + label: "Use Which Object Key For Sort", + }, + useObjectValueAsOutput: { + value: null, + valueDataType: null, + isCollection: false, + label: "Select Field", + }, + allOptionsFieldDescriptorList: { + value: null, + valueDataType: null, + isCollection: true, + label: "Field Descriptors", + }, + allOptionsStringCollection: { + value: null, + valueDataType: null, + isCollection: true, + label: "Values", + }, + allOptionsStringCollectionLabels: { + value: null, + valueDataType: null, + isCollection: true, + label: "Labels", + }, + allOptionsCSV: { + value: null, + valueDataType: null, + isCollection: false, + label: "CSV String", + }, + selectedOptionsStringList: { + value: null, + valueDataType: null, + isCollection: true, + label: "Select List Values", + }, + selectedOptionsCSV: { + value: null, + valueDataType: null, + isCollection: false, + label: "Selected CSV Values", + }, + selectedOptionsPicklist: { + value: null, + valueDataType: null, + isCollection: true, + label: "Selected Picklist Values", + }, + selectedOptionsFieldDescriptorList: { + value: null, + valueDataType: null, + isCollection: true, + label: "Select Field Descriptor Values", + }, + // New picklist properties + usePicklistValues: { + value: null, + valueDataType: null, + isCollection: false, + label: "Use Picklist Values", + }, + objectApiName: { + value: null, + valueDataType: null, + isCollection: false, + label: "Object Name", + }, + fieldApiName: { + value: null, + valueDataType: null, + isCollection: true, + label: "Show which fields?", + serialized: true, + }, + }; + selectDataSourceOptions = [ + { + label: "CSV String", + value: defaults.csv, + allowedAttributes: [ + inputTypeToOutputAttributeName.csv, + inputTypeToInputAttributeName.csv, + ], + }, + { + label: "Two String Collections", + value: defaults.twoLists, + allowedAttributes: [ + inputTypeToOutputAttributeName.list, + inputTypeToInputAttributeName.list, + inputTypeToInputAttributeName.twoLists, + ], + }, + { + label: "One String Collections", + value: defaults.list, + allowedAttributes: [ + inputTypeToOutputAttributeName.list, + inputTypeToInputAttributeName.list, + ], + }, + { + label: + "FieldDescriptor Collection (use with Get Field Information action)", + value: defaults.originalObject, + allowedAttributes: [ + inputTypeToOutputAttributeName.object, + inputTypeToInputAttributeName.object, + ], + }, + { + label: "Picklist Values (from Salesforce field)", + value: defaults.picklist, + allowedAttributes: [ + inputTypeToOutputAttributeName.picklist, + inputTypeToInputAttributeName.picklist, + "objectApiName", + "fieldApiName", + ], + }, + ]; - @api get builderContext() { - return this._builderContext; - } + @api get builderContext() { + return this._builderContext; + } - set builderContext(value) { - this._builderContext = value; - } + set builderContext(value) { + this._builderContext = value; + } - @api get inputVariables() { - return this._values; - } + @api get inputVariables() { + return this._values; + } - set inputVariables(value) { + set inputVariables(value) { + this._values = value; + this.initializeValues(); + } - this._values = value; - this.initializeValues(); - } + @api get genericTypeMappings() { + return this._genericTypeMappings; + } + set genericTypeMappings(value) { + this._typeMappings = value; + this.initializeTypeMappings(); + } - @api get automaticOutputVariables () { - return this._automaticOutputVariables; - } + initializeValues(value) { + console.log("initializeValues called with:", JSON.stringify(this._values)); + if (this._values && this._values.length) { + this._values.forEach((curInputParam) => { + if (curInputParam.name && this.inputValues[curInputParam.name]) { + console.log( + "in initializeValues: " + + curInputParam.name + + " = " + + JSON.stringify(curInputParam.value) + ); + console.log("in initializeValues: " + JSON.stringify(curInputParam)); + if (this.inputValues[curInputParam.name].serialized) { + this.inputValues[curInputParam.name].value = JSON.parse( + curInputParam.value + ); + } else { + this.inputValues[curInputParam.name].value = curInputParam.value; + } + this.inputValues[curInputParam.name].valueDataType = + curInputParam.valueDataType; + } + }); - set automaticOutputVariables (value) { - this._automaticOutputVariables = value; + // Set usePicklistValues based on the data source format + if ( + this.inputValues.allOptionsStringFormat && + this.inputValues.allOptionsStringFormat.value === defaults.picklist + ) { + this.inputValues.usePicklistValues.value = true; + this.dispatchFlowValueChangeEvent("usePicklistValues", true, "Boolean"); + } } + } - _automaticOutputVariables; + @api + validate() { + let validity = []; + // Add validation logic here if needed + return validity; + } - initializeValues(value) { - if (this._values && this._values.length) { - this._values.forEach(curInputParam => { - if (curInputParam.name && this.inputValues[curInputParam.name]) { - this.inputValues[curInputParam.name].value = curInputParam.value; - this.inputValues[curInputParam.name].valueDataType = curInputParam.valueDataType; - } - }); - } - this.handleDefaultAttributes(); + renderedCallback() { + if (!this.rendered) { + this.rendered = true; + for (let flowCombobox of this.template.querySelectorAll( + "c-fsc_flow-combobox" + )) { + flowCombobox.builderContext = this.builderContext; + flowCombobox.automaticOutputVariables = this.automaticOutputVariables; + } } + } - handleDefaultAttributes() { - let isChanged = false; - if (this.isObject) { - if (this.inputValues.useWhichObjectKeyForData.value !== defaults.fieldDescriptorValueAttribute) { - this.inputValues.useWhichObjectKeyForData.value = defaults.fieldDescriptorValueAttribute; - isChanged = true; - } - - } else { - if (this.inputValues.useWhichObjectKeyForData.value !== defaults.defaultValueAttribute) { - this.inputValues.useWhichObjectKeyForData.value = defaults.defaultValueAttribute; - isChanged = true; - } - } - if (isChanged) { - this.dispatchFlowValueChangeEvent(defaults.useWhichObjectKeyForData, this.inputValues.useWhichObjectKeyForData.value, defaults.typeString); - } + handleValueChange(event) { + console.log("handleValueChange called with:", JSON.stringify(event.detail)); + console.log("event.target.name:", event.target?.name); + if (event.currentTarget) { + let curAttributeName = event.currentTarget.name + ? event.currentTarget.name.replace(defaults.inputAttributePrefix, "") + : null; + let value = event.detail ? event.detail.value : event.currentTarget.value; + let curAttributeValue = + event.currentTarget.type === "checkbox" + ? event.currentTarget.checked + : value; + let curAttributeType; + switch (event.currentTarget.type) { + case "checkbox": + curAttributeType = "Boolean"; + break; + case "number": + curAttributeType = "Number"; + break; + default: + curAttributeType = "String"; + } + console.log( + "Dispatching flow value change:", + curAttributeName, + JSON.stringify(curAttributeValue) + ); + this.dispatchFlowValueChangeEvent( + curAttributeName, + curAttributeValue, + curAttributeType + ); + if (curAttributeName === defaults.attributeNameAllOptionsStringFormat) { + this.clearUnusedAttributes(curAttributeValue); + } } + } - handleValueChange(event) { - if (event.target) { - let curAttributeName = event.target.name ? event.target.name.replace(defaults.inputAttributePrefix, '') : null; - let value = event.detail ? event.detail.value : event.target.value - let curAttributeValue = event.target.type === 'checkbox' ? event.target.checked : value; - let curAttributeType; - switch (event.target.type) { - case "checkbox": - curAttributeType = 'Boolean'; - break; - case "number": - curAttributeType = 'Number'; - break; - default: - curAttributeType = 'String'; - } - this.dispatchFlowValueChangeEvent(curAttributeName, curAttributeValue, curAttributeType); - if (curAttributeName === defaults.attributeNameAllOptionsStringFormat) { - this.clearUnusedAttributes(curAttributeValue); - } - } + handleFlowComboboxValueChange(event) { + if (event.target && event.detail) { + let changedAttribute = event.target.name.replace( + defaults.inputAttributePrefix, + "" + ); + this.dispatchFlowValueChangeEvent( + changedAttribute, + event.detail.newValue, + event.detail.newValueDataType + ); } + } - handleFlowComboboxValueChange(event) { - if (event.target && event.detail) { - let changedAttribute = event.target.name.replace(defaults.inputAttributePrefix, ''); - this.dispatchFlowValueChangeEvent(changedAttribute, event.detail.newValue, event.detail.newValueDataType); + initializeTypeMappings() { + this._typeMappings.forEach((typeMapping) => { + // console.log(JSON.stringify(typeMapping)); + if (typeMapping.name && typeMapping.value) { + this.typeValue = typeMapping.value; + } + }); + } + + handleObjectChange(event) { + if (event.target && event.detail) { + console.log("handling a dynamic type mapping"); + console.log("event is " + JSON.stringify(event)); + console.log("event.detail is " + JSON.stringify(event.detail)); + console.log("event.detail.objectType is " + event.detail.objectType); + console.log("event.currentTarget.name is " + event.currentTarget.name); + let typeValue = event.detail.objectType; + const typeName = "T"; + const dynamicTypeMapping = new CustomEvent( + "configuration_editor_input_value_changed", + { + composed: true, + cancelable: false, + bubbles: true, + detail: { + name: event.currentTarget.name, + newValue: event.detail.objectType, + newValueDataType: typeName, + }, } + ); + try { + this.dispatchEvent(dynamicTypeMapping); + } catch (error) { + console.log( + "Error dispatching dynamicTypeMapping:", + JSON.stringify(error) + ); + } + console.log("typeValue is " + typeValue); + // Set the value first, then dispatch + this.inputValues.objectApiName.value = typeValue; + this.dispatchFlowValueChangeEvent( + event.currentTarget.name, + typeValue, + "String" + ); } + } - dispatchFlowValueChangeEvent(id, newValue, newValueDataType) { - const valueChangedEvent = new CustomEvent('configuration_editor_input_value_changed', { - bubbles: true, - cancelable: false, - composed: true, - detail: { - name: id, - newValue: newValue ? newValue : null, - newValueDataType: newValueDataType - } - }); - this.dispatchEvent(valueChangedEvent); + dispatchFlowValueChangeEvent(id, newValue, newValueDataType) { + // Serialize the value if the input is marked as serialized + console.log( + "in dispatchFlowValueChangeEvent: " + id, + JSON.stringify(newValue), + newValueDataType + ); + if (this.inputValues[id] && this.inputValues[id].serialized) { + console.log("serializing value"); + newValue = JSON.stringify(newValue); } - clearUnusedAttributes(newInputFormat) { - let allAttributesToCheck = [...Object.values(inputTypeToOutputAttributeName), ...Object.values(inputTypeToInputAttributeName)]; - let curDataSourceOptions = this.selectDataSourceOptions.find(curDataSource => curDataSource.value === newInputFormat); - allAttributesToCheck.forEach(curAttribute => { - if (this.inputValues[curAttribute].value && (!curDataSourceOptions || !curDataSourceOptions.allowedAttributes.includes(curAttribute))) { - this.inputValues[curAttribute].value = null; - this.dispatchFlowValueChangeEvent(curAttribute, null, defaults.typeString); - } - }); - } + const valueChangedEvent = new CustomEvent( + "configuration_editor_input_value_changed", + { + bubbles: true, + cancelable: false, + composed: true, + detail: { + name: id, + newValue: newValue ? newValue : null, + newValueDataType: newValueDataType, + }, + } + ); + this.dispatchEvent(valueChangedEvent); + } - get isLists() { - if (this.inputValues.allOptionsStringFormat) { - return this.inputValues.allOptionsStringFormat.value === defaults.twoLists || this.inputValues.allOptionsStringFormat.value === defaults.list; - } + clearUnusedAttributes(newInputFormat) { + let allAttributesToCheck = [ + ...Object.values(inputTypeToOutputAttributeName), + ...Object.values(inputTypeToInputAttributeName), + ]; + let curDataSourceOptions = this.selectDataSourceOptions.find( + (curDataSource) => curDataSource.value === newInputFormat + ); + allAttributesToCheck.forEach((curAttribute) => { + if ( + this.inputValues[curAttribute].value && + (!curDataSourceOptions || + !curDataSourceOptions.allowedAttributes.includes(curAttribute)) + ) { + this.inputValues[curAttribute].value = null; + this.dispatchFlowValueChangeEvent( + curAttribute, + null, + defaults.typeString + ); + } + }); + // Automatically set usePicklistValues based on data source selection + if (newInputFormat === defaults.picklist) { + this.inputValues.usePicklistValues.value = true; + this.dispatchFlowValueChangeEvent("usePicklistValues", true, "Boolean"); + } else { + this.inputValues.usePicklistValues.value = false; + this.dispatchFlowValueChangeEvent("usePicklistValues", false, "Boolean"); } + } - get isTwoLists() { - if (this.inputValues.allOptionsStringFormat) { - return this.inputValues.allOptionsStringFormat.value === defaults.twoLists; - } + get isLists() { + if (this.inputValues.allOptionsStringFormat) { + return ( + this.inputValues.allOptionsStringFormat.value === defaults.twoLists || + this.inputValues.allOptionsStringFormat.value === defaults.list + ); } + } - get isCSV() { - if (this.inputValues.allOptionsStringFormat) { - return this.inputValues.allOptionsStringFormat.value === defaults.csv; - } + get isTwoLists() { + if (this.inputValues.allOptionsStringFormat) { + return ( + this.inputValues.allOptionsStringFormat.value === defaults.twoLists + ); } + } - get isObject() { - if (this.inputValues.allOptionsStringFormat) { - return this.inputValues.allOptionsStringFormat.value === defaults.originalObject; - } + get isCSV() { + if (this.inputValues.allOptionsStringFormat) { + return this.inputValues.allOptionsStringFormat.value === defaults.csv; + } + } + get isObject() { + if (this.inputValues.allOptionsStringFormat) { + return ( + this.inputValues.allOptionsStringFormat.value === + defaults.originalObject + ); } + } + get isPicklist() { + if (this.inputValues.allOptionsStringFormat) { + return ( + this.inputValues.allOptionsStringFormat.value === defaults.picklist + ); + } + } } \ No newline at end of file diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js-meta.xml b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js-meta.xml index 3230892c8..fcdae651f 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js-meta.xml +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxCPE/fsc_dualListBoxCPE.js-meta.xml @@ -1,6 +1,6 @@ - 51.0 + 64.0 Dual List Box Cpe true Dual List Box Cpe diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js index 0d1bfc269..8364fbc9e 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js @@ -1,38 +1,45 @@ const defaults = { - csv: 'csv', - list: 'list', - twoLists: 'twoLists', - originalObject: 'object', - valueField: 'value', - labelField: 'label', - inputAttributePrefix: 'select_', - defaultValueAttribute:'value', - defaultLabelAttribute:'label', - fieldDescriptorValueAttribute:'name', - typeString:'String', - useWhichObjectKeyForData: 'useWhichObjectKeyForData', - attributeNameAllOptionsStringFormat: 'allOptionsStringFormat', - originalObjectOutputNotSupported: 'Object output is not supported for this option type.', - valueLabelFieldNamesNotSupported: 'Value and Label field names should be left empty for this option type.', - canNotUseValuesForOutput: 'Values for output can be used only with "csv" or "list" input type.' -}; - -const inputTypeToOutputAttributeName = { - csv: 'selectedOptionsCSV', - list: 'selectedOptionsStringList', - twoLists: 'selectedOptionsStringList', - object: 'selectedOptionsFieldDescriptorList' -}; - -const inputTypeToInputAttributeName = { - csv: 'allOptionsCSV', - list: 'allOptionsStringCollection', - twoLists: 'allOptionsStringCollectionLabels', - object: 'allOptionsFieldDescriptorList' -}; - -export { + csv: "csv", + list: "list", + twoLists: "twoLists", + originalObject: "object", + picklist: "picklist", + valueField: "value", + labelField: "label", + inputAttributePrefix: "select_", + defaultValueAttribute: "value", + defaultLabelAttribute: "label", + fieldDescriptorValueAttribute: "name", + typeString: "String", + useWhichObjectKeyForData: "useWhichObjectKeyForData", + attributeNameAllOptionsStringFormat: "allOptionsStringFormat", + originalObjectOutputNotSupported: + "Object output is not supported for this option type.", + valueLabelFieldNamesNotSupported: + "Value and Label field names should be left empty for this option type.", + canNotUseValuesForOutput: + 'Values for output can be used only with "csv" or "list" input type.', + }; + + const inputTypeToOutputAttributeName = { + csv: "selectedOptionsCSV", + list: "selectedOptionsStringList", + twoLists: "selectedOptionsStringList", + object: "selectedOptionsFieldDescriptorList", + picklist: "selectedOptionsPicklist", + }; + + const inputTypeToInputAttributeName = { + csv: "allOptionsCSV", + list: "allOptionsStringCollection", + twoLists: "allOptionsStringCollectionLabels", + object: "allOptionsFieldDescriptorList", + picklist: "allOptionsStringCollection", + }; + + export { defaults, inputTypeToOutputAttributeName, - inputTypeToInputAttributeName -}; \ No newline at end of file + inputTypeToInputAttributeName, + }; + \ No newline at end of file diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js-meta.xml b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js-meta.xml index 3cd678b4e..32c7d5003 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js-meta.xml +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_dualListBoxUtils/fsc_dualListBoxUtils.js-meta.xml @@ -1,6 +1,6 @@ - 46.0 + 64.0 Dual List Box Utils false Dual List Box Utils diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.html b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.html index 4fed03959..1207a6759 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.html +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.html @@ -7,6 +7,7 @@

{isError}

source-label={sourceLabel} field-level-help={fieldLevelHelp} selected-label={selectedLabel} + required={required} min={min} max={max} disable-reordering={disableReordering} diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js index ead8eb3bc..3e100c996 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js @@ -1,190 +1,269 @@ -import {LightningElement, track, api} from 'lwc'; -import { - defaults -} from 'c/fsc_dualListBoxUtils'; +import { LightningElement, track, api } from "lwc"; +import { defaults } from "c/fsc_dualListBoxUtils"; export default class dualListBox extends LightningElement { + @api label; + @api sourceLabel; + @api fieldLevelHelp; + @api selectedLabel; - @api label; - @api sourceLabel; - @api fieldLevelHelp; - @api selectedLabel; - - @api min; - @api max; - @api disableReordering; - @api size; - @api required; - @api requiredOptions; - - @api selectedValuesStringFormat; - @api allOptionsStringFormat = defaults.csv;//csv;list;object - @api useWhichObjectKeyForData = defaults.valueField; - @api useWhichObjectKeyForLabel = defaults.labelField; - @api useWhichObjectKeyForSort; - @api useObjectValueAsOutput = false; //used with csv or list - @track _options; //always json{label,value} - @track _optionsOriginal; //stores original values - _optionsOriginalMap; - @track _value; //['value1','value2','value3'] - - @api - getValues(valueType) { - return this.formatValuesGet(this._value, valueType); - } + @api min; + @api max; + @api disableReordering; + @api size; + @api required; + @api requiredOptions; - @api - getOptions(valueType) { - return this.formatOptionsGet(this._options, valueType); - } + @api selectedValuesStringFormat; + @api allOptionsStringFormat = defaults.csv; //csv;list;object + @api useWhichObjectKeyForData = defaults.valueField; + @api useWhichObjectKeyForLabel = defaults.labelField; + @api useWhichObjectKeyForSort; + @api useObjectValueAsOutput = false; //used with csv or list + @track _options; //always json{label,value} + @track _optionsOriginal; //stores original values + _optionsOriginalMap; + @track _value; //['value1','value2','value3'] - @api - get selectedOptions() { - return this.getValues(this.selectedValuesStringFormat); - } + @api + getValues(valueType) { + return this.formatValuesGet(this._value, valueType); + } - set selectedOptions(value) { - if (!this.isError) { - this._value = this.formatValuesSet(value, this.selectedValuesStringFormat); - } - } + @api + getOptions(valueType) { + return this.formatOptionsGet(this._options, valueType); + } - @api - get allOptions() { - return this.getOptions(this.allOptionsStringFormat); - } + @api + get selectedOptions() { + return this.getValues(this.selectedValuesStringFormat); + } - set allOptions(value) { - if (!this.isError) { - this._optionsOriginal = value; - if (this.selectedValuesStringFormat === defaults.originalObject || this.selectedValuesStringFormat === defaults.twoLists) { - this.setOriginalObjectMap(); - } - if (this.useWhichObjectKeyForSort) { - let formattedOptions = this.formatOptionsSet(value, this.allOptionsStringFormat); - this._options = this.sortOptionsSet(formattedOptions, this.useWhichObjectKeyForSort); - } else { - this._options = this.formatOptionsSet(value, this.allOptionsStringFormat); - } - } + set selectedOptions(value) { + if (!this.isError) { + this._value = this.formatValuesSet( + value, + this.selectedValuesStringFormat + ); } + } - setOriginalObjectMap() { - let resultValue = {}; - this._optionsOriginal.forEach(curItem => { - resultValue[curItem[this.useWhichObjectKeyForData]] = curItem; - }); - this._optionsOriginalMap = resultValue; - } + @api + get allOptions() { + return this.getOptions(this.allOptionsStringFormat); + } - get isError() { - // if (this.selectedValuesStringFormat === defaults.originalObject && this.allOptionsStringFormat !== defaults.originalObject) { - // return defaults.originalObjectOutputNotSupported; - // } - // if (this.allOptionsStringFormat !== defaults.originalObject && - // ( - // (this.useWhichObjectKeyForData || this.useWhichObjectKeyForLabel) && - // this.useWhichObjectKeyForData !== defaults.valueField || this.useWhichObjectKeyForLabel !== defaults.labelField) - // ) { - // return defaults.valueLabelFieldNamesNotSupported; - // } - // if (this.useObjectValueAsOutput && this.selectedValuesStringFormat === defaults.originalObject) { - // return defaults.canNotUseValuesForOutput; - // } + set allOptions(value) { + console.log( + "allOptions setter called with:", + JSON.stringify(value), + "allOptionsStringFormat:", + this.allOptionsStringFormat + ); + if (!this.isError) { + this._optionsOriginal = value; + if ( + this.selectedValuesStringFormat === defaults.originalObject || + this.selectedValuesStringFormat === defaults.twoLists + ) { + this.setOriginalObjectMap(); + } + if (this.useWhichObjectKeyForSort) { + let formattedOptions = this.formatOptionsSet( + value, + this.allOptionsStringFormat + ); + this._options = this.sortOptionsSet( + formattedOptions, + this.useWhichObjectKeyForSort + ); + } else { + this._options = this.formatOptionsSet( + value, + this.allOptionsStringFormat + ); + } + console.log( + "allOptions setter: final _options:", + JSON.stringify(this._options) + ); + } else { + console.log("allOptions setter: isError is true"); } + } + + setOriginalObjectMap() { + let resultValue = {}; + this._optionsOriginal.forEach((curItem) => { + resultValue[curItem[this.useWhichObjectKeyForData]] = curItem; + }); + this._optionsOriginalMap = resultValue; + } + + get isError() { + // if (this.selectedValuesStringFormat === defaults.originalObject && this.allOptionsStringFormat !== defaults.originalObject) { + // return defaults.originalObjectOutputNotSupported; + // } + // if (this.allOptionsStringFormat !== defaults.originalObject && + // ( + // (this.useWhichObjectKeyForData || this.useWhichObjectKeyForLabel) && + // this.useWhichObjectKeyForData !== defaults.valueField || this.useWhichObjectKeyForLabel !== defaults.labelField) + // ) { + // return defaults.valueLabelFieldNamesNotSupported; + // } + // if (this.useObjectValueAsOutput && this.selectedValuesStringFormat === defaults.originalObject) { + // return defaults.canNotUseValuesForOutput; + // } + } - sortOptionsSet(value, sortField) { - if (!value) { - return; - } - if (!sortField) { - return value; - } - let fieldValue = row => row[sortField] || ''; - return [...value.sort( - (a,b)=>(a=fieldValue(a).toUpperCase(),b=fieldValue(b).toUpperCase(),((a>b)-(b>a))) - )]; + sortOptionsSet(value, sortField) { + if (!value) { + return; } + if (!sortField) { + return value; + } + let fieldValue = (row) => row[sortField] || ""; + return [ + ...value.sort( + (a, b) => ( + (a = fieldValue(a).toUpperCase()), + (b = fieldValue(b).toUpperCase()), + (a > b) - (b > a) + ) + ), + ]; + } - formatValuesSet(value, optionType) { - if (!value) { - return; - } - if (optionType === defaults.csv) { - return value.replace(/^ +| +$|( ) +/g, ' ').split(','); - } else if (optionType === defaults.list || optionType === defaults.twoLists) { - return value; - } else if (optionType === defaults.originalObject) { - return value.map(curItem => { - return curItem[this.useWhichObjectKeyForData] ? curItem[this.useWhichObjectKeyForData] : curItem - }); - } + formatValuesSet(value, optionType) { + if (!value) { + return; + } + if (optionType === defaults.csv) { + return value.replace(/^ +| +$|( ) +/g, " ").split(","); + } else if ( + optionType === defaults.list || + optionType === defaults.twoLists + ) { + return value; + } else if (optionType === defaults.picklist) { + // For picklist format, return the array as-is (it should be an array of values) + return Array.isArray(value) ? value : [value]; + } else if (optionType === defaults.originalObject) { + return value.map((curItem) => { + return curItem[this.useWhichObjectKeyForData] + ? curItem[this.useWhichObjectKeyForData] + : curItem; + }); } + } - formatValuesGet(items, optionType) { - if (!items) { - return; - } - let selectedValues = this.useObjectValueAsOutput ? items : items.map(curItem => { - return this._options.find(curFilterItem => curItem === curFilterItem.value).label; + formatValuesGet(items, optionType) { + if (!items) { + return; + } + let selectedValues = this.useObjectValueAsOutput + ? items + : items.map((curItem) => { + return this._options.find( + (curFilterItem) => curItem === curFilterItem.value + ).label; }); - if (optionType === defaults.csv) { - return selectedValues.join(','); - } else if (optionType === defaults.list || optionType === defaults.twoLists) { - return selectedValues; - } else if (optionType === defaults.originalObject) { - return items.map(curItemId => { - return this._optionsOriginalMap ? this._optionsOriginalMap[curItemId] : {}; - }); - } + if (optionType === defaults.csv || optionType === defaults.picklist) { + return selectedValues.join(","); + } else if ( + optionType === defaults.list || + optionType === defaults.twoLists + ) { + return selectedValues; + } else if (optionType === defaults.originalObject) { + return items.map((curItemId) => { + return this._optionsOriginalMap + ? this._optionsOriginalMap[curItemId] + : {}; + }); } + } - formatOptionsSet(items, optionType) { - if (!items) { - return; - } - if (optionType === defaults.csv) { - let localItems = items.replace(/^ +| +$|( ) +/g, ' ').split(','); - return localItems.map(curItem => { - return {label: curItem, value: curItem} - }); - } else if (optionType === defaults.list) { - return items.map(curItem => { - return {label: curItem, value: curItem} - }); - } else if (optionType === defaults.originalObject || optionType === defaults.twoLists) { - return items.map(curItem => { - return {label: curItem[this.useWhichObjectKeyForLabel], value: curItem[this.useWhichObjectKeyForData]} - }); - } + formatOptionsSet(items, optionType) { + console.log( + "formatOptionsSet called with:", + JSON.stringify(items), + "optionType:", + optionType + ); + if (!items) { + console.log("formatOptionsSet: items is null/undefined"); + return; } - - formatOptionsGet(items, optionType) { - if (!items) { - return; - } - if (optionType === defaults.csv) { - return items.map(curItem => this.useObjectValueAsOutput ? curItem.value : curItem.label).join(','); - } else if (optionType === defaults.list) { - return items.map(curItem => this.useObjectValueAsOutput ? curItem.value : curItem.label); - } else if (optionType === defaults.originalObject || optionType === defaults.twoLists) { - return items.map(curItem => { - return this._optionsOriginalMap ? this._optionsOriginalMap[curItem.value] : {}; - }); - } + if (optionType === defaults.csv) { + let localItems = items.replace(/^ +| +$|( ) +/g, " ").split(","); + return localItems.map((curItem) => { + return { label: curItem, value: curItem }; + }); + } else if (optionType === defaults.list) { + return items.map((curItem) => { + return { label: curItem, value: curItem }; + }); + } else if (optionType === defaults.picklist) { + // Handle picklist format - items are already in {label, value} format + console.log("formatOptionsSet: processing picklist format"); + const result = items.map((curItem) => { + return { label: curItem.label, value: curItem.value }; + }); + console.log("formatOptionsSet: picklist result:", JSON.stringify(result)); + return result; + } else if ( + optionType === defaults.originalObject || + optionType === defaults.twoLists + ) { + return items.map((curItem) => { + return { + label: curItem[this.useWhichObjectKeyForLabel], + value: curItem[this.useWhichObjectKeyForData], + }; + }); } + } - handleChange(event) { - this._value = event.detail.value; - this.dispatchValueChangedEvent(); + formatOptionsGet(items, optionType) { + if (!items) { + return; } - - dispatchValueChangedEvent() { - const valueChangedEvent = new CustomEvent('valuechanged', { - detail: { - value: this.getValues(this.selectedValuesStringFormat) - } - }); - this.dispatchEvent(valueChangedEvent); + if (optionType === defaults.csv) { + return items + .map((curItem) => + this.useObjectValueAsOutput ? curItem.value : curItem.label + ) + .join(","); + } else if (optionType === defaults.list) { + return items.map((curItem) => + this.useObjectValueAsOutput ? curItem.value : curItem.label + ); + } else if ( + optionType === defaults.originalObject || + optionType === defaults.twoLists + ) { + return items.map((curItem) => { + return this._optionsOriginalMap + ? this._optionsOriginalMap[curItem.value] + : {}; + }); } -} \ No newline at end of file + } + + handleChange(event) { + this._value = event.detail.value; + this.dispatchValueChangedEvent(); + } + + dispatchValueChangedEvent() { + const valueChangedEvent = new CustomEvent("valuechanged", { + detail: { + value: this.getValues(this.selectedValuesStringFormat), + }, + }); + this.dispatchEvent(valueChangedEvent); + } +} diff --git a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js-meta.xml b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js-meta.xml index ce2105852..193e34401 100644 --- a/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js-meta.xml +++ b/flow_screen_components/FlowScreenComponentsBasePack/force-app/main/default/lwc/fsc_extendedBaseDualListBox/fsc_extendedBaseDualListBox.js-meta.xml @@ -1,6 +1,6 @@ - 47.0 + 64.0 Dual List Box true Dual List Box