26 Oct 2016

Adding a Responsive Bootstrap Navbar with Hover Dropdown Menus

Currently, I’m working with a client who has given me a lot of creative liberty for UI and UX design. Every once in awhile though, feature requests are made without the knowledge of best practices and standard capabilities within certain libraries.  One such request, a simple navbar I completed yesterday. 

A simple responsive Bootstrap navbar which turned into 5 and a half hours of reading, struggling, thinking I knew better than hundreds of others who have probably faced these problems dozens of times, and ultimately acceptance and settling on a pretty simple but effective approach.  So we started off with the regular Bootstrap nav, with dropdown menus.  Each dropdown menu had sub-items that would take you to an anchor on another page, all grouped by page.  (Ie. Page 1 Anchor 1, Anchor 2, Anchor 3; Page 2 Anchor 1, Anchor 2, Anchor 3; etc., etc.  Easy enough, right?  But, the dropdown menus needed to open on hover.  Okay, I think that should be easy too, and with mobile Bootstrap just replaces hover with touch anyway, and will open the dropdowns when touched

Works great. 

Then I realized the purpose for opening the dropdowns by hover was because the requirement was for the top level menu items to be clickable, and take you to Page 1 or Page 2…  Here’s where I started to see it wasn’t going to be as easy as I thought, and the beginning of my four hour struggle to create a simple responsive navbar.  How do I open the sub menus on mobile if the top level menu items open a new link when clicked?

My immediate plan was to just detect whether the user was using a touchscreen or a mouse for input, using a simple function like this StackOverflow answer.

This test returned true for touchscreen on my laptop, which I just then remembered was a touchscreen laptop.  So I needed a better test right?  I searched and searched, and pretty much every page I found led me to one of two places.  The Modernizr.js homepage, and the article You Can’t Detect a Touchscreen.  Most under the motif of “if you’re trying to detect the difference between a mouse and touchscreen because you want to handle their events differently, you have  a significant logic problem with your usability design.”  How encouraging! 

So, refusing to be proved wrong, I stuck my chest out and decided to add another library to my project, and after hunting through 259 individual features (to Modernizr.js’ credit it’s very easy, and so you don’t have to install a massive library for one or two tests) I found ‘Touch Events’.  Unfortunately, as soon as I clicked to download it, an informative sidebar popped up and told me:

See this article: You Can't Detect A Touchscreen.  It's recommended to bind both mouse and touch/pointer events simultaneously – see this HTML5 Rocks tutorial.

I guess I’m really going to have to rethink my approach. 

My first step was to keep the mobile menu from redirecting to different Pages when I click on the menu items.  Bootstrap by default actually disables href links when you include class="dropdown-toggle" data-toggle="dropdown" in the <a> tag, so this wasn’t really a problem.  What I really needed to do was disable the disabling of href links when the menu was not in its responsive form, which is actually when the browser is wider than 769 pixels, and, when I want my hover to be enabled. 

I found this StackOverflow answer which worked perfectly.  So I ended up with the following:

$('.navbar .dropdown-toggle').hover(function() {
   if (document.documentElement.clientWidth > 769) {
       $(this).addClass('disabled');
       $('ul.nav li.dropdown').hover(function() {
           $(this).find('.dropdown-menu').show();
       }, function() {
           $(this).find('.dropdown-menu').hide();
       });
   }
   else {
       $(this).removeClass('disabled');
   }
});

This checks the client’s document width on hover, and if it’s greater than 769 pixels adds class disabled to the <a>, and shows the menu item’s dropdown on hover (in), hides it on hover (out). If it is less than or equal to 769, pretty much do nothing but remove the disabled class just in case. This worked perfectly. All the requirements were met, except I still couldn’t click the top level menu items on mobile. After a few minutes of thinking, I came up with a simple, arguably non-user-intuitive approach. You’ll have to double-tap the top level menu items to load the page. People may not figure it out, but I spent enough time on it and it seemed like a pretty simple way to accomplish what I needed to do. The way I did this was to set a flag when a top level menu is open. If the user clicks the menu again within a certain amount of time (in the below example 1.5 seconds), then it will redirect to that page. If it’s longer than 1.5 seconds, it will just close the menu. I added the following code to the else statement above, and seems to work fine.

var timer = true;
$('ul.nav li.dropdown').click(function() {
    var el = $(this);
    setTimeout(function(){ timer = false; }, 1500);
    if (el.hasClass('open') && timer == true) {
        var link = el.children('a.dropdown-toggle');
        if (link.length && link.attr('href')) {
            location.href = link.attr('href');
        }
    }
    else { timer = true; }
});

Here is a basic JSFiddle to show it in action.  I’d love to hear if anyone has any suggestions or other ways to go about doing this. I thought about having a hidden div that shows on the responsive menu where the user can click “go” or something similar to take them to that page, but I really don’t know how frequently it would be used. Thanks for reading.