effective_datatables 2.12.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +632 -512
  3. data/app/assets/javascripts/dataTables/buttons/buttons.html5.js +176 -177
  4. data/app/assets/javascripts/dataTables/buttons/buttons.print.js +2 -0
  5. data/app/assets/javascripts/dataTables/buttons/dataTables.buttons.js +14 -14
  6. data/app/assets/javascripts/dataTables/dataTables.bootstrap.js +1 -1
  7. data/app/assets/javascripts/dataTables/jquery.dataTables.js +246 -217
  8. data/app/assets/javascripts/effective_datatables.js +2 -3
  9. data/app/assets/javascripts/effective_datatables/events.js.coffee +7 -0
  10. data/app/assets/javascripts/effective_datatables/filters.js.coffee +6 -0
  11. data/app/assets/javascripts/effective_datatables/initialize.js.coffee +42 -39
  12. data/app/assets/javascripts/effective_datatables/reset.js.coffee +7 -0
  13. data/app/assets/javascripts/vendor/jquery.delayedChange.js +1 -1
  14. data/app/assets/stylesheets/dataTables/dataTables.bootstrap.css +0 -1
  15. data/app/assets/stylesheets/effective_datatables.scss +1 -2
  16. data/app/assets/stylesheets/effective_datatables/{_scopes.scss → _filters.scss} +1 -1
  17. data/app/assets/stylesheets/effective_datatables/_overrides.scss +1 -1
  18. data/app/controllers/effective/datatables_controller.rb +2 -4
  19. data/app/helpers/effective_datatables_helper.rb +56 -91
  20. data/app/helpers/effective_datatables_private_helper.rb +55 -64
  21. data/app/models/effective/datatable.rb +103 -177
  22. data/app/models/effective/datatable_column.rb +28 -0
  23. data/app/models/effective/datatable_column_tool.rb +110 -0
  24. data/app/models/effective/datatable_dsl_tool.rb +28 -0
  25. data/app/models/effective/datatable_value_tool.rb +142 -0
  26. data/app/models/effective/effective_datatable/attributes.rb +25 -0
  27. data/app/models/effective/effective_datatable/collection.rb +38 -0
  28. data/app/models/effective/effective_datatable/compute.rb +154 -0
  29. data/app/models/effective/effective_datatable/cookie.rb +29 -0
  30. data/app/models/effective/effective_datatable/dsl.rb +14 -8
  31. data/app/models/effective/effective_datatable/dsl/bulk_actions.rb +5 -6
  32. data/app/models/effective/effective_datatable/dsl/charts.rb +7 -9
  33. data/app/models/effective/effective_datatable/dsl/datatable.rb +107 -57
  34. data/app/models/effective/effective_datatable/dsl/filters.rb +50 -0
  35. data/app/models/effective/effective_datatable/format.rb +157 -0
  36. data/app/models/effective/effective_datatable/hooks.rb +0 -18
  37. data/app/models/effective/effective_datatable/params.rb +34 -0
  38. data/app/models/effective/effective_datatable/resource.rb +108 -0
  39. data/app/models/effective/effective_datatable/state.rb +178 -0
  40. data/app/views/effective/datatables/_actions_column.html.haml +9 -42
  41. data/app/views/effective/datatables/_bulk_actions_column.html.haml +1 -1
  42. data/app/views/effective/datatables/_bulk_actions_dropdown.html.haml +2 -3
  43. data/app/views/effective/datatables/_chart.html.haml +1 -1
  44. data/app/views/effective/datatables/_datatable.html.haml +7 -25
  45. data/app/views/effective/datatables/_filters.html.haml +21 -0
  46. data/app/views/effective/datatables/_reset.html.haml +2 -0
  47. data/app/views/effective/datatables/_resource_column.html.haml +8 -0
  48. data/app/views/effective/datatables/index.html.haml +0 -1
  49. data/config/effective_datatables.rb +9 -32
  50. data/lib/effective_datatables.rb +2 -6
  51. data/lib/effective_datatables/engine.rb +1 -1
  52. data/lib/effective_datatables/version.rb +1 -1
  53. data/lib/generators/effective_datatables/install_generator.rb +2 -2
  54. metadata +39 -19
  55. data/app/assets/javascripts/dataTables/colreorder/dataTables.colReorder.js +0 -27
  56. data/app/assets/javascripts/dataTables/jszip/jszip.js +0 -9155
  57. data/app/assets/javascripts/effective_datatables/scopes.js.coffee +0 -9
  58. data/app/models/effective/active_record_datatable_tool.rb +0 -242
  59. data/app/models/effective/array_datatable_tool.rb +0 -97
  60. data/app/models/effective/effective_datatable/ajax.rb +0 -101
  61. data/app/models/effective/effective_datatable/charts.rb +0 -20
  62. data/app/models/effective/effective_datatable/dsl/scopes.rb +0 -23
  63. data/app/models/effective/effective_datatable/helpers.rb +0 -24
  64. data/app/models/effective/effective_datatable/options.rb +0 -309
  65. data/app/models/effective/effective_datatable/rendering.rb +0 -365
  66. data/app/views/effective/datatables/_scopes.html.haml +0 -21
