diff --git a/.gitignore b/.gitignore
index 194af7a..8a47fe9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,6 @@
._*
.Spotlight-V100
.Trashes
-Icon?
ehthumbs.db
Thumbs.db
node_modules
diff --git a/.jshintrc b/.jshintrc
index cef76e2..6f3c896 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,7 +1,7 @@
{
"bitwise" : true,
"camelcase" : true,
- "curly" : true,
+ "curly" : false,
"eqeqeq" : true,
"forin" : false,
"immed" : true,
@@ -26,10 +26,14 @@
"addEvent" : true,
"removeEvent" : true,
"addClass" : true,
+ "hasClass" : true,
"removeClass" : true,
"setAttributes" : true,
"getChildren" : true,
+ "elemsByClassName" : true,
"forEach" : true,
+ "next" : true,
+ "closest" : true,
"options" : true,
"opts" : true,
"nav" : true
diff --git a/README.md b/README.md
index 884a60a..498878c 100644
--- a/README.md
+++ b/README.md
@@ -3,16 +3,17 @@
### Responsive navigation plugin without library dependencies and with fast touch screen support.
-[Responsive Nav](http://responsive-nav.com) is a tiny JavaScript plugin which weighs only ~1kb minified and Gzip’ed, and helps you to create a toggled navigation for small screens. It uses touch events and CSS3 transitions for the best possible performance. It also contains a “clever” workaround that makes it possible to transition from `height: 0` to `height: auto`, which isn’t normally possible with CSS3 transitions.
+[Responsive Nav](http://responsive-nav.com) is a tiny JavaScript plugin which weighs only 1.5kb minified and Gzip’ed, and helps you to create a toggled navigation for small screens. It uses touch events and CSS3 transitions for the best possible performance. It also contains a “clever” workaround that makes it possible to transition from `height: 0` to `height: auto`, which isn’t normally possible with CSS3 transitions.
#### Features:
* Simple, semantic markup.
-* Weighs only ~1kb minified and Gzip’ed.
+* Weighs only 1.5kb minified and Gzip’ed.
* Doesn’t require any external library.
* Uses CSS3 transitions and touch events.
* Supports RequireJS and multiple instances.
+* Supports nested sub-navigations up to 3 levels deep.
* Removes the 300ms delay between a physical tap and the click event.
* Makes it possible to use CSS3 transitions with height: auto.
* Built with accessibility in mind, meaning that everything works on screen readers and with JavaScript disabled, too.
@@ -95,19 +96,24 @@ See the [example code here](https://github.com/viljamis/responsive-nav.js/blob/m
`nav.resize();`
+# Changing the breakpoint
+
+Breakpoint is defined in the [responsive-nav.css](https://github.com/viljamis/responsive-nav.js/blob/master/responsive-nav.css) file. Responsive Nav checks on window resize and on orientation change if the navigation toggle has `display: none;` and based on that switches between mobile and desktop states.
+
+
# Supporting old IEs
Even though Responsive Nav works even on IE6, you should remember that IE8 and under do not support media queries and thus can’t change between "small screen" and "large screen" styles. If needed, you can add Media Query support for those browsers using [respond.js](https://github.com/scottjehl/Respond). There’s an example [here](https://github.com/viljamis/responsive-nav.js/tree/master/demos/ie-support-using-respondjs).
-When old IE support is needed you should stick to using only ID selectors with Responsive Nav. That’s because the plugin uses `getElementById` method by default which is widely supported in all browsers. When using classes or element selectors `querySelector` will be used instead which isn’t supported in old IEs.
-
-`closeOnNavClick` option is the only thing that doesn’t work in IE8 & under, but the functionality is built using progressive enhancement, so nothing will break if you want to enable it for other browsers.
+When old IE support is needed you should stick to using ID selector with Responsive Nav. That’s because the plugin uses `getElementById` method by default which is widely supported in all browsers. When using classes or element selectors `querySelector` will be used instead which isn’t supported in old IEs.
# Things to keep in mind
Calculated Max-height doesn't account for top/bottom padding on `.nav-collapse` (this is on purpose). If you need to add padding inside the nav, you can apply it to any other element, for example the `
` inside `.nav-collapse`.
+Currently, you can't use "closeOnNavClick" option and sub-navigations at the same time (this is on purpose too, as it wouldn't make much sense anyway).
+
# Tested on the following platforms
@@ -219,6 +225,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
# Changelog
+`1.0.40` (2014-03-XX) - Major update. Adds support for nested sub-navigations!!! Currently supports navigations up to 3 levels. Sub-navigation toggles use the same method as the main navigation toggle to get rid of the 300ms tap delay, so they feel FAST. "closeOnNavClick" now works on old IEs too, so no more features that only work in modern browsers. Fixes a bug which caused the navigation to sometimes not toggle. Sub-navigations are fully keyboard accessible too (using tab key and enter).
+
`1.0.32` (2014-03-05) - Ditching the `[].forEach.call(NodeList)` hack to make the code more sensible and future-proof.
`1.0.31` (2014-03-02) - Fixes Chrome Mobile rendering issue.
diff --git a/bower.json b/bower.json
index d602080..70da30e 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "responsive-nav",
- "version": "1.0.32",
+ "version": "1.0.40",
"main": [
"responsive-nav.css",
"responsive-nav.js"
diff --git a/client/dist/bower/bower.json b/client/dist/bower/bower.json
index d602080..70da30e 100644
--- a/client/dist/bower/bower.json
+++ b/client/dist/bower/bower.json
@@ -1,6 +1,6 @@
{
"name": "responsive-nav",
- "version": "1.0.32",
+ "version": "1.0.40",
"main": [
"responsive-nav.css",
"responsive-nav.js"
diff --git a/client/dist/responsive-nav.js b/client/dist/responsive-nav.js
index 6336d3c..abe520a 100644
--- a/client/dist/responsive-nav.js
+++ b/client/dist/responsive-nav.js
@@ -1,4 +1,4 @@
-/*! responsive-nav.js 1.0.32
+/*! responsive-nav.js 1.0.40
* https://github.com/viljamis/responsive-nav.js
* http://responsive-nav.com
*
@@ -7,18 +7,22 @@
*/
(function (document, window, index) {
+ // Index is used to keep multiple navs on the same page namespaced
"use strict";
var responsiveNav = function (el, options) {
var computed = !!window.getComputedStyle;
+ var elemsByClassName = !!document.getElementsByClassName;
- // getComputedStyle polyfill
+ /**
+ * getComputedStyle polyfill for old browsers
+ */
if (!computed) {
- window.getComputedStyle = function(el) {
+ window.getComputedStyle = function (el) {
this.el = el;
- this.getPropertyValue = function(prop) {
+ this.getPropertyValue = function (prop) {
var re = /(\-([a-z]){1})/g;
if (prop === "float") {
prop = "styleFloat";
@@ -33,9 +37,37 @@
return this;
};
}
- /* exported addEvent, removeEvent, getChildren, setAttributes, addClass, removeClass, forEach */
- // fn arg can be an object or a function, thanks to handleEvent
- // read more at: http://www.thecssninja.com/javascript/handleevent
+
+ /**
+ * GetElementsByClassName polyfill for old browsers
+ */
+ if (!elemsByClassName) {
+ document.getElementsByClassName = function (match) {
+ var result = [],
+ elements = document.getElementsByTagName("*"),
+ i, elem;
+ match = " " + match + " ";
+ for (i = 0; i < elements.length; i++) {
+ elem = elements[i];
+ if ((" " + (elem.className || elem.getAttribute("class")) + " ").indexOf(match) > -1) {
+ result.push(elem);
+ }
+ }
+ return result;
+ };
+ }
+ /* exported addEvent, removeEvent, getChildren, setAttributes, addClass, removeClass, hasClass, forEach */
+
+ /**
+ * Add Event
+ * fn arg can be an object or a function, thanks to handleEvent
+ * read more at: http://www.thecssninja.com/javascript/handleevent
+ *
+ * @param {element} element
+ * @param {event} event
+ * @param {Function} fn
+ * @param {boolean} bubbling
+ */
var addEvent = function (el, evt, fn, bubble) {
if ("addEventListener" in el) {
// BBOS6 doesn't support handleEvent, catch and polyfill
@@ -64,6 +96,14 @@
}
},
+ /**
+ * Remove Event
+ *
+ * @param {element} element
+ * @param {event} event
+ * @param {Function} fn
+ * @param {boolean} bubbling
+ */
removeEvent = function (el, evt, fn, bubble) {
if ("removeEventListener" in el) {
try {
@@ -88,6 +128,12 @@
}
},
+ /**
+ * Get the children of any element
+ *
+ * @param {element}
+ * @return {array} Returns matching elements in an array
+ */
getChildren = function (e) {
if (e.children.length < 1) {
throw new Error("The Nav container has no containing elements");
@@ -103,25 +149,60 @@
return children;
},
+ /**
+ * Sets multiple attributes at once
+ *
+ * @param {element} element
+ * @param {attrs} attrs
+ */
setAttributes = function (el, attrs) {
for (var key in attrs) {
el.setAttribute(key, attrs[key]);
}
},
+ /**
+ * Adds a class to any element
+ *
+ * @param {element} element
+ * @param {string} class
+ */
addClass = function (el, cls) {
- if (el.className.indexOf(cls) !== 0) {
+ if (!hasClass(el, cls)) {
el.className += " " + cls;
el.className = el.className.replace(/(^\s*)|(\s*$)/g,"");
}
},
+ /**
+ * Remove a class from any element
+ *
+ * @param {element} element
+ * @param {string} class
+ */
removeClass = function (el, cls) {
var reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
el.className = el.className.replace(reg, " ").replace(/(^\s*)|(\s*$)/g,"");
},
- // forEach method that passes back the stuff we need
+ /**
+ * Checks if an element has certain class
+ *
+ * @param {element} element
+ * @param {string} class name
+ * @return {Boolean}
+ */
+ hasClass = function (el, cls) {
+ return el.className && new RegExp("(\\s|^)" + cls + "(\\s|$)").test(el.className);
+ },
+
+ /**
+ * forEach method that passes back the stuff we need
+ *
+ * @param {array} array
+ * @param {Function} callback
+ * @param {scope} scope
+ */
forEach = function (array, callback, scope) {
for (var i = 0; i < array.length; i++) {
callback.call(scope, i, array[i]);
@@ -131,6 +212,7 @@
var nav,
opts,
navToggle,
+ dropdownToggle,
styleElement = document.createElement("style"),
htmlEl = document.documentElement,
hasAnimFinished,
@@ -140,7 +222,11 @@
var ResponsiveNav = function (el, options) {
var i;
- // Default options
+
+ /**
+ * Default options
+ * @type {Object}
+ */
this.options = {
animate: true, // Boolean: Use CSS3 transitions, true or false
transition: 284, // Integer: Speed of the transition, in milliseconds
@@ -194,7 +280,9 @@
ResponsiveNav.prototype = {
- // Public methods
+ /**
+ * Unattaches events and removes any classes that were added
+ */
destroy: function () {
this._removeStyles();
removeClass(nav, "closed");
@@ -213,6 +301,15 @@
removeEvent(navToggle, "keyup", this, false);
removeEvent(navToggle, "click", this, false);
+ var self = this;
+ forEach(dropdownToggle, function (i, el) {
+ removeEvent(el, "touchstart", self, false);
+ removeEvent(el, "touchend", self, false);
+ removeEvent(el, "mouseup", self, false);
+ removeEvent(el, "keyup", self, false);
+ removeEvent(el, "click", self, false);
+ });
+
if (!opts.customToggle) {
navToggle.parentNode.removeChild(navToggle);
} else {
@@ -220,6 +317,9 @@
}
},
+ /**
+ * Toggles the navigation open/close
+ */
toggle: function () {
if (hasAnimFinished === true) {
if (!navOpen) {
@@ -227,9 +327,15 @@
} else {
this.close();
}
+
+ // Enable pointer events again
+ this._enablePointerEvents();
}
},
+ /**
+ * Opens the navigation
+ */
open: function () {
if (!navOpen) {
removeClass(nav, "closed");
@@ -243,6 +349,9 @@
}
},
+ /**
+ * Closes the navigation
+ */
close: function () {
if (navOpen) {
addClass(nav, "closed");
@@ -251,14 +360,25 @@
removeClass(navToggle, "active");
setAttributes(nav, {"aria-hidden": "true"});
+ // If animations are enabled, wait until they finish
if (opts.animate) {
hasAnimFinished = false;
setTimeout(function () {
nav.style.position = "absolute";
hasAnimFinished = true;
+ removeClass(nav, "dropdown-active");
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
}, opts.transition + 10);
+
+ // Animations aren't enabled, we can do these immediately
} else {
nav.style.position = "absolute";
+ removeClass(nav, "dropdown-active");
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
}
navOpen = false;
@@ -266,7 +386,13 @@
}
},
+ /**
+ * Resize is called on window resize and orientation change.
+ * It initializes the CSS styles and height calculations.
+ */
resize: function () {
+
+ // Resize watches navigation toggle's display state
if (window.getComputedStyle(navToggle, null).getPropertyValue("display") !== "none") {
isMobile = true;
@@ -286,10 +412,20 @@
setAttributes(navToggle, {"aria-hidden": "true"});
setAttributes(nav, {"aria-hidden": "false"});
nav.style.position = opts.openPos;
+ removeClass(nav, "dropdown-active");
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
this._removeStyles();
}
},
+ /**
+ * Takes care of all even handling
+ *
+ * @param {event} event
+ * @return {type} returns the type of event that should be used
+ */
handleEvent: function (e) {
var evt = e || window.event;
@@ -316,7 +452,9 @@
}
},
- // Private methods
+ /**
+ * Initializes the widget
+ */
_init: function () {
this.index = index++;
@@ -328,10 +466,15 @@
this._closeOnNavClick();
this._createToggle();
+ this._createDropdowns();
this._transitions();
this.resize();
- // IE8 hack
+ /**
+ * On IE8 the resize event triggers too early for some reason
+ * so it's called here again on init to make sure all the
+ * calculated styles are correct.
+ */
var self = this;
setTimeout(function () {
self.resize();
@@ -345,10 +488,15 @@
addEvent(navToggle, "keyup", this, false);
addEvent(navToggle, "click", this, false);
- // Init callback
+ /**
+ * Init callback here
+ */
opts.init();
},
+ /**
+ * Creates Styles to the
+ */
_createStyles: function () {
if (!styleElement.parentNode) {
styleElement.type = "text/css";
@@ -356,13 +504,19 @@
}
},
+ /**
+ * Removes styles from the
+ */
_removeStyles: function () {
- if (styleElement.parentNode) {
- styleElement.parentNode.removeChild(styleElement);
- }
+ if (styleElement.parentNode) styleElement.parentNode.removeChild(styleElement);
},
+ /**
+ * Creates Navigation Toggle
+ */
_createToggle: function () {
+
+ // If there's no toggle, let's create one
if (!opts.customToggle) {
var toggle = document.createElement("a");
toggle.innerHTML = opts.label;
@@ -371,6 +525,7 @@
"class": "nav-toggle"
});
+ // Determine where to insert the toggle
if (opts.insert === "after") {
nav.parentNode.insertBefore(toggle, nav.nextSibling);
} else {
@@ -378,6 +533,8 @@
}
navToggle = toggle;
+
+ // There is a toggle already, let's use that one
} else {
var toggleEl = opts.customToggle.replace("#", "");
@@ -391,75 +548,225 @@
}
},
+ /**
+ * Attaches event listeners to all sub-navigation toggles
+ */
+ _createDropdowns: function () {
+ var self = this;
+
+ // If getElementsByClassName is supported
+ if (elemsByClassName) {
+ dropdownToggle = nav.getElementsByClassName("dropdown-toggle");
+
+ // If not, use polyfill version (on old IEs)
+ } else {
+ dropdownToggle = document.getElementsByClassName("dropdown-toggle");
+ }
+
+ forEach(dropdownToggle, function (i, el) {
+ addEvent(el, "touchstart", self, false);
+ addEvent(el, "touchend", self, false);
+ addEvent(el, "mouseup", self, false);
+ addEvent(el, "keyup", self, false);
+ addEvent(el, "click", self, false);
+ });
+ },
+
+ /**
+ * Toggles sub-navigations open/closed
+ *
+ * @param {element} The toggle that was tapped
+ */
+ _toggleDropdown: function (targetEl) {
+
+ // Enable active class to let the navigation expand over
+ // the calculated max height
+ addClass(nav, "dropdown-active");
+
+ // Check if the sub-navigation is inside another sub-nav
+ var parentEl = targetEl.parentNode,
+ isInsideSub = hasClass(parentEl.parentNode.parentNode, "dropdown");
+
+ // If it's already open, close it
+ if (hasClass(parentEl, "opened")) {
+ removeClass(parentEl, "opened");
+
+ // If not inside sub-navigation, we can reset the whole nav
+ if (!isInsideSub) removeClass(nav, "dropdown-active");
+
+ // navigation is closed, let's open it
+ } else {
+
+ // If it's inside sub-navigation
+ if (isInsideSub) {
+ forEach(getChildren(parentEl.parentNode), function (i, el) {
+ removeClass(el, "opened");
+ });
+ addClass(parentEl, "opened");
+
+ // Not inside another sub-nav
+ } else {
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
+ addClass(parentEl, "opened");
+ }
+ }
+
+ // Enable pointer events again
+ this._enablePointerEvents();
+ },
+
+ /**
+ * Closes the navigation when a link inside is clicked
+ */
_closeOnNavClick: function () {
- if (opts.closeOnNavClick && "querySelectorAll" in document) {
- var links = nav.querySelectorAll("a"),
+ if (opts.closeOnNavClick) {
+ var links = nav.getElementsByTagName("a"),
self = this;
forEach(links, function (i, el) {
addEvent(links[i], "click", function () {
- if (isMobile) {
- self.toggle();
- }
+ if (isMobile) self.toggle();
}, false);
});
}
},
+ /**
+ * Prevents the default tap functionality
+ *
+ * @param {event} event
+ */
_preventDefault: function(e) {
+ // If you want dropdown links to be clickable on desktop remove comments:
+ /*if (!isMobile) return;*/
if (e.preventDefault) {
+ if (e.stopImmediatePropagation) e.stopImmediatePropagation();
e.preventDefault();
e.stopPropagation();
+ return false;
+
+ // This is strictly for old IE
} else {
e.returnValue = false;
}
},
+ /**
+ * On touch start get the location of the touch
+ * and disable pointer events on the body.
+ *
+ * @param {event} event
+ */
_onTouchStart: function (e) {
- e.stopPropagation();
- if (opts.insert === "after") {
- addClass(document.body, "disable-pointer-events");
- }
+ this._preventDefault(e);
+ addClass(document.body, "disable-pointer-events");
this.startX = e.touches[0].clientX;
this.startY = e.touches[0].clientY;
this.touchHasMoved = false;
- removeEvent(navToggle, "mouseup", this, false);
+
+ /**
+ * We remove mouseup event completely here to avoid
+ * double triggering of events.
+ */
+ removeEvent(e.target, "mouseup", this, false);
},
+ /**
+ * Check if the user is scrolling instead of tapping and
+ * re-enable pointer events if movement happed.
+ *
+ * @param {event} event
+ */
_onTouchMove: function (e) {
if (Math.abs(e.touches[0].clientX - this.startX) > 10 ||
Math.abs(e.touches[0].clientY - this.startY) > 10) {
+ this._enablePointerEvents();
this.touchHasMoved = true;
}
},
+ /**
+ * On touch end toggle either the whole navigation or
+ * a sub-navigation depending on which one was tapped.
+ *
+ * @param {event} event
+ */
_onTouchEnd: function (e) {
this._preventDefault(e);
+ if (!isMobile) return;
+
+ // Get event.target, the old IE way
+ var thisEvent = e || window.event,
+ targetEl = thisEvent.target || thisEvent.srcElement,
+ isDropdownTapped = false;
+
+ // Was it sub-navigation toggle or the main toggle?
+ if (hasClass(targetEl, "dropdown-toggle")) isDropdownTapped = true;
+
+ // If the user isn't scrolling
if (!this.touchHasMoved) {
+
+ // If the event type is touch
if (e.type === "touchend") {
- this.toggle();
- if (opts.insert === "after") {
- setTimeout(function () {
- removeClass(document.body, "disable-pointer-events");
- }, opts.transition + 300);
+
+ // If sub-navigation toggle was tapped
+ if (isDropdownTapped) {
+ this._toggleDropdown(targetEl);
+
+ // If the main toggle was tapped
+ } else {
+ this.toggle();
}
return;
+
+ // Event type was click, not touch
} else {
var evt = e || window.event;
- // If it isn't a right click
+
+ // If it isn't a right click, do toggling
if (!(evt.which === 3 || evt.button === 2)) {
- this.toggle();
+ if (isDropdownTapped) {
+ this._toggleDropdown(targetEl);
+ } else {
+ this.toggle();
+ }
}
}
}
},
+ /**
+ * For keyboard accessibility, toggle the navigation on Enter
+ * keypress too (also sub-navigation is keyboard accessible
+ * which explains the complexity here)
+ *
+ * @param {event} event
+ */
_onKeyUp: function (e) {
- var evt = e || window.event;
- if (evt.keyCode === 13) {
- this.toggle();
+ var evt = e || window.event,
+ targetEl = e.target,
+ isDropdownTapped = false;
+ if (hasClass(targetEl, "dropdown-toggle")) isDropdownTapped = true;
+ if (evt.keyCode === 13) {
+ if (isDropdownTapped) {
+ this._toggleDropdown(targetEl);
+ } else {
+ this.toggle();
+ }
}
},
+ /**
+ * Enable pointer events
+ */
+ _enablePointerEvents: function () {
+ removeClass(document.body, "disable-pointer-events");
+ },
+
+ /**
+ * Adds the needed CSS transitions if animations are enabled
+ */
_transitions: function () {
if (opts.animate) {
var objStyle = nav.style,
@@ -472,12 +779,18 @@
}
},
+ /**
+ * Calculates the height of the navigation and then creates
+ * styles which are later added to the page
+ */
_calcHeight: function () {
var savedHeight = 0;
for (var i = 0; i < nav.inner.length; i++) {
savedHeight += nav.inner[i].offsetHeight;
}
- var innerStyles = "." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened{max-height:" + savedHeight + "px !important}";
+
+ // Pointer event styles are also here since they might only be confusing inside the stylesheet
+ var innerStyles = "." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened{max-height:" + savedHeight + "px !important} ." + opts.jsClass + " .disable-pointer-events{pointer-events:none !important} ." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened.dropdown-active {max-height:9999px !important}";
if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = innerStyles;
@@ -490,6 +803,9 @@
};
+ /**
+ * Return new Responsive Nav
+ */
return new ResponsiveNav(el, options);
};
diff --git a/client/dist/responsive-nav.min.js b/client/dist/responsive-nav.min.js
index 714c165..8f8afdc 100644
--- a/client/dist/responsive-nav.min.js
+++ b/client/dist/responsive-nav.min.js
@@ -1 +1 @@
-!function(a,b,c){"use strict";var d=function(d,e){var f=!!b.getComputedStyle;f||(b.getComputedStyle=function(a){return this.el=a,this.getPropertyValue=function(b){var c=/(\-([a-z]){1})/g;return"float"===b&&(b="styleFloat"),c.test(b)&&(b=b.replace(c,function(){return arguments[2].toUpperCase()})),a.currentStyle[b]?a.currentStyle[b]:null},this});var g,h,i,j,k,l,m=function(a,b,c,d){if("addEventListener"in a)try{a.addEventListener(b,c,d)}catch(e){if("object"!=typeof c||!c.handleEvent)throw e;a.addEventListener(b,function(a){c.handleEvent.call(c,a)},d)}else"attachEvent"in a&&("object"==typeof c&&c.handleEvent?a.attachEvent("on"+b,function(){c.handleEvent.call(c)}):a.attachEvent("on"+b,c))},n=function(a,b,c,d){if("removeEventListener"in a)try{a.removeEventListener(b,c,d)}catch(e){if("object"!=typeof c||!c.handleEvent)throw e;a.removeEventListener(b,function(a){c.handleEvent.call(c,a)},d)}else"detachEvent"in a&&("object"==typeof c&&c.handleEvent?a.detachEvent("on"+b,function(){c.handleEvent.call(c)}):a.detachEvent("on"+b,c))},o=function(a){if(a.children.length<1)throw new Error("The Nav container has no containing elements");for(var b=[],c=0;c10||Math.abs(a.touches[0].clientY-this.startY)>10)&&(this.touchHasMoved=!0)},_onTouchEnd:function(c){if(this._preventDefault(c),!this.touchHasMoved){if("touchend"===c.type)return this.toggle(),"after"===h.insert&&setTimeout(function(){r(a.body,"disable-pointer-events")},h.transition+300),void 0;var d=c||b.event;3!==d.which&&2!==d.button&&this.toggle()}},_onKeyUp:function(a){var c=a||b.event;13===c.keyCode&&this.toggle()},_transitions:function(){if(h.animate){var a=g.style,b="max-height "+h.transition+"ms";a.WebkitTransition=b,a.MozTransition=b,a.OTransition=b,a.transition=b}},_calcHeight:function(){for(var a=0,b=0;b-1&&e.push(d);return e});var h,i,j,k,l,m,n,o=function(a,b,c,d){if("addEventListener"in a)try{a.addEventListener(b,c,d)}catch(e){if("object"!=typeof c||!c.handleEvent)throw e;a.addEventListener(b,function(a){c.handleEvent.call(c,a)},d)}else"attachEvent"in a&&("object"==typeof c&&c.handleEvent?a.attachEvent("on"+b,function(){c.handleEvent.call(c)}):a.attachEvent("on"+b,c))},p=function(a,b,c,d){if("removeEventListener"in a)try{a.removeEventListener(b,c,d)}catch(e){if("object"!=typeof c||!c.handleEvent)throw e;a.removeEventListener(b,function(a){c.handleEvent.call(c,a)},d)}else"detachEvent"in a&&("object"==typeof c&&c.handleEvent?a.detachEvent("on"+b,function(){c.handleEvent.call(c)}):a.detachEvent("on"+b,c))},q=function(a){if(a.children.length<1)throw new Error("The Nav container has no containing elements");for(var b=[],c=0;c10||Math.abs(a.touches[0].clientY-this.startY)>10)&&(this._enablePointerEvents(),this.touchHasMoved=!0)},_onTouchEnd:function(a){if(this._preventDefault(a),m){var c=a||b.event,d=c.target||c.srcElement,e=!1;if(u(d,"dropdown-toggle")&&(e=!0),!this.touchHasMoved){if("touchend"===a.type)return e?this._toggleDropdown(d):this.toggle(),void 0;var f=a||b.event;3!==f.which&&2!==f.button&&(e?this._toggleDropdown(d):this.toggle())}}},_onKeyUp:function(a){var c=a||b.event,d=a.target,e=!1;u(d,"dropdown-toggle")&&(e=!0),13===c.keyCode&&(e?this._toggleDropdown(d):this.toggle())},_enablePointerEvents:function(){t(a.body,"disable-pointer-events")},_transitions:function(){if(i.animate){var a=h.style,b="max-height "+i.transition+"ms";a.WebkitTransition=b,a.MozTransition=b,a.OTransition=b,a.transition=b}},_calcHeight:function(){for(var a=0,b=0;b ul,
+.nav-collapse .dropdown .dropdown.opened > ul {
+ position: relative;
}
-.nav-toggle {
+.nav-toggle,
+.dropdown-toggle {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;
-webkit-user-select: none;
@@ -44,7 +48,8 @@
.js .nav-collapse {
position: relative;
}
- .js .nav-collapse.closed {
+ .js .nav-collapse.closed,
+ .js .nav-collapse.closed .dropdown ul {
max-height: none;
}
.nav-toggle {
diff --git a/client/src/helpers.js b/client/src/helpers.js
index 90ad494..db9f172 100644
--- a/client/src/helpers.js
+++ b/client/src/helpers.js
@@ -1,6 +1,15 @@
-/* exported addEvent, removeEvent, getChildren, setAttributes, addClass, removeClass, forEach */
-// fn arg can be an object or a function, thanks to handleEvent
-// read more at: http://www.thecssninja.com/javascript/handleevent
+/* exported addEvent, removeEvent, getChildren, setAttributes, addClass, removeClass, hasClass, forEach */
+
+/**
+ * Add Event
+ * fn arg can be an object or a function, thanks to handleEvent
+ * read more at: http://www.thecssninja.com/javascript/handleevent
+ *
+ * @param {element} element
+ * @param {event} event
+ * @param {Function} fn
+ * @param {boolean} bubbling
+ */
var addEvent = function (el, evt, fn, bubble) {
if ("addEventListener" in el) {
// BBOS6 doesn't support handleEvent, catch and polyfill
@@ -29,6 +38,14 @@ var addEvent = function (el, evt, fn, bubble) {
}
},
+ /**
+ * Remove Event
+ *
+ * @param {element} element
+ * @param {event} event
+ * @param {Function} fn
+ * @param {boolean} bubbling
+ */
removeEvent = function (el, evt, fn, bubble) {
if ("removeEventListener" in el) {
try {
@@ -53,6 +70,12 @@ var addEvent = function (el, evt, fn, bubble) {
}
},
+ /**
+ * Get the children of any element
+ *
+ * @param {element}
+ * @return {array} Returns matching elements in an array
+ */
getChildren = function (e) {
if (e.children.length < 1) {
throw new Error("The Nav container has no containing elements");
@@ -68,25 +91,60 @@ var addEvent = function (el, evt, fn, bubble) {
return children;
},
+ /**
+ * Sets multiple attributes at once
+ *
+ * @param {element} element
+ * @param {attrs} attrs
+ */
setAttributes = function (el, attrs) {
for (var key in attrs) {
el.setAttribute(key, attrs[key]);
}
},
+ /**
+ * Adds a class to any element
+ *
+ * @param {element} element
+ * @param {string} class
+ */
addClass = function (el, cls) {
- if (el.className.indexOf(cls) !== 0) {
+ if (!hasClass(el, cls)) {
el.className += " " + cls;
el.className = el.className.replace(/(^\s*)|(\s*$)/g,"");
}
},
+ /**
+ * Remove a class from any element
+ *
+ * @param {element} element
+ * @param {string} class
+ */
removeClass = function (el, cls) {
var reg = new RegExp("(\\s|^)" + cls + "(\\s|$)");
el.className = el.className.replace(reg, " ").replace(/(^\s*)|(\s*$)/g,"");
},
- // forEach method that passes back the stuff we need
+ /**
+ * Checks if an element has certain class
+ *
+ * @param {element} element
+ * @param {string} class name
+ * @return {Boolean}
+ */
+ hasClass = function (el, cls) {
+ return el.className && new RegExp("(\\s|^)" + cls + "(\\s|$)").test(el.className);
+ },
+
+ /**
+ * forEach method that passes back the stuff we need
+ *
+ * @param {array} array
+ * @param {Function} callback
+ * @param {scope} scope
+ */
forEach = function (array, callback, scope) {
for (var i = 0; i < array.length; i++) {
callback.call(scope, i, array[i]);
diff --git a/client/src/polyfills.js b/client/src/polyfills.js
index 084d0db..707392a 100644
--- a/client/src/polyfills.js
+++ b/client/src/polyfills.js
@@ -1,10 +1,13 @@
var computed = !!window.getComputedStyle;
+var elemsByClassName = !!document.getElementsByClassName;
-// getComputedStyle polyfill
+/**
+ * getComputedStyle polyfill for old browsers
+ */
if (!computed) {
- window.getComputedStyle = function(el) {
+ window.getComputedStyle = function (el) {
this.el = el;
- this.getPropertyValue = function(prop) {
+ this.getPropertyValue = function (prop) {
var re = /(\-([a-z]){1})/g;
if (prop === "float") {
prop = "styleFloat";
@@ -19,3 +22,22 @@ if (!computed) {
return this;
};
}
+
+/**
+ * GetElementsByClassName polyfill for old browsers
+ */
+if (!elemsByClassName) {
+ document.getElementsByClassName = function (match) {
+ var result = [],
+ elements = document.getElementsByTagName("*"),
+ i, elem;
+ match = " " + match + " ";
+ for (i = 0; i < elements.length; i++) {
+ elem = elements[i];
+ if ((" " + (elem.className || elem.getAttribute("class")) + " ").indexOf(match) > -1) {
+ result.push(elem);
+ }
+ }
+ return result;
+ };
+}
diff --git a/client/src/responsive-nav.js b/client/src/responsive-nav.js
index 60684b3..de812be 100644
--- a/client/src/responsive-nav.js
+++ b/client/src/responsive-nav.js
@@ -7,6 +7,7 @@
*/
(function (document, window, index) {
+ // Index is used to keep multiple navs on the same page namespaced
"use strict";
@@ -18,6 +19,7 @@
var nav,
opts,
navToggle,
+ dropdownToggle,
styleElement = document.createElement("style"),
htmlEl = document.documentElement,
hasAnimFinished,
@@ -27,7 +29,11 @@
var ResponsiveNav = function (el, options) {
var i;
- // Default options
+
+ /**
+ * Default options
+ * @type {Object}
+ */
this.options = {
animate: true, // Boolean: Use CSS3 transitions, true or false
transition: 284, // Integer: Speed of the transition, in milliseconds
@@ -81,7 +87,9 @@
ResponsiveNav.prototype = {
- // Public methods
+ /**
+ * Unattaches events and removes any classes that were added
+ */
destroy: function () {
this._removeStyles();
removeClass(nav, "closed");
@@ -100,6 +108,15 @@
removeEvent(navToggle, "keyup", this, false);
removeEvent(navToggle, "click", this, false);
+ var self = this;
+ forEach(dropdownToggle, function (i, el) {
+ removeEvent(el, "touchstart", self, false);
+ removeEvent(el, "touchend", self, false);
+ removeEvent(el, "mouseup", self, false);
+ removeEvent(el, "keyup", self, false);
+ removeEvent(el, "click", self, false);
+ });
+
if (!opts.customToggle) {
navToggle.parentNode.removeChild(navToggle);
} else {
@@ -107,6 +124,9 @@
}
},
+ /**
+ * Toggles the navigation open/close
+ */
toggle: function () {
if (hasAnimFinished === true) {
if (!navOpen) {
@@ -114,9 +134,15 @@
} else {
this.close();
}
+
+ // Enable pointer events again
+ this._enablePointerEvents();
}
},
+ /**
+ * Opens the navigation
+ */
open: function () {
if (!navOpen) {
removeClass(nav, "closed");
@@ -130,6 +156,9 @@
}
},
+ /**
+ * Closes the navigation
+ */
close: function () {
if (navOpen) {
addClass(nav, "closed");
@@ -138,14 +167,25 @@
removeClass(navToggle, "active");
setAttributes(nav, {"aria-hidden": "true"});
+ // If animations are enabled, wait until they finish
if (opts.animate) {
hasAnimFinished = false;
setTimeout(function () {
nav.style.position = "absolute";
hasAnimFinished = true;
+ removeClass(nav, "dropdown-active");
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
}, opts.transition + 10);
+
+ // Animations aren't enabled, we can do these immediately
} else {
nav.style.position = "absolute";
+ removeClass(nav, "dropdown-active");
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
}
navOpen = false;
@@ -153,7 +193,13 @@
}
},
+ /**
+ * Resize is called on window resize and orientation change.
+ * It initializes the CSS styles and height calculations.
+ */
resize: function () {
+
+ // Resize watches navigation toggle's display state
if (window.getComputedStyle(navToggle, null).getPropertyValue("display") !== "none") {
isMobile = true;
@@ -173,10 +219,20 @@
setAttributes(navToggle, {"aria-hidden": "true"});
setAttributes(nav, {"aria-hidden": "false"});
nav.style.position = opts.openPos;
+ removeClass(nav, "dropdown-active");
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
this._removeStyles();
}
},
+ /**
+ * Takes care of all even handling
+ *
+ * @param {event} event
+ * @return {type} returns the type of event that should be used
+ */
handleEvent: function (e) {
var evt = e || window.event;
@@ -203,7 +259,9 @@
}
},
- // Private methods
+ /**
+ * Initializes the widget
+ */
_init: function () {
this.index = index++;
@@ -215,10 +273,15 @@
this._closeOnNavClick();
this._createToggle();
+ this._createDropdowns();
this._transitions();
this.resize();
- // IE8 hack
+ /**
+ * On IE8 the resize event triggers too early for some reason
+ * so it's called here again on init to make sure all the
+ * calculated styles are correct.
+ */
var self = this;
setTimeout(function () {
self.resize();
@@ -232,10 +295,15 @@
addEvent(navToggle, "keyup", this, false);
addEvent(navToggle, "click", this, false);
- // Init callback
+ /**
+ * Init callback here
+ */
opts.init();
},
+ /**
+ * Creates Styles to the
+ */
_createStyles: function () {
if (!styleElement.parentNode) {
styleElement.type = "text/css";
@@ -243,13 +311,19 @@
}
},
+ /**
+ * Removes styles from the
+ */
_removeStyles: function () {
- if (styleElement.parentNode) {
- styleElement.parentNode.removeChild(styleElement);
- }
+ if (styleElement.parentNode) styleElement.parentNode.removeChild(styleElement);
},
+ /**
+ * Creates Navigation Toggle
+ */
_createToggle: function () {
+
+ // If there's no toggle, let's create one
if (!opts.customToggle) {
var toggle = document.createElement("a");
toggle.innerHTML = opts.label;
@@ -258,6 +332,7 @@
"class": "nav-toggle"
});
+ // Determine where to insert the toggle
if (opts.insert === "after") {
nav.parentNode.insertBefore(toggle, nav.nextSibling);
} else {
@@ -265,6 +340,8 @@
}
navToggle = toggle;
+
+ // There is a toggle already, let's use that one
} else {
var toggleEl = opts.customToggle.replace("#", "");
@@ -278,75 +355,225 @@
}
},
+ /**
+ * Attaches event listeners to all sub-navigation toggles
+ */
+ _createDropdowns: function () {
+ var self = this;
+
+ // If getElementsByClassName is supported
+ if (elemsByClassName) {
+ dropdownToggle = nav.getElementsByClassName("dropdown-toggle");
+
+ // If not, use polyfill version (on old IEs)
+ } else {
+ dropdownToggle = document.getElementsByClassName("dropdown-toggle");
+ }
+
+ forEach(dropdownToggle, function (i, el) {
+ addEvent(el, "touchstart", self, false);
+ addEvent(el, "touchend", self, false);
+ addEvent(el, "mouseup", self, false);
+ addEvent(el, "keyup", self, false);
+ addEvent(el, "click", self, false);
+ });
+ },
+
+ /**
+ * Toggles sub-navigations open/closed
+ *
+ * @param {element} The toggle that was tapped
+ */
+ _toggleDropdown: function (targetEl) {
+
+ // Enable active class to let the navigation expand over
+ // the calculated max height
+ addClass(nav, "dropdown-active");
+
+ // Check if the sub-navigation is inside another sub-nav
+ var parentEl = targetEl.parentNode,
+ isInsideSub = hasClass(parentEl.parentNode.parentNode, "dropdown");
+
+ // If it's already open, close it
+ if (hasClass(parentEl, "opened")) {
+ removeClass(parentEl, "opened");
+
+ // If not inside sub-navigation, we can reset the whole nav
+ if (!isInsideSub) removeClass(nav, "dropdown-active");
+
+ // navigation is closed, let's open it
+ } else {
+
+ // If it's inside sub-navigation
+ if (isInsideSub) {
+ forEach(getChildren(parentEl.parentNode), function (i, el) {
+ removeClass(el, "opened");
+ });
+ addClass(parentEl, "opened");
+
+ // Not inside another sub-nav
+ } else {
+ forEach(dropdownToggle, function (i, el) {
+ removeClass(el.parentNode, "opened");
+ });
+ addClass(parentEl, "opened");
+ }
+ }
+
+ // Enable pointer events again
+ this._enablePointerEvents();
+ },
+
+ /**
+ * Closes the navigation when a link inside is clicked
+ */
_closeOnNavClick: function () {
- if (opts.closeOnNavClick && "querySelectorAll" in document) {
- var links = nav.querySelectorAll("a"),
+ if (opts.closeOnNavClick) {
+ var links = nav.getElementsByTagName("a"),
self = this;
forEach(links, function (i, el) {
addEvent(links[i], "click", function () {
- if (isMobile) {
- self.toggle();
- }
+ if (isMobile) self.toggle();
}, false);
});
}
},
+ /**
+ * Prevents the default tap functionality
+ *
+ * @param {event} event
+ */
_preventDefault: function(e) {
+ // If you want dropdown links to be clickable on desktop remove comments:
+ /*if (!isMobile) return;*/
if (e.preventDefault) {
+ if (e.stopImmediatePropagation) e.stopImmediatePropagation();
e.preventDefault();
e.stopPropagation();
+ return false;
+
+ // This is strictly for old IE
} else {
e.returnValue = false;
}
},
+ /**
+ * On touch start get the location of the touch
+ * and disable pointer events on the body.
+ *
+ * @param {event} event
+ */
_onTouchStart: function (e) {
- e.stopPropagation();
- if (opts.insert === "after") {
- addClass(document.body, "disable-pointer-events");
- }
+ this._preventDefault(e);
+ addClass(document.body, "disable-pointer-events");
this.startX = e.touches[0].clientX;
this.startY = e.touches[0].clientY;
this.touchHasMoved = false;
- removeEvent(navToggle, "mouseup", this, false);
+
+ /**
+ * We remove mouseup event completely here to avoid
+ * double triggering of events.
+ */
+ removeEvent(e.target, "mouseup", this, false);
},
+ /**
+ * Check if the user is scrolling instead of tapping and
+ * re-enable pointer events if movement happed.
+ *
+ * @param {event} event
+ */
_onTouchMove: function (e) {
if (Math.abs(e.touches[0].clientX - this.startX) > 10 ||
Math.abs(e.touches[0].clientY - this.startY) > 10) {
+ this._enablePointerEvents();
this.touchHasMoved = true;
}
},
+ /**
+ * On touch end toggle either the whole navigation or
+ * a sub-navigation depending on which one was tapped.
+ *
+ * @param {event} event
+ */
_onTouchEnd: function (e) {
this._preventDefault(e);
+ if (!isMobile) return;
+
+ // Get event.target, the old IE way
+ var thisEvent = e || window.event,
+ targetEl = thisEvent.target || thisEvent.srcElement,
+ isDropdownTapped = false;
+
+ // Was it sub-navigation toggle or the main toggle?
+ if (hasClass(targetEl, "dropdown-toggle")) isDropdownTapped = true;
+
+ // If the user isn't scrolling
if (!this.touchHasMoved) {
+
+ // If the event type is touch
if (e.type === "touchend") {
- this.toggle();
- if (opts.insert === "after") {
- setTimeout(function () {
- removeClass(document.body, "disable-pointer-events");
- }, opts.transition + 300);
+
+ // If sub-navigation toggle was tapped
+ if (isDropdownTapped) {
+ this._toggleDropdown(targetEl);
+
+ // If the main toggle was tapped
+ } else {
+ this.toggle();
}
return;
+
+ // Event type was click, not touch
} else {
var evt = e || window.event;
- // If it isn't a right click
+
+ // If it isn't a right click, do toggling
if (!(evt.which === 3 || evt.button === 2)) {
- this.toggle();
+ if (isDropdownTapped) {
+ this._toggleDropdown(targetEl);
+ } else {
+ this.toggle();
+ }
}
}
}
},
+ /**
+ * For keyboard accessibility, toggle the navigation on Enter
+ * keypress too (also sub-navigation is keyboard accessible
+ * which explains the complexity here)
+ *
+ * @param {event} event
+ */
_onKeyUp: function (e) {
- var evt = e || window.event;
- if (evt.keyCode === 13) {
- this.toggle();
+ var evt = e || window.event,
+ targetEl = e.target,
+ isDropdownTapped = false;
+ if (hasClass(targetEl, "dropdown-toggle")) isDropdownTapped = true;
+ if (evt.keyCode === 13) {
+ if (isDropdownTapped) {
+ this._toggleDropdown(targetEl);
+ } else {
+ this.toggle();
+ }
}
},
+ /**
+ * Enable pointer events
+ */
+ _enablePointerEvents: function () {
+ removeClass(document.body, "disable-pointer-events");
+ },
+
+ /**
+ * Adds the needed CSS transitions if animations are enabled
+ */
_transitions: function () {
if (opts.animate) {
var objStyle = nav.style,
@@ -359,12 +586,18 @@
}
},
+ /**
+ * Calculates the height of the navigation and then creates
+ * styles which are later added to the page
+ */
_calcHeight: function () {
var savedHeight = 0;
for (var i = 0; i < nav.inner.length; i++) {
savedHeight += nav.inner[i].offsetHeight;
}
- var innerStyles = "." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened{max-height:" + savedHeight + "px !important}";
+
+ // Pointer event styles are also here since they might only be confusing inside the stylesheet
+ var innerStyles = "." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened{max-height:" + savedHeight + "px !important} ." + opts.jsClass + " .disable-pointer-events{pointer-events:none !important} ." + opts.jsClass + " ." + opts.navClass + "-" + this.index + ".opened.dropdown-active {max-height:9999px !important}";
if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = innerStyles;
@@ -377,6 +610,9 @@
};
+ /**
+ * Return new Responsive Nav
+ */
return new ResponsiveNav(el, options);
};
diff --git a/client/src/styles/responsive-nav.css b/client/src/styles/responsive-nav.css
index 470d2d2..790f1b3 100644
--- a/client/src/styles/responsive-nav.css
+++ b/client/src/styles/responsive-nav.css
@@ -13,7 +13,8 @@
display: block;
}
-.js .nav-collapse {
+.js .nav-collapse,
+.js .nav-collapse .dropdown ul {
clip: rect(0 0 0 0);
max-height: 0;
position: absolute;
@@ -22,15 +23,18 @@
zoom: 1;
}
-.nav-collapse.opened {
+.nav-collapse.opened,
+.nav-collapse .dropdown.opened ul {
max-height: 9999px;
}
-.disable-pointer-events {
- pointer-events: none !important;
+.nav-collapse .dropdown.opened > ul,
+.nav-collapse .dropdown .dropdown.opened > ul {
+ position: relative;
}
-.nav-toggle {
+.nav-toggle,
+.dropdown-toggle {
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;
-webkit-user-select: none;
@@ -44,7 +48,8 @@
.js .nav-collapse {
position: relative;
}
- .js .nav-collapse.closed {
+ .js .nav-collapse.closed,
+ .js .nav-collapse.closed .dropdown ul {
max-height: none;
}
.nav-toggle {
diff --git a/client/test/responsive-nav.spec.js b/client/test/responsive-nav.spec.js
index 803cd43..b5555ec 100644
--- a/client/test/responsive-nav.spec.js
+++ b/client/test/responsive-nav.spec.js
@@ -45,7 +45,7 @@ describe("responsive-nav", function () {
insertNav();
var styleEl = document.getElementsByTagName("style")[0],
styleContents = styleEl.innerHTML || styleEl.styleSheet.cssText.replace(/\s+/g, "").replace(/\;/g, "");
- expect(styleContents.replace(/\.opened/g, "")).toBe(".js .nav-collapse-0{max-height:16px !important}");
+ expect(styleContents.replace(/\.opened/g, "")).toBe(".js .nav-collapse-0{max-height:16px !important} .js .disable-pointer-events{pointer-events:none !important} .js .nav-collapse-0.dropdown-active {max-height:9999px !important}");
nav.destroy();
});
@@ -112,7 +112,7 @@ describe("responsive-nav", function () {
insertNav();
var styleEl = document.getElementsByTagName("style")[0],
styleContents = styleEl.innerHTML || styleEl.styleSheet.cssText.replace(/\s+/g, "").replace(/\;/g, "");
- expect(styleContents.replace(/\.opened/g, "")).toBe(".js .nav-collapse-7{max-height:50px !important}");
+ expect(styleContents.replace(/\.opened/g, "")).toBe(".js .nav-collapse-7{max-height:50px !important} .js .disable-pointer-events{pointer-events:none !important} .js .nav-collapse-7.dropdown-active {max-height:9999px !important}");
nav.destroy();
});
diff --git a/demos/icons/responsivenav.eot b/demos/icons/responsivenav.eot
new file mode 100755
index 0000000..16b547e
Binary files /dev/null and b/demos/icons/responsivenav.eot differ
diff --git a/demos/icons/responsivenav.svg b/demos/icons/responsivenav.svg
new file mode 100755
index 0000000..e59f68f
--- /dev/null
+++ b/demos/icons/responsivenav.svg
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/demos/icons/responsivenav.ttf b/demos/icons/responsivenav.ttf
new file mode 100755
index 0000000..9da6226
Binary files /dev/null and b/demos/icons/responsivenav.ttf differ
diff --git a/demos/icons/responsivenav.woff b/demos/icons/responsivenav.woff
new file mode 100755
index 0000000..1b7f4f2
Binary files /dev/null and b/demos/icons/responsivenav.woff differ
diff --git a/demos/multiple-levels/index.html b/demos/multiple-levels/index.html
new file mode 100644
index 0000000..0f2edfe
--- /dev/null
+++ b/demos/multiple-levels/index.html
@@ -0,0 +1,65 @@
+
+
+
+
+ Responsive Nav · Multiple Levels Demo
+
+
+
+
+
+
+
+