Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

dataTables.keyTable.js 41KB

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