1/** 2 * A vanilla-ish script controlling all wpd-simple-accordions, supplying accessible functionality, and the ability to let 3 * accordions "unaccordionize" at particular breakpoints. 4 * 5 * This version SHOULD be the most up-to-date version. If your site has a dependency on the Utils module and still has a lingering 6 * copy of its own accordions script, ditch it in favour of this one. 7 * 8 * @author noahm for Ability Net [WSME-556] 9 */ 10(function($) { 11 'use strict'; 12 13 const EVENT_TRIGGER_STATE_CHANGED_NAME = 'WPDSimpleAccordions__triggerStateChanged'; 14 15 const DEFER_PAGE_SCROLL = 1; 16 const NO_DEFER_PAGE_SCROLL = 2; 17 const IGNORE_PAGE_SCROLL = 3; 18 19 /** 20 * As the name implies, will only attach a particular handler for an event once, no matter how many times 21 * it's told to attach that handler to the specified event. 22 * 23 * @param {jQuery} $targetElem - the element in the DOM triggering the named event 24 * @param {string} eventName - the name of the event being triggered 25 * @param {function} handler - the handler function to provide as a callback 26 * @returns {undefined} 27 */ 28 var attachHandlerOnce = function($targetElem, eventName, handler) { 29 var han = $._data($targetElem.get(0), "events"); 30 if (!han || !han[eventName] || han[eventName].map(function(a) {return a.handler.name}).indexOf(handler.name) < 0) { 31 $targetElem.on(eventName, handler); 32 } 33 }; 34 35 /** 36 * 37 * @param {jQuery} $button 38 * @returns {undefined} 39 */ 40 var triggerPageScroll = function($button) { 41 if ($button.attr('data-accordion-noscroll') !== 'true') { 42 $([document.documentElement, document.body]).animate({ 43 scrollTop: $button.offset().top - (window.fixedHeaderOffset || 0) 44 }, 250); 45 } 46 }; 47 48 /** 49 * Opens/closes the specified `$button`'s owner. An event is emitted at the close of this function which may be listened 50 * to by using the following: 51 * window.addEventListener(window.WPDSimpleAccordions.EVENT_TRIGGER_STATE_CHANGED_NAME, handlerFn); 52 * 53 * @param {jQuery} $button - the trigger button element belonging to the accordion whose state will change 54 * @param {boolean} opened - the new state to be applied to the accordion 55 * @param {int} pageScrollFlag 56 * @returns {null|function} - if the page trigger is being deferred, then the triggerPageScroll function is returned, bound to 57 * the supplied `$button`, so that it may be called later on 58 */ 59 var buttonToggle = function($button, opened, pageScrollFlag, updateDataExpandedFlag) { 60 $button.attr('aria-expanded', opened); 61 if (updateDataExpandedFlag) { 62 $button.attr('data-expanded', opened); 63 } 64 $button.parent().siblings('.accordion__item-content').attr('aria-hidden', !opened); 65 66 let ret = null; 67 68 if (opened) { 69 $button.parent().parent().addClass('expanded'); 70 switch (pageScrollFlag) { 71 case DEFER_PAGE_SCROLL: 72 ret = triggerPageScroll.bind(null, $button); 73 74 case NO_DEFER_PAGE_SCROLL: 75 triggerPageScroll($button); 76 break; 77 } 78 } 79 else { 80 $button.parent().parent().removeClass('expanded'); 81 } 82 83 const event = new CustomEvent(EVENT_TRIGGER_STATE_CHANGED_NAME, { 84 detail: {opened: opened, triggerElement: $button} 85 }); 86 87 window.dispatchEvent(event); 88 89 return ret; 90 }; 91 92 /** 93 * Trigger all accordions to close and show the trigger buttons, also reinstating accordion CSS classes. 94 * To be used in conjunction with unaccordionize. 95 * 96 * @param {jQuery} $elem - the owning root accordion group element 97 * @return {undefined} 98 */ 99 var accordionize = function($elem) { 100 // reinstate the accordion CSS classes, by removing the 'x_' prefix that unaccordionize would have introduced 101 var persistState = $elem.attr('data-accordion-persiststate') === 'true'; 102 $elem.find('[class*="accordion"]').each(function(idx, obj) { 103 const classList = obj.classList; 104 105 classList.forEach(function(cssClass) { 106 if (cssClass.startsWith('x_accordion')) { 107 obj.classList.replace(cssClass, cssClass.substring(2)); 108 } 109 }); 110 }); 111 112 let $buttons = $elem.find('.accordion__item-title'); 113 114 $buttons.each(function(idx, button) { 115 var $button = $(button); 116 // close all the accordions, not triggering any page scroll 117 buttonToggle($button, persistState && $button.attr('data-expanded') == 'true', IGNORE_PAGE_SCROLL, false); 118 // show the buttons 119 $button.show(); 120 }); 121 }; 122 123 /** 124 * Trigger all accordions to open and hide the trigger buttons, also disabling accordion CSS classes. 125 * To be used in conjunction with accordionize. 126 * 127 * @param {jQuery} $elem - the owning root accordion group element 128 * @return {undefined} 129 */ 130 var unaccordionize = function($elem) { 131 let $buttons = $elem.find('.accordion__item-title'); 132 133 $buttons.each(function(idx, button) { 134 var $button = $(button); 135 // open all the accordions, not triggering any page scroll 136 buttonToggle($button, true, IGNORE_PAGE_SCROLL, false); 137 // hide the buttons 138 $button.hide(); 139 }); 140 141 // disable the accordion css classes. They will become 'x_accordion' 142 $elem.find('[class*="accordion"]').each(function(idx, obj) { 143 const classList = obj.classList; 144 145 classList.forEach(function(cssClass) { 146 if (cssClass.startsWith('accordion')) { 147 obj.classList.replace(cssClass, 'x_' + cssClass); 148 } 149 }); 150 }); 151 }; 152 153 /** 154 * Find all wpd-simple-accordions and set up their functionality. 155 * @returns {undefined} 156 */ 157 var initAccordions = function() { 158 var $accordions = $('.wpd-simple-accordion'); 159 var unaccordionizeMap = {}; 160 161 $accordions.each(function(idx, elem) { 162 var $elem = $(elem); 163 var unaccordionizeAt = $elem.attr('data-unaccordionize-at'); 164 165 if (unaccordionizeAt) { 166 // if the value supplied is a CSS variable, we need to resolve it to a concrete value, as it won't work with matchMedia 167 if (unaccordionizeAt.startsWith('--')) { 168 unaccordionizeAt = getComputedStyle(document.body).getPropertyValue(unaccordionizeAt); 169 } 170 171 if (unaccordionizeAt) { 172 if (!unaccordionizeMap[unaccordionizeAt]) { 173 // construct a new object which keeps track of the accordions intended to unaccordionize at the specified 174 // breakpoint 175 unaccordionizeMap[unaccordionizeAt] = { 176 accordions: [], 177 mq: window.matchMedia('(min-width: ' + unaccordionizeAt + ')'), 178 // this is used to keep track of when there is a switch from a matches to a !matches 179 lastMatchesResult: null 180 }; 181 } 182 183 unaccordionizeMap[unaccordionizeAt].accordions.push($elem); 184 } 185 } 186 187 var $buttons = $elem.find('.accordion__item-title'); 188 189 var wpdAccButtonHandler = function() { 190 var $myself = $(this); 191 192 var isExopen = $elem.attr('data-accordion-exopen') === 'true'; 193 194 var deferredScrollTo = buttonToggle($myself, !($myself.attr('data-expanded') === 'true'), isExopen ? DEFER_PAGE_SCROLL : NO_DEFER_PAGE_SCROLL, true); 195 196 if (isExopen && $myself.attr('data-expanded') === 'true') { 197 // close all siblings if we're exclusive open 198 $myself.parent().parent().siblings().find('.accordion__item-title').each(function(jdx, sibling) { 199 buttonToggle($(sibling), false, NO_DEFER_PAGE_SCROLL, true); 200 }); 201 202 // THEN trigger the scroll to $myself (otherwise calculation for scroll may be taking into account heights of open siblings before they're closed). 203 if (typeof deferredScrollTo === 'function') { 204 deferredScrollTo(); 205 } 206 } 207 }; 208 209 $buttons.each(function(kdx, zelem) { 210 attachHandlerOnce($(zelem), 'click', wpdAccButtonHandler); 211 }); 212 }); 213 214 // if we have accordions that need to unaccoridonize at a certain breakpoint, we need to construct a resize handler 215 if (unaccordionizeMap) { 216 var wpdAccWindowResizeHandler = function() { 217 for (const key in unaccordionizeMap) { 218 const val = unaccordionizeMap[key]; 219 220 // we only want to trigger an (un)accordionize when there's actually a change in the matches boolean 221 if (val.lastMatchesResult === null || val.lastMatchesResult !== val.mq.matches) { 222 val.lastMatchesResult = val.mq.matches; 223 val.accordions.forEach(val.lastMatchesResult ? unaccordionize : accordionize); 224 } 225 } 226 }; 227 228 // do initial check for current page width 229 wpdAccWindowResizeHandler(); 230 // begin listening to window width 231 attachHandlerOnce($(window), 'resize', wpdAccWindowResizeHandler); 232 } 233 }; 234 235 236 // Expose an object on the window 237 // Allow init to be called again if necessary, as this script is meant to be included with #{page.script src:''} 238 window.WPDSimpleAccordions = {}; 239 window.WPDSimpleAccordions.init = initAccordions; 240 window.WPDSimpleAccordions.EVENT_TRIGGER_STATE_CHANGED_NAME = EVENT_TRIGGER_STATE_CHANGED_NAME; 241 242 if (typeof Object.freeze === 'function') { 243 Object.freeze(window.WPDSimpleAccordions); 244 } 245 246 //do first setup 247 $(document).ready(function() { 248 window.WPDSimpleAccordions.init(); 249 }); 250})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.