Skip to content

Instantly share code, notes, and snippets.

@johnnyelwailer
Last active July 6, 2016 10:47
Show Gist options
  • Save johnnyelwailer/5089582 to your computer and use it in GitHub Desktop.
Save johnnyelwailer/5089582 to your computer and use it in GitHub Desktop.
keyboard navigation directive / service in angular js (depends on jq and rxjs)
angular.module('keyboard', [])
.factory('keyboardNavigation', function () {
var keyboardContextStack = [];
var navigatables = {};
var activeIndex = 0;
return {
active: null,
register: function (scope, events) {
var self = this;
navigatables[scope.$id] = {
scope: scope,
on: function (key) {
return events(scope)[self.key(key)];
}
};
},
key: function (code) {
return (angular.isNumber(code) ? this.keys[code] : code) || 'default';
},
pushContext: function (scope) {
var i = keyboardContextStack.lastIndexOf(function(x) {
return x.scope === scope;
});
if (i > -1) {
this.active = keyboardContextStack[activeIndex = i];
return;
}
if (activeIndex < keyboardContextStack.length - 1) {
keyboardContextStack.splice(activeIndex + 1);
}
this.active = navigatables[scope.$id];
if (this.active == null) {
throw 'No navigatable registered for scope ' + scope.$id;
}
keyboardContextStack.push(this.active);
activeIndex = keyboardContextStack.length - 1;
},
popContext: function (scope) {
var i = angular.isNumber(scope) ? scope : keyboardContextStack.indexOf(function(x) {
return x.scope === scope;
});
this.active = keyboardContextStack[activeIndex = i - 1];
},
keys: {
37: 'left',
38: 'up',
39: 'right',
40: 'down',
09: 'tab',
13: 'submit',
27: 'escape',
33: 'pageup',
34: 'pagedown'
}
};
})
.directive('uiKeyboardNavigatable', function (keyboardNavigation, $parse) {
$(document).onAsObservable('keydown.navigation')
.where(function (ev) {
return keyboardNavigation.active != null
&& (!$(document.activeElement).is('input,textarea,select') || document.activeElement.popout != null)
&& keyboardNavigation.key(ev.keyCode) != null
&& keyboardNavigation.active.on(ev.keyCode) != null;
})
.doAction(function (ev) {
ev.preventDefault();
ev.stopPropagation();
}).oneInTime(200).subscribe(function (ev) {
var key = keyboardNavigation.key(ev.keyCode);
keyboardNavigation.active.scope.$emit('navigate-' + key);
keyboardNavigation.active.scope.$apply(function () {
var on = keyboardNavigation.active.on(key);
on(String.fromCharCode(ev.which), ev);
});
});
return function (scope, element, attr) {
var explicitElement = $parse(attr.uiKeyboardNavigatable)(scope);
var $element = $(explicitElement || element);
keyboardNavigation.register(scope, $parse(attr.navigateOn));
$element.bind('focus.navigate', function (ev) {
keyboardNavigation.pushContext(scope);
}).bind('blur.navigate', function () {
keyboardNavigation.popContext(scope);
});
};
});
<li class="dropdown" ng-controller="DropdownCtrl">
<a class="dropdown-toggle"
ui:keyboard-navigatable
navigate-on="{up: selectPreviousItem, down: selectNextItem, submit: close, escape: close}">
Click me for a dropdown, yo!
</a>
<ul class="dropdown-menu">
<li ng-repeat="choice in items">
<a>{{choice}}</a>
</li>
</ul>
</li>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment