200 lines
5.1 KiB
JavaScript
200 lines
5.1 KiB
JavaScript
/*!
|
|
* jquery.fancytree.gridnav.js
|
|
*
|
|
* Support keyboard navigation for trees with embedded input controls.
|
|
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
|
|
*
|
|
* Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
|
|
*
|
|
* Released under the MIT license
|
|
* https://github.com/mar10/fancytree/wiki/LicenseInfo
|
|
*
|
|
* @version 2.1.0
|
|
* @date 2014-05-29T16:44
|
|
*/
|
|
|
|
;(function($, window, document, undefined) {
|
|
|
|
"use strict";
|
|
|
|
|
|
/*******************************************************************************
|
|
* Private functions and variables
|
|
*/
|
|
|
|
// Allow these navigation keys even when input controls are focused
|
|
|
|
var KC = $.ui.keyCode,
|
|
// which keys are *not* handled by embedded control, but passed to tree
|
|
// navigation handler:
|
|
NAV_KEYS = {
|
|
"text": [KC.UP, KC.DOWN],
|
|
"checkbox": [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
|
|
"radiobutton": [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
|
|
"select-one": [KC.LEFT, KC.RIGHT],
|
|
"select-multiple": [KC.LEFT, KC.RIGHT]
|
|
};
|
|
|
|
|
|
/* Calculate TD column index (considering colspans).*/
|
|
function getColIdx($tr, $td) {
|
|
var colspan,
|
|
td = $td.get(0),
|
|
idx = 0;
|
|
|
|
$tr.children().each(function () {
|
|
if( this === td ) {
|
|
return false;
|
|
}
|
|
colspan = $(this).prop("colspan");
|
|
idx += colspan ? colspan : 1;
|
|
});
|
|
return idx;
|
|
}
|
|
|
|
|
|
/* Find TD at given column index (considering colspans).*/
|
|
function findTdAtColIdx($tr, colIdx) {
|
|
var colspan,
|
|
res = null,
|
|
idx = 0;
|
|
|
|
$tr.children().each(function () {
|
|
if( idx >= colIdx ) {
|
|
res = $(this);
|
|
return false;
|
|
}
|
|
colspan = $(this).prop("colspan");
|
|
idx += colspan ? colspan : 1;
|
|
});
|
|
return res;
|
|
}
|
|
|
|
|
|
/* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
|
|
function findNeighbourTd($target, keyCode){
|
|
var $tr, colIdx,
|
|
$td = $target.closest("td"),
|
|
$tdNext = null;
|
|
|
|
switch( keyCode ){
|
|
case KC.LEFT:
|
|
$tdNext = $td.prev();
|
|
break;
|
|
case KC.RIGHT:
|
|
$tdNext = $td.next();
|
|
break;
|
|
case KC.UP:
|
|
case KC.DOWN:
|
|
$tr = $td.parent();
|
|
colIdx = getColIdx($tr, $td);
|
|
while( true ) {
|
|
$tr = (keyCode === KC.UP) ? $tr.prev() : $tr.next();
|
|
if( !$tr.length ) {
|
|
break;
|
|
}
|
|
// Skip hidden rows
|
|
if( $tr.is(":hidden") ) {
|
|
continue;
|
|
}
|
|
// Find adjacent cell in the same column
|
|
$tdNext = findTdAtColIdx($tr, colIdx);
|
|
// Skip cells that don't conatain a focusable element
|
|
if( $tdNext && $tdNext.find(":input").length ) {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return $tdNext;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Extension code
|
|
*/
|
|
$.ui.fancytree.registerExtension({
|
|
name: "gridnav",
|
|
version: "0.0.1",
|
|
// Default options for this extension.
|
|
options: {
|
|
autofocusInput: false, // Focus first embedded input if node gets activated
|
|
handleCursorKeys: true // Allow UP/DOWN in inputs to move to prev/next node
|
|
},
|
|
|
|
treeInit: function(ctx){
|
|
// gridnav requires the table extension to be loaded before itself
|
|
this._requireExtension("table", true, true);
|
|
this._super(ctx);
|
|
|
|
this.$container.addClass("fancytree-ext-gridnav");
|
|
|
|
// Activate node if embedded input gets focus (due to a click)
|
|
this.$container.on("focusin", function(event){
|
|
var ctx2,
|
|
node = $.ui.fancytree.getNode(event.target);
|
|
|
|
if( node && !node.isActive() ){
|
|
// Call node.setActive(), but also pass the event
|
|
ctx2 = ctx.tree._makeHookContext(node, event);
|
|
ctx.tree._callHook("nodeSetActive", ctx2, true);
|
|
}
|
|
});
|
|
},
|
|
nodeSetActive: function(ctx, flag) {
|
|
var $outer,
|
|
opts = ctx.options.gridnav,
|
|
node = ctx.node,
|
|
event = ctx.originalEvent || {},
|
|
triggeredByInput = $(event.target).is(":input");
|
|
|
|
flag = (flag !== false);
|
|
|
|
this._super(ctx, flag);
|
|
|
|
if( flag ){
|
|
if( ctx.options.titlesTabbable ){
|
|
if( !triggeredByInput ) {
|
|
$(node.span).find("span.fancytree-title").focus();
|
|
node.setFocus();
|
|
}
|
|
// If one node is tabbable, the container no longer needs to be
|
|
ctx.tree.$container.attr("tabindex", "-1");
|
|
// ctx.tree.$container.removeAttr("tabindex");
|
|
} else if( opts.autofocusInput && !triggeredByInput ){
|
|
// Set focus to input sub input (if node was clicked, but not
|
|
// when TAB was pressed )
|
|
$outer = $(node.tr || node.span);
|
|
$outer.find(":input:enabled:first").focus();
|
|
}
|
|
}
|
|
},
|
|
nodeKeydown: function(ctx) {
|
|
var inputType, handleKeys, $td,
|
|
opts = ctx.options.gridnav,
|
|
event = ctx.originalEvent,
|
|
$target = $(event.target);
|
|
|
|
// jQuery
|
|
inputType = $target.is(":input:enabled") ? $target.prop("type") : null;
|
|
// ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
|
|
|
|
if( inputType && opts.handleCursorKeys ){
|
|
handleKeys = NAV_KEYS[inputType];
|
|
if( handleKeys && $.inArray(event.which, handleKeys) >= 0 ){
|
|
$td = findNeighbourTd($target, event.which);
|
|
// ctx.node.debug("ignore keydown in input", event.which, handleKeys);
|
|
if( $td && $td.length ) {
|
|
$td.find(":input:enabled").focus();
|
|
// Prevent Fancytree default navigation
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
|
|
return this._super(ctx);
|
|
}
|
|
});
|
|
}(jQuery, window, document));
|