Forum Discussion

TylerBrezler's avatar
TylerBrezler
Qrew Cadet
3 years ago

Automatically save a form page as PDF and insert into attachment field?

I've long wondered if there's a solution that's shareable where I could click a button and have a form page automatically saved in PDF format and inserted into an attachment field.  I realize that there are QSPs that provide this service, but I'm more interested in knowing/understanding how to do this myself.

My impression is that you would need to use JS to write the form to PDF and then encode to base64, and then you could use the base64 result to insert into API_UploadFile.  My JS knowledge is minimal but I can cobble stuff together through StackExchange plus trial and error.  I'm not worried about the API end of things - that seems pretty straight-forward.

Has anyone tried this themselves that they'd be willing to share? I've looked on the forum before but it seems the answers always fall into, "Hire a QSP," or "Use IOL or some other technique that is now deprecated."  Presumably I'd need to call the JS from a code page?  Or would I need a local API call on my end to launch something?  If it's possible to do everything in QB without having to host something myself, that's obviously the preferable and most portable method, but if that's 100% not possible, I have access to AWS.

Big picture goal is the user clicks a button, button calls a script that runs through about 20-30 records in a table, uses a specific QB form page to generate a PDF for each record, PDF is uploaded into corresponding record attachment field.  No templates or anything.

