715 lines
20 KiB
JavaScript
715 lines
20 KiB
JavaScript
|
/*!
|
||
|
* jquery.fancytree.ariagrid.js
|
||
|
*
|
||
|
* Support ARIA compliant markup and keyboard navigation for tree grids with
|
||
|
* embedded input controls.
|
||
|
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
|
||
|
*
|
||
|
* @requires ext-table
|
||
|
*
|
||
|
* Copyright (c) 2008-2021, Martin Wendt (https://wwWendt.de)
|
||
|
*
|
||
|
* Released under the MIT license
|
||
|
* https://github.com/mar10/fancytree/wiki/LicenseInfo
|
||
|
*
|
||
|
* @version 2.38.2
|
||
|
* @date 2022-06-30T18:24:06Z
|
||
|
*/
|
||
|
|
||
|
(function (factory) {
|
||
|
if (typeof define === "function" && define.amd) {
|
||
|
// AMD. Register as an anonymous module.
|
||
|
define([
|
||
|
"jquery",
|
||
|
"./jquery.fancytree",
|
||
|
"./jquery.fancytree.table",
|
||
|
], factory);
|
||
|
} else if (typeof module === "object" && module.exports) {
|
||
|
// Node/CommonJS
|
||
|
require("./jquery.fancytree.table"); // core + table
|
||
|
module.exports = factory(require("jquery"));
|
||
|
} else {
|
||
|
// Browser globals
|
||
|
factory(jQuery);
|
||
|
}
|
||
|
})(function ($) {
|
||
|
"use strict";
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Private functions and variables
|
||
|
*/
|
||
|
|
||
|
// Allow these navigation keys even when input controls are focused
|
||
|
|
||
|
var FT = $.ui.fancytree,
|
||
|
clsFancytreeActiveCell = "fancytree-active-cell",
|
||
|
clsFancytreeCellMode = "fancytree-cell-mode",
|
||
|
clsFancytreeCellNavMode = "fancytree-cell-nav-mode",
|
||
|
VALID_MODES = ["allow", "force", "start", "off"],
|
||
|
// Define which keys are handled by embedded <input> control, and should
|
||
|
// *not* be passed to tree navigation handler in cell-edit mode:
|
||
|
INPUT_KEYS = {
|
||
|
text: ["left", "right", "home", "end", "backspace"],
|
||
|
number: ["up", "down", "left", "right", "home", "end", "backspace"],
|
||
|
checkbox: [],
|
||
|
link: [],
|
||
|
radiobutton: ["up", "down"],
|
||
|
"select-one": ["up", "down"],
|
||
|
"select-multiple": ["up", "down"],
|
||
|
},
|
||
|
NAV_KEYS = ["up", "down", "left", "right", "home", "end"];
|
||
|
|
||
|
/* Set aria-activedescendant on container to active cell's ID (generate one if required).*/
|
||
|
function setActiveDescendant(tree, $target) {
|
||
|
var id = $target ? $target.uniqueId().attr("id") : "";
|
||
|
|
||
|
tree.$container.attr("aria-activedescendant", id);
|
||
|
}
|
||
|
|
||
|
/* 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(tree, $target, keyCode) {
|
||
|
var nextNode,
|
||
|
node,
|
||
|
navMap = { "ctrl+home": "first", "ctrl+end": "last" },
|
||
|
$td = $target.closest("td"),
|
||
|
$tr = $td.parent(),
|
||
|
treeOpts = tree.options,
|
||
|
colIdx = getColIdx($tr, $td),
|
||
|
$tdNext = null;
|
||
|
|
||
|
keyCode = navMap[keyCode] || keyCode;
|
||
|
|
||
|
switch (keyCode) {
|
||
|
case "left":
|
||
|
$tdNext = treeOpts.rtl ? $td.next() : $td.prev();
|
||
|
break;
|
||
|
case "right":
|
||
|
$tdNext = treeOpts.rtl ? $td.prev() : $td.next();
|
||
|
break;
|
||
|
case "up":
|
||
|
case "down":
|
||
|
case "ctrl+home":
|
||
|
case "ctrl+end":
|
||
|
node = $tr[0].ftnode;
|
||
|
nextNode = tree.findRelatedNode(node, keyCode);
|
||
|
if (nextNode) {
|
||
|
nextNode.makeVisible();
|
||
|
nextNode.setActive();
|
||
|
$tdNext = findTdAtColIdx($(nextNode.tr), colIdx);
|
||
|
}
|
||
|
break;
|
||
|
case "home":
|
||
|
$tdNext = treeOpts.rtl
|
||
|
? $tr.children("td").last()
|
||
|
: $tr.children("td").first();
|
||
|
break;
|
||
|
case "end":
|
||
|
$tdNext = treeOpts.rtl
|
||
|
? $tr.children("td").first()
|
||
|
: $tr.children("td").last();
|
||
|
break;
|
||
|
}
|
||
|
return $tdNext && $tdNext.length ? $tdNext : null;
|
||
|
}
|
||
|
|
||
|
/* Return a descriptive string of the current mode. */
|
||
|
function getGridNavMode(tree) {
|
||
|
if (tree.$activeTd) {
|
||
|
return tree.forceNavMode ? "cell-nav" : "cell-edit";
|
||
|
}
|
||
|
return "row";
|
||
|
}
|
||
|
|
||
|
/* .*/
|
||
|
function activateEmbeddedLink($td) {
|
||
|
// $td.find( "a" )[ 0 ].trigger("click"); // does not work (always)?
|
||
|
// $td.find( "a" ).trigger("click");
|
||
|
var event = document.createEvent("MouseEvent"),
|
||
|
a = $td.find("a")[0]; // document.getElementById('nameOfID');
|
||
|
|
||
|
event = new CustomEvent("click");
|
||
|
a.dispatchEvent(event);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* [ext-ariagrid] Set active cell and activate cell-nav or cell-edit mode if needed.
|
||
|
* Pass $td=null to enter row-mode.
|
||
|
*
|
||
|
* See also FancytreeNode#setActive(flag, {cell: idx})
|
||
|
*
|
||
|
* @param {jQuery | Element | integer} [$td]
|
||
|
* @param {Event|null} [orgEvent=null]
|
||
|
* @alias Fancytree#activateCell
|
||
|
* @requires jquery.fancytree.ariagrid.js
|
||
|
* @since 2.23
|
||
|
*/
|
||
|
$.ui.fancytree._FancytreeClass.prototype.activateCell = function (
|
||
|
$td,
|
||
|
orgEvent
|
||
|
) {
|
||
|
var colIdx,
|
||
|
$input,
|
||
|
$tr,
|
||
|
res,
|
||
|
tree = this,
|
||
|
$prevTd = this.$activeTd || null,
|
||
|
newNode = $td ? FT.getNode($td) : null,
|
||
|
prevNode = $prevTd ? FT.getNode($prevTd) : null,
|
||
|
anyNode = newNode || prevNode,
|
||
|
$prevTr = $prevTd ? $prevTd.closest("tr") : null;
|
||
|
|
||
|
anyNode.debug(
|
||
|
"activateCell(" +
|
||
|
($prevTd ? $prevTd.text() : "null") +
|
||
|
") -> " +
|
||
|
($td ? $td.text() : "OFF")
|
||
|
);
|
||
|
|
||
|
// Make available as event
|
||
|
|
||
|
if ($td) {
|
||
|
FT.assert($td.length, "Invalid active cell");
|
||
|
colIdx = getColIdx($(newNode.tr), $td);
|
||
|
res = this._triggerNodeEvent("activateCell", newNode, orgEvent, {
|
||
|
activeTd: tree.$activeTd,
|
||
|
colIdx: colIdx,
|
||
|
mode: null, // editMode ? "cell-edit" : "cell-nav"
|
||
|
});
|
||
|
if (res === false) {
|
||
|
return false;
|
||
|
}
|
||
|
this.$container.addClass(clsFancytreeCellMode);
|
||
|
this.$container.toggleClass(
|
||
|
clsFancytreeCellNavMode,
|
||
|
!!this.forceNavMode
|
||
|
);
|
||
|
$tr = $td.closest("tr");
|
||
|
if ($prevTd) {
|
||
|
// cell-mode => cell-mode
|
||
|
if ($prevTd.is($td)) {
|
||
|
return;
|
||
|
}
|
||
|
$prevTd
|
||
|
.removeAttr("tabindex")
|
||
|
.removeClass(clsFancytreeActiveCell);
|
||
|
|
||
|
if (!$prevTr.is($tr)) {
|
||
|
// We are moving to a different row: only the inputs in the
|
||
|
// active row should be tabbable
|
||
|
$prevTr.find(">td :input,a").attr("tabindex", "-1");
|
||
|
}
|
||
|
}
|
||
|
$tr.find(">td :input:enabled,a").attr("tabindex", "0");
|
||
|
newNode.setActive();
|
||
|
$td.addClass(clsFancytreeActiveCell);
|
||
|
this.$activeTd = $td;
|
||
|
|
||
|
$input = $td.find(":input:enabled,a");
|
||
|
this.debug("Focus input", $input);
|
||
|
if ($input.length) {
|
||
|
$input.focus();
|
||
|
setActiveDescendant(this, $input);
|
||
|
} else {
|
||
|
$td.attr("tabindex", "-1").focus();
|
||
|
setActiveDescendant(this, $td);
|
||
|
}
|
||
|
} else {
|
||
|
res = this._triggerNodeEvent("activateCell", prevNode, orgEvent, {
|
||
|
activeTd: null,
|
||
|
colIdx: null,
|
||
|
mode: "row",
|
||
|
});
|
||
|
if (res === false) {
|
||
|
return false;
|
||
|
}
|
||
|
// $td == null: switch back to row-mode
|
||
|
this.$container.removeClass(
|
||
|
clsFancytreeCellMode + " " + clsFancytreeCellNavMode
|
||
|
);
|
||
|
// console.log("activateCell: set row-mode for " + this.activeNode, $prevTd);
|
||
|
if ($prevTd) {
|
||
|
// cell-mode => row-mode
|
||
|
$prevTd
|
||
|
.removeAttr("tabindex")
|
||
|
.removeClass(clsFancytreeActiveCell);
|
||
|
// In row-mode, only embedded inputs of the active row are tabbable
|
||
|
$prevTr
|
||
|
.find("td")
|
||
|
.blur() // we need to blur first, because otherwise the focus frame is not reliably removed(?)
|
||
|
.removeAttr("tabindex");
|
||
|
$prevTr.find(">td :input,a").attr("tabindex", "-1");
|
||
|
this.$activeTd = null;
|
||
|
// The cell lost focus, but the tree still needs to capture keys:
|
||
|
this.activeNode.setFocus();
|
||
|
setActiveDescendant(this, $tr);
|
||
|
} else {
|
||
|
// row-mode => row-mode (nothing to do)
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Extension code
|
||
|
*/
|
||
|
$.ui.fancytree.registerExtension({
|
||
|
name: "ariagrid",
|
||
|
version: "2.38.2",
|
||
|
// Default options for this extension.
|
||
|
options: {
|
||
|
// Internal behavior flags
|
||
|
activateCellOnDoubelclick: true,
|
||
|
cellFocus: "allow",
|
||
|
// TODO: use a global tree option `name` or `title` instead?:
|
||
|
label: "Tree Grid", // Added as `aria-label` attribute
|
||
|
},
|
||
|
|
||
|
treeInit: function (ctx) {
|
||
|
var tree = ctx.tree,
|
||
|
treeOpts = ctx.options,
|
||
|
opts = treeOpts.ariagrid;
|
||
|
|
||
|
// ariagrid requires the table extension to be loaded before itself
|
||
|
if (tree.ext.grid) {
|
||
|
this._requireExtension("grid", true, true);
|
||
|
} else {
|
||
|
this._requireExtension("table", true, true);
|
||
|
}
|
||
|
if (!treeOpts.aria) {
|
||
|
$.error("ext-ariagrid requires `aria: true`");
|
||
|
}
|
||
|
if ($.inArray(opts.cellFocus, VALID_MODES) < 0) {
|
||
|
$.error("Invalid `cellFocus` option");
|
||
|
}
|
||
|
this._superApply(arguments);
|
||
|
|
||
|
// The combination of $activeTd and forceNavMode determines the current
|
||
|
// navigation mode:
|
||
|
this.$activeTd = null; // active cell (null in row-mode)
|
||
|
this.forceNavMode = true;
|
||
|
|
||
|
this.$container
|
||
|
.addClass("fancytree-ext-ariagrid")
|
||
|
.toggleClass(clsFancytreeCellNavMode, !!this.forceNavMode)
|
||
|
.attr("aria-label", "" + opts.label);
|
||
|
this.$container
|
||
|
.find("thead > tr > th")
|
||
|
.attr("role", "columnheader");
|
||
|
|
||
|
// Store table options for easier evaluation of default actions
|
||
|
// depending of active cell column
|
||
|
this.nodeColumnIdx = treeOpts.table.nodeColumnIdx;
|
||
|
this.checkboxColumnIdx = treeOpts.table.checkboxColumnIdx;
|
||
|
if (this.checkboxColumnIdx == null) {
|
||
|
this.checkboxColumnIdx = this.nodeColumnIdx;
|
||
|
}
|
||
|
|
||
|
this.$container
|
||
|
.on("focusin", function (event) {
|
||
|
// Activate node if embedded input gets focus (due to a click)
|
||
|
var node = FT.getNode(event.target),
|
||
|
$td = $(event.target).closest("td");
|
||
|
|
||
|
// tree.debug( "focusin: " + ( node ? node.title : "null" ) +
|
||
|
// ", target: " + ( $td ? $td.text() : null ) +
|
||
|
// ", node was active: " + ( node && node.isActive() ) +
|
||
|
// ", last cell: " + ( tree.$activeTd ? tree.$activeTd.text() : null ) );
|
||
|
// tree.debug( "focusin: target", event.target );
|
||
|
|
||
|
// TODO: add ":input" as delegate filter instead of testing here
|
||
|
if (
|
||
|
node &&
|
||
|
!$td.is(tree.$activeTd) &&
|
||
|
$(event.target).is(":input")
|
||
|
) {
|
||
|
node.debug("Activate cell on INPUT focus event");
|
||
|
tree.activateCell($td);
|
||
|
}
|
||
|
})
|
||
|
.on("fancytreeinit", function (event, data) {
|
||
|
if (
|
||
|
opts.cellFocus === "start" ||
|
||
|
opts.cellFocus === "force"
|
||
|
) {
|
||
|
tree.debug("Enforce cell-mode on init");
|
||
|
tree.debug(
|
||
|
"init",
|
||
|
tree.getActiveNode() || tree.getFirstChild()
|
||
|
);
|
||
|
(
|
||
|
tree.getActiveNode() || tree.getFirstChild()
|
||
|
).setActive(true, { cell: tree.nodeColumnIdx });
|
||
|
tree.debug(
|
||
|
"init2",
|
||
|
tree.getActiveNode() || tree.getFirstChild()
|
||
|
);
|
||
|
}
|
||
|
})
|
||
|
.on("fancytreefocustree", function (event, data) {
|
||
|
// Enforce cell-mode when container gets focus
|
||
|
if (opts.cellFocus === "force" && !tree.$activeTd) {
|
||
|
var node = tree.getActiveNode() || tree.getFirstChild();
|
||
|
tree.debug("Enforce cell-mode on focusTree event");
|
||
|
node.setActive(true, { cell: 0 });
|
||
|
}
|
||
|
})
|
||
|
// .on("fancytreeupdateviewport", function(event, data) {
|
||
|
// tree.debug(event.type, data);
|
||
|
// })
|
||
|
.on("fancytreebeforeupdateviewport", function (event, data) {
|
||
|
// When scrolling, the TR may be re-used by another node, so the
|
||
|
// active cell marker an
|
||
|
// tree.debug(event.type, data);
|
||
|
if (tree.viewport && tree.$activeTd) {
|
||
|
tree.info("Cancel cell-mode due to scroll event.");
|
||
|
tree.activateCell(null);
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
nodeClick: function (ctx) {
|
||
|
var targetType = ctx.targetType,
|
||
|
tree = ctx.tree,
|
||
|
node = ctx.node,
|
||
|
event = ctx.originalEvent,
|
||
|
$target = $(event.target),
|
||
|
$td = $target.closest("td");
|
||
|
|
||
|
tree.debug(
|
||
|
"nodeClick: node: " +
|
||
|
(node ? node.title : "null") +
|
||
|
", targetType: " +
|
||
|
targetType +
|
||
|
", target: " +
|
||
|
($td.length ? $td.text() : null) +
|
||
|
", node was active: " +
|
||
|
(node && node.isActive()) +
|
||
|
", last cell: " +
|
||
|
(tree.$activeTd ? tree.$activeTd.text() : null)
|
||
|
);
|
||
|
|
||
|
if (tree.$activeTd) {
|
||
|
// If already in cell-mode, activate new cell
|
||
|
tree.activateCell($td);
|
||
|
if ($target.is(":input")) {
|
||
|
return;
|
||
|
} else if (
|
||
|
$target.is(".fancytree-checkbox") ||
|
||
|
$target.is(".fancytree-expander")
|
||
|
) {
|
||
|
return this._superApply(arguments);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
nodeDblclick: function (ctx) {
|
||
|
var tree = ctx.tree,
|
||
|
treeOpts = ctx.options,
|
||
|
opts = treeOpts.ariagrid,
|
||
|
event = ctx.originalEvent,
|
||
|
$td = $(event.target).closest("td");
|
||
|
|
||
|
// console.log("nodeDblclick", tree.$activeTd, ctx.options.ariagrid.cellFocus)
|
||
|
if (
|
||
|
opts.activateCellOnDoubelclick &&
|
||
|
!tree.$activeTd &&
|
||
|
opts.cellFocus === "allow"
|
||
|
) {
|
||
|
// If in row-mode, activate new cell
|
||
|
tree.activateCell($td);
|
||
|
return false;
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
nodeRenderStatus: function (ctx) {
|
||
|
// Set classes for current status
|
||
|
var res,
|
||
|
node = ctx.node,
|
||
|
$tr = $(node.tr);
|
||
|
|
||
|
res = this._super(ctx);
|
||
|
|
||
|
if (node.parent) {
|
||
|
$tr.attr("aria-level", node.getLevel())
|
||
|
.attr("aria-setsize", node.parent.children.length)
|
||
|
.attr("aria-posinset", node.getIndex() + 1);
|
||
|
|
||
|
// 2018-06-24: not required according to
|
||
|
// https://github.com/w3c/aria-practices/issues/132#issuecomment-397698250
|
||
|
// if ( $tr.is( ":hidden" ) ) {
|
||
|
// $tr.attr( "aria-hidden", true );
|
||
|
// } else {
|
||
|
// $tr.removeAttr( "aria-hidden" );
|
||
|
// }
|
||
|
|
||
|
// this.debug("nodeRenderStatus: " + this.$activeTd + ", " + $tr.attr("aria-expanded"));
|
||
|
// In cell-mode, move aria-expanded attribute from TR to first child TD
|
||
|
if (this.$activeTd && $tr.attr("aria-expanded") != null) {
|
||
|
$tr.remove("aria-expanded");
|
||
|
$tr.find("td")
|
||
|
.eq(this.nodeColumnIdx)
|
||
|
.attr("aria-expanded", node.isExpanded());
|
||
|
} else {
|
||
|
$tr.find("td")
|
||
|
.eq(this.nodeColumnIdx)
|
||
|
.removeAttr("aria-expanded");
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
},
|
||
|
nodeSetActive: function (ctx, flag, callOpts) {
|
||
|
var $td,
|
||
|
node = ctx.node,
|
||
|
tree = ctx.tree,
|
||
|
$tr = $(node.tr);
|
||
|
|
||
|
flag = flag !== false;
|
||
|
node.debug("nodeSetActive(" + flag + ")", callOpts);
|
||
|
// Support custom `cell` option
|
||
|
if (flag && callOpts && callOpts.cell != null) {
|
||
|
// `cell` may be a col-index, <td>, or `$(td)`
|
||
|
if (typeof callOpts.cell === "number") {
|
||
|
$td = findTdAtColIdx($tr, callOpts.cell);
|
||
|
} else {
|
||
|
$td = $(callOpts.cell);
|
||
|
}
|
||
|
tree.activateCell($td);
|
||
|
return;
|
||
|
}
|
||
|
// tree.debug( "nodeSetActive: activeNode " + this.activeNode );
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
nodeKeydown: function (ctx) {
|
||
|
var handleKeys,
|
||
|
inputType,
|
||
|
res,
|
||
|
$td,
|
||
|
$embeddedCheckbox = null,
|
||
|
tree = ctx.tree,
|
||
|
node = ctx.node,
|
||
|
treeOpts = ctx.options,
|
||
|
opts = treeOpts.ariagrid,
|
||
|
event = ctx.originalEvent,
|
||
|
eventString = FT.eventToString(event),
|
||
|
$target = $(event.target),
|
||
|
$activeTd = this.$activeTd,
|
||
|
$activeTr = $activeTd ? $activeTd.closest("tr") : null,
|
||
|
colIdx = $activeTd ? getColIdx($activeTr, $activeTd) : -1,
|
||
|
forceNav =
|
||
|
$activeTd &&
|
||
|
tree.forceNavMode &&
|
||
|
$.inArray(eventString, NAV_KEYS) >= 0;
|
||
|
|
||
|
if (opts.cellFocus === "off") {
|
||
|
return this._superApply(arguments);
|
||
|
}
|
||
|
|
||
|
if ($target.is(":input:enabled")) {
|
||
|
inputType = $target.prop("type");
|
||
|
} else if ($target.is("a")) {
|
||
|
inputType = "link";
|
||
|
}
|
||
|
if ($activeTd && $activeTd.find(":checkbox:enabled").length === 1) {
|
||
|
$embeddedCheckbox = $activeTd.find(":checkbox:enabled");
|
||
|
inputType = "checkbox";
|
||
|
}
|
||
|
tree.debug(
|
||
|
"nodeKeydown(" +
|
||
|
eventString +
|
||
|
"), activeTd: '" +
|
||
|
($activeTd && $activeTd.text()) +
|
||
|
"', inputType: " +
|
||
|
inputType
|
||
|
);
|
||
|
|
||
|
if (inputType && eventString !== "esc" && !forceNav) {
|
||
|
handleKeys = INPUT_KEYS[inputType];
|
||
|
if (handleKeys && $.inArray(eventString, handleKeys) >= 0) {
|
||
|
return; // Let input control handle the key
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (eventString) {
|
||
|
case "right":
|
||
|
if ($activeTd) {
|
||
|
// Cell mode: move to neighbour (stop on right border)
|
||
|
$td = findNeighbourTd(tree, $activeTd, eventString);
|
||
|
if ($td) {
|
||
|
tree.activateCell($td);
|
||
|
}
|
||
|
} else if (
|
||
|
node &&
|
||
|
!node.isExpanded() &&
|
||
|
node.hasChildren() !== false
|
||
|
) {
|
||
|
// Row mode and current node can be expanded:
|
||
|
// default handling will expand.
|
||
|
break;
|
||
|
} else {
|
||
|
// Row mode: switch to cell-mode
|
||
|
$td = $(node.tr).find(">td").first();
|
||
|
tree.activateCell($td);
|
||
|
}
|
||
|
return false; // no default handling
|
||
|
|
||
|
case "left":
|
||
|
case "home":
|
||
|
case "end":
|
||
|
case "ctrl+home":
|
||
|
case "ctrl+end":
|
||
|
case "up":
|
||
|
case "down":
|
||
|
if ($activeTd) {
|
||
|
// Cell mode: move to neighbour
|
||
|
$td = findNeighbourTd(tree, $activeTd, eventString);
|
||
|
// Note: $td may be null if we move outside bounds. In this case
|
||
|
// we switch back to row-mode (i.e. call activateCell(null) ).
|
||
|
if (!$td && "left right".indexOf(eventString) < 0) {
|
||
|
// Only switch to row-mode if left/right hits the bounds
|
||
|
return false;
|
||
|
}
|
||
|
if ($td || opts.cellFocus !== "force") {
|
||
|
tree.activateCell($td);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case "esc":
|
||
|
if ($activeTd && !tree.forceNavMode) {
|
||
|
// Switch from cell-edit-mode to cell-nav-mode
|
||
|
// $target.closest( "td" ).focus();
|
||
|
tree.forceNavMode = true;
|
||
|
tree.debug("Enter cell-nav-mode");
|
||
|
tree.$container.toggleClass(
|
||
|
clsFancytreeCellNavMode,
|
||
|
!!tree.forceNavMode
|
||
|
);
|
||
|
return false;
|
||
|
} else if ($activeTd && opts.cellFocus !== "force") {
|
||
|
// Switch back from cell-mode to row-mode
|
||
|
tree.activateCell(null);
|
||
|
return false;
|
||
|
}
|
||
|
// tree.$container.toggleClass( clsFancytreeCellNavMode, !!tree.forceNavMode );
|
||
|
break;
|
||
|
|
||
|
case "return":
|
||
|
// Let user override the default action.
|
||
|
// This event is triggered in row-mode and cell-mode
|
||
|
res = tree._triggerNodeEvent(
|
||
|
"defaultGridAction",
|
||
|
node,
|
||
|
event,
|
||
|
{
|
||
|
activeTd: tree.$activeTd ? tree.$activeTd[0] : null,
|
||
|
colIdx: colIdx,
|
||
|
mode: getGridNavMode(tree),
|
||
|
}
|
||
|
);
|
||
|
if (res === false) {
|
||
|
return false;
|
||
|
}
|
||
|
// Implement default actions (for cell-mode only).
|
||
|
if ($activeTd) {
|
||
|
// Apply 'default action' for embedded cell control
|
||
|
if (colIdx === this.nodeColumnIdx) {
|
||
|
node.toggleExpanded();
|
||
|
} else if (colIdx === this.checkboxColumnIdx) {
|
||
|
// TODO: only in checkbox mode!
|
||
|
node.toggleSelected();
|
||
|
} else if ($embeddedCheckbox) {
|
||
|
// Embedded checkboxes are always toggled (ignoring `autoFocusInput`)
|
||
|
$embeddedCheckbox.prop(
|
||
|
"checked",
|
||
|
!$embeddedCheckbox.prop("checked")
|
||
|
);
|
||
|
} else if (tree.forceNavMode && $target.is(":input")) {
|
||
|
tree.forceNavMode = false;
|
||
|
tree.$container.removeClass(
|
||
|
clsFancytreeCellNavMode
|
||
|
);
|
||
|
tree.debug("enable cell-edit-mode");
|
||
|
} else if ($activeTd.find("a").length === 1) {
|
||
|
activateEmbeddedLink($activeTd);
|
||
|
}
|
||
|
} else {
|
||
|
// ENTER in row-mode: Switch from row-mode to cell-mode
|
||
|
// TODO: it was also suggested to expand/collapse instead
|
||
|
// https://github.com/w3c/aria-practices/issues/132#issuecomment-407634891
|
||
|
$td = $(node.tr).find(">td").nth(this.nodeColumnIdx);
|
||
|
tree.activateCell($td);
|
||
|
}
|
||
|
return false; // no default handling
|
||
|
|
||
|
case "space":
|
||
|
if ($activeTd) {
|
||
|
if (colIdx === this.checkboxColumnIdx) {
|
||
|
node.toggleSelected();
|
||
|
} else if ($embeddedCheckbox) {
|
||
|
$embeddedCheckbox.prop(
|
||
|
"checked",
|
||
|
!$embeddedCheckbox.prop("checked")
|
||
|
);
|
||
|
}
|
||
|
return false; // no default handling
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Allow to focus input by typing alphanum keys
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
treeSetOption: function (ctx, key, value) {
|
||
|
var tree = ctx.tree,
|
||
|
opts = tree.options.ariagrid;
|
||
|
|
||
|
if (key === "ariagrid") {
|
||
|
// User called `$().fancytree("option", "ariagrid.SUBKEY", VALUE)`
|
||
|
if (value.cellFocus !== opts.cellFocus) {
|
||
|
if ($.inArray(value.cellFocus, VALID_MODES) < 0) {
|
||
|
$.error("Invalid `cellFocus` option");
|
||
|
}
|
||
|
// TODO: fix current focus and mode
|
||
|
}
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
});
|
||
|
// Value returned by `require('jquery.fancytree..')`
|
||
|
return $.ui.fancytree;
|
||
|
}); // End of closure
|