UNPKG

@eclipse-scout/core

Version:
234 lines (200 loc) 7.45 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 {Action, aria, BoxButtons, BusyIndicatorEventMap, ClickActiveElementKeyStroke, CloseKeyStroke, Event, FocusRule, GlassPaneRenderer, InitModelOf, keys, KeyStrokeContext, scout, strings, Widget, WidgetModel} from '../index'; export interface BusyIndicatorModel extends WidgetModel { /** * Specifies if the {@link BusyIndicator} is cancellable. * * If true, a cancel button is visible and the 'cancel' event may be fired on click. * * Default is true. */ cancellable?: boolean; /** * The time in milliseconds from the moment the {@link BusyIndicator} is rendered until the popup becomes visible. * Before this timeout the glasspanes are rendered and the cursors is displayed as busy. * As soon as the timeout elapsed, the busy indicator popup is shown as well. * Only after the popup is shown it is possible to cancel the indicator. * * Default is 2.5s. */ showTimeout?: number; /** * The text to show in the busy indicator popup. * * Default is 'Please wait'. */ label?: string; /** * An additional text shown in the busy indicator popup below the label. * * Default is no details text. */ details?: string; } export class BusyIndicator extends Widget implements BusyIndicatorModel { declare model: BusyIndicatorModel; declare eventMap: BusyIndicatorEventMap; declare self: BusyIndicator; cancellable: boolean; showTimeout: number; label: string; details: string; cancelButton: Action; boxButtons: BoxButtons; protected _glassPaneRenderer: GlassPaneRenderer; $content: JQuery; $buttons: JQuery; $label: JQuery; $details: JQuery; protected _busyIndicatorTimeoutId: number; constructor() { super(); this.cancellable = true; this.showTimeout = 2500; this.label = null; this.details = null; this.cancelButton = null; this.boxButtons = null; this._glassPaneRenderer = null; this.inheritAccessibility = false; // do not inherit enabled-state. BusyIndicator must always be enabled even if parent is disabled this.$content = null; this.$buttons = null; this.$label = null; this.$details = null; this._busyIndicatorTimeoutId = 0; this._addWidgetProperties(['boxButtons', 'cancelButton']); } protected override _createKeyStrokeContext(): KeyStrokeContext { return new KeyStrokeContext(); } protected override _initKeyStrokeContext() { super._initKeyStrokeContext(); this.keyStrokeContext.registerKeyStrokes([ new ClickActiveElementKeyStroke(this, [keys.SPACE, keys.ENTER]), new CloseKeyStroke(this, (() => { if (!this.cancelButton) { return null; } return this.cancelButton.$container; })) ]); } protected override _init(model: InitModelOf<this>) { super._init(model); this.label = scout.nvl(this.label, this.session.text('ui.PleaseWait_')); if (this.cancellable) { this.boxButtons = scout.create(BoxButtons, {parent: this}); this.cancelButton = this.boxButtons.addButton({text: this.session.text('Cancel')}); this.cancelButton.one('action', event => this._onCancelClick(event)); } } override render($parent?: JQuery) { // Use entry point by default $parent = $parent || this.entryPoint(); super.render($parent); } protected override _render() { // Render busy indicator (still hidden by CSS, will be shown later in setTimeout. // But don't use .hidden, otherwise the box' size cannot be calculated correctly!) this.$container = this.$parent.appendDiv('busyindicator invisible'); aria.role(this.$container, 'alertdialog'); let $handle = this.$container.appendDiv('drag-handle'); this.$container.draggable($handle); this.$content = this.$container.appendDiv('busyindicator-content'); this.$label = this.$content.appendDiv('busyindicator-label'); this.$details = this.$content.appendDiv('busyindicator-details'); if (this.cancellable) { this.boxButtons.render(); this.$buttons = this.boxButtons.$container; this.$buttons.addClass('busyindicator-buttons'); } else { this.$content.addClass('no-buttons'); } // Render properties this._renderLabel(); this._renderDetails(); aria.linkElementWithLabel(this.$container, this.$label); aria.linkElementWithDescription(this.$container, this.$details); // Prevent resizing when message-box is dragged off the viewport this.$container.addClass('calc-helper'); this.$container.css('min-width', this.$container.width()); this.$container.removeClass('calc-helper'); // Now that all texts, paddings, widths etc. are set, we can calculate the position this._position(); // Show busy box with a delay of 2.5 seconds (configurable by this.showTimeout). this._busyIndicatorTimeoutId = setTimeout(() => { this.$container.removeClass('invisible').addClassForAnimation('animate-open'); // Validate first focusable element // Maybe, this is not required if problem with single-button form is solved (see FormController.js) this.session.focusManager.validateFocus(); }, this.showTimeout); // Render modality glass-panes this._glassPaneRenderer = new GlassPaneRenderer(this); this._glassPaneRenderer.renderGlassPanes(); this._glassPaneRenderer.eachGlassPane($glassPane => $glassPane.addClass('busy')); } protected override _postRender() { super._postRender(); this.session.focusManager.installFocusContext(this.$container, FocusRule.AUTO); } protected override _remove() { // Remove busy box (cancel timer in case it was not fired yet) clearTimeout(this._busyIndicatorTimeoutId); // Remove glasspane this._glassPaneRenderer.eachGlassPane($glassPane => $glassPane.removeClass('busy')); this._glassPaneRenderer.removeGlassPanes(); this.session.focusManager.uninstallFocusContext(this.$container); super._remove(); } /** @see BusyIndicatorModel.label */ setLabel(label: string) { this.setProperty('label', label); } protected _renderLabel() { this.$label.text(this.label || ''); } /** @see BusyIndicatorModel.details */ setDetails(details: string) { this.setProperty('details', details); } protected _renderDetails() { this.$details .html(strings.nl2br(this.details)) .setVisible(!!this.details); } protected _position() { this.$container.cssMarginLeft(-this.$container.outerWidth() / 2); } /** * Used by CloseKeyStroke */ close() { if (this.cancelButton && this.cancelButton.$container && this.session.focusManager.requestFocus(this.cancelButton.$container)) { this.cancelButton.$container.focus(); this.cancelButton.doAction(); } } protected _onCancelClick(event: Event) { this.trigger('cancel', event); } /** * Sets the busy indicator into cancelled state. */ cancelled() { if (this.rendered) { // not closed yet this.$label.addClass('cancelled'); if (this.$buttons) { this.$buttons.remove(); } this.$content.addClass('no-buttons'); } } }