Inspired by the work completed in a recent article, How To: Leverage NetSuite’s Advanced PDF / HTML Generation Tools, and from a LinkedIn NetSuite user group discussion and suggestions by Daniel JR Arena, Troy Oltmanns, Corey Hunt, and David Smith, I solved a client request to generate custom PDF files based on a custom record object.
PDF Generation Architecture
The architecture summary is as follows:
- PDF templates will be stored in a special user designated NetSuite folder. We can then use this for a template selection list on the custom record itself. The client can add more templates as needed without a change to the code.
- The basic PDF templates will conform to NetSuite’s implementation of the Big Faceless Report Generator. Because it is not available by NetSuite for custom PDF files, it will not use the FreeMarket syntax as referenced in NetSuite’s Advanced PDF / HTML implementation.
- The data to be merged into the template will take advantage of JavaScript JSON and the capacities built into the TrimPath Template engine. This saves us time by not writing our own parsing routine and it gives us control and looping logic, if needed.
- Users can select the template they are interested in while working with the record; By simply clicking a button, a custom SuiteLet will open in a new browser window to display the resulting PDF.
Custom SuiteLet to Generate the PDF File
function id002_bfo_create_note()
{ var func = 'id002_bfo_create_note '; var id = request.getParameter('id'); var template = request.getParameter('template'); nlapiLogExecution('AUDIT', func + 'with id | template:', id + '|' + template); try { // load the custom record var rec = nlapiLoadRecord('customrecord_dffg_note', id); //at this time, we have one node called "note". But we can add more data elements as needed later if (rec){ var recJSON = JSON.stringify(rec) ; var data = {note : eval('(' + recJSON + ')') }; nlapiLogExecution('AUDIT', func + 'JSON Data:', JSON.stringify(data)); } else { return; } //load the template data (JST plus BFO Syntax) var templateJST = nlapiLoadFile(template); var xml = templateJST.getValue(); // now we want to call TrimPath to parse the XML in JST format // remember that the String object has been prototyped to call TrimPath.parseTemplate on our behalf var result = xml.process(data); nlapiLogExecution('DEBUG', func + 'XML:', nlapiEscapeXML(xml)); nlapiLogExecution('DEBUG', func + 'RESULT:', nlapiEscapeXML(result)); var file = nlapiXMLToPDF( result ); response.setContentType('PDF',id + '.pdf', 'inline'); response.write( file.getValue() ); } catch(ex) { var errorStr = (ex.getCode != null) ? ex.getCode() + ' ' + ex.getDetails() + ' ' + ex.getStackTrace().join(' ') : ex.toString(); nlapiLogExecution('DEBUG', func, 'A problem occurred: ' + ' ' + errorStr); } }
Sample PDF Template Illustrates JSON Data
To help understand what is happening, I have screen-shotted the JSON data that is returned from NetSuite’s standard nlapiLoadRecord call. How convenient because we simply supply the entire row to the template engine without any extra work. Resist the urge to touch the data — the TrimPath JST framework has utilities to format your data.
With the JSON data, you then can have very simple calls to your PDF XML template file following Big Faceless Organization (BFO) guidelines.
Finally, the SuiteLet outputs the result which gave our client full capacity to produce the template and PDF without needing a SuiteScript developer. They like that.
Get Help Optimizing NetSuite
NetSuite was designed to be optimized to fit requirements well. If you would like help to get the most out of your NetSuite system, contact us.
See Related Articles
- Learn the Framework to Extend NetSuite Content Generation
- Video: How to Extend Advanced PDFs with Content Renderer Engine
- How To: Password Protect NetSuite Generated PDF Files
- Supercharge NetSuite Advanced PDF/HTML Templates
- Interrogating NetSuite’s Advanced PDF / HTML Templates
- How To: Leverage NetSuite’s Advanced PDF / HTML Generation Tools
Very nice. That’s much cleaner than my solution. I wish I had know about TrimPath when I made it.
Thanks to the contributions you and the others provided in the LinkedIn group discussion, this solution was inspired. I see a class of problems I want to solve under this approach. One is a custom email generator alternative. Sure feels good to take control over our destiny.
Thanks for sharing!
The above code is nice one but I have one problem I have to create .txt file.
1.How it is create?
2.That file have hexadecimal coded language which is generated on button click.which convert whole existing form in hexadecimal code. How it create? please tell me answer.
How to create custom bar,pie,etc. chart though portlet? please give me example…
Thank you.
Hello Amol,
The output is really all about strings. So you can change the code element that currently illustrates response.setContentType(‘PDF’,id + ‘.pdf’, ‘inline’) to response.setContentType(‘PLAINTEXT’,id + ‘.txt’, ‘inline’).
As am not sure what you mean by Hexadecimal coded language. Can you provide an example?
Marty
The Portlet object works with these types:
1. Lists
2. Form
3. HTML
4. Links
The only one that can draw those graphical elements is HTML. Yet, the HTML can be anything. You won’t easily be able to draw NetSuite provided graphical elements as these are built with NetSuite provided portlet objects in the drag and drop interface.. Instead, you might want to consider using something like the Google Charts API. What you can do is get the data you need, call the Google APIs and draw it in the HTML portlet. It’s basically server side and client side JavaScript to pull it all together.
Great article and blog! I implemented a solution based on your article using Handlebars.js as the template engine. I noticed that TrimPath hasn’t been updated since 2008 and I try to avoid using any abandoned open source projects! Also Handlebars has a lot more functionality and has an ability to precompile the template for much faster execution.
There are actually a lot of options for javascript based template engines.
I also opened an enhancement request with NetSuite to allow passing a custom javascript object into Freemarker (Advanced PDF). It should be possible but NetSuite’s api current only allows native Netsuite object types. I don’t see any reason not to make it more useable.
Hey Matt,
Most interesting. Handlebars looks most useful and I agree, that old TrimPath template engine has not seen any updates in too long. I have been more bullish on FreeMarker and the Advanced PDF because it appears that we can indeed drop in record objects to the template model when we script. We have built a new communications framework that we are plugging into a customer solution but it is not yet ready for market. But I have yet to try this for a generic Javascript JSON object. I want to stay closer to the FreeMarker environment because all NetSuite users are getting acquainted with it. This means our innovations look “best practice”.
Also, I just reviewed some 2015.1 updates and the PDF template API is becoming more robust.
Marty
Hi Marty,
Can we put water mark on Advanced PDF layout
Hi Kishor,
Here is an example NetSuite Advanced PDF with the watermark.
To produce the watermark in your PDF, do the following:
1. Create a Macro to reference a special tag
2. Reference it in the Body definition
See below the code snippet (thanks to Mike, a consultant on our staff, to help with this solution):
<macro id="watermark">
<p id="watermarkbody" rotate="-30" valign="middle" align="center">ORIGINAL</p>
</macro>
<body background-macro="watermark" header="nlheader" header-height="10%" footer="nlfooter" footer-height="20pt" padding="0.5in 0.5in 0.5in 0.5in" size="Letter">