datatables.net-autofill 2.3.9 → 2.5.0

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