Skip to content

Commit

Permalink
Support for multi-session dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
xirelogy committed Aug 24, 2021
1 parent 22a741b commit 5ec5d16
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 81 deletions.
153 changes: 153 additions & 0 deletions resources/js/Bootstrap/VeloBootstrapDialogSession.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import Xw from '@xirelogy/xw'
import veloBootstrapCommon from './VeloBootstrapCommon';
import veloPageMask from '../VeloPageMask';

const _sessions = new Array();


/**
* Get the next z-index
* @return {number}
*/
function getNextZIndex() {
return 1000 + (_sessions.length * 100);
}


/**
* Dialog session
*/
export default class VeloBootstrapDialogSession {

/**
* Constructor
* @param {HTMLElement} target Target dialog element
* @param {function()} onDismiss Notification on dismiss
* @param {object} options Showing options
*/
constructor(target, onDismiss, options) {
// Accept arguments
this._target = Xw.$.requires(target);
this._onDismiss = Xw.$.requires(onDismiss);
const _options = Xw.$.defaultable(options, {});
this._showMs = Xw.$.defaultable(_options.showMs, 300);

// Initial variables
this._dialogListeners = new Map();
this._isShown = false;
}


/**
* Show the dialog
* @return {Promise<void>}
*/
async showModal() {
// Check and protect the state
if (this._isShown) throw new Xw.InvalidStateError();
this._isShown = true;

// Create the mask
const baseZIndex = getNextZIndex();
this._maskId = Xw.$.randomId();
const mask = veloPageMask.create(this._maskId, {
zIndex: baseZIndex,
});
_sessions.push(this);

// Set current z-index
this._target.style.zIndex = baseZIndex + 50;

// Show the dialog by animation
const showArgs = {};
if (this._showMs !== null) showArgs.durationMs = this._showMs;

await Xw.axw.waitAll([
mask.show(),
veloBootstrapCommon.animateFadeIn(this._target, showArgs),
]);

// Create outer listener
const outerListener = async (ev) => {
ev.stopImmediatePropagation();
await this.hideModal();
if (this._onDismiss) this._onDismiss();
};
this._target.addEventListener('click', outerListener);
this._dialogListeners.set(this._target, outerListener);

// Handle target content (protective listener - prevent click propagation outwards)
const contentListener = (ev) => {
ev.stopImmediatePropagation();
};

const targetContents = this._target.getElementsByClassName('modal-content');
for (const targetContent of targetContents) {
targetContent.addEventListener('click', contentListener);
this._dialogListeners.set(targetContent, contentListener);
break;
}

// Handle close button
const closeListener = async (ev) => {
ev.stopImmediatePropagation();
await this.hideModal();
if (this._onDismiss) this._onDismiss();
};

const closeButton = this._target.querySelector('button.close');
if (closeButton) {
closeButton.addEventListener('click', closeListener);
this._dialogListeners.set(closeButton, closeListener);
}
}


/**
* Hide the dialog from modal
* @param {object} [options] Hiding options
* @return {Promise<void>}
*/
async hideModal(options) {
const _options = Xw.$.defaultable(options, {});
const _hideMs = Xw.$.defaultable(_options.hideMs, 250);

const hideArgs = {};
if (_hideMs !== null) hideArgs.durationMs = _hideMs;

const mask = veloPageMask.create(this._maskId);

// Animate hiding
await Xw.axw.waitAll([
veloBootstrapCommon.animateFadeOut(this._target, hideArgs),
mask.hide(),
]);

// Remove the listeners
if (this._dialogListeners.has(this._target)) {
this._target.removeEventListener('click', this._dialogListeners.get(this._target));
this._dialogListeners.delete(this._target);
}

const targetContents = this._target.getElementsByClassName('modal-content');
for (const targetContent of targetContents) {
if (this._dialogListeners.has(targetContent)) {
targetContent.removeEventListener('click', this._dialogListeners.get(targetContent));
this._dialogListeners.delete(targetContent);
}
break;
}

const closeButton = this._target.querySelector('button.close');
if (closeButton) {
if (this._dialogListeners.has(closeButton)) {
closeButton.removeEventListener('click', this._dialogListeners.get(closeButton));
this._dialogListeners.delete(closeButton);
}
}

// Remove mask element and pop session
mask.remove();
_sessions.pop();
}
}
95 changes: 14 additions & 81 deletions resources/js/Bootstrap/VeloBootstrapDialogs.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import Xw from '@xirelogy/xw';
import veloBootstrapCommon from './VeloBootstrapCommon';
import veloPageMask from '../VeloPageMask';
import VeloBootstrapDialogSession from './VeloBootstrapDialogSession';

