UNPKG

@eclipse-scout/core

Version:
381 lines (328 loc) 11.7 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 { AbortKeyStroke, Action, aria, BoxButtons, ClickActiveElementKeyStroke, clipboard, CopyKeyStroke, DisplayParent, EnumObject, Event, FocusAdjacentElementKeyStroke, FocusRule, Form, GlassPaneRenderer, HtmlComponent, Icon, InitModelOf, keys, KeyStrokeContext, MessageBoxEventMap, MessageBoxLayout, MessageBoxModel, objects, scout, Status, StatusSeverity, strings, Widget } from '../index'; import TriggeredEvent = JQuery.TriggeredEvent; export type MessageBoxOption = EnumObject<typeof MessageBox.Buttons>; export class MessageBox extends Widget implements MessageBoxModel { declare model: MessageBoxModel; declare eventMap: MessageBoxEventMap; declare self: MessageBox; severity: StatusSeverity; body: string; iconId: string; header: string; hiddenText: string; html: string; yesButtonText: string; noButtonText: string; cancelButtonText: string; buttons: Action[]; boxButtons: BoxButtons; yesButton: Action; noButton: Action; cancelButton: Action; displayParent: DisplayParent; /** button to be executed when abort() is called, e.g. when ESCAPE is pressed. points to the last (most right) button in the list (one of yes, no or cancel) */ abortButton: Action; $content: JQuery; $header: JQuery; $body: JQuery; $html: JQuery; $hiddenText: JQuery; $buttons: JQuery; protected _icon: Icon; protected _glassPaneRenderer: GlassPaneRenderer; constructor() { super(); this.severity = Status.Severity.INFO; this.body = null; this.iconId = null; this.header = null; this.hiddenText = null; this.html = null; this.yesButtonText = null; this.noButtonText = null; this.cancelButtonText = null; this.displayParent = null; this.buttons = []; this.boxButtons = null; this.yesButton = null; this.noButton = null; this.cancelButton = null; this.abortButton = null; this.inheritAccessibility = false; // do not inherit enabled-state by default. Otherwise the MessageBox cannot be closed anymore this.$content = null; this.$header = null; this.$body = null; this.$hiddenText = null; this.$buttons = null; this.$html = null; this._icon = null; this._addWidgetProperties(['buttons', 'boxButtons', 'yesButton', 'noButton', 'cancelButton', 'abortButton']); } static Buttons = { YES: 'yes', NO: 'no', CANCEL: 'cancel' } as const; protected override _init(model: InitModelOf<this>) { super._init(model); this._setDisplayParent(this.displayParent); this._setIconId(this.iconId); this.boxButtons = scout.create(BoxButtons, {parent: this}); this.yesButton = this._createMessageBoxButton(this.yesButtonText, MessageBox.Buttons.YES); this.noButton = this._createMessageBoxButton(this.noButtonText, MessageBox.Buttons.NO); this.cancelButton = this._createMessageBoxButton(this.cancelButtonText, MessageBox.Buttons.CANCEL); } protected override _createKeyStrokeContext(): KeyStrokeContext { return new KeyStrokeContext(); } protected override _initKeyStrokeContext() { super._initKeyStrokeContext(); this.keyStrokeContext.registerKeyStrokes([ new CopyKeyStroke(this), new FocusAdjacentElementKeyStroke(this.session, this), new ClickActiveElementKeyStroke(this, [ keys.SPACE, keys.ENTER ]), new AbortKeyStroke(this, () => { if (this.abortButton) { return this.abortButton.$container; } return null; }) ]); } protected _createMessageBoxButton(text: string, option: MessageBoxOption): Action { if (!text) { return null; } let button = this.boxButtons.addButton({text: text}); button.one('action', event => this._onButtonClick(event, option)); this.buttons.push(button); this.abortButton = button; return button; } protected override _render() { this.$container = this.$parent.appendDiv('messagebox') .on('mousedown', this._onMouseDown.bind(this)) .on('copy', this._onCopy.bind(this)); aria.role(this.$container, 'alertdialog'); let $handle = this.$container.appendDiv('drag-handle'); this.$container.draggable($handle); this.$content = this.$container.appendDiv('messagebox-content'); this.$header = this.$content.appendDiv('messagebox-label messagebox-header'); this.$body = this.$content.appendDiv('messagebox-label messagebox-body'); this.$html = this.$content.appendDiv('messagebox-label messagebox-html prevent-initial-focus'); this.boxButtons.render(); this.$buttons = this.boxButtons.$container; this.$buttons.addClass('messagebox-buttons'); this.$buttons.on('copy', this._onCopy.bind(this)); this._installScrollbars({ axis: 'y', scrollShadow: 'none' }); // Render properties this._renderSeverity(); this._renderHeader(); this._renderIconId(); this._renderBody(); this._renderHtml(); this._renderHiddenText(); this.htmlComp = HtmlComponent.install(this.$container, this.session); this.htmlComp.setLayout(new MessageBoxLayout(this)); this.htmlComp.validateLayout(); this.$container.addClassForAnimation('animate-open'); // Render modality glass-panes this._glassPaneRenderer = new GlassPaneRenderer(this); this._glassPaneRenderer.renderGlassPanes(); } override get$Scrollable(): JQuery { return this.$content; } protected override _postRender() { super._postRender(); this._installFocusContext(); } protected override _remove() { this._glassPaneRenderer.removeGlassPanes(); this._uninstallFocusContext(); super._remove(); } protected override _installFocusContext() { this.session.focusManager.installFocusContext(this.$container, FocusRule.AUTO); } protected override _uninstallFocusContext() { this.session.focusManager.uninstallFocusContext(this.$container); } protected _renderIconId() { let hasIcon = !!this._icon; this.$container.toggleClass('has-icon', hasIcon); this.$container.toggleClass('no-icon', !hasIcon); if (hasIcon) { this._icon.render(this.$header); this._icon.$container.addClass('messagebox-icon'); } } protected _renderSeverity() { this.$container.removeClass(Status.SEVERITY_CSS_CLASSES); this.$container.addClass(Status.cssClassForSeverity(this.severity)); } protected _renderHeader() { this.$header.html(strings.nl2br(this.header)); this.$header.setVisible(!!this.header || !!this.iconId); this.$header.toggleClass('has-text', strings.hasText(this.header)); if (strings.hasText(this.header)) { aria.linkElementWithLabel(this.$container, this.$header); } } protected _renderBody() { this.$body.html(strings.nl2br(this.body)); this.$body.setVisible(!!this.body); this.$content.toggleClass('has-body', !!this.body); if (strings.hasText(this.body)) { aria.linkElementWithDescription(this.$container, this.$body); } } protected _renderHtml() { this.$html.html(this.html); this.$html.setVisible(!!this.html); // Don't change focus when a link is clicked by mouse this.$html.find('a, .app-link') .attr('tabindex', '0') .unfocusable(); if (strings.hasText(this.html)) { aria.linkElementWithDescription(this.$container, this.$html); } } protected _renderHiddenText() { if (this.$hiddenText) { this.$hiddenText.remove(); } if (this.hiddenText) { this.$hiddenText = this.$content.appendElement('<!-- \n' + this.hiddenText.replace(/<!--|-->/g, '') + '\n -->'); } } protected _onMouseDown() { // If there is a dialog in the parent-hierarchy activate it in order to bring it on top of other dialogs. let parent = this.findParent(p => p instanceof Form && p.isDialog()) as Form; if (parent) { parent.activate(); } } protected _setCopyable(copyable: boolean) { this.$header.toggleClass('copyable', copyable); this.$body.toggleClass('copyable', copyable); this.$html.toggleClass('copyable', copyable); } copy() { this._setCopyable(true); let myDocument = this.$container.document(true); let range = myDocument.createRange(); range.selectNodeContents(this.$content[0]); let selection = this.$container.window(true).getSelection(); selection.removeAllRanges(); selection.addRange(range); myDocument.execCommand('copy'); } protected _onCopy(event: TriggeredEvent) { let clipboardData = objects.optProperty(event, 'originalEvent', 'clipboardData') as DataTransfer; if (clipboardData) { let htmlText = strings.join('<br/>', this.$header[0].outerHTML, this.$body[0].outerHTML, this.$html[0].outerHTML, this.hiddenText); clipboardData.setData('text/html', htmlText); let plainText = strings.join('\n\n', this.header, this.body, strings.plainText(this.$html[0].outerHTML, {compact: true, trim: true}), this.hiddenText); clipboardData.setData('text/plain', plainText); this.$container.window(true).getSelection().removeAllRanges(); this._setCopyable(false); clipboard.showNotification(this); event.preventDefault(); // We want to write our data to the clipboard, not data from any user selection } // else: do default } protected _onButtonClick(event: Event<Action>, option: MessageBoxOption) { this.trigger('action', { option: option }); } setDisplayParent(displayParent: DisplayParent) { this.setProperty('displayParent', displayParent); } protected _setDisplayParent(displayParent: DisplayParent) { this._setProperty('displayParent', displayParent); if (displayParent) { this.setParent(this.findDesktop().computeParentForDisplayParent(displayParent)); } } setIconId(iconId: string) { this.setProperty('iconId', iconId); } protected _setIconId(iconId: string) { this._setProperty('iconId', iconId); if (iconId) { if (this._icon) { this._icon.setIconDesc(iconId); } else { this._icon = scout.create(Icon, { parent: this, iconDesc: iconId, prepend: true }); this._icon.one('destroy', () => { this._icon = null; }); } } else if (this._icon) { this._icon.destroy(); } } /** * Renders the message box and links it with the display parent. */ open() { this.setDisplayParent(this.displayParent || this.session.desktop); this.displayParent.messageBoxController.registerAndRender(this); } /** * Destroys the message box and unlinks it from the display parent. */ close() { if (this.displayParent) { this.displayParent.messageBoxController.unregisterAndRemove(this); } this.destroy(); } /** * Aborts the message box by using the default abort button. Used by the ESC key stroke. */ abort() { if (this.abortButton && this.abortButton.$container && this.session.focusManager.requestFocus(this.abortButton.$container)) { this.abortButton.doAction(); } } protected override _attach() { this.$parent.append(this.$container); super._attach(); } protected override _detach() { this.$container.detach(); super._detach(); } }