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 +16 -6
- package/AppSrvRpt.js +361 -208
- package/jsHarmony.Helper.js +10 -1
- package/jsHarmony.LoadModels.js +9 -3
- package/jsHarmony.js +1 -0
- package/jsHarmonyExtensions.js +2 -0
- package/jsHarmonyServer.js +3 -0
- package/lib/Helper.js +3 -0
- package/lib/body-parser.js +19 -0
- package/package.json +2 -2
- package/themes/jsHarmony.theme.light.css +5 -4
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
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
|
-
|
|
401
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if(
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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(
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
535
|
-
|
|
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
|
-
|
|
538
|
-
|
|
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 = ' ';
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
})
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}); //, { dnodeOpts: { weak: false } }
|
|
768
|
+
});
|
|
769
|
+
};
|
|
770
|
+
return done(null, rsltpath, dispose, data);
|
|
771
|
+
});
|
|
619
772
|
});
|
|
620
773
|
});
|
|
621
774
|
});
|
package/jsHarmony.Helper.js
CHANGED
|
@@ -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(); }
|
package/jsHarmony.LoadModels.js
CHANGED
|
@@ -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 = (
|
|
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;
|
package/jsHarmonyExtensions.js
CHANGED
|
@@ -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"); },
|
package/jsHarmonyServer.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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:
|
|
478
|
+
padding:6px 29px 0 30px;
|
|
479
479
|
background-color:#f5f5f5;
|
|
480
480
|
border-left:1px solid #ccc;
|
|
481
|
-
border-
|
|
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:
|
|
551
|
+
padding-bottom:9px;
|
|
551
552
|
}
|
|
552
553
|
<%-rootcss%> .xmenuhorizontal .xmenu a.xmenu_more {
|
|
553
554
|
background-position-y: 59px;
|