;
175 |
176 | /**
177 | * This method will return a boolean value indicating if AutoFill is enabled or not on the selected table.
178 | *
179 | * @returns boolean signifying if autofill is enables
180 | */
181 | enabled(): boolean;
182 | }
183 |
--------------------------------------------------------------------------------
/js/dataTables.autoFill.js:
--------------------------------------------------------------------------------
1 | /*! AutoFill 2.7.1
2 | * © SpryMedia Ltd - datatables.net/license
3 | */
4 |
5 | /**
6 | * @summary AutoFill
7 | * @description Add Excel like click and drag auto-fill options to DataTables
8 | * @version 2.7.1
9 | * @author SpryMedia Ltd (www.sprymedia.co.uk)
10 | * @copyright SpryMedia Ltd.
11 | *
12 | * This source file is free software, available under the following license:
13 | * MIT license - http://datatables.net/license/mit
14 | *
15 | * This source file is distributed in the hope that it will be useful, but
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17 | * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
18 | *
19 | * For details please refer to: http://www.datatables.net
20 | */
21 |
22 | var _instance = 0;
23 |
24 | /**
25 | * AutoFill provides Excel like auto-fill features for a DataTable
26 | *
27 | * @class AutoFill
28 | * @constructor
29 | * @param {object} oTD DataTables settings object
30 | * @param {object} oConfig Configuration object for AutoFill
31 | */
32 | var AutoFill = function( dt, opts )
33 | {
34 | if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.11' ) ) {
35 | throw( "Warning: AutoFill requires DataTables 1.11 or greater");
36 | }
37 |
38 | // User and defaults configuration object
39 | this.c = $.extend( true, {},
40 | DataTable.defaults.autoFill,
41 | AutoFill.defaults,
42 | opts
43 | );
44 |
45 | /**
46 | * @namespace Settings object which contains customisable information for AutoFill instance
47 | */
48 | this.s = {
49 | /** @type {DataTable.Api} DataTables' API instance */
50 | dt: new DataTable.Api( dt ),
51 |
52 | /** @type {String} Unique namespace for events attached to the document */
53 | namespace: '.autoFill'+(_instance++),
54 |
55 | /** @type {Object} Cached dimension information for use in the mouse move event handler */
56 | scroll: {},
57 |
58 | /** @type {integer} Interval object used for smooth scrolling */
59 | scrollInterval: null,
60 |
61 | handle: {
62 | height: 0,
63 | width: 0
64 | },
65 |
66 | /**
67 | * Enabled setting
68 | * @type {Boolean}
69 | */
70 | enabled: false
71 | };
72 |
73 |
74 | /**
75 | * @namespace Common and useful DOM elements for the class instance
76 | */
77 | this.dom = {
78 | closeButton: $('×
'),
79 |
80 | /** @type {jQuery} AutoFill handle */
81 | handle: $(''),
82 |
83 | /**
84 | * @type {Object} Selected cells outline - Need to use 4 elements,
85 | * otherwise the mouse over if you back into the selected rectangle
86 | * will be over that element, rather than the cells!
87 | */
88 | select: {
89 | top: $(''),
90 | right: $(''),
91 | bottom: $(''),
92 | left: $('')
93 | },
94 |
95 | /** @type {jQuery} Fill type chooser background */
96 | background: $(''),
97 |
98 | /** @type {jQuery} Fill type chooser */
99 | list: $(''+this.s.dt.i18n('autoFill.info', '')+'
')
100 | .attr('aria-modal', true)
101 | .attr('role', 'dialog')
102 | .append(''),
103 |
104 | /** @type {jQuery} DataTables scrolling container */
105 | dtScroll: null,
106 |
107 | /** @type {jQuery} Offset parent element */
108 | offsetParent: null
109 | };
110 |
111 |
112 | /* Constructor logic */
113 | this._constructor();
114 | };
115 |
116 |
117 |
118 | $.extend( AutoFill.prototype, {
119 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
120 | * Public methods (exposed via the DataTables API below)
121 | */
122 | enabled: function ()
123 | {
124 | return this.s.enabled;
125 | },
126 |
127 |
128 | enable: function ( flag )
129 | {
130 | var that = this;
131 |
132 | if ( flag === false ) {
133 | return this.disable();
134 | }
135 |
136 | this.s.enabled = true;
137 |
138 | this._focusListener();
139 |
140 | this.dom.handle.on( 'mousedown touchstart', function (e) {
141 | that._mousedown( e );
142 | return false;
143 | } );
144 |
145 | $(window).on('resize', function() {
146 | var handle = $('div.dt-autofill-handle');
147 | if(handle.length > 0 && that.dom.attachedTo !== undefined) {
148 | that._attach(that.dom.attachedTo)
149 | }
150 | })
151 |
152 | let orientationReset = function() {
153 | that.s.handle = {
154 | height: false,
155 | width: false
156 | };
157 | $(that.dom.handle).css({
158 | 'height': '',
159 | 'width': ''
160 | })
161 | if(that.dom.attachedTo !== undefined) {
162 | that._attach(that.dom.attachedTo)
163 | }
164 | }
165 |
166 | $(window)
167 | .on('orientationchange', function() {
168 | setTimeout(function() {
169 | orientationReset();
170 | setTimeout(orientationReset, 150);
171 | }, 50);
172 | });
173 |
174 | return this;
175 | },
176 |
177 | disable: function ()
178 | {
179 | this.s.enabled = false;
180 |
181 | this._focusListenerRemove();
182 |
183 | return this;
184 | },
185 |
186 |
187 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
188 | * Constructor
189 | */
190 |
191 | /**
192 | * Initialise the RowReorder instance
193 | *
194 | * @private
195 | */
196 | _constructor: function ()
197 | {
198 | var that = this;
199 | var dt = this.s.dt;
200 |
201 | // Selectors for DataTables 1 and 2 - only one will be matched
202 | var dtScroll = $('div.dataTables_scrollBody, div.dt-scroll-body', 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 || this.c.alwaysAsk ) {
303 | // Multiple actions available - ask the end user what they want to do
304 | var list = this.dom.list.children('div.dt-autofill-list-items').empty();
305 |
306 | // Add a cancel option
307 | available.push( 'cancel' );
308 |
309 | $.each( available, function ( i, name ) {
310 | list.append( $('')
311 | .html(actions[ name ].option( dt, cells ))
312 | .append( $('').html(dt.i18n('autoFill.button', '>')))
313 | .on( 'click', function (e) {
314 | if (e.target.nodeName.toLowerCase() !== 'button') {
315 | return;
316 | }
317 |
318 | var result = actions[ name ].execute(
319 | dt, cells, $(this).closest('button')
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=end ; i-- ) {
844 | out.push( i );
845 | }
846 | }
847 |
848 | return out;
849 | },
850 |
851 |
852 | /**
853 | * Move the window and DataTables scrolling during a drag to scroll new
854 | * content into view. This is done by proximity to the edge of the scrolling
855 | * container of the mouse - for example near the top edge of the window
856 | * should scroll up. This is a little complicated as there are two elements
857 | * that can be scrolled - the window and the DataTables scrolling view port
858 | * (if scrollX and / or scrollY is enabled).
859 | *
860 | * @param {object} e Mouse move event object
861 | * @private
862 | */
863 | _shiftScroll: function ( e )
864 | {
865 | var that = this;
866 | var scroll = this.s.scroll;
867 | var runInterval = false;
868 | var scrollSpeed = 5;
869 | var buffer = 65;
870 |
871 | // Different values if using a touchscreen
872 | var pageX = !e.type.includes('touch') ? e.pageX - window.scrollX :e.touches[0].clientX;
873 | var pageY = !e.type.includes('touch') ? e.pageY - window.scrollY :e.touches[0].clientY;
874 | var
875 | windowY = pageY,
876 | windowX = pageX,
877 | windowVert, windowHoriz,
878 | dtVert, dtHoriz;
879 |
880 | // Window calculations - based on the mouse position in the window,
881 | // regardless of scrolling
882 | if ( windowY < buffer ) {
883 | windowVert = scrollSpeed * -1;
884 | }
885 | else if ( windowY > scroll.windowHeight - buffer ) {
886 | windowVert = scrollSpeed;
887 | }
888 |
889 | if ( windowX < buffer ) {
890 | windowHoriz = scrollSpeed * -1;
891 | }
892 | else if ( windowX > scroll.windowWidth - buffer ) {
893 | windowHoriz = scrollSpeed;
894 | }
895 |
896 | // DataTables scrolling calculations - based on the table's position in
897 | // the document and the mouse position on the page
898 | if ( scroll.dtTop !== null && pageY < scroll.dtTop + buffer ) {
899 | dtVert = scrollSpeed * -1;
900 | }
901 | else if ( scroll.dtTop !== null && pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
902 | dtVert = scrollSpeed;
903 | }
904 |
905 | if ( scroll.dtLeft !== null && pageX < scroll.dtLeft + buffer ) {
906 | dtHoriz = scrollSpeed * -1;
907 | }
908 | else if ( scroll.dtLeft !== null && pageX > scroll.dtLeft + scroll.dtWidth - buffer ) {
909 | dtHoriz = scrollSpeed;
910 | }
911 |
912 | // This is where it gets interesting. We want to continue scrolling
913 | // without requiring a mouse move, so we need an interval to be
914 | // triggered. The interval should continue until it is no longer needed,
915 | // but it must also use the latest scroll commands (for example consider
916 | // that the mouse might move from scrolling up to scrolling left, all
917 | // with the same interval running. We use the `scroll` object to "pass"
918 | // this information to the interval. Can't use local variables as they
919 | // wouldn't be the ones that are used by an already existing interval!
920 | if ( windowVert || windowHoriz || dtVert || dtHoriz ) {
921 | scroll.windowVert = windowVert;
922 | scroll.windowHoriz = windowHoriz;
923 | scroll.dtVert = dtVert;
924 | scroll.dtHoriz = dtHoriz;
925 | runInterval = true;
926 | }
927 | else if ( this.s.scrollInterval ) {
928 | // Don't need to scroll - remove any existing timer
929 | clearInterval( this.s.scrollInterval );
930 | this.s.scrollInterval = null;
931 | }
932 |
933 | // If we need to run the interval to scroll and there is no existing
934 | // interval (if there is an existing one, it will continue to run)
935 | if ( ! this.s.scrollInterval && runInterval ) {
936 | this.s.scrollInterval = setInterval( function () {
937 | // Don't need to worry about setting scroll <0 or beyond the
938 | // scroll bound as the browser will just reject that.
939 | window.scrollTo(window.scrollX + (scroll.windowHoriz ? scroll.windowHoriz : 0), window.scrollY + (scroll.windowVert ? scroll.windowVert : 0))
940 |
941 | // DataTables scrolling
942 | if ( scroll.dtVert || scroll.dtHoriz ) {
943 | var scroller = that.dom.dtScroll[0];
944 |
945 | if ( scroll.dtVert ) {
946 | scroller.scrollTop += scroll.dtVert;
947 | }
948 | if ( scroll.dtHoriz ) {
949 | scroller.scrollLeft += scroll.dtHoriz;
950 | }
951 | }
952 | }, 20 );
953 | }
954 | },
955 |
956 |
957 | /**
958 | * Update the DataTable after the user has selected what they want to do
959 | *
960 | * @param {false|undefined} result Return from the `execute` method - can
961 | * be false internally to do nothing. This is not documented for plug-ins
962 | * and is used only by the cancel option.
963 | * @param {array} cells Information about the selected cells from the key
964 | * up function, argumented with the set values
965 | * @private
966 | */
967 | _update: function ( result, cells )
968 | {
969 | // Do nothing on `false` return from an execute function
970 | if ( result === false ) {
971 | return;
972 | }
973 |
974 | var dt = this.s.dt;
975 | var cell;
976 | var columns = dt.columns( this.c.columns ).indexes();
977 |
978 | // Potentially allow modifications to the cells matrix
979 | this._emitEvent( 'preAutoFill', [ dt, cells ] );
980 |
981 | this._editor( cells );
982 |
983 | // Automatic updates are not performed if `update` is null and the
984 | // `editor` parameter is passed in - the reason being that Editor will
985 | // update the data once submitted
986 | var update = this.c.update !== null ?
987 | this.c.update :
988 | this.c.editor ?
989 | false :
990 | true;
991 |
992 | if ( update ) {
993 | for ( var i=0, ien=cells.length ; i'
1032 | );
1033 | },
1034 |
1035 | execute: function ( dt, cells, node ) {
1036 | var value = cells[0][0].data * 1;
1037 | var increment = $('input', node).val() * 1;
1038 |
1039 | for ( var i=0, ien=cells.length ; i%d', cells[0][0].label );
1056 | },
1057 |
1058 | execute: function ( dt, cells, node ) {
1059 | var value = cells[0][0].data;
1060 |
1061 | for ( var i=0, ien=cells.length ; i 1 && cells[0].length > 1;
1072 | },
1073 |
1074 | option: function ( dt, cells ) {
1075 | return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' );
1076 | },
1077 |
1078 | execute: function ( dt, cells, node ) {
1079 | for ( var i=0, ien=cells.length ; i 1 && cells[0].length > 1;
1090 | },
1091 |
1092 | option: function ( dt, cells ) {
1093 | return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' );
1094 | },
1095 |
1096 | execute: function ( dt, cells, node ) {
1097 | for ( var i=0, ien=cells.length ; i