jquery-datatables-rails 3.3.0 → 3.4.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.
Files changed (24) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/dataTables/bootstrap/3/jquery.dataTables.bootstrap.js +64 -29
  3. data/app/assets/javascripts/dataTables/extras/dataTables.autoFill.js +806 -648
  4. data/app/assets/javascripts/dataTables/extras/dataTables.buttons.js +1607 -0
  5. data/app/assets/javascripts/dataTables/extras/dataTables.colReorder.js +220 -267
  6. data/app/assets/javascripts/dataTables/extras/dataTables.fixedColumns.js +164 -69
  7. data/app/assets/javascripts/dataTables/extras/dataTables.fixedHeader.js +469 -870
  8. data/app/assets/javascripts/dataTables/extras/dataTables.keyTable.js +636 -972
  9. data/app/assets/javascripts/dataTables/extras/dataTables.responsive.js +472 -187
  10. data/app/assets/javascripts/dataTables/extras/dataTables.rowReorder.js +619 -0
  11. data/app/assets/javascripts/dataTables/extras/dataTables.scroller.js +146 -111
  12. data/app/assets/javascripts/dataTables/extras/dataTables.select.js +1038 -0
  13. data/app/assets/javascripts/dataTables/jquery.dataTables.api.fnGetColumnData.js +0 -0
  14. data/app/assets/javascripts/dataTables/jquery.dataTables.api.fnReloadAjax.js +0 -0
  15. data/app/assets/javascripts/dataTables/jquery.dataTables.foundation.js +37 -61
  16. data/app/assets/javascripts/dataTables/jquery.dataTables.js +720 -387
  17. data/app/assets/javascripts/dataTables/jquery.dataTables.sorting.ipAddress.js +44 -0
  18. data/app/assets/javascripts/dataTables/jquery.dataTables.sorting.numbersHtml.js +0 -0
  19. data/app/assets/javascripts/dataTables/jquery.dataTables.typeDetection.numbersHtml.js +0 -0
  20. data/app/assets/stylesheets/dataTables/jquery.dataTables.scss +34 -66
  21. data/app/assets/stylesheets/dataTables/src/demo_table.css +1 -1
  22. data/app/assets/stylesheets/dataTables/src/demo_table_jui.css.scss +4 -4
  23. data/lib/jquery/datatables/rails/version.rb +1 -1
  24. metadata +24 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad30fc9e7f7a606ff47490f39e286574de688d5a
4
- data.tar.gz: f92ef5aad051fbcecb88dc0720a70f2c628a7b07
3
+ metadata.gz: 02550545212f3757b2aa5ece5a19cc36c71ef2c5
4
+ data.tar.gz: 20f943bc969881fa472d4f18940a380db97ff00c
5
5
  SHA512:
6
- metadata.gz: b66d96926f2ad06cfefbb6321fe9c5dd72c0c264359769e9c8bb9dab8742978f1d71407f69545053ad61eb06715433fc502b8cbc673e88cc033691eebd2281f7
7
- data.tar.gz: a9e6a1fc77213de678c689b633e3c7d80be50b383026e303b61f2f0a751b149aea575351dbb88ac31c9b768fe3ef4bf214fcf73aac431b64ae12a89299bb63a1
6
+ metadata.gz: 95abfdaca290e4454adc5b7bb882f7925eff24c75ed6f41d83fb122a2dc6be3ce95710e0a15605b6338410b3675b96e81e25c13e1bfc825131a79f80a7b0069f
7
+ data.tar.gz: 76e198867d80e898a3b71f02fadcd486753a26502486441115721677e112d0aa8563ae56c31e9580848f6ab4a83ca7b6c2d9694c85853024cefff7dfb078c8af
@@ -1,5 +1,5 @@
1
1
  /*! DataTables Bootstrap 3 integration
2
- * ©2011-2014 SpryMedia Ltd - datatables.net/license
2
+ * ©2011-2015 SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
5
5
  /**
@@ -10,10 +10,37 @@
10
10
  * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap
11
11
  * for further information.
12
12
  */
13
- (function(window, document, undefined){
13
+ (function( factory ){
14
+ if ( typeof define === 'function' && define.amd ) {
15
+ // AMD
16
+ define( ['jquery', 'datatables.net'], function ( $ ) {
17
+ return factory( $, window, document );
18
+ } );
19
+ }
20
+ else if ( typeof exports === 'object' ) {
21
+ // CommonJS
22
+ module.exports = function (root, $) {
23
+ if ( ! root ) {
24
+ root = window;
25
+ }
26
+
27
+ if ( ! $ || ! $.fn.dataTable ) {
28
+ // Require DataTables, which attaches to jQuery, including
29
+ // jQuery if needed and have a $ property so we can access the
30
+ // jQuery object that is used
31
+ $ = require('datatables.net')(root, $).$;
32
+ }
14
33
 
15
- var factory = function( $, DataTable ) {
16
- "use strict";
34
+ return factory( $, root, root.document );
35
+ };
36
+ }
37
+ else {
38
+ // Browser
39
+ factory( jQuery, window, document );
40
+ }
41
+ }(function( $, window, document, undefined ) {
42
+ 'use strict';
43
+ var DataTable = $.fn.dataTable;
17
44
 
18
45
 
19
46
  /* Set the defaults for DataTables initialisation */
@@ -30,7 +57,8 @@ $.extend( true, DataTable.defaults, {
30
57
  $.extend( DataTable.ext.classes, {
31
58
  sWrapper: "dataTables_wrapper form-inline dt-bootstrap",
32
59
  sFilterInput: "form-control input-sm",
33
- sLengthSelect: "form-control input-sm"
60
+ sLengthSelect: "form-control input-sm",
61
+ sProcessing: "dataTables_processing panel panel-default"
34
62
  } );
35
63
 
36
64
 
@@ -39,14 +67,15 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
39
67
  var api = new DataTable.Api( settings );
40
68
  var classes = settings.oClasses;
41
69
  var lang = settings.oLanguage.oPaginate;
42
- var btnDisplay, btnClass;
70
+ var aria = settings.oLanguage.oAria.paginate || {};
71
+ var btnDisplay, btnClass, counter=0;
43
72
 
44
73
  var attach = function( container, buttons ) {
45
74
  var i, ien, node, button;
46
75
  var clickHandler = function ( e ) {
47
76
  e.preventDefault();
48
- if ( !$(e.currentTarget).hasClass('disabled') ) {
49
- api.page( e.data.action ).draw( false );
77
+ if ( !$(e.currentTarget).hasClass('disabled') && api.page() != e.data.action ) {
78
+ api.page( e.data.action ).draw( 'page' );
50
79
  }
51
80
  };
52
81
 
@@ -62,7 +91,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
62
91
 
63
92
  switch ( button ) {
64
93
  case 'ellipsis':
65
- btnDisplay = '…';
94
+ btnDisplay = '…';
66
95
  btnClass = 'disabled';
67
96
  break;
68
97
 
@@ -100,14 +129,16 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
100
129
  if ( btnDisplay ) {
101
130
  node = $('<li>', {
102
131
  'class': classes.sPageButton+' '+btnClass,
103
- 'aria-controls': settings.sTableId,
104
- 'tabindex': settings.iTabIndex,
105
132
  'id': idx === 0 && typeof button === 'string' ?
106
133
  settings.sTableId +'_'+ button :
107
134
  null
108
135
  } )
109
136
  .append( $('<a>', {
110
- 'href': '#'
137
+ 'href': '#',
138
+ 'aria-controls': settings.sTableId,
139
+ 'aria-label': aria[ button ],
140
+ 'data-dt-idx': counter,
141
+ 'tabindex': settings.iTabIndex
111
142
  } )
112
143
  .html( btnDisplay )
113
144
  )
@@ -116,15 +147,34 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu
116
147
  settings.oApi._fnBindAction(
117
148
  node, {action: button}, clickHandler
118
149
  );
150
+
151
+ counter++;
119
152
  }
120
153
  }
121
154
  }
122
155
  };
123
156
 
157
+ // IE9 throws an 'unknown error' if document.activeElement is used
158
+ // inside an iframe or frame.
159
+ var activeEl;
160
+
161
+ try {
162
+ // Because this approach is destroying and recreating the paging
163
+ // elements, focus is lost on the select button which is bad for
164
+ // accessibility. So we want to restore focus once the draw has
165
+ // completed
166
+ activeEl = $(host).find(document.activeElement).data('dt-idx');
167
+ }
168
+ catch (e) {}
169
+
124
170
  attach(
125
171
  $(host).empty().html('<ul class="pagination"/>').children('ul'),
126
172
  buttons
127
173
  );
174
+
175
+ if ( activeEl ) {
176
+ $(host).find( '[data-dt-idx='+activeEl+']' ).focus();
177
+ }
128
178
  };
129
179
 
130
180
 
@@ -165,21 +215,6 @@ if ( DataTable.TableTools ) {
165
215
  } );
166
216
  }
167
217
 
168
- }; // /factory
169
-
170
-
171
- // Define as an AMD module if possible
172
- if ( typeof define === 'function' && define.amd ) {
173
- define( ['jquery', 'datatables'], factory );
174
- }
175
- else if ( typeof exports === 'object' ) {
176
- // Node/CommonJS
177
- factory( require('jquery'), require('datatables') );
178
- }
179
- else if ( jQuery ) {
180
- // Otherwise simply initialise as normal, stopping multiple evaluation
181
- factory( jQuery, jQuery.fn.dataTable );
182
- }
183
-
184
218
 
185
- })(window, document);
219
+ return DataTable;
220
+ }));
@@ -1,15 +1,15 @@
1
- /*! AutoFill 1.2.1
2
- * ©2008-2014 SpryMedia Ltd - datatables.net/license
1
+ /*! AutoFill 2.1.0
2
+ * ©2008-2015 SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
5
5
  /**
6
6
  * @summary AutoFill
7
7
  * @description Add Excel like click and drag auto-fill options to DataTables
8
- * @version 1.2.1
8
+ * @version 2.1.0
9
9
  * @file dataTables.autoFill.js
10
10
  * @author SpryMedia Ltd (www.sprymedia.co.uk)
11
11
  * @contact www.sprymedia.co.uk/contact
12
- * @copyright Copyright 2010-2014 SpryMedia Ltd.
12
+ * @copyright Copyright 2010-2015 SpryMedia Ltd.
13
13
  *
14
14
  * This source file is free software, available under the following license:
15
15
  * MIT license - http://datatables.net/license/mit
@@ -20,11 +20,37 @@
20
20
  *
21
21
  * For details please refer to: http://www.datatables.net
22
22
  */
