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

Screen Shot 2018-03-23 at 6.32.51 PM

Step 2: In your Skuid page, add a Wrapper component around all the components you wish to generate as PDF

Screen Shot 2018-10-02 at 12.40.58 PM

Step 3: In the Wrapper’s Advanced properties, rename the Unique Id to “pdf-export”

Screen Shot 2018-03-23 at 7.38.03 PM

Step 4: In your Skuid page, add an External Javascript Resource

Screen Shot 2018-10-02 at 12.47.21 PM.png

Step 5: Add an In-Line Javascript Resource

  • Resource Location: In-Line (Snippet)
  • Resource URL: docRaptorGeneratePDFSnippet

Screen Shot 2018-10-02 at 12.45.13 PM

  • 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

Screen Shot 2018-08-22 at 1.23.04 PM

  • 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)

Screen Shot 2018-10-02 at 10.46.10 AM

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.

Screen Shot 2018-10-02 at 10.46.25 AM

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).

Screen Shot 2018-10-02 at 12.25.24 PM

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.