1/*! responsive-nav.js v1.0.20 2 * https://github.com/viljamis/responsive-nav.js 3 * http://responsive-nav.com 4 * 5 * Copyright (c) 2013 @viljamis 6 * Available under the MIT license 7 */ 8 9/* jshint strict:false, forin:false, noarg:true, noempty:true, eqeqeq:true, 10boss:true, bitwise:true, browser:true, devel:true, indent:2 */ 11/* exported responsiveNav */ 12 13var responsiveNav = (function (window, document) { 14 15 var computed = !!window.getComputedStyle; 16 17 // getComputedStyle polyfill 18 if (!window.getComputedStyle) { 19 window.getComputedStyle = function(el) { 20 this.el = el; 21 this.getPropertyValue = function(prop) { 22 var re = /(\-([a-z]){1})/g; 23 if (prop === "float") { 24 prop = "styleFloat"; 25 } 26 if (re.test(prop)) { 27 prop = prop.replace(re, function () { 28 return arguments[2].toUpperCase(); 29 }); 30 } 31 return el.currentStyle[prop] ? el.currentStyle[prop] : null; 32 }; 33 return this; 34 }; 35 } 36 37 var nav, 38 opts, 39 navToggle, 40 styleElement = document.createElement("style"), 41 hasAnimFinished, 42 navOpen, 43 44 // fn arg can be an object or a function, thanks to handleEvent 45 // read more at: http://www.thecssninja.com/javascript/handleevent 46 addEvent = function (el, evt, fn, bubble) { 47 if ("addEventListener" in el) { 48 // BBOS6 doesn't support handleEvent, catch and polyfill 49 try { 50 el.addEventListener(evt, fn, bubble); 51 } catch (e) { 52 if (typeof fn === "object" && fn.handleEvent) { 53 el.addEventListener(evt, function (e) { 54 // Bind fn as this and set first arg as event object 55 fn.handleEvent.call(fn, e); 56 }, bubble); 57 } else { 58 throw e; 59 } 60 } 61 } else if ("attachEvent" in el) { 62 // check if the callback is an object and contains handleEvent 63 if (typeof fn === "object" && fn.handleEvent) { 64 el.attachEvent("on" + evt, function () { 65 // Bind fn as this 66 fn.handleEvent.call(fn); 67 }); 68 } else { 69 el.attachEvent("on" + evt, fn); 70 } 71 } 72 }, 73 74 removeEvent = function (el, evt, fn, bubble) { 75 if ("removeEventListener" in el) { 76 try { 77 el.removeEventListener(evt, fn, bubble); 78 } catch (e) { 79 if (typeof fn === "object" && fn.handleEvent) { 80 el.removeEventListener(evt, function (e) { 81 fn.handleEvent.call(fn, e); 82 }, bubble); 83 } else { 84 throw e; 85 } 86 } 87 } else if ("detachEvent" in el) { 88 if (typeof fn === "object" && fn.handleEvent) { 89 el.detachEvent("on" + evt, function () { 90 fn.handleEvent.call(fn); 91 }); 92 } else { 93 el.detachEvent("on" + evt, fn); 94 } 95 } 96 }, 97 98 getChildren = function (e) { 99 if (e.children.length < 1) { 100 throw new Error("The Nav container has no containing elements"); 101 } 102 // Store all children in array 103 var children = []; 104 // Loop through children and store in array if child != TextNode 105 for (var i = 0; i < e.children.length; i++) { 106 if (e.children[i].nodeType === 1) { 107 children.push(e.children[i]); 108 } 109 } 110 return children; 111 }, 112 113 setAttributes = function (el, attrs) { 114 for (var key in attrs) { 115 el.setAttribute(key, attrs[key]); 116 } 117 }, 118 119 addClass = function (el, cls) { 120 el.className += " " + cls; 121 el.className = el.className.replace(/(^\s*)|(\s*$)/g,""); 122 }, 123 124 removeClass = function (el, cls) { 125 var reg = new RegExp("(\\s|^)" + cls + "(\\s|$)"); 126 el.className = el.className.replace(reg, " ").replace(/(^\s*)|(\s*$)/g,""); 127 }, 128 129 ResponsiveNav = function (el, options) { 130 var i; 131 132 // Default options 133 this.options = { 134 animate: true, // Boolean: Use CSS3 transitions, true or false 135 transition: 350, // Integer: Speed of the transition, in milliseconds 136 label: "Menu", // String: Label for the navigation toggle 137 insert: "after", // String: Insert the toggle before or after the navigation 138 customToggle: "", // Selector: Specify the ID of a custom toggle 139 openPos: "relative", // String: Position of the opened nav, relative or static 140 jsClass: "js", // String: 'JS enabled' class which is added to <html> el 141 init: function(){}, // Function: Init callback 142 open: function(){}, // Function: Open callback 143 close: function(){} // Function: Close callback 144 }; 145 146 // User defined options 147 for (i in options) { 148 this.options[i] = options[i]; 149 } 150 151 // Adds "js" class for <html> 152 addClass(document.documentElement, this.options.jsClass); 153 154 // Wrapper 155 this.wrapperEl = el.replace("#", ""); 156 if (document.getElementById(this.wrapperEl)) { 157 this.wrapper = document.getElementById(this.wrapperEl); 158 } else { 159 // If el doesn't exists, stop here. 160 throw new Error("The nav element you are trying to select doesn't exist"); 161 } 162 163 // Inner wrapper 164 this.wrapper.inner = getChildren(this.wrapper); 165 166 // For minification 167 opts = this.options; 168 nav = this.wrapper; 169 170 // Init 171 this._init(this); 172 }; 173 174 ResponsiveNav.prototype = { 175 // Public methods 176 destroy: function () { 177 this._removeStyles(); 178 removeClass(nav, "closed"); 179 removeClass(nav, "opened"); 180 nav.removeAttribute("style"); 181 nav.removeAttribute("aria-hidden"); 182 nav = null; 183 _instance = null; 184 185 removeEvent(window, "resize", this, false); 186 removeEvent(document.body, "touchmove", this, false); 187 removeEvent(navToggle, "touchstart", this, false); 188 removeEvent(navToggle, "touchend", this, false); 189 removeEvent(navToggle, "keyup", this, false); 190 removeEvent(navToggle, "click", this, false); 191 removeEvent(navToggle, "mouseup", this, false); 192 193 if (!opts.customToggle) { 194 navToggle.parentNode.removeChild(navToggle); 195 } else { 196 navToggle.removeAttribute("aria-hidden"); 197 } 198 }, 199 200 toggle: function () { 201 if (hasAnimFinished === true) { 202 if (!navOpen) { 203 removeClass(nav, "closed"); 204 addClass(nav, "opened"); 205 nav.style.position = opts.openPos; 206 setAttributes(nav, {"aria-hidden": "false"}); 207 208 navOpen = true; 209 opts.open(); 210 } else { 211 removeClass(nav, "opened"); 212 addClass(nav, "closed"); 213 setAttributes(nav, {"aria-hidden": "true"}); 214 215 if (opts.animate) { 216 hasAnimFinished = false; 217 setTimeout(function () { 218 nav.style.position = "absolute"; 219 hasAnimFinished = true; 220 }, opts.transition + 10);
221 } else { 222 nav.style.position = "absolute"; 223 } 224 225 navOpen = false; 226 opts.close(); 227 } 228 } 229 }, 230 231 handleEvent: function (e) { 232 var evt = e || window.event; 233 234 switch (evt.type) { 235 case "touchstart": 236 this._onTouchStart(evt); 237 break; 238 case "touchmove": 239 this._onTouchMove(evt); 240 break; 241 case "touchend": 242 case "mouseup": 243 this._onTouchEnd(evt); 244 break; 245 case "click": 246 this._preventDefault(evt); 247 break; 248 case "keyup": 249 this._onKeyUp(evt); 250 break; 251 case "resize": 252 this._resize(evt); 253 break; 254 } 255 }, 256 257 // Private methods 258 _init: function () { 259 addClass(nav, "closed"); 260 hasAnimFinished = true; 261 navOpen = false; 262 263 this._createToggle(); 264 this._transitions(); 265 this._resize(); 266 267 addEvent(window, "resize", this, false); 268 addEvent(document.body, "touchmove", this, false); 269 addEvent(navToggle, "touchstart", this, false); 270 addEvent(navToggle, "touchend", this, false); 271 addEvent(navToggle, "mouseup", this, false); 272 addEvent(navToggle, "keyup", this, false); 273 addEvent(navToggle, "click", this, false); 274 275 // Init callback 276 opts.init(); 277 }, 278 279 _createStyles: function () { 280 if (!styleElement.parentNode) { 281 document.getElementsByTagName("head")[0].appendChild(styleElement); 282 } 283 }, 284 285 _removeStyles: function () { 286 if (styleElement.parentNode) { 287 styleElement.parentNode.removeChild(styleElement); 288 } 289 }, 290 291 _createToggle: function () { 292 if (!opts.customToggle) { 293 var toggle = document.createElement("a"); 294 toggle.innerHTML = opts.label; 295 setAttributes(toggle, { 296 "href": "#", 297 "id": "nav-toggle" 298 }); 299 300 if (opts.insert === "after") { 301 nav.parentNode.insertBefore(toggle, nav.nextSibling); 302 } else { 303 nav.parentNode.insertBefore(toggle, nav); 304 } 305 306 navToggle = document.getElementById("nav-toggle"); 307 } else { 308 var toggleEl = opts.customToggle.replace("#", ""); 309 310 if (document.getElementById(toggleEl)) { 311 navToggle = document.getElementById(toggleEl); 312 } else { 313 throw new Error("The custom nav toggle you are trying to select doesn't exist"); 314 } 315 } 316 }, 317 318 _preventDefault: function(e) { 319 if (e.preventDefault) { 320 e.preventDefault(); 321 e.stopPropagation(); 322 } else { 323 e.returnValue = false; 324 } 325 }, 326 327 _onTouchStart: function (e) { 328 e.stopPropagation(); 329 this.startX = e.touches[0].clientX; 330 this.startY = e.touches[0].clientY; 331 this.touchHasMoved = false; 332 removeEvent(navToggle, "mouseup", this, false); 333 }, 334 335 _onTouchMove: function (e) { 336 if (Math.abs(e.touches[0].clientX - this.startX) > 10 || 337 Math.abs(e.touches[0].clientY - this.startY) > 10) { 338 this.touchHasMoved = true; 339 } 340 }, 341 342 _onTouchEnd: function (e) { 343 this._preventDefault(e); 344 if (!this.touchHasMoved) { 345 if (e.type === "touchend") { 346 this.toggle(e); 347 // Prevent click on the underlying menu on Android 2.3 348 var that = this; 349 nav.addEventListener("click", that._preventDefault, true); 350 setTimeout(function () { 351 nav.removeEventListener("click", that._preventDefault, true); 352 }, opts.transition + 100); 353 return; 354 } else { 355 var evt = e || window.event; 356 // If it isn't a right click 357 if (!(evt.which === 3 || evt.button === 2)) { 358 this.toggle(e); 359 } 360 } 361 } 362 }, 363 364 _onKeyUp: function (e) { 365 var evt = e || window.event; 366 if (evt.keyCode === 13) { 367 this.toggle(e); 368 } 369 }, 370 371 _transitions: function () { 372 if (opts.animate) { 373 var objStyle = nav.style, 374 transition = "max-height " + opts.transition + "ms"; 375 376 objStyle.WebkitTransition = transition; 377 objStyle.MozTransition = transition; 378 objStyle.OTransition = transition; 379 objStyle.transition = transition; 380 } 381 }, 382 383 _calcHeight: function () { 384 var savedHeight = 0; 385 for (var i = 0; i < nav.inner.length; i++) { 386 savedHeight += nav.inner[i].offsetHeight; 387 } 388 389 var innerStyles = "#" + this.wrapperEl + ".opened{max-height:" + savedHeight + "px}"; 390 391 // Hide from old IE 392 if (computed) { 393 styleElement.innerHTML = innerStyles; 394 innerStyles = ""; 395 } 396 }, 397 398 _resize: function () { 399 if (window.getComputedStyle(navToggle, null).getPropertyValue("display") !== "none") { 400 setAttributes(navToggle, {"aria-hidden": "false"}); 401 402 // If the navigation is hidden 403 if (nav.className.match(/(^|\s)closed(\s|$)/)) { 404 setAttributes(nav, {"aria-hidden": "true"}); 405 nav.style.position = "absolute"; 406 } 407 408 this._createStyles(); 409 this._calcHeight(); 410 } else { 411 setAttributes(navToggle, {"aria-hidden": "true"}); 412 setAttributes(nav, {"aria-hidden": "false"}); 413 nav.style.position = opts.openPos; 414 this._removeStyles(); 415 } 416 } 417 418 }; 419 420 var _instance; 421 function rn (el, options) { 422 if (!_instance) { 423 _instance = new ResponsiveNav(el, options); 424 } 425 return _instance; 426 } 427 428 return rn; 429})(window, document);
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.