From cbf013cfa9bf4d803f52fe1f0155a3c9478b7a32 Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 12:40:06 +0200 Subject: [PATCH 01/12] Added relationship name field in button admin --- QUICKCREATE_RELATIONSHIP_UPDATE.md | 46 ++++++++++++++++ .../field-manager/fields/relationship-name.js | 22 ++++++++ .../src/views/fields/link-button.js | 51 ++++++++++------- .../Resources/i18n/en_US/Admin.json | 55 ++++++++++--------- .../Resources/i18n/en_US/FieldManager.json | 5 +- .../metadata/fields/link-button.json | 8 ++- 6 files changed, 138 insertions(+), 49 deletions(-) create mode 100644 QUICKCREATE_RELATIONSHIP_UPDATE.md create mode 100644 files/client/custom/modules/link-button/src/views/admin/field-manager/fields/relationship-name.js diff --git a/QUICKCREATE_RELATIONSHIP_UPDATE.md b/QUICKCREATE_RELATIONSHIP_UPDATE.md new file mode 100644 index 0000000..f48aef7 --- /dev/null +++ b/QUICKCREATE_RELATIONSHIP_UPDATE.md @@ -0,0 +1,46 @@ +# Link Button Extension - Quick Create Relationship Update + +## Overview +This update adds the ability to specify a custom relationship name when using the Quick Create mode in the Link Button extension. Previously, the extension assumed the relationship name always followed the pattern `entityType.toLowerCase() + 'Id'`, which was not always correct. + +## Changes Made + +### 1. Added New Field Parameter +- Added `relationshipName` parameter to the field metadata (`link-button.json`) +- This field is only visible when "quickCreate" mode is selected +- Includes a tooltip explaining its purpose + +### 2. Created Conditional View +- Created `relationship-name.js` view that extends varchar field +- Shows/hides the relationship name field based on the selected mode +- Located at: `files/client/custom/modules/link-button/src/views/admin/field-manager/fields/relationship-name.js` + +### 3. Updated Quick Create Logic +- Modified `actionQuickCreate()` function in `link-button.js` +- Now checks for the `relationshipName` parameter +- If specified, uses the custom relationship name to link records +- If not specified, falls back to the original behavior (using entity type in lowercase) + +### 4. Updated Language Files +- Added label "Relationship Name" in `Admin.json` +- Added tooltip in `FieldManager.json` explaining the field's purpose + +## Usage + +1. When creating or editing a Link Button field: + - Select "Quick Create" as the mode + - A new field "Relationship Name" will appear + - Enter the exact relationship name (e.g., "account", "contact", "parentAccount") + - If left empty, the system will use the default pattern (entityType.toLowerCase()) + +2. The relationship will be properly established when creating new records through the Quick Create modal + +## Example + +If you have a custom relationship where Opportunities are linked to Accounts via a relationship named "primaryAccount" instead of just "account": +- Set Mode: Quick Create +- Set Relationship Name: primaryAccount +- The new record will be linked using `primaryAccountId` and `primaryAccountName` + +## Backward Compatibility +This update maintains full backward compatibility. Existing Link Button fields will continue to work as before, using the default entity-based relationship naming pattern. diff --git a/files/client/custom/modules/link-button/src/views/admin/field-manager/fields/relationship-name.js b/files/client/custom/modules/link-button/src/views/admin/field-manager/fields/relationship-name.js new file mode 100644 index 0000000..f7ecffa --- /dev/null +++ b/files/client/custom/modules/link-button/src/views/admin/field-manager/fields/relationship-name.js @@ -0,0 +1,22 @@ +define('link-button:views/admin/field-manager/fields/relationship-name', ['views/fields/varchar'], (Dep) => { + return class extends Dep { + + setup() { + super.setup(); + this.listenTo(this.model, 'change:mode', this.toggleRelationshipName); + } + + afterRender() { + super.afterRender(); + this.toggleRelationshipName(); + } + + toggleRelationshipName() { + if (this.model.get('mode') === 'quickCreate') { + this.getParentView().showField('relationshipName'); + } else { + this.getParentView().hideField('relationshipName'); + } + } + }; +}); diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 79e2e96..b999835 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -12,7 +12,7 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { this.events['click button[data-action="open-modal"]'] = () => { this.actionOpenModal(); }; - + this.events['click button[data-action="espo-modal"]'] = () => { this.actionEspoModal(); }; @@ -24,7 +24,7 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { this.events['click button[data-action="quick-create"]'] = () => { this.actionQuickCreate(); }; - + this.events['click button[data-action="run-workflow"]'] = () => { this.actionCheckWorkFlow(); }; @@ -39,11 +39,11 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { const isDetailMode = this.isDetailMode(); const hideOriginalWorkflowAction = this.model.getFieldParam(this.name, 'hideOriginalWorkflowAction'); const mode = this.model.getFieldParam(this.name, 'mode'); - + if (hideLabel === true) { this.getLabelElement().hide(); } - + if (url && isDetailMode && hideOriginalWorkflowAction === true && mode === 'runEspoWorkflow') { const workflowId = url.split('#')[1]?.split('/').pop(); superParent.hideHeaderActionItem(`runWorkflow_${workflowId}`); @@ -88,18 +88,18 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { if (!hashPart) { return Espo.Ui.error('Error: this is not a valid CRM URL'); } - + let parts = hashPart.split('/'); let entityType = parts[0]; if (!entityType) { return Espo.Ui.error('Error: no entity type found'); } - + let recordId = parts[parts.length - 1]; if (!recordId) { return Espo.Ui.error('Error: no record ID found'); } - + this.notify('Loading...'); this.createView('quickView', 'link-button:views/modals/espo-modal', { scope: entityType, @@ -124,7 +124,7 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { if (!hashPart) { return Espo.Ui.error('Error: this is not a valid CRM URL'); } - + let parts = hashPart.split('/'); let entityTypeModal = parts[0]; if (!entityTypeModal) { @@ -137,13 +137,26 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { } else { viewName = this.getMetadata().get('clientDefs.' + entityTypeModal + '.modalViews.edit') || 'views/modals/edit'; } - + let attributes = { parentId: model.id, parentType: model.entityType, parentName: model.get('name'), }; + // Add relationship link if relationship name is specified + let relationshipName = this.model.getFieldParam(this.name, 'relationshipName'); + if (relationshipName) { + // Use the specified relationship name + attributes[relationshipName + 'Id'] = model.id; + attributes[relationshipName + 'Name'] = model.get('name'); + } else { + // Fallback to entity type based relationship (backward compatibility) + let entity = model.entityType; + attributes[entity.toLowerCase() + 'Id'] = model.id; + attributes[entity.toLowerCase() + 'Name'] = model.get('name'); + } + if ( entityTypeModal === 'Email' && ['Contact', 'Lead', 'Account'].includes(model.entityType) && @@ -186,14 +199,14 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { } Espo.Ui.confirm(message, { - confirmText: this.translate('Yes', 'labels'), - cancelText: this.translate('No', 'labels'), - backdrop: true, - isHtml: true, - }) + confirmText: this.translate('Yes', 'labels'), + cancelText: this.translate('No', 'labels'), + backdrop: true, + isHtml: true, + }) .then(() => this.actionEspoWorkFlow()); } - + actionEspoWorkFlow() { let model = this.model; @@ -211,13 +224,13 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { if (entityType !== 'Workflow') { return Espo.Ui.error(('Error: not a workflow')); } - + Espo.Ajax.getRequest('LinkButton/WorkflowCheck/' + workflowId) .then(response => { if (response.isManual === false) { return Espo.Ui.error(('Error: not a manual or active workflow')); } - + Espo.Ajax.postRequest('WorkflowManual/action/run', { targetId: model.id, id: workflowId, @@ -232,11 +245,11 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { Espo.Ui.error(('Error checking workflow type')); }); } - + actionOpenPopup() { const popupHeight = this.model.getFieldParam(this.name, 'popupHeight') || 800; const popupWidth = this.model.getFieldParam(this.name, 'popupWidth') || 600; window.open(this.model.get(this.name), '_blank', `scrollbars=yes,height=${popupHeight},width=${popupWidth}`); } }; -}); \ No newline at end of file +}); diff --git a/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json b/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json index 30a2777..dfc86c5 100644 --- a/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json +++ b/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json @@ -1,32 +1,42 @@ { - "fieldTypes": { - "link-button": "Link Button" + "labels": { + "Link Button": "Link Button" }, "fields": { - "iconLeft": "Left Icon", - "iconRight": "Right Icon", - "mode": "Mode", "buttonLabel": "Button Label", - "placeholder": "Placeholder Value", - "hideLabel": "Hide Label", - "buttonSize": "Button Size", - "style": "Style", + "placeholder": "Placeholder", "title": "Title", + "hideLabel": "Hide Label", + "mode": "Mode", + "hideOriginalWorkflowAction": "Hide Original Workflow Action", "confirmationDialog": "Confirmation Dialog", "confirmationText": "Confirmation Text", - "hideOriginalWorkflowAction": "Hide Original Workflow Action", + "relationshipName": "Relationship Name", "popupHeight": "Popup Height", - "popupWidth": "Popup Width" + "popupWidth": "Popup Width", + "style": "Style", + "buttonSize": "Button Size", + "iconLeft": "Icon Left", + "iconRight": "Icon Right" }, "options": { + "mode": { + "openNewTab": "Open New Tab", + "openUrl": "Open URL", + "openPopup": "Open Popup", + "openModal": "Open Modal", + "openEspoModal": "Open Espo Modal", + "quickCreate": "Quick Create", + "runEspoWorkflow": "Run Espo Workflow" + }, "style": { - "default": "default", + "default": "Default", "primary": "Primary", "secondary": "Secondary", "success": "Success", "danger": "Danger", "warning": "Warning", - "info": "info", + "info": "Info", "dark": "Dark", "light": "Light", "outline-primary": "Outline Primary", @@ -34,7 +44,7 @@ "outline-success": "Outline Success", "outline-danger": "Outline Danger", "outline-warning": "Outline Warning", - "outline-info": "Outline info", + "outline-info": "Outline Info", "outline-dark": "Outline Dark", "outline-light": "Outline Light" }, @@ -42,18 +52,9 @@ "btn-sm": "Small", "btn-md": "Medium", "btn-lg": "Large", - "btn-sm btn-block": "Block Small", - "btn-md btn-block": "Block Medium", - "btn-lg btn-block": "Block Large" - }, - "mode": { - "openNewTab": "Open New Tab", - "openUrl": "Open URL", - "openPopup": "Open Popup", - "openModal": "Open Modal", - "openEspoModal": "Open Espo Modal", - "quickCreate": "Quick Create", - "runEspoWorkflow": "Run Workflow" + "btn-sm btn-block": "Small Block", + "btn-md btn-block": "Medium Block", + "btn-lg btn-block": "Large Block" } } -} +} \ No newline at end of file diff --git a/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/FieldManager.json b/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/FieldManager.json index f260028..7d6da55 100644 --- a/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/FieldManager.json +++ b/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/FieldManager.json @@ -4,6 +4,7 @@ }, "tooltips": { "hideLabel": "If active consider disabling inline edit as well if you plan to use block button size.", - "placeholder": "Text to be shown as a hint when there is no value in the field." + "placeholder": "Text to be shown as a hint when there is no value in the field.", + "relationshipName": "The name of the relationship field to use when linking the newly created record to the parent record. If not specified, it will use the parent entity type in lowercase (e.g., 'account' for Account entity)." } -} +} \ No newline at end of file diff --git a/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json b/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json index 02b6ddb..3e427d3 100644 --- a/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json +++ b/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json @@ -53,6 +53,12 @@ "type": "text", "view": "link-button:views/admin/field-manager/fields/confirmation-text" }, + { + "name": "relationshipName", + "type": "varchar", + "tooltip": true, + "view": "link-button:views/admin/field-manager/fields/relationship-name" + }, { "name": "popupHeight", "type": "int", @@ -138,4 +144,4 @@ "type": "varchar" }, "view": "link-button:views/fields/link-button" -} +} \ No newline at end of file From bbb3e1575334d41817a97854a692cd012f328b58 Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 12:46:02 +0200 Subject: [PATCH 02/12] update to version --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 583c9d9..fa7fa44 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ "acceptableVersions": [ ">=7.0" ], - "version": "2.0.0", + "version": "2.0.1", "skipBackup": true, "releaseDate": "2025-02-15" } \ No newline at end of file From 1a86c4ea788e0e5d67377e1bed24758cb3436560 Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 13:03:21 +0200 Subject: [PATCH 03/12] Update link-button.js --- .../modules/link-button/src/views/fields/link-button.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index b999835..1753d23 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -52,8 +52,10 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { } data() { + const data = super.data(); return { - ...super.data(), + ...data, + url: this.model.get(this.name), iconLeft: this.model.getFieldParam(this.name, 'iconLeft'), iconRight: this.model.getFieldParam(this.name, 'iconRight'), mode: this.model.getFieldParam(this.name, 'mode'), From 19305801d333415de4a868d9dc6dcbe8fc9bf38f Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 13:16:53 +0200 Subject: [PATCH 04/12] bug fix --- QUICKCREATE_RELATIONSHIP_UPDATE.md | 5 +++++ .../link-button/src/views/fields/link-button.js | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/QUICKCREATE_RELATIONSHIP_UPDATE.md b/QUICKCREATE_RELATIONSHIP_UPDATE.md index f48aef7..4edefd3 100644 --- a/QUICKCREATE_RELATIONSHIP_UPDATE.md +++ b/QUICKCREATE_RELATIONSHIP_UPDATE.md @@ -3,6 +3,11 @@ ## Overview This update adds the ability to specify a custom relationship name when using the Quick Create mode in the Link Button extension. Previously, the extension assumed the relationship name always followed the pattern `entityType.toLowerCase() + 'Id'`, which was not always correct. +## Bug Fixes +- Fixed button display issues where buttons were showing as text fields +- Ensured proper template loading for all view modes (detail, list, edit) +- Added default values for style and buttonSize to prevent empty button rendering + ## Changes Made ### 1. Added New Field Parameter diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 1753d23..a919a18 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -5,10 +5,17 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { editTemplate = 'link-button:fields/edit' listTemplate = 'link-button:fields/list' detailTemplate = 'link-button:fields/detail' + searchTemplate = 'link-button:fields/edit' setup() { super.setup(); + // Ensure our custom templates are used + this.editTemplate = 'link-button:fields/edit'; + this.listTemplate = 'link-button:fields/list'; + this.detailTemplate = 'link-button:fields/detail'; + this.searchTemplate = 'link-button:fields/edit'; + this.events['click button[data-action="open-modal"]'] = () => { this.actionOpenModal(); }; @@ -55,15 +62,16 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { const data = super.data(); return { ...data, + value: this.model.get(this.name), url: this.model.get(this.name), iconLeft: this.model.getFieldParam(this.name, 'iconLeft'), iconRight: this.model.getFieldParam(this.name, 'iconRight'), - mode: this.model.getFieldParam(this.name, 'mode'), + mode: this.model.getFieldParam(this.name, 'mode') || 'openNewTab', buttonLabel: this.model.getFieldParam(this.name, 'buttonLabel') || null, placeholder: this.model.getFieldParam(this.name, 'placeholder') || null, title: this.model.getFieldParam(this.name, 'title') || null, - buttonSize: this.model.getFieldParam(this.name, 'buttonSize'), - style: this.model.getFieldParam(this.name, 'style'), + buttonSize: this.model.getFieldParam(this.name, 'buttonSize') || 'btn-md', + style: this.model.getFieldParam(this.name, 'style') || 'default', }; } From abe29a719ba55f968326201f50c69ff002e7d2d7 Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 14:38:24 +0200 Subject: [PATCH 05/12] Improved Quick Create --- QUICKCREATE_RELATIONSHIP_UPDATE.md | 2 + .../src/views/fields/link-button.js | 46 ++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/QUICKCREATE_RELATIONSHIP_UPDATE.md b/QUICKCREATE_RELATIONSHIP_UPDATE.md index 4edefd3..643292e 100644 --- a/QUICKCREATE_RELATIONSHIP_UPDATE.md +++ b/QUICKCREATE_RELATIONSHIP_UPDATE.md @@ -7,6 +7,8 @@ This update adds the ability to specify a custom relationship name when using th - Fixed button display issues where buttons were showing as text fields - Ensured proper template loading for all view modes (detail, list, edit) - Added default values for style and buttonSize to prevent empty button rendering +- Removed URL validation requirement for Quick Create, EspoModal, and Workflow modes +- Now accepts URLs starting with `#` (e.g., `#Notes`, `#Contact/view/123`) ## Changes Made diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index a919a18..6bc0e1c 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -52,8 +52,16 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { } if (url && isDetailMode && hideOriginalWorkflowAction === true && mode === 'runEspoWorkflow') { - const workflowId = url.split('#')[1]?.split('/').pop(); - superParent.hideHeaderActionItem(`runWorkflow_${workflowId}`); + let hashPart; + if (url.startsWith('#')) { + hashPart = url.substring(1); + } else { + hashPart = url.split('#')[1]; + } + const workflowId = hashPart?.split('/').pop(); + if (workflowId) { + superParent.hideHeaderActionItem(`runWorkflow_${workflowId}`); + } } } @@ -94,9 +102,17 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { actionEspoModal() { let model = this.model; let url = this.model.get(this.name); - let hashPart = url.split('#')[1]; + + // Handle URLs that start with # (e.g., #Contact/view/123) + let hashPart; + if (url.startsWith('#')) { + hashPart = url.substring(1); + } else { + hashPart = url.split('#')[1]; + } + if (!hashPart) { - return Espo.Ui.error('Error: this is not a valid CRM URL'); + return Espo.Ui.error('Error: this is not a valid URL'); } let parts = hashPart.split('/'); @@ -130,9 +146,17 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { actionQuickCreate() { let model = this.model; let url = this.model.get(this.name); - let hashPart = url.split('#')[1]; + + // Handle URLs that start with # (e.g., #Notes) + let hashPart; + if (url.startsWith('#')) { + hashPart = url.substring(1); + } else { + hashPart = url.split('#')[1]; + } + if (!hashPart) { - return Espo.Ui.error('Error: this is not a valid CRM URL'); + return Espo.Ui.error('Error: this is not a valid URL'); } let parts = hashPart.split('/'); @@ -221,7 +245,15 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { actionEspoWorkFlow() { let model = this.model; let url = model.get(this.name); - let hashPart = url.split('#')[1]; + + // Handle URLs that start with # (e.g., #Workflow/view/123) + let hashPart; + if (url.startsWith('#')) { + hashPart = url.substring(1); + } else { + hashPart = url.split('#')[1]; + } + if (!hashPart) { return Espo.Ui.error('Error: this is not a valid workflow URL'); } From 00dfbf8f578733f1cf2de029c5fdb64326dc2847 Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 14:51:59 +0200 Subject: [PATCH 06/12] removed URL validation from quick create --- .../link-button/src/views/fields/link-button.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 6bc0e1c..6173f7b 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -38,6 +38,22 @@ define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { } + validateUrl() { + // Get the current mode + const mode = this.model.getFieldParam(this.name, 'mode'); + const value = this.model.get(this.name); + + // For quickCreate, openEspoModal, and runEspoWorkflow modes, allow URLs starting with # + if (['quickCreate', 'openEspoModal', 'runEspoWorkflow'].includes(mode)) { + if (value && value.startsWith('#')) { + return true; // Valid + } + } + + // For other modes or URLs not starting with #, use parent validation + return super.validateUrl(); + } + afterRender() { super.afterRender(); const superParent = this.getParentView().getParentView()._parentView; From f97d39d7704abaf61a106fde4daa5c78bbaef0ae Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 11 Jul 2025 15:01:18 +0200 Subject: [PATCH 07/12] remove URL validation --- .../custom/modules/link-button/src/views/fields/link-button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 6173f7b..f30d858 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -1,4 +1,4 @@ -define('link-button:views/fields/link-button', ['views/fields/url'], (Dep) => { +define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) => { return class extends Dep { type = 'link-button' From e8ecc1ec4a38263933c412e2fd8470615f4b6bef Mon Sep 17 00:00:00 2001 From: rouhu Date: Sat, 12 Jul 2025 08:11:17 +0200 Subject: [PATCH 08/12] new button type added --- BUTTON_PRESSED_FEATURE.md | 51 +++++++++++++++++++ .../res/templates/fields/detail.tpl | 16 +++++- .../src/views/fields/link-button.js | 24 +++++++++ .../Resources/i18n/de_DE/Admin.json | 5 +- .../Resources/i18n/en_US/Admin.json | 3 +- .../metadata/fields/link-button.json | 3 +- manifest.json | 2 +- 7 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 BUTTON_PRESSED_FEATURE.md diff --git a/BUTTON_PRESSED_FEATURE.md b/BUTTON_PRESSED_FEATURE.md new file mode 100644 index 0000000..d7d7faa --- /dev/null +++ b/BUTTON_PRESSED_FEATURE.md @@ -0,0 +1,51 @@ +# Button Pressed Feature + +## Overview +The "Button Pressed" mode is a new feature for the EspoCRM Link Button extension that allows buttons to set their field value to "1" and submit the form when pressed. This enables workflow automation based on button clicks. + +## How It Works +When a Link Button field is configured with the "Button Pressed" mode: +1. The button appears on the detail view without requiring a URL +2. When clicked, the button sets its field value to "1" +3. The form is automatically saved +4. EspoCRM workflows can detect this field value change and trigger actions + +## Use Case Example +**Scenario**: Save as Draft button +1. Create a Link Button field called "saveAsDraft" +2. Set the mode to "Button Pressed" +3. Set the button label to "Save as Draft" +4. Create a workflow that: + - Triggers when the saveAsDraft field equals "1" + - Sets the record status to "Draft" + - Optionally resets the saveAsDraft field back to empty + +## Configuration +1. In Entity Manager, add a new Link Button field +2. Set the following parameters: + - **Mode**: Button Pressed + - **Button Label**: Your desired button text (e.g., "Save as Draft") + - **Style**: Choose button appearance (primary, success, etc.) + - **Button Size**: Select size (small, medium, large) + - **Icon Left/Right**: Optional icons for the button + +## Workflow Integration +To use with workflows: +1. Create a new workflow for your entity +2. Set trigger type to "After record saved" +3. Add condition: `[Your Button Field Name] equals 1` +4. Add desired actions (e.g., update status field) +5. Optionally add action to reset button field to empty + +## Technical Details +- The button field value is set to "1" when pressed +- The record is automatically saved after button click +- No URL validation is required for this mode +- The button is always visible in detail view (doesn't check for field value) + +## Files Modified +- `files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json` - Added "buttonPressed" to mode options +- `files/client/custom/modules/link-button/src/views/fields/link-button.js` - Added button press handler and validation logic +- `files/client/custom/modules/link-button/res/templates/fields/detail.tpl` - Added button template for new mode +- `files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json` - Added English translation +- `files/custom/Espo/Modules/LinkButton/Resources/i18n/de_DE/Admin.json` - Added German translation diff --git a/files/client/custom/modules/link-button/res/templates/fields/detail.tpl b/files/client/custom/modules/link-button/res/templates/fields/detail.tpl index 0a29151..e328ebd 100644 --- a/files/client/custom/modules/link-button/res/templates/fields/detail.tpl +++ b/files/client/custom/modules/link-button/res/templates/fields/detail.tpl @@ -150,4 +150,18 @@ ... {{/if}} {{/if}} -{{/ifEqual}} \ No newline at end of file +{{/ifEqual}} + +{{#ifEqual mode 'buttonPressed'}} + +{{/ifEqual}} diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index f30d858..90c85a2 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -36,6 +36,10 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = this.actionCheckWorkFlow(); }; + this.events['click button[data-action="button-pressed"]'] = () => { + this.actionButtonPressed(); + }; + } validateUrl() { @@ -43,6 +47,11 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = const mode = this.model.getFieldParam(this.name, 'mode'); const value = this.model.get(this.name); + // For buttonPressed mode, no URL validation is needed + if (mode === 'buttonPressed') { + return true; // Always valid + } + // For quickCreate, openEspoModal, and runEspoWorkflow modes, allow URLs starting with # if (['quickCreate', 'openEspoModal', 'runEspoWorkflow'].includes(mode)) { if (value && value.startsWith('#')) { @@ -309,5 +318,20 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = const popupWidth = this.model.getFieldParam(this.name, 'popupWidth') || 600; window.open(this.model.get(this.name), '_blank', `scrollbars=yes,height=${popupHeight},width=${popupWidth}`); } + + actionButtonPressed() { + // Set the field value to "1" + this.model.set(this.name, '1'); + + // Save the model + this.notify('Saving...'); + this.model.save().then(() => { + this.notify('Saved', 'success'); + // Trigger any workflows that might be listening for this field value + this.model.trigger('sync'); + }).catch(() => { + this.notify('Error occurred', 'error'); + }); + } }; }); diff --git a/files/custom/Espo/Modules/LinkButton/Resources/i18n/de_DE/Admin.json b/files/custom/Espo/Modules/LinkButton/Resources/i18n/de_DE/Admin.json index a8da904..f516158 100644 --- a/files/custom/Espo/Modules/LinkButton/Resources/i18n/de_DE/Admin.json +++ b/files/custom/Espo/Modules/LinkButton/Resources/i18n/de_DE/Admin.json @@ -8,7 +8,7 @@ "mode": "Modus", "buttonLabel": "Beschriftung", "placeholder": "Platzhalter Wert", - "hideLabel": "Label ausblenden", + "hideLabel": "Label ausblenden", "buttonSize": "Größe der Schaltfläche", "style": "Stil", "title": "Titel", @@ -53,7 +53,8 @@ "openModal": "Modal öffnen", "openEspoModal": "Espo Modal öffnen", "quickCreate": "Schnelles Erstellen", - "runEspoWorkflow": "Workflow ausführen" + "runEspoWorkflow": "Workflow ausführen", + "buttonPressed": "Schaltfläche gedrückt" } } } \ No newline at end of file diff --git a/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json b/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json index dfc86c5..fa721d2 100644 --- a/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json +++ b/files/custom/Espo/Modules/LinkButton/Resources/i18n/en_US/Admin.json @@ -27,7 +27,8 @@ "openModal": "Open Modal", "openEspoModal": "Open Espo Modal", "quickCreate": "Quick Create", - "runEspoWorkflow": "Run Espo Workflow" + "runEspoWorkflow": "Run Espo Workflow", + "buttonPressed": "Button Pressed" }, "style": { "default": "Default", diff --git a/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json b/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json index 3e427d3..b3cc320 100644 --- a/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json +++ b/files/custom/Espo/Modules/LinkButton/Resources/metadata/fields/link-button.json @@ -34,7 +34,8 @@ "openPopup", "openModal", "openEspoModal", - "quickCreate" + "quickCreate", + "buttonPressed" ], "view": "link-button:views/admin/field-manager/fields/mode" }, diff --git a/manifest.json b/manifest.json index fa7fa44..18c367d 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ "acceptableVersions": [ ">=7.0" ], - "version": "2.0.1", + "version": "2.0.2", "skipBackup": true, "releaseDate": "2025-02-15" } \ No newline at end of file From 0ebdad61d58004214519d506544d2899cf4017d3 Mon Sep 17 00:00:00 2001 From: rouhu Date: Sat, 12 Jul 2025 08:47:28 +0200 Subject: [PATCH 09/12] bug fix --- BUTTON_PRESSED_FEATURE.md | 1 + .../link-button/src/views/fields/link-button.js | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/BUTTON_PRESSED_FEATURE.md b/BUTTON_PRESSED_FEATURE.md index d7d7faa..5ec630f 100644 --- a/BUTTON_PRESSED_FEATURE.md +++ b/BUTTON_PRESSED_FEATURE.md @@ -40,6 +40,7 @@ To use with workflows: ## Technical Details - The button field value is set to "1" when pressed - The record is automatically saved after button click +- After saving, the view automatically returns to detail mode (just like the normal Save button) - No URL validation is required for this mode - The button is always visible in detail view (doesn't check for field value) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 90c85a2..a3b3950 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -327,6 +327,19 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = this.notify('Saving...'); this.model.save().then(() => { this.notify('Saved', 'success'); + + // Get the parent record view + let parentView = this.getParentView(); + while (parentView) { + // Look for the record detail view which has setDetailMode method + if (parentView.setDetailMode && typeof parentView.setDetailMode === 'function') { + // Switch to detail mode + parentView.setDetailMode(); + break; + } + parentView = parentView.getParentView ? parentView.getParentView() : null; + } + // Trigger any workflows that might be listening for this field value this.model.trigger('sync'); }).catch(() => { From 3d4ec901a4572e56e5a67f26584d694440daddf5 Mon Sep 17 00:00:00 2001 From: rouhu Date: Sat, 12 Jul 2025 09:00:50 +0200 Subject: [PATCH 10/12] bug fix --- .../link-button/src/views/fields/link-button.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index a3b3950..90b5fc1 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -331,10 +331,18 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = // Get the parent record view let parentView = this.getParentView(); while (parentView) { - // Look for the record detail view which has setDetailMode method - if (parentView.setDetailMode && typeof parentView.setDetailMode === 'function') { - // Switch to detail mode + // Look for the record detail view which has cancelEdit method + if (parentView.cancelEdit && typeof parentView.cancelEdit === 'function') { + // Use cancelEdit which properly switches to detail mode and updates buttons + parentView.cancelEdit(); + break; + } else if (parentView.setDetailMode && typeof parentView.setDetailMode === 'function') { + // Fallback to setDetailMode if cancelEdit is not available parentView.setDetailMode(); + // Try to update button bar if method exists + if (parentView.updateButtonsPanel && typeof parentView.updateButtonsPanel === 'function') { + parentView.updateButtonsPanel(); + } break; } parentView = parentView.getParentView ? parentView.getParentView() : null; From e8a6055578b0fbfacb185ebf3e780fa94eb71436 Mon Sep 17 00:00:00 2001 From: rouhu Date: Sat, 12 Jul 2025 11:56:10 +0200 Subject: [PATCH 11/12] Update link-button.js --- .../src/views/fields/link-button.js | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 90b5fc1..0e2540e 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -323,36 +323,27 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = // Set the field value to "1" this.model.set(this.name, '1'); - // Save the model - this.notify('Saving...'); - this.model.save().then(() => { - this.notify('Saved', 'success'); - - // Get the parent record view - let parentView = this.getParentView(); - while (parentView) { - // Look for the record detail view which has cancelEdit method - if (parentView.cancelEdit && typeof parentView.cancelEdit === 'function') { - // Use cancelEdit which properly switches to detail mode and updates buttons - parentView.cancelEdit(); - break; - } else if (parentView.setDetailMode && typeof parentView.setDetailMode === 'function') { - // Fallback to setDetailMode if cancelEdit is not available - parentView.setDetailMode(); - // Try to update button bar if method exists - if (parentView.updateButtonsPanel && typeof parentView.updateButtonsPanel === 'function') { - parentView.updateButtonsPanel(); - } - break; - } - parentView = parentView.getParentView ? parentView.getParentView() : null; + // Find the parent view that has the save method + let parentView = this.getParentView(); + while (parentView) { + // Look for the record detail view which has the save method + if (parentView.save && typeof parentView.save === 'function') { + // Use the parent view's save method which handles everything properly + parentView.save(); + break; } + parentView = parentView.getParentView ? parentView.getParentView() : null; + } - // Trigger any workflows that might be listening for this field value - this.model.trigger('sync'); - }).catch(() => { - this.notify('Error occurred', 'error'); - }); + // If no parent view with save method found, fallback to model save + // if (!parentView) { + // this.notify('Saving...'); + // this.model.save().then(() => { + // this.notify('Saved', 'success'); + // }).catch(() => { + // this.notify('Error occurred', 'error'); + // }); + // } } }; }); From 8010b3f5c34dc109d38d630e0d65fd1ae21b57a8 Mon Sep 17 00:00:00 2001 From: rouhu Date: Fri, 22 Aug 2025 11:33:05 +0200 Subject: [PATCH 12/12] Update link-button.js --- .../src/views/fields/link-button.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/files/client/custom/modules/link-button/src/views/fields/link-button.js b/files/client/custom/modules/link-button/src/views/fields/link-button.js index 0e2540e..919dce4 100644 --- a/files/client/custom/modules/link-button/src/views/fields/link-button.js +++ b/files/client/custom/modules/link-button/src/views/fields/link-button.js @@ -328,6 +328,12 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = while (parentView) { // Look for the record detail view which has the save method if (parentView.save && typeof parentView.save === 'function') { + // Listen for the after:save event to handle mode transition + this.listenToOnce(parentView, 'after:save', () => { + // Switch to detail view mode after successful save + this.switchToDetailView(parentView); + }); + // Use the parent view's save method which handles everything properly parentView.save(); break; @@ -345,5 +351,40 @@ define('link-button:views/fields/link-button', ['views/fields/varchar'], (Dep) = // }); // } } + + switchToDetailView(parentView) { + // Method 1: Try to switch the parent view to detail mode if it supports it + if (parentView.setDetailMode && typeof parentView.setDetailMode === 'function') { + parentView.setDetailMode(); + return; + } + + // Method 2: Try to call setReadOnly on the parent view + if (parentView.setReadOnly && typeof parentView.setReadOnly === 'function') { + parentView.setReadOnly(); + return; + } + + // Method 3: Navigate to detail view using router + if (this.model.id) { + const url = '#' + this.model.entityType + '/view/' + this.model.id; + this.getRouter().navigate(url, { trigger: true }); + return; + } + + // Method 4: Try to find and trigger detail mode on the record view + let recordView = parentView; + while (recordView) { + if (recordView.mode && recordView.setDetailMode) { + recordView.setDetailMode(); + return; + } + if (recordView.mode && recordView.setMode && typeof recordView.setMode === 'function') { + recordView.setMode('detail'); + return; + } + recordView = recordView.getParentView ? recordView.getParentView() : null; + } + } }; });