PDF Generation in Geneva
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-20-2016 06:40 PM
This document will cover details on PDF Generator Plugin provided in Geneva as well as Enhancement that I have made to this solution.
1. PDF Generator Plugin
PDF generation feature has been added in Geneva as plugin, which provides the ability to generate PDF documents as per your own layout and data.
PDF Generator New in Geneva application | com.snc.pdf_generator | Provides a tool to generate PDF documents. |
This plugin has been used in Human Resources application to generate 'Employee Verification Letters' which is covered in Configure the employment verification letter and Complete an employment verification request articles in Geneva documentation.
However documentation lacks the details on use of this plugin for any other custom application or OOTB tables i.e. Incidents etc.
If you have been familiar with walter.brame's (fantastic) PDF Generator, this plugin is based on the same scripts. Basically this plugin provides you the ability to generate PDF of any document i.e. Letter which an end user can draft & design in ServiceNow with the data coming form record it is related to.
In order to utilize this for your application, you will need to create a script include similar to 'GeneralHRForm' and a document template table similar to 'hr_document_template' (both introduced with HR application in Geneva).
In Script Include you would be able to configure various aspects of this solution including variables which need to be replaced while PDF generation and document template table keeps all the customized templates which you could format as per your need using HTML editor.
Figure 1 - HR Document Template Record
Figure 2 - PDF Letter Generated
This solution has some limitations around variables you could use in templates as well as some other aspects. While working on it for a customer I have come up with some enhancements, which I would like to share with you in next section.
2. Enhancements
If you are looking to implement PDF letter generation functionality using solution provided for 'Employee Verification Letter' in HR Application, you would find few limitations and I have developed following enhancements to it.
a) Dynamic Variables Parsing
Problem
When you are configuring document template you would notice that variables are being entered as {{Name]}} or {{Position}}. These variables are parsed by following method in 'GeneralHRForm' script include.
Figure 3 - OOTB Script Include Method
parseBody : function(docBody, instance){
var parsedBody = docBody;
var gr = new GlideRecord(this.tableName);
gr.get(this.tableId);
var date = gs.now();
parsedBody = parsedBody.replace(/\{\{Date\}\}/gi, date);
parsedBody = parsedBody.replace(/\{\{Name\}\}/gi, gr.hr_profile.name);
parsedBody = parsedBody.replace(/\{\{Position\}\}/gi, gr.hr_profile.position.position);
parsedBody = parsedBody.replace(/\{\{Time type\}\}/gi, gr.hr_profile.time_type.getDisplayValue());
parsedBody = parsedBody.replace(/\{\{Employment start date\}\}/gi, gr.hr_profile.employment_start_date);
parsedBody = parsedBody.replace(/\{\{Work email\}\}/gi, gr.hr_profile.work_email);
parsedBody = parsedBody.replace(/\{\{Work phone\}\}/gi, gr.hr_profile.work_phone);
parsedBody = parsedBody.replace(/\{\{Prefix\}\}/gi, gr.hr_profile.introduction);
parsedBody = parsedBody.replace(/\{\{Nationality\}\}/gi, gr.hr_profile.nationality);
parsedBody = parsedBody.replace(/\{\{Manager\}\}/gi, gr.hr_profile.manager.name);
parsedBody = parsedBody.replace(/\{\{Department\}\}/gi, gr.hr_profile.department.name);
parsedBody = parsedBody.replace(/\{\{Employee number\}\}/gi, gr.hr_profile.employee_number);
parsedBody = parsedBody.replace(/\{\{Employment status\}\}/gi, gr.hr_profile.employment_status);
parsedBody = parsedBody.replace(/\{\{Employment type\}\}/gi, gr.hr_profile.employment_type);
parsedBody = parsedBody.replace(/\{\{Gender\}\}/gi, gr.hr_profile.gender);
// convert to the right image path
parsedBody = parsedBody.replace(/\/sys_attachment.do\?sys_id=(\w{32})/gi, '/$1.iix');
parsedBody = parsedBody.replace(/\/sys_attachment.do\?sys_id=(\w{32})/gi, '/$1.iix');
parsedBody = parsedBody.replace(/src="\//gi, 'src="' + instance);
return parsedBody;
},
You would notice that variable parsing is hard-coded in the method, which poses a limitation that user can only use variables that have parsing code available in script include method 'parseBody'. Any new variable would require code change and may result in recurring code maintenance job for System Administrators.
Solution
For this solution lets consider we are developing Letter generation solution for an Incident record and have already created following script include and document template table.
Script Include: GeneralIncidentForm
Document template table: x_snc_letters_letter_template
Now we need to follow below steps:
Step 1: Add a new field called 'Table' to the document template form as below:
Figure 4 - Adding table field
Step 2: Changed field type for 'Body' field from 'HTML Translated' to 'HTML Script'.
Figure 5 - Body field type change
Step 3: Set 'Use dependent field' to checked and 'Dependent field' to 'Table' (i.e. new field you created)
Figure 6 - Setting dependent field
This will change your form to look like below:
Figure 7 - Enhanced document template form
This form would now provide the similar functionality as we have for creating email templates i.e.
- You can select table from the table dropdown, i.e. in this case 'Incident'
- 'Select variable' section on right of 'Body' field populates all the fields from Incident table (which was selected in table field above).
- You can select any field from variable list, which would be added to template body.
- You can drill down / dot-walk to a lower level of field and add it to the body.
- Notice that variables are now enclosed in ${}.
Step 4: Now we need to modify script include to parse these variables dynamically. Replace the parseBody method with code given below:
Figure 8 - Enhanced Script Include Method
parseBody : function(docBody, instance){
var parsedBody = docBody;
var gr = new GlideRecord(this.tableName);
gr.get(this.tableId);
var date = gs.now();
parsedBody = parsedBody.replace(/\$\{date\}/gi, date);
parsedBody = parsedBody.replace(/\$\{letterid\}/gi, this.letterid);
// parsing of variables dynamically
var sampleString=docBody.toString();
var reg = new SNC.Regex('/\\$\\{(.*?)\\}/i');
var match = reg.match(sampleString);
var count =0;
var variables = [];
var values = [];
var tmpValue;
while (match != null) {
variables.push(match.toString().substring(match.toString().indexOf(',')+1));
match = reg.match();
values.push(variables[count]);
if(gr.getDisplayValue(values[count])==null || JSUtil.nil(gr.getDisplayValue(values[count]))){
tmpValue='';
}
else
{
tmpValue=gr.getDisplayValue(values[count]);
}
parsedBody = parsedBody.replace('${'+variables[count]+'}', tmpValue);
count++;
}
// convert to the right image path
parsedBody = parsedBody.replace(/\/sys_attachment.do\?sys_id=(\w{32})/gi, '/$1.iix');
parsedBody = parsedBody.replace(/\/sys_attachment.do\?sys_id=(\w{32})/gi, '/$1.iix');
parsedBody = parsedBody.replace(/src="\//gi, 'src="' + instance);
return parsedBody;
},
And when you generate PDF, solution would be able to parse any variable you have included in the document template.
Here is the out put.
Figure 9 - Final generated letter
b) Miscellaneous enhancements
Following minor enhancements were also made and are included in attached script include code.
i) Removal of hard coded references for template table and source table form script include and passing them as parameter.
ii) Setting file name in script include as you like.
iii) and couple of more.
Apart from this I have also developed a solution of 'Letters & Correspondence Management' which covers:
- Creating draft letter, approval and then generation of PDF
- Having ability to select from multiple pre-drafted letter templates
- Ability to generate latter / template with data coming from any ServiceNow table.
- and more.
This solution will be available on Share portal soon.
Please share your feedback on this solution and how could this be improved.
Message was edited by: Syed Faheem
- 39,857 Views
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-11-2017 08:19 AM
Hi Vidya S B,
Can you please share the piece of code which worked for you.
Thank you.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-12-2017 12:48 AM
Hi Tried this,
addHTML : function(html) { | |||||||||
var isValid = new GeneralFormJava.GlideXMLUtil().isValidHTML(html); | |||||||||
if (!isValid) { | |||||||||
this.debug.log('HTML is not valid'); | |||||||||
} else { | |||||||||
this.debug.log('HTML is valid'); | |||||||||
html = GeneralFormJava.GlideStringUtil.unEscapeHTML(html); | |||||||||
} | |||||||||
try { | |||||||||
this.styles = null;//["font-family: journal", "border-color:blue", "color:red", "width:100%"]; | |||||||||
var contentReader = new GeneralFormJava.StringReader(html); | |||||||||
var StringRead = contentReader.toString(); | |||||||||
var providers = null; | |||||||||
var objects = this.htmlWorker.parseToList(contentReader); | |||||||||
for ( var i = 0; i < objects.size(); i += 1) | |||||||||
{ | |||||||||
var element = objects.get(i); | |||||||||
gs.addInfoMessage("In GeneralPDF GetElement" + "-" + element.toString()); | |||||||||
if(element.toString() == "Haasini"){ | |||||||||
this.document.newPage(); | |||||||||
}else{ | |||||||||
this.document.add(element); | |||||||||
} | |||||||||
/*var element = objects.get(i); | |||||||||
this.document.addParagraph(element, this.BaseFnt); | |||||||||
this.document.newPage(); | |||||||||
this.document.add(element); | |||||||||
this.writer.setPageEmpty( false ); | |||||||||
this.document.newPage();*/ | |||||||||
} |
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-12-2017 12:53 AM
Above was tried in GeneralPDF Script Include.
In GeneralIncidentForm script include i added the text to parse body. But this doesnt add Page Break after word "Haasini"
parseBody : function(docBody, instance) | |||||
{ | |||||
var incidentdetails = this.tableId.toString().split(','); | |||||
//gs.addInfoMessage(incidentdetails); | |||||
var parsedBody = docBody; | |||||
var parsebodyarray = []; | |||||
for (var i=0;i<incidentdetails.length;i++) | |||||
{ | |||||
var gr = new GlideRecord(this.tableName); | |||||
// | gr.get(this.tableId); | ||||
gr.addQuery('sys_id', incidentdetails[i]); | |||||
gr.query(); | |||||
if (gr.next()) | |||||
{ | |||||
var date = gs.now(); | |||||
parsedBody = parsedBody.replace(/\$\{number\}/gi, gr.number); |
parsedBody += "Haasini";
gs.addInfoMessage("GetAppendedParsedBody" + "-" + parsedBody); | ||||
} | ||||
parsebodyarray.push(parsedBody); | ||||
} |
// | gs.addInfoMessage(parsebodyarray.toString()); | ||||
//gs.addInfoMessage(parsebodyarray.length); | |||||
return parsebodyarray; | |||||
}, |
Can you please let me know what is wrong here
Regards,
Vidya
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎05-17-2017 05:23 AM
Hi Srin,
One quick question. Have you tried to extend the PdfPageEventHelper class of Java in ServiceNow. This class has OnstartPage and OnEndPage methods for header footer. Am having issues in extending this class.
I referred to Walter's code too in which he extends this class but was not successfully in it.
Code used:
I extended the class at the End of GeneralPDF class as below
GeneralPDF.prototype.PDFTemplate = Object.extendsObject(GeneralFormJava.PdfPageEventHelper, GeneralPDF.prototype.pdfTemplate); or GeneralPDF.prototype.PDFTemplate = new JavaAdapter( iTextPDFUtil.PdfPageEventHelper, GeneralPDF.prototype.pdfTemplate); Both the approaches didnt work. Kindly assist on this incase you have tried it. Regards, Vidya |
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-27-2017 11:40 AM
Hi Syed,
I have been exploring on Page Breaks to be added in GeneralPDF script include.
I have almost narrowed down on where to apply page break.
After these lines of code within addHTML() function in GeneralPDF we have to apply page break
for ( var i = 0; i < objects.size(); i += 1) {
var element = objects.get(i);
this.document.addParagraph(element, this.BaseFnt);
this.document.newPage();
}
But unfortunately this line "this.document.newPage();" is not able to insert pageBreak.
I have tried the same using a sample ItextPDF java code with same kind of code and the document.newPage() worked perfectly fine. Not sure why it is not working in ServiceNow.
Any suggestions/assistance is much appreciated. Looking forward for postive response. Thanks
Regards,
Vidya