jquery-datatables-rails 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
- /*! Scroller 1.2.1
2
- * 2011-2014 SpryMedia Ltd - datatables.net/license
1
+ /*! Scroller 1.2.2
2
+ * ©2011-2014 SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
5
5
  /**
6
6
  * @summary Scroller
7
7
  * @description Virtual rendering for DataTables
8
- * @version 1.2.1
8
+ * @version 1.2.2
9
9
  * @file dataTables.scroller.js
10
10
  * @author SpryMedia Ltd (www.sprymedia.co.uk)
11
11
  * @contact www.sprymedia.co.uk/contact
@@ -126,7 +126,7 @@ var Scroller = function ( oDTSettings, oOpts ) {
126
126
 
127
127
  /**
128
128
  * Pixel location of the boundary for when the next data set should be loaded and drawn
129
- * when scrolling down the way. Note that this is actually caluated as the offset from
129
+ * when scrolling down the way. Note that this is actually calculated as the offset from
130
130
  * the top.
131
131
  * @type int
132
132
  * @default 0
@@ -187,7 +187,8 @@ var Scroller = function ( oDTSettings, oOpts ) {
187
187
  },
188
188
 
189
189
  topRowFloat: 0,
190
- scrollDrawDiff: null
190
+ scrollDrawDiff: null,
191
+ loaderVisible: false
191
192
  };
192
193
 
193
194
  // @todo The defaults should extend a `c` property and the internal settings
@@ -204,9 +205,10 @@ var Scroller = function ( oDTSettings, oOpts ) {
204
205
  *
205
206
  */
206
207
  this.dom = {
207
- "force": document.createElement('div'),
208
+ "force": document.createElement('div'),
208
209
  "scroller": null,
209
- "table": null
210
+ "table": null,
211
+ "loader": null
210
212
  };
211
213
 
212
214
  /* Attach the instance to the DataTables instance so it can be accessed */
@@ -302,7 +304,7 @@ Scroller.prototype = /** @lends Scroller.prototype */{
302
304
  /**
303
305
  * Calculate the row number that will be found at the given pixel position (y-scroll)
304
306
  * @param {int} iRow Row index to scroll to
305
- * @param {bool} [bAnimate=true] Animate the transision or not
307
+ * @param {bool} [bAnimate=true] Animate the transition or not
306
308
  * @returns {void}
307
309
  * @example
308
310
  * $(document).ready(function() {
@@ -354,7 +356,7 @@ Scroller.prototype = /** @lends Scroller.prototype */{
354
356
  // the final scroll event fired
355
357
  setTimeout( function () {
356
358
  that.s.ani = false;
357
- }, 0 );
359
+ }, 25 );
358
360
  } );
359
361
  }
360
362
  else
