Spry UI Menu Bar

We are happy to introduce a revamping Spry Menu Bar.

This Menu Bar widget has been completely written from Spry 1.x Menu Bar.

Version 2.0 has all the features of other Spry UI widgets: Flexible markup, slice-ability/skinnability and plugin, for extra customization.

Markup Structure

The expected markup structure for a menu bar is a standard unordered-list of links. Submenus are nested unordered lists within a list-item *AFTER* the link (after the <a> tag).

Example of Document Markup:

<ul>
   <li><a href=”#”>A</a></li>
   <li><a href=”#”>B</a>
        <ul>
           <li><a href=”#”>C</a></li>
           <li><a href=”#”>D</a></li>
        </ul>
   </li>
</ul>

The structure above describes a menu bar that contains 2 menu items ‘A’ and ‘B’. Menu item ‘B’ has a submenu that contains 2 menu items ‘C’ and ‘D’.

The MenuBar widget parses this initial structure at run-time when its constructor is invoked. The widget will enhance this markup structure with additional markup to allow for enhanced styling and animation. It will also attach semantic classes to every element within the resulting widget structure.

<div class="MenuBar ">
<ul class="MenuBarView">
<li class="MenuItemContainer"> <a class="MenuItem MenuItemFirst" href="#"> <span class="MenuItemLabel">A</span> </a> </li>
<li class="MenuItemContainer"> <a class="MenuItem MenuItemWithSubMenu MenuItemLast" href="#"> <span class="MenuItemLabel">B</span> </a>
<div class="SubMenu MenuLevel1">
<ul class="SubMenuView">
<li class="MenuItemContainer"> <a class="MenuItem MenuItemFirst" href="#"> <span class="MenuItemLabel">C</span> </a> </li>
<li class="MenuItemContainer"> <a class="MenuItem MenuItemLast" href="#"> <span class="MenuItemLabel">D</span> </a> </li>
</ul>
</div>
</li>
</ul>
<br class="MenuBarBreak">
</div>

It should be noted that this additional markup that is injected at run-time is “woven” into the existing list structure. That said, users should style the menu bar, its menu items, and submenus based on the class names and not the tag names. This is because some menu bar plugins will replace <ul> and <li> elements with other elements like <div>.

Behavior

Out of the box, the MenuBar supports both hover and click navigation.
With hover navigation, moving the cursor over a menu item highlights the menu item. If that menu item has a submenu, its submenu will be displayed after a certain amount of time has elapsed. This delay can be configured by the user with the ‘mainMenuShowDelay’ and ‘subMenuShowDelay’ constructor options.

When the cursor leaves a menu item, the menu item will un-hilight and any submenu it has will be hidden. The delay for hiding a submenu can be configured by the user with the ‘mainMenuHideDelay’ and ‘subMenuHideDelay’ constructor options.

To trigger the action associated with a given menu item (links), you simply click on the menu item.

Hover navigation can be turned on or off via the boolean ‘enableHoverNavigation’ constructor option. By default it is enabled.

Click navigation is also provided, but it cannot be disabled in the current version of the MenuBar. Click navigation is important on some platforms like mobile devices where there is no way to generate hover (mouseenter, mouseout) events and the main input mechanism is via touch/click.

When you click on a menu item, the widget first takes a look at the @href attribute of the menu item <a> tag. If the @href attribute is an empty value, or an empty hash, and the menu item has a submenu, the click will be cancelled, preventing the link from being followed, and the menu item’s submenu displayed. Here are examples of links that will allow the menu item’s submenu to be displayed:

<a href=””>A</a>
<a href=”#”>A</a>
<a href=http://foo.com/page.html#”>A</a>  <!—where the current page is http://foo.com/page.html -->

 If the menu item is a link to an external page, or contains a non-empty hash value, then the browser is allowed to follow the link and process it as it normally would. Some examples:

<a href=”#News”>A</a>
<a href=”foo.html”>A</a>
<a href=”http://foo.com/foo.html”>A</a>

Slicing

