Setting up Node.js application with embedded jsreport-core and jsreport-fs-store

1,416 views
Skip to first unread message

Vladimir Kelman

unread,
Aug 30, 2016, 4:35:29 PM8/30/16
to jsreport
Hi,

I'm Setting up Node.js application with embedded jsreport-core and jsreport-fs-store. Doing it for a first time with limited Node experience, so excuse silly questions.

I'm writing jsReportService.js module for jsReport initialization and rendering reports from previously created templates.
I'd like jsReport's 'data' directory to be a sub-folder of the same folder where jsReportService.js is located.

Does the following jsReportService.js code make sense?

const jsReport = require('jsreport-core')({
loadConfig: false,
dataDirectory: path.join(__dirname, 'data'),
connectionString: { 'name': 'fs' }
});

jsReport.use(require('jsreport-handlebars'));
jsReport.use(require('jsreport-phantom-pdf'));
jsreport.use(require('jsreport-fs-store')()); // { dataDirectory: '...', syncModifications: true }
jsReport.init();

When I require('jsreport-fs-store')()  is it necessary to specify dataDirectory again, or is it going to somehow pick it up from require('jsreport-core')() ?

Jan Blaha

unread,
Aug 31, 2016, 2:18:00 AM8/31/16
to jsreport
You are doing it right. It's picked up from the root config.

Vladimir Kelman

unread,
Aug 31, 2016, 10:50:26 AM8/31/16
to jsreport

Jan, next question: is jsReport.init() supposed to run one time per application run?

Right now I'm doing

