@randstad-design/orbit-multitheme
Version:
multitheme Front-end code based on Randstad Human Forward components
263 lines (224 loc) • 6.62 kB
JavaScript
/**
* collapsible.js
* Script to expand and collapse elements
*/
import ElementHelpers from '../helpers/element-helpers';
/**
* Declare constants
*/
const attributeBase = 'data-rs-collapsible';
export class Collapsible {
constructor(element) {
this.element = element;
this.content = this.element.parentElement.querySelector(
`[${this.attributes.content}]`
);
this.triggerElement = this.element;
this.initialExpanded =
this.content.getAttribute(this.attributes.content) ===
this.states.expanded;
this.elementsToClose = ElementHelpers.getElementsByAttribute(
this.attributes.closeOnScroll
);
this.addEventHandlers();
}
/**
* Declare attribute constants
*/
get attributes() {
return {
content: `${attributeBase}-content`,
button: `${attributeBase}-button`,
navigation: 'navigation',
ariaExpanded: 'aria-expanded',
filter: 'filter',
closeOnOutsideClick: `${attributeBase}-close-on-outside-click`,
closeOnScroll: `${attributeBase}-close-on-scroll`
};
}
/**
* Declare classes
*/
get classes() {
return {
collapsibleTriggerExpanded: 'collapsible__trigger--expanded'
};
}
/**
* Declare state constants
*/
get states() {
return {
expanded: 'expanded'
};
}
/**
* Add event handlers
*/
addEventHandlers() {
const initialHeight = this.content.offsetHeight;
const width = window.innerWidth;
if (
this.element.getAttribute(attributeBase) === this.attributes.navigation
) {
this.triggerElement = this.element.querySelector(
`[${this.attributes.button}]`
);
}
if (this.triggerElement != null) {
['click', 'keydown'].forEach((evt) => {
this.triggerElement.addEventListener(evt, (e) => {
if (
(e.type === 'keydown' && (e.keyCode === 13 || e.keyCode === 32)) ||
e.type === 'click'
) {
e.preventDefault();
this.toggle(initialHeight);
}
});
});
}
this.isInitiallyExpanded(initialHeight);
this.isOverflown(this.content, this.element);
// on orientation change, recalculate content height
window.addEventListener('orientationchange', () => {
this.recalculateContentHeight();
this.isOverflown(this.content, this.element);
});
window.addEventListener('resize', () => {
// Fix: Chrome mobile browser triggers a resize because of appearing and disappearing navigation
if (window.innerWidth !== width) {
this.recalculateContentHeight();
this.isOverflown(this.content, this.element);
}
});
if (this.elementsToClose) {
window.addEventListener('scroll', () => {
this.simulateClickOnScroll();
});
}
}
/*
* function set the initial values
*/
setInitialValues() {}
/*
* function to get all the elements that needs to be closed on scroll and are currently expanded
*/
isExpandedElementToClose() {
if (!this.elementsToClose) {
return;
}
let elArr = [];
this.elementsToClose.forEach((element) => {
if (
element.classList.contains(this.classes.collapsibleTriggerExpanded) &&
element.hasAttribute(this.attributes.closeOnScroll)
) {
elArr.push(element);
}
});
return elArr;
}
/*
* if the element is expanded and needs to be closed on scroll, simulate a click
*/
simulateClickOnScroll() {
const elements = this.isExpandedElementToClose();
const windowScrollPos = window.scrollY;
elements.forEach((element) => {
const elementPos = element.parentElement.offsetTop;
if (windowScrollPos > elementPos) {
element.click();
}
});
}
toggle(initialHeight) {
const setHeight = this.initialExpanded ? '0' : initialHeight;
if (this.isTriggerExpanded) {
this.collapseTrigger();
this.collapseContent(setHeight);
} else {
const contentHeight = this.content.scrollHeight;
this.expandTrigger();
this.expandContent(contentHeight);
}
}
get isTriggerExpanded() {
return this.element.classList.contains(
this.classes.collapsibleTriggerExpanded
);
}
collapseTrigger() {
this.element.classList.remove(this.classes.collapsibleTriggerExpanded);
this.element.setAttribute(this.attributes.ariaExpanded, 'false');
}
collapseContent(initialHeight) {
this.content.style.maxHeight = initialHeight + 'px';
this.content.setAttribute(this.attributes.content, '');
this.content.setAttribute(this.attributes.ariaExpanded, 'false');
}
/**
* Handles element on expand - add class and set aria expanded attribute
*/
expandTrigger() {
this.element.classList.add(this.classes.collapsibleTriggerExpanded);
this.element.setAttribute(this.attributes.ariaExpanded, 'true');
}
/**
* Handles content on expand - set maxheight, expanded state en aria hidden attribute
*
* @param {number} contentHeight - height of content element
*/
expandContent(contentHeight) {
this.content.style.maxHeight = `${contentHeight + 30}px`; // add 30px to fit on IOS mobile (gives back different scrollHeight)
this.content.setAttribute(this.attributes.content, this.states.expanded);
this.content.setAttribute(this.attributes.ariaExpanded, 'true');
}
/**
* Handles the component when it is initially expanded
*/
isInitiallyExpanded() {
if (this.initialExpanded) {
this.expandTrigger();
}
}
/**
* Recalculate content height and set max height
*/
recalculateContentHeight() {
this.content = this.element.parentElement.querySelector(
`[${this.attributes.content}]`
);
this.contentHeight = this.content.scrollHeight;
// set maxheight on expanded content
if (
this.content.getAttribute(this.attributes.content) ===
this.states.expanded
) {
this.content.style.maxHeight = `${this.contentHeight}px`;
}
}
/**
* check if the content is not overflown
*/
isOverflown(content, trigger) {
if (trigger.getAttribute(attributeBase) === 'hide-trigger') {
const isOverflown =
content.scrollHeight > content.clientHeight ||
content.scrollWidth > content.clientWidth;
if (!isOverflown) {
trigger.classList.add('display-none');
}
if (isOverflown) {
trigger.classList.remove('display-none');
}
}
}
/**
* Get selector
*/
static getSelector() {
return `[${attributeBase}]`;
}
}