dist/plugin/menu.js

File

			(function(global, undefined) { var kafe = global.kafe, $ = kafe.dependencies.jQuery; kafe.bonify({name:'plugin.menu', version:'1.1.0', obj:(function(){
			
				/**
				* ### Version 1.1.0
				* Attaches javascript behaviors to an HTML menu structure to create a *dropdown* style navigation.
				*
				* To preserve flexibility, the plugin only controls events, speeds, delays and callbacks. It will only manage a single custom class (`kafemenu-open`) on the handle elements upon opening or closing, leaving the positioning, visibility and other asthetic responsabilities to its css.
				*
				* @module kafe.plugin
				* @class kafe.plugin.menu
				*/
				var menu = {};
			
				/**
				* Attach behaviors to the menu structure.
				*
				* @method init
				* @param {Object} options Initial configurations.
				*	@param {String|jQueryObject|DOMElement} options.selector Root element of the menu structure.
				*	@param {String} [options.handle='li'] Children element of the container that will serve as a handle to open and close the submenu.
				*	@param {String} [options.submenus='ul'] Children element of the handle that will serve as a submenu, opening and closing when the handle is used.
				*	@param {String} [options.animation='slide'] Animation used when opening and closing the submenus.
				*	@param {Number} [options.openSpeed=200] Duration (in milliseconds) of the opening animation.
				*	@param {Number} [options.openDelay=500] Delay (in milliseconds) used when entering the handle before starting the opening animation.
				*	@param {Number} [options.closeSpeed=150] Duration (in milliseconds) of the closing animation.
				*	@param {Number} [options.closeDelay=400] Delay (in milliseconds) used when exiting the handle before starting the closing animation.
				*	@param {Function} [options.enterCallback] Upon entering a handle, will be triggered after the delay but before the animation. The current submenu is passed as a first argument.
				*	@param {Function} [options.leaveCallback] Upon exiting a handle, will be triggered after the delay but before the animation. The current submenu is passed as a first argument.
				*
				* @example
				*	// Sample menu structure
				*	<nav id="main-menu">
				*		<ul>
				*			<li><a href="#">Menu 1</a>
				*				<ul>
				*					<li><a href="#">Submenu 1.1</a></li>
				*					<li><a href="#">Submenu 1.2</a></li>
				*					<li><a href="#">Submenu 1.3</a></li>
				*				</ul>
				*			</li>
				*			<li><a href="#">Menu 2</a>
				*				<ul>
				*					<li><a href="#">Submenu 2.1</a></li>
				*					<li><a href="#">Submenu 2.2</a></li>
				*					<li><a href="#">Submenu 2.3</a></li>
				*				</ul>
				*			</li>
				*		</ul>
				*	</nav>
				*
				* @example
				*	// Attach behaviors using...
				*	kafe.plugin.menu.init({ selector: '#main-menu > ul' });
				*
				* @example
				*	// Or use the jQuery alternative...
				*	$('#main-menu > ul').kafeMenu('init', {});
				*/
				menu.init = function() {
					var
						options = (arguments) ? arguments[0] : {},
						c = {
							$menu:         $(options.selector),
							handle:        (options.handle) ? options.handle : 'li',
							handleBtn:     (options.handleBtn) ? options.handleBtn : 'a',
							submenus:      (options.submenus) ? options.submenus : 'ul',
							animation:     (options.animation) ? options.animation : '',
							openSpeed:     !isNaN(Number(options.openSpeed)) ? Number(options.openSpeed) : 200,
							openDelay:     !isNaN(Number(options.openDelay)) ? Number(options.openDelay) : 500,
							closeSpeed:    !isNaN(Number(options.closeSpeed)) ? Number(options.closeSpeed) : 150,
							closeDelay:    !isNaN(Number(options.closeDelay)) ? Number(options.closeDelay) : 400,
							enterCallback: (typeof(options.enterCallback)  == 'function') ? options.enterCallback  : undefined,
							leaveCallback: (typeof(options.leaveCallback)  == 'function') ? options.leaveCallback  : undefined,
							clickOnly:     !!options.clickOnly
						}
					;
			
					if (!c.$menu.length) {
						return false;
					}
			
					var $handles = c.$menu.children(c.handle);
			
					$handles
						.bind('kafemenu:open', function(){ _openMenu(this, 0); })
						.bind('kafemenu:close', function(){ _closeMenu(this, 0); })
					;
			
					if (!c.clickOnly) {
						$handles
							.bind('mouseenter', function(e){ _openMenu(this, c.openDelay); })
							.bind('mouseleave', function(e){ _closeMenu(this, c.closeDelay); })
						;
					}
					else
					{
						$handles.each(function(){
							var $handle = $(this);
							if($handle.children(c.submenus).length > 0) {
								$handle.children(c.handleBtn).on('click', function(e){
									e.preventDefault();
									e.stopPropagation();
									var $handle = $(this).parent();
									if($handle.hasClass('kafemenu-open')){
										$handle.trigger('kafemenu:close');
										document.location = $(this).attr('href');
									}else{
										$handles.filter('.kafemenu-open').trigger('kafemenu:close');
										$handle.trigger('kafemenu:open');
									}
								});
							}
						});
						$('html').on('click', function() {
							c.$menu.children(c.handle).filter('.kafemenu-open').trigger('kafemenu:close');
						});
					}
			
					_closeMenu = function(_handle, _delay){
						var
							$parent = $(_handle),
							$sub = $parent.children(c.submenus),
							_clearclass = function() {
								$parent.removeClass('kafemenu-open');
							}
						;
			
						if ($sub.data('kafemenu-timer') !== undefined) {
							clearTimeout($sub.data('kafemenu-timer'));
						}
			
						if ($sub.length > 0) {
							$sub.data('kafemenu-timer', setTimeout(function() {
								var returnCallback = true;
								if (!!c.leaveCallback) {
									returnCallback = c.leaveCallback($sub);
								}
								if (returnCallback) {
									switch (c.animation) {
										case 'fade' :
											$sub.fadeOut(c.closeSpeed, _clearclass);
										break;
			
										case 'slide' :
											$sub.slideUp(c.closeSpeed, _clearclass);
										break;
			
										default :
											$sub.hide(c.closeSpeed, _clearclass);
										break;
									}
								}
							}, _delay));
						}
					};
			
					_openMenu = function(_handle, _delay) {
						var
							$parent = $(_handle),
							$sub = $parent.children(c.submenus)
						;
			
						if ($sub.data('kafemenu-timer') !== undefined) {
							clearTimeout($sub.data('kafemenu-timer'));
						}
			
						if ($sub.length > 0) {
							$sub.data('kafemenu-timer', setTimeout(function() {
								$parent.addClass('kafemenu-open');
								var returnCallback = true;
								if (!!c.enterCallback) {
									returnCallback = c.enterCallback($sub);
								}
								if (returnCallback) {
									switch (c.animation) {
										case 'fade' :
											$sub.fadeIn(c.openSpeed);
										break;
			
										case 'slide' :
											$sub.slideDown(c.openSpeed);
										break;
			
										default :
											$sub.show(c.openSpeed);
										break;
									}
								}
							}, _delay));
						}
					};
				};
			
			
				// Add as jQuery plugin
				kafe.fn.plugIntojQuery('Menu', {
					init: function(obj, parameters) {
						menu.init($.extend({}, parameters[0], {selector:obj}));
					}
				});
			
				return menu;
			
			})()}); })(typeof window !== 'undefined' ? window : this);