PDFObject 2.0 released

After almost eight years in the making (and nearly 7 years of procrastinating), PDFObject 2.0 has arrived.

PDFObject is an open-source standards-friendly JavaScript utility for embedding PDF files into HTML documents. It’s like SWFObject, but for PDFs.

Version 1.0 was released in 2008 and has enjoyed modest success. Based on stats from PDFObject.com (including devious hot-linkers) and integration with 3rd-party products, I’m guesstimating it has been used on well over a million web pages. (If I had a nickel for every time it was used…)

I updated it a few times over the years, but generally only if someone reported a compatibility issue. Like an old beat-up car, it was a bit crusty, but still ran like a champ. That is, it ran like a champ until the rules of the game were changed — when Microsoft changed their ActiveX strategy in Internet Explorer 10-11 and Microsoft Edge, PDFObject’s checks for ActiveX began to fail, rendering PDFObject useless in those browsers. This marked the beginning of the end for PDFObject 1.x.

An update was overdue, yet I let it sit for a couple of years – I fully admit that kids, my job, and life tend to take precedence over an unfunded open-source project. But I never stopped thinking about PDFObject. I intentionally kept it at arm’s length for a while; I was fascinated by changes in the front-end development world, and waited to see how things would shake out.

It’s incredible how much has changed since 2008. For starters, the browser landscape has completely changed. Chrome, which didn’t exist when PDFObject was first released, now rules the land. It also happens to include built-in PDF support. PDF.js was invented, and eventually became Firefox’s default PDF rendering engine. Safari renders PDFs natively using Preview. iOS and Android exploded onto the scene, as did Node.js and NPM. Conversely, Adobe Reader’s market share took a nosedive thanks to browser vendors making Adobe Reader less relevant, not to mention disdain for Adobe Reader’s bloat and security holes. And, of course, HTML5 is now official, which means the <embed> element is officially sanctioned.

PDFObject 2.0 is a complete rewrite that tries to take all of this into consideration. It supports PDF.js. It’s packaged for NPM. It uses the <embed> element instead of the <object> element (not going to rename it to PDFEmbed though). It doesn’t pollute the global space and uses modern JavaScript conventions. It supports all CSS selectors, not just IDs. If you’re feeling frisky, you can even pass a jQuery element instead of a CSS selector (note: PDFObject does not require jQuery). Lots of little changes, which I hope add up to a better experience, wider compatibility, and lots of flexibility.

If you’d like to learn more about PDFObject 2.0, please visit the official site (completely redesigned as well), with examples, documentation and a code generator: http://pdfobject.com

The code is up on GitHub, and has been posted to npm.

Demos for LearnSWFObject have been moved

For the record, all demos for LearnSWFObject.com have been relocated from my personal server to GitHub. The root URL has changed from demos.learnswfobject.com to learnswfobject.com/demos.

This enables me to keep all of the demos in the same repo as the primary LearnSWFObject site. The site and demos are old, and have not been updated for years, but are still useful to some members of the Flash community. Moving the files to GitHub is a nice way to keep the tutorials and demos online while reducing my personal burden for hosting the sites.

Convert “localhost” to your Mac’s current IP address

When developing web pages, I use MAMP.app or my Mac’s built-in Apache. Viewing the page means using an address such as http://localhost/mypage.html. If you use custom host names (especially easy with the excellent VirtualHostX.app), you may wind up with a localhost address such as http://projectname/mypage.html.

This works great when you’re just testing the pages on your Mac’s browsers. However, once you cross boundaries into Windows testing (via VMs or separate laptops), localhost will no longer resolve. Why? Because localhost is local to your machine.

If you want to view the page in a VM or on another machine, just swap the domain name with your machine’s IP address. For example http://localhost/mypage.html becomes http://10.0.1.14/mypage.html. (Note: you must be on the same network or have a public IP address.)

This works very well, but it’s tiresome to manually grab the IP address anytime you want to use a VM or share the page with coworkers, especially if you’re on DHCP and don’t have a static IP address.

