@eclipse-scout/core
Version:
Eclipse Scout runtime
352 lines (311 loc) • 9.53 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 {
aria, arrays, ContextMenuPopup, EventHandler, FieldStatusEventMap, FieldStatusModel, FormField, FormFieldStatusPosition, HierarchyChangeEvent, HtmlComponent, Menu, PropertyChangeEvent, scout, Status, StatusOrModel, strings, Tooltip,
Widget
} from '../../index';
export class FieldStatus extends Widget implements FieldStatusModel {
declare model: FieldStatusModel;
declare eventMap: FieldStatusEventMap;
declare self: FieldStatus;
autoRemove: boolean;
status: Status;
position: FormFieldStatusPosition;
menus: Menu[];
tooltip: Tooltip;
contextMenu: ContextMenuPopup;
updating: boolean;
protected _parents: Widget[];
protected _parentPropertyChangeListener: EventHandler<PropertyChangeEvent<any, Widget>>;
protected _parentHierarchyChangeListener: EventHandler<HierarchyChangeEvent>;
constructor() {
super();
this.tooltip = null;
this.contextMenu = null;
this.status = null;
this.updating = false;
this.autoRemove = true;
this.position = FormField.StatusPosition.DEFAULT;
this._parents = [];
this._parentPropertyChangeListener = this._onParentPropertyChange.bind(this);
this._parentHierarchyChangeListener = this._onParentHierarchyChange.bind(this);
}
protected override _render() {
this.$container = this.$parent.appendSpan('status')
.on('mousedown', this._onStatusMouseDown.bind(this));
this.htmlComp = HtmlComponent.install(this.$container, this.session);
}
protected override _remove() {
super._remove();
if (this.tooltip) {
this.tooltip.destroy();
this.tooltip = null;
}
if (this.contextMenu) {
this.contextMenu.destroy();
this.contextMenu = null;
}
this._removeParentListeners();
}
protected override _renderProperties() {
super._renderProperties();
this._renderPosition();
}
update(status: StatusOrModel, menus: Menu | Menu[], autoRemove: boolean, showStatus?: boolean) {
this.updating = true;
this.setStatus(status);
this.setMenus(menus);
this.setAutoRemove(autoRemove);
this.updating = false;
this._updatePopup(showStatus);
}
clearStatus() {
this.setStatus(null);
}
setStatus(status: StatusOrModel) {
this.setProperty('status', status);
}
protected _setStatus(status: StatusOrModel) {
status = Status.ensure(status);
this._setProperty('status', status);
}
protected _renderStatus() {
if (!this.updating) {
this._updatePopup();
}
}
setPosition(position: FormFieldStatusPosition) {
this.setProperty('position', position);
}
protected _renderPosition() {
this.$container.toggleClass('top', this.position === FormField.StatusPosition.TOP);
this.invalidateLayoutTree();
}
protected override _renderVisible() {
super._renderVisible();
if (!this.visible) {
this.hidePopup();
}
}
setMenus(menus: Menu | Menu[]) {
this.setProperty('menus', arrays.ensure(menus));
}
protected _renderMenus() {
if (!this.updating) {
this._updatePopup();
}
}
setAutoRemove(autoRemove: boolean) {
this.setProperty('autoRemove', autoRemove);
}
protected _renderAutoRemove() {
if (!this.updating) {
this._updatePopup();
}
}
hideTooltip() {
if (this.tooltip) {
let event = this.trigger('hideTooltip');
if (!event.defaultPrevented) {
this.tooltip.destroy();
this._removeParentListeners();
}
}
}
protected _updatePopup(showStatus?: boolean) {
if (!this._requiresTooltip()) {
this.hideTooltip();
}
if (arrays.empty(this.menus)) {
this.hideContextMenu();
}
if (showStatus === true) {
this.showTooltip();
} else if (showStatus === false) {
this.hideTooltip();
}
}
protected _requiresTooltip(): boolean {
if (!this.status || !this.rendered) {
return false;
}
if (arrays.empty(this.menus) && !strings.hasText(this.status.message)) {
return false;
}
return true;
}
showTooltip() {
if (!this._requiresTooltip()) {
return;
}
let event = this.trigger('showTooltip');
if (event.defaultPrevented) {
return;
}
this._updateParentListeners();
this.hideContextMenu();
if (this.tooltip && this.tooltip.autoRemove !== this.autoRemove) {
// close
this.hideTooltip();
}
if (this.tooltip) {
// update existing tooltip
this.tooltip.setText(this.status.message);
this.tooltip.setSeverity(this.status.severity);
this.tooltip.setMenus(this.menus);
} else {
this.tooltip = scout.create(Tooltip, {
parent: this,
$anchor: this.$container,
text: this.status.message,
severity: this.status.severity,
autoRemove: this.autoRemove,
menus: this.menus
});
this.tooltip.render();
aria.role(this.tooltip.$content, 'alert');
this.$container.addClass('selected');
this.tooltip.one('destroy', () => {
this.tooltip = null;
if (this.$container) {
this.$container.removeClass('selected');
}
});
}
}
hideContextMenu() {
if (this.contextMenu) {
this.contextMenu.close();
}
}
showContextMenu() {
if (arrays.empty(this.menus)) {
// at least one menu item must be visible
return;
}
// close both contextMenu and status tooltip
this.hidePopup();
// Context menu must be removed immediately before it can be opened because cloneMenuItems is false
if (this.contextMenu && this.contextMenu.isRemovalPending()) {
this.contextMenu.removeImmediately();
}
this.contextMenu = scout.create(ContextMenuPopup, {
parent: this,
$anchor: this.$container,
menuItems: this.menus,
cloneMenuItems: false,
closeOnAnchorMouseDown: false
});
this.contextMenu.open();
this.$container.addClass('selected');
this.contextMenu.one('destroy', () => {
this.contextMenu = null;
if (this.$container) {
this.$container.removeClass('selected');
}
});
}
hidePopup() {
this.hideTooltip();
this.hideContextMenu();
}
togglePopup() {
if (this.status) {
// ensure context menu closed
this.hideContextMenu();
this.toggleTooltip();
return;
}
if (!arrays.empty(this.menus)) {
this.hideTooltip();
this.session.onRequestsDone(() => {
if (!this.rendered) { // check needed because function is called asynchronously
return;
}
this.toggleContextMenu();
});
} else {
// close all
this.hidePopup();
}
}
toggleTooltip() {
if (this.tooltip) {
this.hideTooltip();
} else {
this.showTooltip();
}
}
toggleContextMenu() {
if (this.contextMenu) {
this.hideContextMenu();
} else {
this.showContextMenu();
}
}
protected _onStatusMouseDown(event: JQuery.MouseDownEvent) {
let statusDownEvent = this.trigger('statusMouseDown', event);
if (!statusDownEvent.defaultPrevented) {
this.togglePopup();
}
}
protected _updateTooltipVisibility(parent: Widget) {
if (this.isEveryParentVisible()) {
/* We must use a timeout here, because the propertyChange event for the visible property
* is triggered before the _renderVisible() function is called. Which means the DOM is still
* invisible, thus the tooltip cannot be rendered. Because of the timeout we must double-check
* the state of the FieldStatus, because it could have been removed in the meantime.
*/
setTimeout(() => {
if (!this.rendered || !this.isEveryParentVisible()) {
return;
}
if (this.tooltip && !this.tooltip.rendered) {
this.tooltip.render();
}
});
} else {
if (this.tooltip && this.tooltip.rendered) {
this.tooltip.remove();
}
}
}
protected _onParentHierarchyChange(event: HierarchyChangeEvent) {
// If the parent of a widget we're listening to changes, we must re-check the parent hierarchy
// and re-install the property change listener
this._updateParentListeners();
}
protected _onParentPropertyChange(event: PropertyChangeEvent<any, Widget>) {
if ('visible' === event.propertyName) {
this._updateTooltipVisibility(event.source);
}
}
protected _removeParentListeners() {
this._parents.forEach(parent => {
parent.off('hierarchyChange', this._parentHierarchyChangeListener);
parent.off('propertyChange', this._parentPropertyChangeListener);
});
this._parents = [];
}
/**
* Adds a property change listener to every parent of the field status. We keep a list of all parents because
* we need to remove the listeners later, also when the parent hierarchy has changed.
*/
protected _updateParentListeners() {
this._removeParentListeners();
let parent = this.parent;
while (parent) {
parent.on('hierarchyChange', this._parentHierarchyChangeListener);
parent.on('propertyChange', this._parentPropertyChangeListener);
this._parents.push(parent);
parent = parent.parent;
}
}
}