text: /*!
   * 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.3.0
   * @date 2014-08-17T10:39
   */
  
  ;(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));
  