884 lines
20 KiB
JavaScript
884 lines
20 KiB
JavaScript
|
/*! KeyTable 2.1.2
|
||
|
* ©2009-2016 SpryMedia Ltd - datatables.net/license
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @summary KeyTable
|
||
|
* @description Spreadsheet like keyboard navigation for DataTables
|
||
|
* @version 2.1.2
|
||
|
* @file dataTables.keyTable.js
|
||
|
* @author SpryMedia Ltd (www.sprymedia.co.uk)
|
||
|
* @contact www.sprymedia.co.uk/contact
|
||
|
* @copyright Copyright 2009-2016 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 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
|
||
|
};
|
||
|
|
||
|
// 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;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* 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.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() );
|
||
|
|
||
|
// 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.keyTable', 'th, td', function () {
|
||
|
if ( that.s.enable === false ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var cell = dt.cell( this );
|
||
|
|
||
|
if ( ! cell.any() ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
that._focus( cell, null, false );
|
||
|
} );
|
||
|
|
||
|
// Key events
|
||
|
$( document ).on( 'keydown.keyTable', function (e) {
|
||
|
that._key( e );
|
||
|
} );
|
||
|
|
||
|
// Click blur
|
||
|
if ( this.c.blurable ) {
|
||
|
$( document ).on( 'click.keyTable', 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;
|
||
|
}
|
||
|
|
||
|
that._blur();
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
if ( this.c.editor ) {
|
||
|
dt.on( 'key.keyTable', function ( e, dt, key, cell, orig ) {
|
||
|
that._editor( key, orig );
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
// Stave saving
|
||
|
if ( dt.settings()[0].oFeatures.bStateSave ) {
|
||
|
dt.on( 'stateSaveParams.keyTable', function (e, s, d) {
|
||
|
d.keyTable = that.s.lastFocus ?
|
||
|
that.s.lastFocus.index() :
|
||
|
null;
|
||
|
} );
|
||
|
}
|
||
|
|
||
|
// Reload - re-focus on the currently selected item. In SSP mode this
|
||
|
// has the effect of keeping the focus in position when changing page as
|
||
|
// well (which is different from how client-side processing works).
|
||
|
dt.on( 'xhr.keyTable', function ( e ) {
|
||
|
if ( that.s.focusDraw ) {
|
||
|
// Triggered by server-side processing, and thus `_focus` will
|
||
|
// do the refocus on the next draw event
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var lastFocus = that.s.lastFocus;
|
||
|
|
||
|
if ( lastFocus ) {
|
||
|
that.s.lastFocus = null;
|
||
|
|
||
|
dt.one( 'draw', function () {
|
||
|
that._focus( lastFocus );
|
||
|
} );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
dt.on( 'destroy.keyTable', function () {
|
||
|
dt.off( '.keyTable' );
|
||
|
$( dt.table().body() ).off( 'click.keyTable', 'th, td' );
|
||
|
$( document.body )
|
||
|
.off( 'keydown.keyTable' )
|
||
|
.off( 'click.keyTable' );
|
||
|
} );
|
||
|
|
||
|
// 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
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
_blur: function ()
|
||
|
{
|
||
|
if ( ! this.s.enable || ! this.s.lastFocus ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var cell = this.s.lastFocus;
|
||
|
|
||
|
$( cell.node() ).removeClass( this.c.className );
|
||
|
this.s.lastFocus = null;
|
||
|
|
||
|
this._emitEvent( 'key-blur', [ this.s.dt, cell ] );
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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 )
|
||
|
{
|
||
|
var dt = this.s.dt;
|
||
|
var editor = this.c.editor;
|
||
|
|
||
|
orig.stopPropagation();
|
||
|
|
||
|
// Return key should do nothing - for textareas's it would empty the
|
||
|
// contents
|
||
|
if ( key === 13 ) {
|
||
|
orig.preventDefault();
|
||
|
}
|
||
|
|
||
|
editor.inline( this.s.lastFocus.index() );
|
||
|
|
||
|
// Excel style - select all text
|
||
|
var input = $('div.DTE input, div.DTE textarea');
|
||
|
if ( input.length ) {
|
||
|
input[0].select();
|
||
|
}
|
||
|
|
||
|
// Reduce the keys the Keys listens for
|
||
|
dt.keys.enable( 'navigation-only' );
|
||
|
|
||
|
// On blur of the navigation submit
|
||
|
dt.one( 'key-blur.editor', function () {
|
||
|
if ( editor.displayed() ) {
|
||
|
editor.submit();
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
// Restore full key navigation on close
|
||
|
editor.one( 'close', function () {
|
||
|
dt.keys.enable( true );
|
||
|
dt.off( 'key-blur.editor' );
|
||
|
} );
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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 - 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 )
|
||
|
{
|
||
|
var that = this;
|
||
|
var dt = this.s.dt;
|
||
|
var pageInfo = dt.page.info();
|
||
|
var lastFocus = this.s.lastFocus;
|
||
|
|
||
|
if ( ! this.s.enable ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( typeof row !== 'number' ) {
|
||
|
// 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 );
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
dt
|
||
|
.one( 'draw', function () {
|
||
|
that.s.focusDraw = false;
|
||
|
that._focus( row, column );
|
||
|
} )
|
||
|
.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;
|
||
|
}
|
||
|
|
||
|
var cell = dt.cell( ':eq('+row+')', column, {search: 'applied'} );
|
||
|
|
||
|
if ( lastFocus ) {
|
||
|
// Don't trigger a refocus on the same cell
|
||
|
if ( lastFocus.node() === cell.node() ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Otherwise blur the old focus
|
||
|
this._blur();
|
||
|
}
|
||
|
|
||
|
var node = $( cell.node() );
|
||
|
node.addClass( this.c.className );
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
this._emitEvent( 'key-focus', [ this.s.dt, cell ] );
|
||
|
dt.state.save();
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Handle key press
|
||
|
*
|
||
|
* @param {object} e Event
|
||
|
* @private
|
||
|
*/
|
||
|
_key: function ( e )
|
||
|
{
|
||
|
if ( ! this.s.enable ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If not focused, then there is no key action to take
|
||
|
var cell = this.s.lastFocus;
|
||
|
if ( ! cell ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var that = this;
|
||
|
var dt = this.s.dt;
|
||
|
|
||
|
// 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
|
||
|
this._shift( e, e.shiftKey ? 'left' : 'right', true );
|
||
|
break;
|
||
|
|
||
|
case 27: // esc
|
||
|
if ( this.s.blurable && this.s.enable === true ) {
|
||
|
this._blur();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 33: // page up (previous page)
|
||
|
case 34: // page down (next page)
|
||
|
e.preventDefault();
|
||
|
var index = dt.cells( {page: 'current'} ).nodes().indexOf( cell.node() );
|
||
|
|
||
|
dt
|
||
|
.one( 'draw', function () {
|
||
|
var nodes = dt.cells( {page: 'current'} ).nodes();
|
||
|
|
||
|
that._focus( dt.cell( index < nodes.length ?
|
||
|
nodes[ index ] :
|
||
|
nodes[ nodes.length-1 ]
|
||
|
) );
|
||
|
} )
|
||
|
.page( e.keyCode === 33 ? 'previous' : 'next' )
|
||
|
.draw( false );
|
||
|
break;
|
||
|
|
||
|
case 35: // end (end of current page)
|
||
|
case 36: // home (start of current page)
|
||
|
e.preventDefault();
|
||
|
var indexes = dt.cells( {page: 'current'} ).indexes();
|
||
|
|
||
|
this._focus( dt.cell(
|
||
|
indexes[ e.keyCode === 35 ? indexes.length-1 : 0 ]
|
||
|
) );
|
||
|
break;
|
||
|
|
||
|
case 37: // left arrow
|
||
|
this._shift( e, 'left' );
|
||
|
break;
|
||
|
|
||
|
case 38: // up arrow
|
||
|
this._shift( e, 'up' );
|
||
|
break;
|
||
|
|
||
|
case 39: // right arrow
|
||
|
this._shift( e, 'right' );
|
||
|
break;
|
||
|
|
||
|
case 40: // down arrow
|
||
|
this._shift( e, 'down' );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
// Everything else - pass through only when fully enabled
|
||
|
if ( this.s.enable === true ) {
|
||
|
this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus, e ] );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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();
|
||
|
|
||
|
// 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 currentCell = this.s.lastFocus;
|
||
|
var columns = this._columns();
|
||
|
|
||
|
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
|
||
|
) {
|
||
|
e.preventDefault();
|
||
|
|
||
|
this._focus( row, column );
|
||
|
}
|
||
|
else if ( ! keyBlurable || ! this.c.blurable ) {
|
||
|
// No new focus, but if the table isn't blurable, then don't loose
|
||
|
// focus
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
else {
|
||
|
this._blur();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Create 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;
|
||
|
}
|
||
|
|
||
|
var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>')
|
||
|
.css( {
|
||
|
position: 'absolute',
|
||
|
height: 1,
|
||
|
width: 0,
|
||
|
overflow: 'hidden'
|
||
|
} )
|
||
|
.insertBefore( dt.table().node() );
|
||
|
|
||
|
div.children().on( 'focus', function () {
|
||
|
that._focus( dt.cell(':eq(0)', '0:visible', {page: 'current'}) );
|
||
|
} );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
|
||
|
/**
|
||
|
* 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',
|
||
|
|
||
|
/**
|
||
|
* 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,
|
||
|
|
||
|
/**
|
||
|
* 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.1.2";
|
||
|
|
||
|
|
||
|
$.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 );
|
||
|
}
|
||
|
} );
|
||
|
} );
|
||
|
|
||
|
// 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( {}, init, defaults );
|
||
|
|
||
|
if ( init !== false ) {
|
||
|
new KeyTable( settings, opts );
|
||
|
}
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
|
||
|
return KeyTable;
|
||
|
}));
|