effective_datatables 4.3.4 → 4.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -1
  3. data/app/assets/javascripts/dataTables/buttons/buttons.bootstrap4.js +1 -0
  4. data/app/assets/javascripts/dataTables/buttons/buttons.colVis.js +12 -2
  5. data/app/assets/javascripts/dataTables/buttons/buttons.html5.js +58 -6
  6. data/app/assets/javascripts/dataTables/buttons/buttons.print.js +16 -5
  7. data/app/assets/javascripts/dataTables/buttons/dataTables.buttons.js +26 -10
  8. data/app/assets/javascripts/dataTables/rowReorder/dataTables.rowReorder.js +818 -0
  9. data/app/assets/javascripts/dataTables/rowReorder/rowReorder.bootstrap4.js +38 -0
  10. data/app/assets/javascripts/effective_datatables.js +3 -10
  11. data/app/assets/javascripts/effective_datatables/flash.js.coffee +20 -0
  12. data/app/assets/javascripts/effective_datatables/initialize.js.coffee +12 -1
  13. data/app/assets/javascripts/effective_datatables/overrides.js.coffee +0 -24
  14. data/app/assets/javascripts/effective_datatables/reorder.js.coffee +37 -0
  15. data/app/assets/stylesheets/dataTables/buttons/buttons.bootstrap4.css +6 -0
  16. data/app/assets/stylesheets/dataTables/rowReorder/rowReorder.bootstrap4.css +22 -0
  17. data/app/assets/stylesheets/effective_datatables.scss +1 -0
  18. data/app/assets/stylesheets/effective_datatables/_overrides.scss +15 -2
  19. data/app/controllers/effective/datatables_controller.rb +25 -0
  20. data/app/helpers/effective_datatables_helper.rb +3 -1
  21. data/app/helpers/effective_datatables_private_helper.rb +28 -4
  22. data/app/models/effective/datatable.rb +23 -3
  23. data/app/models/effective/effective_datatable/dsl.rb +1 -1
  24. data/app/models/effective/effective_datatable/dsl/datatable.rb +65 -26
  25. data/app/models/effective/effective_datatable/format.rb +7 -4
  26. data/app/models/effective/effective_datatable/resource.rb +0 -2
  27. data/app/models/effective/effective_datatable/state.rb +15 -8
  28. data/app/views/effective/datatables/_reorder_column.html.haml +5 -0
  29. data/config/routes.rb +1 -0
  30. data/lib/effective_datatables/version.rb +1 -1
  31. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4dabd6230608916ad4260f443412c2b81bb4ec80
4
- data.tar.gz: 26a82f35207f348157706794fcb818a1b58e77db
3
+ metadata.gz: 7ce4be5421f36169833da3142e5f171f63f794a6
4
+ data.tar.gz: ca64b139e8056c55d46723abfca5ead307b8667b
5
5
  SHA512:
6
- metadata.gz: 17fd09f4caa9cfd14798fe9744a627e355f4f90504cb40a02943b35155fe25d15a4e7708bfab3d4a167b1acd3a9260ba2d8fa95117566cb085b21679099258de
7
- data.tar.gz: b31833cadf5f81ba9e5e7cb69260891fb2498647b872243afd9cec216070ff8c9d40be2da3137b78b06ff60b21f766d2ea081cbc2f6c2dd77e3a10b68868dbaf
6
+ metadata.gz: 9036c5d175ae16eb22ea83892b49e72c5916dd4d53bfda0863fe7d7f6f6c26abf68715cdb29b2de15822b4486353b18042b473d8f8ff1569c0cca7ded5c943fb
7
+ data.tar.gz: b7519014581a8e3e07f496bf17dba5d8a847ad129c07ed0604ae9bdb273d7968743b3e7fe7aece5adb35ad471a1844be0d4cfc4512df3557fd203da68aa53e59
data/README.md CHANGED
@@ -571,6 +571,24 @@ When not specified, effective_datatables will sort by the first defined column.
571
571
  order :created_at, :asc|:desc
572
572
  ```
573
573
 
574
+ ### reorder
575
+
576
+ Enables drag-and-drop row re-ordering.
577
+
578
+ Only works with ActiveRecord collections.
579
+
580
+ The underlying field must be an Integer, and it's assumed to be a sequential list of unique numbers.
581
+
582
+ When a drag and drop is completed, a POST request is made to the datatables#reorder action that will update the indexes.
583
+
584
+ Both zero and one based lists will work.
585
+
586
+ ```ruby
587
+ reorder :position
588
+ ```
589
+
590
+ Using `reorder` will sort the collection by this field and disable all other column sorting.
591
+
574
592
  ### aggregate
575
593
 
576
594
  The `aggregate` command inserts a row in the table's `tfoot`.
@@ -658,7 +676,7 @@ filters do
658
676
  filter :end_date, nil, parse: -> { |term| Time.zone.local(term).end_of_day }
659
677
  filter :user, current_user, as: :select, collection: User.all
660
678
  filter :year, 2018, as: :select, collection: [2018, 2017], label: false, include_blank: false
661
- filter :reports, '2018', as: :grouped_select, collection: [['Years', [['2017', '2017'], ['2018', '2018']]], ['Months', [['January', '1'], ['February', '2']]]], group_method: :last
679
+ filter :year_group, '2018', as: :select, grouped: true, collection: { 'Years' => [['2017', 2017], ['2018', 2018]], 'Months' => [['January', 1], ['February', 2]] }
662
680
  end
663
681
  ```
664
682
 
@@ -57,6 +57,7 @@ $.extend( true, DataTable.Buttons.defaults, {
57
57
  } );
58
58
 
59
59
  DataTable.ext.buttons.collection.className += ' dropdown-toggle';
60
+ DataTable.ext.buttons.collection.rightAlignClassName = 'dropdown-menu-right';
60
61
 
61
62
  return DataTable.Buttons;
62
63
  }));
