jquery-datatables-rails 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }));