Thursday, March 31, 2022

Easy APEX email using JsPDF

 In my previous post, I explained how to easily create a PDF document of a part of a web page created with APEX.

Now that we know how to create, download and possibly print the PDF, the question arises: can we email this PDF file?

So, here is our APEX again, now with button Email:



The JavaScript to create the PDF file is more or less the same. Instead of downloading the file, we now upload the file to the database, and email the file from there using APEX_MAIL.

The JavaScript function called from the Click Dynamic Action is:


function mailJsPDF() {
    // This isn't mentioned anywhere, but it is needed since 2.0!
    window.jsPDF = window.jspdf.jsPDF;
    var doc = new jsPDF ();
    doc.html(document.getElementById('container'), {
       callback: function (doc) {
         // Save blob:
         var blob = new Blob ( [doc.output('blob')], {type: 'application.pdf'} );
         // Base64 encode:
         var reader = new FileReader();
         reader.onload = function () {
            // Since it contains the Data URI, we should remove the prefix and keep only Base64 string
            var b64 = reader.result.replace(/^data:.+;base64,/, '');
            //Send the blob to the server via Ajax process
            apex.server.process(
              'UPLOAD',
               {
                 p_clob_01: b64
               },
               {
                success: function(data) {
                    alert("The invoice was mailed");
                 }
               }
            );
         };
         reader.readAsDataURL(blob);
       },
            width: 170,
        windowWidth:700,
        margin: [20, 0, 0, 20],
        html2canvas: {
          scale: .238
        }    
    });
  }

The UPLOAD process gets the uploaded CLOB, converts it back to a BLOB, and emails it as an attachment. In a real application you will probably also store the generated PDF file in the database, get the email address from the website etc.

declare
  l_clob   clob;
  l_blob   blob;
  l_id     number;
begin
  --Get the clob passed in from the Ajax process
  l_clob := apex_application.g_clob_01;
  apex_debug.message('Uploaded: '||dbms_lob.substr(l_clob, 200, 1));

  -- Covert it back to a blob
  l_blob := APEX_WEB_SERVICE.CLOBBASE642BLOB (p_clob => l_clob);

  l_id := APEX_MAIL.SEND(
        p_to        => 'you@gmail.com',
        p_from      => 'me@gmail.com',
        p_subj      => 'Invoice with attachment',
        p_body      => 'Goodday, see the attachment.',
        p_body_html => 'Goodday, see the attachment.');
  APEX_MAIL.ADD_ATTACHMENT(
        p_mail_id    => l_id,
        p_attachment => l_blob,
        p_filename   => 'invoice.pdf',
        p_mime_type  => 'application/pdf');
  COMMIT;
  APEX_MAIL.PUSH_QUEUE();
 
  --Write some JSON out for the response
  apex_json.open_object();
  apex_json.write('status', 'success');
  apex_json.close_object();  
end;


 

Monday, February 14, 2022

Easy PDF reports in Oracle APEX

 A frequent question on APEX forums is: how do I create simple reports, like tickets, invoices etc.

Of course you can use some real report generator, like Apex Office Print (AOP) or Jasper. But in some cases the solution may be really simple: an APEX region and jsPDF to create a PDF document of that region. This may work if you are sure that your report will be just one page, like most invoices for instance.

This example was built in the sample database application, on the DEMO_ORDERS table. You can create a simple invoice page, part of which is shown here.

Invoice page


This is just a master-detail page based on  DEMO_ORDERS and  DEMO_ORDER_ITEMS and some additional CSS.
Make sure that everything you want to print is in one container region with a static id, and addressee, company info, items etc. as sub-regions of this container.

Now comes the tricky part: use jsPDF to create a PDF of the container region. I say tricky, because I found it hard to find examples using the latest jsPDF version. Most examples I found were using older versions, and apparently quite a lot has changed.

Start with attaching two JavaScript libraries to your page. jsPDF needs html2canvas to work.
I attached both libraries from a CDN:

https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js

The PRINT button on your page has a JavaScript DA.


window.jsPDF = window.jspdf.jsPDF;
var doc = new jsPDF();   
doc.html(document.getElementById('container'), {
   callback: function (doc) {
     doc.save();
   },
    width: 170,
    windowWidth:700,
    margin: [200020]
});

It took me quite some time that you need "window.jsPDF = window.jspdf.jsPDF;" on the first line. This was not really mentioned anywhere.

It was also surprising that in "margin: [200020]" the last number is the left margin, but it is actually mentioned in the documentation:

html - Documentation (githack.com)

The final result is a PDF document:


You have to trust me here that the first screenshot is of an APEX page and the second of a PDF  document.

There are some hickups in the PDF, e.g. in the email address as you can see. I haven't been using jsPDF and html2canvas long enough to know if that can be improved.  


Edit: if you face this spacing issue, try this:

Add css to the page:

body {
  letter-spacing: 0.2px;
}

Change the JavaScript code to:

window.jsPDF = window.jspdf.jsPDF;
var doc = new jsPDF ();
doc.html(document.getElementById('container'), {
   callback: function (doc) {
     doc.save();
   },
    width: 170,
    windowWidth:700,
    margin: [200020],
    html2canvas: {
      scale: .238
    }    
});

The result of these changes is: