dataTables.keyTable.js 29 KB

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