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