Blame | Letzte Änderung | Log anzeigen | RSS feed
/*! KeyTable 2.0.0* ©2009-2015 SpryMedia Ltd - datatables.net/license*//*** @summary KeyTable* @description Spreadsheet like keyboard navigation for DataTables* @version 2.0.0* @file dataTables.keyTable.js* @author SpryMedia Ltd (www.sprymedia.co.uk)* @contact www.sprymedia.co.uk/contact* @copyright Copyright 2009-2015 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(window, document, undefined) {var factory = function( $, DataTable ) {"use strict";var KeyTable = function ( dt, opts ) {// Sanity check that we are using DataTables 1.10 or newerif ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {throw 'KeyTable requires DataTables 1.10.8 or newer';}// User and defaults configuration objectthis.c = $.extend( true, {},DataTable.defaults.keyTable,KeyTable.defaults,opts);// Internal settingsthis.s = {/** @type {DataTable.Api} DataTables' API instance */dt: new DataTable.Api( dt ),enable: true};// DOM itemsthis.dom = {};// Check if row reorder has already been initialised on this tablevar settings = this.s.dt.settings()[0];var exisiting = settings.keytable;if ( exisiting ) {return exisiting;}settings.keytable = this;this._constructor();};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 ) );},/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** 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 tableif ( 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 );} );// Key events$( document.body ).on( 'keydown.keyTable', function (e) {that._key( e );} );// Click blurif ( this.c.blurable ) {$( document.body ).on( 'click.keyTable', function ( e ) {// Click on the search input will blur focusif ( $(e.target).parents( '.dataTables_filter' ).length ) {that._blur();}// If the click was inside the DataTables container, don't blurif ( $(e.target).parents().filter( dt.table().container() ).length ) {return;}// Don't blur in Editor formif ( $(e.target).parents('div.DTE').length ) {return;}that._blur();} );}if ( this.c.editor ) {dt.on( 'key.kt', function ( e, dt, key, cell, orig ) {that._editor( key, orig );} );}// Stave savingif ( dt.settings()[0].oFeatures.bStateSave ) {dt.on( 'stateSaveParams.keyTable', function (e, s, d) {d.keyTable = that.s.lastFocus ?that.s.lastFocus.index() :null;} );}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 optionsvar state = dt.state.loaded();if ( state && state.keyTable ) {dt.cell( state.keyTable ).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();editor.inline( this.s.lastFocus.index() );// Excel style - select all textvar input = $('div.DTE input');if ( input.length ) {input[0].select();}// Reduce the keys the Keys listens fordt.keys.enable( 'navigation-only' );// On blur of the navigation submitdt.one( 'key-blur.editor', function () {if ( editor.displayed() ) {editor.submit();}} );// Restore full key navigation on closeeditor.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* @private*/_focus: function ( row, column ){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 columnvar 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-sideif ( pageInfo.serverSide ) {row += pageInfo.start;}}// Is the row on the current page? If not, we need to redraw to show the// pageif ( row < pageInfo.start || row >= pageInfo.start+pageInfo.length ) {dt.one( 'draw', function () {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 positionif ( pageInfo.serverSide ) {row -= pageInfo.start;}var cell = dt.cell( ':eq('+row+')', column );if ( lastFocus ) {// Don't trigger a refocus on the same cellif ( lastFocus.node() === cell.node() ) {return;}// Otherwise blur the old focusthis._blur();}var node = $( cell.node() );node.addClass( this.c.className );// Shift viewpoint and page to make cell visiblethis._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 finishthis.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 takevar cell = this.s.lastFocus;if ( ! cell ) {return;}var that = this;var dt = this.s.dt;// If we are not listening for this key, do nothingif ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) {return;}switch( e.keyCode ) {case 9: // tabthis._shift( e, e.shiftKey ? 'left' : 'right', true );break;case 27: // escif ( 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 arrowthis._shift( e, 'left' );break;case 38: // up arrowthis._shift( e, 'up' );break;case 39: // right arrowthis._shift( e, 'right' );break;case 40: // down arrowthis._shift( e, 'down' );break;default:// Everything else - pass through only when fully enabledif ( 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 correctionif ( offset.top < scrollTop ) {scroller.scrollTop( offset.top );}// Left correctionif ( offset.left < scrollLeft ) {scroller.scrollLeft( offset.left );}// Bottom correctionif ( offset.top + height > scrollTop + containerHeight ) {scroller.scrollTop( offset.top + height - containerHeight );}// Right correctionif ( offset.left + width > scrollLeft + 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 pointif ( pageInfo.serverSide ) {currRow += pageInfo.start;}var currCol = dt.columns( columns ).indexes().indexOf( currentCell.index().column );varrow = currRow,column = columns[ currCol ]; // row is the display, column is an indexif ( 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 &&column >= 0 && column <= columns.length) {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// focuse.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)', {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.0.0";$.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 );}} );} );// 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;}; // /factory// Define as an AMD module if possibleif ( typeof define === 'function' && define.amd ) {define( ['jquery', 'datatables'], factory );}else if ( typeof exports === 'object' ) {// Node/CommonJSfactory( require('jquery'), require('datatables') );}else if ( jQuery && !jQuery.fn.dataTable.KeyTable ) {// Otherwise simply initialise as normal, stopping multiple evaluationfactory( jQuery, jQuery.fn.dataTable );}})(window, document);