baseframe-js
Version:
A suite of useful Javascript plugins and functions to help with Front-end Development on websites
184 lines (183 loc) • 7.28 kB
JavaScript
import $ from 'cash-dom';
import { getDataOptions, noop } from './util/helpers';
import smoothScroll from './fn/smoothScroll';
import transition from "./fn/transition";
import UrlState from "./core/UrlState";
import Store from "./core/Store";
;
const VERSION = "4.0.0";
const DATA_NAME = 'Collapse';
const EVENT_NAME = 'collapse';
const DEFAULTS = {
cssPrefix: 'collapse',
toggleDuration: 500,
toggleGroup: false,
moveToTopOnOpen: false,
moveToTopOffset: 0,
scrollSpeed: 100,
urlFilterType: 'hash',
historyType: 'replace',
locationFilter: null,
loadLocation: true,
afterOpen: noop,
afterClose: noop,
afterInit: noop
};
export default class Collapse {
$element;
params;
toggling;
$btnElems;
$activeItem;
initLoaded;
static defaults = DEFAULTS;
static version = VERSION;
static pluginName = DATA_NAME;
#transition = transition();
constructor(element, options, index) {
const s = this;
s.$element = $(element);
const dataOptions = getDataOptions(element, EVENT_NAME);
s.params = $.extend({}, Collapse.defaults, options, dataOptions);
s.toggling = false;
s.$btnElems = s.$element.find(`.${s.params.cssPrefix}__btn`).attr({ 'aria-expanded': 'false' });
s.$activeItem = null;
s.initLoaded = false;
// init
s.loadFromUrl();
s.handleEvents();
s.params.afterInit(s.$element);
Store(element, DATA_NAME, s);
return s;
}
static remove(element, plugin) {
$(element).each(function () {
const s = plugin || Store(this, DATA_NAME);
s.$element.off(`click.${EVENT_NAME} ${EVENT_NAME}`);
$(window).off(`popstate.${EVENT_NAME}`);
// delete the Store item
Store(this, DATA_NAME, null);
});
}
handleEvents() {
const s = this;
const { cssPrefix } = s.params;
s.$element.on(`click.${EVENT_NAME} ${EVENT_NAME}`, `.${cssPrefix}__btn`, function (e) {
const elemId = $(this).attr('aria-controls') || this.hash.substring(1);
s.toggleAction(elemId);
e.preventDefault();
});
$(window).on(`popstate.${EVENT_NAME}`, (e) => {
if (s.params.historyType === 'push') {
s.loadFromUrl();
s.initLoaded = true;
e.preventDefault();
}
});
}
loadFromUrl() {
const s = this;
const p = s.params;
const loadElem = (filterEl) => {
const cssOpen = `${p.cssPrefix}--open`;
const $tryElem = s.$element.find('#' + filterEl);
if ($tryElem.length) {
s.$activeItem = $tryElem;
s.$activeItem.addClass(cssOpen);
s.$btnElems
.filter((i, el) => $(el).attr('aria-controls') === filterEl)
.attr({ 'aria-expanded': 'true' });
}
};
if (p.locationFilter !== null && p.loadLocation && p.urlFilterType !== 'none') {
const filterEl = UrlState.get(p.urlFilterType, p.locationFilter);
const cssOpen = `${p.cssPrefix}--open`;
s.$element.find(`.${p.cssPrefix}__body.${cssOpen}`).removeClass(cssOpen);
s.$btnElems.attr({ 'aria-expanded': 'false' });
if (filterEl) {
if (Array.isArray(filterEl)) {
filterEl.forEach(loadElem);
}
else {
loadElem(filterEl);
}
}
}
}
toggleAction(currElemID) {
const s = this;
if (s.toggling || currElemID === null)
return;
const { cssPrefix } = s.params;
const p = s.params;
s.$activeItem = s.$element.find('#' + currElemID);
if (s.$activeItem.length) {
const cssOpen = `${cssPrefix}--open`, cssToggling = `${cssPrefix}--toggling`, cssOpening = `${cssPrefix}--opening`, cssClosing = `${cssPrefix}--closing`, cssBodyOpen = `.${cssPrefix}__body.${cssOpen}`;
const $currOpenItems = s.$element.find(cssBodyOpen);
const $itemsToClose = $currOpenItems.filter((i, el) => p.toggleGroup || el.id === currElemID);
const activeAlreadyOpen = s.$activeItem.hasClass(cssOpen);
$itemsToClose.each(function () {
$(this).css({ height: this.scrollHeight });
});
s.#transition(() => {
s.toggling = true;
s.$btnElems.each(function () {
const $btn = $(this);
const isCurrent = $btn.attr('aria-controls') === currElemID;
const expanded = isCurrent && $btn.attr('aria-expanded') === 'false';
if (p.toggleGroup) {
$btn.attr({ 'aria-expanded': expanded + '' });
}
else {
if (isCurrent) {
$btn.attr({ 'aria-expanded': expanded + '' });
}
}
});
$itemsToClose
.removeClass(cssOpen)
.addClass(`${cssToggling} ${cssClosing}`)
.css({ height: 0 });
if (!activeAlreadyOpen) {
s.$activeItem
.addClass(`${cssToggling} ${cssOpening}`)
.css({ height: s.$activeItem[0].scrollHeight });
}
}, () => {
s.toggling = false;
$itemsToClose
.removeClass(`${cssToggling} ${cssClosing}`)
.css({ height: null });
s.params.afterClose(s.$btnElems, $itemsToClose);
if (!activeAlreadyOpen) {
s.$activeItem
.addClass(cssOpen)
.removeClass(`${cssToggling} ${cssOpening}`)
.css({ height: null });
}
// Update History in URL
if (p.urlFilterType !== 'none') {
const paramList = [...s.$element.find(cssBodyOpen)].map((el) => el.id);
const paramVal = paramList.length === 1 ? paramList[0] : paramList.length > 0 ? paramList : null;
UrlState.set(p.urlFilterType, p.locationFilter, paramVal, p.historyType);
}
s.params.afterOpen(s.$btnElems, s.$activeItem);
s.moveToTopOnOpen();
}, s.params.toggleDuration);
}
}
moveToTopOnOpen() {
const s = this;
const { cssPrefix, moveToTopOffset, moveToTopOnOpen, scrollSpeed } = s.params;
if (s.$activeItem) {
const $item = s.$activeItem.parent(`.${cssPrefix}__item`) || s.$activeItem;
if ($item.length && moveToTopOnOpen) {
// get the compiler to stop from throwing
// an error it'll never throw by setting to 'any'
const elemOffsetTop = $item.offset().top;
const top = elemOffsetTop - moveToTopOffset;
smoothScroll(top, scrollSpeed);
}
}
}
}