@eclipse-scout/core
Version:
Eclipse Scout runtime
288 lines (248 loc) • 7.83 kB
text/typescript
/*
* Copyright (c) 2010, 2025 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 {
arrays, Event, EventHandler, HtmlComponent, InitModelOf, LayoutData, ObjectIdProvider, scout, SimpleTabArea, SimpleTabBoxController, SimpleTabBoxEventMap, SimpleTabBoxLayout, SimpleTabBoxModel, SimpleTabView, SimpleTabViewContentLayout,
Widget
} from '../index';
export class SimpleTabBox<TView extends SimpleTabView = SimpleTabView> extends Widget implements SimpleTabBoxModel<TView> {
declare model: SimpleTabBoxModel<TView>;
declare eventMap: SimpleTabBoxEventMap<TView>;
declare self: SimpleTabBox<any>;
tabArea: SimpleTabArea<TView>;
viewStack: TView[];
currentView: TView;
controller: SimpleTabBoxController<TView>;
layoutData: LayoutData;
viewContent: HtmlComponent;
$viewContent: JQuery;
$tabArea: JQuery;
protected _removeViewInProgress: number;
protected _viewDestroyedHandler: EventHandler<Event<TView>>;
constructor() {
super();
this._addWidgetProperties(['tabArea']);
this.tabArea = null;
this.viewStack = [];
this.currentView = null;
this.controller = null;
this._removeViewInProgress = 0;
}
protected override _init(model: InitModelOf<this>) {
super._init(model);
this.cssClass = model.cssClass;
if (!this.controller) {
// default controller
this.controller = scout.create(SimpleTabBoxController) as SimpleTabBoxController<TView>;
}
// link
this.controller.install(this, this.tabArea);
this.tabArea = this.controller.tabArea;
this._viewDestroyedHandler = this._onViewDestroyed.bind(this);
}
protected override _render() {
this.$container = this.$parent.appendDiv('simple-tab-box');
if (this.cssClass) {
this.$container.addClass(this.cssClass);
}
this.htmlComp = HtmlComponent.install(this.$container, this.session);
this.htmlComp.setLayout(new SimpleTabBoxLayout(this));
this.htmlComp.layoutData = this.layoutData;
// render content
this.$viewContent = this.$container.appendDiv('tab-content');
this.viewContent = HtmlComponent.install(this.$viewContent, this.session);
this.viewContent.setLayout(new SimpleTabViewContentLayout(this));
}
protected override _renderProperties() {
super._renderProperties();
this._renderTabArea();
this._renderView(this.currentView);
}
protected _renderTabArea() {
this.tabArea.render();
this.$tabArea = this.tabArea.$container;
this.$tabArea.insertBefore(this.$viewContent);
}
protected _renderView(view: TView) {
if (!view) {
return;
}
if (view.rendered) {
return;
}
view.render(this.$viewContent);
view.$container.addClass('view');
}
postRender() {
if (this.viewStack.length > 0 && !this.currentView) {
this.activateView(this.viewStack[this.viewStack.length - 1]);
}
}
activateView(view: TView) {
if (view === this.currentView) {
return;
}
if (this.currentView) {
this.currentView.detach();
this.trigger('viewDeactivate', {
view: this.currentView
});
this.currentView = null;
}
// ensure rendered
if (this.rendered) {
this._renderView(view);
}
if (view && !view.attached) {
view.attach();
}
this.currentView = view;
this.trigger('viewActivate', {
view: view
});
if (this.rendered) {
this.viewContent.invalidateLayoutTree();
}
}
override setLayoutData(layoutData: LayoutData) {
super.setLayoutData(layoutData);
this.layoutData = layoutData;
}
getLayoutData(): LayoutData {
return this.layoutData;
}
/**
* @param bringToTop whether the view should be placed on top of the view stack. the view tab will be selected. Default is true.
*/
addView(view: TView, bringToTop?: boolean) {
let activate = scout.nvl(bringToTop, true);
// add to view stack
let siblingView = this._addToViewStack(view, activate);
// track focus when a view gets removed ond re-rendered.
view.setTrackFocus(true);
view.setParent(this);
this.trigger('viewAdd', {
view: view,
siblingView: siblingView
});
if (activate) {
this.activateView(view);
}
}
/**
* @returns the view which is going to be the sibling to insert the new view tab after.
*/
protected _addToViewStack(view: TView, bringToTop: boolean): TView {
let sibling: TView;
let index = this.viewStack.indexOf(view);
if (index > -1) {
return this.viewStack[index - 1];
}
if (!SimpleTabBoxController.hasViewTab(view)) {
// first
this.viewStack.unshift(view);
this._addDestroyListener(view);
return sibling;
}
if (!this.currentView || !bringToTop) {
// end
sibling = this.viewStack[this.viewStack.length - 1];
this.viewStack.push(view);
this._addDestroyListener(view);
return sibling;
}
let currentIndex = this.viewStack.indexOf(this.currentView);
sibling = this.viewStack[currentIndex];
// it does not matter when index is -1 will be inserted at first position
this.viewStack.splice(currentIndex + 1, 0, view);
return sibling;
}
protected _addDestroyListener(view: TView) {
view.one('destroy', this._viewDestroyedHandler);
}
protected _removeDestroyListener(view: TView) {
view.off('destroy', this._viewDestroyedHandler);
}
protected _onViewDestroyed(event: Event<TView>) {
let view = event.source;
arrays.remove(this.viewStack, view);
if (this.currentView === view) {
if (this.rendered) {
view.remove();
}
this.currentView = null;
}
}
removeView(view: TView, showSiblingView?: boolean) {
if (!view) {
return;
}
// track focus when a view gets removed ond re-rendered.
view.setTrackFocus(false);
showSiblingView = scout.nvl(showSiblingView, true);
let index = this.viewStack.indexOf(view);
let viewToActivate;
// if current view is the view to remove reset current view
if (this.currentView === view) {
this.currentView = null;
} else {
// Don't change selected view if not the selected view was removed
showSiblingView = false;
}
if (index > -1) {
// activate previous
if (showSiblingView) {
if (index - 1 >= 0) {
viewToActivate = this.viewStack[index - 1];
} else if (index + 1 < this.viewStack.length) {
viewToActivate = this.viewStack[index + 1];
}
}
// remove
this.viewStack.splice(index, 1);
if (view.rendered) {
this._removeViewInProgress++;
view.remove();
this._removeViewInProgress--;
}
this.trigger('viewRemove', {
view: view
});
if (this._removeViewInProgress === 0) {
if (viewToActivate) {
this.activateView(viewToActivate);
}
if (this.rendered) {
this.viewContent.invalidateLayoutTree();
}
}
}
}
getController(): SimpleTabBoxController<TView> {
return this.controller;
}
viewCount(): number {
return this.viewStack.length;
}
hasViews(): boolean {
return this.viewStack.length > 0;
}
hasView(view: TView): boolean {
return this.viewStack.filter(v => v === view).length > 0;
}
getViews(displayViewId?: string): TView[] {
return this.viewStack.filter(view => {
if (!displayViewId) {
return true;
}
return displayViewId === view.displayViewId;
});
}
}
ObjectIdProvider.uuidPathSkipWidgets.add(SimpleTabBox);