@eclipse-scout/core
Version:
Eclipse Scout runtime
234 lines (200 loc) • 7.45 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 {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');
}
}
}