I decided to make my life a little easier by writing an AppleScript that looks at the open tabs in Chrome and Safari then replaces “localhost” (or custom domain) with my current IP address. Saving this as a service enables me to go to Chrome > Services to run the script.

Chrome > Services

If you’d like to give it a try, the AppleScript is available as a Gist on GitHub.

Lion Server compared to Snow Leopard Server

Update 10/25/2011: I’ve discovered that Apple provides Lion-ready versions of the older server management tools via a separate download from Apple, called Server Admin Tools 10.7 [link no longer available]. I have amended my post to reflect the capabilities of the Server Admin Tools 10.7 suite. Short version: Server Admin Tools 10.7 looks a lot like Server Admin Tools 10.6, but it has been stripped down, and has reduced functionality compared to Server Admin Tools 10.6 that ships with Snow Leopard. On the bright side, Server Admin Tools 10.7 doesn’t conflict with the new Server.app in Lion.

As I discussed in a previous post, I’ve been hosting this website on a Mac Mini server running Snow Leopard Server. The initial setup was a bit convoluted and not the most pleasant experience. It could have been much worse, but it was nowhere near what I expected from an Apple product.

At work, I recently had the opportunity to set up a Mac Mini running Lion Server. Now that I’ve had a chance to use it a bit, I thought I’d jot down a few comments. If you’ve done any research into Lion Server, you probably won’t be surprised by my comments.

First, the disclaimer: I have used Snow Leopard Server and Lion Server for slightly different tasks, so it’s isn’t a straight-up apples-to-apples comparison.

Web hosting

My Snow Leopard Server is confined strictly to web hosting: Apache, PHP, MySQL, and a dab of FTP. I host multiple sites and use a combination of the Server Preferences app and the Server Admin app to manage everything. Setting up websites in Snow Leopard is really easy via the Server Preferences app, and you have a pretty good assortment of settings to tweak in the Server Admin app if you want to fine-tune things.

Lion Server discarded both the Server Preferences app and the Server Admin app in favor of a new, streamlined “Server” app. It’s even easier to set up a website in Lion than it was in Snow Leopard. BUT… Server doesn’t provide ANY of the fine-tuning options that Server Admin provided in Snow Leopard. If you’re a total server admin n00b who only wants bare-minimum web hosting, this is probably good. But if you’d like any level of real control, you’ll be forced to use the command line. For me — not a n00b anymore but certainly not a command line guru — I’m left in the lurch a bit. I expect to be doing a ton of Google searches in the near future.

One example of how this can affect you: most domains should be set up with an alias to enable the domain to work without the “www” subdomain. For example, http://www.pipwerks.com is an alias that points visitors to http://pipwerks.com. If the www alias isn’t created, anyone who attempts to visit pipwerks.com by typing http://www.pipwerks.com will get a browser error. In Snow Leopard, it’s super easy to set up an alias in the Server Admin app. Lion does not provide this option, so you’ll have to do it via Terminal. More Google searches and wasted time.

Score (5 highest, 1 lowest): Lion: 2, Snow Leopard: 4

MySQL

Apple discarded MySQL in favor of PostgreSQL. This will cause problems for many who depend on MySQL, such as WordPress users. The good news is that you can install MySQL using an installer available for free from mysql.com. The bad news is you’re on your own when it comes to security patches and updates… Apple’s system update will no longer handle patches for you. (To be fair, Apple is notoriously slow to release security updates for server components anyway, so you probably shouldn’t rely on them to keep your system secure.) Not a showstopper, but definitely a stumbling block.

Score (5 highest, 1 lowest): Lion 1, Snow Leopard: 4

File Sharing

Snow Leopard’s file sharing GUI was decent, but I frequently had to tweak settings in multiple places. It was a bit disjointed and I was never quite able to get my FTP access set up the way I preferred. In Lion, the file sharing GUI is extremely simple and much easier to use. Maybe it’s because Apple removed the FTP option in the file sharing preferences! In Lion, you must use the Terminal to control your FTP services (or maybe find a 3rd party app). This seriously sucks. On the bright side, AFP is a cinch to set up. Since I’m only using AFP at the moment, I can be generous in my grading. If I depended on FTP, I’d grade much lower.

