1(function($) { 2 'use strict'; 3 4 // Remove the 'no-js' class since JavaScript is enabled 5 $('.js-accordion-item').removeClass('no-js'); 6 7 8 9 /** 10 * Accordion Blocks plugin function 11 * 12 * @param object options Plugin settings to override the defaults 13 */ 14 $.fn.accordionBlockItem = function(options) { 15 var settings = $.extend({ 16 // Set default settings 17 initiallyOpen: false, 18 autoClose: true, 19 clickToClose: true, 20 scroll: false, 21 scrollOffset: false, 22 }, options); 23 24 var duration = 250; 25 var hashID = window.location.hash.replace('#', ''); 26 27 var item = {}; 28 29 item.self = $(this); 30 item.id = $(this).attr('id'); 31 item.controller = $(this).children('.js-accordion-controller'); 32 item.uuid = getAccordionItemUUID(item.self); 33 item.content = $('#ac-' + item.uuid); 34 item.accordionGroupItems = [item.uuid]; 35 item.accordionAncestorItems = []; 36 37 38 39 /** 40 * Initial setup 41 * Set the scroll offset, and figure out which items should be open by 42 * default. 43 */ 44 (function initialSetup() { 45 /** 46 * Set up some defaults for this controller 47 * These cannot be set in the blocks `save` function because 48 * WordPress strips `tabindex` and `aria-controls` attributes from 49 * saved post content. See `_wp_add_global_attributes` function in 50 * wp-includes/kses.php for list of allowed attributes. 51 */ 52 item.controller.attr({ 53 'tabindex': 0, 54 'aria-controls': 'ac-' + item.uuid, 55 }); 56 57 settings.scrollOffset = Math.floor(parseInt(settings.scrollOffset, 10)) || 0; 58 59 /** 60 * Add any sibling accordion items to the accordionGroupItems array. 61 */ 62 $.each(item.self.siblings('.js-accordion-item'), function(index, ele) { 63 var uuid = getAccordionItemUUID(ele); 64 65 item.accordionGroupItems.push(uuid); 66 }); 67 68 /** 69 * Add any parent accordion items to the accordionAncestorItems array. 70 */ 71 $.each(item.self.parents('.js-accordion-item'), function(index, ele) { 72 var uuid = getAccordionItemUUID(ele); 73 74 item.accordionAncestorItems.push(uuid); 75 }); 76 77 // If this item has `initially-open prop` set to true, open it 78 if (settings.initiallyOpen) { 79 /** 80 * We aren't opening the item here (only setting open attributes) 81 * because the openItem() function fires the `openAccordionItem` 82 * event which, if `autoClose` is set, would override the users 83 * defined initiallyOpen settings. 84 * 85 * Only setting open attributes is fine since the item's content 86 * display (`display: none|block`) is already set by the plugin. 87 */ 88 setOpenItemAttributes(); 89 } 90 // If the hash matches this item, open it 91 else if (item.id === hashID) { 92 /** 93 * Unlike the `initiallyOpen` case above, if a hash is detected 94 * that matches one of the accordion items, we probably _want_ 95 * the other items to close so the user can focus on this item. 96 */ 97 openItem(); 98 99 // Open ancestors if necessary 100 $.each(item.accordionAncestorItems, function(index, uuid) { 101 $(document).trigger('openAncestorAccordionItem', uuid); 102 }); 103 } 104 // Otherwise, close the item 105 else { 106 /** 107 * Don't use closeItem() function call since it animates the 108 * closing. Instead, we only need to set the closed attributes. 109 */ 110 setCloseItemAttributes(); 111 } 112 })(); 113 114 115 116 /** 117 * Default click function 118 * Called when an accordion controller is clicked. 119 */ 120 function clickHandler() { 121 // Only open the item if item isn't already open 122 if (!item.self.hasClass('is-open')) { 123 // Open clicked item 124 openItem(); 125 } 126 // If item is open, and click to close is set, close it 127 else if (settings.clickToClose) { 128 closeItem(); 129 } 130 131 return false; 132 } 133 134 135 136 /** 137 * Get the accordion item UUID for a given accordion item DOM element. 138 */ 139 function getAccordionItemUUID(ele) { 140 return $(ele).children('.js-accordion-controller').attr('id').replace('at-', ''); 141 } 142 143 144 145 /** 146 * Opens an accordion item 147 * Also handles accessibility attribute settings. 148 */ 149 function openItem() { 150 setOpenItemAttributes(); 151 152 // Clear/stop any previous animations before revealing content 153 item.content.clearQueue().stop().slideDown(duration, function() { 154 // Scroll page to the title 155 if (settings.scroll) { 156 // Pause scrolling until other items have closed 157 setTimeout(function() { 158 $('html, body').animate({ 159 scrollTop: item.self.offset().top - settings.scrollOffset 160 }, duration); 161 }, duration); 162 } 163 }); 164 165 $(document).trigger('openAccordionItem', item); 166 } 167 168 169 170 /** 171 * Set open item attributes 172 * Mark accordion item as open and read and set aria attributes. 173 */ 174 function setOpenItemAttributes() { 175 item.self.addClass('is-open is-read'); 176 item.controller.attr('aria-expanded', true); 177 item.content.prop('hidden', false); 178 } 179 180 181 182 /** 183 * Closes an accordion item 184 * Also handles accessibility attribute settings. 185 */ 186 function closeItem() { 187 // Close the item 188 item.content.slideUp(duration, function() { 189 setCloseItemAttributes(); 190 }); 191 } 192 193 194 195 /** 196 * Set closed item attributes 197 * Mark accordion item as closed and set aria attributes. 198 */ 199 function setCloseItemAttributes() { 200 item.self.removeClass('is-open'); 201 item.controller.attr('aria-expanded', false); 202 item.content.attr('hidden', true); 203 } 204 205 206 207 /** 208 * Close all items if auto close is enabled 209 */ 210 function maybeCloseItem() { 211 if (settings.autoClose && item.self.hasClass('is-open')) { 212 closeItem(); 213 } 214 } 215 216 217 218 /** 219 * Add event listeners 220 */ 221 item.controller.on('click', clickHandler); 222 223 224 225 /** 226 * Listen for other accordion items opening 227 *
228 * The `openAccordionItem` event is fired whenever an accordion item is 229 * opened after initial plugin setup. 230 */ 231 $(document).on('openAccordionItem', function(event, ele) { 232 /** 233 * Only trigger potential close these conditions are met: 234 * 235 * 1. This isn't the item the user just clicked to open. 236 * 2. This accordion is in the same group of accordions as the one 237 * that was just clicked to open. 238 * 3. This accordion is not an ancestor of the item that was just 239 * clicked to open. 240 * 241 * This serves two purposes: 242 * 243 * 1. It allows nesting of accordions to work. 244 * 2. It allows users to group accordions to control independently 245 * of other groups of accordions. 246 * 3. It allows child accordions to be opened via hash change 247 * without automatically closing the parent accordion, therefore 248 * hiding the accordion the user just indicated they wanted open. 249 */ 250 if ( 251 ele !== item && 252 ele.accordionGroupItems.indexOf(item.uuid) > 0 && 253 ele.accordionAncestorItems.indexOf(item.uuid) === -1 254 ) { 255 maybeCloseItem(); 256 } 257 }); 258 259 260 261 /** 262 * Listen for ancestor opening requests 263 * 264 * The `openAncestorAccordionItem` event is fired whenever a nested 265 * accordion item is opened, but the ancestors may also need to be 266 * opened. 267 */ 268 $(document).on('openAncestorAccordionItem', function(event, uuid) { 269 if (uuid === item.uuid) { 270 openItem(); 271 } 272 }); 273 274 275 276 item.controller.on('keydown', function(event) { 277 var code = event.which; 278 279 if (item.controller.prop('tagName') !== 'BUTTON') { 280 // 13 = Return, 32 = Space 281 if ((code === 13) || (code === 32)) { 282 // Simulate click on the controller 283 $(this).click(); 284 } 285 } 286 287 // 27 = Esc 288 if (code === 27) { 289 maybeCloseItem(); 290 } 291 }); 292 293 // Listen for hash changes (in page jump links for accordions) 294 $(window).on('hashchange', function() { 295 hashID = window.location.hash.replace('#', ''); 296 297 // Only open this item if the has matches the ID 298 if (hashID === item.id) { 299 var ele = $('#' + hashID); 300 301 // If there is a hash and the hash is on an accordion item 302 if (ele.length && ele.hasClass('js-accordion-item')) { 303 // Open clicked item 304 openItem(); 305 306 // Open ancestors if necessary 307 $.each(item.accordionAncestorItems, function(index, uuid) { 308 $(document).trigger('openAncestorAccordionItem', uuid); 309 }); 310 } 311 } 312 }); 313 314 return this; 315 }; 316 317 318 319 // Loop through accordion settings objects 320 // Wait for the entire page to load before loading the accordion 321 $(window).on('load', function() { 322 $('.js-accordion-item').each(function() { 323 $(this).accordionBlockItem({ 324 // Set default settings 325 initiallyOpen: $(this).data('initially-open'), 326 autoClose: $(this).data('auto-close'), 327 clickToClose: $(this).data('click-to-close'), 328 scroll: $(this).data('scroll'), 329 scrollOffset: $(this).data('scroll-offset'), 330 }); 331 }); 332 }); 333}(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.