jquery-datatables-rails 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/dataTables/bootstrap/3/jquery.dataTables.bootstrap.js +64 -29
  3. data/app/assets/javascripts/dataTables/extras/dataTables.autoFill.js +806 -648
  4. data/app/assets/javascripts/dataTables/extras/dataTables.buttons.js +1607 -0
  5. data/app/assets/javascripts/dataTables/extras/dataTables.colReorder.js +220 -267
  6. data/app/assets/javascripts/dataTables/extras/dataTables.fixedColumns.js +164 -69
  7. data/app/assets/javascripts/dataTables/extras/dataTables.fixedHeader.js +469 -870
  8. data/app/assets/javascripts/dataTables/extras/dataTables.keyTable.js +636 -972
  9. data/app/assets/javascripts/dataTables/extras/dataTables.responsive.js +472 -187
  10. data/app/assets/javascripts/dataTables/extras/dataTables.rowReorder.js +619 -0
  11. data/app/assets/javascripts/dataTables/extras/dataTables.scroller.js +146 -111
  12. data/app/assets/javascripts/dataTables/extras/dataTables.select.js +1038 -0
  13. data/app/assets/javascripts/dataTables/jquery.dataTables.api.fnGetColumnData.js +0 -0
  14. data/app/assets/javascripts/dataTables/jquery.dataTables.api.fnReloadAjax.js +0 -0
  15. data/app/assets/javascripts/dataTables/jquery.dataTables.foundation.js +37 -61
  16. data/app/assets/javascripts/dataTables/jquery.dataTables.js +720 -387
  17. data/app/assets/javascripts/dataTables/jquery.dataTables.sorting.ipAddress.js +44 -0
  18. data/app/assets/javascripts/dataTables/jquery.dataTables.sorting.numbersHtml.js +0 -0
  19. data/app/assets/javascripts/dataTables/jquery.dataTables.typeDetection.numbersHtml.js +0 -0
  20. data/app/assets/stylesheets/dataTables/jquery.dataTables.scss +34 -66
  21. data/app/assets/stylesheets/dataTables/src/demo_table.css +1 -1
  22. data/app/assets/stylesheets/dataTables/src/demo_table_jui.css.scss +4 -4
  23. data/lib/jquery/datatables/rails/version.rb +1 -1
  24. metadata +24 -19
@@ -1,15 +1,15 @@
1
- /*! KeyTable 1.2.1
2
- * ©2010-2014 SpryMedia Ltd - datatables.net/license
1
+ /*! KeyTable 2.1.0
2
+ * ©2009-2015 SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
5
5
  /**
6
6
  * @summary KeyTable
7
7
  * @description Spreadsheet like keyboard navigation for DataTables
8
- * @version 1.2.1
8
+ * @version 2.1.0
9
9
  * @file dataTables.keyTable.js
10
10
  * @author SpryMedia Ltd (www.sprymedia.co.uk)
11
11
  * @contact www.sprymedia.co.uk/contact
12
- * @copyright Copyright 2009-2014 SpryMedia Ltd.
12
+ * @copyright Copyright 2009-2015 SpryMedia Ltd.
13
13
  *
14
14
  * This source file is free software, available under the following license:
15
15
  * MIT license - http://datatables.net/license/mit
@@ -21,1155 +21,819 @@
21
21
  * For details please refer to: http://www.datatables.net
22
22
  */
23
23
 
