Instantly share code, notes, and snippets.
Last active
September 24, 2016 20:05
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save tlaak/928ac49362b2f6b0767c3bee6e6e9ab3 to your computer and use it in GitHub Desktop.
jQuery plugin for a custom megamenu. Found from the depths of my Dropbox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @version 0.1 Alpha | |
* @author Timo Laak 7/6/2013 | |
* | |
*/ | |
(function($) { | |
/** | |
* Constants for recurring use | |
*/ | |
var constants = { | |
/** @const */ | |
PLUGIN_NAME: 'bmmMegamenu', | |
/** @const */ | |
ACTIVE_PARENT_CLASS: 'active', | |
/** @const */ | |
ACTIVE_NODE_CLASS: 'node-active', | |
/** @const */ | |
ACTIVE_SHEET_CLASS: 'sheet-active', | |
/** @const */ | |
BTN_TOGGLE_CLASS: 'btn-toggle-node', | |
/** @const */ | |
PLUGIN_NAME_CLASS: 'bmm-megamenu' | |
}; | |
/** | |
* Templates for repeating elements | |
*/ | |
var templates = { | |
toggleButton: '<i class="' + constants.BTN_TOGGLE_CLASS +'"></i>' | |
}; | |
var helpers = { | |
isTouch: function() { | |
if (window.navigator.msMaxTouchPoints) { | |
return true; | |
} | |
if (typeof Modernizr === "object" && Modernizr.touch) { | |
return true; | |
} | |
return false; | |
} | |
}; | |
/** | |
* Plugin methods | |
*/ | |
var methods = { | |
init: function(options) { | |
if (!this.data('navigationDomBackup')) { | |
this.data('navigationDomBackup', this.html()); | |
} | |
// Check the requested layout and destroy & build only if it's not yet active | |
if (options.layout === 'sheet' && this.data('layout') !== 'sheet') { | |
methods.destroyTree.apply(this); | |
methods.restore.apply(this); | |
methods.buildSheet.apply(this); | |
} else if (options.layout === 'tree' && this.data('layout') !== 'tree') { | |
methods.destroySheet.apply(this); | |
methods.restore.apply(this); | |
methods.buildTree.apply(this); | |
} | |
return this; | |
}, | |
buildTree: function () { | |
this.addClass(constants.PLUGIN_NAME_CLASS); | |
this.data('initialized', 'true'); | |
this.hide(); | |
var root = this.children('ul'); | |
var nodes = root.find('ul'); | |
for (var x = 0, length = nodes.length; x < length; x++) { | |
var node = new Node(nodes[x], this); | |
if (node.isActive()) { | |
node.toggle(); | |
} | |
} | |
// Set the active layout | |
this.data('layout', 'tree'); | |
return this; | |
}, | |
buildSheet: function () { | |
this.show(); | |
this.addClass(constants.PLUGIN_NAME_CLASS); | |
this.data('initialized', 'true'); | |
var topLevel = this.children('ul').children('li'), timerOutId; | |
topLevel.on('mouseenter.topLevel touchstart', function (event) { | |
var _this = this; | |
// Disable main level link click events on touchstart | |
if (event.type === "touchstart") { | |
topLevel.children('a').click(function(event) { | |
event.stopPropagation(); | |
event.preventDefault(); | |
}); | |
} | |
// Add short timeout to prevent accidental opening of menu | |
timerOutId = setTimeout(function() { | |
$(_this).addClass(constants.ACTIVE_SHEET_CLASS); | |
$(_this).siblings().removeClass(constants.ACTIVE_SHEET_CLASS); | |
}, 300); | |
}); | |
topLevel.on('mouseleave.topLevel', function(event) { | |
clearTimeout(timerOutId); | |
}); | |
topLevel.children('ul').each(function() { | |
/* Note! IE10 on Win8 tablets is buggy and fires up | |
mouseout event when you click inside the menu */ | |
$(this).on('mouseleave.sheet', function() { | |
if (!helpers.isTouch()) { | |
$(this).parent().removeClass(constants.ACTIVE_SHEET_CLASS); | |
} | |
}); | |
}); | |
$('html').on('click touchstart', function (event) { | |
var _target = $(event.target); | |
if (!_target.closest('.'+constants.ACTIVE_SHEET_CLASS).length) { | |
_target.removeClass(constants.ACTIVE_SHEET_CLASS); | |
topLevel.removeClass(constants.ACTIVE_SHEET_CLASS); | |
} | |
}); | |
this.data('layout', 'sheet'); | |
var promoContainer = $('.has-promo > ul', this); | |
var sectionNormal = promoContainer.children('li').not('.is-references, .promo'); | |
var sectionReferences = promoContainer.children('li.is-references'); | |
var sectionPromo = promoContainer.children('li.promo'); | |
promoContainer.prepend( | |
$('<li class="sections bmm-megamenu-sections"></li>') | |
.append($('<ul class="normal bmm-megamenu-normal"></ul>') | |
.append(sectionNormal) | |
) | |
); | |
$('.sections', promoContainer).append( | |
$('<ul class="references"></ul>') | |
.append(sectionReferences) | |
); | |
return this; | |
}, | |
destroyTree: function() { | |
// Don't try to destroy a layout which is not active | |
if (this.data('layout') === 'sheet') { | |
return this; | |
} | |
// Find all toggle buttons | |
var toggleButtons = $('.'+constants.BTN_TOGGLE_CLASS); | |
// Remove event bindings | |
toggleButtons.off('.toggle'); | |
// Remove buttons | |
toggleButtons.remove(); | |
// Remove plugin class name from tree | |
this.removeClass(constants.PLUGIN_NAME_CLASS); | |
// Remove data attribute | |
this.removeData('initialized'); | |
this.removeData('layout'); | |
return this; | |
}, | |
destroySheet: function() { | |
var topLevel = this.children('ul').children('li'); | |
topLevel.off('.topLevel'); | |
this.removeData('initialized'); | |
this.removeData('layout'); | |
return this; | |
}, | |
restore: function() { | |
var navigationDomBackup = this.data('navigationDomBackup'); | |
this.html(navigationDomBackup); | |
return this; | |
} | |
}; | |
/** | |
* Node | |
* @constructor | |
*/ | |
var Node = function Node(root, _this) { | |
this.root = root; | |
this.parent = $(root).parent(); | |
this.visible = true; | |
this.toggleButton = $(templates.toggleButton); | |
this.toggleButton.on('click.toggle', null, null, function() { | |
$(this).toggleClass(constants.ACTIVE_NODE_CLASS); | |
$(this).nextAll('ul').attr('style', ''); | |
$(this).nextAll('ul').removeAttr('style'); | |
}); | |
this.parent.prepend(this.toggleButton); | |
}; | |
/** | |
* Add methods for Node object | |
*/ | |
Node.prototype = { | |
/** | |
* Returns true if node parent element (li) has css class indicating its child element is the current page | |
*/ | |
isActive: function isActive() { | |
return this.parent.hasClass(constants.ACTIVE_PARENT_CLASS); | |
}, | |
/** | |
* Returns the count of elements in this node | |
*/ | |
length: function length() { | |
return this.children('li').length; | |
}, | |
/** | |
* Toggles node visibility | |
*/ | |
toggle: function toggle() { | |
this.visible = !this.visible; | |
this.toggleButton.toggleClass(constants.ACTIVE_NODE_CLASS); | |
} | |
}; | |
/** | |
* The actual plugin | |
*/ | |
$.fn[constants.PLUGIN_NAME] = function(method) { | |
if ( methods[method] ) { | |
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); | |
} else if ( typeof method === 'object' || ! method ) { | |
return methods.init.apply( this, arguments ); | |
} else { | |
$.error( 'Method ' + method + ' does not exist on bmmMegamenu' ); | |
} | |
/** | |
* Custom settings | |
*/ | |
var settings = $.extend( { | |
}); | |
}; | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment