Permalink
Browse files

Added arrow-key navigation to Menu component

  • Loading branch information...
1 parent 3f96e4f commit 7f3ba5c688bcc93d663ff71214862dea223703ea @tj tj committed May 21, 2012
Showing with 162 additions and 6 deletions.
  1. +3 −1 build/ui.css
  2. +78 −2 build/ui.js
  3. +3 −1 lib/components/menu/menu.css
  4. +78 −2 lib/components/menu/menu.js
View
@@ -269,13 +269,15 @@
text-decoration: none;
border-top: 1px solid #eee;
color: #2e2e2e;
+ outline: none;
}
.menu li:first-child a {
border-top: none;
}
-.menu li a:hover {
+.menu li a:hover,
+.menu li.selected a {
background: #f1faff;
}
View
@@ -1297,7 +1297,7 @@ exports.Menu = Menu;
*/
exports.menu = function(){
- return new Menu;
+ return new Menu();
};
/**
@@ -1314,11 +1314,13 @@ exports.menu = function(){
*/
function Menu() {
- var self = this;
ui.Emitter.call(this);
this.items = {};
this.el = $(html).hide().appendTo('body');
+ this.el.hover(this.deselect.bind(this));
$('html').click(function(){ self.hide(); });
+ this.on('show', this.bindKeyboardEvents.bind(this));
+ this.on('hide', this.unbindKeyboardEvents.bind(this));
};
/**
@@ -1328,6 +1330,80 @@ function Menu() {
Menu.prototype = new ui.Emitter;
/**
+ * Deselect selected menu items.
+ *
+ * @api private
+ */
+
+Menu.prototype.deselect = function(){
+ this.el.find('.selected').removeClass('selected');
+};
+
+/**
+ * Bind keyboard events.
+ *
+ * @api private
+ */
+
+Menu.prototype.bindKeyboardEvents = function(){
+ $(document).bind('keydown.menu', this.onkeydown.bind(this));
+ return this;
+};
+
+/**
+ * Unbind keyboard events.
+ *
+ * @api private
+ */
+
+Menu.prototype.unbindKeyboardEvents = function(){
+ $(document).unbind('keydown.menu');
+ return this;
+};
+
+/**
+ * Handle keydown events.
+ *
+ * @api private
+ */
+
+Menu.prototype.onkeydown = function(e){
+ switch (e.keyCode) {
+ // up
+ case 38:
+ e.preventDefault();
+ this.move('prev');
+ break;
+ // down
+ case 40:
+ e.preventDefault();
+ this.move('next');
+ break;
+ }
+};
+
+/**
+ * Focus on the next menu item in `direction`.
+ *
+ * @param {String} direction "prev" or "next"
+ * @api public
+ */
+
+Menu.prototype.move = function(direction){
+ var prev = this.el.find('.selected').eq(0);
+
+ var next = prev.length
+ ? prev[direction]()
+ : this.el.find('li:first-child');
+
+ if (next.length) {
+ prev.removeClass('selected');
+ next.addClass('selected');
+ next.find('a').focus();
+ }
+};
+
+/**
* Add menu item with the given `text` and optional callback `fn`.
*
* When the item is clicked `fn()` will be invoked
@@ -19,12 +19,14 @@
text-decoration: none;
border-top: 1px solid #eee;
color: #2e2e2e;
+ outline: none;
}
.menu li:first-child a {
border-top: none;
}
-.menu li a:hover {
+.menu li a:hover,
+.menu li.selected a {
background: #f1faff;
}
@@ -13,7 +13,7 @@ exports.Menu = Menu;
*/
exports.menu = function(){
- return new Menu;
+ return new Menu();
};
/**
@@ -30,11 +30,13 @@ exports.menu = function(){
*/
function Menu() {
- var self = this;
ui.Emitter.call(this);
this.items = {};
this.el = $(html).hide().appendTo('body');
+ this.el.hover(this.deselect.bind(this));
$('html').click(function(){ self.hide(); });
+ this.on('show', this.bindKeyboardEvents.bind(this));
+ this.on('hide', this.unbindKeyboardEvents.bind(this));
};
/**
@@ -44,6 +46,80 @@ function Menu() {
Menu.prototype = new ui.Emitter;
/**
+ * Deselect selected menu items.
+ *
+ * @api private
+ */
+
+Menu.prototype.deselect = function(){
+ this.el.find('.selected').removeClass('selected');
+};
+
+/**
+ * Bind keyboard events.
+ *
+ * @api private
+ */
+
+Menu.prototype.bindKeyboardEvents = function(){
+ $(document).bind('keydown.menu', this.onkeydown.bind(this));
+ return this;
+};
+
+/**
+ * Unbind keyboard events.
+ *
+ * @api private
+ */
+
+Menu.prototype.unbindKeyboardEvents = function(){
+ $(document).unbind('keydown.menu');
+ return this;
+};
+
+/**
+ * Handle keydown events.
+ *
+ * @api private
+ */
+
+Menu.prototype.onkeydown = function(e){
+ switch (e.keyCode) {
+ // up
+ case 38:
+ e.preventDefault();
+ this.move('prev');
+ break;
+ // down
+ case 40:
+ e.preventDefault();
+ this.move('next');
+ break;
+ }
+};
+
+/**
+ * Focus on the next menu item in `direction`.
+ *
+ * @param {String} direction "prev" or "next"
+ * @api public
+ */
+
+Menu.prototype.move = function(direction){
+ var prev = this.el.find('.selected').eq(0);
+
+ var next = prev.length
+ ? prev[direction]()
+ : this.el.find('li:first-child');
+
+ if (next.length) {
+ prev.removeClass('selected');
+ next.addClass('selected');
+ next.find('a').focus();
+ }
+};
+
+/**
* Add menu item with the given `text` and optional callback `fn`.
*
* When the item is clicked `fn()` will be invoked

0 comments on commit 7f3ba5c

Please sign in to comment.