24
- // Global scope for KeyTable for backwards compatibility. Will be removed in 1.3
25
- var KeyTable;
26
-
27
-
28
- (function(window, document, undefined) {
24
+ (function( factory ){
25
+ if ( typeof define === 'function' && define.amd ) {
26
+ // AMD
27
+ define( ['jquery', 'datatables.net'], function ( $ ) {
28
+ return factory( $, window, document );
29
+ } );
30
+ }
31
+ else if ( typeof exports === 'object' ) {
32
+ // CommonJS
33
+ module.exports = function (root, $) {
34
+ if ( ! root ) {
35
+ root = window;
36
+ }
29
37
 
38
+ if ( ! $ || ! $.fn.dataTable ) {
39
+ $ = require('datatables.net')(root, $).$;
40
+ }
30
41
 
31
- var factory = function( $, DataTable ) {
32
- "use strict";
42
+ return factory( $, root, root.document );
43
+ };
44
+ }
45
+ else {
46
+ // Browser
47
+ factory( jQuery, window, document );
48
+ }
49
+ }(function( $, window, document, undefined ) {
50
+ 'use strict';
51
+ var DataTable = $.fn.dataTable;
33
52
 
34
- KeyTable = function ( oInit )
35
- {
36
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
37
- * API parameters
38
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
39
53
 
40
- /*
41
- * Variable: block
42
- * Purpose: Flag whether or not KeyTable events should be processed
43
- * Scope: KeyTable - public
44
- */
45
- this.block = false;
46
-
47
- /*
48
- * Variable: event
49
- * Purpose: Container for all event application methods
50
- * Scope: KeyTable - public
51
- * Notes: This object contains all the public methods for adding and removing events - these
52
- * are dynamically added later on
53
- */
54
- this.event = {
55
- "remove": {}
56
- };
54
+ var KeyTable = function ( dt, opts ) {
55
+ // Sanity check that we are using DataTables 1.10 or newer
56
+ if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
57
+ throw 'KeyTable requires DataTables 1.10.8 or newer';
58
+ }
57
59
 
60
+ // User and defaults configuration object
61
+ this.c = $.extend( true, {},
62
+ DataTable.defaults.keyTable,
63
+ KeyTable.defaults,
64
+ opts
65
+ );
58
66
 
59
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
60
- * API methods
61
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
67
+ // Internal settings
68
+ this.s = {
69
+ /** @type {DataTable.Api} DataTables' API instance */
70
+ dt: new DataTable.Api( dt ),
62
71
 
63
- /*
64
- * Function: fnGetCurrentPosition
65
- * Purpose: Get the currently focused cell's position
66
- * Returns: array int: [ x, y ]
67
- * Inputs: void
68
- */
69
- this.fnGetCurrentPosition = function ()
70
- {
71
- return [ _iOldX, _iOldY ];
72
+ enable: true
72
73
  };
73
74
 
75
+ // DOM items
76
+ this.dom = {
74
77
 
75
- /*
76
- * Function: fnGetCurrentData
77
- * Purpose: Get the currently focused cell's data (innerHTML)
78
- * Returns: string: - data requested
79
- * Inputs: void
80
- */
81
- this.fnGetCurrentData = function ()
82
- {
83
- return _nOldFocus.innerHTML;
84
78
  };
85
79
 
80
+ // Check if row reorder has already been initialised on this table
81
+ var settings = this.s.dt.settings()[0];
82
+ var exisiting = settings.keytable;
83
+ if ( exisiting ) {
84
+ return exisiting;
85
+ }
86
86
 
87
- /*
88
- * Function: fnGetCurrentTD
89
- * Purpose: Get the currently focused cell
90
- * Returns: node: - focused element
91
- * Inputs: void
92
- */
93
- this.fnGetCurrentTD = function ()
94
- {
95
- return _nOldFocus;
96
- };
87
+ settings.keytable = this;
88
+ this._constructor();
89
+ };
97
90
 
98
91
 
99
- /*
100
- * Function: fnSetPosition
101
- * Purpose: Set the position of the focused cell
102
- * Returns: -
103
- * Inputs: int:x - x coordinate
104
- * int:y - y coordinate
105
- * Notes: Thanks to Rohan Daxini for the basis of this function
92
+ $.extend( KeyTable.prototype, {
93
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
94
+ * API methods for DataTables API interface
106
95
  */
107
- this.fnSetPosition = function( x, y )
108
- {
109
- if ( typeof x == 'object' && x.nodeName )
110
- {
111
- _fnSetFocus( x );
112
- }
113
- else
114
- {
115
- _fnSetFocus( _fnCellFromCoords(x, y) );
116
- }
117
- };
118
-
119
-
120
- /*
121
- * Function: fnBlur
122
- * Purpose: Blur the current focus
123
- * Returns: -
124
- * Inputs: -
96
+
97
+ /**
98
+ * Blur the table's cell focus
125
99
  */
126
- this.fnBlur = function()
100
+ blur: function ()
127
101
  {
128
- _fnBlur();
129
- };
130
-
102
+ this._blur();
103
+ },
131
104
 
132
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
133
- * Private parameters
134
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
135
-
136
- /*
137
- * Variable: _nBody
138
- * Purpose: Body node of the table - cached for renference
139
- * Scope: KeyTable - private
105
+ /**
106
+ * Enable cell focus for the table
107
+ *
108
+ * @param {string} state Can be `true`, `false` or `-string navigation-only`
140
109
  */
141
- var _nBody = null;
110
+ enable: function ( state )
111
+ {
112
+ this.s.enable = state;
113
+ },
142
114
 
143
- /*
144
- * Variable:
145
- * Purpose:
146
- * Scope: KeyTable - private
115
+ /**
116
+ * Focus on a cell
117
+ * @param {integer} row Row index
118
+ * @param {integer} column Column index
147
119
  */
148
- var _nOldFocus = null;
120
+ focus: function ( row, column )
121
+ {
122
+ this._focus( this.s.dt.cell( row, column ) );
123
+ },
149
124
 
150
- /*
151
- * Variable: _iOldX and _iOldY
152
- * Purpose: X and Y coords of the old elemet that was focused on
153
- * Scope: KeyTable - private
125
+ /**
126
+ * Is the cell focused
127
+ * @param {object} cell Cell index to check
128
+ * @returns {boolean} true if focused, false otherwise
154
129
  */
155
- var _iOldX = null;
156
- var _iOldY = null;
130
+ focused: function ( cell )
131
+ {
132
+ var lastFocus = this.s.lastFocus;
157
133
 
158
- /*
159
- * Variable: _that
160
- * Purpose: Scope saving for 'this' after a jQuery event
161
- * Scope: KeyTable - private
162
- */
163
- var _that = null;
134
+ if ( ! lastFocus ) {
135
+ return false;
136
+ }
164
137
 
165
- /*
166
- * Variable: sFocusClass
167
- * Purpose: Class that should be used for focusing on a cell
168
- * Scope: KeyTable - private
169
- */
170
- var _sFocusClass = "focus";
138
+ var lastIdx = this.s.lastFocus.index();
139
+ return cell.row === lastIdx.row && cell.column === lastIdx.column;
140
+ },
171
141
 
172
- /*
173
- * Variable: _bKeyCapture
174
- * Purpose: Flag for should KeyTable capture key events or not
175
- * Scope: KeyTable - private
176
- */
177
- var _bKeyCapture = false;
178
142
 
179
- /*
180
- * Variable: _oaoEvents
181
- * Purpose: Event cache object, one array for each supported event for speed of searching
182
- * Scope: KeyTable - private
143
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
144
+ * Constructor
183
145
  */
184
- var _oaoEvents = {
185
- "action": [],
186
- "esc": [],
187
- "focus": [],
188
- "blur": []
189
- };
190
146
 
191
- /*
192
- * Variable: _oDatatable
193
- * Purpose: DataTables settings object for if we are actually using a
194
- * DataTables table
195
- * Scope: KeyTable - private
147
+ /**
148
+ * Initialise the KeyTable instance
149
+ *
150
+ * @private
196
151
  */
197
- var _oDatatable = null;
152
+ _constructor: function ()
153
+ {
154
+ this._tabInput();
198
155
 
199
- var _bForm;
200
- var _nInput;
201
- var _bInputFocused = false;
156
+ var that = this;
157
+ var dt = this.s.dt;
158
+ var table = $( dt.table().node() );
202
159
 
160
+ // Need to be able to calculate the cell positions relative to the table
161
+ if ( table.css('position') === 'static' ) {
162
+ table.css( 'position', 'relative' );
163
+ }
203
164
 
204
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
205
- * Private methods
206
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
165
+ // Click to focus
166
+ $( dt.table().body() ).on( 'click.keyTable', 'th, td', function () {
167
+ if ( that.s.enable === false ) {
168
+ return;
169
+ }
207
170
 
208
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
209
- * Key table events
210
- */
171
+ var cell = dt.cell( this );
211
172
 
212
- /*
213
- * Function: _fnEventAddTemplate
214
- * Purpose: Create a function (with closure for sKey) event addition API
215
- * Returns: function: - template function
216
- * Inputs: string:sKey - type of event to detect
217
- */
218
- function _fnEventAddTemplate( sKey )
219
- {
220
- /*
221
- * Function: -
222
- * Purpose: API function for adding event to cache
223
- * Returns: -
224
- * Inputs: 1. node:x - target node to add event for
225
- * 2. function:y - callback function to apply
226
- * or
227
- * 1. int:x - x coord. of target cell (can be null for live events)
228
- * 2. int:y - y coord. of target cell (can be null for live events)
229
- * 3. function:z - callback function to apply
230
- * Notes: This function is (interally) overloaded (in as much as javascript allows for
231
- * that) - the target cell can be given by either node or coords.
232
- */
233
- return function ( x, y, z ) {
234
- if ( (x===null || typeof x == "number") &&
235
- (y===null || typeof y == "number") &&
236
- typeof z == "function" )
237
- {
238
- _fnEventAdd( sKey, x, y, z );
239
- }
240
- else if ( typeof x == "object" && typeof y == "function" )
241
- {
242
- var aCoords = _fnCoordsFromCell( x );
243
- _fnEventAdd( sKey, aCoords[0], aCoords[1], y );
244
- }
245
- else
246
- {
247
- alert( "Unhandable event type was added: x" +x+ " y:" +y+ " z:" +z );
173
+ if ( ! cell.any() ) {
174
+ return;
248
175
  }
249
- };
250
- }
251
176
 
177
+ that._focus( cell );
178
+ } );
252
179
 
253
- /*
254
- * Function: _fnEventRemoveTemplate
255
- * Purpose: Create a function (with closure for sKey) event removal API
256
- * Returns: function: - template function
257
- * Inputs: string:sKey - type of event to detect
258
- */
259
- function _fnEventRemoveTemplate( sKey )
260
- {
261
- /*
262
- * Function: -
263
- * Purpose: API function for removing event from cache
264
- * Returns: int: - number of events removed
265
- * Inputs: 1. node:x - target node to remove event from
266
- * 2. function:y - callback function to apply
267
- * or
268
- * 1. int:x - x coord. of target cell (can be null for live events)
269
- * 2. int:y - y coord. of target cell (can be null for live events)
270
- * 3. function:z - callback function to remove - optional
271
- * Notes: This function is (interally) overloaded (in as much as javascript allows for
272
- * that) - the target cell can be given by either node or coords and the function
273
- * to remove is optional
274
- */
275
- return function ( x, y, z ) {
276
- if ( (x===null || typeof arguments[0] == "number") &&
277
- (y===null || typeof arguments[1] == "number" ) )
278
- {
279
- if ( typeof arguments[2] == "function" )
280
- {
281
- _fnEventRemove( sKey, x, y, z );
282
- }
283
- else
284
- {
285
- _fnEventRemove( sKey, x, y );
180
+ // Key events
181
+ $( document.body ).on( 'keydown.keyTable', function (e) {
182
+ that._key( e );
183
+ } );
184
+
185
+ // Click blur
186
+ if ( this.c.blurable ) {
187
+ $( document.body ).on( 'click.keyTable', function ( e ) {
188
+ // Click on the search input will blur focus
189
+ if ( $(e.target).parents( '.dataTables_filter' ).length ) {
190
+ that._blur();
286
191
  }
287
- }
288
- else if ( typeof arguments[0] == "object" )
289
- {
290
- var aCoords = _fnCoordsFromCell( x );
291
- if ( typeof arguments[1] == "function" )
292
- {
293
- _fnEventRemove( sKey, aCoords[0], aCoords[1], y );
192
+
193
+ // If the click was inside the DataTables container, don't blur
194
+ if ( $(e.target).parents().filter( dt.table().container() ).length ) {
195
+ return;
294
196
  }
295
- else
296
- {
297
- _fnEventRemove( sKey, aCoords[0], aCoords[1] );
197
+
198
+ // Don't blur in Editor form
199
+ if ( $(e.target).parents('div.DTE').length ) {
200
+ return;
298
201
  }
299
- }
300
- else
301
- {
302
- alert( "Unhandable event type was removed: x" +x+ " y:" +y+ " z:" +z );
303
- }
304
- };
305
- }
306
202
 
307
- /* Use the template functions to add the event API functions */
308
- for ( var sKey in _oaoEvents )
309
- {
310
- if ( sKey )
311
- {
312
- this.event[sKey] = _fnEventAddTemplate( sKey );
313
- this.event.remove[sKey] = _fnEventRemoveTemplate( sKey );
203
+ that._blur();
204
+ } );
314
205
  }
315
- }
316
206
 
207
+ if ( this.c.editor ) {
208
+ dt.on( 'key.kt', function ( e, dt, key, cell, orig ) {
209
+ that._editor( key, orig );
210
+ } );
211
+ }
317
212
 
318
- /*
319
- * Function: _fnEventAdd
320
- * Purpose: Add an event to the internal cache
321
- * Returns: -
322
- * Inputs: string:sType - type of event to add, given by the available elements in _oaoEvents
323
- * int:x - x-coords to add event to - can be null for "blanket" event
324
- * int:y - y-coords to add event to - can be null for "blanket" event
325
- * function:fn - callback function for when triggered
326
- */
327
- function _fnEventAdd( sType, x, y, fn )
328
- {
329
- _oaoEvents[sType].push( {
330
- "x": x,
331
- "y": y,
332
- "fn": fn
213
+ // Stave saving
214
+ if ( dt.settings()[0].oFeatures.bStateSave ) {
215
+ dt.on( 'stateSaveParams.keyTable', function (e, s, d) {
216
+ d.keyTable = that.s.lastFocus ?
217
+ that.s.lastFocus.index() :
218
+ null;
219
+ } );
220
+ }
221
+
222
+ dt.on( 'destroy.keyTable', function () {
223
+ dt.off( '.keyTable' );
224
+ $( dt.table().body() ).off( 'click.keyTable', 'th, td' );
225
+ $( document.body )
226
+ .off( 'keydown.keyTable' )
227
+ .off( 'click.keyTable' );
333
228
  } );
334
- }
335
229
 
230
+ // Initial focus comes from state or options
231
+ var state = dt.state.loaded();
336
232
 
337
- /*
338
- * Function: _fnEventRemove
339
- * Purpose: Remove an event from the event cache
340
- * Returns: int: - number of matching events removed
341
- * Inputs: string:sType - type of event to look for
342
- * node:nTarget - target table cell
343
- * function:fn - optional - remove this function. If not given all handlers of this
344
- * type will be removed
345
- */
346
- function _fnEventRemove( sType, x, y, fn )
347
- {
348
- var iCorrector = 0;
349
-
350
- for ( var i=0, iLen=_oaoEvents[sType].length ; i<iLen-iCorrector ; i++ )
351
- {
352
- if ( typeof fn != 'undefined' )
353
- {
354
- if ( _oaoEvents[sType][i-iCorrector].x == x &&
355
- _oaoEvents[sType][i-iCorrector].y == y &&
356
- _oaoEvents[sType][i-iCorrector].fn == fn )
357
- {
358
- _oaoEvents[sType].splice( i-iCorrector, 1 );
359
- iCorrector++;
360
- }
361
- }
362
- else
363
- {
364
- if ( _oaoEvents[sType][i-iCorrector].x == x &&
365
- _oaoEvents[sType][i-iCorrector].y == y )
366
- {
367
- _oaoEvents[sType].splice( i, 1 );
368
- return 1;
369
- }
370
- }
233
+ if ( state && state.keyTable ) {
234
+ dt.cell( state.keyTable ).focus();
371
235
  }
372
- return iCorrector;
373
- }
374
-
375
-
376
- /*
377
- * Function: _fnEventFire
378
- * Purpose: Look thought the events cache and fire off the event of interest
379
- * Returns: int:iFired - number of events fired
380
- * Inputs: string:sType - type of event to look for
381
- * int:x - x coord of cell
382
- * int:y - y coord of ell
383
- * Notes: It might be more efficient to return after the first event has been tirggered,
384
- * but that would mean that only one function of a particular type can be
385
- * subscribed to a particular node.
386
- */
387
- function _fnEventFire ( sType, x, y )
388
- {
389
- var iFired = 0;
390
- var aEvents = _oaoEvents[sType];
391
- for ( var i=0 ; i<aEvents.length ; i++ )
392
- {
393
- if ( (aEvents[i].x == x && aEvents[i].y == y ) ||
394
- (aEvents[i].x === null && aEvents[i].y == y ) ||
395
- (aEvents[i].x == x && aEvents[i].y === null ) ||
396
- (aEvents[i].x === null && aEvents[i].y === null )
397
- )
398
- {
399
- aEvents[i].fn( _fnCellFromCoords(x,y), x, y );
400
- iFired++;
401
- }
236
+ else if ( this.c.focus ) {
237
+ dt.cell( this.c.focus ).focus();
402
238
  }
403
- return iFired;
404
- }
239
+ },
240
+
405
241
 
406
242
 
407
243
 
408
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
409
- * Focus functions
244
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
245
+ * Private methods
410
246
  */
411
247
 
412
- /*
413
- * Function: _fnSetFocus
414
- * Purpose: Set focus on a node, and remove from an old node if needed
415
- * Returns: -
416
- * Inputs: node:nTarget - node we want to focus on
417
- * bool:bAutoScroll - optional - should we scroll the view port to the display
248
+ /**
249
+ * Blur the control
250
+ *
251
+ * @private
418
252
  */
419
- function _fnSetFocus( nTarget, bAutoScroll )
253
+ _blur: function ()
420
254
  {
421
- /* If node already has focus, just ignore this call */
422
- if ( _nOldFocus == nTarget )
423
- {
255
+ if ( ! this.s.enable || ! this.s.lastFocus ) {
424
256
  return;
425
257
  }
426
258
 
427
- if ( typeof bAutoScroll == 'undefined' )
428
- {
429
- bAutoScroll = true;
430
- }
259
+ var cell = this.s.lastFocus;
431
260
 
432
- /* Remove old focus (with blur event if needed) */
433
- if ( _nOldFocus !== null )
434
- {
435
- _fnRemoveFocus( _nOldFocus );
436
- }
261
+ $( cell.node() ).removeClass( this.c.className );
262
+ this.s.lastFocus = null;
437
263
 
438
- /* Add the new class to highlight the focused cell */
439
- $(nTarget).addClass( _sFocusClass );
440
- $(nTarget).parent().addClass( _sFocusClass );
441
-
442
- /* If it's a DataTable then we need to jump the paging to the relevant page */
443
- var oSettings;
444
- if ( _oDatatable )
445
- {
446
- oSettings = _oDatatable;
447
- var iRow = _fnFindDtCell( nTarget )[1];
448
- var bKeyCaptureCache = _bKeyCapture;
449
-
450
- /* Page forwards */
451
- while ( iRow >= oSettings.fnDisplayEnd() )
452
- {
453
- if ( oSettings._iDisplayLength >= 0 )
454
- {
455
- /* Make sure we are not over running the display array */
456
- if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
457
- {
458
- oSettings._iDisplayStart += oSettings._iDisplayLength;
459
- }
460
- }
461
- else
462
- {
463
- oSettings._iDisplayStart = 0;
464
- }
465
- _oDatatable.oApi._fnCalculateEnd( oSettings );
466
- }
264
+ this._emitEvent( 'key-blur', [ this.s.dt, cell ] );
265
+ },
467
266
 
468
- /* Page backwards */
469
- while ( iRow < oSettings._iDisplayStart )
470
- {
471
- oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ?
472
- oSettings._iDisplayStart - oSettings._iDisplayLength :
473
- 0;
474
267
 
475
- if ( oSettings._iDisplayStart < 0 )
476
- {
477
- oSettings._iDisplayStart = 0;
478
- }
479
- _oDatatable.oApi._fnCalculateEnd( oSettings );
268
+ /**
269
+ * Get an array of the column indexes that KeyTable can operate on. This
270
+ * is a merge of the user supplied columns and the visible columns.
271
+ *
272
+ * @private
273
+ */
274
+ _columns: function ()
275
+ {
276
+ var dt = this.s.dt;
277
+ var user = dt.columns( this.c.columns ).indexes();
278
+ var out = [];
279
+
280
+ dt.columns( ':visible' ).every( function (i) {
281
+ if ( user.indexOf( i ) !== -1 ) {
282
+ out.push( i );
480
283
  }
284
+ } );
481
285
 
482
- /* Re-draw the table */
483
- _oDatatable.oApi._fnDraw( oSettings );
286
+ return out;
287
+ },
484
288
 
485
- /* Restore the key capture */
486
- _bKeyCapture = bKeyCaptureCache;
487
- }
488
289
 
489
- /* Cache the information that we are interested in */
490
- var aNewPos = _fnCoordsFromCell( nTarget );
491
- _nOldFocus = nTarget;
492
- _iOldX = aNewPos[0];
493
- _iOldY = aNewPos[1];
494
-
495
- var iViewportHeight, iViewportWidth, iScrollTop, iScrollLeft, iHeight, iWidth, aiPos;
496
- if ( bAutoScroll )
497
- {
498
- /* Scroll the viewport such that the new cell is fully visible in the rendered window */
499
- iViewportHeight = $(window).height();
500
- iViewportWidth = $(window).width();
501
- iScrollTop = $(document).scrollTop();
502
- iScrollLeft = $(document).scrollLeft();
503
- iHeight = nTarget.offsetHeight;
504
- iWidth = nTarget.offsetWidth;
505
- aiPos = _fnGetPos( nTarget );
506
-
507
- /* Take account of scrolling in DataTables 1.7 - remove scrolling since that would add to
508
- * the positioning calculation
509
- */
510
- if ( _oDatatable && typeof oSettings.oScroll != 'undefined' &&
511
- (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
512
- {
513
- aiPos[1] -= $(oSettings.nTable.parentNode).scrollTop();
514
- aiPos[0] -= $(oSettings.nTable.parentNode).scrollLeft();
515
- }
290
+ /**
291
+ * Perform excel like navigation for Editor by triggering an edit on key
292
+ * press
293
+ *
294
+ * @param {integer} key Key code for the pressed key
295
+ * @param {object} orig Original event
296
+ * @private
297
+ */
298
+ _editor: function ( key, orig )
299
+ {
300
+ var dt = this.s.dt;
301
+ var editor = this.c.editor;
516
302
 
517
- /* Correct viewport positioning for vertical scrolling */
518
- if ( aiPos[1]+iHeight > iScrollTop+iViewportHeight )
519
- {
520
- /* Displayed element if off the bottom of the viewport */
521
- _fnSetScrollTop( aiPos[1]+iHeight - iViewportHeight );
522
- }
523
- else if ( aiPos[1] < iScrollTop )
524
- {
525
- /* Displayed element if off the top of the viewport */
526
- _fnSetScrollTop( aiPos[1] );
527
- }
303
+ orig.stopPropagation();
528
304
 
529
- /* Correct viewport positioning for horizontal scrolling */
530
- if ( aiPos[0]+iWidth > iScrollLeft+iViewportWidth )
531
- {
532
- /* Displayed element is off the bottom of the viewport */
533
- _fnSetScrollLeft( aiPos[0]+iWidth - iViewportWidth );
534
- }
535
- else if ( aiPos[0] < iScrollLeft )
536
- {
537
- /* Displayed element if off the Left of the viewport */
538
- _fnSetScrollLeft( aiPos[0] );
539
- }
305
+ editor.inline( this.s.lastFocus.index() );
306
+
307
+ // Excel style - select all text
308
+ var input = $('div.DTE input');
309
+ if ( input.length ) {
310
+ input[0].select();
540
311
  }
541
312
 
542
- /* Take account of scrolling in DataTables 1.7 */
543
- if ( _oDatatable && typeof oSettings.oScroll != 'undefined' &&
544
- (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
545
- {
546
- var dtScrollBody = oSettings.nTable.parentNode;
547
- iViewportHeight = dtScrollBody.clientHeight;
548
- iViewportWidth = dtScrollBody.clientWidth;
549
- iScrollTop = dtScrollBody.scrollTop;
550
- iScrollLeft = dtScrollBody.scrollLeft;
551
- iHeight = nTarget.offsetHeight;
552
- iWidth = nTarget.offsetWidth;
553
-
554
- /* Correct for vertical scrolling */
555
- if ( nTarget.offsetTop + iHeight > iViewportHeight+iScrollTop )
556
- {
557
- dtScrollBody.scrollTop = (nTarget.offsetTop + iHeight) - iViewportHeight;
558
- }
559
- else if ( nTarget.offsetTop < iScrollTop )
560
- {
561
- dtScrollBody.scrollTop = nTarget.offsetTop;
562
- }
313
+ // Reduce the keys the Keys listens for
314
+ dt.keys.enable( 'navigation-only' );
563
315
 
564
- /* Correct for horizontal scrolling */
565
- if ( nTarget.offsetLeft + iWidth > iViewportWidth+iScrollLeft )
566
- {
567
- dtScrollBody.scrollLeft = (nTarget.offsetLeft + iWidth) - iViewportWidth;
316
+ // On blur of the navigation submit
317
+ dt.one( 'key-blur.editor', function () {
318
+ if ( editor.displayed() ) {
319
+ editor.submit();
568
320
  }
569
- else if ( nTarget.offsetLeft < iScrollLeft )
570
- {
571
- dtScrollBody.scrollLeft = nTarget.offsetLeft;
572
- }
573
- }
574
-
575
- /* Focused - so we want to capture the keys */
576
- _fnCaptureKeys();
321
+ } );
577
322
 
578
- /* Fire of the focus event if there is one */
579
- _fnEventFire( "focus", _iOldX, _iOldY );
580
- }
323
+ // Restore full key navigation on close
324
+ editor.one( 'close', function () {
325
+ dt.keys.enable( true );
326
+ dt.off( 'key-blur.editor' );
327
+ } );
328
+ },
581
329
 
582
330
 
583
- /*
584
- * Function: _fnBlur
585
- * Purpose: Blur focus from the whole table
586
- * Returns: -
587
- * Inputs: -
331
+ /**
332
+ * Emit an event on the DataTable for listeners
333
+ *
334
+ * @param {string} name Event name
335
+ * @param {array} args Event arguments
336
+ * @private
588
337
  */
589
- function _fnBlur()
338
+ _emitEvent: function ( name, args )
590
339
  {
591
- _fnRemoveFocus( _nOldFocus );
592
- _iOldX = null;
593
- _iOldY = null;
594
- _nOldFocus = null;
595
- _fnReleaseKeys();
596
- }
340
+ this.s.dt.iterator( 'table', function ( ctx, i ) {
341
+ $(ctx.nTable).triggerHandler( name, args );
342
+ } );
343
+ },
597
344
 
598
345
 
599
- /*
600
- * Function: _fnRemoveFocus
601
- * Purpose: Remove focus from a cell and fire any blur events which are attached
602
- * Returns: -
603
- * Inputs: node:nTarget - cell of interest
346
+ /**
347
+ * Focus on a particular cell, shifting the table's paging if required
348
+ *
349
+ * @param {DataTables.Api|integer} row Can be given as an API instance that
350
+ * contains the cell to focus or as an integer. As the latter it is the
351
+ * visible row index - NOT the data index
352
+ * @param {integer} [column] Not required if a cell is given as the first
353
+ * parameter. Otherwise this is the column data index for the cell to
354
+ * focus on
355
+ * @private
604
356
  */
605
- function _fnRemoveFocus( nTarget )
357
+ _focus: function ( row, column )
606
358
  {
607
- $(nTarget).removeClass( _sFocusClass );
608
- $(nTarget).parent().removeClass( _sFocusClass );
609
- _fnEventFire( "blur", _iOldX, _iOldY );
610
- }
359
+ var that = this;
360
+ var dt = this.s.dt;
361
+ var pageInfo = dt.page.info();
362
+ var lastFocus = this.s.lastFocus;
611
363
 
364
+ if ( ! this.s.enable ) {
365
+ return;
366
+ }
612
367
 
613
- /*
614
- * Function: _fnClick
615
- * Purpose: Focus on the element that has been clicked on by the user
616
- * Returns: -
617
- * Inputs: event:e - click event
618
- */
619
- function _fnClick ( e )
620
- {
621
- var nTarget = this;
622
- while ( nTarget.nodeName != "TD" )
623
- {
624
- nTarget = nTarget.parentNode;
368
+ if ( typeof row !== 'number' ) {
369
+ // Convert the cell to a row and column
370
+ var index = row.index();
371
+ column = index.column;
372
+ row = dt
373
+ .rows( { filter: 'applied', order: 'applied' } )
374
+ .indexes()
375
+ .indexOf( index.row );
376
+
377
+ // For server-side processing normalise the row by adding the start
378
+ // point, since `rows().indexes()` includes only rows that are
379
+ // available at the client-side
380
+ if ( pageInfo.serverSide ) {
381
+ row += pageInfo.start;
382
+ }
625
383
  }
626
384
 
627
- _fnSetFocus( nTarget );
628
- _fnCaptureKeys();
629
- }
385
+ // Is the row on the current page? If not, we need to redraw to show the
386
+ // page
387
+ if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start+pageInfo.length) ) {
388
+ dt
389
+ .one( 'draw', function () {
390
+ that._focus( row, column );
391
+ } )
392
+ .page( Math.floor( row / pageInfo.length ) )
393
+ .draw( false );
630
394
 
395
+ return;
396
+ }
631
397
 
398
+ // In the available columns?
399
+ if ( $.inArray( column, this._columns() ) === -1 ) {
400
+ return;
401
+ }
632
402
 
633
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
634
- * Key events
635
- */
403
+ // De-normalise the server-side processing row, so we select the row
404
+ // in its displayed position
405
+ if ( pageInfo.serverSide ) {
406
+ row -= pageInfo.start;
407
+ }
408
+
409
+ var cell = dt.cell( ':eq('+row+')', column, {search: 'applied'} );
410
+
411
+ if ( lastFocus ) {
412
+ // Don't trigger a refocus on the same cell
413
+ if ( lastFocus.node() === cell.node() ) {
414
+ return;
415
+ }
416
+
417
+ // Otherwise blur the old focus
418
+ this._blur();
419
+ }
420
+
421
+ var node = $( cell.node() );
422
+ node.addClass( this.c.className );
423
+
424
+ // Shift viewpoint and page to make cell visible
425
+ this._scroll( $(window), $(document.body), node, 'offset' );
426
+
427
+ var bodyParent = dt.table().body().parentNode;
428
+ if ( bodyParent !== dt.table().header().parentNode ) {
429
+ var parent = $(bodyParent.parentNode);
430
+
431
+ this._scroll( parent, parent, node, 'position' );
432
+ }
433
+
434
+ // Event and finish
435
+ this.s.lastFocus = cell;
636
436
 
637
- /*
638
- * Function: _fnKey
639
- * Purpose: Deal with a key events, be it moving the focus or return etc.
640
- * Returns: bool: - allow browser default action
641
- * Inputs: event:e - key event
437
+ this._emitEvent( 'key-focus', [ this.s.dt, cell ] );
438
+ dt.state.save();
439
+ },
440
+
441
+
442
+ /**
443
+ * Handle key press
444
+ *
445
+ * @param {object} e Event
446
+ * @private
642
447
  */
643
- function _fnKey ( e )
448
+ _key: function ( e )
644
449
  {
645
- /* If user or system has blocked KeyTable from doing anything, just ignore this event */
646
- if ( _that.block || !_bKeyCapture )
647
- {
648
- return true;
450
+ if ( ! this.s.enable ) {
451
+ return;
649
452
  }
650
453
 
651
- /* If a modifier key is pressed (exapct shift), ignore the event */
652
- if ( e.metaKey || e.altKey || e.ctrlKey )
653
- {
654
- return true;
655
- }
656
- var
657
- x, y,
658
- iTableWidth = _nBody.getElementsByTagName('tr')[0].getElementsByTagName('td').length,
659
- iTableHeight;
660
-
661
- /* Get table height and width - done here so as to be dynamic (if table is updated) */
662
- if ( _oDatatable )
663
- {
664
- /*
665
- * Locate the current node in the DataTable overriding the old positions - the reason for
666
- * is is that there might have been some DataTables interaction between the last focus and
667
- * now
668
- */
669
- iTableHeight = _oDatatable.aiDisplay.length;
670
-
671
- var aDtPos = _fnFindDtCell( _nOldFocus );
672
- if ( aDtPos === null )
673
- {
674
- /* If the table has been updated such that the focused cell can't be seen - do nothing */
675
- return;
676
- }
677
- _iOldX = aDtPos[ 0 ];
678
- _iOldY = aDtPos[ 1 ];
454
+ if ( e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey ) {
455
+ return;
679
456
  }
680
- else
681
- {
682
- iTableHeight = _nBody.getElementsByTagName('tr').length;
457
+
458
+ // If not focused, then there is no key action to take
459
+ var cell = this.s.lastFocus;
460
+ if ( ! cell ) {
461
+ return;
683
462
  }
684
463
 
685
- /* Capture shift+tab to match the left arrow key */
686
- var iKey = (e.keyCode == 9 && e.shiftKey) ? -1 : e.keyCode;
464
+ var that = this;
465
+ var dt = this.s.dt;
687
466
 
688
- switch( iKey )
689
- {
690
- case 13: /* return */
691
- e.preventDefault();
692
- e.stopPropagation();
693
- _fnEventFire( "action", _iOldX, _iOldY );
694
- return true;
695
-
696
- case 27: /* esc */
697
- if ( !_fnEventFire( "esc", _iOldX, _iOldY ) )
698
- {
699
- /* Only lose focus if there isn't an escape handler on the cell */
700
- _fnBlur();
701
- return;
702
- }
703
- x = _iOldX;
704
- y = _iOldY;
467
+ // If we are not listening for this key, do nothing
468
+ if ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) {
469
+ return;
470
+ }
471
+
472
+ switch( e.keyCode ) {
473
+ case 9: // tab
474
+ this._shift( e, e.shiftKey ? 'left' : 'right', true );
705
475
  break;
706
476
 
707
- case -1:
708
- case 37: /* left arrow */
709
- if ( _iOldX > 0 ) {
710
- x = _iOldX - 1;
711
- y = _iOldY;
712
- } else if ( _iOldY > 0 ) {
713
- x = iTableWidth-1;
714
- y = _iOldY - 1;
715
- } else {
716
- /* at start of table */
717
- if ( iKey == -1 && _bForm )
718
- {
719
- /* If we are in a form, return focus to the 'input' element such that tabbing will
720
- * follow correctly in the browser
721
- */
722
- _bInputFocused = true;
723
- _nInput.focus();
724
-
725
- /* This timeout is a little nasty - but IE appears to have some asyhnc behaviour for
726
- * focus
727
- */
728
- setTimeout( function(){ _bInputFocused = false; }, 0 );
729
- _bKeyCapture = false;
730
- _fnBlur();
731
- return true;
732
- }
733
- else
734
- {
735
- return false;
736
- }
477
+ case 27: // esc
478
+ if ( this.s.blurable && this.s.enable === true ) {
479
+ this._blur();
737
480
  }
738
481
  break;
739
482
 
740
- case 38: /* up arrow */
741
- if ( _iOldY > 0 ) {
742
- x = _iOldX;
743
- y = _iOldY - 1;
744
- } else {
745
- return false;
746
- }
483
+ case 33: // page up (previous page)
484
+ case 34: // page down (next page)
485
+ e.preventDefault();
486
+ var index = dt.cells( {page: 'current'} ).nodes().indexOf( cell.node() );
487
+
488
+ dt
489
+ .one( 'draw', function () {
490
+ var nodes = dt.cells( {page: 'current'} ).nodes();
491
+
492
+ that._focus( dt.cell( index < nodes.length ?
493
+ nodes[ index ] :
494
+ nodes[ nodes.length-1 ]
495
+ ) );
496
+ } )
497
+ .page( e.keyCode === 33 ? 'previous' : 'next' )
498
+ .draw( false );
747
499
  break;
748
500
 
749
- case 36: /* home */
750
- x = _iOldX;
751
- y = 0;
501
+ case 35: // end (end of current page)
502
+ case 36: // home (start of current page)
503
+ e.preventDefault();
504
+ var indexes = dt.cells( {page: 'current'} ).indexes();
505
+
506
+ this._focus( dt.cell(
507
+ indexes[ e.keyCode === 35 ? indexes.length-1 : 0 ]
508
+ ) );
752
509
  break;
753
510
 
754
- case 33: /* page up */
755
- x = _iOldX;
756
- y = _iOldY - 10;
757
- if (y < 0) {
758
- y = 0;
759
- }
511
+ case 37: // left arrow
512
+ this._shift( e, 'left' );
760
513
  break;
761
514
 
762
- case 9: /* tab */
763
- case 39: /* right arrow */
764
- if ( _iOldX < iTableWidth-1 ) {
765
- x = _iOldX + 1;
766
- y = _iOldY;
767
- } else if ( _iOldY < iTableHeight-1 ) {
768
- x = 0;
769
- y = _iOldY + 1;
770
- } else {
771
- /* at end of table */
772
- if ( iKey == 9 && _bForm )
773
- {
774
- /* If we are in a form, return focus to the 'input' element such that tabbing will
775
- * follow correctly in the browser
776
- */
777
- _bInputFocused = true;
778
- _nInput.focus();
779
-
780
- /* This timeout is a little nasty - but IE appears to have some asyhnc behaviour for
781
- * focus
782
- */
783
- setTimeout( function(){ _bInputFocused = false; }, 0 );
784
- _bKeyCapture = false;
785
- _fnBlur();
786
- return true;
787
- }
788
- else
789
- {
790
- return false;
791
- }
792
- }
515
+ case 38: // up arrow
516
+ this._shift( e, 'up' );
793
517
  break;
794
518
 
795
- case 40: /* down arrow */
796
- if ( _iOldY < iTableHeight-1 ) {
797
- x = _iOldX;
798
- y = _iOldY + 1;
799
- } else {
800
- return false;
801
- }
519
+ case 39: // right arrow
520
+ this._shift( e, 'right' );
802
521
  break;
803
522
 
804
- case 35: /* end */
805
- x = _iOldX;
806
- y = iTableHeight-1;
523
+ case 40: // down arrow
524
+ this._shift( e, 'down' );
807
525
  break;
808
526
 
809
- case 34: /* page down */
810
- x = _iOldX;
811
- y = _iOldY+10;
812
- if (y > iTableHeight-1) {
813
- y = iTableHeight-1;
527
+ default:
528
+ // Everything else - pass through only when fully enabled
529
+ if ( this.s.enable === true ) {
530
+ this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus, e ] );
814
531
  }
815
532
  break;
816
-
817
- default: /* Nothing we are interested in */
818
- return true;
819
533
  }
820
-
821
- _fnSetFocus( _fnCellFromCoords(x, y) );
822
- return false;
823
- }
534
+ },
824
535
 
825
536
 
826
- /*
827
- * Function: _fnCaptureKeys
828
- * Purpose: Start capturing key events for this table
829
- * Returns: -
830
- * Inputs: -
537
+ /**
538
+ * Scroll a container to make a cell visible in it. This can be used for
539
+ * both DataTables scrolling and native window scrolling.
540
+ *
541
+ * @param {jQuery} container Scrolling container
542
+ * @param {jQuery} scroller Item being scrolled
543
+ * @param {jQuery} cell Cell in the scroller
544
+ * @param {string} posOff `position` or `offset` - which to use for the
545
+ * calculation. `offset` for the document, otherwise `position`
546
+ * @private
831
547
  */
832
- function _fnCaptureKeys( )
548
+ _scroll: function ( container, scroller, cell, posOff )
833
549
  {
834
- if ( !_bKeyCapture )
835
- {
836
- _bKeyCapture = true;
550
+ var offset = cell[posOff]();
551
+ var height = cell.outerHeight();
552
+ var width = cell.outerWidth();
553
+
554
+ var scrollTop = scroller.scrollTop();
555
+ var scrollLeft = scroller.scrollLeft();
556
+ var containerHeight = container.height();
557
+ var containerWidth = container.width();
558
+
559
+ // Top correction
560
+ if ( offset.top < scrollTop ) {
561
+ scroller.scrollTop( offset.top );
837
562
  }
838
- }
563
+
564
+ // Left correction
565
+ if ( offset.left < scrollLeft ) {
566
+ scroller.scrollLeft( offset.left );
567
+ }
568
+
569
+ // Bottom correction
570
+ if ( offset.top + height > scrollTop + containerHeight ) {
571
+ scroller.scrollTop( offset.top + height - containerHeight );
572
+ }
573
+
574
+ // Right correction
575
+ if ( offset.left + width > scrollLeft + containerWidth ) {
576
+ scroller.scrollLeft( offset.left + width - containerWidth );
577
+ }
578
+ },
839
579
 
840
580
 
841
- /*
842
- * Function: _fnReleaseKeys
843
- * Purpose: Stop capturing key events for this table
844
- * Returns: -
845
- * Inputs: -
581
+ /**
582
+ * Calculate a single offset movement in the table - up, down, left and
583
+ * right and then perform the focus if possible
584
+ *
585
+ * @param {object} e Event object
586
+ * @param {string} direction Movement direction
587
+ * @param {boolean} keyBlurable `true` if the key press can result in the
588
+ * table being blurred. This is so arrow keys won't blur the table, but
589
+ * tab will.
590
+ * @private
846
591
  */
847
- function _fnReleaseKeys( )
592
+ _shift: function ( e, direction, keyBlurable )
848
593
  {
849
- _bKeyCapture = false;
850
- }
594
+ var that = this;
595
+ var dt = this.s.dt;
596
+ var pageInfo = dt.page.info();
597
+ var rows = pageInfo.recordsDisplay;
598
+ var currentCell = this.s.lastFocus;
599
+ var columns = this._columns();
600
+
601
+ if ( ! currentCell ) {
602
+ return;
603
+ }
851
604
 
605
+ var currRow = dt
606
+ .rows( { filter: 'applied', order: 'applied' } )
607
+ .indexes()
608
+ .indexOf( currentCell.index().row );
852
609
 
610
+ // When server-side processing, `rows().indexes()` only gives the rows
611
+ // that are available at the client-side, so we need to normalise the
612
+ // row's current position by the display start point
613
+ if ( pageInfo.serverSide ) {
614
+ currRow += pageInfo.start;
615
+ }
853
616
 
854
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
855
- * Support functions
856
- */
617
+ var currCol = dt
618
+ .columns( columns )
619
+ .indexes()
620
+ .indexOf( currentCell.index().column );
857
621
 
858
- /*
859
- * Function: _fnCellFromCoords
860
- * Purpose: Calulate the target TD cell from x and y coordinates
861
- * Returns: node: - TD target
862
- * Inputs: int:x - x coordinate
863
- * int:y - y coordinate
864
- */
865
- function _fnCellFromCoords( x, y )
866
- {
867
- if ( _oDatatable )
868
- {
869
- if ( typeof _oDatatable.aoData[ _oDatatable.aiDisplay[ y ] ] != 'undefined' )
870
- {
871
- return _oDatatable.aoData[ _oDatatable.aiDisplay[ y ] ].nTr.getElementsByTagName('td')[x];
622
+ var
623
+ row = currRow,
624
+ column = columns[ currCol ]; // row is the display, column is an index
625
+
626
+ if ( direction === 'right' ) {
627
+ if ( currCol >= columns.length - 1 ) {
628
+ row++;
629
+ column = columns[0];
872
630
  }
873
- else
874
- {
875
- return null;
631
+ else {
632
+ column = columns[ currCol+1 ];
876
633
  }
877
634
  }
878
- else
879
- {
880
- return $('tr:eq('+y+')>td:eq('+x+')', _nBody )[0];
635
+ else if ( direction === 'left' ) {
636
+ if ( currCol === 0 ) {
637
+ row--;
638
+ column = columns[ columns.length - 1 ];
639
+ }
640
+ else {
641
+ column = columns[ currCol-1 ];
642
+ }
643
+ }
644
+ else if ( direction === 'up' ) {
645
+ row--;
646
+ }
647
+ else if ( direction === 'down' ) {
648
+ row++;
881
649
  }
882
- }
883
650
 
651
+ if ( row >= 0 && row < rows && $.inArray( column, columns ) !== -1
652
+ ) {
653
+ e.preventDefault();
884
654
 
885
- /*
886
- * Function: _fnCoordsFromCell
887
- * Purpose: Calculate the x and y position in a table from a TD cell
888
- * Returns: array[2] int: [x, y]
889
- * Inputs: node:n - TD cell of interest
890
- * Notes: Not actually interested in this for DataTables since it might go out of date
891
- */
892
- function _fnCoordsFromCell( n )
893
- {
894
- if ( _oDatatable )
895
- {
896
- return [
897
- $('td', n.parentNode).index(n),
898
- $('tr', n.parentNode.parentNode).index(n.parentNode) + _oDatatable._iDisplayStart
899
- ];
655
+ this._focus( row, column );
900
656
  }
901
- else
902
- {
903
- return [
904
- $('td', n.parentNode).index(n),
905
- $('tr', n.parentNode.parentNode).index(n.parentNode)
906
- ];
657
+ else if ( ! keyBlurable || ! this.c.blurable ) {
658
+ // No new focus, but if the table isn't blurable, then don't loose
659
+ // focus
660
+ e.preventDefault();
907
661
  }
908
- }
662
+ else {
663
+ this._blur();
664
+ }
665
+ },
909
666
 
910
667
 
911
- /*
912
- * Function: _fnSetScrollTop
913
- * Purpose: Set the vertical scrolling position
914
- * Returns: -
915
- * Inputs: int:iPos - scrolltop
916
- * Notes: This is so nasty, but without browser detection you can't tell which you should set
917
- * So on browsers that support both, the scroll top will be set twice. I can live with
918
- * that :-)
668
+ /**
669
+ * Create a hidden input element that can receive focus on behalf of the
670
+ * table
671
+ *
672
+ * @private
919
673
  */
920
- function _fnSetScrollTop( iPos )
674
+ _tabInput: function ()
921
675
  {
922
- document.documentElement.scrollTop = iPos;
923
- document.body.scrollTop = iPos;
924
- }
676
+ var that = this;
677
+ var dt = this.s.dt;
678
+ var tabIndex = this.c.tabIndex !== null ?
679
+ this.c.tabIndex :
680
+ dt.settings()[0].iTabIndex;
925
681
 
682
+ if ( tabIndex == -1 ) {
683
+ return;
684
+ }
926
685
 
927
- /*
928
- * Function: _fnSetScrollLeft
929
- * Purpose: Set the horizontal scrolling position
930
- * Returns: -
931
- * Inputs: int:iPos - scrollleft
932
- */
933
- function _fnSetScrollLeft( iPos )
934
- {
935
- document.documentElement.scrollLeft = iPos;
936
- document.body.scrollLeft = iPos;
686
+ var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>')
687
+ .css( {
688
+ position: 'absolute',
689
+ height: 1,
690
+ width: 0,
691
+ overflow: 'hidden'
692
+ } )
693
+ .insertBefore( dt.table().node() );
694
+
695
+ div.children().on( 'focus', function () {
696
+ that._focus( dt.cell(':eq(0)', {page: 'current'}) );
697
+ } );
937
698
  }
699
+ } );
938
700
 
939
701
 
940
- /*
941
- * Function: _fnGetPos
942
- * Purpose: Get the position of an object on the rendered page
943
- * Returns: array[2] int: [left, right]
944
- * Inputs: node:obj - element of interest
702
+ /**
703
+ * KeyTable default settings for initialisation
704
+ *
705
+ * @namespace
706
+ * @name KeyTable.defaults
707
+ * @static
708
+ */
709
+ KeyTable.defaults = {
710
+ /**
711
+ * Can focus be removed from the table
712
+ * @type {Boolean}
945
713
  */
946
- function _fnGetPos ( obj )
947
- {
948
- var iLeft = 0;
949
- var iTop = 0;
950
-
951
- if (obj.offsetParent)
952
- {
953
- iLeft = obj.offsetLeft;
954
- iTop = obj.offsetTop;
955
- obj = obj.offsetParent;
956
- while (obj)
957
- {
958
- iLeft += obj.offsetLeft;
959
- iTop += obj.offsetTop;
960
- obj = obj.offsetParent;
961
- }
962
- }
963
- return [iLeft,iTop];
964
- }
714
+ blurable: true,
965
715
 
716
+ /**
717
+ * Class to give to the focused cell
718
+ * @type {String}
719
+ */
720
+ className: 'focus',
966
721
 
967
- /*
968
- * Function: _fnFindDtCell
969
- * Purpose: Get the coords. of a cell from the DataTables internal information
970
- * Returns: array[2] int: [x, y] coords. or null if not found
971
- * Inputs: node:nTarget - the node of interest
722
+ /**
723
+ * Columns that can be focused. This is automatically merged with the
724
+ * visible columns as only visible columns can gain focus.
725
+ * @type {String}
972
726
  */
973
- function _fnFindDtCell( nTarget )
974
- {
975
- for ( var i=0, iLen=_oDatatable.aiDisplay.length ; i<iLen ; i++ )
976
- {
977
- var nTr = _oDatatable.aoData[ _oDatatable.aiDisplay[i] ].nTr;
978
- var nTds = nTr.getElementsByTagName('td');
979
- for ( var j=0, jLen=nTds.length ; j<jLen ; j++ )
980
- {
981
- if ( nTds[j] == nTarget )
982
- {
983
- return [ j, i ];
984
- }
985
- }
986
- }
987
- return null;
988
- }
727
+ columns: '', // all
989
728
 
729
+ /**
730
+ * Editor instance to automatically perform Excel like navigation
731
+ * @type {Editor}
732
+ */
733
+ editor: null,
990
734
 
735
+ /**
736
+ * Select a cell to automatically select on start up. `null` for no
737
+ * automatic selection
738
+ * @type {cell-selector}
739
+ */
740
+ focus: null,
991
741
 
992
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
993
- * Initialisation
742
+ /**
743
+ * Array of keys to listen for
744
+ * @type {null|array}
994
745
  */
746
+ keys: null,
995
747
 
996
- /*
997
- * Function: _fnInit
998
- * Purpose: Initialise the KeyTable
999
- * Returns: -
1000
- * Inputs: object:oInit - optional - Initalisation object with the following parameters:
1001
- * array[2] int:focus - x and y coordinates of the initial target
1002
- * or
1003
- * node:focus - the node to set initial focus on
1004
- * node:table - the table to use, if not given, first table with class 'KeyTable' will be used
1005
- * string:focusClass - focusing class to give to table elements
1006
- * object:that - focus
1007
- * bool:initScroll - scroll the view port on load, default true
1008
- * int:tabIndex - the tab index to give the hidden input element
748
+ /**
749
+ * Tab index for where the table should sit in the document's tab flow
750
+ * @type {integer|null}
1009
751
  */
1010
- function _fnInit( table, datatable, oInit, that )
1011
- {
1012
- /* Save scope */
1013
- _that = that;
752
+ tabIndex: null
753
+ };
1014
754
 
1015
- /* Capture undefined initialisation and apply the defaults */
1016
- if ( typeof oInit == 'undefined' ) {
1017
- oInit = {};
1018
- }
1019
755
 
1020
- if ( typeof oInit.focus == 'undefined' ) {
1021
- oInit.focus = [0,0];
1022
- }
1023
756
 
1024
- oInit.table = table;
1025
- $(oInit.table).addClass('KeyTable');
757
+ KeyTable.version = "2.1.0";
1026
758
 
1027
- if ( typeof oInit.focusClass != 'undefined' ) {
1028
- _sFocusClass = oInit.focusClass;
1029
- }
1030
759
 
1031
- if ( typeof datatable != 'undefined' ) {
1032
- _oDatatable = datatable;
1033
- }
760
+ $.fn.dataTable.KeyTable = KeyTable;
761
+ $.fn.DataTable.KeyTable = KeyTable;
1034
762
 
1035
- if ( typeof oInit.initScroll == 'undefined' ) {
1036
- oInit.initScroll = true;
1037
- }
1038
763
 
1039
- if ( typeof oInit.form == 'undefined' ) {
1040
- oInit.form = false;
1041
- }
1042
- _bForm = oInit.form;
1043
-
1044
- /* Cache the tbody node of interest */
1045
- _nBody = oInit.table.getElementsByTagName('tbody')[0];
1046
-
1047
- /* If the table is inside a form, then we need a hidden input box which can be used by the
1048
- * browser to catch the browser tabbing for our table
1049
- */
1050
- if ( _bForm )
1051
- {
1052
- var nDiv = document.createElement('div');
1053
- _nInput = document.createElement('input');
1054
- nDiv.style.height = "1px"; /* Opera requires a little something */
1055
- nDiv.style.width = "0px";
1056
- nDiv.style.overflow = "hidden";
1057
- if ( typeof oInit.tabIndex != 'undefined' )
1058
- {
1059
- _nInput.tabIndex = oInit.tabIndex;
1060
- }
1061
- nDiv.appendChild(_nInput);
1062
- oInit.table.parentNode.insertBefore( nDiv, oInit.table.nextSibling );
1063
-
1064
- $(_nInput).focus( function () {
1065
- /* See if we want to 'tab into' the table or out */
1066
- if ( !_bInputFocused )
1067
- {
1068
- _bKeyCapture = true;
1069
- _bInputFocused = false;
1070
- if ( typeof oInit.focus.nodeName != "undefined" )
1071
- {
1072
- _fnSetFocus( oInit.focus, oInit.initScroll );
1073
- }
1074
- else
1075
- {
1076
- _fnSetFocus( _fnCellFromCoords( oInit.focus[0], oInit.focus[1]), oInit.initScroll );
1077
- }
1078
-
1079
- /* Need to interup the thread for this to work */
1080
- setTimeout( function() { _nInput.blur(); }, 0 );
1081
- }
1082
- } );
1083
- _bKeyCapture = false;
1084
- }
1085
- else
1086
- {
1087
- /* Set the initial focus on the table */
1088
- if ( typeof oInit.focus.nodeName != "undefined" )
1089
- {
1090
- _fnSetFocus( oInit.focus, oInit.initScroll );
1091
- }
1092
- else
1093
- {
1094
- _fnSetFocus( _fnCellFromCoords( oInit.focus[0], oInit.focus[1]), oInit.initScroll );
1095
- }
1096
- _fnCaptureKeys();
764
+ DataTable.Api.register( 'cell.blur()', function () {
765
+ return this.iterator( 'table', function (ctx) {
766
+ if ( ctx.keytable ) {
767
+ ctx.keytable.blur();
1097
768
  }
769
+ } );
770
+ } );
1098
771
 
1099
- /* Add event listeners */
1100
- $(document).bind( "keydown", _fnKey );
1101
-
1102
- if ( _oDatatable )
1103
- {
1104
- $(_oDatatable.nTable).on( 'click', 'td', _fnClick );
772
+ DataTable.Api.register( 'cell().focus()', function () {
773
+ return this.iterator( 'cell', function (ctx, row, column) {
774
+ if ( ctx.keytable ) {
775
+ ctx.keytable.focus( row, column );
1105
776
  }
1106
- else
1107
- {
1108
- $(_nBody).on( 'click', 'td', _fnClick );
777
+ } );
778
+ } );
779
+
780
+ DataTable.Api.register( 'keys.disable()', function () {
781
+ return this.iterator( 'table', function (ctx) {
782
+ if ( ctx.keytable ) {
783
+ ctx.keytable.enable( false );
1109
784
  }
785
+ } );
786
+ } );
1110
787
 
1111
- /* Loose table focus when click outside the table */
1112
- $(document).click( function(e) {
1113
- var nTarget = e.target;
1114
- var bTableClick = false;
1115
- while ( nTarget )
1116
- {
1117
- if ( nTarget == oInit.table )
1118
- {
1119
- bTableClick = true;
1120
- break;
1121
- }
1122
- nTarget = nTarget.parentNode;
1123
- }
1124
- if ( !bTableClick )
1125
- {
1126
- _fnBlur();
1127
- }
1128
- } );
1129
- }
788
+ DataTable.Api.register( 'keys.enable()', function ( opts ) {
789
+ return this.iterator( 'table', function (ctx) {
790
+ if ( ctx.keytable ) {
791
+ ctx.keytable.enable( opts === undefined ? true : opts );
792
+ }
793
+ } );
794
+ } );
1130
795
 
1131
- var table, datatable;
796
+ // Cell selector
797
+ DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
798
+ var focused = opts.focused;
799
+ var kt = settings.keytable;
800
+ var out = [];
1132
801
 
1133
- if ( oInit === undefined ) {
1134
- table = $('table.KeyTable')[0];
1135
- datatable = null;
1136
- }
1137
- else if ( $.isPlainObject( oInit ) ) {
1138
- table = oInit.table;
1139
- datatable = oInit.datatable;
1140
- }
1141
- else {
1142
- datatable = new $.fn.dataTable.Api( oInit ).settings()[0];
1143
- table = datatable.nTable;
802
+ if ( ! kt || focused === undefined ) {
803
+ return cells;
1144
804
  }
1145
- /* Initialise our new object */
1146
- _fnInit( table, datatable, oInit, this );
1147
- };
1148
805
 
806
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
807
+ if ( (focused === true && kt.focused( cells[i] ) ) ||
808
+ (focused === false && ! kt.focused( cells[i] ) )
809
+ ) {
810
+ out.push( cells[i] );
811
+ }
812
+ }
1149
813
 
1150
- KeyTable.version = "1.2.1";
1151
-
814
+ return out;
815
+ } );
1152
816
 
1153
- $.fn.dataTable.KeyTable = KeyTable;
1154
- $.fn.DataTable.KeyTable = KeyTable;
1155
817
 
818
+ // Attach a listener to the document which listens for DataTables initialisation
819
+ // events so we can automatically initialise
820
+ $(document).on( 'preInit.dt.dtk', function (e, settings, json) {
821
+ if ( e.namespace !== 'dt' ) {
822
+ return;
823
+ }
1156
824
 
1157
- return KeyTable;
1158
- }; // /factory
825
+ var init = settings.oInit.keys;
826
+ var defaults = DataTable.defaults.keys;
1159
827
 
828
+ if ( init || defaults ) {
829
+ var opts = $.extend( {}, init, defaults );
1160
830
 
1161
- // Define as an AMD module if possible
1162
- if ( typeof define === 'function' && define.amd ) {
1163
- define( ['jquery', 'datatables'], factory );
1164
- }
1165
- else if ( typeof exports === 'object' ) {
1166
- // Node/CommonJS
1167
- factory( require('jquery'), require('datatables') );
1168
- }
1169
- else if ( jQuery && !jQuery.fn.dataTable.KeyTable ) {
1170
- // Otherwise simply initialise as normal, stopping multiple evaluation
1171
- factory( jQuery, jQuery.fn.dataTable );
1172
- }
831
+ if ( init !== false ) {
832
+ new KeyTable( settings, opts );
833
+ }
834
+ }
835
+ } );
1173
836
 
1174
837
 
1175
- })(window, document);
838
+ return KeyTable;
839
+ }));