Slicing is a technique of adding extra containers to the markup at run time in order to give more styling options. Divs are injected in a consistent patter with class names automatically attached. These classes are hooks for adding rounded corner images and other advanced styling elements.

By default, nothing is automatically sliced. The developer controls which elements are sliced using the sliceMap. The sliceMap is an option in the defaultConfig object. In the sliceMap, class names are listed along with the type of slicing that elements with that class name should receive.

Below is an example of a 9sliced element.

These <div>s have established class names applied to them. Using these classes, developers can make scalable elements, add rounded corners or apply other styles.

There are a few levels of slicing: 2, 3-slicing and 9 slicing. The image above shows a 9-sliced element. Start with one and 8 are added.  Note that the top and bottom-mid and -left are within the right div. This layout simplifies alignment and stretching.

3-slicing will only add 2 <div>s. In the above, it would be just the middle row.

2-slicing will wrap the content in 2 divs.

widgetClass:"MenuBar", // Sliceable  
subMenuClass: "SubMenu", // Sliceable  
menuItemContainerClass: "MenuItemContainer", // Sliceable             
menuItemClass: "MenuItem",          //  Sliceable
menuItemLabelClass: "MenuItemLabel",     // Sliceable

Remember the string before the colon is the constructor option for overriding the default class names, the strings after the colon are the default class names that will be placed on elements. Whatever class names are used is what you use in the slice map:

sliceMap: { MenuBar:  “3slice”, MenuItemLabel: “3slice” }

Options

By default, the javascript options are kept in an object at the top of SpryMenu.js.

These defaults can be overwritten in two ways. One, create a custom object to overwrite the default.

Second, overwrite any option as a constructor option:

The constructor options are as follows:

The following options allow you to override the class names that are dynamically placed on the run-time generated widget markup:

   widgetClass: "MenuBar",
   menuBarViewClass: "MenuBarView",
   menuBarBreakClass: "MenuBarBreak",
   subMenuClass: "SubMenu", // Sliceable
   subMenuViewClass: "SubMenuView",
   subMenuBreakClass: "SubMenuBreak",
   subMenuVisibleClass: "SubMenuVisible",
   menuItemContainerClass: "MenuItemContainer", // Sliceable
   menuItemContainerHoverClass: "MenuItemContainerHover",
   menuItemClass: "MenuItem", // Sliceable
   menuItemHoverClass: "MenuItemHover",
   menuItemSelectedClass: "MenuItemSelected",
   menuItemLabelClass: "MenuItemLabel", // Sliceable
   menuItemWithSubMenuClass: "MenuItemWithSubMenu",
   menuItemFirstClass: "MenuItemFirst",
   menuItemLastClass: "MenuItemLast",
   menuLevelClassPrefix: "MenuLevel"

Public APIs

getMenuLevel(menuElement)

            - Returns the menu level for the specified menu. The menu that hangs off the menubar menu item is 1, any submenus that hang off that menu are level 2, etc.

getSubMenuForMenuItem(menuItemElement)

            - Return the submenu element for the specified menu item.

getMenuItemForSubMenu(subMenuElement)

            - Returns the menu item for the specified submenu.

getParentMenuForElement(element)

            - Returns the parent menu for the specified menu item or submenu element.

getMenuItemsForMenu(menuElement)

            - Returns an array of the menu items for the specified menu.

getSubMenuHierarchy(subMenuElement)

            - Returns an array containing the specified submenu and its ancestor menus. The elements in the array are ordered furthest to nearest ancestor, ending with the specified submenu.

getMenuItemHierarchy(menuItemElement)

            - Returns an array containing the specified menu item and its ancestor menu items. The elements in the array are ordered furthest to nearest ancestor, ending with the specified menu item.

hideSubMenu(subMenuElement)

            - Hides the specified submenu and its entire menu hierarchy. If the current menu item is within one of the ancestor menus, only the menus below that ancestor are hidden.

showSubMenu(subMenuElement)

            - Displays the specified submenu.

