I’m currently working on a system which is as close to greenfield development
as you can really get in a 13 year old system written in a propriatary
language. It’s a web system, using modern-ish javascript frameworks,
Knockout and Backbone, with
a C# middle layer covering the complexity of the backend and its older SOAP web
services.
iOS developers have very recently leant at WWDC about huge changes to the
platform, tools, and APIs that we use to make great apps. It’s an exciting
time, with whole new categories of apps becoming feasible and a lot of work on
the horizon. iWatch indeed.
So, at work I came up against an intere^H^H^H^H^H^H infuriating
problem with cross-browser embedding of PDF documents, and I want to document
it here as there really were no leads at all that I could find with a Google
search.
The Situation
A function of the system I work on is document management, and the web project
I am working on will give members access to documents pertaining to them,
subject to all of the neccesary security restrictions. Many of the documents
produced and managed by the system are stored as, or can be converted to, PDF
files.
I was working on the frontend for displaying these documents, and ran in to
cross-browser compatibility issues (I’m looking at you, as ever, Internet
Explorer…) and issues in page rendering caused by timing issues related to
Knockout.js, the framework which we’re using to data-bind our views to our view
controllers etc.
When I started, the document viewer’s code to include a PDF in line was as
follows:
Picking the right element
That’s pretty readable, but I don’t think that <embed> is the correct element
to use for this task. It’s an HTML5 element, but its use by IE for this task
far predates the advent of HTML5. In the HTML5 spec,
it is introduced as:
[…] an integration point for an external (typically non-HTML) application
or interactive content.
Which doesn’t suggest document to me. I could almost understand if you said the
integration is to a PDF reader plugin, but that isn’t my intent, just a way of
achieving the goal of displaying a PDF inline on the page. Fortunately, HTML5
offers a more appropriate element, <object>, which:
[…] can represent an external resource, which, depending on the type of
the resource, will either be treated as an image, as a nested browsing
context, or as an external resource to be processed by a plugin.
Now that’s exactly what we’re looking for. Brilliant, let’s get on with it! I
merrily change things up to the following:
and that’s where the fun really starts. Load this up in IE - nothing, just a
big old empty grey box where the plugin should be. Load it up in Firefox - the
Adobe plugin fires up, displays the document, and also an informational warning
at the top of the screen to say that “This PDF document might not be displayed
correctly.” Huh? It’s right there, and my data-bind is resolving just fine
too… In Chrome it’s even worse - it gets a big grey box with “Couldn’t load
plugin” and a jigsaw puzzle peice emblazoned in the middle, and a yellow
error at the top of the screen to tell the user that it “Could not load Chrome
PDF viewer”.
data-bind it all…
After much headscratching, and some great help
from the all knowing Stack Overflow, I got the issue that was causing the
problems in Chrome and Firefox nailed down - the browser parses the object tag
before data-binding is resolved, and fires up the plugin then. At that point,
the object tag looks like this:
which means there’s no URL for the plugin to load, which really confuses the
browser and the plugin, and leads to the unpredictable behaviour shown in
Chrome and Firefox (but not IE, which just plain doesn’t know what to do with
an object tag, at least prior to v10). The solution would be as follows:
which does fix the problems in both Firefox and Chrome, but unfortunately it’s
invalid, as the spec for <object> requires at least a type or data
attribute be present. We want it to be valid at load time, and for users
who do not have javascript enabled (not that it will do a lot, but you know,
principles etc.) To make it valid before bindings resolve, we can just take
advantage of the fact that data bindings in Knockout overwrite the existing
values of attributes:
Circling back to IE
The type and data-doctype within the binding may be superfluous now, but I
think they help to communicate intent and that there’s an issue with doing this
“the obvious way”. Unfortunately, if we load this page up in IE, we now don’t
even get the big grey box… fortunately <object> has us covered, as we can
provide a fallback element inside the <object>.
And there you have it! Strictly, I think that the <embed> within the object
is still not valid, but it works well and it’s as close as I could get while
meeting my requirements. A better approach given the freedom would be to use
the fallback capability to provide a download link, and then retain full
compliance to HTML5, but not providing the feature to IE users is not
acceptable in the requirements of this project. I hope that’s of help to
someone, let me know on Twitter if you have any
comments!