UNPKG

@eclipse-scout/core

Version:
352 lines (311 loc) 9.53 kB
/* * 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; } } }