setCurrentMenuItem(menuItem)

            - Sets the current menu item of the widget to the specified menu item and forces the entire menu hierarchy to show so that the menu item is visible.

Plugins

KEY NAVIGATION PLUGIN

The Key Navigation Plugin (KNP) adds arrow key navigation for the MenuBar widget.

To enable the keyboard navigation, simply include the SpryMenuBarKeyNavigationPlugin.js file after SpryMenu.js

<script type="text/javascript"  src="Spry-UI-1.7/includes/SpryDOMUtils.js"></script>
<script type="text/javascript"  src="Spry-UI-1.7/includes/SpryWidget.js"></script>
<script type="text/javascript"  src="Spry-UI-1.7/includes/SpryMenu.js"></script>
<script type="text/javascript"  src="Spry-UI-1.7/includes/plugins/MenuBar2/SpryMenuBarKeyNavigationPlugin.js"></script>

The plugin automatically registers itself as a default MenuBar plugin so there’s nothing extra you have to do.

To configure the plugin, you can do one of two things:

- Adjust the defaults by setting the configuration properties directly on the Spry.Widget.MenuBar2.KeyNavigationPlugin.config object.
- Adjust the defaults for a specific widget instance by passing in options at construction time with the ‘KNP’ property. (Example Below)

Currently, the plugin only supports a single configuration option ‘horizontalLayoutMap’. This option is a dictionary that tells the plugin the layout of the menu bar and all menus/submenus. By default it is assumed that only the menubar has a horizontal layout so the map looks like this:

horizontalLayoutMap: { MenuBar: true }

The key used in the dictionary must be “MenuBar” or “MenuLevelX” where ‘X’ is the integer level of the menu. Menus that hang off the menubar are MenuLevel1 and all submenus off of MenuLevel1 menus are MenuLevel2, etc. So for example, if you were creating a menubar that was styled in such a way that its first level menus were also horizontal, you would want to set up the horizontal map like this:

