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 @@ |
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 @@ |
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 @@ |
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 | 23 | // return false; |
24 | 24 | // }); |
25 | 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 | 47 | // var items = $("#answered_table").children(":visible").length; |
30 | 48 | ... | ... |
goals/templates/goals/_answered.html
... | ... | @@ -2,9 +2,6 @@ |
2 | 2 | |
3 | 3 | <div class="row"> |
4 | 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 | 5 | <table id="answered_table" class="table table-striped table-bordered"> |
9 | 6 | <thead> |
10 | 7 | <th> | ... | ... |
goals/templates/goals/reports.html
... | ... | @@ -6,12 +6,15 @@ |
6 | 6 | {% block style %} |
7 | 7 | {{block.super}} |
8 | 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 | 10 | {% endblock %} |
10 | 11 | |
11 | 12 | {% block javascript%} |
12 | 13 | {{ block.super }} |
13 | 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 | 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 | 18 | {% endblock%} |
16 | 19 | |
17 | 20 | {% block breadcrumbs %} |
... | ... | @@ -90,7 +93,9 @@ |
90 | 93 | "sortAscending": "{% trans ': activate to sort column ascending' %}", |
91 | 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 | 99 | </script> |
95 | 100 | <script type="text/javascript" src="{% static 'js/goals_reports.js' %}"></script> |
96 | 101 | {% endblock %} |
97 | 102 | \ No newline at end of file | ... | ... |