// jQuery.dropdown_menu() by Daniel Upshaw 2012-2013
// http://danielupshaw.com/jquery-css-dropdown-plugin/readme.html
// Beginning semi-colon for concatenated scripts and any improperly closed plugins
;(function( $, window, document, undefined ) {
$.fn.extend({
dropdown_menu : function (options) {
var _defaults = {
sub_indicator_class : 'dropdown-menu-sub-indicator', // Class given to LI's with submenus
vertical_class : 'dropdown-menu-vertical', // Class for a vertical menu
shadow_class : 'dropdown-menu-shadow', // Class for drop shadow on submenus
hover_class : 'dropdown-menu-hover', // Class applied to hovered LI's
open_delay : 150, // Delay on menu open
close_delay : 300, // Delay on menu close
animation_open : { opacity : 'show' }, // Animation for menu open
speed_open : 'fast', // Animation speed for menu open
animation_close : { opacity : 'hide' }, // Animation for menu close
speed_close : 'fast', // Animation speed for menu close
sub_indicators : false, // Whether to show arrows for submenus
drop_shadows : false, // Whether to apply drop shadow class to submenus
vertical : false, // Whether the root menu is vertically aligned
viewport_overflow : 'auto', // Handle submenu opening offscreen: "auto", "move", "scroll", or false
init : function() {} // Callback function applied on init
};
// Test for IE <= 7
version = parseFloat(navigator.appVersion.split("MSIE")[1]);
var ie7 = (version < 8) && (document.body.filters);
return this.each(function() {
var menu = $(this);
// Needs this single/double quote precedence for JSON data
//
var metadata = menu.data('options');
var o = $.extend({}, _defaults, options, metadata);
// Arrow element
var sub_indicator = $('»');
// Add vertical menu class
if (o.vertical) {
menu.addClass(o.vertical_class);
}
// Remove whitespace between inline-block elements
$('>li', menu).css({ 'font-size' : menu.css('font-size') });
menu.css({ 'font-size' : '0' });
menu.find('li:has(ul)').each(function() {
// Add a class to the LI to indicate that it has a submenu
$(this).addClass(o.sub_indicator_class);
// Add arrow/indicator element if enabled
if (o.sub_indicators) {
$('>a:first-child', this).append(sub_indicator.clone());
}
// Get the submenu and hide it, but keep display:block so the width can be calculated
var submenu = $('>ul', this).css({ 'visibility' : 'hidden', 'display' : 'block' });
// Add drop shadow class if enabled
if (o.drop_shadows) {
submenu.addClass(o.shadow_class);
}
// IE <= 7
if (ie7) {
// Wrap in setTimeout() else the arrow may not be included
setTimeout(function() {
// Lock submenu UL width in CSS so that the LI's can stretch
submenu.css({ 'width' : submenu.width() });
}, 0);
}
// Handle hover states
$(this).on({
mouseenter : function(e) {
clearTimeout($(this).data('close_timer'));
clearTimeout($(this).data('open_timer'));
// If the submenu has already been opened
if ($(this).hasClass(o.hover_class)) {
return;
}
// $.proxy() keeps "this" context
$(this).data('open_timer', setTimeout($.proxy(function() {
$(this).addClass(o.hover_class);
// For vertical menus
if (o.vertical) {
submenu.css({ 'top' : 0 , 'left' : $(this).width() });
} else {
submenu.css({ 'top' : '', 'left' : '' });
}
// So we can check the offset
submenu.css({ 'visibility' : 'hidden', 'display' : 'block' });
// Check if the submenu is overflowing off the page
overflow_x = submenu.offset().left + submenu.width() > $(window).scrollLeft() + $(window).width();
overflow_y = submenu.offset().top + submenu.height() > $(window).scrollTop() + $(window).height();
overflow = overflow_x || overflow_y;
if (overflow && o.viewport_overflow) {
// Padding to accomodate for drop shadows, etc
var padding = 10;
if (o.viewport_overflow === 'auto') o.viewport_overflow = ie7 ? 'scroll' : 'move';
switch (o.viewport_overflow) {
case 'move' :
var left = overflow_x ? ($(window).scrollLeft() + $(window).width()) - submenu.width() - padding : submenu.offset().left;
var top = overflow_y ? ($(window).scrollTop() + $(window).height()) - submenu.height() - padding : submenu.offset().top;
submenu.offset({ left : left , top : top });
break;
case 'scroll' :
if (overflow_x) {
var scrollLeft = submenu.offset().left - $(window).width() + submenu.width() + padding;
$('html').animate({ scrollLeft : scrollLeft }, 'fast');
//$(window).scrollLeft(scrollLeft);
}
if (overflow_y) {
var scrollTop = submenu.offset().top - $(window).height() + submenu.height() + padding;
$('html').animate({ scrollTop : scrollTop }, 'fast');
//$(window).scrollTop(scrollTop);
}
break;
}
}
// Restore display state
submenu.hide().css({ 'visibility' : 'visible' });
if (o.animation_open) {
submenu.animate(o.animation_open, o.speed_open);
} else {
submenu.show();
}
}, this), o.open_delay));
},
mouseleave : function(e) {
clearTimeout($(this).data('close_timer'));
clearTimeout($(this).data('open_timer'));
$(this).data('close_timer', setTimeout($.proxy(function() {
$(this).removeClass(o.hover_class);
if (o.animation_close) {
submenu.animate(o.animation_close, o.speed_close, function() {
submenu.css({ 'visibility' : 'hidden' });
});
} else {
submenu.hide().css({ 'visibility' : 'hidden' });
}
}, this), o.close_delay));
},
// For touch devices, disable the link if its submenu is not showing yet
touchstart : function(e) {
$('>a:first-child', this).one('click', $.proxy(function(e) {
if (!$(this).hasClass(o.hover_class)) {
e.preventDefault();
} else {
return true;
}
}, this));
}
});
});
// Wrap in setTimeout() to ensure processes have completed
setTimeout(function() {
// Completely hide the submenus
$('ul', menu).hide(1)
.promise()
.done(function() {
// Apply the init callback
o.init.call(menu[0]);
});
}, 0);
});
}
});
})( jQuery, window , document );