| 875 |
lars |
1 |
/*! AutoFill 2.0.0
|
|
|
2 |
* ©2008-2015 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.0.0
|
|
|
9 |
* @file dataTables.autoFill.js
|
|
|
10 |
* @author SpryMedia Ltd (www.sprymedia.co.uk)
|
|
|
11 |
* @contact www.sprymedia.co.uk/contact
|
|
|
12 |
* @copyright Copyright 2010-2015 SpryMedia Ltd.
|
|
|
13 |
*
|
|
|
14 |
* This source file is free software, available under the following license:
|
|
|
15 |
* MIT license - http://datatables.net/license/mit
|
|
|
16 |
*
|
|
|
17 |
* This source file is distributed in the hope that it will be useful, but
|
|
|
18 |
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
19 |
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
|
|
|
20 |
*
|
|
|
21 |
* For details please refer to: http://www.datatables.net
|
|
|
22 |
*/
|
|
|
23 |
|
|
|
24 |
(function( window, document, undefined ) {
|
|
|
25 |
|
|
|
26 |
var factory = function( $, DataTable ) {
|
|
|
27 |
"use strict";
|
|
|
28 |
|
|
|
29 |
var _instance = 0;
|
|
|
30 |
|
|
|
31 |
/**
|
|
|
32 |
* AutoFill provides Excel like auto-fill features for a DataTable
|
|
|
33 |
*
|
|
|
34 |
* @class AutoFill
|
|
|
35 |
* @constructor
|
|
|
36 |
* @param {object} oTD DataTables settings object
|
|
|
37 |
* @param {object} oConfig Configuration object for AutoFill
|
|
|
38 |
*/
|
|
|
39 |
var AutoFill = function( dt, opts )
|
|
|
40 |
{
|
|
|
41 |
if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
|
|
|
42 |
throw( "Warning: AutoFill requires DataTables 1.10.8 or greater");
|
|
|
43 |
}
|
|
|
44 |
|
|
|
45 |
// User and defaults configuration object
|
|
|
46 |
this.c = $.extend( true, {},
|
|
|
47 |
DataTable.defaults.autoFill,
|
|
|
48 |
AutoFill.defaults,
|
|
|
49 |
opts
|
|
|
50 |
);
|
|
|
51 |
|
|
|
52 |
/**
|
|
|
53 |
* @namespace Settings object which contains customisable information for AutoFill instance
|
|
|
54 |
*/
|
|
|
55 |
this.s = {
|
|
|
56 |
/** @type {DataTable.Api} DataTables' API instance */
|
|
|
57 |
dt: new DataTable.Api( dt ),
|
|
|
58 |
|
|
|
59 |
/** @type {String} Unique namespace for events attached to the document */
|
|
|
60 |
namespace: '.autoFill'+(_instance++),
|
|
|
61 |
|
|
|
62 |
/** @type {Object} Cached dimension information for use in the mouse move event handler */
|
|
|
63 |
scroll: {},
|
|
|
64 |
|
|
|
65 |
/** @type {integer} Interval object used for smooth scrolling */
|
|
|
66 |
scrollInterval: null
|
|
|
67 |
};
|
|
|
68 |
|
|
|
69 |
|
|
|
70 |
/**
|
|
|
71 |
* @namespace Common and useful DOM elements for the class instance
|
|
|
72 |
*/
|
|
|
73 |
this.dom = {
|
|
|
74 |
/** @type {jQuery} AutoFill handle */
|
|
|
75 |
handle: $('<div class="dt-autofill-handle"/>'),
|
|
|
76 |
|
|
|
77 |
/**
|
|
|
78 |
* @type {Object} Selected cells outline - Need to use 4 elements,
|
|
|
79 |
* otherwise the mouse over if you back into the selected rectangle
|
|
|
80 |
* will be over that element, rather than the cells!
|
|
|
81 |
*/
|
|
|
82 |
select: {
|
|
|
83 |
top: $('<div class="dt-autofill-select top"/>'),
|
|
|
84 |
right: $('<div class="dt-autofill-select right"/>'),
|
|
|
85 |
bottom: $('<div class="dt-autofill-select bottom"/>'),
|
|
|
86 |
left: $('<div class="dt-autofill-select left"/>')
|
|
|
87 |
},
|
|
|
88 |
|
|
|
89 |
/** @type {jQuery} Fill type chooser background */
|
|
|
90 |
background: $('<div class="dt-autofill-background"/>'),
|
|
|
91 |
|
|
|
92 |
/** @type {jQuery} Fill type chooser */
|
|
|
93 |
list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'<ul/></div>'),
|
|
|
94 |
|
|
|
95 |
/** @type {jQuery} DataTables scrolling container */
|
|
|
96 |
dtScroll: null
|
|
|
97 |
};
|
|
|
98 |
|
|
|
99 |
|
|
|
100 |
/* Constructor logic */
|
|
|
101 |
this._constructor();
|
|
|
102 |
};
|
|
|
103 |
|
|
|
104 |
|
|
|
105 |
|
|
|
106 |
AutoFill.prototype = {
|
|
|
107 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
108 |
* Constructor
|
|
|
109 |
*/
|
|
|
110 |
|
|
|
111 |
/**
|
|
|
112 |
* Initialise the RowReorder instance
|
|
|
113 |
*
|
|
|
114 |
* @private
|
|
|
115 |
*/
|
|
|
116 |
_constructor: function ()
|
|
|
117 |
{
|
|
|
118 |
var that = this;
|
|
|
119 |
var dt = this.s.dt;
|
|
|
120 |
var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container());
|
|
|
121 |
|
|
|
122 |
if ( dtScroll.length ) {
|
|
|
123 |
this.dom.dtScroll = dtScroll;
|
|
|
124 |
|
|
|
125 |
// Need to scroll container to be the offset parent
|
|
|
126 |
if ( dtScroll.css('position') === 'static' ) {
|
|
|
127 |
dtScroll.css( 'position', 'relative' );
|
|
|
128 |
}
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
this._focusListener();
|
|
|
132 |
|
|
|
133 |
this.dom.handle.on( 'mousedown', function (e) {
|
|
|
134 |
that._mousedown( e );
|
|
|
135 |
return false;
|
|
|
136 |
} );
|
|
|
137 |
|
|
|
138 |
dt.on( 'destroy.autoFill', function () {
|
|
|
139 |
dt.off( '.autoFill' );
|
|
|
140 |
$(dt.table().body()).off( that.s.namespace );
|
|
|
141 |
$(document.body).off( that.s.namespace );
|
|
|
142 |
} );
|
|
|
143 |
},
|
|
|
144 |
|
|
|
145 |
|
|
|
146 |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
147 |
* Private methods
|
|
|
148 |
*/
|
|
|
149 |
|
|
|
150 |
/**
|
|
|
151 |
* Display the AutoFill drag handle by appending it to a table cell. This
|
|
|
152 |
* is the opposite of the _detach method.
|
|
|
153 |
*
|
|
|
154 |
* @param {node} node TD/TH cell to insert the handle into
|
|
|
155 |
* @private
|
|
|
156 |
*/
|
|
|
157 |
_attach: function ( node )
|
|
|
158 |
{
|
|
|
159 |
var dt = this.s.dt;
|
|
|
160 |
var idx = dt.cell( node ).index();
|
|
|
161 |
|
|
|
162 |
if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) {
|
|
|
163 |
this._detach();
|
|
|
164 |
return;
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
this.dom.attachedTo = node;
|
|
|
168 |
this.dom.handle.appendTo( node );
|
|
|
169 |
},
|
|
|
170 |
|
|
|
171 |
|
|
|
172 |
/**
|
|
|
173 |
* Determine can the fill type should be. This can be automatic, or ask the
|
|
|
174 |
* end user.
|
|
|
175 |
*
|
|
|
176 |
* @param {array} cells Information about the selected cells from the key
|
|
|
177 |
* up function
|
|
|
178 |
* @private
|
|
|
179 |
*/
|
|
|
180 |
_actionSelector: function ( cells )
|
|
|
181 |
{
|
|
|
182 |
var that = this;
|
|
|
183 |
var dt = this.s.dt;
|
|
|
184 |
var actions = AutoFill.actions;
|
|
|
185 |
var available = [];
|
|
|
186 |
|
|
|
187 |
// "Ask" each plug-in if it wants to handle this data
|
|
|
188 |
$.each( actions, function ( key, action ) {
|
|
|
189 |
if ( action.available( dt, cells ) ) {
|
|
|
190 |
available.push( key );
|
|
|
191 |
}
|
|
|
192 |
} );
|
|
|
193 |
|
|
|
194 |
if ( available.length === 1 && this.c.alwaysAsk === false ) {
|
|
|
195 |
// Only one action available - enact it immediately
|
|
|
196 |
var result = actions[ available[0] ].execute( dt, cells );
|
|
|
197 |
this._update( result, cells );
|
|
|
198 |
}
|
|
|
199 |
else {
|
|
|
200 |
// Multiple actions available - ask the end user what they want to do
|
|
|
201 |
var list = this.dom.list.children('ul').empty();
|
|
|
202 |
|
|
|
203 |
// Add a cancel option
|
|
|
204 |
available.push( 'cancel' );
|
|
|
205 |
|
|
|
206 |
$.each( available, function ( i, name ) {
|
|
|
207 |
list.append( $('<li/>')
|
|
|
208 |
.append(
|
|
|
209 |
'<div class="dt-autofill-question">'+
|
|
|
210 |
actions[ name ].option( dt, cells )+
|
|
|
211 |
'<div>'
|
|
|
212 |
)
|
|
|
213 |
.append( $('<div class="dt-autofill-button">' )
|
|
|
214 |
.append( $('<button class="'+AutoFill.classes.btn+'">'+dt.i18n('autoFill.button', '>')+'</button>')
|
|
|
215 |
.on( 'click', function () {
|
|
|
216 |
var result = actions[ name ].execute(
|
|
|
217 |
dt, cells, $(this).closest('li')
|
|
|
218 |
);
|
|
|
219 |
that._update( result, cells );
|
|
|
220 |
|
|
|
221 |
that.dom.background.remove();
|
|
|
222 |
that.dom.list.remove();
|
|
|
223 |
} )
|
|
|
224 |
)
|
|
|
225 |
)
|
|
|
226 |
);
|
|
|
227 |
} );
|
|
|
228 |
|
|
|
229 |
this.dom.background.appendTo( 'body' );
|
|
|
230 |
this.dom.list.appendTo( 'body' );
|
|
|
231 |
|
|
|
232 |
this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 );
|
|
|
233 |
}
|
|
|
234 |
},
|
|
|
235 |
|
|
|
236 |
|
|
|
237 |
/**
|
|
|
238 |
* Remove the AutoFill handle from the document
|
|
|
239 |
*
|
|
|
240 |
* @private
|
|
|
241 |
*/
|
|
|
242 |
_detach: function ()
|
|
|
243 |
{
|
|
|
244 |
this.dom.attachedTo = null;
|
|
|
245 |
this.dom.handle.detach();
|
|
|
246 |
},
|
|
|
247 |
|
|
|
248 |
|
|
|
249 |
/**
|
|
|
250 |
* Draw the selection outline by calculating the range between the start
|
|
|
251 |
* and end cells, then placing the highlighting elements to draw a rectangle
|
|
|
252 |
*
|
|
|
253 |
* @param {node} target End cell
|
|
|
254 |
* @param {object} e Originating event
|
|
|
255 |
* @private
|
|
|
256 |
*/
|
|
|
257 |
_drawSelection: function ( target, e )
|
|
|
258 |
{
|
|
|
259 |
// Calculate boundary for start cell to this one
|
|
|
260 |
var dt = this.s.dt;
|
|
|
261 |
var start = this.s.start;
|
|
|
262 |
var startCell = $(this.dom.start);
|
|
|
263 |
var endCell = $(target);
|
|
|
264 |
var end = {
|
|
|
265 |
row: endCell.parent().index(),
|
|
|
266 |
column: endCell.index()
|
|
|
267 |
};
|
|
|
268 |
|
|
|
269 |
// Be sure that is a DataTables controlled cell
|
|
|
270 |
if ( ! dt.cell( endCell ).any() ) {
|
|
|
271 |
return;
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
// if target is not in the columns available - do nothing
|
|
|
275 |
if ( dt.columns( this.c.columns ).indexes().indexOf( end.column ) === -1 ) {
|
|
|
276 |
return;
|
|
|
277 |
}
|
|
|
278 |
|
|
|
279 |
this.s.end = end;
|
|
|
280 |
|
|
|
281 |
var top, bottom, left, right, height, width;
|
|
|
282 |
|
|
|
283 |
top = start.row < end.row ? startCell : endCell;
|
|
|
284 |
bottom = start.row < end.row ? endCell : startCell;
|
|
|
285 |
left = start.column < end.column ? startCell : endCell;
|
|
|
286 |
right = start.column < end.column ? endCell : startCell;
|
|
|
287 |
|
|
|
288 |
top = top.position().top;
|
|
|
289 |
left = left.position().left;
|
|
|
290 |
height = bottom.position().top + bottom.outerHeight() - top;
|
|
|
291 |
width = right.position().left + right.outerWidth() - left;
|
|
|
292 |
|
|
|
293 |
var dtScroll = this.dom.dtScroll;
|
|
|
294 |
if ( dtScroll ) {
|
|
|
295 |
top += dtScroll.scrollTop();
|
|
|
296 |
left += dtScroll.scrollLeft();
|
|
|
297 |
}
|
|
|
298 |
|
|
|
299 |
var select = this.dom.select;
|
|
|
300 |
select.top.css( {
|
|
|
301 |
top: top,
|
|
|
302 |
left: left,
|
|
|
303 |
width: width
|
|
|
304 |
} );
|
|
|
305 |
|
|
|
306 |
select.left.css( {
|
|
|
307 |
top: top,
|
|
|
308 |
left: left,
|
|
|
309 |
height: height
|
|
|
310 |
} );
|
|
|
311 |
|
|
|
312 |
select.bottom.css( {
|
|
|
313 |
top: top + height,
|
|
|
314 |
left: left,
|
|
|
315 |
width: width
|
|
|
316 |
} );
|
|
|
317 |
|
|
|
318 |
select.right.css( {
|
|
|
319 |
top: top,
|
|
|
320 |
left: left + width,
|
|
|
321 |
height: height
|
|
|
322 |
} );
|
|
|
323 |
},
|
|
|
324 |
|
|
|
325 |
|
|
|
326 |
/**
|
|
|
327 |
* Use the Editor API to perform an update based on the new data for the
|
|
|
328 |
* cells
|
|
|
329 |
*
|
|
|
330 |
* @param {array} cells Information about the selected cells from the key
|
|
|
331 |
* up function
|
|
|
332 |
* @private
|
|
|
333 |
*/
|
|
|
334 |
_editor: function ( cells )
|
|
|
335 |
{
|
|
|
336 |
var dt = this.s.dt;
|
|
|
337 |
var editor = this.c.editor;
|
|
|
338 |
|
|
|
339 |
if ( ! editor ) {
|
|
|
340 |
return;
|
|
|
341 |
}
|
|
|
342 |
|
|
|
343 |
// Build the object structure for Editor's multi-row editing
|
|
|
344 |
var idValues = {};
|
|
|
345 |
var nodes = [];
|
|
|
346 |
|
|
|
347 |
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
|
|
348 |
for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
|
|
|
349 |
var cell = cells[i][j];
|
|
|
350 |
|
|
|
351 |
// Determine the field name for the cell being edited
|
|
|
352 |
var col = dt.settings()[0].aoColumns[ cell.index.column ];
|
|
|
353 |
var dataSrc = col.editField !== undefined ?
|
|
|
354 |
col.editField :
|
|
|
355 |
col.mData;
|
|
|
356 |
|
|
|
357 |
if ( ! dataSrc ) {
|
|
|
358 |
throw 'Could not automatically determine field name. '+
|
|
|
359 |
'Please see https://datatables.net/tn/11';
|
|
|
360 |
}
|
|
|
361 |
|
|
|
362 |
if ( ! idValues[ dataSrc ] ) {
|
|
|
363 |
idValues[ dataSrc ] = {};
|
|
|
364 |
}
|
|
|
365 |
|
|
|
366 |
var id = dt.row( cell.index.row ).id();
|
|
|
367 |
idValues[ dataSrc ][ id ] = cell.set;
|
|
|
368 |
|
|
|
369 |
// Keep a list of cells so we can activate the bubble editing
|
|
|
370 |
// with them
|
|
|
371 |
nodes.push( cell.index );
|
|
|
372 |
}
|
|
|
373 |
}
|
|
|
374 |
|
|
|
375 |
// Perform the edit using bubble editing as it allows us to specify
|
|
|
376 |
// the cells to be edited, rather than using full rows
|
|
|
377 |
editor
|
|
|
378 |
.bubble( nodes, false )
|
|
|
379 |
.multiSet( idValues )
|
|
|
380 |
.submit();
|
|
|
381 |
},
|
|
|
382 |
|
|
|
383 |
|
|
|
384 |
/**
|
|
|
385 |
* Emit an event on the DataTable for listeners
|
|
|
386 |
*
|
|
|
387 |
* @param {string} name Event name
|
|
|
388 |
* @param {array} args Event arguments
|
|
|
389 |
* @private
|
|
|
390 |
*/
|
|
|
391 |
_emitEvent: function ( name, args )
|
|
|
392 |
{
|
|
|
393 |
this.s.dt.iterator( 'table', function ( ctx, i ) {
|
|
|
394 |
$(ctx.nTable).triggerHandler( name+'.dt', args );
|
|
|
395 |
} );
|
|
|
396 |
},
|
|
|
397 |
|
|
|
398 |
|
|
|
399 |
/**
|
|
|
400 |
* Attach suitable listeners (based on the configuration) that will attach
|
|
|
401 |
* and detach the AutoFill handle in the document.
|
|
|
402 |
*
|
|
|
403 |
* @private
|
|
|
404 |
*/
|
|
|
405 |
_focusListener: function ()
|
|
|
406 |
{
|
|
|
407 |
var that = this;
|
|
|
408 |
var dt = this.s.dt;
|
|
|
409 |
var namespace = this.s.namespace;
|
|
|
410 |
var focus = this.c.focus !== null ?
|
|
|
411 |
this.c.focus :
|
|
|
412 |
dt.settings()[0].keytable ?
|
|
|
413 |
'focus' :
|
|
|
414 |
'hover';
|
|
|
415 |
|
|
|
416 |
// All event listeners attached here are removed in the `destroy`
|
|
|
417 |
// callback in the constructor
|
|
|
418 |
if ( focus === 'focus' ) {
|
|
|
419 |
dt
|
|
|
420 |
.on( 'key-focus.autoFill', function ( e, dt, cell ) {
|
|
|
421 |
that._attach( cell.node() );
|
|
|
422 |
} )
|
|
|
423 |
.on( 'key-blur.autoFill', function ( e, dt, cell ) {
|
|
|
424 |
that._detach();
|
|
|
425 |
} );
|
|
|
426 |
}
|
|
|
427 |
else if ( focus === 'click' ) {
|
|
|
428 |
$(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) {
|
|
|
429 |
that._attach( this );
|
|
|
430 |
} );
|
|
|
431 |
|
|
|
432 |
$(document.body).on( 'click'+namespace, function (e) {
|
|
|
433 |
if ( ! $(e.target).parents().filter( dt.table().body() ).length ) {
|
|
|
434 |
that._detach();
|
|
|
435 |
}
|
|
|
436 |
} );
|
|
|
437 |
}
|
|
|
438 |
else {
|
|
|
439 |
$(dt.table().body())
|
|
|
440 |
.on( 'mouseenter'+namespace, 'td, th', function (e) {
|
|
|
441 |
that._attach( this );
|
|
|
442 |
} )
|
|
|
443 |
.on( 'mouseleave'+namespace, function (e) {
|
|
|
444 |
that._detach();
|
|
|
445 |
} );
|
|
|
446 |
}
|
|
|
447 |
},
|
|
|
448 |
|
|
|
449 |
|
|
|
450 |
/**
|
|
|
451 |
* Start mouse drag - selects the start cell
|
|
|
452 |
*
|
|
|
453 |
* @param {object} e Mouse down event
|
|
|
454 |
* @private
|
|
|
455 |
*/
|
|
|
456 |
_mousedown: function ( e )
|
|
|
457 |
{
|
|
|
458 |
var that = this;
|
|
|
459 |
var dt = this.s.dt;
|
|
|
460 |
|
|
|
461 |
this.dom.start = this.dom.attachedTo;
|
|
|
462 |
this.s.start = {
|
|
|
463 |
row: $(this.dom.start).parent().index(),
|
|
|
464 |
column: $(this.dom.start).index()
|
|
|
465 |
};
|
|
|
466 |
|
|
|
467 |
$(document.body)
|
|
|
468 |
.on( 'mousemove.autoFill', function (e) {
|
|
|
469 |
that._mousemove( e );
|
|
|
470 |
} )
|
|
|
471 |
.on( 'mouseup.autoFill', function (e) {
|
|
|
472 |
that._mouseup( e );
|
|
|
473 |
} );
|
|
|
474 |
|
|
|
475 |
var select = this.dom.select;
|
|
|
476 |
var offsetParent = $(this.s.dt.table().body()).offsetParent();
|
|
|
477 |
select.top.appendTo( offsetParent );
|
|
|
478 |
select.left.appendTo( offsetParent );
|
|
|
479 |
select.right.appendTo( offsetParent );
|
|
|
480 |
select.bottom.appendTo( offsetParent );
|
|
|
481 |
|
|
|
482 |
this._drawSelection( this.dom.start, e );
|
|
|
483 |
|
|
|
484 |
this.dom.handle.css( 'display', 'none' );
|
|
|
485 |
|
|
|
486 |
// Cache scrolling information so mouse move doesn't need to read.
|
|
|
487 |
// This assumes that the window and DT scroller will not change size
|
|
|
488 |
// during an AutoFill drag, which I think is a fair assumption
|
|
|
489 |
var scrollWrapper = this.dom.dtScroll;
|
|
|
490 |
this.s.scroll = {
|
|
|
491 |
windowHeight: $(window).height(),
|
|
|
492 |
windowWidth: $(window).width(),
|
|
|
493 |
dtTop: scrollWrapper ? scrollWrapper.offset().top : null,
|
|
|
494 |
dtLeft: scrollWrapper ? scrollWrapper.offset().left : null,
|
|
|
495 |
dtHeight: scrollWrapper ? scrollWrapper.outerHeight() : null,
|
|
|
496 |
dtWidth: scrollWrapper ? scrollWrapper.outerWidth() : null
|
|
|
497 |
};
|
|
|
498 |
},
|
|
|
499 |
|
|
|
500 |
|
|
|
501 |
/**
|
|
|
502 |
* Mouse drag - selects the end cell and update the selection display for
|
|
|
503 |
* the end user
|
|
|
504 |
*
|
|
|
505 |
* @param {object} e Mouse move event
|
|
|
506 |
* @private
|
|
|
507 |
*/
|
|
|
508 |
_mousemove: function ( e )
|
|
|
509 |
{
|
|
|
510 |
var that = this;
|
|
|
511 |
var dt = this.s.dt;
|
|
|
512 |
var name = e.target.nodeName.toLowerCase();
|
|
|
513 |
if ( name !== 'td' && name !== 'th' ) {
|
|
|
514 |
return;
|
|
|
515 |
}
|
|
|
516 |
|
|
|
517 |
this._drawSelection( e.target, e );
|
|
|
518 |
this._shiftScroll( e );
|
|
|
519 |
},
|
|
|
520 |
|
|
|
521 |
|
|
|
522 |
/**
|
|
|
523 |
* End mouse drag - perform the update actions
|
|
|
524 |
*
|
|
|
525 |
* @param {object} e Mouse up event
|
|
|
526 |
* @private
|
|
|
527 |
*/
|
|
|
528 |
_mouseup: function ( e )
|
|
|
529 |
{
|
|
|
530 |
$(document.body).off( '.autoFill' );
|
|
|
531 |
|
|
|
532 |
var dt = this.s.dt;
|
|
|
533 |
var select = this.dom.select;
|
|
|
534 |
select.top.remove();
|
|
|
535 |
select.left.remove();
|
|
|
536 |
select.right.remove();
|
|
|
537 |
select.bottom.remove();
|
|
|
538 |
|
|
|
539 |
this.dom.handle.css( 'display', 'block' );
|
|
|
540 |
|
|
|
541 |
// Display complete - now do something useful with the selection!
|
|
|
542 |
var start = this.s.start;
|
|
|
543 |
var end = this.s.end;
|
|
|
544 |
|
|
|
545 |
// Haven't selected multiple cells, so nothing to do
|
|
|
546 |
if ( start.row === end.row && start.column === end.column ) {
|
|
|
547 |
return;
|
|
|
548 |
}
|
|
|
549 |
|
|
|
550 |
// Build a matrix representation of the selected rows
|
|
|
551 |
var rows = this._range( start.row, end.row );
|
|
|
552 |
var columns = this._range( start.column, end.column );
|
|
|
553 |
var selected = [];
|
|
|
554 |
|
|
|
555 |
// Can't use Array.prototype.map as IE8 doesn't support it
|
|
|
556 |
// Can't use $.map as jQuery flattens 2D arrays
|
|
|
557 |
// Need to use a good old fashioned for loop
|
|
|
558 |
for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) {
|
|
|
559 |
selected.push(
|
|
|
560 |
$.map( columns, function (column) {
|
|
|
561 |
var cell = dt.cell( ':eq('+rows[rowIdx]+')', column+':visible', {page:'current'} );
|
|
|
562 |
|
|
|
563 |
return {
|
|
|
564 |
cell: cell,
|
|
|
565 |
data: cell.data(),
|
|
|
566 |
index: cell.index()
|
|
|
567 |
};
|
|
|
568 |
} )
|
|
|
569 |
);
|
|
|
570 |
}
|
|
|
571 |
|
|
|
572 |
this._actionSelector( selected );
|
|
|
573 |
},
|
|
|
574 |
|
|
|
575 |
|
|
|
576 |
/**
|
|
|
577 |
* Create an array with a range of numbers defined by the start and end
|
|
|
578 |
* parameters passed in (inclusive!).
|
|
|
579 |
*
|
|
|
580 |
* @param {integer} start Start
|
|
|
581 |
* @param {integer} end End
|
|
|
582 |
* @private
|
|
|
583 |
*/
|
|
|
584 |
_range: function ( start, end )
|
|
|
585 |
{
|
|
|
586 |
var out = [];
|
|
|
587 |
var i;
|
|
|
588 |
|
|
|
589 |
if ( start <= end ) {
|
|
|
590 |
for ( i=start ; i<=end ; i++ ) {
|
|
|
591 |
out.push( i );
|
|
|
592 |
}
|
|
|
593 |
}
|
|
|
594 |
else {
|
|
|
595 |
for ( i=start ; i>=end ; i-- ) {
|
|
|
596 |
out.push( i );
|
|
|
597 |
}
|
|
|
598 |
}
|
|
|
599 |
|
|
|
600 |
return out;
|
|
|
601 |
},
|
|
|
602 |
|
|
|
603 |
|
|
|
604 |
/**
|
|
|
605 |
* Move the window and DataTables scrolling during a drag to scroll new
|
|
|
606 |
* content into view. This is done by proximity to the edge of the scrolling
|
|
|
607 |
* container of the mouse - for example near the top edge of the window
|
|
|
608 |
* should scroll up. This is a little complicated as there are two elements
|
|
|
609 |
* that can be scrolled - the window and the DataTables scrolling view port
|
|
|
610 |
* (if scrollX and / or scrollY is enabled).
|
|
|
611 |
*
|
|
|
612 |
* @param {object} e Mouse move event object
|
|
|
613 |
* @private
|
|
|
614 |
*/
|
|
|
615 |
_shiftScroll: function ( e )
|
|
|
616 |
{
|
|
|
617 |
var that = this;
|
|
|
618 |
var dt = this.s.dt;
|
|
|
619 |
var scroll = this.s.scroll;
|
|
|
620 |
var runInterval = false;
|
|
|
621 |
var scrollSpeed = 5;
|
|
|
622 |
var buffer = 65;
|
|
|
623 |
var
|
|
|
624 |
windowY = e.pageY - document.body.scrollTop,
|
|
|
625 |
windowX = e.pageX - document.body.scrollLeft,
|
|
|
626 |
windowVert, windowHoriz,
|
|
|
627 |
dtVert, dtHoriz;
|
|
|
628 |
|
|
|
629 |
// Window calculations - based on the mouse position in the window,
|
|
|
630 |
// regardless of scrolling
|
|
|
631 |
if ( windowY < buffer ) {
|
|
|
632 |
windowVert = scrollSpeed * -1;
|
|
|
633 |
}
|
|
|
634 |
else if ( windowY > scroll.windowHeight - buffer ) {
|
|
|
635 |
windowVert = scrollSpeed;
|
|
|
636 |
}
|
|
|
637 |
|
|
|
638 |
if ( windowX < buffer ) {
|
|
|
639 |
windowHoriz = scrollSpeed * -1;
|
|
|
640 |
}
|
|
|
641 |
else if ( windowX > scroll.windowWidth - buffer ) {
|
|
|
642 |
windowHoriz = scrollSpeed;
|
|
|
643 |
}
|
|
|
644 |
|
|
|
645 |
// DataTables scrolling calculations - based on the table's position in
|
|
|
646 |
// the document and the mouse position on the page
|
|
|
647 |
if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) {
|
|
|
648 |
dtVert = scrollSpeed * -1;
|
|
|
649 |
}
|
|
|
650 |
else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
|
|
|
651 |
dtVert = scrollSpeed;
|
|
|
652 |
}
|
|
|
653 |
|
|
|
654 |
if ( scroll.dtLeft !== null && e.pageX < scroll.dtLeft + buffer ) {
|
|
|
655 |
dtHoriz = scrollSpeed * -1;
|
|
|
656 |
}
|
|
|
657 |
else if ( scroll.dtLeft !== null && e.pageX > scroll.dtLeft + scroll.dtWidth - buffer ) {
|
|
|
658 |
dtHoriz = scrollSpeed;
|
|
|
659 |
}
|
|
|
660 |
|
|
|
661 |
// This is where it gets interesting. We want to continue scrolling
|
|
|
662 |
// without requiring a mouse move, so we need an interval to be
|
|
|
663 |
// triggered. The interval should continue until it is no longer needed,
|
|
|
664 |
// but it must also use the latest scroll commands (for example consider
|
|
|
665 |
// that the mouse might move from scrolling up to scrolling left, all
|
|
|
666 |
// with the same interval running. We use the `scroll` object to "pass"
|
|
|
667 |
// this information to the interval. Can't use local variables as they
|
|
|
668 |
// wouldn't be the ones that are used by an already existing interval!
|
|
|
669 |
if ( windowVert || windowHoriz || dtVert || dtHoriz ) {
|
|
|
670 |
scroll.windowVert = windowVert;
|
|
|
671 |
scroll.windowHoriz = windowHoriz;
|
|
|
672 |
scroll.dtVert = dtVert;
|
|
|
673 |
scroll.dtHoriz = dtHoriz;
|
|
|
674 |
runInterval = true;
|
|
|
675 |
}
|
|
|
676 |
else if ( this.s.scrollInterval ) {
|
|
|
677 |
// Don't need to scroll - remove any existing timer
|
|
|
678 |
clearInterval( this.s.scrollInterval );
|
|
|
679 |
this.s.scrollInterval = null;
|
|
|
680 |
}
|
|
|
681 |
|
|
|
682 |
// If we need to run the interval to scroll and there is no existing
|
|
|
683 |
// interval (if there is an existing one, it will continue to run)
|
|
|
684 |
if ( ! this.s.scrollInterval && runInterval ) {
|
|
|
685 |
this.s.scrollInterval = setInterval( function () {
|
|
|
686 |
// Don't need to worry about setting scroll <0 or beyond the
|
|
|
687 |
// scroll bound as the browser will just reject that.
|
|
|
688 |
if ( scroll.windowVert ) {
|
|
|
689 |
document.body.scrollTop += scroll.windowVert;
|
|
|
690 |
}
|
|
|
691 |
if ( scroll.windowHoriz ) {
|
|
|
692 |
document.body.scrollLeft += scroll.windowHoriz;
|
|
|
693 |
}
|
|
|
694 |
|
|
|
695 |
// DataTables scrolling
|
|
|
696 |
if ( scroll.dtVert || scroll.dtHoriz ) {
|
|
|
697 |
var scroller = that.dom.dtScroll[0];
|
|
|
698 |
|
|
|
699 |
if ( scroll.dtVert ) {
|
|
|
700 |
scroller.scrollTop += scroll.dtVert;
|
|
|
701 |
}
|
|
|
702 |
if ( scroll.dtHoriz ) {
|
|
|
703 |
scroller.scrollLeft += scroll.dtHoriz;
|
|
|
704 |
}
|
|
|
705 |
}
|
|
|
706 |
}, 20 );
|
|
|
707 |
}
|
|
|
708 |
},
|
|
|
709 |
|
|
|
710 |
|
|
|
711 |
/**
|
|
|
712 |
* Update the DataTable after the user has selected what they want to do
|
|
|
713 |
*
|
|
|
714 |
* @param {false|undefined} result Return from the `execute` method - can
|
|
|
715 |
* be false internally to do nothing. This is not documented for plug-ins
|
|
|
716 |
* and is used only by the cancel option.
|
|
|
717 |
* @param {array} cells Information about the selected cells from the key
|
|
|
718 |
* up function, argumented with the set values
|
|
|
719 |
* @private
|
|
|
720 |
*/
|
|
|
721 |
_update: function ( result, cells )
|
|
|
722 |
{
|
|
|
723 |
// Do nothing on `false` return from an execute function
|
|
|
724 |
if ( result === false ) {
|
|
|
725 |
return;
|
|
|
726 |
}
|
|
|
727 |
|
|
|
728 |
var dt = this.s.dt;
|
|
|
729 |
var cell;
|
|
|
730 |
|
|
|
731 |
// Potentially allow modifications to the cells matrix
|
|
|
732 |
this._emitEvent( 'preAutoFill', [ dt, cells ] );
|
|
|
733 |
|
|
|
734 |
this._editor( cells );
|
|
|
735 |
|
|
|
736 |
// Automatic updates are not performed if `update` is null and the
|
|
|
737 |
// `editor` parameter is passed in - the reason being that Editor will
|
|
|
738 |
// update the data once submitted
|
|
|
739 |
var update = this.c.update !== null ?
|
|
|
740 |
this.c.update :
|
|
|
741 |
this.c.editor ?
|
|
|
742 |
false :
|
|
|
743 |
true;
|
|
|
744 |
|
|
|
745 |
if ( update ) {
|
|
|
746 |
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
|
|
747 |
for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
|
|
|
748 |
cell = cells[i][j];
|
|
|
749 |
|
|
|
750 |
cell.cell.data( cell.set );
|
|
|
751 |
}
|
|
|
752 |
}
|
|
|
753 |
|
|
|
754 |
dt.draw(false);
|
|
|
755 |
}
|
|
|
756 |
|
|
|
757 |
this._emitEvent( 'autoFill', [ dt, cells ] );
|
|
|
758 |
}
|
|
|
759 |
};
|
|
|
760 |
|
|
|
761 |
|
|
|
762 |
/**
|
|
|
763 |
* AutoFill actions. The options here determine how AutoFill will fill the data
|
|
|
764 |
* in the table when the user has selected a range of cells. Please see the
|
|
|
765 |
* documentation on the DataTables site for full details on how to create plug-
|
|
|
766 |
* ins.
|
|
|
767 |
*
|
|
|
768 |
* @type {Object}
|
|
|
769 |
*/
|
|
|
770 |
AutoFill.actions = {
|
|
|
771 |
increment: {
|
|
|
772 |
available: function ( dt, cells ) {
|
|
|
773 |
return $.isNumeric( cells[0][0].data );
|
|
|
774 |
},
|
|
|
775 |
|
|
|
776 |
option: function ( dt, cells ) {
|
|
|
777 |
return dt.i18n(
|
|
|
778 |
'autoFill.increment',
|
|
|
779 |
'Increment / decrement each cell by: <input type="number" value="1">'
|
|
|
780 |
);
|
|
|
781 |
},
|
|
|
782 |
|
|
|
783 |
execute: function ( dt, cells, node ) {
|
|
|
784 |
var value = cells[0][0].data * 1;
|
|
|
785 |
var increment = $('input', node).val() * 1;
|
|
|
786 |
|
|
|
787 |
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
|
|
788 |
for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
|
|
|
789 |
cells[i][j].set = value;
|
|
|
790 |
|
|
|
791 |
value += increment;
|
|
|
792 |
}
|
|
|
793 |
}
|
|
|
794 |
}
|
|
|
795 |
},
|
|
|
796 |
|
|
|
797 |
fill: {
|
|
|
798 |
available: function ( dt, cells ) {
|
|
|
799 |
return true;
|
|
|
800 |
},
|
|
|
801 |
|
|
|
802 |
option: function ( dt, cells ) {
|
|
|
803 |
return dt.i18n('autoFill.fill', 'Fill all cells with <i>'+cells[0][0].data+'</i>' );
|
|
|
804 |
},
|
|
|
805 |
|
|
|
806 |
execute: function ( dt, cells, node ) {
|
|
|
807 |
var value = cells[0][0].data;
|
|
|
808 |
|
|
|
809 |
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
|
|
810 |
for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
|
|
|
811 |
cells[i][j].set = value;
|
|
|
812 |
}
|
|
|
813 |
}
|
|
|
814 |
}
|
|
|
815 |
},
|
|
|
816 |
|
|
|
817 |
fillHorizontal: {
|
|
|
818 |
available: function ( dt, cells ) {
|
|
|
819 |
return cells.length > 1 && cells[0].length > 1;
|
|
|
820 |
},
|
|
|
821 |
|
|
|
822 |
option: function ( dt, cells ) {
|
|
|
823 |
return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' );
|
|
|
824 |
},
|
|
|
825 |
|
|
|
826 |
execute: function ( dt, cells, node ) {
|
|
|
827 |
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
|
|
828 |
for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
|
|
|
829 |
cells[i][j].set = cells[i][0].data;
|
|
|
830 |
}
|
|
|
831 |
}
|
|
|
832 |
}
|
|
|
833 |
},
|
|
|
834 |
|
|
|
835 |
fillVertical: {
|
|
|
836 |
available: function ( dt, cells ) {
|
|
|
837 |
return cells.length > 1 && cells[0].length > 1;
|
|
|
838 |
},
|
|
|
839 |
|
|
|
840 |
option: function ( dt, cells ) {
|
|
|
841 |
return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' );
|
|
|
842 |
},
|
|
|
843 |
|
|
|
844 |
execute: function ( dt, cells, node ) {
|
|
|
845 |
for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
|
|
|
846 |
for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
|
|
|
847 |
cells[i][j].set = cells[0][j].data;
|
|
|
848 |
}
|
|
|
849 |
}
|
|
|
850 |
}
|
|
|
851 |
},
|
|
|
852 |
|
|
|
853 |
// Special type that does not make itself available, but is added
|
|
|
854 |
// automatically by AutoFill if a multi-choice list is shown. This allows
|
|
|
855 |
// sensible code reuse
|
|
|
856 |
cancel: {
|
|
|
857 |
available: function () {
|
|
|
858 |
return false;
|
|
|
859 |
},
|
|
|
860 |
|
|
|
861 |
option: function ( dt ) {
|
|
|
862 |
return dt.i18n('autoFill.cancel', 'Cancel' );
|
|
|
863 |
},
|
|
|
864 |
|
|
|
865 |
execute: function () {
|
|
|
866 |
return false;
|
|
|
867 |
}
|
|
|
868 |
}
|
|
|
869 |
};
|
|
|
870 |
|
|
|
871 |
|
|
|
872 |
/**
|
|
|
873 |
* AutoFill version
|
|
|
874 |
*
|
|
|
875 |
* @static
|
|
|
876 |
* @type String
|
|
|
877 |
*/
|
|
|
878 |
AutoFill.version = '2.0.0';
|
|
|
879 |
|
|
|
880 |
|
|
|
881 |
/**
|
|
|
882 |
* AutoFill defaults
|
|
|
883 |
*
|
|
|
884 |
* @namespace
|
|
|
885 |
*/
|
|
|
886 |
AutoFill.defaults = {
|
|
|
887 |
/** @type {Boolean} Ask user what they want to do, even for a single option */
|
|
|
888 |
alwaysAsk: false,
|
|
|
889 |
|
|
|
890 |
/** @type {string|null} What will trigger a focus */
|
|
|
891 |
focus: null, // focus, click, hover
|
|
|
892 |
|
|
|
893 |
/** @type {column-selector} Columns to provide auto fill for */
|
|
|
894 |
columns: '', // all
|
|
|
895 |
|
|
|
896 |
/** @type {boolean|null} Update the cells after a drag */
|
|
|
897 |
update: null, // false is editor given, true otherwise
|
|
|
898 |
|
|
|
899 |
/** @type {DataTable.Editor} Editor instance for automatic submission */
|
|
|
900 |
editor: null
|
|
|
901 |
};
|
|
|
902 |
|
|
|
903 |
|
|
|
904 |
/**
|
|
|
905 |
* Classes used by AutoFill that are configurable
|
|
|
906 |
*
|
|
|
907 |
* @namespace
|
|
|
908 |
*/
|
|
|
909 |
AutoFill.classes = {
|
|
|
910 |
/** @type {String} Class used by the selection button */
|
|
|
911 |
btn: 'btn'
|
|
|
912 |
};
|
|
|
913 |
|
|
|
914 |
|
|
|
915 |
// Attach a listener to the document which listens for DataTables initialisation
|
|
|
916 |
// events so we can automatically initialise
|
|
|
917 |
$(document).on( 'init.dt.autofill', function (e, settings, json) {
|
|
|
918 |
if ( e.namespace !== 'dt' ) {
|
|
|
919 |
return;
|
|
|
920 |
}
|
|
|
921 |
|
|
|
922 |
var init = settings.oInit.autoFill;
|
|
|
923 |
var defaults = DataTable.defaults.autoFill;
|
|
|
924 |
|
|
|
925 |
if ( init || defaults ) {
|
|
|
926 |
var opts = $.extend( {}, init, defaults );
|
|
|
927 |
|
|
|
928 |
if ( init !== false ) {
|
|
|
929 |
new AutoFill( settings, opts );
|
|
|
930 |
}
|
|
|
931 |
}
|
|
|
932 |
} );
|
|
|
933 |
|
|
|
934 |
|
|
|
935 |
// Alias for access
|
|
|
936 |
DataTable.AutoFill = AutoFill;
|
|
|
937 |
DataTable.AutoFill = AutoFill;
|
|
|
938 |
|
|
|
939 |
|
|
|
940 |
return AutoFill;
|
|
|
941 |
};
|
|
|
942 |
|
|
|
943 |
|
|
|
944 |
// Define as an AMD module if possible
|
|
|
945 |
if ( typeof define === 'function' && define.amd ) {
|
|
|
946 |
define( ['jquery', 'datatables'], factory );
|
|
|
947 |
}
|
|
|
948 |
else if ( typeof exports === 'object' ) {
|
|
|
949 |
// Node/CommonJS
|
|
|
950 |
factory( require('jquery'), require('datatables') );
|
|
|
951 |
}
|
|
|
952 |
else if ( jQuery && !jQuery.fn.dataTable.AutoFill ) {
|
|
|
953 |
// Otherwise simply initialise as normal, stopping multiple evaluation
|
|
|
954 |
factory( jQuery, jQuery.fn.dataTable );
|
|
|
955 |
}
|
|
|
956 |
|
|
|
957 |
|
|
|
958 |
}(window, document));
|
|
|
959 |
|