Tuesday, November 18, 2014

Generate a note with a PDF attachment from a CRM report

Hi guys,

it's about time to make another entry on my blog.
I have been struggling for almost 2 days to get this to work and I finally got it.

A customer of ours had a requirement to generate a report to PDF, save the report to an entity and afterwards, make a mail with the attachment.

This blog is about the first part: generating the report and save it as a note attachment.

First I Googled for a starting point. I found this one:
http://xrmmatrix.blogspot.sk/2011/06/creating-report-as-pdf-attachment-in.html

This solution made use of a html web resource. In short: you put the code in a html web resource, and place the web resource on the form. The solution shows a button to trigger the generation of the report and also makes use of an unsupported VB script.

I wanted to do the same, but without the button, without the VB script and during the OnSave of the form.

There were a couple of problems to start with: the VB script needed to be converted to JavaScript and has to work in most modern browsers (IE, Chrome, ...)

Since I'm still pretty new to CRM and web development, I'm sure there are still a couple of things that can be done better. For instance: now I provide the report name and report id hardcoded in the script. I want to modify this a bit so I only have to provide the report name and the id is retrieved from the system.

Another thing I'm not sure about: to make it work in most browsers I detect if the script is being run in Internet Explorer or something else. In IE we can use responseBody from the XMLHttpRequest. In other browsers there is no responeBody propertie for the XMLHttpRequest. In the IE method I'm making use of the VBArray function to convert the response, but I'm not 100% sure if this is comptable with every IE version.

Anyway, long story short, here is my modified code. Enjoy:

function form_onsave () {
var isDirty = Xrm.Page.getAttribute("new_status").getIsDirty();
if(isDirty && Xrm.Page.getAttribute("new_status").getValue() == 1) {
// If Status is changed to Accepted, then create a note with attached a pdf of a report
createAttachment();
}
}
function getReportingSession() {
var reportName = "Guaranty Certificate"; //set this to the report you are trying to download
var reportId = "3AAF3865-5D6F-E411-80C0-001DD8B71EDF"; //set this to the guid of the report you are trying to download
var rptPathString = ""; //set this to the CRMF_Filtered parameter
var pth = Xrm.Page.context.getClientUrl() + "/CRMReports/rsviewer/reportviewer.aspx";
var retrieveEntityReq = new XMLHttpRequest();
retrieveEntityReq.open("POST", pth, false);
retrieveEntityReq.setRequestHeader("Accept", "*/*");
retrieveEntityReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var strParameterXML = "<ReportFilter><ReportEntity paramname=\"CRM_FilteredContract\" displayname=\"Guaranties\" donotconvert=\"1\">" +
"<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" distinct=\"false\">"+
"<entity name=\"contract\"><all-attributes/><filter type=\"and\">" +
"<condition attribute=\"contractid\" operator=\"eq\" uitype=\"contract\" " +
"value=\"" + Xrm.Page.data.entity.getId() + "\"/></filter></entity></fetch></ReportEntity></ReportFilter>";
rptPathString = "id=%7B" + reportId +
"%7D&uniquename=" + Xrm.Page.context.getOrgUniqueName() +
"&iscustomreport=true&reportnameonsrs=&reportName=" + reportName + "&isScheduledReport=false" +
"&CRM_Filter=" + encodeURI(strParameterXML);
retrieveEntityReq.send(rptPathString);
var x = retrieveEntityReq.responseText.indexOf("ReportSession=");
var ret = new Array();
//ret[0] = retrieveEntityReq.responseText.substr(x + 14, retrieveEntityReq.responseText.indexOf("&", x) - x - 14); //the session id
ret[0] = retrieveEntityReq.responseText.substr(x + 14, retrieveEntityReq.responseText.indexOf("\\u0026", x) - x - 14); //the session id
x = retrieveEntityReq.responseText.indexOf("ControlID=");
//ret[1] = retrieveEntityReq.responseText.substr(x + 10, retrieveEntityReq.responseText.indexOf("&", x) - x - 10); //the control id
ret[1] = retrieveEntityReq.responseText.substr(x + 10, retrieveEntityReq.responseText.indexOf("\\u0026", x) - x - 10); //the control id
return ret;
}
function createEntity(ent, entName, upd) {
var jsonEntity = JSON.stringify(ent);
var createEntityReq = new XMLHttpRequest();
var ODataPath = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
createEntityReq.open("POST", ODataPath + "/" + entName + "Set" + upd, false);
createEntityReq.setRequestHeader("Accept", "application/json");
createEntityReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
createEntityReq.send(jsonEntity);
var newEntity = JSON.parse(createEntityReq.responseText).d;
return newEntity;
}
function createAttachment() {
var params = getReportingSession();
if(msieversion() >= 1){
encodePdf_IEOnly(params);
} else {
encodePdf(params);
}
}
var StringMaker = function () {
this.parts = [];
this.length = 0;
this.append = function (s) {
this.parts.push(s);
this.length += s.length;
}
this.prepend = function (s) {
this.parts.unshift(s);
this.length += s.length;
}
this.toString = function () {
return this.parts.join('');
}
}
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function encode64(input) {
var output = new StringMaker();
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
while (i < input.length) {
chr1 = input[i++];
chr2 = input[i++];
chr3 = input[i++];
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output.append(keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4));
}
return output.toString();
}
function encodePdf_IEOnly(params) {
var bdy = new Array();
var retrieveEntityReq = new XMLHttpRequest();
var pth = Xrm.Page.context.getClientUrl() + "/Reserved.ReportViewerWebControl.axd?ReportSession=" + params[0] + "&Culture=1033&CultureOverrides=True&UICulture=1033&UICultureOverrides=True&ReportStack=1&ControlID=" + params[1] + "&OpType=Export&FileName=Public&ContentDisposition=OnlyHtmlInline&Format=PDF";
retrieveEntityReq.open("GET", pth, false);
retrieveEntityReq.setRequestHeader("Accept", "*/*");
retrieveEntityReq.send();
bdy = new VBArray(retrieveEntityReq.responseBody).toArray(); // minimum IE9 required
createNotesAttachment(encode64(bdy));
}
function encodePdf(params) {
var xhr = new XMLHttpRequest();
var pth = Xrm.Page.context.getClientUrl() + "/Reserved.ReportViewerWebControl.axd?ReportSession=" + params[0] + "&Culture=1033&CultureOverrides=True&UICulture=1033&UICultureOverrides=True&ReportStack=1&ControlID=" + params[1] + "&OpType=Export&FileName=Public&ContentDisposition=OnlyHtmlInline&Format=PDF";
xhr.open('GET', pth, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
if (this.status == 200) {
var uInt8Array = new Uint8Array(this.response);
base64 = encode64(uInt8Array);
createNotesAttachment(base64);
}
};
xhr.send();
}
function msieversion() {
var ua = window.navigator.userAgent;
var msie = ua.indexOf("MSIE ");
if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) // If Internet Explorer, return version number
return parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)));
else // If another browser, return 0
return 0;
}
function createNotesAttachment(base64data){
var post = Object();
post.DocumentBody = base64data;
post.Subject = "File Attachment";
post.FileName = "GuarantyCertificate.pdf";
post.MimeType = "application/pdf";
post.ObjectId = Object();
post.ObjectId.LogicalName = Xrm.Page.data.entity.getEntityName();
post.ObjectId.Id = Xrm.Page.data.entity.getId();
createEntity(post, "Annotation", "");
}
view raw generatepdf.js hosted with ❤ by GitHub