diff --git a/BUTTON_PRESSED_FEATURE.md b/BUTTON_PRESSED_FEATURE.md new file mode 100644 index 0000000..5ec630f --- /dev/null +++ b/BUTTON_PRESSED_FEATURE.md @@ -0,0 +1,52 @@ +# 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 +- 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) + +## 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/QUICKCREATE_RELATIONSHIP_UPDATE.md b/QUICKCREATE_RELATIONSHIP_UPDATE.md new file mode 100644 index 0000000..643292e --- /dev/null +++ b/QUICKCREATE_RELATIONSHIP_UPDATE.md @@ -0,0 +1,53 @@ +# 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. + +## 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 +- Removed URL validation requirement for Quick Create, EspoModal, and Workflow modes +- Now accepts URLs starting with `#` (e.g., `#Notes`, `#Contact/view/123`) + +## 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/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/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..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 @@ -1,18 +1,25 @@ -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' 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(); }; - + this.events['click button[data-action="espo-modal"]'] = () => { this.actionEspoModal(); }; @@ -24,11 +31,36 @@ 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(); }; + this.events['click button[data-action="button-pressed"]'] = () => { + this.actionButtonPressed(); + }; + + } + + validateUrl() { + // Get the current mode + 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('#')) { + return true; // Valid + } + } + + // For other modes or URLs not starting with #, use parent validation + return super.validateUrl(); } afterRender() { @@ -39,29 +71,40 @@ 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}`); + 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}`); + } } } data() { + const data = super.data(); return { - ...super.data(), + ...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', }; } @@ -84,22 +127,30 @@ 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('/'); 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, @@ -120,11 +171,19 @@ 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('/'); let entityTypeModal = parts[0]; if (!entityTypeModal) { @@ -137,13 +196,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,19 +258,27 @@ 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; 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'); } @@ -211,13 +291,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 +312,79 @@ 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}`); } + + actionButtonPressed() { + // Set the field value to "1" + this.model.set(this.name, '1'); + + // 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') { + // 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; + } + parentView = parentView.getParentView ? parentView.getParentView() : null; + } + + // 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'); + // }); + // } + } + + 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; + } + } }; -}); \ No newline at end of file +}); 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 30a2777..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 @@ -1,32 +1,43 @@ { - "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", + "buttonPressed": "Button Pressed" + }, "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 +45,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 +53,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..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" }, @@ -53,6 +54,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 +145,4 @@ "type": "varchar" }, "view": "link-button:views/fields/link-button" -} +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index 583c9d9..18c367d 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ "acceptableVersions": [ ">=7.0" ], - "version": "2.0.0", + "version": "2.0.2", "skipBackup": true, "releaseDate": "2025-02-15" } \ No newline at end of file