diff --git a/sfdx-project.json b/sfdx-project.json index 5633a02..07ad846 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,8 +4,8 @@ "path": "utils-core", "default": true, "package": "LWC Utils", - "versionName": "ver 1.5.4", - "versionNumber": "1.5.4.NEXT", + "versionName": "ver 1.6.0", + "versionNumber": "1.6.0.NEXT", "postInstallUrl": "https://github.com/tsalb/lwc-utils/releases", "releaseNotesUrl": "https://github.com/tsalb/lwc-utils/releases" }, @@ -13,13 +13,14 @@ "path": "utils-recipes", "default": false, "package": "LWC Utils Recipes", - "versionName": "ver 1.5.4", - "versionNumber": "1.5.4.NEXT", + "versionName": "ver 1.6.0", + "versionNumber": "1.6.0.NEXT", "postInstallUrl": "https://github.com/tsalb/lwc-utils/releases", "releaseNotesUrl": "https://github.com/tsalb/lwc-utils/releases", "dependencies": [ { - "package": "LWC Utils@1.5.4-1" + "package": "LWC Utils", + "versionNumber": "1.6.0.LATEST" } ] } @@ -29,10 +30,9 @@ "sourceApiVersion": "51.0", "packageAliases": { "LWC Utils Recipes": "0Ho1Q000000blJnSAI", - "LWC Utils Recipes@1.5.4-1": "04t1Q000001Qi2jQAC", + "LWC Utils Recipes@1.6.0-1": "04t1Q000001MRpjQAG", "LWC Utils": "0Ho1Q000000blJiSAI", - "LWC Utils@1.4.0-3": "04t1Q000001QhyDQAS", - "LWC Utils@1.5.0-1": "04t1Q000001Qi0sQAC", - "LWC Utils@1.5.4-1": "04t1Q000001Qi2eQAC" + "LWC Utils@1.5.4-1": "04t1Q000001Qi2eQAC", + "LWC Utils@1.6.0-2": "04t1Q000001MRpeQAG" } } \ No newline at end of file diff --git a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.cmp b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.cmp index 321b4dc..0a15d30 100644 --- a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.cmp +++ b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.cmp @@ -1,5 +1,19 @@ - - - + + + + + + + + diff --git a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.design b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.design new file mode 100644 index 0000000..6e02b0c --- /dev/null +++ b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandler.design @@ -0,0 +1,7 @@ + + + diff --git a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerController.js b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerController.js index 073b1a3..84044be 100644 --- a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerController.js +++ b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerController.js @@ -31,6 +31,21 @@ */ ({ + doInit: function (component, event, helper) { + const customBoundary = component.get('v.customBoundary'); + if (!!customBoundary) { + const messageService = helper.messageService(component); + const recordId = component.get('v.recordId'); + const isRecordIdRequested = customBoundary === '$recordId'; + // When recordId context is not available but was requested, leave messageService to its defaults + if (!recordId && isRecordIdRequested) { + return; + } + const finalBoundary = recordId && isRecordIdRequested ? recordId : customBoundary; + messageService.set('v.useRecordIdAsBoundary', recordId && isRecordIdRequested); + messageService.set('v.boundary', finalBoundary); + } + }, handleDialogService: function (component, event, helper) { const payload = event.getParam('value'); const singleton = helper.singleton(component); @@ -42,5 +57,38 @@ singleton.setIsCreatingModal(true); helper.executeDialogService(component, payload); + }, + handleWorkspaceApi: function (component, event, helper) { + const payload = event.getParam('value'); + const singleton = helper.singleton(component); + + if (singleton.getIsMessaging()) { + return; + } + singleton.setIsMessaging(true); + + helper.executeWorkspaceApi(component, payload); + }, + handleRecordEdit: function (component, event, helper) { + const payload = event.getParam('value'); + const singleton = helper.singleton(component); + + if (singleton.getIsMessaging()) { + return; + } + singleton.setIsMessaging(true); + + helper.fireRecordEdit(component, payload); + }, + handleRecordCreate: function (component, event, helper) { + const payload = event.getParam('value'); + const singleton = helper.singleton(component); + + if (singleton.getIsMessaging()) { + return; + } + singleton.setIsMessaging(true); + + helper.fireRecordCreate(component, payload); } }); diff --git a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerHelper.js b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerHelper.js index eafbd5d..058c11e 100644 --- a/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerHelper.js +++ b/utils-core/main/default/aura/MessageServiceHandler/MessageServiceHandlerHelper.js @@ -31,11 +31,14 @@ */ ({ + messageService: function (component) { + return component.find('messageService'); + }, dialogService: function (component) { return component.find('dialogService'); }, - messageService: function (component) { - return component.find('messageService'); + workspaceService: function (component) { + return component.find('workspaceService'); }, singleton: function (component) { return component.find('singleton'); @@ -73,14 +76,13 @@ // nothing } }, - // mainActionReference only works for aura components modal: function (component, config) { this.dialogService(component).modal( config.auraId, config.headerLabel, config.component, config.componentParams, - config.mainActionReference, + config.mainActionReference, // mainActionReference only works for aura components config.mainActionLabel ); }, @@ -99,5 +101,34 @@ config.component, config.componentParams ); + }, + executeWorkspaceApi: function (component, payload) { + switch (payload.method) { + case 'openTab': + this.workspaceService(component).openTab(payload.config); + break; + case 'openSubtab': + this.workspaceService(component).openSubtab(payload.config); + break; + case 'closeTabByTitle': + this.workspaceService(component).closeTabByTitle(payload.config); + break; + default: + // nothing + } + }, + fireRecordEdit: function (component, payload) { + $A.get('e.force:editRecord').setParams({ recordId: payload.recordId }).fire(); + this.singleton(component).setIsMessaging(false); + }, + fireRecordCreate: function (component, payload) { + $A.get('e.force:createRecord') + .setParams({ + entityApiName: payload.entityApiName, + recordTypeId: payload.recordTypeId, + defaultFieldValues: payload.defaultFieldValues + }) + .fire(); + this.singleton(component).setIsMessaging(false); } }); diff --git a/utils-core/main/default/aura/WorkspaceService/WorkspaceService.cmp b/utils-core/main/default/aura/WorkspaceService/WorkspaceService.cmp new file mode 100644 index 0000000..cec1af3 --- /dev/null +++ b/utils-core/main/default/aura/WorkspaceService/WorkspaceService.cmp @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/utils-core/main/default/aura/WorkspaceService/WorkspaceService.cmp-meta.xml b/utils-core/main/default/aura/WorkspaceService/WorkspaceService.cmp-meta.xml new file mode 100644 index 0000000..dc8ed55 --- /dev/null +++ b/utils-core/main/default/aura/WorkspaceService/WorkspaceService.cmp-meta.xml @@ -0,0 +1,5 @@ + + + 51.0 + A Lightning Component Bundle + \ No newline at end of file diff --git a/utils-core/main/default/aura/WorkspaceService/WorkspaceService.css b/utils-core/main/default/aura/WorkspaceService/WorkspaceService.css new file mode 100644 index 0000000..3f9f749 --- /dev/null +++ b/utils-core/main/default/aura/WorkspaceService/WorkspaceService.css @@ -0,0 +1,3 @@ +.THIS { + display: none; +} diff --git a/utils-core/main/default/aura/WorkspaceService/WorkspaceServiceController.js b/utils-core/main/default/aura/WorkspaceService/WorkspaceServiceController.js new file mode 100644 index 0000000..6577cbc --- /dev/null +++ b/utils-core/main/default/aura/WorkspaceService/WorkspaceServiceController.js @@ -0,0 +1,102 @@ +/** + * BSD 3-Clause License + * + * Copyright (c) 2021, james@sparkworks.io + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +({ + handleOpenTab: function (component, event, helper) { + const params = event.getParam('arguments'); + const messageService = helper.messageService(component); + const singleton = helper.singleton(component); + const workspace = helper.workspaceApi(component); + + workspace + .openTab(params.config) + .then(tabId => { + const successPayload = { + tabId: tabId + }; + messageService.publish({ key: 'opentabresolve', value: successPayload }); + }) + .catch(err => { + console.error(err); + const errorPayload = { + error: err + }; + messageService.publish({ key: 'opentabresolve', value: errorPayload }); + }); + + singleton.setIsMessaging(false); + }, + handleOpenSubtab: function (component, event, helper) { + const params = event.getParam('arguments'); + const messageService = helper.messageService(component); + const singleton = helper.singleton(component); + const workspace = helper.workspaceApi(component); + + // Try to open subtab from current tab + if (!params.config.parentTabId) { + workspace.getEnclosingTabId().then(result => { + params.config.parentTabId = result; + }); + } + workspace + .openSubtab(params.config) + .then(tabId => { + const successPayload = { + tabId: tabId + }; + messageService.publish({ key: 'opensubtabresolve', value: successPayload }); + }) + .catch(err => { + console.error(err); + const errorPayload = { + error: err + }; + messageService.publish({ key: 'opensubtabresolve', value: errorPayload }); + }); + + singleton.setIsMessaging(false); + }, + handleCloseTabByTitle: function (component, event, helper) { + const params = event.getParam('arguments'); + const singleton = helper.singleton(component); + const workspace = helper.workspaceApi(component); + + workspace.getAllTabInfo().then(allTabInfo => { + for (let tab of allTabInfo) { + if (tab.title === params.config.title) { + workspace.closeTab({ tabId: tab.tabId }); + } + } + }); + singleton.setIsMessaging(false); + } +}); diff --git a/utils-core/main/default/aura/WorkspaceService/WorkspaceServiceHelper.js b/utils-core/main/default/aura/WorkspaceService/WorkspaceServiceHelper.js new file mode 100644 index 0000000..9cc34fe --- /dev/null +++ b/utils-core/main/default/aura/WorkspaceService/WorkspaceServiceHelper.js @@ -0,0 +1,43 @@ +/** + * BSD 3-Clause License + * + * Copyright (c) 2021, james@sparkworks.io + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +({ + messageService: function (component) { + return component.find('messageService'); + }, + singleton: function (component) { + return component.find('singleton'); + }, + workspaceApi: function (component) { + return component.find('workspaceApi'); + } +}); diff --git a/utils-core/main/default/lwc/datatable/datatable.js b/utils-core/main/default/lwc/datatable/datatable.js index 4c4eb37..888531a 100644 --- a/utils-core/main/default/lwc/datatable/datatable.js +++ b/utils-core/main/default/lwc/datatable/datatable.js @@ -49,7 +49,7 @@ const MAX_ROW_SELECTION = 200; const OBJECTS_WITH_COMPOUND_NAMES = ['Contact']; // Lower is less fuzzy / better hit result -const SEARCH_THRESHOLD = 0.3; +const SEARCH_THRESHOLD = 0.1; // Datatable_Action_Config__mdt const LEGACY_TABLE_ACTION_ONE_STRING = 'Primary'; diff --git a/utils-core/main/default/lwc/datatableEditRowForm/datatableEditRowForm.js b/utils-core/main/default/lwc/datatableEditRowForm/datatableEditRowForm.js index 85e8d79..f776652 100644 --- a/utils-core/main/default/lwc/datatableEditRowForm/datatableEditRowForm.js +++ b/utils-core/main/default/lwc/datatableEditRowForm/datatableEditRowForm.js @@ -46,11 +46,13 @@ export default class DatatableEditRowForm extends LightningElement { this.showSpinner = false; } - handleCancel() { + handleCancel(event) { + event.preventDefault(); this.messageService.notifyClose(); } - handleSuccess() { + handleSuccess(event) { + event.preventDefault(); this.messageService.notifySuccess('Successfully Updated'); this._refreshViewAndClose(); } diff --git a/utils-core/main/default/lwc/messageService/messageService.js b/utils-core/main/default/lwc/messageService/messageService.js index e9405e3..1a33140 100644 --- a/utils-core/main/default/lwc/messageService/messageService.js +++ b/utils-core/main/default/lwc/messageService/messageService.js @@ -36,23 +36,36 @@ import OPEN_CHANNEL from '@salesforce/messageChannel/OpenChannel__c'; // Toast import { ShowToastEvent } from 'lightning/platformShowToastEvent'; -import { reduceErrors } from 'c/utils'; +import { reduceErrors, isRecordId } from 'c/utils'; export default class MessageService extends LightningElement { @api boundary; + @api + get useRecordIdAsBoundary() { + return this._useRecordIdAsBoundary; + } + set useRecordIdAsBoundary(value = false) { + this._useRecordIdAsBoundary = value; + } + subscription = null; - @wire(MessageContext) messageContext; + + @wire(MessageContext) + messageContext; connectedCallback() { if (this.subscription) { return; } - this.subscription = subscribe(this.messageContext, OPEN_CHANNEL, payload => { - if (Object.prototype.hasOwnProperty.call(payload, 'boundary') && payload.boundary !== this.boundary) { - return; - } - this.dispatchEvent(new CustomEvent(payload.key, { detail: { value: payload.value } })); - }); + // Always run this unless user configures MessageServiceHandler with customBoundary. + // Helps with adding some definition to backwards compatible usage of recordId as boundary. + if (!this.useRecordIdAsBoundary) { + this._useRecordIdAsBoundary = this.boundary && this._isBoundaryRecordId(this.boundary); + } + // Then, listen to subscriptions so long as they pass the dispatch conditions + this.subscription = subscribe(this.messageContext, OPEN_CHANNEL, payload => + this._handleOpenChannelPayload(payload) + ); } disconnectedCallback() { @@ -65,6 +78,11 @@ export default class MessageService extends LightningElement { this._messageServicePublish({ key: 'opendialog', value: payload }); } + @api + workspaceApi(payload) { + this.publish({ key: 'workspaceapi', value: payload }); + } + @api notifyClose() { this._messageServicePublish({ key: 'closedialog' }); @@ -90,6 +108,16 @@ export default class MessageService extends LightningElement { eval("$A.get('e.force:refreshView').fire();"); } + @api + forceRecordEdit(recordIdPayload) { + this.publish({ key: 'recordedit', value: recordIdPayload }); + } + + @api + forceCreateRecord(entityApiName) { + this.publish({ key: 'recordcreate', value: entityApiName }); + } + @api notifySuccess(title, message = null) { this.dispatchEvent( @@ -124,7 +152,25 @@ export default class MessageService extends LightningElement { ); } - // private + // private funcs + + _handleOpenChannelPayload(payload) { + if (!this._hasBoundaryProp(payload)) { + this._dispatchKeyValueEvent(payload); + } else { + if (!this.useRecordIdAsBoundary && payload.boundary === this.boundary) { + this._dispatchKeyValueEvent(payload); + } + if ( + this.useRecordIdAsBoundary && + this._isBoundaryRecordId(this.boundary) && + this._isBoundaryRecordId(payload.boundary) && + payload.boundary === this.boundary + ) { + this._dispatchKeyValueEvent(payload); + } + } + } _messageServicePublish(payload) { publish(this.messageContext, OPEN_CHANNEL, { key: payload.key, value: payload.value }); @@ -133,4 +179,16 @@ export default class MessageService extends LightningElement { _messageServicePublishWithBoundary(payload) { publish(this.messageContext, OPEN_CHANNEL, { boundary: this.boundary, key: payload.key, value: payload.value }); } + + _dispatchKeyValueEvent(payload) { + this.dispatchEvent(new CustomEvent(payload.key, { detail: { value: payload.value } })); + } + + _hasBoundaryProp(payload) { + return Object.prototype.hasOwnProperty.call(payload, 'boundary'); + } + + _isBoundaryRecordId(boundary) { + return isRecordId(boundary); + } } diff --git a/utils-core/main/default/lwc/singleton/singleton.js b/utils-core/main/default/lwc/singleton/singleton.js index 9d3771a..d0e1803 100644 --- a/utils-core/main/default/lwc/singleton/singleton.js +++ b/utils-core/main/default/lwc/singleton/singleton.js @@ -31,9 +31,16 @@ */ let isCreating = false; +let isMessaging = false; export const setIsCreatingModal = value => { isCreating = value; }; export const getIsCreatingModal = () => isCreating; + +export const setIsMessaging = value => { + isMessaging = value; +}; + +export const getIsMessaging = () => isMessaging; diff --git a/utils-core/main/default/lwc/tableService/tableService.js b/utils-core/main/default/lwc/tableService/tableService.js index eb8eca4..7f1a000 100644 --- a/utils-core/main/default/lwc/tableService/tableService.js +++ b/utils-core/main/default/lwc/tableService/tableService.js @@ -34,10 +34,7 @@ import getTableCache from '@salesforce/apex/DataTableService.getTableCache'; import getQueryExceptionMessage from '@salesforce/apex/DataTableService.getQueryExceptionMessage'; import { updateRecord } from 'lightning/uiRecordApi'; import * as tableUtils from 'c/tableServiceUtils'; - -const isRecordId = str => { - return str.length === 18 || str.length === 15; -}; +import { isRecordId } from 'c/utils'; const checkQueryException = async queryString => { // Here, we actually want to use await to denote a thenable value diff --git a/utils-core/main/default/lwc/utils/utils.js b/utils-core/main/default/lwc/utils/utils.js index cc2c9aa..487ed4e 100644 --- a/utils-core/main/default/lwc/utils/utils.js +++ b/utils-core/main/default/lwc/utils/utils.js @@ -38,17 +38,22 @@ const generateUUID = () => { }); }; -const removeWhiteSpace = function () { - return this.replaceAll(new RegExp('\\s+', 'g'), ''); +const isRecordId = string => { + const re = new RegExp('[a-zA-Z0-9]{15}|[a-zA-Z0-9]{18}'); + return !!string?.match(re); }; -const flatten = function () { - return this.replaceAll(new RegExp('\\.', 'g'), '_'); +const removeWhiteSpace = value => { + return value ? value.replaceAll(new RegExp('\\s+', 'g'), '') : ''; +}; + +const flatten = value => { + return value ? value.replaceAll(new RegExp('\\.', 'g'), '_') : ''; }; const createFlattenedSetFromDelimitedString = (string, delimiter) => { - const cleanString = removeWhiteSpace.call(string); - const flatString = flatten.call(cleanString); + const cleanString = removeWhiteSpace(string); + const flatString = flatten(cleanString); return new Set(flatString.split(delimiter)); }; @@ -139,4 +144,4 @@ const reduceErrors = errors => { ); }; -export { generateUUID, createFlattenedSetFromDelimitedString, convertToSingleLineString, reduceErrors }; +export { generateUUID, isRecordId, createFlattenedSetFromDelimitedString, convertToSingleLineString, reduceErrors }; diff --git a/utils-recipes/main/default/applications/LWC_Utils_Console.app-meta.xml b/utils-recipes/main/default/applications/LWC_Utils_Console.app-meta.xml index efc90d0..f84aade 100644 --- a/utils-recipes/main/default/applications/LWC_Utils_Console.app-meta.xml +++ b/utils-recipes/main/default/applications/LWC_Utils_Console.app-meta.xml @@ -1,18 +1,27 @@ + + Tab + LWC_Utils_Console_Home + Large + false + Flexipage + standard-home + #0070D2 false - Samples using Record Flexipages + Examples with console tabs and record flexipages Large false - false + true Console + all + standard-home standard-Account standard-Contact - standard-Opportunity Lightning LWC_Utils_Console_UtilityBar @@ -24,8 +33,7 @@ standard-Contact - AccountId - standard-Opportunity + standard-home diff --git a/utils-recipes/main/default/flexipages/Account_Record_Page.flexipage-meta.xml b/utils-recipes/main/default/flexipages/Account_Record_Page.flexipage-meta.xml index aecacc4..460f290 100644 --- a/utils-recipes/main/default/flexipages/Account_Record_Page.flexipage-meta.xml +++ b/utils-recipes/main/default/flexipages/Account_Record_Page.flexipage-meta.xml @@ -79,6 +79,10 @@ showRefreshButton true + + showSearch + false + sortedDirection asc @@ -133,6 +137,10 @@ showRefreshButton true + + showSearch + false + sortedDirection asc @@ -187,6 +195,10 @@ showRefreshButton true + + showSearch + false + sortedDirection asc @@ -241,6 +253,10 @@ showRefreshButton true + + showSearch + false + sortedDirection asc @@ -262,10 +278,19 @@ - lwcContactDatatable + recordPagePublisher - Facet-867b732e-beb2-4844-a53f-833078ef1afa + Facet-ecc1ac3a-5aef-4e01-b56a-4bb2ffe005b0 + Facet + + + + + recordPageSubscriber + + + Facet-wfm6nxgoplf Facet @@ -290,11 +315,24 @@ body - Facet-867b732e-beb2-4844-a53f-833078ef1afa + Facet-ecc1ac3a-5aef-4e01-b56a-4bb2ffe005b0 + + + title + MessageService Publish + + flexipage:tab + + + + + + body + Facet-wfm6nxgoplf title - lwcContactDatatable + MessageService Sub flexipage:tab @@ -312,24 +350,6 @@ flexipage:tabset - - - - decorate - false - - - richTextValue - <p style="text-align: center;">MessageServiceHandler is placed below on the flexipage directly to handle any LMS events.</p> - - flexipage:richText - - - - - MessageServiceHandler - - Replace main Region @@ -387,6 +407,28 @@ Facet + + + + decorate + false + + + richTextValue + <p style="text-align: center;"><b>MessageServiceHandler</b> is below to handle messageService events. </p><p style="text-align: center;"><br></p><p style="text-align: center;">This one is using <b>$recordId</b> as its boundary.</p> + + flexipage:richText + + + + + + customBoundary + $recordId + + MessageServiceHandler + + diff --git a/utils-recipes/main/default/flexipages/LWC_Utils_Console_Home.flexipage-meta.xml b/utils-recipes/main/default/flexipages/LWC_Utils_Console_Home.flexipage-meta.xml new file mode 100644 index 0000000..3c26521 --- /dev/null +++ b/utils-recipes/main/default/flexipages/LWC_Utils_Console_Home.flexipage-meta.xml @@ -0,0 +1,39 @@ + + + + + + + decorate + true + + + richTextValue + <p style="text-align: center;">Message Service Handler is placed here on the Home Page.</p><p style="text-align: center;"><br></p><p style="text-align: center;">It is also placed on each Record Flexipage that is opened by the tab controls here.</p> + + flexipage:richText + + + + + + customBoundary + workspace_api_example + + MessageServiceHandler + + + + + workspaceApiExamples + + + main + Region + + LWC Utils Console Home + + HomePage + diff --git a/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js b/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js index 798feb4..d21a4ef 100644 --- a/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js +++ b/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js @@ -32,8 +32,8 @@ import { LightningElement, api, wire } from 'lwc'; import { updateRecord } from 'lightning/uiRecordApi'; +import { refreshApex } from '@salesforce/apex'; import wireContactsByAccountId from '@salesforce/apex/DataServiceCtrl.wireContactsByAccountId'; -import getContactsByAccountId from '@salesforce/apex/DataServiceCtrl.getContactsByAccountId'; const TABLE_COLUMNS = [ { label: 'Name', fieldName: 'Name', type: 'text', initialWidth: 110 }, @@ -152,7 +152,7 @@ export default class LwcContactDatatable extends LightningElement { async reloadTable() { try { - this.contacts.data = await getContactsByAccountId({ accountId: this._accountId }); + await refreshApex(this.contacts); } catch (error) { this._messageService.notifySingleError('Error Reloading Table', error); } diff --git a/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js-meta.xml b/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js-meta.xml index f2afda8..6a61842 100644 --- a/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js-meta.xml +++ b/utils-recipes/main/default/lwc/lwcContactDatatable/lwcContactDatatable.js-meta.xml @@ -5,6 +5,5 @@ true lightning__AppPage - lightning__RecordPage diff --git a/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.html b/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.html new file mode 100644 index 0000000..033324e --- /dev/null +++ b/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.html @@ -0,0 +1,7 @@ + diff --git a/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.js b/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.js new file mode 100644 index 0000000..2955a5f --- /dev/null +++ b/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.js @@ -0,0 +1,13 @@ +import { LightningElement, api } from 'lwc'; + +export default class RecordPagePublisher extends LightningElement { + @api recordId; + + get messageService() { + return this.template.querySelector('c-message-service'); + } + + handleIncrementPub() { + this.messageService.publish({ key: 'incrementfrompub' }); + } +} diff --git a/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.js-meta.xml b/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.js-meta.xml new file mode 100644 index 0000000..8f1f49f --- /dev/null +++ b/utils-recipes/main/default/lwc/recordPagePublisher/recordPagePublisher.js-meta.xml @@ -0,0 +1,9 @@ + + + Record Page Publisher + 51.0 + true + + lightning__RecordPage + + diff --git a/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.html b/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.html new file mode 100644 index 0000000..cf304f1 --- /dev/null +++ b/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.html @@ -0,0 +1,5 @@ + diff --git a/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.js b/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.js new file mode 100644 index 0000000..896477b --- /dev/null +++ b/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.js @@ -0,0 +1,12 @@ +import { LightningElement, api } from 'lwc'; + +export default class RecordPageSubscriber extends LightningElement { + @api recordId; + + currentCount = 0; + + handleIncrementSub() { + // no event + this.currentCount += 1; + } +} diff --git a/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.js-meta.xml b/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.js-meta.xml new file mode 100644 index 0000000..ea78b30 --- /dev/null +++ b/utils-recipes/main/default/lwc/recordPageSubscriber/recordPageSubscriber.js-meta.xml @@ -0,0 +1,9 @@ + + + Record Page Subscriber + 51.0 + true + + lightning__RecordPage + + diff --git a/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.html b/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.html new file mode 100644 index 0000000..a3c2e96 --- /dev/null +++ b/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.html @@ -0,0 +1,40 @@ + diff --git a/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.js b/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.js new file mode 100644 index 0000000..e727a61 --- /dev/null +++ b/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.js @@ -0,0 +1,141 @@ +import { LightningElement } from 'lwc'; +import { fetchTableCache } from 'c/tableService'; +import { NavigationMixin } from 'lightning/navigation'; + +export default class WorkspaceApiExamples extends NavigationMixin(LightningElement) { + // private + _firstAccountId; + _firstContactId; + _firstContactAccountId; + + // for workspace service "callbacks" + _tabId; + + isInputEmpty = true; + + get messageService() { + return this.template.querySelector('c-message-service'); + } + + get tabTitleInput() { + return this.template.querySelector('.tab-title-input'); + } + + get isAccountIdNull() { + return !this._firstAccountId; + } + + get isContactIdNull() { + return !this._firstContactId; + } + + async connectedCallback() { + const accData = await fetchTableCache({ + queryString: `SELECT Id FROM Account ORDER BY Id ASC LIMIT 1` + }); + const conData = await fetchTableCache({ + queryString: `SELECT Id, AccountId FROM Contact ORDER BY Id DESC LIMIT 1` + }); + if (accData?.tableData?.length) { + this._firstAccountId = accData.tableData[0].Id; + } + if (conData?.tableData?.length) { + this._firstContactId = conData.tableData[0].Id; + this._firstContactAccountId = conData.tableData[0].AccountId; + } + } + + // event handlers + + handleOpenAccount() { + const workspaceApiPayload = { + method: 'openTab', + config: { + focus: false, + recordId: this._firstAccountId + } + }; + // Since this uses Lightning Message Channel, it's not possible to get a callback through LMS directly + this.messageService.workspaceApi(workspaceApiPayload); + + // However, we can do something tricky like this: + + // eslint-disable-next-line @lwc/lwc/no-async-operation + let checkTabIdInterval = setInterval(checkTabId.bind(this), 500); + + // Don't use fat arrow, avoids no-use-before-define eslint error + function checkTabId() { + if (this._tabId) { + clearInterval(checkTabIdInterval); + // only to illustrate that this can come via LMS event from message-service on template + console.log(this._tabId); + // clear it out for the next usage + this._tabId = null; + } + } + } + + async handleOpenContact() { + // Since callbacks aren't easy, this makes multi-step operations challenging from LWC + const parentTabPayload = { + method: 'openTab', + config: { + focus: false, + recordId: this._firstContactAccountId + } + }; + this.messageService.workspaceApi(parentTabPayload); + + // eslint-disable-next-line @lwc/lwc/no-async-operation + let checkTabIdInterval = setInterval(checkTabId.bind(this), 500); + + // Don't use fat arrow, avoids no-use-before-define eslint error + function checkTabId() { + if (this._tabId) { + clearInterval(checkTabIdInterval); + const subTabPayload = { + method: 'openSubtab', + config: { + focus: true, + parentTabId: this._tabId, + recordId: this._firstContactId + } + }; + this.messageService.workspaceApi(subTabPayload); + this._tabId = null; + } + } + } + + handleOpenTabResolve(event) { + const payload = event.detail.value; + if (payload.error) { + console.error(payload.error); + } else if (payload.tabId) { + this._tabId = payload.tabId; + } + } + + handleOpenSubtabResolve(event) { + const payload = event.detail.value; + if (payload.error) { + console.error(payload.error); + } else if (payload.tabId) { + console.log(payload.tabId); + } + } + + handleInputChange(event) { + this.isInputEmpty = !event.detail.value; + } + + handleCloseTab() { + const workspaceApiPayload = { + method: 'closeTabByTitle', + config: { + title: this.tabTitleInput.value + } + }; + this.messageService.workspaceApi(workspaceApiPayload); + } +} diff --git a/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.js-meta.xml b/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.js-meta.xml new file mode 100644 index 0000000..4c7e3bd --- /dev/null +++ b/utils-recipes/main/default/lwc/workspaceApiExamples/workspaceApiExamples.js-meta.xml @@ -0,0 +1,9 @@ + + + Worksapce API Examples + 51.0 + true + + lightning__HomePage + +