diff --git a/client/src/app.js b/client/src/app.js index 3d0d0b7937..a54ef9102c 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -58,6 +58,7 @@ define( 'ajax', 'number', 'page-title', + 'broadcast-channel' ], function ( Espo, @@ -88,7 +89,8 @@ define( WebSocketManager, Ajax, NumberUtil, - PageTitle + PageTitle, + BroadcastChannel ) { let App = function (options, callback) { @@ -149,6 +151,8 @@ define( responseCache: null, + broadcastChannel: null, + initCache: function (options) { let cacheTimestamp = options.cacheTimestamp || null; let storedCacheTimestamp = null; @@ -290,6 +294,8 @@ define( this.webSocketManager.connect(this.auth, this.user.id); } + this.initBroadcastChannel(); + let promiseList = []; let aclImplementationClassMap = {}; @@ -426,6 +432,7 @@ define( storage: this.storage, metadata: this.metadata, dateTime: this.dateTime, + broadcastChannel: this.broadcastChannel, }; }, @@ -649,14 +656,17 @@ define( this.unsetCookieAuth(); + if (this.broadcastChannel.object) { + this.broadcastChannel.object.close(); + } + + this.broadcastChannel = null; + xhr = new XMLHttpRequest; xhr.open('GET', this.basePath + this.apiUrl + '/'); - xhr.setRequestHeader('Authorization', 'Basic ' + base64.encode('**logout:logout')); - xhr.send(''); - xhr.abort(); this.loadStylesheet(); @@ -884,6 +894,37 @@ define( }); }, + initBroadcastChannel: function () { + this.broadcastChannel = new BroadcastChannel(); + + this.broadcastChannel.subscribe(event => { + if (event.data === 'update:all') { + this.metadata.loadSkipCache(); + this.settings.loadSkipCache(); + this.language.loadSkipCache(); + this.viewHelper.layoutManager.clearLoadedData(); + } + + if (event.data === 'update:metadata') { + this.metadata.loadSkipCache(); + } + + if (event.data === 'update:config') { + this.settings.load(); + } + + if (event.data === 'update:language') { + this.language.loadSkipCache(); + } + + if (event.data === 'update:layout') { + this.viewHelper.layoutManager.clearLoadedData(); + } + }); + + this.viewHelper.broadcastChannel = this.broadcastChannel; + }, + }, Backbone.Events); App.extend = Backbone.Router.extend; diff --git a/client/src/broadcast-channel.js b/client/src/broadcast-channel.js new file mode 100644 index 0000000000..017e1b16b0 --- /dev/null +++ b/client/src/broadcast-channel.js @@ -0,0 +1,58 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko + * Website: https://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +define('broadcast-channel', [], function () { + + let Channel = class { + constructor() { + this.object = null; + + if (BroadcastChannel) { + this.object = new BroadcastChannel('app'); + } + } + + postMessage(message) { + if (!this.object) { + return; + } + + this.object.postMessage(message); + } + + subscribe(callback) { + if (!this.object) { + return; + } + + this.object.addEventListener('message', callback); + } + }; + + return Channel; +}); diff --git a/client/src/controller.js b/client/src/controller.js index 94141a8897..ec620085ac 100644 --- a/client/src/controller.js +++ b/client/src/controller.js @@ -50,6 +50,7 @@ define('controller', [], function () { this._storage = injections.storage || null; this._metadata = injections.metadata || null; this._dateTime = injections.dateTime || null; + this._broadcastChannel = injections.broadcastChannel || null; this.set('masterRendered', false); }; diff --git a/client/src/controllers/admin.js b/client/src/controllers/admin.js index 538a8fa6b2..a6363ea0b4 100644 --- a/client/src/controllers/admin.js +++ b/client/src/controllers/admin.js @@ -116,6 +116,12 @@ define('controllers/admin', ['controller', 'search-manager'], function (Dep, Sea let model = this.getConfig().clone(); model.defs = this.getConfig().defs; + this.listenTo(model, 'after:save', () => { + this.getConfig().load(); + + this._broadcastChannel.postMessage('update:config'); + }); + return model; }, diff --git a/client/src/layout-manager.js b/client/src/layout-manager.js index ade64bfb15..02ceb1f802 100644 --- a/client/src/layout-manager.js +++ b/client/src/layout-manager.js @@ -176,6 +176,10 @@ define('layout-manager', [], function () { ); }, + clearLoadedData: function () { + this.data = {}; + }, + }, Backbone.Events); return LayoutManager; diff --git a/client/src/views/admin/entity-manager/modals/edit-entity.js b/client/src/views/admin/entity-manager/modals/edit-entity.js index fdc21b7464..c9a4612670 100644 --- a/client/src/views/admin/entity-manager/modals/edit-entity.js +++ b/client/src/views/admin/entity-manager/modals/edit-entity.js @@ -705,6 +705,8 @@ define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'model'] scope: name, }; + this.broadcastUpdate(); + this.trigger('after:save', o); }); }) @@ -731,6 +733,8 @@ define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'model'] this.model.fetchedAttributes = this.model.getClonedAttributes(); this.notify('Done', 'success'); + + this.broadcastUpdate(); }); }); }); @@ -827,5 +831,11 @@ define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'model'] return filtersOptionList; }, + broadcastUpdate: function () { + this.getHelper().broadcastChannel.postMessage('update:metadata'); + this.getHelper().broadcastChannel.postMessage('update:language'); + this.getHelper().broadcastChannel.postMessage('update:config'); + }, + }); }); diff --git a/client/src/views/admin/entity-manager/scope.js b/client/src/views/admin/entity-manager/scope.js index 12fd3e8ef2..d0a03d5c0a 100644 --- a/client/src/views/admin/entity-manager/scope.js +++ b/client/src/views/admin/entity-manager/scope.js @@ -148,6 +148,8 @@ define('views/admin/entity-manager/scope', 'view', function (Dep) { this.getConfig().load().then(() => { Espo.Ui.notify(false); + this.broadcastUpdate(); + this.getRouter().navigate('#Admin/entityManager', {trigger: true}); }); }); @@ -195,5 +197,10 @@ define('views/admin/entity-manager/scope', 'view', function (Dep) { this.$el.find('.item-dropdown-button"]').removeClass('disabled').removeAttr('disabled'); }, + broadcastUpdate: function () { + this.getHelper().broadcastChannel.postMessage('update:metadata'); + this.getHelper().broadcastChannel.postMessage('update:settings'); + }, + }); }); diff --git a/client/src/views/admin/field-manager/edit.js b/client/src/views/admin/field-manager/edit.js index eae7b5b7bf..2760849e2f 100644 --- a/client/src/views/admin/field-manager/edit.js +++ b/client/src/views/admin/field-manager/edit.js @@ -565,6 +565,8 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model .then(() => this.trigger('after:save')); this.model.fetchedAttributes = this.model.getClonedAttributes(); + + this.broadcastUpdate(); }); this.notify('Saving...'); @@ -648,6 +650,8 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model this.notify('Done', 'success'); this.reRender(); + + this.broadcastUpdate(); }); }); }); @@ -655,5 +659,11 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model }, this); }, + broadcastUpdate: function () { + this.getHelper().broadcastChannel.postMessage('update:metadata'); + this.getHelper().broadcastChannel.postMessage('update:language'); + this.getHelper().broadcastChannel.postMessage('update:settings'); + }, + }); }); diff --git a/client/src/views/admin/field-manager/list.js b/client/src/views/admin/field-manager/list.js index de10fde692..49d38e26fa 100644 --- a/client/src/views/admin/field-manager/list.js +++ b/client/src/views/admin/field-manager/list.js @@ -36,7 +36,7 @@ define('views/admin/field-manager/list', 'view', function (Dep) { return { scope: this.scope, fieldDefsArray: this.fieldDefsArray, - typeList: this.typeList + typeList: this.typeList, }; }, @@ -57,22 +57,26 @@ define('views/admin/field-manager/list', 'view', function (Dep) { }, buildFieldDefs: function () { - return this.getModelFactory().create(this.scope).then( - function (model) { - this.fields = model.defs.fields; - this.fieldList = Object.keys(this.fields).sort(); - this.fieldDefsArray = []; - this.fieldList.forEach(function (field) { - var defs = this.fields[field]; - if (defs.customizationDisabled) return; - this.fieldDefsArray.push({ - name: field, - isCustom: defs.isCustom || false, - type: defs.type - }); - }, this); - }.bind(this) - ); + return this.getModelFactory().create(this.scope).then(model => { + this.fields = model.defs.fields; + + this.fieldList = Object.keys(this.fields).sort(); + this.fieldDefsArray = []; + + this.fieldList.forEach(field => { + var defs = this.fields[field]; + + if (defs.customizationDisabled) { + return; + } + + this.fieldDefsArray.push({ + name: field, + isCustom: defs.isCustom || false, + type: defs.type, + }); + }); + }); }, removeField: function (field) { @@ -87,15 +91,24 @@ define('views/admin/field-manager/list', 'view', function (Dep) { delete data['entityDefs'][this.scope]['fields'][field]; - this.getMetadata().loadSkipCache(() => { + this.getMetadata().loadSkipCache().then(() => this.buildFieldDefs() .then(() => { + this.broadcastUpdate(); + return this.reRender(); }) - .then(() => Espo.Ui.success(this.translate('Removed'))); - }); + .then(() => + Espo.Ui.success(this.translate('Removed')) + ) + ); }); }); }, + + broadcastUpdate: function () { + this.getHelper().broadcastChannel.postMessage('update:metadata'); + this.getHelper().broadcastChannel.postMessage('update:language'); + }, }); }); diff --git a/client/src/views/admin/label-manager/edit.js b/client/src/views/admin/label-manager/edit.js index ca07b1f3c5..edcef4c665 100644 --- a/client/src/views/admin/label-manager/edit.js +++ b/client/src/views/admin/label-manager/edit.js @@ -134,8 +134,9 @@ define('views/admin/label-manager/edit', 'view', function (Dep) { this.ajaxPostRequest('LabelManager/action/saveLabels', { scope: this.scope, language: this.language, - labels: data - }).then(returnData => { + labels: data, + }) + .then(returnData => { this.scopeDataInitial = Espo.Utils.cloneDeep(this.scopeData); this.dirtyLabelList = []; this.setConfirmLeaveOut(false); @@ -149,7 +150,12 @@ define('views/admin/label-manager/edit', 'view', function (Dep) { } Espo.Ui.success(this.translate('Saved')); - }).fail(() => { + + this.getHelper().broadcastChannel.postMessage('update:language'); + + this.getLanguage().loadSkipCache(); + }) + .fail(() => { this.$save.removeClass('disabled').removeAttr('disabled'); this.$cancel.removeClass('disabled').removeAttr('disabled'); }); diff --git a/client/src/views/admin/layouts/base.js b/client/src/views/admin/layouts/base.js index 5e90b074f9..de6efb8915 100644 --- a/client/src/views/admin/layouts/base.js +++ b/client/src/views/admin/layouts/base.js @@ -109,6 +109,8 @@ define('views/admin/layouts/base', 'view', function (Dep) { if (typeof callback === 'function') { callback(); } + + this.getHelper().broadcastChannel.postMessage('update:layout'); }, this.setId); }, diff --git a/client/src/views/admin/link-manager/modals/edit.js b/client/src/views/admin/link-manager/modals/edit.js index 09d1104326..b636c6f264 100644 --- a/client/src/views/admin/link-manager/modals/edit.js +++ b/client/src/views/admin/link-manager/modals/edit.js @@ -746,6 +746,8 @@ define('views/admin/link-manager/modals/edit', } this.getMetadata().loadSkipCache().then(() => { + this.broadcastUpdate(); + this.trigger('after:save'); this.close(); }); @@ -759,8 +761,13 @@ define('views/admin/link-manager/modals/edit', var linkDefs = this.getMetadata().get(['entityDefs', item, 'links']) || {}; var isFound = false; + for (var i in linkDefs) { - if (linkDefs[i].foreign == link && linkDefs[i].entity == entityType && linkDefs[i].type === 'hasChildren') { + if ( + linkDefs[i].foreign === link && + linkDefs[i].entity === entityType && + linkDefs[i].type === 'hasChildren' + ) { if (onlyNotCustom) { if (linkDefs[i].isCustom) { continue; @@ -778,5 +785,10 @@ define('views/admin/link-manager/modals/edit', return list; }, + + broadcastUpdate: function () { + this.getHelper().broadcastChannel.postMessage('update:metadata'); + this.getHelper().broadcastChannel.postMessage('update:language'); + }, }); }); diff --git a/client/src/views/site/navbar.js b/client/src/views/site/navbar.js index c17bc241d9..d6842e7d95 100644 --- a/client/src/views/site/navbar.js +++ b/client/src/views/site/navbar.js @@ -357,38 +357,47 @@ define('views/site/navbar', 'view', function (Dep) { this.getRouter().on('routed', (e) => { if (e.controller) { this.selectTab(e.controller); + + return; } - else { - this.selectTab(false); - } + + this.selectTab(false); }); - var tabList = this.getTabList(); + this.createView('notificationsBadge', 'views/notification/badge', { + el: this.options.el + ' .notifications-badge-container' + }); + let setup = () => { + this.setupQuickCreateList(); + this.setupGlobalSearch(); + this.setupTabDefsList(); + }; + + setup(); + + this.listenTo(this.getHelper().settings, 'sync', () => { + setup(); + + this.reRender(); + }); + + this.listenTo(this.getHelper().language, 'sync', () => { + setup(); + + this.reRender(); + }); + + this.once('remove', () => { + $(window).off('resize.navbar'); + $(window).off('scroll.navbar'); + $(window).off('scroll.navbar-tab-group'); + }); + }, + + setupQuickCreateList: function () { var scopes = this.getMetadata().get('scopes') || {}; - this.tabList = tabList.filter(item => { - if (!item) { - return false; - } - - if (typeof item === 'object') { - item.itemList = item.itemList || []; - - item.itemList = item.itemList.filter(item => { - return this.filterTabItem(item); - }); - - if (!item.itemList.length) { - return false; - } - - return true; - } - - return this.filterTabItem(item); - }); - this.quickCreateList = this.getQuickCreateList().filter(scope =>{ if (!scopes[scope]) { return false; @@ -404,20 +413,6 @@ define('views/site/navbar', 'view', function (Dep) { return true; }); - - this.createView('notificationsBadge', 'views/notification/badge', { - el: this.options.el + ' .notifications-badge-container' - }); - - this.setupGlobalSearch(); - - this.setupTabDefsList(); - - this.once('remove', () => { - $(window).off('resize.navbar'); - $(window).off('scroll.navbar'); - $(window).off('scroll.navbar-tab-group'); - }); }, filterTabItem: function (scope) { @@ -890,6 +885,30 @@ define('views/site/navbar', 'view', function (Dep) { }, setupTabDefsList: function () { + var tabList = this.getTabList(); + + this.tabList = tabList.filter(item => { + if (!item) { + return false; + } + + if (typeof item === 'object') { + item.itemList = item.itemList || []; + + item.itemList = item.itemList.filter(item => { + return this.filterTabItem(item); + }); + + if (!item.itemList.length) { + return false; + } + + return true; + } + + return this.filterTabItem(item); + }); + var tabDefsList = []; var colorsDisabled =