UNPKG

datatables.net-buttons

Version:
1,657 lines (1,445 loc) 46.3 kB
/*! * HTML5 export buttons for Buttons and DataTables. * © SpryMedia Ltd - datatables.net/license * * FileSaver.js (1.3.3) - MIT license * Copyright © 2016 Eli Grey - http://eligrey.com */ import jQuery from 'jquery'; import DataTable from 'datatables.net'; import Buttons from 'datatables.net-buttons'; // Allow reassignment of the $ variable let $ = jQuery; // Allow the constructor to pass in JSZip and PDFMake from external requires. // Otherwise, use globally defined variables, if they are available. var useJszip; var usePdfmake; function _jsZip() { return useJszip || window.JSZip; } function _pdfMake() { return usePdfmake || window.pdfMake; } DataTable.Buttons.pdfMake = function (_) { if (!_) { return _pdfMake(); } usePdfmake = _; }; DataTable.Buttons.jszip = function (_) { if (!_) { return _jsZip(); } useJszip = _; }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * FileSaver.js dependency */ /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ var _saveAs = (function (view) { 'use strict'; // IE <10 is explicitly unsupported if ( typeof view === 'undefined' || (typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) ) { return; } var doc = view.document, // only get URL when necessary in case Blob.js hasn't overridden it yet get_URL = function () { return view.URL || view.webkitURL || view; }, save_link = doc.createElementNS('http://www.w3.org/1999/xhtml', 'a'), can_use_save_link = 'download' in save_link, click = function (node) { var event = new MouseEvent('click'); node.dispatchEvent(event); }, is_safari = /constructor/i.test(view.HTMLElement) || view.safari, is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), throw_outside = function (ex) { (view.setImmediate || view.setTimeout)(function () { throw ex; }, 0); }, force_saveable_type = 'application/octet-stream', // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to arbitrary_revoke_timeout = 1000 * 40, // in ms revoke = function (file) { var revoker = function () { if (typeof file === 'string') { // file is an object URL get_URL().revokeObjectURL(file); } else { // file is a File file.remove(); } }; setTimeout(revoker, arbitrary_revoke_timeout); }, dispatch = function (filesaver, event_types, event) { event_types = [].concat(event_types); var i = event_types.length; while (i--) { var listener = filesaver['on' + event_types[i]]; if (typeof listener === 'function') { try { listener.call(filesaver, event || filesaver); } catch (ex) { throw_outside(ex); } } } }, auto_bom = function (blob) { // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF if ( /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test( blob.type ) ) { return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type }); } return blob; }, FileSaver = function (blob, name, no_auto_bom) { if (!no_auto_bom) { blob = auto_bom(blob); } // First try a.download, then web filesystem, then object URLs var filesaver = this, type = blob.type, force = type === force_saveable_type, object_url, dispatch_all = function () { dispatch( filesaver, 'writestart progress write writeend'.split(' ') ); }, // on any filesys errors revert to saving with object URLs fs_error = function () { if ( (is_chrome_ios || (force && is_safari)) && view.FileReader ) { // Safari doesn't allow downloading of blob urls var reader = new FileReader(); reader.onloadend = function () { var url = is_chrome_ios ? reader.result : reader.result.replace( /^data:[^;]*;/, 'data:attachment/file;' ); var popup = view.open(url, '_blank'); if (!popup) view.location.href = url; url = undefined; // release reference before dispatching filesaver.readyState = filesaver.DONE; dispatch_all(); }; reader.readAsDataURL(blob); filesaver.readyState = filesaver.INIT; return; } // don't create more object URLs than needed if (!object_url) { object_url = get_URL().createObjectURL(blob); } if (force) { view.location.href = object_url; } else { var opened = view.open(object_url, '_blank'); if (!opened) { // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html view.location.href = object_url; } } filesaver.readyState = filesaver.DONE; dispatch_all(); revoke(object_url); }; filesaver.readyState = filesaver.INIT; if (can_use_save_link) { object_url = get_URL().createObjectURL(blob); setTimeout(function () { save_link.href = object_url; save_link.download = name; click(save_link); dispatch_all(); revoke(object_url); filesaver.readyState = filesaver.DONE; }); return; } fs_error(); }, FS_proto = FileSaver.prototype, saveAs = function (blob, name, no_auto_bom) { return new FileSaver( blob, name || blob.name || 'download', no_auto_bom ); }; // IE 10+ (native saveAs) if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) { return function (blob, name, no_auto_bom) { name = name || blob.name || 'download'; if (!no_auto_bom) { blob = auto_bom(blob); } return navigator.msSaveOrOpenBlob(blob, name); }; } FS_proto.abort = function () {}; FS_proto.readyState = FS_proto.INIT = 0; FS_proto.WRITING = 1; FS_proto.DONE = 2; FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null; return saveAs; })( (typeof self !== 'undefined' && self) || (typeof window !== 'undefined' && window) || this.content ); // Expose file saver on the DataTables API. Can't attach to `DataTables.Buttons` // since this file can be loaded before Button's core! DataTable.fileSave = _saveAs; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Local (private) functions */ /** * Get the sheet name for Excel exports. * * @param {object} config Button configuration */ var _sheetname = function (config) { var sheetName = 'Sheet1'; if (config.sheetName) { sheetName = config.sheetName.replace(/[\[\]\*\/\\\?\:]/g, ''); } return sheetName; }; /** * Get the newline character(s) * * @param {object} config Button configuration * @return {string} Newline character */ var _newLine = function (config) { return config.newline ? config.newline : navigator.userAgent.match(/Windows/) ? '\r\n' : '\n'; }; /** * Combine the data from the `buttons.exportData` method into a string that * will be used in the export file. * * @param {DataTable.Api} dt DataTables API instance * @param {object} config Button configuration * @return {object} The data to export */ var _exportData = function (dt, config) { var newLine = _newLine(config); var data = dt.buttons.exportData(config.exportOptions); var boundary = config.fieldBoundary; var separator = config.fieldSeparator; var reBoundary = new RegExp(boundary, 'g'); var escapeChar = config.escapeChar !== undefined ? config.escapeChar : '\\'; var join = function (a) { var s = ''; // If there is a field boundary, then we might need to escape it in // the source data for (var i = 0, ien = a.length; i < ien; i++) { if (i > 0) { s += separator; } s += boundary ? boundary + ('' + a[i]).replace(reBoundary, escapeChar + boundary) + boundary : a[i]; } return s; }; var header = ''; var footer = ''; var body = []; if (config.header) { header = data.headerStructure .map(function (row) { return join( row.map(function (cell) { return cell ? cell.title : ''; }) ); }) .join(newLine) + newLine; } if (config.footer && data.footer) { footer = data.footerStructure .map(function (row) { return join( row.map(function (cell) { return cell ? cell.title : ''; }) ); }) .join(newLine) + newLine; } for (var i = 0, ien = data.body.length; i < ien; i++) { body.push(join(data.body[i])); } return { str: header + body.join(newLine) + newLine + footer, rows: body.length }; }; /** * Older versions of Safari (prior to tech preview 18) don't support the * download option required. * * @return {Boolean} `true` if old Safari */ var _isDuffSafari = function () { var safari = navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1 && navigator.userAgent.indexOf('Opera') === -1; if (!safari) { return false; } var version = navigator.userAgent.match(/AppleWebKit\/(\d+\.\d+)/); if (version && version.length > 1 && version[1] * 1 < 603.1) { return true; } return false; }; /** * Convert from numeric position to letter for column names in Excel * @param {int} n Column number * @return {string} Column letter(s) name */ function createCellPos(n) { var ordA = 'A'.charCodeAt(0); var ordZ = 'Z'.charCodeAt(0); var len = ordZ - ordA + 1; var s = ''; while (n >= 0) { s = String.fromCharCode((n % len) + ordA) + s; n = Math.floor(n / len) - 1; } return s; } try { var _serialiser = new XMLSerializer(); var _ieExcel; } catch (t) { // noop } /** * Recursively add XML files from an object's structure to a ZIP file. This * allows the XSLX file to be easily defined with an object's structure matching * the files structure. * * @param {JSZip} zip ZIP package * @param {object} obj Object to add (recursive) */ function _addToZip(zip, obj) { if (_ieExcel === undefined) { // Detect if we are dealing with IE's _awful_ serialiser by seeing if it // drop attributes _ieExcel = _serialiser .serializeToString( new window.DOMParser().parseFromString( excelStrings['xl/worksheets/sheet1.xml'], 'text/xml' ) ) .indexOf('xmlns:r') === -1; } $.each(obj, function (name, val) { if ($.isPlainObject(val)) { var newDir = zip.folder(name); _addToZip(newDir, val); } else { if (_ieExcel) { // IE's XML serialiser will drop some name space attributes from // from the root node, so we need to save them. Do this by // replacing the namespace nodes with a regular attribute that // we convert back when serialised. Edge does not have this // issue var worksheet = val.childNodes[0]; var i, ien; var attrs = []; for (i = worksheet.attributes.length - 1; i >= 0; i--) { var attrName = worksheet.attributes[i].nodeName; var attrValue = worksheet.attributes[i].nodeValue; if (attrName.indexOf(':') !== -1) { attrs.push({ name: attrName, value: attrValue }); worksheet.removeAttribute(attrName); } } for (i = 0, ien = attrs.length; i < ien; i++) { var attr = val.createAttribute( attrs[i].name.replace(':', '_dt_b_namespace_token_') ); attr.value = attrs[i].value; worksheet.setAttributeNode(attr); } } var str = _serialiser.serializeToString(val); // Fix IE's XML if (_ieExcel) { // IE doesn't include the XML declaration if (str.indexOf('<?xml') === -1) { str = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + str; } // Return namespace attributes to being as such str = str.replace(/_dt_b_namespace_token_/g, ':'); // Remove testing name space that IE puts into the space preserve attr str = str.replace(/xmlns:NS[\d]+="" NS[\d]+:/g, ''); } // Safari, IE and Edge will put empty name space attributes onto // various elements making them useless. This strips them out str = str.replace(/<([^<>]*?) xmlns=""([^<>]*?)>/g, '<$1 $2>'); zip.file(name, str); } }); } /** * Create an XML node and add any children, attributes, etc without needing to * be verbose in the DOM. * * @param {object} doc XML document * @param {string} nodeName Node name * @param {object} opts Options - can be `attr` (attributes), `children` * (child nodes) and `text` (text content) * @return {node} Created node */ function _createNode(doc, nodeName, opts) { var tempNode = doc.createElement(nodeName); if (opts) { if (opts.attr) { $(tempNode).attr(opts.attr); } if (opts.children) { $.each(opts.children, function (key, value) { tempNode.appendChild(value); }); } if (opts.text !== null && opts.text !== undefined) { tempNode.appendChild(doc.createTextNode(opts.text)); } } return tempNode; } /** * Get the width for an Excel column based on the contents of that column * @param {object} data Data for export * @param {int} col Column index * @return {int} Column width */ function _excelColWidth(data, col) { var max = data.header[col].length; var len, lineSplit, str; if (data.footer && data.footer[col] && data.footer[col].length > max) { max = data.footer[col].length; } for (var i = 0, ien = data.body.length; i < ien; i++) { var point = data.body[i][col]; str = point !== null && point !== undefined ? point.toString() : ''; // If there is a newline character, workout the width of the column // based on the longest line in the string if (str.indexOf('\n') !== -1) { lineSplit = str.split('\n'); lineSplit.sort(function (a, b) { return b.length - a.length; }); len = lineSplit[0].length; } else { len = str.length; } if (len > max) { max = len; } // Max width rather than having potentially massive column widths if (max > 40) { return 54; // 40 * 1.35 } } max *= 1.35; // And a min width return max > 6 ? max : 6; } // Excel - Pre-defined strings to build a basic XLSX file var excelStrings = { '_rels/.rels': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' + '</Relationships>', 'xl/_rels/workbook.xml.rels': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' + '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' + '</Relationships>', '[Content_Types].xml': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' + '<Default Extension="xml" ContentType="application/xml" />' + '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />' + '<Default Extension="jpeg" ContentType="image/jpeg" />' + '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" />' + '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" />' + '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" />' + '</Types>', 'xl/workbook.xml': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + '<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="24816"/>' + '<workbookPr showInkAnnotation="0" autoCompressPictures="0"/>' + '<bookViews>' + '<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>' + '</bookViews>' + '<sheets>' + '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>' + '</sheets>' + '<definedNames/>' + '</workbook>', 'xl/worksheets/sheet1.xml': '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + '<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">' + '<sheetData/>' + '<mergeCells count="0"/>' + '</worksheet>', 'xl/styles.xml': '<?xml version="1.0" encoding="UTF-8"?>' + '<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">' + '<numFmts count="6">' + '<numFmt numFmtId="164" formatCode="[$$-409]#,##0.00;-[$$-409]#,##0.00"/>' + '<numFmt numFmtId="165" formatCode="&quot;£&quot;#,##0.00"/>' + '<numFmt numFmtId="166" formatCode="[$€-2] #,##0.00"/>' + '<numFmt numFmtId="167" formatCode="0.0%"/>' + '<numFmt numFmtId="168" formatCode="#,##0;(#,##0)"/>' + '<numFmt numFmtId="169" formatCode="#,##0.00;(#,##0.00)"/>' + '</numFmts>' + '<fonts count="5" x14ac:knownFonts="1">' + '<font>' + '<sz val="11" />' + '<name val="Calibri" />' + '</font>' + '<font>' + '<sz val="11" />' + '<name val="Calibri" />' + '<color rgb="FFFFFFFF" />' + '</font>' + '<font>' + '<sz val="11" />' + '<name val="Calibri" />' + '<b />' + '</font>' + '<font>' + '<sz val="11" />' + '<name val="Calibri" />' + '<i />' + '</font>' + '<font>' + '<sz val="11" />' + '<name val="Calibri" />' + '<u />' + '</font>' + '</fonts>' + '<fills count="6">' + '<fill>' + '<patternFill patternType="none" />' + '</fill>' + '<fill>' + // Excel appears to use this as a dotted background regardless of values but '<patternFill patternType="none" />' + // to be valid to the schema, use a patternFill '</fill>' + '<fill>' + '<patternFill patternType="solid">' + '<fgColor rgb="FFD9D9D9" />' + '<bgColor indexed="64" />' + '</patternFill>' + '</fill>' + '<fill>' + '<patternFill patternType="solid">' + '<fgColor rgb="FFD99795" />' + '<bgColor indexed="64" />' + '</patternFill>' + '</fill>' + '<fill>' + '<patternFill patternType="solid">' + '<fgColor rgb="ffc6efce" />' + '<bgColor indexed="64" />' + '</patternFill>' + '</fill>' + '<fill>' + '<patternFill patternType="solid">' + '<fgColor rgb="ffc6cfef" />' + '<bgColor indexed="64" />' + '</patternFill>' + '</fill>' + '</fills>' + '<borders count="2">' + '<border>' + '<left />' + '<right />' + '<top />' + '<bottom />' + '<diagonal />' + '</border>' + '<border diagonalUp="false" diagonalDown="false">' + '<left style="thin">' + '<color auto="1" />' + '</left>' + '<right style="thin">' + '<color auto="1" />' + '</right>' + '<top style="thin">' + '<color auto="1" />' + '</top>' + '<bottom style="thin">' + '<color auto="1" />' + '</bottom>' + '<diagonal />' + '</border>' + '</borders>' + '<cellStyleXfs count="1">' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />' + '</cellStyleXfs>' + '<cellXfs count="68">' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="2" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="3" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="4" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="5" borderId="0" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="0" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="2" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="3" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="4" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="1" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="2" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="3" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="4" fillId="5" borderId="1" applyFont="1" applyFill="1" applyBorder="1"/>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + '<alignment horizontal="left"/>' + '</xf>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + '<alignment horizontal="center"/>' + '</xf>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + '<alignment horizontal="right"/>' + '</xf>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + '<alignment horizontal="fill"/>' + '</xf>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + '<alignment textRotation="90"/>' + '</xf>' + '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyAlignment="1">' + '<alignment wrapText="1"/>' + '</xf>' + '<xf numFmtId="9" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="164" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="165" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="166" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="167" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="168" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="169" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="3" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="4" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="1" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="2" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '<xf numFmtId="14" fontId="0" fillId="0" borderId="0" applyFont="1" applyFill="1" applyBorder="1" xfId="0" applyNumberFormat="1"/>' + '</cellXfs>' + '<cellStyles count="1">' + '<cellStyle name="Normal" xfId="0" builtinId="0" />' + '</cellStyles>' + '<dxfs count="0" />' + '<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4" />' + '</styleSheet>' }; // Note we could use 3 `for` loops for the styles, but when gzipped there is // virtually no difference in size, since the above can be easily compressed // Pattern matching for special number formats. Perhaps this should be exposed // via an API in future? // Ref: section 3.8.30 - built in formatters in open spreadsheet // https://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf var _excelSpecials = [ { match: /^\-?\d+\.\d%$/, style: 60, fmt: function (d) { return d / 100; } }, // Percent with d.p. { match: /^\-?\d+\.?\d*%$/, style: 56, fmt: function (d) { return d / 100; } }, // Percent { match: /^\-?\$[\d,]+.?\d*$/, style: 57 }, // Dollars { match: /^\-?£[\d,]+.?\d*$/, style: 58 }, // Pounds { match: /^\-?€[\d,]+.?\d*$/, style: 59 }, // Euros { match: /^\-?\d+$/, style: 65 }, // Numbers without thousand separators { match: /^\-?\d+\.\d{2}$/, style: 66 }, // Numbers 2 d.p. without thousands separators { match: /^\([\d,]+\)$/, style: 61, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets { match: /^\([\d,]+\.\d{2}\)$/, style: 62, fmt: function (d) { return -1 * d.replace(/[\(\)]/g, ''); } }, // Negative numbers indicated by brackets - 2d.p. { match: /^\-?[\d,]+$/, style: 63 }, // Numbers with thousand separators { match: /^\-?[\d,]+\.\d{2}$/, style: 64 }, { match: /^(19\d\d|[2-9]\d\d\d)\-(0\d|1[012])\-[0123][\d]$/, style: 67, fmt: function (d) { return Math.round(25569 + Date.parse(d) / (86400 * 1000)); } } //Date yyyy-mm-dd ]; var _excelMergeCells = function (rels, row, column, rowspan, colspan) { var mergeCells = $('mergeCells', rels); mergeCells[0].appendChild( _createNode(rels, 'mergeCell', { attr: { ref: createCellPos(column) + row + ':' + createCellPos(column + colspan - 1) + (row + rowspan - 1) } }) ); mergeCells.attr('count', parseFloat(mergeCells.attr('count')) + 1); }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Buttons */ // // Copy to clipboard // DataTable.ext.buttons.copyHtml5 = { className: 'buttons-copy buttons-html5', text: function (dt) { return dt.i18n('buttons.copy', 'Copy'); }, action: function (e, dt, button, config, cb) { var exportData = _exportData(dt, config); var info = dt.buttons.exportInfo(config); var newline = _newLine(config); var output = exportData.str; var hiddenDiv = $('<div/>').css({ height: 1, width: 1, overflow: 'hidden', position: 'fixed', top: 0, left: 0 }); if (info.title) { output = info.title + newline + newline + output; } if (info.messageTop) { output = info.messageTop + newline + newline + output; } if (info.messageBottom) { output = output + newline + newline + info.messageBottom; } if (config.customize) { output = config.customize(output, config, dt); } var textarea = $('<textarea readonly/>') .val(output) .appendTo(hiddenDiv); // For browsers that support the copy execCommand, try to use it if (document.queryCommandSupported('copy')) { hiddenDiv.appendTo(dt.table().container()); textarea[0].focus(); textarea[0].select(); try { var successful = document.execCommand('copy'); hiddenDiv.remove(); if (successful) { if (config.copySuccess) { dt.buttons.info( dt.i18n('buttons.copyTitle', 'Copy to clipboard'), dt.i18n( 'buttons.copySuccess', { 1: 'Copied one row to clipboard', _: 'Copied %d rows to clipboard' }, exportData.rows ), 2000 ); } cb(); return; } } catch (t) { // noop } } // Otherwise we show the text box and instruct the user to use it var message = $( '<span>' + dt.i18n( 'buttons.copyKeys', 'Press <i>ctrl</i> or <i>\u2318</i> + <i>C</i> to copy the table data<br>to your system clipboard.<br><br>' + 'To cancel, click this message or press escape.' ) + '</span>' ).append(hiddenDiv); dt.buttons.info( dt.i18n('buttons.copyTitle', 'Copy to clipboard'), message, 0 ); // Select the text so when the user activates their system clipboard // it will copy that text textarea[0].focus(); textarea[0].select(); // Event to hide the message when the user is done var container = $(message).closest('.dt-button-info'); var close = function () { container.off('click.buttons-copy'); $(document).off('.buttons-copy'); dt.buttons.info(false); }; container.on('click.buttons-copy', function () { close(); cb(); }); $(document) .on('keydown.buttons-copy', function (e) { if (e.keyCode === 27) { // esc close(); cb(); } }) .on('copy.buttons-copy cut.buttons-copy', function () { close(); cb(); }); }, async: 100, copySuccess: true, exportOptions: {}, fieldSeparator: '\t', fieldBoundary: '', header: true, footer: true, title: '*', messageTop: '*', messageBottom: '*' }; // // CSV export // DataTable.ext.buttons.csvHtml5 = { bom: false, className: 'buttons-csv buttons-html5', available: function () { return window.FileReader !== undefined && window.Blob; }, text: function (dt) { return dt.i18n('buttons.csv', 'CSV'); }, action: function (e, dt, button, config, cb) { // Set the text var output = _exportData(dt, config).str; var info = dt.buttons.exportInfo(config); var charset = config.charset; if (config.customize) { output = config.customize(output, config, dt); } if (charset !== false) { if (!charset) { charset = document.characterSet || document.charset; } if (charset) { charset = ';charset=' + charset; } } else { charset = ''; } if (config.bom) { output = String.fromCharCode(0xfeff) + output; } _saveAs( new Blob([output], { type: 'text/csv' + charset }), info.filename, true ); cb(); }, async: 100, filename: '*', extension: '.csv', exportOptions: { escapeExcelFormula: true }, fieldSeparator: ',', fieldBoundary: '"', escapeChar: '"', charset: null, header: true, footer: true }; // // Excel (xlsx) export // DataTable.ext.buttons.excelHtml5 = { className: 'buttons-excel buttons-html5', available: function () { return ( window.FileReader !== undefined && _jsZip() !== undefined && !_isDuffSafari() && _serialiser ); }, text: function (dt) { return dt.i18n('buttons.excel', 'Excel'); }, action: function (e, dt, button, config, cb) { var rowPos = 0; var dataStartRow, dataEndRow; var getXml = function (type) { var str = excelStrings[type]; //str = str.replace( /xmlns:/g, 'xmlns_' ).replace( /mc:/g, 'mc_' ); return $.parseXML(str); }; var rels = getXml('xl/worksheets/sheet1.xml'); var relsGet = rels.getElementsByTagName('sheetData')[0]; var xlsx = { _rels: { '.rels': getXml('_rels/.rels') }, xl: { _rels: { 'workbook.xml.rels': getXml('xl/_rels/workbook.xml.rels') }, 'workbook.xml': getXml('xl/workbook.xml'), 'styles.xml': getXml('xl/styles.xml'), worksheets: { 'sheet1.xml': rels } }, '[Content_Types].xml': getXml('[Content_Types].xml') }; var data = dt.buttons.exportData(config.exportOptions); var currentRow, rowNode; var addRow = function (row) { currentRow = rowPos + 1; rowNode = _createNode(rels, 'row', { attr: { r: currentRow } }); for (var i = 0, ien = row.length; i < ien; i++) { // Concat both the Cell Columns as a letter and the Row of the cell. var cellId = createCellPos(i) + '' + currentRow; var cell = null; // For null, undefined of blank cell, continue so it doesn't create the _createNode if (row[i] === null || row[i] === undefined || row[i] === '') { if (config.createEmptyCells === true) { row[i] = ''; } else { continue; } } var originalContent = row[i]; row[i] = typeof row[i].trim === 'function' ? row[i].trim() : row[i]; // Special number formatting options for (var j = 0, jen = _excelSpecials.length; j < jen; j++) { var special = _excelSpecials[j]; // TODO Need to provide the ability for the specials to say // if they are returning a string, since at the moment it is // assumed to be a number if ( row[i].match && !row[i].match(/^0\d+/) && row[i].match(special.match) ) { var val = row[i].replace(/[^\d\.\-]/g, ''); if (special.fmt) { val = special.fmt(val); } cell = _createNode(rels, 'c', { attr: { r: cellId, s: special.style }, children: [_createNode(rels, 'v', { text: val })] }); break; } } if (!cell) { if ( typeof row[i] === 'number' || (row[i].match && row[i].match(/^-?\d+(\.\d+)?([eE]\-?\d+)?$/) && // Includes exponential format !row[i].match(/^0\d+/)) ) { // Detect numbers - don't match numbers with leading zeros // or a negative anywhere but the start cell = _createNode(rels, 'c', { attr: { t: 'n', r: cellId }, children: [_createNode(rels, 'v', { text: row[i] })] }); } else { // String output - replace non standard characters for text output /*eslint no-control-regex: "off"*/ var text = !originalContent.replace ? originalContent : originalContent.replace( /[\x00-\x09\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, '' ); cell = _createNode(rels, 'c', { attr: { t: 'inlineStr', r: cellId }, children: { row: _createNode(rels, 'is', { children: { row: _createNode(rels, 't', { text: text, attr: { 'xml:space': 'preserve' } }) } }) } }); } } rowNode.appendChild(cell); } relsGet.appendChild(rowNode); rowPos++; }; var addHeader = function (structure) { structure.forEach(function (row) { addRow( row.map(function (cell) { return cell ? cell.title : ''; }), rowPos ); $('row:last c', rels).attr('s', '2'); // bold // Add any merge cells row.forEach(function (cell, columnCounter) { if (cell && (cell.colSpan > 1 || cell.rowSpan > 1)) { _excelMergeCells( rels, rowPos, columnCounter, cell.rowSpan, cell.colSpan ); } }); }); }; // Title and top messages var exportInfo = dt.buttons.exportInfo(config); if (exportInfo.title) { addRow([exportInfo.title], rowPos); _excelMergeCells(rels, rowPos, 0, 1, data.header.length); $('row:last c', rels).attr('s', '51'); // centre } if (exportInfo.messageTop) { addRow([exportInfo.messageTop], rowPos); _excelMergeCells(rels, rowPos, 0, 1, data.header.length); } // Table header if (config.header) { addHeader(data.headerStructure); } dataStartRow = rowPos; // Table body for (var n = 0, ie = data.body.length; n < ie; n++) { addRow(data.body[n], rowPos); } dataEndRow = rowPos; // Table footer if (config.footer && data.footer) { addHeader(data.footerStructure); } // Below the table if (exportInfo.messageBottom) { addRow([exportInfo.messageBottom], rowPos); _excelMergeCells(rels, rowPos, 0, 1, data.header.length); } // Set column widths var cols = _createNode(rels, 'cols'); $('worksheet', rels).prepend(cols); for (var i = 0, ien = data.header.length; i < ien; i++) { cols.appendChild( _createNode(rels, 'col', { attr: { min: i + 1, max: i + 1, width: _excelColWidth(data, i), customWidth: 1 } }) ); } // Workbook modifications var workbook = xlsx.xl['workbook.xml']; $('sheets sheet', workbook).attr('name', _sheetname(config)); // Auto filter for columns if (config.autoFilter) { $('mergeCells', rels).before( _createNode(rels, 'autoFilter', { attr: { ref: 'A' + dataStartRow + ':' + createCellPos(data.header.length - 1) + dataEndRow } }) ); $('definedNames', workbook).append( _createNode(workbook, 'definedName', { attr: { name: '_xlnm._FilterDatabase', localSheetId: '0', hidden: 1 }, text: '\'' + _sheetname(config).replace(/'/g, '\'\'') + '\'!$A$' + dataStartRow + ':' + createCellPos(data.header.length - 1) + dataEndRow }) ); } // Let the developer customise the document if they want to if (config.customize) { config.customize(xlsx, config, dt); } // Excel doesn't like an empty mergeCells tag if ($('mergeCells', rels).children().length === 0) { $('mergeCells', rels).remove(); } var jszip = _jsZip(); var zip = new jszip(); var zipConfig = { compression: 'DEFLATE', type: 'blob', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }; _addToZip(zip, xlsx); // Modern Excel has a 218 character limit on the file name + path of the file (why!?) // https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 // So we truncate to allow for this. var filename = exportInfo.filename; if (filename > 175) { filename = filename.substr(0, 175); } // Let the developer customize the final zip file if they want to before it is generated and sent to the browser if (config.customizeZip) { config.customizeZip(zip, data, filename); } if (zip.generateAsync) { // JSZip 3+ zip.generateAsync(zipConfig).then(function (blob) { _saveAs(blob, filename); cb(); }); } else { // JSZip 2.5 _saveAs(zip.generate(zipConfig), filename); cb(); } }, async: 100, filename: '*', extension: '.xlsx', exportOptions: {}, header: true, footer: true, title: '*', messageTop: '*', messageBottom: '*', createEmptyCells: false, autoFilter: false, sheetName: '' }; // // PDF export - using pdfMake - http://pdfmake.org // DataTable.ext.buttons.pdfHtml5 = { className: 'buttons-pdf buttons-html5', available: function () { return window.FileReader !== undefined && _pdfMake(); }, text: function (dt) { return dt.i18n('buttons.pdf', 'PDF'); }, action: function (e, dt, button, config, cb) { var data = dt.buttons.exportData(config.exportOptions); var info = dt.buttons.exportInfo(config); var rows = []; if (config.header) { data.headerStructure.forEach(function (row) { rows.push( row.map(function (cell) { return cell ? { text: cell.title, colSpan: cell.colspan, rowSpan: cell.rowspan, style: 'tableHeader' } : {}; }) ); }); } for (var i = 0, ien = data.body.length; i < ien; i++) { rows.push( data.body[i].map(function (d) { return { text: d === null || d === undefined ? '' : typeof d === 'string' ? d : d.toString() }; }) ); } if (config.footer) { data.footerStructure.forEach(function (row) { rows.push( row.map(function (cell) { return cell ? { text: cell.title, colSpan: cell.colspan, rowSpan: cell.rowspan, style: 'tableFooter' } : {}; }) ); }); } var doc = { pageSize: config.pageSize, pageOrientation: config.orientation, content: [ { style: 'table', table: { headerRows: data.headerStructure.length, footerRows: data.footerStructure.length, // Used for styling, doesn't do anything in pdfmake body: rows }, layout: { hLineWidth: function (i, node) { if (i === 0 || i === node.table.body.length) { return 0; } return 0.5; }, vLineWidth: function () { return 0; }, hLineColor: function (i, node) { return i === node.table.headerRows || i === node.table.body.length - node.table.footerRows ? '#333' : '#ddd'; }, fillColor: function (rowIndex) { if (rowIndex < data.headerStructure.length) { return '#fff'; } return rowIndex % 2 === 0 ? '#f3f3f3' : null; }, paddingTop: function () { return 5; }, paddingBottom: function () { return 5; } } } ], styles: { tableHeader: { bold: true, fontSize: 11, alignment: 'center' }, tableFooter: { bold: true, fontSize: 11, alignment: 'center' }, table: { margin: [0, 5, 0, 5] }, title: { alignment: 'center', fontSize: 13 }, message: {} }, defaultStyle: { fontSize: 10 } }; if (info.messageTop) { doc.content.unshift({ text: info.messageTop, style: 'message', margin: [0, 0, 0, 12] }); } if (info.messageBottom) { doc.content.push({ text: info.messageBottom, style: 'message', margin: [0, 0, 0, 12] }); } if (info.title) { doc.content.unshift({ text: info.title, style: 'title', margin: [0, 0, 0, 12] }); } if (config.customize) { config.customize(doc, config, dt); } var pdf = _pdfMake().createPdf(doc); if (config.download === 'open' && !_isDuffSafari()) { pdf.open(); } else { pdf.download(info.filename); } cb(); }, async: 100, title: '*', filename: '*', extension: '.pdf', exportOptions: {}, orientation: 'portrait', // This isn't perfect, but it is close pageSize: navigator.language === 'en-US' || navigator.language === 'en-CA' ? 'LETTER' : 'A4', header: true, footer: true, messageTop: '*', messageBottom: '*', customize: null, download: 'download' }; export default DataTable;