1

I'm creating an additional tab on a menu dynamically (let's call this new tab, Watch), and all was going pretty well including the submenu that showed up once the new tab was hovered over. I looked at articles on event bubbling and took a look at other examples, but couldn't find a solution to my issue.

Whenever I hover over Watch, the submenu appears, but when I try to hover from Watch to its submenu, the submenu disappears. I need the submenu to persist when a user hovers over Watch or the submenu, and the submenu should only disappear once the user hovers out of either. Just to note, I cannot use CSS for my solution. I've attached my current code below with comments:

//PREPEND AS FIRST CHILD
var prependChild = function(parent, newFirstChild) {
    parent.insertBefore(newFirstChild, parent.firstChild)
}

//DECLARING VARS
var navMenu               = document.getElementsByClassName('navGlobal-list')[0];
    categoryExplorer      = document.getElementsByClassName('categoryExplorer')[0];

//CREATING NEW TAB
var exploreTab            = document.createElement('li');
    exploreTab.className  = 'navGlobal-category';

//CREATING NEW SEARCH FORM
var searchHtml = ['<div class="searchProgram searchProgram--categoryExplorer">',
                        '<div class="searchProgram-container">',
                            '<input type="search" class="form-control form-control--light form-control--searchProgram" placeholder="Search programs" value="">',
                        '</div>',
                    '</div>'].join('');

//CREATING NEW WATCH CATEGORY EXPLORER CONTENT      
var watchCategoryExplorerContent = document.createElement('div');
    watchCategoryExplorerContent.className = 'categoryExplorer-content target-watch-content';
    watchCategoryExplorerContent.innerHTML = searchHtml;
    prependChild(categoryExplorer, watchCategoryExplorerContent)

var watchLink = document.createElement('a');    
    watchLink.setAttribute('href','/watch');
    watchLink.innerHTML = 'watch'.toUpperCase();    

exploreTab.appendChild(watchLink);
navMenu.appendChild(exploreTab); //ADDED 'WATCH' TO THE NAVIGATION

//CHANGE CLASSES ON HOVER
exploreTab.addEventListener("mouseover", function() { 
    exploreTab.className = 'navGlobal-category navGlobal-category--open';
    categoryExplorer.className = 'categoryExplorer categoryExplorer--open'; 
    watchCategoryExplorerContent.className = 'categoryExplorer-content categoryExplorer-content--open target-watch-content';
}, false);

exploreTab.addEventListener("mouseleave", function() { 
    exploreTab.className = 'navGlobal-category';
    categoryExplorer.className = 'categoryExplorer';
    watchCategoryExplorerContent.className = 'categoryExplorer-content target-watch-content';
}, false);
Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
Ao C
  • 127
  • 1
  • 9

2 Answers2

2

A potential (layout-dependent) way to keep the menu open would be to make it a child of the tab - that way, provided there is no space between the tab and the hover menu, you can hover from one to the other without creating a mouseleave event on the tab.

Another solution that is not layout-dependent would be to add some delay between the initial mouseleave event and the submenu closing. I've done something like this using jQuery before, but the same thing should be possible without it.

$('.navGlobal-category').mouseleave(function(){
   setTimeout(
    function(){
       if(!isHovered($('.navGlobal-category')[0])){
        exploreTab.className = 'navGlobal-category';
        categoryExplorer.className = 'categoryExplorer';
        watchCategoryExplorerContent.className = 'categoryExplorer-content target-watch-content';
       }
    }, 200);
});

function isHovered(e){
    return ((e.parentNode.querySelector(":hover") ||
     e.querySelector(":hover")) === e);
}

Credit to zb' for the isHovered solution: https://stackoverflow.com/a/14800287/5403341

Community
  • 1
  • 1
Ben Seymour
  • 76
  • 15
2

For the non-layout solution @B1SeeMore suggests you don't actually need a delay.

Here is a working demo: https://jsfiddle.net/jw22ddzk/

<div id="one" class="menuitem"></div>
<div id="two" class="menuitem" style="display: none;"></div>

var elems = document.getElementsByClassName("menuitem");
for (var i = 0; i < elems.length; i += 1) {
  elems[i].addEventListener("mouseleave", function () {
    console.log("leave", this.id);
    document.getElementById("two").style.display = "none";
  });
  elems[i].addEventListener("mouseenter", function () {
    console.log("enter", this.id);
    document.getElementById("two").style.display = "";
  });
}

The trick is that mouseenter fires for two even if mouseleave hides the element. Just remember to show the element again. A likey explanation is that the mouseenter and mouseleave events spawn in pairs. So mouseenter happens regardless of the effects of mouseleave.

Note that this only works if the elements are beside eachother with pixel accuracy.


Another note: I notice that you're using mouseover and mouseleave. I wouldn't recommend doing that. There are two event pairs for detecting hovers: mouseenter/leave and mouseover/out. They are different events. They differ specifically in that mouseover/out will trigger also for child elments. My recommendation is that you don't interchange the pairs or you might get unexpected behaviour.

Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • It seems that changing the display does not show the submenu and that the only way the menu shows is when the classes, categoryExplorer--open, navGlobal-category--open, and categoryExplorer-content--open are present - is there a way around this that wouldn't be adjusting display? – Ao C Aug 08 '16 at 16:42
  • That's strange on it's own. Is there an `!important` in there? I don't really understand how you're using your classes. They're not intuitive to me and without seeing the definition I don't understand it. – Halcyon Aug 08 '16 at 23:28
  • It's okay, I ended up replacing the menu as one approach and am working on another approach without the mouseenter mouseleave. The layout is built off React and the submenu was only showing based on whether classes existed or not on a separate component. – Ao C Aug 09 '16 at 18:52