Skip to content

Instantly share code, notes, and snippets.

@JawsomeJason
Created June 22, 2014 02:24
Show Gist options
  • Save JawsomeJason/f32fd1206a687ed93fd0 to your computer and use it in GitHub Desktop.
Save JawsomeJason/f32fd1206a687ed93fd0 to your computer and use it in GitHub Desktop.
Angular Menu Directive
/* Angular directive for enabling a expandable/collapsible hover dropdown menu
* May seem like a lot of code just for a dropdown, but normally, dropdowns
* don't play nice on touch devices, because onmouseleave is not fired unless
* the user touches another element that is clickable or focusable, such as
* a link. This ensures that any touch outside the menu will be considered
* the same as a mouseleave
*
* example menu:
* <nav>
* <ul class="menu" x-jf-menu x-ng-onmouseenter="open()" x-ng-onmouseleave="close()">
* <li>
* <div class="menu__item" x-jf-menu-trigger>Categories</div>
* <ul class="menu" x-ng-class="{ 'is-menu-open': menuOpen, 'is-menu-ready': menuReady }">
* <li class="menu__item"><a href="#">Category 1</li>
* <li class="menu__item"><a href="#">Category 2</li>
* <!-- ... -->
* </ul>
* </li>
* </ul>
* </nav>
*/
HuntersAlley.directive("jfMenu", [
'$document', function($document) {
return {
restrict: "A",
controller: [
'$scope', function($scope) {
$scope.menuOpen = false;
$scope.toggle = function(state) {
if (state == null) {
state = !$scope.menuOpen;
}
$scope.$apply(function() {
$scope.menuOpen = state;
});
};
$scope.open = function() {
$scope.toggle(true);
};
$scope.close = function() {
$scope.toggle(false);
};
/* expose a trigger for subdirectives like toggle buttons*/
return this.triggered = function(event) {
$scope.toggle();
};
}
],
link: function(scope, element, attrs) {
/* anytime state changes, toggle menu */
var closeIfOutsideInteraction, handleOuterBindings;
scope.$watch('menuOpen', function() {
return handleOuterBindings();
});
/* open close on menu hover (touch needs touchstart instead) */
element.bind('mouseenter', scope.open);
element.bind('mouseleave', scope.close);
/* if touch is detected, there is no such thing as mouseenter/mouseleave */
element.bind('touchstart', function() {
element.unbind('mouseenter', scope.open);
return element.unbind('mouseleave', scope.close);
});
handleOuterBindings = function() {
/* when open, look for outside interaction and close */
$document[ scope.menuOpen && 'bind' || 'unbind' ]('touchend', closeIfOutsideInteraction);
};
/* close menu if open and a click event happens outside */
closeIfOutsideInteraction = function(event) {
/* only close menu if interaction was outside of menu */
if (element.find('*').andSelf().filter(event.target).length) {
return;
}
/* don't fire default behavior or listen to any other events */
event.preventDefault();
event.stopImmediatePropagation();
return scope.toggle(false);
};
/* for progressive enhancement */
scope.menuReady = true;
}
};
}
]);
/* toggle button directive for inside menus */
HuntersAlley.directive("jfMenuTrigger", function() {
return {
restrict: 'A',
require: "^jfMenu",
link: function(scope, element, attrs, menu) {
element.bind('click', menu.triggered);
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment