Integrating A JavaScript PDF Viewer Into Native FileMaker—No JavaScript Coding Required!
We recently built an application that included PDF document management. While it might seem as simple as a quick FileMaker container field, we wanted to impress our client with an integrated solution on par with a content management system (CMS). Here’s how we built a powerful PDF viewer directly into FileMaker.
Iteration 1: Container Field
FileMaker can directly store binary data, via built-in container fields. A nice option is the ability to store the data either internally in the database itself or externally in the host file system. It is often beneficial to store PDFs externally to save space, increase performance, and give the host administrator flexibility to use separate partitions or drives.
So we created a new table for this imported data, added a container field to hold the PDFs, and configured the field for external storage. Done?
Not quite.
By default the container field is set for static content, such as images. When a user tries to import a PDF, the PDF option is grayed out and the user is forced to insert a generic file. When they view that container field, they see just an icon for the file (after Insert > File) or a picture of the first page (after drag and drop). It all works, but is a poor experience.
For PDFs, be sure to go to the Inspector data tab to optimize the container field for interactive content. This is a better experience, enabling users to choose the logical “Insert PDF” menu option and always view a thumbnail of the first page.
Even better, now the user has some control over the PDF: the ability to scroll between pages, change viewing options, and zoom and resize. Downloading a PDF is still unintuitive, requiring a trip to the Edit > Export Field Contents menu option, but at least a control-click reveals the ability to Open In Preview.
Preview has some great capabilities for enhancing the PDF experience, but we’re FileMaker developers and don’t want to shunt someone off to an external program to get functionality that should be available out of the box.
Enter the FileMaker Web Viewer and Mozilla’s PDF.js library…
Iteration 2: Web Viewer
What happens if instead of displaying the PDF in a container field, we export it and display it in a Web Viewer?
That straightforward process grants us the hover controls we find in Safari, which makes sense since the Web Viewer uses the Safari engine under the hood.
These controls allow for basic zooming, opening in Preview, and downloading. All nice, but still none of the advanced capabilities, like searching or viewing the table of contents, for which our clients were looking.
Iteration 3: JavaScript
Now you—like me—may be thinking, “I’m not a JavaScript developer!”. Don’t worry, we won’t touch even a single line of JavaScript. And the functionality benefits to your users are well worth the small integration effort.
In the screenshot you can see users now have the ability to search; highlight; view the table of contents or thumbnails of each page; open, download, and print; and more—use a truly full-featured PDF viewer, right within FileMaker. My focus from here isn’t on everything you can now do with PDFs—you’ll find your own business case there—but rather how to build this functionality into your FileMaker solutions.
The demo file at the end of this blog post (PDFViewerDemo.fmp12) demonstrates a FileMaker implementation of Mozilla’s free, open source, Apache-licensed PDF.js library.
https://mozilla.github.io/pdf.js/
You’ll still save the PDF in a container field, but now you’ll be displaying it in the Web Viewer rather than in the container field itself.
There are just five steps to move this into your own solution:
- Copy the PDF_JS_ASSETS table into your file, and import the single record.
- Import the two custom functions, and copy the two scripts in the PDF.js folder.
- Build a simple layout with a Web Viewer. In fact, it can have just the Web Viewer since the JavaScript library provides everything else. Or put a Web Viewer on an existing layout. It’s your choice as a developer.
- Add three lines to your existing PDF viewer script:
- Plug in your own values for PDF_VIEWER_DEMO::SAMPLE_PDF_FILE and Object Name: “pdf_viewer”.
That’s it!
Now, let’s see how we can optimize it.
Iteration 4: Optimization
Naturally there are many variables when determining how fast the PDF will display, including host performance, network speed, and PDF size. That said, in our testing, we found a 500 KB PDF displayed in about ten seconds and a 20 MB PDF in about a minute. Functional, though not ideal.
Let’s take a look at what’s going on behind the scenes:
- The first of three lines we added to our script copies the PDF from the permanent container field to a global container field on which the supporting scripts will operate. In our testing, this took about 40% of the total time to view.
- The second line calls a script which does three things. First, it calls a subscript to export the PDF.js library to the FileMaker temporary folder. This happens only the first time a user views a PDF—subsequent views during the same session do not require re-exporting the library.
- The next thing the script does is Base64-encode the PDF in the global field. It inserts this encoded text into an HTML template, which acts as a wrapper for the JavaScript PDF viewer and encoded PDF. Finally, it saves this combined HTML into the other global container field as UTF-8–encoded text. We found this takes another 40% of the total time.
- Finally, the encoded text file is exported to the FileMaker temporary folder, the path is passed back to your script, which then displays it in the web viewer. This accounts for the last 20%.
Integrating this example into our solution was plug-‘n’-play easy, but came at the expense of performance. There is clearly room for further improvement, so let’s see what happens if we more tightly couple the sample code with our specific application.
While the basic process detailed above is the same, we eliminated steps one and three by pre-caching the UTF-8 text. We also improved step two by using macOS shell commands to offload the zip extraction so we do not have to wait. Implementation details will depend on your own architecture, but here is a high-level overview of what we did.
At the time the user initially imports the PDF, we go ahead and Base64-encode it, combine it with the HTML template, and save it all as a UTF-8 text file into a separate container field alongside the actual PDF. It looks something like this:
Set Field [ PDF_VIEWER_DEMO::PDF_ENCODED ; Let ( [ ~html_template = TextDecode ( PDF_JS_ASSETS::VIEWER_HTML_TEMPLATE ; "UTF-8" ) ; ~replacement_tag = “{{[BASE_64_PDF_DATA]}}” ; ~base64_data = Base64EncodeRFC ( 3548 ; PDF_VIEWER_DEMO::SAMPLE_PDF_FILE ) ; ~template_with_base_64_data = Substitute ( ~html_template ; [ ~replacement_tag ; ~base64_data ] ) ] ; TextEncode ( ~template_with_base_64_data ; "UTF-8" ; 1 ) ) ]
Note you can use Perform Script On Server (PSoS) to make this relatively lengthy operation invisible to the user. So we’ve saved something like 80% of the processing time, eliminated both global fields, and folded one of the two copied scripts into our existing script. If your solution already has a developer table (one record to hold solution-wide fields), you can copy the two asset containers into that table and avoid adding the PDF_JS_ASSETS table.
When we re-ran our performance tests, we initially thought something was broken, for in nearly all cases the PDF now displayed in just a second or two!
These phenomenal performance gains require additional effort, a detailed knowledge of both your solution (which I hope you already have), and an understanding of the underpinnings of how to implement this library (which I hope I’ve given you). An investment in this effort seems well worth it to provide the best experience for your users.
Download the PDFViewerDemo.fmp12 demo file.
Thanks to Mozilla, UK Film Education, University of Florida, Steve Senft-Herrera, Andrew Witschonke, Jay Gonzales, and Vince Menanno.
NOTE: This technique was built for macOS, but will not work on FileMaker Pro for Windows (which uses Internet Explorer for Web Viewer).