Tag Archives: javascript

I had a previous post on creating a CSS only multi-level flyout menu. I'm in the process of recovering that one, and will relink it when it's back up, but for now, a recent task (a.k.a take home test for an interview) asked me to create that very same menu, using a combo of JavaScript and CSS.

The idea of course, is to make it programmatic so it will scale to an infinite level of submenus. I find many of the tests we're asked to do during interviews involve some form of recursion - flatten a nested array of nested arrays, build minesweeper, and this one is no exception. So this was my solution for it. It's one way to do it - not the only way however.

I am including the original code in the snippets, though the running model below (is a bit wonky because of integration with WordPress I suspect), is a bit modified due to JavaScript scope conflicts and I added a div wrapper in the html to give sufficient height for the menu to display.

On to the design. My idea was to have an html element with a unique id to hang this menu off of.

HTML

So the html was pretty simple:

<ul id="dropdown_menu">
    </ul>

Like I said, simple. Now for the CSS.

CSS

The CSS needs to handle a few things - the styling of the unordered list and it's list items, plus any nested lists and list items. It also needs to deal with some state & interactivity - namely the concept of something being hidden vs. visible, and the mouseover, or hover, interaction animation.

Again, not overwhelmingly difficult - notice the craziness with the multiple nested uls though. That should probably be addressed for enhanced scalability.

html, body {
        padding: 0;
        margin: 0;
        font: normal 16px/135% sans-serif;
      }
      ul,
      ul li {
        list-style: none outside none;
        padding: 0;
        margin: 0;
      }

      .hidden {
        display: none;
      }

      ul li > ul {
        position: relative;
        top: -65px;
        right: -100%;
      }

      .menu-item {
        width: 200px;
        height: 70px;
        vertical-align: middle;
        line-height: 400%;
        text-align: center;
        background-color: lightblue;
        box-sizing: border-box;
        border: 1px solid black;
        opacity: 0.85;
      }
      .menu-item:hover {
        cursor: pointer;
        opacity: 1;
      }
      .menu-item > ul > .menu-item {
        background-color: violet;
      }
      .menu-item > ul > .menu-item > ul > .menu-item {
        background-color: lightgreen;
      }

      /* testing further levels with modified js object */
      .menu-item > ul > .menu-item > ul > .menu-item > ul > .menu-item {
        background-color: orange;
      }

Again, slight changes have been made to the working model below.

JavaScript

Finally the JS. This is where the bulk of the work is being done. I opted to use jQuery, just for convenience, though the entire thing can be done in native JS of course.

We're starting with a native JSON object. Originally, this was composed as 3 separate files - the js, css, and HTML file referencing the previous 2. Thus, the 'window' on the MENU variable so it was globally available.

window.MENU = [
      {
        'title': 'Item 1',
        'submenu': null,
      },
      {
        'title': 'Item 2',
        'submenu': [
          {
            'title': 'Sub 2 Sub1',
            'submenu': [
              {
                'title': 'Sub2 SubSub 1',
                'submenu': [
                  {
                    'title': 'Sub2 SubSubSub 1',
                    'submenu': null,
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        'title': 'Item 3',
        'submenu': [
          {
....

 

The most difficult thing about a task like this is probably structuring the design. I decided I wanted to keep it fairly simple. I'd start with variables for the HTML I wanted to insert into the parent, so an empty string will do for that, and something to hold the MENU object.

A couple of things about me - I like comments, and I try to be defensive in my coding, meaning it won't blow up if the unexpected happens. So this first declaration is just to return nothing basically, if for some reason the MENU object can't be parsed:

// defense ...
      if(MENU == null || MENU == undefined) {
        return false;
      }

Then the html variable is basically a document.getElementById('blah') ala jQuery.

//parent node to which will append further html
var html = $('#dropdown_menu');
var list = '';

 

Then I wanted a function which would slurp up the object and parse it, and a second function which would handle the action of creating lists or list items as needed. This first function is recursive, calling itself at each level as it descends:

function parseMenuBlock(data) {
        if(data.submenu == null) {
          list += createListItem(data.title, 1);
        }
        else {
          list += createListItem(data.title, 0);
          list += ('<ul class="hidden">');
          for(n in data.submenu) {
            parseMenuBlock(data.submenu[n]);
          }
          list += ('</ul>');
          list += closeListItem();
        }
      }

So this keeps appending onto the 'list' variable, the composed HTML string, as it's parsing the MENU object. It's looking at the submenu attribute and if its null, meaning it has no children, it just generates a list item for that block and stops. Otherwise, it creates the list item, and begins a new unordered list. Inside the unordered list, it recurses, calling itself to parse the next child level of the MENU object.

Obviously there's another method being called inside of here - the createListItem(). That I'm just passing the title attribute value to, so we can use that in the HTML title attribute, and a boolean flag. This flag decides whether we close the HTML for the list item ('</il>'), or leave it open (in the event there's a nested unordered list inside it).

// tackles the creation of a single list item element, close argument decides whether or not to close it or leave open for nested ul
function createListItem(data, close) {
  if(close == 1){
    return '<li class="menu-item" title="' + data + '">' + data + '</li>';
  }
  else {
    return '<li class="menu-item" title="' + data + '">' + data;
  }
}

function closeListItem() {
  return  '</li>';
}

 

Lastly, we need to kick the whole thing off by opening a loop to iterate over the MENU object, calling our ingestion method - parseMenuBlock(), and return the list value, and append it to the DOM object

for(i in MENU) {
        parseMenuBlock(MENU[i]);
      }
html.append(list);

 

The other anonymous functions in here deal with interactions, so just to take a quick look at those. This first one is called when the user's mouse moves into a menu item. If it can find a hidden child item, it reveals it, otherwise, do nothing.

$(document).on('mouseenter', 'li', function(e){
        //check if the element is hidden and remove 
        if($(this).children(':first').hasClass('hidden')) {
          $(this).children(':first').toggleClass('hidden');
        }
        //otherwise leave it alone...can be removed... left for readability
        else {
          return;
        }
      });

 

This next one sets a timeout to hide the child after the user moves the mouse away - a slight delay of 250ms in case the user is inaccurate with the mouse. Notice the t= $(this). That sets t to the object we're binding the mouse event to - in this case, an 'li'.

//bind mouseleave function to a delayed visibility toggle
      $(document).on('mouseleave', 'li', function(e) {
        var t = $(this);
        setTimeout(function(){
          t.children(':first').toggleClass('hidden');}, 250);// end setTimeout ...
      });

 

This last one is probably not desirable actually, but I was using for debugging and testing. It simply alerts the title attribute for the bound element. Notice the $(document).on(event, element, function()) syntax. This is necessary because at the time the page is loaded, none of these elements are present in the DOM, so the DOM has no knowledge of their existence. So we need to bind at the document level, to dynamic elements being added after the initial render of the DOM.

//bind click event to dynamically inserted elements
      $(document).on('click', 'li', function(e) {
        e.stopPropagation();
        alert($(this).attr('title'));
      });

 

And that's pretty much it. As I mentioned, below is a slightly wonky version of the menu in action. Enjoy, and feel free to comment.