Run custom javascript to modify report inputs/outputs or to send a report
The script can be created, like any other entity, through the jsreport studio. You just need to associate it with a template through template properties afterward.
Then define a global functions beforeRender
or (and) afterRender
in script and use parameters req
and res
to reach your needs. script functions expect parameters to be req, res, done
or req, res
.
async function beforeRender(req, res) {
// merge in some values for later use in engine
// and preserve other values which are already in
req.data = Object.assign({}, req.data, {foo: "foo"})
req.data.computedValue = await someAsyncComputation()
}
or
function beforeRender(req, res, done) {
// merge in some values for later use in engine
// and preserve other values which are already in
req.data = Object.assign({}, req.data, {foo: "foo"})
done()
}
You can find an example here.
You can require
external modules as it's common in node.js. However, you need to opt-in in the config first. You can do it either by setting trustUserCode=true
or naming the allowed modules using:
{
"sandbox": {
"allowedModules": ["axios"]
}
}
Alternatively, you can enable all modules also using sandbox.allowedModules="*"
You can also require modules implemented in your assets. See the assets extension for the details.
The following example uses popular axios module to make a rest call and nodemailer to email the output service.
//load some data from the remote API
const axios = require('axios')
async function beforeRender(req, res) {
const r = await axios.get('http://localhost:5488/odata/templates')
req.data = { ...req.data, ...r.data}
}
//send the pdf report by mail
const nodemailer = require('nodemailer')
async function afterRender(req, res) {
const smtpTransport = nodemailer.createTransport({...})
const mail = {
from: '..>',
to: '...',
subject: '...',
text: '...',
attachments: [
{
filename: 'Report.pdf',
content: new Buffer(res.content)
}],
}
try {
await smtpTransport.sendMail(mail)
} finally {
smtpTransport.close()
}
}
You could be interested also in the jsreport npm extension that processes automatically install from the package manager.
req.template
- modify report template, mostly content
and helpers
attributesreq.data
- json object used to modify report input datareq.context.http
- object which contain information about input http headers, query params, etc. (this is opt-in and you need to enable it using config extensions.express.exposeHttpHeaders=true
)res.content
- buffer with output reportres.meta.headers
- output headersYou can associate multiple scripts to the report template. The scripts are then serially executed one after one in the order specified in the jsreport studio. The req
and res
parameters are properly passed through the scripts chain. This means you can use for example req.data
to pass information between scripts.
The script scope defines when it should be executed:
The order of the executed script is the following: global scoped scripts (sorted alphabetically) -> folder scope scripts (sorted alphabetically) -> template scoped scripts (template specific order)
You can reach environment variables using node.js process module.
const process = require('process')
function beforeRender(req, res) {
const myEnv = process.env.MY_ENV
}
You can also read your config file if you prefer it.
const path = require('path')
const promisify= require('util').promisify
const readFileAsync = promisify(require('fs').readFile)
async function beforeRender(req, res) {
const configPath = path.join(__appDirectory, 'myConfig.json')
const config = (await readFileAsync(configPath)).toString()
console.log(config)
}
Script can invoke rendering of another template. To do this you need to require
special module jsreport-proxy
and call render
function on it. The jsreport-proxy
can't be installed from the npm, it's is just a virtual module provided automatically to your sandbox.
const jsreport = require('jsreport-proxy')
async function beforeRender(req, res) {
console.log('starting rendering from script')
const result = await jsreport.render({ template: { shortid: 'xxxxxx' } })
console.log('finished rendering with ' + result.content.toString())
}
Script can query the jsreport store and load an asset with config for example.
const jsreport = require('jsreport-proxy')
async function beforeRender(req, res) {
const assets = await jsreport.documentStore.collection('assets').find({name: 'myConfig'})
const config = JSON.parse(assets[0].content.toString())
req.data.config = config
}
The console
calls are propagated to the debug calls from the studio as well to the standard jsreport log.
function beforeRender(req, res) {
console.log('i\'m generating logs with debug level')
console.warn('i\'m generating logs with warn level')
console.error('i\'m generating logs with error level')
}
You can use standard OData API to manage and query script entities. For example you can query all scripts using
A custom script is physically linked to the stored report template using its shortid or name. In this cases there is nothing to do in the API call, because the script is automatically applied.
POST: { template: { name: 'My Template with script' }, data: { } }
If you are rendering anonymous template you can identify the script by its name or shortid
POST: {
template : {
content: "foo",
scripts: [{
shortid: "sd543fds"
}, {
name: "My Script"
}]
}
}
The last option is to specify the script content directly
POST: {
template : {
content: "foo",
scripts: [{
content: "function beforeRender(req, res, done) { req.template.content='hello'; done(); }"
}]
}
}
Some people prefer to push data into jsreport from the client and some people prefer to use scripts
extension to actively fetch them. Where pushing data to the jsreport is more straight forward, using scripts
can provide better architecture with fully separated reporting server where report itself is responsible for defining input as well as fetching them. The choice is up to you.