|
1 /*! AutoFill 1.2.0 |
|
2 * ©2008-2014 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 1.2.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-2014 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 /** |
|
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 (oDT, oConfig) { |
|
38 /* Sanity check that we are a new instance */ |
|
39 if (!(this instanceof AutoFill)) { |
|
40 throw( "Warning: AutoFill must be initialised with the keyword 'new'" ); |
|
41 } |
|
42 |
|
43 if (!$.fn.dataTableExt.fnVersionCheck('1.7.0')) { |
|
44 throw( "Warning: AutoFill requires DataTables 1.7 or greater"); |
|
45 } |
|
46 |
|
47 |
|
48 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
49 * Public class variables |
|
50 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
51 |
|
52 this.c = {}; |
|
53 |
|
54 /** |
|
55 * @namespace Settings object which contains customisable information for AutoFill instance |
|
56 */ |
|
57 this.s = { |
|
58 /** |
|
59 * @namespace Cached information about the little dragging icon (the filler) |
|
60 */ |
|
61 "filler": { |
|
62 "height": 0, |
|
63 "width": 0 |
|
64 }, |
|
65 |
|
66 /** |
|
67 * @namespace Cached information about the border display |
|
68 */ |
|
69 "border": { |
|
70 "width": 2 |
|
71 }, |
|
72 |
|
73 /** |
|
74 * @namespace Store for live information for the current drag |
|
75 */ |
|
76 "drag": { |
|
77 "startX": -1, |
|
78 "startY": -1, |
|
79 "startTd": null, |
|
80 "endTd": null, |
|
81 "dragging": false |
|
82 }, |
|
83 |
|
84 /** |
|
85 * @namespace Data cache for information that we need for scrolling the screen when we near |
|
86 * the edges |
|
87 */ |
|
88 "screen": { |
|
89 "interval": null, |
|
90 "y": 0, |
|
91 "height": 0, |
|
92 "scrollTop": 0 |
|
93 }, |
|
94 |
|
95 /** |
|
96 * @namespace Data cache for the position of the DataTables scrolling element (when scrolling |
|
97 * is enabled) |
|
98 */ |
|
99 "scroller": { |
|
100 "top": 0, |
|
101 "bottom": 0 |
|
102 }, |
|
103 |
|
104 /** |
|
105 * @namespace Information stored for each column. An array of objects |
|
106 */ |
|
107 "columns": [] |
|
108 }; |
|
109 |
|
110 |
|
111 /** |
|
112 * @namespace Common and useful DOM elements for the class instance |
|
113 */ |
|
114 this.dom = { |
|
115 "table": null, |
|
116 "filler": null, |
|
117 "borderTop": null, |
|
118 "borderRight": null, |
|
119 "borderBottom": null, |
|
120 "borderLeft": null, |
|
121 "currentTarget": null |
|
122 }; |
|
123 |
|
124 |
|
125 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
126 * Public class methods |
|
127 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
128 |
|
129 /** |
|
130 * Retreieve the settings object from an instance |
|
131 * @method fnSettings |
|
132 * @returns {object} AutoFill settings object |
|
133 */ |
|
134 this.fnSettings = function () { |
|
135 return this.s; |
|
136 }; |
|
137 |
|
138 |
|
139 /* Constructor logic */ |
|
140 this._fnInit(oDT, oConfig); |
|
141 return this; |
|
142 }; |
|
143 |
|
144 |
|
145 AutoFill.prototype = { |
|
146 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
147 * Private methods (they are of course public in JS, but recommended as private) |
|
148 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
149 |
|
150 /** |
|
151 * Initialisation |
|
152 * @method _fnInit |
|
153 * @param {object} dt DataTables settings object |
|
154 * @param {object} config Configuration object for AutoFill |
|
155 * @returns void |
|
156 */ |
|
157 "_fnInit": function (dt, config) { |
|
158 var |
|
159 that = this, |
|
160 i, iLen; |
|
161 |
|
162 // Use DataTables API to get the settings allowing selectors, instances |
|
163 // etc to be used, or for backwards compatibility get from the old |
|
164 // fnSettings method |
|
165 this.s.dt = DataTable.Api ? |
|
166 new DataTable.Api(dt).settings()[0] : |
|
167 dt.fnSettings(); |
|
168 this.s.init = config || {}; |
|
169 this.dom.table = this.s.dt.nTable; |
|
170 |
|
171 $.extend(true, this.c, AutoFill.defaults, config); |
|
172 |
|
173 /* Add and configure the columns */ |
|
174 this._initColumns(); |
|
175 |
|
176 /* Auto Fill click and drag icon */ |
|
177 var filler = $('<div/>', { |
|
178 'class': 'AutoFill_filler' |
|
179 }) |
|
180 .appendTo('body'); |
|
181 this.dom.filler = filler[0]; |
|
182 |
|
183 // Get the height / width of the click element |
|
184 this.s.filler.height = filler.height(); |
|
185 this.s.filler.width = filler.width(); |
|
186 filler[0].style.display = "none"; |
|
187 |
|
188 /* Border display - one div for each side. We can't just use a single |
|
189 * one with a border, as we want the events to effectively pass through |
|
190 * the transparent bit of the box |
|
191 */ |
|
192 var border; |
|
193 var appender = document.body; |
|
194 if (that.s.dt.oScroll.sY !== "") { |
|
195 that.s.dt.nTable.parentNode.style.position = "relative"; |
|
196 appender = that.s.dt.nTable.parentNode; |
|
197 } |
|
198 |
|
199 border = $('<div/>', { |
|
200 "class": "AutoFill_border" |
|
201 }); |
|
202 this.dom.borderTop = border.clone().appendTo(appender)[0]; |
|
203 this.dom.borderRight = border.clone().appendTo(appender)[0]; |
|
204 this.dom.borderBottom = border.clone().appendTo(appender)[0]; |
|
205 this.dom.borderLeft = border.clone().appendTo(appender)[0]; |
|
206 |
|
207 /* Events */ |
|
208 filler.on('mousedown.DTAF', function (e) { |
|
209 this.onselectstart = function () { |
|
210 return false; |
|
211 }; |
|
212 that._fnFillerDragStart.call(that, e); |
|
213 return false; |
|
214 }); |
|
215 |
|
216 $('tbody', this.dom.table).on( |
|
217 'mouseover.DTAF mouseout.DTAF', |
|
218 '>tr>td, >tr>th', |
|
219 function (e) { |
|
220 that._fnFillerDisplay.call(that, e); |
|
221 } |
|
222 ); |
|
223 |
|
224 $(this.dom.table).on('destroy.dt.DTAF', function () { |
|
225 filler.off('mousedown.DTAF').remove(); |
|
226 $('tbody', this.dom.table).off('mouseover.DTAF mouseout.DTAF'); |
|
227 }); |
|
228 }, |
|
229 |
|
230 |
|
231 _initColumns: function () { |
|
232 var that = this; |
|
233 var i, ien; |
|
234 var dt = this.s.dt; |
|
235 var config = this.s.init; |
|
236 |
|
237 for (i = 0, ien = dt.aoColumns.length; i < ien; i++) { |
|
238 this.s.columns[i] = $.extend(true, {}, AutoFill.defaults.column); |
|
239 } |
|
240 |
|
241 dt.oApi._fnApplyColumnDefs( |
|
242 dt, |
|
243 config.aoColumnDefs || config.columnDefs, |
|
244 config.aoColumns || config.columns, |
|
245 function (colIdx, def) { |
|
246 that._fnColumnOptions(colIdx, def); |
|
247 } |
|
248 ); |
|
249 |
|
250 // For columns which don't have read, write, step functions defined, |
|
251 // use the default ones |
|
252 for (i = 0, ien = dt.aoColumns.length; i < ien; i++) { |
|
253 var column = this.s.columns[i]; |
|
254 |
|
255 if (!column.read) { |
|
256 column.read = this._fnReadCell; |
|
257 } |
|
258 if (!column.write) { |
|
259 column.read = this._fnWriteCell; |
|
260 } |
|
261 if (!column.step) { |
|
262 column.read = this._fnStep; |
|
263 } |
|
264 } |
|
265 }, |
|
266 |
|
267 |
|
268 "_fnColumnOptions": function (i, opts) { |
|
269 var column = this.s.columns[ i ]; |
|
270 var set = function (outProp, inProp) { |
|
271 if (opts[ inProp[0] ] !== undefined) { |
|
272 column[ outProp ] = opts[ inProp[0] ]; |
|
273 } |
|
274 if (opts[ inProp[1] ] !== undefined) { |
|
275 column[ outProp ] = opts[ inProp[1] ]; |
|
276 } |
|
277 }; |
|
278 |
|
279 // Compatibility with the old Hungarian style of notation |
|
280 set('enable', ['bEnable', 'enable']); |
|
281 set('read', ['fnRead', 'read']); |
|
282 set('write', ['fnWrite', 'write']); |
|
283 set('step', ['fnStep', 'step']); |
|
284 set('increment', ['bIncrement', 'increment']); |
|
285 }, |
|
286 |
|
287 |
|
288 /** |
|
289 * Find out the coordinates of a given TD cell in a table |
|
290 * @method _fnTargetCoords |
|
291 * @param {Node} nTd |
|
292 * @returns {Object} x and y properties, for the position of the cell in the tables DOM |
|
293 */ |
|
294 "_fnTargetCoords": function (nTd) { |
|
295 var nTr = $(nTd).parents('tr')[0]; |
|
296 var position = this.s.dt.oInstance.fnGetPosition(nTd); |
|
297 |
|
298 return { |
|
299 "x": $('td', nTr).index(nTd), |
|
300 "y": $('tr', nTr.parentNode).index(nTr), |
|
301 "row": position[0], |
|
302 "column": position[2] |
|
303 }; |
|
304 }, |
|
305 |
|
306 |
|
307 /** |
|
308 * Display the border around one or more cells (from start to end) |
|
309 * @method _fnUpdateBorder |
|
310 * @param {Node} nStart Starting cell |
|
311 * @param {Node} nEnd Ending cell |
|
312 * @returns void |
|
313 */ |
|
314 "_fnUpdateBorder": function (nStart, nEnd) { |
|
315 var |
|
316 border = this.s.border.width, |
|
317 offsetStart = $(nStart).offset(), |
|
318 offsetEnd = $(nEnd).offset(), |
|
319 x1 = offsetStart.left - border, |
|
320 x2 = offsetEnd.left + $(nEnd).outerWidth(), |
|
321 y1 = offsetStart.top - border, |
|
322 y2 = offsetEnd.top + $(nEnd).outerHeight(), |
|
323 width = offsetEnd.left + $(nEnd).outerWidth() - offsetStart.left + (2 * border), |
|
324 height = offsetEnd.top + $(nEnd).outerHeight() - offsetStart.top + (2 * border), |
|
325 oStyle; |
|
326 |
|
327 // Recalculate start and end (when dragging "backwards") |
|
328 if (offsetStart.left > offsetEnd.left) { |
|
329 x1 = offsetEnd.left - border; |
|
330 x2 = offsetStart.left + $(nStart).outerWidth(); |
|
331 width = offsetStart.left + $(nStart).outerWidth() - offsetEnd.left + (2 * border); |
|
332 } |
|
333 |
|
334 if (this.s.dt.oScroll.sY !== "") { |
|
335 /* The border elements are inside the DT scroller - so position relative to that */ |
|
336 var |
|
337 offsetScroll = $(this.s.dt.nTable.parentNode).offset(), |
|
338 scrollTop = $(this.s.dt.nTable.parentNode).scrollTop(), |
|
339 scrollLeft = $(this.s.dt.nTable.parentNode).scrollLeft(); |
|
340 |
|
341 x1 -= offsetScroll.left - scrollLeft; |
|
342 x2 -= offsetScroll.left - scrollLeft; |
|
343 y1 -= offsetScroll.top - scrollTop; |
|
344 y2 -= offsetScroll.top - scrollTop; |
|
345 } |
|
346 |
|
347 /* Top */ |
|
348 oStyle = this.dom.borderTop.style; |
|
349 oStyle.top = y1 + "px"; |
|
350 oStyle.left = x1 + "px"; |
|
351 oStyle.height = this.s.border.width + "px"; |
|
352 oStyle.width = width + "px"; |
|
353 |
|
354 /* Bottom */ |
|
355 oStyle = this.dom.borderBottom.style; |
|
356 oStyle.top = y2 + "px"; |
|
357 oStyle.left = x1 + "px"; |
|
358 oStyle.height = this.s.border.width + "px"; |
|
359 oStyle.width = width + "px"; |
|
360 |
|
361 /* Left */ |
|
362 oStyle = this.dom.borderLeft.style; |
|
363 oStyle.top = y1 + "px"; |
|
364 oStyle.left = x1 + "px"; |
|
365 oStyle.height = height + "px"; |
|
366 oStyle.width = this.s.border.width + "px"; |
|
367 |
|
368 /* Right */ |
|
369 oStyle = this.dom.borderRight.style; |
|
370 oStyle.top = y1 + "px"; |
|
371 oStyle.left = x2 + "px"; |
|
372 oStyle.height = height + "px"; |
|
373 oStyle.width = this.s.border.width + "px"; |
|
374 }, |
|
375 |
|
376 |
|
377 /** |
|
378 * Mouse down event handler for starting a drag |
|
379 * @method _fnFillerDragStart |
|
380 * @param {Object} e Event object |
|
381 * @returns void |
|
382 */ |
|
383 "_fnFillerDragStart": function (e) { |
|
384 var that = this; |
|
385 var startingTd = this.dom.currentTarget; |
|
386 |
|
387 this.s.drag.dragging = true; |
|
388 |
|
389 that.dom.borderTop.style.display = "block"; |
|
390 that.dom.borderRight.style.display = "block"; |
|
391 that.dom.borderBottom.style.display = "block"; |
|
392 that.dom.borderLeft.style.display = "block"; |
|
393 |
|
394 var coords = this._fnTargetCoords(startingTd); |
|
395 this.s.drag.startX = coords.x; |
|
396 this.s.drag.startY = coords.y; |
|
397 |
|
398 this.s.drag.startTd = startingTd; |
|
399 this.s.drag.endTd = startingTd; |
|
400 |
|
401 this._fnUpdateBorder(startingTd, startingTd); |
|
402 |
|
403 $(document).bind('mousemove.AutoFill', function (e) { |
|
404 that._fnFillerDragMove.call(that, e); |
|
405 }); |
|
406 |
|
407 $(document).bind('mouseup.AutoFill', function (e) { |
|
408 that._fnFillerFinish.call(that, e); |
|
409 }); |
|
410 |
|
411 /* Scrolling information cache */ |
|
412 this.s.screen.y = e.pageY; |
|
413 this.s.screen.height = $(window).height(); |
|
414 this.s.screen.scrollTop = $(document).scrollTop(); |
|
415 |
|
416 if (this.s.dt.oScroll.sY !== "") { |
|
417 this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top; |
|
418 this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height(); |
|
419 } |
|
420 |
|
421 /* Scrolling handler - we set an interval (which is cancelled on mouse up) which will fire |
|
422 * regularly and see if we need to do any scrolling |
|
423 */ |
|
424 this.s.screen.interval = setInterval(function () { |
|
425 var iScrollTop = $(document).scrollTop(); |
|
426 var iScrollDelta = iScrollTop - that.s.screen.scrollTop; |
|
427 that.s.screen.y += iScrollDelta; |
|
428 |
|
429 if (that.s.screen.height - that.s.screen.y + iScrollTop < 50) { |
|
430 $('html, body').animate({ |
|
431 "scrollTop": iScrollTop + 50 |
|
432 }, 240, 'linear'); |
|
433 } |
|
434 else if (that.s.screen.y - iScrollTop < 50) { |
|
435 $('html, body').animate({ |
|
436 "scrollTop": iScrollTop - 50 |
|
437 }, 240, 'linear'); |
|
438 } |
|
439 |
|
440 if (that.s.dt.oScroll.sY !== "") { |
|
441 if (that.s.screen.y > that.s.scroller.bottom - 50) { |
|
442 $(that.s.dt.nTable.parentNode).animate({ |
|
443 "scrollTop": $(that.s.dt.nTable.parentNode).scrollTop() + 50 |
|
444 }, 240, 'linear'); |
|
445 } |
|
446 else if (that.s.screen.y < that.s.scroller.top + 50) { |
|
447 $(that.s.dt.nTable.parentNode).animate({ |
|
448 "scrollTop": $(that.s.dt.nTable.parentNode).scrollTop() - 50 |
|
449 }, 240, 'linear'); |
|
450 } |
|
451 } |
|
452 }, 250); |
|
453 }, |
|
454 |
|
455 |
|
456 /** |
|
457 * Mouse move event handler for during a move. See if we want to update the display based on the |
|
458 * new cursor position |
|
459 * @method _fnFillerDragMove |
|
460 * @param {Object} e Event object |
|
461 * @returns void |
|
462 */ |
|
463 "_fnFillerDragMove": function (e) { |
|
464 if (e.target && e.target.nodeName.toUpperCase() == "TD" && |
|
465 e.target != this.s.drag.endTd) { |
|
466 var coords = this._fnTargetCoords(e.target); |
|
467 |
|
468 if (this.c.mode == "y" && coords.x != this.s.drag.startX) { |
|
469 e.target = $('tbody>tr:eq(' + coords.y + ')>td:eq(' + this.s.drag.startX + ')', this.dom.table)[0]; |
|
470 } |
|
471 if (this.c.mode == "x" && coords.y != this.s.drag.startY) { |
|
472 e.target = $('tbody>tr:eq(' + this.s.drag.startY + ')>td:eq(' + coords.x + ')', this.dom.table)[0]; |
|
473 } |
|
474 |
|
475 if (this.c.mode == "either") { |
|
476 if (coords.x != this.s.drag.startX) { |
|
477 e.target = $('tbody>tr:eq(' + this.s.drag.startY + ')>td:eq(' + coords.x + ')', this.dom.table)[0]; |
|
478 } |
|
479 else if (coords.y != this.s.drag.startY) { |
|
480 e.target = $('tbody>tr:eq(' + coords.y + ')>td:eq(' + this.s.drag.startX + ')', this.dom.table)[0]; |
|
481 } |
|
482 } |
|
483 |
|
484 // update coords |
|
485 if (this.c.mode !== "both") { |
|
486 coords = this._fnTargetCoords(e.target); |
|
487 } |
|
488 |
|
489 var drag = this.s.drag; |
|
490 drag.endTd = e.target; |
|
491 |
|
492 if (coords.y >= this.s.drag.startY) { |
|
493 this._fnUpdateBorder(drag.startTd, drag.endTd); |
|
494 } |
|
495 else { |
|
496 this._fnUpdateBorder(drag.endTd, drag.startTd); |
|
497 } |
|
498 this._fnFillerPosition(e.target); |
|
499 } |
|
500 |
|
501 /* Update the screen information so we can perform scrolling */ |
|
502 this.s.screen.y = e.pageY; |
|
503 this.s.screen.scrollTop = $(document).scrollTop(); |
|
504 |
|
505 if (this.s.dt.oScroll.sY !== "") { |
|
506 this.s.scroller.scrollTop = $(this.s.dt.nTable.parentNode).scrollTop(); |
|
507 this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top; |
|
508 this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height(); |
|
509 } |
|
510 }, |
|
511 |
|
512 |
|
513 /** |
|
514 * Mouse release handler - end the drag and take action to update the cells with the needed values |
|
515 * @method _fnFillerFinish |
|
516 * @param {Object} e Event object |
|
517 * @returns void |
|
518 */ |
|
519 "_fnFillerFinish": function (e) { |
|
520 var that = this, i, iLen, j; |
|
521 |
|
522 $(document).unbind('mousemove.AutoFill mouseup.AutoFill'); |
|
523 |
|
524 this.dom.borderTop.style.display = "none"; |
|
525 this.dom.borderRight.style.display = "none"; |
|
526 this.dom.borderBottom.style.display = "none"; |
|
527 this.dom.borderLeft.style.display = "none"; |
|
528 |
|
529 this.s.drag.dragging = false; |
|
530 |
|
531 clearInterval(this.s.screen.interval); |
|
532 |
|
533 var cells = []; |
|
534 var table = this.dom.table; |
|
535 var coordsStart = this._fnTargetCoords(this.s.drag.startTd); |
|
536 var coordsEnd = this._fnTargetCoords(this.s.drag.endTd); |
|
537 var columnIndex = function (visIdx) { |
|
538 return that.s.dt.oApi._fnVisibleToColumnIndex(that.s.dt, visIdx); |
|
539 }; |
|
540 |
|
541 // xxx - urgh - there must be a way of reducing this... |
|
542 if (coordsStart.y <= coordsEnd.y) { |
|
543 for (i = coordsStart.y; i <= coordsEnd.y; i++) { |
|
544 if (coordsStart.x <= coordsEnd.x) { |
|
545 for (j = coordsStart.x; j <= coordsEnd.x; j++) { |
|
546 cells.push({ |
|
547 node: $('tbody>tr:eq(' + i + ')>td:eq(' + j + ')', table)[0], |
|
548 x: j - coordsStart.x, |
|
549 y: i - coordsStart.y, |
|
550 colIdx: columnIndex(j) |
|
551 }); |
|
552 } |
|
553 } |
|
554 else { |
|
555 for (j = coordsStart.x; j >= coordsEnd.x; j--) { |
|
556 cells.push({ |
|
557 node: $('tbody>tr:eq(' + i + ')>td:eq(' + j + ')', table)[0], |
|
558 x: j - coordsStart.x, |
|
559 y: i - coordsStart.y, |
|
560 colIdx: columnIndex(j) |
|
561 }); |
|
562 } |
|
563 } |
|
564 } |
|
565 } |
|
566 else { |
|
567 for (i = coordsStart.y; i >= coordsEnd.y; i--) { |
|
568 if (coordsStart.x <= coordsEnd.x) { |
|
569 for (j = coordsStart.x; j <= coordsEnd.x; j++) { |
|
570 cells.push({ |
|
571 node: $('tbody>tr:eq(' + i + ')>td:eq(' + j + ')', table)[0], |
|
572 x: j - coordsStart.x, |
|
573 y: i - coordsStart.y, |
|
574 colIdx: columnIndex(j) |
|
575 }); |
|
576 } |
|
577 } |
|
578 else { |
|
579 for (j = coordsStart.x; j >= coordsEnd.x; j--) { |
|
580 cells.push({ |
|
581 node: $('tbody>tr:eq(' + i + ')>td:eq(' + j + ')', table)[0], |
|
582 x: coordsStart.x - j, |
|
583 y: coordsStart.y - i, |
|
584 colIdx: columnIndex(j) |
|
585 }); |
|
586 } |
|
587 } |
|
588 } |
|
589 } |
|
590 |
|
591 // An auto-fill requires 2 or more cells |
|
592 if (cells.length <= 1) { |
|
593 return; |
|
594 } |
|
595 |
|
596 var edited = []; |
|
597 var previous; |
|
598 |
|
599 for (i = 0, iLen = cells.length; i < iLen; i++) { |
|
600 var cell = cells[i]; |
|
601 var column = this.s.columns[ cell.colIdx ]; |
|
602 var read = column.read.call(column, cell.node); |
|
603 var stepValue = column.step.call(column, cell.node, read, previous, i, cell.x, cell.y); |
|
604 |
|
605 column.write.call(column, cell.node, stepValue); |
|
606 |
|
607 previous = stepValue; |
|
608 edited.push({ |
|
609 cell: cell, |
|
610 colIdx: cell.colIdx, |
|
611 newValue: stepValue, |
|
612 oldValue: read |
|
613 }); |
|
614 } |
|
615 |
|
616 if (this.c.complete !== null) { |
|
617 this.c.complete.call(this, edited); |
|
618 } |
|
619 |
|
620 // In 1.10 we can do a static draw |
|
621 if (DataTable.Api) { |
|
622 new DataTable.Api(this.s.dt).draw(false); |
|
623 } |
|
624 else { |
|
625 this.s.dt.oInstance.fnDraw(); |
|
626 } |
|
627 }, |
|
628 |
|
629 |
|
630 /** |
|
631 * Display the drag handle on mouse over cell |
|
632 * @method _fnFillerDisplay |
|
633 * @param {Object} e Event object |
|
634 * @returns void |
|
635 */ |
|
636 "_fnFillerDisplay": function (e) { |
|
637 var filler = this.dom.filler; |
|
638 |
|
639 /* Don't display automatically when dragging */ |
|
640 if (this.s.drag.dragging) { |
|
641 return; |
|
642 } |
|
643 |
|
644 /* Check that we are allowed to AutoFill this column or not */ |
|
645 var nTd = (e.target.nodeName.toLowerCase() == 'td') ? e.target : $(e.target).parents('td')[0]; |
|
646 var iX = this._fnTargetCoords(nTd).column; |
|
647 if (!this.s.columns[iX].enable) { |
|
648 filler.style.display = "none"; |
|
649 return; |
|
650 } |
|
651 |
|
652 if (e.type == 'mouseover') { |
|
653 this.dom.currentTarget = nTd; |
|
654 this._fnFillerPosition(nTd); |
|
655 |
|
656 filler.style.display = "block"; |
|
657 } |
|
658 else if (!e.relatedTarget || !e.relatedTarget.className.match(/AutoFill/)) { |
|
659 filler.style.display = "none"; |
|
660 } |
|
661 }, |
|
662 |
|
663 |
|
664 /** |
|
665 * Position the filler icon over a cell |
|
666 * @method _fnFillerPosition |
|
667 * @param {Node} nTd Cell to position filler icon over |
|
668 * @returns void |
|
669 */ |
|
670 "_fnFillerPosition": function (nTd) { |
|
671 var offset = $(nTd).offset(); |
|
672 var filler = this.dom.filler; |
|
673 filler.style.top = (offset.top - (this.s.filler.height / 2) - 1 + $(nTd).outerHeight()) + "px"; |
|
674 filler.style.left = (offset.left - (this.s.filler.width / 2) - 1 + $(nTd).outerWidth()) + "px"; |
|
675 } |
|
676 }; |
|
677 |
|
678 |
|
679 // Alias for access |
|
680 DataTable.AutoFill = AutoFill; |
|
681 DataTable.AutoFill = AutoFill; |
|
682 |
|
683 |
|
684 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
685 * Constants |
|
686 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
|
687 |
|
688 /** |
|
689 * AutoFill version |
|
690 * @constant version |
|
691 * @type String |
|
692 * @default See code |
|
693 */ |
|
694 AutoFill.version = "1.2.0"; |
|
695 |
|
696 |
|
697 /** |
|
698 * AutoFill defaults |
|
699 * @namespace |
|
700 */ |
|
701 AutoFill.defaults = { |
|
702 /** |
|
703 * Mode for dragging (restrict to y-axis only, x-axis only, either one or none): |
|
704 * |
|
705 * * `y` - y-axis only (default) |
|
706 * * `x` - x-axis only |
|
707 * * `either` - either one, but not both axis at the same time |
|
708 * * `both` - multiple cells allowed |
|
709 * |
|
710 * @type {string} |
|
711 * @default `y` |
|
712 */ |
|
713 mode: 'y', |
|
714 |
|
715 complete: null, |
|
716 |
|
717 /** |
|
718 * Column definition defaults |
|
719 * @namespace |
|
720 */ |
|
721 column: { |
|
722 /** |
|
723 * If AutoFill should be enabled on this column |
|
724 * |
|
725 * @type {boolean} |
|
726 * @default true |
|
727 */ |
|
728 enable: true, |
|
729 |
|
730 /** |
|
731 * Allow automatic increment / decrement on this column if a number |
|
732 * is found. |
|
733 * |
|
734 * @type {boolean} |
|
735 * @default true |
|
736 */ |
|
737 increment: true, |
|
738 |
|
739 /** |
|
740 * Cell read function |
|
741 * |
|
742 * Default function will simply read the value from the HTML of the |
|
743 * cell. |
|
744 * |
|
745 * @type {function} |
|
746 * @param {node} cell `th` / `td` element to read the value from |
|
747 * @return {string} Data that has been read |
|
748 */ |
|
749 read: function (cell) { |
|
750 return $(cell).html(); |
|
751 }, |
|
752 |
|
753 /** |
|
754 * Cell write function |
|
755 * |
|
756 * Default function will simply write to the HTML and tell the DataTable |
|
757 * to update. |
|
758 * |
|
759 * @type {function} |
|
760 * @param {node} cell `th` / `td` element to write the value to |
|
761 * @return {string} Data two write |
|
762 */ |
|
763 write: function (cell, val) { |
|
764 var table = $(cell).parents('table'); |
|
765 if (DataTable.Api) { |
|
766 // 1.10 |
|
767 table.DataTable().cell(cell).data(val); |
|
768 } |
|
769 else { |
|
770 // 1.9 |
|
771 var dt = table.dataTable(); |
|
772 var pos = dt.fnGetPosition(); |
|
773 dt.fnUpdate(val, pos[0], pos[2], false); |
|
774 } |
|
775 }, |
|
776 |
|
777 /** |
|
778 * Step function. This provides the ability to customise how the values |
|
779 * are incremented. |
|
780 * |
|
781 * @param {node} cell `th` / `td` element that is being operated upon |
|
782 * @param {string} read Cell value from `read` function |
|
783 * @param {string} last Value of the previous cell |
|
784 * @param {integer} i Loop counter |
|
785 * @param {integer} x Cell x-position in the current auto-fill. The |
|
786 * starting cell is coordinate 0 regardless of its physical position |
|
787 * in the DataTable. |
|
788 * @param {integer} y Cell y-position in the current auto-fill. The |
|
789 * starting cell is coordinate 0 regardless of its physical position |
|
790 * in the DataTable. |
|
791 * @return {string} Value to write |
|
792 */ |
|
793 step: function (cell, read, last, i, x, y) { |
|
794 // Increment a number if it is found |
|
795 var re = /(\-?\d+)/; |
|
796 var match = this.increment && last ? last.match(re) : null; |
|
797 if (match) { |
|
798 return last.replace(re, parseInt(match[1], 10) + (x < 0 || y < 0 ? -1 : 1)); |
|
799 } |
|
800 return last === undefined ? |
|
801 read : |
|
802 last; |
|
803 } |
|
804 } |
|
805 }; |
|
806 |
|
807 return AutoFill; |
|
808 }; // factory |
|
809 |
|
810 |
|
811 factory(jQuery, jQuery.fn.dataTable); |
|
812 |
|
813 }(window, document)); |
|
814 |