23
+ (function( factory ){
24
+ if ( typeof define === 'function' && define.amd ) {
25
+ // AMD
26
+ define( ['jquery', 'datatables.net'], function ( $ ) {
27
+ return factory( $, window, document );
28
+ } );
29
+ }
30
+ else if ( typeof exports === 'object' ) {
31
+ // CommonJS
32
+ module.exports = function (root, $) {
33
+ if ( ! root ) {
34
+ root = window;
35
+ }
23
36
 
24
- (function( window, document, undefined ) {
37
+ if ( ! $ || ! $.fn.dataTable ) {
38
+ $ = require('datatables.net')(root, $).$;
39
+ }
25
40
 
26
- var factory = function( $, DataTable ) {
27
- "use strict";
41
+ return factory( $, root, root.document );
42
+ };
43
+ }
44
+ else {
45
+ // Browser
46
+ factory( jQuery, window, document );
47
+ }
48
+ }(function( $, window, document, undefined ) {
49
+ 'use strict';
50
+ var DataTable = $.fn.dataTable;
51
+
52
+
53
+ var _instance = 0;
28
54
 
29
55
  /**
30
56
  * AutoFill provides Excel like auto-fill features for a DataTable
@@ -34,78 +60,39 @@ var factory = function( $, DataTable ) {
34
60
  * @param {object} oTD DataTables settings object
35
61
  * @param {object} oConfig Configuration object for AutoFill
36
62
  */
37
- var AutoFill = function( oDT, oConfig )
63
+ var AutoFill = function( dt, opts )
38
64
  {
39
- /* Sanity check that we are a new instance */
40
- if ( ! (this instanceof AutoFill) ) {
41
- throw( "Warning: AutoFill must be initialised with the keyword 'new'" );
42
- }
43
-
44
- if ( ! $.fn.dataTableExt.fnVersionCheck('1.7.0') ) {
45
- throw( "Warning: AutoFill requires DataTables 1.7 or greater");
65
+ if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
66
+ throw( "Warning: AutoFill requires DataTables 1.10.8 or greater");
46
67
  }
47
68
 
48
-
49
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
50
- * Public class variables
51
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
52
-
53
- this.c = {};
69
+ // User and defaults configuration object
70
+ this.c = $.extend( true, {},
71
+ DataTable.defaults.autoFill,
72
+ AutoFill.defaults,
73
+ opts
74
+ );
54
75
 
55
76
  /**
56
77
  * @namespace Settings object which contains customisable information for AutoFill instance
57
78
  */
58
79
  this.s = {
59
- /**
60
- * @namespace Cached information about the little dragging icon (the filler)
61
- */
62
- "filler": {
63
- "height": 0,
64
- "width": 0
65
- },
80
+ /** @type {DataTable.Api} DataTables' API instance */
81
+ dt: new DataTable.Api( dt ),
66
82
 
67
- /**
68
- * @namespace Cached information about the border display
69
- */
70
- "border": {
71
- "width": 2
72
- },
83
+ /** @type {String} Unique namespace for events attached to the document */
84
+ namespace: '.autoFill'+(_instance++),
73
85
 
74
- /**
75
- * @namespace Store for live information for the current drag
76
- */
77
- "drag": {
78
- "startX": -1,
79
- "startY": -1,
80
- "startTd": null,
81
- "endTd": null,
82
- "dragging": false
83
- },
84
-
85
- /**
86
- * @namespace Data cache for information that we need for scrolling the screen when we near
87
- * the edges
88
- */
89
- "screen": {
90
- "interval": null,
91
- "y": 0,
92
- "height": 0,
93
- "scrollTop": 0
94
- },
86
+ /** @type {Object} Cached dimension information for use in the mouse move event handler */
87
+ scroll: {},
95
88
 
96
- /**
97
- * @namespace Data cache for the position of the DataTables scrolling element (when scrolling
98
- * is enabled)
99
- */
100
- "scroller": {
101
- "top": 0,
102
- "bottom": 0
103
- },
89
+ /** @type {integer} Interval object used for smooth scrolling */
90
+ scrollInterval: null,
104
91
 
105
- /**
106
- * @namespace Information stored for each column. An array of objects
107
- */
108
- "columns": []
92
+ handle: {
93
+ height: 0,
94
+ width: 0
95
+ }
109
96
  };
110
97
 
111
98
 
@@ -113,743 +100,914 @@ var AutoFill = function( oDT, oConfig )
113
100
  * @namespace Common and useful DOM elements for the class instance
114
101
  */
115
102
  this.dom = {
116
- "table": null,
117
- "filler": null,
118
- "borderTop": null,
119
- "borderRight": null,
120
- "borderBottom": null,
121
- "borderLeft": null,
122
- "currentTarget": null
123
- };
103
+ /** @type {jQuery} AutoFill handle */
104
+ handle: $('<div class="dt-autofill-handle"/>'),
105
+
106
+ /**
107
+ * @type {Object} Selected cells outline - Need to use 4 elements,
108
+ * otherwise the mouse over if you back into the selected rectangle
109
+ * will be over that element, rather than the cells!
110
+ */
111
+ select: {
112
+ top: $('<div class="dt-autofill-select top"/>'),
113
+ right: $('<div class="dt-autofill-select right"/>'),
114
+ bottom: $('<div class="dt-autofill-select bottom"/>'),
115
+ left: $('<div class="dt-autofill-select left"/>')
116
+ },
124
117
 
118
+ /** @type {jQuery} Fill type chooser background */
119
+ background: $('<div class="dt-autofill-background"/>'),
125
120
 
121
+ /** @type {jQuery} Fill type chooser */
122
+ list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'<ul/></div>'),
126
123
 
127
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
128
- * Public class methods
129
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
124
+ /** @type {jQuery} DataTables scrolling container */
125
+ dtScroll: null,
130
126
 
131
- /**
132
- * Retreieve the settings object from an instance
133
- * @method fnSettings
134
- * @returns {object} AutoFill settings object
135
- */
136
- this.fnSettings = function () {
137
- return this.s;
127
+ /** @type {jQuery} Offset parent element */
128
+ offsetParent: null
138
129
  };
139
130
 
140
131
 
141
132
  /* Constructor logic */
142
- this._fnInit( oDT, oConfig );
143
- return this;
133
+ this._constructor();
144
134
  };
145
135
 
146
136
 
147
137
 
148
- AutoFill.prototype = {
149
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
150
- * Private methods (they are of course public in JS, but recommended as private)
151
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
138
+ $.extend( AutoFill.prototype, {
139
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
140
+ * Constructor
141
+ */
152
142
 
153
143
  /**
154
- * Initialisation
155
- * @method _fnInit
156
- * @param {object} dt DataTables settings object
157
- * @param {object} config Configuration object for AutoFill
158
- * @returns void
144
+ * Initialise the RowReorder instance
145
+ *
146
+ * @private
159
147
  */
160
- "_fnInit": function ( dt, config )
148
+ _constructor: function ()
161
149
  {
162
- var
163
- that = this,
164
- i, iLen;
165
-
166
- // Use DataTables API to get the settings allowing selectors, instances
167
- // etc to be used, or for backwards compatibility get from the old
168
- // fnSettings method
169
- this.s.dt = DataTable.Api ?
170
- new DataTable.Api( dt ).settings()[0] :
171
- dt.fnSettings();
172
- this.s.init = config || {};
173
- this.dom.table = this.s.dt.nTable;
174
-
175
- $.extend( true, this.c, AutoFill.defaults, config );
176
-
177
- /* Add and configure the columns */
178
- this._initColumns();
179
-
180
- /* Auto Fill click and drag icon */
181
- var filler = $('<div/>', {
182
- 'class': 'AutoFill_filler'
183
- } )
184
- .appendTo( 'body' );
185
- this.dom.filler = filler[0];
150
+ var that = this;
151
+ var dt = this.s.dt;
152
+ var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container());
186
153
 
187
- // Get the height / width of the click element
188
- this.s.filler.height = filler.height();
189
- this.s.filler.width = filler.width();
190
- filler[0].style.display = "none";
154
+ if ( dtScroll.length ) {
155
+ this.dom.dtScroll = dtScroll;
191
156
 
192
- /* Border display - one div for each side. We can't just use a single
193
- * one with a border, as we want the events to effectively pass through
194
- * the transparent bit of the box
195
- */
196
- var border;
197
- var appender = document.body;
198
- if ( that.s.dt.oScroll.sY !== "" ) {
199
- that.s.dt.nTable.parentNode.style.position = "relative";
200
- appender = that.s.dt.nTable.parentNode;
157
+ // Need to scroll container to be the offset parent
158
+ if ( dtScroll.css('position') === 'static' ) {
159
+ dtScroll.css( 'position', 'relative' );
160
+ }
201
161
  }
202
162
 
203
- border = $('<div/>', {
204
- "class": "AutoFill_border"
205
- } );
206
- this.dom.borderTop = border.clone().appendTo( appender )[0];
207
- this.dom.borderRight = border.clone().appendTo( appender )[0];
208
- this.dom.borderBottom = border.clone().appendTo( appender )[0];
209
- this.dom.borderLeft = border.clone().appendTo( appender )[0];
210
-
211
- /* Events */
212
- filler.on( 'mousedown.DTAF', function (e) {
213
- this.onselectstart = function() { return false; };
214
- that._fnFillerDragStart.call( that, e );
163
+ this._focusListener();
164
+
165
+ this.dom.handle.on( 'mousedown', function (e) {
166
+ that._mousedown( e );
215
167
  return false;
216
168
  } );
217
169
 
218
- $('tbody', this.dom.table).on(
219
- 'mouseover.DTAF mouseout.DTAF',
220
- '>tr>td, >tr>th',
221
- function (e) {
222
- that._fnFillerDisplay.call( that, e );
223
- }
224
- );
225
-
226
- $(this.dom.table).on( 'destroy.dt.DTAF', function () {
227
- filler.off( 'mousedown.DTAF' ).remove();
228
- $('tbody', this.dom.table).off( 'mouseover.DTAF mouseout.DTAF' );
170
+ dt.on( 'destroy.autoFill', function () {
171
+ dt.off( '.autoFill' );
172
+ $(dt.table().body()).off( that.s.namespace );
173
+ $(document.body).off( that.s.namespace );
229
174
  } );
230
175
  },
231
176
 
232
177
 
233
- _initColumns: function ( )
178
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
179
+ * Private methods
180
+ */
181
+
182
+ /**
183
+ * Display the AutoFill drag handle by appending it to a table cell. This
184
+ * is the opposite of the _detach method.
185
+ *
186
+ * @param {node} node TD/TH cell to insert the handle into
187
+ * @private
188
+ */
189
+ _attach: function ( node )
234
190
  {
235
- var that = this;
236
- var i, ien;
237
191
  var dt = this.s.dt;
238
- var config = this.s.init;
192
+ var idx = dt.cell( node ).index();
193
+ var handle = this.dom.handle;
194
+ var handleDim = this.s.handle;
239
195
 
240
- for ( i=0, ien=dt.aoColumns.length ; i<ien ; i++ ) {
241
- this.s.columns[i] = $.extend( true, {}, AutoFill.defaults.column );
196
+ if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) {
197
+ this._detach();
198
+ return;
242
199
  }
243
200
 
244
- dt.oApi._fnApplyColumnDefs(
245
- dt,
246
- config.aoColumnDefs || config.columnDefs,
247
- config.aoColumns || config.columns,
248
- function (colIdx, def) {
249
- that._fnColumnOptions( colIdx, def );
250
- }
251
- );
252
-
253
- // For columns which don't have read, write, step functions defined,
254
- // use the default ones
255
- for ( i=0, ien=dt.aoColumns.length ; i<ien ; i++ ) {
256
- var column = this.s.columns[i];
257
-
258
- if ( ! column.read ) {
259
- column.read = this._fnReadCell;
260
- }
261
- if ( ! column.write ) {
262
- column.read = this._fnWriteCell;
263
- }
264
- if ( ! column.step ) {
265
- column.read = this._fnStep;
266
- }
201
+ if ( ! this.dom.offsetParent ) {
202
+ this.dom.offsetParent = $(node).offsetParent();
267
203
  }
268
- },
269
204
 
205
+ if ( ! handleDim.height || ! handleDim.width ) {
206
+ // Append to document so we can get its size. Not expecting it to
207
+ // change during the life time of the page
208
+ handle.appendTo( 'body' );
209
+ handleDim.height = handle.outerHeight();
210
+ handleDim.width = handle.outerWidth();
211
+ }
270
212
 
271
- "_fnColumnOptions": function ( i, opts )
272
- {
273
- var column = this.s.columns[ i ];
274
- var set = function ( outProp, inProp ) {
275
- if ( opts[ inProp[0] ] !== undefined ) {
276
- column[ outProp ] = opts[ inProp[0] ];
277
- }
278
- if ( opts[ inProp[1] ] !== undefined ) {
279
- column[ outProp ] = opts[ inProp[1] ];
280
- }
281
- };
213
+ var offset = $(node).position();
282
214
 
283
- // Compatibility with the old Hungarian style of notation
284
- set( 'enable', ['bEnable', 'enable'] );
285
- set( 'read', ['fnRead', 'read'] );
286
- set( 'write', ['fnWrite', 'write'] );
287
- set( 'step', ['fnStep', 'step'] );
288
- set( 'increment', ['bIncrement', 'increment'] );
215
+ this.dom.attachedTo = node;
216
+ handle
217
+ .css( {
218
+ top: offset.top + node.offsetHeight - handleDim.height,
219
+ left: offset.left + node.offsetWidth - handleDim.width
220
+ } )
221
+ .appendTo( this.dom.offsetParent );
289
222
  },
290
223
 
291
224
 
292
225
  /**
293
- * Find out the coordinates of a given TD cell in a table
294
- * @method _fnTargetCoords
295
- * @param {Node} nTd
296
- * @returns {Object} x and y properties, for the position of the cell in the tables DOM
226
+ * Determine can the fill type should be. This can be automatic, or ask the
227
+ * end user.
228
+ *
229
+ * @param {array} cells Information about the selected cells from the key
230
+ * up function
231
+ * @private
297
232
  */
298
- "_fnTargetCoords": function ( nTd )
233
+ _actionSelector: function ( cells )
299
234
  {
300
- var nTr = $(nTd).parents('tr')[0];
301
- var position = this.s.dt.oInstance.fnGetPosition( nTd );
302
-
303
- return {
304
- "x": $('td', nTr).index(nTd),
305
- "y": $('tr', nTr.parentNode).index(nTr),
306
- "row": position[0],
307
- "column": position[2]
308
- };
235
+ var that = this;
236
+ var dt = this.s.dt;
237
+ var actions = AutoFill.actions;
238
+ var available = [];
239
+
240
+ // "Ask" each plug-in if it wants to handle this data
241
+ $.each( actions, function ( key, action ) {
242
+ if ( action.available( dt, cells ) ) {
243
+ available.push( key );
244
+ }
245
+ } );
246
+
247
+ if ( available.length === 1 && this.c.alwaysAsk === false ) {
248
+ // Only one action available - enact it immediately
249
+ var result = actions[ available[0] ].execute( dt, cells );
250
+ this._update( result, cells );
251
+ }
252
+ else {
253
+ // Multiple actions available - ask the end user what they want to do
254
+ var list = this.dom.list.children('ul').empty();
255
+
256
+ // Add a cancel option
257
+ available.push( 'cancel' );
258
+
259
+ $.each( available, function ( i, name ) {
260
+ list.append( $('<li/>')
261
+ .append(
262
+ '<div class="dt-autofill-question">'+
263
+ actions[ name ].option( dt, cells )+
264
+ '<div>'
265
+ )
266
+ .append( $('<div class="dt-autofill-button">' )
267
+ .append( $('<button class="'+AutoFill.classes.btn+'">'+dt.i18n('autoFill.button', '&gt;')+'</button>')
268
+ .on( 'click', function () {
269
+ var result = actions[ name ].execute(
270
+ dt, cells, $(this).closest('li')
271
+ );
272
+ that._update( result, cells );
273
+
274
+ that.dom.background.remove();
275
+ that.dom.list.remove();
276
+ } )
277
+ )
278
+ )
279
+ );
280
+ } );
281
+
282
+ this.dom.background.appendTo( 'body' );
283
+ this.dom.list.appendTo( 'body' );
284
+
285
+ this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 );
286
+ }
309
287
  },
310
288
 
311
289
 
312
290
  /**
313
- * Display the border around one or more cells (from start to end)
314
- * @method _fnUpdateBorder
315
- * @param {Node} nStart Starting cell
316
- * @param {Node} nEnd Ending cell
317
- * @returns void
291
+ * Remove the AutoFill handle from the document
292
+ *
293
+ * @private
318
294
  */
319
- "_fnUpdateBorder": function ( nStart, nEnd )
295
+ _detach: function ()
320
296
  {
321
- var
322
- border = this.s.border.width,
323
- offsetStart = $(nStart).offset(),
324
- offsetEnd = $(nEnd).offset(),
325
- x1 = offsetStart.left - border,
326
- x2 = offsetEnd.left + $(nEnd).outerWidth(),
327
- y1 = offsetStart.top - border,
328
- y2 = offsetEnd.top + $(nEnd).outerHeight(),
329
- width = offsetEnd.left + $(nEnd).outerWidth() - offsetStart.left + (2*border),
330
- height = offsetEnd.top + $(nEnd).outerHeight() - offsetStart.top + (2*border),
331
- oStyle;
332
-
333
- // Recalculate start and end (when dragging "backwards")
334
- if( offsetStart.left > offsetEnd.left) {
335
- x1 = offsetEnd.left - border;
336
- x2 = offsetStart.left + $(nStart).outerWidth();
337
- width = offsetStart.left + $(nStart).outerWidth() - offsetEnd.left + (2*border);
338
- }
339
-
340
- if ( this.s.dt.oScroll.sY !== "" )
341
- {
342
- /* The border elements are inside the DT scroller - so position relative to that */
343
- var
344
- offsetScroll = $(this.s.dt.nTable.parentNode).offset(),
345
- scrollTop = $(this.s.dt.nTable.parentNode).scrollTop(),
346
- scrollLeft = $(this.s.dt.nTable.parentNode).scrollLeft();
347
-
348
- x1 -= offsetScroll.left - scrollLeft;
349
- x2 -= offsetScroll.left - scrollLeft;
350
- y1 -= offsetScroll.top - scrollTop;
351
- y2 -= offsetScroll.top - scrollTop;
352
- }
353
-
354
- /* Top */
355
- oStyle = this.dom.borderTop.style;
356
- oStyle.top = y1+"px";
357
- oStyle.left = x1+"px";
358
- oStyle.height = this.s.border.width+"px";
359
- oStyle.width = width+"px";
360
-
361
- /* Bottom */
362
- oStyle = this.dom.borderBottom.style;
363
- oStyle.top = y2+"px";
364
- oStyle.left = x1+"px";
365
- oStyle.height = this.s.border.width+"px";
366
- oStyle.width = width+"px";
367
-
368
- /* Left */
369
- oStyle = this.dom.borderLeft.style;
370
- oStyle.top = y1+"px";
371
- oStyle.left = x1+"px";
372
- oStyle.height = height+"px";
373
- oStyle.width = this.s.border.width+"px";
374
-
375
- /* Right */
376
- oStyle = this.dom.borderRight.style;
377
- oStyle.top = y1+"px";
378
- oStyle.left = x2+"px";
379
- oStyle.height = height+"px";
380
- oStyle.width = this.s.border.width+"px";
297
+ this.dom.attachedTo = null;
298
+ this.dom.handle.detach();
381
299
  },
382
300
 
383
301
 
384
302
  /**
385
- * Mouse down event handler for starting a drag
386
- * @method _fnFillerDragStart
387
- * @param {Object} e Event object
388
- * @returns void
303
+ * Draw the selection outline by calculating the range between the start
304
+ * and end cells, then placing the highlighting elements to draw a rectangle
305
+ *
306
+ * @param {node} target End cell
307
+ * @param {object} e Originating event
308
+ * @private
389
309
  */
390
- "_fnFillerDragStart": function (e)
310
+ _drawSelection: function ( target, e )
391
311
  {
392
- var that = this;
393
- var startingTd = this.dom.currentTarget;
312
+ // Calculate boundary for start cell to this one
313
+ var dt = this.s.dt;
314
+ var start = this.s.start;
315
+ var startCell = $(this.dom.start);
316
+ var endCell = $(target);
317
+ var end = {
318
+ row: dt.rows( { page: 'current' } ).nodes().indexOf( endCell.parent()[0] ),
319
+ column: endCell.index()
320
+ };
394
321
 
395
- this.s.drag.dragging = true;
322
+ // Be sure that is a DataTables controlled cell
323
+ if ( ! dt.cell( endCell ).any() ) {
324
+ return;
325
+ }
396
326
 
397
- that.dom.borderTop.style.display = "block";
398
- that.dom.borderRight.style.display = "block";
399
- that.dom.borderBottom.style.display = "block";
400
- that.dom.borderLeft.style.display = "block";
327
+ // if target is not in the columns available - do nothing
328
+ if ( dt.columns( this.c.columns ).indexes().indexOf( end.column ) === -1 ) {
329
+ return;
330
+ }
401
331
 
402
- var coords = this._fnTargetCoords( startingTd );
403
- this.s.drag.startX = coords.x;
404
- this.s.drag.startY = coords.y;
332
+ this.s.end = end;
405
333
 
406
- this.s.drag.startTd = startingTd;
407
- this.s.drag.endTd = startingTd;
334
+ var top, bottom, left, right, height, width;
408
335
 
409
- this._fnUpdateBorder( startingTd, startingTd );
336
+ top = start.row < end.row ? startCell : endCell;
337
+ bottom = start.row < end.row ? endCell : startCell;
338
+ left = start.column < end.column ? startCell : endCell;
339
+ right = start.column < end.column ? endCell : startCell;
410
340
 
411
- $(document).bind('mousemove.AutoFill', function (e) {
412
- that._fnFillerDragMove.call( that, e );
413
- } );
341
+ top = top.position().top;
342
+ left = left.position().left;
343
+ height = bottom.position().top + bottom.outerHeight() - top;
344
+ width = right.position().left + right.outerWidth() - left;
414
345
 
415
- $(document).bind('mouseup.AutoFill', function (e) {
416
- that._fnFillerFinish.call( that, e );
417
- } );
346
+ var dtScroll = this.dom.dtScroll;
347
+ if ( dtScroll ) {
348
+ top += dtScroll.scrollTop();
349
+ left += dtScroll.scrollLeft();
350
+ }
418
351
 
419
- /* Scrolling information cache */
420
- this.s.screen.y = e.pageY;
421
- this.s.screen.height = $(window).height();
422
- this.s.screen.scrollTop = $(document).scrollTop();
352
+ var select = this.dom.select;
353
+ select.top.css( {
354
+ top: top,
355
+ left: left,
356
+ width: width
357
+ } );
423
358
 
424
- if ( this.s.dt.oScroll.sY !== "" )
425
- {
426
- this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top;
427
- this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height();
428
- }
359
+ select.left.css( {
360
+ top: top,
361
+ left: left,
362
+ height: height
363
+ } );
429
364
 
430
- /* Scrolling handler - we set an interval (which is cancelled on mouse up) which will fire
431
- * regularly and see if we need to do any scrolling
432
- */
433
- this.s.screen.interval = setInterval( function () {
434
- var iScrollTop = $(document).scrollTop();
435
- var iScrollDelta = iScrollTop - that.s.screen.scrollTop;
436
- that.s.screen.y += iScrollDelta;
437
-
438
- if ( that.s.screen.height - that.s.screen.y + iScrollTop < 50 )
439
- {
440
- $('html, body').animate( {
441
- "scrollTop": iScrollTop + 50
442
- }, 240, 'linear' );
443
- }
444
- else if ( that.s.screen.y - iScrollTop < 50 )
445
- {
446
- $('html, body').animate( {
447
- "scrollTop": iScrollTop - 50
448
- }, 240, 'linear' );
449
- }
365
+ select.bottom.css( {
366
+ top: top + height,
367
+ left: left,
368
+ width: width
369
+ } );
450
370
 
451
- if ( that.s.dt.oScroll.sY !== "" )
452
- {
453
- if ( that.s.screen.y > that.s.scroller.bottom - 50 )
454
- {
455
- $(that.s.dt.nTable.parentNode).animate( {
456
- "scrollTop": $(that.s.dt.nTable.parentNode).scrollTop() + 50
457
- }, 240, 'linear' );
458
- }
459
- else if ( that.s.screen.y < that.s.scroller.top + 50 )
460
- {
461
- $(that.s.dt.nTable.parentNode).animate( {
462
- "scrollTop": $(that.s.dt.nTable.parentNode).scrollTop() - 50
463
- }, 240, 'linear' );
464
- }
465
- }
466
- }, 250 );
371
+ select.right.css( {
372
+ top: top,
373
+ left: left + width,
374
+ height: height
375
+ } );
467
376
  },
468
377
 
469
378
 
470
379
  /**
471
- * Mouse move event handler for during a move. See if we want to update the display based on the
472
- * new cursor position
473
- * @method _fnFillerDragMove
474
- * @param {Object} e Event object
475
- * @returns void
380
+ * Use the Editor API to perform an update based on the new data for the
381
+ * cells
382
+ *
383
+ * @param {array} cells Information about the selected cells from the key
384
+ * up function
385
+ * @private
476
386
  */
477
- "_fnFillerDragMove": function (e)
387
+ _editor: function ( cells )
478
388
  {
479
- if ( e.target && e.target.nodeName.toUpperCase() == "TD" &&
480
- e.target != this.s.drag.endTd )
481
- {
482
- var coords = this._fnTargetCoords( e.target );
483
-
484
- if ( this.c.mode == "y" && coords.x != this.s.drag.startX )
485
- {
486
- e.target = $('tbody>tr:eq('+coords.y+')>td:eq('+this.s.drag.startX+')', this.dom.table)[0];
487
- }
488
- if ( this.c.mode == "x" && coords.y != this.s.drag.startY )
489
- {
490
- e.target = $('tbody>tr:eq('+this.s.drag.startY+')>td:eq('+coords.x+')', this.dom.table)[0];
491
- }
389
+ var dt = this.s.dt;
390
+ var editor = this.c.editor;
391
+
392
+ if ( ! editor ) {
393
+ return;
394
+ }
395
+
396
+ // Build the object structure for Editor's multi-row editing
397
+ var idValues = {};
398
+ var nodes = [];
399
+ var fields = editor.fields();
400
+
401
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
402
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
403
+ var cell = cells[i][j];
404
+
405
+ // Determine the field name for the cell being edited
406
+ var col = dt.settings()[0].aoColumns[ cell.index.column ];
407
+ var fieldName = col.editField;
408
+
409
+ if ( fieldName === undefined ) {
410
+ var dataSrc = col.mData;
411
+
412
+ // dataSrc is the `field.data` property, but we need to set
413
+ // using the field name, so we need to translate from the
414
+ // data to the name
415
+ for ( var k=0, ken=fields.length ; k<ken ; k++ ) {
416
+ var field = editor.field( fields[k] );
492
417
 
493
- if ( this.c.mode == "either")
494
- {
495
- if(coords.x != this.s.drag.startX )
496
- {
497
- e.target = $('tbody>tr:eq('+this.s.drag.startY+')>td:eq('+coords.x+')', this.dom.table)[0];
418
+ if ( field.dataSrc() === dataSrc ) {
419
+ fieldName = field.name();
420
+ break;
421
+ }
422
+ }
498
423
  }
499
- else if ( coords.y != this.s.drag.startY ) {
500
- e.target = $('tbody>tr:eq('+coords.y+')>td:eq('+this.s.drag.startX+')', this.dom.table)[0];
424
+
425
+ if ( ! fieldName ) {
426
+ throw 'Could not automatically determine field data. '+
427
+ 'Please see https://datatables.net/tn/11';
501
428
  }
502
- }
503
429
 
504
- // update coords
505
- if ( this.c.mode !== "both" ) {
506
- coords = this._fnTargetCoords( e.target );
507
- }
430
+ if ( ! idValues[ fieldName ] ) {
431
+ idValues[ fieldName ] = {};
432
+ }
508
433
 
509
- var drag = this.s.drag;
510
- drag.endTd = e.target;
434
+ var id = dt.row( cell.index.row ).id();
435
+ idValues[ fieldName ][ id ] = cell.set;
511
436
 
512
- if ( coords.y >= this.s.drag.startY ) {
513
- this._fnUpdateBorder( drag.startTd, drag.endTd );
437
+ // Keep a list of cells so we can activate the bubble editing
438
+ // with them
439
+ nodes.push( cell.index );
514
440
  }
515
- else {
516
- this._fnUpdateBorder( drag.endTd, drag.startTd );
517
- }
518
- this._fnFillerPosition( e.target );
519
441
  }
520
442
 
521
- /* Update the screen information so we can perform scrolling */
522
- this.s.screen.y = e.pageY;
523
- this.s.screen.scrollTop = $(document).scrollTop();
524
-
525
- if ( this.s.dt.oScroll.sY !== "" )
526
- {
527
- this.s.scroller.scrollTop = $(this.s.dt.nTable.parentNode).scrollTop();
528
- this.s.scroller.top = $(this.s.dt.nTable.parentNode).offset().top;
529
- this.s.scroller.bottom = this.s.scroller.top + $(this.s.dt.nTable.parentNode).height();
530
- }
443
+ // Perform the edit using bubble editing as it allows us to specify
444
+ // the cells to be edited, rather than using full rows
445
+ editor
446
+ .bubble( nodes, false )
447
+ .multiSet( idValues )
448
+ .submit();
531
449
  },
532
450
 
533
451
 
534
452
  /**
535
- * Mouse release handler - end the drag and take action to update the cells with the needed values
536
- * @method _fnFillerFinish
537
- * @param {Object} e Event object
538
- * @returns void
453
+ * Emit an event on the DataTable for listeners
454
+ *
455
+ * @param {string} name Event name
456
+ * @param {array} args Event arguments
457
+ * @private
539
458
  */
540
- "_fnFillerFinish": function (e)
459
+ _emitEvent: function ( name, args )
541
460
  {
542
- var that = this, i, iLen, j;
543
-
544
- $(document).unbind('mousemove.AutoFill mouseup.AutoFill');
545
-
546
- this.dom.borderTop.style.display = "none";
547
- this.dom.borderRight.style.display = "none";
548
- this.dom.borderBottom.style.display = "none";
549
- this.dom.borderLeft.style.display = "none";
550
-
551
- this.s.drag.dragging = false;
461
+ this.s.dt.iterator( 'table', function ( ctx, i ) {
462
+ $(ctx.nTable).triggerHandler( name+'.dt', args );
463
+ } );
464
+ },
552
465
 
553
- clearInterval( this.s.screen.interval );
554
466
 
555
- var cells = [];
556
- var table = this.dom.table;
557
- var coordsStart = this._fnTargetCoords( this.s.drag.startTd );
558
- var coordsEnd = this._fnTargetCoords( this.s.drag.endTd );
559
- var columnIndex = function ( visIdx ) {
560
- return that.s.dt.oApi._fnVisibleToColumnIndex( that.s.dt, visIdx );
561
- };
467
+ /**
468
+ * Attach suitable listeners (based on the configuration) that will attach
469
+ * and detach the AutoFill handle in the document.
470
+ *
471
+ * @private
472
+ */
473
+ _focusListener: function ()
474
+ {
475
+ var that = this;
476
+ var dt = this.s.dt;
477
+ var namespace = this.s.namespace;
478
+ var focus = this.c.focus !== null ?
479
+ this.c.focus :
480
+ dt.settings()[0].keytable ?
481
+ 'focus' :
482
+ 'hover';
483
+
484
+ // All event listeners attached here are removed in the `destroy`
485
+ // callback in the constructor
486
+ if ( focus === 'focus' ) {
487
+ dt
488
+ .on( 'key-focus.autoFill', function ( e, dt, cell ) {
489
+ that._attach( cell.node() );
490
+ } )
491
+ .on( 'key-blur.autoFill', function ( e, dt, cell ) {
492
+ that._detach();
493
+ } );
494
+ }
495
+ else if ( focus === 'click' ) {
496
+ $(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) {
497
+ that._attach( this );
498
+ } );
562
499
 
563
- // xxx - urgh - there must be a way of reducing this...
564
- if ( coordsStart.y <= coordsEnd.y ) {
565
- for ( i=coordsStart.y ; i<=coordsEnd.y ; i++ ) {
566
- if ( coordsStart.x <= coordsEnd.x ) {
567
- for ( j=coordsStart.x ; j<=coordsEnd.x ; j++ ) {
568
- cells.push( {
569
- node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],
570
- x: j - coordsStart.x,
571
- y: i - coordsStart.y,
572
- colIdx: columnIndex( j )
573
- } );
574
- }
575
- }
576
- else {
577
- for ( j=coordsStart.x ; j>=coordsEnd.x ; j-- ) {
578
- cells.push( {
579
- node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],
580
- x: j - coordsStart.x,
581
- y: i - coordsStart.y,
582
- colIdx: columnIndex( j )
583
- } );
584
- }
500
+ $(document.body).on( 'click'+namespace, function (e) {
501
+ if ( ! $(e.target).parents().filter( dt.table().body() ).length ) {
502
+ that._detach();
585
503
  }
586
- }
504
+ } );
587
505
  }
588
506
  else {
589
- for ( i=coordsStart.y ; i>=coordsEnd.y ; i-- ) {
590
- if ( coordsStart.x <= coordsEnd.x ) {
591
- for ( j=coordsStart.x ; j<=coordsEnd.x ; j++ ) {
592
- cells.push( {
593
- node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],
594
- x: j - coordsStart.x,
595
- y: i - coordsStart.y,
596
- colIdx: columnIndex( j )
597
- } );
598
- }
599
- }
600
- else {
601
- for ( j=coordsStart.x ; j>=coordsEnd.x ; j-- ) {
602
- cells.push( {
603
- node: $('tbody>tr:eq('+i+')>td:eq('+j+')', table)[0],
604
- x: coordsStart.x - j,
605
- y: coordsStart.y - i,
606
- colIdx: columnIndex( j )
607
- } );
507
+ $(dt.table().body())
508
+ .on( 'mouseenter'+namespace, 'td, th', function (e) {
509
+ that._attach( this );
510
+ } )
511
+ .on( 'mouseleave'+namespace, function (e) {
512
+ if ( $(e.relatedTarget).hasClass('dt-autofill-handle') ) {
513
+ return;
608
514
  }
609
- }
610
- }
515
+
516
+ that._detach();
517
+ } );
611
518
  }
519
+ },
520
+
612
521
 
613
- // An auto-fill requires 2 or more cells
614
- if ( cells.length <= 1 ) {
522
+ /**
523
+ * Start mouse drag - selects the start cell
524
+ *
525
+ * @param {object} e Mouse down event
526
+ * @private
527
+ */
528
+ _mousedown: function ( e )
529
+ {
530
+ var that = this;
531
+ var dt = this.s.dt;
532
+
533
+ this.dom.start = this.dom.attachedTo;
534
+ this.s.start = {
535
+ row: dt.rows( { page: 'current' } ).nodes().indexOf( $(this.dom.start).parent()[0] ),
536
+ column: $(this.dom.start).index()
537
+ };
538
+
539
+ $(document.body)
540
+ .on( 'mousemove.autoFill', function (e) {
541
+ that._mousemove( e );
542
+ } )
543
+ .on( 'mouseup.autoFill', function (e) {
544
+ that._mouseup( e );
545
+ } );
546
+
547
+ var select = this.dom.select;
548
+ var offsetParent = $(this.s.dt.table().body()).offsetParent();
549
+ select.top.appendTo( offsetParent );
550
+ select.left.appendTo( offsetParent );
551
+ select.right.appendTo( offsetParent );
552
+ select.bottom.appendTo( offsetParent );
553
+
554
+ this._drawSelection( this.dom.start, e );
555
+
556
+ this.dom.handle.css( 'display', 'none' );
557
+
558
+ // Cache scrolling information so mouse move doesn't need to read.
559
+ // This assumes that the window and DT scroller will not change size
560
+ // during an AutoFill drag, which I think is a fair assumption
561
+ var scrollWrapper = this.dom.dtScroll;
562
+ this.s.scroll = {
563
+ windowHeight: $(window).height(),
564
+ windowWidth: $(window).width(),
565
+ dtTop: scrollWrapper ? scrollWrapper.offset().top : null,
566
+ dtLeft: scrollWrapper ? scrollWrapper.offset().left : null,
567
+ dtHeight: scrollWrapper ? scrollWrapper.outerHeight() : null,
568
+ dtWidth: scrollWrapper ? scrollWrapper.outerWidth() : null
569
+ };
570
+ },
571
+
572
+
573
+ /**
574
+ * Mouse drag - selects the end cell and update the selection display for
575
+ * the end user
576
+ *
577
+ * @param {object} e Mouse move event
578
+ * @private
579
+ */
580
+ _mousemove: function ( e )
581
+ {
582
+ var that = this;
583
+ var dt = this.s.dt;
584
+ var name = e.target.nodeName.toLowerCase();
585
+ if ( name !== 'td' && name !== 'th' ) {
615
586
  return;
616
587
  }
617
588
 
618
- var edited = [];
619
- var previous;
589
+ this._drawSelection( e.target, e );
590
+ this._shiftScroll( e );
591
+ },
592
+
593
+
594
+ /**
595
+ * End mouse drag - perform the update actions
596
+ *
597
+ * @param {object} e Mouse up event
598
+ * @private
599
+ */
600
+ _mouseup: function ( e )
601
+ {
602
+ $(document.body).off( '.autoFill' );
603
+
604
+ var dt = this.s.dt;
605
+ var select = this.dom.select;
606
+ select.top.remove();
607
+ select.left.remove();
608
+ select.right.remove();
609
+ select.bottom.remove();
620
610
 
621
- for ( i=0, iLen=cells.length ; i<iLen ; i++ ) {
622
- var cell = cells[i];
623
- var column = this.s.columns[ cell.colIdx ];
624
- var read = column.read.call( column, cell.node );
625
- var stepValue = column.step.call( column, cell.node, read, previous, i, cell.x, cell.y );
611
+ this.dom.handle.css( 'display', 'block' );
626
612
 
627
- column.write.call( column, cell.node, stepValue );
613
+ // Display complete - now do something useful with the selection!
614
+ var start = this.s.start;
615
+ var end = this.s.end;
628
616
 
629
- previous = stepValue;
630
- edited.push( {
631
- cell: cell,
632
- colIdx: cell.colIdx,
633
- newValue: stepValue,
634
- oldValue: read
635
- } );
617
+ // Haven't selected multiple cells, so nothing to do
618
+ if ( start.row === end.row && start.column === end.column ) {
619
+ return;
636
620
  }
637
621
 
638
- if ( this.c.complete !== null ) {
639
- this.c.complete.call( this, edited );
622
+ // Build a matrix representation of the selected rows
623
+ var rows = this._range( start.row, end.row );
624
+ var columns = this._range( start.column, end.column );
625
+ var selected = [];
626
+
627
+ // Can't use Array.prototype.map as IE8 doesn't support it
628
+ // Can't use $.map as jQuery flattens 2D arrays
629
+ // Need to use a good old fashioned for loop
630
+ for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) {
631
+ selected.push(
632
+ $.map( columns, function (column) {
633
+ var cell = dt.cell( ':eq('+rows[rowIdx]+')', column+':visible', {page:'current'} );
634
+
635
+ return {
636
+ cell: cell,
637
+ data: cell.data(),
638
+ index: cell.index()
639
+ };
640
+ } )
641
+ );
640
642
  }
641
643
 
642
- // In 1.10 we can do a static draw
643
- if ( DataTable.Api ) {
644
- new DataTable.Api( this.s.dt ).draw( false );
644
+ this._actionSelector( selected );
645
+ },
646
+
647
+
648
+ /**
649
+ * Create an array with a range of numbers defined by the start and end
650
+ * parameters passed in (inclusive!).
651
+ *
652
+ * @param {integer} start Start
653
+ * @param {integer} end End
654
+ * @private
655
+ */
656
+ _range: function ( start, end )
657
+ {
658
+ var out = [];
659
+ var i;
660
+
661
+ if ( start <= end ) {
662
+ for ( i=start ; i<=end ; i++ ) {
663
+ out.push( i );
664
+ }
645
665
  }
646
666
  else {
647
- this.s.dt.oInstance.fnDraw();
667
+ for ( i=start ; i>=end ; i-- ) {
668
+ out.push( i );
669
+ }
648
670
  }
671
+
672
+ return out;
649
673
  },
650
674
 
651
675
 
652
676
  /**
653
- * Display the drag handle on mouse over cell
654
- * @method _fnFillerDisplay
655
- * @param {Object} e Event object
656
- * @returns void
677
+ * Move the window and DataTables scrolling during a drag to scroll new
678
+ * content into view. This is done by proximity to the edge of the scrolling
679
+ * container of the mouse - for example near the top edge of the window
680
+ * should scroll up. This is a little complicated as there are two elements
681
+ * that can be scrolled - the window and the DataTables scrolling view port
682
+ * (if scrollX and / or scrollY is enabled).
683
+ *
684
+ * @param {object} e Mouse move event object
685
+ * @private
657
686
  */
658
- "_fnFillerDisplay": function (e)
687
+ _shiftScroll: function ( e )
659
688
  {
660
- var filler = this.dom.filler;
689
+ var that = this;
690
+ var dt = this.s.dt;
691
+ var scroll = this.s.scroll;
692
+ var runInterval = false;
693
+ var scrollSpeed = 5;
694
+ var buffer = 65;
695
+ var
696
+ windowY = e.pageY - document.body.scrollTop,
697
+ windowX = e.pageX - document.body.scrollLeft,
698
+ windowVert, windowHoriz,
699
+ dtVert, dtHoriz;
700
+
701
+ // Window calculations - based on the mouse position in the window,
702
+ // regardless of scrolling
703
+ if ( windowY < buffer ) {
704
+ windowVert = scrollSpeed * -1;
705
+ }
706
+ else if ( windowY > scroll.windowHeight - buffer ) {
707
+ windowVert = scrollSpeed;
708
+ }
661
709
 
662
- /* Don't display automatically when dragging */
663
- if ( this.s.drag.dragging)
664
- {
665
- return;
710
+ if ( windowX < buffer ) {
711
+ windowHoriz = scrollSpeed * -1;
712
+ }
713
+ else if ( windowX > scroll.windowWidth - buffer ) {
714
+ windowHoriz = scrollSpeed;
666
715
  }
667
716
 
668
- /* Check that we are allowed to AutoFill this column or not */
669
- var nTd = (e.target.nodeName.toLowerCase() == 'td') ? e.target : $(e.target).parents('td')[0];
670
- var iX = this._fnTargetCoords(nTd).column;
671
- if ( !this.s.columns[iX].enable )
672
- {
673
- filler.style.display = "none";
674
- return;
717
+ // DataTables scrolling calculations - based on the table's position in
718
+ // the document and the mouse position on the page
719
+ if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) {
720
+ dtVert = scrollSpeed * -1;
721
+ }
722
+ else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
723
+ dtVert = scrollSpeed;
675
724
  }
676
725
 
677
- if (e.type == 'mouseover')
678
- {
679
- this.dom.currentTarget = nTd;
680
- this._fnFillerPosition( nTd );
726
+ if ( scroll.dtLeft !== null && e.pageX < scroll.dtLeft + buffer ) {
727
+ dtHoriz = scrollSpeed * -1;
728
+ }
729
+ else if ( scroll.dtLeft !== null && e.pageX > scroll.dtLeft + scroll.dtWidth - buffer ) {
730
+ dtHoriz = scrollSpeed;
731
+ }
681
732
 
682
- filler.style.display = "block";
733
+ // This is where it gets interesting. We want to continue scrolling
734
+ // without requiring a mouse move, so we need an interval to be
735
+ // triggered. The interval should continue until it is no longer needed,
736
+ // but it must also use the latest scroll commands (for example consider
737
+ // that the mouse might move from scrolling up to scrolling left, all
738
+ // with the same interval running. We use the `scroll` object to "pass"
739
+ // this information to the interval. Can't use local variables as they
740
+ // wouldn't be the ones that are used by an already existing interval!
741
+ if ( windowVert || windowHoriz || dtVert || dtHoriz ) {
742
+ scroll.windowVert = windowVert;
743
+ scroll.windowHoriz = windowHoriz;
744
+ scroll.dtVert = dtVert;
745
+ scroll.dtHoriz = dtHoriz;
746
+ runInterval = true;
747
+ }
748
+ else if ( this.s.scrollInterval ) {
749
+ // Don't need to scroll - remove any existing timer
750
+ clearInterval( this.s.scrollInterval );
751
+ this.s.scrollInterval = null;
683
752
  }
684
- else if ( !e.relatedTarget || !e.relatedTarget.className.match(/AutoFill/) )
685
- {
686
- filler.style.display = "none";
753
+
754
+ // If we need to run the interval to scroll and there is no existing
755
+ // interval (if there is an existing one, it will continue to run)
756
+ if ( ! this.s.scrollInterval && runInterval ) {
757
+ this.s.scrollInterval = setInterval( function () {
758
+ // Don't need to worry about setting scroll <0 or beyond the
759
+ // scroll bound as the browser will just reject that.
760
+ if ( scroll.windowVert ) {
761
+ document.body.scrollTop += scroll.windowVert;
762
+ }
763
+ if ( scroll.windowHoriz ) {
764
+ document.body.scrollLeft += scroll.windowHoriz;
765
+ }
766
+
767
+ // DataTables scrolling
768
+ if ( scroll.dtVert || scroll.dtHoriz ) {
769
+ var scroller = that.dom.dtScroll[0];
770
+
771
+ if ( scroll.dtVert ) {
772
+ scroller.scrollTop += scroll.dtVert;
773
+ }
774
+ if ( scroll.dtHoriz ) {
775
+ scroller.scrollLeft += scroll.dtHoriz;
776
+ }
777
+ }
778
+ }, 20 );
687
779
  }
688
780
  },
689
781
 
690
782
 
691
783
  /**
692
- * Position the filler icon over a cell
693
- * @method _fnFillerPosition
694
- * @param {Node} nTd Cell to position filler icon over
695
- * @returns void
784
+ * Update the DataTable after the user has selected what they want to do
785
+ *
786
+ * @param {false|undefined} result Return from the `execute` method - can
787
+ * be false internally to do nothing. This is not documented for plug-ins
788
+ * and is used only by the cancel option.
789
+ * @param {array} cells Information about the selected cells from the key
790
+ * up function, argumented with the set values
791
+ * @private
696
792
  */
697
- "_fnFillerPosition": function ( nTd )
793
+ _update: function ( result, cells )
698
794
  {
699
- var offset = $(nTd).offset();
700
- var filler = this.dom.filler;
701
- filler.style.top = (offset.top - (this.s.filler.height / 2)-1 + $(nTd).outerHeight())+"px";
702
- filler.style.left = (offset.left - (this.s.filler.width / 2)-1 + $(nTd).outerWidth())+"px";
703
- }
704
- };
795
+ // Do nothing on `false` return from an execute function
796
+ if ( result === false ) {
797
+ return;
798
+ }
705
799
 
800
+ var dt = this.s.dt;
801
+ var cell;
706
802
 
707
- // Alias for access
708
- DataTable.AutoFill = AutoFill;
709
- DataTable.AutoFill = AutoFill;
803
+ // Potentially allow modifications to the cells matrix
804
+ this._emitEvent( 'preAutoFill', [ dt, cells ] );
710
805
 
806
+ this._editor( cells );
711
807
 
808
+ // Automatic updates are not performed if `update` is null and the
809
+ // `editor` parameter is passed in - the reason being that Editor will
810
+ // update the data once submitted
811
+ var update = this.c.update !== null ?
812
+ this.c.update :
813
+ this.c.editor ?
814
+ false :
815
+ true;
712
816
 
713
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
714
- * Constants
715
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
817
+ if ( update ) {
818
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
819
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
820
+ cell = cells[i][j];
716
821
 
717
- /**
718
- * AutoFill version
719
- * @constant version
720
- * @type String
721
- * @default See code
722
- */
723
- AutoFill.version = "1.2.1";
822
+ cell.cell.data( cell.set );
823
+ }
824
+ }
825
+
826
+ dt.draw(false);
827
+ }
828
+
829
+ this._emitEvent( 'autoFill', [ dt, cells ] );
830
+ }
831
+ } );
724
832
 
725
833
 
726
834
  /**
727
- * AutoFill defaults
728
- * @namespace
835
+ * AutoFill actions. The options here determine how AutoFill will fill the data
836
+ * in the table when the user has selected a range of cells. Please see the
837
+ * documentation on the DataTables site for full details on how to create plug-
838
+ * ins.
839
+ *
840
+ * @type {Object}
729
841
  */
730
- AutoFill.defaults = {
731
- /**
732
- * Mode for dragging (restrict to y-axis only, x-axis only, either one or none):
733
- *
734
- * * `y` - y-axis only (default)
735
- * * `x` - x-axis only
736
- * * `either` - either one, but not both axis at the same time
737
- * * `both` - multiple cells allowed
738
- *
739
- * @type {string}
740
- * @default `y`
741
- */
742
- mode: 'y',
842
+ AutoFill.actions = {
843
+ increment: {
844
+ available: function ( dt, cells ) {
845
+ return $.isNumeric( cells[0][0].data );
846
+ },
743
847
 
744
- complete: null,
848
+ option: function ( dt, cells ) {
849
+ return dt.i18n(
850
+ 'autoFill.increment',
851
+ 'Increment / decrement each cell by: <input type="number" value="1">'
852
+ );
853
+ },
745
854
 
746
- /**
747
- * Column definition defaults
748
- * @namespace
749
- */
750
- column: {
751
- /**
752
- * If AutoFill should be enabled on this column
753
- *
754
- * @type {boolean}
755
- * @default true
756
- */
757
- enable: true,
855
+ execute: function ( dt, cells, node ) {
856
+ var value = cells[0][0].data * 1;
857
+ var increment = $('input', node).val() * 1;
758
858
 
759
- /**
760
- * Allow automatic increment / decrement on this column if a number
761
- * is found.
762
- *
763
- * @type {boolean}
764
- * @default true
765
- */
766
- increment: true,
859
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
860
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
861
+ cells[i][j].set = value;
767
862
 
768
- /**
769
- * Cell read function
770
- *
771
- * Default function will simply read the value from the HTML of the
772
- * cell.
773
- *
774
- * @type {function}
775
- * @param {node} cell `th` / `td` element to read the value from
776
- * @return {string} Data that has been read
777
- */
778
- read: function ( cell ) {
779
- return $(cell).html();
863
+ value += increment;
864
+ }
865
+ }
866
+ }
867
+ },
868
+
869
+ fill: {
870
+ available: function ( dt, cells ) {
871
+ return true;
780
872
  },
781
873
 
782
- /**
783
- * Cell write function
784
- *
785
- * Default function will simply write to the HTML and tell the DataTable
786
- * to update.
787
- *
788
- * @type {function}
789
- * @param {node} cell `th` / `td` element to write the value to
790
- * @return {string} Data two write
791
- */
792
- write: function ( cell, val ) {
793
- var table = $(cell).parents('table');
794
- if ( DataTable.Api ) {
795
- // 1.10
796
- table.DataTable().cell( cell ).data( val );
874
+ option: function ( dt, cells ) {
875
+ return dt.i18n('autoFill.fill', 'Fill all cells with <i>'+cells[0][0].data+'</i>' );
876
+ },
877
+
878
+ execute: function ( dt, cells, node ) {
879
+ var value = cells[0][0].data;
880
+
881
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
882
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
883
+ cells[i][j].set = value;
884
+ }
797
885
  }
798
- else {
799
- // 1.9
800
- var dt = table.dataTable();
801
- var pos = dt.fnGetPosition( cell );
802
- dt.fnUpdate( val, pos[0], pos[2], false );
886
+ }
887
+ },
888
+
889
+ fillHorizontal: {
890
+ available: function ( dt, cells ) {
891
+ return cells.length > 1 && cells[0].length > 1;
892
+ },
893
+
894
+ option: function ( dt, cells ) {
895
+ return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' );
896
+ },
897
+
898
+ execute: function ( dt, cells, node ) {
899
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
900
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
901
+ cells[i][j].set = cells[i][0].data;
902
+ }
803
903
  }
904
+ }
905
+ },
906
+
907
+ fillVertical: {
908
+ available: function ( dt, cells ) {
909
+ return cells.length > 1 && cells[0].length > 1;
804
910
  },
805
911
 
806
- /**
807
- * Step function. This provides the ability to customise how the values
808
- * are incremented.
809
- *
810
- * @param {node} cell `th` / `td` element that is being operated upon
811
- * @param {string} read Cell value from `read` function
812
- * @param {string} last Value of the previous cell
813
- * @param {integer} i Loop counter
814
- * @param {integer} x Cell x-position in the current auto-fill. The
815
- * starting cell is coordinate 0 regardless of its physical position
816
- * in the DataTable.
817
- * @param {integer} y Cell y-position in the current auto-fill. The
818
- * starting cell is coordinate 0 regardless of its physical position
819
- * in the DataTable.
820
- * @return {string} Value to write
821
- */
822
- step: function ( cell, read, last, i, x, y ) {
823
- // Increment a number if it is found
824
- var re = /(\-?\d+)/;
825
- var match = this.increment && last ? last.match(re) : null;
826
- if ( match ) {
827
- return last.replace( re, parseInt(match[1],10) + (x<0 || y<0 ? -1 : 1) );
912
+ option: function ( dt, cells ) {
913
+ return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' );
914
+ },
915
+
916
+ execute: function ( dt, cells, node ) {
917
+ for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
918
+ for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
919
+ cells[i][j].set = cells[0][j].data;
920
+ }
828
921
  }
829
- return last === undefined ?
830
- read :
831
- last;
922
+ }
923
+ },
924
+
925
+ // Special type that does not make itself available, but is added
926
+ // automatically by AutoFill if a multi-choice list is shown. This allows
927
+ // sensible code reuse
928
+ cancel: {
929
+ available: function () {
930
+ return false;
931
+ },
932
+
933
+ option: function ( dt ) {
934
+ return dt.i18n('autoFill.cancel', 'Cancel' );
935
+ },
936
+
937
+ execute: function () {
938
+ return false;
832
939
  }
833
940
  }
834
941
  };
835
942
 
836
- return AutoFill;
943
+
944
+ /**
945
+ * AutoFill version
946
+ *
947
+ * @static
948
+ * @type String
949
+ */
950
+ AutoFill.version = '2.1.0';
951
+
952
+
953
+ /**
954
+ * AutoFill defaults
955
+ *
956
+ * @namespace
957
+ */
958
+ AutoFill.defaults = {
959
+ /** @type {Boolean} Ask user what they want to do, even for a single option */
960
+ alwaysAsk: false,
961
+
962
+ /** @type {string|null} What will trigger a focus */
963
+ focus: null, // focus, click, hover
964
+
965
+ /** @type {column-selector} Columns to provide auto fill for */
966
+ columns: '', // all
967
+
968
+ /** @type {boolean|null} Update the cells after a drag */
969
+ update: null, // false is editor given, true otherwise
970
+
971
+ /** @type {DataTable.Editor} Editor instance for automatic submission */
972
+ editor: null
973
+ };
974
+
975
+
976
+ /**
977
+ * Classes used by AutoFill that are configurable
978
+ *
979
+ * @namespace
980
+ */
981
+ AutoFill.classes = {
982
+ /** @type {String} Class used by the selection button */
983
+ btn: 'btn'
837
984
  };
838
985
 
839
986
 
840
- // Define as an AMD module if possible
841
- if ( typeof define === 'function' && define.amd ) {
842
- define( ['jquery', 'datatables'], factory );
843
- }
844
- else if ( typeof exports === 'object' ) {
845
- // Node/CommonJS
846
- factory( require('jquery'), require('datatables') );
847
- }
848
- else if ( jQuery && !jQuery.fn.dataTable.AutoFill ) {
849
- // Otherwise simply initialise as normal, stopping multiple evaluation
850
- factory( jQuery, jQuery.fn.dataTable );
851
- }
987
+ // Attach a listener to the document which listens for DataTables initialisation
988
+ // events so we can automatically initialise
989
+ $(document).on( 'preInit.dt.autofill', function (e, settings, json) {
990
+ if ( e.namespace !== 'dt' ) {
991
+ return;
992
+ }
993
+
994
+ var init = settings.oInit.autoFill;
995
+ var defaults = DataTable.defaults.autoFill;
996
+
997
+ if ( init || defaults ) {
998
+ var opts = $.extend( {}, init, defaults );
999
+
1000
+ if ( init !== false ) {
1001
+ new AutoFill( settings, opts );
1002
+ }
1003
+ }
1004
+ } );
1005
+
852
1006
 
1007
+ // Alias for access
1008
+ DataTable.AutoFill = AutoFill;
1009
+ DataTable.AutoFill = AutoFill;
853
1010
 
854
- }(window, document));
855
1011
 
1012
+ return AutoFill;
1013
+ }));