Score (5 highest, 1 lowest): Lion: 2.5, Snow Leopard: 2.5

User management

Configuring the directory in Snow Leopard Server was the single most painful experience I’ve had with Apple products (well, at least since the emergence of OS X — Mac OS 9 caused quite a bit of pain back in the day!). Configuring the directory seriously sucked and required hours and hours of troubleshooting, including multiple calls to Apple support. One little mistake when you first set up your system and it’s hosed, requiring a clean install.

Lion Server simplifies the process, and was astoundingly easy to set up. In fact, one of the key differences between the two systems is what happens when you first install the Server system: Snow Leopard forces you to configure your directory right away (even if you only intend to have a single user), while Lion doesn’t even mention the directory until you try to configure a service that relies on it. In this event, the configuration screen pops up and the process is so quick you’re done before you realize it. I can’t begin to tell you how happy that made me.

Snow Leopard has an app dedicated to user management (Workgroup Manager). Lion took the most basic elements of Workgroup Manager and turned them into a pane inside Server.app.

Update: Workgroup Manager is still available as part of the Server Admin Tools 10.7 [link no longer available] package, available as a separate download from Apple.

Since neither of my servers are used by clients (I only have a handful of accounts), I’m much happier with the slimmer approach used by Lion. Of course if you have many clients, you may prefer Snow Leopard’s far-reaching Workgroup Manager app.

Score (5 highest, 1 lowest): Lion 4, Snow Leopard: 1

Firewall

Snow Leopard Server includes a Firewall pane in Server Admin app, which makes it very easy to configure your firewall settings. This is gone in Lion.

Update: The new version of the Server Admin app (part of the Server Admin Tools 10.7 [link no longer available] package) provides a GUI for Firewall configuration. It’s a no-frills management panel, but it’s there if you need it.

Score (5 highest, 1 lowest): Lion 1 3, Snow Leopard: 4

Managed Clients

A “Managed Client” is a Mac that is associated with your server, allowing you to perform remote updates, enforce security options, etc. This is basically the feature that allows you to be an IT department and lock down computers registered on your system.

I never used the Managed Client feature in Snow Leopard, so I can’t make any comments. In Lion, the Managed Client feature (accessed via Profile Manager) is so easy to use, it makes me giggle. Granted, I’m not a “real” IT professional when it comes to managed clients, so take my comments with a grain of salt. All I know is, I needed to manage about a dozen Macs at work (think: computer lab-style shared workstations), and Profile Manager has been very easy for me to use, without having any real technical experience in the area.

One downside: all the Mac clients must have the Lion OS if they’re to be managed by Lion Server.

Update: From what I’ve researched, Snow Leopard clients can be managed from a Lion Server, but there are certain incompatibilities to watch out for. Clearly Apple would prefer everyone to use Lion.

Score (5 highest, 1 lowest): Lion 4.5, Snow Leopard: n/a

Summary

Lion Server really trimmed the fat, and Apple appears to have gotten so carried away they trimmed some of the good meat as well. If you’re looking for a GUI with lots of options, go with Snow Leopard Server. If you want the most streamlined experience you can get and only need to do basic web hosting, go with Lion. If you’re comfortable with Terminal and the command line, I’d suggest going with Lion because the GUI won’t get in your way as much and you probably already know what you’re doing.

I will be experimenting with Lion server for a while before I try and upgrade from Snow Leopard Server (if I upgrade). And if I decide to upgrade from Snow Leopard to Lion, I will definitely do a clean install… so many things have changed, I can foresee a lot of configuration problems arising from upgrades.

Using the object element to dynamically embed Flash SWFs in Internet Explorer

This is a journey into the madness of Internet Explorer.

Yes, there is a happy ending. Jump to the end of the post if you just want the solution and don’t care about how we got there.

The Scenario

You want to embed a Flash SWF into your HTML document using the object element, and you need to be able to do it using JavaScript. (Let’s pretend SWFObject doesn’t exist, ok?)

If you were using HTML markup without JavaScript, embedding a Flash SWF using an object element would be pretty straightforward:


