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