In this article, we add key events into the Radial Menu. The goal is to allow end-users to navigate the menu with the keyboard, once it has been opened, using the arrow keys. Additionally, end-users will be able to select panels with the enter
key and close the menu with the escape
key.
Setup
Instead of adding keyboard support as a plugin, I decided to incorporate it directly into the menu, as widgets should be navigable via the keyboard, as well as the mouse. Thus, there is no additional setup required.How Its Done…
When the Radial Menu is opened, a keydown
and keyup
listener is attached to the document. The callback for the keydown
event is:_handleKeyDown: function(e) { var panels = this.get(panels), lastPanel = this._lastPanel, i = lastPanel ? lastPanel.getRadialIndex() : 0, n = panels.length, isValid = false, hoverClass = this.get(hoverClass), m, l=n%2; switch (e.keyCode) { case 38: // up if (0 != i) { isValid = true; if (n / 2 > i) { i -= 1; } else { i += 1; } } break; case 39: // right m = n / 4; if (m != i && ! (l && _isBetween(i, m-1, m+1))) { isValid = true; if (m >= i + 1 || n - m <= i) { i += 1; } else if (m <= i - 1) { i -= 1; } } break; case 40: // down m = n / 2; if (m != i && ! (l && _isBetween(i, m-1, m+1))) { isValid = true; if (m >= i + 1) { i += 1; } else if (m <= i - 1) { i -= 1; } } break; case 37: // left m = n / 4; if (n - m != i && ! (l && _isBetween(i, n-m-1, n-m+1))) { isValid = true; if (m < i && n - m >= i + 1) { i += 1; } else if (n - m <= i - 1 || i <= m) { i -= 1; } } break; case 13: // enter if (lastPanel) { e.target = lastPanel._node; this._handleClick(e); } break; case 27: // escape this.hide(); break; } if (isValid) { if (this._timerKeyDown) {this._timerKeyDown.cancel();} if (0 > i){ i = n - 1; } else if (n - 1 < i) { i = 0; } n = this.get(keyHoldTimeout); if (0 < n) { this._timerKeyDown = Y.later(n, this, this._handleKeyDown, e); } if (lastPanel) {lastPanel._node.removeClass(hoverClass);} lastPanel = panels[i]; this._lastPanel = lastPanel; lastPanel._node.addClass(hoverClass); this._isKeyPressed = true; } },
The keyup
event listener simply stops the event timer started at the end of the keydown
function:
_handleKeyUp: function(e) { if (this._timerKeyDown) {this._timerKeyDown.cancel();} this._isKeyPressed = false; },
How It Works…
When an arrow key is detected, the position of the currently selected panel is fetched (or ZERO is used), and a calculation is made to determine whether the menu has additional panels in the desired direction available for selection. If there are additional panels, then the position is changed by one.
If there is a value for the new property keyHoldTimeout
, then a timer is set to call the _handleKeyDown
function again. This facilitates instances where end-users hold down an arrow key. The rest of the function handles caching the current panel and applying the hover class.
The enter
key calls the click handler, and the escape
key calls the hide
function>
Theres More…
All events and pointers are attached when the menu is opened, and removed when the menu is closed, to remove impact on the page performance.
You may notice minor issues with the arrow navigation when using a small number of panels. You can use the arrow keys to reach all positions, but sometimes a position isn&rsquot;t reachable by all arrows that you think it should be (ie., you expect down and right arrows will get you to a position, but only the down arrow does). It did not seem to be a blocking issue, but if you have a solution, feel free to post it in the comments.
See also
The article, Radial Menu, where this widget was first introduced.The test page, Radial Menu Test, where you can play with the animations yourself.