baseframe-js
Version:
A suite of useful Javascript plugins and functions to help with Front-end Development on websites
225 lines (224 loc) • 8 kB
JavaScript
import $ from 'cash-dom';
import Store from "./core/Store";
import UrlState from "./core/UrlState";
import trapFocus from './fn/trapFocus';
import { camelCase, getDataOptions, reflow } from './util/helpers';
import { noop } from './util/helpers';
import h from "./fn/hyperScript";
const VERSION = '1.2.0';
const EVENT_NAME = 'modal';
const DATA_NAME = 'Modal';
const DEFAULTS = {
enableEvent: 'click',
appendTo: document.body,
ariaLabelledby: null,
ariaLabel: null,
cssPrefix: 'modal',
closeBtnIconCss: 'ico i-close',
closeBtnLabel: 'Close',
closeOutDelay: 250,
focusInDelay: 0,
backDropClose: true,
fromDOM: false,
modalCss: null,
modalID: null,
src: '',
urlFilterType: 'hash',
historyType: 'replace',
locationFilter: 'modal',
loadLocation: true,
onOpenOnce: noop,
onOpen: noop,
onClose: noop,
afterClose: noop
};
const hasCb = (cb, modalObj) => {
if (cb && typeof cb === 'function') {
cb(modalObj);
}
};
export default class Modal {
element;
params;
modalID;
modalObj;
modalEvent;
trappedFocus;
enabledElem;
openedOnce;
static defaults = DEFAULTS;
static version = VERSION;
static pluginName = DATA_NAME;
constructor(element, options) {
const s = this;
s.element = element;
const dataOptions = getDataOptions(element, EVENT_NAME);
s.params = $.extend({}, Modal.defaults, options, dataOptions);
s.modalID = s.params.modalID;
s.modalObj = s.getModalObj();
s.modalEvent = EVENT_NAME + '_' + s.modalID;
s.trappedFocus;
s.enabledElem;
s.openedOnce = false;
s.handleEvents();
s.loadFromUrl();
Store(element, DATA_NAME, s);
return s;
}
static remove(element, plugin) {
$(element).each(function () {
const s = plugin || Store(this, DATA_NAME);
const params = s.params;
const { $backdrop, $closeBtn } = s.modalObj;
s.modalObj.show && s.modalObj.close();
$(s.element).off(`${s.params.enableEvent}.${s.modalEvent}`);
$closeBtn.off(`click.${s.modalEvent}Dismiss`);
if (params.backDropClose)
$backdrop.off(`click.${s.modalEvent}Dismiss`);
$(document)
.off(`keydown.${s.modalEvent}Dismiss`)
.off(`${s.modalEvent}Dismiss`);
$(window).off(`popstate.${s.modalEvent} ${s.modalEvent}`);
Store(this, DATA_NAME, null);
});
}
handleEvents() {
const s = this;
$(s.element).on(`${s.params.enableEvent}.${s.modalEvent}`, function (e) {
s.setDisplayAndEvents();
e.preventDefault();
});
}
setDisplayAndEvents() {
const s = this;
s.enableModal();
$(document).on(`keydown.${s.modalEvent}Dismiss`, function (e) {
const ekey = e.code || e.originalEvent.key; //cash-dom || jquery
if (ekey === 'Escape') {
s.close();
e.preventDefault();
}
});
$(document).on(`${s.modalEvent}Dismiss`, s.close);
}
getModalObj() {
const s = this, p = s.params, $closeBtn = h(`button.${p.cssPrefix}__btn-dismiss`, {
type: 'button',
'aria-label': p.closeBtnLabel
}).append(h(`i.${p.closeBtnIconCss}`)), $dialogContent = h(`div.${p.cssPrefix}__dialog-content`), $dialog = h(`div.${p.cssPrefix}__dialog`).append($closeBtn, $dialogContent), $backdrop = h(`div.${p.cssPrefix}__backdrop`), $modal = h(`div`, {
class: p.cssPrefix + (p.modalCss ? ' ' + p.modalCss : ''),
'aria-label': (p.ariaLabel || s.element.dataset.ariaLabel) || null,
'aria-labelledby': (p.ariaLabelledby || s.element.dataset.ariaLabelledby) || null,
id: s.modalID
}).append($backdrop, $dialog), $content = $(s.params.src || s.element.hash || s.element.dataset.modalSrc);
return {
$backdrop,
$content,
contentAppended: false,
$dialog,
$dialogContent,
$closeBtn,
id: s.modalID,
$modal,
close: () => s.close(),
show: false
};
}
enableModal() {
const s = this;
const { $backdrop, $closeBtn, $content, $modal } = s.modalObj;
const p = s.params;
s.enabledElem = document.activeElement;
if (p.fromDOM) {
$content.after(h(`span.${p.cssPrefix}-content-placemarker#${s.modalObj.id}_marker`));
}
if (!s.modalObj.contentAppended) {
s.modalObj.$dialogContent.append($content);
$.extend(s.modalObj, { contentAppended: true });
}
$(p.appendTo).append($modal);
// attach events after appended to DOM
$closeBtn.on(`click.${s.modalEvent}Dismiss`, () => s.close());
if (p.backDropClose)
$backdrop.on(`click.${s.modalEvent}Dismiss`, () => s.close());
hasCb(p.onOpen, s.modalObj);
if (!s.openedOnce) {
hasCb(p.onOpenOnce, s.modalObj);
s.openedOnce = true;
}
$modal.attr({
role: 'dialog',
'aria-modal': 'true'
});
reflow($modal[0]);
setTimeout(() => {
$modal.addClass(p.cssPrefix + '--show');
$.extend(s.modalObj, { show: true });
}, 0);
if (p.focusInDelay !== null) {
setTimeout(() => {
s.trappedFocus = trapFocus($modal, { nameSpace: camelCase(s.modalID) });
}, p.focusInDelay);
}
$(p.appendTo).addClass(p.cssPrefix + '-open').css({
overflow: 'hidden',
'padding-right': '0px'
});
if (p.urlFilterType !== 'none') {
UrlState.set(p.urlFilterType, p.locationFilter, s.modalID, p.historyType);
}
}
close() {
const s = this;
const { $backdrop, $closeBtn, $content, $modal } = s.modalObj;
const p = s.params;
hasCb(p.onClose, s.modalObj);
$modal.addClass(p.cssPrefix + '--dismissing');
$modal.removeClass(p.cssPrefix + '--show');
// detach events
$closeBtn.off(`click.${s.modalEvent}Dismiss`);
if (p.backDropClose)
$backdrop.off(`click.${s.modalEvent}Dismiss`);
$(document)
.off(`keydown.${s.modalEvent}Dismiss`)
.off(`${s.modalEvent}Dismiss`);
if (p.urlFilterType !== 'none') {
UrlState.set(p.urlFilterType, p.locationFilter, null, p.historyType);
}
setTimeout(() => {
$modal.attr({
role: 'dialog',
'aria-modal': ''
}).removeClass(p.cssPrefix + '--dismissing').css({
display: ''
});
$(p.appendTo).removeClass(p.cssPrefix + '-open').css({
overflow: '',
'padding-right': ''
});
if (p.focusInDelay !== null) {
s.trappedFocus.remove();
}
if (s.enabledElem && s.enabledElem instanceof HTMLElement) {
s.enabledElem.focus();
}
if (p.fromDOM) {
$('#' + s.modalObj.id + '_marker').after($content).remove();
$.extend(s.modalObj, { contentAppended: false });
}
hasCb(p.afterClose, s.modalObj);
$modal.remove();
$.extend(s.modalObj, { show: false });
}, p.closeOutDelay);
}
loadFromUrl() {
const s = this;
const p = s.params;
if (p.locationFilter !== null && p.loadLocation && p.urlFilterType !== 'none') {
const filterEl = UrlState.get(p.urlFilterType, p.locationFilter);
if (filterEl === s.modalID) {
s.modalObj.show ? s.close() : s.setDisplayAndEvents();
}
}
}
}