| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283 |
- /*! KeyTable 2.6.1
- * ©2009-2021 SpryMedia Ltd - datatables.net/license
- */
-
- /**
- * @summary KeyTable
- * @description Spreadsheet like keyboard navigation for DataTables
- * @version 2.6.1
- * @file dataTables.keyTable.js
- * @author SpryMedia Ltd (www.sprymedia.co.uk)
- * @contact www.sprymedia.co.uk/contact
- * @copyright Copyright 2009-2021 SpryMedia Ltd.
- *
- * This source file is free software, available under the following license:
- * MIT license - http://datatables.net/license/mit
- *
- * This source file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
- *
- * For details please refer to: http://www.datatables.net
- */
-
- (function (factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD
- define(['jquery', 'datatables.net'], function ($) {
- return factory($, window, document);
- });
- } else if (typeof exports === 'object') {
- // CommonJS
- module.exports = function (root, $) {
- if (!root) {
- root = window;
- }
-
- if (!$ || !$.fn.dataTable) {
- $ = require('datatables.net')(root, $).$;
- }
-
- return factory($, root, root.document);
- };
- } else {
- // Browser
- factory(jQuery, window, document);
- }
- }(function ($, window, document, undefined) {
- 'use strict';
- var DataTable = $.fn.dataTable;
- var namespaceCounter = 0;
- var editorNamespaceCounter = 0;
-
-
- var KeyTable = function (dt, opts) {
- // Sanity check that we are using DataTables 1.10 or newer
- if (!DataTable.versionCheck || !DataTable.versionCheck('1.10.8')) {
- throw 'KeyTable requires DataTables 1.10.8 or newer';
- }
-
- // User and defaults configuration object
- this.c = $.extend(true, {},
- DataTable.defaults.keyTable,
- KeyTable.defaults,
- opts
- );
-
- // Internal settings
- this.s = {
- /** @type {DataTable.Api} DataTables' API instance */
- dt: new DataTable.Api(dt),
-
- enable: true,
-
- /** @type {bool} Flag for if a draw is triggered by focus */
- focusDraw: false,
-
- /** @type {bool} Flag to indicate when waiting for a draw to happen.
- * Will ignore key presses at this point
- */
- waitingForDraw: false,
-
- /** @type {object} Information about the last cell that was focused */
- lastFocus: null,
-
- /** @type {string} Unique namespace per instance */
- namespace: '.keyTable-' + (namespaceCounter++),
-
- /** @type {Node} Input element for tabbing into the table */
- tabInput: null
- };
-
- // DOM items
- this.dom = {};
-
- // Check if row reorder has already been initialised on this table
- var settings = this.s.dt.settings()[0];
- var exisiting = settings.keytable;
- if (exisiting) {
- return exisiting;
- }
-
- settings.keytable = this;
- this._constructor();
- };
-
-
- $.extend(KeyTable.prototype, {
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * API methods for DataTables API interface
- */
-
- /**
- * Blur the table's cell focus
- */
- blur: function () {
- this._blur();
- },
-
- /**
- * Enable cell focus for the table
- *
- * @param {string} state Can be `true`, `false` or `-string navigation-only`
- */
- enable: function (state) {
- this.s.enable = state;
- },
-
- /**
- * Get enable status
- */
- enabled: function () {
- return this.s.enable;
- },
-
- /**
- * Focus on a cell
- * @param {integer} row Row index
- * @param {integer} column Column index
- */
- focus: function (row, column) {
- this._focus(this.s.dt.cell(row, column));
- },
-
- /**
- * Is the cell focused
- * @param {object} cell Cell index to check
- * @returns {boolean} true if focused, false otherwise
- */
- focused: function (cell) {
- var lastFocus = this.s.lastFocus;
-
- if (!lastFocus) {
- return false;
- }
-
- var lastIdx = this.s.lastFocus.cell.index();
- return cell.row === lastIdx.row && cell.column === lastIdx.column;
- },
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Constructor
- */
-
- /**
- * Initialise the KeyTable instance
- *
- * @private
- */
- _constructor: function () {
- this._tabInput();
-
- var that = this;
- var dt = this.s.dt;
- var table = $(dt.table().node());
- var namespace = this.s.namespace;
- var editorBlock = false;
-
- // Need to be able to calculate the cell positions relative to the table
- if (table.css('position') === 'static') {
- table.css('position', 'relative');
- }
-
- // Click to focus
- $(dt.table().body()).on('click' + namespace, 'th, td', function (e) {
- if (that.s.enable === false) {
- return;
- }
-
- var cell = dt.cell(this);
-
- if (!cell.any()) {
- return;
- }
-
- that._focus(cell, null, false, e);
- });
-
- // Key events
- $(document).on('keydown' + namespace, function (e) {
- if (!editorBlock) {
- that._key(e);
- }
- });
-
- // Click blur
- if (this.c.blurable) {
- $(document).on('mousedown' + namespace, function (e) {
- // Click on the search input will blur focus
- if ($(e.target).parents('.dataTables_filter').length) {
- that._blur();
- }
-
- // If the click was inside the DataTables container, don't blur
- if ($(e.target).parents().filter(dt.table().container()).length) {
- return;
- }
-
- // Don't blur in Editor form
- if ($(e.target).parents('div.DTE').length) {
- return;
- }
-
- // Or an Editor date input
- if (
- $(e.target).parents('div.editor-datetime').length ||
- $(e.target).parents('div.dt-datetime').length
- ) {
- return;
- }
-
- //If the click was inside the fixed columns container, don't blur
- if ($(e.target).parents().filter('.DTFC_Cloned').length) {
- return;
- }
-
- that._blur();
- });
- }
-
- if (this.c.editor) {
- var editor = this.c.editor;
-
- // Need to disable KeyTable when the main editor is shown
- editor.on('open.keyTableMain', function (e, mode, action) {
- if (mode !== 'inline' && that.s.enable) {
- that.enable(false);
-
- editor.one('close' + namespace, function () {
- that.enable(true);
- });
- }
- });
-
- if (this.c.editOnFocus) {
- dt.on('key-focus' + namespace + ' key-refocus' + namespace, function (e, dt, cell, orig) {
- that._editor(null, orig, true);
- });
- }
-
- // Activate Editor when a key is pressed (will be ignored, if
- // already active).
- dt.on('key' + namespace, function (e, dt, key, cell, orig) {
- that._editor(key, orig, false);
- });
-
- // Active editing on double click - it will already have focus from
- // the click event handler above
- $(dt.table().body()).on('dblclick' + namespace, 'th, td', function (e) {
- if (that.s.enable === false) {
- return;
- }
-
- var cell = dt.cell(this);
-
- if (!cell.any()) {
- return;
- }
-
- if (that.s.lastFocus && this !== that.s.lastFocus.cell.node()) {
- return;
- }
-
- that._editor(null, e, true);
- });
-
- // While Editor is busy processing, we don't want to process any key events
- editor
- .on('preSubmit', function () {
- editorBlock = true;
- })
- .on('preSubmitCancelled', function () {
- editorBlock = false;
- })
- .on('submitComplete', function () {
- editorBlock = false;
- });
- }
-
- // Stave saving
- if (dt.settings()[0].oFeatures.bStateSave) {
- dt.on('stateSaveParams' + namespace, function (e, s, d) {
- d.keyTable = that.s.lastFocus ?
- that.s.lastFocus.cell.index() :
- null;
- });
- }
-
- dt.on('column-visibility' + namespace, function (e) {
- that._tabInput();
- });
-
- // Redraw - retain focus on the current cell
- dt.on('draw' + namespace, function (e) {
- that._tabInput();
-
- if (that.s.focusDraw) {
- return;
- }
-
- var lastFocus = that.s.lastFocus;
-
- if (lastFocus) {
- var relative = that.s.lastFocus.relative;
- var info = dt.page.info();
- var row = relative.row + info.start;
-
- if (info.recordsDisplay === 0) {
- return;
- }
-
- // Reverse if needed
- if (row >= info.recordsDisplay) {
- row = info.recordsDisplay - 1;
- }
-
- that._focus(row, relative.column, true, e);
- }
- });
-
- // Clipboard support
- if (this.c.clipboard) {
- this._clipboard();
- }
-
- dt.on('destroy' + namespace, function () {
- that._blur(true);
-
- // Event tidy up
- dt.off(namespace);
-
- $(dt.table().body())
- .off('click' + namespace, 'th, td')
- .off('dblclick' + namespace, 'th, td');
-
- $(document)
- .off('mousedown' + namespace)
- .off('keydown' + namespace)
- .off('copy' + namespace)
- .off('paste' + namespace);
- });
-
- // Initial focus comes from state or options
- var state = dt.state.loaded();
-
- if (state && state.keyTable) {
- // Wait until init is done
- dt.one('init', function () {
- var cell = dt.cell(state.keyTable);
-
- // Ensure that the saved cell still exists
- if (cell.any()) {
- cell.focus();
- }
- });
- } else if (this.c.focus) {
- dt.cell(this.c.focus).focus();
- }
- },
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * Private methods
- */
-
- /**
- * Blur the control
- *
- * @param {boolean} [noEvents=false] Don't trigger updates / events (for destroying)
- * @private
- */
- _blur: function (noEvents) {
- if (!this.s.enable || !this.s.lastFocus) {
- return;
- }
-
- var cell = this.s.lastFocus.cell;
-
- $(cell.node()).removeClass(this.c.className);
- this.s.lastFocus = null;
-
- if (!noEvents) {
- this._updateFixedColumns(cell.index().column);
-
- this._emitEvent('key-blur', [this.s.dt, cell]);
- }
- },
-
-
- /**
- * Clipboard interaction handlers
- *
- * @private
- */
- _clipboard: function () {
- var dt = this.s.dt;
- var that = this;
- var namespace = this.s.namespace;
-
- // IE8 doesn't support getting selected text
- if (!window.getSelection) {
- return;
- }
-
- $(document).on('copy' + namespace, function (ejq) {
- var e = ejq.originalEvent;
- var selection = window.getSelection().toString();
- var focused = that.s.lastFocus;
-
- // Only copy cell text to clipboard if there is no other selection
- // and there is a focused cell
- if (!selection && focused) {
- e.clipboardData.setData(
- 'text/plain',
- focused.cell.render(that.c.clipboardOrthogonal)
- );
- e.preventDefault();
- }
- });
-
- $(document).on('paste' + namespace, function (ejq) {
- var e = ejq.originalEvent;
- var focused = that.s.lastFocus;
- var activeEl = document.activeElement;
- var editor = that.c.editor;
- var pastedText;
-
- if (focused && (!activeEl || activeEl.nodeName.toLowerCase() === 'body')) {
- e.preventDefault();
-
- if (window.clipboardData && window.clipboardData.getData) {
- // IE
- pastedText = window.clipboardData.getData('Text');
- } else if (e.clipboardData && e.clipboardData.getData) {
- // Everything else
- pastedText = e.clipboardData.getData('text/plain');
- }
-
- if (editor) {
- // Got Editor - need to activate inline editing,
- // set the value and submit
- editor
- .inline(focused.cell.index())
- .set(editor.displayed()[0], pastedText)
- .submit();
- } else {
- // No editor, so just dump the data in
- focused.cell.data(pastedText);
- dt.draw(false);
- }
- }
- });
- },
-
-
- /**
- * Get an array of the column indexes that KeyTable can operate on. This
- * is a merge of the user supplied columns and the visible columns.
- *
- * @private
- */
- _columns: function () {
- var dt = this.s.dt;
- var user = dt.columns(this.c.columns).indexes();
- var out = [];
-
- dt.columns(':visible').every(function (i) {
- if (user.indexOf(i) !== -1) {
- out.push(i);
- }
- });
-
- return out;
- },
-
-
- /**
- * Perform excel like navigation for Editor by triggering an edit on key
- * press
- *
- * @param {integer} key Key code for the pressed key
- * @param {object} orig Original event
- * @private
- */
- _editor: function (key, orig, hardEdit) {
- // If nothing focused, we can't take any action
- if (!this.s.lastFocus) {
- return;
- }
-
- // DataTables draw event
- if (orig && orig.type === 'draw') {
- return;
- }
-
- var that = this;
- var dt = this.s.dt;
- var editor = this.c.editor;
- var editCell = this.s.lastFocus.cell;
- var namespace = this.s.namespace + 'e' + editorNamespaceCounter++;
-
- // Do nothing if there is already an inline edit in this cell
- if ($('div.DTE', editCell.node()).length) {
- return;
- }
-
- // Don't activate Editor on control key presses
- if (key !== null && (
- (key >= 0x00 && key <= 0x09) ||
- key === 0x0b ||
- key === 0x0c ||
- (key >= 0x0e && key <= 0x1f) ||
- (key >= 0x70 && key <= 0x7b) ||
- (key >= 0x7f && key <= 0x9f)
- )) {
- return;
- }
-
- if (orig) {
- orig.stopPropagation();
-
- // Return key should do nothing - for textareas it would empty the
- // contents
- if (key === 13) {
- orig.preventDefault();
- }
- }
-
- var editInline = function () {
- editor
- .one('open' + namespace, function () {
- // Remove cancel open
- editor.off('cancelOpen' + namespace);
-
- // Excel style - select all text
- if (!hardEdit) {
- $('div.DTE_Field_InputControl input, div.DTE_Field_InputControl textarea').select();
- }
-
- // Reduce the keys the Keys listens for
- dt.keys.enable(hardEdit ? 'tab-only' : 'navigation-only');
-
- // On blur of the navigation submit
- dt.on('key-blur.editor', function (e, dt, cell) {
- if (editor.displayed() && cell.node() === editCell.node()) {
- editor.submit();
- }
- });
-
- // Highlight the cell a different colour on full edit
- if (hardEdit) {
- $(dt.table().container()).addClass('dtk-focus-alt');
- }
-
- // If the dev cancels the submit, we need to return focus
- editor.on('preSubmitCancelled' + namespace, function () {
- setTimeout(function () {
- that._focus(editCell, null, false);
- }, 50);
- });
-
- editor.on('submitUnsuccessful' + namespace, function () {
- that._focus(editCell, null, false);
- });
-
- // Restore full key navigation on close
- editor.one('close' + namespace, function () {
- dt.keys.enable(true);
- dt.off('key-blur.editor');
- editor.off(namespace);
- $(dt.table().container()).removeClass('dtk-focus-alt');
-
- if (that.s.returnSubmit) {
- that.s.returnSubmit = false;
- that._emitEvent('key-return-submit', [dt, editCell]);
- }
- });
- })
- .one('cancelOpen' + namespace, function () {
- // `preOpen` can cancel the display of the form, so it
- // might be that the open event handler isn't needed
- editor.off(namespace);
- })
- .inline(editCell.index());
- };
-
- // Editor 1.7 listens for `return` on keyup, so if return is the trigger
- // key, we need to wait for `keyup` otherwise Editor would just submit
- // the content triggered by this keypress.
- if (key === 13) {
- hardEdit = true;
-
- $(document).one('keyup', function () { // immediately removed
- editInline();
- });
- } else {
- editInline();
- }
- },
-
-
- /**
- * Emit an event on the DataTable for listeners
- *
- * @param {string} name Event name
- * @param {array} args Event arguments
- * @private
- */
- _emitEvent: function (name, args) {
- this.s.dt.iterator('table', function (ctx, i) {
- $(ctx.nTable).triggerHandler(name, args);
- });
- },
-
-
- /**
- * Focus on a particular cell, shifting the table's paging if required
- *
- * @param {DataTables.Api|integer} row Can be given as an API instance that
- * contains the cell to focus or as an integer. As the latter it is the
- * visible row index (from the whole data set) - NOT the data index
- * @param {integer} [column] Not required if a cell is given as the first
- * parameter. Otherwise this is the column data index for the cell to
- * focus on
- * @param {boolean} [shift=true] Should the viewport be moved to show cell
- * @private
- */
- _focus: function (row, column, shift, originalEvent) {
- var that = this;
- var dt = this.s.dt;
- var pageInfo = dt.page.info();
- var lastFocus = this.s.lastFocus;
-
- if (!originalEvent) {
- originalEvent = null;
- }
-
- if (!this.s.enable) {
- return;
- }
-
- if (typeof row !== 'number') {
- // Its an API instance - check that there is actually a row
- if (!row.any()) {
- return;
- }
-
- // Convert the cell to a row and column
- var index = row.index();
- column = index.column;
- row = dt
- .rows({filter: 'applied', order: 'applied'})
- .indexes()
- .indexOf(index.row);
-
- // Don't focus rows that were filtered out.
- if (row < 0) {
- return;
- }
-
- // For server-side processing normalise the row by adding the start
- // point, since `rows().indexes()` includes only rows that are
- // available at the client-side
- if (pageInfo.serverSide) {
- row += pageInfo.start;
- }
- }
-
- // Is the row on the current page? If not, we need to redraw to show the
- // page
- if (pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start + pageInfo.length)) {
- this.s.focusDraw = true;
- this.s.waitingForDraw = true;
-
- dt
- .one('draw', function () {
- that.s.focusDraw = false;
- that.s.waitingForDraw = false;
- that._focus(row, column, undefined, originalEvent);
- })
- .page(Math.floor(row / pageInfo.length))
- .draw(false);
-
- return;
- }
-
- // In the available columns?
- if ($.inArray(column, this._columns()) === -1) {
- return;
- }
-
- // De-normalise the server-side processing row, so we select the row
- // in its displayed position
- if (pageInfo.serverSide) {
- row -= pageInfo.start;
- }
-
- // Get the cell from the current position - ignoring any cells which might
- // not have been rendered (therefore can't use `:eq()` selector).
- var cells = dt.cells(null, column, {search: 'applied', order: 'applied'}).flatten();
- var cell = dt.cell(cells[row]);
-
- if (lastFocus) {
- // Don't trigger a refocus on the same cell
- if (lastFocus.node === cell.node()) {
- this._emitEvent('key-refocus', [this.s.dt, cell, originalEvent || null]);
- return;
- }
-
- // Otherwise blur the old focus
- this._blur();
- }
-
- // Clear focus from other tables
- this._removeOtherFocus();
-
- var node = $(cell.node());
- node.addClass(this.c.className);
-
- this._updateFixedColumns(column);
-
- // Shift viewpoint and page to make cell visible
- if (shift === undefined || shift === true) {
- this._scroll($(window), $(document.body), node, 'offset');
-
- var bodyParent = dt.table().body().parentNode;
- if (bodyParent !== dt.table().header().parentNode) {
- var parent = $(bodyParent.parentNode);
-
- this._scroll(parent, parent, node, 'position');
- }
- }
-
- // Event and finish
- this.s.lastFocus = {
- cell: cell,
- node: cell.node(),
- relative: {
- row: dt.rows({page: 'current'}).indexes().indexOf(cell.index().row),
- column: cell.index().column
- }
- };
-
- this._emitEvent('key-focus', [this.s.dt, cell, originalEvent || null]);
- dt.state.save();
- },
-
-
- /**
- * Handle key press
- *
- * @param {object} e Event
- * @private
- */
- _key: function (e) {
- // If we are waiting for a draw to happen from another key event, then
- // do nothing for this new key press.
- if (this.s.waitingForDraw) {
- e.preventDefault();
- return;
- }
-
- var enable = this.s.enable;
- this.s.returnSubmit = (enable === 'navigation-only' || enable === 'tab-only') && e.keyCode === 13
- ? true
- : false;
-
- var navEnable = enable === true || enable === 'navigation-only';
- if (!enable) {
- return;
- }
-
- if ((e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey) && !(e.ctrlKey && e.altKey)) {
- return;
- }
-
- // If not focused, then there is no key action to take
- var lastFocus = this.s.lastFocus;
- if (!lastFocus) {
- return;
- }
-
- // And the last focus still exists!
- if (!this.s.dt.cell(lastFocus.node).any()) {
- this.s.lastFocus = null;
- return;
- }
-
- var that = this;
- var dt = this.s.dt;
- var scrolling = this.s.dt.settings()[0].oScroll.sY ? true : false;
-
- // If we are not listening for this key, do nothing
- if (this.c.keys && $.inArray(e.keyCode, this.c.keys) === -1) {
- return;
- }
-
- switch (e.keyCode) {
- case 9: // tab
- // `enable` can be tab-only
- this._shift(e, e.shiftKey ? 'left' : 'right', true);
- break;
-
- case 27: // esc
- if (this.s.blurable && enable === true) {
- this._blur();
- }
- break;
-
- case 33: // page up (previous page)
- case 34: // page down (next page)
- if (navEnable && !scrolling) {
- e.preventDefault();
-
- dt
- .page(e.keyCode === 33 ? 'previous' : 'next')
- .draw(false);
- }
- break;
-
- case 35: // end (end of current page)
- case 36: // home (start of current page)
- if (navEnable) {
- e.preventDefault();
- var indexes = dt.cells({page: 'current'}).indexes();
- var colIndexes = this._columns();
-
- this._focus(dt.cell(
- indexes[e.keyCode === 35 ? indexes.length - 1 : colIndexes[0]]
- ), null, true, e);
- }
- break;
-
- case 37: // left arrow
- if (navEnable) {
- this._shift(e, 'left');
- }
- break;
-
- case 38: // up arrow
- if (navEnable) {
- this._shift(e, 'up');
- }
- break;
-
- case 39: // right arrow
- if (navEnable) {
- this._shift(e, 'right');
- }
- break;
-
- case 40: // down arrow
- if (navEnable) {
- this._shift(e, 'down');
- }
- break;
-
- case 113: // F2 - Excel like hard edit
- if (this.c.editor) {
- this._editor(null, e, true);
- break;
- }
- // else fallthrough
-
- default:
- // Everything else - pass through only when fully enabled
- if (enable === true) {
- this._emitEvent('key', [dt, e.keyCode, this.s.lastFocus.cell, e]);
- }
- break;
- }
- },
-
- /**
- * Remove focus from all tables other than this one
- */
- _removeOtherFocus: function () {
- var thisTable = this.s.dt.table().node();
-
- $.fn.dataTable.tables({api: true}).iterator('table', function (settings) {
- if (this.table().node() !== thisTable) {
- this.cell.blur();
- }
- });
- },
-
- /**
- * Scroll a container to make a cell visible in it. This can be used for
- * both DataTables scrolling and native window scrolling.
- *
- * @param {jQuery} container Scrolling container
- * @param {jQuery} scroller Item being scrolled
- * @param {jQuery} cell Cell in the scroller
- * @param {string} posOff `position` or `offset` - which to use for the
- * calculation. `offset` for the document, otherwise `position`
- * @private
- */
- _scroll: function (container, scroller, cell, posOff) {
- var offset = cell[posOff]();
- var height = cell.outerHeight();
- var width = cell.outerWidth();
-
- var scrollTop = scroller.scrollTop();
- var scrollLeft = scroller.scrollLeft();
- var containerHeight = container.height();
- var containerWidth = container.width();
-
- // If Scroller is being used, the table can be `position: absolute` and that
- // needs to be taken account of in the offset. If no Scroller, this will be 0
- if (posOff === 'position') {
- offset.top += parseInt(cell.closest('table').css('top'), 10);
- }
-
- // Top correction
- if (offset.top < scrollTop) {
- scroller.scrollTop(offset.top);
- }
-
- // Left correction
- if (offset.left < scrollLeft) {
- scroller.scrollLeft(offset.left);
- }
-
- // Bottom correction
- if (offset.top + height > scrollTop + containerHeight && height < containerHeight) {
- scroller.scrollTop(offset.top + height - containerHeight);
- }
-
- // Right correction
- if (offset.left + width > scrollLeft + containerWidth && width < containerWidth) {
- scroller.scrollLeft(offset.left + width - containerWidth);
- }
- },
-
-
- /**
- * Calculate a single offset movement in the table - up, down, left and
- * right and then perform the focus if possible
- *
- * @param {object} e Event object
- * @param {string} direction Movement direction
- * @param {boolean} keyBlurable `true` if the key press can result in the
- * table being blurred. This is so arrow keys won't blur the table, but
- * tab will.
- * @private
- */
- _shift: function (e, direction, keyBlurable) {
- var that = this;
- var dt = this.s.dt;
- var pageInfo = dt.page.info();
- var rows = pageInfo.recordsDisplay;
- var columns = this._columns();
- var last = this.s.lastFocus;
- if (!last) {
- return;
- }
-
- var currentCell = last.cell;
- if (!currentCell) {
- return;
- }
-
- var currRow = dt
- .rows({filter: 'applied', order: 'applied'})
- .indexes()
- .indexOf(currentCell.index().row);
-
- // When server-side processing, `rows().indexes()` only gives the rows
- // that are available at the client-side, so we need to normalise the
- // row's current position by the display start point
- if (pageInfo.serverSide) {
- currRow += pageInfo.start;
- }
-
- var currCol = dt
- .columns(columns)
- .indexes()
- .indexOf(currentCell.index().column);
-
- var
- row = currRow,
- column = columns[currCol]; // row is the display, column is an index
-
- if (direction === 'right') {
- if (currCol >= columns.length - 1) {
- row++;
- column = columns[0];
- } else {
- column = columns[currCol + 1];
- }
- } else if (direction === 'left') {
- if (currCol === 0) {
- row--;
- column = columns[columns.length - 1];
- } else {
- column = columns[currCol - 1];
- }
- } else if (direction === 'up') {
- row--;
- } else if (direction === 'down') {
- row++;
- }
-
- if (row >= 0 && row < rows && $.inArray(column, columns) !== -1) {
- if (e) {
- e.preventDefault();
- }
-
- this._focus(row, column, true, e);
- } else if (!keyBlurable || !this.c.blurable) {
- // No new focus, but if the table isn't blurable, then don't loose
- // focus
- if (e) {
- e.preventDefault();
- }
- } else {
- this._blur();
- }
- },
-
-
- /**
- * Create and insert a hidden input element that can receive focus on behalf
- * of the table
- *
- * @private
- */
- _tabInput: function () {
- var that = this;
- var dt = this.s.dt;
- var tabIndex = this.c.tabIndex !== null ?
- this.c.tabIndex :
- dt.settings()[0].iTabIndex;
-
- if (tabIndex == -1) {
- return;
- }
-
- // Only create the input element once on first class
- if (!this.s.tabInput) {
- var div = $('<div><input type="text" tabindex="' + tabIndex + '"/></div>')
- .css({
- position: 'absolute',
- height: 1,
- width: 0,
- overflow: 'hidden'
- });
-
- div.children().on('focus', function (e) {
- var cell = dt.cell(':eq(0)', that._columns(), {page: 'current'});
-
- if (cell.any()) {
- that._focus(cell, null, true, e);
- }
- });
-
- this.s.tabInput = div;
- }
-
- // Insert the input element into the first cell in the table's body
- var cell = this.s.dt.cell(':eq(0)', '0:visible', {page: 'current', order: 'current'}).node();
- if (cell) {
- $(cell).prepend(this.s.tabInput);
- }
- },
-
- /**
- * Update fixed columns if they are enabled and if the cell we are
- * focusing is inside a fixed column
- * @param {integer} column Index of the column being changed
- * @private
- */
- _updateFixedColumns: function (column) {
- var dt = this.s.dt;
- var settings = dt.settings()[0];
-
- if (settings._oFixedColumns) {
- var leftCols = settings._oFixedColumns.s.iLeftColumns;
- var rightCols = settings.aoColumns.length - settings._oFixedColumns.s.iRightColumns;
-
- if (column < leftCols || column >= rightCols) {
- dt.fixedColumns().update();
- }
- }
- }
- });
-
-
- /**
- * KeyTable default settings for initialisation
- *
- * @namespace
- * @name KeyTable.defaults
- * @static
- */
- KeyTable.defaults = {
- /**
- * Can focus be removed from the table
- * @type {Boolean}
- */
- blurable: true,
-
- /**
- * Class to give to the focused cell
- * @type {String}
- */
- className: 'focus',
-
- /**
- * Enable or disable clipboard support
- * @type {Boolean}
- */
- clipboard: true,
-
- /**
- * Orthogonal data that should be copied to clipboard
- * @type {string}
- */
- clipboardOrthogonal: 'display',
-
- /**
- * Columns that can be focused. This is automatically merged with the
- * visible columns as only visible columns can gain focus.
- * @type {String}
- */
- columns: '', // all
-
- /**
- * Editor instance to automatically perform Excel like navigation
- * @type {Editor}
- */
- editor: null,
-
- /**
- * Trigger editing immediately on focus
- * @type {boolean}
- */
- editOnFocus: false,
-
- /**
- * Select a cell to automatically select on start up. `null` for no
- * automatic selection
- * @type {cell-selector}
- */
- focus: null,
-
- /**
- * Array of keys to listen for
- * @type {null|array}
- */
- keys: null,
-
- /**
- * Tab index for where the table should sit in the document's tab flow
- * @type {integer|null}
- */
- tabIndex: null
- };
-
-
- KeyTable.version = "2.6.1";
-
-
- $.fn.dataTable.KeyTable = KeyTable;
- $.fn.DataTable.KeyTable = KeyTable;
-
-
- DataTable.Api.register('cell.blur()', function () {
- return this.iterator('table', function (ctx) {
- if (ctx.keytable) {
- ctx.keytable.blur();
- }
- });
- });
-
- DataTable.Api.register('cell().focus()', function () {
- return this.iterator('cell', function (ctx, row, column) {
- if (ctx.keytable) {
- ctx.keytable.focus(row, column);
- }
- });
- });
-
- DataTable.Api.register('keys.disable()', function () {
- return this.iterator('table', function (ctx) {
- if (ctx.keytable) {
- ctx.keytable.enable(false);
- }
- });
- });
-
- DataTable.Api.register('keys.enable()', function (opts) {
- return this.iterator('table', function (ctx) {
- if (ctx.keytable) {
- ctx.keytable.enable(opts === undefined ? true : opts);
- }
- });
- });
-
- DataTable.Api.register('keys.enabled()', function (opts) {
- var ctx = this.context;
-
- if (ctx.length) {
- return ctx[0].keytable
- ? ctx[0].keytable.enabled()
- : false;
- }
-
- return false;
- });
-
- DataTable.Api.register('keys.move()', function (dir) {
- return this.iterator('table', function (ctx) {
- if (ctx.keytable) {
- ctx.keytable._shift(null, dir, false);
- }
- });
- });
-
- // Cell selector
- DataTable.ext.selector.cell.push(function (settings, opts, cells) {
- var focused = opts.focused;
- var kt = settings.keytable;
- var out = [];
-
- if (!kt || focused === undefined) {
- return cells;
- }
-
- for (var i = 0, ien = cells.length; i < ien; i++) {
- if ((focused === true && kt.focused(cells[i])) ||
- (focused === false && !kt.focused(cells[i]))
- ) {
- out.push(cells[i]);
- }
- }
-
- return out;
- });
-
-
- // Attach a listener to the document which listens for DataTables initialisation
- // events so we can automatically initialise
- $(document).on('preInit.dt.dtk', function (e, settings, json) {
- if (e.namespace !== 'dt') {
- return;
- }
-
- var init = settings.oInit.keys;
- var defaults = DataTable.defaults.keys;
-
- if (init || defaults) {
- var opts = $.extend({}, defaults, init);
-
- if (init !== false) {
- new KeyTable(settings, opts);
- }
- }
- });
-
-
- return KeyTable;
- }));
|