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: 'body', 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(){ 118 //Emulate menu close if set 119 if (settings.closeOnClick) 120 $($this.btn).click(); 121 }); 122 }); 123 124 // structure is in place, now hide appropriate items 125 $(items).each(function () { 126 var data = $(this).data("menu"); 127 $this._visibilityToggle(data.children, false, null, true); 128 }); 129 130 // finally toggle entire menu 131 $this._visibilityToggle($this.mobileNav, false, 'init', true); 132 133 // accessibility for menu button 134 $this.mobileNav.attr('role','menu'); 135 136 // outline prevention when using mouse 137 $(document).mousedown(function(){ 138 $this._outlines(false); 139 }); 140 141 $(document).keyup(function(){ 142 $this._outlines(true); 143 }); 144 145 // menu button click 146 $($this.btn).click(function (e) { 147 e.preventDefault(); 148 $this._menuToggle(); 149 }); 150 151 // click on menu parent 152 $this.mobileNav.on('click', '.'+prefix+'_item', function(e){ 153 e.preventDefault(); 154 $this._itemClick($(this)); 155 }); 156 157 // check for enter key on menu button and menu parents 158 $($this.btn).keydown(function (e) { 159 var ev = e || event; 160 if(ev.keyCode == 13) { 161 e.preventDefault(); 162 $this._menuToggle(); 163 } 164 }); 165 166 $this.mobileNav.on('keydown', '.'+prefix+'_item', function(e) { 167 var ev = e || event; 168 if(ev.keyCode == 13) { 169 e.preventDefault(); 170 $this._itemClick($(e.target)); 171 } 172 }); 173 174 // allow links clickable within parent tags if set 175 if (settings.allowParentLinks) { 176 $('.'+prefix+'_item a').click(function(e){ 177 e.stopImmediatePropagation(); 178 }); 179 } 180 }; 181 182 //toggle menu 183 Plugin.prototype._menuToggle = function(el){ 184 var $this = this; 185 var btn = $this.btn; 186 var mobileNav = $this.mobileNav; 187 188 if (btn.hasClass(prefix+'_collapsed')) { 189 btn.removeClass(prefix+'_collapsed');
190 btn.addClass(prefix+'_open'); 191 } else { 192 btn.removeClass(prefix+'_open'); 193 btn.addClass(prefix+'_collapsed'); 194 } 195 btn.addClass(prefix+'_animating'); 196 $this._visibilityToggle(mobileNav, true, btn); 197 } 198 199 // toggle clicked items 200 Plugin.prototype._itemClick = function(el) { 201 var $this = this; 202 var settings = $this.settings; 203 var data = el.data("menu"); 204 if (!data) { 205 data = {}; 206 data.arrow = el.children('.'+prefix+'_arrow'); 207 data.ul = el.next('ul'); 208 data.parent = el.parent(); 209 el.data("menu", data); 210 } 211 if (data.parent.hasClass(prefix+'_collapsed')) { 212 data.arrow.html(settings.openedSymbol); 213 data.parent.removeClass(prefix+'_collapsed'); 214 data.parent.addClass(prefix+'_open'); 215 data.parent.addClass(prefix+'_animating'); 216 $this._visibilityToggle(data.ul, true, el); 217 } else { 218 data.arrow.html(settings.closedSymbol); 219 data.parent.addClass(prefix+'_collapsed'); 220 data.parent.removeClass(prefix+'_open'); 221 data.parent.addClass(prefix+'_animating'); 222 $this._visibilityToggle(data.ul, true, el); 223 } 224 } 225 226 // toggle actual visibility and accessibility tags 227 Plugin.prototype._visibilityToggle = function(el, animate, trigger, init) { 228 var $this = this; 229 var settings = $this.settings; 230 var items = $this._getActionItems(el); 231 var duration = 0; 232 if (animate) 233 duration = settings.duration; 234 235 if (el.hasClass(prefix+'_hidden')) { 236 el.removeClass(prefix+'_hidden'); 237 el.slideDown(duration, settings.easingOpen, function(){ 238 239 $(trigger).removeClass(prefix+'_animating'); 240 $(trigger).parent().removeClass(prefix+'_animating'); 241 242 //Fire open callback 243 if (!init) { 244 settings.open(trigger); 245 } 246 }); 247 el.attr('aria-hidden','false'); 248 items.attr('tabindex', '0'); 249 $this._setVisAttr(el, false); 250 } else { 251 el.addClass(prefix+'_hidden'); 252 el.slideUp(duration, this.settings.easingClose, function() { 253 el.attr('aria-hidden','true'); 254 items.attr('tabindex', '-1'); 255 $this._setVisAttr(el, true); 256 el.hide(); //jQuery 1.7 bug fix 257 258 $(trigger).removeClass(prefix+'_animating'); 259 $(trigger).parent().removeClass(prefix+'_animating'); 260 261 //Fire init or close callback 262 if (!init) 263 settings.close(trigger); 264 else if (trigger == 'init') 265 settings.init(); 266 }); 267 } 268 } 269 270 // set attributes of element and children based on visibility 271 Plugin.prototype._setVisAttr = function(el, hidden) { 272 var $this = this; 273 274 // select all parents that aren't hidden 275 var nonHidden = el.children('li').children('ul').not('.'+prefix+'_hidden'); 276 277 // iterate over all items setting appropriate tags 278 if (!hidden) { 279 nonHidden.each(function(){ 280 var ul = $(this); 281 ul.attr('aria-hidden','false'); 282 var items = $this._getActionItems(ul); 283 items.attr('tabindex', '0'); 284 $this._setVisAttr(ul, hidden); 285 }); 286 } else { 287 nonHidden.each(function(){ 288 var ul = $(this); 289 ul.attr('aria-hidden','true'); 290 var items = $this._getActionItems(ul); 291 items.attr('tabindex', '-1'); 292 $this._setVisAttr(ul, hidden); 293 }); 294 } 295 } 296 297 // get all 1st level items that are clickable 298 Plugin.prototype._getActionItems = function(el) { 299 var data = el.data("menu"); 300 if (!data) { 301 data = {}; 302 var items = el.children('li'); 303 var anchors = items.children('a'); 304 data.links = anchors.add(items.children('.'+prefix+'_item')); 305 el.data("menu", data); 306 } 307 return data.links; 308 } 309 310 Plugin.prototype._outlines = function(state) { 311 if (!state) { 312 $('.'+prefix+'_item, .'+prefix+'_btn').css('outline','none'); 313 } else { 314 $('.'+prefix+'_item, .'+prefix+'_btn').css('outline',''); 315 } 316 } 317 318 Plugin.prototype.toggle = function(){ 319 $this._menuToggle(); 320 } 321 322 Plugin.prototype.open = function(){ 323 $this = this; 324 if ($this.btn.hasClass(prefix+'_collapsed')) { 325 $this._menuToggle(); 326 } 327 } 328 329 Plugin.prototype.close = function(){ 330 $this = this; 331 if ($this.btn.hasClass(prefix+'_open')) { 332 $this._menuToggle(); 333 } 334 } 335 336 $.fn[mobileMenu] = function ( options ) { 337 var args = arguments; 338 339 // Is the first parameter an object (options), or was omitted, instantiate a new instance 340 if (options === undefined || typeof options === 'object') { 341 return this.each(function () { 342 343 // Only allow the plugin to be instantiated once due to methods 344 if (!$.data(this, 'plugin_' + mobileMenu)) { 345 346 // if it has no instance, create a new one, pass options to our plugin constructor, 347 // and store the plugin instance in the elements jQuery data object. 348 $.data(this, 'plugin_' + mobileMenu, new Plugin( this, options )); 349 } 350 }); 351 352 // If is a string and doesn't start with an underscore or 'init' function, treat this as a call to a public method. 353 } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { 354 355 // Cache the method call to make it possible to return a value 356 var returns; 357 358 this.each(function () { 359 var instance = $.data(this, 'plugin_' + mobileMenu); 360 361 // Tests that there's already a plugin-instance and checks that the requested public method exists 362 if (instance instanceof Plugin && typeof instance[options] === 'function') { 363 364 // Call the method of our plugin instance, and pass it the supplied arguments. 365 returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) ); 366 } 367 }); 368 369 // If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability. 370 return returns !== undefined ? returns : this; 371 } 372 }; 373}(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.