You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

219 lines
5.8 KiB

  1. /*!
  2. * jquery.fancytree.gridnav.js
  3. *
  4. * Support keyboard navigation for trees with embedded input controls.
  5. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  6. *
  7. * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
  8. *
  9. * Released under the MIT license
  10. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  11. *
  12. * @version 2.38.3
  13. * @date 2023-02-01T20:52:50Z
  14. */
  15. (function (factory) {
  16. if (typeof define === "function" && define.amd) {
  17. // AMD. Register as an anonymous module.
  18. define([
  19. "jquery",
  20. "./jquery.fancytree",
  21. "./jquery.fancytree.table",
  22. ], factory);
  23. } else if (typeof module === "object" && module.exports) {
  24. // Node/CommonJS
  25. require("./jquery.fancytree.table"); // core + table
  26. module.exports = factory(require("jquery"));
  27. } else {
  28. // Browser globals
  29. factory(jQuery);
  30. }
  31. })(function ($) {
  32. "use strict";
  33. /*******************************************************************************
  34. * Private functions and variables
  35. */
  36. // Allow these navigation keys even when input controls are focused
  37. var KC = $.ui.keyCode,
  38. // which keys are *not* handled by embedded control, but passed to tree
  39. // navigation handler:
  40. NAV_KEYS = {
  41. text: [KC.UP, KC.DOWN],
  42. checkbox: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
  43. link: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
  44. radiobutton: [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
  45. "select-one": [KC.LEFT, KC.RIGHT],
  46. "select-multiple": [KC.LEFT, KC.RIGHT],
  47. };
  48. /* Calculate TD column index (considering colspans).*/
  49. function getColIdx($tr, $td) {
  50. var colspan,
  51. td = $td.get(0),
  52. idx = 0;
  53. $tr.children().each(function () {
  54. if (this === td) {
  55. return false;
  56. }
  57. colspan = $(this).prop("colspan");
  58. idx += colspan ? colspan : 1;
  59. });
  60. return idx;
  61. }
  62. /* Find TD at given column index (considering colspans).*/
  63. function findTdAtColIdx($tr, colIdx) {
  64. var colspan,
  65. res = null,
  66. idx = 0;
  67. $tr.children().each(function () {
  68. if (idx >= colIdx) {
  69. res = $(this);
  70. return false;
  71. }
  72. colspan = $(this).prop("colspan");
  73. idx += colspan ? colspan : 1;
  74. });
  75. return res;
  76. }
  77. /* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
  78. function findNeighbourTd($target, keyCode) {
  79. var $tr,
  80. colIdx,
  81. $td = $target.closest("td"),
  82. $tdNext = null;
  83. switch (keyCode) {
  84. case KC.LEFT:
  85. $tdNext = $td.prev();
  86. break;
  87. case KC.RIGHT:
  88. $tdNext = $td.next();
  89. break;
  90. case KC.UP:
  91. case KC.DOWN:
  92. $tr = $td.parent();
  93. colIdx = getColIdx($tr, $td);
  94. while (true) {
  95. $tr = keyCode === KC.UP ? $tr.prev() : $tr.next();
  96. if (!$tr.length) {
  97. break;
  98. }
  99. // Skip hidden rows
  100. if ($tr.is(":hidden")) {
  101. continue;
  102. }
  103. // Find adjacent cell in the same column
  104. $tdNext = findTdAtColIdx($tr, colIdx);
  105. // Skip cells that don't conatain a focusable element
  106. if ($tdNext && $tdNext.find(":input,a").length) {
  107. break;
  108. }
  109. }
  110. break;
  111. }
  112. return $tdNext;
  113. }
  114. /*******************************************************************************
  115. * Extension code
  116. */
  117. $.ui.fancytree.registerExtension({
  118. name: "gridnav",
  119. version: "2.38.3",
  120. // Default options for this extension.
  121. options: {
  122. autofocusInput: false, // Focus first embedded input if node gets activated
  123. handleCursorKeys: true, // Allow UP/DOWN in inputs to move to prev/next node
  124. },
  125. treeInit: function (ctx) {
  126. // gridnav requires the table extension to be loaded before itself
  127. this._requireExtension("table", true, true);
  128. this._superApply(arguments);
  129. this.$container.addClass("fancytree-ext-gridnav");
  130. // Activate node if embedded input gets focus (due to a click)
  131. this.$container.on("focusin", function (event) {
  132. var ctx2,
  133. node = $.ui.fancytree.getNode(event.target);
  134. if (node && !node.isActive()) {
  135. // Call node.setActive(), but also pass the event
  136. ctx2 = ctx.tree._makeHookContext(node, event);
  137. ctx.tree._callHook("nodeSetActive", ctx2, true);
  138. }
  139. });
  140. },
  141. nodeSetActive: function (ctx, flag, callOpts) {
  142. var $outer,
  143. opts = ctx.options.gridnav,
  144. node = ctx.node,
  145. event = ctx.originalEvent || {},
  146. triggeredByInput = $(event.target).is(":input");
  147. flag = flag !== false;
  148. this._superApply(arguments);
  149. if (flag) {
  150. if (ctx.options.titlesTabbable) {
  151. if (!triggeredByInput) {
  152. $(node.span).find("span.fancytree-title").focus();
  153. node.setFocus();
  154. }
  155. // If one node is tabbable, the container no longer needs to be
  156. ctx.tree.$container.attr("tabindex", "-1");
  157. // ctx.tree.$container.removeAttr("tabindex");
  158. } else if (opts.autofocusInput && !triggeredByInput) {
  159. // Set focus to input sub input (if node was clicked, but not
  160. // when TAB was pressed )
  161. $outer = $(node.tr || node.span);
  162. $outer.find(":input:enabled").first().focus();
  163. }
  164. }
  165. },
  166. nodeKeydown: function (ctx) {
  167. var inputType,
  168. handleKeys,
  169. $td,
  170. opts = ctx.options.gridnav,
  171. event = ctx.originalEvent,
  172. $target = $(event.target);
  173. if ($target.is(":input:enabled")) {
  174. inputType = $target.prop("type");
  175. } else if ($target.is("a")) {
  176. inputType = "link";
  177. }
  178. // ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
  179. if (inputType && opts.handleCursorKeys) {
  180. handleKeys = NAV_KEYS[inputType];
  181. if (handleKeys && $.inArray(event.which, handleKeys) >= 0) {
  182. $td = findNeighbourTd($target, event.which);
  183. if ($td && $td.length) {
  184. // ctx.node.debug("ignore keydown in input", event.which, handleKeys);
  185. $td.find(":input:enabled,a").focus();
  186. // Prevent Fancytree default navigation
  187. return false;
  188. }
  189. }
  190. return true;
  191. }
  192. // ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
  193. return this._superApply(arguments);
  194. },
  195. });
  196. // Value returned by `require('jquery.fancytree..')`
  197. return $.ui.fancytree;
  198. }); // End of closure