Idea: Page to Print, Page to PDF, Page to Email, Page to Content actions

  • 15
  • Idea
  • Updated 8 months ago
  • Under Consideration
I'm sure there are bigger issues the Skuid team is tackling, but I'll continue to post my "practical wish list":
It would be neat if there was an action that would convert a Skuid page to PDF/HTML and allow it to be presented for printing, uploaded into content like the Skuid upload component does, or sent through email. Then you could build custom email templates right in Skuid that leverage Skuid models, UI fields, etc...
If you build email templates in Salesforce, I can't easily get.... Say a table of my top 5 opportunities that all begin with the letter j and have addresses in Alaska with summary totals of vaious fields displayed in decorative boxes at the top of the template and headers and footers that match my theme.
If I could build email/PDF templates in Skuid as regular pages, I could rule the world (I may be exaggerating)
Photo of Skuidward Tentacles (Raymond)

Skuidward Tentacles (Raymond), Champion

  • 17,224 Points 10k badge 2x thumb

Posted 3 years ago

  • 15
Photo of Rob Hatch

Rob Hatch, Official Rep

  • 44,006 Points 20k badge 2x thumb
Yes.  Often discussed.  But every time our devs point out a big bundle of complexity that would have to be resolved.  Definitely on our roadmap, definitely not in the immedate future. 
Photo of Skuidward Tentacles (Raymond)

Skuidward Tentacles (Raymond), Champion

  • 17,224 Points 10k badge 2x thumb
As I have said before, it is a lot easier to have the ideas than to build them. I'll keep having ideas. You guys do the hard part! ;)
Photo of Mansour

Mansour

  • 1,932 Points 1k badge 2x thumb
We've been able to achieve this using DocRaptor
http://docraptor.com/
Photo of Skuidward Tentacles (Raymond)

Skuidward Tentacles (Raymond), Champion

  • 17,224 Points 10k badge 2x thumb
Awesome! I'll give it a look. Thanks Mansour
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
We are taking a look at Doc Raptor thanks to this thread.  Can anyone tell me the basics on how to do it via Skuid?
Photo of Henry Goddard

Henry Goddard

  • 776 Points 500 badge 2x thumb
Hi Folks,

The solution Mansour is referring to goes a little something like this:

1) Sign up for Docraptor and get your API Key


2) Wrap what you're interested in turning a PDF using the 'Wrapper' component and give it a unique id.


3) Add a custom Inline Javascript snippet that we'll attach to a button (or some other trigger for that matter)


var params = arguments[0],	$ = skuid.$;

var downloadPDF = function(url, data, method){
  //url and data options required
  if( url && data ){
    $('<form style="display: none" id="dr_submission" action="' + url
           + '" method="' + (method||'post') + '">'
           + '</form>').appendTo('body');
    //credentials
    $('form#dr_submission').append('<textarea name="user_credentials"></textarea>');
    $('form#dr_submission textarea[name=user_credentials]').val(data.user_credentials);

    //doc values
    for(var key in data.doc) {
      $('form#dr_submission').append('<textarea name="doc['+key+']"></textarea>');
      $('form#dr_submission textarea[name="doc['+key+']"]').val(data.doc[key]);
    }

    //submit the form
    if(confirm("Submit Form")) {$('form#dr_submission').submit().remove(); }
  };
};

// setup the string represeting the html we want to submit
var content = $('#pdf-export').html();


var data = {
  doc: {
    test: true,
    document_type: 'pdf',
    name: 'test.pdf',
    document_content: content,
    strict: 'none'
  },
  user_credentials: "<DOCRAPTOR API KEY>"
};


// this drops a form on the page and submits, which will result in a download dialog popping up
downloadPDF("https://docraptor.com/docs";, data);
4) Run Multiple actions on button click, and trigger the snippet
 

5) Preview the page, click your new button and you should be prompted to download the pdf.

It will likely take a few tries to get the page looking the way you'd like, so keep at it. If the styles aren't showing up, it means that Docraptor can't access the .css and you'll have to inject the stylesheet directly into the 'content' variable you send over. Fortunately you can set 'test: true,' (See the bolded above) and not incur an costs while you tweak it.
Photo of Daniel Camp