const _renderReport = function(data) {
return jsReport.init().then(function() {
return jsReport.render({
template: {
shortid : 'SySyf2Lw',
engine: 'handlebars',
recipe: 'phantom-pdf'
},
data: data
})
.then(function(resp) { ... }

Should I instead perform jsReport.init() on application startup before running server.listen(port);.  Or maybe just include jsReport.init(); on the top of module (not sure it's correct since init() is asynchronous and returns a promise)

  Thank you.

Jan Blaha

unread,
Aug 31, 2016, 11:51:27 AM8/31/16
to jsreport
Yes, do it just one time.

For example:
jsReport.init().then(function() {
  server.listen(port)
})

Vladimir Kelman

unread,
Aug 31, 2016, 12:17:54 PM8/31/16
to jsreport
Thank you, that's exactly what I was going to do.

Vladimir Kelman

unread,
Aug 31, 2016, 1:47:49 PM8/31/16
to jsreport
Hmm... Now I'm catching the following error after calling render():   "Error during rendering report: Cannot read property 'replace' of undefined".
I report template there is no 'replace' anywhere. I'm sending helpers: "My helpers here" as a string.  The same report template works fine in jsReport Studio and works fine when is called from NodeJs app using jsreport-client  (with the same data sent) ....

Jan Blaha

unread,
Aug 31, 2016, 1:50:40 PM8/31/16
to jsreport
Please share the whole error (stack) and the code where you call render.

Vladimir Kelman

unread,
Aug 31, 2016, 2:31:03 PM8/31/16
to jsreport
Here is the stack:

TypeError: Cannot read property 'replace' of undefined
    at module.exports (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\jsreport-handlebars\lib\handlebarsEngine.js:14:14)
    at engine (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\jsreport-core\lib\render\engineScript.js:118:30)
    at evalmachine.<anonymous>:1:15
    at ContextifyScript.Script.runInContext (vm.js:35:29)
    at ContextifyScript.Script.runInNewContext (vm.js:41:15)
    at Object.exports.runInNewContext (vm.js:72:17)
    at module.exports (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\jsreport-core\lib\render\engineScript.js:168:8)
    at process.<anonymous> (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\script-manager\lib\worker-processes.js:45:38)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:191:7)
From previous event:
    at C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\jsreport-core\lib\render\render.js:143:54
    at _fulfilled (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\listener-collection\node_modules\q\q.js:794:54)
    at self.promiseDispatch.done (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\listener-collection\node_modules\q\q.js:823:30)
    at Promise.promise.promiseDispatch (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\listener-collection\node_modules\q\q.js:756:13)
    at C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\listener-collection\node_modules\q\q.js:564:44
    at flush (C:\PlanetRisk\Projects\WebstormProjects\ReportServerWithJsReport\node_modules\listener-collection\node_modules\q\q.js:110:17)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

Vladimir Kelman

unread,
Aug 31, 2016, 2:40:36 PM8/31/16
to jsreport
An entire module:

'use strict';

const path = require('path');

const jsReport = require('jsreport-core')({
loadConfig: false,
dataDirectory: path.join(__dirname, 'data'),
connectionString: { 'name': 'fs' }
});

jsReport.use(require('jsreport-handlebars')());
jsReport.use(require('jsreport-phantom-pdf')());
jsReport.use(require('jsreport-fs-store')());


const jsReportService = function() {

//#region Private Member variables
//#endregion Private Member variables

//#region Private Methods

const _renderReport = function(data) {
return jsReport.init().then(function() {
return jsReport.render({
template: {
                    shortid : 'rJ_wcqNi'
,engine: 'handlebars'
,recipe: 'phantom-pdf'
                },
data: data
})
.then(function(resp) {
                    console.log(resp.content.toString());
})
.catch(e => {
console.error("Cannot render.");
console.error(e);
throw e;
})
}).catch(e => {
console.error("Cannot init.");
throw e;
});
};

//#endregion Private Methods

//#region Export to the public namespace only properties / methods we want to be public, leave the private ones hidden.

return {
renderReport: _renderReport,
};

//#endregion Export to the public namespace only properties
};

module.exports = jsReportService;

Vladimir Kelman

unread,
Aug 31, 2016, 2:48:02 PM8/31/16
to jsreport
Simplified template I'm using to debug everything. This template works fine when calling jsReport server from NodeJs app using jsreport-client.  I copied an entire content of jsReport data folder to data folder of my new NodeJs app which uses jsreport-core.  In case dataDirectory is not found, would it be a different error?



<h2>Hospitals</h2>
<table style='border-style:solid; border-spacing: 8px; width:70%'>
  <tr>
    <th>Name</th>
    <th>Phone</th>
    <th>Distance Away</th>
  </tr>
  {{#each hereData.hospitals}}
    <tr>
        <td style='padding: 10px;'>
            {{title}} <br/>
            {{vicinity}}
        </td>
        <td style='text-align:right; padding: 10px;'>
            {{details.contacts.phone.[0].value}}
        </td>
        <td style='text-align:right; padding: 18px;'>
            {{distance}}
        </td>
    </tr>
  {{/each}}    
</table>

Vladimir Kelman

unread,
Aug 31, 2016, 3:02:13 PM8/31/16
to jsreport
I replaced 

template: {
content: '<h1>Hello {{foo}}</h1>',

engine: 'handlebars',
recipe: 'phantom-pdf'
},
data: {
foo: "world"
}
// template: {
// shortid : 'rJ_wcqNi'
// ,engine: 'handlebars'
// ,recipe: 'phantom-pdf'
// },
// data: data

This works.  So, maybe it's not finding my template? I'm going to create that Hello {{foo}} template in jsReport Studio and then to copy it to data folder of my NodeJs + jsreport-core application. If it doesn't work, then problem is with finding template in dataDirectory.

Vladimir Kelman

unread,
Aug 31, 2016, 3:48:43 PM8/31/16
to jsreport
Yes, it's almost definitely not finding templates. I have in jsReportService.js module:

const jsReport = require('jsreport-core')({
loadConfig: false,
    autoTempCleanup: true,

dataDirectory: path.join(__dirname, 'data'),
connectionString: { 'name': 'fs' }
});

...
...
   jsReport.render( ...) 

Corresponding data directory which I copied from full jsReportStudio  is located on the same level as jsReportService.js module:

-- jsreportservice folder
---- jsReportService.js
---- data
------ data
------ images
------ scripts
------ storage
------ templates
-------- foo
---------- config.json
---------- content.handlebars
---------- footer.handlebars
---------- header.handlebars
---------- helper.js
------ xlsxTemplates

Is it wrong?

Jan Blaha

unread,
Aug 31, 2016, 4:14:53 PM8/31/16
to jsreport
Thank you for excellent problem description.

I'm very sorry I didn't spot this in your the first snipped, but you are missing one line in your code. Without it the jsreport doesn't even try to load the template.

jsReport.use(require('jsreport-templates')());


Vladimir Kelman

unread,
Aug 31, 2016, 4:28:17 PM8/31/16
to jsreport
Jan,

I really appreciate your quick support. We're still in evaluating mode, but if my efforts succeed our company will likely buy jsReport subscription.

I run npm install jsreport-templates --save and added that missing jsReport.use(require('jsreport-templates')()); line.   My simple "foo"  template with shortid='H1-ljjVi' immediately began working.

template: {
shortid : 'H1-ljjVi'
,engine: 'handlebars'
,recipe: 'phantom-pdf'
},
data: { foo: "world" }

I'm sure real report template will work as well.
I think it makes sense to add information about necessity of  npm install jsreport-templates --save and jsReport.use(require('jsreport-templates')());  to documentation on https://github.com/jsreport/jsreport-core#engines

  Thanks again.

Vladimir Kelman

unread,
Aug 31, 2016, 5:23:07 PM8/31/16
to jsreport
Actually, on  https://github.com/jsreport/jsreport-core there is jsreport-templates link, but it is hard to notice and doesn't have explanations.

Vladimir Kelman

unread,
Sep 1, 2016, 12:55:39 PM9/1/16
to jsreport
Jan,

Just wanted to confirm: when I was working with the full jsReport  server it was enough to specify just shortid when rendering report through jsreport-client:

jsReportClient.render(
{
template: { "shortid" : "H1-ljjVi" },
data: data
},
function(err, response) {
if (err) {
return next(err);
}
response.pipe(res);
}
);

With jsreport-core it's required to also supply engine and recipe, shortid is not enough, correct?   Like

jsReport.render({

template: {
shortid : 'H1-ljjVi'
,engine: 'handlebars'
,recipe: 'phantom-pdf'
},
data: { foo: "world" }
})

Vladimir Kelman

unread,
Sep 16, 2016, 3:31:37 PM9/16/16
to jsreport

On Wednesday, August 31, 2016 at 11:51:27 AM UTC-4, Jan Blaha wrote:
Jan,
I'm "requiring" jsReport like

const jsReport = require('jsreport-core')({

    tasks
: {
        allowedModules
: ['round10']
   
},
    loadConfig
: false,
    autoTempCleanup
: true,

    dataDirectory
: path.join(__dirname, 'data'),
    connectionString
: { 'name': 'fs' }
});

Then there are multiple lines like
jsReport.use(require('jsreport-templates')());
Finally
jsReport.init().then(function() { return jsReport.render( . . .)}).then ....

Question:
 If I move jsReport.init() part to application bootstrap code like you suggested above, would I need inside that bootstrap code to require jsreport-core with the same options and to add "use" statements?
  Or do I just need init only inside bootstrap like
require('jsreport-core')().init().then(function() {
  server
.listen(port)
})

 while  all the jsreport-core options and "use" statements need to be only in a code where I render report?


 

Vladimir Kelman

unread,
Sep 16, 2016, 4:03:18 PM9/16/16
to jsreport
I tried

require('jsreport-core')().init().then(function() {
  server
.listen(port)
})

inside application bootstrap and then to use just jsReport.render() without init()  to render reports. It didn't work, I got "Not initialized, you need to call jsreport.init().then before rendering"  error.

I suppose, if I initialize in a bootstrap like that I then need to pass initialized jsReport variable around instead of using require(''jsreport-core') again?  It would be too much of a burden...

Jan Blaha

unread,
Sep 17, 2016, 3:53:34 AM9/17/16
to jsreport
Hi,

you should initialize just once in bootstrap and then pass the instance.

If you don't want to pass the instance, but rather "require" it from everywhere, you can use this approach

-- reports.js
var jsreport = require('jsreport-core')

module.exports = jsreport.render.bind(jsreport)

module.exports.init = function () {
  jsreport.use(require('jsreport-templates')())
  return jsreport.init()
}

-- myBootstrap
var reports = require('./reports.js')

.... reports.init().then()....

--myCode
var reports = require('./reports')

reports({ template: { name: 'foo' } })

The main idea is to have one extra module where you keep the jsreport instance....






Vladimir Kelman

unread,
Sep 19, 2016, 2:12:51 PM9/19/16
to jsreport
Jan,

Thanks a lot for a beautiful working solution.

(It was difficult to me to directly pass an initialized instance of jsReport because my "Report Server" app is just a part of much bigger collective project. I'm trying to keep my code self-contained and to pollute outer space as little as possible.)
Reply all
Reply to author
Forward
0 new messages