ExtJS 4 官方指南翻译:键盘导航 Keyboard Navigation

原文:http://docs.sencha.com/ext-js/4-0/#!/guide/keyboard_nav

翻译:frank/sp42 转载请保留本页信息

使用键盘的原因,无非大致两个原因:一、控制鼠标指针不及敲键盘来得快;二、某些用户用不了鼠标,这是可用性方面的问题(accessibility)。

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.

为了能够在例子演示用键盘完全控制,我们把原来的复杂布局的例子修改一下,并且增加“快捷键”的方式,使得人们操作起来更快速流畅。

We're going to convert a modified version of the relatively complicated Ext JScomplex layout example 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.

学习完这篇教程后,相信大家不但会懂得键盘导航的基本所需知识,而且还能够学习,如何利用 KeyNav, KeyMap 和 FocusManager 这几个类来联合实现键盘导航。

By the end of this guide you will have an understanding of where keyboard navigation is most needed and how to utilize keyboard navigation withKeyNav,KeyMap and theFocusManager.

入门 Getting Started

这里是必要的例子。在你喜欢的编辑器中打开里面的 complex.html 和 complex.js 文件。记得还要一份 ExtJS 4 在同一目录底下,而且记住要改名为“ext”。

These are the files we'll be starting from. 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'.

焦点管理器 The Focus Manager

焦点管理器提供一个非常快速的方式打开键盘的导航功能。只是一句如下:

The Focus Manager provides a very quick way to enable basic keyboard navigation. What's more, it's simple to implement:

Ext.FocusManager.enable(true);
在 Ext.onReady 里调用即可;我们传入一个 true 的布尔值表示要在表单的焦点区域显示“可视的提示”(即聚焦框 Focus Frame)。通过聚焦框可以很快看出焦点在哪里。用户按下回车键可以进入应用程序,然后出入不同“层面”,按下回车表示下一步的层面,Esc 键表示返回上一层面。按下 Tab 键可以在相邻的几个元素之间来回切换(那些元素均与处于同一水平上)。

Write this line inside Ext.onReady; we pass the 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).

纯粹的元素周围导航用 FocusManager 来做。如果可以,请关掉你的鼠标。虽然合理,但你可能会发现,某些地区是无法访问(如 Grid),或在屏幕上得到相当繁琐。要解决这个我们采用“快捷方式”,这将允许用户在应用程序某些面板中作的轻松跳转。

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.

是否要为面板而设置快捷键,看看是否满足以下的条件:

When deciding which panels should have shortcuts to them it's useful to have some criteria to go by:

  • 用得频率高不高?Is it often used?
  • 作为一个锚吗?——也就是说,它可以提供一块垫脚石,使其他偏远地区更容易获得呢? Could it be used as an anchor - that is, could it provide a stepping stone to make other remote areas easier to get to?
  • 操作是否符合直觉?Does it feel intuitive to navigate to?

如果答对了里面其中一项,都表示你可以替你的最终用户添加上命令的快捷方式。

If the answer is yes to at least one of these, give it a keyboard shortcut and aid your end-users.

KeyNav

顾名思义,KeyNav 所负责的职责就是提供“导航键”部分的按键。所谓“导航键”,罗列如下:

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:

  • Enter
  • Space
  • Left
  • Right
  • Up
  • Down
  • Tab
  • Escape
  • Page Up
  • Page Down
  • Delete
  • Backspace
  • Home
  • End

不过请谨记,部分用户上的机子,却是没有某些键的,例如,苹果机用户便没有 Page Up、 Page Down、 Del、 Home 或 End 键。参见下列例子演示其用法。

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.

var nav = Ext.create('Ext.util.KeyNav', "my-element", {
    "left" : function(e){
        this.moveLeft(e.ctrlKey);
    },
    "right" : function(e){
        this.moveRight(e.ctrlKey);
    },
    "enter" : function(e){
        this.save();
    },
    scope : this
});
KeyNav 可以说转为方向键而设的,于是我们把例子中本来用 tab、回车、esc 键控制的命令(切换不同的面板),转换为方向键控制的。

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.

var nav = Ext.create('Ext.util.KeyNav', Ext.getBody(), {
    "left" : function(){
        var el = Ext.FocusManager.focusedCmp;
        if (el.previousSibling()) el.previousSibling().focus();
    },
    "right" : function(){
        var el = Ext.FocusManager.focusedCmp;
        if (el.nextSibling()) el.nextSibling().focus();
    },
    "up" : function() {
        var el = Ext.FocusManager.focusedCmp;
        if (el.up()) el.up().focus();
    },
    "down" : function() {
        var el = Ext.FocusManager.focusedCmp;
        if (el.items) el.items.items[0].focus();
    },
    scope : this
});
当前我们通过 focusedCmp 来聚焦组件。如果函数有一个值而非 null 的话,表示我们聚焦到了那个元素,上一个兄弟元素就绑定了 Left 箭头键,而第一个子元素就绑定了 Down 键。这样,我们的应用程序就能更方便的控制起来了。下面我们进入 部分,看看如何增加特殊功能的键。

