vue-json-excel.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = global || self, global.JsonExcel = factory());
  5. }(this, (function () { 'use strict';
  6. var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  7. function createCommonjsModule(fn, module) {
  8. return module = { exports: {} }, fn(module, module.exports), module.exports;
  9. }
  10. var download = createCommonjsModule(function (module, exports) {
  11. //download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
  12. // v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
  13. // v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
  14. // v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
  15. // v4 adds AMD/UMD, commonJS, and plain browser support
  16. // v4.1 adds url download capability via solo URL argument (same domain/CORS only)
  17. // v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
  18. // https://github.com/rndme/download
  19. (function (root, factory) {
  20. {
  21. // Node. Does not work with strict CommonJS, but
  22. // only CommonJS-like environments that support module.exports,
  23. // like Node.
  24. module.exports = factory();
  25. }
  26. }(commonjsGlobal, function () {
  27. return function download(data, strFileName, strMimeType) {
  28. var self = window, // this script is only for browsers anyway...
  29. defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
  30. mimeType = strMimeType || defaultMime,
  31. payload = data,
  32. url = !strFileName && !strMimeType && payload,
  33. anchor = document.createElement("a"),
  34. toString = function(a){return String(a);},
  35. myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
  36. fileName = strFileName || "download",
  37. blob,
  38. reader;
  39. myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
  40. if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
  41. payload=[payload, mimeType];
  42. mimeType=payload[0];
  43. payload=payload[1];
  44. }
  45. if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
  46. fileName = url.split("/").pop().split("?")[0];
  47. anchor.href = url; // assign href prop to temp anchor
  48. if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
  49. var ajax=new XMLHttpRequest();
  50. ajax.open( "GET", url, true);
  51. ajax.responseType = 'blob';
  52. ajax.onload= function(e){
  53. download(e.target.response, fileName, defaultMime);
  54. };
  55. setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
  56. return ajax;
  57. } // end if valid url?
  58. } // end if url?
  59. //go ahead and download dataURLs right away
  60. if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
  61. if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
  62. payload=dataUrlToBlob(payload);
  63. mimeType=payload.type || defaultMime;
  64. }else {
  65. return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
  66. navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
  67. saver(payload) ; // everyone else can save dataURLs un-processed
  68. }
  69. }else {//not data url, is it a string with special needs?
  70. if(/([\x80-\xff])/.test(payload)){
  71. var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
  72. for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
  73. payload=new myBlob([tempUiArr], {type: mimeType});
  74. }
  75. }
  76. blob = payload instanceof myBlob ?
  77. payload :
  78. new myBlob([payload], {type: mimeType}) ;
  79. function dataUrlToBlob(strUrl) {
  80. var parts= strUrl.split(/[:;,]/),
  81. type= parts[1],
  82. decoder= parts[2] == "base64" ? atob : decodeURIComponent,
  83. binData= decoder( parts.pop() ),
  84. mx= binData.length,
  85. i= 0,
  86. uiArr= new Uint8Array(mx);
  87. for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
  88. return new myBlob([uiArr], {type: type});
  89. }
  90. function saver(url, winMode){
  91. if ('download' in anchor) { //html5 A[download]
  92. anchor.href = url;
  93. anchor.setAttribute("download", fileName);
  94. anchor.className = "download-js-link";
  95. anchor.innerHTML = "downloading...";
  96. anchor.style.display = "none";
  97. document.body.appendChild(anchor);
  98. setTimeout(function() {
  99. anchor.click();
  100. document.body.removeChild(anchor);
  101. if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
  102. }, 66);
  103. return true;
  104. }
  105. // handle non-a[download] safari as best we can:
  106. if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
  107. if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
  108. if(!window.open(url)){ // popup blocked, offer direct download:
  109. if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
  110. }
  111. return true;
  112. }
  113. //do iframe dataURL download (old ch+FF):
  114. var f = document.createElement("iframe");
  115. document.body.appendChild(f);
  116. if(!winMode && /^data:/.test(url)){ // force a mime that will download:
  117. url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
  118. }
  119. f.src=url;
  120. setTimeout(function(){ document.body.removeChild(f); }, 333);
  121. }//end saver
  122. if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
  123. return navigator.msSaveBlob(blob, fileName);
  124. }
  125. if(self.URL){ // simple fast and modern way using Blob and URL:
  126. saver(self.URL.createObjectURL(blob), true);
  127. }else {
  128. // handle non-Blob()+non-URL browsers:
  129. if(typeof blob === "string" || blob.constructor===toString ){
  130. try{
  131. return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
  132. }catch(y){
  133. return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
  134. }
  135. }
  136. // Blob but not URL support:
  137. reader=new FileReader();
  138. reader.onload=function(e){
  139. saver(this.result);
  140. };
  141. reader.readAsDataURL(blob);
  142. }
  143. return true;
  144. }; /* end download() */
  145. }));
  146. });
  147. //
  148. var script = {
  149. props: {
  150. // mime type [xls, csv]
  151. type: {
  152. type: String,
  153. default: "xls",
  154. },
  155. // Json to download
  156. data: {
  157. type: Array,
  158. required: false,
  159. default: null,
  160. },
  161. // fields inside the Json Object that you want to export
  162. // if no given, all the properties in the Json are exported
  163. fields: {
  164. type: Object,
  165. default: () => null,
  166. },
  167. // this prop is used to fix the problem with other components that use the
  168. // variable fields, like vee-validate. exportFields works exactly like fields
  169. exportFields: {
  170. type: Object,
  171. default: () => null,
  172. },
  173. // Use as fallback when the row has no field values
  174. defaultValue: {
  175. type: String,
  176. required: false,
  177. default: "",
  178. },
  179. // Title(s) for the data, could be a string or an array of strings (multiple titles)
  180. header: {
  181. default: null,
  182. },
  183. // Footer(s) for the data, could be a string or an array of strings (multiple footers)
  184. footer: {
  185. default: null,
  186. },
  187. // filename to export
  188. name: {
  189. type: String,
  190. default: "data.xls",
  191. },
  192. fetch: {
  193. type: Function,
  194. },
  195. meta: {
  196. type: Array,
  197. default: () => [],
  198. },
  199. worksheet: {
  200. type: String,
  201. default: "Sheet1",
  202. },
  203. //event before generate was called
  204. beforeGenerate: {
  205. type: Function,
  206. },
  207. //event before download pops up
  208. beforeFinish: {
  209. type: Function,
  210. },
  211. // Determine if CSV Data should be escaped
  212. escapeCsv: {
  213. type: Boolean,
  214. default: true,
  215. },
  216. // long number stringify
  217. stringifyLongNum: {
  218. type: Boolean,
  219. default: false,
  220. },
  221. },
  222. computed: {
  223. // unique identifier
  224. idName() {
  225. var now = new Date().getTime();
  226. return "export_" + now;
  227. },
  228. downloadFields() {
  229. if (this.fields) return this.fields;
  230. if (this.exportFields) return this.exportFields;
  231. },
  232. },
  233. methods: {
  234. async generate() {
  235. if (typeof this.beforeGenerate === "function") {
  236. await this.beforeGenerate();
  237. }
  238. let data = this.data;
  239. if (typeof this.fetch === "function" || !data) data = await this.fetch();
  240. if (!data || !data.length) {
  241. return;
  242. }
  243. let json = this.getProcessedJson(data, this.downloadFields);
  244. if (this.type === "html") {
  245. // this is mainly for testing
  246. return this.export(
  247. this.jsonToXLS(json),
  248. this.name.replace(".xls", ".html"),
  249. "text/html"
  250. );
  251. } else if (this.type === "csv") {
  252. return this.export(
  253. this.jsonToCSV(json),
  254. this.name.replace(".xls", ".csv"),
  255. "application/csv"
  256. );
  257. }
  258. return this.export(
  259. this.jsonToXLS(json),
  260. this.name,
  261. "application/vnd.ms-excel"
  262. );
  263. },
  264. /*
  265. Use downloadjs to generate the download link
  266. */
  267. export: async function (data, filename, mime) {
  268. let blob = this.base64ToBlob(data, mime);
  269. if (typeof this.beforeFinish === "function") await this.beforeFinish();
  270. download(blob, filename, mime);
  271. },
  272. /*
  273. jsonToXLS
  274. ---------------
  275. Transform json data into an xml document with MS Excel format, sadly
  276. it shows a prompt when it opens, that is a default behavior for
  277. Microsoft office and cannot be avoided. It's recommended to use CSV format instead.
  278. */
  279. jsonToXLS(data) {
  280. let xlsTemp =
  281. '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>${worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style>br {mso-data-placement: same-cell;}</style></head><body><table>${table}</table></body></html>';
  282. let xlsData = "<thead>";
  283. const colspan = Object.keys(data[0]).length;
  284. let _self = this;
  285. //Header
  286. const header = this.header || this.$attrs.title;
  287. if (header) {
  288. xlsData += this.parseExtraData(
  289. header,
  290. '<tr><th colspan="' + colspan + '">${data}</th></tr>'
  291. );
  292. }
  293. //Fields
  294. xlsData += "<tr>";
  295. for (let key in data[0]) {
  296. xlsData += "<th>" + key + "</th>";
  297. }
  298. xlsData += "</tr>";
  299. xlsData += "</thead>";
  300. //Data
  301. xlsData += "<tbody>";
  302. data.map(function (item, index) {
  303. xlsData += "<tr>";
  304. for (let key in item) {
  305. xlsData +=
  306. "<td>" +
  307. _self.preprocessLongNum(
  308. _self.valueReformattedForMultilines(item[key])
  309. ) +
  310. "</td>";
  311. }
  312. xlsData += "</tr>";
  313. });
  314. xlsData += "</tbody>";
  315. //Footer
  316. if (this.footer != null) {
  317. xlsData += "<tfoot>";
  318. xlsData += this.parseExtraData(
  319. this.footer,
  320. '<tr><td colspan="' + colspan + '">${data}</td></tr>'
  321. );
  322. xlsData += "</tfoot>";
  323. }
  324. return xlsTemp
  325. .replace("${table}", xlsData)
  326. .replace("${worksheet}", this.worksheet);
  327. },
  328. /*
  329. jsonToCSV
  330. ---------------
  331. Transform json data into an CSV file.
  332. */
  333. jsonToCSV(data) {
  334. let _self = this;
  335. var csvData = [];
  336. //Header
  337. const header = this.header || this.$attrs.title;
  338. if (header) {
  339. csvData.push(this.parseExtraData(header, "${data}\r\n"));
  340. }
  341. //Fields
  342. for (let key in data[0]) {
  343. csvData.push(key);
  344. csvData.push(",");
  345. }
  346. csvData.pop();
  347. csvData.push("\r\n");
  348. //Data
  349. data.map(function (item) {
  350. for (let key in item) {
  351. let escapedCSV = item[key] + "";
  352. // Escaped CSV data to string to avoid problems with numbers or other types of values
  353. // this is controlled by the prop escapeCsv
  354. if (_self.escapeCsv) {
  355. escapedCSV = '="' + escapedCSV + '"'; // cast Numbers to string
  356. if (escapedCSV.match(/[,"\n]/)) {
  357. escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
  358. }
  359. }
  360. csvData.push(escapedCSV);
  361. csvData.push(",");
  362. }
  363. csvData.pop();
  364. csvData.push("\r\n");
  365. });
  366. //Footer
  367. if (this.footer != null) {
  368. csvData.push(this.parseExtraData(this.footer, "${data}\r\n"));
  369. }
  370. return csvData.join("");
  371. },
  372. /*
  373. getProcessedJson
  374. ---------------
  375. Get only the data to export, if no fields are set return all the data
  376. */
  377. getProcessedJson(data, header) {
  378. let keys = this.getKeys(data, header);
  379. let newData = [];
  380. let _self = this;
  381. data.map(function (item, index) {
  382. let newItem = {};
  383. for (let label in keys) {
  384. let property = keys[label];
  385. newItem[label] = _self.getValue(property, item);
  386. }
  387. newData.push(newItem);
  388. });
  389. return newData;
  390. },
  391. getKeys(data, header) {
  392. if (header) {
  393. return header;
  394. }
  395. let keys = {};
  396. for (let key in data[0]) {
  397. keys[key] = key;
  398. }
  399. return keys;
  400. },
  401. /*
  402. parseExtraData
  403. ---------------
  404. Parse title and footer attribute to the csv format
  405. */
  406. parseExtraData(extraData, format) {
  407. let parseData = "";
  408. if (Array.isArray(extraData)) {
  409. for (var i = 0; i < extraData.length; i++) {
  410. if (extraData[i])
  411. parseData += format.replace("${data}", extraData[i]);
  412. }
  413. } else {
  414. parseData += format.replace("${data}", extraData);
  415. }
  416. return parseData;
  417. },
  418. getValue(key, item) {
  419. const field = typeof key !== "object" ? key : key.field;
  420. let indexes = typeof field !== "string" ? [] : field.split(".");
  421. let value = this.defaultValue;
  422. if (!field) value = item;
  423. else if (indexes.length > 1)
  424. value = this.getValueFromNestedItem(item, indexes);
  425. else value = this.parseValue(item[field]);
  426. if (key.hasOwnProperty("callback"))
  427. value = this.getValueFromCallback(value, key.callback);
  428. return value;
  429. },
  430. /*
  431. convert values with newline \n characters into <br/>
  432. */
  433. valueReformattedForMultilines(value) {
  434. if (typeof value == "string") return value.replace(/\n/gi, "<br/>");
  435. else return value;
  436. },
  437. preprocessLongNum(value) {
  438. if (this.stringifyLongNum) {
  439. if (String(value).startsWith("0x")) {
  440. return value;
  441. }
  442. if (!isNaN(value) && value != "") {
  443. if (value > 99999999999 || value < 0.0000000000001) {
  444. return '="' + value + '"';
  445. }
  446. }
  447. }
  448. return value;
  449. },
  450. getValueFromNestedItem(item, indexes) {
  451. let nestedItem = item;
  452. for (let index of indexes) {
  453. if (nestedItem) nestedItem = nestedItem[index];
  454. }
  455. return this.parseValue(nestedItem);
  456. },
  457. getValueFromCallback(item, callback) {
  458. if (typeof callback !== "function") return this.defaultValue;
  459. const value = callback(item);
  460. return this.parseValue(value);
  461. },
  462. parseValue(value) {
  463. return value || value === 0 || typeof value === "boolean"
  464. ? value
  465. : this.defaultValue;
  466. },
  467. base64ToBlob(data, mime) {
  468. let base64 = window.btoa(window.unescape(encodeURIComponent(data)));
  469. let bstr = atob(base64);
  470. let n = bstr.length;
  471. let u8arr = new Uint8ClampedArray(n);
  472. while (n--) {
  473. u8arr[n] = bstr.charCodeAt(n);
  474. }
  475. return new Blob([u8arr], { type: mime });
  476. },
  477. }, // end methods
  478. };
  479. function normalizeComponent(template, style, script, scopeId, isFunctionalTemplate, moduleIdentifier
  480. /* server only */
  481. , shadowMode, createInjector, createInjectorSSR, createInjectorShadow) {
  482. if (typeof shadowMode !== 'boolean') {
  483. createInjectorSSR = createInjector;
  484. createInjector = shadowMode;
  485. shadowMode = false;
  486. } // Vue.extend constructor export interop.
  487. var options = typeof script === 'function' ? script.options : script; // render functions
  488. if (template && template.render) {
  489. options.render = template.render;
  490. options.staticRenderFns = template.staticRenderFns;
  491. options._compiled = true; // functional template
  492. if (isFunctionalTemplate) {
  493. options.functional = true;
  494. }
  495. } // scopedId
  496. if (scopeId) {
  497. options._scopeId = scopeId;
  498. }
  499. var hook;
  500. if (moduleIdentifier) {
  501. // server build
  502. hook = function hook(context) {
  503. // 2.3 injection
  504. context = context || // cached call
  505. this.$vnode && this.$vnode.ssrContext || // stateful
  506. this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext; // functional
  507. // 2.2 with runInNewContext: true
  508. if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
  509. context = __VUE_SSR_CONTEXT__;
  510. } // inject component styles
  511. if (style) {
  512. style.call(this, createInjectorSSR(context));
  513. } // register component module identifier for async chunk inference
  514. if (context && context._registeredComponents) {
  515. context._registeredComponents.add(moduleIdentifier);
  516. }
  517. }; // used by ssr in case component is cached and beforeCreate
  518. // never gets called
  519. options._ssrRegister = hook;
  520. } else if (style) {
  521. hook = shadowMode ? function () {
  522. style.call(this, createInjectorShadow(this.$root.$options.shadowRoot));
  523. } : function (context) {
  524. style.call(this, createInjector(context));
  525. };
  526. }
  527. if (hook) {
  528. if (options.functional) {
  529. // register for functional component in vue file
  530. var originalRender = options.render;
  531. options.render = function renderWithStyleInjection(h, context) {
  532. hook.call(context);
  533. return originalRender(h, context);
  534. };
  535. } else {
  536. // inject component registration as beforeCreate hook
  537. var existing = options.beforeCreate;
  538. options.beforeCreate = existing ? [].concat(existing, hook) : [hook];
  539. }
  540. }
  541. return script;
  542. }
  543. var normalizeComponent_1 = normalizeComponent;
  544. /* script */
  545. const __vue_script__ = script;
  546. /* template */
  547. var __vue_render__ = function() {
  548. var _vm = this;
  549. var _h = _vm.$createElement;
  550. var _c = _vm._self._c || _h;
  551. return _c(
  552. "div",
  553. { attrs: { id: _vm.idName }, on: { click: _vm.generate } },
  554. [_vm._t("default", [_vm._v(" Download " + _vm._s(_vm.name) + " ")])],
  555. 2
  556. )
  557. };
  558. var __vue_staticRenderFns__ = [];
  559. __vue_render__._withStripped = true;
  560. /* style */
  561. const __vue_inject_styles__ = undefined;
  562. /* scoped */
  563. const __vue_scope_id__ = undefined;
  564. /* module identifier */
  565. const __vue_module_identifier__ = undefined;
  566. /* functional template */
  567. const __vue_is_functional_template__ = false;
  568. /* style inject */
  569. /* style inject SSR */
  570. var JsonExcel = normalizeComponent_1(
  571. { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
  572. __vue_inject_styles__,
  573. __vue_script__,
  574. __vue_scope_id__,
  575. __vue_is_functional_template__,
  576. __vue_module_identifier__,
  577. undefined,
  578. undefined
  579. );
  580. return JsonExcel;
  581. })));