Commit 3543a79115a018255c66b3df3921c99acac291a7
1 parent
d01cc219
Exists in
master
and in
3 other branches
Adding goals answered reports csv download
Showing
6 changed files
with
3132 additions
and
6 deletions
Show diff stats
@@ -0,0 +1,102 @@ | @@ -0,0 +1,102 @@ | ||
1 | +div.dt-button-info { | ||
2 | + position: fixed; | ||
3 | + top: 50%; | ||
4 | + left: 50%; | ||
5 | + width: 400px; | ||
6 | + margin-top: -100px; | ||
7 | + margin-left: -200px; | ||
8 | + background-color: white; | ||
9 | + border: 2px solid #111; | ||
10 | + box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.3); | ||
11 | + border-radius: 3px; | ||
12 | + text-align: center; | ||
13 | + z-index: 21; | ||
14 | +} | ||
15 | +div.dt-button-info h2 { | ||
16 | + padding: 0.5em; | ||
17 | + margin: 0; | ||
18 | + font-weight: normal; | ||
19 | + border-bottom: 1px solid #ddd; | ||
20 | + background-color: #f3f3f3; | ||
21 | +} | ||
22 | +div.dt-button-info > div { | ||
23 | + padding: 1em; | ||
24 | +} | ||
25 | + | ||
26 | +ul.dt-button-collection.dropdown-menu { | ||
27 | + display: block; | ||
28 | + z-index: 2002; | ||
29 | + -webkit-column-gap: 8px; | ||
30 | + -moz-column-gap: 8px; | ||
31 | + -ms-column-gap: 8px; | ||
32 | + -o-column-gap: 8px; | ||
33 | + column-gap: 8px; | ||
34 | +} | ||
35 | +ul.dt-button-collection.dropdown-menu.fixed { | ||
36 | + position: fixed; | ||
37 | + top: 50%; | ||
38 | + left: 50%; | ||
39 | + margin-left: -75px; | ||
40 | + border-radius: 0; | ||
41 | +} | ||
42 | +ul.dt-button-collection.dropdown-menu.fixed.two-column { | ||
43 | + margin-left: -150px; | ||
44 | +} | ||
45 | +ul.dt-button-collection.dropdown-menu.fixed.three-column { | ||
46 | + margin-left: -225px; | ||
47 | +} | ||
48 | +ul.dt-button-collection.dropdown-menu.fixed.four-column { | ||
49 | + margin-left: -300px; | ||
50 | +} | ||
51 | +ul.dt-button-collection.dropdown-menu > * { | ||
52 | + -webkit-column-break-inside: avoid; | ||
53 | + break-inside: avoid; | ||
54 | +} | ||
55 | +ul.dt-button-collection.dropdown-menu.two-column { | ||
56 | + width: 300px; | ||
57 | + padding-bottom: 1px; | ||
58 | + -webkit-column-count: 2; | ||
59 | + -moz-column-count: 2; | ||
60 | + -ms-column-count: 2; | ||
61 | + -o-column-count: 2; | ||
62 | + column-count: 2; | ||
63 | +} | ||
64 | +ul.dt-button-collection.dropdown-menu.three-column { | ||
65 | + width: 450px; | ||
66 | + padding-bottom: 1px; | ||
67 | + -webkit-column-count: 3; | ||
68 | + -moz-column-count: 3; | ||
69 | + -ms-column-count: 3; | ||
70 | + -o-column-count: 3; | ||
71 | + column-count: 3; | ||
72 | +} | ||
73 | +ul.dt-button-collection.dropdown-menu.four-column { | ||
74 | + width: 600px; | ||
75 | + padding-bottom: 1px; | ||
76 | + -webkit-column-count: 4; | ||
77 | + -moz-column-count: 4; | ||
78 | + -ms-column-count: 4; | ||
79 | + -o-column-count: 4; | ||
80 | + column-count: 4; | ||
81 | +} | ||
82 | + | ||
83 | +div.dt-button-background { | ||
84 | + position: fixed; | ||
85 | + top: 0; | ||
86 | + left: 0; | ||
87 | + width: 100%; | ||
88 | + height: 100%; | ||
89 | + z-index: 2001; | ||
90 | +} | ||
91 | + | ||
92 | +@media screen and (max-width: 767px) { | ||
93 | + div.dt-buttons { | ||
94 | + float: none; | ||
95 | + width: 100%; | ||
96 | + text-align: center; | ||
97 | + margin-bottom: 0.5em; | ||
98 | + } | ||
99 | + div.dt-buttons a.btn { | ||
100 | + float: none; | ||
101 | + } | ||
102 | +} |
@@ -0,0 +1,1340 @@ | @@ -0,0 +1,1340 @@ | ||
1 | +/*! | ||
2 | + * HTML5 export buttons for Buttons and DataTables. | ||
3 | + * 2016 SpryMedia Ltd - datatables.net/license | ||
4 | + * | ||
5 | + * FileSaver.js (1.3.3) - MIT license | ||
6 | + * Copyright © 2016 Eli Grey - http://eligrey.com | ||
7 | + */ | ||
8 | + | ||
9 | +(function( factory ){ | ||
10 | + if ( typeof define === 'function' && define.amd ) { | ||
11 | + // AMD | ||
12 | + define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) { | ||
13 | + return factory( $, window, document ); | ||
14 | + } ); | ||
15 | + } | ||
16 | + else if ( typeof exports === 'object' ) { | ||
17 | + // CommonJS | ||
18 | + module.exports = function (root, $, jszip, pdfmake) { | ||
19 | + if ( ! root ) { | ||
20 | + root = window; | ||
21 | + } | ||
22 | + | ||
23 | + if ( ! $ || ! $.fn.dataTable ) { | ||
24 | + $ = require('datatables.net')(root, $).$; | ||
25 | + } | ||
26 | + | ||
27 | + if ( ! $.fn.dataTable.Buttons ) { | ||
28 | + require('datatables.net-buttons')(root, $); | ||
29 | + } | ||
30 | + | ||
31 | + return factory( $, root, root.document, jszip, pdfmake ); | ||
32 | + }; | ||
33 | + } | ||
34 | + else { | ||
35 | + // Browser | ||
36 | + factory( jQuery, window, document ); | ||
37 | + } | ||
38 | +}(function( $, window, document, jszip, pdfmake, undefined ) { | ||
39 | +'use strict'; | ||
40 | +var DataTable = $.fn.dataTable; | ||
41 | + | ||
42 | +// Allow the constructor to pass in JSZip and PDFMake from external requires. | ||
43 | +// Otherwise, use globally defined variables, if they are available. | ||
44 | +function _jsZip () { | ||
45 | + return jszip || window.JSZip; | ||
46 | +} | ||
47 | +function _pdfMake () { | ||
48 | + return pdfmake || window.pdfMake; | ||
49 | +} | ||
50 | + | ||
51 | + | ||
52 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
53 | + * FileSaver.js dependency | ||
54 | + */ | ||
55 | + | ||
56 | +/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ | ||
57 | + | ||
58 | +var _saveAs = (function(view) { | ||
59 | + "use strict"; | ||
60 | + // IE <10 is explicitly unsupported | ||
61 | + if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { | ||
62 | + return; | ||
63 | + } | ||
64 | + var | ||
65 | + doc = view.document | ||
66 | + // only get URL when necessary in case Blob.js hasn't overridden it yet | ||
67 | + , get_URL = function() { | ||
68 | + return view.URL || view.webkitURL || view; | ||
69 | + } | ||
70 | + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") | ||
71 | + , can_use_save_link = "download" in save_link | ||
72 | + , click = function(node) { | ||
73 | + var event = new MouseEvent("click"); | ||
74 | + node.dispatchEvent(event); | ||
75 | + } | ||
76 | + , is_safari = /constructor/i.test(view.HTMLElement) || view.safari | ||
77 | + , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) | ||
78 | + , throw_outside = function(ex) { | ||
79 | + (view.setImmediate || view.setTimeout)(function() { | ||
80 | + throw ex; | ||
81 | + }, 0); | ||
82 | + } | ||
83 | + , force_saveable_type = "application/octet-stream" | ||
84 | + // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to | ||
85 | + , arbitrary_revoke_timeout = 1000 * 40 // in ms | ||
86 | + , revoke = function(file) { | ||
87 | + var revoker = function() { | ||
88 | + if (typeof file === "string") { // file is an object URL | ||
89 | + get_URL().revokeObjectURL(file); | ||
90 | + } else { // file is a File | ||
91 | + file.remove(); | ||
92 | + } | ||
93 | + }; | ||
94 | + setTimeout(revoker, arbitrary_revoke_timeout); | ||
95 | + } | ||
96 | + , dispatch = function(filesaver, event_types, event) { | ||
97 | + event_types = [].concat(event_types); | ||
98 | + var i = event_types.length; | ||
99 | + while (i--) { | ||
100 | + var listener = filesaver["on" + event_types[i]]; | ||
101 | + if (typeof listener === "function") { | ||
102 | + try { | ||
103 | + listener.call(filesaver, event || filesaver); | ||
104 | + } catch (ex) { | ||
105 | + throw_outside(ex); | ||
106 | + } | ||
107 | + } | ||
108 | + } | ||
109 | + } | ||
110 | + , auto_bom = function(blob) { | ||
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 | ||
113 | + if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { | ||
114 | + return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); | ||
115 | + } | ||
116 | + return blob; | ||
117 | + } | ||
118 | + , FileSaver = function(blob, name, no_auto_bom) { | ||
119 | + if (!no_auto_bom) { | ||
120 | + blob = auto_bom(blob); | ||
121 | + } | ||
122 | + // First try a.download, then web filesystem, then object URLs | ||
123 | + var | ||
124 | + filesaver = this | ||
125 | + , type = blob.type | ||
126 | + , force = type === force_saveable_type | ||
127 | + , object_url | ||
128 | + , dispatch_all = function() { | ||
129 | + dispatch(filesaver, "writestart progress write writeend".split(" ")); | ||
130 | + } | ||
131 | + // on any filesys errors revert to saving with object URLs | ||
132 | + , fs_error = function() { | ||
133 | + if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { | ||
134 | + // Safari doesn't allow downloading of blob urls | ||
135 | + var reader = new FileReader(); | ||
136 | + reader.onloadend = function() { | ||
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 | ||
141 | + filesaver.readyState = filesaver.DONE; | ||
142 | + dispatch_all(); | ||
143 | + }; | ||
144 | + reader.readAsDataURL(blob); | ||
145 | + filesaver.readyState = filesaver.INIT; | ||
146 | + return; | ||
147 | + } | ||
148 | + // don't create more object URLs than needed | ||
149 | + if (!object_url) { | ||
150 | + object_url = get_URL().createObjectURL(blob); | ||
151 | + } | ||
152 | + if (force) { | ||
153 | + view.location.href = object_url; | ||
154 | + } else { | ||
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; | ||
159 | + } | ||
160 | + } | ||
161 | + filesaver.readyState = filesaver.DONE; | ||
162 | + dispatch_all(); | ||
163 | + revoke(object_url); | ||
164 | + } | ||
165 | + ; | ||
166 | + filesaver.readyState = filesaver.INIT; | ||
167 | + | ||
168 | + if (can_use_save_link) { | ||
169 | + object_url = get_URL().createObjectURL(blob); | ||
170 | + setTimeout(function() { | ||
171 | + save_link.href = object_url; | ||
172 | + save_link.download = name; | ||
173 | + click(save_link); | ||
174 | + dispatch_all(); | ||
175 | + revoke(object_url); | ||
176 | + filesaver.readyState = filesaver.DONE; | ||
177 | + }); | ||
178 | + return; | ||
179 | + } | ||
180 | + | ||
181 | + fs_error(); | ||
182 | + } | ||
183 | + , FS_proto = FileSaver.prototype | ||
184 | + , saveAs = function(blob, name, no_auto_bom) { | ||
185 | + return new FileSaver(blob, name || blob.name || "download", no_auto_bom); | ||
186 | + } | ||
187 | + ; | ||
188 | + // IE 10+ (native saveAs) | ||
189 | + if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { | ||
190 | + return function(blob, name, no_auto_bom) { | ||
191 | + name = name || blob.name || "download"; | ||
192 | + | ||
193 | + if (!no_auto_bom) { | ||
194 | + blob = auto_bom(blob); | ||
195 | + } | ||
196 | + return navigator.msSaveOrOpenBlob(blob, name); | ||
197 | + }; | ||
198 | + } | ||
199 | + | ||
200 | + FS_proto.abort = function(){}; | ||
201 | + FS_proto.readyState = FS_proto.INIT = 0; | ||
202 | + FS_proto.WRITING = 1; | ||
203 | + FS_proto.DONE = 2; | ||
204 | + | ||
205 | + FS_proto.error = | ||
206 | + FS_proto.onwritestart = | ||
207 | + FS_proto.onprogress = | ||
208 | + FS_proto.onwrite = | ||
209 | + FS_proto.onabort = | ||
210 | + FS_proto.onerror = | ||
211 | + FS_proto.onwriteend = | ||
212 | + null; | ||
213 | + | ||
214 | + return saveAs; | ||
215 | +}( | ||
216 | + typeof self !== "undefined" && self | ||
217 | + || typeof window !== "undefined" && window | ||
218 | + || this.content | ||
219 | +)); | ||
220 | + | ||
221 | + | ||
222 | +// Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons` | ||
223 | +// since this file can be loaded before Button's core! | ||
224 | +DataTable.fileSave = _saveAs; | ||
225 | + | ||
226 | + | ||
227 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
228 | + * Local (private) functions | ||
229 | + */ | ||
230 | + | ||
231 | +/** | ||
232 | + * Get the file name for an exported file. | ||
233 | + * | ||
234 | + * @param {object} config Button configuration | ||
235 | + * @param {boolean} incExtension Include the file name extension | ||
236 | + */ | ||
237 | +var _filename = function ( config, incExtension ) | ||
238 | +{ | ||
239 | + // Backwards compatibility | ||
240 | + var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined ? | ||
241 | + config.title : | ||
242 | + config.filename; | ||
243 | + | ||
244 | + if ( typeof filename === 'function' ) { | ||
245 | + filename = filename(); | ||
246 | + } | ||
247 | + | ||
248 | + if ( filename.indexOf( '*' ) !== -1 ) { | ||
249 | + filename = $.trim( filename.replace( '*', $('title').text() ) ); | ||
250 | + } | ||
251 | + | ||
252 | + // Strip characters which the OS will object to | ||
253 | + filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ""); | ||
254 | + | ||
255 | + return incExtension === undefined || incExtension === true ? | ||
256 | + filename+config.extension : | ||
257 | + filename; | ||
258 | +}; | ||
259 | + | ||
260 | +/** | ||
261 | + * Get the sheet name for Excel exports. | ||
262 | + * | ||
263 | + * @param {object} config Button configuration | ||
264 | + */ | ||
265 | +var _sheetname = function ( config ) | ||
266 | +{ | ||
267 | + var sheetName = 'Sheet1'; | ||
268 | + | ||
269 | + if ( config.sheetName ) { | ||
270 | + sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, ''); | ||
271 | + } | ||
272 | + | ||
273 | +return sheetName; | ||
274 | +}; | ||
275 | + | ||
276 | +/** | ||
277 | + * Get the title for an exported file. | ||
278 | + * | ||
279 | + * @param {object} config Button configuration | ||
280 | + */ | ||
281 | +var _title = function ( config ) | ||
282 | +{ | ||
283 | + var title = config.title; | ||
284 | + | ||
285 | + if ( typeof title === 'function' ) { | ||
286 | + title = title(); | ||
287 | + } | ||
288 | + | ||
289 | + return title.indexOf( '*' ) !== -1 ? | ||
290 | + title.replace( '*', $('title').text() || 'Exported data' ) : | ||
291 | + title; | ||
292 | +}; | ||
293 | + | ||
294 | +/** | ||
295 | + * Get the newline character(s) | ||
296 | + * | ||
297 | + * @param {object} config Button configuration | ||
298 | + * @return {string} Newline character | ||
299 | + */ | ||
300 | +var _newLine = function ( config ) | ||
301 | +{ | ||
302 | + return config.newline ? | ||
303 | + config.newline : | ||
304 | + navigator.userAgent.match(/Windows/) ? | ||
305 | + '\r\n' : | ||
306 | + '\n'; | ||
307 | +}; | ||
308 | + | ||
309 | +/** | ||
310 | + * Combine the data from the `buttons.exportData` method into a string that | ||
311 | + * will be used in the export file. | ||
312 | + * | ||
313 | + * @param {DataTable.Api} dt DataTables API instance | ||
314 | + * @param {object} config Button configuration | ||
315 | + * @return {object} The data to export | ||
316 | + */ | ||
317 | +var _exportData = function ( dt, config ) | ||
318 | +{ | ||
319 | + var newLine = _newLine( config ); | ||
320 | + var data = dt.buttons.exportData( config.exportOptions ); | ||
321 | + var boundary = config.fieldBoundary; | ||
322 | + var separator = config.fieldSeparator; | ||
323 | + var reBoundary = new RegExp( boundary, 'g' ); | ||
324 | + var escapeChar = config.escapeChar !== undefined ? | ||
325 | + config.escapeChar : | ||
326 | + '\\'; | ||
327 | + var join = function ( a ) { | ||
328 | + var s = ''; | ||
329 | + | ||
330 | + // If there is a field boundary, then we might need to escape it in | ||
331 | + // the source data | ||
332 | + for ( var i=0, ien=a.length ; i<ien ; i++ ) { | ||
333 | + if ( i > 0 ) { | ||
334 | + s += separator; | ||
335 | + } | ||
336 | + | ||
337 | + s += boundary ? | ||
338 | + boundary + ('' + a[i]).replace( reBoundary, escapeChar+boundary ) + boundary : | ||
339 | + a[i]; | ||
340 | + } | ||
341 | + | ||
342 | + return s; | ||
343 | + }; | ||
344 | + | ||
345 | + var header = config.header ? join( data.header )+newLine : ''; | ||
346 | + var footer = config.footer && data.footer ? newLine+join( data.footer ) : ''; | ||
347 | + var body = []; | ||
348 | + | ||
349 | + for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { | ||
350 | + body.push( join( data.body[i] ) ); | ||
351 | + } | ||
352 | + | ||
353 | + return { | ||
354 | + str: header + body.join( newLine ) + footer, | ||
355 | + rows: body.length | ||
356 | + }; | ||
357 | +}; | ||
358 | + | ||
359 | +/** | ||
360 | + * Older versions of Safari (prior to tech preview 18) don't support the | ||
361 | + * download option required. | ||
362 | + * | ||
363 | + * @return {Boolean} `true` if old Safari | ||
364 | + */ | ||
365 | +var _isDuffSafari = function () | ||
366 | +{ | ||
367 | + var safari = navigator.userAgent.indexOf('Safari') !== -1 && | ||
368 | + navigator.userAgent.indexOf('Chrome') === -1 && | ||
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; | ||
381 | +}; | ||
382 | + | ||
383 | +/** | ||
384 | + * Convert from numeric position to letter for column names in Excel | ||
385 | + * @param {int} n Column number | ||
386 | + * @return {string} Column letter(s) name | ||
387 | + */ | ||
388 | +function createCellPos( n ){ | ||
389 | + var ordA = 'A'.charCodeAt(0); | ||
390 | + var ordZ = 'Z'.charCodeAt(0); | ||
391 | + var len = ordZ - ordA + 1; | ||
392 | + var s = ""; | ||
393 | + | ||
394 | + while( n >= 0 ) { | ||
395 | + s = String.fromCharCode(n % len + ordA) + s; | ||
396 | + n = Math.floor(n / len) - 1; | ||
397 | + } | ||
398 | + | ||
399 | + return s; | ||
400 | +} | ||
401 | + | ||
402 | +try { | ||
403 | + var _serialiser = new XMLSerializer(); | ||
404 | + var _ieExcel; | ||
405 | +} | ||
406 | +catch (t) {} | ||
407 | + | ||
408 | +/** | ||
409 | + * Recursively add XML files from an object's structure to a ZIP file. This | ||
410 | + * allows the XSLX file to be easily defined with an object's structure matching | ||
411 | + * the files structure. | ||
412 | + * | ||
413 | + * @param {JSZip} zip ZIP package | ||
414 | + * @param {object} obj Object to add (recursive) | ||
415 | + */ | ||
416 | +function _addToZip( zip, obj ) { | ||
417 | + if ( _ieExcel === undefined ) { | ||
418 | + // Detect if we are dealing with IE's _awful_ serialiser by seeing if it | ||
419 | + // drop attributes | ||
420 | + _ieExcel = _serialiser | ||
421 | + .serializeToString( | ||
422 | + $.parseXML( excelStrings['xl/worksheets/sheet1.xml'] ) | ||
423 | + ) | ||
424 | + .indexOf( 'xmlns:r' ) === -1; | ||
425 | + } | ||
426 | + | ||
427 | + $.each( obj, function ( name, val ) { | ||
428 | + if ( $.isPlainObject( val ) ) { | ||
429 | + var newDir = zip.folder( name ); | ||
430 | + _addToZip( newDir, val ); | ||
431 | + } | ||
432 | + else { | ||
433 | + if ( _ieExcel ) { | ||
434 | + // IE's XML serialiser will drop some name space attributes from | ||
435 | + // from the root node, so we need to save them. Do this by | ||
436 | + // replacing the namespace nodes with a regular attribute that | ||
437 | + // we convert back when serialised. Edge does not have this | ||
438 | + // issue | ||
439 | + var worksheet = val.childNodes[0]; | ||
440 | + var i, ien; | ||
441 | + var attrs = []; | ||
442 | + | ||
443 | + for ( i=worksheet.attributes.length-1 ; i>=0 ; i-- ) { | ||
444 | + var attrName = worksheet.attributes[i].nodeName; | ||
445 | + var attrValue = worksheet.attributes[i].nodeValue; | ||
446 | + | ||
447 | + if ( attrName.indexOf( ':' ) !== -1 ) { | ||
448 | + attrs.push( { name: attrName, value: attrValue } ); | ||
449 | + | ||
450 | + worksheet.removeAttribute( attrName ); | ||
451 | + } | ||
452 | + } | ||
453 | + | ||
454 | + for ( i=0, ien=attrs.length ; i<ien ; i++ ) { | ||
455 | + var attr = val.createAttribute( attrs[i].name.replace( ':', '_dt_b_namespace_token_' ) ); | ||
456 | + attr.value = attrs[i].value; | ||
457 | + worksheet.setAttributeNode( attr ); | ||
458 | + } | ||
459 | + } | ||
460 | + | ||
461 | + var str = _serialiser.serializeToString(val); | ||
462 | + | ||
463 | + // Fix IE's XML | ||
464 | + if ( _ieExcel ) { | ||
465 | + // IE doesn't include the XML declaration | ||
466 | + if ( str.indexOf( '<?xml' ) === -1 ) { | ||
467 | + str = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+str; | ||
468 | + } | ||
469 | + | ||
470 | + // Return namespace attributes to being as such | ||
471 | + str = str.replace( /_dt_b_namespace_token_/g, ':' ); | ||
472 | + } | ||
473 | + | ||
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>' ); | ||
477 | + | ||
478 | + zip.file( name, str ); | ||
479 | + } | ||
480 | + } ); | ||
481 | +} | ||
482 | + | ||
483 | +/** | ||
484 | + * Create an XML node and add any children, attributes, etc without needing to | ||
485 | + * be verbose in the DOM. | ||
486 | + * | ||
487 | + * @param {object} doc XML document | ||
488 | + * @param {string} nodeName Node name | ||
489 | + * @param {object} opts Options - can be `attr` (attributes), `children` | ||
490 | + * (child nodes) and `text` (text content) | ||
491 | + * @return {node} Created node | ||
492 | + */ | ||
493 | +function _createNode( doc, nodeName, opts ) { | ||
494 | + var tempNode = doc.createElement( nodeName ); | ||
495 | + | ||
496 | + if ( opts ) { | ||
497 | + if ( opts.attr ) { | ||
498 | + $(tempNode).attr( opts.attr ); | ||
499 | + } | ||
500 | + | ||
501 | + if( opts.children ) { | ||
502 | + $.each( opts.children, function ( key, value ) { | ||
503 | + tempNode.appendChild( value ); | ||
504 | + }); | ||
505 | + } | ||
506 | + | ||
507 | + if( opts.text ) { | ||
508 | + tempNode.appendChild( doc.createTextNode( opts.text ) ); | ||
509 | + } | ||
510 | + } | ||
511 | + | ||
512 | + return tempNode; | ||
513 | +} | ||
514 | + | ||
515 | +/** | ||
516 | + * Get the width for an Excel column based on the contents of that column | ||
517 | + * @param {object} data Data for export | ||
518 | + * @param {int} col Column index | ||
519 | + * @return {int} Column width | ||
520 | + */ | ||
521 | +function _excelColWidth( data, col ) { | ||
522 | + var max = data.header[col].length; | ||
523 | + var len, lineSplit, str; | ||
524 | + | ||
525 | + if ( data.footer && data.footer[col].length > max ) { | ||
526 | + max = data.footer[col].length; | ||
527 | + } | ||
528 | + | ||
529 | + for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { | ||
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 | + } | ||
545 | + | ||
546 | + if ( len > max ) { | ||
547 | + max = len; | ||
548 | + } | ||
549 | + | ||
550 | + // Max width rather than having potentially massive column widths | ||
551 | + if ( max > 40 ) { | ||
552 | + break; | ||
553 | + } | ||
554 | + } | ||
555 | + | ||
556 | + max *= 1.3; | ||
557 | + | ||
558 | + // And a min width | ||
559 | + return max > 6 ? max : 6; | ||
560 | +} | ||
561 | + | ||
562 | +// Excel - Pre-defined strings to build a basic XLSX file | ||
563 | +var excelStrings = { | ||
564 | + "_rels/.rels": | ||
565 | + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ | ||
566 | + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+ | ||
567 | + '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'+ | ||
568 | + '</Relationships>', | ||
569 | + | ||
570 | + "xl/_rels/workbook.xml.rels": | ||
571 | + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ | ||
572 | + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'+ | ||
573 | + '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'+ | ||
574 | + '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>'+ | ||
575 | + '</Relationships>', | ||
576 | + | ||
577 | + "[Content_Types].xml": | ||
578 | + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ | ||
579 | + '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'+ | ||
580 | + '<Default Extension="xml" ContentType="application/xml" />'+ | ||
581 | + '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />'+ | ||
582 | + '<Default Extension="jpeg" ContentType="image/jpeg" />'+ | ||
583 | + '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" />'+ | ||
584 | + '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" />'+ | ||
585 | + '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" />'+ | ||
586 | + '</Types>', | ||
587 | + | ||
588 | + "xl/workbook.xml": | ||
589 | + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ | ||
590 | + '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'+ | ||
591 | + '<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="24816"/>'+ | ||
592 | + '<workbookPr showInkAnnotation="0" autoCompressPictures="0"/>'+ | ||
593 | + '<bookViews>'+ | ||
594 | + '<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>'+ | ||
595 | + '</bookViews>'+ | ||
596 | + '<sheets>'+ | ||
597 | + '<sheet name="" sheetId="1" r:id="rId1"/>'+ | ||
598 | + '</sheets>'+ | ||
599 | + '</workbook>', | ||
600 | + | ||
601 | + "xl/worksheets/sheet1.xml": | ||
602 | + '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+ | ||
603 | + '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">'+ | ||
604 | + '<sheetData/>'+ | ||
605 | + '</worksheet>', | ||
606 | + | ||
607 | + "xl/styles.xml": | ||
608 | + '<?xml version="1.0" encoding="UTF-8"?>'+ | ||
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=""£"#,##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>'+ | ||
618 | + '<fonts count="5" x14ac:knownFonts="1">'+ | ||
619 | + '<font>'+ | ||
620 | + '<sz val="11" />'+ | ||
621 | + '<name val="Calibri" />'+ | ||
622 | + '</font>'+ | ||
623 | + '<font>'+ | ||
624 | + '<sz val="11" />'+ | ||
625 | + '<name val="Calibri" />'+ | ||
626 | + '<color rgb="FFFFFFFF" />'+ | ||
627 | + '</font>'+ | ||
628 | + '<font>'+ | ||
629 | + '<sz val="11" />'+ | ||
630 | + '<name val="Calibri" />'+ | ||
631 | + '<b />'+ | ||
632 | + '</font>'+ | ||
633 | + '<font>'+ | ||
634 | + '<sz val="11" />'+ | ||
635 | + '<name val="Calibri" />'+ | ||
636 | + '<i />'+ | ||
637 | + '</font>'+ | ||
638 | + '<font>'+ | ||
639 | + '<sz val="11" />'+ | ||
640 | + '<name val="Calibri" />'+ | ||
641 | + '<u />'+ | ||
642 | + '</font>'+ | ||
643 | + '</fonts>'+ | ||
644 | + '<fills count="6">'+ | ||
645 | + '<fill>'+ | ||
646 | + '<patternFill patternType="none" />'+ | ||
647 | + '</fill>'+ | ||
648 | + '<fill/>'+ // Excel appears to use this as a dotted background regardless of values | ||
649 | + '<fill>'+ | ||
650 | + '<patternFill patternType="solid">'+ | ||
651 | + '<fgColor rgb="FFD9D9D9" />'+ | ||
652 | + '<bgColor indexed="64" />'+ | ||
653 | + '</patternFill>'+ | ||
654 | + '</fill>'+ | ||
655 | + '<fill>'+ | ||
656 | + '<patternFill patternType="solid">'+ | ||
657 | + '<fgColor rgb="FFD99795" />'+ | ||
658 | + '<bgColor indexed="64" />'+ | ||
659 | + '</patternFill>'+ | ||
660 | + '</fill>'+ | ||
661 | + '<fill>'+ | ||
662 | + '<patternFill patternType="solid">'+ | ||
663 | + '<fgColor rgb="ffc6efce" />'+ | ||
664 | + '<bgColor indexed="64" />'+ | ||
665 | + '</patternFill>'+ | ||
666 | + '</fill>'+ | ||
667 | + '<fill>'+ | ||
668 | + '<patternFill patternType="solid">'+ | ||
669 | + '<fgColor rgb="ffc6cfef" />'+ | ||
670 | + '<bgColor indexed="64" />'+ | ||
671 | + '</patternFill>'+ | ||
672 | + '</fill>'+ | ||
673 | + '</fills>'+ | ||
674 | + '<borders count="2">'+ | ||
675 | + '<border>'+ | ||
676 | + '<left />'+ | ||
677 | + '<right />'+ | ||
678 | + '<top />'+ | ||
679 | + '<bottom />'+ | ||
680 | + '<diagonal />'+ | ||
681 | + '</border>'+ | ||
682 | + '<border diagonalUp="false" diagonalDown="false">'+ | ||
683 | + '<left style="thin">'+ | ||
684 | + '<color auto="1" />'+ | ||
685 | + '</left>'+ | ||
686 | + '<right style="thin">'+ | ||
687 | + '<color auto="1" />'+ | ||
688 | + '</right>'+ | ||
689 | + '<top style="thin">'+ | ||
690 | + '<color auto="1" />'+ | ||
691 | + '</top>'+ | ||
692 | + '<bottom style="thin">'+ | ||
693 | + '<color auto="1" />'+ | ||
694 | + '</bottom>'+ | ||
695 | + '<diagonal />'+ | ||
696 | + '</border>'+ | ||
697 | + '</borders>'+ | ||
698 | + '<cellStyleXfs count="1">'+ | ||
699 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />'+ | ||
700 | + '</cellStyleXfs>'+ | ||
701 | + '<cellXfs count="65">'+ | ||
702 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
703 | + '<xf numFmtId="0" fontId="1" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
704 | + '<xf numFmtId="0" fontId="2" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
705 | + '<xf numFmtId="0" fontId="3" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
706 | + '<xf numFmtId="0" fontId="4" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
707 | + '<xf numFmtId="0" fontId="0" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
708 | + '<xf numFmtId="0" fontId="1" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
709 | + '<xf numFmtId="0" fontId="2" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
710 | + '<xf numFmtId="0" fontId="3" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
711 | + '<xf numFmtId="0" fontId="4" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
712 | + '<xf numFmtId="0" fontId="0" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
713 | + '<xf numFmtId="0" fontId="1" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
714 | + '<xf numFmtId="0" fontId="2" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
715 | + '<xf numFmtId="0" fontId="3" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
716 | + '<xf numFmtId="0" fontId="4" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
717 | + '<xf numFmtId="0" fontId="0" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
718 | + '<xf numFmtId="0" fontId="1" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
719 | + '<xf numFmtId="0" fontId="2" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
720 | + '<xf numFmtId="0" fontId="3" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
721 | + '<xf numFmtId="0" fontId="4" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
722 | + '<xf numFmtId="0" fontId="0" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
723 | + '<xf numFmtId="0" fontId="1" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
724 | + '<xf numFmtId="0" fontId="2" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
725 | + '<xf numFmtId="0" fontId="3" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
726 | + '<xf numFmtId="0" fontId="4" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
727 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
728 | + '<xf numFmtId="0" fontId="1" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
729 | + '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
730 | + '<xf numFmtId="0" fontId="3" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
731 | + '<xf numFmtId="0" fontId="4" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
732 | + '<xf numFmtId="0" fontId="0" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
733 | + '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
734 | + '<xf numFmtId="0" fontId="2" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
735 | + '<xf numFmtId="0" fontId="3" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
736 | + '<xf numFmtId="0" fontId="4" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
737 | + '<xf numFmtId="0" fontId="0" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
738 | + '<xf numFmtId="0" fontId="1" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
739 | + '<xf numFmtId="0" fontId="2" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
740 | + '<xf numFmtId="0" fontId="3" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
741 | + '<xf numFmtId="0" fontId="4" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
742 | + '<xf numFmtId="0" fontId="0" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
743 | + '<xf numFmtId="0" fontId="1" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
744 | + '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
745 | + '<xf numFmtId="0" fontId="3" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
746 | + '<xf numFmtId="0" fontId="4" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
747 | + '<xf numFmtId="0" fontId="0" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
748 | + '<xf numFmtId="0" fontId="1" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
749 | + '<xf numFmtId="0" fontId="2" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
750 | + '<xf numFmtId="0" fontId="3" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
751 | + '<xf numFmtId="0" fontId="4" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>'+ | ||
752 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ | ||
753 | + '<alignment horizontal="left"/>'+ | ||
754 | + '</xf>'+ | ||
755 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ | ||
756 | + '<alignment horizontal="center"/>'+ | ||
757 | + '</xf>'+ | ||
758 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ | ||
759 | + '<alignment horizontal="right"/>'+ | ||
760 | + '</xf>'+ | ||
761 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ | ||
762 | + '<alignment horizontal="fill"/>'+ | ||
763 | + '</xf>'+ | ||
764 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ | ||
765 | + '<alignment textRotation="90"/>'+ | ||
766 | + '</xf>'+ | ||
767 | + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">'+ | ||
768 | + '<alignment wrapText="1"/>'+ | ||
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 | + '</cellXfs>'+ | ||
780 | + '<cellStyles count="1">'+ | ||
781 | + '<cellStyle name="Normal" xfId="0" builtinId="0" />'+ | ||
782 | + '</cellStyles>'+ | ||
783 | + '<dxfs count="0" />'+ | ||
784 | + '<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4" />'+ | ||
785 | + '</styleSheet>' | ||
786 | +}; | ||
787 | +// Note we could use 3 `for` loops for the styles, but when gzipped there is | ||
788 | +// virtually no difference in size, since the above can be easily compressed | ||
789 | + | ||
790 | +// Pattern matching for special number formats. Perhaps this should be exposed | ||
791 | +// via an API in future? | ||
792 | +// Ref: section 3.8.30 - built in formatters in open spreadsheet | ||
793 | +// https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf | ||
794 | +var _excelSpecials = [ | ||
795 | + { match: /^\-?\d+\.\d%$/, style: 60, fmt: function (d) { return d/100; } }, // Precent with d.p. | ||
796 | + { match: /^\-?\d+\.?\d*%$/, style: 56, fmt: function (d) { return d/100; } }, // Percent | ||
797 | + { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars | ||
798 | + { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds | ||
799 | + { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros | ||
800 | + { match: /^\([\d,]+\)$/, style: 61, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets | ||
801 | + { match: /^\([\d,]+\.\d{2}\)$/, style: 62, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - 2d.p. | ||
802 | + { match: /^[\d,]+$/, style: 63 }, // Numbers with thousand separators | ||
803 | + { match: /^[\d,]+\.\d{2}$/, style: 64 } // Numbers with 2d.p. and thousands separators | ||
804 | +]; | ||
805 | + | ||
806 | + | ||
807 | + | ||
808 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
809 | + * Buttons | ||
810 | + */ | ||
811 | + | ||
812 | +// | ||
813 | +// Copy to clipboard | ||
814 | +// | ||
815 | +DataTable.ext.buttons.copyHtml5 = { | ||
816 | + className: 'buttons-copy buttons-html5', | ||
817 | + | ||
818 | + text: function ( dt ) { | ||
819 | + return dt.i18n( 'buttons.copy', 'Copy' ); | ||
820 | + }, | ||
821 | + | ||
822 | + action: function ( e, dt, button, config ) { | ||
823 | + var exportData = _exportData( dt, config ); | ||
824 | + var output = exportData.str; | ||
825 | + var hiddenDiv = $('<div/>') | ||
826 | + .css( { | ||
827 | + height: 1, | ||
828 | + width: 1, | ||
829 | + overflow: 'hidden', | ||
830 | + position: 'fixed', | ||
831 | + top: 0, | ||
832 | + left: 0 | ||
833 | + } ); | ||
834 | + | ||
835 | + if ( config.customize ) { | ||
836 | + output = config.customize( output, config ); | ||
837 | + } | ||
838 | + | ||
839 | + var textarea = $('<textarea readonly/>') | ||
840 | + .val( output ) | ||
841 | + .appendTo( hiddenDiv ); | ||
842 | + | ||
843 | + // For browsers that support the copy execCommand, try to use it | ||
844 | + if ( document.queryCommandSupported('copy') ) { | ||
845 | + hiddenDiv.appendTo( dt.table().container() ); | ||
846 | + textarea[0].focus(); | ||
847 | + textarea[0].select(); | ||
848 | + | ||
849 | + try { | ||
850 | + var successful = document.execCommand( 'copy' ); | ||
851 | + hiddenDiv.remove(); | ||
852 | + | ||
853 | + if (successful) { | ||
854 | + dt.buttons.info( | ||
855 | + dt.i18n( 'buttons.copyTitle', 'Copy to clipboard' ), | ||
856 | + dt.i18n( 'buttons.copySuccess', { | ||
857 | + 1: 'Copied one row to clipboard', | ||
858 | + _: 'Copied %d rows to clipboard' | ||
859 | + }, exportData.rows ), | ||
860 | + 2000 | ||
861 | + ); | ||
862 | + return; | ||
863 | + } | ||
864 | + } | ||
865 | + catch (t) {} | ||
866 | + } | ||
867 | + | ||
868 | + // Otherwise we show the text box and instruct the user to use it | ||
869 | + var message = $('<span>'+dt.i18n( 'buttons.copyKeys', | ||
870 | + 'Press <i>ctrl</i> or <i>\u2318</i> + <i>C</i> to copy the table data<br>to your system clipboard.<br><br>'+ | ||
871 | + 'To cancel, click this message or press escape.' )+'</span>' | ||
872 | + ) | ||
873 | + .append( hiddenDiv ); | ||
874 | + | ||
875 | + dt.buttons.info( dt.i18n( 'buttons.copyTitle', 'Copy to clipboard' ), message, 0 ); | ||
876 | + | ||
877 | + // Select the text so when the user activates their system clipboard | ||
878 | + // it will copy that text | ||
879 | + textarea[0].focus(); | ||
880 | + textarea[0].select(); | ||
881 | + | ||
882 | + // Event to hide the message when the user is done | ||
883 | + var container = $(message).closest('.dt-button-info'); | ||
884 | + var close = function () { | ||
885 | + container.off( 'click.buttons-copy' ); | ||
886 | + $(document).off( '.buttons-copy' ); | ||
887 | + dt.buttons.info( false ); | ||
888 | + }; | ||
889 | + | ||
890 | + container.on( 'click.buttons-copy', close ); | ||
891 | + $(document) | ||
892 | + .on( 'keydown.buttons-copy', function (e) { | ||
893 | + if ( e.keyCode === 27 ) { // esc | ||
894 | + close(); | ||
895 | + } | ||
896 | + } ) | ||
897 | + .on( 'copy.buttons-copy cut.buttons-copy', function () { | ||
898 | + close(); | ||
899 | + } ); | ||
900 | + }, | ||
901 | + | ||
902 | + exportOptions: {}, | ||
903 | + | ||
904 | + fieldSeparator: '\t', | ||
905 | + | ||
906 | + fieldBoundary: '', | ||
907 | + | ||
908 | + header: true, | ||
909 | + | ||
910 | + footer: false | ||
911 | +}; | ||
912 | + | ||
913 | +// | ||
914 | +// CSV export | ||
915 | +// | ||
916 | +DataTable.ext.buttons.csvHtml5 = { | ||
917 | + bom: false, | ||
918 | + | ||
919 | + className: 'buttons-csv buttons-html5', | ||
920 | + | ||
921 | + available: function () { | ||
922 | + return window.FileReader !== undefined && window.Blob; | ||
923 | + }, | ||
924 | + | ||
925 | + text: function ( dt ) { | ||
926 | + return dt.i18n( 'buttons.csv', 'CSV' ); | ||
927 | + }, | ||
928 | + | ||
929 | + action: function ( e, dt, button, config ) { | ||
930 | + // Set the text | ||
931 | + var output = _exportData( dt, config ).str; | ||
932 | + var charset = config.charset; | ||
933 | + | ||
934 | + if ( config.customize ) { | ||
935 | + output = config.customize( output, config ); | ||
936 | + } | ||
937 | + | ||
938 | + if ( charset !== false ) { | ||
939 | + if ( ! charset ) { | ||
940 | + charset = document.characterSet || document.charset; | ||
941 | + } | ||
942 | + | ||
943 | + if ( charset ) { | ||
944 | + charset = ';charset='+charset; | ||
945 | + } | ||
946 | + } | ||
947 | + else { | ||
948 | + charset = ''; | ||
949 | + } | ||
950 | + | ||
951 | + if ( config.bom ) { | ||
952 | + output = '\ufeff' + output; | ||
953 | + } | ||
954 | + | ||
955 | + _saveAs( | ||
956 | + new Blob( [output], {type: 'text/csv'+charset} ), | ||
957 | + _filename( config ), | ||
958 | + true | ||
959 | + ); | ||
960 | + }, | ||
961 | + | ||
962 | + filename: '*', | ||
963 | + | ||
964 | + extension: '.csv', | ||
965 | + | ||
966 | + exportOptions: {}, | ||
967 | + | ||
968 | + fieldSeparator: ',', | ||
969 | + | ||
970 | + fieldBoundary: '"', | ||
971 | + | ||
972 | + escapeChar: '"', | ||
973 | + | ||
974 | + charset: null, | ||
975 | + | ||
976 | + header: true, | ||
977 | + | ||
978 | + footer: false | ||
979 | +}; | ||
980 | + | ||
981 | +// | ||
982 | +// Excel (xlsx) export | ||
983 | +// | ||
984 | +DataTable.ext.buttons.excelHtml5 = { | ||
985 | + className: 'buttons-excel buttons-html5', | ||
986 | + | ||
987 | + available: function () { | ||
988 | + return window.FileReader !== undefined && _jsZip() !== undefined && ! _isDuffSafari() && _serialiser; | ||
989 | + }, | ||
990 | + | ||
991 | + text: function ( dt ) { | ||
992 | + return dt.i18n( 'buttons.excel', 'Excel' ); | ||
993 | + }, | ||
994 | + | ||
995 | + action: function ( e, dt, button, config ) { | ||
996 | + var rowPos = 0; | ||
997 | + var getXml = function ( type ) { | ||
998 | + var str = excelStrings[ type ]; | ||
999 | + | ||
1000 | + //str = str.replace( /xmlns:/g, 'xmlns_' ).replace( /mc:/g, 'mc_' ); | ||
1001 | + | ||
1002 | + return $.parseXML( str ); | ||
1003 | + }; | ||
1004 | + var rels = getXml('xl/worksheets/sheet1.xml'); | ||
1005 | + var relsGet = rels.getElementsByTagName( "sheetData" )[0]; | ||
1006 | + | ||
1007 | + var xlsx = { | ||
1008 | + _rels: { | ||
1009 | + ".rels": getXml('_rels/.rels') | ||
1010 | + }, | ||
1011 | + xl: { | ||
1012 | + _rels: { | ||
1013 | + "workbook.xml.rels": getXml('xl/_rels/workbook.xml.rels') | ||
1014 | + }, | ||
1015 | + "workbook.xml": getXml('xl/workbook.xml'), | ||
1016 | + "styles.xml": getXml('xl/styles.xml'), | ||
1017 | + "worksheets": { | ||
1018 | + "sheet1.xml": rels | ||
1019 | + } | ||
1020 | + | ||
1021 | + }, | ||
1022 | + "[Content_Types].xml": getXml('[Content_Types].xml') | ||
1023 | + }; | ||
1024 | + | ||
1025 | + var data = dt.buttons.exportData( config.exportOptions ); | ||
1026 | + var currentRow, rowNode; | ||
1027 | + var addRow = function ( row ) { | ||
1028 | + currentRow = rowPos+1; | ||
1029 | + rowNode = _createNode( rels, "row", { attr: {r:currentRow} } ); | ||
1030 | + | ||
1031 | + for ( var i=0, ien=row.length ; i<ien ; i++ ) { | ||
1032 | + // Concat both the Cell Columns as a letter and the Row of the cell. | ||
1033 | + var cellId = createCellPos(i) + '' + currentRow; | ||
1034 | + var cell = null; | ||
1035 | + | ||
1036 | + // For null, undefined of blank cell, continue so it doesn't create the _createNode | ||
1037 | + if ( row[i] === null || row[i] === undefined || row[i] === '' ) { | ||
1038 | + continue; | ||
1039 | + } | ||
1040 | + | ||
1041 | + row[i] = $.trim( row[i] ); | ||
1042 | + | ||
1043 | + // Special number formatting options | ||
1044 | + for ( var j=0, jen=_excelSpecials.length ; j<jen ; j++ ) { | ||
1045 | + var special = _excelSpecials[j]; | ||
1046 | + | ||
1047 | + if ( row[i].match && row[i].match( special.match ) ) { | ||
1048 | + var val = row[i].replace(/[^\d\.\-]/g, ''); | ||
1049 | + | ||
1050 | + if ( special.fmt ) { | ||
1051 | + val = special.fmt( val ); | ||
1052 | + } | ||
1053 | + | ||
1054 | + cell = _createNode( rels, 'c', { | ||
1055 | + attr: { | ||
1056 | + r: cellId, | ||
1057 | + s: special.style | ||
1058 | + }, | ||
1059 | + children: [ | ||
1060 | + _createNode( rels, 'v', { text: val } ) | ||
1061 | + ] | ||
1062 | + } ); | ||
1063 | + | ||
1064 | + break; | ||
1065 | + } | ||
1066 | + } | ||
1067 | + | ||
1068 | + if ( ! cell ) { | ||
1069 | + if ( typeof row[i] === 'number' || ( | ||
1070 | + row[i].match && | ||
1071 | + row[i].match(/^-?\d+(\.\d+)?$/) && | ||
1072 | + ! row[i].match(/^0\d+/) ) | ||
1073 | + ) { | ||
1074 | + // Detect numbers - don't match numbers with leading zeros | ||
1075 | + // or a negative anywhere but the start | ||
1076 | + cell = _createNode( rels, 'c', { | ||
1077 | + attr: { | ||
1078 | + t: 'n', | ||
1079 | + r: cellId | ||
1080 | + }, | ||
1081 | + children: [ | ||
1082 | + _createNode( rels, 'v', { text: row[i] } ) | ||
1083 | + ] | ||
1084 | + } ); | ||
1085 | + } | ||
1086 | + else { | ||
1087 | + // String output - replace non standard characters for text output | ||
1088 | + var text = ! row[i].replace ? | ||
1089 | + row[i] : | ||
1090 | + row[i].replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, ''); | ||
1091 | + | ||
1092 | + cell = _createNode( rels, 'c', { | ||
1093 | + attr: { | ||
1094 | + t: 'inlineStr', | ||
1095 | + r: cellId | ||
1096 | + }, | ||
1097 | + children:{ | ||
1098 | + row: _createNode( rels, 'is', { | ||
1099 | + children: { | ||
1100 | + row: _createNode( rels, 't', { | ||
1101 | + text: text | ||
1102 | + } ) | ||
1103 | + } | ||
1104 | + } ) | ||
1105 | + } | ||
1106 | + } ); | ||
1107 | + } | ||
1108 | + } | ||
1109 | + | ||
1110 | + rowNode.appendChild( cell ); | ||
1111 | + } | ||
1112 | + | ||
1113 | + relsGet.appendChild(rowNode); | ||
1114 | + rowPos++; | ||
1115 | + }; | ||
1116 | + | ||
1117 | + $( 'sheets sheet', xlsx.xl['workbook.xml'] ).attr( 'name', _sheetname( config ) ); | ||
1118 | + | ||
1119 | + if ( config.customizeData ) { | ||
1120 | + config.customizeData( data ); | ||
1121 | + } | ||
1122 | + | ||
1123 | + if ( config.header ) { | ||
1124 | + addRow( data.header, rowPos ); | ||
1125 | + $('row c', rels).attr( 's', '2' ); // bold | ||
1126 | + } | ||
1127 | + | ||
1128 | + for ( var n=0, ie=data.body.length ; n<ie ; n++ ) { | ||
1129 | + addRow( data.body[n], rowPos ); | ||
1130 | + } | ||
1131 | + | ||
1132 | + if ( config.footer && data.footer ) { | ||
1133 | + addRow( data.footer, rowPos); | ||
1134 | + $('row:last c', rels).attr( 's', '2' ); // bold | ||
1135 | + } | ||
1136 | + | ||
1137 | + // Set column widths | ||
1138 | + var cols = _createNode( rels, 'cols' ); | ||
1139 | + $('worksheet', rels).prepend( cols ); | ||
1140 | + | ||
1141 | + for ( var i=0, ien=data.header.length ; i<ien ; i++ ) { | ||
1142 | + cols.appendChild( _createNode( rels, 'col', { | ||
1143 | + attr: { | ||
1144 | + min: i+1, | ||
1145 | + max: i+1, | ||
1146 | + width: _excelColWidth( data, i ), | ||
1147 | + customWidth: 1 | ||
1148 | + } | ||
1149 | + } ) ); | ||
1150 | + } | ||
1151 | + | ||
1152 | + // Let the developer customise the document if they want to | ||
1153 | + if ( config.customize ) { | ||
1154 | + config.customize( xlsx ); | ||
1155 | + } | ||
1156 | + | ||
1157 | + var jszip = _jsZip(); | ||
1158 | + var zip = new jszip(); | ||
1159 | + var zipConfig = { | ||
1160 | + type: 'blob', | ||
1161 | + mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' | ||
1162 | + }; | ||
1163 | + | ||
1164 | + _addToZip( zip, xlsx ); | ||
1165 | + | ||
1166 | + if ( zip.generateAsync ) { | ||
1167 | + // JSZip 3+ | ||
1168 | + zip | ||
1169 | + .generateAsync( zipConfig ) | ||
1170 | + .then( function ( blob ) { | ||
1171 | + _saveAs( blob, _filename( config ) ); | ||
1172 | + } ); | ||
1173 | + } | ||
1174 | + else { | ||
1175 | + // JSZip 2.5 | ||
1176 | + _saveAs( | ||
1177 | + zip.generate( zipConfig ), | ||
1178 | + _filename( config ) | ||
1179 | + ); | ||
1180 | + } | ||
1181 | + }, | ||
1182 | + | ||
1183 | + filename: '*', | ||
1184 | + | ||
1185 | + extension: '.xlsx', | ||
1186 | + | ||
1187 | + exportOptions: {}, | ||
1188 | + | ||
1189 | + header: true, | ||
1190 | + | ||
1191 | + footer: false | ||
1192 | +}; | ||
1193 | + | ||
1194 | +// | ||
1195 | +// PDF export - using pdfMake - http://pdfmake.org | ||
1196 | +// | ||
1197 | +DataTable.ext.buttons.pdfHtml5 = { | ||
1198 | + className: 'buttons-pdf buttons-html5', | ||
1199 | + | ||
1200 | + available: function () { | ||
1201 | + return window.FileReader !== undefined && _pdfMake(); | ||
1202 | + }, | ||
1203 | + | ||
1204 | + text: function ( dt ) { | ||
1205 | + return dt.i18n( 'buttons.pdf', 'PDF' ); | ||
1206 | + }, | ||
1207 | + | ||
1208 | + action: function ( e, dt, button, config ) { | ||
1209 | + var newLine = _newLine( config ); | ||
1210 | + var data = dt.buttons.exportData( config.exportOptions ); | ||
1211 | + var rows = []; | ||
1212 | + | ||
1213 | + if ( config.header ) { | ||
1214 | + rows.push( $.map( data.header, function ( d ) { | ||
1215 | + return { | ||
1216 | + text: typeof d === 'string' ? d : d+'', | ||
1217 | + style: 'tableHeader' | ||
1218 | + }; | ||
1219 | + } ) ); | ||
1220 | + } | ||
1221 | + | ||
1222 | + for ( var i=0, ien=data.body.length ; i<ien ; i++ ) { | ||
1223 | + rows.push( $.map( data.body[i], function ( d ) { | ||
1224 | + return { | ||
1225 | + text: typeof d === 'string' ? d : d+'', | ||
1226 | + style: i % 2 ? 'tableBodyEven' : 'tableBodyOdd' | ||
1227 | + }; | ||
1228 | + } ) ); | ||
1229 | + } | ||
1230 | + | ||
1231 | + if ( config.footer && data.footer) { | ||
1232 | + rows.push( $.map( data.footer, function ( d ) { | ||
1233 | + return { | ||
1234 | + text: typeof d === 'string' ? d : d+'', | ||
1235 | + style: 'tableFooter' | ||
1236 | + }; | ||
1237 | + } ) ); | ||
1238 | + } | ||
1239 | + | ||
1240 | + var doc = { | ||
1241 | + pageSize: config.pageSize, | ||
1242 | + pageOrientation: config.orientation, | ||
1243 | + content: [ | ||
1244 | + { | ||
1245 | + table: { | ||
1246 | + headerRows: 1, | ||
1247 | + body: rows | ||
1248 | + }, | ||
1249 | + layout: 'noBorders' | ||
1250 | + } | ||
1251 | + ], | ||
1252 | + styles: { | ||
1253 | + tableHeader: { | ||
1254 | + bold: true, | ||
1255 | + fontSize: 11, | ||
1256 | + color: 'white', | ||
1257 | + fillColor: '#2d4154', | ||
1258 | + alignment: 'center' | ||
1259 | + }, | ||
1260 | + tableBodyEven: {}, | ||
1261 | + tableBodyOdd: { | ||
1262 | + fillColor: '#f3f3f3' | ||
1263 | + }, | ||
1264 | + tableFooter: { | ||
1265 | + bold: true, | ||
1266 | + fontSize: 11, | ||
1267 | + color: 'white', | ||
1268 | + fillColor: '#2d4154' | ||
1269 | + }, | ||
1270 | + title: { | ||
1271 | + alignment: 'center', | ||
1272 | + fontSize: 15 | ||
1273 | + }, | ||
1274 | + message: {} | ||
1275 | + }, | ||
1276 | + defaultStyle: { | ||
1277 | + fontSize: 10 | ||
1278 | + } | ||
1279 | + }; | ||
1280 | + | ||
1281 | + if ( config.message ) { | ||
1282 | + doc.content.unshift( { | ||
1283 | + text: typeof config.message == 'function' ? config.message(dt, button, config) : config.message, | ||
1284 | + style: 'message', | ||
1285 | + margin: [ 0, 0, 0, 12 ] | ||
1286 | + } ); | ||
1287 | + } | ||
1288 | + | ||
1289 | + if ( config.title ) { | ||
1290 | + doc.content.unshift( { | ||
1291 | + text: _title( config, false ), | ||
1292 | + style: 'title', | ||
1293 | + margin: [ 0, 0, 0, 12 ] | ||
1294 | + } ); | ||
1295 | + } | ||
1296 | + | ||
1297 | + if ( config.customize ) { | ||
1298 | + config.customize( doc, config ); | ||
1299 | + } | ||
1300 | + | ||
1301 | + var pdf = _pdfMake().createPdf( doc ); | ||
1302 | + | ||
1303 | + if ( config.download === 'open' && ! _isDuffSafari() ) { | ||
1304 | + pdf.open(); | ||
1305 | + } | ||
1306 | + else { | ||
1307 | + pdf.getBuffer( function (buffer) { | ||
1308 | + var blob = new Blob( [buffer], {type:'application/pdf'} ); | ||
1309 | + | ||
1310 | + _saveAs( blob, _filename( config ) ); | ||
1311 | + } ); | ||
1312 | + } | ||
1313 | + }, | ||
1314 | + | ||
1315 | + title: '*', | ||
1316 | + | ||
1317 | + filename: '*', | ||
1318 | + | ||
1319 | + extension: '.pdf', | ||
1320 | + | ||
1321 | + exportOptions: {}, | ||
1322 | + | ||
1323 | + orientation: 'portrait', | ||
1324 | + | ||
1325 | + pageSize: 'A4', | ||
1326 | + | ||
1327 | + header: true, | ||
1328 | + | ||
1329 | + footer: false, | ||
1330 | + | ||
1331 | + message: null, | ||
1332 | + | ||
1333 | + customize: null, | ||
1334 | + | ||
1335 | + download: 'download' | ||
1336 | +}; | ||
1337 | + | ||
1338 | + | ||
1339 | +return DataTable.Buttons; | ||
1340 | +})); |
@@ -0,0 +1,1664 @@ | @@ -0,0 +1,1664 @@ | ||
1 | +/*! Buttons for DataTables 1.2.3 | ||
2 | + * ©2016 SpryMedia Ltd - datatables.net/license | ||
3 | + */ | ||
4 | + | ||
5 | +(function( factory ){ | ||
6 | + if ( typeof define === 'function' && define.amd ) { | ||
7 | + // AMD | ||
8 | + define( ['jquery', 'datatables.net'], function ( $ ) { | ||
9 | + return factory( $, window, document ); | ||
10 | + } ); | ||
11 | + } | ||
12 | + else if ( typeof exports === 'object' ) { | ||
13 | + // CommonJS | ||
14 | + module.exports = function (root, $) { | ||
15 | + if ( ! root ) { | ||
16 | + root = window; | ||
17 | + } | ||
18 | + | ||
19 | + if ( ! $ || ! $.fn.dataTable ) { | ||
20 | + $ = require('datatables.net')(root, $).$; | ||
21 | + } | ||
22 | + | ||
23 | + return factory( $, root, root.document ); | ||
24 | + }; | ||
25 | + } | ||
26 | + else { | ||
27 | + // Browser | ||
28 | + factory( jQuery, window, document ); | ||
29 | + } | ||
30 | +}(function( $, window, document, undefined ) { | ||
31 | +'use strict'; | ||
32 | +var DataTable = $.fn.dataTable; | ||
33 | + | ||
34 | + | ||
35 | +// Used for namespacing events added to the document by each instance, so they | ||
36 | +// can be removed on destroy | ||
37 | +var _instCounter = 0; | ||
38 | + | ||
39 | +// Button namespacing counter for namespacing events on individual buttons | ||
40 | +var _buttonCounter = 0; | ||
41 | + | ||
42 | +var _dtButtons = DataTable.ext.buttons; | ||
43 | + | ||
44 | +/** | ||
45 | + * [Buttons description] | ||
46 | + * @param {[type]} | ||
47 | + * @param {[type]} | ||
48 | + */ | ||
49 | +var Buttons = function( dt, config ) | ||
50 | +{ | ||
51 | + // Allow a boolean true for defaults | ||
52 | + if ( config === true ) { | ||
53 | + config = {}; | ||
54 | + } | ||
55 | + | ||
56 | + // For easy configuration of buttons an array can be given | ||
57 | + if ( $.isArray( config ) ) { | ||
58 | + config = { buttons: config }; | ||
59 | + } | ||
60 | + | ||
61 | + this.c = $.extend( true, {}, Buttons.defaults, config ); | ||
62 | + | ||
63 | + // Don't want a deep copy for the buttons | ||
64 | + if ( config.buttons ) { | ||
65 | + this.c.buttons = config.buttons; | ||
66 | + } | ||
67 | + | ||
68 | + this.s = { | ||
69 | + dt: new DataTable.Api( dt ), | ||
70 | + buttons: [], | ||
71 | + listenKeys: '', | ||
72 | + namespace: 'dtb'+(_instCounter++) | ||
73 | + }; | ||
74 | + | ||
75 | + this.dom = { | ||
76 | + container: $('<'+this.c.dom.container.tag+'/>') | ||
77 | + .addClass( this.c.dom.container.className ) | ||
78 | + }; | ||
79 | + | ||
80 | + this._constructor(); | ||
81 | +}; | ||
82 | + | ||
83 | + | ||
84 | +$.extend( Buttons.prototype, { | ||
85 | + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
86 | + * Public methods | ||
87 | + */ | ||
88 | + | ||
89 | + /** | ||
90 | + * Get the action of a button | ||
91 | + * @param {int|string} Button index | ||
92 | + * @return {function} | ||
93 | + *//** | ||
94 | + * Set the action of a button | ||
95 | + * @param {node} node Button element | ||
96 | + * @param {function} action Function to set | ||
97 | + * @return {Buttons} Self for chaining | ||
98 | + */ | ||
99 | + action: function ( node, action ) | ||
100 | + { | ||
101 | + var button = this._nodeToButton( node ); | ||
102 | + | ||
103 | + if ( action === undefined ) { | ||
104 | + return button.conf.action; | ||
105 | + } | ||
106 | + | ||
107 | + button.conf.action = action; | ||
108 | + | ||
109 | + return this; | ||
110 | + }, | ||
111 | + | ||
112 | + /** | ||
113 | + * Add an active class to the button to make to look active or get current | ||
114 | + * active state. | ||
115 | + * @param {node} node Button element | ||
116 | + * @param {boolean} [flag] Enable / disable flag | ||
117 | + * @return {Buttons} Self for chaining or boolean for getter | ||
118 | + */ | ||
119 | + active: function ( node, flag ) { | ||
120 | + var button = this._nodeToButton( node ); | ||
121 | + var klass = this.c.dom.button.active; | ||
122 | + var jqNode = $(button.node); | ||
123 | + | ||
124 | + if ( flag === undefined ) { | ||
125 | + return jqNode.hasClass( klass ); | ||
126 | + } | ||
127 | + | ||
128 | + jqNode.toggleClass( klass, flag === undefined ? true : flag ); | ||
129 | + | ||
130 | + return this; | ||
131 | + }, | ||
132 | + | ||
133 | + /** | ||
134 | + * Add a new button | ||
135 | + * @param {object} config Button configuration object, base string name or function | ||
136 | + * @param {int|string} [idx] Button index for where to insert the button | ||
137 | + * @return {Buttons} Self for chaining | ||
138 | + */ | ||
139 | + add: function ( config, idx ) | ||
140 | + { | ||
141 | + var buttons = this.s.buttons; | ||
142 | + | ||
143 | + if ( typeof idx === 'string' ) { | ||
144 | + var split = idx.split('-'); | ||
145 | + var base = this.s; | ||
146 | + | ||
147 | + for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) { | ||
148 | + base = base.buttons[ split[i]*1 ]; | ||
149 | + } | ||
150 | + | ||
151 | + buttons = base.buttons; | ||
152 | + idx = split[ split.length-1 ]*1; | ||
153 | + } | ||
154 | + | ||
155 | + this._expandButton( buttons, config, false, idx ); | ||
156 | + this._draw(); | ||
157 | + | ||
158 | + return this; | ||
159 | + }, | ||
160 | + | ||
161 | + /** | ||
162 | + * Get the container node for the buttons | ||
163 | + * @return {jQuery} Buttons node | ||
164 | + */ | ||
165 | + container: function () | ||
166 | + { | ||
167 | + return this.dom.container; | ||
168 | + }, | ||
169 | + | ||
170 | + /** | ||
171 | + * Disable a button | ||
172 | + * @param {node} node Button node | ||
173 | + * @return {Buttons} Self for chaining | ||
174 | + */ | ||
175 | + disable: function ( node ) { | ||
176 | + var button = this._nodeToButton( node ); | ||
177 | + | ||
178 | + $(button.node).addClass( this.c.dom.button.disabled ); | ||
179 | + | ||
180 | + return this; | ||
181 | + }, | ||
182 | + | ||
183 | + /** | ||
184 | + * Destroy the instance, cleaning up event handlers and removing DOM | ||
185 | + * elements | ||
186 | + * @return {Buttons} Self for chaining | ||
187 | + */ | ||
188 | + destroy: function () | ||
189 | + { | ||
190 | + // Key event listener | ||
191 | + $('body').off( 'keyup.'+this.s.namespace ); | ||
192 | + | ||
193 | + // Individual button destroy (so they can remove their own events if | ||
194 | + // needed). Take a copy as the array is modified by `remove` | ||
195 | + var buttons = this.s.buttons.slice(); | ||
196 | + var i, ien; | ||
197 | + | ||
198 | + for ( i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
199 | + this.remove( buttons[i].node ); | ||
200 | + } | ||
201 | + | ||
202 | + // Container | ||
203 | + this.dom.container.remove(); | ||
204 | + | ||
205 | + // Remove from the settings object collection | ||
206 | + var buttonInsts = this.s.dt.settings()[0]; | ||
207 | + | ||
208 | + for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) { | ||
209 | + if ( buttonInsts.inst === this ) { | ||
210 | + buttonInsts.splice( i, 1 ); | ||
211 | + break; | ||
212 | + } | ||
213 | + } | ||
214 | + | ||
215 | + return this; | ||
216 | + }, | ||
217 | + | ||
218 | + /** | ||
219 | + * Enable / disable a button | ||
220 | + * @param {node} node Button node | ||
221 | + * @param {boolean} [flag=true] Enable / disable flag | ||
222 | + * @return {Buttons} Self for chaining | ||
223 | + */ | ||
224 | + enable: function ( node, flag ) | ||
225 | + { | ||
226 | + if ( flag === false ) { | ||
227 | + return this.disable( node ); | ||
228 | + } | ||
229 | + | ||
230 | + var button = this._nodeToButton( node ); | ||
231 | + $(button.node).removeClass( this.c.dom.button.disabled ); | ||
232 | + | ||
233 | + return this; | ||
234 | + }, | ||
235 | + | ||
236 | + /** | ||
237 | + * Get the instance name for the button set selector | ||
238 | + * @return {string} Instance name | ||
239 | + */ | ||
240 | + name: function () | ||
241 | + { | ||
242 | + return this.c.name; | ||
243 | + }, | ||
244 | + | ||
245 | + /** | ||
246 | + * Get a button's node | ||
247 | + * @param {node} node Button node | ||
248 | + * @return {jQuery} Button element | ||
249 | + */ | ||
250 | + node: function ( node ) | ||
251 | + { | ||
252 | + var button = this._nodeToButton( node ); | ||
253 | + return $(button.node); | ||
254 | + }, | ||
255 | + | ||
256 | + /** | ||
257 | + * Remove a button. | ||
258 | + * @param {node} node Button node | ||
259 | + * @return {Buttons} Self for chaining | ||
260 | + */ | ||
261 | + remove: function ( node ) | ||
262 | + { | ||
263 | + var button = this._nodeToButton( node ); | ||
264 | + var host = this._nodeToHost( node ); | ||
265 | + var dt = this.s.dt; | ||
266 | + | ||
267 | + // Remove any child buttons first | ||
268 | + if ( button.buttons.length ) { | ||
269 | + for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) { | ||
270 | + this.remove( button.buttons[i].node ); | ||
271 | + } | ||
272 | + } | ||
273 | + | ||
274 | + // Allow the button to remove event handlers, etc | ||
275 | + if ( button.conf.destroy ) { | ||
276 | + button.conf.destroy.call( dt.button(node), dt, $(node), button.conf ); | ||
277 | + } | ||
278 | + | ||
279 | + this._removeKey( button.conf ); | ||
280 | + | ||
281 | + $(button.node).remove(); | ||
282 | + | ||
283 | + var idx = $.inArray( button, host ); | ||
284 | + host.splice( idx, 1 ); | ||
285 | + | ||
286 | + return this; | ||
287 | + }, | ||
288 | + | ||
289 | + /** | ||
290 | + * Get the text for a button | ||
291 | + * @param {int|string} node Button index | ||
292 | + * @return {string} Button text | ||
293 | + *//** | ||
294 | + * Set the text for a button | ||
295 | + * @param {int|string|function} node Button index | ||
296 | + * @param {string} label Text | ||
297 | + * @return {Buttons} Self for chaining | ||
298 | + */ | ||
299 | + text: function ( node, label ) | ||
300 | + { | ||
301 | + var button = this._nodeToButton( node ); | ||
302 | + var buttonLiner = this.c.dom.collection.buttonLiner; | ||
303 | + var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ? | ||
304 | + buttonLiner.tag : | ||
305 | + this.c.dom.buttonLiner.tag; | ||
306 | + var dt = this.s.dt; | ||
307 | + var jqNode = $(button.node); | ||
308 | + var text = function ( opt ) { | ||
309 | + return typeof opt === 'function' ? | ||
310 | + opt( dt, jqNode, button.conf ) : | ||
311 | + opt; | ||
312 | + }; | ||
313 | + | ||
314 | + if ( label === undefined ) { | ||
315 | + return text( button.conf.text ); | ||
316 | + } | ||
317 | + | ||
318 | + button.conf.text = label; | ||
319 | + | ||
320 | + if ( linerTag ) { | ||
321 | + jqNode.children( linerTag ).html( text(label) ); | ||
322 | + } | ||
323 | + else { | ||
324 | + jqNode.html( text(label) ); | ||
325 | + } | ||
326 | + | ||
327 | + return this; | ||
328 | + }, | ||
329 | + | ||
330 | + | ||
331 | + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
332 | + * Constructor | ||
333 | + */ | ||
334 | + | ||
335 | + /** | ||
336 | + * Buttons constructor | ||
337 | + * @private | ||
338 | + */ | ||
339 | + _constructor: function () | ||
340 | + { | ||
341 | + var that = this; | ||
342 | + var dt = this.s.dt; | ||
343 | + var dtSettings = dt.settings()[0]; | ||
344 | + var buttons = this.c.buttons; | ||
345 | + | ||
346 | + if ( ! dtSettings._buttons ) { | ||
347 | + dtSettings._buttons = []; | ||
348 | + } | ||
349 | + | ||
350 | + dtSettings._buttons.push( { | ||
351 | + inst: this, | ||
352 | + name: this.c.name | ||
353 | + } ); | ||
354 | + | ||
355 | + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
356 | + this.add( buttons[i] ); | ||
357 | + } | ||
358 | + | ||
359 | + dt.on( 'destroy', function () { | ||
360 | + that.destroy(); | ||
361 | + } ); | ||
362 | + | ||
363 | + // Global key event binding to listen for button keys | ||
364 | + $('body').on( 'keyup.'+this.s.namespace, function ( e ) { | ||
365 | + if ( ! document.activeElement || document.activeElement === document.body ) { | ||
366 | + // SUse a string of characters for fast lookup of if we need to | ||
367 | + // handle this | ||
368 | + var character = String.fromCharCode(e.keyCode).toLowerCase(); | ||
369 | + | ||
370 | + if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) { | ||
371 | + that._keypress( character, e ); | ||
372 | + } | ||
373 | + } | ||
374 | + } ); | ||
375 | + }, | ||
376 | + | ||
377 | + | ||
378 | + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
379 | + * Private methods | ||
380 | + */ | ||
381 | + | ||
382 | + /** | ||
383 | + * Add a new button to the key press listener | ||
384 | + * @param {object} conf Resolved button configuration object | ||
385 | + * @private | ||
386 | + */ | ||
387 | + _addKey: function ( conf ) | ||
388 | + { | ||
389 | + if ( conf.key ) { | ||
390 | + this.s.listenKeys += $.isPlainObject( conf.key ) ? | ||
391 | + conf.key.key : | ||
392 | + conf.key; | ||
393 | + } | ||
394 | + }, | ||
395 | + | ||
396 | + /** | ||
397 | + * Insert the buttons into the container. Call without parameters! | ||
398 | + * @param {node} [container] Recursive only - Insert point | ||
399 | + * @param {array} [buttons] Recursive only - Buttons array | ||
400 | + * @private | ||
401 | + */ | ||
402 | + _draw: function ( container, buttons ) | ||
403 | + { | ||
404 | + if ( ! container ) { | ||
405 | + container = this.dom.container; | ||
406 | + buttons = this.s.buttons; | ||
407 | + } | ||
408 | + | ||
409 | + container.children().detach(); | ||
410 | + | ||
411 | + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
412 | + container.append( buttons[i].inserter ); | ||
413 | + | ||
414 | + if ( buttons[i].buttons && buttons[i].buttons.length ) { | ||
415 | + this._draw( buttons[i].collection, buttons[i].buttons ); | ||
416 | + } | ||
417 | + } | ||
418 | + }, | ||
419 | + | ||
420 | + /** | ||
421 | + * Create buttons from an array of buttons | ||
422 | + * @param {array} attachTo Buttons array to attach to | ||
423 | + * @param {object} button Button definition | ||
424 | + * @param {boolean} inCollection true if the button is in a collection | ||
425 | + * @private | ||
426 | + */ | ||
427 | + _expandButton: function ( attachTo, button, inCollection, attachPoint ) | ||
428 | + { | ||
429 | + var dt = this.s.dt; | ||
430 | + var buttonCounter = 0; | ||
431 | + var buttons = ! $.isArray( button ) ? | ||
432 | + [ button ] : | ||
433 | + button; | ||
434 | + | ||
435 | + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
436 | + var conf = this._resolveExtends( buttons[i] ); | ||
437 | + | ||
438 | + if ( ! conf ) { | ||
439 | + continue; | ||
440 | + } | ||
441 | + | ||
442 | + // If the configuration is an array, then expand the buttons at this | ||
443 | + // point | ||
444 | + if ( $.isArray( conf ) ) { | ||
445 | + this._expandButton( attachTo, conf, inCollection, attachPoint ); | ||
446 | + continue; | ||
447 | + } | ||
448 | + | ||
449 | + var built = this._buildButton( conf, inCollection ); | ||
450 | + if ( ! built ) { | ||
451 | + continue; | ||
452 | + } | ||
453 | + | ||
454 | + if ( attachPoint !== undefined ) { | ||
455 | + attachTo.splice( attachPoint, 0, built ); | ||
456 | + attachPoint++; | ||
457 | + } | ||
458 | + else { | ||
459 | + attachTo.push( built ); | ||
460 | + } | ||
461 | + | ||
462 | + if ( built.conf.buttons ) { | ||
463 | + var collectionDom = this.c.dom.collection; | ||
464 | + built.collection = $('<'+collectionDom.tag+'/>') | ||
465 | + .addClass( collectionDom.className ); | ||
466 | + built.conf._collection = built.collection; | ||
467 | + | ||
468 | + this._expandButton( built.buttons, built.conf.buttons, true, attachPoint ); | ||
469 | + } | ||
470 | + | ||
471 | + // init call is made here, rather than buildButton as it needs to | ||
472 | + // be selectable, and for that it needs to be in the buttons array | ||
473 | + if ( conf.init ) { | ||
474 | + conf.init.call( dt.button( built.node ), dt, $(built.node), conf ); | ||
475 | + } | ||
476 | + | ||
477 | + buttonCounter++; | ||
478 | + } | ||
479 | + }, | ||
480 | + | ||
481 | + /** | ||
482 | + * Create an individual button | ||
483 | + * @param {object} config Resolved button configuration | ||
484 | + * @param {boolean} inCollection `true` if a collection button | ||
485 | + * @return {jQuery} Created button node (jQuery) | ||
486 | + * @private | ||
487 | + */ | ||
488 | + _buildButton: function ( config, inCollection ) | ||
489 | + { | ||
490 | + var buttonDom = this.c.dom.button; | ||
491 | + var linerDom = this.c.dom.buttonLiner; | ||
492 | + var collectionDom = this.c.dom.collection; | ||
493 | + var dt = this.s.dt; | ||
494 | + var text = function ( opt ) { | ||
495 | + return typeof opt === 'function' ? | ||
496 | + opt( dt, button, config ) : | ||
497 | + opt; | ||
498 | + }; | ||
499 | + | ||
500 | + if ( inCollection && collectionDom.button ) { | ||
501 | + buttonDom = collectionDom.button; | ||
502 | + } | ||
503 | + | ||
504 | + if ( inCollection && collectionDom.buttonLiner ) { | ||
505 | + linerDom = collectionDom.buttonLiner; | ||
506 | + } | ||
507 | + | ||
508 | + // Make sure that the button is available based on whatever requirements | ||
509 | + // it has. For example, Flash buttons require Flash | ||
510 | + if ( config.available && ! config.available( dt, config ) ) { | ||
511 | + return false; | ||
512 | + } | ||
513 | + | ||
514 | + var action = function ( e, dt, button, config ) { | ||
515 | + config.action.call( dt.button( button ), e, dt, button, config ); | ||
516 | + | ||
517 | + $(dt.table().node()).triggerHandler( 'buttons-action.dt', [ | ||
518 | + dt.button( button ), dt, button, config | ||
519 | + ] ); | ||
520 | + }; | ||
521 | + | ||
522 | + var button = $('<'+buttonDom.tag+'/>') | ||
523 | + .addClass( buttonDom.className ) | ||
524 | + .attr( 'tabindex', this.s.dt.settings()[0].iTabIndex ) | ||
525 | + .attr( 'aria-controls', this.s.dt.table().node().id ) | ||
526 | + .on( 'click.dtb', function (e) { | ||
527 | + e.preventDefault(); | ||
528 | + | ||
529 | + if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { | ||
530 | + action( e, dt, button, config ); | ||
531 | + } | ||
532 | + | ||
533 | + button.blur(); | ||
534 | + } ) | ||
535 | + .on( 'keyup.dtb', function (e) { | ||
536 | + if ( e.keyCode === 13 ) { | ||
537 | + if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { | ||
538 | + action( e, dt, button, config ); | ||
539 | + } | ||
540 | + } | ||
541 | + } ); | ||
542 | + | ||
543 | + // Make `a` tags act like a link | ||
544 | + if ( buttonDom.tag.toLowerCase() === 'a' ) { | ||
545 | + button.attr( 'href', '#' ); | ||
546 | + } | ||
547 | + | ||
548 | + if ( linerDom.tag ) { | ||
549 | + var liner = $('<'+linerDom.tag+'/>') | ||
550 | + .html( text( config.text ) ) | ||
551 | + .addClass( linerDom.className ); | ||
552 | + | ||
553 | + if ( linerDom.tag.toLowerCase() === 'a' ) { | ||
554 | + liner.attr( 'href', '#' ); | ||
555 | + } | ||
556 | + | ||
557 | + button.append( liner ); | ||
558 | + } | ||
559 | + else { | ||
560 | + button.html( text( config.text ) ); | ||
561 | + } | ||
562 | + | ||
563 | + if ( config.enabled === false ) { | ||
564 | + button.addClass( buttonDom.disabled ); | ||
565 | + } | ||
566 | + | ||
567 | + if ( config.className ) { | ||
568 | + button.addClass( config.className ); | ||
569 | + } | ||
570 | + | ||
571 | + if ( config.titleAttr ) { | ||
572 | + button.attr( 'title', config.titleAttr ); | ||
573 | + } | ||
574 | + | ||
575 | + if ( ! config.namespace ) { | ||
576 | + config.namespace = '.dt-button-'+(_buttonCounter++); | ||
577 | + } | ||
578 | + | ||
579 | + var buttonContainer = this.c.dom.buttonContainer; | ||
580 | + var inserter; | ||
581 | + if ( buttonContainer && buttonContainer.tag ) { | ||
582 | + inserter = $('<'+buttonContainer.tag+'/>') | ||
583 | + .addClass( buttonContainer.className ) | ||
584 | + .append( button ); | ||
585 | + } | ||
586 | + else { | ||
587 | + inserter = button; | ||
588 | + } | ||
589 | + | ||
590 | + this._addKey( config ); | ||
591 | + | ||
592 | + return { | ||
593 | + conf: config, | ||
594 | + node: button.get(0), | ||
595 | + inserter: inserter, | ||
596 | + buttons: [], | ||
597 | + inCollection: inCollection, | ||
598 | + collection: null | ||
599 | + }; | ||
600 | + }, | ||
601 | + | ||
602 | + /** | ||
603 | + * Get the button object from a node (recursive) | ||
604 | + * @param {node} node Button node | ||
605 | + * @param {array} [buttons] Button array, uses base if not defined | ||
606 | + * @return {object} Button object | ||
607 | + * @private | ||
608 | + */ | ||
609 | + _nodeToButton: function ( node, buttons ) | ||
610 | + { | ||
611 | + if ( ! buttons ) { | ||
612 | + buttons = this.s.buttons; | ||
613 | + } | ||
614 | + | ||
615 | + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
616 | + if ( buttons[i].node === node ) { | ||
617 | + return buttons[i]; | ||
618 | + } | ||
619 | + | ||
620 | + if ( buttons[i].buttons.length ) { | ||
621 | + var ret = this._nodeToButton( node, buttons[i].buttons ); | ||
622 | + | ||
623 | + if ( ret ) { | ||
624 | + return ret; | ||
625 | + } | ||
626 | + } | ||
627 | + } | ||
628 | + }, | ||
629 | + | ||
630 | + /** | ||
631 | + * Get container array for a button from a button node (recursive) | ||
632 | + * @param {node} node Button node | ||
633 | + * @param {array} [buttons] Button array, uses base if not defined | ||
634 | + * @return {array} Button's host array | ||
635 | + * @private | ||
636 | + */ | ||
637 | + _nodeToHost: function ( node, buttons ) | ||
638 | + { | ||
639 | + if ( ! buttons ) { | ||
640 | + buttons = this.s.buttons; | ||
641 | + } | ||
642 | + | ||
643 | + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
644 | + if ( buttons[i].node === node ) { | ||
645 | + return buttons; | ||
646 | + } | ||
647 | + | ||
648 | + if ( buttons[i].buttons.length ) { | ||
649 | + var ret = this._nodeToHost( node, buttons[i].buttons ); | ||
650 | + | ||
651 | + if ( ret ) { | ||
652 | + return ret; | ||
653 | + } | ||
654 | + } | ||
655 | + } | ||
656 | + }, | ||
657 | + | ||
658 | + /** | ||
659 | + * Handle a key press - determine if any button's key configured matches | ||
660 | + * what was typed and trigger the action if so. | ||
661 | + * @param {string} character The character pressed | ||
662 | + * @param {object} e Key event that triggered this call | ||
663 | + * @private | ||
664 | + */ | ||
665 | + _keypress: function ( character, e ) | ||
666 | + { | ||
667 | + var run = function ( conf, node ) { | ||
668 | + if ( ! conf.key ) { | ||
669 | + return; | ||
670 | + } | ||
671 | + | ||
672 | + if ( conf.key === character ) { | ||
673 | + $(node).click(); | ||
674 | + } | ||
675 | + else if ( $.isPlainObject( conf.key ) ) { | ||
676 | + if ( conf.key.key !== character ) { | ||
677 | + return; | ||
678 | + } | ||
679 | + | ||
680 | + if ( conf.key.shiftKey && ! e.shiftKey ) { | ||
681 | + return; | ||
682 | + } | ||
683 | + | ||
684 | + if ( conf.key.altKey && ! e.altKey ) { | ||
685 | + return; | ||
686 | + } | ||
687 | + | ||
688 | + if ( conf.key.ctrlKey && ! e.ctrlKey ) { | ||
689 | + return; | ||
690 | + } | ||
691 | + | ||
692 | + if ( conf.key.metaKey && ! e.metaKey ) { | ||
693 | + return; | ||
694 | + } | ||
695 | + | ||
696 | + // Made it this far - it is good | ||
697 | + $(node).click(); | ||
698 | + } | ||
699 | + }; | ||
700 | + | ||
701 | + var recurse = function ( a ) { | ||
702 | + for ( var i=0, ien=a.length ; i<ien ; i++ ) { | ||
703 | + run( a[i].conf, a[i].node ); | ||
704 | + | ||
705 | + if ( a[i].buttons.length ) { | ||
706 | + recurse( a[i].buttons ); | ||
707 | + } | ||
708 | + } | ||
709 | + }; | ||
710 | + | ||
711 | + recurse( this.s.buttons ); | ||
712 | + }, | ||
713 | + | ||
714 | + /** | ||
715 | + * Remove a key from the key listener for this instance (to be used when a | ||
716 | + * button is removed) | ||
717 | + * @param {object} conf Button configuration | ||
718 | + * @private | ||
719 | + */ | ||
720 | + _removeKey: function ( conf ) | ||
721 | + { | ||
722 | + if ( conf.key ) { | ||
723 | + var character = $.isPlainObject( conf.key ) ? | ||
724 | + conf.key.key : | ||
725 | + conf.key; | ||
726 | + | ||
727 | + // Remove only one character, as multiple buttons could have the | ||
728 | + // same listening key | ||
729 | + var a = this.s.listenKeys.split(''); | ||
730 | + var idx = $.inArray( character, a ); | ||
731 | + a.splice( idx, 1 ); | ||
732 | + this.s.listenKeys = a.join(''); | ||
733 | + } | ||
734 | + }, | ||
735 | + | ||
736 | + /** | ||
737 | + * Resolve a button configuration | ||
738 | + * @param {string|function|object} conf Button config to resolve | ||
739 | + * @return {object} Button configuration | ||
740 | + * @private | ||
741 | + */ | ||
742 | + _resolveExtends: function ( conf ) | ||
743 | + { | ||
744 | + var dt = this.s.dt; | ||
745 | + var i, ien; | ||
746 | + var toConfObject = function ( base ) { | ||
747 | + var loop = 0; | ||
748 | + | ||
749 | + // Loop until we have resolved to a button configuration, or an | ||
750 | + // array of button configurations (which will be iterated | ||
751 | + // separately) | ||
752 | + while ( ! $.isPlainObject(base) && ! $.isArray(base) ) { | ||
753 | + if ( base === undefined ) { | ||
754 | + return; | ||
755 | + } | ||
756 | + | ||
757 | + if ( typeof base === 'function' ) { | ||
758 | + base = base( dt, conf ); | ||
759 | + | ||
760 | + if ( ! base ) { | ||
761 | + return false; | ||
762 | + } | ||
763 | + } | ||
764 | + else if ( typeof base === 'string' ) { | ||
765 | + if ( ! _dtButtons[ base ] ) { | ||
766 | + throw 'Unknown button type: '+base; | ||
767 | + } | ||
768 | + | ||
769 | + base = _dtButtons[ base ]; | ||
770 | + } | ||
771 | + | ||
772 | + loop++; | ||
773 | + if ( loop > 30 ) { | ||
774 | + // Protect against misconfiguration killing the browser | ||
775 | + throw 'Buttons: Too many iterations'; | ||
776 | + } | ||
777 | + } | ||
778 | + | ||
779 | + return $.isArray( base ) ? | ||
780 | + base : | ||
781 | + $.extend( {}, base ); | ||
782 | + }; | ||
783 | + | ||
784 | + conf = toConfObject( conf ); | ||
785 | + | ||
786 | + while ( conf && conf.extend ) { | ||
787 | + // Use `toConfObject` in case the button definition being extended | ||
788 | + // is itself a string or a function | ||
789 | + if ( ! _dtButtons[ conf.extend ] ) { | ||
790 | + throw 'Cannot extend unknown button type: '+conf.extend; | ||
791 | + } | ||
792 | + | ||
793 | + var objArray = toConfObject( _dtButtons[ conf.extend ] ); | ||
794 | + if ( $.isArray( objArray ) ) { | ||
795 | + return objArray; | ||
796 | + } | ||
797 | + else if ( ! objArray ) { | ||
798 | + // This is a little brutal as it might be possible to have a | ||
799 | + // valid button without the extend, but if there is no extend | ||
800 | + // then the host button would be acting in an undefined state | ||
801 | + return false; | ||
802 | + } | ||
803 | + | ||
804 | + // Stash the current class name | ||
805 | + var originalClassName = objArray.className; | ||
806 | + | ||
807 | + conf = $.extend( {}, objArray, conf ); | ||
808 | + | ||
809 | + // The extend will have overwritten the original class name if the | ||
810 | + // `conf` object also assigned a class, but we want to concatenate | ||
811 | + // them so they are list that is combined from all extended buttons | ||
812 | + if ( originalClassName && conf.className !== originalClassName ) { | ||
813 | + conf.className = originalClassName+' '+conf.className; | ||
814 | + } | ||
815 | + | ||
816 | + // Buttons to be added to a collection -gives the ability to define | ||
817 | + // if buttons should be added to the start or end of a collection | ||
818 | + var postfixButtons = conf.postfixButtons; | ||
819 | + if ( postfixButtons ) { | ||
820 | + if ( ! conf.buttons ) { | ||
821 | + conf.buttons = []; | ||
822 | + } | ||
823 | + | ||
824 | + for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) { | ||
825 | + conf.buttons.push( postfixButtons[i] ); | ||
826 | + } | ||
827 | + | ||
828 | + conf.postfixButtons = null; | ||
829 | + } | ||
830 | + | ||
831 | + var prefixButtons = conf.prefixButtons; | ||
832 | + if ( prefixButtons ) { | ||
833 | + if ( ! conf.buttons ) { | ||
834 | + conf.buttons = []; | ||
835 | + } | ||
836 | + | ||
837 | + for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) { | ||
838 | + conf.buttons.splice( i, 0, prefixButtons[i] ); | ||
839 | + } | ||
840 | + | ||
841 | + conf.prefixButtons = null; | ||
842 | + } | ||
843 | + | ||
844 | + // Although we want the `conf` object to overwrite almost all of | ||
845 | + // the properties of the object being extended, the `extend` | ||
846 | + // property should come from the object being extended | ||
847 | + conf.extend = objArray.extend; | ||
848 | + } | ||
849 | + | ||
850 | + return conf; | ||
851 | + } | ||
852 | +} ); | ||
853 | + | ||
854 | + | ||
855 | + | ||
856 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
857 | + * Statics | ||
858 | + */ | ||
859 | + | ||
860 | +/** | ||
861 | + * Show / hide a background layer behind a collection | ||
862 | + * @param {boolean} Flag to indicate if the background should be shown or | ||
863 | + * hidden | ||
864 | + * @param {string} Class to assign to the background | ||
865 | + * @static | ||
866 | + */ | ||
867 | +Buttons.background = function ( show, className, fade ) { | ||
868 | + if ( fade === undefined ) { | ||
869 | + fade = 400; | ||
870 | + } | ||
871 | + | ||
872 | + if ( show ) { | ||
873 | + $('<div/>') | ||
874 | + .addClass( className ) | ||
875 | + .css( 'display', 'none' ) | ||
876 | + .appendTo( 'body' ) | ||
877 | + .fadeIn( fade ); | ||
878 | + } | ||
879 | + else { | ||
880 | + $('body > div.'+className) | ||
881 | + .fadeOut( fade, function () { | ||
882 | + $(this) | ||
883 | + .removeClass( className ) | ||
884 | + .remove(); | ||
885 | + } ); | ||
886 | + } | ||
887 | +}; | ||
888 | + | ||
889 | +/** | ||
890 | + * Instance selector - select Buttons instances based on an instance selector | ||
891 | + * value from the buttons assigned to a DataTable. This is only useful if | ||
892 | + * multiple instances are attached to a DataTable. | ||
893 | + * @param {string|int|array} Instance selector - see `instance-selector` | ||
894 | + * documentation on the DataTables site | ||
895 | + * @param {array} Button instance array that was attached to the DataTables | ||
896 | + * settings object | ||
897 | + * @return {array} Buttons instances | ||
898 | + * @static | ||
899 | + */ | ||
900 | +Buttons.instanceSelector = function ( group, buttons ) | ||
901 | +{ | ||
902 | + if ( ! group ) { | ||
903 | + return $.map( buttons, function ( v ) { | ||
904 | + return v.inst; | ||
905 | + } ); | ||
906 | + } | ||
907 | + | ||
908 | + var ret = []; | ||
909 | + var names = $.map( buttons, function ( v ) { | ||
910 | + return v.name; | ||
911 | + } ); | ||
912 | + | ||
913 | + // Flatten the group selector into an array of single options | ||
914 | + var process = function ( input ) { | ||
915 | + if ( $.isArray( input ) ) { | ||
916 | + for ( var i=0, ien=input.length ; i<ien ; i++ ) { | ||
917 | + process( input[i] ); | ||
918 | + } | ||
919 | + return; | ||
920 | + } | ||
921 | + | ||
922 | + if ( typeof input === 'string' ) { | ||
923 | + if ( input.indexOf( ',' ) !== -1 ) { | ||
924 | + // String selector, list of names | ||
925 | + process( input.split(',') ); | ||
926 | + } | ||
927 | + else { | ||
928 | + // String selector individual name | ||
929 | + var idx = $.inArray( $.trim(input), names ); | ||
930 | + | ||
931 | + if ( idx !== -1 ) { | ||
932 | + ret.push( buttons[ idx ].inst ); | ||
933 | + } | ||
934 | + } | ||
935 | + } | ||
936 | + else if ( typeof input === 'number' ) { | ||
937 | + // Index selector | ||
938 | + ret.push( buttons[ input ].inst ); | ||
939 | + } | ||
940 | + }; | ||
941 | + | ||
942 | + process( group ); | ||
943 | + | ||
944 | + return ret; | ||
945 | +}; | ||
946 | + | ||
947 | +/** | ||
948 | + * Button selector - select one or more buttons from a selector input so some | ||
949 | + * operation can be performed on them. | ||
950 | + * @param {array} Button instances array that the selector should operate on | ||
951 | + * @param {string|int|node|jQuery|array} Button selector - see | ||
952 | + * `button-selector` documentation on the DataTables site | ||
953 | + * @return {array} Array of objects containing `inst` and `idx` properties of | ||
954 | + * the selected buttons so you know which instance each button belongs to. | ||
955 | + * @static | ||
956 | + */ | ||
957 | +Buttons.buttonSelector = function ( insts, selector ) | ||
958 | +{ | ||
959 | + var ret = []; | ||
960 | + var nodeBuilder = function ( a, buttons, baseIdx ) { | ||
961 | + var button; | ||
962 | + var idx; | ||
963 | + | ||
964 | + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
965 | + button = buttons[i]; | ||
966 | + | ||
967 | + if ( button ) { | ||
968 | + idx = baseIdx !== undefined ? | ||
969 | + baseIdx+i : | ||
970 | + i+''; | ||
971 | + | ||
972 | + a.push( { | ||
973 | + node: button.node, | ||
974 | + name: button.conf.name, | ||
975 | + idx: idx | ||
976 | + } ); | ||
977 | + | ||
978 | + if ( button.buttons ) { | ||
979 | + nodeBuilder( a, button.buttons, idx+'-' ); | ||
980 | + } | ||
981 | + } | ||
982 | + } | ||
983 | + }; | ||
984 | + | ||
985 | + var run = function ( selector, inst ) { | ||
986 | + var i, ien; | ||
987 | + var buttons = []; | ||
988 | + nodeBuilder( buttons, inst.s.buttons ); | ||
989 | + | ||
990 | + var nodes = $.map( buttons, function (v) { | ||
991 | + return v.node; | ||
992 | + } ); | ||
993 | + | ||
994 | + if ( $.isArray( selector ) || selector instanceof $ ) { | ||
995 | + for ( i=0, ien=selector.length ; i<ien ; i++ ) { | ||
996 | + run( selector[i], inst ); | ||
997 | + } | ||
998 | + return; | ||
999 | + } | ||
1000 | + | ||
1001 | + if ( selector === null || selector === undefined || selector === '*' ) { | ||
1002 | + // Select all | ||
1003 | + for ( i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
1004 | + ret.push( { | ||
1005 | + inst: inst, | ||
1006 | + node: buttons[i].node | ||
1007 | + } ); | ||
1008 | + } | ||
1009 | + } | ||
1010 | + else if ( typeof selector === 'number' ) { | ||
1011 | + // Main button index selector | ||
1012 | + ret.push( { | ||
1013 | + inst: inst, | ||
1014 | + node: inst.s.buttons[ selector ].node | ||
1015 | + } ); | ||
1016 | + } | ||
1017 | + else if ( typeof selector === 'string' ) { | ||
1018 | + if ( selector.indexOf( ',' ) !== -1 ) { | ||
1019 | + // Split | ||
1020 | + var a = selector.split(','); | ||
1021 | + | ||
1022 | + for ( i=0, ien=a.length ; i<ien ; i++ ) { | ||
1023 | + run( $.trim(a[i]), inst ); | ||
1024 | + } | ||
1025 | + } | ||
1026 | + else if ( selector.match( /^\d+(\-\d+)*$/ ) ) { | ||
1027 | + // Sub-button index selector | ||
1028 | + var indexes = $.map( buttons, function (v) { | ||
1029 | + return v.idx; | ||
1030 | + } ); | ||
1031 | + | ||
1032 | + ret.push( { | ||
1033 | + inst: inst, | ||
1034 | + node: buttons[ $.inArray( selector, indexes ) ].node | ||
1035 | + } ); | ||
1036 | + } | ||
1037 | + else if ( selector.indexOf( ':name' ) !== -1 ) { | ||
1038 | + // Button name selector | ||
1039 | + var name = selector.replace( ':name', '' ); | ||
1040 | + | ||
1041 | + for ( i=0, ien=buttons.length ; i<ien ; i++ ) { | ||
1042 | + if ( buttons[i].name === name ) { | ||
1043 | + ret.push( { | ||
1044 | + inst: inst, | ||
1045 | + node: buttons[i].node | ||
1046 | + } ); | ||
1047 | + } | ||
1048 | + } | ||
1049 | + } | ||
1050 | + else { | ||
1051 | + // jQuery selector on the nodes | ||
1052 | + $( nodes ).filter( selector ).each( function () { | ||
1053 | + ret.push( { | ||
1054 | + inst: inst, | ||
1055 | + node: this | ||
1056 | + } ); | ||
1057 | + } ); | ||
1058 | + } | ||
1059 | + } | ||
1060 | + else if ( typeof selector === 'object' && selector.nodeName ) { | ||
1061 | + // Node selector | ||
1062 | + var idx = $.inArray( selector, nodes ); | ||
1063 | + | ||
1064 | + if ( idx !== -1 ) { | ||
1065 | + ret.push( { | ||
1066 | + inst: inst, | ||
1067 | + node: nodes[ idx ] | ||
1068 | + } ); | ||
1069 | + } | ||
1070 | + } | ||
1071 | + }; | ||
1072 | + | ||
1073 | + | ||
1074 | + for ( var i=0, ien=insts.length ; i<ien ; i++ ) { | ||
1075 | + var inst = insts[i]; | ||
1076 | + | ||
1077 | + run( selector, inst ); | ||
1078 | + } | ||
1079 | + | ||
1080 | + return ret; | ||
1081 | +}; | ||
1082 | + | ||
1083 | + | ||
1084 | +/** | ||
1085 | + * Buttons defaults. For full documentation, please refer to the docs/option | ||
1086 | + * directory or the DataTables site. | ||
1087 | + * @type {Object} | ||
1088 | + * @static | ||
1089 | + */ | ||
1090 | +Buttons.defaults = { | ||
1091 | + buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ], | ||
1092 | + name: 'main', | ||
1093 | + tabIndex: 0, | ||
1094 | + dom: { | ||
1095 | + container: { | ||
1096 | + tag: 'div', | ||
1097 | + className: 'dt-buttons' | ||
1098 | + }, | ||
1099 | + collection: { | ||
1100 | + tag: 'div', | ||
1101 | + className: 'dt-button-collection' | ||
1102 | + }, | ||
1103 | + button: { | ||
1104 | + tag: 'a', | ||
1105 | + className: 'dt-button', | ||
1106 | + active: 'active', | ||
1107 | + disabled: 'disabled' | ||
1108 | + }, | ||
1109 | + buttonLiner: { | ||
1110 | + tag: 'span', | ||
1111 | + className: '' | ||
1112 | + } | ||
1113 | + } | ||
1114 | +}; | ||
1115 | + | ||
1116 | +/** | ||
1117 | + * Version information | ||
1118 | + * @type {string} | ||
1119 | + * @static | ||
1120 | + */ | ||
1121 | +Buttons.version = '1.2.3'; | ||
1122 | + | ||
1123 | + | ||
1124 | +$.extend( _dtButtons, { | ||
1125 | + collection: { | ||
1126 | + text: function ( dt ) { | ||
1127 | + return dt.i18n( 'buttons.collection', 'Collection' ); | ||
1128 | + }, | ||
1129 | + className: 'buttons-collection', | ||
1130 | + action: function ( e, dt, button, config ) { | ||
1131 | + var host = button; | ||
1132 | + var hostOffset = host.offset(); | ||
1133 | + var tableContainer = $( dt.table().container() ); | ||
1134 | + var multiLevel = false; | ||
1135 | + | ||
1136 | + // Remove any old collection | ||
1137 | + if ( $('div.dt-button-background').length ) { | ||
1138 | + multiLevel = $('.dt-button-collection').offset(); | ||
1139 | + $('body').trigger( 'click.dtb-collection' ); | ||
1140 | + } | ||
1141 | + | ||
1142 | + config._collection | ||
1143 | + .addClass( config.collectionLayout ) | ||
1144 | + .css( 'display', 'none' ) | ||
1145 | + .appendTo( 'body' ) | ||
1146 | + .fadeIn( config.fade ); | ||
1147 | + | ||
1148 | + var position = config._collection.css( 'position' ); | ||
1149 | + | ||
1150 | + if ( multiLevel && position === 'absolute' ) { | ||
1151 | + config._collection.css( { | ||
1152 | + top: multiLevel.top, | ||
1153 | + left: multiLevel.left | ||
1154 | + } ); | ||
1155 | + } | ||
1156 | + else if ( position === 'absolute' ) { | ||
1157 | + config._collection.css( { | ||
1158 | + top: hostOffset.top + host.outerHeight(), | ||
1159 | + left: hostOffset.left | ||
1160 | + } ); | ||
1161 | + | ||
1162 | + var listRight = hostOffset.left + config._collection.outerWidth(); | ||
1163 | + var tableRight = tableContainer.offset().left + tableContainer.width(); | ||
1164 | + if ( listRight > tableRight ) { | ||
1165 | + config._collection.css( 'left', hostOffset.left - ( listRight - tableRight ) ); | ||
1166 | + } | ||
1167 | + } | ||
1168 | + else { | ||
1169 | + // Fix position - centre on screen | ||
1170 | + var top = config._collection.height() / 2; | ||
1171 | + if ( top > $(window).height() / 2 ) { | ||
1172 | + top = $(window).height() / 2; | ||
1173 | + } | ||
1174 | + | ||
1175 | + config._collection.css( 'marginTop', top*-1 ); | ||
1176 | + } | ||
1177 | + | ||
1178 | + if ( config.background ) { | ||
1179 | + Buttons.background( true, config.backgroundClassName, config.fade ); | ||
1180 | + } | ||
1181 | + | ||
1182 | + // Need to break the 'thread' for the collection button being | ||
1183 | + // activated by a click - it would also trigger this event | ||
1184 | + setTimeout( function () { | ||
1185 | + // This is bonkers, but if we don't have a click listener on the | ||
1186 | + // background element, iOS Safari will ignore the body click | ||
1187 | + // listener below. An empty function here is all that is | ||
1188 | + // required to make it work... | ||
1189 | + $('div.dt-button-background').on( 'click.dtb-collection', function () {} ); | ||
1190 | + | ||
1191 | + $('body').on( 'click.dtb-collection', function (e) { | ||
1192 | + // andSelf is deprecated in jQ1.8, but we want 1.7 compat | ||
1193 | + var back = $.fn.addBack ? 'addBack' : 'andSelf'; | ||
1194 | + | ||
1195 | + if ( ! $(e.target).parents()[back]().filter( config._collection ).length ) { | ||
1196 | + config._collection | ||
1197 | + .fadeOut( config.fade, function () { | ||
1198 | + config._collection.detach(); | ||
1199 | + } ); | ||
1200 | + | ||
1201 | + $('div.dt-button-background').off( 'click.dtb-collection' ); | ||
1202 | + Buttons.background( false, config.backgroundClassName, config.fade ); | ||
1203 | + | ||
1204 | + $('body').off( 'click.dtb-collection' ); | ||
1205 | + dt.off( 'buttons-action.b-internal' ); | ||
1206 | + } | ||
1207 | + } ); | ||
1208 | + }, 10 ); | ||
1209 | + | ||
1210 | + if ( config.autoClose ) { | ||
1211 | + dt.on( 'buttons-action.b-internal', function () { | ||
1212 | + $('div.dt-button-background').click(); | ||
1213 | + } ); | ||
1214 | + } | ||
1215 | + }, | ||
1216 | + background: true, | ||
1217 | + collectionLayout: '', | ||
1218 | + backgroundClassName: 'dt-button-background', | ||
1219 | + autoClose: false, | ||
1220 | + fade: 400 | ||
1221 | + }, | ||
1222 | + copy: function ( dt, conf ) { | ||
1223 | + if ( _dtButtons.copyHtml5 ) { | ||
1224 | + return 'copyHtml5'; | ||
1225 | + } | ||
1226 | + if ( _dtButtons.copyFlash && _dtButtons.copyFlash.available( dt, conf ) ) { | ||
1227 | + return 'copyFlash'; | ||
1228 | + } | ||
1229 | + }, | ||
1230 | + csv: function ( dt, conf ) { | ||
1231 | + // Common option that will use the HTML5 or Flash export buttons | ||
1232 | + if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) { | ||
1233 | + return 'csvHtml5'; | ||
1234 | + } | ||
1235 | + if ( _dtButtons.csvFlash && _dtButtons.csvFlash.available( dt, conf ) ) { | ||
1236 | + return 'csvFlash'; | ||
1237 | + } | ||
1238 | + }, | ||
1239 | + excel: function ( dt, conf ) { | ||
1240 | + // Common option that will use the HTML5 or Flash export buttons | ||
1241 | + if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) { | ||
1242 | + return 'excelHtml5'; | ||
1243 | + } | ||
1244 | + if ( _dtButtons.excelFlash && _dtButtons.excelFlash.available( dt, conf ) ) { | ||
1245 | + return 'excelFlash'; | ||
1246 | + } | ||
1247 | + }, | ||
1248 | + pdf: function ( dt, conf ) { | ||
1249 | + // Common option that will use the HTML5 or Flash export buttons | ||
1250 | + if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) { | ||
1251 | + return 'pdfHtml5'; | ||
1252 | + } | ||
1253 | + if ( _dtButtons.pdfFlash && _dtButtons.pdfFlash.available( dt, conf ) ) { | ||
1254 | + return 'pdfFlash'; | ||
1255 | + } | ||
1256 | + }, | ||
1257 | + pageLength: function ( dt ) { | ||
1258 | + var lengthMenu = dt.settings()[0].aLengthMenu; | ||
1259 | + var vals = $.isArray( lengthMenu[0] ) ? lengthMenu[0] : lengthMenu; | ||
1260 | + var lang = $.isArray( lengthMenu[0] ) ? lengthMenu[1] : lengthMenu; | ||
1261 | + var text = function ( dt ) { | ||
1262 | + return dt.i18n( 'buttons.pageLength', { | ||
1263 | + "-1": 'Show all rows', | ||
1264 | + _: 'Show %d rows' | ||
1265 | + }, dt.page.len() ); | ||
1266 | + }; | ||
1267 | + | ||
1268 | + return { | ||
1269 | + extend: 'collection', | ||
1270 | + text: text, | ||
1271 | + className: 'buttons-page-length', | ||
1272 | + autoClose: true, | ||
1273 | + buttons: $.map( vals, function ( val, i ) { | ||
1274 | + return { | ||
1275 | + text: lang[i], | ||
1276 | + className: 'button-page-length', | ||
1277 | + action: function ( e, dt ) { | ||
1278 | + dt.page.len( val ).draw(); | ||
1279 | + }, | ||
1280 | + init: function ( dt, node, conf ) { | ||
1281 | + var that = this; | ||
1282 | + var fn = function () { | ||
1283 | + that.active( dt.page.len() === val ); | ||
1284 | + }; | ||
1285 | + | ||
1286 | + dt.on( 'length.dt'+conf.namespace, fn ); | ||
1287 | + fn(); | ||
1288 | + }, | ||
1289 | + destroy: function ( dt, node, conf ) { | ||
1290 | + dt.off( 'length.dt'+conf.namespace ); | ||
1291 | + } | ||
1292 | + }; | ||
1293 | + } ), | ||
1294 | + init: function ( dt, node, conf ) { | ||
1295 | + var that = this; | ||
1296 | + dt.on( 'length.dt'+conf.namespace, function () { | ||
1297 | + that.text( text( dt ) ); | ||
1298 | + } ); | ||
1299 | + }, | ||
1300 | + destroy: function ( dt, node, conf ) { | ||
1301 | + dt.off( 'length.dt'+conf.namespace ); | ||
1302 | + } | ||
1303 | + }; | ||
1304 | + } | ||
1305 | +} ); | ||
1306 | + | ||
1307 | + | ||
1308 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
1309 | + * DataTables API | ||
1310 | + * | ||
1311 | + * For complete documentation, please refer to the docs/api directory or the | ||
1312 | + * DataTables site | ||
1313 | + */ | ||
1314 | + | ||
1315 | +// Buttons group and individual button selector | ||
1316 | +DataTable.Api.register( 'buttons()', function ( group, selector ) { | ||
1317 | + // Argument shifting | ||
1318 | + if ( selector === undefined ) { | ||
1319 | + selector = group; | ||
1320 | + group = undefined; | ||
1321 | + } | ||
1322 | + | ||
1323 | + this.selector.buttonGroup = group; | ||
1324 | + | ||
1325 | + var res = this.iterator( true, 'table', function ( ctx ) { | ||
1326 | + if ( ctx._buttons ) { | ||
1327 | + return Buttons.buttonSelector( | ||
1328 | + Buttons.instanceSelector( group, ctx._buttons ), | ||
1329 | + selector | ||
1330 | + ); | ||
1331 | + } | ||
1332 | + }, true ); | ||
1333 | + | ||
1334 | + res._groupSelector = group; | ||
1335 | + return res; | ||
1336 | +} ); | ||
1337 | + | ||
1338 | +// Individual button selector | ||
1339 | +DataTable.Api.register( 'button()', function ( group, selector ) { | ||
1340 | + // just run buttons() and truncate | ||
1341 | + var buttons = this.buttons( group, selector ); | ||
1342 | + | ||
1343 | + if ( buttons.length > 1 ) { | ||
1344 | + buttons.splice( 1, buttons.length ); | ||
1345 | + } | ||
1346 | + | ||
1347 | + return buttons; | ||
1348 | +} ); | ||
1349 | + | ||
1350 | +// Active buttons | ||
1351 | +DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) { | ||
1352 | + if ( flag === undefined ) { | ||
1353 | + return this.map( function ( set ) { | ||
1354 | + return set.inst.active( set.node ); | ||
1355 | + } ); | ||
1356 | + } | ||
1357 | + | ||
1358 | + return this.each( function ( set ) { | ||
1359 | + set.inst.active( set.node, flag ); | ||
1360 | + } ); | ||
1361 | +} ); | ||
1362 | + | ||
1363 | +// Get / set button action | ||
1364 | +DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) { | ||
1365 | + if ( action === undefined ) { | ||
1366 | + return this.map( function ( set ) { | ||
1367 | + return set.inst.action( set.node ); | ||
1368 | + } ); | ||
1369 | + } | ||
1370 | + | ||
1371 | + return this.each( function ( set ) { | ||
1372 | + set.inst.action( set.node, action ); | ||
1373 | + } ); | ||
1374 | +} ); | ||
1375 | + | ||
1376 | +// Enable / disable buttons | ||
1377 | +DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) { | ||
1378 | + return this.each( function ( set ) { | ||
1379 | + set.inst.enable( set.node, flag ); | ||
1380 | + } ); | ||
1381 | +} ); | ||
1382 | + | ||
1383 | +// Disable buttons | ||
1384 | +DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () { | ||
1385 | + return this.each( function ( set ) { | ||
1386 | + set.inst.disable( set.node ); | ||
1387 | + } ); | ||
1388 | +} ); | ||
1389 | + | ||
1390 | +// Get button nodes | ||
1391 | +DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () { | ||
1392 | + var jq = $(); | ||
1393 | + | ||
1394 | + // jQuery will automatically reduce duplicates to a single entry | ||
1395 | + $( this.each( function ( set ) { | ||
1396 | + jq = jq.add( set.inst.node( set.node ) ); | ||
1397 | + } ) ); | ||
1398 | + | ||
1399 | + return jq; | ||
1400 | +} ); | ||
1401 | + | ||
1402 | +// Get / set button text (i.e. the button labels) | ||
1403 | +DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) { | ||
1404 | + if ( label === undefined ) { | ||
1405 | + return this.map( function ( set ) { | ||
1406 | + return set.inst.text( set.node ); | ||
1407 | + } ); | ||
1408 | + } | ||
1409 | + | ||
1410 | + return this.each( function ( set ) { | ||
1411 | + set.inst.text( set.node, label ); | ||
1412 | + } ); | ||
1413 | +} ); | ||
1414 | + | ||
1415 | +// Trigger a button's action | ||
1416 | +DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () { | ||
1417 | + return this.each( function ( set ) { | ||
1418 | + set.inst.node( set.node ).trigger( 'click' ); | ||
1419 | + } ); | ||
1420 | +} ); | ||
1421 | + | ||
1422 | +// Get the container elements | ||
1423 | +DataTable.Api.registerPlural( 'buttons().containers()', 'buttons().container()', function () { | ||
1424 | + var jq = $(); | ||
1425 | + var groupSelector = this._groupSelector; | ||
1426 | + | ||
1427 | + // We need to use the group selector directly, since if there are no buttons | ||
1428 | + // the result set will be empty | ||
1429 | + this.iterator( true, 'table', function ( ctx ) { | ||
1430 | + if ( ctx._buttons ) { | ||
1431 | + var insts = Buttons.instanceSelector( groupSelector, ctx._buttons ); | ||
1432 | + | ||
1433 | + for ( var i=0, ien=insts.length ; i<ien ; i++ ) { | ||
1434 | + jq = jq.add( insts[i].container() ); | ||
1435 | + } | ||
1436 | + } | ||
1437 | + } ); | ||
1438 | + | ||
1439 | + return jq; | ||
1440 | +} ); | ||
1441 | + | ||
1442 | +// Add a new button | ||
1443 | +DataTable.Api.register( 'button().add()', function ( idx, conf ) { | ||
1444 | + var ctx = this.context; | ||
1445 | + | ||
1446 | + // Don't use `this` as it could be empty - select the instances directly | ||
1447 | + if ( ctx.length ) { | ||
1448 | + var inst = Buttons.instanceSelector( this._groupSelector, ctx[0]._buttons ); | ||
1449 | + | ||
1450 | + if ( inst.length ) { | ||
1451 | + inst[0].add( conf, idx ); | ||
1452 | + } | ||
1453 | + } | ||
1454 | + | ||
1455 | + return this.button( this._groupSelector, idx ); | ||
1456 | +} ); | ||
1457 | + | ||
1458 | +// Destroy the button sets selected | ||
1459 | +DataTable.Api.register( 'buttons().destroy()', function () { | ||
1460 | + this.pluck( 'inst' ).unique().each( function ( inst ) { | ||
1461 | + inst.destroy(); | ||
1462 | + } ); | ||
1463 | + | ||
1464 | + return this; | ||
1465 | +} ); | ||
1466 | + | ||
1467 | +// Remove a button | ||
1468 | +DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () { | ||
1469 | + this.each( function ( set ) { | ||
1470 | + set.inst.remove( set.node ); | ||
1471 | + } ); | ||
1472 | + | ||
1473 | + return this; | ||
1474 | +} ); | ||
1475 | + | ||
1476 | +// Information box that can be used by buttons | ||
1477 | +var _infoTimer; | ||
1478 | +DataTable.Api.register( 'buttons.info()', function ( title, message, time ) { | ||
1479 | + var that = this; | ||
1480 | + | ||
1481 | + if ( title === false ) { | ||
1482 | + $('#datatables_buttons_info').fadeOut( function () { | ||
1483 | + $(this).remove(); | ||
1484 | + } ); | ||
1485 | + clearTimeout( _infoTimer ); | ||
1486 | + _infoTimer = null; | ||
1487 | + | ||
1488 | + return this; | ||
1489 | + } | ||
1490 | + | ||
1491 | + if ( _infoTimer ) { | ||
1492 | + clearTimeout( _infoTimer ); | ||
1493 | + } | ||
1494 | + | ||
1495 | + if ( $('#datatables_buttons_info').length ) { | ||
1496 | + $('#datatables_buttons_info').remove(); | ||
1497 | + } | ||
1498 | + | ||
1499 | + title = title ? '<h2>'+title+'</h2>' : ''; | ||
1500 | + | ||
1501 | + $('<div id="datatables_buttons_info" class="dt-button-info"/>') | ||
1502 | + .html( title ) | ||
1503 | + .append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) ) | ||
1504 | + .css( 'display', 'none' ) | ||
1505 | + .appendTo( 'body' ) | ||
1506 | + .fadeIn(); | ||
1507 | + | ||
1508 | + if ( time !== undefined && time !== 0 ) { | ||
1509 | + _infoTimer = setTimeout( function () { | ||
1510 | + that.buttons.info( false ); | ||
1511 | + }, time ); | ||
1512 | + } | ||
1513 | + | ||
1514 | + return this; | ||
1515 | +} ); | ||
1516 | + | ||
1517 | +// Get data from the table for export - this is common to a number of plug-in | ||
1518 | +// buttons so it is included in the Buttons core library | ||
1519 | +DataTable.Api.register( 'buttons.exportData()', function ( options ) { | ||
1520 | + if ( this.context.length ) { | ||
1521 | + return _exportData( new DataTable.Api( this.context[0] ), options ); | ||
1522 | + } | ||
1523 | +} ); | ||
1524 | + | ||
1525 | + | ||
1526 | +var _exportTextarea = $('<textarea/>')[0]; | ||
1527 | +var _exportData = function ( dt, inOpts ) | ||
1528 | +{ | ||
1529 | + var config = $.extend( true, {}, { | ||
1530 | + rows: null, | ||
1531 | + columns: '', | ||
1532 | + modifier: { | ||
1533 | + search: 'applied', | ||
1534 | + order: 'applied' | ||
1535 | + }, | ||
1536 | + orthogonal: 'display', | ||
1537 | + stripHtml: true, | ||
1538 | + stripNewlines: true, | ||
1539 | + decodeEntities: true, | ||
1540 | + trim: true, | ||
1541 | + format: { | ||
1542 | + header: function ( d ) { | ||
1543 | + return strip( d ); | ||
1544 | + }, | ||
1545 | + footer: function ( d ) { | ||
1546 | + return strip( d ); | ||
1547 | + }, | ||
1548 | + body: function ( d ) { | ||
1549 | + return strip( d ); | ||
1550 | + } | ||
1551 | + } | ||
1552 | + }, inOpts ); | ||
1553 | + | ||
1554 | + var strip = function ( str ) { | ||
1555 | + if ( typeof str !== 'string' ) { | ||
1556 | + return str; | ||
1557 | + } | ||
1558 | + | ||
1559 | + if ( config.stripHtml ) { | ||
1560 | + str = str.replace( /<[^>]*>/g, '' ); | ||
1561 | + } | ||
1562 | + | ||
1563 | + if ( config.trim ) { | ||
1564 | + str = str.replace( /^\s+|\s+$/g, '' ); | ||
1565 | + } | ||
1566 | + | ||
1567 | + if ( config.stripNewlines ) { | ||
1568 | + str = str.replace( /\n/g, ' ' ); | ||
1569 | + } | ||
1570 | + | ||
1571 | + if ( config.decodeEntities ) { | ||
1572 | + _exportTextarea.innerHTML = str; | ||
1573 | + str = _exportTextarea.value; | ||
1574 | + } | ||
1575 | + | ||
1576 | + return str; | ||
1577 | + }; | ||
1578 | + | ||
1579 | + | ||
1580 | + var header = dt.columns( config.columns ).indexes().map( function (idx) { | ||
1581 | + var el = dt.column( idx ).header(); | ||
1582 | + return config.format.header( el.innerHTML, idx, el ); | ||
1583 | + } ).toArray(); | ||
1584 | + | ||
1585 | + var footer = dt.table().footer() ? | ||
1586 | + dt.columns( config.columns ).indexes().map( function (idx) { | ||
1587 | + var el = dt.column( idx ).footer(); | ||
1588 | + return config.format.footer( el ? el.innerHTML : '', idx, el ); | ||
1589 | + } ).toArray() : | ||
1590 | + null; | ||
1591 | + | ||
1592 | + var selectedCells = dt.cells( config.rows, config.columns, config.modifier ); | ||
1593 | + var cells = selectedCells | ||
1594 | + .render( config.orthogonal ) | ||
1595 | + .toArray(); | ||
1596 | + var cellNodes = selectedCells | ||
1597 | + .nodes() | ||
1598 | + .toArray(); | ||
1599 | + | ||
1600 | + var columns = header.length; | ||
1601 | + var rows = columns > 0 ? cells.length / columns : 0; | ||
1602 | + var body = new Array( rows ); | ||
1603 | + var cellCounter = 0; | ||
1604 | + | ||
1605 | + for ( var i=0, ien=rows ; i<ien ; i++ ) { | ||
1606 | + var row = new Array( columns ); | ||
1607 | + | ||
1608 | + for ( var j=0 ; j<columns ; j++ ) { | ||
1609 | + row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] ); | ||
1610 | + cellCounter++; | ||
1611 | + } | ||
1612 | + | ||
1613 | + body[i] = row; | ||
1614 | + } | ||
1615 | + | ||
1616 | + return { | ||
1617 | + header: header, | ||
1618 | + footer: footer, | ||
1619 | + body: body | ||
1620 | + }; | ||
1621 | +}; | ||
1622 | + | ||
1623 | + | ||
1624 | +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
1625 | + * DataTables interface | ||
1626 | + */ | ||
1627 | + | ||
1628 | +// Attach to DataTables objects for global access | ||
1629 | +$.fn.dataTable.Buttons = Buttons; | ||
1630 | +$.fn.DataTable.Buttons = Buttons; | ||
1631 | + | ||
1632 | + | ||
1633 | + | ||
1634 | +// DataTables creation - check if the buttons have been defined for this table, | ||
1635 | +// they will have been if the `B` option was used in `dom`, otherwise we should | ||
1636 | +// create the buttons instance here so they can be inserted into the document | ||
1637 | +// using the API. Listen for `init` for compatibility with pre 1.10.10, but to | ||
1638 | +// be removed in future. | ||
1639 | +$(document).on( 'init.dt plugin-init.dt', function (e, settings) { | ||
1640 | + if ( e.namespace !== 'dt' ) { | ||
1641 | + return; | ||
1642 | + } | ||
1643 | + | ||
1644 | + var opts = settings.oInit.buttons || DataTable.defaults.buttons; | ||
1645 | + | ||
1646 | + if ( opts && ! settings._buttons ) { | ||
1647 | + new Buttons( settings, opts ).container(); | ||
1648 | + } | ||
1649 | +} ); | ||
1650 | + | ||
1651 | +// DataTables `dom` feature option | ||
1652 | +DataTable.ext.feature.push( { | ||
1653 | + fnInit: function( settings ) { | ||
1654 | + var api = new DataTable.Api( settings ); | ||
1655 | + var opts = api.init().buttons || DataTable.defaults.buttons; | ||
1656 | + | ||
1657 | + return new Buttons( api, opts ).container(); | ||
1658 | + }, | ||
1659 | + cFeature: "B" | ||
1660 | +} ); | ||
1661 | + | ||
1662 | + | ||
1663 | +return Buttons; | ||
1664 | +})); |
amadeus/static/js/goals_reports.js
@@ -23,8 +23,26 @@ function getAnswered() { | @@ -23,8 +23,26 @@ function getAnswered() { | ||
23 | // return false; | 23 | // return false; |
24 | // }); | 24 | // }); |
25 | $('#answered_table').DataTable({ | 25 | $('#answered_table').DataTable({ |
26 | - "dom": "frtip", | ||
27 | - "language": dataTablei18n | 26 | + "dom": "Bfrtip", |
27 | + "language": dataTablei18n, | ||
28 | + buttons: { | ||
29 | + dom: { | ||
30 | + container: { | ||
31 | + className: 'col-md-3' | ||
32 | + }, | ||
33 | + buttonContainer: { | ||
34 | + tag: 'h4', | ||
35 | + className: 'history-header' | ||
36 | + }, | ||
37 | + }, | ||
38 | + buttons: [ | ||
39 | + { | ||
40 | + extend: 'csv', | ||
41 | + text: csvBtnLabeli18n, | ||
42 | + filename: 'report-answered' | ||
43 | + } | ||
44 | + ] | ||
45 | + } | ||
28 | }); | 46 | }); |
29 | // var items = $("#answered_table").children(":visible").length; | 47 | // var items = $("#answered_table").children(":visible").length; |
30 | 48 |
goals/templates/goals/_answered.html
@@ -2,9 +2,6 @@ | @@ -2,9 +2,6 @@ | ||
2 | 2 | ||
3 | <div class="row"> | 3 | <div class="row"> |
4 | <div class="col-md-12"> | 4 | <div class="col-md-12"> |
5 | - <div class="col-md-3"> | ||
6 | - <h4 class="history-header"><a href="#"><i class="fa fa-download"></i> {% trans 'Download .csv file' %}</a></h4> | ||
7 | - </div> | ||
8 | <table id="answered_table" class="table table-striped table-bordered"> | 5 | <table id="answered_table" class="table table-striped table-bordered"> |
9 | <thead> | 6 | <thead> |
10 | <th> | 7 | <th> |
goals/templates/goals/reports.html
@@ -6,12 +6,15 @@ | @@ -6,12 +6,15 @@ | ||
6 | {% block style %} | 6 | {% block style %} |
7 | {{block.super}} | 7 | {{block.super}} |
8 | <link rel="stylesheet" type="text/css" href="{% static 'css/dataTables.bootstrap.css' %}"> | 8 | <link rel="stylesheet" type="text/css" href="{% static 'css/dataTables.bootstrap.css' %}"> |
9 | + <link rel="stylesheet" type="text/css" href="{% static 'css/dataTables.buttons.bootstrap.css' %}"> | ||
9 | {% endblock %} | 10 | {% endblock %} |
10 | 11 | ||
11 | {% block javascript%} | 12 | {% block javascript%} |
12 | {{ block.super }} | 13 | {{ block.super }} |
13 | <script type="text/javascript" src="{% static 'js/jquery.dataTables.js' %} "></script> | 14 | <script type="text/javascript" src="{% static 'js/jquery.dataTables.js' %} "></script> |
15 | + <script type="text/javascript" src="{% static 'js/dataTables.buttons.js' %} "></script> | ||
14 | <script type="text/javascript" src="{% static 'js/dataTables.bootstrap.js' %} "></script> | 16 | <script type="text/javascript" src="{% static 'js/dataTables.bootstrap.js' %} "></script> |
17 | + <script type="text/javascript" src="{% static 'js/dataTables.buttons.html5.js' %} "></script> | ||
15 | {% endblock%} | 18 | {% endblock%} |
16 | 19 | ||
17 | {% block breadcrumbs %} | 20 | {% block breadcrumbs %} |
@@ -90,7 +93,9 @@ | @@ -90,7 +93,9 @@ | ||
90 | "sortAscending": "{% trans ': activate to sort column ascending' %}", | 93 | "sortAscending": "{% trans ': activate to sort column ascending' %}", |
91 | "sortDescending": "{% trans ': activate to sort column descending' %}" | 94 | "sortDescending": "{% trans ': activate to sort column descending' %}" |
92 | } | 95 | } |
93 | - } | 96 | + }; |
97 | + | ||
98 | + var csvBtnLabeli18n = "<i class='fa fa-download'></i> {% trans 'Download .csv file' %}"; | ||
94 | </script> | 99 | </script> |
95 | <script type="text/javascript" src="{% static 'js/goals_reports.js' %}"></script> | 100 | <script type="text/javascript" src="{% static 'js/goals_reports.js' %}"></script> |
96 | {% endblock %} | 101 | {% endblock %} |
97 | \ No newline at end of file | 102 | \ No newline at end of file |