We get the currently focused component with 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.

KeyMap

一般 Ext 应用程序划分为不同的区域:North、South、East 和 West。我们创意一个 KeyMap 对象,不仅可以聚焦这些元素,而且可以把闭合的区域展开开来。我们先看看典型的 KeyMap 大致如何。

You'll notice that there are many regions to our Ext application: North, South, East, and West. We're going to create aKeyMap 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.

var map = Ext.create('Ext.util.KeyMap', "my-element", {
    key: 13, // or Ext.EventObject.ENTER
    ctrl: true,
    shift: false,
    fn: myHandler,
    scope: myObject
});
第一个配置项属性 key 是键对应的数值。这里是十分有用的文档,告诉我们哪些键对应着哪些数字。下面两个配置项 ctrl 和 shift,分别表示要不要同时按下形成组合键的意思。这个例子中设 ctrl 是需要的。所以,按下了 ctrl + 回车就会执行 myHandler 函数。该项可以是 inline 或者函数的引用。最后,scope 就是定义 KeyMap 在哪里有效。

The first property, key is the numeric keycode that maps a key. A useful document that maps which numbers correlate to which keys can befound here. 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 invokemyHandler.fn is the function to be called. This can either be inline or a reference to a function. Finally,scope defines where thisKeyMap will be effective.

KeyMap

单个按键对应一个函数,KeyMap处理起来毫无问题,多个按键对应同一函数,KeyMap亦能胜任。如果我们有多个键对应 myHandler 函数,比如我们说 [10,13]。

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 invokemyHandler we'd write it likekey: [10, 13].

首先,我们将集中在主面板:北部,南部,东部和西部。

We'll start by concentrating on the main panels: north, south, east and west.

var map = Ext.create('Ext.util.KeyMap', Ext.getBody(), [
    {
        key: Ext.EventObject.E, // E for east
        shift: true,
        ctrl: false, // explicitly set as false to avoid collisions
        fn: function() {
            var parentPanel = eastPanel;
            expand(parentPanel);
        }
    },
    {
        key: Ext.EventObject.W, // W for west
        shift: true,
        ctrl: false,
        fn: function() {
            var parentPanel = westPanel;
            expand(parentPanel);
        }
    },
    {
        key: Ext.EventObject.S, // S for south
        shift: true,
        ctrl: false,
        fn: function() {
            var parentPanel = southPanel;
            expand(parentPanel);
        }
    }
]);
Ext.EventObject.X 就是指向我们侦听的键,写法上比较清晰。其余的应该是从上面的例子可以看出。然后我们写出展开函数 expand():

We use 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 theexpand() function underneath:

function expand(parentPanel) {
    parentPanel.toggleCollapse();
    parentPanel.on('expand', function(){
        parentPanel.el.focus();
    });
    parentPanel.on('collapse', function(){
        viewport.el.focus();
    });
}
该函数的作用,在于当面板展开的时候,该面板的元素会自动获得焦点;或者说,当面板闭合的时候,其上一级的对象,即 viewport,会获得焦点。

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, theviewport.

从上面的代码可以看到,无论闭合操作还是展开操作,用按键执行面板执行的话都比鼠标执行来得快。

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!

如此类推,我们在西边的面板上执行导航(Navigation)、设置(Settings)和信息(Information)的操作。我们已经知道 parentPanels,所以为避免命名上的冲突,我称它们“子面板 subPanels”。

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 otherparentPanels that we've seen already.

{
    key: Ext.EventObject.S, // S for settings
    ctrl: true,
    fn: function() {
        var parentPanel = westPanel;
        var subPanel = settings;
        expand(parentPanel, subPanel);
    }
},
{
    key: Ext.EventObject.I, // I for information
    ctrl: true,
    fn: function() {
        var parentPanel = westPanel;
        var subPanel = information;
        expand(parentPanel, subPanel);
    }
},
{
    key: Ext.EventObject.N, // N for navigation
    ctrl: true,
    fn: function(){
        var parentPanel = westPanel;
        var subPanel = navigation;
        expand(parentPanel, subPanel);
    }
}
和前面的模式类似,只不过,我们加上一个 subPanel 的变量。之前的 expand 函数不适应新的 subPanels,于是我们在原来的基础上修改如下:

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 whethersubPanel is declared or not.

