jquery-datatables-rails 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }));