Scripts extension

Run custom javascript to modify report inputs/outputs or to send a report

Basics

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.

script associate

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.

Use external modules

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.

request, response

  • req.template - modify report template, mostly content and helpers attributes
  • req.data - json object used to modify report input data
  • req.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 report
  • res.meta.headers - output headers

Multiple scripts

You 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.

Script scope

The script scope defines when it should be executed:

  • template - run for templates where its explicitly referenced
  • global - run for all templates
  • folder - run for all templates in the same folder hierarchy

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)

Environment variables and configuration

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)
}

Rendering another template from script

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())
}

Query an entity from script

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
}

Logging

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')  
}

API

You can use standard OData API to manage and query script entities. For example you can query all scripts using

GET http://jsreport-host/odata/scripts

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(); }"
      }]      
  }
}

Using script to load data

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.

Examples


jsreport version