src/pyams_skin/resources/js/ext/jquery-dataTables-keyTable.js
changeset 566 a1707c607eec
parent 565 318533413200
child 567 bca1726b1d85
equal deleted inserted replaced
565:318533413200 566:a1707c607eec
     1 /*! KeyTable 1.2.0
       
     2  * ©2010-2014 SpryMedia Ltd - datatables.net/license
       
     3  */
       
     4 
       
     5 /**
       
     6  * @summary     KeyTable
       
     7  * @description Spreadsheet like keyboard navigation for DataTables
       
     8  * @version     1.2.0
       
     9  * @file        dataTables.keyTable.js
       
    10  * @author      SpryMedia Ltd (www.sprymedia.co.uk)
       
    11  * @contact     www.sprymedia.co.uk/contact
       
    12  * @copyright   Copyright 2009-2014 SpryMedia Ltd.
       
    13  *
       
    14  * This source file is free software, available under the following license:
       
    15  *   MIT license - http://datatables.net/license/mit
       
    16  *
       
    17  * This source file is distributed in the hope that it will be useful, but
       
    18  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
       
    19  * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
       
    20  *
       
    21  * For details please refer to: http://www.datatables.net
       
    22  */
       
    23 
       
    24 // Global scope for KeyTable for backwards compatibility. Will be removed in 1.3
       
    25 var KeyTable;
       
    26 
       
    27 
       
    28 (function (window, document, undefined) {
       
    29 
       
    30 
       
    31 	var factory = function ($, DataTable) {
       
    32 		"use strict";
       
    33 
       
    34 		KeyTable = function (oInit) {
       
    35 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
    36 			 * API parameters
       
    37 			 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
       
    38 
       
    39 			/*
       
    40 			 * Variable: block
       
    41 			 * Purpose:  Flag whether or not KeyTable events should be processed
       
    42 			 * Scope:    KeyTable - public
       
    43 			 */
       
    44 			this.block = false;
       
    45 
       
    46 			/*
       
    47 			 * Variable: event
       
    48 			 * Purpose:  Container for all event application methods
       
    49 			 * Scope:    KeyTable - public
       
    50 			 * Notes:    This object contains all the public methods for adding and removing events - these
       
    51 			 *           are dynamically added later on
       
    52 			 */
       
    53 			this.event = {
       
    54 				"remove": {}
       
    55 			};
       
    56 
       
    57 
       
    58 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
    59 			 * API methods
       
    60 			 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
       
    61 
       
    62 			/*
       
    63 			 * Function: fnGetCurrentPosition
       
    64 			 * Purpose:  Get the currently focused cell's position
       
    65 			 * Returns:  array int: [ x, y ]
       
    66 			 * Inputs:   void
       
    67 			 */
       
    68 			this.fnGetCurrentPosition = function () {
       
    69 				return [ _iOldX, _iOldY ];
       
    70 			};
       
    71 
       
    72 
       
    73 			/*
       
    74 			 * Function: fnGetCurrentData
       
    75 			 * Purpose:  Get the currently focused cell's data (innerHTML)
       
    76 			 * Returns:  string: - data requested
       
    77 			 * Inputs:   void
       
    78 			 */
       
    79 			this.fnGetCurrentData = function () {
       
    80 				return _nOldFocus.innerHTML;
       
    81 			};
       
    82 
       
    83 
       
    84 			/*
       
    85 			 * Function: fnGetCurrentTD
       
    86 			 * Purpose:  Get the currently focused cell
       
    87 			 * Returns:  node: - focused element
       
    88 			 * Inputs:   void
       
    89 			 */
       
    90 			this.fnGetCurrentTD = function () {
       
    91 				return _nOldFocus;
       
    92 			};
       
    93 
       
    94 
       
    95 			/*
       
    96 			 * Function: fnSetPosition
       
    97 			 * Purpose:  Set the position of the focused cell
       
    98 			 * Returns:  -
       
    99 			 * Inputs:   int:x - x coordinate
       
   100 			 *           int:y - y coordinate
       
   101 			 * Notes:    Thanks to Rohan Daxini for the basis of this function
       
   102 			 */
       
   103 			this.fnSetPosition = function (x, y) {
       
   104 				if (typeof x == 'object' && x.nodeName) {
       
   105 					_fnSetFocus(x);
       
   106 				}
       
   107 				else {
       
   108 					_fnSetFocus(_fnCellFromCoords(x, y));
       
   109 				}
       
   110 			};
       
   111 
       
   112 
       
   113 			/*
       
   114 			 * Function: fnBlur
       
   115 			 * Purpose:  Blur the current focus
       
   116 			 * Returns:  -
       
   117 			 * Inputs:   -
       
   118 			 */
       
   119 			this.fnBlur = function () {
       
   120 				_fnBlur();
       
   121 			};
       
   122 
       
   123 
       
   124 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   125 			 * Private parameters
       
   126 			 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
       
   127 
       
   128 			/*
       
   129 			 * Variable: _nBody
       
   130 			 * Purpose:  Body node of the table - cached for renference
       
   131 			 * Scope:    KeyTable - private
       
   132 			 */
       
   133 			var _nBody = null;
       
   134 
       
   135 			/*
       
   136 			 * Variable:
       
   137 			 * Purpose:
       
   138 			 * Scope:    KeyTable - private
       
   139 			 */
       
   140 			var _nOldFocus = null;
       
   141 
       
   142 			/*
       
   143 			 * Variable: _iOldX and _iOldY
       
   144 			 * Purpose:  X and Y coords of the old elemet that was focused on
       
   145 			 * Scope:    KeyTable - private
       
   146 			 */
       
   147 			var _iOldX = null;
       
   148 			var _iOldY = null;
       
   149 
       
   150 			/*
       
   151 			 * Variable: _that
       
   152 			 * Purpose:  Scope saving for 'this' after a jQuery event
       
   153 			 * Scope:    KeyTable - private
       
   154 			 */
       
   155 			var _that = null;
       
   156 
       
   157 			/*
       
   158 			 * Variable: sFocusClass
       
   159 			 * Purpose:  Class that should be used for focusing on a cell
       
   160 			 * Scope:    KeyTable - private
       
   161 			 */
       
   162 			var _sFocusClass = "focus";
       
   163 
       
   164 			/*
       
   165 			 * Variable: _bKeyCapture
       
   166 			 * Purpose:  Flag for should KeyTable capture key events or not
       
   167 			 * Scope:    KeyTable - private
       
   168 			 */
       
   169 			var _bKeyCapture = false;
       
   170 
       
   171 			/*
       
   172 			 * Variable: _oaoEvents
       
   173 			 * Purpose:  Event cache object, one array for each supported event for speed of searching
       
   174 			 * Scope:    KeyTable - private
       
   175 			 */
       
   176 			var _oaoEvents = {
       
   177 				"action": [],
       
   178 				"esc": [],
       
   179 				"focus": [],
       
   180 				"blur": []
       
   181 			};
       
   182 
       
   183 			/*
       
   184 			 * Variable: _oDatatable
       
   185 			 * Purpose:  DataTables settings object for if we are actually using a
       
   186 			 *           DataTables table
       
   187 			 * Scope:    KeyTable - private
       
   188 			 */
       
   189 			var _oDatatable = null;
       
   190 
       
   191 			var _bForm;
       
   192 			var _nInput;
       
   193 			var _bInputFocused = false;
       
   194 
       
   195 
       
   196 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   197 			 * Private methods
       
   198 			 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
       
   199 
       
   200 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   201 			 * Key table events
       
   202 			 */
       
   203 
       
   204 			/*
       
   205 			 * Function: _fnEventAddTemplate
       
   206 			 * Purpose:  Create a function (with closure for sKey) event addition API
       
   207 			 * Returns:  function: - template function
       
   208 			 * Inputs:   string:sKey - type of event to detect
       
   209 			 */
       
   210 			function _fnEventAddTemplate(sKey) {
       
   211 				/*
       
   212 				 * Function: -
       
   213 				 * Purpose:  API function for adding event to cache
       
   214 				 * Returns:  -
       
   215 				 * Inputs:   1. node:x - target node to add event for
       
   216 				 *           2. function:y - callback function to apply
       
   217 				 *         or
       
   218 				 *           1. int:x - x coord. of target cell (can be null for live events)
       
   219 				 *           2. int:y - y coord. of target cell (can be null for live events)
       
   220 				 *           3. function:z - callback function to apply
       
   221 				 * Notes:    This function is (interally) overloaded (in as much as javascript allows for
       
   222 				 *           that) - the target cell can be given by either node or coords.
       
   223 				 */
       
   224 				return function (x, y, z) {
       
   225 					if ((x === null || typeof x == "number") &&
       
   226 						(y === null || typeof y == "number") &&
       
   227 						typeof z == "function") {
       
   228 						_fnEventAdd(sKey, x, y, z);
       
   229 					}
       
   230 					else if (typeof x == "object" && typeof y == "function") {
       
   231 						var aCoords = _fnCoordsFromCell(x);
       
   232 						_fnEventAdd(sKey, aCoords[0], aCoords[1], y);
       
   233 					}
       
   234 					else {
       
   235 						alert("Unhandable event type was added: x" + x + "  y:" + y + "  z:" + z);
       
   236 					}
       
   237 				};
       
   238 			}
       
   239 
       
   240 
       
   241 			/*
       
   242 			 * Function: _fnEventRemoveTemplate
       
   243 			 * Purpose:  Create a function (with closure for sKey) event removal API
       
   244 			 * Returns:  function: - template function
       
   245 			 * Inputs:   string:sKey - type of event to detect
       
   246 			 */
       
   247 			function _fnEventRemoveTemplate(sKey) {
       
   248 				/*
       
   249 				 * Function: -
       
   250 				 * Purpose:  API function for removing event from cache
       
   251 				 * Returns:  int: - number of events removed
       
   252 				 * Inputs:   1. node:x - target node to remove event from
       
   253 				 *           2. function:y - callback function to apply
       
   254 				 *         or
       
   255 				 *           1. int:x - x coord. of target cell (can be null for live events)
       
   256 				 *           2. int:y - y coord. of target cell (can be null for live events)
       
   257 				 *           3. function:z - callback function to remove - optional
       
   258 				 * Notes:    This function is (interally) overloaded (in as much as javascript allows for
       
   259 				 *           that) - the target cell can be given by either node or coords and the function
       
   260 				 *           to remove is optional
       
   261 				 */
       
   262 				return function (x, y, z) {
       
   263 					if ((x === null || typeof arguments[0] == "number") &&
       
   264 						(y === null || typeof arguments[1] == "number" )) {
       
   265 						if (typeof arguments[2] == "function") {
       
   266 							_fnEventRemove(sKey, x, y, z);
       
   267 						}
       
   268 						else {
       
   269 							_fnEventRemove(sKey, x, y);
       
   270 						}
       
   271 					}
       
   272 					else if (typeof arguments[0] == "object") {
       
   273 						var aCoords = _fnCoordsFromCell(x);
       
   274 						if (typeof arguments[1] == "function") {
       
   275 							_fnEventRemove(sKey, aCoords[0], aCoords[1], y);
       
   276 						}
       
   277 						else {
       
   278 							_fnEventRemove(sKey, aCoords[0], aCoords[1]);
       
   279 						}
       
   280 					}
       
   281 					else {
       
   282 						alert("Unhandable event type was removed: x" + x + "  y:" + y + "  z:" + z);
       
   283 					}
       
   284 				};
       
   285 			}
       
   286 
       
   287 			/* Use the template functions to add the event API functions */
       
   288 			for (var sKey in _oaoEvents) {
       
   289 				if (sKey) {
       
   290 					this.event[sKey] = _fnEventAddTemplate(sKey);
       
   291 					this.event.remove[sKey] = _fnEventRemoveTemplate(sKey);
       
   292 				}
       
   293 			}
       
   294 
       
   295 
       
   296 			/*
       
   297 			 * Function: _fnEventAdd
       
   298 			 * Purpose:  Add an event to the internal cache
       
   299 			 * Returns:  -
       
   300 			 * Inputs:   string:sType - type of event to add, given by the available elements in _oaoEvents
       
   301 			 *           int:x - x-coords to add event to - can be null for "blanket" event
       
   302 			 *           int:y - y-coords to add event to - can be null for "blanket" event
       
   303 			 *           function:fn - callback function for when triggered
       
   304 			 */
       
   305 			function _fnEventAdd(sType, x, y, fn) {
       
   306 				_oaoEvents[sType].push({
       
   307 										   "x": x,
       
   308 										   "y": y,
       
   309 										   "fn": fn
       
   310 									   });
       
   311 			}
       
   312 
       
   313 
       
   314 			/*
       
   315 			 * Function: _fnEventRemove
       
   316 			 * Purpose:  Remove an event from the event cache
       
   317 			 * Returns:  int: - number of matching events removed
       
   318 			 * Inputs:   string:sType - type of event to look for
       
   319 			 *           node:nTarget - target table cell
       
   320 			 *           function:fn - optional - remove this function. If not given all handlers of this
       
   321 			 *             type will be removed
       
   322 			 */
       
   323 			function _fnEventRemove(sType, x, y, fn) {
       
   324 				var iCorrector = 0;
       
   325 
       
   326 				for (var i = 0, iLen = _oaoEvents[sType].length; i < iLen - iCorrector; i++) {
       
   327 					if (typeof fn != 'undefined') {
       
   328 						if (_oaoEvents[sType][i - iCorrector].x == x &&
       
   329 							_oaoEvents[sType][i - iCorrector].y == y &&
       
   330 							_oaoEvents[sType][i - iCorrector].fn == fn) {
       
   331 							_oaoEvents[sType].splice(i - iCorrector, 1);
       
   332 							iCorrector++;
       
   333 						}
       
   334 					}
       
   335 					else {
       
   336 						if (_oaoEvents[sType][i - iCorrector].x == x &&
       
   337 							_oaoEvents[sType][i - iCorrector].y == y) {
       
   338 							_oaoEvents[sType].splice(i, 1);
       
   339 							return 1;
       
   340 						}
       
   341 					}
       
   342 				}
       
   343 				return iCorrector;
       
   344 			}
       
   345 
       
   346 
       
   347 			/*
       
   348 			 * Function: _fnEventFire
       
   349 			 * Purpose:  Look thought the events cache and fire off the event of interest
       
   350 			 * Returns:  int:iFired - number of events fired
       
   351 			 * Inputs:   string:sType - type of event to look for
       
   352 			 *           int:x - x coord of cell
       
   353 			 *           int:y - y coord of  ell
       
   354 			 * Notes:    It might be more efficient to return after the first event has been tirggered,
       
   355 			 *           but that would mean that only one function of a particular type can be
       
   356 			 *           subscribed to a particular node.
       
   357 			 */
       
   358 			function _fnEventFire(sType, x, y) {
       
   359 				var iFired = 0;
       
   360 				var aEvents = _oaoEvents[sType];
       
   361 				for (var i = 0; i < aEvents.length; i++) {
       
   362 					if ((aEvents[i].x == x && aEvents[i].y == y    ) ||
       
   363 						(aEvents[i].x === null && aEvents[i].y == y    ) ||
       
   364 						(aEvents[i].x == x && aEvents[i].y === null ) ||
       
   365 						(aEvents[i].x === null && aEvents[i].y === null )
       
   366 						) {
       
   367 						aEvents[i].fn(_fnCellFromCoords(x, y), x, y);
       
   368 						iFired++;
       
   369 					}
       
   370 				}
       
   371 				return iFired;
       
   372 			}
       
   373 
       
   374 
       
   375 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   376 			 * Focus functions
       
   377 			 */
       
   378 
       
   379 			/*
       
   380 			 * Function: _fnSetFocus
       
   381 			 * Purpose:  Set focus on a node, and remove from an old node if needed
       
   382 			 * Returns:  -
       
   383 			 * Inputs:   node:nTarget - node we want to focus on
       
   384 			 *           bool:bAutoScroll - optional - should we scroll the view port to the display
       
   385 			 */
       
   386 			function _fnSetFocus(nTarget, bAutoScroll) {
       
   387 				/* If node already has focus, just ignore this call */
       
   388 				if (_nOldFocus == nTarget) {
       
   389 					return;
       
   390 				}
       
   391 
       
   392 				if (typeof bAutoScroll == 'undefined') {
       
   393 					bAutoScroll = true;
       
   394 				}
       
   395 
       
   396 				/* Remove old focus (with blur event if needed) */
       
   397 				if (_nOldFocus !== null) {
       
   398 					_fnRemoveFocus(_nOldFocus);
       
   399 				}
       
   400 
       
   401 				/* Add the new class to highlight the focused cell */
       
   402 				$(nTarget).addClass(_sFocusClass);
       
   403 				$(nTarget).parent().addClass(_sFocusClass);
       
   404 
       
   405 				/* If it's a DataTable then we need to jump the paging to the relevant page */
       
   406 				var oSettings;
       
   407 				if (_oDatatable) {
       
   408 					oSettings = _oDatatable;
       
   409 					var iRow = _fnFindDtCell(nTarget)[1];
       
   410 					var bKeyCaptureCache = _bKeyCapture;
       
   411 
       
   412 					/* Page forwards */
       
   413 					while (iRow >= oSettings.fnDisplayEnd()) {
       
   414 						if (oSettings._iDisplayLength >= 0) {
       
   415 							/* Make sure we are not over running the display array */
       
   416 							if (oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay()) {
       
   417 								oSettings._iDisplayStart += oSettings._iDisplayLength;
       
   418 							}
       
   419 						}
       
   420 						else {
       
   421 							oSettings._iDisplayStart = 0;
       
   422 						}
       
   423 						_oDatatable.oApi._fnCalculateEnd(oSettings);
       
   424 					}
       
   425 
       
   426 					/* Page backwards */
       
   427 					while (iRow < oSettings._iDisplayStart) {
       
   428 						oSettings._iDisplayStart = oSettings._iDisplayLength >= 0 ?
       
   429 							oSettings._iDisplayStart - oSettings._iDisplayLength :
       
   430 							0;
       
   431 
       
   432 						if (oSettings._iDisplayStart < 0) {
       
   433 							oSettings._iDisplayStart = 0;
       
   434 						}
       
   435 						_oDatatable.oApi._fnCalculateEnd(oSettings);
       
   436 					}
       
   437 
       
   438 					/* Re-draw the table */
       
   439 					_oDatatable.oApi._fnDraw(oSettings);
       
   440 
       
   441 					/* Restore the key capture */
       
   442 					_bKeyCapture = bKeyCaptureCache;
       
   443 				}
       
   444 
       
   445 				/* Cache the information that we are interested in */
       
   446 				var aNewPos = _fnCoordsFromCell(nTarget);
       
   447 				_nOldFocus = nTarget;
       
   448 				_iOldX = aNewPos[0];
       
   449 				_iOldY = aNewPos[1];
       
   450 
       
   451 				var iViewportHeight, iViewportWidth, iScrollTop, iScrollLeft, iHeight, iWidth, aiPos;
       
   452 				if (bAutoScroll) {
       
   453 					/* Scroll the viewport such that the new cell is fully visible in the rendered window */
       
   454 					iViewportHeight = $(window).height();
       
   455 					iViewportWidth = $(window).width();
       
   456 					iScrollTop = $(document).scrollTop();
       
   457 					iScrollLeft = $(document).scrollLeft();
       
   458 					iHeight = nTarget.offsetHeight;
       
   459 					iWidth = nTarget.offsetWidth;
       
   460 					aiPos = _fnGetPos(nTarget);
       
   461 
       
   462 					/* Take account of scrolling in DataTables 1.7 - remove scrolling since that would add to
       
   463 					 * the positioning calculation
       
   464 					 */
       
   465 					if (_oDatatable && typeof oSettings.oScroll != 'undefined' &&
       
   466 						(oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "")) {
       
   467 						aiPos[1] -= $(oSettings.nTable.parentNode).scrollTop();
       
   468 						aiPos[0] -= $(oSettings.nTable.parentNode).scrollLeft();
       
   469 					}
       
   470 
       
   471 					/* Correct viewport positioning for vertical scrolling */
       
   472 					if (aiPos[1] + iHeight > iScrollTop + iViewportHeight) {
       
   473 						/* Displayed element if off the bottom of the viewport */
       
   474 						_fnSetScrollTop(aiPos[1] + iHeight - iViewportHeight);
       
   475 					}
       
   476 					else if (aiPos[1] < iScrollTop) {
       
   477 						/* Displayed element if off the top of the viewport */
       
   478 						_fnSetScrollTop(aiPos[1]);
       
   479 					}
       
   480 
       
   481 					/* Correct viewport positioning for horizontal scrolling */
       
   482 					if (aiPos[0] + iWidth > iScrollLeft + iViewportWidth) {
       
   483 						/* Displayed element is off the bottom of the viewport */
       
   484 						_fnSetScrollLeft(aiPos[0] + iWidth - iViewportWidth);
       
   485 					}
       
   486 					else if (aiPos[0] < iScrollLeft) {
       
   487 						/* Displayed element if off the Left of the viewport */
       
   488 						_fnSetScrollLeft(aiPos[0]);
       
   489 					}
       
   490 				}
       
   491 
       
   492 				/* Take account of scrolling in DataTables 1.7 */
       
   493 				if (_oDatatable && typeof oSettings.oScroll != 'undefined' &&
       
   494 					(oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "")) {
       
   495 					var dtScrollBody = oSettings.nTable.parentNode;
       
   496 					iViewportHeight = dtScrollBody.clientHeight;
       
   497 					iViewportWidth = dtScrollBody.clientWidth;
       
   498 					iScrollTop = dtScrollBody.scrollTop;
       
   499 					iScrollLeft = dtScrollBody.scrollLeft;
       
   500 					iHeight = nTarget.offsetHeight;
       
   501 					iWidth = nTarget.offsetWidth;
       
   502 
       
   503 					/* Correct for vertical scrolling */
       
   504 					if (nTarget.offsetTop + iHeight > iViewportHeight + iScrollTop) {
       
   505 						dtScrollBody.scrollTop = (nTarget.offsetTop + iHeight) - iViewportHeight;
       
   506 					}
       
   507 					else if (nTarget.offsetTop < iScrollTop) {
       
   508 						dtScrollBody.scrollTop = nTarget.offsetTop;
       
   509 					}
       
   510 
       
   511 					/* Correct for horizontal scrolling */
       
   512 					if (nTarget.offsetLeft + iWidth > iViewportWidth + iScrollLeft) {
       
   513 						dtScrollBody.scrollLeft = (nTarget.offsetLeft + iWidth) - iViewportWidth;
       
   514 					}
       
   515 					else if (nTarget.offsetLeft < iScrollLeft) {
       
   516 						dtScrollBody.scrollLeft = nTarget.offsetLeft;
       
   517 					}
       
   518 				}
       
   519 
       
   520 				/* Focused - so we want to capture the keys */
       
   521 				_fnCaptureKeys();
       
   522 
       
   523 				/* Fire of the focus event if there is one */
       
   524 				_fnEventFire("focus", _iOldX, _iOldY);
       
   525 			}
       
   526 
       
   527 
       
   528 			/*
       
   529 			 * Function: _fnBlur
       
   530 			 * Purpose:  Blur focus from the whole table
       
   531 			 * Returns:  -
       
   532 			 * Inputs:   -
       
   533 			 */
       
   534 			function _fnBlur() {
       
   535 				_fnRemoveFocus(_nOldFocus);
       
   536 				_iOldX = null;
       
   537 				_iOldY = null;
       
   538 				_nOldFocus = null;
       
   539 				_fnReleaseKeys();
       
   540 			}
       
   541 
       
   542 
       
   543 			/*
       
   544 			 * Function: _fnRemoveFocus
       
   545 			 * Purpose:  Remove focus from a cell and fire any blur events which are attached
       
   546 			 * Returns:  -
       
   547 			 * Inputs:   node:nTarget - cell of interest
       
   548 			 */
       
   549 			function _fnRemoveFocus(nTarget) {
       
   550 				$(nTarget).removeClass(_sFocusClass);
       
   551 				$(nTarget).parent().removeClass(_sFocusClass);
       
   552 				_fnEventFire("blur", _iOldX, _iOldY);
       
   553 			}
       
   554 
       
   555 
       
   556 			/*
       
   557 			 * Function: _fnClick
       
   558 			 * Purpose:  Focus on the element that has been clicked on by the user
       
   559 			 * Returns:  -
       
   560 			 * Inputs:   event:e - click event
       
   561 			 */
       
   562 			function _fnClick(e) {
       
   563 				var nTarget = this;
       
   564 				while (nTarget.nodeName != "TD") {
       
   565 					nTarget = nTarget.parentNode;
       
   566 				}
       
   567 
       
   568 				_fnSetFocus(nTarget);
       
   569 				_fnCaptureKeys();
       
   570 			}
       
   571 
       
   572 
       
   573 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   574 			 * Key events
       
   575 			 */
       
   576 
       
   577 			/*
       
   578 			 * Function: _fnKey
       
   579 			 * Purpose:  Deal with a key events, be it moving the focus or return etc.
       
   580 			 * Returns:  bool: - allow browser default action
       
   581 			 * Inputs:   event:e - key event
       
   582 			 */
       
   583 			function _fnKey(e) {
       
   584 				/* If user or system has blocked KeyTable from doing anything, just ignore this event */
       
   585 				if (_that.block || !_bKeyCapture) {
       
   586 					return true;
       
   587 				}
       
   588 
       
   589 				/* If a modifier key is pressed (exapct shift), ignore the event */
       
   590 				if (e.metaKey || e.altKey || e.ctrlKey) {
       
   591 					return true;
       
   592 				}
       
   593 				var
       
   594 					x, y,
       
   595 					iTableWidth = _nBody.getElementsByTagName('tr')[0].getElementsByTagName('td').length,
       
   596 					iTableHeight;
       
   597 
       
   598 				/* Get table height and width - done here so as to be dynamic (if table is updated) */
       
   599 				if (_oDatatable) {
       
   600 					/*
       
   601 					 * Locate the current node in the DataTable overriding the old positions - the reason for
       
   602 					 * is is that there might have been some DataTables interaction between the last focus and
       
   603 					 * now
       
   604 					 */
       
   605 					iTableHeight = _oDatatable.aiDisplay.length;
       
   606 
       
   607 					var aDtPos = _fnFindDtCell(_nOldFocus);
       
   608 					if (aDtPos === null) {
       
   609 						/* If the table has been updated such that the focused cell can't be seen - do nothing */
       
   610 						return;
       
   611 					}
       
   612 					_iOldX = aDtPos[ 0 ];
       
   613 					_iOldY = aDtPos[ 1 ];
       
   614 				}
       
   615 				else {
       
   616 					iTableHeight = _nBody.getElementsByTagName('tr').length;
       
   617 				}
       
   618 
       
   619 				/* Capture shift+tab to match the left arrow key */
       
   620 				var iKey = (e.keyCode == 9 && e.shiftKey) ? -1 : e.keyCode;
       
   621 
       
   622 				switch (iKey) {
       
   623 					case 13: /* return */
       
   624 						e.preventDefault();
       
   625 						e.stopPropagation();
       
   626 						_fnEventFire("action", _iOldX, _iOldY);
       
   627 						return true;
       
   628 
       
   629 					case 27: /* esc */
       
   630 						if (!_fnEventFire("esc", _iOldX, _iOldY)) {
       
   631 							/* Only lose focus if there isn't an escape handler on the cell */
       
   632 							_fnBlur();
       
   633 							return;
       
   634 						}
       
   635 						x = _iOldX;
       
   636 						y = _iOldY;
       
   637 						break;
       
   638 
       
   639 					case -1:
       
   640 					case 37: /* left arrow */
       
   641 						if (_iOldX > 0) {
       
   642 							x = _iOldX - 1;
       
   643 							y = _iOldY;
       
   644 						} else if (_iOldY > 0) {
       
   645 							x = iTableWidth - 1;
       
   646 							y = _iOldY - 1;
       
   647 						} else {
       
   648 							/* at start of table */
       
   649 							if (iKey == -1 && _bForm) {
       
   650 								/* If we are in a form, return focus to the 'input' element such that tabbing will
       
   651 								 * follow correctly in the browser
       
   652 								 */
       
   653 								_bInputFocused = true;
       
   654 								_nInput.focus();
       
   655 
       
   656 								/* This timeout is a little nasty - but IE appears to have some asyhnc behaviour for
       
   657 								 * focus
       
   658 								 */
       
   659 								setTimeout(function () {
       
   660 									_bInputFocused = false;
       
   661 								}, 0);
       
   662 								_bKeyCapture = false;
       
   663 								_fnBlur();
       
   664 								return true;
       
   665 							}
       
   666 							else {
       
   667 								return false;
       
   668 							}
       
   669 						}
       
   670 						break;
       
   671 
       
   672 					case 38: /* up arrow */
       
   673 						if (_iOldY > 0) {
       
   674 							x = _iOldX;
       
   675 							y = _iOldY - 1;
       
   676 						} else {
       
   677 							return false;
       
   678 						}
       
   679 						break;
       
   680 
       
   681 					case 36: /* home */
       
   682 						x = _iOldX;
       
   683 						y = 0;
       
   684 						break;
       
   685 
       
   686 					case 33: /* page up */
       
   687 						x = _iOldX;
       
   688 						y = _iOldY - 10;
       
   689 						if (y < 0) {
       
   690 							y = 0;
       
   691 						}
       
   692 						break;
       
   693 
       
   694 					case 9: /* tab */
       
   695 					case 39: /* right arrow */
       
   696 						if (_iOldX < iTableWidth - 1) {
       
   697 							x = _iOldX + 1;
       
   698 							y = _iOldY;
       
   699 						} else if (_iOldY < iTableHeight - 1) {
       
   700 							x = 0;
       
   701 							y = _iOldY + 1;
       
   702 						} else {
       
   703 							/* at end of table */
       
   704 							if (iKey == 9 && _bForm) {
       
   705 								/* If we are in a form, return focus to the 'input' element such that tabbing will
       
   706 								 * follow correctly in the browser
       
   707 								 */
       
   708 								_bInputFocused = true;
       
   709 								_nInput.focus();
       
   710 
       
   711 								/* This timeout is a little nasty - but IE appears to have some asyhnc behaviour for
       
   712 								 * focus
       
   713 								 */
       
   714 								setTimeout(function () {
       
   715 									_bInputFocused = false;
       
   716 								}, 0);
       
   717 								_bKeyCapture = false;
       
   718 								_fnBlur();
       
   719 								return true;
       
   720 							}
       
   721 							else {
       
   722 								return false;
       
   723 							}
       
   724 						}
       
   725 						break;
       
   726 
       
   727 					case 40: /* down arrow */
       
   728 						if (_iOldY < iTableHeight - 1) {
       
   729 							x = _iOldX;
       
   730 							y = _iOldY + 1;
       
   731 						} else {
       
   732 							return false;
       
   733 						}
       
   734 						break;
       
   735 
       
   736 					case 35: /* end */
       
   737 						x = _iOldX;
       
   738 						y = iTableHeight - 1;
       
   739 						break;
       
   740 
       
   741 					case 34: /* page down */
       
   742 						x = _iOldX;
       
   743 						y = _iOldY + 10;
       
   744 						if (y > iTableHeight - 1) {
       
   745 							y = iTableHeight - 1;
       
   746 						}
       
   747 						break;
       
   748 
       
   749 					default: /* Nothing we are interested in */
       
   750 						return true;
       
   751 				}
       
   752 
       
   753 				_fnSetFocus(_fnCellFromCoords(x, y));
       
   754 				return false;
       
   755 			}
       
   756 
       
   757 
       
   758 			/*
       
   759 			 * Function: _fnCaptureKeys
       
   760 			 * Purpose:  Start capturing key events for this table
       
   761 			 * Returns:  -
       
   762 			 * Inputs:   -
       
   763 			 */
       
   764 			function _fnCaptureKeys() {
       
   765 				if (!_bKeyCapture) {
       
   766 					_bKeyCapture = true;
       
   767 				}
       
   768 			}
       
   769 
       
   770 
       
   771 			/*
       
   772 			 * Function: _fnReleaseKeys
       
   773 			 * Purpose:  Stop capturing key events for this table
       
   774 			 * Returns:  -
       
   775 			 * Inputs:   -
       
   776 			 */
       
   777 			function _fnReleaseKeys() {
       
   778 				_bKeyCapture = false;
       
   779 			}
       
   780 
       
   781 
       
   782 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   783 			 * Support functions
       
   784 			 */
       
   785 
       
   786 			/*
       
   787 			 * Function: _fnCellFromCoords
       
   788 			 * Purpose:  Calulate the target TD cell from x and y coordinates
       
   789 			 * Returns:  node: - TD target
       
   790 			 * Inputs:   int:x - x coordinate
       
   791 			 *           int:y - y coordinate
       
   792 			 */
       
   793 			function _fnCellFromCoords(x, y) {
       
   794 				if (_oDatatable) {
       
   795 					if (typeof _oDatatable.aoData[ _oDatatable.aiDisplay[ y ] ] != 'undefined') {
       
   796 						return _oDatatable.aoData[ _oDatatable.aiDisplay[ y ] ].nTr.getElementsByTagName('td')[x];
       
   797 					}
       
   798 					else {
       
   799 						return null;
       
   800 					}
       
   801 				}
       
   802 				else {
       
   803 					return $('tr:eq(' + y + ')>td:eq(' + x + ')', _nBody)[0];
       
   804 				}
       
   805 			}
       
   806 
       
   807 
       
   808 			/*
       
   809 			 * Function: _fnCoordsFromCell
       
   810 			 * Purpose:  Calculate the x and y position in a table from a TD cell
       
   811 			 * Returns:  array[2] int: [x, y]
       
   812 			 * Inputs:   node:n - TD cell of interest
       
   813 			 * Notes:    Not actually interested in this for DataTables since it might go out of date
       
   814 			 */
       
   815 			function _fnCoordsFromCell(n) {
       
   816 				if (_oDatatable) {
       
   817 					return [
       
   818 						$('td', n.parentNode).index(n),
       
   819 						$('tr', n.parentNode.parentNode).index(n.parentNode) + _oDatatable._iDisplayStart
       
   820 					];
       
   821 				}
       
   822 				else {
       
   823 					return [
       
   824 						$('td', n.parentNode).index(n),
       
   825 						$('tr', n.parentNode.parentNode).index(n.parentNode)
       
   826 					];
       
   827 				}
       
   828 			}
       
   829 
       
   830 
       
   831 			/*
       
   832 			 * Function: _fnSetScrollTop
       
   833 			 * Purpose:  Set the vertical scrolling position
       
   834 			 * Returns:  -
       
   835 			 * Inputs:   int:iPos - scrolltop
       
   836 			 * Notes:    This is so nasty, but without browser detection you can't tell which you should set
       
   837 			 *           So on browsers that support both, the scroll top will be set twice. I can live with
       
   838 			 *           that :-)
       
   839 			 */
       
   840 			function _fnSetScrollTop(iPos) {
       
   841 				document.documentElement.scrollTop = iPos;
       
   842 				document.body.scrollTop = iPos;
       
   843 			}
       
   844 
       
   845 
       
   846 			/*
       
   847 			 * Function: _fnSetScrollLeft
       
   848 			 * Purpose:  Set the horizontal scrolling position
       
   849 			 * Returns:  -
       
   850 			 * Inputs:   int:iPos - scrollleft
       
   851 			 */
       
   852 			function _fnSetScrollLeft(iPos) {
       
   853 				document.documentElement.scrollLeft = iPos;
       
   854 				document.body.scrollLeft = iPos;
       
   855 			}
       
   856 
       
   857 
       
   858 			/*
       
   859 			 * Function: _fnGetPos
       
   860 			 * Purpose:  Get the position of an object on the rendered page
       
   861 			 * Returns:  array[2] int: [left, right]
       
   862 			 * Inputs:   node:obj - element of interest
       
   863 			 */
       
   864 			function _fnGetPos(obj) {
       
   865 				var iLeft = 0;
       
   866 				var iTop = 0;
       
   867 
       
   868 				if (obj.offsetParent) {
       
   869 					iLeft = obj.offsetLeft;
       
   870 					iTop = obj.offsetTop;
       
   871 					obj = obj.offsetParent;
       
   872 					while (obj) {
       
   873 						iLeft += obj.offsetLeft;
       
   874 						iTop += obj.offsetTop;
       
   875 						obj = obj.offsetParent;
       
   876 					}
       
   877 				}
       
   878 				return [iLeft, iTop];
       
   879 			}
       
   880 
       
   881 
       
   882 			/*
       
   883 			 * Function: _fnFindDtCell
       
   884 			 * Purpose:  Get the coords. of a cell from the DataTables internal information
       
   885 			 * Returns:  array[2] int: [x, y] coords. or null if not found
       
   886 			 * Inputs:   node:nTarget - the node of interest
       
   887 			 */
       
   888 			function _fnFindDtCell(nTarget) {
       
   889 				for (var i = 0, iLen = _oDatatable.aiDisplay.length; i < iLen; i++) {
       
   890 					var nTr = _oDatatable.aoData[ _oDatatable.aiDisplay[i] ].nTr;
       
   891 					var nTds = nTr.getElementsByTagName('td');
       
   892 					for (var j = 0, jLen = nTds.length; j < jLen; j++) {
       
   893 						if (nTds[j] == nTarget) {
       
   894 							return [ j, i ];
       
   895 						}
       
   896 					}
       
   897 				}
       
   898 				return null;
       
   899 			}
       
   900 
       
   901 
       
   902 			/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
       
   903 			 * Initialisation
       
   904 			 */
       
   905 
       
   906 			/*
       
   907 			 * Function: _fnInit
       
   908 			 * Purpose:  Initialise the KeyTable
       
   909 			 * Returns:  -
       
   910 			 * Inputs:   object:oInit - optional - Initalisation object with the following parameters:
       
   911 			 *   array[2] int:focus - x and y coordinates of the initial target
       
   912 			 *     or
       
   913 			 *     node:focus - the node to set initial focus on
       
   914 			 *   node:table - the table to use, if not given, first table with class 'KeyTable' will be used
       
   915 			 *   string:focusClass - focusing class to give to table elements
       
   916 			 *           object:that - focus
       
   917 			 *   bool:initScroll - scroll the view port on load, default true
       
   918 			 *   int:tabIndex - the tab index to give the hidden input element
       
   919 			 */
       
   920 			function _fnInit(table, datatable, oInit, that) {
       
   921 				/* Save scope */
       
   922 				_that = that;
       
   923 
       
   924 				/* Capture undefined initialisation and apply the defaults */
       
   925 				if (typeof oInit == 'undefined') {
       
   926 					oInit = {};
       
   927 				}
       
   928 
       
   929 				if (typeof oInit.focus == 'undefined') {
       
   930 					oInit.focus = [0, 0];
       
   931 				}
       
   932 
       
   933 				oInit.table = table;
       
   934 				$(oInit.table).addClass('KeyTable');
       
   935 
       
   936 				if (typeof oInit.focusClass != 'undefined') {
       
   937 					_sFocusClass = oInit.focusClass;
       
   938 				}
       
   939 
       
   940 				if (typeof datatable != 'undefined') {
       
   941 					_oDatatable = datatable;
       
   942 				}
       
   943 
       
   944 				if (typeof oInit.initScroll == 'undefined') {
       
   945 					oInit.initScroll = true;
       
   946 				}
       
   947 
       
   948 				if (typeof oInit.form == 'undefined') {
       
   949 					oInit.form = false;
       
   950 				}
       
   951 				_bForm = oInit.form;
       
   952 
       
   953 				/* Cache the tbody node of interest */
       
   954 				_nBody = oInit.table.getElementsByTagName('tbody')[0];
       
   955 
       
   956 				/* If the table is inside a form, then we need a hidden input box which can be used by the
       
   957 				 * browser to catch the browser tabbing for our table
       
   958 				 */
       
   959 				if (_bForm) {
       
   960 					var nDiv = document.createElement('div');
       
   961 					_nInput = document.createElement('input');
       
   962 					nDiv.style.height = "1px";
       
   963 					/* Opera requires a little something */
       
   964 					nDiv.style.width = "0px";
       
   965 					nDiv.style.overflow = "hidden";
       
   966 					if (typeof oInit.tabIndex != 'undefined') {
       
   967 						_nInput.tabIndex = oInit.tabIndex;
       
   968 					}
       
   969 					nDiv.appendChild(_nInput);
       
   970 					oInit.table.parentNode.insertBefore(nDiv, oInit.table.nextSibling);
       
   971 
       
   972 					$(_nInput).focus(function () {
       
   973 						/* See if we want to 'tab into' the table or out */
       
   974 						if (!_bInputFocused) {
       
   975 							_bKeyCapture = true;
       
   976 							_bInputFocused = false;
       
   977 							if (typeof oInit.focus.nodeName != "undefined") {
       
   978 								_fnSetFocus(oInit.focus, oInit.initScroll);
       
   979 							}
       
   980 							else {
       
   981 								_fnSetFocus(_fnCellFromCoords(oInit.focus[0], oInit.focus[1]), oInit.initScroll);
       
   982 							}
       
   983 
       
   984 							/* Need to interup the thread for this to work */
       
   985 							setTimeout(function () {
       
   986 								_nInput.blur();
       
   987 							}, 0);
       
   988 						}
       
   989 					});
       
   990 					_bKeyCapture = false;
       
   991 				}
       
   992 				else {
       
   993 					/* Set the initial focus on the table */
       
   994 					if (typeof oInit.focus.nodeName != "undefined") {
       
   995 						_fnSetFocus(oInit.focus, oInit.initScroll);
       
   996 					}
       
   997 					else {
       
   998 						_fnSetFocus(_fnCellFromCoords(oInit.focus[0], oInit.focus[1]), oInit.initScroll);
       
   999 					}
       
  1000 					_fnCaptureKeys();
       
  1001 				}
       
  1002 
       
  1003 				/* Add event listeners */
       
  1004 				$(document).bind("keydown", _fnKey);
       
  1005 
       
  1006 				if (_oDatatable) {
       
  1007 					$(_oDatatable.nTable).on('click', 'td', _fnClick);
       
  1008 				}
       
  1009 				else {
       
  1010 					$(_nBody).on('click', 'td', _fnClick);
       
  1011 				}
       
  1012 
       
  1013 				/* Loose table focus when click outside the table */
       
  1014 				$(document).click(function (e) {
       
  1015 					var nTarget = e.target;
       
  1016 					var bTableClick = false;
       
  1017 					while (nTarget) {
       
  1018 						if (nTarget == oInit.table) {
       
  1019 							bTableClick = true;
       
  1020 							break;
       
  1021 						}
       
  1022 						nTarget = nTarget.parentNode;
       
  1023 					}
       
  1024 					if (!bTableClick) {
       
  1025 						_fnBlur();
       
  1026 					}
       
  1027 				});
       
  1028 			}
       
  1029 
       
  1030 			var table, datatable;
       
  1031 
       
  1032 			if (oInit === undefined) {
       
  1033 				table = $('table.KeyTable')[0];
       
  1034 				datatable = null;
       
  1035 			}
       
  1036 			else if ($.isPlainObject(oInit)) {
       
  1037 				table = oInit.table;
       
  1038 				datatable = oInit.datatable;
       
  1039 			}
       
  1040 			else {
       
  1041 				datatable = new $.fn.dataTable.Api(oInit).settings()[0];
       
  1042 				table = datatable.nTable;
       
  1043 			}
       
  1044 			/* Initialise our new object */
       
  1045 			_fnInit(table, datatable, oInit, this);
       
  1046 		};
       
  1047 
       
  1048 
       
  1049 		KeyTable.version = "1.2.0";
       
  1050 
       
  1051 
       
  1052 		$.fn.dataTable.KeyTable = KeyTable;
       
  1053 		$.fn.DataTable.KeyTable = KeyTable;
       
  1054 
       
  1055 
       
  1056 		return KeyTable;
       
  1057 	}; // /factory
       
  1058 
       
  1059 
       
  1060 	factory(jQuery, jQuery.fn.dataTable);
       
  1061 
       
  1062 
       
  1063 })(window, document);