Daniel Camp

  • 80 Points 75 badge 2x thumb
Henry,
Thank you very much! Have it working.

FYI - I had to chnage the last line of code to:
downloadPDF("https://docraptor.com/docs", data);
That semicolon in there was throwing an error.

Question - had any success attaching the prodcued pdf back to a record in salesfroce (easily/automatically)?

Thank you,
Daniel
Photo of Henry Goddard

Henry Goddard

  • 776 Points 500 badge 2x thumb
Hi Chandra,

You can inject the css from a different location with an 'Inline' Javascript snippet like this:

(function(skuid){
	var $ = skuid.$;
	$(document.body).one('pageload',function(){
     //Change the static resource to the org's static resource location TODO: Dynamically set this 
            $.when($.get("/resource/###############/<THEME_NAME>/skuidtheme.css"))
            .done(function(response) {
                // Create style tag to inject
                var style = $('<style />');
                style.append(response);
                //inject it into the page
                style.appendTo($('#pdf-export'));
            });
	});
})(skuid);
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
Henry - this worked perfectly.  Thank so much!  Please tell me you are going to Dreamforce or something, and I will buy you a beer or 12.  

Last question (haha).... I have a record detail page and want my user to click the "print doc" button.  The document layout (in my pdf wrapper) is not the same as the detail page.  I don't want the user to see the wrapper/pdf but if I hide it with conditional rendering, then the pdf ends up blank.  What do you do with your wrapper/pdf on your detail page so it loads on your page, but the users don't see it?
Photo of Henry Goddard

Henry Goddard

  • 776 Points 500 badge 2x thumb
Chandra if I find myself in San Fransisco for Dreamforce I'll certainly take you up on the beer offering!

As for hiding your print button, you can either put the button outside of the wrapper of id 'pdf-export' or do the following.

1) Create a UI only checkbox called 'hideprintbutton' or something like that.
2) Put a render condition on the button on the 'hideprintbutton' being true.
3) Then perform the following 'multiple actions' on the button click:



