source: public/vendors/dataTable/KeyTable-2.5.1/js/dataTables.keyTable.js@ 495dd28

develop
Last change on this file since 495dd28 was 7304c7f, checked in by beratkjufliju <kufliju@…>, 3 years ago

added user authentication, create & forgot password methods and blades

  • Property mode set to 100644
File size: 28.8 KB
RevLine 
[7304c7f]1/*! KeyTable 2.5.1
2 * ©2009-2019 SpryMedia Ltd - datatables.net/license
3 */
4
5/**
6 * @summary KeyTable
7 * @description Spreadsheet like keyboard navigation for DataTables
8 * @version 2.5.1
9 * @file dataTables.keyTable.js
10 * @author SpryMedia Ltd (www.sprymedia.co.uk)
11 * @contact www.sprymedia.co.uk/contact
12 * @copyright Copyright 2009-2019 SpryMedia Ltd.
13 *
14 * This source file is free software, available under the following license:
15 * MIT license - http://datatables.net/license/mit
16 *
17 * This source file is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
20 *
21 * For details please refer to: http://www.datatables.net
22 */
23
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 }
37
38 if ( ! $ || ! $.fn.dataTable ) {
39 $ = require('datatables.net')(root, $).$;
40 }
41
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';
51var DataTable = $.fn.dataTable;
52var namespaceCounter = 0;
53
54
55var KeyTable = function ( dt, opts ) {
56 // Sanity check that we are using DataTables 1.10 or newer
57 if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
58 throw 'KeyTable requires DataTables 1.10.8 or newer';
59 }
60
61 // User and defaults configuration object
62 this.c = $.extend( true, {},
63 DataTable.defaults.keyTable,
64 KeyTable.defaults,
65 opts
66 );
67
68 // Internal settings
69 this.s = {
70 /** @type {DataTable.Api} DataTables' API instance */
71 dt: new DataTable.Api( dt ),
72
73 enable: true,
74
75 /** @type {bool} Flag for if a draw is triggered by focus */
76 focusDraw: false,
77
78 /** @type {bool} Flag to indicate when waiting for a draw to happen.
79 * Will ignore key presses at this point
80 */
81 waitingForDraw: false,
82
83 /** @type {object} Information about the last cell that was focused */
84 lastFocus: null,
85
86 /** @type {string} Unique namespace per instance */
87 namespace: '.keyTable-'+(namespaceCounter++)
88 };
89
90 // DOM items
91 this.dom = {
92
93 };
94
95 // Check if row reorder has already been initialised on this table
96 var settings = this.s.dt.settings()[0];
97 var exisiting = settings.keytable;
98 if ( exisiting ) {
99 return exisiting;
100 }
101
102 settings.keytable = this;
103 this._constructor();
104};
105
106
107$.extend( KeyTable.prototype, {
108 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
109 * API methods for DataTables API interface
110 */
111
112 /**
113 * Blur the table's cell focus
114 */
115 blur: function ()
116 {
117 this._blur();
118 },
119
120 /**
121 * Enable cell focus for the table
122 *
123 * @param {string} state Can be `true`, `false` or `-string navigation-only`
124 */
125 enable: function ( state )
126 {
127 this.s.enable = state;
128 },
129
130 /**
131 * Focus on a cell
132 * @param {integer} row Row index
133 * @param {integer} column Column index
134 */
135 focus: function ( row, column )
136 {
137 this._focus( this.s.dt.cell( row, column ) );
138 },
139
140 /**
141 * Is the cell focused
142 * @param {object} cell Cell index to check
143 * @returns {boolean} true if focused, false otherwise
144 */
145 focused: function ( cell )
146 {
147 var lastFocus = this.s.lastFocus;
148
149 if ( ! lastFocus ) {
150 return false;
151 }
152
153 var lastIdx = this.s.lastFocus.cell.index();
154 return cell.row === lastIdx.row && cell.column === lastIdx.column;
155 },
156
157
158 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
159 * Constructor
160 */
161
162 /**
163 * Initialise the KeyTable instance
164 *
165 * @private
166 */
167 _constructor: function ()
168 {
169 this._tabInput();
170
171 var that = this;
172 var dt = this.s.dt;
173 var table = $( dt.table().node() );
174 var namespace = this.s.namespace;
175 var editorBlock = false;
176
177 // Need to be able to calculate the cell positions relative to the table
178 if ( table.css('position') === 'static' ) {
179 table.css( 'position', 'relative' );
180 }
181
182 // Click to focus
183 $( dt.table().body() ).on( 'click'+namespace, 'th, td', function (e) {
184 if ( that.s.enable === false ) {
185 return;
186 }
187
188 var cell = dt.cell( this );
189
190 if ( ! cell.any() ) {
191 return;
192 }
193
194 that._focus( cell, null, false, e );
195 } );
196
197 // Key events
198 $( document ).on( 'keydown'+namespace, function (e) {
199 if ( ! editorBlock ) {
200 that._key( e );
201 }
202 } );
203
204 // Click blur
205 if ( this.c.blurable ) {
206 $( document ).on( 'mousedown'+namespace, function ( e ) {
207 // Click on the search input will blur focus
208 if ( $(e.target).parents( '.dataTables_filter' ).length ) {
209 that._blur();
210 }
211
212 // If the click was inside the DataTables container, don't blur
213 if ( $(e.target).parents().filter( dt.table().container() ).length ) {
214 return;
215 }
216
217 // Don't blur in Editor form
218 if ( $(e.target).parents('div.DTE').length ) {
219 return;
220 }
221
222 // Or an Editor date input
223 if ( $(e.target).parents('div.editor-datetime').length ) {
224 return;
225 }
226
227 //If the click was inside the fixed columns container, don't blur
228 if ( $(e.target).parents().filter('.DTFC_Cloned').length ) {
229 return;
230 }
231
232 that._blur();
233 } );
234 }
235
236 if ( this.c.editor ) {
237 var editor = this.c.editor;
238
239 // Need to disable KeyTable when the main editor is shown
240 editor.on( 'open.keyTableMain', function (e, mode, action) {
241 if ( mode !== 'inline' && that.s.enable ) {
242 that.enable( false );
243
244 editor.one( 'close'+namespace, function () {
245 that.enable( true );
246 } );
247 }
248 } );
249
250 if ( this.c.editOnFocus ) {
251 dt.on( 'key-focus'+namespace+' key-refocus'+namespace, function ( e, dt, cell, orig ) {
252 that._editor( null, orig, true );
253 } );
254 }
255
256 // Activate Editor when a key is pressed (will be ignored, if
257 // already active).
258 dt.on( 'key'+namespace, function ( e, dt, key, cell, orig ) {
259 that._editor( key, orig, false );
260 } );
261
262 // Active editing on double click - it will already have focus from
263 // the click event handler above
264 $( dt.table().body() ).on( 'dblclick'+namespace, 'th, td', function (e) {
265 if ( that.s.enable === false ) {
266 return;
267 }
268
269 var cell = dt.cell( this );
270
271 if ( ! cell.any() ) {
272 return;
273 }
274
275 that._editor( null, e, true );
276 } );
277
278 // While Editor is busy processing, we don't want to process any key events
279 editor
280 .on('preSubmit', function () {
281 editorBlock = true;
282 } )
283 .on('preSubmitCancelled', function () {
284 editorBlock = false;
285 } )
286 .on('submitComplete', function () {
287 editorBlock = false;
288 } );
289 }
290
291 // Stave saving
292 if ( dt.settings()[0].oFeatures.bStateSave ) {
293 dt.on( 'stateSaveParams'+namespace, function (e, s, d) {
294 d.keyTable = that.s.lastFocus ?
295 that.s.lastFocus.cell.index() :
296 null;
297 } );
298 }
299
300 // Redraw - retain focus on the current cell
301 dt.on( 'draw'+namespace, function (e) {
302 if ( that.s.focusDraw ) {
303 return;
304 }
305
306 var lastFocus = that.s.lastFocus;
307
308 if ( lastFocus && lastFocus.node && $(lastFocus.node).closest('body') === document.body ) {
309 var relative = that.s.lastFocus.relative;
310 var info = dt.page.info();
311 var row = relative.row + info.start;
312
313 if ( info.recordsDisplay === 0 ) {
314 return;
315 }
316
317 // Reverse if needed
318 if ( row >= info.recordsDisplay ) {
319 row = info.recordsDisplay - 1;
320 }
321
322 that._focus( row, relative.column, true, e );
323 }
324 } );
325
326 // Clipboard support
327 if ( this.c.clipboard ) {
328 this._clipboard();
329 }
330
331 dt.on( 'destroy'+namespace, function () {
332 that._blur( true );
333
334 // Event tidy up
335 dt.off( namespace );
336
337 $( dt.table().body() )
338 .off( 'click'+namespace, 'th, td' )
339 .off( 'dblclick'+namespace, 'th, td' );
340
341 $( document )
342 .off( 'mousedown'+namespace )
343 .off( 'keydown'+namespace )
344 .off( 'copy'+namespace )
345 .off( 'paste'+namespace );
346 } );
347
348 // Initial focus comes from state or options
349 var state = dt.state.loaded();
350
351 if ( state && state.keyTable ) {
352 // Wait until init is done
353 dt.one( 'init', function () {
354 var cell = dt.cell( state.keyTable );
355
356 // Ensure that the saved cell still exists
357 if ( cell.any() ) {
358 cell.focus();
359 }
360 } );
361 }
362 else if ( this.c.focus ) {
363 dt.cell( this.c.focus ).focus();
364 }
365 },
366
367
368 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
369 * Private methods
370 */
371
372 /**
373 * Blur the control
374 *
375 * @param {boolean} [noEvents=false] Don't trigger updates / events (for destroying)
376 * @private
377 */
378 _blur: function (noEvents)
379 {
380 if ( ! this.s.enable || ! this.s.lastFocus ) {
381 return;
382 }
383
384 var cell = this.s.lastFocus.cell;
385
386 $( cell.node() ).removeClass( this.c.className );
387 this.s.lastFocus = null;
388
389 if ( ! noEvents ) {
390 this._updateFixedColumns(cell.index().column);
391
392 this._emitEvent( 'key-blur', [ this.s.dt, cell ] );
393 }
394 },
395
396
397 /**
398 * Clipboard interaction handlers
399 *
400 * @private
401 */
402 _clipboard: function () {
403 var dt = this.s.dt;
404 var that = this;
405 var namespace = this.s.namespace;
406
407 // IE8 doesn't support getting selected text
408 if ( ! window.getSelection ) {
409 return;
410 }
411
412 $(document).on( 'copy'+namespace, function (ejq) {
413 var e = ejq.originalEvent;
414 var selection = window.getSelection().toString();
415 var focused = that.s.lastFocus;
416
417 // Only copy cell text to clipboard if there is no other selection
418 // and there is a focused cell
419 if ( ! selection && focused ) {
420 e.clipboardData.setData(
421 'text/plain',
422 focused.cell.render( that.c.clipboardOrthogonal )
423 );
424 e.preventDefault();
425 }
426 } );
427
428 $(document).on( 'paste'+namespace, function (ejq) {
429 var e = ejq.originalEvent;
430 var focused = that.s.lastFocus;
431 var activeEl = document.activeElement;
432 var editor = that.c.editor;
433 var pastedText;
434
435 if ( focused && (! activeEl || activeEl.nodeName.toLowerCase() === 'body') ) {
436 e.preventDefault();
437
438 if ( window.clipboardData && window.clipboardData.getData ) {
439 // IE
440 pastedText = window.clipboardData.getData('Text');
441 }
442 else if ( e.clipboardData && e.clipboardData.getData ) {
443 // Everything else
444 pastedText = e.clipboardData.getData('text/plain');
445 }
446
447 if ( editor ) {
448 // Got Editor - need to activate inline editing,
449 // set the value and submit
450 editor
451 .inline( focused.cell.index() )
452 .set( editor.displayed()[0], pastedText )
453 .submit();
454 }
455 else {
456 // No editor, so just dump the data in
457 focused.cell.data( pastedText );
458 dt.draw(false);
459 }
460 }
461 } );
462 },
463
464
465 /**
466 * Get an array of the column indexes that KeyTable can operate on. This
467 * is a merge of the user supplied columns and the visible columns.
468 *
469 * @private
470 */
471 _columns: function ()
472 {
473 var dt = this.s.dt;
474 var user = dt.columns( this.c.columns ).indexes();
475 var out = [];
476
477 dt.columns( ':visible' ).every( function (i) {
478 if ( user.indexOf( i ) !== -1 ) {
479 out.push( i );
480 }
481 } );
482
483 return out;
484 },
485
486
487 /**
488 * Perform excel like navigation for Editor by triggering an edit on key
489 * press
490 *
491 * @param {integer} key Key code for the pressed key
492 * @param {object} orig Original event
493 * @private
494 */
495 _editor: function ( key, orig, hardEdit )
496 {
497 var that = this;
498 var dt = this.s.dt;
499 var editor = this.c.editor;
500 var editCell = this.s.lastFocus.cell;
501 var namespace = this.s.namespace;
502
503 // Do nothing if there is already an inline edit in this cell
504 if ( $('div.DTE', editCell.node()).length ) {
505 return;
506 }
507
508 // Don't activate Editor on control key presses
509 if ( key !== null && (
510 (key >= 0x00 && key <= 0x09) ||
511 key === 0x0b ||
512 key === 0x0c ||
513 (key >= 0x0e && key <= 0x1f) ||
514 (key >= 0x70 && key <= 0x7b) ||
515 (key >= 0x7f && key <= 0x9f)
516 ) ) {
517 return;
518 }
519
520 orig.stopPropagation();
521
522 // Return key should do nothing - for textareas it would empty the
523 // contents
524 if ( key === 13 ) {
525 orig.preventDefault();
526 }
527
528 var editInline = function () {
529 editor
530 .one( 'open'+namespace, function () {
531 // Remove cancel open
532 editor.off( 'cancelOpen'+namespace );
533
534 // Excel style - select all text
535 if ( ! hardEdit ) {
536 $('div.DTE_Field_InputControl input, div.DTE_Field_InputControl textarea').select();
537 }
538
539 // Reduce the keys the Keys listens for
540 dt.keys.enable( hardEdit ? 'tab-only' : 'navigation-only' );
541
542 // On blur of the navigation submit
543 dt.on( 'key-blur.editor', function (e, dt, cell) {
544 if ( editor.displayed() && cell.node() === editCell.node() ) {
545 editor.submit();
546 }
547 } );
548
549 // Highlight the cell a different colour on full edit
550 if ( hardEdit ) {
551 $( dt.table().container() ).addClass('dtk-focus-alt');
552 }
553
554 // If the dev cancels the submit, we need to return focus
555 editor.on( 'preSubmitCancelled'+namespace, function () {
556 setTimeout( function () {
557 that._focus( editCell, null, false );
558 }, 50 );
559 } );
560
561 editor.on( 'submitUnsuccessful'+namespace, function () {
562 that._focus( editCell, null, false );
563 } );
564
565 // Restore full key navigation on close
566 editor.one( 'close', function () {
567 dt.keys.enable( true );
568 dt.off( 'key-blur.editor' );
569 editor.off( namespace );
570 $( dt.table().container() ).removeClass('dtk-focus-alt');
571 } );
572 } )
573 .one( 'cancelOpen'+namespace, function () {
574 // `preOpen` can cancel the display of the form, so it
575 // might be that the open event handler isn't needed
576 editor.off( namespace );
577 } )
578 .inline( editCell.index() );
579 };
580
581 // Editor 1.7 listens for `return` on keyup, so if return is the trigger
582 // key, we need to wait for `keyup` otherwise Editor would just submit
583 // the content triggered by this keypress.
584 if ( key === 13 ) {
585 hardEdit = true;
586
587 $(document).one( 'keyup', function () { // immediately removed
588 editInline();
589 } );
590 }
591 else {
592 editInline();
593 }
594 },
595
596
597 /**
598 * Emit an event on the DataTable for listeners
599 *
600 * @param {string} name Event name
601 * @param {array} args Event arguments
602 * @private
603 */
604 _emitEvent: function ( name, args )
605 {
606 this.s.dt.iterator( 'table', function ( ctx, i ) {
607 $(ctx.nTable).triggerHandler( name, args );
608 } );
609 },
610
611
612 /**
613 * Focus on a particular cell, shifting the table's paging if required
614 *
615 * @param {DataTables.Api|integer} row Can be given as an API instance that
616 * contains the cell to focus or as an integer. As the latter it is the
617 * visible row index (from the whole data set) - NOT the data index
618 * @param {integer} [column] Not required if a cell is given as the first
619 * parameter. Otherwise this is the column data index for the cell to
620 * focus on
621 * @param {boolean} [shift=true] Should the viewport be moved to show cell
622 * @private
623 */
624 _focus: function ( row, column, shift, originalEvent )
625 {
626 var that = this;
627 var dt = this.s.dt;
628 var pageInfo = dt.page.info();
629 var lastFocus = this.s.lastFocus;
630
631 if ( ! originalEvent) {
632 originalEvent = null;
633 }
634
635 if ( ! this.s.enable ) {
636 return;
637 }
638
639 if ( typeof row !== 'number' ) {
640 // Its an API instance - check that there is actually a row
641 if ( ! row.any() ) {
642 return;
643 }
644
645 // Convert the cell to a row and column
646 var index = row.index();
647 column = index.column;
648 row = dt
649 .rows( { filter: 'applied', order: 'applied' } )
650 .indexes()
651 .indexOf( index.row );
652
653 // Don't focus rows that were filtered out.
654 if ( row < 0 ) {
655 return;
656 }
657
658 // For server-side processing normalise the row by adding the start
659 // point, since `rows().indexes()` includes only rows that are
660 // available at the client-side
661 if ( pageInfo.serverSide ) {
662 row += pageInfo.start;
663 }
664 }
665
666 // Is the row on the current page? If not, we need to redraw to show the
667 // page
668 if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start+pageInfo.length) ) {
669 this.s.focusDraw = true;
670 this.s.waitingForDraw = true;
671
672 dt
673 .one( 'draw', function () {
674 that.s.focusDraw = false;
675 that.s.waitingForDraw = false;
676 that._focus( row, column, undefined, originalEvent );
677 } )
678 .page( Math.floor( row / pageInfo.length ) )
679 .draw( false );
680
681 return;
682 }
683
684 // In the available columns?
685 if ( $.inArray( column, this._columns() ) === -1 ) {
686 return;
687 }
688
689 // De-normalise the server-side processing row, so we select the row
690 // in its displayed position
691 if ( pageInfo.serverSide ) {
692 row -= pageInfo.start;
693 }
694
695 // Get the cell from the current position - ignoring any cells which might
696 // not have been rendered (therefore can't use `:eq()` selector).
697 var cells = dt.cells( null, column, {search: 'applied', order: 'applied'} ).flatten();
698 var cell = dt.cell( cells[ row ] );
699
700 if ( lastFocus ) {
701 // Don't trigger a refocus on the same cell
702 if ( lastFocus.node === cell.node() ) {
703 this._emitEvent( 'key-refocus', [ this.s.dt, cell, originalEvent || null ] );
704 return;
705 }
706
707 // Otherwise blur the old focus
708 this._blur();
709 }
710
711 // Clear focus from other tables
712 this._removeOtherFocus();
713
714 var node = $( cell.node() );
715 node.addClass( this.c.className );
716
717 this._updateFixedColumns(column);
718
719 // Shift viewpoint and page to make cell visible
720 if ( shift === undefined || shift === true ) {
721 this._scroll( $(window), $(document.body), node, 'offset' );
722
723 var bodyParent = dt.table().body().parentNode;
724 if ( bodyParent !== dt.table().header().parentNode ) {
725 var parent = $(bodyParent.parentNode);
726
727 this._scroll( parent, parent, node, 'position' );
728 }
729 }
730
731 // Event and finish
732 this.s.lastFocus = {
733 cell: cell,
734 node: cell.node(),
735 relative: {
736 row: dt.rows( { page: 'current' } ).indexes().indexOf( cell.index().row ),
737 column: cell.index().column
738 }
739 };
740
741 this._emitEvent( 'key-focus', [ this.s.dt, cell, originalEvent || null ] );
742 dt.state.save();
743 },
744
745
746 /**
747 * Handle key press
748 *
749 * @param {object} e Event
750 * @private
751 */
752 _key: function ( e )
753 {
754 // If we are waiting for a draw to happen from another key event, then
755 // do nothing for this new key press.
756 if ( this.s.waitingForDraw ) {
757 e.preventDefault();
758 return;
759 }
760
761 var enable = this.s.enable;
762 var navEnable = enable === true || enable === 'navigation-only';
763 if ( ! enable ) {
764 return;
765 }
766
767 if ( (e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey) && !(e.ctrlKey && e.altKey) ) {
768 return;
769 }
770
771 // If not focused, then there is no key action to take
772 var lastFocus = this.s.lastFocus;
773 if ( ! lastFocus ) {
774 return;
775 }
776
777 // And the last focus still exists!
778 if ( ! this.s.dt.cell(lastFocus.node).any() ) {
779 this.s.lastFocus = null;
780 return;
781 }
782
783 var that = this;
784 var dt = this.s.dt;
785 var scrolling = this.s.dt.settings()[0].oScroll.sY ? true : false;
786
787 // If we are not listening for this key, do nothing
788 if ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) {
789 return;
790 }
791
792 switch( e.keyCode ) {
793 case 9: // tab
794 // `enable` can be tab-only
795 this._shift( e, e.shiftKey ? 'left' : 'right', true );
796 break;
797
798 case 27: // esc
799 if ( this.s.blurable && enable === true ) {
800 this._blur();
801 }
802 break;
803
804 case 33: // page up (previous page)
805 case 34: // page down (next page)
806 if ( navEnable && !scrolling ) {
807 e.preventDefault();
808
809 dt
810 .page( e.keyCode === 33 ? 'previous' : 'next' )
811 .draw( false );
812 }
813 break;
814
815 case 35: // end (end of current page)
816 case 36: // home (start of current page)
817 if ( navEnable ) {
818 e.preventDefault();
819 var indexes = dt.cells( {page: 'current'} ).indexes();
820 var colIndexes = this._columns();
821
822 this._focus( dt.cell(
823 indexes[ e.keyCode === 35 ? indexes.length-1 : colIndexes[0] ]
824 ), null, true, e );
825 }
826 break;
827
828 case 37: // left arrow
829 if ( navEnable ) {
830 this._shift( e, 'left' );
831 }
832 break;
833
834 case 38: // up arrow
835 if ( navEnable ) {
836 this._shift( e, 'up' );
837 }
838 break;
839
840 case 39: // right arrow
841 if ( navEnable ) {
842 this._shift( e, 'right' );
843 }
844 break;
845
846 case 40: // down arrow
847 if ( navEnable ) {
848 this._shift( e, 'down' );
849 }
850 break;
851
852 case 113: // F2 - Excel like hard edit
853 if ( this.c.editor ) {
854 this._editor(null, e, true);
855 break;
856 }
857 // else fallthrough
858
859 default:
860 // Everything else - pass through only when fully enabled
861 if ( enable === true ) {
862 this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus.cell, e ] );
863 }
864 break;
865 }
866 },
867
868 /**
869 * Remove focus from all tables other than this one
870 */
871 _removeOtherFocus: function ()
872 {
873 var thisTable = this.s.dt.table().node();
874
875 $.fn.dataTable.tables({api:true}).iterator('table', function (settings) {
876 if (this.table().node() !== thisTable) {
877 this.cell.blur();
878 }
879 });
880 },
881
882 /**
883 * Scroll a container to make a cell visible in it. This can be used for
884 * both DataTables scrolling and native window scrolling.
885 *
886 * @param {jQuery} container Scrolling container
887 * @param {jQuery} scroller Item being scrolled
888 * @param {jQuery} cell Cell in the scroller
889 * @param {string} posOff `position` or `offset` - which to use for the
890 * calculation. `offset` for the document, otherwise `position`
891 * @private
892 */
893 _scroll: function ( container, scroller, cell, posOff )
894 {
895 var offset = cell[posOff]();
896 var height = cell.outerHeight();
897 var width = cell.outerWidth();
898
899 var scrollTop = scroller.scrollTop();
900 var scrollLeft = scroller.scrollLeft();
901 var containerHeight = container.height();
902 var containerWidth = container.width();
903
904 // If Scroller is being used, the table can be `position: absolute` and that
905 // needs to be taken account of in the offset. If no Scroller, this will be 0
906 if ( posOff === 'position' ) {
907 offset.top += parseInt( cell.closest('table').css('top'), 10 );
908 }
909
910 // Top correction
911 if ( offset.top < scrollTop ) {
912 scroller.scrollTop( offset.top );
913 }
914
915 // Left correction
916 if ( offset.left < scrollLeft ) {
917 scroller.scrollLeft( offset.left );
918 }
919
920 // Bottom correction
921 if ( offset.top + height > scrollTop + containerHeight && height < containerHeight ) {
922 scroller.scrollTop( offset.top + height - containerHeight );
923 }
924
925 // Right correction
926 if ( offset.left + width > scrollLeft + containerWidth && width < containerWidth ) {
927 scroller.scrollLeft( offset.left + width - containerWidth );
928 }
929 },
930
931
932 /**
933 * Calculate a single offset movement in the table - up, down, left and
934 * right and then perform the focus if possible
935 *
936 * @param {object} e Event object
937 * @param {string} direction Movement direction
938 * @param {boolean} keyBlurable `true` if the key press can result in the
939 * table being blurred. This is so arrow keys won't blur the table, but
940 * tab will.
941 * @private
942 */
943 _shift: function ( e, direction, keyBlurable )
944 {
945 var that = this;
946 var dt = this.s.dt;
947 var pageInfo = dt.page.info();
948 var rows = pageInfo.recordsDisplay;
949 var currentCell = this.s.lastFocus.cell;
950 var columns = this._columns();
951
952 if ( ! currentCell ) {
953 return;
954 }
955
956 var currRow = dt
957 .rows( { filter: 'applied', order: 'applied' } )
958 .indexes()
959 .indexOf( currentCell.index().row );
960
961 // When server-side processing, `rows().indexes()` only gives the rows
962 // that are available at the client-side, so we need to normalise the
963 // row's current position by the display start point
964 if ( pageInfo.serverSide ) {
965 currRow += pageInfo.start;
966 }
967
968 var currCol = dt
969 .columns( columns )
970 .indexes()
971 .indexOf( currentCell.index().column );
972
973 var
974 row = currRow,
975 column = columns[ currCol ]; // row is the display, column is an index
976
977 if ( direction === 'right' ) {
978 if ( currCol >= columns.length - 1 ) {
979 row++;
980 column = columns[0];
981 }
982 else {
983 column = columns[ currCol+1 ];
984 }
985 }
986 else if ( direction === 'left' ) {
987 if ( currCol === 0 ) {
988 row--;
989 column = columns[ columns.length - 1 ];
990 }
991 else {
992 column = columns[ currCol-1 ];
993 }
994 }
995 else if ( direction === 'up' ) {
996 row--;
997 }
998 else if ( direction === 'down' ) {
999 row++;
1000 }
1001
1002 if ( row >= 0 && row < rows && $.inArray( column, columns ) !== -1 ) {
1003 if (e) {
1004 e.preventDefault();
1005 }
1006
1007 this._focus( row, column, true, e );
1008 }
1009 else if ( ! keyBlurable || ! this.c.blurable ) {
1010 // No new focus, but if the table isn't blurable, then don't loose
1011 // focus
1012 if (e) {
1013 e.preventDefault();
1014 }
1015 }
1016 else {
1017 this._blur();
1018 }
1019 },
1020
1021
1022 /**
1023 * Create a hidden input element that can receive focus on behalf of the
1024 * table
1025 *
1026 * @private
1027 */
1028 _tabInput: function ()
1029 {
1030 var that = this;
1031 var dt = this.s.dt;
1032 var tabIndex = this.c.tabIndex !== null ?
1033 this.c.tabIndex :
1034 dt.settings()[0].iTabIndex;
1035
1036 if ( tabIndex == -1 ) {
1037 return;
1038 }
1039
1040 var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>')
1041 .css( {
1042 position: 'absolute',
1043 height: 1,
1044 width: 0,
1045 overflow: 'hidden'
1046 } )
1047 .insertBefore( dt.table().node() );
1048
1049 div.children().on( 'focus', function (e) {
1050 var cell = dt.cell(':eq(0)', that._columns(), {page: 'current'});
1051
1052 if ( cell.any() ) {
1053 that._focus( cell, null, true, e );
1054 }
1055 } );
1056 },
1057
1058 /**
1059 * Update fixed columns if they are enabled and if the cell we are
1060 * focusing is inside a fixed column
1061 * @param {integer} column Index of the column being changed
1062 * @private
1063 */
1064 _updateFixedColumns: function( column )
1065 {
1066 var dt = this.s.dt;
1067 var settings = dt.settings()[0];
1068
1069 if ( settings._oFixedColumns ) {
1070 var leftCols = settings._oFixedColumns.s.iLeftColumns;
1071 var rightCols = settings.aoColumns.length - settings._oFixedColumns.s.iRightColumns;
1072
1073 if (column < leftCols || column >= rightCols) {
1074 dt.fixedColumns().update();
1075 }
1076 }
1077 }
1078} );
1079
1080
1081/**
1082 * KeyTable default settings for initialisation
1083 *
1084 * @namespace
1085 * @name KeyTable.defaults
1086 * @static
1087 */
1088KeyTable.defaults = {
1089 /**
1090 * Can focus be removed from the table
1091 * @type {Boolean}
1092 */
1093 blurable: true,
1094
1095 /**
1096 * Class to give to the focused cell
1097 * @type {String}
1098 */
1099 className: 'focus',
1100
1101 /**
1102 * Enable or disable clipboard support
1103 * @type {Boolean}
1104 */
1105 clipboard: true,
1106
1107 /**
1108 * Orthogonal data that should be copied to clipboard
1109 * @type {string}
1110 */
1111 clipboardOrthogonal: 'display',
1112
1113 /**
1114 * Columns that can be focused. This is automatically merged with the
1115 * visible columns as only visible columns can gain focus.
1116 * @type {String}
1117 */
1118 columns: '', // all
1119
1120 /**
1121 * Editor instance to automatically perform Excel like navigation
1122 * @type {Editor}
1123 */
1124 editor: null,
1125
1126 /**
1127 * Trigger editing immediately on focus
1128 * @type {boolean}
1129 */
1130 editOnFocus: false,
1131
1132 /**
1133 * Select a cell to automatically select on start up. `null` for no
1134 * automatic selection
1135 * @type {cell-selector}
1136 */
1137 focus: null,
1138
1139 /**
1140 * Array of keys to listen for
1141 * @type {null|array}
1142 */
1143 keys: null,
1144
1145 /**
1146 * Tab index for where the table should sit in the document's tab flow
1147 * @type {integer|null}
1148 */
1149 tabIndex: null
1150};
1151
1152
1153
1154KeyTable.version = "2.5.1";
1155
1156
1157$.fn.dataTable.KeyTable = KeyTable;
1158$.fn.DataTable.KeyTable = KeyTable;
1159
1160
1161DataTable.Api.register( 'cell.blur()', function () {
1162 return this.iterator( 'table', function (ctx) {
1163 if ( ctx.keytable ) {
1164 ctx.keytable.blur();
1165 }
1166 } );
1167} );
1168
1169DataTable.Api.register( 'cell().focus()', function () {
1170 return this.iterator( 'cell', function (ctx, row, column) {
1171 if ( ctx.keytable ) {
1172 ctx.keytable.focus( row, column );
1173 }
1174 } );
1175} );
1176
1177DataTable.Api.register( 'keys.disable()', function () {
1178 return this.iterator( 'table', function (ctx) {
1179 if ( ctx.keytable ) {
1180 ctx.keytable.enable( false );
1181 }
1182 } );
1183} );
1184
1185DataTable.Api.register( 'keys.enable()', function ( opts ) {
1186 return this.iterator( 'table', function (ctx) {
1187 if ( ctx.keytable ) {
1188 ctx.keytable.enable( opts === undefined ? true : opts );
1189 }
1190 } );
1191} );
1192
1193DataTable.Api.register( 'keys.move()', function ( dir ) {
1194 return this.iterator( 'table', function (ctx) {
1195 if ( ctx.keytable ) {
1196 ctx.keytable._shift( null, dir, false );
1197 }
1198 } );
1199} );
1200
1201// Cell selector
1202DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
1203 var focused = opts.focused;
1204 var kt = settings.keytable;
1205 var out = [];
1206
1207 if ( ! kt || focused === undefined ) {
1208 return cells;
1209 }
1210
1211 for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
1212 if ( (focused === true && kt.focused( cells[i] ) ) ||
1213 (focused === false && ! kt.focused( cells[i] ) )
1214 ) {
1215 out.push( cells[i] );
1216 }
1217 }
1218
1219 return out;
1220} );
1221
1222
1223// Attach a listener to the document which listens for DataTables initialisation
1224// events so we can automatically initialise
1225$(document).on( 'preInit.dt.dtk', function (e, settings, json) {
1226 if ( e.namespace !== 'dt' ) {
1227 return;
1228 }
1229
1230 var init = settings.oInit.keys;
1231 var defaults = DataTable.defaults.keys;
1232
1233 if ( init || defaults ) {
1234 var opts = $.extend( {}, defaults, init );
1235
1236 if ( init !== false ) {
1237 new KeyTable( settings, opts );
1238 }
1239 }
1240} );
1241
1242
1243return KeyTable;
1244}));
Note: See TracBrowser for help on using the repository browser.