datatables.net-autofill 2.4.0 → 2.5.1

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.
@@ -0,0 +1,1236 @@
1
+ /*! AutoFill 2.5.1
2
+ * ©2008-2022 SpryMedia Ltd - datatables.net/license
3
+ */
4
+
5
+ import $ from 'jquery';
6
+ import DataTable from 'datatables.net';
7
+
8
+
9
+
10
+ /**
11
+ * @summary AutoFill
12
+ * @description Add Excel like click and drag auto-fill options to DataTables
13
+ * @version 2.5.1
14
+ * @author SpryMedia Ltd (www.sprymedia.co.uk)
15
+ * @copyright SpryMedia Ltd.
16
+ *
17
+ * This source file is free software, available under the following license:
18
+ * MIT license - http://datatables.net/license/mit
19
+ *
20
+ * This source file is distributed in the hope that it will be useful, but
21
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
23
+ *
24
+ * For details please refer to: http://www.datatables.net
25
+ */
26
+
27
+ var _instance = 0;
28
+
29
+ /**
30
+ * AutoFill provides Excel like auto-fill features for a DataTable
31
+ *
32
+ * @class AutoFill
33
+ * @constructor
34
+ * @param {object} oTD DataTables settings object
35
+ * @param {object} oConfig Configuration object for AutoFill
36
+ */
37
+ var AutoFill = function( dt, opts )
38
+ {
39
+ if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
40
+ throw( "Warning: AutoFill requires DataTables 1.10.8 or greater");
41
+ }
42
+
43
+ // User and defaults configuration object
44
+ this.c = $.extend( true, {},
45
+ DataTable.defaults.autoFill,
46
+ AutoFill.defaults,
47
+ opts
48
+ );
49
+
50
+ /**
51
+ * @namespace Settings object which contains customisable information for AutoFill instance
52
+ */
53
+ this.s = {
54
+ /** @type {DataTable.Api} DataTables' API instance */
55
+ dt: new DataTable.Api( dt ),
56
+
57
+ /** @type {String} Unique namespace for events attached to the document */
58
+ namespace: '.autoFill'+(_instance++),
59
+
60
+ /** @type {Object} Cached dimension information for use in the mouse move event handler */
61
+ scroll: {},
62
+
63
+ /** @type {integer} Interval object used for smooth scrolling */
64
+ scrollInterval: null,
65
+
66
+ handle: {
67
+ height: 0,
68
+ width: 0
69
+ },
70
+
71
+ /**
72
+ * Enabled setting
73
+ * @type {Boolean}
74
+ */
75
+ enabled: false
76
+ };
77
+
78
+
79
+ /**
80
+ * @namespace Common and useful DOM elements for the class instance
81
+ */
82
+ this.dom = {
83
+ closeButton: $('<div class="dtaf-popover-close">x</div>'),
84
+
85
+ /** @type {jQuery} AutoFill handle */
86
+ handle: $('<div class="dt-autofill-handle"/>'),
87
+
88
+ /**
89
+ * @type {Object} Selected cells outline - Need to use 4 elements,
90
+ * otherwise the mouse over if you back into the selected rectangle
91
+ * will be over that element, rather than the cells!
92
+ */
93
+ select: {
94
+ top: $('<div class="dt-autofill-select top"/>'),
95
+ right: $('<div class="dt-autofill-select right"/>'),
96
+ bottom: $('<div class="dt-autofill-select bottom"/>'),
97
+ left: $('<div class="dt-autofill-select left"/>')
98
+ },
99
+
100
+ /** @type {jQuery} Fill type chooser background */
101
+ background: $('<div class="dt-autofill-background"/>'),
102
+
103
+ /** @type {jQuery} Fill type chooser */
104
+ list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'<ul/></div>'),
105
+
106
+ /** @type {jQuery} DataTables scrolling container */
107
+ dtScroll: null,
108
+
109
+ /** @type {jQuery} Offset parent element */
110
+ offsetParent: null
111
+ };
112
+
113
+
114
+ /* Constructor logic */
115
+ this._constructor();
116
+ };
117
+
118
+
119
+
120
+ $.extend( AutoFill.prototype, {
121
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
122
+ * Public methods (exposed via the DataTables API below)
123
+ */
124
+ enabled: function ()
125
+ {
126
+ return this.s.enabled;
127
+ },
128
+
129
+
130
+ enable: function ( flag )
131
+ {
132
+ var that = this;
133
+
134
+ if ( flag === false ) {
135
+ return this.disable();
136
+ }
137
+
138
+ this.s.enabled = true;
139
+
140
+ this._focusListener();
141
+
142
+ this.dom.handle.on( 'mousedown touchstart', function (e) {
143
+ that._mousedown( e );
144
+ return false;
145
+ } );
146
+
147
+ $(window).on('resize', function() {
148
+ var handle = $('div.dt-autofill-handle');
149
+ if(handle.length > 0 && that.dom.attachedTo !== undefined) {
150
+ that._attach(that.dom.attachedTo)
151
+ }
152
+ })
153
+
154
+ let orientationReset = function() {
155
+ that.s.handle = {
156
+ height: false,
157
+ width: false
158
+ };
159
+ $(that.dom.handle).css({
160
+ 'height': '',
161
+ 'width': ''
162
+ })
163
+ if(that.dom.attachedTo !== undefined) {
164
+ that._attach(that.dom.attachedTo)
165
+ }
166
+ }
167
+
168
+ $(window)
169
+ .on('orientationchange', function() {
170
+ setTimeout(function() {
171
+ orientationReset();
172
+ setTimeout(orientationReset, 150);
173
+ }, 50);
174
+ });
175
+
176
+ return this;
177
+ },
178
+
179
+ disable: function ()
180
+ {
181
+ this.s.enabled = false;
182
+
183
+ this._focusListenerRemove();
184
+
185
+ return this;
186
+ },
187
+
188
+
189
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
190
+ * Constructor
191
+ */
192
+
193
+ /**
194
+ * Initialise the RowReorder instance
195
+ *
196
+ * @private
197
+ */
198
+ _constructor: function ()
199
+ {
200
+ var that = this;
201
+ var dt = this.s.dt;
202
+ var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container());
203
+
204
+ // Make the instance accessible to the API
205
+ dt.settings()[0].autoFill = this;
206
+
207
+ if ( dtScroll.length ) {
208
+ this.dom.dtScroll = dtScroll;
209
+
210
+ // Need to scroll container to be the offset parent
211
+ if ( dtScroll.css('position') === 'static' ) {
212
+ dtScroll.css( 'position', 'relative' );
213
+ }
214
+ }
215
+
216
+ if ( this.c.enable !== false ) {
217
+ this.enable();
218
+ }
219
+
220
+ dt.on( 'destroy.autoFill', function () {
221
+ that._focusListenerRemove();
222
+ } );
223
+ },
224
+
225
+
226
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
227
+ * Private methods
228
+ */
229
+
230
+ /**
231
+ * Display the AutoFill drag handle by appending it to a table cell. This
232
+ * is the opposite of the _detach method.
233
+ *
234
+ * @param {node} node TD/TH cell to insert the handle into
235
+ * @private
236
+ */
237
+ _attach: function ( node )
238
+ {
239
+ var dt = this.s.dt;
240
+ var idx = dt.cell( node ).index();
241
+ var handle = this.dom.handle;
242
+ var handleDim = this.s.handle;
243
+
244
+ if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) {
245
+ this._detach();
246
+ return;
247
+ }
248
+
249
+ if ( ! this.dom.offsetParent ) {
250
+ // We attach to the table's offset parent
251
+ this.dom.offsetParent = $( dt.table().node() ).offsetParent();
252
+ }
253
+
254
+ if ( ! handleDim.height || ! handleDim.width ) {
255
+ // Append to document so we can get its size. Not expecting it to
256
+ // change during the life time of the page
257
+ handle.appendTo( 'body' );
258
+ handleDim.height = handle.outerHeight();
259
+ handleDim.width = handle.outerWidth();
260
+ }
261
+
262
+ // Might need to go through multiple offset parents
263
+ var offset = this._getPosition( node, this.dom.offsetParent );
264
+
265
+ this.dom.attachedTo = node;
266
+ handle
267
+ .css( {
268
+ top: offset.top + node.offsetHeight - handleDim.height,
269
+ left: offset.left + node.offsetWidth - handleDim.width
270
+ } )
271
+ .appendTo( this.dom.offsetParent );
272
+ },
273
+
274
+
275
+ /**
276
+ * Determine can the fill type should be. This can be automatic, or ask the
277
+ * end user.
278
+ *
279
+ * @param {array} cells Information about the selected cells from the key
280
+ * up function
281
+ * @private
282
+ */
283
+ _actionSelector: function ( cells )
284
+ {
285
+ var that = this;
286
+ var dt = this.s.dt;
287
+ var actions = AutoFill.actions;
288
+ var available = [];
289
+
290
+ // "Ask" each plug-in if it wants to handle this data
291
+ $.each( actions, function ( key, action ) {
292
+ if ( action.available( dt, cells ) ) {
293
+ available.push( key );
294
+ }
295
+ } );
296
+
297
+ if ( available.length === 1 && this.c.alwaysAsk === false ) {
298
+ // Only one action available - enact it immediately
299
+ var result = actions[ available[0] ].execute( dt, cells );
300
+ this._update( result, cells );
301
+ }
302
+ else if ( available.length > 1 ) {
303
+ // Multiple actions available - ask the end user what they want to do
304
+ var list = this.dom.list.children('ul').empty();
305
+
306
+ // Add a cancel option
307
+ available.push( 'cancel' );
308
+
309
+ $.each( available, function ( i, name ) {
310
+ list.append( $('<li/>')
311
+ .append(
312
+ '<div class="dt-autofill-question">'+
313
+ actions[ name ].option( dt, cells )+
314
+ '<div>'
315
+ )
316
+ .append( $('<div class="dt-autofill-button">' ).append( $('<button class="'+AutoFill.classes.btn+'">'+dt.i18n('autoFill.button', '&gt;')+'</button>')))
317
+ .on( 'click', function () {
318
+ var result = actions[ name ].execute(
319
+ dt, cells, $(this).closest('li')
320
+ );
321
+ that._update( result, cells );
322
+
323
+ that.dom.background.remove();
324
+ that.dom.list.remove();
325
+ } )
326
+ );
327
+ } );
328
+
329
+ this.dom.background.appendTo( 'body' );
330
+ this.dom.background.one('click', function() {
331
+ that.dom.background.remove();
332
+ that.dom.list.remove();
333
+ })
334
+ this.dom.list.appendTo( 'body' );
335
+
336
+ if (this.c.closeButton) {
337
+ this.dom.list.prepend(this.dom.closeButton).addClass(AutoFill.classes.closeable)
338
+ this.dom.closeButton.on('click', function() {
339
+ return that.dom.background.click()
340
+ });
341
+ }
342
+
343
+ this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 );
344
+ }
345
+ },
346
+
347
+
348
+ /**
349
+ * Remove the AutoFill handle from the document
350
+ *
351
+ * @private
352
+ */
353
+ _detach: function ()
354
+ {
355
+ this.dom.attachedTo = null;
356
+ this.dom.handle.detach();
357
+ },
358
+
359
+
360
+ /**
361
+ * Draw the selection outline by calculating the range between the start
362
+ * and end cells, then placing the highlighting elements to draw a rectangle
363
+ *
364
+ * @param {node} target End cell
365
+ * @param {object} e Originating event
366
+ * @private
367
+ */
368
+ _drawSelection: function ( target, e )
369
+ {
370
+ // Calculate boundary for start cell to this one
371
+ var dt = this.s.dt;
372
+ var start = this.s.start;
373
+ var startCell = $(this.dom.start);
374
+ var end = {
375
+ row: this.c.vertical ?
376
+ dt.rows( { page: 'current' } ).nodes().indexOf( target.parentNode ) :
377
+ start.row,
378
+ column: this.c.horizontal ?
379
+ $(target).index() :
380
+ start.column
381
+ };
382
+ var colIndx = dt.column.index( 'toData', end.column );
383
+ var endRow = dt.row( ':eq('+end.row+')', { page: 'current' } ); // Workaround for M581
384
+ var endCell = $( dt.cell( endRow.index(), colIndx ).node() );
385
+
386
+ // Be sure that is a DataTables controlled cell
387
+ if ( ! dt.cell( endCell ).any() ) {
388
+ return;
389
+ }
390
+
391
+ // if target is not in the columns available - do nothing
392
+ if ( dt.columns( this.c.columns ).indexes().indexOf( colIndx ) === -1 || end.row === -1) {
393
+ return;
394
+ }
395
+
396
+ this.s.end = end;
397
+
398
+ var top, bottom, left, right, height, width;
399
+
400
+ top = start.row < end.row ? startCell : endCell;
401
+ bottom = start.row < end.row ? endCell : startCell;
402
+ left = start.column < end.column ? startCell : endCell;
403
+ right = start.column < end.column ? endCell : startCell;
404
+
405
+ top = this._getPosition( top.get(0) ).top;
406
+ left = this._getPosition( left.get(0) ).left;
407
+ height = this._getPosition( bottom.get(0) ).top + bottom.outerHeight() - top;
408
+ width = this._getPosition( right.get(0) ).left + right.outerWidth() - left;
409
+
410
+ var select = this.dom.select;
411
+ select.top.css( {
412
+ top: top,
413
+ left: left,
414
+ width: width
415
+ } );
416
+
417
+ select.left.css( {
418
+ top: top,
419
+ left: left,
420
+ height: height
421
+ } );
422
+
423
+ select.bottom.css( {
424
+ top: top + height,
425
+ left: left,
426
+ width: width
427
+ } );
428
+
429
+ select.right.css( {
430
+ top: top,
431
+ left: left + width,
432
+ height: height
433
+ } );
434
+ },
435
+
436
+
437
+ /**
438
+ * Use the Editor API to perform an update based on the new data for the
439
+ * cells
440
+ *
441
+ * @param {array} cells Information about the selected cells from the key
442
+ * up function
443
+ * @private
444
+ */
445
+ _editor: function ( cells )
446
+ {
447
+ var dt = this.s.dt;
448
+ var editor = this.c.editor;
449
+
450
+ if ( ! editor ) {
451
+ return;
452
+ }
453
+
454
+ // Build the object structure for Editor's multi-row editing
455
+ var idValues = {};
456
+ var nodes = [];
457
+ var fields = editor.fields();
458
+
459
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
460
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
461
+ var cell = cells[i][j];
462
+
463
+ // Determine the field name for the cell being edited
464
+ var col = dt.settings()[0].aoColumns[ cell.index.column ];
465
+ var fieldName = col.editField;
466
+
467
+ if ( fieldName === undefined ) {
468
+ var dataSrc = col.mData;
469
+
470
+ // dataSrc is the `field.data` property, but we need to set
471
+ // using the field name, so we need to translate from the
472
+ // data to the name
473
+ for ( var k=0, ken=fields.length ; k<ken ; k++ ) {
474
+ var field = editor.field( fields[k] );
475
+
476
+ if ( field.dataSrc() === dataSrc ) {
477
+ fieldName = field.name();
478
+ break;
479
+ }
480
+ }
481
+ }
482
+
483
+ if ( ! fieldName ) {
484
+ throw 'Could not automatically determine field data. '+
485
+ 'Please see https://datatables.net/tn/11';
486
+ }
487
+
488
+ if ( ! idValues[ fieldName ] ) {
489
+ idValues[ fieldName ] = {};
490
+ }
491
+
492
+ var id = dt.row( cell.index.row ).id();
493
+ idValues[ fieldName ][ id ] = cell.set;
494
+
495
+ // Keep a list of cells so we can activate the bubble editing
496
+ // with them
497
+ nodes.push( cell.index );
498
+ }
499
+ }
500
+
501
+ // Perform the edit using bubble editing as it allows us to specify
502
+ // the cells to be edited, rather than using full rows
503
+ editor
504
+ .bubble( nodes, false )
505
+ .multiSet( idValues )
506
+ .submit();
507
+ },
508
+
509
+
510
+ /**
511
+ * Emit an event on the DataTable for listeners
512
+ *
513
+ * @param {string} name Event name
514
+ * @param {array} args Event arguments
515
+ * @private
516
+ */
517
+ _emitEvent: function ( name, args )
518
+ {
519
+ this.s.dt.iterator( 'table', function ( ctx, i ) {
520
+ $(ctx.nTable).triggerHandler( name+'.dt', args );
521
+ } );
522
+ },
523
+
524
+
525
+ /**
526
+ * Attach suitable listeners (based on the configuration) that will attach
527
+ * and detach the AutoFill handle in the document.
528
+ *
529
+ * @private
530
+ */
531
+ _focusListener: function ()
532
+ {
533
+ var that = this;
534
+ var dt = this.s.dt;
535
+ var namespace = this.s.namespace;
536
+ var focus = this.c.focus !== null ?
537
+ this.c.focus :
538
+ dt.init().keys || dt.settings()[0].keytable ?
539
+ 'focus' :
540
+ 'hover';
541
+
542
+ // All event listeners attached here are removed in the `destroy`
543
+ // callback in the constructor
544
+ if ( focus === 'focus' ) {
545
+ dt
546
+ .on( 'key-focus.autoFill', function ( e, dt, cell ) {
547
+ that._attach( cell.node() );
548
+ } )
549
+ .on( 'key-blur.autoFill', function ( e, dt, cell ) {
550
+ that._detach();
551
+ } );
552
+ }
553
+ else if ( focus === 'click' ) {
554
+ $(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) {
555
+ that._attach( this );
556
+ } );
557
+
558
+ $(document.body).on( 'click'+namespace, function (e) {
559
+ if ( ! $(e.target).parents().filter( dt.table().body() ).length ) {
560
+ that._detach();
561
+ }
562
+ } );
563
+ }
564
+ else {
565
+ $(dt.table().body())
566
+ .on( 'mouseenter'+namespace+' touchstart'+namespace, 'td, th', function (e) {
567
+ that._attach( this );
568
+ } )
569
+ .on( 'mouseleave'+namespace+'touchend'+namespace, function (e) {
570
+ if ( $(e.relatedTarget).hasClass('dt-autofill-handle') ) {
571
+ return;
572
+ }
573
+
574
+ that._detach();
575
+ } );
576
+
577
+
578
+ }
579
+ },
580
+
581
+
582
+ _focusListenerRemove: function ()
583
+ {
584
+ var dt = this.s.dt;
585
+
586
+ dt.off( '.autoFill' );
587
+ $(dt.table().body()).off( this.s.namespace );
588
+ $(document.body).off( this.s.namespace );
589
+ },
590
+
591
+
592
+ /**
593
+ * Get the position of a node, relative to another, including any scrolling
594
+ * offsets.
595
+ * @param {Node} node Node to get the position of
596
+ * @param {jQuery} targetParent Node to use as the parent
597
+ * @return {object} Offset calculation
598
+ * @private
599
+ */
600
+ _getPosition: function ( node, targetParent )
601
+ {
602
+ var
603
+ currNode = node,
604
+ currOffsetParent,
605
+ top = 0,
606
+ left = 0;
607
+
608
+ if ( ! targetParent ) {
609
+ targetParent = $( $( this.s.dt.table().node() )[0].offsetParent );
610
+ }
611
+
612
+ do {
613
+ // Don't use jQuery().position() the behaviour changes between 1.x and 3.x for
614
+ // tables
615
+ var positionTop = currNode.offsetTop;
616
+ var positionLeft = currNode.offsetLeft;
617
+
618
+ // jQuery doesn't give a `table` as the offset parent oddly, so use DOM directly
619
+ currOffsetParent = $( currNode.offsetParent );
620
+
621
+ top += positionTop + parseInt( currOffsetParent.css('border-top-width') || 0 ) * 1;
622
+ left += positionLeft + parseInt( currOffsetParent.css('border-left-width') || 0 ) * 1;
623
+
624
+ // Emergency fall back. Shouldn't happen, but just in case!
625
+ if ( currNode.nodeName.toLowerCase() === 'body' ) {
626
+ break;
627
+ }
628
+
629
+ currNode = currOffsetParent.get(0); // for next loop
630
+ }
631
+ while ( currOffsetParent.get(0) !== targetParent.get(0) )
632
+
633
+ return {
634
+ top: top,
635
+ left: left
636
+ };
637
+ },
638
+
639
+
640
+ /**
641
+ * Start mouse drag - selects the start cell
642
+ *
643
+ * @param {object} e Mouse down event
644
+ * @private
645
+ */
646
+ _mousedown: function ( e )
647
+ {
648
+ var that = this;
649
+ var dt = this.s.dt;
650
+
651
+ this.dom.start = this.dom.attachedTo;
652
+ this.s.start = {
653
+ row: dt.rows( { page: 'current' } ).nodes().indexOf( $(this.dom.start).parent()[0] ),
654
+ column: $(this.dom.start).index()
655
+ };
656
+
657
+ $(document.body)
658
+ .on( 'mousemove.autoFill touchmove.autoFill', function (e) {
659
+ that._mousemove( e );
660
+ // If it is a touch event then when the touch ends we need to remove the handle
661
+ if(e.type === 'touchmove') {
662
+ $(document.body).one('touchend.autoFill', function() {
663
+ that._detach();
664
+ })
665
+ }
666
+ } )
667
+ .on( 'mouseup.autoFill touchend.autoFill', function (e) {
668
+ that._mouseup( e );
669
+ } );
670
+
671
+ var select = this.dom.select;
672
+ var offsetParent = $( dt.table().node() ).offsetParent();
673
+ select.top.appendTo( offsetParent );
674
+ select.left.appendTo( offsetParent );
675
+ select.right.appendTo( offsetParent );
676
+ select.bottom.appendTo( offsetParent );
677
+
678
+ this._drawSelection( this.dom.start, e );
679
+
680
+ this.dom.handle.css( 'display', 'none' );
681
+
682
+ // Cache scrolling information so mouse move doesn't need to read.
683
+ // This assumes that the window and DT scroller will not change size
684
+ // during an AutoFill drag, which I think is a fair assumption
685
+ var scrollWrapper = this.dom.dtScroll;
686
+ this.s.scroll = {
687
+ windowHeight: $(window).height(),
688
+ windowWidth: $(window).width(),
689
+ dtTop: scrollWrapper ? scrollWrapper.offset().top : null,
690
+ dtLeft: scrollWrapper ? scrollWrapper.offset().left : null,
691
+ dtHeight: scrollWrapper ? scrollWrapper.outerHeight() : null,
692
+ dtWidth: scrollWrapper ? scrollWrapper.outerWidth() : null
693
+ };
694
+ },
695
+
696
+
697
+ /**
698
+ * Mouse drag - selects the end cell and update the selection display for
699
+ * the end user
700
+ *
701
+ * @param {object} e Mouse move event
702
+ * @private
703
+ */
704
+ _mousemove: function ( e )
705
+ {
706
+ var that = this;
707
+ var dt = this.s.dt;
708
+ var target = !e.type.includes('touch') ? e.target : document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY);
709
+ var name = target.nodeName.toLowerCase();
710
+ if ( name !== 'td' && name !== 'th' ) {
711
+ return;
712
+ }
713
+
714
+ this._drawSelection( target, e );
715
+ this._shiftScroll( e );
716
+ },
717
+
718
+
719
+ /**
720
+ * End mouse drag - perform the update actions
721
+ *
722
+ * @param {object} e Mouse up event
723
+ * @private
724
+ */
725
+ _mouseup: function ( e )
726
+ {
727
+ $(document.body).off( '.autoFill' );
728
+
729
+ var that = this;
730
+ var dt = this.s.dt;
731
+ var select = this.dom.select;
732
+ select.top.remove();
733
+ select.left.remove();
734
+ select.right.remove();
735
+ select.bottom.remove();
736
+
737
+ this.dom.handle.css( 'display', 'block' );
738
+
739
+ // Display complete - now do something useful with the selection!
740
+ var start = this.s.start;
741
+ var end = this.s.end;
742
+
743
+ // Haven't selected multiple cells, so nothing to do
744
+ if ( start.row === end.row && start.column === end.column ) {
745
+ return;
746
+ }
747
+
748
+ var startDt = dt.cell( ':eq('+start.row+')', start.column+':visible', {page:'current'} );
749
+
750
+ // If Editor is active inside this cell (inline editing) we need to wait for Editor to
751
+ // submit and then we can loop back and trigger the fill.
752
+ if ( $('div.DTE', startDt.node()).length ) {
753
+ var editor = dt.editor();
754
+
755
+ editor
756
+ .on( 'submitSuccess.dtaf close.dtaf', function () {
757
+ editor.off( '.dtaf');
758
+
759
+ setTimeout( function () {
760
+ that._mouseup( e );
761
+ }, 100 );
762
+ } )
763
+ .on( 'submitComplete.dtaf preSubmitCancelled.dtaf close.dtaf', function () {
764
+ editor.off( '.dtaf');
765
+ } );
766
+
767
+ // Make the current input submit
768
+ editor.submit();
769
+
770
+ return;
771
+ }
772
+
773
+ // Build a matrix representation of the selected rows
774
+ var rows = this._range( start.row, end.row );
775
+ var columns = this._range( start.column, end.column );
776
+ var selected = [];
777
+ var dtSettings = dt.settings()[0];
778
+ var dtColumns = dtSettings.aoColumns;
779
+ var enabledColumns = dt.columns( this.c.columns ).indexes();
780
+
781
+ // Can't use Array.prototype.map as IE8 doesn't support it
782
+ // Can't use $.map as jQuery flattens 2D arrays
783
+ // Need to use a good old fashioned for loop
784
+ for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) {
785
+ selected.push(
786
+ $.map( columns, function (column) {
787
+ var row = dt.row( ':eq('+rows[rowIdx]+')', {page:'current'} ); // Workaround for M581
788
+ var cell = dt.cell( row.index(), column+':visible' );
789
+ var data = cell.data();
790
+ var cellIndex = cell.index();
791
+ var editField = dtColumns[ cellIndex.column ].editField;
792
+
793
+ if ( editField !== undefined ) {
794
+ data = dtSettings.oApi._fnGetObjectDataFn( editField )( dt.row( cellIndex.row ).data() );
795
+ }
796
+
797
+ if ( enabledColumns.indexOf(cellIndex.column) === -1 ) {
798
+ return;
799
+ }
800
+
801
+ return {
802
+ cell: cell,
803
+ data: data,
804
+ label: cell.data(),
805
+ index: cellIndex
806
+ };
807
+ } )
808
+ );
809
+ }
810
+
811
+ this._actionSelector( selected );
812
+
813
+ // Stop shiftScroll
814
+ clearInterval( this.s.scrollInterval );
815
+ this.s.scrollInterval = null;
816
+ },
817
+
818
+
819
+ /**
820
+ * Create an array with a range of numbers defined by the start and end
821
+ * parameters passed in (inclusive!).
822
+ *
823
+ * @param {integer} start Start
824
+ * @param {integer} end End
825
+ * @private
826
+ */
827
+ _range: function ( start, end )
828
+ {
829
+ var out = [];
830
+ var i;
831
+
832
+ if ( start <= end ) {
833
+ for ( i=start ; i<=end ; i++ ) {
834
+ out.push( i );
835
+ }
836
+ }
837
+ else {
838
+ for ( i=start ; i>=end ; i-- ) {
839
+ out.push( i );
840
+ }
841
+ }
842
+
843
+ return out;
844
+ },
845
+
846
+
847
+ /**
848
+ * Move the window and DataTables scrolling during a drag to scroll new
849
+ * content into view. This is done by proximity to the edge of the scrolling
850
+ * container of the mouse - for example near the top edge of the window
851
+ * should scroll up. This is a little complicated as there are two elements
852
+ * that can be scrolled - the window and the DataTables scrolling view port
853
+ * (if scrollX and / or scrollY is enabled).
854
+ *
855
+ * @param {object} e Mouse move event object
856
+ * @private
857
+ */
858
+ _shiftScroll: function ( e )
859
+ {
860
+ var that = this;
861
+ var dt = this.s.dt;
862
+ var scroll = this.s.scroll;
863
+ var runInterval = false;
864
+ var scrollSpeed = 5;
865
+ var buffer = 65;
866
+
867
+ // Different values if using a touchscreen
868
+ var pageX = !e.type.includes('touch') ? e.pageX - window.scrollX :e.touches[0].clientX;
869
+ var pageY = !e.type.includes('touch') ? e.pageY - window.scrollY :e.touches[0].clientY;
870
+ var
871
+ windowY = pageY,
872
+ windowX = pageX,
873
+ windowVert, windowHoriz,
874
+ dtVert, dtHoriz;
875
+
876
+ // Window calculations - based on the mouse position in the window,
877
+ // regardless of scrolling
878
+ if ( windowY < buffer ) {
879
+ windowVert = scrollSpeed * -1;
880
+ }
881
+ else if ( windowY > scroll.windowHeight - buffer ) {
882
+ windowVert = scrollSpeed;
883
+ }
884
+
885
+ if ( windowX < buffer ) {
886
+ windowHoriz = scrollSpeed * -1;
887
+ }
888
+ else if ( windowX > scroll.windowWidth - buffer ) {
889
+ windowHoriz = scrollSpeed;
890
+ }
891
+
892
+ // DataTables scrolling calculations - based on the table's position in
893
+ // the document and the mouse position on the page
894
+ if ( scroll.dtTop !== null && pageY < scroll.dtTop + buffer ) {
895
+ dtVert = scrollSpeed * -1;
896
+ }
897
+ else if ( scroll.dtTop !== null && pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
898
+ dtVert = scrollSpeed;
899
+ }
900
+
901
+ if ( scroll.dtLeft !== null && pageX < scroll.dtLeft + buffer ) {
902
+ dtHoriz = scrollSpeed * -1;
903
+ }
904
+ else if ( scroll.dtLeft !== null && pageX > scroll.dtLeft + scroll.dtWidth - buffer ) {
905
+ dtHoriz = scrollSpeed;
906
+ }
907
+
908
+ // This is where it gets interesting. We want to continue scrolling
909
+ // without requiring a mouse move, so we need an interval to be
910
+ // triggered. The interval should continue until it is no longer needed,
911
+ // but it must also use the latest scroll commands (for example consider
912
+ // that the mouse might move from scrolling up to scrolling left, all
913
+ // with the same interval running. We use the `scroll` object to "pass"
914
+ // this information to the interval. Can't use local variables as they
915
+ // wouldn't be the ones that are used by an already existing interval!
916
+ if ( windowVert || windowHoriz || dtVert || dtHoriz ) {
917
+ scroll.windowVert = windowVert;
918
+ scroll.windowHoriz = windowHoriz;
919
+ scroll.dtVert = dtVert;
920
+ scroll.dtHoriz = dtHoriz;
921
+ runInterval = true;
922
+ }
923
+ else if ( this.s.scrollInterval ) {
924
+ // Don't need to scroll - remove any existing timer
925
+ clearInterval( this.s.scrollInterval );
926
+ this.s.scrollInterval = null;
927
+ }
928
+
929
+ // If we need to run the interval to scroll and there is no existing
930
+ // interval (if there is an existing one, it will continue to run)
931
+ if ( ! this.s.scrollInterval && runInterval ) {
932
+ this.s.scrollInterval = setInterval( function () {
933
+ // Don't need to worry about setting scroll <0 or beyond the
934
+ // scroll bound as the browser will just reject that.
935
+ window.scrollTo(window.scrollX + (scroll.windowHoriz ? scroll.windowHoriz : 0), window.scrollY + (scroll.windowVert ? scroll.windowVert : 0))
936
+
937
+ // DataTables scrolling
938
+ if ( scroll.dtVert || scroll.dtHoriz ) {
939
+ var scroller = that.dom.dtScroll[0];
940
+
941
+ if ( scroll.dtVert ) {
942
+ scroller.scrollTop += scroll.dtVert;
943
+ }
944
+ if ( scroll.dtHoriz ) {
945
+ scroller.scrollLeft += scroll.dtHoriz;
946
+ }
947
+ }
948
+ }, 20 );
949
+ }
950
+ },
951
+
952
+
953
+ /**
954
+ * Update the DataTable after the user has selected what they want to do
955
+ *
956
+ * @param {false|undefined} result Return from the `execute` method - can
957
+ * be false internally to do nothing. This is not documented for plug-ins
958
+ * and is used only by the cancel option.
959
+ * @param {array} cells Information about the selected cells from the key
960
+ * up function, argumented with the set values
961
+ * @private
962
+ */
963
+ _update: function ( result, cells )
964
+ {
965
+ // Do nothing on `false` return from an execute function
966
+ if ( result === false ) {
967
+ return;
968
+ }
969
+
970
+ var dt = this.s.dt;
971
+ var cell;
972
+ var columns = dt.columns( this.c.columns ).indexes();
973
+
974
+ // Potentially allow modifications to the cells matrix
975
+ this._emitEvent( 'preAutoFill', [ dt, cells ] );
976
+
977
+ this._editor( cells );
978
+
979
+ // Automatic updates are not performed if `update` is null and the
980
+ // `editor` parameter is passed in - the reason being that Editor will
981
+ // update the data once submitted
982
+ var update = this.c.update !== null ?
983
+ this.c.update :
984
+ this.c.editor ?
985
+ false :
986
+ true;
987
+
988
+ if ( update ) {
989
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
990
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
991
+ cell = cells[i][j];
992
+
993
+ if ( columns.indexOf(cell.index.column) !== -1 ) {
994
+ cell.cell.data( cell.set );
995
+ }
996
+ }
997
+ }
998
+
999
+ dt.draw(false);
1000
+ }
1001
+
1002
+ this._emitEvent( 'autoFill', [ dt, cells ] );
1003
+ }
1004
+ } );
1005
+
1006
+
1007
+ /**
1008
+ * AutoFill actions. The options here determine how AutoFill will fill the data
1009
+ * in the table when the user has selected a range of cells. Please see the
1010
+ * documentation on the DataTables site for full details on how to create plug-
1011
+ * ins.
1012
+ *
1013
+ * @type {Object}
1014
+ */
1015
+ AutoFill.actions = {
1016
+ increment: {
1017
+ available: function ( dt, cells ) {
1018
+ var d = cells[0][0].label;
1019
+
1020
+ // is numeric test based on jQuery's old `isNumeric` function
1021
+ return !isNaN( d - parseFloat( d ) );
1022
+ },
1023
+
1024
+ option: function ( dt, cells ) {
1025
+ return dt.i18n(
1026
+ 'autoFill.increment',
1027
+ 'Increment / decrement each cell by: <input type="number" value="1">'
1028
+ );
1029
+ },
1030
+
1031
+ execute: function ( dt, cells, node ) {
1032
+ var value = cells[0][0].data * 1;
1033
+ var increment = $('input', node).val() * 1;
1034
+
1035
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
1036
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
1037
+ cells[i][j].set = value;
1038
+
1039
+ value += increment;
1040
+ }
1041
+ }
1042
+ }
1043
+ },
1044
+
1045
+ fill: {
1046
+ available: function ( dt, cells ) {
1047
+ return true;
1048
+ },
1049
+
1050
+ option: function ( dt, cells ) {
1051
+ return dt.i18n('autoFill.fill', 'Fill all cells with <i>%d</i>', cells[0][0].label );
1052
+ },
1053
+
1054
+ execute: function ( dt, cells, node ) {
1055
+ var value = cells[0][0].data;
1056
+
1057
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
1058
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
1059
+ cells[i][j].set = value;
1060
+ }
1061
+ }
1062
+ }
1063
+ },
1064
+
1065
+ fillHorizontal: {
1066
+ available: function ( dt, cells ) {
1067
+ return cells.length > 1 && cells[0].length > 1;
1068
+ },
1069
+
1070
+ option: function ( dt, cells ) {
1071
+ return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' );
1072
+ },
1073
+
1074
+ execute: function ( dt, cells, node ) {
1075
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
1076
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
1077
+ cells[i][j].set = cells[i][0].data;
1078
+ }
1079
+ }
1080
+ }
1081
+ },
1082
+
1083
+ fillVertical: {
1084
+ available: function ( dt, cells ) {
1085
+ return cells.length > 1 && cells[0].length > 1;
1086
+ },
1087
+
1088
+ option: function ( dt, cells ) {
1089
+ return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' );
1090
+ },
1091
+
1092
+ execute: function ( dt, cells, node ) {
1093
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
1094
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
1095
+ cells[i][j].set = cells[0][j].data;
1096
+ }
1097
+ }
1098
+ }
1099
+ },
1100
+
1101
+ // Special type that does not make itself available, but is added
1102
+ // automatically by AutoFill if a multi-choice list is shown. This allows
1103
+ // sensible code reuse
1104
+ cancel: {
1105
+ available: function () {
1106
+ return false;
1107
+ },
1108
+
1109
+ option: function ( dt ) {
1110
+ return dt.i18n('autoFill.cancel', 'Cancel' );
1111
+ },
1112
+
1113
+ execute: function () {
1114
+ return false;
1115
+ }
1116
+ }
1117
+ };
1118
+
1119
+
1120
+ /**
1121
+ * AutoFill version
1122
+ *
1123
+ * @static
1124
+ * @type String
1125
+ */
1126
+ AutoFill.version = '2.5.1';
1127
+
1128
+
1129
+ /**
1130
+ * AutoFill defaults
1131
+ *
1132
+ * @namespace
1133
+ */
1134
+ AutoFill.defaults = {
1135
+ /** @type {Boolean} Ask user what they want to do, even for a single option */
1136
+ alwaysAsk: false,
1137
+
1138
+ closeButton: true,
1139
+
1140
+ /** @type {string|null} What will trigger a focus */
1141
+ focus: null, // focus, click, hover
1142
+
1143
+ /** @type {column-selector} Columns to provide auto fill for */
1144
+ columns: '', // all
1145
+
1146
+ /** @type {Boolean} Enable AutoFill on load */
1147
+ enable: true,
1148
+
1149
+ /** @type {boolean|null} Update the cells after a drag */
1150
+ update: null, // false is editor given, true otherwise
1151
+
1152
+ /** @type {DataTable.Editor} Editor instance for automatic submission */
1153
+ editor: null,
1154
+
1155
+ /** @type {boolean} Enable vertical fill */
1156
+ vertical: true,
1157
+
1158
+ /** @type {boolean} Enable horizontal fill */
1159
+ horizontal: true
1160
+ };
1161
+
1162
+
1163
+ /**
1164
+ * Classes used by AutoFill that are configurable
1165
+ *
1166
+ * @namespace
1167
+ */
1168
+ AutoFill.classes = {
1169
+ /** @type {String} Class used by the selection button */
1170
+ btn: 'btn',
1171
+
1172
+ closeable: 'dtaf-popover-closeable'
1173
+ };
1174
+
1175
+
1176
+ /*
1177
+ * API
1178
+ */
1179
+ var Api = $.fn.dataTable.Api;
1180
+
1181
+ // Doesn't do anything - Not documented
1182
+ Api.register( 'autoFill()', function () {
1183
+ return this;
1184
+ } );
1185
+
1186
+ Api.register( 'autoFill().enabled()', function () {
1187
+ var ctx = this.context[0];
1188
+
1189
+ return ctx.autoFill ?
1190
+ ctx.autoFill.enabled() :
1191
+ false;
1192
+ } );
1193
+
1194
+ Api.register( 'autoFill().enable()', function ( flag ) {
1195
+ return this.iterator( 'table', function ( ctx ) {
1196
+ if ( ctx.autoFill ) {
1197
+ ctx.autoFill.enable( flag );
1198
+ }
1199
+ } );
1200
+ } );
1201
+
1202
+ Api.register( 'autoFill().disable()', function () {
1203
+ return this.iterator( 'table', function ( ctx ) {
1204
+ if ( ctx.autoFill ) {
1205
+ ctx.autoFill.disable();
1206
+ }
1207
+ } );
1208
+ } );
1209
+
1210
+
1211
+ // Attach a listener to the document which listens for DataTables initialisation
1212
+ // events so we can automatically initialise
1213
+ $(document).on( 'preInit.dt.autofill', function (e, settings, json) {
1214
+ if ( e.namespace !== 'dt' ) {
1215
+ return;
1216
+ }
1217
+
1218
+ var init = settings.oInit.autoFill;
1219
+ var defaults = DataTable.defaults.autoFill;
1220
+
1221
+ if ( init || defaults ) {
1222
+ var opts = $.extend( {}, init, defaults );
1223
+
1224
+ if ( init !== false ) {
1225
+ new AutoFill( settings, opts );
1226
+ }
1227
+ }
1228
+ } );
1229
+
1230
+
1231
+ // Alias for access
1232
+ DataTable.AutoFill = AutoFill;
1233
+ DataTable.AutoFill = AutoFill;
1234
+
1235
+
1236
+ export default DataTable;