diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml index 9a57f9a1e3..46e872e544 100644 --- a/.idea/jsonSchemas.xml +++ b/.idea/jsonSchemas.xml @@ -854,6 +854,25 @@ + + + + + + + diff --git a/.vscode/settings.json b/.vscode/settings.json index eee86920e9..5be8a352d8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -352,6 +352,12 @@ ], "url": "./schema/metadata/app/metadata.json" }, + { + "fileMatch": [ + "*/Resources/metadata/app/clientNavbar.json" + ], + "url": "./schema/metadata/app/clientNavbar.json" + }, { "fileMatch": [ "*/Resources/metadata/app/orm.json" diff --git a/application/Espo/Resources/metadata/app/clientNavbar.json b/application/Espo/Resources/metadata/app/clientNavbar.json new file mode 100644 index 0000000000..8a4284e07f --- /dev/null +++ b/application/Espo/Resources/metadata/app/clientNavbar.json @@ -0,0 +1,11 @@ +{ + "itemDefs": { + "quickCreate": { + "view": "views/site/navbar/quick-create", + "class": "dropdown hidden-xs quick-create-container" + } + }, + "itemList": [ + "quickCreate" + ] +} diff --git a/client/res/templates/site/navbar.tpl b/client/res/templates/site/navbar.tpl index f5108abc28..94e23b293f 100644 --- a/client/res/templates/site/navbar.tpl +++ b/client/res/templates/site/navbar.tpl @@ -174,28 +174,9 @@ - {{#if enableQuickCreate}} - - {{/if}} + {{#each itemDataList}} +
  • {{{var key ../this}}}
  • + {{/each}} diff --git a/client/src/views/site/navbar.js b/client/src/views/site/navbar.js index 365730a15e..02b92d0efc 100644 --- a/client/src/views/site/navbar.js +++ b/client/src/views/site/navbar.js @@ -56,14 +56,6 @@ class NavbarSiteView extends View { this.xsCollapse(); }, /** @this NavbarSiteView */ - 'click a[data-action="quick-create"]': function (e) { - e.preventDefault(); - - const scope = $(e.currentTarget).data('name'); - - this.quickCreate(scope); - }, - /** @this NavbarSiteView */ 'click a.minimizer': function () { this.switchMinimizer(); }, @@ -100,10 +92,9 @@ class NavbarSiteView extends View { tabDefsList2: this.tabDefsList.filter(item => item.isInMore), title: this.options.title, menuDataList: this.getMenuDataList(), - quickCreateList: this.quickCreateList, - enableQuickCreate: this.quickCreateList.length > 0, userId: this.getUser().id, logoSrc: this.getLogoSrc(), + itemDataList: this.getItemDataList(), }; } @@ -360,10 +351,6 @@ class NavbarSiteView extends View { return tabList; } - getQuickCreateList() { - return this.getConfig().get('quickCreateList') || []; - } - setup() { this.getRouter().on('routed', (e) => { if (e.controller) { @@ -375,30 +362,29 @@ class NavbarSiteView extends View { this.selectTab(false); }); + /** @type {string[]} */ + this.itemList = this.getMetadata().get(['app', 'clientNavbar', 'itemList']) || []; + this.createView('notificationsBadge', 'views/notification/badge', { selector: '.notifications-badge-container', }); const setup = () => { - this.setupQuickCreateList(); this.setupTabDefsList(); + + return Promise + .all(this.itemList.map(item => this.createItemView(item))); + }; + + const update = () => { + setup().then(() => this.reRender()); }; this.setupGlobalSearch(); - setup(); - this.listenTo(this.getHelper().settings, 'sync', () => { - setup(); - - this.reRender(); - }); - - this.listenTo(this.getHelper().language, 'sync', () => { - setup(); - - this.reRender(); - }); + this.listenTo(this.getHelper().settings, 'sync', () => update()); + this.listenTo(this.getHelper().language, 'sync', () => update()); this.once('remove', () => { $(window).off('resize.navbar'); @@ -409,24 +395,61 @@ class NavbarSiteView extends View { }); } - setupQuickCreateList() { - const scopes = this.getMetadata().get('scopes') || {}; + getItemDataList() { + const defsMap = {}; - this.quickCreateList = this.getQuickCreateList().filter(scope =>{ - if (!scopes[scope]) { - return false; - } - - if ((scopes[scope] || {}).disabled) { - return; - } - - if ((scopes[scope] || {}).acl) { - return this.getAcl().check(scope, 'create'); - } - - return true; + this.itemList.forEach(name => { + defsMap[name] = this.getItemDefs(name); }); + + return this.itemList + .filter(name => { + const defs = defsMap[name]; + + if (!defs) { + return false; + } + + const view = this.getView(name + 'Item'); + + if ('isAvailable' in view) { + return view.isAvailable(); + } + + return true; + }) + .map(name => { + return { + key: name + 'Item', + name: name, + class: defsMap[name].class || '', + }; + }); + } + + /** + * + * @param {string} name + * @return {{view: string, class: string}} + */ + getItemDefs(name) { + return this.getMetadata().get(['app', 'clientNavbar', 'itemDefs', name]); + } + + /** + * @param {string} name + * @return {Promise} + */ + createItemView(name) { + const defs = this.getItemDefs(name) + + if (!defs || !defs.view) { + return Promise.resolve(); + } + + const key = name + 'Item'; + + return this.createView(key, defs.view, {selector: `[data-item="${name}"]`}); } filterTabItem(scope) { @@ -1200,19 +1223,6 @@ class NavbarSiteView extends View { return list; } - quickCreate(scope) { - Espo.Ui.notify(' ... '); - - const type = this.getMetadata().get(['clientDefs', scope, 'quickCreateModalType']) || 'edit'; - const viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', type]) || 'views/modals/edit'; - - this.createView('quickCreate', viewName , {scope: scope}, (view) => { - view.once('after:render', () => Espo.Ui.notify(false)); - - view.render(); - }); - } - // noinspection JSUnusedGlobalSymbols actionLogout() { this.getRouter().logout(); diff --git a/client/src/views/site/navbar/quick-create.js b/client/src/views/site/navbar/quick-create.js new file mode 100644 index 0000000000..a55ad72459 --- /dev/null +++ b/client/src/views/site/navbar/quick-create.js @@ -0,0 +1,105 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2023 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. + ************************************************************************/ + +import View from 'view'; + +class QuickCreateNavbarView extends View { + + templateContent = ` + + + ` + + data() { + return { + list: this.list, + }; + } + + setup() { + this.addActionHandler('quickCreate', (e, element) => { + e.preventDefault(); + + this.processCreate(element.dataset.name); + }); + + const scopes = this.getMetadata().get('scopes') || {}; + + /** @type {string[]} */ + const list = this.getConfig().get('quickCreateList') || []; + + this.list = list.filter(scope => { + if (!scopes[scope]) { + return false; + } + + if ((scopes[scope] || {}).disabled) { + return; + } + + if ((scopes[scope] || {}).acl) { + return this.getAcl().check(scope, 'create'); + } + + return true; + }); + } + + isAvailable() { + return this.list.length > 0; + } + + processCreate(scope) { + Espo.Ui.notify(' ... '); + + const type = this.getMetadata().get(['clientDefs', scope, 'quickCreateModalType']) || 'edit'; + const viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', type]) || 'views/modals/edit'; + + this.createView('dialog', viewName , {scope: scope}) + .then(view => view.render()) + .then(() => Espo.Ui.notify(false)); + } +} + +export default QuickCreateNavbarView; diff --git a/schema/metadata/app/clientNavbar.json b/schema/metadata/app/clientNavbar.json new file mode 100644 index 0000000000..5d11fff53d --- /dev/null +++ b/schema/metadata/app/clientNavbar.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.espocrm.com/schema/metadata/app/client.json", + "title": "app/navbar", + "description": "Navbar definitions.", + "type": "object", + "properties": { + "itemList": { + "type": "array", + "description": "Navbar items.", + "items": { + "type": "string", + "anyOf": [ + {"type": "string"}, + {"const": "__APPEND__"} + ] + } + }, + "itemDefs": { + "description": "Navbar item definitions", + "type": "object", + "additionalProperties": { + "description": "An item.", + "type": "object", + "properties": { + "view": { + "type": "string", + "description": "A view." + }, + "class": { + "type": "string", + "description": "A CSS class." + } + } + } + } + } +}