tab groups

This commit is contained in:
Yuri Kuznetsov
2020-11-25 13:47:48 +02:00
parent 3d2857eec2
commit 4272376278
15 changed files with 1083 additions and 115 deletions
@@ -235,6 +235,7 @@
"maintenanceMode": "Only administrators will have access to the system."
},
"labels": {
"Group Tab": "Group Tab",
"System": "System",
"Locale": "Locale",
"Search": "Search",
+102 -6
View File
@@ -11,8 +11,18 @@
<ul class="nav navbar-nav tabs">
{{#each tabDefsList}}
{{#unless isInMore}}
<li data-name="{{name}}" class="not-in-more tab">
<a href="{{link}}" class="{{aClassName}}"{{#if color}} style="border-color: {{color}}"{{/if}}>
<li data-name="{{name}}" class="not-in-more tab{{#if isGroup}} tab-group dropdown{{/if}}">
<a
href="{{link}}"
class="{{aClassName}}"
{{#if color}}
style="border-color: {{color}}"
{{/if}}
{{#if isGroup}}
id="nav-tab-group-{{name}}"
data-toggle="dropdown"
{{/if}}
>
<span class="short-label"{{#if label}} title="{{label}}"{{/if}}{{#if color}} style="color: {{color}}"{{/if}}>
{{#if iconClass}}
<span class="{{iconClass}}"></span>
@@ -27,17 +37,68 @@
<span class="full-label">{{label}}</span>
{{/if}}
{{#if html}}{{{html}}}{{/if}}
{{#if isGroup}}
<span class="fas fa-caret-right group-caret"><span>
{{/if}}
</a>
{{#if isGroup}}
<ul class="dropdown-menu" role="menu" aria-labelledby="nav-tab-group-{{name}}">
{{#each itemList}}
<li data-name="{{name}}" class="in-group tab">
<a
href="{{link}}"
class="{{aClassName}}"
{{#if color}}
style="border-color: {{color}}"
{{/if}}
{{#if isGroup}}
id="nav-tab-group-{{name}}"
data-toggle="dropdown"
{{/if}}
>
<span class="short-label"{{#if color}} style="color: {{color}}"{{/if}}>
{{#if iconClass}}
<span class="{{iconClass}}"></span>
{{else}}
{{#if colorIconClass}}
<span class="{{colorIconClass}}" style="color: {{color}}"></span>
{{/if}}
<span class="short-label-text">&nbsp;</span>
{{/if}}
</span>
<span class="full-label">{{label}}</span>
</a>
</li>
{{/each}}
</ul>
{{/if}}
</li>
{{/unless}}
{{/each}}
<li class="dropdown more">
<a id="nav-more-tabs-dropdown" class="dropdown-toggle" data-toggle="dropdown" href="#"><span class="fas fa-ellipsis-h more-icon"></span></a>
<ul class="dropdown-menu" role="menu" aria-labelledby="nav-more-tabs-dropdown">
<a
id="nav-more-tabs-dropdown"
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
><span class="fas fa-ellipsis-h more-icon"></span></a>
<ul class="dropdown-menu more-dropdown-menu" role="menu" aria-labelledby="nav-more-tabs-dropdown">
{{#each tabDefsList}}
{{#if isInMore}}
<li data-name="{{name}}" class="in-more tab{{#if className}} {{className}}{{/if}}">
<a href="{{link}}" class="{{aClassName}}"{{#if color}} style="border-color: {{color}}"{{/if}}>
<li
data-name="{{name}}"
class="in-more tab{{#if className}} {{className}}{{/if}}{{#if isGroup}} dropdown tab-group{{/if}}"
>
<a
href="{{link}}"
class="{{aClassName}}"
{{#if color}} style="border-color: {{color}}"{{/if}}
{{#if isGroup}}
id="nav-tab-group-{{name}}"
data-toggle="dropdown"
{{/if}}
>
<span class="short-label"{{#if color}} style="color: {{color}}"{{/if}}>
{{#if iconClass}}
<span class="{{iconClass}}"></span>
@@ -52,7 +113,42 @@
<span class="full-label">{{label}}</span>
{{/if}}
{{#if html}}{{{html}}}{{/if}}
{{#if isGroup}}
<span class="fas fa-caret-right group-caret"><span>
{{/if}}
</a>
{{#if isGroup}}
<ul class="dropdown-menu" role="menu" aria-labelledby="nav-tab-group-{{name}}">
{{#each itemList}}
<li data-name="{{name}}" class="in-group tab">
<a
href="{{link}}"
class="{{aClassName}}"
{{#if color}}
style="border-color: {{color}}"
{{/if}}
{{#if isGroup}}
id="nav-tab-group-{{name}}"
data-toggle="dropdown"
{{/if}}
>
<span class="short-label"{{#if color}} style="color: {{color}}"{{/if}}>
{{#if iconClass}}
<span class="{{iconClass}}"></span>
{{else}}
{{#if colorIconClass}}
<span class="{{colorIconClass}}" style="color: {{color}}"></span>
{{/if}}
<span class="short-label-text">&nbsp;</span>
{{/if}}
</span>
<span class="full-label">{{label}}</span>
</a>
</li>
{{/each}}
</ul>
{{/if}}
</li>
{{/if}}
{{/each}}
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/admin/entity-manager/fields/icon-class', 'views/fields/base', function (Dep) {
define('views/admin/entity-manager/fields/icon-class', 'views/fields/base', function (Dep) {
return Dep.extend({
@@ -44,15 +44,24 @@ Espo.define('views/admin/entity-manager/fields/icon-class', 'views/fields/base',
this.createView('dialog', 'views/admin/entity-manager/modals/select-icon', {}, function (view) {
view.render();
this.listenToOnce(view, 'select', function (value) {
if (value === '') {
value = null;
}
this.model.set(this.name, value);
view.close();
}, this);
});
},
fetch: function () {
return {};
}
var data = {};
data[this.name] = this.model.get(this.name);
return data;
},
});
});
+25 -18
View File
@@ -46,6 +46,8 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
validations: ['required', 'maxCount'],
addItemModalView: 'views/modals/array-field-add',
data: function () {
var itemHtmlList = [];
(this.selected || []).forEach(function (value) {
@@ -70,24 +72,8 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
this.removeValue(value);
},
'click [data-action="showAddModal"]': function () {
var options = [];
this.params.options.forEach(function (item) {
if (!~this.selected.indexOf(item)) {
options.push(item);
}
}, this);
this.createView('addModal', 'views/modals/array-field-add', {
options: options,
translatedOptions: this.translatedOptions
}, function (view) {
view.render();
this.listenToOnce(view, 'add', function (item) {
this.addValue(item);
view.close();
}.bind(this));
}.bind(this));
}
this.actionAddItem();
},
},
setup: function () {
@@ -590,5 +576,26 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
getSearchType: function () {
return this.getSearchParamsData().type || 'anyOf';
},
actionAddItem: function () {
var options = [];
this.params.options.forEach(function (item) {
if (!~this.selected.indexOf(item)) {
options.push(item);
}
}, this);
this.createView('addModal', this.addItemModalView, {
options: options,
translatedOptions: this.translatedOptions,
}, function (view) {
view.render();
this.listenToOnce(view, 'add', function (item) {
this.addValue(item);
view.close();
}.bind(this));
}.bind(this));
},
});
});
+2 -9
View File
@@ -41,7 +41,7 @@ define('views/modals/array-field-add', 'views/modal', function (Dep) {
data: function () {
return {
optionList: this.options.options,
translatedOptions: this.options.translatedOptions
translatedOptions: this.options.translatedOptions,
};
},
@@ -53,16 +53,9 @@ define('views/modals/array-field-add', 'views/modal', function (Dep) {
},
setup: function () {
this.header = this.translate('Add Item');
this.buttonList = [
{
name: 'cancel',
label: 'Cancel'
}
];
this.buttonList = [];
},
});
+12
View File
@@ -124,6 +124,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
panelSoftLockedTypeList: ['default', 'acl', 'delimiter', 'dynamicLogic'],
confirmLeaveDisabled: false,
events: {
'click .button-container .action': function (e) {
Espo.Utils.handleAction(this, e);
@@ -1085,11 +1087,21 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
setIsChanged: function () {
this.isChanged = true;
if (this.confirmLeaveDisabled) {
return;
}
this.setConfirmLeaveOut(true);
},
setIsNotChanged: function () {
this.isChanged = false;
if (this.confirmLeaveDisabled) {
return;
}
this.setConfirmLeaveOut(false);
},
@@ -40,5 +40,7 @@ define('views/record/edit-for-modal', 'views/record/edit', function (Dep) {
accessControlDisabled: true,
confirmLeaveDisabled: true,
});
});
@@ -0,0 +1,38 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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('views/settings/fields/group-tab-list', 'views/settings/fields/tab-list', function (Dep) {
return Dep.extend({
noGroups: true,
noDelimiters: true,
});
});
+205 -8
View File
@@ -30,17 +30,59 @@ define('views/settings/fields/tab-list', 'views/fields/array', function (Dep) {
return Dep.extend({
addItemModalView: 'views/settings/modals/tab-list-field-add',
noGroups: false,
noDelimiters: false,
setup: function () {
Dep.prototype.setup.call(this);
this.selected.forEach(function (item) {
if (item && typeof item === 'object') {
if (!item.id) {
item.id = this.generateItemId();
}
}
}, this);
this.events['click [data-action="editGroup"]'] = function (e) {
var id = $(e.currentTarget).parent().data('value').toString();
this.editGroup(id);
}.bind(this);
},
generateItemId: function () {
return Math.floor(Math.random() * 1000000 + 1).toString();
},
setupOptions: function () {
this.params.options = Object.keys(this.getMetadata().get('scopes'))
.filter(
function (scope) {
if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
return false;
}
this.params.options = Object.keys(this.getMetadata().get('scopes')).filter(function (scope) {
if (this.getMetadata().get('scopes.' + scope + '.disabled')) return;
return this.getMetadata().get('scopes.' + scope + '.tab');
}, this).sort(function (v1, v2) {
return this.translate(v1, 'scopeNamesPlural').localeCompare(this.translate(v2, 'scopeNamesPlural'));
}.bind(this));
if (!this.getAcl().checkScope(scope)) {
return false;
}
this.params.options.push('_delimiter_');
this.params.options.push('_delimiter-ext_');
return this.getMetadata().get('scopes.' + scope + '.tab');
},
this
).sort(
function (v1, v2) {
return this.translate(v1, 'scopeNamesPlural').localeCompare(this.translate(v2, 'scopeNamesPlural'));
}.bind(this)
);
if (!this.noDelimiters) {
this.params.options.push('_delimiter_');
this.params.options.push('_delimiter-ext_');
}
this.translatedOptions = {};
@@ -52,5 +94,160 @@ define('views/settings/fields/tab-list', 'views/fields/array', function (Dep) {
this.translatedOptions['_delimiter-ext_'] = '. . .';
},
addValue: function (value) {
if (value && typeof value === 'object') {
if (!value.id) {
value.id = this.generateItemId();
}
var html = this.getItemHtml(value);
this.$list.append(html);
this.selected.push(value);
this.trigger('change');
return;
}
Dep.prototype.addValue.call(this, value);
},
removeValue: function (value) {
var index = this.getGroupIndexById(value);
if (~index) {
this.$list.children('[data-value="' + value + '"]').remove();
this.selected.splice(index, 1);
this.trigger('change');
return;
}
Dep.prototype.removeValue.call(this, value);
},
getItemHtml: function (value) {
if (value && typeof value === 'object') {
return this.getGroupItemHtml(value);
}
return Dep.prototype.getItemHtml.call(this, value);
},
getGroupItemHtml: function (item) {
var label = this.escapeValue(item.text || '');
var html = '<div class="list-group-item" data-value="' + item.id + '" style="cursor: default;">' +
'<a href="javascript:" class="" data-value="' + item.id + '" ' +
'data-action="editGroup" style="margin-right: 7px;">' +
'<span class="fas fa-pencil-alt fa-sm"></span>' +
'</a>' +
label +
'&nbsp;' +
'<a href="javascript:" class="pull-right" data-value="' + item.id + '" data-action="removeValue">' +
'<span class="fas fa-times"></span>' +
'</a>' +
'</div>';
return html;
},
fetchFromDom: function () {
var selected = [];
this.$el.find('.list-group .list-group-item').each(function (i, el) {
var value = $(el).data('value').toString();
var groupItem = this.getGroupValueById(value);
if (groupItem) {
selected.push(groupItem);
return;
}
selected.push(value);
}.bind(this));
this.selected = selected;
},
getGroupIndexById: function (id) {
for (var i = 0; i < this.selected.length; i++) {
var item = this.selected[i];
if (item && typeof item === 'object') {
if (item.id === id) {
return i;
}
}
}
return -1;
},
getGroupValueById: function (id) {
for (var item of this.selected) {
if (item && typeof item === 'object') {
if (item.id === id) {
return item;
}
}
}
return null;
},
editGroup: function (id) {
var item = Espo.Utils.cloneDeep(
this.getGroupValueById(id) || {}
);
var index = this.getGroupIndexById(id);
var tabList = Espo.Utils.cloneDeep(this.selected);
this.createView('dialog', 'views/settings/modals/edit-tab-group', {
itemData: item,
}, function (view) {
view.render();
this.listenToOnce(view, 'apply', function (itemData) {
for (var a in itemData) {
tabList[index][a] = itemData[a];
}
this.model.set(this.name, tabList);
view.close();
}, this);
});
},
actionAddItem: function () {
var options = [];
this.params.options.forEach(function (item) {
if (!~this.selected.indexOf(item)) {
options.push(item);
}
}, this);
this.createView('addModal', this.addItemModalView, {
options: options,
translatedOptions: this.translatedOptions,
noGroups: this.noGroups,
}, function (view) {
view.render();
this.listenToOnce(view, 'add', function (item) {
this.addValue(item);
view.close();
}.bind(this));
}.bind(this));
},
});
});
@@ -0,0 +1,125 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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('views/settings/modals/edit-tab-group', ['views/modal', 'model'], function (Dep, Model) {
return Dep.extend({
templateContent: '<div class="record">{{{record}}}</div>',
setup: function () {
Dep.prototype.setup.call(this);
this.headerHtml = this.translate('Group Tab', 'labels', 'Settings');
this.buttonList.push({
name: 'apply',
label: 'Apply',
style: 'danger',
});
this.buttonList.push({
name: 'cancel',
label: 'Cancel',
});
var detailLayout = [
{
rows: [
[
{
name: 'text',
labelText: this.translate('label', 'fields', 'Admin'),
},
{
name: 'iconClass',
labelText: this.translate('iconClass', 'fields', 'EntityManager'),
},
{
name: 'color',
labelText: this.translate('color', 'fields', 'EntityManager'),
},
],
[
{
name: 'itemList',
labelText: this.translate('tabList', 'fields', 'Settings'),
},
false
]
]
}
];
var model = this.model = new Model();
model.name = 'GroupTab';
model.set(this.options.itemData);
model.setDefs({
fields: {
text: {
type: 'varchar',
},
iconClass: {
type: 'base',
view: 'views/admin/entity-manager/fields/icon-class',
},
color: {
type: 'base',
view: 'views/fields/colorpicker',
},
itemList: {
type: 'array',
view: 'views/settings/fields/group-tab-list',
},
},
});
this.createView('record', 'views/record/edit-for-modal', {
detailLayout: detailLayout,
model: model,
el: this.getSelector() + ' .record',
});
},
actionApply: function () {
var recordView = this.getView('record');
if (recordView.validate()) {
return;
}
var data = recordView.fetch();
this.trigger('apply', data);
},
});
});
@@ -0,0 +1,54 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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('views/settings/modals/tab-list-field-add', 'views/modals/array-field-add', function (Dep) {
return Dep.extend({
setup: function () {
Dep.prototype.setup.call(this);
if (!this.options.noGroups) {
this.buttonList.push({
name: 'addGroup',
html: this.translate('Group Tab', 'labels', 'Settings'),
});
}
},
actionAddGroup: function () {
this.trigger('add', {
type: 'group',
text: this.translate('Group Tab', 'labels', 'Settings'),
iconClass: null,
color: null,
});
},
});
});
+390 -65
View File
@@ -91,6 +91,157 @@ define('views/site/navbar', 'view', function (Dep) {
e.stopPropagation();
this.showMoreTabs();
},
'click .not-in-more > .nav-link-group': function (e) {
this.handleGroupDropdownClick(e);
},
'click .in-more .nav-link-group': function (e) {
this.handleGroupDropdownClick(e);
},
},
handleGroupDropdownClick: function (e) {
var $target = $(e.currentTarget).parent();
if ($target.parent().hasClass('more-dropdown-menu')) {
e.stopPropagation();
if ($target.hasClass('open')) {
$target.removeClass('open');
return;
}
this.handleGroupDropdownInMoreOpen($target);
return;
}
if ($target.hasClass('open')) {
return;
}
this.handleGroupDropdownOpen($target);
},
handleGroupMenuPosition: function ($menu, $target) {
if (this.navbarAdjustmentHandler && this.navbarAdjustmentHandler.handleGroupMenuPosition()) {
this.handleGroupMenuPosition($menu, $target);
return;
}
var rectItem = $target.get(0).getBoundingClientRect();
var windowHeight = window.innerHeight;
var isVertical = this.getThemeManager().getParam('navbarIsVertical');
if (
!isVertical &&
!$target.parent().hasClass('more-dropdown-menu')
) {
var maxHeight = windowHeight - rectItem.bottom;
this.handleGroupMenuScrolling($menu, $target, maxHeight);
return;
}
var itemCount = $menu.children().length;
if (isVertical) {
var tabHeight = this.$tabs.find('> .tab').height();
} else {
var tabHeight = this.$tabs.find('.tab-group > ul > li:visible').height();
}
var menuHeight = tabHeight * itemCount;
var top = rectItem.top - 1;
if (top + menuHeight > windowHeight) {
top = windowHeight - menuHeight - 2;
if (top < 0) {
top = 0;
}
}
$menu.css({
top: top + 'px',
});
var maxHeight = windowHeight - top;
this.handleGroupMenuScrolling($menu, $target, maxHeight);
},
handleGroupMenuScrolling: function ($menu, $target, maxHeight) {
$menu.css({
maxHeight: maxHeight + 'px',
});
var $window = $(window);
$window.off('scroll.navbar-tab-group');
$window.on('scroll.navbar-tab-group', function () {
if (!$menu.get(0) || !$target.get(0)) {
return;
}
if (!$target.hasClass('open')) {
return;
}
$menu.scrollTop($window.scrollTop());
});
},
handleGroupDropdownOpen: function ($target) {
var $menu = $target.find('.dropdown-menu');
this.handleGroupMenuPosition($menu, $target);
setTimeout(function () {
this.adjustBodyMinHeight();
}.bind(this), 50);
$target.off('hidden.bs.dropdown');
$target.on('hidden.bs.dropdown', function () {
this.adjustBodyMinHeight();
}.bind(this));
},
handleGroupDropdownInMoreOpen: function ($target) {
this.$el.find('.tab-group.tab.dropdown').removeClass('open');
var $parentDropdown = this.$el.find('.more-dropdown-menu');
$target.addClass('open');
var $menu = $target.find('.dropdown-menu');
var rectDropdown = $parentDropdown.get(0).getBoundingClientRect();
var left = rectDropdown.right;
$menu.css({
left: left + 'px',
});
this.handleGroupMenuPosition($menu, $target);
this.adjustBodyMinHeight();
if (!this.getThemeManager().getParam('isVertical')) {
if (left + $menu.width() > window.innerWidth) {
$menu.css({
left: rectDropdown.left - $menu.width() - 2,
});
}
}
},
isCollapsableVisible: function () {
@@ -183,12 +334,16 @@ define('views/site/navbar', 'view', function (Dep) {
},
getTabList: function () {
var tabList = this.getPreferences().get('useCustomTabList') ? this.getPreferences().get('tabList') : this.getConfig().get('tabList');
tabList = Espo.Utils.clone(tabList || []);
var tabList = this.getPreferences().get('useCustomTabList') ?
this.getPreferences().get('tabList') :
this.getConfig().get('tabList');
tabList = Espo.Utils.cloneDeep(tabList || []);
if (this.getThemeManager().getParam('navbarIsVertical')) {
tabList.unshift('Home');
}
return tabList;
},
@@ -209,23 +364,33 @@ define('views/site/navbar', 'view', function (Dep) {
var scopes = this.getMetadata().get('scopes') || {};
this.tabList = tabList.filter(function (scope) {
if (~['Home', '_delimiter_', '_delimiter-ext_'].indexOf(scope)) return true;
if (!scopes[scope]) return false;
this.tabList = tabList.filter(
function (item) {
if (!item) {
return false;
}
var defs = scopes[scope] || {};
if (typeof item === 'object') {
item.itemList = item.itemList || [];
if (defs.disabled) return;
item.itemList = item.itemList.filter(
function (item) {
return this.filterTabItem(item);
},
this
);
if (defs.acl) {
return this.getAcl().check(scope);
}
if (defs.tabAclPermission) {
var level = this.getAcl().get(defs.tabAclPermission);
return level && level !== 'no';
}
return true;
}, this);
if (!item.itemList.length) {
return false;
}
return true;
}
return this.filterTabItem(item);
},
this
);
this.quickCreateList = this.getQuickCreateList().filter(function (scope) {
if (!scopes[scope]) return false;
@@ -247,9 +412,39 @@ define('views/site/navbar', 'view', function (Dep) {
this.once('remove', function () {
$(window).off('resize.navbar');
$(window).off('scroll.navbar');
$(window).off('scroll.navbar-tab-group');
});
},
filterTabItem: function (scope) {
if (~['Home', '_delimiter_', '_delimiter-ext_'].indexOf(scope)) {
return true;
}
var scopes = this.getMetadata().get('scopes') || {};
if (!scopes[scope]) {
return false;
}
var defs = scopes[scope] || {};
if (defs.disabled) {
return;
}
if (defs.acl) {
return this.getAcl().check(scope);
}
if (defs.tabAclPermission) {
var level = this.getAcl().get(defs.tabAclPermission);
return level && level !== 'no';
}
return true;
},
setupGlobalSearch: function () {
this.globalSearchAvailable = false;
(this.getConfig().get('globalSearchEntityList') || []).forEach(function (scope) {
@@ -491,23 +686,73 @@ define('views/site/navbar', 'view', function (Dep) {
minHeight = Math.max(minHeight, moreHeight);
var tabHeight = this.$tabs.find('> .tab').height();
this.tabList.forEach(function (item, i) {
if (typeof item !== 'object') {
return;
}
var $li = this.$el.find('li.tab[data-name="group-'+i+'"]');
if (!$li.hasClass('open')) {
return;
}
var tabCount = (item.itemList || []).length;
var menuHeight = tabHeight * tabCount;
if (menuHeight > minHeight) {
minHeight = menuHeight;
}
}, this);
this.$body.css('minHeight', minHeight + 'px');
},
adjustBodyMinHeightHorizontal: function () {
var height = this.getNavbarHeight();
var minHeight = this.getNavbarHeight();
this.$more.find('> li').each(function (i, el) {
var $el = $(el);
if (!this.isMoreTabsShown) {
if ($el.hasClass('after-show-more')) return;
if ($el.hasClass('after-show-more')) {
return;
}
} else {
if ($el.hasClass('show-more')) return;
if ($el.hasClass('show-more')) {
return;
}
}
height += $el.height();
minHeight += $el.height();
}.bind(this));
this.$body.css('minHeight', height + 'px');
var tabHeight = this.$tabs.find('.tab-group > ul > li:visible').height();
this.tabList.forEach(function (item, i) {
if (typeof item !== 'object') {
return;
}
var $li = this.$el.find('li.tab[data-name="group-'+i+'"]');
if (!$li.hasClass('open')) {
return;
}
var tabCount = (item.itemList || []).length;
var menuHeight = tabHeight * tabCount;
if (menuHeight > minHeight) {
minHeight = menuHeight;
}
}, this);
this.$body.css('minHeight', minHeight + 'px');
},
afterRender: function () {
@@ -551,6 +796,9 @@ define('views/site/navbar', 'view', function (Dep) {
if (handlerClassName) {
require(handlerClassName, function (Handler) {
var handler = new Handler(this);
this.navbarAdjustmentHandler = handler;
handler.process();
}.bind(this));
}
@@ -606,6 +854,7 @@ define('views/site/navbar', 'view', function (Dep) {
setupTabDefsList: function () {
var tabDefsList = [];
var moreIsMet = false;
var isHidden = false;
@@ -614,16 +863,32 @@ define('views/site/navbar', 'view', function (Dep) {
this.getPreferences().get('tabColorsDisabled') ||
this.getConfig().get('scopeColorsDisabled') ||
this.getConfig().get('tabColorsDisabled');
var tabIconsDisabled = this.getConfig().get('tabIconsDisabled');
var params = {
colorsDisabled: colorsDisabled,
tabIconsDisabled: tabIconsDisabled,
};
var vars = {
moreIsMet: false,
isHidden: false,
};
this.tabList.forEach(function (tab, i) {
if (tab === '_delimiter_' || tab === '_delimiter-ext_') {
if (!moreIsMet) {
moreIsMet = true;
if (!vars.moreIsMet) {
vars.moreIsMet = true;
return;
} else {
if (i == this.tabList.length - 1) return;
isHidden = true;
if (i == this.tabList.length - 1) {
return;
}
vars.isHidden = true;
tabDefsList.push({
name: 'show-more',
link: 'javascript:',
@@ -631,53 +896,104 @@ define('views/site/navbar', 'view', function (Dep) {
className: 'show-more',
html: '<span class="fas fa-ellipsis-h more-icon"></span>',
});
return;
}
}
var label;
var link;
if (tab == 'Home') {
label = this.getLanguage().translate(tab);
link = '#';
} else {
label = this.getLanguage().translate(tab, 'scopeNamesPlural');
link = '#' + tab;
}
var color = null;
if (!colorsDisabled) {
var color = this.getMetadata().get(['clientDefs', tab, 'color']);
}
var shortLabel = label.substr(0, 2);
var iconClass = null;
if (!tabIconsDisabled) {
iconClass = this.getMetadata().get(['clientDefs', tab, 'iconClass'])
}
var o = {
link: link,
label: label,
shortLabel: shortLabel,
name: tab,
isInMore: moreIsMet,
color: color,
iconClass: iconClass,
isAfterShowMore: isHidden,
aClassName: 'nav-link',
};
if (isHidden) o.className = 'after-show-more';
if (color && !iconClass) {
o.colorIconClass = 'color-icon fas fa-square-full';
}
tabDefsList.push(o);
tabDefsList.push(
this.prepareTabItemDefs(params, tab, i, vars)
);
}, this);
this.tabDefsList = tabDefsList;
},
prepareTabItemDefs: function (params, tab, i, vars) {
var label;
var link;
var iconClass = null;
var color = null;
var isGroup = false;
var name = tab;
var aClassName = 'nav-link';
if (tab == 'Home') {
label = this.getLanguage().translate(tab);
link = '#';
}
else if (typeof tab === 'object') {
isGroup = true;
label = tab.text;
color = tab.color;
iconClass = tab.iconClass;
name = 'group-' + i;
link = 'javascript:';
aClassName = 'nav-link-group';
if (label.indexOf('label@') === 0) {
label = this.translate(label.substr(6), 'tabs');
}
}
else {
label = this.getLanguage().translate(tab, 'scopeNamesPlural');
link = '#' + tab;
}
label = label || '';
var shortLabel = label.substr(0, 2);
if (!params.colorsDisabled && !isGroup) {
color = this.getMetadata().get(['clientDefs', tab, 'color']);
}
if (!params.tabIconsDisabled && !isGroup) {
iconClass = this.getMetadata().get(['clientDefs', tab, 'iconClass'])
}
var o = {
link: link,
label: label,
shortLabel: shortLabel,
name: name,
isInMore: vars.moreIsMet,
color: color,
iconClass: iconClass,
isAfterShowMore: vars.isHidden,
aClassName: aClassName,
isGroup: isGroup,
};
if (isGroup) {
o.itemList = tab.itemList.map(
function (tab, i) {
return this.prepareTabItemDefs(params, tab, i, vars);
},
this
);
}
if (vars.isHidden) {
o.className = 'after-show-more';
}
if (color && !iconClass) {
o.colorIconClass = 'color-icon fas fa-square-full';
}
return o;
},
getMenuDataList: function () {
var avatarHtml = this.getHelper().getAvatarHtml(this.getUser().id, 'small', 16, 'avatar-link');
if (avatarHtml) avatarHtml += ' ';
@@ -765,15 +1081,24 @@ define('views/site/navbar', 'view', function (Dep) {
},
showMoreTabs: function () {
this.$el.find('.tab-group.tab.dropdown').removeClass('open');
this.isMoreTabsShown = true;
this.$more.addClass('more-expanded');
this.adjustBodyMinHeight();
this.trigger('show-more-tabs');
},
hideMoreTabs: function () {
if (!this.isMoreTabsShown) return;
if (!this.isMoreTabsShown) {
return;
}
this.$more.removeClass('more-expanded');
this.adjustBodyMinHeight();
this.isMoreTabsShown = false;
+32 -4
View File
@@ -46,17 +46,40 @@
padding-right: 0;
}
.nav > li.more > .dropdown-menu {
.nav > li.more > .dropdown-menu > li > ul.dropdown-menu {
margin-top: 0;
}
.nav > li.more > .dropdown-menu,
.nav > li.more > .dropdown-menu > li.tab-group > ul.dropdown-menu,
.nav > li.tab-group > .dropdown-menu {
top: 0;
left: @navbar-width;
padding-top: 0;
padding-bottom: 0;
position: fixed;
border-left-width: 0;
border-top-width: 0;
overflow-y: hidden;
}
.nav > li.more > .dropdown-menu,
.nav > li.tab-group > .dropdown-menu {
border-left-width: 0;
}
.nav > li.more > .dropdown-menu {
border-top-width: 0;
}
.nav li.tab-group {
span.full-label {
padding-right: 8px;
}
.group-caret {
position: absolute;
}
}
ul.tabs {
width: @navbar-width;
margin-left: 0;
@@ -369,7 +392,8 @@ body {
}
#navbar {
.nav > li.more > ul.dropdown-menu {
.nav > li.more > ul.dropdown-menu,
.nav > li.tab-group > .dropdown-menu {
left: @navbar-minimized-width;
}
@@ -432,6 +456,10 @@ body {
left: -1px;
visibility: visible;
}
> li.tab-group .group-caret {
right: 5px;
}
}
.navbar-right {
+43
View File
@@ -36,6 +36,16 @@
top: -2px;
}
ul.tabs {
li > a > .group-caret {
position: absolute;
top: 13px;
right: 13px;
font-size: 12px;
opacity: 0.6;
}
}
@media screen and (min-width: @screen-sm-min) {
.tabs > .more {
li.show-more a {
@@ -131,6 +141,39 @@
}
}
}
> li > a > span.full-label,
li > a > span.full-label {
display: inline-block;
text-overflow: ellipsis;
max-width: calc(100%);
overflow: hidden;
white-space: nowrap;
}
}
li > a > .group-caret {
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}
li > a {
height: @navbar-height-xsmall;
}
li.tab-group {
background-color: @navbar-inverse-bg;
> a > .full-label {
padding-right: 8px;
}
}
li.tab-group.open > a {
background-color: @navbar-inverse-link-active-bg;
}
}
+40 -2
View File
@@ -49,11 +49,15 @@
display: inline;
}
> li.more {
> ul {
> li.more,
> li.tab-group {
> ul,
> ul > li > ul {
padding-top: 0;
padding-bottom: 0;
}
> ul > li > ul > li > a,
> ul > li > a {
padding-top: 9px;
padding-bottom: 9px;
@@ -75,6 +79,10 @@
display: none;
}
}
li.tab-group .group-caret {
position: absolute;
}
}
.navbar-right {
@@ -175,6 +183,36 @@
border-color: transparent;
padding-left: 15px - @navbar-color-border-width;
}
.nav > li.more > .dropdown-menu > li.tab-group > ul.dropdown-menu,
.nav > li.tab-group > .dropdown-menu {
overflow-y: hidden;
}
.nav > li.more > .dropdown-menu > li.tab-group > ul.dropdown-menu {
position: fixed;
}
.nav li.tab-group {
span.full-label {
padding-right: 8px;
}
}
ul.nav > li.tab-group > a {
> span.full-label {
padding-right: 10px;
}
> span.group-caret {
top: 17px;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}
}
}
}