Before we start, you need a DocRaptor account…
Go here to get a DocRaptor Free Trial and sign up for a 7-Day FREE Trial.
…and if you somehow landed here without having a Skuid account…
Hop on over to the Skuid Free Trial page to get a 14-Day FREE Trial and start building beautiful custom enterprise pages at blazing fast speed on your data platform of choice (e.g. Salesforce, Microsoft, AWS, Oracle, SAP, SQL, etc.)
Step 1: In DocRaptor, get your API Key
Step 2: In your Skuid page, add a Wrapper component around all the components you wish to generate as PDF
Step 3: In the Wrapper’s Advanced properties, rename the Unique Id to “pdf-export”
Step 4: In your Skuid page, add an External Javascript Resource
- Resource Location: External
- Resource URL: http://docraptor.com/docraptor-1.0.0.js
- ResourceName (optional): can be anything you like
Step 5: Add an In-Line Javascript Resource
- Resource Location: In-Line (Snippet)
- Resource URL: docRaptorGeneratePDFSnippet
- Snippet Body: remove the default code, and paste in the code below; I have highlighted the parts in yellow that you will need to modify for your own setup.
- API KEY –> copy & paste your API Key from Step 1
- document.querySelector(‘#pdf-export’) –> make sure this is the same name as your Wrapper’s Unique Id from Step 3
// JavaScript to massage Skuid pages to better work with DocRpator generateDocRaptorPDF("#pdf-export"); function generateDocRaptorPDF(targetContent) { var $ = skuid.$; var tempImageClass = "temporary-docraptor-image"; // ** DOCUMENT CREATION **/ var tempElement = createTemporaryContentElement(); convertCanvasElementsToImages(); cloneContent(); cleanupCanvasElementsAndImages(); replicateHTMLStructure(); resizeHighcarts(); cloneStyleTags(); cloneExternalLinks(); var link_requests = internalLinkRequests(); // Process all the internal link requests, then make the PDF runAllPromises(link_requests).then(function(results){ DocRaptor.createAndDownloadDoc("YOUR_API_KEY_HERE", { test: true, document_content: tempElement.html(), document_type: 'pdf', name: 'test.pdf', javascript: 'false', prince_options: { // Makes relative links and asset URLs work baseurl: document.location.origin, } }); }); tempElement.remove(); // HELPER FUNCTONS function createTemporaryContentElement() { // We must add a temporary element to the DOM for jQuery wrapping to work // return $("<div style='display: none;' />").appendTo("body"); return $("<div />").appendTo("body"); } function convertCanvasElementsToImages() { // DocRaptor does not support canvas elements, so convert them to PNGs. // This must run before cloneContent() as canvas elements aren't // cloned by jQuery. $(targetContent).find("canvas").each(function(){ var imageElement = $("<img />") .addClass(tempImageClass) .width($(this).width()) .hide() .attr("src", $(this).get(0).toDataURL("image/png")); imageElement.insertBefore($(this)); }); } function cloneContent() { // Clone the desired page content and append to temp element tempElement.append( $(targetContent).clone() ); } function cleanupCanvasElementsAndImages() { // Remove the hidden images from the actual DOM $(targetContent).find("." + tempImageClass).remove(); // Remove the canvas elements because they're not support by DocRaptor tempElement.find("canvas").remove(); // Show the images we converted from the canvas elements tempElement.find("." + tempImageClass).show(); } function replicateHTMLStructure() { // Replicate the HTML stucture (all the parents above our target content) // so CSS selector rules continue to work containerClass = "docraptor-container-for-css-override"; $(targetContent).parentsUntil("html").each(function(){ tempElement.wrapInner($(this).clone().empty().addClass(containerClass) ); }); // We want the selectors for their impact on the target content, but // we don't want them to impact our document styling or positioning, // so let's try to negate any styling they may have tempElement.prepend( $('<style> .' + containerClass + '{ width: auto !important; padding: 0 !important; margin: 0 !important; height: auto !important; display: block !important; } </style>') ); } function resizeHighcarts() { // Highcharts are not responsive, so we add some CSS to allow // them to resize to fit the PDF size tempElement.find("svg.highcharts-root") .removeAttr("height") .removeAttr("width") .width("100%"); tempElement.find(".highcharts-container, .sk-chart-container, .sk-chart-wrapper") .width("auto") .height("auto") .css("position", "static"); } function cloneStyleTags() { tempElement.prepend( $("style").clone() ); } function cloneExternalLinks() { // External link elements are unlikely to require authentication, // so we'll simply clone then tempElement.prepend( $("link[href*='css']:not([href^='" + getTLD() + "/']):not([href^='/'])").clone() ); } function internalLinkRequests() { // CSS files hosted by Skuid likely require authentication, so we'll try to // download the files and include the CSS in a style element var requests = []; local_css = $("link[href*='css'][href*='" + getTLD() + "/']").add("link[href*='css'][href^='/']"); local_css.each(function() { console.log($(this).attr("href")); requests.push( $.ajax({ url: $(this).attr("href"), context: this, success: function(response) { tempElement.prepend( $('<style />').append(response) ); }, error: function(response) { // can't load via AJAX, likely due to CORS, so fallback to the original link tempElement.prepend( $(this).clone() ); } }) ); }); return requests; } // Keeps processing promise array even if failure occurs // From https://stackoverflow.com/a/23625847/2422416 function runAllPromises(promises){ var d = $.Deferred(), results = []; var remaining = promises.length; for(var i = 0; i < promises.length; i++){ promises[i].then(function(res){ results.push(res); // on success, add to results }).always(function(res){ remaining--; // always mark as finished if(!remaining) d.resolve(results); }) } if (promises.length == 0) { d.resolve(results); } return d.promise(); // return a promise on the remaining values } function getTLD() { // Gets the rootdomain.com part of the URL. This is a very brittle // regex, but as far as I'm aware, all this code runs on Force.com. return (location.host.match(/([^.]+\.\w{2,3}(?:\.\w{2})?)$/) || [])[1]; } }
Step 6: Add a Button to your Skuid page
- Button Type: Custom: Run Skuid Snippet
- Button Label: whatever you prefer
- Button Icon: I chose but whatever you prefer
- Snippet Name: select in-line snippet from Step 5 (docRaptorGeneratePDFSnippet)
Step 7: Save your Skuid Page and Preview
Additional Considerations
Hide Button during PDF Generation: Your PDF may generate with the button included. To hide the button during PDF generation, change over your button type to “Run Multiple Actions”, and include 2 Toggle Component actions, one before the Run Skuid Snippet action to hide the button set and one after to display the button set again after the Snippet executes.
Images: Any images you wish to include on your PDF (via an image component) must be accessible to the public internet in order for DocRaptor to generate them into the PDF. Therefore, you must use an image source of URL (no static resources or attachments in Salesforce) for your image component and reference an external URL (outside of Salesforce).
Electronic Signatures via jSignature: Electronic signatures can be incorporated into your PDF pages using the sample javascript included above. First, be sure to check out how to build an electronic signature into your Skuid page by clicking here.