<object id="mySWF" width="550" height="400" 
        data="mymovie.swf" 
        type="application/x-shockwave-flash">
   <param name="flashvars" value="dog=woof&cat=meow" />
</object>

Microsoft designed Internet Explorer’s object to work a bit differently, so the markup for Internet Explorer becomes:


<object id="mySWF" width="550" height="400"
        classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
   <param name="movie" value="mymovie.swf" />
   <param name="flashvars" value="dog=woof&cat=meow" />
</object>

The key differences are:

  • Internet Explorer requires the classid attribute instead of type
  • Internet Explorer requires the param name="movie" child node instead of the data attribute in the object

Annoying, perhaps, but workable. If you’re working with hard-coded markup — what SWFObject refers to as static publishing — you can drop in some conditional comments and be done:


<!--[if IE]>
<object id="mySWF" width="550" height="400"
        classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
   <param name="movie" value="mymovie.swf">
<![endif]-->

<!--[if !IE]>-->
<object id="mySWF" width="550" height="400"
        data="mymovie.swf"
        type="application/x-shockwave-flash">
<!--<![endif]-->

   <param name="flashvars" value="dog=woof&cat=meow" />

<p>Fallback content for people without Flash Player</p>

</object>

That’s nice, but I need to use JavaScript

On the surface, using JavaScript to recreate the HTML markup appears to be a trivial task; we just need to add conditional logic to handle the two small differences between Internet Explorer’s object element and the object element used by the other browsers:


var target_element = document.getElementById("replaceMe"),
   obj = document.createElement("object"),
   isMSIE = /*@cc_on!@*/false;

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

if (isMSIE) {            

   //IE requires the 'classid' attribute
   obj.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
   
   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);

} else {

   //Non-IE browsers require the 'type' attribute
   obj.setAttribute("type", "application/x-shockwave-flash");
   
   //Non-IE browsers require the 'data' attribute
   obj.setAttribute("data", "test.swf");

}

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Seems simple enough, right? Not so fast. If you try running this in Internet Explorer (6-9), you’ll notice the SWF fails to load, and IE will behave as though it’s stuck trying to load a file — the “loading” icon never goes away.

Issues with Microsoft’s proprietary classid attribute

It turns out Internet Explorer’s object does not like having the classid appended after the object has been created. This is very similar to the well-known IE bug for adding the name attribute to certain form elements. For the form element bug, Microsoft’s solution is to include the name attribute in the createElement string:

Attributes can be included with the sTag as long as the entire string is valid HTML. To include the NAME attribute at run time on objects created with the createElement method, use the sTag.

Microsoft provides the following example:


var newRadioButton = document.createElement("<INPUT TYPE='RADIO' NAME='RADIOTEST' VALUE='First Choice'>")

What happens if we use this solution for the classid issue?


var node_name = (isMSIE) ? "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />" : "object";
var obj = document.createElement(node_name);

This works like a charm… in IE 8 and lower. Unfortunately, IE 9 chucks a wobbly. Microsoft, in their wisdom, decided that angled brackets should no longer be valid inside the createElement method. This is actually excellent news: Internet Explorer 9 is behaving like other browsers, so let’s just use a try/catch to target IE versions prior to IE9, and use standard W3C code for IE9:


var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj;

if (isMSIE) {

   try {
      
      //For IE 8 and lower
      obj = document.createElement("<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />");

      //IE requires the 'movie' <param>
      var param_movie = document.createElement("param");
      param_movie.setAttribute("name", "movie");
      param_movie.setAttribute("value", "test.swf");
      obj.appendChild(param_movie);
      
   } catch (e) {
      
      //Let IE9 and higher fall through and use the standard browser markup
            
   }
   
}