const _l = Xw.i18n.init('VeloBootstrapDialogs');


/**
* ID of the mask
* @type {string}
*/
const MASK_ID = 'velo-bootstrap-modal-mask';

/**
* ID of the prompt
* @type {string}
Expand All @@ -31,11 +26,11 @@ const PROMPT_BUTTON_CANCEL_ID = 'velo-bootstrap-prompt-cancel-button';


/**
* Listeners for dialog events
* @type {Map<HTMLElement, function(Event)>}
* Dialog sessions
* @type {Map<HTMLElement, VeloBootstrapDialogSession>}
* @private
*/
const _dialogListeners = new Map();
const _sessions = new Map();


/**
Expand Down Expand Up @@ -111,51 +106,19 @@ export default class VeloBootstrapDialogs {
*/
async showModal(target, options) {

const _target = Xw.$.requires(target);
const _options = Xw.$.defaultable(options, {});
const _showMs = Xw.$.defaultable(_options.showMs, 300);
const _onDismiss = Xw.$.defaultable(_options.onDismiss);

const mask = veloPageMask.create(MASK_ID);
document.body.appendChild(target);

const showArgs = {};
if (_showMs !== null) showArgs.durationMs = _showMs;

await Xw.axw.waitAll([
mask.show(),
veloBootstrapCommon.animateFadeIn(target, showArgs),
]);

const outerListener = async (ev) => {
ev.stopImmediatePropagation();
await this.hideModal(target);
if (_onDismiss) _onDismiss();
};
target.addEventListener('click', outerListener);
_dialogListeners.set(target, outerListener);

const contentListener = (ev) => {
ev.stopImmediatePropagation();
};
const _onDismiss = Xw.$.defaultable(_options.onDismiss, null);

const closeListener = async (ev) => {
ev.stopImmediatePropagation();
await this.hideModal(target);
const _localDismiss = () => {
_sessions.delete(target);
if (_onDismiss) _onDismiss();
};

const targetContents = target.getElementsByClassName('modal-content');
for (const targetContent of targetContents) {
targetContent.addEventListener('click', contentListener);
_dialogListeners.set(targetContent, contentListener);
break;
}
const session = new VeloBootstrapDialogSession(target, _localDismiss, options);
_sessions.set(target, session);

const closeButton = target.querySelector('button.close');
if (closeButton) {
closeButton.addEventListener('click', closeListener);
_dialogListeners.set(closeButton, closeListener);
}
await session.showModal();
}


Expand All @@ -167,40 +130,10 @@ export default class VeloBootstrapDialogs {
*/
async hideModal(target, options) {

const _options = Xw.$.defaultable(options, {});
const _hideMs = Xw.$.defaultable(_options.hideMs, 250);

const mask = veloPageMask.create(MASK_ID);
if (!_sessions.has(target)) return;

const hideArgs = {};
if (_hideMs !== null) hideArgs.durationMs = _hideMs;

await Xw.axw.waitAll([
veloBootstrapCommon.animateFadeOut(target, hideArgs),
mask.hide(),
]);

if (_dialogListeners.has(target)) {
target.removeEventListener('click', _dialogListeners.get(target));
_dialogListeners.delete(target);
}

const targetContents = target.getElementsByClassName('modal-content');
for (const targetContent of targetContents) {
if (_dialogListeners.has(targetContent)) {
target.removeEventListener('click', _dialogListeners.get(targetContent));
_dialogListeners.delete(targetContent);
}
break;
}

const closeButton = target.querySelector('button.close');
if (closeButton) {
if (_dialogListeners.has(closeButton)) {
target.removeEventListener('click', _dialogListeners.get(closeButton));
_dialogListeners.delete(closeButton);
}
}
const session = _sessions.get(target);
await session.hideModal(options);
}


Expand Down

0 comments on commit 5ec5d16

Please sign in to comment.