Thinking Anew

Preview PDF Uploads in an APEX App (Cross-post)

Photo by Kaleidico

Came across an interesting question on stackoverflow this morning, so I thought I’d give it a shot. Turned out to be an exciting morning of learning and a pretty lengthy response that I thought I’d cross-post to keep a copy for myself. If you have any suggestions for improvement, please feel free to contribute to the thread. Thanks!

Demo Application

Link: https://apex.oracle.com/pls/apex/f?p=34781

Username: demo
Password: demo

The Recipe

This uses the PDF.js project by Mozilla. Here’s a quick recipe of what you may need.

  1. Create a File Browse page item and set the Storage Type to Table APEX_APPLICATION_TEMP_FILES.
  2. Create a page button to submit the page.
  3. Create a Classic Report region and enter the following query:
1
2
3
4
5
select
id
, filename
from apex_application_temp_files
where application_id = :APP_ID
  1. Add a virtual column and set the HTML Expression:
1
<button type="button" class="btn-preview-pdf" data-id="#ID#">Preview</button>
  1. Create a region and enter the following in the Source:
1
<canvas id="preview-pane"></canvas>
  1. Create a Click dynamic action.
    a. Set the selection Type to jQuery Selector.
    b. Enter the jQuery Selector .btn-preview-pdf.

  2. Add a Execute JavaScript Code action with the following JS code (check out the examples from the PDF.js website for more details on what the code does):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var fileId = $(this.triggeringElement).data('id');
var docUrl = 'f?p=&APP_ID.:0:&APP_SESSION.:APPLICATION_PROCESS=DOWNLOADPDF:::FILE_ID:' + fileId;
var previewPane = this.affectedElements[0];

// from PDF.js examples
pdfjsLib.getDocument(docUrl).then(function(pdf) {
var pageNumber = 1;
pdf.getPage(pageNumber).then(function(page) {
console.log('Page loaded');

var scale = 1.5;
var viewport = page.getViewport(scale);

// Prepare canvas using PDF page dimensions
var canvas = previewPane;
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;

// Render PDF page into canvas context
var renderContext = {
canvasContext: context,
viewport: viewport
};
var renderTask = page.render(renderContext);
renderTask.then(function () {
console.log('Page rendered');
});
})
}, function(reason) {
console.error(reason);
});
  1. For the action, also set the Affected Elements:
    a. Selection Type: jQuery Selector
    b. jQuery Selector: #preview-pane

  2. Follow Joel Kallman’s post on creating a link to download a file. You will need an Application Process (DOWNLOADPDF) and an Application Item (FILE_ID) The modified code for the Application Process DOWNLOADPDF looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
begin
for file in (select *
from apex_application_temp_files
where id = :FILE_ID) loop
--
sys.htp.init;
sys.owa_util.mime_header( file.mime_type, FALSE );
sys.htp.p('Content-length: ' || sys.dbms_lob.getlength( file.blob_content));
sys.htp.p('Content-Disposition: attachment; filename="' || file.filename || '"' );
sys.htp.p('Cache-Control: max-age=3600');
sys.owa_util.http_header_close;
sys.wpg_docload.download_file( file.blob_content );

apex_application.stop_apex_engine;
end loop;
end;
  1. Almost missed this out. On the Page Attributes, set the JavaScript File URLs to any of the CDNs listed. For example:
1
//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.550/pdf.min.js

Note that this is a very basic prototype. The preview only allows you to view the first page. You will need to figure out the API and then do the necessary to allow multipage viewing.