Short of that, clicking the button once per record would also be acceptable, but it would be rad to grab the whole table in one go.
  • Hey Tyler, 

    I messaged you yesterday and was able to clean it up this morning. This should work for clicking a button and having it save to the original record. I was thinking of working on an idea to loop through and have it save all the records in a table, but haven't gotten to it yet.

    Here is the button, or rich text in my case, formula needed:

    var text usertoken = "enter_user_token_here";
    
    // Enter the form ID to save in myFormID
    var text myReq = "&myDbid=" & Dbid() & "&recId=" & [Record ID#] & "&myFormId=10&usertoken=" & $usertoken;
    
    var text apptoken = "app_token_here";
    
    var text myURL = URLRoot() & "db/" & Dbid() & "?a=API_GetRecordAsHTML&rid=" & [Record ID#] & "dfid=10&apptoken=" & $apptoken;
    
    var text url = URLRoot() & "db/" & AppID() & "?a=dbpage&pageID=10" & "&apptoken=" & $apptoken & $myReq;
    
    
    
    
    "<a class='OpenAsPopup' href=" & $url  & ">Print as HTML</a>"​


    Here is the html page:

    <html>
        <head>
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.js"></script>
        </head>
    
        <body>
            <h1>Record as HTML</h1>
                <p> A Quickbase table embedded in a page of HTML</p>
            <div id="dbPagePayload"></div>
        
    
    
        </body>
        <script src="https://yourRealm.quickbase.com/db/yourAppID?a=dbpage&pageID=YourJSPageID"></script> 
    </html>


    Here is the javascript page:

    let urlParams = new URLSearchParams(window.location.search);
    let dbid = urlParams.get("myDbid");
    let recId = urlParams.get('recId');  
    let myForm = urlParams.get('myFormId');
    let usertoken = urlParams.get('usertoken');
    let apptoken = urlParams.get('apptoken');  
    let root = location.protocol + '//' + location.host;
    let myURL = `${root}/db/${dbid}?a=API_GetRecordAsHTML&rid=${recId}&dfid=${myForm}&apptoken=${apptoken}`;
    let myHostName = "EnterHostName.quickbase.com";
    
    const getURL = () => {
        $.post(myURL, {}, function (response) {
            $("#dbPagePayload").html(response);
        }).then(getDom);
    };
    
    
    
    const getDom = () => {
        let myDoc = document.getElementById("dbFormContainer");
        console.log(myDoc);
        var opt = {
            margin: 1,
            filename: 'myfile.pdf',
            image: { type: 'jpeg', quality: 0.98 },
            html2canvas: { scale: 2 },
            jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
        };
    
        html2pdf().from(myDoc).set(opt).outputPdf('dataurlstring').then(async res => {
          var base64result = res.split(',')[1];
                
          var headers = {
              'QB-Realm-Hostname': myHostName,
              'User-Agent': 'File_Upload',
              'Authorization': `QB-USER-TOKEN ${usertoken}`,
              'Content-Type': 'application/json'
          }
    
    
          // 59 is my attachment field. Replace it with yours.
          body = { "to": dbid, "data": [{"3": {"value": recId}, "59": { "value": { "fileName": "FormPDF.pdf", "data": base64result }}  }], "fieldsToReturn": [ 3, 59 ] }
    
    
          fetch('https://api.quickbase.com/v1/records',
            {
              method: 'POST',
              headers: headers,
              body: JSON.stringify(body)
            })
          .then(async res => {
            if (res.ok) {
              return res.json().then(res => {
                console.log(res)
                window.close()
              });
            }
            return res.json().then(resBody => Promise.reject({status: res.status, ...resBody}));
          })
        .catch(err => console.log(err))
      });
    };
    getURL();
    

    I can admit it is not the prettiest, nor the most optimized solution. But, it works and should get the job done. I will probably work on this in my free time later this week/weekend to have it loop through each record, so I will update this thread when/if I finish.

    ------------------------------
    Sean Connaughton
    ------------------------------
    • TylerBrezler's avatar
      TylerBrezler
      Qrew Cadet
      Very cool! Thanks for working on this, Sean. I'm going to try this out as soon as I get the chance.
      • SeanConnaughto1's avatar
        SeanConnaughto1
        Qrew Cadet
        Like I said before, this isn't the prettiest, but it worked for me! This should loop through and save each form within the range you enter in the popup.

        Formula Rich-Text Field:
        var text usertoken = "enter_user_token_here";
        
        // Enter the form ID to save in myFormID
        var text myReq = "&myDbid=" & Dbid() & "&recId=" & [Record ID#] & "&myFormId=10&usertoken=" & $usertoken;
        
        var text apptoken = "app_token_here";
        
        var text url = URLRoot() & "db/" & AppID() & "?a=dbpage&pageID=10" & "&apptoken=" & $apptoken & $myReq;
        
        "<a class='OpenAsPopup' href='" & $url & "'data-height=600 data-width=600>Print as HTML</a>"​

        HTML:
        <html>
            <head>
                <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
                <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.js"></script>
            </head>
        
            <body>
                <h1>Saving Forms as PDF</h1>
                    <p>Records will populate below when completed.</p>
                    <p>Depending on the size, it may take longer than expected to start. Check the console for more details.</p>
                <div id="dbPagePayload"></div>
            
        
        
            </body>
            <script src="https://yourRealm.quickbase.com/db/yourAppID?a=dbpage&pageID=YourJSPageID"></script> 
        </html>​

        JavaScript Page:
        let urlParams = new URLSearchParams(window.location.search);
        let lowRec = prompt("Enter the low Record ID#");
        let highRec = prompt("Enter the high Record ID#");
        let countLength = 0;
        let dbid = urlParams.get("myDbid");
        let myForm = urlParams.get('myFormId');
        let usertoken = urlParams.get('usertoken');
        let apptoken = urlParams.get('apptoken');  
        let root = location.protocol + '//' + location.host;
        const myHostName = "EnterHostName.quickbase.com";
        
        const myDiv = document.getElementById('dbPagePayload');
        var headers = {
          	'QB-Realm-Hostname': myHostName,
        	'User-Agent': 'Find_Records',
        	'Authorization': `QB-USER-TOKEN ${usertoken}`,
            'Content-Type': 'application/json'
        }
        var body = {"from": dbid,"select":[3],"where":`{3.GTE.'${lowRec}'}AND{3.LTE.'${highRec}'}`}
        
        fetch('https://api.quickbase.com/v1/records/query',
          {
            method: 'POST',
            headers: headers,
            body: JSON.stringify(body)
          })
        .then(async res => {
          if (res.ok) {
            return res.json().then(res => {
                console.log(res)
                countLength = res.metadata["numRecords"];
                if (countLength > 1) {
                    myDiv.innerHTML += `Found ${countLength} records within the entered range.<br><br>`
                }else if (countLength == 1) {
                    myDiv.innerHTML += `Found ${countLength} record within the entered range.<br><br>`
                }else {
                    myDiv.innerHTML += `Found 0 records within the entered range.<br><br>`
                };
                console.log(countLength)
                myData(res.data)
            });
          }
          return res.json().then(resBody => Promise.reject({status: res.status, ...resBody}));
        })
        .catch(err => console.log(err))
        
        const myData = (resOriginal) => {
            var counter = 0
            resOriginal.forEach(ele => {
                let val = ele[3].value;
                console.log(`Done - Rec ID Level: ${val}`)
                let myURL = `${root}/db/${dbid}?a=API_GetRecordAsHTML&rid=${val}&dfid=${myForm}&apptoken=${apptoken}`;
                $.post(myURL, {}, function (response) {
                    let myDoc = response;
                    var opt = {
                        margin: 1,
                        filename: 'myfile.pdf',
                        image: { type: 'jpeg', quality: 0.98 },
                        html2canvas: { scale: 2 },
                        jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
                    };
        
                    html2pdf().from(myDoc).set(opt).outputPdf('dataurlstring').then(async res => {
                        var base64result = res.split(',')[1];
                        console.log(val);
                                
                        var headers = {
                            'QB-Realm-Hostname': myHostName,
                            'User-Agent': 'File_Upload',
                            'Authorization': `QB-USER-TOKEN ${usertoken}`,
                            'Content-Type': 'application/json'
                        }
                        body = { "to": dbid, "data": [{"3": {"value": val}, "59": { "value": { "fileName": "FormPDF.pdf", "data": base64result }}  }], "fieldsToReturn": [ 3, 59 ] }
        
                        fetch('https://api.quickbase.com/v1/records',
                            {
                            method: 'POST',
                            headers: headers,
                            body: JSON.stringify(body)
                            })
                        .then(async res => {
                            if (res.ok) {
                                return res.json().then(res => {
                                    console.log(res)
                                    counter++
                                    myDiv.innerHTML += `Record ID#: ${val} - Complete - ${counter}/${countLength}<br><br>`
                                });
                            }
                            return res.json().then(resBody => Promise.reject({status: res.status, ...resBody}));
                        }).catch(err => console.log(err))
                    });
                });
            console.log(`Done - getURL Record: ${val}`)
            })
        };
        
        
        ​




        ------------------------------
        Sean Connaughton
        ------------------------------
    • JeffPeterson1's avatar
      JeffPeterson1
      Qrew Captain

      I have this working but I can't get it to load any embedded reports on the form to be saved.  Any idea why it won't load them?   

      They are there if you access the link manually, but when it's done via the script it only loads the fields in that table.


      Edit:  It's not the setting for 'When there are embedded reports on the form' in form properties.     Seems to be related to the JS.

      ------------------------------
      Jeff Peterson
      ------------------------------
    • GregoryGebhardt's avatar
      GregoryGebhardt
      Qrew Cadet

      Very Cool stuff!  Thanks for sharing.  Has anyone here thought how we accomplish this with pipelines?  I'd be interested if someone solved this in that way or any thoughts on how it could be accomplished (if at all).  Thoughts?



      ------------------------------
      Greg
      ------------------------------
  • I have need of this function also and so far I've found the same as you.  Interested if anyone has figured out a QB native solution yet.

    ------------------------------
    Jeff Peterson
    ------------------------------