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