1/*! 2 * jQuery Accordion 0.0.1 3 * (c) 2014 Victor Fernandez <[email protected]> 4 * MIT Licensed. 5 * 6 * modified to set aria-hidden & aria-expanded on open/close for A11Y 7 * sets tabindex for <a> elements in the accordion content when opened or closed 8 */ 9 10(function ($, window, document, undefined) { 11 var pluginName = "accordion", 12 defaults = { 13 transitionSpeed: 300, 14 transitionEasing: "ease", 15 controlElement: "[data-control]", 16 contentElement: "[data-content]", 17 groupElement: "[data-accordion-group]", 18 singleOpen: true, 19 }; 20 21 function Accordion(element, options) { 22 this.element = element; 23 this.options = $.extend({}, defaults, options); 24 this._defaults = defaults; 25 this._name = pluginName; 26 this.init(); 27 } 28 29 Accordion.prototype.init = function () { 30 var self = this, 31 opts = self.options; 32 33 var $accordion = $(self.element), 34 $controls = $accordion.find("> " + opts.controlElement), 35 $content = $accordion.find("> " + opts.contentElement); 36 37 var accordionParentsQty = $accordion.parents("[data-accordion]").length, 38 accordionHasParent = accordionParentsQty > 0; 39 40 var closedCSS = { "max-height": 0, overflow: "hidden" }; 41 42 var CSStransitions = supportsTransitions(); 43 44 function debounce(func, threshold, execAsap) { 45 var timeout; 46 47 return function debounced() { 48 var obj = this, 49 args = arguments; 50 51 function delayed() { 52 if (!execAsap) func.apply(obj, args); 53 timeout = null; 54 } 55 56 if (timeout) clearTimeout(timeout); 57 else if (execAsap) func.apply(obj, args); 58 59 timeout = setTimeout(delayed, threshold || 100); 60 }; 61 } 62 63 function supportsTransitions() { 64 var b = document.body || document.documentElement, 65 s = b.style, 66 p = "transition"; 67 68 if (typeof s[p] == "string") { 69 return true; 70 } 71 72 var v = ["Moz", "webkit", "Webkit", "Khtml", "O", "ms"]; 73 74 p = "Transition"; 75 76 for (var i = 0; i < v.length; i++) { 77 if (typeof s[v[i] + p] == "string") { 78 return true; 79 } 80 } 81 82 return false; 83 } 84 85 function requestAnimFrame(cb) { 86 if ( 87 window.requestAnimationFrame || 88 window.webkitRequestAnimationFrame || 89 window.mozRequestAnimationFrame 90 ) { 91 return ( 92 requestAnimationFrame(cb) || 93 webkitRequestAnimationFrame(cb) || 94 mozRequestAnimationFrame(cb) 95 ); 96 } else { 97 return setTimeout(cb, 1000 / 60); 98 } 99 } 100 101 function toggleTransition($el, remove) { 102 if (!remove) { 103 $content.css({ 104 "-webkit-transition": 105 "max-height " + 106 opts.transitionSpeed + 107 "ms " + 108 opts.transitionEasing, 109 transition: 110 "max-height " + 111 opts.transitionSpeed + 112 "ms " + 113 opts.transitionEasing, 114 }); 115 } else { 116 $content.css({ 117 "-webkit-transition": "", 118 transition: "", 119 }); 120 } 121 } 122 123 function calculateHeight($el) { 124 var height = 0; 125 126 $el.children().each(function () { 127 height = height + $(this).outerHeight(true); 128 }); 129 130 $el.data("oHeight", height); 131 } 132 133 function updateParentHeight( 134 $parentAccordion, 135 $currentAccordion, 136 qty, 137 operation 138 ) { 139 var $content = $parentAccordion 140 .filter(".accordion--open") 141 .find("> [data-content]"), 142 $childs = $content.find( 143 "[data-accordion].accordion--open > [data-content]" 144 ), 145 $matched; 146 147 if (!opts.singleOpen) { 148 $childs = $childs.not( 149 $currentAccordion 150 .siblings("[data-accordion].accordion--open") 151 .find("> [data-content]") 152 ); 153 } 154 155 $matched = $content.add($childs); 156 157 if ($parentAccordion.hasClass("accordion--open")) { 158 $matched.each(function () { 159 var currentHeight = $(this).data("oHeight"); 160 161 switch (operation) { 162 case "+": 163 $(this).data("oHeight", currentHeight + qty); 164 break; 165 case "-": 166 $(this).data("oHeight", currentHeight - qty); 167 break; 168 default: 169 throw "updateParentHeight method needs an operation"; 170 } 171 172 $(this).css("max-height", $(this).data("oHeight")); 173 }); 174 } 175 } 176 177 function refreshHeight($accordion) { 178 if ($accordion.hasClass("accordion--open")) { 179 var $content = $accordion.find("> [data-content]"), 180 $childs = $content.find( 181 "[data-accordion].accordion--open > [data-content]" 182 ), 183 $matched = $content.add($childs); 184 185 calculateHeight($matched); 186 187 $matched.css("max-height", $matched.data("oHeight")); 188 } 189 } 190 191 function closeAccordion($accordion, $content) { 192 $accordion.trigger("accordion.close"); 193 $content.attr('aria-hidden', true); 194 $controls.attr('aria-expanded', false); 195 196 $content.find("a").attr("tabindex", -1); // Set tabindex to -1 197 198 if (CSStransitions) {
199 if (accordionHasParent) { 200 var $parentAccordions = 201 $accordion.parents("[data-accordion]"); 202 203 updateParentHeight( 204 $parentAccordions, 205 $accordion, 206 $content.data("oHeight"), 207 "-" 208 ); 209 } 210 211 $content.css(closedCSS); 212 213 $accordion.removeClass("accordion--open"); 214 } else { 215 $content.css("max-height", $content.data("oHeight")); 216 217 $content.animate(closedCSS, opts.transitionSpeed); 218 219 $accordion.removeClass("accordion--open"); 220 } 221 } 222 223 function openAccordion($accordion, $content) { 224 $accordion.trigger("accordion.accordion--open"); 225 $content.attr('aria-hidden', false); 226 $controls.attr('aria-expanded', true); 227 228 $content.find("a").attr("tabindex", 0); // Set tabindex to 0 229 230 if (CSStransitions) { 231 toggleTransition($content); 232 233 if (accordionHasParent) { 234 var $parentAccordions = 235 $accordion.parents("[data-accordion]"); 236 237 updateParentHeight( 238 $parentAccordions, 239 $accordion, 240 $content.data("oHeight"), 241 "+" 242 ); 243 } 244 245 requestAnimFrame(function () { 246 $content.css("max-height", $content.data("oHeight")); 247 }); 248 249 $accordion.addClass("accordion--open"); 250 } else { 251 $content.animate( 252 { 253 "max-height": $content.data("oHeight"), 254 }, 255 opts.transitionSpeed, 256 function () { 257 $content.css({ "max-height": "none" }); 258 } 259 ); 260 261 $accordion.addClass("accordion--open"); 262 } 263 } 264 265 function closeSiblingAccordions($accordion) { 266 var $accordionGroup = $accordion.closest(opts.groupElement); 267 268 var $siblings = $accordion 269 .siblings("[data-accordion]") 270 .filter(".accordion--open"), 271 $siblingsChildren = $siblings 272 .find("[data-accordion]") 273 .filter(".accordion--open"); 274 275 var $otherAccordions = $siblings.add($siblingsChildren); 276 277 $otherAccordions.each(function () { 278 var $accordion = $(this), 279 $content = $accordion.find(opts.contentElement); 280 281 closeAccordion($accordion, $content); 282 }); 283 284 $otherAccordions.removeClass("accordion--open"); 285 } 286 287 function toggleAccordion() { 288 var isAccordionGroup = opts.singleOpen 289 ? $accordion.parents(opts.groupElement).length > 0 290 : false; 291 292 calculateHeight($content); 293 294 if (isAccordionGroup) { 295 closeSiblingAccordions($accordion); 296 } 297 298 if ($accordion.hasClass("accordion--open")) { 299 closeAccordion($accordion, $content); 300 } else { 301 openAccordion($accordion, $content); 302 } 303 return false; 304 } 305 306 function addEventListeners() { 307 $controls.on("click", toggleAccordion); 308 309 $controls.on("accordion.toggle", function () { 310 if (opts.singleOpen && $controls.length > 1) { 311 return false; 312 } 313 314 toggleAccordion(); 315 }); 316 317 $(window).on( 318 "resize", 319 debounce(function () { 320 refreshHeight($accordion); 321 }) 322 ); 323 } 324 325 function setup() { 326 $content.each(function () { 327 var $curr = $(this); 328 329 if ($curr.css("max-height") != 0) { 330 if ( 331 !$curr 332 .closest("[data-accordion]") 333 .hasClass("accordion--open") 334 ) { 335 $curr.css({ "max-height": 0, overflow: "hidden" }); 336 $curr.find("a").attr("tabindex", -1); // Set tabindex to -1 for closed accordions 337 } else { 338 toggleTransition($curr); 339 calculateHeight($curr); 340 341 $curr.css("max-height", $curr.data("oHeight")); 342 $curr.find("a").attr("tabindex", 0); // Set tabindex to 0 for open accordions 343 } 344 } 345 }); 346 347 if (!$accordion.attr("data-accordion")) { 348 $accordion.attr("data-accordion", ""); 349 $accordion.find(opts.controlElement).attr("data-control", ""); 350 $accordion.find(opts.contentElement).attr("data-content", ""); 351 } 352 } 353 354 setup(); 355 addEventListeners(); 356 }; 357 358 $.fn[pluginName + "Class"] = function ($item) { 359 return new Accordion($item); 360 }; 361 362 $.fn[pluginName] = function (options) { 363 return this.each(function () { 364 if (!$.data(this, "plugin_" + pluginName)) { 365 $.data( 366 this, 367 "plugin_" + pluginName, 368 new Accordion(this, options) 369 ); 370 } 371 }); 372 }; 373})(jQuery, 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.