dataTables.keyTable.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. /*! KeyTable 2.1.2
  2. * ©2009-2016 SpryMedia Ltd - datatables.net/license
  3. */
  4. /**
  5. * @summary KeyTable
  6. * @description Spreadsheet like keyboard navigation for DataTables
  7. * @version 2.1.2
  8. * @file dataTables.keyTable.js
  9. * @author SpryMedia Ltd (www.sprymedia.co.uk)
  10. * @contact www.sprymedia.co.uk/contact
  11. * @copyright Copyright 2009-2016 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. };
  67. // DOM items
  68. this.dom = {
  69. };
  70. // Check if row reorder has already been initialised on this table
  71. var settings = this.s.dt.settings()[0];
  72. var exisiting = settings.keytable;
  73. if ( exisiting ) {
  74. return exisiting;
  75. }
  76. settings.keytable = this;
  77. this._constructor();
  78. };
  79. $.extend( KeyTable.prototype, {
  80. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  81. * API methods for DataTables API interface
  82. */
  83. /**
  84. * Blur the table's cell focus
  85. */
  86. blur: function ()
  87. {
  88. this._blur();
  89. },
  90. /**
  91. * Enable cell focus for the table
  92. *
  93. * @param {string} state Can be `true`, `false` or `-string navigation-only`
  94. */
  95. enable: function ( state )
  96. {
  97. this.s.enable = state;
  98. },
  99. /**
  100. * Focus on a cell
  101. * @param {integer} row Row index
  102. * @param {integer} column Column index
  103. */
  104. focus: function ( row, column )
  105. {
  106. this._focus( this.s.dt.cell( row, column ) );
  107. },
  108. /**
  109. * Is the cell focused
  110. * @param {object} cell Cell index to check
  111. * @returns {boolean} true if focused, false otherwise
  112. */
  113. focused: function ( cell )
  114. {
  115. var lastFocus = this.s.lastFocus;
  116. if ( ! lastFocus ) {
  117. return false;
  118. }
  119. var lastIdx = this.s.lastFocus.index();
  120. return cell.row === lastIdx.row && cell.column === lastIdx.column;
  121. },
  122. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  123. * Constructor
  124. */
  125. /**
  126. * Initialise the KeyTable instance
  127. *
  128. * @private
  129. */
  130. _constructor: function ()
  131. {
  132. this._tabInput();
  133. var that = this;
  134. var dt = this.s.dt;
  135. var table = $( dt.table().node() );
  136. // Need to be able to calculate the cell positions relative to the table
  137. if ( table.css('position') === 'static' ) {
  138. table.css( 'position', 'relative' );
  139. }
  140. // Click to focus
  141. $( dt.table().body() ).on( 'click.keyTable', 'th, td', function () {
  142. if ( that.s.enable === false ) {
  143. return;
  144. }
  145. var cell = dt.cell( this );
  146. if ( ! cell.any() ) {
  147. return;
  148. }
  149. that._focus( cell, null, false );
  150. } );
  151. // Key events
  152. $( document ).on( 'keydown.keyTable', function (e) {
  153. that._key( e );
  154. } );
  155. // Click blur
  156. if ( this.c.blurable ) {
  157. $( document ).on( 'click.keyTable', function ( e ) {
  158. // Click on the search input will blur focus
  159. if ( $(e.target).parents( '.dataTables_filter' ).length ) {
  160. that._blur();
  161. }
  162. // If the click was inside the DataTables container, don't blur
  163. if ( $(e.target).parents().filter( dt.table().container() ).length ) {
  164. return;
  165. }
  166. // Don't blur in Editor form
  167. if ( $(e.target).parents('div.DTE').length ) {
  168. return;
  169. }
  170. that._blur();
  171. } );
  172. }
  173. if ( this.c.editor ) {
  174. dt.on( 'key.keyTable', function ( e, dt, key, cell, orig ) {
  175. that._editor( key, orig );
  176. } );
  177. }
  178. // Stave saving
  179. if ( dt.settings()[0].oFeatures.bStateSave ) {
  180. dt.on( 'stateSaveParams.keyTable', function (e, s, d) {
  181. d.keyTable = that.s.lastFocus ?
  182. that.s.lastFocus.index() :
  183. null;
  184. } );
  185. }
  186. // Reload - re-focus on the currently selected item. In SSP mode this
  187. // has the effect of keeping the focus in position when changing page as
  188. // well (which is different from how client-side processing works).
  189. dt.on( 'xhr.keyTable', function ( e ) {
  190. if ( that.s.focusDraw ) {
  191. // Triggered by server-side processing, and thus `_focus` will
  192. // do the refocus on the next draw event
  193. return;
  194. }
  195. var lastFocus = that.s.lastFocus;
  196. if ( lastFocus ) {
  197. that.s.lastFocus = null;
  198. dt.one( 'draw', function () {
  199. that._focus( lastFocus );
  200. } );
  201. }
  202. } );
  203. dt.on( 'destroy.keyTable', function () {
  204. dt.off( '.keyTable' );
  205. $( dt.table().body() ).off( 'click.keyTable', 'th, td' );
  206. $( document.body )
  207. .off( 'keydown.keyTable' )
  208. .off( 'click.keyTable' );
  209. } );
  210. // Initial focus comes from state or options
  211. var state = dt.state.loaded();
  212. if ( state && state.keyTable ) {
  213. // Wait until init is done
  214. dt.one( 'init', function () {
  215. var cell = dt.cell( state.keyTable );
  216. // Ensure that the saved cell still exists
  217. if ( cell.any() ) {
  218. cell.focus();
  219. }
  220. } );
  221. }
  222. else if ( this.c.focus ) {
  223. dt.cell( this.c.focus ).focus();
  224. }
  225. },
  226. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  227. * Private methods
  228. */
  229. /**
  230. * Blur the control
  231. *
  232. * @private
  233. */
  234. _blur: function ()
  235. {
  236. if ( ! this.s.enable || ! this.s.lastFocus ) {
  237. return;
  238. }
  239. var cell = this.s.lastFocus;
  240. $( cell.node() ).removeClass( this.c.className );
  241. this.s.lastFocus = null;
  242. this._emitEvent( 'key-blur', [ this.s.dt, cell ] );
  243. },
  244. /**
  245. * Get an array of the column indexes that KeyTable can operate on. This
  246. * is a merge of the user supplied columns and the visible columns.
  247. *
  248. * @private
  249. */
  250. _columns: function ()
  251. {
  252. var dt = this.s.dt;
  253. var user = dt.columns( this.c.columns ).indexes();
  254. var out = [];
  255. dt.columns( ':visible' ).every( function (i) {
  256. if ( user.indexOf( i ) !== -1 ) {
  257. out.push( i );
  258. }
  259. } );
  260. return out;
  261. },
  262. /**
  263. * Perform excel like navigation for Editor by triggering an edit on key
  264. * press
  265. *
  266. * @param {integer} key Key code for the pressed key
  267. * @param {object} orig Original event
  268. * @private
  269. */
  270. _editor: function ( key, orig )
  271. {
  272. var dt = this.s.dt;
  273. var editor = this.c.editor;
  274. orig.stopPropagation();
  275. // Return key should do nothing - for textareas's it would empty the
  276. // contents
  277. if ( key === 13 ) {
  278. orig.preventDefault();
  279. }
  280. editor.inline( this.s.lastFocus.index() );
  281. // Excel style - select all text
  282. var input = $('div.DTE input, div.DTE textarea');
  283. if ( input.length ) {
  284. input[0].select();
  285. }
  286. // Reduce the keys the Keys listens for
  287. dt.keys.enable( 'navigation-only' );
  288. // On blur of the navigation submit
  289. dt.one( 'key-blur.editor', function () {
  290. if ( editor.displayed() ) {
  291. editor.submit();
  292. }
  293. } );
  294. // Restore full key navigation on close
  295. editor.one( 'close', function () {
  296. dt.keys.enable( true );
  297. dt.off( 'key-blur.editor' );
  298. } );
  299. },
  300. /**
  301. * Emit an event on the DataTable for listeners
  302. *
  303. * @param {string} name Event name
  304. * @param {array} args Event arguments
  305. * @private
  306. */
  307. _emitEvent: function ( name, args )
  308. {
  309. this.s.dt.iterator( 'table', function ( ctx, i ) {
  310. $(ctx.nTable).triggerHandler( name, args );
  311. } );
  312. },
  313. /**
  314. * Focus on a particular cell, shifting the table's paging if required
  315. *
  316. * @param {DataTables.Api|integer} row Can be given as an API instance that
  317. * contains the cell to focus or as an integer. As the latter it is the
  318. * visible row index - NOT the data index
  319. * @param {integer} [column] Not required if a cell is given as the first
  320. * parameter. Otherwise this is the column data index for the cell to
  321. * focus on
  322. * @param {boolean} [shift=true] Should the viewport be moved to show cell
  323. * @private
  324. */
  325. _focus: function ( row, column, shift )
  326. {
  327. var that = this;
  328. var dt = this.s.dt;
  329. var pageInfo = dt.page.info();
  330. var lastFocus = this.s.lastFocus;
  331. if ( ! this.s.enable ) {
  332. return;
  333. }
  334. if ( typeof row !== 'number' ) {
  335. // Convert the cell to a row and column
  336. var index = row.index();
  337. column = index.column;
  338. row = dt
  339. .rows( { filter: 'applied', order: 'applied' } )
  340. .indexes()
  341. .indexOf( index.row );
  342. // For server-side processing normalise the row by adding the start
  343. // point, since `rows().indexes()` includes only rows that are
  344. // available at the client-side
  345. if ( pageInfo.serverSide ) {
  346. row += pageInfo.start;
  347. }
  348. }
  349. // Is the row on the current page? If not, we need to redraw to show the
  350. // page
  351. if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start+pageInfo.length) ) {
  352. this.s.focusDraw = true;
  353. dt
  354. .one( 'draw', function () {
  355. that.s.focusDraw = false;
  356. that._focus( row, column );
  357. } )
  358. .page( Math.floor( row / pageInfo.length ) )
  359. .draw( false );
  360. return;
  361. }
  362. // In the available columns?
  363. if ( $.inArray( column, this._columns() ) === -1 ) {
  364. return;
  365. }
  366. // De-normalise the server-side processing row, so we select the row
  367. // in its displayed position
  368. if ( pageInfo.serverSide ) {
  369. row -= pageInfo.start;
  370. }
  371. var cell = dt.cell( ':eq('+row+')', column, {search: 'applied'} );
  372. if ( lastFocus ) {
  373. // Don't trigger a refocus on the same cell
  374. if ( lastFocus.node() === cell.node() ) {
  375. return;
  376. }
  377. // Otherwise blur the old focus
  378. this._blur();
  379. }
  380. var node = $( cell.node() );
  381. node.addClass( this.c.className );
  382. // Shift viewpoint and page to make cell visible
  383. if ( shift === undefined || shift === true ) {
  384. this._scroll( $(window), $(document.body), node, 'offset' );
  385. var bodyParent = dt.table().body().parentNode;
  386. if ( bodyParent !== dt.table().header().parentNode ) {
  387. var parent = $(bodyParent.parentNode);
  388. this._scroll( parent, parent, node, 'position' );
  389. }
  390. }
  391. // Event and finish
  392. this.s.lastFocus = cell;
  393. this._emitEvent( 'key-focus', [ this.s.dt, cell ] );
  394. dt.state.save();
  395. },
  396. /**
  397. * Handle key press
  398. *
  399. * @param {object} e Event
  400. * @private
  401. */
  402. _key: function ( e )
  403. {
  404. if ( ! this.s.enable ) {
  405. return;
  406. }
  407. if ( e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey ) {
  408. return;
  409. }
  410. // If not focused, then there is no key action to take
  411. var cell = this.s.lastFocus;
  412. if ( ! cell ) {
  413. return;
  414. }
  415. var that = this;
  416. var dt = this.s.dt;
  417. // If we are not listening for this key, do nothing
  418. if ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) {
  419. return;
  420. }
  421. switch( e.keyCode ) {
  422. case 9: // tab
  423. this._shift( e, e.shiftKey ? 'left' : 'right', true );
  424. break;
  425. case 27: // esc
  426. if ( this.s.blurable && this.s.enable === true ) {
  427. this._blur();
  428. }
  429. break;
  430. case 33: // page up (previous page)
  431. case 34: // page down (next page)
  432. e.preventDefault();
  433. var index = dt.cells( {page: 'current'} ).nodes().indexOf( cell.node() );
  434. dt
  435. .one( 'draw', function () {
  436. var nodes = dt.cells( {page: 'current'} ).nodes();
  437. that._focus( dt.cell( index < nodes.length ?
  438. nodes[ index ] :
  439. nodes[ nodes.length-1 ]
  440. ) );
  441. } )
  442. .page( e.keyCode === 33 ? 'previous' : 'next' )
  443. .draw( false );
  444. break;
  445. case 35: // end (end of current page)
  446. case 36: // home (start of current page)
  447. e.preventDefault();
  448. var indexes = dt.cells( {page: 'current'} ).indexes();
  449. this._focus( dt.cell(
  450. indexes[ e.keyCode === 35 ? indexes.length-1 : 0 ]
  451. ) );
  452. break;
  453. case 37: // left arrow
  454. this._shift( e, 'left' );
  455. break;
  456. case 38: // up arrow
  457. this._shift( e, 'up' );
  458. break;
  459. case 39: // right arrow
  460. this._shift( e, 'right' );
  461. break;
  462. case 40: // down arrow
  463. this._shift( e, 'down' );
  464. break;
  465. default:
  466. // Everything else - pass through only when fully enabled
  467. if ( this.s.enable === true ) {
  468. this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus, e ] );
  469. }
  470. break;
  471. }
  472. },
  473. /**
  474. * Scroll a container to make a cell visible in it. This can be used for
  475. * both DataTables scrolling and native window scrolling.
  476. *
  477. * @param {jQuery} container Scrolling container
  478. * @param {jQuery} scroller Item being scrolled
  479. * @param {jQuery} cell Cell in the scroller
  480. * @param {string} posOff `position` or `offset` - which to use for the
  481. * calculation. `offset` for the document, otherwise `position`
  482. * @private
  483. */
  484. _scroll: function ( container, scroller, cell, posOff )
  485. {
  486. var offset = cell[posOff]();
  487. var height = cell.outerHeight();
  488. var width = cell.outerWidth();
  489. var scrollTop = scroller.scrollTop();
  490. var scrollLeft = scroller.scrollLeft();
  491. var containerHeight = container.height();
  492. var containerWidth = container.width();
  493. // Top correction
  494. if ( offset.top < scrollTop ) {
  495. scroller.scrollTop( offset.top );
  496. }
  497. // Left correction
  498. if ( offset.left < scrollLeft ) {
  499. scroller.scrollLeft( offset.left );
  500. }
  501. // Bottom correction
  502. if ( offset.top + height > scrollTop + containerHeight && height < containerHeight ) {
  503. scroller.scrollTop( offset.top + height - containerHeight );
  504. }
  505. // Right correction
  506. if ( offset.left + width > scrollLeft + containerWidth && width < containerWidth ) {
  507. scroller.scrollLeft( offset.left + width - containerWidth );
  508. }
  509. },
  510. /**
  511. * Calculate a single offset movement in the table - up, down, left and
  512. * right and then perform the focus if possible
  513. *
  514. * @param {object} e Event object
  515. * @param {string} direction Movement direction
  516. * @param {boolean} keyBlurable `true` if the key press can result in the
  517. * table being blurred. This is so arrow keys won't blur the table, but
  518. * tab will.
  519. * @private
  520. */
  521. _shift: function ( e, direction, keyBlurable )
  522. {
  523. var that = this;
  524. var dt = this.s.dt;
  525. var pageInfo = dt.page.info();
  526. var rows = pageInfo.recordsDisplay;
  527. var currentCell = this.s.lastFocus;
  528. var columns = this._columns();
  529. if ( ! currentCell ) {
  530. return;
  531. }
  532. var currRow = dt
  533. .rows( { filter: 'applied', order: 'applied' } )
  534. .indexes()
  535. .indexOf( currentCell.index().row );
  536. // When server-side processing, `rows().indexes()` only gives the rows
  537. // that are available at the client-side, so we need to normalise the
  538. // row's current position by the display start point
  539. if ( pageInfo.serverSide ) {
  540. currRow += pageInfo.start;
  541. }
  542. var currCol = dt
  543. .columns( columns )
  544. .indexes()
  545. .indexOf( currentCell.index().column );
  546. var
  547. row = currRow,
  548. column = columns[ currCol ]; // row is the display, column is an index
  549. if ( direction === 'right' ) {
  550. if ( currCol >= columns.length - 1 ) {
  551. row++;
  552. column = columns[0];
  553. }
  554. else {
  555. column = columns[ currCol+1 ];
  556. }
  557. }
  558. else if ( direction === 'left' ) {
  559. if ( currCol === 0 ) {
  560. row--;
  561. column = columns[ columns.length - 1 ];
  562. }
  563. else {
  564. column = columns[ currCol-1 ];
  565. }
  566. }
  567. else if ( direction === 'up' ) {
  568. row--;
  569. }
  570. else if ( direction === 'down' ) {
  571. row++;
  572. }
  573. if ( row >= 0 && row < rows && $.inArray( column, columns ) !== -1
  574. ) {
  575. e.preventDefault();
  576. this._focus( row, column );
  577. }
  578. else if ( ! keyBlurable || ! this.c.blurable ) {
  579. // No new focus, but if the table isn't blurable, then don't loose
  580. // focus
  581. e.preventDefault();
  582. }
  583. else {
  584. this._blur();
  585. }
  586. },
  587. /**
  588. * Create a hidden input element that can receive focus on behalf of the
  589. * table
  590. *
  591. * @private
  592. */
  593. _tabInput: function ()
  594. {
  595. var that = this;
  596. var dt = this.s.dt;
  597. var tabIndex = this.c.tabIndex !== null ?
  598. this.c.tabIndex :
  599. dt.settings()[0].iTabIndex;
  600. if ( tabIndex == -1 ) {
  601. return;
  602. }
  603. var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>')
  604. .css( {
  605. position: 'absolute',
  606. height: 1,
  607. width: 0,
  608. overflow: 'hidden'
  609. } )
  610. .insertBefore( dt.table().node() );
  611. div.children().on( 'focus', function () {
  612. that._focus( dt.cell(':eq(0)', '0:visible', {page: 'current'}) );
  613. } );
  614. }
  615. } );
  616. /**
  617. * KeyTable default settings for initialisation
  618. *
  619. * @namespace
  620. * @name KeyTable.defaults
  621. * @static
  622. */
  623. KeyTable.defaults = {
  624. /**
  625. * Can focus be removed from the table
  626. * @type {Boolean}
  627. */
  628. blurable: true,
  629. /**
  630. * Class to give to the focused cell
  631. * @type {String}
  632. */
  633. className: 'focus',
  634. /**
  635. * Columns that can be focused. This is automatically merged with the
  636. * visible columns as only visible columns can gain focus.
  637. * @type {String}
  638. */
  639. columns: '', // all
  640. /**
  641. * Editor instance to automatically perform Excel like navigation
  642. * @type {Editor}
  643. */
  644. editor: null,
  645. /**
  646. * Select a cell to automatically select on start up. `null` for no
  647. * automatic selection
  648. * @type {cell-selector}
  649. */
  650. focus: null,
  651. /**
  652. * Array of keys to listen for
  653. * @type {null|array}
  654. */
  655. keys: null,
  656. /**
  657. * Tab index for where the table should sit in the document's tab flow
  658. * @type {integer|null}
  659. */
  660. tabIndex: null
  661. };
  662. KeyTable.version = "2.1.2";
  663. $.fn.dataTable.KeyTable = KeyTable;
  664. $.fn.DataTable.KeyTable = KeyTable;
  665. DataTable.Api.register( 'cell.blur()', function () {
  666. return this.iterator( 'table', function (ctx) {
  667. if ( ctx.keytable ) {
  668. ctx.keytable.blur();
  669. }
  670. } );
  671. } );
  672. DataTable.Api.register( 'cell().focus()', function () {
  673. return this.iterator( 'cell', function (ctx, row, column) {
  674. if ( ctx.keytable ) {
  675. ctx.keytable.focus( row, column );
  676. }
  677. } );
  678. } );
  679. DataTable.Api.register( 'keys.disable()', function () {
  680. return this.iterator( 'table', function (ctx) {
  681. if ( ctx.keytable ) {
  682. ctx.keytable.enable( false );
  683. }
  684. } );
  685. } );
  686. DataTable.Api.register( 'keys.enable()', function ( opts ) {
  687. return this.iterator( 'table', function (ctx) {
  688. if ( ctx.keytable ) {
  689. ctx.keytable.enable( opts === undefined ? true : opts );
  690. }
  691. } );
  692. } );
  693. // Cell selector
  694. DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
  695. var focused = opts.focused;
  696. var kt = settings.keytable;
  697. var out = [];
  698. if ( ! kt || focused === undefined ) {
  699. return cells;
  700. }
  701. for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
  702. if ( (focused === true && kt.focused( cells[i] ) ) ||
  703. (focused === false && ! kt.focused( cells[i] ) )
  704. ) {
  705. out.push( cells[i] );
  706. }
  707. }
  708. return out;
  709. } );
  710. // Attach a listener to the document which listens for DataTables initialisation
  711. // events so we can automatically initialise
  712. $(document).on( 'preInit.dt.dtk', function (e, settings, json) {
  713. if ( e.namespace !== 'dt' ) {
  714. return;
  715. }
  716. var init = settings.oInit.keys;
  717. var defaults = DataTable.defaults.keys;
  718. if ( init || defaults ) {
  719. var opts = $.extend( {}, init, defaults );
  720. if ( init !== false ) {
  721. new KeyTable( settings, opts );
  722. }
  723. }
  724. } );
  725. return KeyTable;
  726. }));