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