if (!obj) {

   obj = document.createElement("object");
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
   
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Still works in Internet Explorer 8 and lower, but fails in IE 9. Okay, perhaps IE9 still requires the classid attribute and ‘movie’ param. Let’s fork the code some more and try it out.


var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj;

if (isMSIE) {

   try {
      
      //For IE 8 and lower
      obj = document.createElement("<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />");
      
   } catch (e) {
      
      //IE9 doesn't support classid in createElement, so let's add it afterward
      obj = document.createElement("object");
      obj.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
      
   }

   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);
   
} else {

   //Standard browsers
   obj = document.createElement("object");
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
   
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Are you ready for the surprise? This doesn’t work in IE9, either. Adding classid to the object after it has been created simply will not work in any version of Internet Explorer. Microsoft removed their own recommended workaround — adding the attribute in the createElement string — to standardize their browser, yet they failed to provide full support for the related W3C standards being used by their competitors.

In their announcement of the createElement change for IE9, Microsoft recommended using the W3C standard setAttribute method, yet it fails for classid. *grumble*

To their credit, Microsoft also provided a second workaround consisting of strings and innerHTML:


var parent=document.createElement("div");
parent.innerHTML="<div id='myDiv'></div>";
var elm=parent.firstChild;

For our purposes, it would look like this:


function createIeObject(){
   var div = document.createElement("div");
   div.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'></object>";
   return div.firstChild;
}

var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj = (isMSIE) ? createIeObject() : document.createElement("object");

if (isMSIE) {
   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);
} else {
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

This feels like it’s getting us somewhere. Unfortunately, the SWF still doesn’t load. What else can we try? What if the ‘movie’ param were added at the same time the object is created and classid attribute is specified in Internet Explorer?


function createIeObject(url){
   var div = document.createElement("div");
   div.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'><param name='movie' value='" +url + "'></object>";
   return div.firstChild;
}

var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj = (isMSIE) ? createIeObject("test.swf") : document.createElement("object");

if (!isMSIE) {
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

EUREKA! The SWF now displays as it should in all versions of Internet Explorer, and IE no longer behaves as if it’s stuck trying to load something. The takeaway is that Internet Explorer’s proprietary classid attribute and ‘movie’ param need to be created together or all is lost.

Why not use innerHTML all the way?

Many developers will say: Why not use innerHTML for the whole thing? After all, SWFObject 2.0 through 2.2 uses string building and innerHTML for the entire object creation in Internet Explorer:


el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';

In SWFObject, the attributes (att) and parameters (par) are added to the string via string concatenation. This is functional, but means that a completely different workflow — one devoid of standard W3C techniques — is required for Internet Explorer versus all other browsers.

For example, if there are many param nodes to add to the object, they will have to be generated as a long string of params in IE, while the fork for the non-IE browsers will all use a createElement/appendChild combination. In my opinion, it would be best to keep the code simple and use the same code for handling attributes and parameters in all browsers. This is ideal for maintenance, security, and file size.

Using the proposed IE solution allows us to modify the object via W3C techniques — we can add parameters and attributes and they all function as expected. The only difference between IE and other browsers is the creation of the initial object, but once it’s set up, IE’s object behaves the same in all browsers.

The obligatory poke at Microsoft

This is a perfect example of why there is not much love for Internet Explorer (and by extension, Microsoft) in many web developer circles. A task that should be trivial is utterly complicated, and requires hours of troubleshooting and experimentation just to achieve some basic functionality.

It’s my sincere hope that Internet Explorer 10+ will use the standard object code, but it probably won’t, since IE will probably still rely on ActiveX for the Flash Plugin. Falling short of using the standard object element, I hope that IE 10+ will at least allow us to add the classid attribute using setAttribute, and also allow us to add the movie param using createElement and appendChild.

Sniffing Internet Explorer via JavaScript

I’ve been reviewing bug submissions for the SWFObject project and was reminded of a big problem with SWFObject 2.2: the JavaScript technique it uses for detecting Internet Explorer does not work in Internet Explorer 9.

SWFObject 2.2 currently uses an IE detection technique proposed by Andrea Giammarchi in 2009:


var isIE = !+"v1";

It’s a hack that relied on Internet Explorer’s unique handling of vertical spaces (v). Now that it has stopped working, I decided to take a quick look at what others are doing.

One of my favorite approaches is Dean Edwards’ “sniff” technique, which takes advantage of Microsoft’s conditional compilation.


var isIE = /*@cc_on!@*/!1;

It’s 4 years old, but still works like a charm. The only problem is that some JavaScript compressors and optimizers (including YUI Compressor and Google Closure) have a hard time with the inline comment and strip it out. It makes the code difficult to maintain, because it requires editing post-compression.

One way to get around the compressor issue (as pointed out by a commenter on Dean Edwards’ post) is to wrap the conditional compilation statement in an eval() function:


var isIE = eval("/*@cc_on!@*/!1");

Since eval() is evil, I won’t use this approach.

One of the most long-standing methods of detecting Internet Explorer is to examine the userAgent string:


var isIE = /msie/gi.test(navigator.userAgent);

However, this isn’t foolproof, as most browsers allow you to change the userAgent string at will. For example, I changed Safari’s userAgent string to report itself as Internet Explorer 8, and /msie/gi.test(navigator.userAgent); returned true!

So far, the only test I’ve found that seems to fit the bill is the old navigator.appName test:


var isIE = navigator.appName === 'Microsoft Internet Explorer';

Is it perfect? Not a chance, but it has a lot of upside: it works in IE9, doesn’t get damaged when compressed, doesn’t rely on any hacky tricks (as fun as they might be), and isn’t affected by spoofed userAgent strings.

What are the Frameworks using?

jQuery uses userAgent sniffing, and includes a warning that it’s unreliable. MooTools uses a combination of properties from the navigator object, including navigator.userAgent and navigator.platform. It’s interesting to note that MooTools previously used feature detection to infer the browser brand, but changes introduced by Firefox 3.6 prompted the MooTools team to switch to a userAgent-based detection method instead. (Nicholas Zakas wrote an interesting blog post about MooTools’ prior detection technique.)
Dustin Diaz’s Bowser detection script uses userAgent exclusively.

Why sniff in the first place?

Before you get down on me for even talking about sniffing, relax, I agree with you. I much prefer feature detection to browser sniffing; this is especially important with our quickly changing browser landscape: HTML5, web storage, geolocation, etc. (Modernizr is a great tool for modern feature detection.)

But Internet Explorer always makes us do things we’re not quite comfortable doing. Sometimes it’s not very easy to detect IE’s support for a given issue. For example, IE is notorious for not allowing JavaScript developers to set the name attribute of form elements via setAttribute; it requires including the name in the createElement invocation. Craziness!

So we carry on. I use feature detection for just about everything, but once in a while I need a global “are you Internet Explorer?”, and browser sniffing fits the bill.

PDFObject Updated, Moved to GitHub

PDFObject is a JavaScript utility I created in 2008 to embed PDFs in HTML documents. It was modeled on SWFObject.

Three years have passed since PDFObject 1.0 was released, and the browser landscape has changed dramatically. I figured it’s time to dust off PDFObject and see if it can be improved and/or updated for today’s browsers.

I’ve placed a modified edition of PDFObject (version 1.1) on GitHub as an open-source project, allowing anyone to create a fork and make modifications. There are also a handful of example/test HTML files there, too. I could use some help testing the examples in different OS/browser combinations, and with different PDF plugins.

If you have suggestions for improvements, or have a few minutes to test PDFObject examples in your browser/OS of choice, I’d be grateful to hear from you. Just leave a comment below, or send me a message on twitter. So far I’ve only performed some cursory tests in Firefox 3.6 in OSX 10.6.6, but everything seems to work as expected.

https://github.com/pipwerks/PDFObject

I plan to update PDFObject.com soon, too.

IFrames and cross-domain security, part 3

In 2008 I posted a quick writeup on how I dealt with cross-domain security issues for some of my e-learning courseware. Since then, I’ve had a lot of people contact me with various questions and requests for a working example.

Tonight I decided to revisit the topic and whip up some quick example files. To my chagrin, I discovered a load of typos in my original post. Yikes! No wonder people wanted to see some working examples.

Well, ask no more, here they are:

The explanation provided in the previous post is still valid, just refer to these new sample files instead of the code in that post’s examples. (I will update that post with new code examples soon.)

Remember, the proxy.html file must reside on the same domain as the parent frame. This solution will not work if you don’t have FTP access to both domains, since you need to place support scripts on both domains.