dataTables.keyTable.js 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. /*! KeyTable 2.4.0
  2. * ©2009-2018 SpryMedia Ltd - datatables.net/license
  3. */
  4. /**
  5. * @summary KeyTable
  6. * @description Spreadsheet like keyboard navigation for DataTables
  7. * @version 2.4.0
  8. * @file dataTables.keyTable.js
  9. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  10. * @contact www.sprymedia.co.uk/contact
  11. * @copyright Copyright 2009-2018 SpryMedia Ltd.
  12. *
  13. * This source file is free software, available under the following license:
  14. * MIT license - http://datatables.net/license/mit
  15. *
  16. * This source file is distributed in the hope that it will be useful, but
  17. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  18. * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
  19. *
  20. * For details please refer to: http://www.datatables.net
  21. */
  22. (function( factory ){
  23. if ( typeof define === 'function' && define.amd ) {
  24. // AMD
  25. define( ['jquery', 'datatables.net'], function ( $ ) {
  26. return factory( $, window, document );
  27. } );
  28. }
  29. else if ( typeof exports === 'object' ) {
  30. // CommonJS
  31. module.exports = function (root, $) {
  32. if ( ! root ) {
  33. root = window;
  34. }
  35. if ( ! $ || ! $.fn.dataTable ) {
  36. $ = require('datatables.net')(root, $).$;
  37. }
  38. return factory( $, root, root.document );
  39. };
  40. }
  41. else {
  42. // Browser
  43. factory( jQuery, window, document );
  44. }
  45. }(function( $, window, document, undefined ) {
  46. 'use strict';
  47. var DataTable = $.fn.dataTable;
  48. var KeyTable = function ( dt, opts ) {
  49. // Sanity check that we are using DataTables 1.10 or newer
  50. if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
  51. throw 'KeyTable requires DataTables 1.10.8 or newer';
  52. }
  53. // User and defaults configuration object
  54. this.c = $.extend( true, {},
  55. DataTable.defaults.keyTable,
  56. KeyTable.defaults,
  57. opts
  58. );
  59. // Internal settings
  60. this.s = {
  61. /** @type {DataTable.Api} DataTables' API instance */
  62. dt: new DataTable.Api( dt ),
  63. enable: true,
  64. /** @type {bool} Flag for if a draw is triggered by focus */
  65. focusDraw: false,
  66. /** @type {bool} Flag to indicate when waiting for a draw to happen.
  67. * Will ignore key presses at this point
  68. */
  69. waitingForDraw: false,
  70. /** @type {object} Information about the last cell that was focused */
  71. lastFocus: null
  72. };
  73. // DOM items
  74. this.dom = {
  75. };
  76. // Check if row reorder has already been initialised on this table
  77. var settings = this.s.dt.settings()[0];
  78. var exisiting = settings.keytable;
  79. if ( exisiting ) {
  80. return exisiting;
  81. }
  82. settings.keytable = this;
  83. this._constructor();
  84. };
  85. $.extend( KeyTable.prototype, {
  86. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  87. * API methods for DataTables API interface
  88. */
  89. /**
  90. * Blur the table's cell focus
  91. */
  92. blur: function ()
  93. {
  94. this._blur();
  95. },
  96. /**
  97. * Enable cell focus for the table
  98. *
  99. * @param {string} state Can be `true`, `false` or `-string navigation-only`
  100. */
  101. enable: function ( state )
  102. {
  103. this.s.enable = state;
  104. },
  105. /**
  106. * Focus on a cell
  107. * @param {integer} row Row index
  108. * @param {integer} column Column index
  109. */
  110. focus: function ( row, column )
  111. {
  112. this._focus( this.s.dt.cell( row, column ) );
  113. },
  114. /**
  115. * Is the cell focused
  116. * @param {object} cell Cell index to check
  117. * @returns {boolean} true if focused, false otherwise
  118. */
  119. focused: function ( cell )
  120. {
  121. var lastFocus = this.s.lastFocus;
  122. if ( ! lastFocus ) {
  123. return false;
  124. }
  125. var lastIdx = this.s.lastFocus.cell.index();
  126. return cell.row === lastIdx.row && cell.column === lastIdx.column;
  127. },
  128. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  129. * Constructor
  130. */
  131. /**
  132. * Initialise the KeyTable instance
  133. *
  134. * @private
  135. */
  136. _constructor: function ()
  137. {
  138. this._tabInput();
  139. var that = this;
  140. var dt = this.s.dt;
  141. var table = $( dt.table().node() );
  142. // Need to be able to calculate the cell positions relative to the table
  143. if ( table.css('position') === 'static' ) {
  144. table.css( 'position', 'relative' );
  145. }
  146. // Click to focus
  147. $( dt.table().body() ).on( 'click.keyTable', 'th, td', function (e) {
  148. if ( that.s.enable === false ) {
  149. return;
  150. }
  151. var cell = dt.cell( this );
  152. if ( ! cell.any() ) {
  153. return;
  154. }
  155. that._focus( cell, null, false, e );
  156. } );
  157. // Key events
  158. $( document ).on( 'keydown.keyTable', function (e) {
  159. that._key( e );
  160. } );
  161. // Click blur
  162. if ( this.c.blurable ) {
  163. $( document ).on( 'mousedown.keyTable', function ( e ) {
  164. // Click on the search input will blur focus
  165. if ( $(e.target).parents( '.dataTables_filter' ).length ) {
  166. that._blur();
  167. }
  168. // If the click was inside the DataTables container, don't blur
  169. if ( $(e.target).parents().filter( dt.table().container() ).length ) {
  170. return;
  171. }
  172. // Don't blur in Editor form
  173. if ( $(e.target).parents('div.DTE').length ) {
  174. return;
  175. }
  176. // Or an Editor date input
  177. if ( $(e.target).parents('div.editor-datetime').length ) {
  178. return;
  179. }
  180. //If the click was inside the fixed columns container, don't blur
  181. if ( $(e.target).parents().filter('.DTFC_Cloned').length ) {
  182. return;
  183. }
  184. that._blur();
  185. } );
  186. }
  187. if ( this.c.editor ) {
  188. var editor = this.c.editor;
  189. // Need to disable KeyTable when the main editor is shown
  190. editor.on( 'open.keyTableMain', function (e, mode, action) {
  191. if ( mode !== 'inline' && that.s.enable ) {
  192. that.enable( false );
  193. editor.one( 'close.keyTable', function () {
  194. that.enable( true );
  195. } );
  196. }
  197. } );
  198. if ( this.c.editOnFocus ) {
  199. dt.on( 'key-focus.keyTable key-refocus.keyTable', function ( e, dt, cell, orig ) {
  200. that._editor( null, orig );
  201. } );
  202. }
  203. // Activate Editor when a key is pressed (will be ignored, if
  204. // already active).
  205. dt.on( 'key.keyTable', function ( e, dt, key, cell, orig ) {
  206. that._editor( key, orig );
  207. } );
  208. }
  209. // Stave saving
  210. if ( dt.settings()[0].oFeatures.bStateSave ) {
  211. dt.on( 'stateSaveParams.keyTable', function (e, s, d) {
  212. d.keyTable = that.s.lastFocus ?
  213. that.s.lastFocus.cell.index() :
  214. null;
  215. } );
  216. }
  217. // Redraw - retain focus on the current cell
  218. dt.on( 'draw.keyTable', function (e) {
  219. if ( that.s.focusDraw ) {
  220. return;
  221. }
  222. var lastFocus = that.s.lastFocus;
  223. if ( lastFocus && lastFocus.node && $(lastFocus.node).closest('body') === document.body ) {
  224. var relative = that.s.lastFocus.relative;
  225. var info = dt.page.info();
  226. var row = relative.row + info.start;
  227. if ( info.recordsDisplay === 0 ) {
  228. return;
  229. }
  230. // Reverse if needed
  231. if ( row >= info.recordsDisplay ) {
  232. row = info.recordsDisplay - 1;
  233. }
  234. that._focus( row, relative.column, true, e );
  235. }
  236. } );
  237. // Clipboard support
  238. if ( this.c.clipboard ) {
  239. this._clipboard();
  240. }
  241. dt.on( 'destroy.keyTable', function () {
  242. dt.off( '.keyTable' );
  243. $( dt.table().body() ).off( 'click.keyTable', 'th, td' );
  244. $( document )
  245. .off( 'keydown.keyTable' )
  246. .off( 'click.keyTable' )
  247. .off( 'copy.keyTable' )
  248. .off( 'paste.keyTable' );
  249. } );
  250. // Initial focus comes from state or options
  251. var state = dt.state.loaded();
  252. if ( state && state.keyTable ) {
  253. // Wait until init is done
  254. dt.one( 'init', function () {
  255. var cell = dt.cell( state.keyTable );
  256. // Ensure that the saved cell still exists
  257. if ( cell.any() ) {
  258. cell.focus();
  259. }
  260. } );
  261. }
  262. else if ( this.c.focus ) {
  263. dt.cell( this.c.focus ).focus();
  264. }
  265. },
  266. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  267. * Private methods
  268. */
  269. /**
  270. * Blur the control
  271. *
  272. * @private
  273. */
  274. _blur: function ()
  275. {
  276. if ( ! this.s.enable || ! this.s.lastFocus ) {
  277. return;
  278. }
  279. var cell = this.s.lastFocus.cell;
  280. $( cell.node() ).removeClass( this.c.className );
  281. this.s.lastFocus = null;
  282. this._updateFixedColumns(cell.index().column);
  283. this._emitEvent( 'key-blur', [ this.s.dt, cell ] );
  284. },
  285. /**
  286. * Clipboard interaction handlers
  287. *
  288. * @private
  289. */
  290. _clipboard: function () {
  291. var dt = this.s.dt;
  292. var that = this;
  293. // IE8 doesn't support getting selected text
  294. if ( ! window.getSelection ) {
  295. return;
  296. }
  297. $(document).on( 'copy.keyTable', function (ejq) {
  298. var e = ejq.originalEvent;
  299. var selection = window.getSelection().toString();
  300. var focused = that.s.lastFocus;
  301. // Only copy cell text to clipboard if there is no other selection
  302. // and there is a focused cell
  303. if ( ! selection && focused ) {
  304. e.clipboardData.setData(
  305. 'text/plain',
  306. focused.cell.render( that.c.clipboardOrthogonal )
  307. );
  308. e.preventDefault();
  309. }
  310. } );
  311. $(document).on( 'paste.keyTable', function (ejq) {
  312. var e = ejq.originalEvent;
  313. var focused = that.s.lastFocus;
  314. var activeEl = document.activeElement;
  315. var editor = that.c.editor;
  316. var pastedText;
  317. if ( focused && (! activeEl || activeEl.nodeName.toLowerCase() === 'body') ) {
  318. e.preventDefault();
  319. if ( window.clipboardData && window.clipboardData.getData ) {
  320. // IE
  321. pastedText = window.clipboardData.getData('Text');
  322. }
  323. else if ( e.clipboardData && e.clipboardData.getData ) {
  324. // Everything else
  325. pastedText = e.clipboardData.getData('text/plain');
  326. }
  327. if ( editor ) {
  328. // Got Editor - need to activate inline editing,
  329. // set the value and submit
  330. editor
  331. .inline( focused.cell.index() )
  332. .set( editor.displayed()[0], pastedText )
  333. .submit();
  334. }
  335. else {
  336. // No editor, so just dump the data in
  337. focused.cell.data( pastedText );
  338. dt.draw(false);
  339. }
  340. }
  341. } );
  342. },
  343. /**
  344. * Get an array of the column indexes that KeyTable can operate on. This
  345. * is a merge of the user supplied columns and the visible columns.
  346. *
  347. * @private
  348. */
  349. _columns: function ()
  350. {
  351. var dt = this.s.dt;
  352. var user = dt.columns( this.c.columns ).indexes();
  353. var out = [];
  354. dt.columns( ':visible' ).every( function (i) {
  355. if ( user.indexOf( i ) !== -1 ) {
  356. out.push( i );
  357. }
  358. } );
  359. return out;
  360. },
  361. /**
  362. * Perform excel like navigation for Editor by triggering an edit on key
  363. * press
  364. *
  365. * @param {integer} key Key code for the pressed key
  366. * @param {object} orig Original event
  367. * @private
  368. */
  369. _editor: function ( key, orig )
  370. {
  371. var that = this;
  372. var dt = this.s.dt;
  373. var editor = this.c.editor;
  374. // Do nothing if there is already an inline edit in this cell
  375. if ( $('div.DTE', this.s.lastFocus.cell.node()).length ) {
  376. return;
  377. }
  378. // Don't activate Editor on control key presses
  379. if (
  380. (key >= 0x00 && key <= 0x09) ||
  381. key === 0x0b ||
  382. key === 0x0c ||
  383. (key >= 0x0e && key <= 0x1f) ||
  384. (key >= 0x70 && key <= 0x7b) ||
  385. (key >= 0x7f && key <= 0x9f)
  386. ) {
  387. return;
  388. }
  389. orig.stopPropagation();
  390. // Return key should do nothing - for textareas it would empty the
  391. // contents
  392. if ( key === 13 ) {
  393. orig.preventDefault();
  394. }
  395. var editInline = function () {
  396. editor
  397. .one( 'open.keyTable', function () {
  398. // Remove cancel open
  399. editor.off( 'cancelOpen.keyTable' );
  400. // Excel style - select all text
  401. if ( that.c.editAutoSelect ) {
  402. $('div.DTE_Field_InputControl input, div.DTE_Field_InputControl textarea').select();
  403. }
  404. // Reduce the keys the Keys listens for
  405. dt.keys.enable( that.c.editorKeys );
  406. // On blur of the navigation submit
  407. dt.one( 'key-blur.editor', function () {
  408. if ( editor.displayed() ) {
  409. editor.submit();
  410. }
  411. } );
  412. // Restore full key navigation on close
  413. editor.one( 'close', function () {
  414. dt.keys.enable( true );
  415. dt.off( 'key-blur.editor' );
  416. } );
  417. } )
  418. .one( 'cancelOpen.keyTable', function () {
  419. // `preOpen` can cancel the display of the form, so it
  420. // might be that the open event handler isn't needed
  421. editor.off( 'open.keyTable' );
  422. } )
  423. .inline( that.s.lastFocus.cell.index() );
  424. };
  425. // Editor 1.7 listens for `return` on keyup, so if return is the trigger
  426. // key, we need to wait for `keyup` otherwise Editor would just submit
  427. // the content triggered by this keypress.
  428. if ( key === 13 ) {
  429. $(document).one( 'keyup', function () { // immediately removed
  430. editInline();
  431. } );
  432. }
  433. else {
  434. editInline();
  435. }
  436. },
  437. /**
  438. * Emit an event on the DataTable for listeners
  439. *
  440. * @param {string} name Event name
  441. * @param {array} args Event arguments
  442. * @private
  443. */
  444. _emitEvent: function ( name, args )
  445. {
  446. this.s.dt.iterator( 'table', function ( ctx, i ) {
  447. $(ctx.nTable).triggerHandler( name, args );
  448. } );
  449. },
  450. /**
  451. * Focus on a particular cell, shifting the table's paging if required
  452. *
  453. * @param {DataTables.Api|integer} row Can be given as an API instance that
  454. * contains the cell to focus or as an integer. As the latter it is the
  455. * visible row index (from the whole data set) - NOT the data index
  456. * @param {integer} [column] Not required if a cell is given as the first
  457. * parameter. Otherwise this is the column data index for the cell to
  458. * focus on
  459. * @param {boolean} [shift=true] Should the viewport be moved to show cell
  460. * @private
  461. */
  462. _focus: function ( row, column, shift, originalEvent )
  463. {
  464. var that = this;
  465. var dt = this.s.dt;
  466. var pageInfo = dt.page.info();
  467. var lastFocus = this.s.lastFocus;
  468. if ( ! originalEvent) {
  469. originalEvent = null;
  470. }
  471. if ( ! this.s.enable ) {
  472. return;
  473. }
  474. if ( typeof row !== 'number' ) {
  475. // Its an API instance - check that there is actually a row
  476. if ( ! row.any() ) {
  477. return;
  478. }
  479. // Convert the cell to a row and column
  480. var index = row.index();
  481. column = index.column;
  482. row = dt
  483. .rows( { filter: 'applied', order: 'applied' } )
  484. .indexes()
  485. .indexOf( index.row );
  486. // For server-side processing normalise the row by adding the start
  487. // point, since `rows().indexes()` includes only rows that are
  488. // available at the client-side
  489. if ( pageInfo.serverSide ) {
  490. row += pageInfo.start;
  491. }
  492. }
  493. // Is the row on the current page? If not, we need to redraw to show the
  494. // page
  495. if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start+pageInfo.length) ) {
  496. this.s.focusDraw = true;
  497. this.s.waitingForDraw = true;
  498. dt
  499. .one( 'draw', function () {
  500. that.s.focusDraw = false;
  501. that.s.waitingForDraw = false;
  502. that._focus( row, column, undefined, originalEvent );
  503. } )
  504. .page( Math.floor( row / pageInfo.length ) )
  505. .draw( false );
  506. return;
  507. }
  508. // In the available columns?
  509. if ( $.inArray( column, this._columns() ) === -1 ) {
  510. return;
  511. }
  512. // De-normalise the server-side processing row, so we select the row
  513. // in its displayed position
  514. if ( pageInfo.serverSide ) {
  515. row -= pageInfo.start;
  516. }
  517. // Get the cell from the current position - ignoring any cells which might
  518. // not have been rendered (therefore can't use `:eq()` selector).
  519. var cells = dt.cells( null, column, {search: 'applied', order: 'applied'} ).flatten();
  520. var cell = dt.cell( cells[ row ] );
  521. if ( lastFocus ) {
  522. // Don't trigger a refocus on the same cell
  523. if ( lastFocus.node === cell.node() ) {
  524. this._emitEvent( 'key-refocus', [ this.s.dt, cell, originalEvent || null ] );
  525. return;
  526. }
  527. // Otherwise blur the old focus
  528. this._blur();
  529. }
  530. var node = $( cell.node() );
  531. node.addClass( this.c.className );
  532. this._updateFixedColumns(column);
  533. // Shift viewpoint and page to make cell visible
  534. if ( shift === undefined || shift === true ) {
  535. this._scroll( $(window), $(document.body), node, 'offset' );
  536. var bodyParent = dt.table().body().parentNode;
  537. if ( bodyParent !== dt.table().header().parentNode ) {
  538. var parent = $(bodyParent.parentNode);
  539. this._scroll( parent, parent, node, 'position' );
  540. }
  541. }
  542. // Event and finish
  543. this.s.lastFocus = {
  544. cell: cell,
  545. node: cell.node(),
  546. relative: {
  547. row: dt.rows( { page: 'current' } ).indexes().indexOf( cell.index().row ),
  548. column: cell.index().column
  549. }
  550. };
  551. this._emitEvent( 'key-focus', [ this.s.dt, cell, originalEvent || null ] );
  552. dt.state.save();
  553. },
  554. /**
  555. * Handle key press
  556. *
  557. * @param {object} e Event
  558. * @private
  559. */
  560. _key: function ( e )
  561. {
  562. // If we are waiting for a draw to happen from another key event, then
  563. // do nothing for this new key press.
  564. if ( this.s.waitingForDraw ) {
  565. e.preventDefault();
  566. return;
  567. }
  568. var enable = this.s.enable;
  569. var navEnable = enable === true || enable === 'navigation-only';
  570. if ( ! enable ) {
  571. return;
  572. }
  573. if ( (e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey) && !(e.ctrlKey && e.altKey) ) {
  574. return;
  575. }
  576. // If not focused, then there is no key action to take
  577. var lastFocus = this.s.lastFocus;
  578. if ( ! lastFocus ) {
  579. return;
  580. }
  581. var that = this;
  582. var dt = this.s.dt;
  583. var scrolling = this.s.dt.settings()[0].oScroll.sY ? true : false;
  584. // If we are not listening for this key, do nothing
  585. if ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) {
  586. return;
  587. }
  588. switch( e.keyCode ) {
  589. case 9: // tab
  590. // `enable` can be tab-only
  591. this._shift( e, e.shiftKey ? 'left' : 'right', true );
  592. break;
  593. case 27: // esc
  594. if ( this.s.blurable && enable === true ) {
  595. this._blur();
  596. }
  597. break;
  598. case 33: // page up (previous page)
  599. case 34: // page down (next page)
  600. if ( navEnable && !scrolling ) {
  601. e.preventDefault();
  602. dt
  603. .page( e.keyCode === 33 ? 'previous' : 'next' )
  604. .draw( false );
  605. }
  606. break;
  607. case 35: // end (end of current page)
  608. case 36: // home (start of current page)
  609. if ( navEnable ) {
  610. e.preventDefault();
  611. var indexes = dt.cells( {page: 'current'} ).indexes();
  612. var colIndexes = this._columns();
  613. this._focus( dt.cell(
  614. indexes[ e.keyCode === 35 ? indexes.length-1 : colIndexes[0] ]
  615. ), null, true, e );
  616. }
  617. break;
  618. case 37: // left arrow
  619. if ( navEnable ) {
  620. this._shift( e, 'left' );
  621. }
  622. break;
  623. case 38: // up arrow
  624. if ( navEnable ) {
  625. this._shift( e, 'up' );
  626. }
  627. break;
  628. case 39: // right arrow
  629. if ( navEnable ) {
  630. this._shift( e, 'right' );
  631. }
  632. break;
  633. case 40: // down arrow
  634. if ( navEnable ) {
  635. this._shift( e, 'down' );
  636. }
  637. break;
  638. default:
  639. // Everything else - pass through only when fully enabled
  640. if ( enable === true ) {
  641. this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus.cell, e ] );
  642. }
  643. break;
  644. }
  645. },
  646. /**
  647. * Scroll a container to make a cell visible in it. This can be used for
  648. * both DataTables scrolling and native window scrolling.
  649. *
  650. * @param {jQuery} container Scrolling container
  651. * @param {jQuery} scroller Item being scrolled
  652. * @param {jQuery} cell Cell in the scroller
  653. * @param {string} posOff `position` or `offset` - which to use for the
  654. * calculation. `offset` for the document, otherwise `position`
  655. * @private
  656. */
  657. _scroll: function ( container, scroller, cell, posOff )
  658. {
  659. var offset = cell[posOff]();
  660. var height = cell.outerHeight();
  661. var width = cell.outerWidth();
  662. var scrollTop = scroller.scrollTop();
  663. var scrollLeft = scroller.scrollLeft();
  664. var containerHeight = container.height();
  665. var containerWidth = container.width();
  666. // If Scroller is being used, the table can be `position: absolute` and that
  667. // needs to be taken account of in the offset. If no Scroller, this will be 0
  668. if ( posOff === 'position' ) {
  669. offset.top += parseInt( cell.closest('table').css('top'), 10 );
  670. }
  671. // Top correction
  672. if ( offset.top < scrollTop ) {
  673. scroller.scrollTop( offset.top );
  674. }
  675. // Left correction
  676. if ( offset.left < scrollLeft ) {
  677. scroller.scrollLeft( offset.left );
  678. }
  679. // Bottom correction
  680. if ( offset.top + height > scrollTop + containerHeight && height < containerHeight ) {
  681. scroller.scrollTop( offset.top + height - containerHeight );
  682. }
  683. // Right correction
  684. if ( offset.left + width > scrollLeft + containerWidth && width < containerWidth ) {
  685. scroller.scrollLeft( offset.left + width - containerWidth );
  686. }
  687. },
  688. /**
  689. * Calculate a single offset movement in the table - up, down, left and
  690. * right and then perform the focus if possible
  691. *
  692. * @param {object} e Event object
  693. * @param {string} direction Movement direction
  694. * @param {boolean} keyBlurable `true` if the key press can result in the
  695. * table being blurred. This is so arrow keys won't blur the table, but
  696. * tab will.
  697. * @private
  698. */
  699. _shift: function ( e, direction, keyBlurable )
  700. {
  701. var that = this;
  702. var dt = this.s.dt;
  703. var pageInfo = dt.page.info();
  704. var rows = pageInfo.recordsDisplay;
  705. var currentCell = this.s.lastFocus.cell;
  706. var columns = this._columns();
  707. if ( ! currentCell ) {
  708. return;
  709. }
  710. var currRow = dt
  711. .rows( { filter: 'applied', order: 'applied' } )
  712. .indexes()
  713. .indexOf( currentCell.index().row );
  714. // When server-side processing, `rows().indexes()` only gives the rows
  715. // that are available at the client-side, so we need to normalise the
  716. // row's current position by the display start point
  717. if ( pageInfo.serverSide ) {
  718. currRow += pageInfo.start;
  719. }
  720. var currCol = dt
  721. .columns( columns )
  722. .indexes()
  723. .indexOf( currentCell.index().column );
  724. var
  725. row = currRow,
  726. column = columns[ currCol ]; // row is the display, column is an index
  727. if ( direction === 'right' ) {
  728. if ( currCol >= columns.length - 1 ) {
  729. row++;
  730. column = columns[0];
  731. }
  732. else {
  733. column = columns[ currCol+1 ];
  734. }
  735. }
  736. else if ( direction === 'left' ) {
  737. if ( currCol === 0 ) {
  738. row--;
  739. column = columns[ columns.length - 1 ];
  740. }
  741. else {
  742. column = columns[ currCol-1 ];
  743. }
  744. }
  745. else if ( direction === 'up' ) {
  746. row--;
  747. }
  748. else if ( direction === 'down' ) {
  749. row++;
  750. }
  751. if ( row >= 0 && row < rows && $.inArray( column, columns ) !== -1
  752. ) {
  753. e.preventDefault();
  754. this._focus( row, column, true, e );
  755. }
  756. else if ( ! keyBlurable || ! this.c.blurable ) {
  757. // No new focus, but if the table isn't blurable, then don't loose
  758. // focus
  759. e.preventDefault();
  760. }
  761. else {
  762. this._blur();
  763. }
  764. },
  765. /**
  766. * Create a hidden input element that can receive focus on behalf of the
  767. * table
  768. *
  769. * @private
  770. */
  771. _tabInput: function ()
  772. {
  773. var that = this;
  774. var dt = this.s.dt;
  775. var tabIndex = this.c.tabIndex !== null ?
  776. this.c.tabIndex :
  777. dt.settings()[0].iTabIndex;
  778. if ( tabIndex == -1 ) {
  779. return;
  780. }
  781. var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>')
  782. .css( {
  783. position: 'absolute',
  784. height: 1,
  785. width: 0,
  786. overflow: 'hidden'
  787. } )
  788. .insertBefore( dt.table().node() );
  789. div.children().on( 'focus', function (e) {
  790. if ( dt.cell(':eq(0)', {page: 'current'}).any() ) {
  791. that._focus( dt.cell(':eq(0)', '0:visible', {page: 'current'}), null, true, e );
  792. }
  793. } );
  794. },
  795. /**
  796. * Update fixed columns if they are enabled and if the cell we are
  797. * focusing is inside a fixed column
  798. * @param {integer} column Index of the column being changed
  799. * @private
  800. */
  801. _updateFixedColumns: function( column )
  802. {
  803. var dt = this.s.dt;
  804. var settings = dt.settings()[0];
  805. if ( settings._oFixedColumns ) {
  806. var leftCols = settings._oFixedColumns.s.iLeftColumns;
  807. var rightCols = settings.aoColumns.length - settings._oFixedColumns.s.iRightColumns;
  808. if (column < leftCols || column >= rightCols) {
  809. dt.fixedColumns().update();
  810. }
  811. }
  812. }
  813. } );
  814. /**
  815. * KeyTable default settings for initialisation
  816. *
  817. * @namespace
  818. * @name KeyTable.defaults
  819. * @static
  820. */
  821. KeyTable.defaults = {
  822. /**
  823. * Can focus be removed from the table
  824. * @type {Boolean}
  825. */
  826. blurable: true,
  827. /**
  828. * Class to give to the focused cell
  829. * @type {String}
  830. */
  831. className: 'focus',
  832. /**
  833. * Enable or disable clipboard support
  834. * @type {Boolean}
  835. */
  836. clipboard: true,
  837. /**
  838. * Orthogonal data that should be copied to clipboard
  839. * @type {string}
  840. */
  841. clipboardOrthogonal: 'display',
  842. /**
  843. * Columns that can be focused. This is automatically merged with the
  844. * visible columns as only visible columns can gain focus.
  845. * @type {String}
  846. */
  847. columns: '', // all
  848. /**
  849. * Editor instance to automatically perform Excel like navigation
  850. * @type {Editor}
  851. */
  852. editor: null,
  853. /**
  854. * Option that defines what KeyTable's behaviour will be when used with
  855. * Editor's inline editing. Can be `navigation-only` or `tab-only`.
  856. * @type {String}
  857. */
  858. editorKeys: 'navigation-only',
  859. /**
  860. * Set if Editor should automatically select the text in the input
  861. * @type {Boolean}
  862. */
  863. editAutoSelect: true,
  864. /**
  865. * Control if editing should be activated immediately upon focus
  866. * @type {Boolean}
  867. */
  868. editOnFocus: false,
  869. /**
  870. * Select a cell to automatically select on start up. `null` for no
  871. * automatic selection
  872. * @type {cell-selector}
  873. */
  874. focus: null,
  875. /**
  876. * Array of keys to listen for
  877. * @type {null|array}
  878. */
  879. keys: null,
  880. /**
  881. * Tab index for where the table should sit in the document's tab flow
  882. * @type {integer|null}
  883. */
  884. tabIndex: null
  885. };
  886. KeyTable.version = "2.4.0";
  887. $.fn.dataTable.KeyTable = KeyTable;
  888. $.fn.DataTable.KeyTable = KeyTable;
  889. DataTable.Api.register( 'cell.blur()', function () {
  890. return this.iterator( 'table', function (ctx) {
  891. if ( ctx.keytable ) {
  892. ctx.keytable.blur();
  893. }
  894. } );
  895. } );
  896. DataTable.Api.register( 'cell().focus()', function () {
  897. return this.iterator( 'cell', function (ctx, row, column) {
  898. if ( ctx.keytable ) {
  899. ctx.keytable.focus( row, column );
  900. }
  901. } );
  902. } );
  903. DataTable.Api.register( 'keys.disable()', function () {
  904. return this.iterator( 'table', function (ctx) {
  905. if ( ctx.keytable ) {
  906. ctx.keytable.enable( false );
  907. }
  908. } );
  909. } );
  910. DataTable.Api.register( 'keys.enable()', function ( opts ) {
  911. return this.iterator( 'table', function (ctx) {
  912. if ( ctx.keytable ) {
  913. ctx.keytable.enable( opts === undefined ? true : opts );
  914. }
  915. } );
  916. } );
  917. // Cell selector
  918. DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
  919. var focused = opts.focused;
  920. var kt = settings.keytable;
  921. var out = [];
  922. if ( ! kt || focused === undefined ) {
  923. return cells;
  924. }
  925. for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
  926. if ( (focused === true && kt.focused( cells[i] ) ) ||
  927. (focused === false && ! kt.focused( cells[i] ) )
  928. ) {
  929. out.push( cells[i] );
  930. }
  931. }
  932. return out;
  933. } );
  934. // Attach a listener to the document which listens for DataTables initialisation
  935. // events so we can automatically initialise
  936. $(document).on( 'preInit.dt.dtk', function (e, settings, json) {
  937. if ( e.namespace !== 'dt' ) {
  938. return;
  939. }
  940. var init = settings.oInit.keys;
  941. var defaults = DataTable.defaults.keys;
  942. if ( init || defaults ) {
  943. var opts = $.extend( {}, defaults, init );
  944. if ( init !== false ) {
  945. new KeyTable( settings, opts );
  946. }
  947. }
  948. } );
  949. return KeyTable;
  950. }));