I am a creator — PDF Document Generator - Walter Brame

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
04-08-2014 08:09 PM
------------------
EDIT
------------------
A lot has changed over the years.
Be sure to check here first:
https://developer.servicenow.com/dev.do#!/reference/api/quebec/server/sn_pdfgeneratorutils-namespace/CellBothAPI?navFilter=pdf
Have also enjoyed repurposing Webkit HTML to PDF plugin:
https://docs.servicenow.com/bundle/geneva-performance-analytics-and-reporting/page/use/performance_analytics/task/t_ActivateWHTP.html
------------------
ORIGINAL
------------------
PDF Document Generator is used by the ServiceNow, Inc. sales organization and is a generic HTML form creation and PDF Document generation framework. The framework supports creating HTML forms and PDF files using any UI Form in the instance as a template.
The PDF Document Generator will replicate how that standard UI Form looks but then provides the functionality to change it easily as a front-end user without requiring development.
A PDF feature exists today in the standard platform but it is not customizable. The PDF Document Generator allows for rapid deployment of multiple templates and making changes on the fly to every component visible on the screen very quickly and easily.
Previously, the Sales organization was challenged with the ability to produce Quotes and Sales Orders from a CRM environment. With this custom ServiceNow, Inc. app in place, it is now possible for the front-end business users (sales operations, systems managers, administrators, power users, non-developers) to access configuration tools that put control of the following features directly into their hands:
- Add/remove fields as needed
- Change any fields placement and the field's label, orientation, or value
- Change any section/grouping or fields / Table of information (E.g. "Section 1", "Section 2", etc.)
- Add/remove new sections present in the form
- Manage an entire section's placement, splits, annotations, and headers
- Re arrange the placement of components in the form (E.g. one component relative to the next)
- Change the look and feel (color, border, padding, alignment, etc.) via common HTML and CSS style attributes.
- A scripting API to create dynamic conditions to change any aspect of the form and data (style, value, label, section, etc.).
- Save templates of form layouts
- Preview and print to PDF
The functionality of this PDF Document Generator was made possible by leveraging the ability to write custom scripts in the standard ServiceNow platform, and by having access to the backend table structure and data where forms are stored in the instance (standard UI page, CSS, HTML, and JavaScript). A single developer, in a time frame of one business quarter, built the PDF Document Generator.
!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-26-2014 12:25 PM
Hi Walter,
Thanks for putting together this framework. I'm trying to find my way around - are you still planning on putting out a guide?
Quick question - When I export to pdf using built in tools, it takes my current view and creates a pdf of the fields available in that view. How do I go about getting that same functionality as a starting point using your framework?
I have a really simple business case and very limited css/html experience. I just need to be able to export one field to pdf (a field that stores letters for approval). Can you give me the 1,2,3 on the steps to go about accomplishing this? What prevents me from using the built in tools is the fact that a table and field label exists and cannot be removed.
Thanks

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-04-2014 06:21 AM
Hi Bryan,
Received your message and will put together steps for you. Will add them to the user guide that is work in progress and upload the user guide so others may reference it as well in the future.
For quick reference here is what I do to create a custom implementation for any other table but I want to provide better steps then this in the user guide.
1. Insert / Insert & Stay from the Script Include called GeneralFormDemo which becomes your custom implementation of the application. In your copy just change the variable called "this.tableName" to use any table you like.
It looks like this in the code provided:
initialize : function() { | ||
GeneralForm.prototype.initialize.apply(this, arguments); | ||
// Set the table | ||
this.tableName = 'u_pdf_document_generator_demo'; | ||
// Set the theme | ||
this.themeId = 'base'; | ||
} |
For Example to use the application on "Incident" table:
initialize : function() { | ||
GeneralForm.prototype.initialize.apply(this, arguments); | ||
// Set the table | ||
this.tableName = 'incident'; | ||
// Set the theme | ||
this.themeId = 'base'; | ||
} |
2. Insert / Insert & Stay from the UI Action called "Create PDF" , change the Table the UI Action lives on and update the code to use your Script Include instead of GeneralFormDemo.
This will accomplish using the 'Default View' for any table of your choosing. The most common implementation is to make a new view in your own table which becomes your custom PDF Template. (For an example create a new view and add one HTML field to it to contain text for a legal document. This HTML field can contain "Global Variables" to dynamically change things such as customer names and dates. An earlier reply in this thread demonstrates how to add the global variables with the scripting API available.)
If you remove the "tableName" declaration from the "initialize" method in your script include entirely then you can simply control it from the UI Action called "Create PDF".
The only reason we would hard code any of the variables in the Script Include is if we wanted to make a permanent implementation. For example the "GeneralFormDemo" Script Include in the code provided is intended to only be used with "u_pdf_document_generator_demo" table so I hard coded it. That was done simply to make the call to it from the UI Action cleaner because all I have to do is pass the sys_id.
The "Create PDF" UI Action looks like this in the code provided:
GeneralFormDemo.generate(current.sys_id);
action.setRedirectURL(current);
If we look at the static method called "generate" you see we could simply pass all of the variables when creating a new object instance of the "GeneralFormDemo" class.
For example the code provided:
GeneralFormDemo.generate = function(sysId, generalDebug) {
var generalForm = new GeneralFormDemo({
tableId : (sysId) ? sysId : null,
generalDebug : (generalDebug) ? generalDebug : null,
mode : 'pdf'
});
generalForm.start();
generalForm.createPDF();
};
May be changed to the following:
GeneralFormDemo.generate = function(sysId, generalDebug, tableName) {
var generalForm = new GeneralFormDemo({
tableId : (sysId) ? sysId : null,
generalDebug : (generalDebug) ? generalDebug : null,
mode : 'pdf',
tableName : tableName
});
generalForm.start();
generalForm.createPDF();
};
The Script Include would be changed as well to support this:
initialize : function() { | ||
GeneralForm.prototype.initialize.apply(this, arguments); | ||
// Set the table // Comment out the table declaration | ||
// | this.tableName = 'incident'; | |
// Set the theme | ||
this.themeId = 'base'; | ||
} |
All of the variables pass in the constructor when initializing a new object instance of the GeneralFormDemo class is automatically done for you by the following method:
GeneralForm.prototype.initialize.apply(this, arguments);
This is because the method I mentioned above will will run the following method:
General.prototype.init.apply(this, arguments);
If you look in the class called "General" for the "init" method you can see how this is accomplished using the native "arguments" keyword
For Example:
init : function() {
var args2 = {};
for ( var i = 0; i < arguments.length; i += 1) {
var args = arguments[i];
for ( var arg in args) {
if (args.hasOwnProperty(arg)) {
if (!args2[this.getType()]) {
args2[this.getType()] = {};
}
args2[this.getType()][arg] = args[arg];
// The default behavior is to assign whatever is passed in
// but special cases can be handled here
if (arg == General.STATICS.DEBUG_ARG) {
var deb = args[arg];
this.initDebug(deb); // Caller controlled debug
} else {
this[arg] = args[arg];
}
}
}
}
if (this.debug.level >= 3) {
for ( var arg2 in args2) {
if (args2.hasOwnProperty(arg2)) {
this.debug.log(3, 'INIT: ' + arg2);
var args1 = args2[arg2];
for ( var arg1 in args1) {
if (args1.hasOwnProperty(arg1)) {
this.debug.log(3, 'ARG: ' + arg1 + ' = '
+ args1[arg1]);
}
}
}
}
}
}
Meaning declaration of any variables is handled by the call to the object itself making it so you do not have to change the Scrip Include. This further serves to make the application a generic framework.
3. The last step would be to provide your own view.
For Example:
GeneralFormDemo.generate = function(sysId, generalDebug, tableName, viewId) {
var generalForm = new GeneralFormDemo({
tableId : (sysId) ? sysId : null,
generalDebug : (generalDebug) ? generalDebug : null,
mode : 'pdf',
tableName : tableName,
viewId : viewId
});
generalForm.start();
generalForm.createPDF();
};
This step is the most clunky part of the application in its current state because of an issue with the current version of the code.
Which I will try to add a fix for in the 1.4 version I mentioned in my reply to the post Christophe just made about an issue he encountered in Dublin patch4 instance.
The problem is when you make a brand new view in the platform and if you do not create at least one "Form Section" in addition to the default form section for the new view. The application will not work and the "Create PDF" button will not produce a PDF attachment.
If we look under the hood at how the View is structured by going to the sys_ui_view table we see several Related Lists make up a "View" in the platform:
For Example:
/sys_ui_view.do?sys_id=Default%20view
Forms
Form Sections
Lists
The three related records above are needed by the application in order to replicate the "View" when creating a PDF file.
The root cause of the problem is that in the baseline platform it does not create a record for "Forms" if there is not at least one additional "Form Section" created for the "View".
The solution is very simple because all we have to do is add the "Form" record. If you spend some time familiarizing yourself with how the tables are related by looking at other views which have more than one form section it becomes easy enough to just click the New button and create the Form record.
If you are not familiar with the table relationships you can get the platform to automatically create the Form record for you by using the "Personalize > Form Layout" and creating a new "Form Section" from there. After you create a Form Section using the Form Layout editor go back and look at the View and you will see the Form record has been created for you.
This is where it gets clunky currently because if you create a new Form Section just for the purpose of resolving that issue but you do not want more than one Form Section in your View and in your PDF file you then have to delete that new Form Section.
It is simple enough to delete the From Section from the View record by viewing the new Form Section record and clicking the Delete button or by selecting the new Form Section from the Related List view and clicking Delete from the List UI Action.
However until I add a code change for this in a future version the act of deleting a Form Section also breaks the application. For that reason if you delete a Form Section you need to go back and "touch" any Form Sections remaining in your View. What this means is you simply need to "Personalize > Form Layout" again, add any field to the Form Section and save. Then edit the layout again and remove the field you added. That will "touch" the Form Section and resolve this issue.
This would need to be done to each Form Section remaining in the entire View if ever you delete any single Form Section for that View. Believe this is a result of cache being used by the baseline platform which my application does not account for. In a future version I will resolve this by automating the "touch" to the remaining sections anytime you delete a Form Section or by incorporating how the baseline platform uses cache.
4. Technically you are finished but the PDF will not look pretty until you create your own "Theme". From the provided code you will see I am calling to a Theme called "base" and if you look at the module called "Elements" in the Left Hand Navigation of your instance under the Application called "PDF Document Generator" you will see examples of how to make your own Theme.
The user guide in its current state (Will upload soon) has a lot of useful information regarding how the Theme works and how to create your own.
Thank you,
Walter
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
09-04-2014 01:49 PM
Wow! Thanks for your awesome step by step on this - I was able to get it to work the other day but it was a real hack job - I'll follow these steps which I'm sure will result in a much more reusable process.
I also ran into the issue with the form sections breaking the application, I appreciate the explanation.
I look forward to the user guide.
Have a great day and thanks again,
Bryan
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
05-26-2015 05:20 PM
Hi Walter,
Are you able to share the user guide the way it is now?
Thanks,
Will
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
08-27-2014 05:39 PM
Hi Walter,
I just install the PDF Generator 1.3 on a Dublin patch4 instance, and got the below error when I try to generate the PDFs using the demo application. It worked fine on another instance on patch0, and works fine in my instance on Eureka. Did you already face that error ?
Thanks a lot,
Christophe
Extract of the error logs :
org.mozilla.javascript.EcmaError: expected java class object
Caused by error in Script Include: 'GeneralPDF' at line 673
670: this[name] = value;
671: }
672: };
==> 673: GeneralPDF.prototype.PDFTemplate = new JavaAdapter(
674: iTextPDFUtil.PdfPageEventHelper, GeneralPDF.prototype.pdfTemplate);
java.lang.SecurityException: Illegal attempt to access class 'com.itextpdf' via script
Caused by error in Script Include: 'GeneralFormJava' at line 11
8: /**
9: * PDF API Suite
10: */
==> 11: GeneralFormJava.iTextPDF = Packages.com.itextpdf;
12: GeneralFormJava.PdfName = GeneralFormJava.iTextPDF.text.pdf.PdfName;
13: GeneralFormJava.PDFTable = GeneralFormJava.iTextPDF.text.pdf.PdfPTable;
14: GeneralFormJava.PDFCell = GeneralFormJava.iTextPDF.text.pdf.PdfPCell;
org.mozilla.javascript.EcmaError: undefined is not a function.
Caused by error in Script Include: 'GeneralPDF' at line 20
17: initialize : function(pdfDoc, css, glideRecord) {
18: this.document = null;
19: this.writer = null;
==> 20: this.outputStream = new GeneralFormJava.ByteArrayOutputStream();
21: this.pdfDoc = null;
22: this.glideRecord = null;
23: this.htmlWorker = null;