@@ -108,6 +108,7 @@ $.extend( DataTable.ext.buttons, {
108
108
  },
109
109
  init: function ( dt, button, conf ) {
110
110
  var that = this;
111
+ button.attr( 'data-cv-idx', conf.columns );
111
112
 
112
113
  dt
113
114
  .on( 'column-visibility.dt'+conf.namespace, function (e, settings) {
@@ -122,8 +123,17 @@ $.extend( DataTable.ext.buttons, {
122
123
  return;
123
124
  }
124
125
 
125
- that.text( conf._columnText( dt, conf ) );
126
- that.active( dt.column( conf.columns ).visible() );
126
+ conf.columns = $.inArray( conf.columns, details.mapping );
127
+ button.attr( 'data-cv-idx', conf.columns );
128
+
129
+ // Reorder buttons for new table order
130
+ button
131
+ .parent()
132
+ .children('[data-cv-idx]')
133
+ .sort( function (a, b) {
134
+ return (a.getAttribute('data-cv-idx')*1) - (b.getAttribute('data-cv-idx')*1);
135
+ } )
136
+ .appendTo(button.parent());
127
137
  } );
128
138
 
129
139
  this.active( dt.column( conf.columns ).visible() );
@@ -48,6 +48,20 @@ function _pdfMake () {
48
48
  return pdfmake || window.pdfMake;
49
49
  }
50
50
 
51
+ DataTable.Buttons.pdfMake = function (_) {
52
+ if ( ! _ ) {
53
+ return _pdfMake();
54
+ }
55
+ pdfmake = m_ake;
56
+ }
57
+
58
+ DataTable.Buttons.jszip = function (_) {
59
+ if ( ! _ ) {
60
+ return _jsZip();
61
+ }
62
+ jszip = _;
63
+ }
64
+
51
65
 
52
66
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
53
67
  * FileSaver.js dependency
@@ -422,6 +436,9 @@ function _addToZip( zip, obj ) {
422
436
 
423
437
  // Return namespace attributes to being as such
424
438
  str = str.replace( /_dt_b_namespace_token_/g, ':' );
439
+
440
+ // Remove testing name space that IE puts into the space preserve attr
441
+ str = str.replace( /xmlns:NS[\d]+="" NS[\d]+:/g, '' );
425
442
  }
426
443
 
427
444
  // Safari, IE and Edge will put empty name space attributes onto
@@ -505,11 +522,11 @@ function _excelColWidth( data, col ) {
505
522
 
506
523
  // Max width rather than having potentially massive column widths
507
524
  if ( max > 40 ) {
508
- return 52; // 40 * 1.3
525
+ return 54; // 40 * 1.35
509
526
  }
510
527
  }
511
528
 
512
- max *= 1.3;
529
+ max *= 1.35;
513
530
 
514
531
  // And a min width
515
532
  return max > 6 ? max : 6;
@@ -550,8 +567,9 @@ var excelStrings = {
550
567
  '<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>'+
551
568
  '</bookViews>'+
552
569
  '<sheets>'+
553
- '<sheet name="" sheetId="1" r:id="rId1"/>'+
570
+ '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>'+
554
571
  '</sheets>'+
572
+ '<definedNames/>'+
555
573
  '</workbook>',
556
574
 
557
575
  "xl/worksheets/sheet1.xml":
@@ -992,6 +1010,7 @@ DataTable.ext.buttons.excelHtml5 = {
992
1010
 
993
1011
  var that = this;
994
1012
  var rowPos = 0;
1013
+ var dataStartRow, dataEndRow;
995
1014
  var getXml = function ( type ) {
996
1015
  var str = excelStrings[ type ];
997
1016
 
@@ -1124,8 +1143,6 @@ DataTable.ext.buttons.excelHtml5 = {
1124
1143
  rowPos++;
1125
1144
  };
1126
1145
 
1127
- $( 'sheets sheet', xlsx.xl['workbook.xml'] ).attr( 'name', _sheetname( config ) );
1128
-
1129
1146
  if ( config.customizeData ) {
1130
1147
  config.customizeData( data );
1131
1148
  }
@@ -1154,16 +1171,21 @@ DataTable.ext.buttons.excelHtml5 = {
1154
1171
  mergeCells( rowPos, data.header.length-1 );
1155
1172
  }
1156
1173
 
1174
+
1157
1175
  // Table itself
1158
1176
  if ( config.header ) {
1159
1177
  addRow( data.header, rowPos );
1160
1178
  $('row:last c', rels).attr( 's', '2' ); // bold
1161
1179
  }
1162
1180
 
1181
+ dataStartRow = rowPos;
1182
+
1163
1183
  for ( var n=0, ie=data.body.length ; n<ie ; n++ ) {
1164
1184
  addRow( data.body[n], rowPos );
1165
1185
  }
1166
1186
 
1187
+ dataEndRow = rowPos;
1188
+
1167
1189
  if ( config.footer && data.footer ) {
1168
1190
  addRow( data.footer, rowPos);
1169
1191
  $('row:last c', rels).attr( 's', '2' ); // bold
@@ -1190,6 +1212,29 @@ DataTable.ext.buttons.excelHtml5 = {
1190
1212
  } ) );
1191
1213
  }
1192
1214
 
1215
+ // Auto filter for columns
1216
+ $('mergeCells', rels).before( _createNode( rels, 'autoFilter', {
1217
+ attr: {
1218
+ ref: 'A'+dataStartRow+':'+createCellPos(data.header.length-1)+dataEndRow
1219
+ }
1220
+ } ) );
1221
+
1222
+ // Workbook modifications
1223
+ var workbook = xlsx.xl['workbook.xml'];
1224
+
1225
+ $( 'sheets sheet', workbook ).attr( 'name', _sheetname( config ) );
1226
+
1227
+ if ( config.autoFilter ) {
1228
+ $('definedNames', workbook).append( _createNode( workbook, 'definedName', {
1229
+ attr: {
1230
+ name: '_xlnm._FilterDatabase',
1231
+ localSheetId: '0',
1232
+ hidden: 1
1233
+ },
1234
+ text: _sheetname(config)+'!$A$'+dataStartRow+':'+createCellPos(data.header.length-1)+dataEndRow
1235
+ } ) );
1236
+ }
1237
+
1193
1238
  // Let the developer customise the document if they want to
1194
1239
  if ( config.customize ) {
1195
1240
  config.customize( xlsx, config, dt );
@@ -1244,7 +1289,11 @@ DataTable.ext.buttons.excelHtml5 = {
1244
1289
 
1245
1290
  messageBottom: '*',
1246
1291
 
1247
- createEmptyCells: false
1292
+ createEmptyCells: false,
1293
+
1294
+ autoFilter: false,
1295
+
1296
+ sheetName: ''
1248
1297
  };
1249
1298
 
1250
1299
  //
@@ -1280,6 +1329,9 @@ DataTable.ext.buttons.pdfHtml5 = {
1280
1329
 
1281
1330
  for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
1282
1331
  rows.push( $.map( data.body[i], function ( d ) {
1332
+ if ( d === null || d === undefined ) {
1333
+ d = '';
1334
+ }
1283
1335
  return {
1284
1336
  text: typeof d === 'string' ? d : d+'',
1285
1337
  style: i % 2 ? 'tableBodyEven' : 'tableBodyOdd'
@@ -91,9 +91,13 @@ DataTable.ext.buttons.print = {
91
91
  $.extend( {decodeEntities: false}, config.exportOptions ) // XSS protection
92
92
  );
93
93
  var exportInfo = dt.buttons.exportInfo( config );
94
- var columnClasses = $.map( dt.settings()[0].aoColumns, function (col, key) {
95
- return col.sClass;
96
- } );
94
+ var columnClasses = dt
95
+ .columns( config.exportOptions.columns )
96
+ .flatten()
97
+ .map( function (idx) {
98
+ return dt.settings()[0].aoColumns[dt.column(idx).index()].sClass;
99
+ } )
100
+ .toArray();
97
101
 
98
102
  var addRow = function ( d, tag ) {
99
103
  var str = '<tr>';
@@ -169,12 +173,19 @@ DataTable.ext.buttons.print = {
169
173
  }
170
174
 
171
175
  // Allow stylesheets time to load
172
- win.setTimeout( function () {
176
+ var autoPrint = function () {
173
177
  if ( config.autoPrint ) {
174
178
  win.print(); // blocking - so close will not
175
179
  win.close(); // execute until this is done
176
180
  }
177
- }, 1000 );
181
+ };
182
+
183
+ if ( navigator.userAgent.match(/Trident\/\d.\d/) ) { // IE needs to call this without a setTimeout
184
+ autoPrint();
185
+ }
186
+ else {
187
+ win.setTimeout( autoPrint, 1000 );
188
+ }
178
189
  },
179
190
 
180
191
  title: '*',
@@ -1,4 +1,4 @@
1
- /*! Buttons for DataTables 1.5.2
1
+ /*! Buttons for DataTables 1.5.4
2
2
  * ©2016-2018 SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
@@ -489,7 +489,7 @@ $.extend( Buttons.prototype, {
489
489
  var collectionDom = this.c.dom.collection;
490
490
  built.collection = $('<'+collectionDom.tag+'/>')
491
491
  .addClass( collectionDom.className )
492
- .attr( 'role', 'menu') ;
492
+ .attr( 'role', 'menu' ) ;
493
493
  built.conf._collection = built.collection;
494
494
 
495
495
  this._expandButton( built.buttons, built.conf.buttons, true, attachPoint );
@@ -908,20 +908,23 @@ $.extend( Buttons.prototype, {
908
908
  * @param {string} Class to assign to the background
909
909
  * @static
910
910
  */
911
- Buttons.background = function ( show, className, fade ) {
911
+ Buttons.background = function ( show, className, fade, insertPoint ) {
912
912
  if ( fade === undefined ) {
913
913
  fade = 400;
914
914
  }
915
+ if ( ! insertPoint ) {
916
+ insertPoint = document.body;
917
+ }
915
918
 
916
919
  if ( show ) {
917
920
  $('<div/>')
918
921
  .addClass( className )
919
922
  .css( 'display', 'none' )
920
- .appendTo( 'body' )
923
+ .insertAfter( insertPoint )
921
924
  .fadeIn( fade );
922
925
  }
923
926
  else {
924
- $('body > div.'+className)
927
+ $('div.'+className)
925
928
  .fadeOut( fade, function () {
926
929
  $(this)
927
930
  .removeClass( className )
@@ -1145,7 +1148,10 @@ Buttons.defaults = {
1145
1148
  className: 'dt-button-collection'
1146
1149
  },
1147
1150
  button: {
1148
- tag: 'button',
1151
+ // Flash buttons will not work with `<button>` in IE - it has to be `<a>`
1152
+ tag: 'ActiveXObject' in window ?
1153
+ 'a' :
1154
+ 'button',
1149
1155
  className: 'dt-button',
1150
1156
  active: 'active',
1151
1157
  disabled: 'disabled'
@@ -1162,7 +1168,7 @@ Buttons.defaults = {
1162
1168
  * @type {string}
1163
1169
  * @static
1164
1170
  */
1165
- Buttons.version = '1.5.2';
1171
+ Buttons.version = '1.5.4';
1166
1172
 
1167
1173
 
1168
1174
  $.extend( _dtButtons, {
@@ -1190,13 +1196,15 @@ $.extend( _dtButtons, {
1190
1196
  insertPoint = document.body.lastChild;
1191
1197
  }
1192
1198
 
1199
+ config._collection.find('.dt-button-collection-title').remove();
1200
+ config._collection.prepend('<div class="dt-button-collection-title">'+config.collectionTitle+'</div>');
1201
+
1193
1202
  config._collection
1194
1203
  .addClass( config.collectionLayout )
1195
1204
  .css( 'display', 'none' )
1196
1205
  .insertAfter( insertPoint )
1197
1206
  .fadeIn( config.fade );
1198
1207
 
1199
-
1200
1208
  var position = config._collection.css( 'position' );
1201
1209
 
1202
1210
  if ( multiLevel && position === 'absolute' ) {
@@ -1226,6 +1234,12 @@ $.extend( _dtButtons, {
1226
1234
  config._collection.css( 'top', hostPosition.top - config._collection.outerHeight() - 5);
1227
1235
  }
1228
1236
 
1237
+ // Right alignment is enabled on a class, e.g. bootstrap:
1238
+ // $.fn.dataTable.Buttons.defaults.dom.collection.className += " dropdown-menu-right";
1239
+ if ( config._collection.hasClass( config.rightAlignClassName ) ) {
1240
+ config._collection.css( 'left', hostPosition.left + host.outerWidth() - config._collection.outerWidth() );
1241
+ }
1242
+
1229
1243
  // Right alignment in table container
1230
1244
  var listRight = hostPosition.left + config._collection.outerWidth();
1231
1245
  var tableRight = tableContainer.offset().left + tableContainer.width();
@@ -1250,7 +1264,7 @@ $.extend( _dtButtons, {
1250
1264
  }
1251
1265
 
1252
1266
  if ( config.background ) {
1253
- Buttons.background( true, config.backgroundClassName, config.fade );
1267
+ Buttons.background( true, config.backgroundClassName, config.fade, insertPoint );
1254
1268
  }
1255
1269
 
1256
1270
  var close = function () {
@@ -1260,7 +1274,7 @@ $.extend( _dtButtons, {
1260
1274
  } );
1261
1275
 
1262
1276
  $('div.dt-button-background').off( 'click.dtb-collection' );
1263
- Buttons.background( false, config.backgroundClassName, config.fade );
1277
+ Buttons.background( false, config.backgroundClassName, config.fade, insertPoint );
1264
1278
 
1265
1279
  $('body').off( '.dtb-collection' );
1266
1280
  dt.off( 'buttons-action.b-internal' );
@@ -1299,7 +1313,9 @@ $.extend( _dtButtons, {
1299
1313
  },
1300
1314
  background: true,
1301
1315
  collectionLayout: '',
1316
+ collectionTitle: '',
1302
1317
  backgroundClassName: 'dt-button-background',
1318
+ rightAlignClassName: 'dt-button-right',
1303
1319
  autoClose: false,
1304
1320
  fade: 400,
1305
1321
  attr: {
@@ -0,0 +1,818 @@
1
+ /*! RowReorder 1.2.4
2
+ * 2015-2018 SpryMedia Ltd - datatables.net/license
3
+ */
4
+
5
+ /**
6
+ * @summary RowReorder
7
+ * @description Row reordering extension for DataTables
8
+ * @version 1.2.4
9
+ * @file dataTables.rowReorder.js
10
+ * @author SpryMedia Ltd (www.sprymedia.co.uk)
11
+ * @contact www.sprymedia.co.uk/contact
12
+ * @copyright Copyright 2015-2018 SpryMedia Ltd.
13
+ *
14
+ * This source file is free software, available under the following license:
15
+ * MIT license - http://datatables.net/license/mit
16
+ *
17
+ * This source file is distributed in the hope that it will be useful, but
18
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
20
+ *
21
+ * For details please refer to: http://www.datatables.net
22
+ */
23
+
24
+ (function( factory ){
25
+ if ( typeof define === 'function' && define.amd ) {
26
+ // AMD
27
+ define( ['jquery', 'datatables.net'], function ( $ ) {
28
+ return factory( $, window, document );
29
+ } );
30
+ }
31
+ else if ( typeof exports === 'object' ) {
32
+ // CommonJS
33
+ module.exports = function (root, $) {
34
+ if ( ! root ) {
35
+ root = window;
36
+ }
37
+
38
+ if ( ! $ || ! $.fn.dataTable ) {
39
+ $ = require('datatables.net')(root, $).$;
40
+ }
41
+
42
+ return factory( $, root, root.document );
43
+ };
44
+ }
45
+ else {
46
+ // Browser
47
+ factory( jQuery, window, document );
48
+ }
49
+ }(function( $, window, document, undefined ) {
50
+ 'use strict';
51
+ var DataTable = $.fn.dataTable;
52
+
53
+
54
+ /**
55
+ * RowReorder provides the ability in DataTables to click and drag rows to
56
+ * reorder them. When a row is dropped the data for the rows effected will be
57
+ * updated to reflect the change. Normally this data point should also be the
58
+ * column being sorted upon in the DataTable but this does not need to be the
59
+ * case. RowReorder implements a "data swap" method - so the rows being
60
+ * reordered take the value of the data point from the row that used to occupy
61
+ * the row's new position.
62
+ *
63
+ * Initialisation is done by either:
64
+ *
65
+ * * `rowReorder` parameter in the DataTable initialisation object
66
+ * * `new $.fn.dataTable.RowReorder( table, opts )` after DataTables
67
+ * initialisation.
68
+ *
69
+ * @class
70
+ * @param {object} settings DataTables settings object for the host table
71
+ * @param {object} [opts] Configuration options
72
+ * @requires jQuery 1.7+
73
+ * @requires DataTables 1.10.7+
74
+ */
75
+ var RowReorder = function ( dt, opts ) {
76
+ // Sanity check that we are using DataTables 1.10 or newer
77
+ if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
78
+ throw 'DataTables RowReorder requires DataTables 1.10.8 or newer';
79
+ }
80
+
81
+ // User and defaults configuration object
82
+ this.c = $.extend( true, {},
83
+ DataTable.defaults.rowReorder,
84
+ RowReorder.defaults,
85
+ opts
86
+ );
87
+
88
+ // Internal settings
89
+ this.s = {
90
+ /** @type {integer} Scroll body top cache */
91
+ bodyTop: null,
92
+
93
+ /** @type {DataTable.Api} DataTables' API instance */
94
+ dt: new DataTable.Api( dt ),
95
+
96
+ /** @type {function} Data fetch function */
97
+ getDataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc ),
98
+
99
+ /** @type {array} Pixel positions for row insertion calculation */
100
+ middles: null,
101
+
102
+ /** @type {Object} Cached dimension information for use in the mouse move event handler */
103
+ scroll: {},
104
+
105
+ /** @type {integer} Interval object used for smooth scrolling */
106
+ scrollInterval: null,
107
+
108
+ /** @type {function} Data set function */
109
+ setDataFn: DataTable.ext.oApi._fnSetObjectDataFn( this.c.dataSrc ),
110
+
111
+ /** @type {Object} Mouse down information */
112
+ start: {
113
+ top: 0,
114
+ left: 0,
115
+ offsetTop: 0,
116
+ offsetLeft: 0,
117
+ nodes: []
118
+ },
119
+
120
+ /** @type {integer} Window height cached value */
121
+ windowHeight: 0,
122
+
123
+ /** @type {integer} Document outer height cached value */
124
+ documentOuterHeight: 0,
125
+
126
+ /** @type {integer} DOM clone outer height cached value */
127
+ domCloneOuterHeight: 0
128
+ };
129
+
130
+ // DOM items
131
+ this.dom = {
132
+ /** @type {jQuery} Cloned row being moved around */
133
+ clone: null,
134
+
135
+ /** @type {jQuery} DataTables scrolling container */
136
+ dtScroll: $('div.dataTables_scrollBody', this.s.dt.table().container())
137
+ };
138
+
139
+ // Check if row reorder has already been initialised on this table
140
+ var settings = this.s.dt.settings()[0];
141
+ var exisiting = settings.rowreorder;
142
+ if ( exisiting ) {
143
+ return exisiting;
144
+ }
145
+
146
+ settings.rowreorder = this;
147
+ this._constructor();
148
+ };
149
+
150
+
151
+ $.extend( RowReorder.prototype, {
152
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
153
+ * Constructor
154
+ */
155
+
156
+ /**
157
+ * Initialise the RowReorder instance
158
+ *
159
+ * @private
160
+ */
161
+ _constructor: function ()
162
+ {
163
+ var that = this;
164
+ var dt = this.s.dt;
165
+ var table = $( dt.table().node() );
166
+
167
+ // Need to be able to calculate the row positions relative to the table
168
+ if ( table.css('position') === 'static' ) {
169
+ table.css( 'position', 'relative' );
170
+ }
171
+
172
+ // listen for mouse down on the target column - we have to implement
173
+ // this rather than using HTML5 drag and drop as drag and drop doesn't
174
+ // appear to work on table rows at this time. Also mobile browsers are
175
+ // not supported.
176
+ // Use `table().container()` rather than just the table node for IE8 -
177
+ // otherwise it only works once...
178
+ $(dt.table().container()).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) {
179
+ if ( ! that.c.enable ) {
180
+ return;
181
+ }
182
+
183
+ // Ignore excluded children of the selector
184
+ if ( $(e.target).is(that.c.excludedChildren) ) {
185
+ return true;
186
+ }
187
+
188
+ var tr = $(this).closest('tr');
189
+ var row = dt.row( tr );
190
+
191
+ // Double check that it is a DataTable row
192
+ if ( row.any() ) {
193
+ that._emitEvent( 'pre-row-reorder', {
194
+ node: row.node(),
195
+ index: row.index()
196
+ } );
197
+
198
+ that._mouseDown( e, tr );
199
+ return false;
200
+ }
201
+ } );
202
+
203
+ dt.on( 'destroy.rowReorder', function () {
204
+ $(dt.table().container()).off( '.rowReorder' );
205
+ dt.off( '.rowReorder' );
206
+ } );
207
+ },
208
+
209
+
210
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
211
+ * Private methods
212
+ */
213
+
214
+ /**
215
+ * Cache the measurements that RowReorder needs in the mouse move handler
216
+ * to attempt to speed things up, rather than reading from the DOM.
217
+ *
218
+ * @private
219
+ */
220
+ _cachePositions: function ()
221
+ {
222
+ var dt = this.s.dt;
223
+
224
+ // Frustratingly, if we add `position:relative` to the tbody, the
225
+ // position is still relatively to the parent. So we need to adjust
226
+ // for that
227
+ var headerHeight = $( dt.table().node() ).find('thead').outerHeight();
228
+
229
+ // Need to pass the nodes through jQuery to get them in document order,
230
+ // not what DataTables thinks it is, since we have been altering the
231
+ // order
232
+ var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
233
+ var tops = $.map( nodes, function ( node, i ) {
234
+ return $(node).position().top - headerHeight;
235
+ } );
236
+
237
+ var middles = $.map( tops, function ( top, i ) {
238
+ return tops.length < i-1 ?
239
+ (top + tops[i+1]) / 2 :
240
+ (top + top + $( dt.row( ':last-child' ).node() ).outerHeight() ) / 2;
241
+ } );
242
+
243
+ this.s.middles = middles;
244
+ this.s.bodyTop = $( dt.table().body() ).offset().top;
245
+ this.s.windowHeight = $(window).height();
246
+ this.s.documentOuterHeight = $(document).outerHeight();
247
+ },
248
+
249
+
250
+ /**
251
+ * Clone a row so it can be floated around the screen
252
+ *
253
+ * @param {jQuery} target Node to be cloned
254
+ * @private
255
+ */
256
+ _clone: function ( target )
257
+ {
258
+ var dt = this.s.dt;
259
+ var clone = $( dt.table().node().cloneNode(false) )
260
+ .addClass( 'dt-rowReorder-float' )
261
+ .append('<tbody/>')
262
+ .append( target.clone( false ) );
263
+
264
+ // Match the table and column widths - read all sizes before setting
265
+ // to reduce reflows
266
+ var tableWidth = target.outerWidth();
267
+ var tableHeight = target.outerHeight();
268
+ var sizes = target.children().map( function () {
269
+ return $(this).width();
270
+ } );
271
+
272
+ clone
273
+ .width( tableWidth )
274
+ .height( tableHeight )
275
+ .find('tr').children().each( function (i) {
276
+ this.style.width = sizes[i]+'px';
277
+ } );
278
+
279
+ // Insert into the document to have it floating around
280
+ clone.appendTo( 'body' );
281
+
282
+ this.dom.clone = clone;
283
+ this.s.domCloneOuterHeight = clone.outerHeight();
284
+ },
285
+
286
+
287
+ /**
288
+ * Update the cloned item's position in the document
289
+ *
290
+ * @param {object} e Event giving the mouse's position
291
+ * @private
292
+ */
293
+ _clonePosition: function ( e )
294
+ {
295
+ var start = this.s.start;
296
+ var topDiff = this._eventToPage( e, 'Y' ) - start.top;
297
+ var leftDiff = this._eventToPage( e, 'X' ) - start.left;
298
+ var snap = this.c.snapX;
299
+ var left;
300
+ var top = topDiff + start.offsetTop;
301
+
302
+ if ( snap === true ) {
303
+ left = start.offsetLeft;
304
+ }
305
+ else if ( typeof snap === 'number' ) {
306
+ left = start.offsetLeft + snap;
307
+ }
308
+ else {
309
+ left = leftDiff + start.offsetLeft;
310
+ }
311
+
312
+ if(top < 0) {
313
+ top = 0
314
+ }
315
+ else if(top + this.s.domCloneOuterHeight > this.s.documentOuterHeight) {
316
+ top = this.s.documentOuterHeight - this.s.domCloneOuterHeight;
317
+ }
318
+
319
+ this.dom.clone.css( {
320
+ top: top,
321
+ left: left
322
+ } );
323
+ },
324
+
325
+
326
+ /**
327
+ * Emit an event on the DataTable for listeners
328
+ *
329
+ * @param {string} name Event name
330
+ * @param {array} args Event arguments
331
+ * @private
332
+ */
333
+ _emitEvent: function ( name, args )
334
+ {
335
+ this.s.dt.iterator( 'table', function ( ctx, i ) {
336
+ $(ctx.nTable).triggerHandler( name+'.dt', args );
337
+ } );
338
+ },
339
+
340
+
341
+ /**
342
+ * Get pageX/Y position from an event, regardless of if it is a mouse or
343
+ * touch event.
344
+ *
345
+ * @param {object} e Event
346
+ * @param {string} pos X or Y (must be a capital)
347
+ * @private
348
+ */
349
+ _eventToPage: function ( e, pos )
350
+ {
351
+ if ( e.type.indexOf( 'touch' ) !== -1 ) {
352
+ return e.originalEvent.touches[0][ 'page'+pos ];
353
+ }
354
+
355
+ return e[ 'page'+pos ];
356
+ },
357
+
358
+
359
+ /**
360
+ * Mouse down event handler. Read initial positions and add event handlers
361
+ * for the move.
362
+ *
363
+ * @param {object} e Mouse event
364
+ * @param {jQuery} target TR element that is to be moved
365
+ * @private
366
+ */
367
+ _mouseDown: function ( e, target )
368
+ {
369
+ var that = this;
370
+ var dt = this.s.dt;
371
+ var start = this.s.start;
372
+
373
+ var offset = target.offset();
374
+ start.top = this._eventToPage( e, 'Y' );
375
+ start.left = this._eventToPage( e, 'X' );
376
+ start.offsetTop = offset.top;
377
+ start.offsetLeft = offset.left;
378
+ start.nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
379
+
380
+ this._cachePositions();
381
+ this._clone( target );
382
+ this._clonePosition( e );
383
+
384
+ this.dom.target = target;
385
+ target.addClass( 'dt-rowReorder-moving' );
386
+
387
+ $( document )
388
+ .on( 'mouseup.rowReorder touchend.rowReorder', function (e) {
389
+ that._mouseUp(e);
390
+ } )
391
+ .on( 'mousemove.rowReorder touchmove.rowReorder', function (e) {
392
+ that._mouseMove(e);
393
+ } );
394
+
395
+ // Check if window is x-scrolling - if not, disable it for the duration
396
+ // of the drag
397
+ if ( $(window).width() === $(document).width() ) {
398
+ $(document.body).addClass( 'dt-rowReorder-noOverflow' );
399
+ }
400
+
401
+ // Cache scrolling information so mouse move doesn't need to read.
402
+ // This assumes that the window and DT scroller will not change size
403
+ // during an row drag, which I think is a fair assumption
404
+ var scrollWrapper = this.dom.dtScroll;
405
+ this.s.scroll = {
406
+ windowHeight: $(window).height(),
407
+ windowWidth: $(window).width(),
408
+ dtTop: scrollWrapper.length ? scrollWrapper.offset().top : null,
409
+ dtLeft: scrollWrapper.length ? scrollWrapper.offset().left : null,
410
+ dtHeight: scrollWrapper.length ? scrollWrapper.outerHeight() : null,
411
+ dtWidth: scrollWrapper.length ? scrollWrapper.outerWidth() : null
412
+ };
413
+ },
414
+
415
+
416
+ /**
417
+ * Mouse move event handler - move the cloned row and shuffle the table's
418
+ * rows if required.
419
+ *
420
+ * @param {object} e Mouse event
421
+ * @private
422
+ */
423
+ _mouseMove: function ( e )
424
+ {
425
+ this._clonePosition( e );
426
+
427
+ // Transform the mouse position into a position in the table's body
428
+ var bodyY = this._eventToPage( e, 'Y' ) - this.s.bodyTop;
429
+ var middles = this.s.middles;
430
+ var insertPoint = null;
431
+ var dt = this.s.dt;
432
+ var body = dt.table().body();
433
+
434
+ // Determine where the row should be inserted based on the mouse
435
+ // position
436
+ for ( var i=0, ien=middles.length ; i<ien ; i++ ) {
437
+ if ( bodyY < middles[i] ) {
438
+ insertPoint = i;
439
+ break;
440
+ }
441
+ }
442
+
443
+ if ( insertPoint === null ) {
444
+ insertPoint = middles.length;
445
+ }
446
+
447
+ // Perform the DOM shuffle if it has changed from last time
448
+ if ( this.s.lastInsert === null || this.s.lastInsert !== insertPoint ) {
449
+ if ( insertPoint === 0 ) {
450
+ this.dom.target.prependTo( body );
451
+ }
452
+ else {
453
+ var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
454
+
455
+ if ( insertPoint > this.s.lastInsert ) {
456
+ this.dom.target.insertAfter( nodes[ insertPoint-1 ] );
457
+ }
458
+ else {
459
+ this.dom.target.insertBefore( nodes[ insertPoint ] );
460
+ }
461
+ }
462
+
463
+ this._cachePositions();
464
+
465
+ this.s.lastInsert = insertPoint;
466
+ }
467
+
468
+ this._shiftScroll( e );
469
+ },
470
+
471
+
472
+ /**
473
+ * Mouse up event handler - release the event handlers and perform the
474
+ * table updates
475
+ *
476
+ * @param {object} e Mouse event
477
+ * @private
478
+ */
479
+ _mouseUp: function ( e )
480
+ {
481
+ var that = this;
482
+ var dt = this.s.dt;
483
+ var i, ien;
484
+ var dataSrc = this.c.dataSrc;
485
+
486
+ this.dom.clone.remove();
487
+ this.dom.clone = null;
488
+
489
+ this.dom.target.removeClass( 'dt-rowReorder-moving' );
490
+ //this.dom.target = null;
491
+
492
+ $(document).off( '.rowReorder' );
493
+ $(document.body).removeClass( 'dt-rowReorder-noOverflow' );
494
+
495
+ clearInterval( this.s.scrollInterval );
496
+ this.s.scrollInterval = null;
497
+
498
+ // Calculate the difference
499
+ var startNodes = this.s.start.nodes;
500
+ var endNodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
501
+ var idDiff = {};
502
+ var fullDiff = [];
503
+ var diffNodes = [];
504
+ var getDataFn = this.s.getDataFn;
505
+ var setDataFn = this.s.setDataFn;
506
+
507
+ for ( i=0, ien=startNodes.length ; i<ien ; i++ ) {
508
+ if ( startNodes[i] !== endNodes[i] ) {
509
+ var id = dt.row( endNodes[i] ).id();
510
+ var endRowData = dt.row( endNodes[i] ).data();
511
+ var startRowData = dt.row( startNodes[i] ).data();
512
+
513
+ if ( id ) {
514
+ idDiff[ id ] = getDataFn( startRowData );
515
+ }
516
+
517
+ fullDiff.push( {
518
+ node: endNodes[i],
519
+ oldData: getDataFn( endRowData ),
520
+ newData: getDataFn( startRowData ),
521
+ newPosition: i,
522
+ oldPosition: $.inArray( endNodes[i], startNodes )
523
+ } );
524
+
525
+ diffNodes.push( endNodes[i] );
526
+ }
527
+ }
528
+
529
+ // Create event args
530
+ var eventArgs = [ fullDiff, {
531
+ dataSrc: dataSrc,
532
+ nodes: diffNodes,
533
+ values: idDiff,
534
+ triggerRow: dt.row( this.dom.target )
535
+ } ];
536
+
537
+ // Emit event
538
+ this._emitEvent( 'row-reorder', eventArgs );
539
+
540
+ var update = function () {
541
+ if ( that.c.update ) {
542
+ for ( i=0, ien=fullDiff.length ; i<ien ; i++ ) {
543
+ var row = dt.row( fullDiff[i].node );
544
+ var rowData = row.data();
545
+
546
+ setDataFn( rowData, fullDiff[i].newData );
547
+
548
+ // Invalidate the cell that has the same data source as the dataSrc
549
+ dt.columns().every( function () {
550
+ if ( this.dataSrc() === dataSrc ) {
551
+ dt.cell( fullDiff[i].node, this.index() ).invalidate( 'data' );
552
+ }
553
+ } );
554
+ }
555
+
556
+ // Trigger row reordered event
557
+ that._emitEvent( 'row-reordered', eventArgs );
558
+
559
+ dt.draw( false );
560
+ }
561
+ };
562
+
563
+ // Editor interface
564
+ if ( this.c.editor ) {
565
+ // Disable user interaction while Editor is submitting
566
+ this.c.enable = false;
567
+
568
+ this.c.editor
569
+ .edit(
570
+ diffNodes,
571
+ false,
572
+ $.extend( {submit: 'changed'}, this.c.formOptions )
573
+ )
574
+ .multiSet( dataSrc, idDiff )
575
+ .one( 'preSubmitCancelled.rowReorder', function () {
576
+ that.c.enable = true;
577
+ that.c.editor.off( '.rowReorder' );
578
+ dt.draw( false );
579
+ } )
580
+ .one( 'submitUnsuccessful.rowReorder', function () {
581
+ dt.draw( false );
582
+ } )
583
+ .one( 'submitSuccess.rowReorder', function () {
584
+ update();
585
+ } )
586
+ .one( 'submitComplete', function () {
587
+ that.c.enable = true;
588
+ that.c.editor.off( '.rowReorder' );
589
+ } )
590
+ .submit();
591
+ }
592
+ else {
593
+ update();
594
+ }
595
+ },
596
+
597
+
598
+ /**
599
+ * Move the window and DataTables scrolling during a drag to scroll new
600
+ * content into view.
601
+ *
602
+ * This matches the `_shiftScroll` method used in AutoFill, but only
603
+ * horizontal scrolling is considered here.
604
+ *
605
+ * @param {object} e Mouse move event object
606
+ * @private
607
+ */
608
+ _shiftScroll: function ( e )
609
+ {
610
+ var that = this;
611
+ var dt = this.s.dt;
612
+ var scroll = this.s.scroll;
613
+ var runInterval = false;
614
+ var scrollSpeed = 5;
615
+ var buffer = 65;
616
+ var
617
+ windowY = e.pageY - document.body.scrollTop,
618
+ windowVert,
619
+ dtVert;
620
+
621
+ // Window calculations - based on the mouse position in the window,
622
+ // regardless of scrolling
623
+ if ( windowY < buffer ) {
624
+ windowVert = scrollSpeed * -1;
625
+ }
626
+ else if ( windowY > scroll.windowHeight - buffer ) {
627
+ windowVert = scrollSpeed;
628
+ }
629
+
630
+ // DataTables scrolling calculations - based on the table's position in
631
+ // the document and the mouse position on the page
632
+ if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) {
633
+ dtVert = scrollSpeed * -1;
634
+ }
635
+ else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
636
+ dtVert = scrollSpeed;
637
+ }
638
+
639
+ // This is where it gets interesting. We want to continue scrolling
640
+ // without requiring a mouse move, so we need an interval to be
641
+ // triggered. The interval should continue until it is no longer needed,
642
+ // but it must also use the latest scroll commands (for example consider
643
+ // that the mouse might move from scrolling up to scrolling left, all
644
+ // with the same interval running. We use the `scroll` object to "pass"
645
+ // this information to the interval. Can't use local variables as they
646
+ // wouldn't be the ones that are used by an already existing interval!
647
+ if ( windowVert || dtVert ) {
648
+ scroll.windowVert = windowVert;
649
+ scroll.dtVert = dtVert;
650
+ runInterval = true;
651
+ }
652
+ else if ( this.s.scrollInterval ) {
653
+ // Don't need to scroll - remove any existing timer
654
+ clearInterval( this.s.scrollInterval );
655
+ this.s.scrollInterval = null;
656
+ }
657
+
658
+ // If we need to run the interval to scroll and there is no existing
659
+ // interval (if there is an existing one, it will continue to run)
660
+ if ( ! this.s.scrollInterval && runInterval ) {
661
+ this.s.scrollInterval = setInterval( function () {
662
+ // Don't need to worry about setting scroll <0 or beyond the
663
+ // scroll bound as the browser will just reject that.
664
+ if ( scroll.windowVert ) {
665
+ document.body.scrollTop += scroll.windowVert;
666
+ }
667
+
668
+ // DataTables scrolling
669
+ if ( scroll.dtVert ) {
670
+ var scroller = that.dom.dtScroll[0];
671
+
672
+ if ( scroll.dtVert ) {
673
+ scroller.scrollTop += scroll.dtVert;
674
+ }
675
+ }
676
+ }, 20 );
677
+ }
678
+ }
679
+ } );
680
+
681
+
682
+
683
+ /**
684
+ * RowReorder default settings for initialisation
685
+ *
686
+ * @namespace
687
+ * @name RowReorder.defaults
688
+ * @static
689
+ */
690
+ RowReorder.defaults = {
691
+ /**
692
+ * Data point in the host row's data source object for where to get and set
693
+ * the data to reorder. This will normally also be the sorting column.
694
+ *
695
+ * @type {Number}
696
+ */
697
+ dataSrc: 0,
698
+
699
+ /**
700
+ * Editor instance that will be used to perform the update
701
+ *
702
+ * @type {DataTable.Editor}
703
+ */
704
+ editor: null,
705
+
706
+ /**
707
+ * Enable / disable RowReorder's user interaction
708
+ * @type {Boolean}
709
+ */
710
+ enable: true,
711
+
712
+ /**
713
+ * Form options to pass to Editor when submitting a change in the row order.
714
+ * See the Editor `from-options` object for details of the options
715
+ * available.
716
+ * @type {Object}
717
+ */
718
+ formOptions: {},
719
+
720
+ /**
721
+ * Drag handle selector. This defines the element that when dragged will
722
+ * reorder a row.
723
+ *
724
+ * @type {String}
725
+ */
726
+ selector: 'td:first-child',
727
+
728
+ /**
729
+ * Optionally lock the dragged row's x-position. This can be `true` to
730
+ * fix the position match the host table's, `false` to allow free movement
731
+ * of the row, or a number to define an offset from the host table.
732
+ *
733
+ * @type {Boolean|number}
734
+ */
735
+ snapX: false,
736
+
737
+ /**
738
+ * Update the table's data on drop
739
+ *
740
+ * @type {Boolean}
741
+ */
742
+ update: true,
743
+
744
+ /**
745
+ * Selector for children of the drag handle selector that mouseDown events
746
+ * will be passed through to and drag will not activate
747
+ *
748
+ * @type {String}
749
+ */
750
+ excludedChildren: 'a'
751
+ };
752
+
753
+
754
+ /*
755
+ * API
756
+ */
757
+ var Api = $.fn.dataTable.Api;
758
+
759
+ // Doesn't do anything - work around for a bug in DT... Not documented
760
+ Api.register( 'rowReorder()', function () {
761
+ return this;
762
+ } );
763
+
764
+ Api.register( 'rowReorder.enable()', function ( toggle ) {
765
+ if ( toggle === undefined ) {
766
+ toggle = true;
767
+ }
768
+
769
+ return this.iterator( 'table', function ( ctx ) {
770
+ if ( ctx.rowreorder ) {
771
+ ctx.rowreorder.c.enable = toggle;
772
+ }
773
+ } );
774
+ } );
775
+
776
+ Api.register( 'rowReorder.disable()', function () {
777
+ return this.iterator( 'table', function ( ctx ) {
778
+ if ( ctx.rowreorder ) {
779
+ ctx.rowreorder.c.enable = false;
780
+ }
781
+ } );
782
+ } );
783
+
784
+
785
+ /**
786
+ * Version information
787
+ *
788
+ * @name RowReorder.version
789
+ * @static
790
+ */
791
+ RowReorder.version = '1.2.4';
792
+
793
+
794
+ $.fn.dataTable.RowReorder = RowReorder;
795
+ $.fn.DataTable.RowReorder = RowReorder;
796
+
797
+ // Attach a listener to the document which listens for DataTables initialisation
798
+ // events so we can automatically initialise
799
+ $(document).on( 'init.dt.dtr', function (e, settings, json) {
800
+ if ( e.namespace !== 'dt' ) {
801
+ return;
802
+ }
803
+
804
+ var init = settings.oInit.rowReorder;
805
+ var defaults = DataTable.defaults.rowReorder;
806
+
807
+ if ( init || defaults ) {
808
+ var opts = $.extend( {}, init, defaults );
809
+
810
+ if ( init !== false ) {
811
+ new RowReorder( settings, opts );
812
+ }
813
+ }
814
+ } );
815
+
816
+
817
+ return RowReorder;
818
+ }));