jsharmony 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AppSrv.Report.js CHANGED
@@ -36,12 +36,22 @@ exports.getReport = function (req, res, fullmodelid, Q, P, callback) {
36
36
  HelperFS.getFileStats(req, res, tmppath, function (err, stat) {
37
37
  if (err != null) return dispose();
38
38
  var fsize = stat.size;
39
- //Get MIME type
40
- res.writeHead(200, {
41
- 'Content-Type': 'application/pdf',
42
- 'Content-Length': stat.size,
43
- 'Content-Disposition': 'filename = ' + encodeURIComponent(fullmodelid + '.pdf')
44
- });
39
+ var model = _this.jsh.getModel(req, fullmodelid);
40
+ //Send MIME type
41
+ if(model.format=='xlsx'){
42
+ res.writeHead(200, {
43
+ 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
44
+ 'Content-Length': fsize,
45
+ 'Content-Disposition': 'filename = ' + encodeURIComponent(fullmodelid + '.xlsx')
46
+ });
47
+ }
48
+ else {
49
+ res.writeHead(200, {
50
+ 'Content-Type': 'application/pdf',
51
+ 'Content-Length': fsize,
52
+ 'Content-Disposition': 'filename = ' + encodeURIComponent(fullmodelid + '.pdf')
53
+ });
54
+ }
45
55
  var rs = fs.createReadStream(tmppath);
46
56
  rs.pipe(res).on('finish', function () { dispose(); });
47
57
  });
package/AppSrvRpt.js CHANGED
@@ -123,6 +123,7 @@ AppSrvRpt.prototype.batchReport = function (req, res, db, dbcontext, model, sql_
123
123
  var jsh = thisapp.jsh;
124
124
  var _this = this;
125
125
  if(!model.batch || !model.batch.sql) return errorHandler(-6, 'Batch reports require model.batch.sql');
126
+ if(model.format != 'pdf') return errorHandler(-6, 'Batch reports requires model.format "pdf"');
126
127
 
127
128
  //Parameters should already be validated
128
129
 
@@ -389,233 +390,385 @@ AppSrvRpt.prototype.genReportContent = function(req, res, fullmodelid, params, d
389
390
  return rslt;
390
391
  }
391
392
 
392
- AppSrvRpt.prototype.genReport = function (req, res, fullmodelid, params, data, done) {
393
+ AppSrvRpt.prototype.genReportPdf = function (req, res, fullmodelid, params, data, tmppath, callback) {
393
394
  var _this = this;
394
395
  var jsh = _this.AppSrv.jsh;
395
- var report_folder = jsh.Config.datadir + 'temp/report/';
396
396
  var model = jsh.getModel(req, fullmodelid);
397
397
  if(!model) throw new Error('Model '+fullmodelid+' not found');
398
398
  if (model.layout !== 'report') throw new Error('Model '+fullmodelid+' is not a report');
399
399
 
400
- HelperFS.createFolderIfNotExists(report_folder, function (err) {
401
- if (err) throw err;
402
- HelperFS.clearFiles(report_folder, jsh.Config.public_temp_expiration, -1, function () {
403
- tmp.file({ dir: report_folder }, function (tmperr, tmppath, tmpfd) {
404
- if (tmperr) throw tmperr;
405
- _this.getBrowser(function (err, browser) {
406
- var page = null;
407
-
408
- function genReportError(err, source){
409
- var rpterr = Helper.NewError("Error occurred during report generation"+(source ? ' - ' + source: '')+" (" + err.toString() + ')', -99999);
410
- if (page != null){
411
- return page.close()
412
- .then(function(){ return done(rpterr, null); })
413
- .catch(function (err) { jsh.Log.error(err); return done(rpterr, null); });
414
- }
415
- else return done(rpterr, null);
416
- }
400
+ _this.getBrowser(function (err, browser) {
401
+ var page = null;
417
402
 
418
- if(err) return genReportError(err, 'getBrowser');
419
-
420
- try {
421
- browser.newPage().then(function (_page) {
422
- var tmppdfpath = tmppath + '.pdf';
423
- var tmphtmlpath = tmppath + '.html';
424
- page = _page;
425
-
426
- var rptcontent = _this.genReportContent(req, res, fullmodelid, params, data);
427
-
428
- var pagesettings = {
429
- format: 'Letter',
430
- landscape: false,
431
- printBrackground: true,
432
- margin: {
433
- top: '1cm',
434
- bottom: '1cm',
435
- left: '1cm',
436
- right: '1cm',
437
- }
438
- };
439
- if ('pagesettings' in model){
440
- if(model.pagesettings.width && model.pagesettings.height) delete pagesettings.format;
441
- pagesettings = _.extend(pagesettings, model.pagesettings);
442
- }
443
- pagesettings.path = tmppdfpath;
444
-
445
- var dpi = 96;
446
- var default_header = '1cm';//Math.floor(0.4 * dpi) + 'px';
447
-
448
- var headerheight = 0;
449
- var footerheight = 0;
450
- if(rptcontent.header){
451
- pagesettings.displayHeaderFooter = true;
452
- pagesettings.headerTemplate = rptcontent.header;
453
- headerheight = default_header;
454
- if ('headerheight' in model) headerheight = model.headerheight;
403
+ function genReportError(err, source){
404
+ var rpterr = Helper.NewError("Error occurred during report generation"+(source ? ' - ' + source: '')+" (" + err.toString() + ')', -99999);
405
+ if (page != null){
406
+ return page.close()
407
+ .then(function(){ return callback(rpterr, null); })
408
+ .catch(function (err) { jsh.Log.error(err); return callback(rpterr, null); });
409
+ }
410
+ else return callback(rpterr, null);
411
+ }
412
+
413
+ if(err) return genReportError(err, 'getBrowser');
414
+
415
+ try {
416
+ browser.newPage().then(function (_page) {
417
+
418
+ var tmppdfpath = tmppath + '.pdf';
419
+ var tmphtmlpath = tmppath + '.html';
420
+ page = _page;
421
+ var pagesettings = {
422
+ format: 'Letter',
423
+ landscape: false,
424
+ printBrackground: true,
425
+ margin: {
426
+ top: '1cm',
427
+ bottom: '1cm',
428
+ left: '1cm',
429
+ right: '1cm',
430
+ }
431
+ };
432
+ var rptcontent = {};
433
+ var contentWidth = 0;
434
+ var contentHeight = 0;
435
+ var font_css = '';
436
+ var font_render = [];
437
+
438
+ var cancelRender = false;
439
+ async.waterfall([
440
+ //model.onrender
441
+ function(rpt_cb){
442
+ if(!model.onrender) return rpt_cb();
443
+ model.onrender({ workbook: workbook, data: data, params: params }, function(err, _cancelRender){
444
+ cancelRender = _cancelRender;
445
+ return rpt_cb(err);
446
+ }, require, jsh, fullmodelid);
447
+ },
448
+ //Initialize report
449
+ function(rpt_cb){
450
+ if(cancelRender) return rpt_cb();
451
+
452
+ rptcontent = _this.genReportContent(req, res, fullmodelid, params, data);
453
+
454
+ if ('pagesettings' in model){
455
+ if(model.pagesettings.width && model.pagesettings.height) delete pagesettings.format;
456
+ pagesettings = _.extend(pagesettings, model.pagesettings);
457
+ }
458
+ pagesettings.path = tmppdfpath;
459
+
460
+ var dpi = 96;
461
+ var default_header = '1cm';//Math.floor(0.4 * dpi) + 'px';
462
+
463
+ var headerheight = 0;
464
+ var footerheight = 0;
465
+ if(rptcontent.header){
466
+ pagesettings.displayHeaderFooter = true;
467
+ pagesettings.headerTemplate = rptcontent.header;
468
+ headerheight = default_header;
469
+ if ('headerheight' in model) headerheight = model.headerheight;
470
+ }
471
+ if(rptcontent.footer){
472
+ pagesettings.displayHeaderFooter = true;
473
+ pagesettings.footerTemplate = rptcontent.footer;
474
+ footerheight = default_header;
475
+ if ('footerheight' in model) footerheight = model.footerheight;
476
+ }
477
+ if(pagesettings.displayHeaderFooter) pagesettings.footerTemplate = pagesettings.footerTemplate || ' ';
478
+
479
+ //page.set('viewportSize',{width:700,height:800},function(){
480
+
481
+ //Calculate page width
482
+ var marginLeft = 0;
483
+ var marginRight = 0;
484
+ var marginTop = 0;
485
+ var marginBottom = 0;
486
+ var pageWidth = 1; //px
487
+ var pageHeight = 1; //px
488
+ var headerHeightPx = 0;
489
+ var footerHeightPx = 0;
490
+ if(pagesettings.margin){
491
+ var basemargin = pagesettings.margin;
492
+ if(!basemargin) basemargin = 0;
493
+ if(_.isString(basemargin)) basemargin = parseUnitsPx(basemargin,dpi);
494
+ if(_.isNumber(basemargin)){
495
+ marginLeft = basemargin;
496
+ marginRight = basemargin;
497
+ marginTop = basemargin;
498
+ marginBottom = basemargin;
455
499
  }
456
- if(rptcontent.footer){
457
- pagesettings.displayHeaderFooter = true;
458
- pagesettings.footerTemplate = rptcontent.footer;
459
- footerheight = default_header;
460
- if ('footerheight' in model) footerheight = model.footerheight;
500
+ else {
501
+ if(basemargin.left) marginLeft = parseUnitsPx(basemargin.left,dpi);
502
+ if(basemargin.right) marginRight = parseUnitsPx(basemargin.right,dpi);
503
+ if(basemargin.top) marginTop = parseUnitsPx(basemargin.top,dpi);
504
+ if(basemargin.bottom) marginBottom = parseUnitsPx(basemargin.bottom,dpi);
461
505
  }
462
- if(pagesettings.displayHeaderFooter) pagesettings.footerTemplate = pagesettings.footerTemplate || ' ';
463
-
464
- //page.set('viewportSize',{width:700,height:800},function(){
465
-
466
- //Calculate page width
467
- var marginLeft = 0;
468
- var marginRight = 0;
469
- var marginTop = 0;
470
- var marginBottom = 0;
471
- var pageWidth = 1; //px
472
- var pageHeight = 1; //px
473
- var headerHeightPx = 0;
474
- var footerHeightPx = 0;
475
- if(pagesettings.margin){
476
- var basemargin = pagesettings.margin;
477
- if(!basemargin) basemargin = 0;
478
- if(_.isString(basemargin)) basemargin = parseUnitsPx(basemargin,dpi);
479
- if(_.isNumber(basemargin)){
480
- marginLeft = basemargin;
481
- marginRight = basemargin;
482
- marginTop = basemargin;
483
- marginBottom = basemargin;
484
- }
485
- else {
486
- if(basemargin.left) marginLeft = parseUnitsPx(basemargin.left,dpi);
487
- if(basemargin.right) marginRight = parseUnitsPx(basemargin.right,dpi);
488
- if(basemargin.top) marginTop = parseUnitsPx(basemargin.top,dpi);
489
- if(basemargin.bottom) marginBottom = parseUnitsPx(basemargin.bottom,dpi);
490
- }
506
+ }
507
+ if(pagesettings.format){
508
+ var fmt = pagesettings.format.toLowerCase();
509
+ var w = 1;
510
+ var h = 1;
511
+ //Width and height in millimeters
512
+ if(fmt=='a0'){ w = 841; h=1189; }
513
+ if(fmt=='a1'){ w = 594; h=841; }
514
+ if(fmt=='a2'){ w = 420; h=594; }
515
+ if(fmt=='a3'){ w = 297; h=420; }
516
+ else if(fmt=='a4'){ w=210; h=297; }
517
+ else if(fmt=='a5'){ w=148; h=210; }
518
+ else if(fmt=='a6'){ w=105; h=148; }
519
+ else if(fmt=='a7'){ w=74; h=105; }
520
+ else if(fmt=='legal'){ w=215.9; h=355.6; }
521
+ else if(fmt=='letter'){ w=215.9; h=279.4; }
522
+ else if(fmt=='tabloid'){ w=279.4; h=431.8; }
523
+ else if(fmt=='ledger'){ w=279.4; h=431.8; }
524
+ else return jsh.Log.error('Invalid report format: '+pagesettings.format)
525
+ if(pagesettings.landscape){
526
+ _w = w;
527
+ w = h;
528
+ h = _w;
491
529
  }
492
- if(pagesettings.format){
493
- var fmt = pagesettings.format.toLowerCase();
494
- var w = 1;
495
- var h = 1;
496
- //Width and height in millimeters
497
- if(fmt=='a0'){ w = 841; h=1189; }
498
- if(fmt=='a1'){ w = 594; h=841; }
499
- if(fmt=='a2'){ w = 420; h=594; }
500
- if(fmt=='a3'){ w = 297; h=420; }
501
- else if(fmt=='a4'){ w=210; h=297; }
502
- else if(fmt=='a5'){ w=148; h=210; }
503
- else if(fmt=='a6'){ w=105; h=148; }
504
- else if(fmt=='a7'){ w=74; h=105; }
505
- else if(fmt=='legal'){ w=215.9; h=355.6; }
506
- else if(fmt=='letter'){ w=215.9; h=279.4; }
507
- else if(fmt=='tabloid'){ w=279.4; h=431.8; }
508
- else if(fmt=='ledger'){ w=279.4; h=431.8; }
509
- else return jsh.Log.error('Invalid report format: '+pagesettings.format)
510
- if(pagesettings.landscape){
511
- _w = w;
512
- w = h;
513
- h = _w;
514
- }
515
- //Width and height in pixels
516
- pageWidth = (w / (25.4)) * dpi;
517
- pageHeight = (h / (25.4)) * dpi;
530
+ //Width and height in pixels
531
+ pageWidth = (w / (25.4)) * dpi;
532
+ pageHeight = (h / (25.4)) * dpi;
533
+ }
534
+ if(pagesettings.width) pageWidth = parseUnitsPx(pagesettings.width,dpi);
535
+ if(pagesettings.height) pageHeight = parseUnitsPx(pagesettings.height,dpi);
536
+ if(headerheight) headerHeightPx = parseUnitsPx(headerheight,dpi);
537
+ if(footerheight) footerHeightPx = parseUnitsPx(footerheight,dpi);
538
+
539
+ contentWidth = pageWidth - marginLeft - marginRight;
540
+ contentHeight = pageHeight - marginTop - marginBottom - headerHeightPx - footerHeightPx;
541
+ if (jsh.Config.debug_params.report_debug) { jsh.Log.debug('Calculated Page Size: '+contentHeight + 'x'+contentWidth); }
542
+
543
+ pagesettings.margin = {
544
+ top: (marginTop+headerHeightPx)+'px',
545
+ right: marginRight+'px',
546
+ bottom: (marginBottom+footerHeightPx)+'px',
547
+ left: marginLeft+'px',
548
+ }
549
+ if(pagesettings.headerTemplate){
550
+ pagesettings.headerTemplate = '<style type="text/css">#header{ padding:'+Math.round(marginTop*_HEADER_ZOOM)+'px '+Math.round(marginRight*_HEADER_ZOOM)+'px '+Math.round(marginBottom*_HEADER_ZOOM)+'px '+Math.round(marginLeft*_HEADER_ZOOM)+'px; -webkit-print-color-adjust: exact; }</style><div style="position:absolute;width:'+(contentWidth)+'px;font-size:12px;transform: scale('+_HEADER_ZOOM+'); transform-origin: top left;">'+pagesettings.headerTemplate+'</div>';
551
+ }
552
+ if(pagesettings.footerTemplate){
553
+ pagesettings.footerTemplate = '<style type="text/css">#footer{ padding:'+Math.round(marginTop*_HEADER_ZOOM)+'px '+Math.round(marginRight*_HEADER_ZOOM)+'px '+Math.round(marginBottom*_HEADER_ZOOM)+'px '+Math.round(marginLeft*_HEADER_ZOOM)+'px; -webkit-print-color-adjust: exact; }</style><div style="position:absolute;width:'+(contentWidth)+'px;font-size:12px;transform: scale('+_HEADER_ZOOM+'); transform-origin: bottom left;">'+pagesettings.footerTemplate+'</div>';
554
+ }
555
+ return rpt_cb();
556
+ },
557
+ //Load fonts
558
+ function(rpt_cb){
559
+ if(cancelRender) return rpt_cb();
560
+ var report_fonts = [].concat(jsh.Config.default_report_fonts||[]).concat(model.fonts||[]);
561
+ jsh.loadFonts(report_fonts, function(err, _font_css){
562
+ if(err) return jsh.Log.error(err);
563
+ font_css = _font_css;
564
+ for(var i=0;i<report_fonts.length;i++){
565
+ var font = report_fonts[i];
566
+ var font_str = '';
567
+ if(font['font-family']) font_str += "font-family:'"+Helper.escapeCSS(font['font-family'].toString())+"';";
568
+ if(font['font-style']) font_str += "font-style:"+font['font-style'].toString()+";";
569
+ if(font['font-weight']) font_str += "font-weight:"+font['font-weight'].toString()+";";
570
+ if(font_str) font_render.push(font_str);
518
571
  }
519
- if(pagesettings.width) pageWidth = parseUnitsPx(pagesettings.width,dpi);
520
- if(pagesettings.height) pageHeight = parseUnitsPx(pagesettings.height,dpi);
521
- if(headerheight) headerHeightPx = parseUnitsPx(headerheight,dpi);
522
- if(footerheight) footerHeightPx = parseUnitsPx(footerheight,dpi);
523
-
524
- var contentWidth = pageWidth - marginLeft - marginRight;
525
- var contentHeight = pageHeight - marginTop - marginBottom - headerHeightPx - footerHeightPx;
526
- if (jsh.Config.debug_params.report_debug) { jsh.Log.debug('Calculated Page Size: '+contentHeight + 'x'+contentWidth); }
527
-
528
- pagesettings.margin = {
529
- top: (marginTop+headerHeightPx)+'px',
530
- right: marginRight+'px',
531
- bottom: (marginBottom+footerHeightPx)+'px',
532
- left: marginLeft+'px',
572
+ if(font_css){
573
+ if(pagesettings.headerTemplate) pagesettings.headerTemplate = '<style type="text/css">'+font_css+'</style>' + pagesettings.headerTemplate;
574
+ if(pagesettings.footerTemplate) pagesettings.footerTemplate = '<style type="text/css">'+font_css+'</style>' + pagesettings.footerTemplate;
533
575
  }
534
- if(pagesettings.headerTemplate){
535
- pagesettings.headerTemplate = '<style type="text/css">#header{ padding:'+Math.round(marginTop*_HEADER_ZOOM)+'px '+Math.round(marginRight*_HEADER_ZOOM)+'px '+Math.round(marginBottom*_HEADER_ZOOM)+'px '+Math.round(marginLeft*_HEADER_ZOOM)+'px; -webkit-print-color-adjust: exact; }</style><div style="position:absolute;width:'+(contentWidth)+'px;font-size:12px;transform: scale('+_HEADER_ZOOM+'); transform-origin: top left;">'+pagesettings.headerTemplate+'</div>';
576
+ return rpt_cb();
577
+ });
578
+ },
579
+ //model.onrendered
580
+ function(rpt_cb){
581
+ if(!model.onrendered) return rpt_cb();
582
+ model.onrendered({ page: page, content: rptcontent, data: data, params: params }, function(err){
583
+ if(err) return genReportError(err, 'onrendered');
584
+ return rpt_cb();
585
+ }, require, jsh, fullmodelid);
586
+ },
587
+ //Render
588
+ function(rpt_cb){
589
+ if(cancelRender) return rpt_cb();
590
+
591
+ //Sets styles and returns document width
592
+ var onPageLoad = function(font_render,font_css){
593
+ if(!document||!document.body) return 0;
594
+ //Load CSS in header
595
+ var head = document.getElementsByTagName('head');
596
+ if(head.length) head = head[0];
597
+ if(head){
598
+ var css = document.createElement('style');
599
+ css.type='text/css';
600
+ css.innerHTML = font_css;
601
+ head.appendChild(css);
536
602
  }
537
- if(pagesettings.footerTemplate){
538
- pagesettings.footerTemplate = '<style type="text/css">#footer{ padding:'+Math.round(marginTop*_HEADER_ZOOM)+'px '+Math.round(marginRight*_HEADER_ZOOM)+'px '+Math.round(marginBottom*_HEADER_ZOOM)+'px '+Math.round(marginLeft*_HEADER_ZOOM)+'px; -webkit-print-color-adjust: exact; }</style><div style="position:absolute;width:'+(contentWidth)+'px;font-size:12px;transform: scale('+_HEADER_ZOOM+'); transform-origin: bottom left;">'+pagesettings.footerTemplate+'</div>';
603
+ //Add fonts to body (otherwise using them in the page header / footer will cause a Page Crash)
604
+ if(font_render) for(var i=0;i<font_render.length;i++){
605
+ var fontElement = document.createElement('div');
606
+ fontElement.innerHTML = '&nbsp;';
607
+ fontElement.setAttribute('style', font_render[i]+'visibility:hidden;position:absolute;top:0px;left:0px;');
608
+ document.body.appendChild(fontElement);
539
609
  }
610
+ document.body.style['-webkit-print-color-adjust'] = 'exact';
611
+ return document.body.clientWidth;
612
+ };
613
+
614
+ fs.writeFile(tmphtmlpath, rptcontent.body||'','utf8',function(err){
615
+ if(err) return jsh.Log.error(err);
616
+ page.goto('file://'+tmphtmlpath, { timeout: jsh.Config.report_timeout, waitUntil: 'networkidle0' })
617
+ .then(function(){
618
+ page.evaluate(onPageLoad, font_render, font_css).then(function(documentWidth){
619
+ if(documentWidth && !pagesettings.scale){
620
+ var scale = contentWidth * 0.998 / documentWidth;
621
+ if(scale < 0.1) scale = 0.1;
622
+ if(scale > 1) scale = 1;
623
+ pagesettings.scale = scale;
624
+ }
625
+ return rpt_cb();
626
+ }).catch(function (err) { genReportError(err, 'onPageLoad'); });
627
+ }).catch(function (err) { genReportError(err, 'page.goto'); });
628
+ });
629
+ },
630
+ //Save to PDF
631
+ function(rpt_cb){
632
+ setTimeout(function(){
633
+ page.pdf(pagesettings).then(function () {
634
+ page.close().then(function () {
635
+ page = null;
636
+ return rpt_cb();
637
+ }).catch(function (err) { jsh.Log.error(err); return rpt_cb(); });
638
+ }).catch(function (err) { genReportError(err, 'page.pdf'); });
639
+ }, (jsh.Config.debug_params.report_interactive ? 500000 : 0));
640
+ }
641
+ ], function(err){
642
+ if(err) return genReportError(err);
643
+ return callback(null, tmppdfpath);
644
+ });
645
+ }).catch(function (err) { genReportError(err, 'browser.newPage'); });
646
+ } catch (err) {
647
+ genReportError(err);
648
+ }
649
+ }); //, { dnodeOpts: { weak: false } }
650
+ }
540
651
 
541
- var report_fonts = [].concat(jsh.Config.default_report_fonts||[]).concat(model.fonts||[]);
542
- jsh.loadFonts(report_fonts, function(err, font_css){
543
- if(err) return jsh.Log.error(err);
544
- var font_render = [];
545
- for(var i=0;i<report_fonts.length;i++){
546
- var font = report_fonts[i];
547
- var font_str = '';
548
- if(font['font-family']) font_str += "font-family:'"+Helper.escapeCSS(font['font-family'].toString())+"';";
549
- if(font['font-style']) font_str += "font-style:"+font['font-style'].toString()+";";
550
- if(font['font-weight']) font_str += "font-weight:"+font['font-weight'].toString()+";";
551
- if(font_str) font_render.push(font_str);
552
- }
553
- if(font_css){
554
- if(pagesettings.headerTemplate) pagesettings.headerTemplate = '<style type="text/css">'+font_css+'</style>' + pagesettings.headerTemplate;
555
- if(pagesettings.footerTemplate) pagesettings.footerTemplate = '<style type="text/css">'+font_css+'</style>' + pagesettings.footerTemplate;
652
+ AppSrvRpt.prototype.genReportXlsx = function (req, res, fullmodelid, params, data, tmppath, callback) {
653
+ var _this = this;
654
+ var jsh = _this.AppSrv.jsh;
655
+ var model = jsh.getModel(req, fullmodelid);
656
+ if(!model) throw new Error('Model '+fullmodelid+' not found');
657
+ if (model.layout !== 'report') throw new Error('Model '+fullmodelid+' is not a report');
658
+
659
+ function genReportError(err, source){
660
+ var rpterr = Helper.NewError("Error occurred during report generation"+(source ? ' - ' + source: '')+" (" + err.toString() + ')', -99999);
661
+ jsh.Log.error(rpterr);
662
+ return callback(rpterr, null);
663
+ }
664
+
665
+ var rsltpath = tmppath + '.xlsx';
666
+ jsh.Extensions.report.getExcelJS(function(err, excelJS){
667
+ if(err) return genReportError(err);
668
+ try{
669
+ //Create workbook
670
+ var cancelRender = false;
671
+ var workbook = new excelJS.Workbook();
672
+ async.waterfall([
673
+ //model.onrender
674
+ function(rpt_cb){
675
+ if(!model.onrender) return rpt_cb();
676
+ model.onrender({ workbook: workbook, data: data, params: params }, function(err, _cancelRender){
677
+ cancelRender = _cancelRender;
678
+ return rpt_cb(err);
679
+ }, require, jsh, fullmodelid);
680
+ },
681
+ //Generate worksheets
682
+ function(rpt_cb){
683
+ if(cancelRender) return rpt_cb();
684
+ var sheetNames = [];
685
+ if(data) sheetNames = _.keys(data).reverse();
686
+ async.eachSeries(sheetNames, function(rsName, rs_cb){
687
+ var rsData = data[rsName];
688
+ try{
689
+ //Create worksheet
690
+ var worksheet = workbook.addWorksheet(rsName);
691
+ //Add Data
692
+ if(rsData && rsData.length){
693
+ var cols = _.keys(rsData[0]);
694
+ worksheet.addRow(cols);
695
+ _.each(rsData, function(row){ worksheet.addRow(_.values(row)); });
696
+ if(cols.length){
697
+ //AutoFilter
698
+ worksheet.autoFilter = {
699
+ from: { row: 1, column: 1 },
700
+ to: { row: 1, column: cols.length },
701
+ };
556
702
  }
703
+ }
704
+ //AutoSize
705
+ _.each(worksheet.columns, function(col){
706
+ var maxlen = 10;
707
+ col.eachCell({ includeEmpty: true }, function(cell){
708
+ var len = 0;
709
+ if(_.isDate(cell.value)) len = 15;
710
+ else len = (cell.value||'').toString().length + 3;
711
+ if(len > 100) len = 100;
712
+ if(len > maxlen) maxlen = len;
713
+ });
714
+ col.width = maxlen;
715
+ });
716
+ }
717
+ catch(ex){
718
+ return genReportError(ex, "Sheet "+rsName);
719
+ }
720
+ return rs_cb();
721
+ }, rpt_cb);
722
+ },
723
+ //model.onrendered
724
+ function(rpt_cb){
725
+ if(!model.onrendered) return rpt_cb();
726
+ model.onrendered({ workbook: workbook, data: data, params: params }, rpt_cb, require, jsh, fullmodelid);
727
+ },
728
+ //Save to disk
729
+ function(rpt_cb){
730
+ workbook.xlsx.writeFile(rsltpath)
731
+ .then(function(){ return rpt_cb(); })
732
+ .catch(function(err){ return rpt_cb(err); });
733
+ },
734
+ ], function(err){
735
+ if(err) return genReportError(ex);
736
+ return callback(null, rsltpath);
737
+ });
738
+ }
739
+ catch(ex){
740
+ return genReportError(ex);
741
+ }
742
+ });
743
+ }
557
744
 
558
- //Sets styles and returns document width
559
- var onPageLoad = function(font_render,font_css){
560
- if(!document||!document.body) return 0;
561
- //Load CSS in header
562
- var head = document.getElementsByTagName('head');
563
- if(head.length) head = head[0];
564
- if(head){
565
- var css = document.createElement('style');
566
- css.type='text/css';
567
- css.innerHTML = font_css;
568
- head.appendChild(css);
569
- }
570
- //Add fonts to body (otherwise using them in the page header / footer will cause a Page Crash)
571
- if(font_render) for(var i=0;i<font_render.length;i++){
572
- var fontElement = document.createElement('div');
573
- fontElement.innerHTML = '&nbsp;';
574
- fontElement.setAttribute('style', font_render[i]+'visibility:hidden;position:absolute;top:0px;left:0px;');
575
- document.body.appendChild(fontElement);
576
- }
577
- document.body.style['-webkit-print-color-adjust'] = 'exact';
578
- return document.body.clientWidth;
579
- };
580
-
581
- fs.writeFile(tmphtmlpath, rptcontent.body||'','utf8',function(err){
582
- if(err) return jsh.Log.error(err);
583
- page.goto('file://'+tmphtmlpath, { timeout: jsh.Config.report_timeout, waitUntil: 'networkidle0' })
584
- .then(function(){
585
- page.evaluate(onPageLoad, font_render, font_css).then(function(documentWidth){
586
- if(documentWidth && !pagesettings.scale){
587
- var scale = contentWidth * 0.998 / documentWidth;
588
- if(scale < 0.1) scale = 0.1;
589
- if(scale > 1) scale = 1;
590
- pagesettings.scale = scale;
591
- }
592
- setTimeout(function(){
593
- page.pdf(pagesettings).then(function () {
594
- // *** Be sure to call dispose independently of the callback, because dispose may fail, but it should not be treated as a critical failure
595
- var dispose = function (disposedone) {
596
- page.close().then(function () {
597
- page = null;
598
- fs.close(tmpfd, function () {
599
- fs.unlink(tmphtmlpath, function (err) {
600
- fs.unlink(tmppath, function (err) {
601
- if (disposedone) disposedone();
602
- });
603
- });
604
- });
605
- }).catch(function (err) { jsh.Log.error(err); if (disposedone) disposedone(); });
606
- };
607
- done(null, tmppdfpath, dispose, data);
608
- }).catch(function (err) { genReportError(err, 'page.pdf'); });
609
- }, (jsh.Config.debug_params.report_interactive ? 500000 : 0));
610
- }).catch(function (err) { genReportError(err, 'onPageLoad'); });
611
- }).catch(function (err) { genReportError(err, 'page.goto'); });
745
+ AppSrvRpt.prototype.genReport = function (req, res, fullmodelid, params, data, done) {
746
+ var _this = this;
747
+ var jsh = _this.AppSrv.jsh;
748
+ var report_folder = jsh.Config.datadir + 'temp/report/';
749
+ var model = jsh.getModel(req, fullmodelid);
750
+ if(!model) throw new Error('Model '+fullmodelid+' not found');
751
+ if (model.layout !== 'report') throw new Error('Model '+fullmodelid+' is not a report');
752
+
753
+ HelperFS.createFolderIfNotExists(report_folder, function (err) {
754
+ if (err) throw err;
755
+ HelperFS.clearFiles(report_folder, jsh.Config.public_temp_expiration, -1, function () {
756
+ tmp.file({ dir: report_folder }, function (tmperr, tmppath, tmpfd) {
757
+ if (tmperr) throw tmperr;
758
+ var reportFunc = (model.format == 'xlsx' ? _this.genReportXlsx : _this.genReportPdf);
759
+ reportFunc.call(_this, req, res, fullmodelid, params, data, tmppath, function(err, rsltpath){
760
+ if(err) return done(err, null);
761
+ var dispose = function (disposedone) {
762
+ fs.close(tmpfd, function () {
763
+ fs.unlink(rsltpath, function (err) {
764
+ fs.unlink(tmppath, function (err) {
765
+ if (disposedone) disposedone();
612
766
  });
613
767
  });
614
- }).catch(function (err) { genReportError(err, 'browser.newPage'); });
615
- } catch (err) {
616
- genReportError(err);
617
- }
618
- }); //, { dnodeOpts: { weak: false } }
768
+ });
769
+ };
770
+ return done(null, rsltpath, dispose, data);
771
+ });
619
772
  });
620
773
  });
621
774
  });
@@ -759,10 +759,19 @@ exports.SendBaseEmail = function (dbcontext, email_subject, email_text, email_ht
759
759
  catch (e) {
760
760
  return callback(e);
761
761
  }
762
- mparams.subject = ejs.render(mparams.subject, { data: params, _: _ });
762
+ mparams.subject = ejs.render(mparams.subject, { data: params, _: _, moment: moment });
763
763
  _this.SendEmail(mparams, callback);
764
764
  }
765
765
 
766
+ exports.createFunction = function (script, args, desc){
767
+ try {
768
+ return Function.prototype.bind.apply(Function, [null].concat(args||[]).concat([script]))();
769
+ }
770
+ catch(ex){
771
+ throw new Error('Error creating function '+desc+': '+ex.toString()+'\n:: in ::\n'+script);
772
+ }
773
+ }
774
+
766
775
  exports.SendEmail = function (mparams,callback){
767
776
  var _this = this;
768
777
  if(_this.Config.debug_params.disable_email){ _this.Log.console('DEBUG - NO EMAIL SENT '+(mparams.to||'')+' '+(mparams.subject||'')+' '+(mparams.text||mparams.html||'')); return callback(); }
@@ -282,7 +282,7 @@ exports.AddModel = function (modelname, model, prefix, modelpath, modeldir, modu
282
282
  prependPropFile('onroute',modelpathbase + '.onroute.js');
283
283
  }
284
284
  if (!('helpid' in model) && !('inherits' in model)) model.helpid = modelname;
285
- if ('onroute' in model) model.onroute = (new Function('routetype', 'req', 'res', 'callback', 'require', 'jsh', 'modelid', 'params', model.onroute));
285
+ if ('onroute' in model) model.onroute = _this.createFunction(model.onroute, ['routetype', 'req', 'res', 'callback', 'require', 'jsh', 'modelid', 'params'], model.id+' model.onroute');
286
286
  //Check if model inherits self - if so, add to _transforms
287
287
  if((modelname in this.Models) && model.inherits){
288
288
  var parentModel = _this.getModel(null,model.inherits,model);
@@ -1047,6 +1047,12 @@ exports.ParseEntities = function () {
1047
1047
  else model.title = model.caption[1];
1048
1048
  }
1049
1049
  }
1050
+ if(model.layout=='report'){
1051
+ if(!('format' in model)) model.format = 'pdf';
1052
+ if(!_.includes(['pdf','xlsx'], model.format)) _this.LogInit_ERROR(model.id + ': Unsupported report format: ' + model.format);
1053
+ if('onrender' in model) model.onrender = _this.createFunction(model.onrender, ['report', 'callback', 'require', 'jsh', 'modelid'], model.id+' model.onrender');
1054
+ if('onrendered' in model) model.onrendered = _this.createFunction(model.onrendered, ['report', 'callback', 'require', 'jsh', 'modelid'], model.id+' model.onrendered');
1055
+ }
1050
1056
  if (!('ejs' in model)) model.ejs = '';
1051
1057
  if (!('header' in model)) model.header = '';
1052
1058
  if (!('templates' in model)) model.templates = {};
@@ -1907,10 +1913,10 @@ exports.ParseEntities = function () {
1907
1913
  var _v_model = [
1908
1914
  'comment', 'layout', 'title', 'table', 'actions', 'roles', 'caption', 'sort', 'dev', 'sites', 'class', 'using',
1909
1915
  'samplerepeat', 'menu', 'id', 'idmd5', '_inherits', '_transforms', '_referencedby', '_parentbindings', '_childbindings', '_parentmodels', '_auto', '_sysconfig', '_dbdef', 'groups', 'helpid', 'querystring', 'buttons', 'xvalidate', 'task', 'source_files_prefix',
1910
- 'pagesettings', 'pageheader', 'pageheaderjs', 'reportbody', 'headerheight', 'pagefooter', 'pagefooterjs', 'zoom', 'reportdata', 'description', 'template', 'fields', 'jobqueue', 'batch', 'fonts',
1916
+ 'pagesettings', 'format', 'pageheader', 'pageheaderjs', 'reportbody', 'headerheight', 'pagefooter', 'pagefooterjs', 'zoom', 'reportdata', 'description', 'template', 'fields', 'jobqueue', 'batch', 'fonts',
1911
1917
  'hide_system_buttons', 'grid_expand_search', 'grid_rowcount', 'reselectafteredit', 'newrowposition', 'commitlevel', 'validationlevel',
1912
1918
  'grid_require_search', 'default_search', 'grid_static', 'rowstyle', 'rowclass', 'rowlimit', 'disableautoload',
1913
- 'oninit', 'oncommit', 'onload', 'oninsert', 'onupdate', 'onvalidate', 'onloadstate', 'ongetstate', 'onrowbind', 'onrowunbind', 'ondestroy', 'onchange', 'getapi',
1919
+ 'oninit', 'oncommit', 'onload', 'oninsert', 'onupdate', 'onvalidate', 'onloadstate', 'ongetstate', 'onrowbind', 'onrowunbind', 'ondestroy', 'onchange', 'getapi', 'onrender', 'onrendered',
1914
1920
  'js', 'jslib', 'ejs', 'header', 'css', 'dberrors', 'tablestyle', 'tableclass', 'formstyle', 'formclass', 'popup', 'onloadimmediate', 'sqlwhere', 'sqlwhere_disabled_on_insert', 'breadcrumbs', 'tabpos', 'tabs', 'tabpanelstyle',
1915
1921
  'nokey', 'nodatalock', 'unbound', 'duplicate', 'sqlselect', 'sqlupdate', 'sqlinsert', 'sqlgetinsertkeys', 'sqldelete', 'sqlexec', 'sqlexec_comment', 'sqltype', 'onroute', 'tabcode', 'noresultsmessage', 'bindings',
1916
1922
  'path', 'module', 'templates', 'db', 'onecolumn', 'namespace',
package/jsHarmony.js CHANGED
@@ -404,6 +404,7 @@ jsHarmony.prototype.requireFolder = function(fpath,desc){
404
404
  }
405
405
  };
406
406
 
407
+ jsHarmony.prototype.getPrototype = function(){ return jsHarmony; };
407
408
  jsHarmony.prototype.Auth = require('./lib/Auth.js');
408
409
  jsHarmony.prototype.AppSrvClass = AppSrv;
409
410
  jsHarmony.Auth = jsHarmony.prototype.Auth;
@@ -33,6 +33,8 @@ function jsHarmonyExtensions(){
33
33
  this.report = {
34
34
  type: '',
35
35
  init: function(callback){ return callback(new Error("Report Extensions have not been enabled. Please configure jsh.Extensions.report")); },
36
+ excelJS: function(){ throw new Error("Report Extensions have not been enabled. Please configure jsh.Extensions.report"); },
37
+ getExcelJS: function(callback){ return callback(new Error("Report Extensions have not been enabled. Please configure jsh.Extensions.report")); },
36
38
  pdfMerge: function(){ throw new Error("Report Extensions have not been enabled. Please configure jsh.Extensions.report"); },
37
39
  getPdfMerge: function(callback){ return callback(new Error("Report Extensions have not been enabled. Please configure jsh.Extensions.report")); },
38
40
  puppeteer: function(){ throw new Error("Report Extensions have not been enabled. Please configure jsh.Extensions.report"); },
@@ -39,6 +39,7 @@ function jsHarmonyServer(serverConfig, jsh){
39
39
  this.serverConfig = serverConfig||{};
40
40
  this.servers = [];
41
41
  if(!('add_default_routes' in serverConfig)) serverConfig.add_default_routes = true;
42
+ this.redirectRouter = null; //Express router for redirect HTTP server
42
43
  /*
43
44
  serverConfig: {
44
45
  add_default_routes: true,
@@ -419,6 +420,8 @@ jsHarmonyServer.prototype.Run = function(cb){
419
420
  if(!http_redirect) start_https_server(cb);
420
421
  else {
421
422
  var redirect_app = express();
423
+ _this.redirectRouter = express.Router();
424
+ redirect_app.use(_this.redirectRouter);
422
425
  redirect_app.get('*', function (req, res) {
423
426
  var hostname = Helper.GetIP(req);
424
427
  if(req.headers && req.headers.host){
package/lib/Helper.js CHANGED
@@ -274,6 +274,9 @@ exports.pushCreate = function(parent, idx, val){
274
274
  parent[idx].push(val);
275
275
  }
276
276
  exports.getFullURL = function (req, url) {
277
+ url = (url||'').toString();
278
+ if(url.toLowerCase().indexOf('http://')==0) return url;
279
+ else if(url.toLowerCase().indexOf('https://')==0) return url;
277
280
  return req.protocol + '://' + req.get('host') + url;
278
281
  };
279
282
  exports.getLine = function(txt, lineno){
@@ -0,0 +1,19 @@
1
+ /*
2
+ Copyright 2022 apHarmony
3
+
4
+ This file is part of jsHarmony.
5
+
6
+ jsHarmony is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ jsHarmony is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with this package. If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+ exports = module.exports = require('body-parser');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsharmony",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Rapid Application Development (RAD) Platform for Node.js Database Application Development",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -43,7 +43,7 @@
43
43
  "ejs": "^2.6.1",
44
44
  "express": "^4.17.1",
45
45
  "form-data": "^2.5.0",
46
- "jsharmony-db": "^1.1.17",
46
+ "jsharmony-db": "^1.2.0",
47
47
  "jsharmony-validate": "^1.1.5",
48
48
  "lodash": "^4.17.19",
49
49
  "mime": "^1.3.4",
@@ -392,7 +392,7 @@ MENU STYLES
392
392
  border-left:1px solid #00000033;
393
393
  }
394
394
  <%-rootcss%> .xmenuhorizontal .xmenu_more_bg {
395
- padding:43px 22px 20px 28px;
395
+ padding:43px 20px 20px 26px;
396
396
  background-position-y:-5px;
397
397
  background-repeat:repeat-x;
398
398
  }
@@ -475,10 +475,11 @@ MENU STYLES
475
475
  display:block;
476
476
  top:-8px;
477
477
  right:0px;
478
- padding:7px 29px 0 29px;
478
+ padding:6px 29px 0 30px;
479
479
  background-color:#f5f5f5;
480
480
  border-left:1px solid #ccc;
481
- border-right:1px solid #ccc;
481
+ border-top:1px solid #ddd;
482
+ border-bottom:1px solid #ddd;
482
483
  height:23px;
483
484
  z-index:2;
484
485
  }
@@ -547,7 +548,7 @@ RESPONSIVE
547
548
  }
548
549
  <%-rootcss%> .xmenuhorizontal .xmenu_more_bg {
549
550
  padding-top:54px;
550
- padding-bottom:19px;
551
+ padding-bottom:9px;
551
552
  }
552
553
  <%-rootcss%> .xmenuhorizontal .xmenu a.xmenu_more {
553
554
  background-position-y: 59px;