@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
text/typescript
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