UNPKG

just-add-juice

Version:

A responsive frontend framework with modular components.

497 lines (427 loc) 21 kB
/* ======================================================================== JUICE -> COMPONENTS -> ACCORDION ======================================================================== */ ;(function (root, factory) { // Set the plugin name const plugin_name = 'Accordion'; // Check if instantiation should be via amd, commonjs or the browser if (typeof define === 'function' && define.amd) { define([], factory(plugin_name)); } else if (typeof exports === 'object') { module.exports = factory(plugin_name); } else { root[plugin_name] = factory(plugin_name); } }((window || module || {}), function(plugin_name) { // Use strict mode 'use strict'; // Create an empty plugin object const plugin = {}; // Set the plugin defaults const defaults = { openMultipleItems: false, toggle: 'slide', toggleDuration: 200, callbackInitializeBefore: () => { console.log('Accordion: callbackInitializeBefore'); }, callbackInitializeAfter: () => { console.log('Accordion: callbackInitializeAfter'); }, callbackToggleBefore: () => { console.log('Accordion: callbackToggleBefore'); }, callbackToggleAfter: () => { console.log('Accordion: callbackToggleAfter'); }, callbackRefreshBefore: () => { console.log('Accordion: callbackRefreshBefore'); }, callbackRefreshAfter: () => { console.log('Accordion: callbackRefreshAfter'); }, callbackDestroyBefore: () => { console.log('Accordion: callbackDestroyBefore'); }, callbackDestroyAfter: () => { console.log('Accordion: callbackDestroyAfter'); } }; /** * Constructor. * @param {element} element The initialized element. * @param {object} options The plugin options. * @return {void} */ function Plugin(element, options) { // Set the plugin object plugin.this = this; plugin.name = plugin_name; plugin.element = element; plugin.defaults = defaults; plugin.options = options; plugin.settings = Object.assign({}, defaults, options); // Initialize the plugin plugin.this.initialize(); } /** * Click event handler to toggle an accordion item. * @param {object} event The event object. * @return {void} */ const clickAccordionToggleEventHandler = (event) => { // Check if the event target is the toggle or a descendant of the toggle if (isTargetSelector(event.target, 'class', 'js-accordion-toggle')) { // Prevent the default action event.preventDefault(); // Set the toggle const $toggle = event.target; // Set the item and accordion const $item = $toggle.closest('.accordion__item'); const $accordion = $item.closest('.accordion'); // Set the accordion type const open_multiple_items = $accordion.dataset.accordionOpenMultipleItems || plugin.settings.openMultipleItems; // Check if opening multiple items is not allowed if (!open_multiple_items) { // Create an empty item siblings array const $siblings = []; // Set the first item sibling let $sibling = $item.parentNode.firstChild; // Start a while loop for the item siblings existance while ($sibling) { // Check if the item sibling is an element and not the current item if ($sibling.nodeType === 1 && $sibling !== $item) { // Add the item sibling to the siblings array $siblings.push($sibling); } // Set the next item sibling $sibling = $sibling.nextSibling } // Cycle through all of the item siblings $siblings.forEach(($sibling) => { // Check if the item sibling has the is expanded state hook if ($sibling.classList.contains('is-expanded')) { // Toggle the item sibling plugin.this.toggle($sibling); } }); } // Toggle the accordion item plugin.this.toggle($item); } }; /** * Check if an event target is a target selector or a descendant of a target selector. * @param {element} target The event target. * @param {string} attribute The event target attribute to check. * @param {string} selector The id/class selector. * @return {bool} True if event target, false otherwise. */ const isTargetSelector = (target, attribute, selector) => { // Check if the target is an element node if (target.nodeType !== Node.ELEMENT_NODE) { // Return false return false; } // Start a switch statement for the attribute switch (attribute) { // Default default: // Return false return false; // Class case 'class': // Return true if event target, false otherwise return ((target.classList.contains(selector)) || target.closest(`.${selector}`)); // Id case ('id'): // Return true if event target, false otherwise return ((target.id == selector) || target.closest(`#${selector}`)); } }; /** * Public variables and methods. * @type {object} */ Plugin.prototype = { /** * Initialize the plugin. * @param {bool} silent Suppress callbacks. * @return {void} */ initialize: (silent = false) => { // Destroy the existing initialization silently plugin.this.destroySilently(); // Check if the callbacks should not be suppressed if (!silent) { // Call the initialize before callback plugin.settings.callbackInitializeBefore.call(); } // Set the accordions const $accordions = document.querySelectorAll(plugin.element); // Check if any accordions exist if ($accordions) { // Cycle through all of the accordions $accordions.forEach(($accordion) => { // Set the items const $items = $accordion.querySelectorAll('.accordion__item'); // Check if any items exist if ($items) { // Cycle through all of the items $items.forEach(($item, index) => { // Set the open multiple items const open_multiple_items = $accordion.dataset.accordionOpenMultipleItems || plugin.settings.openMultipleItems; // Check if the item doesn't have the expanded or collapsed state hooks if (!$item.classList.contains('is-expanded') && !$item.classList.contains('is-collapsed')) { // Set the item expanded/collapsed state hooks $item.classList.add('is-collapsed'); } // Check if opening multiple items is not allowed if (!open_multiple_items) { // Check if the item has the expanded state hook if ($item.classList.contains('is-expanded')) { // Check if this is the first iterated item in the loop if (index === 0) { // Set the item expanded/collapsed state hooks $item.classList.add('is-expanded'); $item.classList.remove('is-collapsed'); } else { // Set the item expanded/collapsed state hooks $item.classList.add('is-collapsed'); $item.classList.remove('is-expanded'); } } } }); } }); } // Add a click event handler to toggle an accordion item document.addEventListener('click', clickAccordionToggleEventHandler); // Check if the callbacks should not be suppressed if (!silent) { // Call the initialize after callback plugin.settings.callbackInitializeAfter.call(); } }, /** * Toggle an accordion item. * @param {element} $item The accordion item. * @param {bool} silent Suppress callbacks. * @return {void} */ toggle: ($item, silent = false) => { // Check if the item exists and isn't animating if ($item && !$item.classList.contains('is-animating')) { // Set the accordion and body const $accordion = $item.closest('.accordion'); const $body = $item.querySelector('.accordion__body'); // Check if the item exists and isn't animating if (!$accordion.classList.contains('is-animating')) { // Check if the callbacks should not be suppressed if (!silent) { // Call the toggle before callback plugin.settings.callbackToggleBefore.call(); } // Set the toggle settings const toggle = $accordion.dataset.accordionToggle || plugin.settings.toggle; const toggle_duration = $accordion.dataset.accordionToggleDuration || plugin.settings.toggleDuration; // Start a switch statement for the toggle animation switch (toggle) { // Default default: // Set the item expanded/collapsed state hooks $item.classList.toggle('is-expanded'); $item.classList.toggle('is-collapsed'); // Check if the callbacks should not be suppressed if (!silent) { // Call the toggle after callback plugin.settings.callbackToggleAfter.call(); } // Break the switch break; // Slide case 'slide': // Set the accordion animation classes $accordion.classList.remove('has-animated'); $accordion.classList.add('is-animating'); // Set the item animation classes $item.classList.remove('has-animated'); $item.classList.add('is-animating'); // Check if the accordion item has the expanded state hook if ($item.classList.contains('is-expanded')) { // Slide the body up Velocity($body, 'slideUp', { complete: () => { // Set the accordion animation classes $accordion.classList.remove('is-animating'); $accordion.classList.add('has-animated'); // Set the item animation classes $item.classList.remove('is-animating'); $item.classList.add('has-animated'); // Set the item expanded/collapsed state hooks $item.classList.toggle('is-expanded'); $item.classList.toggle('is-collapsed'); // Check if the callbacks should not be suppressed if (!silent) { // Call the toggle after callback plugin.settings.callbackToggleAfter.call(); } }, duration: toggle_duration }); } // Check if the item has the collapsed state hook if ($item.classList.contains('is-collapsed')) { // Set the item expanded/collapsed state hooks $item.classList.toggle('is-expanded'); $item.classList.toggle('is-collapsed'); // Slide the body down Velocity($body, 'slideDown', { complete: () => { // Set the accordion animation classes $accordion.classList.remove('is-animating'); $accordion.classList.add('has-animated'); // Set the item animation classes $item.classList.remove('is-animating'); $item.classList.add('has-animated'); // Check if the callbacks should not be suppressed if (!silent) { // Call the toggle after callback plugin.settings.callbackToggleAfter.call(); } }, duration: toggle_duration }); } // Break the switch break; // Fade case 'fade': // Set the accordion animation classes $accordion.classList.remove('has-animated'); $accordion.classList.add('is-animating'); // Set the item animation classes $item.classList.remove('has-animated'); $item.classList.add('is-animating'); // Check if the item has the expanded state hook if ($item.classList.contains('is-expanded')) { // Fade the body out Velocity($body, 'fadeOut', { complete: () => { // Set the accordion animation classes $accordion.classList.remove('is-animating'); $accordion.classList.add('has-animated'); // Set the item animation classes $item.classList.remove('is-animating'); $item.classList.add('has-animated'); // Set the item expanded/collapsed state hooks $item.classList.toggle('is-expanded'); $item.classList.toggle('is-collapsed'); // Check if the callbacks should not be suppressed if (!silent) { // Call the toggle after callback plugin.settings.callbackToggleAfter.call(); } }, duration: toggle_duration }); } // Check if the item has the collapsed state hook if ($item.classList.contains('is-collapsed')) { // Set the item expanded/collapsed state hooks $item.classList.toggle('is-expanded'); $item.classList.toggle('is-collapsed'); // Fade the body in Velocity($body, 'fadeIn', { complete: () => { // Set the accordion animation classes $accordion.classList.remove('is-animating'); $accordion.classList.add('has-animated'); // Set the item animation classes $item.classList.remove('is-animating'); $item.classList.add('has-animated'); // Check if the callbacks should not be suppressed if (!silent) { // Call the toggle after callback plugin.settings.callbackToggleAfter.call(); } }, duration: toggle_duration }); } // Break the switch break; } } } }, /** * Refresh the plugins initialization. * @param {bool} silent Suppress callbacks. * @return {void} */ refresh: (silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the refresh before callback plugin.settings.callbackRefreshBefore.call(); } // Destroy the existing initialization plugin.this.destroy(silent); // Initialize the plugin plugin.this.initialize(silent); // Check if the callbacks should not be suppressed if (!silent) { // Call the refresh after callback plugin.settings.callbackRefreshAfter.call(); } }, /** * Destroy an existing initialization. * @param {bool} silent Suppress callbacks. * @return {void} */ destroy: (silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the destroy before callback plugin.settings.callbackDestroyBefore.call(); } // Remove the click event handler to toggle an accordion item document.removeEventListener('click', clickAccordionToggleEventHandler); // Check if the callbacks should not be suppressed if (!silent) { // Call the destroy after callback plugin.settings.callbackDestroyAfter.call(); } }, /** * Call the toggle method silently. * @param {element} $item The accordion item. * @return {void} */ toggleSilently: ($item) => { // Call the toggle method silently plugin.this.toggle($item, true); }, /** * Call the refresh method silently. * @return {void} */ refreshSilently: () => { // Call the refresh method silently plugin.this.refresh(true); }, /** * Call the destroy method silently. * @return {void} */ destroySilently: () => { // Call the destroy method silently plugin.this.destroy(true); } }; // Return the plugin return Plugin; }));