diff --git a/client/src/helpers/action-item-setup.js b/client/src/helpers/action-item-setup.js index 0dbe9f2609..55cf1e11fe 100644 --- a/client/src/helpers/action-item-setup.js +++ b/client/src/helpers/action-item-setup.js @@ -55,9 +55,11 @@ define(() => { * @param {function(Object): void} addFunc * @param {function(string): void} showFunc * @param {function(string): void} hideFunc + * @param {{listenToViewModelSync?: boolean}} options */ - setup(view, type, waitFunc, addFunc, showFunc, hideFunc) { - let additionalActionList = []; + setup(view, type, waitFunc, addFunc, showFunc, hideFunc, options) { + options = options || {}; + let actionList = []; let scope = view.scope || view.model.entityType; @@ -92,7 +94,7 @@ define(() => { item.hidden = true; } - additionalActionList.push(item); + actionList.push(item); let data = item.data || {}; let handlerName = item.handler || data.handler; @@ -128,12 +130,12 @@ define(() => { })); }); - if (!additionalActionList.length) { + if (!actionList.length) { return; } - view.listenTo(view.model, 'sync', () => { - additionalActionList.forEach(item => { + let onSync = () => { + actionList.forEach(item => { if (item.handlerInstance && item.checkVisibilityFunction) { let isNotVisible = !item.handlerInstance[item.checkVisibilityFunction] .call(item.handlerInstance); @@ -153,7 +155,15 @@ define(() => { hideFunc(item.name); }); - }); + }; + + if (options.listenToViewModelSync) { + view.listenTo(view, 'model-sync', () => onSync()); + + return; + } + + view.listenTo(view.model, 'sync', () => onSync()); } } diff --git a/client/src/ui.js b/client/src/ui.js index da6981e664..73219bf347 100644 --- a/client/src/ui.js +++ b/client/src/ui.js @@ -346,8 +346,8 @@ define('ui', [], function () { this.buttonList.forEach(o => { if (typeof o.onClick === 'function') { $('#' + this.id + ' .modal-footer button[data-name="' + o.name + '"]') - .on('click', () => { - o.onClick(this); + .on('click', (e) => { + o.onClick(this, e); }); } }); @@ -355,8 +355,8 @@ define('ui', [], function () { this.dropdownItemList.forEach(o => { if (typeof o.onClick === 'function') { $('#' + this.id + ' .modal-footer a[data-name="' + o.name + '"]') - .on('click', () => { - o.onClick(this); + .on('click', (e) => { + o.onClick(this, e); }); } }); @@ -436,7 +436,8 @@ define('ui', [], function () { this.dropdownItemList.forEach(o => { rightPart += '
  • ' + - ''+(o.html || o.text)+'
  • '; + ''+(o.html || o.text)+''; }); rightPart += ''; diff --git a/client/src/utils.js b/client/src/utils.js index 445e7c431b..fce468d1a1 100644 --- a/client/src/utils.js +++ b/client/src/utils.js @@ -41,10 +41,12 @@ define('utils', [], function () { * * @param {module:view.Class} viewObject A view. * @param {Event} e An event. + * @param {string} [action] An action. If not specified, will be fetched from a target element. + * @param {string} [handler] A handler name. */ - handleAction: function (viewObject, e) { + handleAction: function (viewObject, e, action, handler) { let $target = $(e.currentTarget); - let action = $target.data('action'); + action = action || $target.data('action'); let fired = false; if (!action) { @@ -53,6 +55,7 @@ define('utils', [], function () { let data = $target.data(); let method = 'action' + Espo.Utils.upperCaseFirst(action); + handler = handler || data.handler; if (typeof viewObject[method] === 'function') { viewObject[method].call(viewObject, data, e); @@ -62,13 +65,13 @@ define('utils', [], function () { fired = true; } - else if (data.handler) { + else if (handler) { e.preventDefault(); e.stopPropagation(); fired = true; - require(data.handler, function (Handler) { + require(handler, function (Handler) { let handler = new Handler(viewObject); handler[method].call(handler, data, e); diff --git a/client/src/views/modal.js b/client/src/views/modal.js index f0b9c3e74f..b611fe8107 100644 --- a/client/src/views/modal.js +++ b/client/src/views/modal.js @@ -348,8 +348,6 @@ define('views/modal', ['view'], function (Dep) { return; } - var text = o.text; - if (!o.text) { if ('label' in o) { o.text = this.translate(o.label, 'labels', this.scope); @@ -358,9 +356,11 @@ define('views/modal', ['view'], function (Dep) { } } - o.onClick = o.onClick || ( - this['action' + Espo.Utils.upperCaseFirst(o.name)] || function () {} - ).bind(this); + o.onClick = o.onClick || ((d, e) => { + let handler = o.handler || (o.data || {}).handler; + + Espo.Utils.handleAction(this, e, o.name, handler); + }); buttonListExt.push(o); }); @@ -388,8 +388,6 @@ define('views/modal', ['view'], function (Dep) { return; } - var text = o.text; - if (!o.text) { if ('label' in o) { o.text = this.translate(o.label, 'labels', this.scope) @@ -398,9 +396,11 @@ define('views/modal', ['view'], function (Dep) { } } - o.onClick = o.onClick || ( - this['action' + Espo.Utils.upperCaseFirst(o.name)] || function () {} - ).bind(this); + o.onClick = o.onClick || ((d, e) => { + let handler = o.handler || (o.data || {}).handler; + + Espo.Utils.handleAction(this, e, o.name, handler); + }); dropdownItemListExt.push(o); }); diff --git a/client/src/views/modals/detail.js b/client/src/views/modals/detail.js index ca167a97d0..e7dfc93b2e 100644 --- a/client/src/views/modals/detail.js +++ b/client/src/views/modals/detail.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/modals/detail', ['views/modal'], function (Dep) { +define('views/modals/detail', ['views/modal', 'helpers/action-item-setup'], function (Dep, ActionItemSetup) { /** * A quick view modal. @@ -145,7 +145,12 @@ define('views/modals/detail', ['views/modal'], function (Dep) { this.setupAfterModelCreated(); - this.listenTo(this.model, 'sync', () => this.controlRecordButtonsVisibility()); + this.listenTo(this.model, 'sync', () => { + this.controlRecordButtonsVisibility(); + + this.trigger('model-sync'); + }); + this.listenToOnce(this.model, 'sync', () => this.createRecordView()); this.model.fetch(); @@ -162,12 +167,17 @@ define('views/modals/detail', ['views/modal'], function (Dep) { this.sourceModel.set(this.model.getClonedAttributes()); }); - this.listenTo(this.model, 'sync', () => this.controlRecordButtonsVisibility()); + this.listenTo(this.model, 'sync', () => { + this.controlRecordButtonsVisibility(); + + this.trigger('model-sync'); + }); this.once('after:render', () => { this.model.fetch(); }); + this.setupActionItems(); this.createRecordView(); }); @@ -183,8 +193,37 @@ define('views/modals/detail', ['views/modal'], function (Dep) { } }, + /** + * @private + */ + setupActionItems: function () { + /** @var {module:helpers/action-item-setup.Class} */ + let actionItemSetup = new ActionItemSetup( + this.getMetadata(), + this.getHelper(), + this.getAcl(), + this.getLanguage() + ); + + actionItemSetup.setup( + this, + 'modalDetail', + promise => this.wait(promise), + item => this.addDropdownItem(item), + name => this.showActionItem(name), + name => this.hideActionItem(name), + {listenToViewModelSync: true} + ); + }, + + /** + * @protected + */ setupAfterModelCreated: function () {}, + /** + * @protected + */ setupRecordButtons: function () { if (!this.removeDisabled) { this.addRemoveButton(); @@ -375,7 +414,7 @@ define('views/modals/detail', ['views/modal'], function (Dep) { return; } - var previousModel = this.model; + let previousModel = this.model; this.sourceModel = this.model.collection.at(indexOfRecord); @@ -391,18 +430,24 @@ define('views/modals/detail', ['views/modal'], function (Dep) { this.model = this.sourceModel.clone(); this.model.collection = this.sourceModel.collection; + this.stopListening(previousModel, 'change'); + this.stopListening(previousModel, 'sync'); + this.listenTo(this.model, 'change', () => { this.sourceModel.set(this.model.getClonedAttributes()); }); - this.listenTo(this.model, 'sync', () => this.controlRecordButtonsVisibility()); + this.listenTo(this.model, 'sync', () => { + this.controlRecordButtonsVisibility(); - this.once('after:render', () => { - this.model.fetch(); + this.trigger('model-sync'); }); this.createRecordView(() => { - this.reRender(); + this.reRender() + .then(() => { + this.model.fetch(); + }) }); this.controlNavigationButtons(); @@ -504,6 +549,8 @@ define('views/modals/detail', ['views/modal'], function (Dep) { this.trigger('after:save', model); this.controlRecordButtonsVisibility(); + + this.trigger('model-sync'); }); view.render(); diff --git a/client/src/views/record/detail.js b/client/src/views/record/detail.js index 0f08e83e20..fac49e9241 100644 --- a/client/src/views/record/detail.js +++ b/client/src/views/record/detail.js @@ -131,6 +131,7 @@ function (Dep, ViewRecordHelper, ActionItemSetup) { * @property {string} [label] A label. * @property {string} [html] An HTML. * @property {boolean} [hidden] Hidden. + * @property {Object.} [data] Data attributes. */ /**