/*! * 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 */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } if ( ! $.fn.dataTable.Buttons ) { require('datatables.net-buttons')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $, jszip, pdfmake) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document, jszip, pdfmake ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.document ); } } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, jszip, pdfmake, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; // 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 = config.header ? join(data.header) + newLine : ''; var footer = config.footer && data.footer ? newLine + join(data.footer) : ''; var body = []; for (var i = 0, ien = data.body.length; i < ien; i++) { body.push(join(data.body[i])); } return { str: header + body.join(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) {} /** * 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('' + 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].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': '' + '' + '' + '', 'xl/_rels/workbook.xml.rels': '' + '' + '' + '' + '', '[Content_Types].xml': '' + '' + '' + '' + '' + '' + '' + '' + '', 'xl/workbook.xml': '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '', 'xl/worksheets/sheet1.xml': '' + '' + '' + '' + '', 'xl/styles.xml': '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + // Excel appears to use this as a dotted background regardless of values but '' + // to be valid to the schema, use a patternFill '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' }; // 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: /^[\d]{4}\-[01][\d]\-[0123][\d]$/, style: 67, fmt: function (d) { return Math.round(25569 + Date.parse(d) / (86400 * 1000)); } } //Date yyyy-mm-dd ]; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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) { this.processing(true); var that = this; var exportData = _exportData(dt, config); var info = dt.buttons.exportInfo(config); var newline = _newLine(config); var output = exportData.str; var hiddenDiv = $('
').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 = $('