otsdaq_utilities  v2_05_02_indev
download.js
1 var Download = function(table){
2  this.table = table; //hold Tabulator object
3  this.fields = {}; //hold filed multi dimension arrays
4  this.columnsByIndex = []; //hold columns in their order in the table
5  this.columnsByField = {}; //hold columns with lookup by field name
6  this.config = {};
7  this.active = false;
8 };
9 
10 //trigger file download
11 Download.prototype.download = function(type, filename, options, active, interceptCallback){
12  var self = this,
13  downloadFunc = false;
14  this.processConfig();
15  this.active = active;
16 
17  function buildLink(data, mime){
18  if(interceptCallback){
19  if(interceptCallback === true){
20  self.triggerDownload(data, mime, type, filename, true);
21  }else{
22  interceptCallback(data);
23  }
24 
25  }else{
26  self.triggerDownload(data, mime, type, filename);
27  }
28  }
29 
30  if(typeof type == "function"){
31  downloadFunc = type;
32  }else{
33  if(self.downloaders[type]){
34  downloadFunc = self.downloaders[type];
35  }else{
36  console.warn("Download Error - No such download type found: ", type);
37  }
38  }
39 
40  this.processColumns();
41 
42  if(downloadFunc){
43  downloadFunc.call(this, self.processDefinitions(), self.processData(active || "active") , options || {}, buildLink, this.config);
44  }
45 };
46 
47 Download.prototype.processConfig = function(){
48  var config = { //download config
49  columnGroups:true,
50  rowGroups:true,
51  columnCalcs:true,
52  };
53 
54  if(this.table.options.downloadConfig){
55  for(var key in this.table.options.downloadConfig){
56  config[key] = this.table.options.downloadConfig[key];
57  }
58  }
59 
60  if (config.rowGroups && this.table.options.groupBy && this.table.modExists("groupRows")){
61  this.config.rowGroups = true;
62  }
63 
64  if (config.columnGroups && this.table.columnManager.columns.length != this.table.columnManager.columnsByIndex.length){
65  this.config.columnGroups = true;
66  }
67 
68  if (config.columnCalcs && this.table.modExists("columnCalcs")){
69  this.config.columnCalcs = true;
70  }
71 };
72 
73 Download.prototype.processColumns = function () {
74  var self = this;
75 
76  self.columnsByIndex = [];
77  self.columnsByField = {};
78 
79  self.table.columnManager.columnsByIndex.forEach(function (column) {
80 
81  if (column.field && column.definition.download !== false && (column.visible || (!column.visible && column.definition.download))) {
82  self.columnsByIndex.push(column);
83  self.columnsByField[column.field] = column;
84  }
85  });
86 };
87 
88 Download.prototype.processDefinitions = function(){
89  var self = this,
90  processedDefinitions = [];
91 
92  if(this.config.columnGroups){
93  self.table.columnManager.columns.forEach(function(column){
94  var colData = self.processColumnGroup(column);
95 
96  if(colData){
97  processedDefinitions.push(colData);
98  }
99  });
100  }else{
101  self.columnsByIndex.forEach(function(column){
102  if(column.download !== false){
103  //isolate definiton from defintion object
104  processedDefinitions.push(self.processDefinition(column));
105  }
106  });
107  }
108 
109  return processedDefinitions;
110 };
111 
112 Download.prototype.processColumnGroup = function(column){
113  var subGroups = column.columns,
114  maxDepth = 0;
115  var processedColumn = this.processDefinition(column);
116  var groupData = {
117  type:"group",
118  title:processedColumn.title,
119  depth:1,
120  };
121 
122 
123  if(subGroups.length){
124  groupData.subGroups = [];
125  groupData.width = 0;
126 
127  subGroups.forEach((subGroup) => {
128  var subGroupData = this.processColumnGroup(subGroup);
129 
130  if(subGroupData.depth > maxDepth){
131  maxDepth = subGroupData.depth;
132  }
133 
134  if(subGroupData){
135  groupData.width += subGroupData.width;
136  groupData.subGroups.push(subGroupData);
137  }
138  });
139 
140  groupData.depth += maxDepth;
141 
142  if(!groupData.width){
143  return false;
144  }
145  }else{
146  if(column.field && column.definition.download !== false && (column.visible || (!column.visible && column.definition.download))){
147  groupData.width = 1;
148  groupData.definition = processedColumn;
149  }else{
150  return false;
151  }
152  }
153 
154  return groupData;
155 };
156 
157 Download.prototype.processDefinition = function(column){
158  var def = {};
159 
160  for(var key in column.definition){
161  def[key] = column.definition[key];
162  }
163 
164  if(typeof column.definition.downloadTitle != "undefined"){
165  def.title = column.definition.downloadTitle;
166  }
167 
168  return def;
169 };
170 
171 Download.prototype.processData = function(active){
172  var self = this,
173  data = [],
174  groups = [],
175  rows = false,
176  calcs = {};
177 
178  if(this.config.rowGroups){
179 
180  if(active == "visible"){
181 
182  rows = self.table.rowManager.getRows(active);
183 
184  rows.forEach((row) => {
185  if(row.type == "row"){
186  var group = row.getGroup();
187 
188  if(groups.indexOf(group) === -1){
189  groups.push(group);
190  }
191  }
192  });
193  }else{
194  groups = this.table.modules.groupRows.getGroups();
195  }
196 
197  groups.forEach((group) => {
198  data.push(this.processGroupData(group, rows));
199  });
200 
201  }else{
202  data = self.table.rowManager.getData(active, "download");
203  }
204 
205 
206  if(this.config.columnCalcs){
207  calcs = this.table.getCalcResults();
208 
209  data = {
210  calcs: calcs,
211  data: data,
212  };
213  }
214 
215  //bulk data processing
216  if(typeof self.table.options.downloadDataFormatter == "function"){
217  data = self.table.options.downloadDataFormatter(data);
218  }
219 
220  return data;
221 };
222 
223 
224 Download.prototype.processGroupData = function(group, visRows){
225  var subGroups = group.getSubGroups();
226 
227  var groupData = {
228  type:"group",
229  key:group.key
230  };
231 
232  if(subGroups.length){
233  groupData.subGroups = [];
234 
235  subGroups.forEach((subGroup) => {
236  groupData.subGroups.push(this.processGroupData(subGroup, visRows));
237  });
238  }else{
239  if(visRows){
240  groupData.rows = [];
241 
242  group.rows.forEach(function(row){
243  if(visRows.indexOf(row) > -1){
244  groupData.rows.push(row.getData("download"));
245  }
246  });
247  }else{
248  groupData.rows = group.getData(true, "download");
249  }
250 
251  }
252 
253  return groupData;
254 };
255 
256 Download.prototype.triggerDownload = function(data, mime, type, filename, newTab){
257  var element = document.createElement('a'),
258  blob = new Blob([data],{type:mime}),
259  filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type);
260 
261  blob = this.table.options.downloadReady.call(this.table, data, blob);
262 
263  if(blob){
264 
265  if(newTab){
266  window.open(window.URL.createObjectURL(blob));
267  }else{
268  if(navigator.msSaveOrOpenBlob){
269  navigator.msSaveOrOpenBlob(blob, filename);
270  }else{
271  element.setAttribute('href', window.URL.createObjectURL(blob));
272 
273  //set file title
274  element.setAttribute('download', filename);
275 
276  //trigger download
277  element.style.display = 'none';
278  document.body.appendChild(element);
279  element.click();
280 
281  //remove temporary link element
282  document.body.removeChild(element);
283  }
284  }
285 
286 
287  if(this.table.options.downloadComplete){
288  this.table.options.downloadComplete();
289  }
290  }
291 
292 };
293 
294 //nested field lookup
295 Download.prototype.getFieldValue = function(field, data){
296  var column = this.columnsByField[field];
297 
298  if(column){
299  return column.getFieldValue(data);
300  }
301 
302  return false;
303 };
304 
305 
306 Download.prototype.commsReceived = function(table, action, data){
307  switch(action){
308  case "intercept":
309  this.download(data.type, "", data.options, data.active, data.intercept);
310  break;
311  }
312 };
313 
314 
315 //downloaders
316 Download.prototype.downloaders = {
317  csv:function(columns, data, options, setFileContents, config){
318  var self = this,
319  titles = [],
320  fields = [],
321  delimiter = options && options.delimiter ? options.delimiter : ",",
322  fileContents, output;
323 
324  //build column headers
325  function parseSimpleTitles(){
326  columns.forEach(function(column){
327  titles.push('"' + String(column.title).split('"').join('""') + '"');
328  fields.push(column.field);
329  });
330  }
331 
332  function parseColumnGroup(column, level){
333  if(column.subGroups){
334  column.subGroups.forEach(function(subGroup){
335  parseColumnGroup(subGroup, level+1);
336  });
337  }else{
338  titles.push('"' + String(column.title).split('"').join('""') + '"');
339  fields.push(column.definition.field);
340  }
341  }
342 
343  if(config.columnGroups){
344  console.warn("Download Warning - CSV downloader cannot process column groups");
345 
346  columns.forEach(function(column){
347  parseColumnGroup(column,0);
348  });
349  }else{
350  parseSimpleTitles();
351  }
352 
353 
354  //generate header row
355  fileContents = [titles.join(delimiter)];
356 
357  function parseRows(data){
358  //generate each row of the table
359  data.forEach(function(row){
360  var rowData = [];
361 
362  fields.forEach(function(field){
363  var value = self.getFieldValue(field, row);
364 
365  switch(typeof value){
366  case "object":
367  value = JSON.stringify(value);
368  break;
369 
370  case "undefined":
371  case "null":
372  value = "";
373  break;
374 
375  default:
376  value = value;
377  }
378 
379  //escape quotation marks
380  rowData.push('"' + String(value).split('"').join('""') + '"');
381  });
382 
383  fileContents.push(rowData.join(delimiter));
384  });
385  }
386 
387  function parseGroup(group){
388  if(group.subGroups){
389  group.subGroups.forEach(function(subGroup){
390  parseGroup(subGroup);
391  });
392  }else{
393  parseRows(group.rows);
394  }
395  }
396 
397  if(config.columnCalcs){
398  console.warn("Download Warning - CSV downloader cannot process column calculations");
399  data = data.data;
400  }
401 
402  if(config.rowGroups){
403  console.warn("Download Warning - CSV downloader cannot process row groups");
404 
405  data.forEach(function(group){
406  parseGroup(group);
407  });
408  }else{
409  parseRows(data);
410  }
411 
412  output = fileContents.join("\n");
413 
414  if(options.bom){
415  output = "\ufeff" + output;
416  }
417 
418  setFileContents(output, "text/csv");
419  },
420 
421  json:function(columns, data, options, setFileContents, config){
422  var fileContents;
423 
424  if(config.columnCalcs){
425  console.warn("Download Warning - CSV downloader cannot process column calculations");
426  data = data.data;
427  }
428 
429  fileContents = JSON.stringify(data, null, '\t');
430 
431  setFileContents(fileContents, "application/json");
432  },
433 
434  pdf:function(columns, data, options, setFileContents, config){
435  var self = this,
436  fields = [],
437  header = [],
438  body = [],
439  calcs = {},
440  headerDepth = 1,
441  table = "",
442  autoTableParams = {},
443  rowGroupStyles = options.rowGroupStyles || {
444  fontStyle: "bold",
445  fontSize: 12,
446  cellPadding: 6,
447  fillColor: 220,
448  },
449  rowCalcStyles = options.rowCalcStyles || {
450  fontStyle: "bold",
451  fontSize: 10,
452  cellPadding: 4,
453  fillColor: 232,
454  },
455  jsPDFParams = options.jsPDF || {},
456  title = options && options.title ? options.title : "";
457 
458  if(config.columnCalcs){
459  calcs = data.calcs;
460  data = data.data;
461  }
462 
463  if(!jsPDFParams.orientation){
464  jsPDFParams.orientation = options.orientation || "landscape";
465  }
466 
467  if(!jsPDFParams.unit){
468  jsPDFParams.unit = "pt";
469  }
470 
471  //build column headers
472  function parseSimpleTitles(){
473  columns.forEach(function(column){
474  if(column.field){
475  header.push(column.title || "");
476  fields.push(column.field);
477  }
478  });
479 
480  header = [header];
481  }
482 
483  function parseColumnGroup(column, level){
484  var colSpan = column.width,
485  rowSpan = 1,
486  col = {
487  content:column.title || "",
488  };
489 
490  if(column.subGroups){
491  column.subGroups.forEach(function(subGroup){
492  parseColumnGroup(subGroup, level+1);
493  });
494  rowSpan = 1;
495  }else{
496  fields.push(column.definition.field);
497  rowSpan = headerDepth - level;
498  }
499 
500  col.rowSpan = rowSpan;
501  // col.colSpan = colSpan;
502 
503  header[level].push(col);
504 
505  colSpan--;
506 
507  if(rowSpan > 1){
508  for(var i = level + 1; i < headerDepth; i++){
509  header[i].push("");
510  }
511  }
512 
513  for(var i = 0; i < colSpan; i++){
514  header[level].push("");
515  }
516  }
517 
518  if(config.columnGroups){
519  columns.forEach(function(column){
520  if(column.depth > headerDepth){
521  headerDepth = column.depth;
522  }
523  });
524 
525  for(var i=0; i < headerDepth; i++){
526  header.push([]);
527  }
528 
529  columns.forEach(function(column){
530  parseColumnGroup(column,0);
531  });
532 
533  }else{
534  parseSimpleTitles();
535  }
536 
537  function parseValue(value){
538  switch(typeof value){
539  case "object":
540  value = JSON.stringify(value);
541  break;
542 
543  case "undefined":
544  case "null":
545  value = "";
546  break;
547 
548  default:
549  value = value;
550  }
551 
552  return value;
553  }
554 
555  function parseRows(data){
556  //build table rows
557  data.forEach(function(row){
558  body.push(parseRow(row));
559  });
560  }
561 
562  function parseRow(row, styles){
563  var rowData = [];
564 
565  fields.forEach(function(field){
566  var value = self.getFieldValue(field, row);
567  value = parseValue(value);
568 
569  if(styles){
570  rowData.push({
571  content:value,
572  styles:styles,
573  });
574  }else{
575  rowData.push(value);
576  }
577  });
578 
579  return rowData;
580  }
581 
582  function parseGroup(group, calcObj){
583  var groupData = [];
584 
585  groupData.push({content:parseValue(group.key), colSpan:fields.length, styles:rowGroupStyles});
586 
587  body.push(groupData);
588 
589  if(group.subGroups){
590  group.subGroups.forEach(function(subGroup){
591  parseGroup(subGroup, calcObj[group.key] ? calcObj[group.key].groups || {} : {});
592  });
593  }else{
594 
595  if(config.columnCalcs){
596  addCalcRow(calcObj, group.key, "top");
597  }
598 
599  parseRows(group.rows);
600 
601  if(config.columnCalcs){
602  addCalcRow(calcObj, group.key, "bottom");
603  }
604  }
605  }
606 
607  function addCalcRow(calcs, selector, pos){
608  var calcData = calcs[selector];
609 
610  if(calcData){
611  if(pos){
612  calcData = calcData[pos];
613  }
614 
615  if(Object.keys(calcData).length){
616  body.push(parseRow(calcData, rowCalcStyles));
617  }
618  }
619  }
620 
621  if(config.rowGroups){
622  data.forEach(function(group){
623  parseGroup(group, calcs);
624  });
625  }else{
626  if(config.columnCalcs){
627  addCalcRow(calcs, "top");
628  }
629 
630  parseRows(data);
631 
632  if(config.columnCalcs){
633  addCalcRow(calcs, "bottom");
634  }
635  }
636 
637  var doc = new jsPDF(jsPDFParams); //set document to landscape, better for most tables
638 
639  if(options && options.autoTable){
640  if(typeof options.autoTable === "function"){
641  autoTableParams = options.autoTable(doc) || {};
642  }else{
643  autoTableParams = options.autoTable;
644  }
645  }
646 
647  if(title){
648  autoTableParams.addPageContent = function(data) {
649  doc.text(title, 40, 30);
650  };
651  }
652 
653  autoTableParams.head = header;
654  autoTableParams.body = body;
655 
656  doc.autoTable(autoTableParams);
657 
658  if(options && options.documentProcessing){
659  options.documentProcessing(doc);
660  }
661 
662  setFileContents(doc.output("arraybuffer"), "application/pdf");
663  },
664 
665  xlsx:function(columns, data, options, setFileContents, config){
666  var self = this,
667  sheetName = options.sheetName || "Sheet1",
668  workbook = XLSX.utils.book_new(),
669  calcs = {},
670  groupRowIndexs = [],
671  groupColumnIndexs = [],
672  calcRowIndexs = [],
673  output;
674 
675  workbook.SheetNames = [];
676  workbook.Sheets = {};
677 
678  if(config.columnCalcs){
679  calcs = data.calcs;
680  data = data.data;
681  }
682 
683  function generateSheet(){
684  var titles = [],
685  fields = [],
686  rows = [],
687  worksheet;
688 
689  //convert rows to worksheet
690  function rowsToSheet(){
691  var sheet = {};
692  var range = {s: {c:0, r:0}, e: {c:fields.length, r:rows.length }};
693 
694  XLSX.utils.sheet_add_aoa(sheet, rows);
695 
696  sheet['!ref'] = XLSX.utils.encode_range(range);
697 
698  var merges = generateMerges();
699 
700  if(merges.length){
701  sheet["!merges"] = merges;
702  }
703 
704  return sheet;
705  }
706 
707  function parseSimpleTitles(){
708  //get field lists
709  columns.forEach(function(column){
710  titles.push(column.title);
711  fields.push(column.field);
712  });
713 
714  rows.push(titles);
715  }
716 
717  function parseColumnGroup(column, level){
718 
719  if(typeof titles[level] === "undefined"){
720  titles[level] = [];
721  }
722 
723  if(typeof groupColumnIndexs[level] === "undefined"){
724  groupColumnIndexs[level] = [];
725  }
726 
727  if(column.width > 1){
728 
729  groupColumnIndexs[level].push({
730  type:"hoz",
731  start:titles[level].length,
732  end:titles[level].length + column.width - 1,
733  });
734  }
735 
736  titles[level].push(column.title);
737 
738  if(column.subGroups){
739  column.subGroups.forEach(function(subGroup){
740  parseColumnGroup(subGroup, level+1);
741  });
742  }else{
743  fields.push(column.definition.field);
744  padColumnTitles(fields.length - 1, level);
745 
746  groupColumnIndexs[level].push({
747  type:"vert",
748  start:fields.length - 1,
749  });
750 
751  }
752  }
753 
754 
755  function padColumnTitles(){
756  var max = 0;
757 
758  titles.forEach(function(title){
759  var len = title.length;
760  if(len > max){
761  max = len;
762  }
763  });
764 
765  titles.forEach(function(title){
766  var len = title.length;
767  if(len < max){
768  for(var i = len; i < max; i++){
769  title.push("");
770  }
771  }
772  });
773  }
774 
775  if(config.columnGroups){
776  columns.forEach(function(column){
777  parseColumnGroup(column,0);
778  });
779 
780  titles.forEach(function(title){
781  rows.push(title);
782  });
783  }else{
784  parseSimpleTitles();
785  }
786 
787  function generateMerges(){
788  var output = [];
789 
790  groupRowIndexs.forEach(function(index){
791  output.push({s:{r:index,c:0},e:{r:index,c:fields.length - 1}});
792  });
793 
794  groupColumnIndexs.forEach(function(merges, level){
795  merges.forEach(function(merge){
796  if(merge.type === "hoz"){
797  output.push({s:{r:level,c:merge.start},e:{r:level,c:merge.end}});
798  }else{
799  if(level != titles.length - 1){
800  output.push({s:{r:level,c:merge.start},e:{r:titles.length - 1,c:merge.start}});
801  }
802  }
803  });
804  });
805 
806  return output;
807  }
808 
809  //generate each row of the table
810  function parseRows(data){
811  data.forEach(function(row){
812  rows.push(parseRow(row));
813  });
814  }
815 
816  function parseRow(row){
817  var rowData = [];
818 
819  fields.forEach(function(field){
820  var value = self.getFieldValue(field, row);
821  rowData.push(!(value instanceof Date) && typeof value === "object" ? JSON.stringify(value) : value);
822  });
823 
824  return rowData;
825  }
826 
827 
828  function addCalcRow(calcs, selector, pos){
829  var calcData = calcs[selector];
830 
831  if(calcData){
832  if(pos){
833  calcData = calcData[pos];
834  }
835 
836  if(Object.keys(calcData).length){
837  calcRowIndexs.push(rows.length);
838  rows.push(parseRow(calcData));
839  }
840  }
841  }
842 
843  function parseGroup(group, calcObj){
844  var groupData = [];
845 
846  groupData.push(group.key);
847 
848  groupRowIndexs.push(rows.length);
849 
850  rows.push(groupData);
851 
852  if(group.subGroups){
853  group.subGroups.forEach(function(subGroup){
854  parseGroup(subGroup, calcObj[group.key] ? calcObj[group.key].groups || {} : {});
855  });
856  }else{
857 
858  if(config.columnCalcs){
859  addCalcRow(calcObj, group.key, "top");
860  }
861 
862  parseRows(group.rows);
863 
864  if(config.columnCalcs){
865  addCalcRow(calcObj, group.key, "bottom");
866  }
867  }
868 
869 
870  }
871 
872  if(config.rowGroups){
873  data.forEach(function(group){
874  parseGroup(group, calcs);
875  });
876  }else{
877  if(config.columnCalcs){
878  addCalcRow(calcs, "top");
879  }
880 
881  parseRows(data);
882 
883  if(config.columnCalcs){
884  addCalcRow(calcs, "bottom");
885  }
886  }
887 
888  worksheet = rowsToSheet();
889 
890  return worksheet;
891  }
892 
893  if(options.sheetOnly){
894  setFileContents(generateSheet());
895  return;
896  }
897 
898  if(options.sheets){
899  for(var sheet in options.sheets){
900 
901  if(options.sheets[sheet] === true){
902  workbook.SheetNames.push(sheet);
903  workbook.Sheets[sheet] = generateSheet();
904  }else{
905 
906  workbook.SheetNames.push(sheet);
907 
908  this.table.modules.comms.send(options.sheets[sheet], "download", "intercept",{
909  type:"xlsx",
910  options:{sheetOnly:true},
911  active:self.active,
912  intercept:function(data){
913  workbook.Sheets[sheet] = data;
914  }
915  });
916  }
917  }
918  }else{
919  workbook.SheetNames.push(sheetName);
920  workbook.Sheets[sheetName] = generateSheet();
921  }
922 
923  if(options.documentProcessing){
924  workbook = options.documentProcessing(workbook);
925  }
926 
927  //convert workbook to binary array
928  function s2ab(s) {
929  var buf = new ArrayBuffer(s.length);
930  var view = new Uint8Array(buf);
931  for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
932  return buf;
933  }
934 
935  output = XLSX.write(workbook, {bookType:'xlsx', bookSST:true, type: 'binary'});
936 
937  setFileContents(s2ab(output), "application/octet-stream");
938  },
939 
940  html:function(columns, data, options, setFileContents, config){
941  if(this.table.modExists("htmlTableExport", true)){
942  setFileContents(this.table.modules.htmlTableExport.getHtml(true, options.style, config), "text/html");
943  }
944  }
945 
946 };
947 
948 
949 Tabulator.prototype.registerModule("download", Download);