var mb = new  Spry.Widget.MenuBar2(“#navbar”, { KNP: { horizontalLayoutMap: { MenuBar: true,  MenuLevel1: true }} });

If all your menubars on your page were following this same layout, you could also adjust the plugin defaults so that you don’t have to pass them into the menubar constructor:

Spry.Widget.MenuBar2.KeyNavigationPlugin.config.horizontalLayoutMap  = { MenuBar: true, MenuLevel1: true };

The horizontalLayoutMap determines what the arrow keys do.

Note for Safari users: By default, tabbing to links is not enabled. To ensure proper keyboard navigation, users will have to go to Preferences > Advanced > "Press Tab to highlight..".

HORIZONTAL LAYOUT – MenuBar/Menu in map with true

            <Arrow Left>               Previous menu item
            <Arrow Right>             Next menu item
            <Arrow Up>                Close current menu select parent menu item
            <Arrow Down>            Open submenu and select its first menu item
            <ESC>                        Close all submenus
            <Space>                     Open submenu for menu item
            <Enter>                      Open submenu for menu item *OR* follow menu item link

VERTICAL LAYOUT – MenuBar/Menu not in map, or is in map with value of false

            <Arrow Left>               Close current menu select parent menu item
            <Arrow Right>             Open submenu and select its first menu item
            <Arrow Up>                Previous menu item
            <Arrow Down>            Next menu item
            <ESC>                       Close all submenus
            <Space>                     Open submenu for menu item
            <Enter>                      Open submenu for menu item *OR* follow menu item link

IE WORKAROUNDS PLUGIN

The Internet Explorer 6 Workarounds Plugin (IEWP) implements a number of JavaScript workarounds, for IE6, to get functionality and styling of the MenuBar widget on par with modern browsers.

To enable the plugin, simply include the 'SpryMenuBarIEWorkaroundsPlugin.js' file after 'SpryMenu.js'.

<script type="text/javascript"  src="Spry-UI-1.7/includes/SpryDOMUtils.js"></script>
<script type="text/javascript"  src="Spry-UI-1.7/includes/SpryWidget.js"></script>
<script type="text/javascript"  src="Spry-UI-1.7/includes/SpryMenu.js"></script>
<script type="text/javascript"  src="Spry-UI-1.7/includes/plugins/MenuBar2/SpryMenuBarIEWorkaroundsPlugin.js"></script>

By default, the plugin will only be enabled when the page is loaded within an IE6 browser, but the developer can control whether it is on or off in other browser contexts with a constructor option.

The plugin attempts to workaround the following problems with IE6:

A. Submenus render underneath <select> elements.

When the plugin is enabled, it will automatically insert transparent iframes beneath each subMenu. This has the effect of forcing the subMenu to render above elements like <select> that use native windows.

B. Multiple class selectors are not supported.

IE6 does not support selectors that make use of multiple class names on a single element.  For example a selector of the form .MenuItemWithSubMenu.MenuItemHover will be interpreted by IE6 as simply .MenuItemHover. To workaround this problem set the 'useCombinedClassNames' option for the plugin (true by default) which will generate combined class names such as  .MenuItemWithSubMenuHover, .MenuItemFirstHover, .MenuItemFirstWithSubMenuHover, etc. Class names generated by this plugin are as follows:

          MenuItemFirstWithSubMenu
          MenuItemLastWithSubMenu
          MenuItemFirstHover
          MenuItemLastHover
          MenuItemWithSubMenuHover
          MenuItemFirstWithSubMenuHover
          MenuItemLastWithSubMenuHover

C. Elements with position values other than 'static' establish a new z-index context.

This problem manifests itself when subMenus overlap. You will see subMenus rendering behind menu items that come after the subMenu's parent menu item. The solution for this is to set the z-index of the entire subMenu parent element hierarchy such that they are higher than any of their siblings. By default, the plugin will place a z-index of 1000 on all parents of the currentSubMenu. You can change the z-index value by passing the 'miContainerZIndex' option with the desired z-index integer value. If you would prefer to control the z-index via a CSS rule, set the 'miContainerHoverZIndex' option to undefined, and then use the class 'MenuItemContainerHover' in your stylesheet to set the desired z-index value.

You can read more about this problem here:

http://www.adobe.com/cfusion/communityengine/index.cfm?event=showdetails&postId=829&productId=1&loc=en_US

D. <li> elements that contain <a> elements render with gaps below them.

This one is a doozy to work around. For menu items with no submenus, you can get rid of gaps by using this CSS trick:

http://www.adobe.com/cfusion/communityengine/index.cfm?event=showdetails&postId=824&productId=1&loc=en_US

But the trick above does not work for menu items with sub menus since once they become visible, IE6 adds the gap. There are all sorts of tricks that work in specific scenarios, that involve setting the <li> to inline and floating, etc, etc. But that doesn't seem to work in every styling case/situation. The only fool-proof way to get rid of this problem is to get rid of the <ul> and <li> elements in the generated markup for the menubar.

Getting rid of <ul>/<li> elements is the default behavior for this plugin. If you really want to deal with the headache/IE Hacks necessary to get rid of the gaps, you can turn this behavior off by setting the 'useDivs' option to false. If 'useDivs' is true, you will not have to use any tricks/hacks in your CSS to get rid of gaps.

One other bug that you may see, while styling the menubar, that is *NOT* covered by this plugin is when <a> tags are modified to be display:block, sometimes they are not clickable unless you click over the text. This problem can be solved by using one of the tricks mentioned here:

http://www.adobe.com/cfusion/communityengine/index.cfm?event=showdetails&postId=1643&productId=1&loc=en_US

Options:

To set one of the options above when creating a widget, use the IEWP option:

var  mb = new Spry.Widget.MenuBar2(“#navbar”, { IEWP: { miContainerHoverZIndex: 2000  }});

If you want all instances of your menubar widget to use specific settings, you can also modify the default configuration object for the widget:

Spry.Widget.MenuBar2.IEWorkAroundsPlugin.config.miContainerHoverZIndex  = 2000;
var  mb = new Spry.Widget.MenuBar2(“#navbar);