@@ -370,7 +372,7 @@ Scroller.prototype = /** @lends Scroller.prototype */{
370
372
  * rendering. This can be particularly useful if the table is initially
371
373
  * drawn in a hidden element - for example in a tab.
372
374
  * @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with
373
- * the new dimentions forming the basis for the draw.
375
+ * the new dimensions forming the basis for the draw.
374
376
  * @returns {void}
375
377
  * @example
376
378
  * $(document).ready(function() {
@@ -406,7 +408,7 @@ Scroller.prototype = /** @lends Scroller.prototype */{
406
408
  this.s.viewportRows = parseInt( heights.viewport / heights.row, 10 )+1;
407
409
  this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer;
408
410
 
409
- if ( typeof bRedraw == 'undefined' || bRedraw )
411
+ if ( bRedraw === undefined || bRedraw )
410
412
  {
411
413
  this.s.dt.oInstance.fnDraw();
412
414
  }
@@ -456,9 +458,12 @@ Scroller.prototype = /** @lends Scroller.prototype */{
456
458
  // Add a 'loading' indicator
457
459
  if ( this.s.loadingIndicator )
458
460
  {
461
+ this.dom.loader = $('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>')
462
+ .css('display', 'none');
463
+
459
464
  $(this.dom.scroller.parentNode)
460
465
  .css('position', 'relative')
461
- .append('<div class="DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>');
466
+ .append( this.dom.loader );
462
467
  }
463
468
 
464
469
  /* Initial size calculations */
@@ -468,12 +473,19 @@ Scroller.prototype = /** @lends Scroller.prototype */{
468
473
  }
469
474
  this.fnMeasure( false );
470
475
 
471
- /* Scrolling callback to see if a page change is needed */
472
- $(this.dom.scroller).on( 'scroll.DTS', function () {
476
+ /* Scrolling callback to see if a page change is needed - use a throttled
477
+ * function for the save save callback so we aren't hitting it on every
478
+ * scroll
479
+ */
480
+ this.s.ingnoreScroll = true;
481
+ this.s.stateSaveThrottle = this.s.dt.oApi._fnThrottle( function () {
482
+ that.s.dt.oApi._fnSaveState( that.s.dt );
483
+ }, 500 );
484
+ $(this.dom.scroller).on( 'scroll.DTS', function (e) {
473
485
  that._fnScroll.call( that );
474
486
  } );
475
487
 
476
- /* In iOS we catch the touchstart event incase the user tries to scroll
488
+ /* In iOS we catch the touchstart event in case the user tries to scroll
477
489
  * while the display is already scrolling
478
490
  */
479
491
  $(this.dom.scroller).on('touchstart.DTS', function () {
@@ -492,6 +504,7 @@ Scroller.prototype = /** @lends Scroller.prototype */{
492
504
 
493
505
  /* On resize, update the information element, since the number of rows shown might change */
494
506
  $(window).on( 'resize.DTS', function () {
507
+ that.fnMeasure( false );
495
508
  that._fnInfo();
496
509
  } );
497
510
 
@@ -504,12 +517,18 @@ Scroller.prototype = /** @lends Scroller.prototype */{
504
517
  */
505
518
  if(initialStateSave && that.s.dt.oLoadedState){
506
519
  oData.iScroller = that.s.dt.oLoadedState.iScroller;
520
+ oData.iScrollerTopRow = that.s.dt.oLoadedState.iScrollerTopRow;
507
521
  initialStateSave = false;
508
522
  } else {
509
523
  oData.iScroller = that.dom.scroller.scrollTop;
524
+ oData.iScrollerTopRow = that.s.topRowFloat;
510
525
  }
511
526
  }, "Scroller_State" );
512
527
 
528
+ if ( this.s.dt.oLoadedState ) {
529
+ this.s.topRowFloat = this.s.dt.oLoadedState.iScrollerTopRow || 0;
530
+ }
531
+
513
532
  /* Destructor */
514
533
  this.s.dt.aoDestroyCallback.push( {
515
534
  "sName": "Scroller",
@@ -548,6 +567,10 @@ Scroller.prototype = /** @lends Scroller.prototype */{
548
567
  return;
549
568
  }
550
569
 
570
+ if ( this.s.ingnoreScroll ) {
571
+ return;
572
+ }
573
+
551
574
  /* If the table has been sorted or filtered, then we use the redraw that
552
575
  * DataTables as done, rather than performing our own
553
576
  */
@@ -628,10 +651,16 @@ Scroller.prototype = /** @lends Scroller.prototype */{
628
651
  else {
629
652
  draw();
630
653
  }
654
+
655
+ if ( this.dom.loader && ! this.s.loaderVisible ) {
656
+ this.dom.loader.css( 'display', 'block' );
657
+ this.s.loaderVisible = true;
658
+ }
631
659
  }
632
660
  }
633
661
 
634
662
  this.s.lastScrollTop = iScrollTop;
663
+ this.s.stateSaveThrottle();
635
664
  },
636
665
 
637
666
 
@@ -770,20 +799,15 @@ Scroller.prototype = /** @lends Scroller.prototype */{
770
799
 
771
800
  this.s.skip = false;
772
801
 
773
- // Because of the order of the DT callbacks, the info update will
774
- // take precidence over the one we want here. So a 'thread' break is
775
- // needed
776
- setTimeout( function () {
777
- that._fnInfo.call( that );
778
- }, 0 );
779
-
780
802
  // Restore the scrolling position that was saved by DataTable's state
781
803
  // saving Note that this is done on the second draw when data is Ajax
782
804
  // sourced, and the first draw when DOM soured
783
805
  if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null &&
784
806
  typeof this.s.dt.oLoadedState.iScroller != 'undefined' )
785
807
  {
786
- var ajaxSourced = this.s.dt.sAjaxSource || that.s.dt.ajax ?
808
+ // A quirk of DataTables is that the draw callback will occur on an
809
+ // empty set if Ajax sourced, but not if server-side processing.
810
+ var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && ! this.s.dt.oFeatures.bServerSide ?
787
811
  true :
788
812
  false;
789
813
 
@@ -793,9 +817,31 @@ Scroller.prototype = /** @lends Scroller.prototype */{
793
817
  setTimeout( function () {
794
818
  $(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.iScroller );
795
819
  that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (heights.viewport/2);
820
+
821
+ // In order to prevent layout thrashing we need another
822
+ // small delay
823
+ setTimeout( function () {
824
+ that.s.ingnoreScroll = false;
825
+ }, 0 );
796
826
  }, 0 );
797
827
  }
798
828
  }
829
+ else {
830
+ that.s.ingnoreScroll = false;
831
+ }
832
+
833
+ // Because of the order of the DT callbacks, the info update will
834
+ // take precedence over the one we want here. So a 'thread' break is
835
+ // needed
836
+ setTimeout( function () {
837
+ that._fnInfo.call( that );
838
+ }, 0 );
839
+
840
+ // Hide the loading indicator
841
+ if ( this.dom.loader && this.s.loaderVisible ) {
842
+ this.dom.loader.css( 'display', 'none' );
843
+ this.s.loaderVisible = false;
844
+ }
799
845
  },
800
846
 
801
847
 
@@ -836,13 +882,14 @@ Scroller.prototype = /** @lends Scroller.prototype */{
836
882
  */
837
883
  "_fnCalcRowHeight": function ()
838
884
  {
839
- var origTable = this.s.dt.nTable;
885
+ var dt = this.s.dt;
886
+ var origTable = dt.nTable;
840
887
  var nTable = origTable.cloneNode( false );
841
888
  var tbody = $('<tbody/>').appendTo( nTable );
842
889
  var container = $(
843
- '<div class="'+this.s.dt.oClasses.sWrapper+' DTS">'+
844
- '<div class="'+this.s.dt.oClasses.sScrollWrapper+'">'+
845
- '<div class="'+this.s.dt.oClasses.sScrollBody+'"></div>'+
890
+ '<div class="'+dt.oClasses.sWrapper+' DTS">'+
891
+ '<div class="'+dt.oClasses.sScrollWrapper+'">'+
892
+ '<div class="'+dt.oClasses.sScrollBody+'"></div>'+
846
893
  '</div>'+
847
894
  '</div>'
848
895
  );
@@ -854,9 +901,19 @@ Scroller.prototype = /** @lends Scroller.prototype */{
854
901
  tbody.append( '<tr><td>&nbsp;</td></tr>' );
855
902
  }
856
903
 
857
- $('div.'+this.s.dt.oClasses.sScrollBody, container).append( nTable );
904
+ $('div.'+dt.oClasses.sScrollBody, container).append( nTable );
858
905
 
859
- container.appendTo( this.s.dt.nHolding );
906
+ var appendTo;
907
+ if (dt._bInitComplete) {
908
+ appendTo = origTable.parentNode;
909
+ } else {
910
+ if (!this.s.dt.nHolding) {
911
+ this.s.dt.nHolding = $( '<div></div>' ).insertBefore( this.s.dt.nTable );
912
+ }
913
+ appendTo = this.s.dt.nHolding;
914
+ }
915
+
916
+ container.appendTo( appendTo );
860
917
  this.s.heights.row = $('tr', tbody).eq(1).outerHeight();
861
918
  container.remove();
862
919
  },
@@ -878,6 +935,7 @@ Scroller.prototype = /** @lends Scroller.prototype */{
878
935
 
879
936
  var
880
937
  dt = this.s.dt,
938
+ language = dt.oLanguage,
881
939
  iScrollTop = this.dom.scroller.scrollTop,
882
940
  iStart = Math.floor( this.fnPixelsToRow(iScrollTop, false, this.s.ani)+1 ),
883
941
  iMax = dt.fnRecordsTotal(),
@@ -894,34 +952,45 @@ Scroller.prototype = /** @lends Scroller.prototype */{
894
952
  dt.fnRecordsDisplay() == dt.fnRecordsTotal() )
895
953
  {
896
954
  /* Empty record set */
897
- sOut = dt.oLanguage.sInfoEmpty+ dt.oLanguage.sInfoPostFix;
955
+ sOut = language.sInfoEmpty+ language.sInfoPostFix;
898
956
  }
899
957
  else if ( dt.fnRecordsDisplay() === 0 )
900
958
  {
901
- /* Rmpty record set after filtering */
902
- sOut = dt.oLanguage.sInfoEmpty +' '+
903
- dt.oLanguage.sInfoFiltered.replace('_MAX_', sMax)+
904
- dt.oLanguage.sInfoPostFix;
959
+ /* Empty record set after filtering */
960
+ sOut = language.sInfoEmpty +' '+
961
+ language.sInfoFiltered.replace('_MAX_', sMax)+
962
+ language.sInfoPostFix;
905
963
  }
906
964
  else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() )
907
965
  {
908
966
  /* Normal record set */
909
- sOut = dt.oLanguage.sInfo.
967
+ sOut = language.sInfo.
910
968
  replace('_START_', sStart).
911
969
  replace('_END_', sEnd).
970
+ replace('_MAX_', sMax).
912
971
  replace('_TOTAL_', sTotal)+
913
- dt.oLanguage.sInfoPostFix;
972
+ language.sInfoPostFix;
914
973
  }
915
974
  else
916
975
  {
917
976
  /* Record set after filtering */
918
- sOut = dt.oLanguage.sInfo.
977
+ sOut = language.sInfo.
919
978
  replace('_START_', sStart).
920
979
  replace('_END_', sEnd).
980
+ replace('_MAX_', sMax).
921
981
  replace('_TOTAL_', sTotal) +' '+
922
- dt.oLanguage.sInfoFiltered.replace('_MAX_',
923
- dt.fnFormatNumber(dt.fnRecordsTotal()))+
924
- dt.oLanguage.sInfoPostFix;
982
+ language.sInfoFiltered.replace(
983
+ '_MAX_',
984
+ dt.fnFormatNumber(dt.fnRecordsTotal())
985
+ )+
986
+ language.sInfoPostFix;
987
+ }
988
+
989
+ var callback = language.fnInfoCallback;
990
+ if ( callback ) {
991
+ sOut = callback.call( dt.oInstance,
992
+ dt, iStart, iEnd, iMax, iTotal, sOut
993
+ );
925
994
  }
926
995
 
927
996
  var n = dt.aanFeatures.i;
@@ -1086,7 +1155,7 @@ Scroller.oDefaults = Scroller.defaults;
1086
1155
  * @name Scroller.version
1087
1156
  * @static
1088
1157
  */
1089
- Scroller.version = "1.2.1";
1158
+ Scroller.version = "1.2.2";
1090
1159
 
1091
1160
 
1092
1161
 
@@ -1127,6 +1196,10 @@ $.fn.DataTable.Scroller = Scroller;
1127
1196
  if ( $.fn.dataTable.Api ) {
1128
1197
  var Api = $.fn.dataTable.Api;
1129
1198
 
1199
+ Api.register( 'scroller()', function () {
1200
+ return this;
1201
+ } );
1202
+
1130
1203
  Api.register( 'scroller().rowToPixels()', function ( rowIdx, intParse, virtual ) {
1131
1204
  var ctx = this.context;
1132
1205
 
@@ -1173,7 +1246,11 @@ return Scroller;
1173
1246
 
1174
1247
  // Define as an AMD module if possible
1175
1248
  if ( typeof define === 'function' && define.amd ) {
1176
- define( 'datatables-scroller', ['jquery', 'datatables'], factory );
1249
+ define( ['jquery', 'datatables'], factory );
1250
+ }
1251
+ else if ( typeof exports === 'object' ) {
1252
+ // Node/CommonJS
1253
+ factory( require('jquery'), require('datatables') );
1177
1254
  }
1178
1255
  else if ( jQuery && !jQuery.fn.dataTable.Scroller ) {
1179
1256
  // Otherwise simply initialise as normal, stopping multiple evaluation
@@ -1,4 +1,4 @@
1
- /*! TableTools 2.2.1
1
+ /*! TableTools 2.2.2
2
2
  * 2009-2014 SpryMedia Ltd - datatables.net/license
3
3
  *
4
4
  * ZeroClipboard 1.0.4
@@ -8,7 +8,7 @@
8
8
  /**
9
9
  * @summary TableTools
10
10
  * @description Tools and buttons for DataTables
11
- * @version 2.2.1
11
+ * @version 2.2.2
12
12
  * @file dataTables.tableTools.js
13
13
  * @author SpryMedia Ltd (www.sprymedia.co.uk)
14
14
  * @contact www.sprymedia.co.uk/contact
@@ -738,6 +738,8 @@ TableTools = function( oDT, oOpts )
738
738
  oOpts = {};
739
739
  }
740
740
 
741
+
742
+ TableTools._aInstances.push( this );
741
743
  this._fnConstruct( oOpts );
742
744
 
743
745
  return this;
@@ -816,6 +818,49 @@ TableTools.prototype = {
816
818
  },
817
819
 
818
820
 
821
+ /**
822
+ * Get the indexes of the selected rows
823
+ * @returns {array} List of row indexes
824
+ * @param {boolean} [filtered=false] Get only selected rows which are
825
+ * available given the filtering applied to the table. By default
826
+ * this is false - i.e. all rows, regardless of filtering are
827
+ selected.
828
+ */
829
+ "fnGetSelectedIndexes": function ( filtered )
830
+ {
831
+ var
832
+ out = [],
833
+ data = this.s.dt.aoData,
834
+ displayed = this.s.dt.aiDisplay,
835
+ i, iLen;
836
+
837
+ if ( filtered )
838
+ {
839
+ // Only consider filtered rows
840
+ for ( i=0, iLen=displayed.length ; i<iLen ; i++ )
841
+ {
842
+ if ( data[ displayed[i] ]._DTTT_selected )
843
+ {
844
+ out.push( displayed[i] );
845
+ }
846
+ }
847
+ }
848
+ else
849
+ {
850
+ // Use all rows
851
+ for ( i=0, iLen=data.length ; i<iLen ; i++ )
852
+ {
853
+ if ( data[i]._DTTT_selected )
854
+ {
855
+ out.push( i );
856
+ }
857
+ }
858
+ }
859
+
860
+ return out;
861
+ },
862
+
863
+
819
864
  /**
820
865
  * Check to see if a current row is selected or not
821
866
  * @param {Node} n TR node to check if it is currently selected or not
@@ -836,11 +881,9 @@ TableTools.prototype = {
836
881
  */
837
882
  "fnSelectAll": function ( filtered )
838
883
  {
839
- var s = this._fnGetMasterSettings();
840
-
841
- this._fnRowSelect( (filtered === true) ?
842
- s.dt.aiDisplay :
843
- s.dt.aoData
884
+ this._fnRowSelect( filtered ?
885
+ this.s.dt.aiDisplay :
886
+ this.s.dt.aoData
844
887
  );
845
888
  },
846
889
 
@@ -853,9 +896,7 @@ TableTools.prototype = {
853
896
  */
854
897
  "fnSelectNone": function ( filtered )
855
898
  {
856
- var s = this._fnGetMasterSettings();
857
-
858
- this._fnRowDeselect( this.fnGetSelected(filtered) );
899
+ this._fnRowDeselect( this.fnGetSelectedIndexes(filtered) );
859
900
  },
860
901
 
861
902
 
@@ -1212,10 +1253,14 @@ TableTools.prototype = {
1212
1253
  buttonDef = $.extend( o, buttonSet[i], true );
1213
1254
  }
1214
1255
 
1215
- wrapper.appendChild( this._fnCreateButton(
1256
+ var button = this._fnCreateButton(
1216
1257
  buttonDef,
1217
1258
  $(wrapper).hasClass(this.classes.collection.container)
1218
- ) );
1259
+ );
1260
+
1261
+ if ( button ) {
1262
+ wrapper.appendChild( button );
1263
+ }
1219
1264
  }
1220
1265
  },
1221
1266
 
@@ -1233,6 +1278,10 @@ TableTools.prototype = {
1233
1278
 
1234
1279
  if ( oConfig.sAction.match(/flash/) )
1235
1280
  {
1281
+ if ( ! this._fnHasFlash() ) {
1282
+ return false;
1283
+ }
1284
+
1236
1285
  this._fnFlashConfig( nButton, oConfig );
1237
1286
  }
1238
1287
  else if ( oConfig.sAction == "text" )
@@ -1807,6 +1856,34 @@ TableTools.prototype = {
1807
1856
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1808
1857
  * Flash button functions
1809
1858
  */
1859
+
1860
+ /**
1861
+ * Check if the Flash plug-in is available
1862
+ * @method _fnHasFlash
1863
+ * @returns {boolean} `true` if Flash available, `false` otherwise
1864
+ * @private
1865
+ */
1866
+ "_fnHasFlash": function ()
1867
+ {
1868
+ try {
1869
+ var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
1870
+ if (fo) {
1871
+ return true;
1872
+ }
1873
+ }
1874
+ catch (e) {
1875
+ if (
1876
+ navigator.mimeTypes &&
1877
+ navigator.mimeTypes['application/x-shockwave-flash'] !== undefined &&
1878
+ navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin
1879
+ ) {
1880
+ return true;
1881
+ }
1882
+ }
1883
+
1884
+ return false;
1885
+ },
1886
+
1810
1887
 
1811
1888
  /**
1812
1889
  * Configure a flash based button for interaction events
@@ -2068,15 +2145,18 @@ TableTools.prototype = {
2068
2145
  var aSelected = this.fnGetSelected();
2069
2146
  bSelectedOnly = this.s.select.type !== "none" && bSelectedOnly && aSelected.length !== 0;
2070
2147
 
2071
- var aDataIndex = dt.oInstance
2072
- .$('tr', oConfig.oSelectorOpts)
2073
- .map( function (id, row) {
2074
- // If "selected only", then ensure that the row is in the selected list
2075
- return bSelectedOnly && $.inArray( row, aSelected ) === -1 ?
2076
- null :
2077
- dt.oInstance.fnGetPosition( row );
2078
- } )
2079
- .get();
2148
+ var api = $.fn.dataTable.Api;
2149
+ var aDataIndex = api ?
2150
+ new api( dt ).rows( oConfig.oSelectorOpts ).indexes().flatten().toArray() :
2151
+ dt.oInstance
2152
+ .$('tr', oConfig.oSelectorOpts)
2153
+ .map( function (id, row) {
2154
+ // If "selected only", then ensure that the row is in the selected list
2155
+ return bSelectedOnly && $.inArray( row, aSelected ) === -1 ?
2156
+ null :
2157
+ dt.oInstance.fnGetPosition( row );
2158
+ } )
2159
+ .get();
2080
2160
 
2081
2161
  for ( j=0, jLen=aDataIndex.length ; j<jLen ; j++ )
2082
2162
  {
@@ -2226,7 +2306,7 @@ TableTools.prototype = {
2226
2306
 
2227
2307
  var n = document.createElement('div');
2228
2308
 
2229
- return sData.replace( /&([^\s]*);/g, function( match, match2 ) {
2309
+ return sData.replace( /&([^\s]*?);/g, function( match, match2 ) {
2230
2310
  if ( match.substr(1, 1) === '#' )
2231
2311
  {
2232
2312
  return String.fromCharCode( Number(match2.substr(1)) );
@@ -2724,12 +2804,12 @@ TableTools.BUTTONS = {
2724
2804
  this.fnSetText( flash, this.fnGetTableData(oConfig) );
2725
2805
  },
2726
2806
  "fnComplete": function(nButton, oConfig, flash, text) {
2727
- var
2728
- lines = text.split('\n').length,
2729
- len = this.s.dt.nTFoot === null ? lines-1 : lines-2,
2730
- plural = (len==1) ? "" : "s";
2807
+ var lines = text.split('\n').length;
2808
+ if (oConfig.bHeader) lines--;
2809
+ if (this.s.dt.nTFoot !== null && oConfig.bFooter) lines--;
2810
+ var plural = (lines==1) ? "" : "s";
2731
2811
  this.fnInfo( '<h6>Table copied</h6>'+
2732
- '<p>Copied '+len+' row'+plural+' to the clipboard.</p>',
2812
+ '<p>Copied '+lines+' row'+plural+' to the clipboard.</p>',
2733
2813
  1500
2734
2814
  );
2735
2815
  }
@@ -2974,7 +3054,7 @@ TableTools.prototype.CLASS = "TableTools";
2974
3054
  * @type String
2975
3055
  * @default See code
2976
3056
  */
2977
- TableTools.version = "2.2.1";
3057
+ TableTools.version = "2.2.2";
2978
3058
 
2979
3059
 
2980
3060
 
@@ -3017,10 +3097,7 @@ if ( typeof $.fn.dataTable == "function" &&
3017
3097
  init.tableTools || init.oTableTools || {} :
3018
3098
  {};
3019
3099
 
3020
- var oTT = new TableTools( oDTSettings.oInstance, opts );
3021
- TableTools._aInstances.push( oTT );
3022
-
3023
- return oTT.dom.container;
3100
+ return new TableTools( oDTSettings.oInstance, opts ).dom.container;
3024
3101
  },
3025
3102
  "cFeature": "T",
3026
3103
  "sFeature": "TableTools"
@@ -3072,7 +3149,11 @@ return TableTools;
3072
3149
 
3073
3150
  // Define as an AMD module if possible
3074
3151
  if ( typeof define === 'function' && define.amd ) {
3075
- define( 'datatables-tabletools', ['jquery', 'datatables'], factory );
3152
+ define( ['jquery', 'datatables'], factory );
3153
+ }
3154
+ else if ( typeof exports === 'object' ) {
3155
+ // Node/CommonJS
3156
+ factory( require('jquery'), require('datatables') );
3076
3157
  }
3077
3158
  else if ( jQuery && !jQuery.fn.dataTable.TableTools ) {
3078
3159
  // Otherwise simply initialise as normal, stopping multiple evaluation