dhtml_menu-8.x-1.x-dev/dhtml_menu.js
dhtml_menu.js
/**
* @file dhtml_menu.js
* The Javascript code for DHTML Menu
*/
(function($) {
Drupal.dhtmlMenu = {};
Drupal.dhtmlMenu.animation = {show:{}, hide:{}, count:0};
/**
* Initialize the module's JS functions
* @TODO Indentation maintained to keep coherence with HEAD.
*/
Drupal.behaviors.dhtmlMenu = function() {
var settings = Drupal.settings.dhtmlMenu;
// Initialize the animation effects from the settings.
for (i in settings.animation.effects) {
if (settings.animation.effects[i]) {
Drupal.dhtmlMenu.animation.show[i] = 'show';
Drupal.dhtmlMenu.animation.hide[i] = 'hide';
Drupal.dhtmlMenu.animation.count++;
}
}
// Sanitize by removing "expanded" on menus already marked "collapsed".
$('li.dhtml-menu.collapsed.expanded').removeClass('expanded');
/* Relevant only on "open-only" menus:
* The links of expanded items should be marked for emphasis.
*/
if (settings.nav == 'open') {
$('li.dhtml-menu.expanded').addClass('dhtml-menu-open');
}
/* Relevant only when hovering:
*
* If a context menu is opened (as most users do when opening links in a
* new tab), the mouseleave event will be triggered. Although the context
* menu still works, having the menu close underneath it is confusing.
*
* This code will "freeze" the menu's collapse if the body is left
* (which happens when a context menu opens), and only release it when the cursor
* reenters the menu.
*
* Note that due to the order in which events are called,
* the hovering collapse must work asynchronously so
* this event is triggered before the collapse.
*/
else if (settings.nav == 'hover') {
var freeze = false;
$('ul.menu').bind('mouseenter', function() {freeze = false});
$('body').bind('mouseleave', function() {freeze = true});
}
/* Relevant only on bullet-icon expansion:
* Create the markup for the bullet overlay, and the amount to shift it to the right in RTL mode.
*/
else if (settings.nav == 'bullet') {
var bullet = $('<a href="#" class="dhtml-menu-icon"></a>');
var rtl = $('html').attr('dir') == 'rtl' ? Math.ceil($('.menu li').css('margin-right').replace('px', '')) + 1 : 0;
}
/* Relevant only when adding cloned links:
* Create the markup for the cloned list item container.
*/
else if (settings.nav == 'clone') {
// Note: a single long class is used here to avoid matching the .dhtml-menu.leaf selector later on.
var cloned = $('<li class="leaf dhtml-menu-cloned-leaf"></li>');
}
/* Add jQuery effects and listeners to all menu items. */
$('ul.menu li.dhtml-menu:not(.leaf)').each(function() {
var li = $(this);
var link = $(this).find('a:first');
var ul = $(this).find('ul:first');
// Only work on menus with an actual sub-menu.
if (link.length && ul.length) {
/* When using cloned items:
* - Clone the menu link and mark it as a clone.
*/
if (settings.nav == 'clone') {
link.clone().prependTo(ul).wrap(cloned);
}
/* When using double-click:
* - Add a dblclick event handler that allows the normal link action to complete.
*/
else if (settings.nav == 'doubleclick') {
link.dblclick(function(e) {
return true;
});
}
/* When using bullet expansion:
* - Change the icon to a folder image
* - Add the clickable overlay and its handler
* - In RTL mode, shift the overlay to the right of the text.
* - @TODO: Explore whether "float:right" in dhtml_menu-rtl.css could solve this.
*/
else if (settings.nav == 'bullet') {
li.addClass('dhtml-folder');
var b = bullet.clone().prependTo(link).click(function(e) {
Drupal.dhtmlMenu.toggleMenu(li, link, ul);
if (settings.effects.remember) {
Drupal.dhtmlMenu.cookieSet();
}
return false;
});
// When using RTL, each overlay must be shifted to the other side of the link text, individually.
if (rtl) {
// Shift the overlay right by the width of the text and the distance between text and icon.
b.css('right', '-' + (Math.ceil(link.css('width').replace('px', '')) + rtl) + 'px');
}
}
/* When using hover expansion:
* - Add mouse-hovering events.
*/
else if (settings.nav == 'hover') {
link.bind('mouseenter', function(e) {
Drupal.dhtmlMenu.switchMenu(li, link, ul, true);
});
li.bind('mouseleave', function(e) {
// Only collapse the menu if it was initially collapsed.
if (li.hasClass('start-collapsed')) {
/* As explained earlier, this event fires before the body event.
* We need to wait to make sure that the user isn't browsing a
* context menu right now, in which case the menu isn't collapsed.
*/
setTimeout(function() {
if (!freeze) {
Drupal.dhtmlMenu.switchMenu(li, link, ul, false);
}
}, 10);
}
});
}
/* When using menus that cannot collapse:
* Toggle the menu normally, but only if the menu is closed.
*/
else if (settings.nav == 'open') {
link.click(function(e) {
// Don't collapse expanded menus.
if (li.hasClass('expanded')) {
return true;
}
Drupal.dhtmlMenu.toggleMenu(li, link, ul);
$('.dhtml-menu-open').removeClass('dhtml-menu-open');
$('li.dhtml-menu.expanded').addClass('dhtml-menu-open');
return false;
});
}
// These three options make links simply toggle when clicked.
if (settings.nav == 'clone' || settings.nav == 'doubleclick' || settings.nav == 'none') {
link.click(function(e) {
Drupal.dhtmlMenu.toggleMenu(li, link, ul);
if (settings.effects.remember) {
Drupal.dhtmlMenu.cookieSet();
}
return false;
});
}
}
});
// When using LTR, all icons can be shifted as one, as the text width is not relevant.
if (settings.nav == 'bullet' && !rtl) {
// Shift overlay to the left by the width of the icon and the distance between icon and text.
var shift = '-' + (Math.ceil(($('.menu li').css('margin-left').replace('px', ''))) + 16) + 'px';
// Shift the overlay using a negative left-hand offset, and the text using a negative right-hand margin.
$('.dhtml-menu-icon').css('left', shift).css('margin-right', shift);
}
}
/**
* Toggles the menu's state between open and closed.
*
* @param li
* Object. The <li> element that will be expanded or collapsed.
* @param link
* Object. The <a> element representing the menu link anchor.
* @param ul
* Object. The <ul> element containing the sub-items.
*/
Drupal.dhtmlMenu.toggleMenu = function(li, link, ul) {
// Make it open if closed, close if open.
Drupal.dhtmlMenu.switchMenu(li, link, ul, !li.hasClass('expanded'));
}
/**
* Switches the menu's state to a defined value.
* This function does nothing if the menu is in the target state already.
*
* @param li
* Object. The <li> element that will be expanded or collapsed.
* @param link
* Object. The <a> element representing the menu link anchor.
* @param ul
* Object. The <ul> element containing the sub-items.
*/
Drupal.dhtmlMenu.switchMenu = function(li, link, ul, open) {
// No need for switching. Menu is already in desired state.
if (open == li.hasClass('expanded')) {
return;
}
var effects = Drupal.settings.dhtmlMenu.effects;
if (open) {
Drupal.dhtmlMenu.animate(ul, 'show');
li.removeClass('collapsed').addClass('expanded');
// If the siblings effect is on, close all sibling menus.
if (effects.siblings != 'none') {
var id = link.attr('id');
/* Siblings are all open menus that are neither parents nor children of this menu.
* First, mark this item's children for exclusion.
*/
li.find('li').addClass('own-children-temp');
// If the relativity option is on, select only the siblings that have the same root
if (effects.siblings == 'close-same-tree') {
var root = li.parent();
}
else {
var root = $('ul.menu');
}
var siblings = root.find('li.expanded').not('.own-children-temp').not(':has(#' + id + ')');
// If children should not get closed automatically...
if (effects.children == 'none') {
// Remove items that are currently hidden from view (do not close these).
$('li.collapsed li.expanded').addClass('sibling-children-temp');
// Only close the top-most open sibling, not its children.
siblings.find('li.expanded').addClass('sibling-children-temp');
siblings = $(siblings).not('.sibling-children-temp');
}
// The temp classes can now be removed.
$('.own-children-temp, .sibling-children-temp')
.removeClass('own-children-temp')
.removeClass('sibling-children-temp');
Drupal.dhtmlMenu.animate(siblings.find('ul:first'), 'hide');
siblings.removeClass('expanded').addClass('collapsed');
}
}
else {
Drupal.dhtmlMenu.animate(ul, 'hide');
li.removeClass('expanded').addClass('collapsed');
// If children are closed automatically, find and close them now.
if (effects.children == 'close-children') {
// If a sub-menu closes in the forest and nobody sees it, is animation a waste of performance? Yes.
li.find('li.expanded')
.removeClass('expanded').addClass('collapsed')
.find('ul:first').css('display', 'none');
}
}
}
/**
* Animate a specific block element using the configured DHTML effects.
*
* @param element
* The element to be animated. DHTML Menu only animates <ul> elements,
* but this could in theory be any block (not inline) element.
*
* @param action
* One of either 'show' or 'hide'.
*/
Drupal.dhtmlMenu.animate = function(element, action) {
var effects = Drupal.dhtmlMenu.animation;
var speed = Drupal.settings.dhtmlMenu.animation.speed;
if (effects.count) {
element.animate(effects[action], speed * 1);
}
else {
element.css('display', action == 'show' ? 'block' : 'none');
}
}
/**
* Saves the dhtml_menu cookie.
*/
Drupal.dhtmlMenu.cookieSet = function() {
var expanded = new Array();
$('li.expanded').each(function() {
expanded.push($(this).children('a:first').attr('id'));
});
document.cookie = 'dhtml_menu=' + expanded.join(',') + ';path=/';
}
})(jQuery);
