123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883 |
- /*! 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;
- }));
|