@eclipse-scout/core
Version:
Eclipse Scout runtime
186 lines (166 loc) • 6.58 kB
text/typescript
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {AbstractLayout, ContextMenuPopup, Dimension, graphics, Menu, PrefSizeOptions, scout, SimpleTabArea, styles, widgets} from '../index';
import $ from 'jquery';
export class SimpleTabAreaLayout extends AbstractLayout {
tabArea: SimpleTabArea;
tabWidth: number;
tabMinWidth: number;
overflowTabItemWidth: number;
protected _$overflowTab: JQuery;
protected _overflowTabsIndizes: number[];
constructor(tabArea: SimpleTabArea) {
super();
this.tabArea = tabArea;
this._$overflowTab = null;
this._overflowTabsIndizes = [];
this.tabWidth = null;
this.tabMinWidth = null;
this.overflowTabItemWidth = null;
}
override layout($container: JQuery) {
let htmlContainer = this.tabArea.htmlComp,
containerSize = htmlContainer.size({
exact: true
}),
$tabs = htmlContainer.$comp.children('.simple-tab'),
numTabs = this.tabArea.getTabs().length,
smallPrefSize = this.smallPrefSize();
containerSize = containerSize.subtract(htmlContainer.insets());
this._initSizes();
// Reset tabs
if (this._$overflowTab) {
this._$overflowTab.remove();
}
$tabs.setVisible(true);
this._overflowTabsIndizes = [];
widgets.updateFirstLastMarker(this.tabArea.getTabs());
// All tabs fit in container -> no overflow menu necessary
if (smallPrefSize.width <= containerSize.width) {
$container.removeClass('overflown');
return;
}
// Not all tabs fit in container -> put tabs into overflow menu
$container.addClass('overflown');
containerSize.width -= this.overflowTabItemWidth;
// check how many tabs fit into remaining containerSize.width
let numVisibleTabs = Math.floor(containerSize.width / this.tabMinWidth);
let numOverflowTabs = numTabs - numVisibleTabs;
let selectedIndex = 0;
$tabs.each((i, tab) => {
if ($(tab).hasClass('selected')) {
selectedIndex = i;
}
});
// determine visible range
let rightEnd;
let leftEnd = selectedIndex - Math.floor(numVisibleTabs / 2);
if (leftEnd < 0) {
leftEnd = 0;
rightEnd = numVisibleTabs - 1;
} else {
rightEnd = leftEnd + numVisibleTabs - 1;
if (rightEnd > numTabs - 1) {
rightEnd = numTabs - 1;
leftEnd = rightEnd - numVisibleTabs + 1;
}
}
this._$overflowTab = htmlContainer.$comp
.appendDiv('simple-overflow-tab-item')
.on('mousedown', this._onOverflowTabItemMouseDown.bind(this));
this._$overflowTab.appendDiv('num-tabs').text(numOverflowTabs);
$tabs.each((i, tab) => {
if (i < leftEnd || i > rightEnd) {
$(tab).setVisible(false);
this._overflowTabsIndizes.push(i);
}
});
widgets.updateFirstLastMarker(this.tabArea.getVisibleTabs());
}
smallPrefSize(options: PrefSizeOptions & { minTabWidth?: number } = {}): Dimension {
this._initSizes();
options = $.extend({minTabWidth: this.tabMinWidth}, options);
return this.preferredLayoutSize(this.tabArea.$container, options);
}
override preferredLayoutSize($container: JQuery, options?: PrefSizeOptions & { minTabWidth?: number }): Dimension {
this._initSizes();
let minTabWidth = scout.nvl(options.minTabWidth, 0) || scout.nvl(this.tabWidth, 0);
let numTabs = this.tabArea.getTabs().length;
let minWidth = numTabs * minTabWidth;
options = $.extend({useCssSize: true}, options);
let prefSize = graphics.prefSize(this.tabArea.$container, options);
if (options.widthHint && this.tabArea.displayStyle === SimpleTabArea.DisplayStyle.SPREAD_EVEN) {
minWidth = Math.max(options.widthHint, minWidth);
}
return new Dimension(minWidth, prefSize.height);
}
/**
* Reads the default sizes from CSS -> the tabs need to specify a width and a min-width.
* The layout expects all tabs to have the same width.
*/
protected _initSizes() {
if (this.tabWidth != null && this.tabMinWidth != null && this.overflowTabItemWidth != null) {
return;
}
let $tab = this.tabArea.$container.children('.simple-tab').eq(0);
if ($tab.length === 0) {
return;
}
$tab = $tab.clone().addClass('selected'); // Non selected items have a margin, selected ones don't -> we need to get the width incl. margin
let tabAreaClasses = this.tabArea.$container.attr('class');
let tabItemClasses = $tab.attr('class');
if (this.tabWidth === null) {
this.tabWidth = styles.getSize([tabAreaClasses, tabItemClasses], 'width', 'width', 0);
}
if (this.tabMinWidth === null) {
this.tabMinWidth = styles.getSize([tabAreaClasses, tabItemClasses], 'min-width', 'minWidth');
}
if (this.overflowTabItemWidth === null) {
this.overflowTabItemWidth = styles.getSize([tabAreaClasses, 'simple-overflow-tab-item'], 'min-width', 'minWidth');
this.overflowTabItemWidth += styles.getSize([tabAreaClasses, 'simple-overflow-tab-item'], 'margin-left', 'marginLeft');
this.overflowTabItemWidth += styles.getSize([tabAreaClasses, 'simple-overflow-tab-item'], 'margin-right', 'marginRight');
}
}
protected _onOverflowTabItemMouseDown(event: JQuery.MouseDownEvent) {
let tabArea = this.tabArea;
let overflowMenus = [];
let $overflowTabItem = $(event.currentTarget);
if ($overflowTabItem.data('popup')) {
$overflowTabItem.data('popup').close();
return;
}
this._overflowTabsIndizes.forEach(i => {
let tab = this.tabArea.getTabs()[i];
let menu = scout.create(Menu, {
parent: this.tabArea,
text: tab.getMenuText()
});
menu.on('action', function() {
$.log.isDebugEnabled() && $.log.debug('(SimpleTabAreaLayout#_onMouseDownOverflow) tab=' + this);
tabArea.selectTab(this);
}.bind(tab));
overflowMenus.push(menu);
});
let popup = scout.create(ContextMenuPopup, {
parent: this.tabArea,
menuItems: overflowMenus,
cloneMenuItems: false,
$anchor: $overflowTabItem,
closeOnAnchorMouseDown: false
});
$overflowTabItem.addClass('selected');
$overflowTabItem.data('popup', popup);
popup.one('remove', () => {
$overflowTabItem.removeClass('selected');
$overflowTabItem.data('popup', null);
});
popup.open();
}
}