UNPKG

jquery.fancytree

Version:

Fancytree is a JavaScript tree view plugin for jQuery with support for persistence, keyboard, checkboxes, drag'n'drop, and lazy loading.

204 lines (177 loc) 5.33 kB
/*! * 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) 2008-2017, Martin Wendt (http://wwWendt.de) * * Released under the MIT license * https://github.com/mar10/fancytree/wiki/LicenseInfo * * @version @VERSION * @date @DATE */ ;(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], "link": [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,a").length ) { break; } } break; } return $tdNext; } /******************************************************************************* * Extension code */ $.ui.fancytree.registerExtension({ name: "gridnav", version: "@VERSION", // 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._superApply(arguments); 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, callOpts) { var $outer, opts = ctx.options.gridnav, node = ctx.node, event = ctx.originalEvent || {}, triggeredByInput = $(event.target).is(":input"); flag = (flag !== false); this._superApply(arguments); 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); if( $target.is(":input:enabled") ) { inputType = $target.prop("type"); } else if( $target.is("a") ) { inputType = "link"; } // 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); if( $td && $td.length ) { // ctx.node.debug("ignore keydown in input", event.which, handleKeys); $td.find(":input:enabled,a").focus(); // Prevent Fancytree default navigation return false; } } return true; } // ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType); return this._superApply(arguments); } }); }(jQuery, window, document));