From d8a114c90a0d59079538dc8c180883b804b1a3ee Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 30 Oct 2023 15:48:18 +0200 Subject: [PATCH 1/6] popover improvements --- client/src/ui.js | 102 +++++++++++++++++++++----------- client/src/views/fields/base.js | 65 +++++++++----------- 2 files changed, 95 insertions(+), 72 deletions(-) diff --git a/client/src/ui.js b/client/src/ui.js index 08e54f803a..5b468028af 100644 --- a/client/src/ui.js +++ b/client/src/ui.js @@ -886,6 +886,7 @@ Espo.Ui = { * @property {'manual'|'click'|'hover'|'focus'} [trigger='manual'] A trigger type. * @property {boolean} [noToggleInit=false] Skip init toggle on click. * @property {boolean} [preventDestroyOnRender=false] Don't destroy on re-render. + * @property {boolean} [noHideOnOutsideClick=false] Don't hide on clicking outside. * @property {function(): void} [onShow] On-show callback. * @property {function(): void} [onHide] On-hide callback. */ @@ -896,18 +897,18 @@ Espo.Ui = { * @param {Element|JQuery} element An element. * @param {Espo.Ui~PopoverOptions} o Options. * @param {module:view} [view] A view. + * @return {{hide: function(), destroy: function(), show: function(), detach: function()}} */ popover: function (element, o, view) { - let $el = $(element); - - let $body = $('body') - let content = o.content || Handlebars.Utils.escapeExpression(o.text || ''); + const $el = $(element); + const $body = $('body'); + const content = o.content || Handlebars.Utils.escapeExpression(o.text || ''); let isShown = false; let container = o.container; if (!container) { - let $modalBody = $el.closest('.modal-body'); + const $modalBody = $el.closest('.modal-body'); container = $modalBody.length ? $modalBody : 'body'; } @@ -929,25 +930,27 @@ Espo.Ui = { return; } - $body.off('click.popover-' + view.cid); - - $body.on('click.popover-' + view.cid, e => { - if ($(e.target).closest('.popover-content').get(0)) { - return; - } - - if ($.contains($el.get(0), e.target)) { - return; - } - - if ($el.get(0) === e.target) { - return; - } - + if (view && !o.noHideOnOutsideClick) { $body.off('click.popover-' + view.cid); - // noinspection JSUnresolvedReference - $el.popover('hide'); - }); + + $body.on('click.popover-' + view.cid, e => { + if ($(e.target).closest('.popover-content').get(0)) { + return; + } + + if ($.contains($el.get(0), e.target)) { + return; + } + + if ($el.get(0) === e.target) { + return; + } + + $body.off('click.popover-' + view.cid); + // noinspection JSUnresolvedReference + $el.popover('hide'); + }); + } if (o.onShow) { o.onShow(); @@ -968,26 +971,46 @@ Espo.Ui = { }); } - if (view) { - let hide = () => { - if (!isShown) { - return; - } + let isDetached = false; - // noinspection JSUnresolvedReference - $el.popover('hide'); - }; - - let destroy = () => { - // noinspection JSUnresolvedReference - $el.popover('destroy'); + const detach = () => { + if (view) { $body.off('click.popover-' + view.cid); view.off('remove', destroy); view.off('render', destroy); view.off('render', hide); - }; + } + isDetached = true; + }; + + const destroy = () => { + if (isDetached) { + return; + } + + // noinspection JSUnresolvedReference + $el.popover('destroy'); + + detach(); + }; + + const hide = () => { + if (!isShown) { + return; + } + + // noinspection JSUnresolvedReference + $el.popover('hide'); + }; + + const show = () => { + // noinspection JSUnresolvedReference + $el.popover('show'); + }; + + if (view) { view.once('remove', destroy); if (!o.preventDestroyOnRender) { @@ -998,6 +1021,13 @@ Espo.Ui = { view.on('render', hide); } } + + return { + hide: () => hide(), + destroy: () => destroy(), + show: () => show(), + detach: () => detach(), + }; }, /** diff --git a/client/src/views/fields/base.js b/client/src/views/fields/base.js index 24448defac..9666e8a600 100644 --- a/client/src/views/fields/base.js +++ b/client/src/views/fields/base.js @@ -1443,8 +1443,10 @@ class BaseFieldView extends View { * * @param {string} message A message. * @param {string|JQuery|Element} [target] A target element or selector. + * @param {module:view} [view] A child view that contains the target. The closest view should to passed. + * Should be omitted if there is no child views or the target is not rendered by a child view. */ - showValidationMessage(message, target) { + showValidationMessage(message, target, view) { if (this.validationMessageSuspended) { return; } @@ -1468,7 +1470,7 @@ class BaseFieldView extends View { } if ($el.length) { - let rect = $el.get(0).getBoundingClientRect(); + const rect = $el.get(0).getBoundingClientRect(); this.lastValidationMessage = message; @@ -1477,51 +1479,42 @@ class BaseFieldView extends View { } } - $el - .popover({ - placement: 'bottom', - container: 'body', - content: this.getHelper().transformMarkdownText(message).toString(), - html: true, - trigger: 'manual' - }) - .popover('show'); + this._popoverMap = this._popoverMap || new WeakMap(); + const element = $el.get(0); - let isDestroyed = false; + if (!element) { + return; + } - $el.closest('.field').one('mousedown click', () => { - if (isDestroyed) { - return; + if (this._popoverMap[element]) { + try { + this._popoverMap[element].detach(); } + catch (e) {} + } - $el.popover('destroy'); - isDestroyed = true; - }); + const popover = Espo.Ui.popover($el, { + placement: 'bottom', + container: 'body', + content: this.getHelper().transformMarkdownText(message).toString(), + trigger: 'manual', + noToggleInit: true, + noHideOnOutsideClick: true, + }, view || this); - this.once('render remove', () => { - if (isDestroyed) { - return; - } + popover.show(); - if ($el) { - $el.popover('destroy'); - isDestroyed = true; - } - }); + this._popoverMap[element] = popover; + + $el.closest('.field').one('mousedown click', () => popover.destroy()); + + this.once('render remove', () => popover.destroy()); if (this._timeout) { clearTimeout(this._timeout); } - this._timeout = setTimeout(() => { - if (isDestroyed) { - return; - } - - $el.popover('destroy'); - isDestroyed = true; - - }, this.VALIDATION_POPOVER_TIMEOUT); + this._timeout = setTimeout(() => popover.destroy(), this.VALIDATION_POPOVER_TIMEOUT); } /** From b3391df6e84409e41e87f782b140d8b9be085329 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 1 Nov 2023 16:43:05 +0200 Subject: [PATCH 2/6] mass update targetLists --- .../Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json | 1 - .../Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json | 1 - .../Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json | 1 - 3 files changed, 3 deletions(-) diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json index 55bf271855..d9c1c67d58 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json @@ -194,7 +194,6 @@ "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, - "layoutMassUpdateDisabled": true, "importDisabled": true, "exportDisabled": true, "noLoad": true diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json index 860946f7cc..815a5b65dc 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json @@ -466,7 +466,6 @@ "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, - "layoutMassUpdateDisabled": true, "importDisabled": true, "noLoad": true }, diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json index 868d52e354..de974cb9cd 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json @@ -216,7 +216,6 @@ "type": "linkMultiple", "layoutDetailDisabled": true, "layoutListDisabled": true, - "layoutMassUpdateDisabled": true, "importDisabled": true, "noLoad": true }, From 986edeb79e67d342bda9db65eb629f6d63a94e0d Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 1 Nov 2023 16:49:13 +0200 Subject: [PATCH 3/6] cs --- client/src/views/modals/mass-update.js | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/client/src/views/modals/mass-update.js b/client/src/views/modals/mass-update.js index 027ee849ad..7a3eb47154 100644 --- a/client/src/views/modals/mass-update.js +++ b/client/src/views/modals/mass-update.js @@ -52,7 +52,7 @@ class MassUpdateModalView extends ModalView { events = { /** @this MassUpdateModalView */ 'click a[data-action="add-field"]': function (e) { - let field = $(e.currentTarget).data('name'); + const field = $(e.currentTarget).data('name'); this.addField(field); }, @@ -86,7 +86,7 @@ class MassUpdateModalView extends ModalView { this.hasActionMap = {}; - let totalCount = this.options.totalCount; + const totalCount = this.options.totalCount; this.helper = new MassActionHelper(this); @@ -139,11 +139,11 @@ class MassUpdateModalView extends ModalView { this.addedFieldList.push(name); - let label = this.getHelper().escapeString( + const label = this.getHelper().escapeString( this.translate(name, 'fields', this.entityType) ); - let $cell = + const $cell = $('
') .addClass('cell form-group') .attr('data-name', name) @@ -158,7 +158,7 @@ class MassUpdateModalView extends ModalView { .attr('data-name', name) ); - let $row = + const $row = $('
') .addClass('item grid-auto-fill-md') .attr('data-name', name) @@ -166,13 +166,13 @@ class MassUpdateModalView extends ModalView { this.$el.find('.fields-container').append($row); - let type = this.model.getFieldType(name); - let viewName = this.model.getFieldParam(name, 'view') || this.getFieldManager().getViewName(type); + const type = this.model.getFieldType(name); + const viewName = this.model.getFieldParam(name, 'view') || this.getFieldManager().getViewName(type); - let actionList = this.getMetadata().get(['entityDefs', this.entityType, name, 'massUpdateActionList']) || + const actionList = this.getMetadata().get(['entityDefs', this.entityType, name, 'massUpdateActionList']) || this.getMetadata().get(['fields', type, 'massUpdateActionList']); - let hasActionDropdown = actionList !== null; + const hasActionDropdown = actionList !== null; this.hasActionMap[name] = hasActionDropdown; @@ -192,13 +192,13 @@ class MassUpdateModalView extends ModalView { }); if (hasActionDropdown) { - let $select = + const $select = $('