1;(function ( w, doc, undefined ) { 2 'use strict'; 3 4 /** 5 * Local object for method references 6 * and define script meta-data 7 */ 8 var ARIAaccordion = {}; 9 w.ARIAaccordion = ARIAaccordion; 10 11 ARIAaccordion.NS = 'ARIAaccordion'; 12 ARIAaccordion.AUTHOR = 'Scott O\'Hara'; 13 ARIAaccordion.VERSION = '3.2.1'; 14 ARIAaccordion.LICENSE = 'https://github.com/scottaohara/accessible_accordions/blob/master/LICENSE'; 15 16 var widgetClass = 'accordion'; 17 var widgetTriggerClass = widgetClass + '__trigger'; 18 var widgetHeadingClass = widgetClass + '__heading'; 19 var widgetPanelClass = widgetClass + '__panel'; 20 21 var widgetHeading = '[data-aria-accordion-heading]'; 22 var widgetPanel = '[data-aria-accordion-panel]' 23 24 var idCounter = 0; 25 26 /** 27 * Global Create 28 * 29 * This function validates that the minimum required markup 30 * is present to create the ARIA widget(s). 31 * Any additional markup elements or attributes that 32 * do not exist in the found required markup patterns 33 * will be generated via this function. 34 */ 35 ARIAaccordion.create = function () { 36 var self; 37 var panels; 38 var defaultPanel = 'none'; 39 var headings; 40 var triggers; 41 var constantPanel; 42 var multiPanel; 43 var i; 44 45 var widget = doc.querySelectorAll('[data-aria-accordion]'); 46 console.log(widget); 47 idCounter += 1; 48 49 for ( i = 0; i < widget.length; i++ ) { 50 self = widget[i]; 51 var t; 52 53 /** 54 * Check for IDs and create arrays of necessary 55 * panels & headings for further setup functions. 56 */ 57 if ( !self.hasAttribute('id') ) { 58 self.id = 'acc_' + idCounter + '-' + i; 59 } 60 61 /** 62 * Setup accordion classes 63 */ 64 self.classList.add(widgetClass); 65 66 67 /** 68 * Get all panels & headings of an accordion pattern based 69 * on a specific ID > direct child selector (this will ensure 70 * that nested accordions don't get properties meant for 71 * the parent accordion, or vice-versa). 72 * 73 * If accordions are contained within an ol/ul, the selector 74 * needs to be different. 75 */ 76 if ( doc.querySelectorAll('#' + self.id + '> li').length ) { 77 panels = doc.querySelectorAll('#' + self.id + ' li > ' + widgetPanel); 78 headings = doc.querySelectorAll('#' + self.id + ' li > ' + widgetHeading); 79 } 80 else { 81 panels = doc.querySelectorAll('#' + self.id + ' > ' + widgetPanel); 82 headings = doc.querySelectorAll('#' + self.id + ' > ' + widgetHeading); 83 } 84 85 /** 86 * Check for options: 87 * data-default - is there a default opened panel? 88 * data-constant - should the accordion always have A panel open? 89 */ 90 if ( self.hasAttribute('data-default') ) { 91 defaultPanel = self.getAttribute('data-default'); 92 } 93 94 /** 95 * Accordions with a constantly open panel are not a default 96 * but if a data-constant attribute is used, then we need this 97 * to be true. 98 */ 99 constantPanel = self.hasAttribute('data-constant'); 100 101 /** 102 * Accordions can have multiple panels open at a time, 103 * if they have a data-multi attribute. 104 */ 105 multiPanel = self.hasAttribute('data-multi'); 106 107 /** 108 * If accordion panels are meant to transition, apply this inline style. 109 * This is to help mitigate a quick flash of CSS being applied to the 110 * no-js styling, and having an unwanted transition on initial page load. 111 */ 112 if ( self.hasAttribute('data-transition') ) { 113 var thesePanels = self.querySelectorAll(widgetPanel); 114 115 for ( t = 0; t < thesePanels.length; t++ ) { 116 thesePanels[t].classList.add(widgetPanelClass + '--transition'); 117 } 118 } 119 120 /** 121 * Setup Panels, Headings & Buttons 122 */ 123 ARIAaccordion.setupPanels(self.id, panels, defaultPanel, constantPanel); 124 ARIAaccordion.setupHeadingButton(headings, constantPanel); 125 126 if ( doc.querySelectorAll('#' + self.id + '> li').length ) { 127 triggers = doc.querySelectorAll('#' + self.id + ' li > ' + widgetHeading + ' .' + widgetTriggerClass); 128 } 129 else { 130 triggers = doc.querySelectorAll('#' + self.id + ' > ' + widgetHeading + ' .' + widgetTriggerClass); 131 } 132 133 /** 134 * Now that the headings/triggers and panels are setup 135 * we can grab all the triggers and setup their functionality. 136 */ 137 for ( t = 0; t < triggers.length; t++ ) { 138 triggers[t].addEventListener('click', ARIAaccordion.actions); 139 triggers[t].addEventListener('keydown', ARIAaccordion.keytrolls); 140 } 141 } // for(widget.length) 142 }; // ARIAaccordion.create() 143 144 145 ARIAaccordion.setupPanels = function ( id, panels, defaultPanel, constantPanel ) { 146 var i; 147 var panel; 148 var panelID; 149 var setPanel; 150 var constant; 151 152 for ( i = 0; i < panels.length; i++ ) { 153 panel = panels[i]; 154 panelID = id + '_panel_' + (i + 1); 155 setPanel = defaultPanel; 156 constant = constantPanel; 157 158 panel.setAttribute('id', panelID); 159 ariaHidden(panels[0], true); 160 161 panel.classList.add(widgetPanelClass); 162 163 /** 164 * Set the accordion to have the appropriately 165 * opened panel if a data-default value is set. 166 * If no value set, then no panels are open. 167 */ 168 if ( setPanel !== 'none' && parseInt(setPanel) !== NaN ) { 169 // if value is 1 or less 170 if ( setPanel <= 1 ) { 171 ariaHidden(panels[0], false); 172 } 173 // if value is more than the number of panels, then open 174 // the last panel by default 175 else if ( (setPanel - 1) >= panels.length ) { 176 ariaHidden(panels[panels.length - 1], false); 177 } 178 // for any other value between 2 - the last panel #, open that one 179 else { 180 ariaHidden(panels[(setPanel - 1)], false); 181 } 182 } 183 184 /** 185 * If an accordion is meant to have a consistently open panel, 186 * and a default open panel was not set (or was not set correctly), 187 * then run one more check. 188 */ 189 if ( constant && setPanel === 'none' || parseInt(setPanel) === NaN ) { 190 ariaHidden(panels[0], false); 191 } 192 } 193 }; // ARIAaccordion.setupPanels 194 195 196 ARIAaccordion.setupHeadingButton = function ( headings, constantPanel ) { 197 var heading; 198 var targetID; 199 var targetState; 200 var newButton; 201 var buttonText; 202 var i; 203 204 for ( i = 0; i < headings.length; i++ ) { 205 heading = headings[i]; 206 targetID = heading.nextElementSibling.id; 207 targetState = doc.getElementById(targetID).getAttribute('aria-hidden'); 208 209 // setup new heading buttons 210 newButton = doc.createElement('button'); 211 buttonText = heading.innerHTML; 212 // clear out the heading's content 213 heading.innerHTML = ''; 214 // provide the heading with a class for styling 215 heading.classList.add(widgetHeadingClass); 216 217 newButton.setAttribute('type', 'button'); 218 newButton.setAttribute('aria-controls', targetID); 219 newButton.setAttribute('id', targetID + '_trigger'); 220 newButton.classList.add(widgetTriggerClass); 221 222 /** 223 * Check the corresponding panel to see if it was set up 224 * to be hidden or shown by default. Add an aria-expanded 225 * attribute value that is appropriate. 226 */ 227 if ( targetState === 'false' ) { 228 ariaExpanded(newButton, true); 229 isCurrent(newButton, true); 230 231 /** 232 * Check to see if this an accordion that needs a constantly 233 * opened panel, and if the button's target is not hidden. 234 */ 235 if ( constantPanel ) { 236 newButton.setAttribute('aria-disabled', 'true'); 237 } 238 } 239 else { 240 ariaExpanded(newButton, false); 241 isCurrent(newButton, false); 242 } 243 244 // Add the Button & previous heading text 245 heading.appendChild(newButton); 246 newButton.innerHTML = buttonText; 247 } 248 }; // ARIAaccordion.createButton 249 250 251 ARIAaccordion.actions = function ( e ) { 252 // Need to pass in if this is a multi accordion or not. 253 // Also need to pass in existing trigger arrays. 254 var thisAccordion = this.id.replace(/_panel.*$/g, ''); 255 var thisTarget = doc.getElementById(this.getAttribute('aria-controls')); 256 var thisTriggers; 257 258 if ( doc.querySelectorAll('#' + thisAccordion + '> li').length ) { 259 thisTriggers = doc.querySelectorAll('#' + thisAccordion + ' li > ' + widgetHeading + ' .' + widgetTriggerClass); 260 } 261 else { 262 thisTriggers = doc.querySelectorAll('#' + thisAccordion + ' > ' + widgetHeading + ' .' + widgetTriggerClass); 263 } 264 265 e.preventDefault(); 266 267 ARIAaccordion.togglePanel( e, thisAccordion, thisTarget, thisTriggers ); 268 }; // ARIAaccordion.actions() 269 270 271 ARIAaccordion.togglePanel = function ( e, thisAccordion, targetPanel, triggers ) { 272 var getID; 273 var i; 274 var thisTrigger = e.target; 275 276 // check to see if a trigger is disabled 277 if ( thisTrigger.getAttribute('aria-disabled') !== 'true' ) { 278 279 getID = thisTrigger.getAttribute('aria-controls'); 280 281 isCurrent(thisTrigger, 'true'); 282 283 if ( thisTrigger.getAttribute('aria-expanded') === 'true' ) { 284 ariaExpanded(thisTrigger, 'false'); 285 ariaHidden(targetPanel, 'true'); 286 } 287 else { 288 ariaExpanded(thisTrigger, 'true'); 289 ariaHidden(targetPanel, 'false'); 290 291 if ( doc.getElementById(thisAccordion).hasAttribute('data-constant') ) { 292 ariaDisabled(thisTrigger, 'true'); 293 } 294 } 295 296 if ( doc.getElementById(thisAccordion).hasAttribute('data-constant') || 297 !doc.getElementById(thisAccordion).hasAttribute('data-multi') ) { 298 299 for ( i = 0; i < triggers.length; i++ ) { 300 if ( thisTrigger !== triggers[i] ) { 301 isCurrent(triggers[i], 'false'); 302 getID = triggers[i].getAttribute('aria-controls'); 303 ariaDisabled(triggers[i], 'false'); 304 ariaExpanded(triggers[i], 'false'); 305 ariaHidden(doc.getElementById(getID), 'true'); 306 } 307 } 308 } 309 } 310 }; 311 312 313 ARIAaccordion.keytrolls = function ( e ) { 314 if ( e.target.classList.contains(widgetTriggerClass) ) { 315 var keyCode = e.keyCode || e.which; 316 317 // var keyUp = 38; 318 // var keyDown = 40; 319 var keyHome = 36; 320 var keyEnd = 35; 321 322 var thisAccordion = this.id.replace(/_panel.*$/g, ''); 323 var thisTriggers; 324 325 if ( doc.querySelectorAll('#' + thisAccordion + '> li').length ) { 326 thisTriggers = doc.querySelectorAll('#' + thisAccordion + ' li > ' + widgetHeading + ' .' + widgetTriggerClass); 327 } 328 else { 329 thisTriggers = doc.querySelectorAll('#' + thisAccordion + ' > ' + widgetHeading + ' .' + widgetTriggerClass); 330 } 331 332 333 switch ( keyCode ) { 334 /** 335 * keyUp & keyDown are optional controls 336 * for accordion components. 337 */ 338 // case keyUp: 339 // if ( doc.getElementById(thisAccordion).hasAttribute('data-up-down') ) { 340 // e.preventDefault(); 341 // // optional up arrow controls 342 // } 343 // break; 344 345 // case keyDown: 346 // if ( doc.getElementById(thisAccordion).hasAttribute('data-up-down') ) { 347 // e.preventDefault(); 348 // // optional down arrow control 349 // } 350 // break; 351 352 /** 353 * keyEnd/Home are optional functions that may not be inherently known 354 * to most users and, in the case of END, conflict with expected 355 * usage of that key with NVDA. 356 */ 357 case keyEnd: 358 e.preventDefault(); 359 thisTriggers[thisTriggers.length - 1].focus(); 360 break; 361 362 case keyHome: 363 e.preventDefault();
364 thisTriggers[0].focus(); 365 break; 366 367 default: 368 break; 369 } 370 } 371 }; // ARIAaccordion.keytrolls() 372 373 374 /** 375 * Initialize Accordion Functions 376 * if expanding this script, place any other 377 * initialize functions within here. 378 */ 379 ARIAaccordion.init = function () { 380 ARIAaccordion.create(); 381 }; 382 383 384 /** 385 * Helper Functions 386 * Just to cut down on the verboseness of some declarations 387 */ 388 var ariaHidden = function ( el, state ) { 389 el.setAttribute('aria-hidden', state); 390 }; 391 392 var ariaExpanded = function ( el, state ) { 393 el.setAttribute('aria-expanded', state); 394 }; 395 396 var ariaDisabled = function ( el, state ) { 397 el.setAttribute('aria-disabled', state); 398 }; 399 400 var isCurrent = function ( el, state ) { 401 el.setAttribute('data-current', state); 402 }; 403 404 // go go JavaScript 405 ARIAaccordion.init(); 406 407})( window, document );
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.