| @@ -272,44 +272,60 @@ | ||
| 272 | 272 | if (!$scope.find('.king-addons-table-export-button-cont').length) return; |
| 273 | 273 | |
| 274 | 274 | const exportBtn = $scope.find('.king-addons-table-export-button-cont .king-addons-button'); |
| 275 | 275 | |
| 276 | - const htmlToCSV = (filename, $view) => { | |
| 276 | + const csvEscape = (value) => { | |
| 277 | + const text = String(value == null ? '' : value).replace(/\r?\n/g, ' ').trim(); | |
| 278 | + if (/[",]/.test(text)) { | |
| 279 | + return '"' + text.replace(/"/g, '""') + '"'; | |
| 280 | + } | |
| 281 | + return text; | |
| 282 | + }; | |
| 283 | + | |
| 284 | + const tableRowsToCSV = ($view) => { | |
| 277 | 285 | const rows = $view.find(".king-addons-table-row"); |
| 278 | 286 | const data = []; |
| 279 | 287 | rows.each((_, row) => { |
| 280 | 288 | const cols = row.querySelectorAll(".king-addons-table-text"); |
| 281 | - const rowData = Array.from(cols).map(cell => cell.innerText); | |
| 289 | + const rowData = Array.from(cols).map(cell => csvEscape(cell.innerText)); | |
| 282 | 290 | data.push(rowData.join(",")); |
| 283 | 291 | }); |
| 284 | - downloadCSVFile(data.join("\n"), filename); | |
| 292 | + return data.join("\r\n"); | |
| 285 | 293 | }; |
| 286 | 294 | |
| 287 | - const downloadCSVFile = (csv, filename) => { | |
| 288 | - const csvFile = new Blob([csv], { type: "text/csv" }); | |
| 295 | + const triggerDownload = (blob, filename) => { | |
| 296 | + const url = URL.createObjectURL(blob); | |
| 289 | 297 | const downloadLink = document.createElement("a"); |
| 290 | 298 | downloadLink.download = filename; |
| 291 | - downloadLink.href = window.URL.createObjectURL(csvFile); | |
| 292 | - downloadLink.style.display = "none"; | |
| 299 | + downloadLink.href = url; | |
| 300 | + downloadLink.rel = "noopener"; | |
| 301 | + downloadLink.style.position = "fixed"; | |
| 302 | + downloadLink.style.left = "-9999px"; | |
| 293 | 303 | document.body.appendChild(downloadLink); |
| 294 | 304 | downloadLink.click(); |
| 305 | + setTimeout(() => { | |
| 306 | + URL.revokeObjectURL(url); | |
| 307 | + downloadLink.remove(); | |
| 308 | + }, 1500); | |
| 295 | 309 | }; |
| 296 | 310 | |
| 297 | 311 | exportBtn.each(function () { |
| 298 | 312 | const $btn = $(this); |
| 299 | 313 | if ($btn.hasClass('king-addons-xls')) { |
| 300 | - // XLS export | |
| 301 | - $btn.on('click', () => { | |
| 302 | - const $table = $scope.find('table'); | |
| 303 | - TableToExcel.convert($table[0], { | |
| 304 | - name: 'export.xlsx', | |
| 305 | - sheet: { name: 'Sheet 1' }, | |
| 306 | - }); | |
| 314 | + $btn.on('click', (event) => { | |
| 315 | + event.preventDefault(); | |
| 316 | + const table = $scope.find('table').get(0); | |
| 317 | + if (!table) { | |
| 318 | + return; | |
| 319 | + } | |
| 320 | + const html = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel"><head><meta charset="UTF-8"></head><body>' + table.outerHTML + '</body></html>'; | |
| 321 | + triggerDownload(new Blob(['\uFEFF' + html], { type: 'application/vnd.ms-excel' }), 'export.xls'); | |
| 307 | 322 | }); |
| 308 | 323 | } else if ($btn.hasClass('king-addons-csv')) { |
| 309 | - // CSV export | |
| 310 | - $btn.on('click', () => { | |
| 311 | - htmlToCSV("placeholder.csv", $scope.find('.king-addons-data-table')); | |
| 324 | + $btn.on('click', (event) => { | |
| 325 | + event.preventDefault(); | |
| 326 | + const csv = tableRowsToCSV($scope.find('.king-addons-data-table')); | |
| 327 | + triggerDownload(new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }), 'export.csv'); | |
| 312 | 328 | }); |
| 313 | 329 | } |
| 314 | 330 | }); |
| 315 | 331 | }; |