Or should I say...
MonocleToggle is a standalone jQuery plugin that works out of the box with any conventionally marked-up navigation.
Just markup your nav like normal and MonocleToggle will do the rest.
<nav>
<ul>
<li><a href="#">Parent Link</a>
<ul>
<li><a href="#">Nested Link</a></li>
<li><a href="#">Nested Link</a></li>
<li><a href="#">Nested Link</a></li>
</ul>
</li>
</ul>
</nav>
Works with any number of nested ul's.
Usage:
.monocle-hover a,
.monocle-open a,
a:hover {
// Hover styles here
}
Demo:
So how do we get hover, tap, and click to play nice?
Previous methods have tried to serve up one block of code for touch devices and another block of code for traditional devices.
But eloquently addressed here, http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
a browser cannot truly detect if a touch capable display is present.
So play it safe and assume ALL devices have a touchscreen AND a mouse.
The Next Logical Step... Separate touch and mouse events?
The only problem is, in an attempt to remain forward and backward compatible, browsers choose to fire every possible event they can think of.
Open this link on a touchscreen laptop or a computer with a touchscreen monitor and you will see how browsers blur the line between the two.
An example being, mousedown will not fire touchstart, but touchstart WILL fire mousedown.
So we can't detect a touchscreen, and we can't separate touch and mouse events, Where do we go from here?
The solution is by use of a temporary "I'm opening" state that is replaced by an "I'm now open" state.
The logic follows:
The end result being, a dropdown nav that will open on hover or touch and will take you to the parent nav element's destination with a single click.
Drawbacks/Devil's advocate:
The only drawback to this approach is speed. If you are quick enough with your mouse, you can have an intended click-through on a parent nav element prevented if you do so before the "I'm opening" state has been removed.
This is unavoidable until such time that browsers decide to handle a tap and a click as two different events.
So you have to leverage the advantage of having the nav behave uniformly for all devices against the possibility of a would-be "Quick Draw McGraw" with a mouse.
I've tried to be as concise as possible, but if you are interested in the how and why, click here
If this article saves you some development woes, or you just like the plugin.
Feel free to donate to it's upkeep
I'm a senior Front-end developer at Vehicle Media
I love coding and finding a better way.
I've benefitted from the generosity of others, and I am trying to do my part to pitch in.
If you have any questions or comments, you can reach me at dave@davecarney.net
Big thanks to Osvaldas Valutis
for starting me on the right path.
His plugin doubleTapToGo was the building block for MonocleToggle.
Breaking down MonocleToggle
What's happening in the browser?
In the presence of both a mouse and a touchscreen, when a touch event occurs, most browsers immediately move the cursor to the point of contact. The exception being Edge. It only hides it.
So click, touchstart, onpointerdown, mouseover, and mouseenter all happen simultaneously during a touch event.
What this means is, we can't simply watch for the cursor to be over the nav to allow click-throughs. You would touch the nav to open the dropdown, the browser would see that the cursor is over the nav, and you would be taken to the link, when all you were trying to do was open the dropdown.
Hence the need for the "I'm opening" state. Set that state to last for 500ms, so the initial touch event can complete.
And you guessed it, Edge is 200ms slower than all other browsers. The other accomodation we have to make for Edge is that it only hides the cursor. So following a touch event, Edge will recognize the cursor as being in two places at the same time.
What this means is, we can't rely on mouseover to keep the dropdown open, or rely on mouseleave to close the dropdown. That also goes for jQuery's .hover() event because it uses mouseleave as it's listener as well.
What we have to do is watch for mousemove outside of the nav.
Now that we know what the browser is doing, we can put our logic in order
Not mentioned in that logic flow are checks in place to make sure you don't have two sibling dropdowns open at the same time.
MonocleToggle natively uses .children('ul') to show the nested ul's incrementally as you hover downward. In the event that you need a 2nd tier ul to appear in it's open state, simply change it to use .find('ul')
Larger implications
Edge's double cursor behavior extends to any element on the page that has hidden content.
You cannot rely on traditional hover techniques to reveal them.
But with a grasp of the logic being used here, you can create similar listeners to achieve the same effect.
Don't be surprised that because of one browser, you have to triple the amount of code needed to pull it off.