vendor: 5,203 bytes, lines 1-161
1;(function ($, document, window) { 2 var 3 // default settings object. 4 defaults = { 5 label: 'MENU', 6 duplicate: true, 7 duration: 200, 8 easingOpen: 'swing', 9 easingClose: 'swing', 10 closedSymbol: '►', 11 openedSymbol: '▼', 12 prependTo: 'body', 13 appendTo: '', 14 parentTag: 'a', 15 closeOnClick: false, 16 allowParentLinks: false, 17 nestedParentLinks: true, 18 showChildren: false, 19 removeIds: true, 20 removeClasses: false, 21 removeStyles: false, 22 brand: '', 23 animations: 'jquery', 24 init: function () {}, 25 beforeOpen: function () {}, 26 beforeClose: function () {}, 27 afterOpen: function () {}, 28 afterClose: function () {} 29 }, 30 mobileMenu = 'slicknav', 31 prefix = 'slicknav', 32 33 Keyboard = { 34 DOWN: 40, 35 ENTER: 13, 36 ESCAPE: 27, 37 LEFT: 37, 38 RIGHT: 39, 39 SPACE: 32, 40 TAB: 9, 41 UP: 38, 42 }; 43 44 function Plugin(element, options) { 45 this.element = element; 46 47 // jQuery has an extend method which merges the contents of two or 48 // more objects, storing the result in the first object. The first object 49 // is generally empty as we don't want to alter the default options for 50 // future instances of the plugin 51 this.settings = $.extend({}, defaults, options); 52 53 // Don't remove IDs by default if duplicate is false 54 if (!this.settings.duplicate && !options.hasOwnProperty("removeIds")) { 55 this.settings.removeIds = false; 56 } 57 58 this._defaults = defaults; 59 this._name = mobileMenu; 60 61 this.init(); 62 } 63 64 Plugin.prototype.init = function () { 65 var $this = this, 66 menu = $(this.element), 67 settings = this.settings, 68 iconClass, 69 menuBar; 70 71 // clone menu if needed 72 if (settings.duplicate) { 73 $this.mobileNav = menu.clone(); 74 } else { 75 $this.mobileNav = menu; 76 } 77 78 // remove IDs if set 79 if (settings.removeIds) { 80 $this.mobileNav.removeAttr('id'); 81 $this.mobileNav.find('*').each(function (i, e) { 82 $(e).removeAttr('id'); 83 }); 84 } 85 86 // remove classes if set 87 if (settings.removeClasses) { 88 $this.mobileNav.removeAttr('class'); 89 $this.mobileNav.find('*').each(function (i, e) { 90 $(e).removeAttr('class'); 91 }); 92 } 93 94 // remove styles if set 95 if (settings.removeStyles) { 96 $this.mobileNav.removeAttr('style'); 97 $this.mobileNav.find('*').each(function (i, e) { 98 $(e).removeAttr('style'); 99 }); 100 } 101 102 // styling class for the button 103 iconClass = prefix + '_icon'; 104 105 if (settings.label === '') { 106 iconClass += ' ' + prefix + '_no-text'; 107 } 108 109 if (settings.parentTag == 'a') { 110 settings.parentTag = 'a href="#"'; 111 } 112 113 // create menu bar 114 $this.mobileNav.attr('class', prefix + '_nav'); 115 menuBar = $('<div class="' + prefix + '_menu"></div>'); 116 if (settings.brand !== '') { 117 var brand = $('<div class="' + prefix + '_brand">'+settings.brand+'</div>'); 118 $(menuBar).append(brand); 119 } 120 $this.btn = $( 121 ['<' + settings.parentTag + ' aria-haspopup="true" role="button" tabindex="0" class="' + prefix + '_btn ' + prefix + '_collapsed">', 122 '<span class="' + prefix + '_menutxt">' + settings.label + '</span>', 123 '<span class="' + iconClass + '">', 124 '<span class="' + prefix + '_icon-bar"></span>', 125 '<span class="' + prefix + '_icon-bar"></span>', 126 '<span class="' + prefix + '_icon-bar"></span>', 127 '</span>', 128 '</' + settings.parentTag + '>' 129 ].join('') 130 ); 131 $(menuBar).append($this.btn); 132 if(settings.appendTo !== '') { 133 $(settings.appendTo).append(menuBar); 134 } else { 135 $(settings.prependTo).prepend(menuBar); 136 } 137 menuBar.append($this.mobileNav); 138 139 // iterate over structure adding additional structure 140 var items = $this.mobileNav.find('li'); 141 $(items).each(function () { 142 var item = $(this), 143 data = {}; 144 data.children = item.children('ul').attr('role', 'menu'); 145 item.data('menu', data); 146 147 // if a list item has a nested menu 148 if (data.children.length > 0) { 149 150 // select all text before the child menu 151 // check for anchors 152 153 var a = item.contents(), 154 containsAnchor = false, 155 nodes = []; 156 157 $(a).each(function () { 158 if (!$(this).is('ul')) { 159 nodes.push(this); 160 } else { 161 return false;
162 } 163 164 if($(this).is("a")) { 165 containsAnchor = true; 166 } 167 }); 168 169 var wrapElement = $( 170 '<' + settings.parentTag + ' role="menuitem" aria-haspopup="true" tabindex="-1" class="' + prefix + '_item"/>' 171 ); 172 173 // wrap item text with tag and add classes unless we are separating parent links 174 if ((!settings.allowParentLinks || settings.nestedParentLinks) || !containsAnchor) { 175 var $wrap = $(nodes).wrapAll(wrapElement).parent(); 176 $wrap.addClass(prefix+'_row'); 177 } else 178 $(nodes).wrapAll('<span class="'+prefix+'_parent-link '+prefix+'_row"/>').parent(); 179 180 if (!settings.showChildren) { 181 item.addClass(prefix+'_collapsed'); 182 } else { 183 item.addClass(prefix+'_open'); 184 } 185 186 item.addClass(prefix+'_parent'); 187 188 // create parent arrow. wrap with link if parent links and separating 189 var arrowElement = $('<span class="'+prefix+'_arrow">'+(settings.showChildren?settings.openedSymbol:settings.closedSymbol)+'</span>'); 190 191 if (settings.allowParentLinks && !settings.nestedParentLinks && containsAnchor) 192 arrowElement = arrowElement.wrap(wrapElement).parent(); 193 194 //append arrow 195 $(nodes).last().after(arrowElement); 196 197 198 } else if ( item.children().length === 0) { 199 item.addClass(prefix+'_txtnode'); 200 } 201 202 // accessibility for links 203 item.children('a').attr('role', 'menuitem').click(function(event){ 204 //Ensure that it's not a parent 205 if (settings.closeOnClick && !$(event.target).parent().closest('li').hasClass(prefix+'_parent')) { 206 //Emulate menu close if set 207 $($this.btn).click(); 208 } 209 }); 210 211 //also close on click if parent links are set 212 if (settings.closeOnClick && settings.allowParentLinks) { 213 item.children('a').children('a').click(function (event) { 214 //Emulate menu close 215 $($this.btn).click(); 216 }); 217 218 item.find('.'+prefix+'_parent-link a:not(.'+prefix+'_item)').click(function(event){ 219 //Emulate menu close 220 $($this.btn).click(); 221 }); 222 } 223 }); 224 225 // structure is in place, now hide appropriate items 226 $(items).each(function () { 227 var data = $(this).data('menu'); 228 if (!settings.showChildren){ 229 $this._visibilityToggle(data.children, null, false, null, true); 230 } 231 }); 232 233 // finally toggle entire menu 234 $this._visibilityToggle($this.mobileNav, null, false, 'init', true); 235 236 // accessibility for menu button 237 $this.mobileNav.attr('role','menu'); 238 239 // outline prevention when using mouse 240 $(document).mousedown(function(){ 241 $this._outlines(false); 242 }); 243 244 $(document).keyup(function(){ 245 $this._outlines(true); 246 }); 247 248 // menu button click 249 $($this.btn).click(function (e) { 250 e.preventDefault(); 251 $this._menuToggle(); 252 }); 253 254 // click on menu parent 255 $this.mobileNav.on('click', '.' + prefix + '_item', function (e) { 256 e.preventDefault(); 257 $this._itemClick($(this)); 258 }); 259 260 // check for keyboard events on menu button and menu parents 261 $($this.btn).keydown(function (e) { 262 var ev = e || event; 263 264 switch(ev.keyCode) { 265 case Keyboard.ENTER: 266 case Keyboard.SPACE: 267 case Keyboard.DOWN: 268 e.preventDefault(); 269 if (ev.keyCode !== Keyboard.DOWN || !$($this.btn).hasClass(prefix+'_open')){ 270 $this._menuToggle(); 271 } 272 273 $($this.btn).next().find('[role="menuitem"]').first().focus(); 274 break; 275 } 276 277 278 }); 279 280 $this.mobileNav.on('keydown', '.'+prefix+'_item', function(e) { 281 var ev = e || event; 282 283 switch(ev.keyCode) { 284 case Keyboard.ENTER: 285 e.preventDefault(); 286 $this._itemClick($(e.target)); 287 break; 288 case Keyboard.RIGHT: 289 e.preventDefault(); 290 if ($(e.target).parent().hasClass(prefix+'_collapsed')) { 291 $this._itemClick($(e.target)); 292 } 293 $(e.target).next().find('[role="menuitem"]').first().focus(); 294 break; 295 } 296 }); 297 298 $this.mobileNav.on('keydown', '[role="menuitem"]', function(e) { 299 var ev = e || event; 300 301 switch(ev.keyCode){
302 case Keyboard.DOWN: 303 e.preventDefault(); 304 var allItems = $(e.target).parent().parent().children().children('[role="menuitem"]:visible'); 305 var idx = allItems.index( e.target ); 306 var nextIdx = idx + 1; 307 if (allItems.length <= nextIdx) { 308 nextIdx = 0; 309 } 310 var next = allItems.eq( nextIdx ); 311 next.focus(); 312 break; 313 case Keyboard.UP: 314 e.preventDefault(); 315 var allItems = $(e.target).parent().parent().children().children('[role="menuitem"]:visible'); 316 var idx = allItems.index( e.target ); 317 var next = allItems.eq( idx - 1 ); 318 next.focus(); 319 break; 320 case Keyboard.LEFT: 321 e.preventDefault(); 322 if ($(e.target).parent().parent().parent().hasClass(prefix+'_open')) { 323 var parent = $(e.target).parent().parent().prev(); 324 parent.focus(); 325 $this._itemClick(parent); 326 } else if ($(e.target).parent().parent().hasClass(prefix+'_nav')){ 327 $this._menuToggle(); 328 $($this.btn).focus(); 329 } 330 break; 331 case Keyboard.ESCAPE: 332 e.preventDefault(); 333 $this._menuToggle(); 334 $($this.btn).focus(); 335 break; 336 } 337 }); 338 339 // allow links clickable within parent tags if set 340 if (settings.allowParentLinks && settings.nestedParentLinks) { 341 $('.'+prefix+'_item a').click(function(e){ 342 e.stopImmediatePropagation(); 343 }); 344 } 345 }; 346 347 //toggle menu 348 Plugin.prototype._menuToggle = function (el) { 349 var $this = this; 350 var btn = $this.btn; 351 var mobileNav = $this.mobileNav; 352 353 if (btn.hasClass(prefix+'_collapsed')) { 354 btn.removeClass(prefix+'_collapsed'); 355 btn.addClass(prefix+'_open'); 356 } else { 357 btn.removeClass(prefix+'_open'); 358 btn.addClass(prefix+'_collapsed'); 359 } 360 btn.addClass(prefix+'_animating'); 361 $this._visibilityToggle(mobileNav, btn.parent(), true, btn); 362 }; 363 364 // toggle clicked items 365 Plugin.prototype._itemClick = function (el) { 366 var $this = this; 367 var settings = $this.settings; 368 var data = el.data('menu'); 369 if (!data) { 370 data = {}; 371 data.arrow = el.children('.'+prefix+'_arrow'); 372 data.ul = el.next('ul'); 373 data.parent = el.parent(); 374 //Separated parent link structure 375 if (data.parent.hasClass(prefix+'_parent-link')) { 376 data.parent = el.parent().parent(); 377 data.ul = el.parent().next('ul'); 378 } 379 el.data('menu', data); 380 } 381 if (data.parent.hasClass(prefix+'_collapsed')) { 382 data.arrow.html(settings.openedSymbol); 383 data.parent.removeClass(prefix+'_collapsed'); 384 data.parent.addClass(prefix+'_open'); 385 data.parent.addClass(prefix+'_animating'); 386 $this._visibilityToggle(data.ul, data.parent, true, el); 387 } else { 388 data.arrow.html(settings.closedSymbol); 389 data.parent.addClass(prefix+'_collapsed'); 390 data.parent.removeClass(prefix+'_open'); 391 data.parent.addClass(prefix+'_animating'); 392 $this._visibilityToggle(data.ul, data.parent, true, el); 393 } 394 }; 395 396 // toggle actual visibility and accessibility tags 397 Plugin.prototype._visibilityToggle = function(el, parent, animate, trigger, init) { 398 var $this = this; 399 var settings = $this.settings; 400 var items = $this._getActionItems(el); 401 var duration = 0; 402 if (animate) { 403 duration = settings.duration; 404 } 405 406 function afterOpen(trigger, parent) { 407 $(trigger).removeClass(prefix+'_animating'); 408 $(parent).removeClass(prefix+'_animating'); 409 410 //Fire afterOpen callback 411 if (!init) { 412 settings.afterOpen(trigger); 413 } 414 } 415
vendor: 6,210 bytes, lines 416-584
416 function afterClose(trigger, parent) { 417 el.attr('aria-hidden','true'); 418 items.attr('tabindex', '-1'); 419 $this._setVisAttr(el, true); 420 el.hide(); //jQuery 1.7 bug fix 421 422 $(trigger).removeClass(prefix+'_animating'); 423 $(parent).removeClass(prefix+'_animating'); 424 425 //Fire init or afterClose callback 426 if (!init){ 427 settings.afterClose(trigger); 428 } else if (trigger == 'init'){ 429 settings.init(); 430 } 431 } 432 433 if (el.hasClass(prefix+'_hidden')) { 434 el.removeClass(prefix+'_hidden'); 435 //Fire beforeOpen callback 436 if (!init) { 437 settings.beforeOpen(trigger); 438 } 439 if (settings.animations === 'jquery') { 440 el.stop(true,true).slideDown(duration, settings.easingOpen, function(){ 441 afterOpen(trigger, parent); 442 }); 443 } else if(settings.animations === 'velocity') { 444 el.velocity("finish").velocity("slideDown", { 445 duration: duration, 446 easing: settings.easingOpen, 447 complete: function() { 448 afterOpen(trigger, parent); 449 } 450 }); 451 } 452 el.attr('aria-hidden','false'); 453 items.attr('tabindex', '0'); 454 $this._setVisAttr(el, false); 455 } else { 456 el.addClass(prefix+'_hidden'); 457 458 //Fire init or beforeClose callback 459 if (!init){ 460 settings.beforeClose(trigger); 461 } 462 463 if (settings.animations === 'jquery') { 464 el.stop(true,true).slideUp(duration, this.settings.easingClose, function() { 465 afterClose(trigger, parent) 466 }); 467 } else if (settings.animations === 'velocity') { 468 469 el.velocity("finish").velocity("slideUp", { 470 duration: duration, 471 easing: settings.easingClose, 472 complete: function() { 473 afterClose(trigger, parent); 474 } 475 }); 476 } 477 } 478 }; 479 480 // set attributes of element and children based on visibility 481 Plugin.prototype._setVisAttr = function(el, hidden) { 482 var $this = this; 483 484 // select all parents that aren't hidden 485 var nonHidden = el.children('li').children('ul').not('.'+prefix+'_hidden'); 486 487 // iterate over all items setting appropriate tags 488 if (!hidden) { 489 nonHidden.each(function(){ 490 var ul = $(this); 491 ul.attr('aria-hidden','false'); 492 var items = $this._getActionItems(ul); 493 items.attr('tabindex', '0'); 494 $this._setVisAttr(ul, hidden); 495 }); 496 } else { 497 nonHidden.each(function(){ 498 var ul = $(this); 499 ul.attr('aria-hidden','true'); 500 var items = $this._getActionItems(ul); 501 items.attr('tabindex', '-1'); 502 $this._setVisAttr(ul, hidden); 503 }); 504 } 505 }; 506 507 // get all 1st level items that are clickable 508 Plugin.prototype._getActionItems = function(el) { 509 var data = el.data("menu"); 510 if (!data) { 511 data = {}; 512 var items = el.children('li'); 513 var anchors = items.find('a'); 514 data.links = anchors.add(items.find('.'+prefix+'_item')); 515 el.data('menu', data); 516 } 517 return data.links; 518 }; 519 520 Plugin.prototype._outlines = function(state) { 521 if (!state) { 522 $('.'+prefix+'_item, .'+prefix+'_btn').css('outline','none'); 523 } else { 524 $('.'+prefix+'_item, .'+prefix+'_btn').css('outline',''); 525 } 526 }; 527 528 Plugin.prototype.toggle = function(){ 529 var $this = this; 530 $this._menuToggle(); 531 }; 532 533 Plugin.prototype.open = function(){ 534 var $this = this; 535 if ($this.btn.hasClass(prefix+'_collapsed')) { 536 $this._menuToggle(); 537 } 538 }; 539 540 Plugin.prototype.close = function(){ 541 var $this = this; 542 if ($this.btn.hasClass(prefix+'_open')) { 543 $this._menuToggle(); 544 } 545 }; 546 547 $.fn[mobileMenu] = function ( options ) { 548 var args = arguments; 549 550 // Is the first parameter an object (options), or was omitted, instantiate a new instance 551 if (options === undefined || typeof options === 'object') { 552 return this.each(function () { 553 554 // Only allow the plugin to be instantiated once due to methods 555 if (!$.data(this, 'plugin_' + mobileMenu)) { 556 557 // if it has no instance, create a new one, pass options to our plugin constructor, 558 // and store the plugin instance in the elements jQuery data object. 559 $.data(this, 'plugin_' + mobileMenu, new Plugin( this, options )); 560 } 561 }); 562 563 // If is a string and doesn't start with an underscore or 'init' function, treat this as a call to a public method. 564 } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { 565 566 // Cache the method call to make it possible to return a value 567 var returns; 568 569 this.each(function () { 570 var instance = $.data(this, 'plugin_' + mobileMenu); 571 572 // Tests that there's already a plugin-instance and checks that the requested public method exists 573 if (instance instanceof Plugin && typeof instance[options] === 'function') { 574 575 // Call the method of our plugin instance, and pass it the supplied arguments. 576 returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) ); 577 } 578 }); 579 580 // If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability. 581 return returns !== undefined ? returns : this; 582 } 583 }; 584}(jQuery, document, window));
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.