function expand(parentPanel, subPanel) {

    if (subPanel) {
        function subPanelExpand(subPanel) {
            // set listener for expand function
            subPanel.on('expand', function() {
                setTimeout(function() { subPanel.focus(); }, 200);
            });
            // expand the subPanel
            subPanel.expand();
        }

        if (parentPanel.collapsed) {
            // enclosing panel is collapsed, open it
            parentPanel.expand();
            subPanelExpand(subPanel);
        }
        else if (!subPanel.collapsed) {
            // subPanel is open and just needs focusing
            subPanel.focus();
        }
        else {
            // parentPanel isn't collapsed but subPanel is
            subPanelExpand(subPanel);
        }
    }
    else {
        // no subPanel detected
        parentPanel.toggleCollapse();
        parentPanel.on('expand', function(){
            parentPanel.el.focus();
        });
        parentPanel.on('collapse', function(){
            viewport.el.focus();
        });
    }
}
事件侦听器 expand 中执行了聚焦的行为,也就是说面板展开后才执行 focus,它需要包装在 setTimeout 中,否则会过早聚焦在面板还是小的时候,那样的话便不准确了(即在展开过程中就聚焦了)。延时 200毫秒可解决这个问题,而 parentPanels 则没有这个问题。

Despite focus being in the expand event listener, which is meant to fireafter the panel has been expanded, it needs wrapping in asetTimeout 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 theparentPanels.

在这一点上,你可以打开和关闭面板,以及单纯的键盘(如 Shift + E或 Ctrl + S)。当然,任何按键事件都可以触发任意的 Ext JS 的函数,使得应用程序有更自然感觉。

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.

最后我们加入 KeyMap 对象,这里有两张 Tab 面板,一个在 Center 面板中,另一个在 “Eye Data”面板旁边。要是我们能够像在浏览器中关闭 tab 面板就很有用了,也就是 ctrl+w。

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.

{
    key: Ext.EventObject.W, // W to close
    ctrl: true,
    fn: function(){
        var el = Ext.FocusManager.focusedCmp;
        if (el.xtype === 'tab' && el.closable) {
            el.up().focus();
            el.destroy();
        }
    },
    scope: this
}
以上的配置过程,说明一下就是,我们从焦点管理器的  focusedCmp 的属性获取到了当前焦点所在的组件,如果这是一个 tab 组件而且允许关闭的,则其父面板获取焦点而且自身销毁掉。

We've configured which key presses we're listening for and get which component is currently focused with the Focus Manager'sfocusedCmp 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.

固定 Grid Fixing the Grid

您可能已经注意到,没有鼠标的帮助下我们是不能让 Grid 中的行对象获得焦点。通过控制台的线索,我们得到不能为什么不能这样的原因——控制台的报告说,“pos is undefined”。click 事件中会传入有关记录的信息,包括其在 Grid 中的位置是什么。不过,使用的FocusManager,并没有送入我们需要的信息传递,于是我们需要通过模拟一个带有行和列属性的对象来指定位置。请观察下面的 viewport 对象:

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 therow andcolumn . Do the following underneath theviewport variable:

var easttab = Ext.getCmp('easttab');

var gridMap = Ext.create('Ext.util.KeyMap', 'eastPanel', [
    {
        key: '\r', // Return key
        fn: function() {
            easttab.getSelectionModel().setCurrentPosition({row: 0, column: 1});
        },
        scope: 'eastPanel'
    },
    {
        key: Ext.EventObject.ESC,
        fn: function() {
            easttab.el.focus();
        },
        scope: 'eastPanel'
    }
]);
不妨试试,成功的话就可以用键盘来进入或退出 grid。另外注意 scope 这里设为“eastPanel”,否则不能从 grid 离开。

Try this and you'll see that we can successfully enter and exit the grid using the keyboard. It's important that we specify thescope on these keys otherwise you won't be able to escape from the grid.

切换键盘映射 Toggling Keyboard Mapping

KeyMap 一项有用的功能就是轻松地设置 enabled 或 disabled。比如说,当您的应用程序的大多数用户点击一个元素的时候却不想出现聚焦框,你可以做一个按钮来(或其他键盘映射)激活这种行为。

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 anotherKeyMap) that enables this behavior.

如果你想添加一个全局性的按键,打开和关闭键盘导航,你可以用下面的:

If you wanted to add a global key press that would turn on and off keyboard navigation you could do so with the following:

var initMap = Ext.create('Ext.util.KeyMap', Ext.getBody(), {
    key: Ext.EventObject.T, // T for toggle
    shift: true,
    fn: function(){
        map.enabled ? map.disable() : map.enable();
        Ext.FocusManager.enabled ? Ext.FocusManager.disable() : Ext.FocusManager.enable(true);
    }
});
我们已经创建了一个新的键盘映射,用来关闭 Shift + T 的键盘导航,反之亦然。我们不能够使用现有的 KeyMap,是因为它自动关闭了,将无法重新初始化。

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 existingKeyMap because it would be turning itself off and wouldn't be able to be reinitialized.

结论 Conclusion

我们把原来无法使用键盘控制的例子转换为可以的了。我们也遇到必须通过聚焦管理器才能加入的自定义功能。

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.

我们还学习了在不同面板中 KeyMap 实现跳转切换。最后我们用 KeyNav 做到用方向键移动程序是件多么轻松的事。

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, withKeyNav we've seen how easy it is to move around an application with arrow keys.

你可能感兴趣的:(function,manager,application,ExtJs,keyboard,returning)