We currently perform the latter option and it works very nicely. Hope this does the trick for you too!
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
Worked again!!  :)))
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
New problem - page loading in the skuidtheme.css has caused this portion of the Theme to apply to my whole page, so that means my pages are missing the skuidicon.css portion of the Theme.  So no icons on my page.  :(

I moved this problem to a more detail new thread here:
https://community.skuid.com/skuid/topics/embed-css-into-wrapper-component-page?rfm=1&topic_submi...
Photo of Griffin Brown

Griffin Brown

  • 2,766 Points 2k badge 2x thumb
#Mansourisagenius
Photo of Jack Sanford

Jack Sanford, Champion

  • 8,322 Points 5k badge 2x thumb
so, yeah #Mansourisagenius. DocRaptor is pretty amazing too. I've been implementing this today, and their tech support said that the above snippet I was using was old, that they've got a JS library now, so it's better to reference that. 

So, step 1, create an external snippet with this url: http://docraptor.com/docraptor-1.0.0.js
(or download that and upload as a static resource)

step 2, create an new snippet, remove all the standard code skuid adds in, then use this:
function downloadPDF() {
      DocRaptor.createAndDownloadDoc("YOUR_API_KEY_HERE", {
        
        test: true,
        document_type: 'pdf',
        name: 'test.pdf',
        document_content: document.querySelector('#pdf-export').innerHTML,
        strict: 'none',
        javascript: 'true',
        prince_options: {
// if you want relative links to work
            baseurl: 'YOUR_SALESFORCE_BASE_URL'
        }
      });
}
	
downloadPDF();
Everything else about the inlineJS to append your them to the wrapper is key, as is naming your wrapper with a unique id "pdf-export". 

also, in the inlineJS to append the theme css, you can add extra styles. for example, I had trouble adding the docrapter-specific @page selector to my theme (that controls landscape vs. portrait, and other page sizes), so I added it directly in the inlineJS, as such:
 var style2 = $('<style> @page { size : US-Legal landscape } </style>');
                style2.appendTo($('#pdf-export'));
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
Great stuff!  We have several doc raptor docs incorporated into our skuid processes.  I did a new one this morning using the new DocRaptor library.  Worked great!

Thanks for the update on this!
Photo of Jack Sanford

Jack Sanford, Champion

  • 8,322 Points 5k badge 2x thumb
You're welcome Chandra! Thank YOU for all the formatting tips you shared. Any chance you could share the apex code you used to grab the document that DocRaptor created and attach it back to a record in Salesforce? Did you have to run the async call to DocRaptor and get the callback_url?
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
ok, here we go.  First, the apex class which is generic and will attach to any record id that you pass it.  So we use this class for lots of objects.
/*---=============================================================================
---       Program Name          : AFLattachAsPDF
---
---       Program Description   : This APEX class attaches pdf documents from given url
---                               to the record specified.
---                          
---       Date Written          :  25-Jun-2016
---=============================================================================*/
global class AFLattachAsPDF{
    webservice static void attachAsPDF(string downloadURL, ID theId, string theName){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        string theURL = downloadURL;
        req.setEndpoint(theURL);
        req.setMethod('GET');
        //another example would be 'image/jpeg'
        req.setHeader('Content-Type', 'application/pdf');
        req.setCompressed(true);
        req.setTimeout(60000);             
        HttpResponse res = null;
        res = h.send(req);
        //next three lines for dealing with error situations
        //string responseValue = '';
        //responseValue = res.getBody();
        //system.debug('Response Body for File: ' + responseValue);
        blob thePDF = res.getBodyAsBlob();
        
        Attachment attachment = new Attachment();
        attachment.Body = thePDF;
        attachment.Name = theName + '.pdf';
        attachment.ContentType = 'application/pdf';
        attachment.ParentId = theId;
      insert attachment;
  }
}



Then, the Skuid snippet that runs.  It has the DocRaptor call, brings in our special custom Skuid theme that we use just for documents, polls docraptor checking for completion of the pdf, creates a document name and passes all the info to the apex class (the url exposed by DocRaptor, the Id to attach the pdf to and the document name you want.)

This example is from our Quote page where the user clicks a button which calls the document skuid page in a pop up window, and this snippet runs on page load.  Once the document is done, the window closes and poof the pdf is attached to the quote.

var params = arguments[0],
$ = skuid.$;

var quoteModel = skuid.model.getModel('quote');
var quoteRow = quoteModel.getFirstRow();
var quoteNum = quoteModel.getFieldValue(quoteRow,'Name');
var quoteRev = quoteModel.getFieldValue(quoteRow, 'Quote_Revision__c');
var docName = quoteNum + 'R' + quoteRev;
var docURL;

// pull in the Skuid theme to apply to the page
$.get("/resource/yourthemepath/yourthemename/skuidtheme.css",function(response){
   // Create style tag to inject
   var style = $('<style />');
   style.append(response);
   // apply a custom border to a dynamically created table
   style.append('table, th, td {border: 1px solid black;border-collapse: collapse;padding: 1px;text-align: left;}thead {background-color: #000000;color: #FFFFFF;}');

   //inject it into the page
   style.appendTo($('#pdf-export'));
   
    function checkDocumentStatus(id){ 
           $('.status span').html('Polling...'); 
           $.ajax({ 
             type: 'GET', 
             url: 'https://docraptor.com/status/'+id
             dataType: 'JSON', 
             beforeSend: function(req) { 
                 req.setRequestHeader('Authorization', 'Basic ' + btoa('YOUR ACCESS CODE')); 
             }, 
             success: function(data){ 
               if(data.status == 'queued' || data.status == "working"){ 
                 // wait 1 second, check again 
                 setTimeout(checkDocumentStatus, 1000, id); 
               }else if(data.status == 'completed'){ 
                 $('.status span').html('Completed'); 
                 $('.url span').html(data.download_url);
                 docURL = data.download_url;
                 try{
                    var attName = quoteModel.getFieldValue(quoteRow, 'Name') + 'R' + quoteModel.getFieldValue(quoteRow, 'Quote_Revision__c');
                    var result = sforce.apex.execute('AFLattachAsPDF', 'attachAsPDF', {downloadURL: docURL, theId: quoteModel.getFieldValue(quoteRow, 'Id'), theName: attName});
                    skuid.model.updateData([quoteModel],function(){
                          quoteModel.save();
                    });
                   
                    window.close();
                    
                }catch(e){
                    alert(e);
                }
                 //window.prompt('Download URL', data.download_url); 
               }else{ 
                 $('.status span').html(data.status);
               } 
             } 
           });
         }
         
    
    function makeDocument(content){ 
           $('.status span').html('Requested'); 
      
           content = content || '<html></html>'; 
      
           $.ajax({ 
             type: 'POST', 
             url: 'https://docraptor.com/docs', 
             data: JSON.stringify({ 
               'type': 'pdf', 
               'document_content': content, 
               'name': docName, 
               //'test': true, 
               'async': true,
               //'pipeline': 6,
               'prince_options': {
                    'media': 'print',
                    'owner_password': quoteNum,
                    'disallow_modify': true,
                    'encrypt': true,
                    'key_bits': '128'
               } 
             }), 
             contentType: 'application/json', 
             dataType: 'json', 
             beforeSend: function(req) { 
                 req.setRequestHeader('Authorization', 'Basic ' + btoa('YOUR ACCESS CODE')); 
             }, 
             // upon success of the ajax request 
             success: function(data){ 
               $('.status span').html('request made'); 
               checkDocumentStatus(data.status_id); 
             } 
           }); 
         }
    
    
    //----------------------------------------------------------------------------------------------------
    
    // setup the string represeting the html we want to submit
    var content = $('#pdf-export').html();
    
    makeDocument(content);

});
Photo of Skuidward Tentacles (Raymond)

Skuidward Tentacles (Raymond), Champion

  • 17,224 Points 10k badge 2x thumb
Wow! Awesome... thanks for sharing!
Photo of James Paden

James Paden

  • 170 Points 100 badge 2x thumb
Hi everyone, I'm a member of the DocRaptor support team and just stumbled across this. Thanks for sharing this info, Henry. If anyone needs assistance with DocRaptor, please don't hesitate to reply to me here or contact support@docraptor.com. Thanks!
Photo of Jack Sanford

Jack Sanford, Champion

  • 8,322 Points 5k badge 2x thumb
Hi James, thanks for finding us. I was just reading a bit about Domains on the DocRaptor site. Seems like this could make implementation within skuid even easier, just create a link on a skuid page with the right query parameters. Also keeps it more secure because you don't have to put your api key into a js snippet. 

However, i've been unable to get this to work with some minor tests. I was wondering if, given some of the examples people have shown above, you could give us an expanded example of how to use the domains feature?

I've already gone through the documentation here: https://docraptor.com/documentation/api#referrer_based 
(Edited)
Photo of Chandra V

Chandra V, Champion

  • 6,966 Points 5k badge 2x thumb
Jack - we saw this as well, although our stuff is on auto-pilot so we did not go back and update it.  The only change we made since my old posts is to have the apex insert the returned pdf as a File instead of an attachment, since we have now moved our org off of notes and attachments.
Photo of James Paden

James Paden

  • 170 Points 100 badge 2x thumb
Jack, could you send support@docraptor.com your code and what URL the code is being tested on? 

The examples included on the documentation page already are as advanced as the code gets, from a code perspective. It's mainly a matter of troubleshooting any domain vs referrer problems (not all websites all the sending of referrers, for example).
Photo of James Paden

James Paden

  • 170 Points 100 badge 2x thumb
I wanted to follow up on this topic as I now have a much better understanding of how Skuid works. I don't think DocRaptor's Referrer Based system will work for Skuid users because it requires that the page to be converted by public (not secured behind a login and password). I'm working on better documenting the process for adding DocRaptor to your Skuid app and hope to have a streamlined guide for that shortly (mainly drawn from Chandra's code above).