UNPKG

@syncfusion/ej2-popups

Version:

A package of Essential JS 2 popup components such as Dialog and Tooltip that is used to display information or messages in separate pop-ups.

1,372 lines (1,333 loc) 101 kB
import { Component, Property, Event, Collection, L10n, EmitType, Complex, compile, createElement, animationMode } from '@syncfusion/ej2-base'; import { addClass, removeClass, detach, attributes, prepend, setStyleAttribute } from '@syncfusion/ej2-base'; import { NotifyPropertyChanges, INotifyPropertyChanged, ChildProperty, isBlazor } from '@syncfusion/ej2-base'; import { isNullOrUndefined, formatUnit, append, EventHandler, Draggable, extend } from '@syncfusion/ej2-base'; import { BlazorDragEventArgs, SanitizeHtmlHelper, Browser } from '@syncfusion/ej2-base'; import { Button, ButtonModel } from '@syncfusion/ej2-buttons'; import { Popup, PositionData, getZindexPartial } from '../popup/popup'; import { PositionDataModel } from '../popup/popup-model'; import { ButtonPropsModel, DialogModel, AnimationSettingsModel } from './dialog-model'; import { createResize, removeResize, setMinHeight, setMaxWidth, setMaxHeight, resizeDestroy } from '../common/resize'; /** * Defines the types of a button in the dialog. */ export type ButtonType = 'Button' | 'Submit' | 'Reset'; /** * Provides information about a SanitizeSelectors. */ export interface SanitizeSelectors { /** Returns the tags. */ tags?: string[] /** Returns the attributes. */ attributes?: SanitizeRemoveAttrs[] } /** * Provides information about a BeforeSanitizeHtml event. */ export interface BeforeSanitizeHtmlArgs { /** Illustrates whether the current action needs to be prevented or not. */ cancel?: boolean /** It is a callback function and executed it before our inbuilt action. It should return HTML as a string. * * @function * @param {string} value - Returns the value. * @returns {string} */ helper?: Function /** Returns the selectors object which carrying both tags and attributes selectors to block list of cross-site scripting attack. *Also possible to modify the block list in this event. */ selectors?: SanitizeSelectors } /** * Provides information about a SanitizeRemoveAttributes. */ export interface SanitizeRemoveAttrs { /** Defines the attribute name to sanitize */ attribute?: string /** Defines the selector that sanitize the specified attributes within the selector */ selector?: string } export class ButtonProps extends ChildProperty<ButtonProps> { /** * Specifies the flat appearance of the dialog buttons * * @default true */ @Property(true) public isFlat: boolean; /** * Specifies the button component properties to render the dialog buttons. */ @Property() public buttonModel: ButtonModel; /** * Specify the type of the button. * Possible values are Button, Submit and Reset. * * @default 'Button' * @aspType string * @blazorType string */ @Property('Button') public type: ButtonType | string; /** * Event triggers when `click` the dialog button. * * @event 'object' * @blazorProperty 'OnClick' */ @Event() public click: EmitType<Object>; /* eslint-enable */ } /** * Configures the animation properties for both open and close the dialog. */ export class AnimationSettings extends ChildProperty<AnimationSettings> { /** * Specifies the animation name that should be applied on open and close the dialog. * If user sets Fade animation, the dialog will open with `FadeIn` effect and close with `FadeOut` effect. * The following are the list of animation effects available to configure to the dialog: * 1. Fade * 2. FadeZoom * 3. FlipLeftDown * 4. FlipLeftUp * 5. FlipRightDown * 6. FlipRightUp * 7. FlipXDown * 8. FlipXUp * 9. FlipYLeft * 10. FlipYRight * 11. SlideBottom * 12. SlideLeft * 13. SlideRight * 14. SlideTop * 15. Zoom * 16. None * * @default 'Fade' */ @Property('Fade') public effect: DialogEffect; /** * Specifies the duration in milliseconds that the animation takes to open or close the dialog. * * @default 400 */ @Property(400) public duration: number; /** * Specifies the delay in milliseconds to start animation. * * @default 0 */ @Property(0) public delay: number; } /** * Specifies the Dialog animation effects. */ export type DialogEffect = 'Fade' | 'FadeZoom' | 'FlipLeftDown' | 'FlipLeftUp' | 'FlipRightDown' | 'FlipRightUp' | 'FlipXDown' | 'FlipXUp' | 'FlipYLeft' | 'FlipYRight' | 'SlideBottom' | 'SlideLeft' | 'SlideRight' | 'SlideTop' | 'Zoom' | 'None'; /** * Specifies the Resize Handles. */ export type ResizeDirections = 'South' | 'North' | 'East' | 'West' | 'NorthEast' | 'NorthWest' | 'SouthEast' | 'SouthWest' | 'All'; const ROOT: string = 'e-dialog'; const RTL: string = 'e-rtl'; const DLG_HEADER_CONTENT: string = 'e-dlg-header-content'; const DLG_HEADER: string = 'e-dlg-header'; const DLG_FOOTER_CONTENT: string = 'e-footer-content'; const MODAL_DLG: string = 'e-dlg-modal'; const DLG_CONTENT: string = 'e-dlg-content'; const DLG_CLOSE_ICON: string = 'e-icon-dlg-close'; const DLG_OVERLAY: string = 'e-dlg-overlay'; const DLG_TARGET: string = 'e-dlg-target'; const DLG_CONTAINER: string = 'e-dlg-container'; const SCROLL_DISABLED: string = 'e-scroll-disabled'; const DLG_PRIMARY_BUTTON: string = 'e-primary'; const ICON: string = 'e-icons'; const POPUP_ROOT: string = 'e-popup'; const DEVICE: string = 'e-device'; const FULLSCREEN: string = 'e-dlg-fullscreen'; const DLG_CLOSE_ICON_BTN: string = 'e-dlg-closeicon-btn'; const DLG_HIDE: string = 'e-popup-close'; const DLG_SHOW: string = 'e-popup-open'; const DLG_UTIL_DEFAULT_TITLE: string = 'Information'; const DLG_UTIL_ROOT: string = 'e-scroll-disabled'; const DLG_UTIL_ALERT: string = 'e-alert-dialog'; const DLG_UTIL_CONFIRM: string = 'e-confirm-dialog'; const DLG_RESIZABLE: string = 'e-dlg-resizable'; const DLG_RESTRICT_LEFT_VALUE: string = 'e-restrict-left'; const DLG_RESTRICT_WIDTH_VALUE: string = 'e-resize-viewport'; const DLG_REF_ELEMENT: string = 'e-dlg-ref-element'; const DLG_USER_ACTION_CLOSED: string = 'user action'; const DLG_CLOSE_ICON_CLOSED: string = 'close icon'; const DLG_ESCAPE_CLOSED: string = 'escape'; const DLG_OVERLAYCLICK_CLOSED: string = 'overlayClick'; const DLG_DRAG : string = 'e-draggable'; /** * Provides information about a BeforeOpen event. */ export interface BeforeOpenEventArgs { /** * Specify the value to override max-height value of dialog. */ maxHeight: string /** * Defines whether the current action can be prevented. */ cancel: boolean /** * Returns the root container element of the dialog. */ container: HTMLElement /** * Returns the element of the dialog. */ element: Element /** * Returns the target element of the dialog. * * @aspType string * @blazorType string * @deprecated */ target?: HTMLElement | string } /** * Provides information about a BeforeClose event. */ export interface BeforeCloseEventArgs { /** * Defines whether the current action can be prevented. */ cancel: boolean /** * Determines whether the event is triggered by interaction. */ isInteracted: boolean /** * Returns the root container element of the dialog. */ container: HTMLElement /** * Returns the element of the dialog. */ element: Element /** * Returns the target element of the dialog. * * @aspType string * @blazorType string * @deprecated */ target?: HTMLElement | string /** * Returns the original event arguments. */ event: Event /** * Returns whether the dialog, is closed by "close icon", "overlayClick", "escape" and "user action" */ closedBy?: string } /** * Provides information about a DialogOpen event. */ export interface OpenEventArgs { /** * Defines whether the focus action can be prevented in dialog. */ preventFocus: boolean /** * Defines whether the current action can be prevented. */ cancel: boolean /** * Returns the root container element of the dialog. */ container: HTMLElement /** * Returns the element of the dialog. */ element: Element /** * Specify the name of the event. */ name: string } /** * Provides information about a Close event. */ export interface CloseEventArgs { /** * Defines whether the current action can be prevented. */ cancel: boolean /** * Returns the root container element of the dialog. */ container: HTMLElement /** * Returns the element of the dialog. */ element: Element /** * Returns the original event arguments. */ event: Event /** * Determines whether the event is triggered by interaction. */ isInteracted: boolean /** * Specify the name of the event. */ name: string } /** * Provides information about a DragStart event. */ export interface DragStartEventArgs { /** * Returns the original event arguments. * * @blazorType MouseEventArgs */ event: Event /** * Returns the element of the dialog. */ element: Element /** * Returns the target element of the dialog. */ target: HTMLElement /** * Returns the name of the event. */ name: string } /** * Provides information about a DragStop event. */ export interface DragStopEventArgs { /** * Returns the original event arguments. * * @blazorType MouseEventArgs */ event: Event /** * Returns the element of the dialog. */ element: Element /** * Returns the target element of the dialog. */ target: HTMLElement /** * Returns the helper element. */ helper: Element /** * Returns the name of the event. */ name: string } /** * Provides information about a Drag event. */ export interface DragEventArgs { /** * Returns the original event arguments. * * @blazorType MouseEventArgs */ event: Event /** * Returns the element of the dialog. */ element: Element /** * Returns the target element of the dialog. */ target: HTMLElement /** * Returns the name of the event. */ name: string } /** * Represents the dialog component that displays the information and get input from the user. * Two types of dialog components are `Modal and Modeless (non-modal)` depending on its interaction with parent application. * ```html * <div id="dialog"></div> * ``` * ```typescript * <script> * var dialogObj = new Dialog({ header: 'Dialog' }); * dialogObj.appendTo("#dialog"); * </script> * ``` */ @NotifyPropertyChanges export class Dialog extends Component<HTMLElement> implements INotifyPropertyChanged { // Internal variables private closeIconClickEventHandler: Function; private dlgOverlayClickEventHandler: Function; private createEventHandler: Function; /* eslint-enable */ private contentEle: HTMLElement; private dlgOverlay: HTMLElement; private dlgContainer: HTMLElement; private headerEle: HTMLElement; private buttonContent: string[]; private ftrTemplateContent: HTMLElement; private headerContent: HTMLElement; private closeIcon: HTMLElement; private popupObj: Popup; private btnObj: Button[]; private closeIconBtnObj: Button; private dragObj: Draggable; private primaryButtonEle: HTMLElement; private targetEle: HTMLElement; private dialogOpen: boolean; private initialRender: boolean; private innerContentElement: Node; private storeActiveElement: HTMLElement; private focusElements: HTMLElement[]; private focusIndex: number; private l10n: L10n; private clonedEle: HTMLElement; private closeArgs: Object; private calculatezIndex: boolean; private allowMaxHeight: boolean; private preventVisibility: boolean; private IsDragStop: boolean; private refElement: HTMLElement; private dlgClosedBy: string; private isModelResize: boolean; private boundWindowResizeHandler: () => void; /** * Specifies the value that can be displayed in dialog's content area. * It can be information, list, or other HTML elements. * The content of dialog can be loaded with dynamic data such as database, AJAX content, and more. * * {% codeBlock src="dialog/content-api/index.ts" %}{% endcodeBlock %} * * {% codeBlock src="dialog/content-api/index.html" %}{% endcodeBlock %} * * @default '' * @blazorType string * @aspType string */ @Property('') public content: string | HTMLElement | Function; /** * Defines whether to allow the cross-scripting site or not. * * @default true */ @Property(true) public enableHtmlSanitizer: boolean; /** * Enables or disables the persistence of the dialog's dimensions and position state between page reloads. * * @default false */ @Property(false) public enablePersistence: boolean; /** * Specifies the value that represents whether the close icon is shown in the dialog component. * * @default false */ @Property(false) public showCloseIcon: boolean; /** * Specifies the Boolean value whether the dialog can be displayed as modal or non-modal. * * `Modal`: It creates overlay that disable interaction with the parent application and user should * respond with modal before continuing with other applications. * * `Modeless`: It does not prevent user interaction with parent application. * * @default false */ @Property(false) public isModal: boolean; /** * Specifies the value that can be displayed in the dialog's title area that can be configured with plain text or HTML elements. * This is optional property and the dialog can be displayed without header, if the header property is null. * * @default '' * @blazorType string * @aspType string */ @Property('') public header: string | HTMLElement | Function; /** * Specifies the value that represents whether the dialog component is visible. * * @default true */ @Property(true) public visible: boolean; /** * Specifies the value whether the dialog component can be resized by the end-user. * If enableResize is true, the dialog component creates grip to resize it diagonal direction. * * @default false */ @Property(false) public enableResize: boolean; /** * Specifies the resize handles direction in the dialog component that can be resized by the end-user. * * @default ['South-East'] */ @Property(['South-East']) public resizeHandles: ResizeDirections[]; /** * Specifies the height of the dialog component. * * @default 'auto' * @blazorType string */ @Property('auto') public height: string | number; /** * Specify the min-height of the dialog component. * * @default '' * @blazorType string */ @Property('') public minHeight: string | number; /** * Specifies the width of the dialog. * * @default '100%' * @blazorType string */ @Property('100%') public width: string | number; /** * Specifies the CSS class name that can be appended with root element of the dialog. * One or more custom CSS classes can be added to a dialog. * * @default '' */ @Property('') public cssClass: string; /** * Specifies the z-order for rendering that determines whether the dialog is displayed in front or behind of another component. * * @default 1000 */ @Property(1000) public zIndex: number; /** * Specifies the target element in which to display the dialog. * The default value is null, which refers the `document.body` element. * * @default null * @blazorType string */ @Property(null) public target: HTMLElement | string; /** * Specifies the template value that can be displayed with dialog's footer area. * This is optional property and can be used only when the footer is occupied with information or custom components. * By default, the footer is configured with action [buttons](#buttons). * If footer template is configured to dialog, the action buttons property will be disabled. * * > More information on the footer template configuration can be found on this [documentation](../../dialog/template/#footer) section. * * @default '' * @blazorType string * @aspType string */ @Property('') public footerTemplate: HTMLElement | string | Function; /** * Specifies the value whether the dialog component can be dragged by the end-user. * The dialog allows to drag by selecting the header and dragging it for re-position the dialog. * * > More information on the draggable behavior can be found on this [documentation](../../dialog/getting-started/#draggable) section. * * {% codeBlock src='dialog/allowDragging/index.md' %}{% endcodeBlock %} * * @default false */ @Property(false) public allowDragging: boolean; /** * Configures the action `buttons` that contains button properties with primary attributes and click events. * One or more action buttons can be configured to the dialog. * * > More information on the button configuration can be found on this * [documentation](../../dialog/getting-started/#enable-footer) section. * * {% codeBlock src="dialog/buttons-api/index.ts" %}{% endcodeBlock %} * * {% codeBlock src="dialog/buttons-api/index.html" %}{% endcodeBlock %} * * {% codeBlock src='dialog/buttons/index.md' %}{% endcodeBlock %} * * @default [{}] */ @Collection<ButtonPropsModel>([{}], ButtonProps) public buttons: ButtonPropsModel[]; /** * Specifies the boolean value whether the dialog can be closed with the escape key * that is used to control the dialog's closing behavior. * * @default true */ @Property(true) public closeOnEscape: boolean; /** * Specifies the animation settings of the dialog component. * The animation effect can be applied on open and close the dialog with duration and delay. * * > More information on the animation settings in dialog can be found on this [documentation](../../dialog/animation/) section. * * {% codeBlock src="dialog/animation-api/index.ts" %}{% endcodeBlock %} * * {% codeBlock src="dialog/animation-api/index.html" %}{% endcodeBlock %} * * {% codeBlock src='dialog/animationSettings/index.md' %}{% endcodeBlock %} * * @default { effect: 'Fade', duration: 400, delay:0 } */ @Complex<AnimationSettingsModel>({}, AnimationSettings) public animationSettings: AnimationSettingsModel; /** * Specifies the value where the dialog can be positioned within the document or target. * The position can be represented with pre-configured positions or specific X and Y values. * * `X value`: left, center, right, or offset value. * * `Y value`: top, center, bottom, or offset value. * * > More information on the positioning in dialog can be found on this [documentation](../../dialog/getting-started/#positioning) section. * * {% codeBlock src='dialog/position/index.md' %}{% endcodeBlock %} * * @default { X: 'center', Y: 'center' } */ @Complex<PositionDataModel>({ X: 'center', Y: 'center' }, PositionData) public position: PositionDataModel; /** * Event triggers when the dialog is created. * * @event 'object' * @blazorProperty 'Created' */ @Event() public created: EmitType<Object>; /* eslint-enable */ /** * Event triggers when a dialog is opened. * * @event 'object' * @blazorProperty 'Opened' * @blazorType OpenEventArgs */ @Event() public open: EmitType<Object>; /* eslint-enable */ /** * Event triggers before sanitize the value. * * @event 'object' * @blazorProperty 'OnSanitizeHtml' */ @Event() public beforeSanitizeHtml: EmitType<BeforeSanitizeHtmlArgs>; /** * Event triggers when the dialog is being opened. * If you cancel this event, the dialog remains closed. * Set the cancel argument to true to cancel the open of a dialog. * * @event 'object' * @blazorProperty 'OnOpen' */ @Event() public beforeOpen: EmitType<BeforeOpenEventArgs>; /** * Event triggers after the dialog has been closed. * * @event 'object' * @blazorProperty 'Closed' * @blazorType CloseEventArgs */ @Event() public close: EmitType<Object>; /* eslint-enable */ /** * Event triggers before the dialog is closed. * If you cancel this event, the dialog remains opened. * Set the cancel argument to true to cancel the closure of a dialog. * * @event 'object' * @blazorProperty 'OnClose' */ @Event() public beforeClose: EmitType<BeforeCloseEventArgs>; /** * Event triggers when the user begins dragging the dialog. * * @event 'object' * @blazorProperty 'OnDragStart' * @blazorType DragStartEventArgs */ @Event() public dragStart: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the user stop dragging the dialog. * * @event 'object' * @blazorProperty 'OnDragStop' * @blazorType DragStopEventArgs */ @Event() public dragStop: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the user drags the dialog. * * @event 'object' * @blazorProperty 'OnDrag' * @blazorType DragEventArgs */ @Event() public drag: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the overlay of dialog is clicked. * * @event 'object' * @blazorProperty 'OnOverlayClick' */ @Event() public overlayClick: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the user begins to resize a dialog. * * @event 'object' * @blazorProperty 'OnResizeStart' */ @Event() public resizeStart: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the user resize the dialog. * * @event 'object' * @blazorProperty 'Resizing' */ @Event() public resizing: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the user stop to resize a dialog. * * @event 'object' * @blazorProperty 'OnResizeStop' */ @Event() public resizeStop: EmitType<Object>; /* eslint-enable */ /** * Event triggers when the dialog is destroyed. * * @event 'object' * @blazorProperty 'Destroyed' */ @Event() public destroyed: EmitType<Event>; protected needsID: boolean; /* * * Constructor for creating the widget * * @param * @param * @hidden */ constructor(options?: DialogModel, element?: string | HTMLElement) { super(options, <HTMLElement | string>element); this.needsID = true; } /** *Initialize the control rendering * * @returns {void} * @private */ public render(): void { this.initialize(); this.initRender(); this.wireEvents(); if (this.width === '100%') { this.element.style.width = ''; } if (this.minHeight !== '') { this.element.style.minHeight = formatUnit(this.minHeight); } if (this.enableResize) { this.setResize(); if (this.isModal) { this.isModelResize = true; } if (this.animationSettings.effect === 'None') { this.getMinHeight(); } } this.renderComplete(); } private initializeValue(): void{ this.dlgClosedBy = DLG_USER_ACTION_CLOSED; } /** *Initialize the event handler * * @returns {void} * @private */ protected preRender(): void { this.initializeValue(); this.headerContent = null; this.allowMaxHeight = true; this.preventVisibility = true; this.clonedEle = <HTMLElement>this.element.cloneNode(true); this.closeIconClickEventHandler = (event: Event): void => { this.dlgClosedBy = DLG_CLOSE_ICON_CLOSED; this.hide(event); }; this.dlgOverlayClickEventHandler = (event: Object): void => { this.dlgClosedBy = DLG_OVERLAYCLICK_CLOSED; (event as {[key: string]: boolean}).preventFocus = false; this.trigger('overlayClick', event, (overlayClickEventArgs: {[key: string]: object}) => { if (!overlayClickEventArgs.preventFocus) { this.focusContent(); } this.dlgClosedBy = DLG_USER_ACTION_CLOSED; }); }; const localeText: object = { close: 'Close' }; this.l10n = new L10n('dialog', localeText, this.locale); this.checkPositionData(); if (isNullOrUndefined(this.target)) { const prevOnChange: boolean = this.isProtectedOnChange; this.isProtectedOnChange = true; this.target = document.body; this.isProtectedOnChange = prevOnChange; } } private updatePersistData(): void { if (this.enablePersistence){ this.setProperties({ width : parseFloat(this.element.style.width), height : parseFloat(this.element.style.height), position : { X: parseFloat(this.dragObj.element.style.left), Y: parseFloat(this.dragObj.element.style.top)} }, true); } } private isNumberValue(value: string): boolean { const isNumber: boolean = /^[-+]?\d*\.?\d+$/.test(value); return isNumber; } private checkPositionData(): void { if (!isNullOrUndefined(this.position)) { if ( !isNullOrUndefined(this.position.X) && ( typeof(this.position.X) !== 'number')) { const isNumber: boolean = this.isNumberValue(this.position.X); if (isNumber) { const prevOnChange: boolean = this.isProtectedOnChange; this.isProtectedOnChange = true; this.position.X = parseFloat(this.position.X); this.isProtectedOnChange = prevOnChange; } } if ( !isNullOrUndefined(this.position.Y) && ( typeof(this.position.Y) !== 'number')) { const isNumber: boolean = this.isNumberValue(this.position.Y); if (isNumber) { const prevOnChange: boolean = this.isProtectedOnChange; this.isProtectedOnChange = true; this.position.Y = parseFloat(this.position.Y); this.isProtectedOnChange = prevOnChange; } } } } private getEle(list: HTMLCollection, selector: string): Element { let element: Element = undefined; for (let i: number = 0; i < list.length; i++) { if ((list[i as number] as Element).classList.contains(selector)) { element = list[i as number] as Element; break; } } return element; } /* istanbul ignore next */ private getMinHeight(): number { let computedHeaderHeight: string = '0px'; let computedFooterHeight: string = '0px'; if (!isNullOrUndefined(this.element.querySelector('.' + DLG_HEADER_CONTENT))) { computedHeaderHeight = getComputedStyle(this.headerContent).height; } const footerEle: Element = this.getEle(this.element.children, DLG_FOOTER_CONTENT); if (!isNullOrUndefined(footerEle)) { computedFooterHeight = getComputedStyle(footerEle).height; } const headerHeight: number = parseInt(computedHeaderHeight.slice(0, computedHeaderHeight.indexOf('p')), 10); const footerHeight: number = parseInt(computedFooterHeight.slice(0, computedFooterHeight.indexOf('p')), 10); setMinHeight(headerHeight + 30 + (isNaN(footerHeight) ? 0 : footerHeight)); return (headerHeight + 30 + footerHeight); } private onResizeStart(args: ResizeMouseEventArgs | ResizeTouchEventArgs, dialogObj: Dialog): boolean { dialogObj.trigger('resizeStart', args); if (!args.cancel && this.isModelResize && !isNullOrUndefined(this.dlgContainer) && this.dlgContainer.classList.contains('e-dlg-' + this.position.X + '-' + this.position.Y)) { this.setPopupPosition(); this.dlgContainer.classList.remove('e-dlg-' + this.position.X + '-' + this.position.Y); const targetType: HTMLElement = this.getTargetContainer(this.target); if (targetType instanceof Element) { const computedStyle: CSSStyleDeclaration = window.getComputedStyle(targetType); if (computedStyle.getPropertyValue('direction') === 'rtl') { this.element.style.position = 'absolute'; } else { this.element.style.position = 'relative'; } } else { this.element.style.position = 'relative'; } if (this.element.classList.contains(DLG_RESTRICT_LEFT_VALUE)) { this.element.classList.remove(DLG_RESTRICT_LEFT_VALUE); } this.isModelResize = false; } return args.cancel; } private onResizing(args: MouseEvent | TouchEvent, dialogObj: Dialog): void { dialogObj.trigger('resizing', args); } private onResizeComplete(args: MouseEvent | TouchEvent, dialogObj: Dialog): void { dialogObj.trigger('resizeStop', args); this.updatePersistData(); } private setResize(): void { if (this.enableResize) { this.element.classList.add(DLG_RESIZABLE); const computedHeight: string = getComputedStyle(this.element).minHeight; const computedWidth: string = getComputedStyle(this.element).minWidth; let direction: string = ''; for (let i: number = 0; i < this.resizeHandles.length; i++) { if (this.resizeHandles[i as number] === 'All') { direction = 'south north east west north-east north-west south-east south-west'; break; } else { let directionValue: string = ''; switch (this.resizeHandles[i as number].toString()) { case 'SouthEast': directionValue = 'south-east'; break; case 'SouthWest': directionValue = 'south-west'; break; case 'NorthEast': directionValue = 'north-east'; break; case 'NorthWest': directionValue = 'north-west'; break; default: directionValue = this.resizeHandles[i as number].toString(); break; } direction += directionValue.toLocaleLowerCase() + ' '; } } if (this.enableRtl && direction.trim() === 'south-east') { direction = 'south-west'; } else if (this.enableRtl && direction.trim() === 'south-west') { direction = 'south-east'; } if (this.isModal && this.enableRtl) { this.element.classList.add(DLG_RESTRICT_LEFT_VALUE); } else if (this.isModal && this.target === document.body) { this.element.classList.add(DLG_RESTRICT_WIDTH_VALUE); } createResize({ element: this.element, direction: direction, minHeight: parseInt(computedHeight.slice(0, computedWidth.indexOf('p')), 10), maxHeight: this.targetEle.clientHeight, minWidth: parseInt(computedWidth.slice(0, computedWidth.indexOf('p')), 10), maxWidth: this.targetEle.clientWidth, boundary: this.target === document.body ? null : this.targetEle, resizeBegin: this.onResizeStart.bind(this), resizeComplete: this.onResizeComplete.bind(this), resizing: this.onResizing.bind(this), proxy: this }); this.wireWindowResizeEvent(); } else { removeResize(); this.unWireWindowResizeEvent(); if (this.isModal) { this.element.classList.remove(DLG_RESTRICT_LEFT_VALUE); } else { this.element.classList.remove(DLG_RESTRICT_WIDTH_VALUE); } this.element.classList.remove(DLG_RESIZABLE); } } private getFocusElement(target: HTMLElement): Button { const value: string = 'input,select,textarea,button:enabled,a,[contenteditable="true"],[tabindex]'; const items: NodeListOf<HTMLElement> = target.querySelectorAll(value); return { element: items[items.length - 1] as HTMLElement } as Button; } /* istanbul ignore next */ private keyDown(event: KeyboardEvent): void { if (event.keyCode === 9) { if (this.isModal) { let buttonObj: Button; if (!isNullOrUndefined(this.btnObj)) { buttonObj = this.btnObj[this.btnObj.length - 1]; } if ((isNullOrUndefined(this.btnObj)) && (!isNullOrUndefined(this.ftrTemplateContent))) { buttonObj = this.getFocusElement(this.ftrTemplateContent); } if (isNullOrUndefined(this.btnObj) && isNullOrUndefined(this.ftrTemplateContent) && !isNullOrUndefined(this.contentEle)) { buttonObj = this.getFocusElement(this.contentEle); } if (!isNullOrUndefined(buttonObj) && document.activeElement === buttonObj.element && !event.shiftKey) { event.preventDefault(); this.focusableElements(this.element).focus(); } if (document.activeElement === this.focusableElements(this.element) && event.shiftKey) { event.preventDefault(); if (!isNullOrUndefined(buttonObj)) { buttonObj.element.focus(); } } } } const element: HTMLElement = <HTMLElement>document.activeElement; const isTagName: boolean = (['input', 'textarea'].indexOf(element.tagName.toLowerCase()) > -1); let isContentEdit: boolean = false; if (!isTagName) { isContentEdit = element.hasAttribute('contenteditable') && element.getAttribute('contenteditable') === 'true'; } if (event.keyCode === 27 && this.closeOnEscape) { this.dlgClosedBy = DLG_ESCAPE_CLOSED; const query: HTMLElement = <HTMLElement>document.querySelector('.e-popup-open:not(.e-dialog)'); // 'document.querySelector' is used to find the elements rendered based on body if (!(!isNullOrUndefined(query) && !query.classList.contains('e-toolbar-pop') && !query.classList.contains('e-slider-tooltip'))){ this.hide(event); } } if ((event.keyCode === 13 && !event.ctrlKey && element.tagName.toLowerCase() !== 'textarea' && isTagName && !isNullOrUndefined(this.primaryButtonEle)) || (event.keyCode === 13 && event.ctrlKey && (element.tagName.toLowerCase() === 'textarea' || isContentEdit)) && !isNullOrUndefined(this.primaryButtonEle)) { let buttonIndex: number; const firstPrimary: boolean = this.buttons.some((data: { [key: string]: Object }, index: number) => { buttonIndex = index; const buttonModel: { [key: string]: Object } = (data.buttonModel as { [key: string]: Object }); return !isNullOrUndefined(buttonModel) && buttonModel.isPrimary === true; }); if (firstPrimary && typeof (this.buttons[buttonIndex as number].click) === 'function' && !(this.primaryButtonEle as HTMLButtonElement).disabled) { setTimeout(() => { this.buttons[buttonIndex as number].click.call(this, event); }); } } } /** * Initialize the control rendering * * @returns {void} * @private */ private initialize(): void { if (!isNullOrUndefined(this.target)) { this.targetEle = ((typeof this.target) === 'string') ? <HTMLElement>document.querySelector(<string>this.target) : <HTMLElement>this.target; } if (!this.isBlazorServerRender()) { addClass([this.element], ROOT); } if (Browser.isDevice) { addClass([this.element], DEVICE); } if (!this.isBlazorServerRender()) { this.setCSSClass(); } this.setMaxHeight(); } /** * Initialize the rendering * * @returns {void} * @private */ private initRender(): void { this.initialRender = true; if (!this.isBlazorServerRender()) { attributes(this.element, { role: 'dialog' }); } if (this.zIndex === 1000) { this.setzIndex(this.element, false); this.calculatezIndex = true; } else { this.calculatezIndex = false; } this.setTargetContent(); if (this.header !== '' && !isNullOrUndefined(this.header)) { this.setHeader(); } this.renderCloseIcon(); this.setContent(); if (this.footerTemplate !== '' && !isNullOrUndefined(this.footerTemplate)) { this.setFooterTemplate(); } else if (!isNullOrUndefined(this.buttons[0]) && !isNullOrUndefined(this.buttons[0].buttonModel)) { this.setButton(); } if (this.allowDragging && (!isNullOrUndefined(this.headerContent))) { this.setAllowDragging(); } attributes(this.element, { 'aria-modal': (this.isModal ? 'true' : 'false') }); if (this.isModal) { this.setIsModal(); } if (this.element.classList.contains(DLG_UTIL_ALERT) !== true && this.element.classList.contains(DLG_UTIL_CONFIRM) !== true && !isNullOrUndefined(this.element.parentElement)) { const parentEle: HTMLElement = this.isModal ? this.dlgContainer.parentElement : this.element.parentElement; this.refElement = this.createElement('div', { className: DLG_REF_ELEMENT }); parentEle.insertBefore(this.refElement, (this.isModal ? this.dlgContainer : this.element)); } if (!isNullOrUndefined(this.targetEle)) { if (this.isModal) { this.targetEle.appendChild(this.dlgContainer); } else { this.targetEle.appendChild(this.element); } } this.popupObj = new Popup(this.element, { height: this.height, width: this.width, zIndex: this.zIndex, relateTo: this.target, actionOnScroll: 'none', enableRtl: this.enableRtl, // eslint-disable-next-line open: (event: Event) => { const eventArgs: object = { container: this.isModal ? this.dlgContainer : this.element, element: this.element, target: this.target, preventFocus: false }; if (this.enableResize) { this.resetResizeIcon(); } this.trigger('open', eventArgs, (openEventArgs: {[key: string]: object} ) => { if (!openEventArgs.preventFocus) { this.focusContent(); } }); }, // eslint-disable-next-line close: (event: Event) => { if (this.isModal) { addClass([this.dlgOverlay], 'e-fade'); } this.unBindEvent(this.element); if (this.isModal) { this.dlgContainer.style.display = 'none'; } this.trigger('close', this.closeArgs); const activeEle: HTMLElement = document.activeElement as HTMLElement; if (!isNullOrUndefined(activeEle) && !isNullOrUndefined((activeEle).blur)) { activeEle.blur(); } if (!isNullOrUndefined(this.storeActiveElement) && !isNullOrUndefined(this.storeActiveElement.focus)) { this.storeActiveElement.focus(); } } }); this.positionChange(); this.setEnableRTL(); if (!this.isBlazorServerRender()) { addClass([this.element], DLG_HIDE); if (this.isModal) { this.setOverlayZindex(); } } if (this.visible) { this.show(); if (this.isModal) { const targetType: HTMLElement = this.getTargetContainer(this.target); if (targetType instanceof Element){ const computedStyle: CSSStyleDeclaration = window.getComputedStyle(targetType); if (computedStyle.getPropertyValue('direction') === 'rtl') { this.setPopupPosition(); } } } } else { if (this.isModal) { this.dlgOverlay.style.display = 'none'; } } this.initialRender = false; } private getTargetContainer(targetValue: HTMLElement | string): HTMLElement | null { let targetElement: null | HTMLElement = null; if (typeof targetValue === 'string') { if (targetValue.startsWith('#')) { targetElement = document.getElementById(targetValue.substring(1)); } else if (targetValue.startsWith('.')) { const elements: HTMLCollectionOf<Element> = document.getElementsByClassName(targetValue.substring(1)); targetElement = elements.length > 0 ? elements[0] as HTMLElement : null; } else { if (!((targetValue as any) instanceof HTMLElement) && (targetValue as any) !== document.body) { targetElement = document.querySelector(targetValue) as HTMLElement; } } } else if (targetValue instanceof HTMLElement) { targetElement = targetValue; } return targetElement; } private resetResizeIcon(): void { const dialogConHeight: number = this.getMinHeight(); if (this.targetEle.offsetHeight < dialogConHeight) { const className: string = this.enableRtl ? 'e-south-west' : 'e-south-east'; const resizeIcon: HTMLElement = this.element.querySelector('.' + className); if (!isNullOrUndefined(resizeIcon)) { resizeIcon.style.bottom = '-' + dialogConHeight.toString() + 'px'; } } } private setOverlayZindex(zIndexValue?: number): void { let zIndex: number; if (isNullOrUndefined(zIndexValue)) { zIndex = parseInt(this.element.style.zIndex, 10) ? parseInt(this.element.style.zIndex, 10) : this.zIndex; } else { zIndex = zIndexValue; } this.dlgOverlay.style.zIndex = (zIndex - 1).toString(); this.dlgContainer.style.zIndex = zIndex.toString(); } private positionChange(): void { if (this.isModal) { if (!isNaN(parseFloat(this.position.X as string)) && !isNaN(parseFloat(this.position.Y as string))) { this.setPopupPosition(); } else if ((!isNaN(parseFloat(this.position.X as string)) && isNaN(parseFloat(this.position.Y as string))) || (isNaN(parseFloat(this.position.X as string)) && !isNaN(parseFloat(this.position.Y as string)))) { this.setPopupPosition(); } else { this.element.style.top = '0px'; this.element.style.left = '0px'; this.dlgContainer.classList.add('e-dlg-' + this.position.X + '-' + this.position.Y); } } else { this.setPopupPosition(); } } private setPopupPosition(): void { this.popupObj.setProperties({ position: { X: this.position.X, Y: this.position.Y } }); } private setAllowDragging(): void { const handleContent: string = '.' + DLG_HEADER_CONTENT; if (!this.element.classList.contains( DLG_DRAG)){ this.dragObj = new Draggable(this.element, { clone: false, isDragScroll: true, abort: '.e-dlg-closeicon-btn', handle: handleContent, dragStart: (event: Object & BlazorDragEventArgs) => { this.trigger('dragStart', event, (dragEventArgs: Object & BlazorDragEventArgs) => { if (isBlazor()) { dragEventArgs.bindEvents(event.dragElement); } }); }, dragStop: (event: Object) => { if (this.isModal) { this.IsDragStop = true; if (!isNullOrUndefined(this.position)) { this.dlgContainer.classList.remove('e-dlg-' + this.position.X + '-' + this.position.Y); } // Reset the dialog position after drag completion. const targetType: HTMLElement = this.getTargetContainer(this.target); if (targetType instanceof Element) { const computedStyle: CSSStyleDeclaration = window.getComputedStyle(targetType); if (computedStyle.getPropertyValue('direction') === 'rtl') { this.element.style.position = 'absolute'; } else { this.element.style.position = 'relative'; } } else { this.element.style.position = 'relative'; } } this.trigger('dragStop', event); this.isModelResize = false; this.element.classList.remove(DLG_RESTRICT_LEFT_VALUE); this.updatePersistData(); }, drag: (event: Object) => { this.trigger('drag', event); } }); if (!isNullOrUndefined(this.targetEle)) { this.dragObj.dragArea = this.targetEle; } } } private setButton(): void { if (!this.isBlazorServerRender()) { this.buttonContent = []; this.btnObj = []; for (let i: number = 0; i < this.buttons.length; i++) { if (isNullOrUndefined(this.buttons[i as number].buttonModel)) { continue; } const buttonType: string = !isNullOrUndefined(this.buttons[i as number].type) ? this.buttons[i as number].type.toLowerCase() : 'button'; const btn: HTMLElement = this.createElement('button', { className: this.cssClass, attrs: {type: buttonType, tabindex: '0' }}); this.buttonContent.push(btn.outerHTML); } this.setFooterTemplate(); } let footerBtn: NodeListOf<Element>; for (let i: number = 0, childNodes: HTMLCollection = this.element.children; i < childNodes.length; i++) { if (childNodes[i as number].classList.contains(DLG_FOOTER_CONTENT)) { footerBtn = <NodeListOf<Element>>(childNodes[i as number] as HTMLElement).querySelectorAll('button'); } } for (let i: number = 0; i < this.buttons.length; i++) { if (isNullOrUndefined(this.buttons[i as number].buttonModel)) { continue; } if (!this.isBlazorServerRender()) { this.btnObj[i as number] = new Button(this.buttons[i as number].buttonModel); } if (!isNullOrUndefined(this.ftrTemplateContent) && footerBtn.length > 0) { if (typeof (this.buttons[i as number].click) === 'function') { EventHandler.add(footerBtn[i as number], 'click', this.buttons[i as number].click, this); } if (typeof (this.buttons[i as number].click) === 'object') { EventHandler.add(footerBtn[i as number], 'click', this.buttonClickHandler.bind(this, i), this); } } if (!this.isBlazorServerRen