@@ -2,7 +2,7 @@
2
2
  * HTML5 export buttons for Buttons and DataTables.
3
3
  * 2016 SpryMedia Ltd - datatables.net/license
4
4
  *
5
- * FileSaver.js (1.1.20160328) - MIT license
5
+ * FileSaver.js (1.3.3) - MIT license
6
6
  * Copyright © 2016 Eli Grey - http://eligrey.com
7
7
  */
8
8
 
@@ -58,7 +58,7 @@ function _pdfMake () {
58
58
  var _saveAs = (function(view) {
59
59
  "use strict";
60
60
  // IE <10 is explicitly unsupported
61
- if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
61
+ if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
62
62
  return;
63
63
  }
64
64
  var
@@ -73,16 +73,14 @@ var _saveAs = (function(view) {
73
73
  var event = new MouseEvent("click");
74
74
  node.dispatchEvent(event);
75
75
  }
76
- , is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
77
- , webkit_req_fs = view.webkitRequestFileSystem
78
- , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
76
+ , is_safari = /constructor/i.test(view.HTMLElement) || view.safari
77
+ , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
79
78
  , throw_outside = function(ex) {
80
79
  (view.setImmediate || view.setTimeout)(function() {
81
80
  throw ex;
82
81
  }, 0);
83
82
  }
84
83
  , force_saveable_type = "application/octet-stream"
85
- , fs_min_size = 0
86
84
  // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
87
85
  , arbitrary_revoke_timeout = 1000 * 40 // in ms
88
86
  , revoke = function(file) {
@@ -93,22 +91,6 @@ var _saveAs = (function(view) {
93
91
  file.remove();
94
92
  }
95
93
  };
96
- /* // Take note W3C:
97
- var
98
- uri = typeof file === "string" ? file : file.toURL()
99
- , revoker = function(evt) {
100
- // idealy DownloadFinishedEvent.data would be the URL requested
101
- if (evt.data === uri) {
102
- if (typeof file === "string") { // file is an object URL
103
- get_URL().revokeObjectURL(file);
104
- } else { // file is a File
105
- file.remove();
106
- }
107
- }
108
- }
109
- ;
110
- view.addEventListener("downloadfinished", revoker);
111
- */
112
94
  setTimeout(revoker, arbitrary_revoke_timeout);
113
95
  }
114
96
  , dispatch = function(filesaver, event_types, event) {
@@ -127,8 +109,9 @@ var _saveAs = (function(view) {
127
109
  }
128
110
  , auto_bom = function(blob) {
129
111
  // prepend BOM for UTF-8 XML and text/* types (including HTML)
112
+ // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
130
113
  if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
131
- return new Blob(["\ufeff", blob], {type: blob.type});
114
+ return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
132
115
  }
133
116
  return blob;
134
117
  }
@@ -140,20 +123,21 @@ var _saveAs = (function(view) {
140
123
  var
141
124
  filesaver = this
142
125
  , type = blob.type
143
- , blob_changed = false
126
+ , force = type === force_saveable_type
144
127
  , object_url
145
- , target_view
146
128
  , dispatch_all = function() {
147
129
  dispatch(filesaver, "writestart progress write writeend".split(" "));
148
130
  }
149
131
  // on any filesys errors revert to saving with object URLs
150
132
  , fs_error = function() {
151
- if (target_view && is_safari && typeof FileReader !== "undefined") {
133
+ if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
152
134
  // Safari doesn't allow downloading of blob urls
153
135
  var reader = new FileReader();
154
136
  reader.onloadend = function() {
155
- var base64Data = reader.result;
156
- target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/));
137
+ var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
138
+ var popup = view.open(url, '_blank');
139
+ if(!popup) view.location.href = url;
140
+ url=undefined; // release reference before dispatching
157
141
  filesaver.readyState = filesaver.DONE;
158
142
  dispatch_all();
159
143
  };
@@ -162,36 +146,25 @@ var _saveAs = (function(view) {
162
146
  return;
163
147
  }
164
148
  // don't create more object URLs than needed
165
- if (blob_changed || !object_url) {
149
+ if (!object_url) {
166
150
  object_url = get_URL().createObjectURL(blob);
167
151
  }
168
- if (target_view) {
169
- target_view.location.href = object_url;
152
+ if (force) {
153
+ view.location.href = object_url;
170
154
  } else {
171
- var new_tab = view.open(object_url, "_blank");
172
- if (new_tab === undefined && is_safari) {
173
- //Apple do not allow window.open, see http://bit.ly/1kZffRI
174
- view.location.href = object_url
155
+ var opened = view.open(object_url, "_blank");
156
+ if (!opened) {
157
+ // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
158
+ view.location.href = object_url;
175
159
  }
176
160
  }
177
161
  filesaver.readyState = filesaver.DONE;
178
162
  dispatch_all();
179
163
  revoke(object_url);
180
164
  }
181
- , abortable = function(func) {
182
- return function() {
183
- if (filesaver.readyState !== filesaver.DONE) {
184
- return func.apply(this, arguments);
185
- }
186
- };
187
- }
188
- , create_if_not_found = {create: true, exclusive: false}
189
- , slice
190
165
  ;
191
166
  filesaver.readyState = filesaver.INIT;
192
- if (!name) {
193
- name = "download";
194
- }
167
+
195
168
  if (can_use_save_link) {
196
169
  object_url = get_URL().createObjectURL(blob);
197
170
  setTimeout(function() {
@@ -204,93 +177,27 @@ var _saveAs = (function(view) {
204
177
  });
205
178
  return;
206
179
  }
207
- // Object and web filesystem URLs have a problem saving in Google Chrome when
208
- // viewed in a tab, so I force save with application/octet-stream
209
- // http://code.google.com/p/chromium/issues/detail?id=91158
210
- // Update: Google errantly closed 91158, I submitted it again:
211
- // https://code.google.com/p/chromium/issues/detail?id=389642
212
- if (view.chrome && type && type !== force_saveable_type) {
213
- slice = blob.slice || blob.webkitSlice;
214
- blob = slice.call(blob, 0, blob.size, force_saveable_type);
215
- blob_changed = true;
216
- }
217
- // Since I can't be sure that the guessed media type will trigger a download
218
- // in WebKit, I append .download to the filename.
219
- // https://bugs.webkit.org/show_bug.cgi?id=65440
220
- if (webkit_req_fs && name !== "download") {
221
- name += ".download";
222
- }
223
- if (type === force_saveable_type || webkit_req_fs) {
224
- target_view = view;
225
- }
226
- if (!req_fs) {
227
- fs_error();
228
- return;
229
- }
230
- fs_min_size += blob.size;
231
- req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
232
- fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
233
- var save = function() {
234
- dir.getFile(name, create_if_not_found, abortable(function(file) {
235
- file.createWriter(abortable(function(writer) {
236
- writer.onwriteend = function(event) {
237
- target_view.location.href = file.toURL();
238
- filesaver.readyState = filesaver.DONE;
239
- dispatch(filesaver, "writeend", event);
240
- revoke(file);
241
- };
242
- writer.onerror = function() {
243
- var error = writer.error;
244
- if (error.code !== error.ABORT_ERR) {
245
- fs_error();
246
- }
247
- };
248
- "writestart progress write abort".split(" ").forEach(function(event) {
249
- writer["on" + event] = filesaver["on" + event];
250
- });
251
- writer.write(blob);
252
- filesaver.abort = function() {
253
- writer.abort();
254
- filesaver.readyState = filesaver.DONE;
255
- };
256
- filesaver.readyState = filesaver.WRITING;
257
- }), fs_error);
258
- }), fs_error);
259
- };
260
- dir.getFile(name, {create: false}, abortable(function(file) {
261
- // delete file if it already exists
262
- file.remove();
263
- save();
264
- }), abortable(function(ex) {
265
- if (ex.code === ex.NOT_FOUND_ERR) {
266
- save();
267
- } else {
268
- fs_error();
269
- }
270
- }));
271
- }), fs_error);
272
- }), fs_error);
180
+
181
+ fs_error();
273
182
  }
274
183
  , FS_proto = FileSaver.prototype
275
184
  , saveAs = function(blob, name, no_auto_bom) {
276
- return new FileSaver(blob, name, no_auto_bom);
185
+ return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
277
186
  }
278
187
  ;
279
188
  // IE 10+ (native saveAs)
280
189
  if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
281
190
  return function(blob, name, no_auto_bom) {
191
+ name = name || blob.name || "download";
192
+
282
193
  if (!no_auto_bom) {
283
194
  blob = auto_bom(blob);
284
195
  }
285
- return navigator.msSaveOrOpenBlob(blob, name || "download");
196
+ return navigator.msSaveOrOpenBlob(blob, name);
286
197
  };
287
198
  }
288
199
 
289
- FS_proto.abort = function() {
290
- var filesaver = this;
291
- filesaver.readyState = filesaver.DONE;
292
- dispatch(filesaver, "abort");
293
- };
200
+ FS_proto.abort = function(){};
294
201
  FS_proto.readyState = FS_proto.INIT = 0;
295
202
  FS_proto.WRITING = 1;
296
203
  FS_proto.DONE = 2;
@@ -450,17 +357,27 @@ var _exportData = function ( dt, config )
450
357
  };
451
358
 
452
359
  /**
453
- * Safari's data: support for creating and downloading files is really poor, so
454
- * various options need to be disabled in it. See
455
- * https://bugs.webkit.org/show_bug.cgi?id=102914
360
+ * Older versions of Safari (prior to tech preview 18) don't support the
361
+ * download option required.
456
362
  *
457
- * @return {Boolean} `true` if Safari
363
+ * @return {Boolean} `true` if old Safari
458
364
  */
459
- var _isSafari = function ()
365
+ var _isDuffSafari = function ()
460
366
  {
461
- return navigator.userAgent.indexOf('Safari') !== -1 &&
367
+ var safari = navigator.userAgent.indexOf('Safari') !== -1 &&
462
368
  navigator.userAgent.indexOf('Chrome') === -1 &&
463
369
  navigator.userAgent.indexOf('Opera') === -1;
370
+
371
+ if ( ! safari ) {
372
+ return false;
373
+ }
374
+
375
+ var version = navigator.userAgent.match( /AppleWebKit\/(\d+\.\d+)/ );
376
+ if ( version && version.length > 1 && version[1]*1 < 603.1 ) {
377
+ return true;
378
+ }
379
+
380
+ return false;
464
381
  };
465
382
 
466
383
  /**
@@ -554,11 +471,9 @@ function _addToZip( zip, obj ) {
554
471
  str = str.replace( /_dt_b_namespace_token_/g, ':' );
555
472
  }
556
473
 
557
- // Both IE and Edge will put empty name space attributes onto the
558
- // rows and columns making them useless
559
- str = str
560
- .replace( /<row xmlns="" /g, '<row ' )
561
- .replace( /<cols xmlns="">/g, '<cols>' );
474
+ // Safari, IE and Edge will put empty name space attributes onto
475
+ // various elements making them useless. This strips them out
476
+ str = str.replace( /<(.*?) xmlns=""(.*?)>/g, '<$1 $2>' );
562
477
 
563
478
  zip.file( name, str );
564
479
  }
@@ -605,14 +520,28 @@ function _createNode( doc, nodeName, opts ) {
605
520
  */
606
521
  function _excelColWidth( data, col ) {
607
522
  var max = data.header[col].length;
608
- var len;
523
+ var len, lineSplit, str;
609
524
 
610
525
  if ( data.footer && data.footer[col].length > max ) {
611
526
  max = data.footer[col].length;
612
527
  }
613
528
 
614
529
  for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
615
- len = data.body[i][col].toString().length;
530
+ str = data.body[i][col].toString();
531
+
532
+ // If there is a newline character, workout the width of the column
533
+ // based on the longest line in the string
534
+ if ( str.indexOf('\n') !== -1 ) {
535
+ lineSplit = str.split('\n');
536
+ lineSplit.sort( function (a, b) {
537
+ return b.length - a.length;
538
+ } );
539
+
540
+ len = lineSplit[0].length;
541
+ }
542
+ else {
543
+ len = str.length;
544
+ }
616
545
 
617
546
  if ( len > max ) {
618
547
  max = len;
@@ -624,8 +553,10 @@ function _excelColWidth( data, col ) {
624
553
  }
625
554
  }
626
555
 
556
+ max *= 1.3;
557
+
627
558
  // And a min width
628
- return max > 5 ? max : 5;
559
+ return max > 6 ? max : 6;
629
560
  }
630
561
 
631
562
  // Excel - Pre-defined strings to build a basic XLSX file
@@ -676,6 +607,14 @@ var excelStrings = {
676
607
  "xl/styles.xml":
677
608
  '<?xml version="1.0" encoding="UTF-8"?>'+
678
609
  '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">'+
610
+ '<numFmts count="6">'+
611
+ '<numFmt numFmtId="164" formatCode="#,##0.00_-\ [$$-45C]"/>'+
612
+ '<numFmt numFmtId="165" formatCode="&quot;£&quot;#,##0.00"/>'+
613
+ '<numFmt numFmtId="166" formatCode="[$€-2]\ #,##0.00"/>'+
614
+ '<numFmt numFmtId="167" formatCode="0.0%"/>'+
615
+ '<numFmt numFmtId="168" formatCode="#,##0;(#,##0)"/>'+
616
+ '<numFmt numFmtId="169" formatCode="#,##0.00;(#,##0.00)"/>'+
617
+ '</numFmts>'+
679
618
  '<fonts count="5" x14ac:knownFonts="1">'+
680
619
  '<font>'+
681
620
  '<sz val="11" />'+
@@ -759,7 +698,7 @@ var excelStrings = {
759
698
  '<cellStyleXfs count="1">'+
760
699
  '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />'+
761
700
  '</cellStyleXfs>'+
762
- '<cellXfs count="56">'+
701
+ '<cellXfs count="67">'+
763
702
  '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+
764
703
  '<xf numFmtId="0" fontId="1" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+
765
704
  '<xf numFmtId="0" fontId="2" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+
@@ -828,6 +767,17 @@ var excelStrings = {
828
767
  '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+
829
768
  '<alignment wrapText="1"/>'+
830
769
  '</xf>'+
770
+ '<xf numFmtId="9" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
771
+ '<xf numFmtId="164" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
772
+ '<xf numFmtId="165" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
773
+ '<xf numFmtId="166" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
774
+ '<xf numFmtId="167" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
775
+ '<xf numFmtId="168" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
776
+ '<xf numFmtId="169" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
777
+ '<xf numFmtId="3" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
778
+ '<xf numFmtId="4" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
779
+ '<xf numFmtId="1" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
780
+ '<xf numFmtId="2" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>'+
831
781
  '</cellXfs>'+
832
782
  '<cellStyles count="1">'+
833
783
  '<cellStyle name="Normal" xfId="0" builtinId="0" />'+
@@ -839,6 +789,24 @@ var excelStrings = {
839
789
  // Note we could use 3 `for` loops for the styles, but when gzipped there is
840
790
  // virtually no difference in size, since the above can be easily compressed
841
791
 
792
+ // Pattern matching for special number formats. Perhaps this should be exposed
793
+ // via an API in future?
794
+ // Ref: section 3.8.30 - built in formatters in open spreadsheet
795
+ // https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf
796
+ var _excelSpecials = [
797
+ { match: /^\-?\d+\.\d%$/, style: 60, fmt: function (d) { return d/100; } }, // Precent with d.p.
798
+ { match: /^\-?\d+\.?\d*%$/, style: 56, fmt: function (d) { return d/100; } }, // Percent
799
+ { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars
800
+ { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds
801
+ { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros
802
+ { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators
803
+ { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators
804
+ { match: /^\([\d,]+\)$/, style: 61, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets
805
+ { match: /^\([\d,]+\.\d{2}\)$/, style: 62, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - 2d.p.
806
+ { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators
807
+ { match: /^\-?[\d,]+\.\d{2}$/, style: 64 } // Numbers with 2 d.p. and thousands separators
808
+ ];
809
+
842
810
 
843
811
 
844
812
  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -1021,7 +989,7 @@ DataTable.ext.buttons.excelHtml5 = {
1021
989
  className: 'buttons-excel buttons-html5',
1022
990
 
1023
991
  available: function () {
1024
- return window.FileReader !== undefined && _jsZip() !== undefined && ! _isSafari() && _serialiser;
992
+ return window.FileReader !== undefined && _jsZip() !== undefined && ! _isDuffSafari() && _serialiser;
1025
993
  },
1026
994
 
1027
995
  text: function ( dt ) {
@@ -1067,54 +1035,85 @@ DataTable.ext.buttons.excelHtml5 = {
1067
1035
  for ( var i=0, ien=row.length ; i<ien ; i++ ) {
1068
1036
  // Concat both the Cell Columns as a letter and the Row of the cell.
1069
1037
  var cellId = createCellPos(i) + '' + currentRow;
1070
- var cell;
1038
+ var cell = null;
1071
1039
 
1072
- if ( row[i] === null || row[i] === undefined ) {
1073
- row[i] = '';
1040
+ // For null, undefined of blank cell, continue so it doesn't create the _createNode
1041
+ if ( row[i] === null || row[i] === undefined || row[i] === '' ) {
1042
+ continue;
1074
1043
  }
1075
1044
 
1076
- // Detect numbers - don't match numbers with leading zeros or a negative
1077
- // anywhere but the start
1078
- if ( typeof row[i] === 'number' || (
1079
- row[i].match &&
1080
- $.trim(row[i]).match(/^-?\d+(\.\d+)?$/) &&
1081
- ! $.trim(row[i]).match(/^0\d+/) )
1082
- ) {
1083
- cell = _createNode( rels, 'c', {
1084
- attr: {
1085
- t: 'n',
1086
- r: cellId
1087
- },
1088
- children: [
1089
- _createNode( rels, 'v', { text: row[i] } )
1090
- ]
1091
- } );
1092
- }
1093
- else {
1094
- // Replace non standard characters for text output
1095
- var text = ! row[i].replace ?
1096
- row[i] :
1097
- row[i].replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, '');
1098
-
1099
- cell = _createNode( rels, 'c', {
1100
- attr: {
1101
- t: 'inlineStr',
1102
- r: cellId
1103
- },
1104
- children:{
1105
- row: _createNode( rels, 'is', {
1106
- children: {
1107
- row: _createNode( rels, 't', {
1108
- text: text
1109
- } )
1110
- }
1111
- } )
1045
+ row[i] = $.trim( row[i] );
1046
+
1047
+ // Special number formatting options
1048
+ for ( var j=0, jen=_excelSpecials.length ; j<jen ; j++ ) {
1049
+ var special = _excelSpecials[j];
1050
+
1051
+ if ( row[i].match && row[i].match( special.match ) ) {
1052
+ var val = row[i].replace(/[^\d\.\-]/g, '');
1053
+
1054
+ if ( special.fmt ) {
1055
+ val = special.fmt( val );
1112
1056
  }
1113
- } );
1057
+
1058
+ cell = _createNode( rels, 'c', {
1059
+ attr: {
1060
+ r: cellId,
1061
+ s: special.style
1062
+ },
1063
+ children: [
1064
+ _createNode( rels, 'v', { text: val } )
1065
+ ]
1066
+ } );
1067
+
1068
+ break;
1069
+ }
1070
+ }
1071
+
1072
+ if ( ! cell ) {
1073
+ if ( typeof row[i] === 'number' || (
1074
+ row[i].match &&
1075
+ row[i].match(/^-?\d+(\.\d+)?$/) &&
1076
+ ! row[i].match(/^0\d+/) )
1077
+ ) {
1078
+ // Detect numbers - don't match numbers with leading zeros
1079
+ // or a negative anywhere but the start
1080
+ cell = _createNode( rels, 'c', {
1081
+ attr: {
1082
+ t: 'n',
1083
+ r: cellId
1084
+ },
1085
+ children: [
1086
+ _createNode( rels, 'v', { text: row[i] } )
1087
+ ]
1088
+ } );
1089
+ }
1090
+ else {
1091
+ // String output - replace non standard characters for text output
1092
+ var text = ! row[i].replace ?
1093
+ row[i] :
1094
+ row[i].replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, '');
1095
+
1096
+ cell = _createNode( rels, 'c', {
1097
+ attr: {
1098
+ t: 'inlineStr',
1099
+ r: cellId
1100
+ },
1101
+ children:{
1102
+ row: _createNode( rels, 'is', {
1103
+ children: {
1104
+ row: _createNode( rels, 't', {
1105
+ text: text
1106
+ } )
1107
+ }
1108
+ } )
1109
+ }
1110
+ } );
1111
+ }
1114
1112
  }
1115
1113
 
1116
1114
  rowNode.appendChild( cell );
1117
1115
  }
1116
+
1118
1117
  relsGet.appendChild(rowNode);
1119
1118
  rowPos++;
1120
1119
  };
@@ -1305,7 +1304,7 @@ DataTable.ext.buttons.pdfHtml5 = {
1305
1304
 
1306
1305
  var pdf = _pdfMake().createPdf( doc );
1307
1306
 
1308
- if ( config.download === 'open' && ! _isSafari() ) {
1307
+ if ( config.download === 'open' && ! _isDuffSafari() ) {
1309
1308
  pdf.open();
1310
1309
  }
1311
1310
  else {