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