2 ______________________________________________
4 Navigating with your keyboard is often faster than using the cursor and is useful for both power users and to provide accessibility to users that find a mouse difficult to use.
6 We're going to convert a modified version of the relatively complicated Ext JS [complex layout example](#!/example/layout/complex.html) into an application that is fully accessible through the keyboard. We'll also add keyboard shortcuts to potentially make navigation via the keyboard faster than the cursor.
8 By the end of this guide you will have an understanding of where keyboard navigation is most needed and how to utilize keyboard navigation with {@link Ext.util.KeyNav KeyNav}, {@link Ext.util.KeyMap KeyMap} and the {@link Ext.FocusManager FocusManager}.
12 [These are the files we'll be starting from](guides/keyboard_nav/start.zip). Unzip them and open complex.html and complex.js in your preferred editor. Also unzip a copy of Ext JS 4 into the same directory and rename it 'ext'.
16 The Focus Manager provides a very quick way to enable basic keyboard navigation. What's more, it's simple to implement:
18 Ext.FocusManager.enable(true);
20 Write this line inside {@link Ext#onReady Ext.onReady}; we pass the {@link Boolean} `true` to show a "visual cue" in the form of a blue ring around the focused area (this is known as a focus frame). This focus frame makes it easy to see at a glance which area has focus. The user presses Enter to enter the application and then move up and down 'levels', with Enter to go down a level and Backspace or Escape to go up a level. The Tab key can be used to jump between sibling elements (those that are on the same level).
22 Experiment with navigating around the elements solely with the FocusManager. If you can, turn off your mouse too. Although adequate, you'll probably find that certain areas are either inaccessible (such as the grid) or it's quite cumbersome to get around the screen. We're going to tackle this with 'shortcuts' which will allow users to jump to certain panels of the application with ease.
24 When deciding which panels should have shortcuts to them it's useful to have some criteria to go by:
27 - Could it be used as an anchor - that is, could it provide a stepping stone to make other remote areas easier to get to?
28 - Does it feel intuitive to navigate to?
30 If the answer is yes to at least one of these, give it a keyboard shortcut and aid your end-users.
34 It's the job of KeyNav to provide easy keyboard navigation. It allows you to use the following keys to navigate through your Ext JS application:
51 It's also worth keeping in mind users with limited keyboards that might not have certain keys, for example certain Apple computers don't have Page Up, Page Down, Del, Home or End keys. Let's take a look at an example of how you'd use it.
53 var nav = Ext.create('Ext.util.KeyNav', "my-element", {
55 this.moveLeft(e.ctrlKey);
57 "right" : function(e){
58 this.moveRight(e.ctrlKey);
60 "enter" : function(e){
66 KeyNav's speciality is listening for arrow keys, so we're going to add the ability to navigate around panels with the arrow keys instead of having to use tab, enter and escape.
68 var nav = Ext.create('Ext.util.KeyNav', Ext.getBody(), {
70 var el = Ext.FocusManager.focusedCmp;
71 if (el.previousSibling()) el.previousSibling().focus();
74 var el = Ext.FocusManager.focusedCmp;
75 if (el.nextSibling()) el.nextSibling().focus();
78 var el = Ext.FocusManager.focusedCmp;
79 if (el.up()) el.up().focus();
82 var el = Ext.FocusManager.focusedCmp;
83 if (el.items) el.items.items[0].focus();
88 We get the currently focused component with {@link Ext.FocusManager#focusedCmp focusedCmp}. If the function has a value other than `null`, we focus on the element that we want, be it the previous sibling with the Left arrow key or the first child component with the Down key. Already we've made our application much easier to navigate. Next, we're going to look at Ext.util.KeyMap and how to add specific functionality to keys.
92 You'll notice that there are many regions to our Ext application: North, South, East, and West. We're going to create a {@link Ext.util.KeyMap KeyMap} that will not only focus on these elements but, if the region is collapsed, expand it too. Let's have a look at what a typical KeyMap object looks like.
94 var map = Ext.create('Ext.util.KeyMap', "my-element", {
95 key: 13, // or Ext.EventObject.ENTER
102 The first property, `key` is the numeric keycode that maps a key. A useful document that maps which numbers correlate to which keys can be [found here](ext-js/4-0/source/EventObject.html). The next two, `ctrl` and `shift`, specify if the respective key is required to be held down to activate the function. In our case ctrl does, so ctrl+Enter will invoke `myHandler`. `fn` is the function to be called. This can either be inline or a reference to a function. Finally, `scope` defines where this `KeyMap` will be effective.
104 `KeyMap` is versatile in that it allows you to specify either one key that carries out a function or an array of keys that carry out the same function. If we wanted a number of keys to invoke `myHandler` we'd write it like `key: [10, 13]`.
106 We'll start by concentrating on the main panels: north, south, east and west.
108 var map = Ext.create('Ext.util.KeyMap', Ext.getBody(), [
110 key: Ext.EventObject.E, // E for east
112 ctrl: false, // explicitly set as false to avoid collisions
114 var parentPanel = eastPanel;
119 key: Ext.EventObject.W, // W for west
123 var parentPanel = westPanel;
128 key: Ext.EventObject.S, // S for south
132 var parentPanel = southPanel;
138 We use {@link Ext.EventObject Ext.EventObject.X} to make it obvious which key we're listening for, the rest should be clear from the example above. Then we write the `expand()` function underneath:
140 function expand(parentPanel) {
141 parentPanel.toggleCollapse();
142 parentPanel.on('expand', function(){
143 parentPanel.el.focus();
145 parentPanel.on('collapse', function(){
150 This function toggles the collapsing of the panel and focuses on it if it's been expanded, or collapses it if it's already expanded, returning focus to the next level up, the `viewport`.
152 Now that all of the code is in place, try expanding and collapsing the panels with a key press versus clicking on the small button that expands or collapses them. It's much faster with the keyboard!
154 Next, we'll go through a similar process with the Navigation, Settings and Information tabs on the West Panel. I've called them subPanels because they're children to the other `parentPanels` that we've seen already.
157 key: Ext.EventObject.S, // S for settings
160 var parentPanel = westPanel;
161 var subPanel = settings;
162 expand(parentPanel, subPanel);
166 key: Ext.EventObject.I, // I for information
169 var parentPanel = westPanel;
170 var subPanel = information;
171 expand(parentPanel, subPanel);
175 key: Ext.EventObject.N, // N for navigation
178 var parentPanel = westPanel;
179 var subPanel = navigation;
180 expand(parentPanel, subPanel);
184 We follow the same pattern as we've used before but added a variable called `subPanel`. Our `expand` function won't know what to do with these so we'll refactor it to act accordingly depending on whether `subPanel` is declared or not.
186 function expand(parentPanel, subPanel) {
189 function subPanelExpand(subPanel) {
190 // set listener for expand function
191 subPanel.on('expand', function() {
192 setTimeout(function() { subPanel.focus(); }, 200);
194 // expand the subPanel
198 if (parentPanel.collapsed) {
199 // enclosing panel is collapsed, open it
200 parentPanel.expand();
201 subPanelExpand(subPanel);
203 else if (!subPanel.collapsed) {
204 // subPanel is open and just needs focusing
208 // parentPanel isn't collapsed but subPanel is
209 subPanelExpand(subPanel);
213 // no subPanel detected
214 parentPanel.toggleCollapse();
215 parentPanel.on('expand', function(){
216 parentPanel.el.focus();
218 parentPanel.on('collapse', function(){
224 Despite `focus` being in the `expand` event listener, which is meant to fire _after_ the panel has been expanded, it needs wrapping in a `setTimeout` because otherwise it focuses too early resulting in a focus frame smaller than the panel (that is, it focuses while it's expanding). Compensating 200 milliseconds gets around this; this problem isn't present with the `parentPanel`s.
226 At this point you can open and close panels, as well as focus them, purely with the keyboard (e.g. shift+e or ctrl+s). Naturally, any function of Ext JS can be triggered by a key press leading to many possibilities for a more native-feeling application.
228 There's one last object to add to our `KeyMap`, there are two tabs, one on the center panel and the other next to the 'Eye Data' tab. It would be useful to be able to close these as you would a tab in a browser with ctrl+w.
231 key: Ext.EventObject.W, // W to close
234 var el = Ext.FocusManager.focusedCmp;
235 if (el.xtype === 'tab' && el.closable) {
243 We've configured which key presses we're listening for and get which component is currently focused with the Focus Manager's {@link Ext.FocusManager#focusedCmp focusedCmp} property. If the currently focused component is a tab and it's closable, then we set the focus to the parent panel and destroy the tab.
247 You may have noticed that when we try to focus a row in the grid it isn't possible without the mouse. If you look in the console we get a clue as to why this is, it reports that "pos is undefined". The click event passes information on the record, including what its position in the grid is. However, using the FocusManager, it doesn't pass this information so we need to emulate it by passing an object that specifies the `row` and `column` properties. Do the following underneath the `viewport` variable:
249 var easttab = Ext.getCmp('easttab');
251 var gridMap = Ext.create('Ext.util.KeyMap', 'eastPanel', [
253 key: '\r', // Return key
255 easttab.getSelectionModel().setCurrentPosition({row: 0, column: 1});
260 key: Ext.EventObject.ESC,
268 Try this and you'll see that we can successfully enter and exit the grid using the keyboard. It's important that we specify the `scope` on these keys otherwise you won't be able to escape from the grid.
270 ### Toggling Keyboard Mapping
272 A useful feature of `KeyMap` is that it can easily be enabled or disabled. It could be that you don't want most users of your application to have a focus frame when they click on an element, so you could make a button (or another `KeyMap`) that enables this behavior.
274 If you wanted to add a global key press that would turn on and off keyboard navigation you could do so with the following:
276 var initMap = Ext.create('Ext.util.KeyMap', Ext.getBody(), {
277 key: Ext.EventObject.T, // T for toggle
280 map.enabled ? map.disable() : map.enable();
281 Ext.FocusManager.enabled ? Ext.FocusManager.disable() : Ext.FocusManager.enable(true);
285 We've created a new `KeyMap` that will turn off keyboard navigation with shift+t and turn it back on with the same. We aren't able to use the existing `KeyMap` because it would be turning itself off and wouldn't be able to be reinitialized.
289 We've converted a complex series of panels that would have otherwise been inaccessible to use the keyboard. We've also encountered some examples where we've had to add some custom functionality on top of the Focus Manager.
291 With `KeyMap`, we've learnt that we can jump to different Panels as well as invoke any functionality that we'd usually write with a keystroke. Finally, with `KeyNav` we've seen how easy it is to move around an application with arrow keys.