SpecKeyboard - mar10/fancytree GitHub Wiki

Vision

Fancytree should be controllable using the keyboard. When embedded into a web form, it should behave like a control (e.g. a listbox).

Status

There is an experimental extension (see demo).

Yet there are still open questions, so this feature is open for discussion and the API is subject to change.

Please discuss here: https://github.com/mar10/fancytree/issues/71

Requirements

The option keyboard: true should be used to control if standard keyboard navigation is enabled.

  • Key mapping:

    • +: Expand node.
    • -: Collapse node.
    • SPACE: in select mode: toggle selection. Otherwise set active.
    • ENTER: Set active.
    • BACKSPACE: Set focus to parent.
    • LEFT: If expanded: collapse. Otherwise set focus to parent.
    • RIGHT: If collapsed: expand. Otherwise set focus to first child.
    • UP: Set focus to previous sibling (or parent if already on first sibling).
    • DOWN: Set focus to next visible node.
    • Ctrl-Cursor: Same as Cursor, but only sets the focus, even if autoActivate is on.
    • F2 edit title (requires edit extension)
  • Note that we distinguish 'focused', 'active', and 'selected'. 'Set focus' only sets a dotted border around the node. When autoActivate is enabled (which is on by default), the node also gets activated.

  • Special case table extension: If the table is only used readonly to get a column-alignment, we can use the above mapping.
    ??? If the grid contains checkboxes or input fields however, it would be desirable to navigate between the grid cells with TAB, Shift-TAB, UP, DOWN?

The tabbable: true option was added to control the behavior when the tree is part of a web form. If we TAB through a form that contains a tree and other controls, the tree should behave like a listbox, i.e.

  • be entered / exited using TAB, i.e. be part of the tab index chain
  • set focus to the active node when entered using TAB
  • it is possible that the tree control is active, but no node is yet active (see the listbox in the 'Embed in Forms' demo, at least in Safari).
  • optionally activate first node when entered using TAB and no node was active
  • gray out active node marker when tree control looses focus

If the default implementation for keyboard: true also satisfies these requirements (and makes all users happy), maybe we can remove the tabbable option.

Must Have

  • Support multiple trees on one page
  • Play nicely with other controls (listbox, input, ...) on the same page or form.
  • A disabled tree should ignore keystrokes
  • Don't break key handling for other controls on the same page (see https://github.com/mar10/fancytree/issues/71 )
  • Setting keyboard: false will disable keyboard navigation for the tree. (??? but keep it in the TAB-chain?)
  • Allow implementation of WAI-ARIA support (see SpecAria)
  • Should work if the node titles contain tabbable elemts (like <a> or <input>).

Should Have

  • Use valid HTML
  • Setting tabbable: false will remove the tree from the tab chain.
  • Should be portable to the table extension

Discussion

Resources

Problems

Proposal

(TODO)

Variant 1: Bind key handler to container (prev. implementation in Dynatree)

Dynatree is the predecessor of Fancytree.

  • Nodes use <a> tags for titles, which get the system focus on click.
  • bind "keydown.dynatree" to widget.element --> node._onKeydown(event)
  • bind focus + blur to tree.divTree --> node._onFocus(event)
  • _onKeydown: implement navgation and preventDefault()
  • _onKeypress: does nothing
  • _onFocus: set tree.tnFocused and add class focused to the node

Known Problems

Using <a> tags may cause problems on IE (i.e. auto-scrolling to the left sometimes, when user click on a node).

Variant 2: Bind key handler to document (current implementation)

  • nodes are simple span tags
  • container: <ul tabindex="0"> (when tabbable option is true)
  • keydown is bound to document, because $container might not receive key events, because the nodes cannot have the system focus.
  • $(document).on("keydown" + ns) --> maintain global focusTree variable --> tree.nodeKeydown(event)
  • tree.$container.on("focusin" + ns + " focusout" + ns) --> tree.treeOnFocusInOut(event);
  • nodeKeydown(event): implement default navigation key mapping (see above)
  • treeOnFocusInOut(event): calls nodeSetFocus()
  • nodeSetFocus(event): maintain node.tree.focusNode and calls nodeRenderStatus()
<div id="tree">
  <ul class="fancytree-container" tabindex="0" >
  </ul>
</div>

Known Problems

Backspace and arrow key don't work in input elements (including elements outside of tree contianer if tabbable options is false, see issue #71).

Variant 3: Bind key handler to container (implemented in 'tabbable' branch)

  • nodes are simple span tags
  • when tabbable option is true, tabindex="0" is added to container
  • keydown is bound to $container. (NOTE: If tabbable option is false $container can't receive key events)
  • $container.on("keydown" + ns) --> maintain global focusTree variable --> tree.nodeKeydown(event)
  • tree.$container.on("focusin" + ns + " focusout" + ns) --> tree.treeOnFocusInOut(event);
  • nodeKeydown(event): implement default navigation key mapping (see above)
  • treeOnFocusInOut(event): calls nodeSetFocus()
  • nodeSetFocus(event): maintain node.tree.focusNode and calls nodeRenderStatus()

Known Problems

If tabbable option is false $container can't receive key events, so keyboard navigation should be disabled. But you can't move focus to the tree by keyboard in that mode, therefore keyboard navigation is useless (when tabbable option is false). It's possible to insert invisible dummy element with tabIndex=0 inside $container, so $container will be able to handle key events even if tabbable option is false. But in this case the tree will be tabbable in fact, but system focus will be invisible. It seems that it's useless complication.

Possible improvements

Remove global focusTree variable. It seems that the boolean field focused is enough. That field will be set and cleared in treeSetFocus.

Variant 4: Bind key handler to container and set tabIndex for nodes (implemented in 'tabbable' branch)

tabbable option can be set to "nodes" value (tabbable: "nodes"). In this mode individual nodes can receive system focus:

  • nodes: <span tabIndex="0"></span>
  • $container doesn't have tabIndex attribute
  • keydown is bound to $container.
  • $container.on("keydown" + ns) --> maintain global focusTree variable --> tree.nodeKeydown(event)
  • tree.$container.on("focusin" + ns + " focusout" + ns) --> tree.treeOnFocusInOut(event);
  • nodeKeydown(event): implement default navigation key mapping (see above)
  • treeOnFocusInOut(event): calls nodeSetFocus()
  • nodeSetFocus(event) --> trigger focus event for node.span element --> maintain node.tree.focusNode and calls nodeRenderStatus()

When tabbable options has other values, this variant is equivalent to variant #3.

Variant 5: Bind key handler to nodes

(not tested, but maybe worth a try?)

  • The currently focused node has tabindex=0; all other nodes don't have a tabindex (or have it set to -1?)
  • Container has tabindex=0 when no node is focused, (-1 or none otherwise?)
  • A global focusTree variable is no longer required, but we need to detect a blur event for the tree
  • If a node span contains <input> fields, the node itself doesn't need to be focusable(?)
    Instead when the node is activated it sets the focus to the first embedded control.
    Vice versa, when an embedded input control gets the focus, it marks the parent node as active/focused.
  • 'node span' means 'node tr' when ext-table is used
  • Optionally UP, DOWN, TAB could be intercepted in embedded controls to allow keyboard navigation?

Current Specification

Currently 'Variant 2' is implemented.
Variants 3 and 4 are implemented in 'tabbable' experimental branch.
Variant 5 is implemented in 'gridnav' experimental branch.

⚠️ **GitHub.com Fallback** ⚠️