Source: jquery.fancytree.ariagrid.js

  1. /*!
  2. * jquery.fancytree.ariagrid.js
  3. *
  4. * Support ARIA compliant markup and keyboard navigation for tree grids with
  5. * embedded input controls.
  6. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  7. *
  8. * @requires ext-table
  9. *
  10. * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
  11. *
  12. * Released under the MIT license
  13. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  14. *
  15. * @version @VERSION
  16. * @date @DATE
  17. */
  18. (function (factory) {
  19. if (typeof define === "function" && define.amd) {
  20. // AMD. Register as an anonymous module.
  21. define([
  22. "jquery",
  23. "./jquery.fancytree",
  24. "./jquery.fancytree.table",
  25. ], factory);
  26. } else if (typeof module === "object" && module.exports) {
  27. // Node/CommonJS
  28. require("./jquery.fancytree.table"); // core + table
  29. module.exports = factory(require("jquery"));
  30. } else {
  31. // Browser globals
  32. factory(jQuery);
  33. }
  34. })(function ($) {
  35. "use strict";
  36. /*******************************************************************************
  37. * Private functions and variables
  38. */
  39. // Allow these navigation keys even when input controls are focused
  40. var FT = $.ui.fancytree,
  41. clsFancytreeActiveCell = "fancytree-active-cell",
  42. clsFancytreeCellMode = "fancytree-cell-mode",
  43. clsFancytreeCellNavMode = "fancytree-cell-nav-mode",
  44. VALID_MODES = ["allow", "force", "start", "off"],
  45. // Define which keys are handled by embedded <input> control, and should
  46. // *not* be passed to tree navigation handler in cell-edit mode:
  47. INPUT_KEYS = {
  48. text: ["left", "right", "home", "end", "backspace"],
  49. number: ["up", "down", "left", "right", "home", "end", "backspace"],
  50. checkbox: [],
  51. link: [],
  52. radiobutton: ["up", "down"],
  53. "select-one": ["up", "down"],
  54. "select-multiple": ["up", "down"],
  55. },
  56. NAV_KEYS = ["up", "down", "left", "right", "home", "end"];
  57. /* Set aria-activedescendant on container to active cell's ID (generate one if required).*/
  58. function setActiveDescendant(tree, $target) {
  59. var id = $target ? $target.uniqueId().attr("id") : "";
  60. tree.$container.attr("aria-activedescendant", id);
  61. }
  62. /* Calculate TD column index (considering colspans).*/
  63. function getColIdx($tr, $td) {
  64. var colspan,
  65. td = $td.get(0),
  66. idx = 0;
  67. $tr.children().each(function () {
  68. if (this === td) {
  69. return false;
  70. }
  71. colspan = $(this).prop("colspan");
  72. idx += colspan ? colspan : 1;
  73. });
  74. return idx;
  75. }
  76. /* Find TD at given column index (considering colspans).*/
  77. function findTdAtColIdx($tr, colIdx) {
  78. var colspan,
  79. res = null,
  80. idx = 0;
  81. $tr.children().each(function () {
  82. if (idx >= colIdx) {
  83. res = $(this);
  84. return false;
  85. }
  86. colspan = $(this).prop("colspan");
  87. idx += colspan ? colspan : 1;
  88. });
  89. return res;
  90. }
  91. /* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
  92. function findNeighbourTd(tree, $target, keyCode) {
  93. var nextNode,
  94. node,
  95. navMap = { "ctrl+home": "first", "ctrl+end": "last" },
  96. $td = $target.closest("td"),
  97. $tr = $td.parent(),
  98. treeOpts = tree.options,
  99. colIdx = getColIdx($tr, $td),
  100. $tdNext = null;
  101. keyCode = navMap[keyCode] || keyCode;
  102. switch (keyCode) {
  103. case "left":
  104. $tdNext = treeOpts.rtl ? $td.next() : $td.prev();
  105. break;
  106. case "right":
  107. $tdNext = treeOpts.rtl ? $td.prev() : $td.next();
  108. break;
  109. case "up":
  110. case "down":
  111. case "ctrl+home":
  112. case "ctrl+end":
  113. node = $tr[0].ftnode;
  114. nextNode = tree.findRelatedNode(node, keyCode);
  115. if (nextNode) {
  116. nextNode.makeVisible();
  117. nextNode.setActive();
  118. $tdNext = findTdAtColIdx($(nextNode.tr), colIdx);
  119. }
  120. break;
  121. case "home":
  122. $tdNext = treeOpts.rtl
  123. ? $tr.children("td").last()
  124. : $tr.children("td").first();
  125. break;
  126. case "end":
  127. $tdNext = treeOpts.rtl
  128. ? $tr.children("td").first()
  129. : $tr.children("td").last();
  130. break;
  131. }
  132. return $tdNext && $tdNext.length ? $tdNext : null;
  133. }
  134. /* Return a descriptive string of the current mode. */
  135. function getGridNavMode(tree) {
  136. if (tree.$activeTd) {
  137. return tree.forceNavMode ? "cell-nav" : "cell-edit";
  138. }
  139. return "row";
  140. }
  141. /* .*/
  142. function activateEmbeddedLink($td) {
  143. // $td.find( "a" )[ 0 ].trigger("click"); // does not work (always)?
  144. // $td.find( "a" ).trigger("click");
  145. var event = document.createEvent("MouseEvent"),
  146. a = $td.find("a")[0]; // document.getElementById('nameOfID');
  147. event = new CustomEvent("click");
  148. a.dispatchEvent(event);
  149. }
  150. /**
  151. * [ext-ariagrid] Set active cell and activate cell-nav or cell-edit mode if needed.
  152. * Pass $td=null to enter row-mode.
  153. *
  154. * See also FancytreeNode#setActive(flag, {cell: idx})
  155. *
  156. * @param {jQuery | Element | integer} [$td]
  157. * @param {Event|null} [orgEvent=null]
  158. * @alias Fancytree#activateCell
  159. * @requires jquery.fancytree.ariagrid.js
  160. * @since 2.23
  161. */
  162. $.ui.fancytree._FancytreeClass.prototype.activateCell = function (
  163. $td,
  164. orgEvent
  165. ) {
  166. var colIdx,
  167. $input,
  168. $tr,
  169. res,
  170. tree = this,
  171. $prevTd = this.$activeTd || null,
  172. newNode = $td ? FT.getNode($td) : null,
  173. prevNode = $prevTd ? FT.getNode($prevTd) : null,
  174. anyNode = newNode || prevNode,
  175. $prevTr = $prevTd ? $prevTd.closest("tr") : null;
  176. anyNode.debug(
  177. "activateCell(" +
  178. ($prevTd ? $prevTd.text() : "null") +
  179. ") -> " +
  180. ($td ? $td.text() : "OFF")
  181. );
  182. // Make available as event
  183. if ($td) {
  184. FT.assert($td.length, "Invalid active cell");
  185. colIdx = getColIdx($(newNode.tr), $td);
  186. res = this._triggerNodeEvent("activateCell", newNode, orgEvent, {
  187. activeTd: tree.$activeTd,
  188. colIdx: colIdx,
  189. mode: null, // editMode ? "cell-edit" : "cell-nav"
  190. });
  191. if (res === false) {
  192. return false;
  193. }
  194. this.$container.addClass(clsFancytreeCellMode);
  195. this.$container.toggleClass(
  196. clsFancytreeCellNavMode,
  197. !!this.forceNavMode
  198. );
  199. $tr = $td.closest("tr");
  200. if ($prevTd) {
  201. // cell-mode => cell-mode
  202. if ($prevTd.is($td)) {
  203. return;
  204. }
  205. $prevTd
  206. .removeAttr("tabindex")
  207. .removeClass(clsFancytreeActiveCell);
  208. if (!$prevTr.is($tr)) {
  209. // We are moving to a different row: only the inputs in the
  210. // active row should be tabbable
  211. $prevTr.find(">td :input,a").attr("tabindex", "-1");
  212. }
  213. }
  214. $tr.find(">td :input:enabled,a").attr("tabindex", "0");
  215. newNode.setActive();
  216. $td.addClass(clsFancytreeActiveCell);
  217. this.$activeTd = $td;
  218. $input = $td.find(":input:enabled,a");
  219. this.debug("Focus input", $input);
  220. if ($input.length) {
  221. $input.focus();
  222. setActiveDescendant(this, $input);
  223. } else {
  224. $td.attr("tabindex", "-1").focus();
  225. setActiveDescendant(this, $td);
  226. }
  227. } else {
  228. res = this._triggerNodeEvent("activateCell", prevNode, orgEvent, {
  229. activeTd: null,
  230. colIdx: null,
  231. mode: "row",
  232. });
  233. if (res === false) {
  234. return false;
  235. }
  236. // $td == null: switch back to row-mode
  237. this.$container.removeClass(
  238. clsFancytreeCellMode + " " + clsFancytreeCellNavMode
  239. );
  240. // console.log("activateCell: set row-mode for " + this.activeNode, $prevTd);
  241. if ($prevTd) {
  242. // cell-mode => row-mode
  243. $prevTd
  244. .removeAttr("tabindex")
  245. .removeClass(clsFancytreeActiveCell);
  246. // In row-mode, only embedded inputs of the active row are tabbable
  247. $prevTr
  248. .find("td")
  249. .blur() // we need to blur first, because otherwise the focus frame is not reliably removed(?)
  250. .removeAttr("tabindex");
  251. $prevTr.find(">td :input,a").attr("tabindex", "-1");
  252. this.$activeTd = null;
  253. // The cell lost focus, but the tree still needs to capture keys:
  254. this.activeNode.setFocus();
  255. setActiveDescendant(this, $tr);
  256. } else {
  257. // row-mode => row-mode (nothing to do)
  258. }
  259. }
  260. };
  261. /*******************************************************************************
  262. * Extension code
  263. */
  264. $.ui.fancytree.registerExtension({
  265. name: "ariagrid",
  266. version: "@VERSION",
  267. // Default options for this extension.
  268. options: {
  269. // Internal behavior flags
  270. activateCellOnDoubelclick: true,
  271. cellFocus: "allow",
  272. // TODO: use a global tree option `name` or `title` instead?:
  273. label: "Tree Grid", // Added as `aria-label` attribute
  274. },
  275. treeInit: function (ctx) {
  276. var tree = ctx.tree,
  277. treeOpts = ctx.options,
  278. opts = treeOpts.ariagrid;
  279. // ariagrid requires the table extension to be loaded before itself
  280. if (tree.ext.grid) {
  281. this._requireExtension("grid", true, true);
  282. } else {
  283. this._requireExtension("table", true, true);
  284. }
  285. if (!treeOpts.aria) {
  286. $.error("ext-ariagrid requires `aria: true`");
  287. }
  288. if ($.inArray(opts.cellFocus, VALID_MODES) < 0) {
  289. $.error("Invalid `cellFocus` option");
  290. }
  291. this._superApply(arguments);
  292. // The combination of $activeTd and forceNavMode determines the current
  293. // navigation mode:
  294. this.$activeTd = null; // active cell (null in row-mode)
  295. this.forceNavMode = true;
  296. this.$container
  297. .addClass("fancytree-ext-ariagrid")
  298. .toggleClass(clsFancytreeCellNavMode, !!this.forceNavMode)
  299. .attr("aria-label", "" + opts.label);
  300. this.$container
  301. .find("thead > tr > th")
  302. .attr("role", "columnheader");
  303. // Store table options for easier evaluation of default actions
  304. // depending of active cell column
  305. this.nodeColumnIdx = treeOpts.table.nodeColumnIdx;
  306. this.checkboxColumnIdx = treeOpts.table.checkboxColumnIdx;
  307. if (this.checkboxColumnIdx == null) {
  308. this.checkboxColumnIdx = this.nodeColumnIdx;
  309. }
  310. this.$container
  311. .on("focusin", function (event) {
  312. // Activate node if embedded input gets focus (due to a click)
  313. var node = FT.getNode(event.target),
  314. $td = $(event.target).closest("td");
  315. // tree.debug( "focusin: " + ( node ? node.title : "null" ) +
  316. // ", target: " + ( $td ? $td.text() : null ) +
  317. // ", node was active: " + ( node && node.isActive() ) +
  318. // ", last cell: " + ( tree.$activeTd ? tree.$activeTd.text() : null ) );
  319. // tree.debug( "focusin: target", event.target );
  320. // TODO: add ":input" as delegate filter instead of testing here
  321. if (
  322. node &&
  323. !$td.is(tree.$activeTd) &&
  324. $(event.target).is(":input")
  325. ) {
  326. node.debug("Activate cell on INPUT focus event");
  327. tree.activateCell($td);
  328. }
  329. })
  330. .on("fancytreeinit", function (event, data) {
  331. if (
  332. opts.cellFocus === "start" ||
  333. opts.cellFocus === "force"
  334. ) {
  335. tree.debug("Enforce cell-mode on init");
  336. tree.debug(
  337. "init",
  338. tree.getActiveNode() || tree.getFirstChild()
  339. );
  340. (
  341. tree.getActiveNode() || tree.getFirstChild()
  342. ).setActive(true, { cell: tree.nodeColumnIdx });
  343. tree.debug(
  344. "init2",
  345. tree.getActiveNode() || tree.getFirstChild()
  346. );
  347. }
  348. })
  349. .on("fancytreefocustree", function (event, data) {
  350. // Enforce cell-mode when container gets focus
  351. if (opts.cellFocus === "force" && !tree.$activeTd) {
  352. var node = tree.getActiveNode() || tree.getFirstChild();
  353. tree.debug("Enforce cell-mode on focusTree event");
  354. node.setActive(true, { cell: 0 });
  355. }
  356. })
  357. // .on("fancytreeupdateviewport", function(event, data) {
  358. // tree.debug(event.type, data);
  359. // })
  360. .on("fancytreebeforeupdateviewport", function (event, data) {
  361. // When scrolling, the TR may be re-used by another node, so the
  362. // active cell marker an
  363. // tree.debug(event.type, data);
  364. if (tree.viewport && tree.$activeTd) {
  365. tree.info("Cancel cell-mode due to scroll event.");
  366. tree.activateCell(null);
  367. }
  368. });
  369. },
  370. nodeClick: function (ctx) {
  371. var targetType = ctx.targetType,
  372. tree = ctx.tree,
  373. node = ctx.node,
  374. event = ctx.originalEvent,
  375. $target = $(event.target),
  376. $td = $target.closest("td");
  377. tree.debug(
  378. "nodeClick: node: " +
  379. (node ? node.title : "null") +
  380. ", targetType: " +
  381. targetType +
  382. ", target: " +
  383. ($td.length ? $td.text() : null) +
  384. ", node was active: " +
  385. (node && node.isActive()) +
  386. ", last cell: " +
  387. (tree.$activeTd ? tree.$activeTd.text() : null)
  388. );
  389. if (tree.$activeTd) {
  390. // If already in cell-mode, activate new cell
  391. tree.activateCell($td);
  392. if ($target.is(":input")) {
  393. return;
  394. } else if (
  395. $target.is(".fancytree-checkbox") ||
  396. $target.is(".fancytree-expander")
  397. ) {
  398. return this._superApply(arguments);
  399. }
  400. return false;
  401. }
  402. return this._superApply(arguments);
  403. },
  404. nodeDblclick: function (ctx) {
  405. var tree = ctx.tree,
  406. treeOpts = ctx.options,
  407. opts = treeOpts.ariagrid,
  408. event = ctx.originalEvent,
  409. $td = $(event.target).closest("td");
  410. // console.log("nodeDblclick", tree.$activeTd, ctx.options.ariagrid.cellFocus)
  411. if (
  412. opts.activateCellOnDoubelclick &&
  413. !tree.$activeTd &&
  414. opts.cellFocus === "allow"
  415. ) {
  416. // If in row-mode, activate new cell
  417. tree.activateCell($td);
  418. return false;
  419. }
  420. return this._superApply(arguments);
  421. },
  422. nodeRenderStatus: function (ctx) {
  423. // Set classes for current status
  424. var res,
  425. node = ctx.node,
  426. $tr = $(node.tr);
  427. res = this._super(ctx);
  428. if (node.parent) {
  429. $tr.attr("aria-level", node.getLevel())
  430. .attr("aria-setsize", node.parent.children.length)
  431. .attr("aria-posinset", node.getIndex() + 1);
  432. // 2018-06-24: not required according to
  433. // https://github.com/w3c/aria-practices/issues/132#issuecomment-397698250
  434. // if ( $tr.is( ":hidden" ) ) {
  435. // $tr.attr( "aria-hidden", true );
  436. // } else {
  437. // $tr.removeAttr( "aria-hidden" );
  438. // }
  439. // this.debug("nodeRenderStatus: " + this.$activeTd + ", " + $tr.attr("aria-expanded"));
  440. // In cell-mode, move aria-expanded attribute from TR to first child TD
  441. if (this.$activeTd && $tr.attr("aria-expanded") != null) {
  442. $tr.remove("aria-expanded");
  443. $tr.find("td")
  444. .eq(this.nodeColumnIdx)
  445. .attr("aria-expanded", node.isExpanded());
  446. } else {
  447. $tr.find("td")
  448. .eq(this.nodeColumnIdx)
  449. .removeAttr("aria-expanded");
  450. }
  451. }
  452. return res;
  453. },
  454. nodeSetActive: function (ctx, flag, callOpts) {
  455. var $td,
  456. node = ctx.node,
  457. tree = ctx.tree,
  458. $tr = $(node.tr);
  459. flag = flag !== false;
  460. node.debug("nodeSetActive(" + flag + ")", callOpts);
  461. // Support custom `cell` option
  462. if (flag && callOpts && callOpts.cell != null) {
  463. // `cell` may be a col-index, <td>, or `$(td)`
  464. if (typeof callOpts.cell === "number") {
  465. $td = findTdAtColIdx($tr, callOpts.cell);
  466. } else {
  467. $td = $(callOpts.cell);
  468. }
  469. tree.activateCell($td);
  470. return;
  471. }
  472. // tree.debug( "nodeSetActive: activeNode " + this.activeNode );
  473. return this._superApply(arguments);
  474. },
  475. nodeKeydown: function (ctx) {
  476. var handleKeys,
  477. inputType,
  478. res,
  479. $td,
  480. $embeddedCheckbox = null,
  481. tree = ctx.tree,
  482. node = ctx.node,
  483. treeOpts = ctx.options,
  484. opts = treeOpts.ariagrid,
  485. event = ctx.originalEvent,
  486. eventString = FT.eventToString(event),
  487. $target = $(event.target),
  488. $activeTd = this.$activeTd,
  489. $activeTr = $activeTd ? $activeTd.closest("tr") : null,
  490. colIdx = $activeTd ? getColIdx($activeTr, $activeTd) : -1,
  491. forceNav =
  492. $activeTd &&
  493. tree.forceNavMode &&
  494. $.inArray(eventString, NAV_KEYS) >= 0;
  495. if (opts.cellFocus === "off") {
  496. return this._superApply(arguments);
  497. }
  498. if ($target.is(":input:enabled")) {
  499. inputType = $target.prop("type");
  500. } else if ($target.is("a")) {
  501. inputType = "link";
  502. }
  503. if ($activeTd && $activeTd.find(":checkbox:enabled").length === 1) {
  504. $embeddedCheckbox = $activeTd.find(":checkbox:enabled");
  505. inputType = "checkbox";
  506. }
  507. tree.debug(
  508. "nodeKeydown(" +
  509. eventString +
  510. "), activeTd: '" +
  511. ($activeTd && $activeTd.text()) +
  512. "', inputType: " +
  513. inputType
  514. );
  515. if (inputType && eventString !== "esc" && !forceNav) {
  516. handleKeys = INPUT_KEYS[inputType];
  517. if (handleKeys && $.inArray(eventString, handleKeys) >= 0) {
  518. return; // Let input control handle the key
  519. }
  520. }
  521. switch (eventString) {
  522. case "right":
  523. if ($activeTd) {
  524. // Cell mode: move to neighbour (stop on right border)
  525. $td = findNeighbourTd(tree, $activeTd, eventString);
  526. if ($td) {
  527. tree.activateCell($td);
  528. }
  529. } else if (
  530. node &&
  531. !node.isExpanded() &&
  532. node.hasChildren() !== false
  533. ) {
  534. // Row mode and current node can be expanded:
  535. // default handling will expand.
  536. break;
  537. } else {
  538. // Row mode: switch to cell-mode
  539. $td = $(node.tr).find(">td").first();
  540. tree.activateCell($td);
  541. }
  542. return false; // no default handling
  543. case "left":
  544. case "home":
  545. case "end":
  546. case "ctrl+home":
  547. case "ctrl+end":
  548. case "up":
  549. case "down":
  550. if ($activeTd) {
  551. // Cell mode: move to neighbour
  552. $td = findNeighbourTd(tree, $activeTd, eventString);
  553. // Note: $td may be null if we move outside bounds. In this case
  554. // we switch back to row-mode (i.e. call activateCell(null) ).
  555. if (!$td && "left right".indexOf(eventString) < 0) {
  556. // Only switch to row-mode if left/right hits the bounds
  557. return false;
  558. }
  559. if ($td || opts.cellFocus !== "force") {
  560. tree.activateCell($td);
  561. }
  562. return false;
  563. }
  564. break;
  565. case "esc":
  566. if ($activeTd && !tree.forceNavMode) {
  567. // Switch from cell-edit-mode to cell-nav-mode
  568. // $target.closest( "td" ).focus();
  569. tree.forceNavMode = true;
  570. tree.debug("Enter cell-nav-mode");
  571. tree.$container.toggleClass(
  572. clsFancytreeCellNavMode,
  573. !!tree.forceNavMode
  574. );
  575. return false;
  576. } else if ($activeTd && opts.cellFocus !== "force") {
  577. // Switch back from cell-mode to row-mode
  578. tree.activateCell(null);
  579. return false;
  580. }
  581. // tree.$container.toggleClass( clsFancytreeCellNavMode, !!tree.forceNavMode );
  582. break;
  583. case "return":
  584. // Let user override the default action.
  585. // This event is triggered in row-mode and cell-mode
  586. res = tree._triggerNodeEvent(
  587. "defaultGridAction",
  588. node,
  589. event,
  590. {
  591. activeTd: tree.$activeTd ? tree.$activeTd[0] : null,
  592. colIdx: colIdx,
  593. mode: getGridNavMode(tree),
  594. }
  595. );
  596. if (res === false) {
  597. return false;
  598. }
  599. // Implement default actions (for cell-mode only).
  600. if ($activeTd) {
  601. // Apply 'default action' for embedded cell control
  602. if (colIdx === this.nodeColumnIdx) {
  603. node.toggleExpanded();
  604. } else if (colIdx === this.checkboxColumnIdx) {
  605. // TODO: only in checkbox mode!
  606. node.toggleSelected();
  607. } else if ($embeddedCheckbox) {
  608. // Embedded checkboxes are always toggled (ignoring `autoFocusInput`)
  609. $embeddedCheckbox.prop(
  610. "checked",
  611. !$embeddedCheckbox.prop("checked")
  612. );
  613. } else if (tree.forceNavMode && $target.is(":input")) {
  614. tree.forceNavMode = false;
  615. tree.$container.removeClass(
  616. clsFancytreeCellNavMode
  617. );
  618. tree.debug("enable cell-edit-mode");
  619. } else if ($activeTd.find("a").length === 1) {
  620. activateEmbeddedLink($activeTd);
  621. }
  622. } else {
  623. // ENTER in row-mode: Switch from row-mode to cell-mode
  624. // TODO: it was also suggested to expand/collapse instead
  625. // https://github.com/w3c/aria-practices/issues/132#issuecomment-407634891
  626. $td = $(node.tr).find(">td").nth(this.nodeColumnIdx);
  627. tree.activateCell($td);
  628. }
  629. return false; // no default handling
  630. case "space":
  631. if ($activeTd) {
  632. if (colIdx === this.checkboxColumnIdx) {
  633. node.toggleSelected();
  634. } else if ($embeddedCheckbox) {
  635. $embeddedCheckbox.prop(
  636. "checked",
  637. !$embeddedCheckbox.prop("checked")
  638. );
  639. }
  640. return false; // no default handling
  641. }
  642. break;
  643. default:
  644. // Allow to focus input by typing alphanum keys
  645. }
  646. return this._superApply(arguments);
  647. },
  648. treeSetOption: function (ctx, key, value) {
  649. var tree = ctx.tree,
  650. opts = tree.options.ariagrid;
  651. if (key === "ariagrid") {
  652. // User called `$().fancytree("option", "ariagrid.SUBKEY", VALUE)`
  653. if (value.cellFocus !== opts.cellFocus) {
  654. if ($.inArray(value.cellFocus, VALID_MODES) < 0) {
  655. $.error("Invalid `cellFocus` option");
  656. }
  657. // TODO: fix current focus and mode
  658. }
  659. }
  660. return this._superApply(arguments);
  661. },
  662. });
  663. // Value returned by `require('jquery.fancytree..')`
  664. return $.ui.fancytree;
  665. }); // End of closure

Fork me on GitHub