UNPKG

free-jqgrid-fork

Version:

maintained version of free-jqGrid

758 lines (658 loc) 23.4 kB
/** * GPL licenses * Module to Download CSV or Excel files from Jqgrid * @module Jqgriddownload */ /** @alias module: Jqgriddownload @param {string} - format: csv or xlsx @param {string} - separator, default ',' (optional) @param {string} - endline, default '\n' (optional) @param {object} - Grid object (optional) @param {array} - Grid data object (optional) @param {bolan} - Quoted Output for text (optional), default none @example jQuery("#grid").jqGrid( { colModel: col_model, data: data, loadonce: true, } }).jqGrid("filterToolbar", { searchOnEnter: true, enableClear: false }).jqGrid("navButtonAdd", "#grid_toppager", { caption: "Download CSV", id: "download", buttonicon: "fa-file-excel-o", onClickButton: function(err, res) { jQuery(this).jqGrid("jqgrid_download", 'csv'); // for quouted output // jQuery(this).jqGrid("jqgrid_download", 'csv',',','\n',false,[],true); } }).jqGrid("navButtonAdd", "#grid_toppager", { caption: "Excel", id: "Excel", buttonicon: "fa-file-excel-o", onClickButton: function(err, res) { jQuery(this).jqGrid("jqgrid_download", 'xlsx'); } }); */ (function (global, factory) { "use strict"; if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define([ "jquery", "./jquery.contextmenu-ui", "free-jqgrid/grid.base" ], function ($) { return factory($, global, global.document); }); } else if (typeof module === "object" && module.exports) { // Node/CommonJS module.exports = function (root, $) { if ($ === undefined) { // require("jquery") returns a factory that requires window to // build a jQuery instance, we normalize how we use modules // that require this pattern but the window provided is a noop // if it's defined (how jquery works) $ = typeof window !== "undefined" ? require("jquery") : require("jquery")(root || window); } require("./jquery.contextmenu-ui"); require("free-jqgrid/grid.base"); factory($, root, root.document); return $; }; } else { // Browser globals factory(jQuery, global, global.document); } }(typeof window !== "undefined" ? window : this, function ($, window, document){ "use strict"; $.jgrid.extend({ jqgrid_download: function (format = 'csv', separator = ',', endline = '\n', obj = false, _data=[],_quoted_output=false) { format = format.toLowerCase(); switch(format) { case 'csv': this._csv(separator, endline, obj, _data, _quoted_output); break; case 'xlsx': this._xlsx(separator, endline, obj, _data); break; default: alert('Format ' + format + ' is not yet supported \n Please use: csv, xlsx formats'); } }, _csv: function(separator, endline, obj, data, quoted_output) { if(data.length === 0) { data = jQuery(this).jqGrid("getGridParam", "lastSelectedData"); } let col = jQuery(this).jqGrid("getGridParam", "colModel"); let caption = jQuery(this).jqGrid("getGridParam", "caption").replace(/[^a-zA-Z0-9]/g, ""); let format = {}; let formatoptions = {}; let formatoptions_default = ''; if (caption === "") { caption = 'jqgriddownload'; } let rows = []; let header = []; for(let c in col) { if(col[c]['name'] != 'rn' && col[c]['name'] != 'cb' && col[c]['name'] != 'subgrid') { let name = col[c]['name']; name = '"' + col[c]['label'] + '"'; if(col[c]['label']) { if(col[c]['hidden'] != true) { header.push(name); } } //console.log(col[c]['download_formatter']); format[col[c]['name']]= {'formatter': col[c]['download_formatter']}; } if(col[c]['formatoptions']) { if(col[c]['formatoptions']['defaultValue']) { formatoptions_default = col[c]['formatoptions']['defaultValue']; } if(col[c]['formatoptions']['value']) { const s = col[c]['formatoptions']['value']; const a = s.split(";"); for(let i in a) { const a2 =a[i].split(":"); if(a2.length === 2) { formatoptions[a2[0]] = a2[1]; } } } } } //console.log(rows); //console.log(header); rows.push(header); for(let d in data) { let row = []; for(let c in col) { if(col[c]['name'] != 'rn' && col[c]['name'] != 'cb' && col[c]['name'] != 'subgrid' && col[c]['hidden'] != true) { let name = col[c]['name']; let col_val = data[d][name]; if(col[c]['formatter']) { if(col[c]['formatter'] == 'number') { if(col[c]['formatoptions']) { if(col[c]['formatoptions']['decimalPlaces']) { let no = col[c]['formatoptions']['decimalPlaces'] if(is_int(no)) { col_val = col_val.toFixed(no); } } } } } if(formatoptions && col[c]['formatoptions']) { formatoptions.hasOwnProperty(col_val) { if(formatoptions[col_val]) { col_val = formatoptions[col_val]; } } } if (typeof col_val === 'string' || col_val instanceof String) // check if a string has a comma { if(col_val.indexOf(',') !== -1) { col_val = '"' + col_val + '"'; } } if(format[name]) { if(format[name]['formatter']) { //console.log(format[name]['formatter']); if (typeof format[name]['formatter'] == 'function') { let fn = format[name]['formatter']; col_val = fn(col_val); } if (typeof format[name]['formatter'] == 'string') { let s = format[name]['formatter']; //console.log(s); //console.log(col_val); //check if its a placeholder function to eval if(s.indexOf('\{0\}') !== -1) { let fs = s.replace(/\{0\}/g, '"'+col_val+'"'); col_val = eval(fs); } else if(s === "date_formatter") { col_val = date_formatter(col_val); } else if(s.indexOf("date_formatter--") > -1) { const a = s.split('--'); col_val = date_formatter(col_val,a[1]); //console.log(a[1]); //console.log(col_val); } else { //check if its really a function to eval if(s.indexOf('(') > -1) { let fun = eval(s); col_val = fun(col_val); } } } } } if(quoted_output) { if(col_val.indexOf(' ') >= 0) { col_val = '"' + col_val + '"'; } } row.push(col_val); } } rows.push(row); } let csv = rows.map(e => e.join(separator)).join(endline); //console.log(csv); let blob = new Blob([csv],{type: 'text/csv;charset=utf-8;'}); let uri = URL.createObjectURL(blob); let link = document.createElement("a"); link.download = caption + '.csv'; link.href = uri; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, _xlsx: async function(separator, endline, obj, data) { self = this; self.wb = new ExcelJS.Workbook(); self.pictures = []; if(data.length === 0) { data = jQuery(this).jqGrid("getGridParam", "lastSelectedData"); } let col = jQuery(this).jqGrid("getGridParam", "colModel"); let rows = []; let header = {}; let labels = {}; let format = {}; let ws_data = []; let caption = jQuery(this).jqGrid("getGridParam", "caption").replace(/[^a-zA-Z0-9]/g, ""); let logger = jQuery(this).jqGrid("getGridParam", "logger"); let log = new Log2textarea("jqgriddownload"); if(logger != null) { log = new Log2textarea(logger); } log.info("...init log"); log.info("...download as excel"); if (caption === "") { caption = 'jqgriddownload'; } /* Set the first header row */ for(let i in col) { if(["rn","cb","name"].indexOf(col[i]['name']) === -1) { let label = col[i]['name']; if(col[i]['label']) { label = col[i]['label']; } header[col[i]['name']] = label; labels[col[i]['label']] = col[i]['name']; format[col[i]['name']] = { 'width': col[i]['width'], 'formatter': col[i]['download_formatter'], 'styler' : col[i]['download_styler'], 'picture': col[i]['picture'], 'height': col[i]['height'] }; } } let _headers = []; for(let i in header) { if(['rn','subgrid'].indexOf(i) < 0 ) { _headers.push(i); } } self.ws = self.wb.addWorksheet('Download'); self.ws.properties.defaultRowWidth = 300; self.ws.addRow(_headers); let styler = []; /* Set the data to the rows */ for(let i in data) { //ws.addRow(data[i]); let row = []; for(let ii in _headers) { let key = _headers[ii] ; if(data[i].hasOwnProperty(key)) { let val = data[i][key]; let row_id = self.ws.lastRow._number; let col_id = Number(ii); let col_letter = this.num_to_letter(col_id); let cell = col_letter + '' + row_id + ':' + col_letter + '' + row_id; if(typeof format[key]['formatter'] == 'function' && val ) { let fn = format[key]['formatter']; val = fn(val); } if(format[key]['picture']) { let pic_width = format[key]['width']; let pic_height = format[key]['height']; self.pictures.push([cell, val, row_id, col_id, pic_width, pic_height, col_letter ]); val = ''; } else { if(format[key]['styler']) { let row_id2 = row_id+1 ; //increate the row to 1 for the styling let cell2 = col_letter + '' + row_id2 + ':' + col_letter + '' + row_id; styler.push([cell2, val, format[key]['styler']]); } } row.push(val); } } //break; self.ws.addRow(row); } await this.set_styles(styler); autofitColumns(self.ws); await this.add_img(log); await log.info("...render excel and download"); await this.download_excel(caption); await log.info("...completed"); }, add_img: async function(log) { for(let i =0;i < self.pictures.length;i++) { const cell = self.pictures[i][0]; const url = self.pictures[i][1]; const row_id = self.pictures[i][2]; const col_id = self.pictures[i][3]; const pic_width = self.pictures[i][4]; const pic_height = self.pictures[i][5]; const col_name = self.pictures[i][6]; await log.info("...get " + url); const img_base = await this.get_img_base(url); const image_id = self.wb.addImage({ base64: img_base, extension: 'png' }); self.ws.addImage(image_id, { tl: { col: col_id, row: row_id }, ext: { width: pic_width, height: pic_height } //hyperlinks: { //hyperlink: url, //tooltip: 'Click to get the Picture Source' //} }); self.ws.getRow(row_id+1).height = pic_height; let xcol = self.ws.getColumn(col_name); xcol.width = pic_width/4; } }, set_styles: async function(styler) { for(let i=0;i< styler.length;i++) { const cell = styler[i][0]; const val = styler[i][1]; const f = styler[i][2]; if(typeof f === 'function') { self.ws.getCell(cell).fill = f(val); } } }, get_img_base: async function(url) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("GET", url,true); xhr.responseType = 'blob'; xhr.onload = function() { var reader = new FileReader(); reader.onloadend = function() { return resolve(reader.result); } reader.readAsDataURL(xhr.response); }; xhr.onerror = function() { return reject(xhr.statusText); }; xhr.send(); }); }, to_data_url: async function (url, callback) { var xhr = new XMLHttpRequest(); xhr.onload = function() { var reader = new FileReader(); reader.onloadend = function() { callback(reader.result); } reader.readAsDataURL(xhr.response); }; xhr.open('GET', url); xhr.responseType = 'blob'; xhr.send(); }, download_excel: async function(caption) { let buf = await self.wb.xlsx.writeBuffer(); let blob = new Blob([buf]); let uri = URL.createObjectURL(blob); let link = document.createElement("a"); link.download = caption + '.xlsx'; link.href = uri; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, today_date: function() { let today = new Date(); let dd = String(today.getDate()).padStart(2, '0'); let mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0! let yyyy = today.getFullYear(); today = mm + '/' + dd + '/' + yyyy; return today; }, num_to_letter: function(n) { let ordA = 'a'.charCodeAt(0); let ordZ = 'z'.charCodeAt(0); let len = ordZ - ordA + 1; let s = ""; while(n >= 0) { s = String.fromCharCode(n % len + ordA) + s; n = Math.floor(n / len) - 1; } s = s.toUpperCase(); return s; }, letter_to_number: function(string) { string = string.toUpperCase(); let letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; let sum = 0, i; for (i = 0; i < string.length; i++) { sum += Math.pow(letters.length, i) * (letters.indexOf(string.substr(((i + 1) * -1), 1)) + 1); } return sum; } }); })); /** @param {string} -- check if integer */ function is_int(value) { return !isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10)); } /** @param {string} - format: csv or xlsx */ function eachColumnInRange(ws, col1, col2, cb){ for(let c = col1; c <= col2; c++){ let col = ws.getColumn(c); cb(col); } } // which is the number of default font's characters which fit into a cell. // default font is Arial 10, which is used here // observation: (column width) ~= (column pixel width)/7 // (column width) = ((exceljs width) - 0.71) when (exceljs width) is an integer // bolding increases width by ~5-9% function autofitColumns(ws){ // no good way to get text widths eachColumnInRange(ws, 1, ws.columnCount, column => { let maxWidth=10; column.eachCell( cell => { if( !cell.isMerged && cell.value ){ // doesn't handle merged cells let text = ""; if( typeof cell.value != "object" ){ // string, number, ... text = cell.value.toString(); } else if( cell.value.richText ){ // richText text = cell.value.richText.reduce((text, obj)=>text+obj.text.toString(),""); } // handle new lines -> don't forget to set wrapText: true let values = text.split(/[\n\r]+/); for( let value of values ){ let width = value.length; if(cell.font && cell.font.bold){ width *= 1.08; // bolding increases width } maxWidth = Math.max(maxWidth, width); } } }); maxWidth += 0.71; // compensate for observed reduction maxWidth += 1; // buffer space column.width = maxWidth; }); } // maps between arbitrary columns more generally represent a cyclic graph. // To propagate each max value properly, must explore entirity of each subgraph. // sheetMap: [[ sheetId1, sheetId2, [[columnId1, columnId2],...] ],...] function linkColumnWidths(workbook, sheetMap){ let graph = {}; let sheetCols = sheetMap.reduce((sheetCols, colMap) => sheetCols.concat( colMap[2].map( edge => [colMap[0],edge[0]] ), colMap[2].map( edge => [colMap[1],edge[1]] ) ), []); // [[sheetIdN, columnIdN], ...] for( let [sheetId, columnId] of sheetCols ){ let sheet = workbook.getWorksheet(sheetId); let column = sheet.getColumn(columnId); graph[`${sheetId}-${columnId}`] = { sheetId: sheetId, columnId: columnId, width: column.width, edges: {} }; } for( let [sheetId1, sheetId2, colMap] of sheetMap ){ for( let [columnId1, columnId2] of colMap ){ let key1 = `${sheetId1}-${columnId1}`; let key2 = `${sheetId2}-${columnId2}`; graph[key1].edges[key2] = graph[key2]; graph[key2].edges[key1] = graph[key1]; } } let dfs = function(node, unvisited, visited){ unvisited.delete(node); visited.add(node); return Math.max( node.width, ...Object.values(node.edges) .filter( edge => unvisited.has(edge) ) .map( edge => dfs(edge, unvisited, visited) ) ); } let unvisited = new Set( Object.values(graph) ); while( unvisited.size > 0 ){ let visited = new Set(); let initialNode = unvisited.values().next().value; let maxWidth = dfs(initialNode, unvisited, visited); for( let node of visited ){ node.width = maxWidth; } } for( let node of Object.values(graph) ){ let sheet = workbook.getWorksheet(node.sheetId); let column = sheet.getColumn(node.columnId); column.width = node.width; } } /** @param {string} - cel_value as a string, for the moment only iso 112 20230602 is supported @param {string} - format, style like 'dd/mm/yyy' @return {string} = string like '02/06/2023' @example: your col_model : .... {'name': 'order_date', 'download_formatter': 'date_formatter--dd/mm/yyyy'}, .... */ function date_formatter(cell_value, format = 'dd/mm/yyyy') { if (cell_value) { cell_value = cell_value.toString().trim(); if (cell_value.length == 8 ) { const year = parseInt(cell_value.substr(0, 4)); const month = parseInt(cell_value.substr(4, 2))-1; const day = parseInt(cell_value.substr(6, 2)); const _date=new Date(year,month,day); const format_date = date_to_string(_date, format); return format_date; } else { return cell_value == 0 ? '' : cell_value; } } else { return ''; } } /** @param {object} - date object @param {string} - format, style like 'dd/mm/yyy' @return {string} = string like '02/06/2023' @example: let d = new Date(); let x = date_to_string(d,'dd/mm/yyyy'); console.log(x); */ function date_to_string(date, y) { const z = { yyyy:date.getFullYear(), mm: date.getMonth() + 1, dd: date.getDate(), h: date.getHours(), m: date.getMinutes(), s: date.getSeconds() }; y = y.replace(/(dd|hh|mm|ss|yyyy)/g, function(v) { let value = (z[v] < 10) ? "0" + z[v] : z[v]; return value; }); return y; }