import {
bodyLock,
consoleInfo,
focusLoop,
getClosest,
getNodes,
trigger,
uniqueId,
} from '@gravityforms/utils';
/**
* @function dialogTemplate
* @description The template function that returns html for the dialog. Options below are passed from constructor and
* described there.
*
* @since 1.0.6
*
* @param {object} options The options for the dialog.
* @param {string} options.alertButtonText The text for the alert button.
* @param {string} options.cancelButtonText The text for the cancel button.
* @param {string} options.closeButtonClasses The classes for the close button. Space seperated string.
* @param {string} options.closeButtonAriaLabel The aria label for the close button.
* @param {string} options.closeButtonSize The size for the close button.
* @param {string} options.closeButtonTitle The title for the close button.
* @param {string} options.closeButtonType The type for the close button.
* @param {string} options.confirmButtonAttributes The attributes for the confirm button. Space seperated string.
* @param {string} options.confirmButtonIcon The icon for the confirm button.
* @param {string} options.confirmButtonText The text for the confirm button.
* @param {string} options.content The content for the dialog. HTML allowed.
* @param {string} options.id The id for the dialog.
* @param {boolean} options.maskBlur Whether to blur the background when the dialog is open.
* @param {string} options.maskClasses The classes for the mask. Space seperated string.
* @param {boolean} options.maskTheme Background mask theme, `none`, `light` or `dark`.
* @param {string} options.mode The mode for the dialog. `alert`, `confirm` or `dialog`.
* @param {string} options.position The position for the dialog. `center`, `top` or `bottom`.
* @param {string} options.title The title for the dialog.
* @param {string} options.titleIcon The icon for the title.
* @param {string} options.titleIconColor The color for the title icon.
* @param {string} options.wrapperClasses The classes for the wrapper. Space seperated string.
* @param {number} options.zIndex The z-index for the dialog.
*
* @return string
* @example
* import { dialogTemplate } from '@gravityforms/components/html/admin/modules/Dialog';
*
* function Example() {
* const dialogHTML = dialogTemplate( options );
* document.body.insertAdjacentHTML( 'beforeend', dialogHTML );
* }
*
*/
export const dialogTemplate = ( {
alertButtonText = '',
cancelButtonText = '',
closeButtonAriaLabel = '',
closeButtonClasses = '',
closeButtonSize = 'md',
closeButtonTitle = '',
closeButtonType = 'circular',
confirmButtonAttributes = '',
confirmButtonIcon = '',
confirmButtonText = '',
content = '',
id = '',
maskBlur = true,
maskClasses = '',
maskTheme = 'none',
mode = '',
position = 'fixed',
title = '',
titleIcon = '',
titleIconColor = '',
wrapperClasses = '',
zIndex = 10,
} ) =>
`
<div class="${ maskClasses } gform-dialog__mask--position-${ position } gform-dialog__mask--theme-${ maskTheme }${ maskBlur ? ` gform-dialog__mask--blur` : '' }" data-js="gform-dialog-mask" style="z-index: ${ zIndex };">
<article
id="${ id }"
class="${ wrapperClasses }"
data-js="${ id }"
>
<button
class="gform-dialog__close ${ closeButtonClasses } gform-button ${ ( closeButtonType === 'circular' ) ? 'gform-button--secondary' : '' } gform-button--${ closeButtonType } gform-button--size-${ closeButtonSize }"
data-js="gform-dialog-close"
style="z-index: ${ zIndex + 1 };"
title="${ closeButtonTitle }"
aria-label="${ closeButtonAriaLabel }"
>
<span class="gform-button__icon gform-common-icon gform-common-icon--x"></span>
</button>
${ title ? '<header class="gform-dialog__head" data-js="gform-dialog-header">' : '' }
${ title ? `<h5 class="gform-dialog__title${ titleIcon ? ` gform-dialog__title--has-icon` : '' }">${ titleIcon ? `<span class="gform-dialog__title-icon gform-icon gform-icon--${ titleIcon }"${ titleIconColor ? ` style="color: ${ titleIconColor };"` : '' }></span>` : '' }${ title }</h5>` : '' }
${ title ? '</header>' : '' }
<div class="gform-dialog__content" data-js="gform-dialog-content">${ content }</div>
${ ( mode === 'dialog' || mode === 'alert' ) ? '<footer class="gform-dialog__footer" data-js="gform-dialog-footer">' : '' }
${ mode === 'dialog' ? `
<button
class="gform-dialog__cancel gform-button gform-button--white"
data-js="gform-dialog-cancel"
>
${ cancelButtonText }
</button>
<button
id="${ id }-dialog-confirm-button"
class="gform-dialog__confirm gform-button gform-button--primary-new${ confirmButtonIcon ? ` gform-button--icon-leading` : '' }"
data-js="gform-dialog-confirm"
${ confirmButtonAttributes }
>
${ confirmButtonIcon ? `<span class="gform-button__icon gform-icon gform-icon--${ confirmButtonIcon }"></span>` : '' }${ confirmButtonText }
</button>
` : '' }
${ mode === 'alert' ? `
<button
class="gform-dialog__alert gform-button gform-button--primary-new"
data-js="gform-dialog-alert"
>
${ alertButtonText }
</button>
` : '' }
${ ( mode === 'dialog' || mode === 'alert' ) ? '</footer>' : '' }
</article>
</div>
`;
/**
* @class Dialog
* @description A dialog component to house modals, dialogs or prompts.
*
* @since 1.0.6
*
* @borrows dialogTemplate as dialogTemplate
*
* @param {object} options The options for the dialog.
* @param {string} options.alertButtonText The text for the alert button.
* @param {string} options.cancelButtonText The text for the cancel button.
* @param {string} options.closeButtonClasses The classes for the close button. Space seperated string.
* @param {string} options.closeButtonTitle The title for the close button.
* @param {string} options.confirmButtonAttributes The attributes for the confirm button. Space seperated string.
* @param {string} options.confirmButtonIcon The icon for the confirm button.
* @param {string} options.confirmButtonText The text for the confirm button.
* @param {string} options.content The content for the dialog. HTML allowed.
* @param {string} options.id The id for the dialog.
* @param {boolean} options.lockBody Whether to lock the body behind the dialog
* @param {boolean} options.maskBlur Whether to blur the background when the dialog is open.
* @param {string} options.maskClasses The classes for the mask. Space seperated string.
* @param {boolean} options.maskTheme Background mask theme, `none`, `light` or `dark`.
* @param {string} options.mode The mode for the dialog. `alert`, `confirm` or `dialog`.
* @param {Function} options.onClose Function to fire when closed
* @param {Function} options.onConfirm Function to fire when confirm button is clicked in dialog mode
* @param {Function} options.onOpen Function to fire when opened
* @param {string} options.position The position for the dialog. `center`, `top` or `bottom`.
* @param {boolean} options.renderOnInit Render on initialization?
* @param {string} options.target Append target for the dialog and its mask. Uses querySelectorAll
* @param {string} options.title The title for the dialog.
* @param {string} options.titleIcon The icon for the title.
* @param {string} options.titleIconColor The color for the title icon.
* @param {string} options.triggers The optional selector[s] of the trigger that shows it
* @param {string} options.wrapperClasses The classes for the wrapper. Space seperated string.
* @param {number} options.zIndex The z-index for the dialog.
*
* @return {Class} The class instance.
* @example
* import Dialog from '@gravityforms/components/html/admin/modules/Dialog';
*
* function Example() {
* const dialogInstance = new Dialog( {
* id: 'example-dialog',
* renderOnInit: false,
* target: '#example-target',
* targetPosition: 'beforeend',
* } );
*
* // Some time later we can render it. This is only done if we set renderOnInit to false.
* // If true it will render on initialization.
* dialogInstance.init();
* }
*
*/
export default class Dialog {
constructor( options = {} ) {
this.options = {};
Object.assign(
this.options,
{
alertButtonText: '', // text for the ok button if in alert mode
animationDelay: 250, // animationDelay for the dialog
cancelButtonText: '', // text for the cancel button if in dialog mode
closeButtonClasses: 'gform-dialog__close', // classes for the close button
closeButtonTitle: '', // text for the close button title
closeOnMaskClick: true, // does clicking the mask close the dialog?
closeOnConfirmClick: true, // does clicking the confirm button close the dialog?
confirmButtonAttributes: '', // arbitrary additional attributes for the confirm button.
confirmButtonIcon: '', // if in dialog mode, the optional confirmation button icon before the button text
confirmButtonText: '', // the text for teh confirmation button if in dialog mode
id: uniqueId( 'dialog' ), // id for the dialog
lockBody: false, // whether to lock the body behind the dialog
maskBlur: true, // whether the background mask has a blur effect or not
maskClasses: 'gform-dialog__mask', // classes for the mask
maskTheme: 'light', // background mask theme, none, light or dark
mode: '', // mode for the dialog: can be modal, alert or dialog
onClose: () => {}, // function to fire when closed
onConfirm: () => {}, // function to fire when confirm button is clicked in dialog mode
onOpen: () => {}, // function to fire when opened
position: 'fixed', // fixed or absolute position?
renderOnInit: true, // render on initialization?
target: 'body', // append target for the dialog and its mask. Uses querySelectorAll
title: '', // the optional title for the dialog
titleIcon: '', // the optional title icon for the dialog
titleIconColor: '', // the optional title icon color for the dialog
triggers: '', // the optional selector[s] of the trigger that shows it
wrapperClasses: 'gform-dialog', // classes for the wrapper
zIndex: 10, // z-index for the dialog
},
options
);
/**
* @event gform/dialog/pre_init
* @type {object}
* @description Fired before the component has started any internal init functions. A great chance to augment the options.
*
* @since 1.1.16
*
* @property {object} instance The Component class instance.
*/
trigger( { event: 'gform/dialog/pre_init', native: false, data: { instance: this } } );
this.elements = {};
this.state = {
open: false,
};
if ( this.options.renderOnInit ) {
this.init();
}
}
/**
* @memberof Dialog
* @description Opens the dialog and fires the onOpen function that can be passed in.
*
* @since 1.0.6
*
* @return {void}
*/
showDialog() {
const { mask } = this.elements;
if ( this.options.lockBody ) {
bodyLock.lock();
}
this.options.onOpen();
mask.classList.add( 'gform-dialog--anim-in-ready' );
window.setTimeout( () => {
mask.classList.add( 'gform-dialog--anim-in-active' );
}, 25 );
this.elements.closeButton.focus();
this.state.open = true;
}
/**
* @memberof Dialog
* @description Closes the dialog and fires the onClose function that can be passed in. Can be used by external
* developers by firing the method on the instance. Also closes all instances if the event 'gform/dialog/close-all' is
* fired on the document.
*
* @since 1.0.6
*
* @return {void}
*/
closeDialog = () => {
const { mask } = this.elements;
const { animationDelay, onClose } = this.options;
if ( ! mask.classList.contains( 'gform-dialog--anim-in-active' ) ) {
return;
}
mask.classList.remove( 'gform-dialog--anim-in-active' );
window.setTimeout( () => {
mask.classList.remove( 'gform-dialog--anim-in-ready' );
}, animationDelay );
this.state.open = false;
if ( this.elements.activeTrigger ) {
this.elements.activeTrigger.focus();
}
if ( this.options.lockBody ) {
bodyLock.unlock();
}
onClose();
};
/**
* @param e
* @memberof Dialog
* @description Closes all instances except the one that gets passed the activeId when the event 'gform/dialog/close' is
* fired on the document.
*
* @since 1.0.6
*
* @return {void}
*/
maybeCloseDialog = ( e ) => {
if ( e.detail?.activeId === this.options.id ) {
return;
}
this.closeDialog();
};
/**
* @memberof Dialog
* @description Handles accessibility focus looping on the dialog using the focusLoop util.
*
* @since 1.0.6
*
* @param {PointerEvent} e The event object.
*
* @return {void}
*/
handleKeyEvents = ( e ) =>
focusLoop(
e,
this.elements.activeTrigger,
this.elements.dialog,
this.closeDialog
);
/**
* @memberof Dialog
* @description Handles opening/closing the dialog on a trigger click.
*
* @since 1.0.6
*
* @param {PointerEvent} e The event object.
*
* @return {void}
*/
handleTriggerClick = ( e ) => {
this.elements.activeTrigger = e.target;
if ( this.state.open ) {
this.closeDialog();
} else {
this.showDialog();
}
};
/**
* @memberof Dialog
* @description Handles closing the dialog on mask click.
*
* @since 1.0.6
*
* @param {PointerEvent} e The event object.
*
* @return {void}
*/
handleMaskClick = ( e ) => {
if ( e.target.id === this.options.id || getClosest( e.target, `[data-js="${ this.options.id }"]` ) ) {
return;
}
this.closeDialog();
};
/**
* @memberof Dialog
* @description Handles a confirm button click and closes if the option is true.
*
* @since 1.0.6
*
* @param {PointerEvent} e The event object.
*
* @return {void}
*/
handleConfirm = ( e ) => {
const { onConfirm } = this.options;
trigger( { event: 'gform/dialog/confirm', native: false, data: { instance: this, button: e.target } } );
if ( this.options.closeOnConfirmClick ) {
this.closeDialog();
}
onConfirm();
};
/**
* @memberof Dialog
* @description Stores useful HTMLElements on the instance in the elements namespace after render
*
* @since 1.0.6
*
* @return {void}
*/
storeElements() {
const dialog = getNodes( this.options.id )[ 0 ];
this.elements = {
activeTrigger: null,
alertButton: getNodes( 'gform-dialog-alert', false, dialog )[ 0 ],
content: getNodes( 'gform-dialog-content', false, dialog )[ 0 ],
cancelButton: getNodes( 'gform-dialog-cancel', false, dialog )[ 0 ],
closeButton: getNodes( 'gform-dialog-close', false, dialog )[ 0 ],
confirmButton: getNodes( 'gform-dialog-confirm', false, dialog )[ 0 ],
dialog,
footer: getNodes( 'gform-dialog-footer', false, dialog )[ 0 ],
header: getNodes( 'gform-dialog-header', false, dialog )[ 0 ],
mask: dialog.parentNode,
triggers: this.options.triggers ? getNodes( this.options.triggers, true, document, true ) : [],
};
}
/**
* @memberof Dialog
* @description Renders the component into the dom.
*
* @since 1.0.6
*
* @return {void}
*/
render() {
const { target } = this.options;
const renderTarget = getNodes( target, false, document, true )[ 0 ];
renderTarget.insertAdjacentHTML(
'beforeend',
dialogTemplate( this.options )
);
}
/**
* @memberof Dialog
* @description Binds the events for this component.
*
* @since 1.0.6
*
* @return {void}
*/
bindEvents() {
this.elements.dialog.addEventListener( 'keydown', this.handleKeyEvents );
this.elements.closeButton.addEventListener( 'click', this.closeDialog );
if ( this.options.triggers ) {
getNodes( this.options.triggers, true, document, true )
.forEach( ( t ) => t.addEventListener( 'click', this.handleTriggerClick ) );
}
if ( this.options.closeOnMaskClick ) {
this.elements.mask.addEventListener( 'click', this.handleMaskClick );
}
if ( this.elements.alertButton ) {
this.elements.alertButton.addEventListener( 'click', this.closeDialog );
}
if ( this.elements.cancelButton ) {
this.elements.cancelButton.addEventListener( 'click', this.closeDialog );
}
if ( this.elements.confirmButton ) {
this.elements.confirmButton.addEventListener( 'click', this.handleConfirm );
}
document.addEventListener( 'gform/dialog/close', this.maybeCloseFlyout );
document.addEventListener( 'gform/dialog/close-all', this.closeFlyout );
}
/**
* @memberof Dialog
* @description Initialize the component.
*
* @fires gform/dialog/post_render
*
* @since 1.1.16
*
* @return {void}
*/
init() {
this.render();
this.storeElements();
this.bindEvents();
/**
* @event gform/dialog/post_render
* @type {object}
* @description Fired when the component has completed rendering and all class init functions have completed.
*
* @since 1.0.6
*
* @property {object} instance The Component class instance.
*/
trigger( { event: 'gform/dialog/post_render', native: false, data: { instance: this } } );
consoleInfo( `Gravity Forms Admin: Initialized dialog component.` );
}
}