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.

Dear Apple and Adobe

 

Update: Steve Jobs Responds! Well, not to my letter directly, but it hits on the major points and is a well-written explanation of Apple’s position.

Dear Apple and Adobe

I’m a long-time customer and have spent more money on your products than I have on just about any other aspect of my life. I’ve spent more money on your products than I’ve spent on my healthcare, vacations, kitchen appliances, children’s school supplies, or home entertainment system.

In return, you’ve increasingly shown a disregard for my needs and concerns, and have acted in ways that demonstrate all you want from me is my money.

For example, both of you have constantly forced me (or at a minimum pressured me) to buy updates to products I already paid for. For years I went along with it because I bought into the sales hype and assumed these updates would somehow make my life better.  In most cases, they did not.

Adobe, your constant tinkering with the Creative Suite has brought a few nifty tools to the world, but these new tools will not get me to overlook the incredible bloat you’ve unleashed on my computers — almost 6GB of program files on my Windows PC at work, and over 7GB of app files on my Mac at home. Your applications feel more unstable with every release, and your UI feels slow and unresponsive despite the extra RAM and other hardware upgrades on my machines. Some of the biggest security holes on my computers are due to your Acrobat software — the very same Acrobat software I’ve learned to hate because of how bloated, complicated, and unfriendly it has become. It feels like it gets worse with each release.

Apple, your innovation is refreshing. Adobe could learn a thing or two by examining your software: increased productivity through reduced feature sets and cleaner UI. Simple is usually best. However, despite your continued excellence in design, your behavior is repulsive. You’ve consistently screwed your early adoptors via your pricing schemes and forced millions of Americans to use a phone network they detest. (Why? Because AT&T was willing to give you a bigger cut of the revenue?) Worst of all, the totalitarianism displayed in your latest iPhone developer agreement is breathtaking. It appears your goal is to piss off everyone, even your staunchest allies… like Adobe.

Apple and Adobe, you used to play well together. You both benefited from your long-term relationship and grew into very large, very successful companies. I sincerely doubt either of you would have survived the 1990s intact if it weren’t for your partnership. Desktop publishing was the Mac’s forte and the one thing that kept it afloat when the buzzards were circling. And who provided the most popular DTP software? Adobe (and the companies Adobe acquired, like Aldus and Macromedia).

Adobe, I know you’re mad because Apple won’t let you put your Flash technology on the new iPhone platform (iPhone, iPod, iPad). Honestly, if I were controlling a platform, I would have major concerns, too. As I mentioned earlier, your track record for software quality seems to be in a steady decline. Your products have become infamous for security holes, bloat, and crashing. It didn’t used to be that way. Somewhere along the line you dropped the ball, and now it’s coming back to bite you. The good news is that it isn’t too late for you to reign things in and regain control of your software. Stop trying to please everyone by adding every conceivable feature under the sun, and really focus on the most important elements. Drop the cruft. Clean the cupboards. Get that lint out of your bellybutton. Once your software is respectable again, you’ll be in a much stronger position to complain about Apple.

Apple, I don’t know what happened to you. You went from being a popular underdog to being the class bully. You’re in danger of becoming as popular as Microsoft in the European court system. From where I sit, your biggest mistake has been the idea that you can take over the world, one industry at a time. Of course, many companies are aggressive and set big goals for themselves, but they don’t stab their partners in the back as quickly and viciously as you seem to do. Your hubris and eagerness to expand into your partners’ markets is going to be your downfall. People have liked you because of your design sensibilities and because you were the hip underdog. You are no longer the hip underdog, and with time, other companies will create products that will be (almost) as stylish but also cheaper and with equivalent or greater capabilities.

The bottom line is that neither of you are choir boys, and I’m fed up with your bickering.

Adobe, stop playing the sympathy card. It’s a complete turn-off because I know how crappy your software can be. Granted, it’s unfortunate that so many people depend on Flash and Flash doesn’t work on the iPhone platform, but Flash is not a web standard. For all its shortcomings, the iPhone platform has one excellent quality: a top-notch HTML5 browser. Standardistas have been warning people not to go all-in with Flash for years, and now we see why. If it isn’t part of a standard, it will not be incorporated into some products. It’s the vendor’s choice. Simple as that.

Apple, stop trying to take over the world. We’ve seen what happens to other companies who try it, and it never looks pretty. Focus on your core values and let your partners do their thing without stepping on their toes.

Oh, and ditch AT&T already, will ya?

Respectfully,

Philip

Speaking of IMS…

IMS produces standards they want everyone to use.

Why, then, do I have to log in to their site in order to VIEW the documentation for their standards?

Situation: I found a link to a PDF on their site. When I clicked it, I was presented with this warm greeting [link no longer available]:

Sorry
The page you are trying to access is reserved for participants in the IMS GLC Public Community or is reserved for IMS GLC Members, Affiliates or Alliance Members.

If you have already set up an account and just need to login, please do so here. If you would like to register for a free public community account, just head on over to our registration page.

So… in order for me to even read their standards, I have to tell them who I am and put my personal information in their database? How does this foster adoption of standards?

Geez these guys burn my britches…

UPDATE:

The IMS site requires the following information before you can view any of their documentation: Name, email, organization name, job title, job description, country

Pretty invasive if you ask me.

UPDATE #2:

After registering for site access, I was greeted with a second page asking for persona information, and asking me to agree to IMS’s licensing terms. They require the following information: Name, email, “entity on whose behalf you are accepting this agreement”, street address (includes city, zip, country), phone number

(Phone number? Seriously?)

The license terms are FIVE PAGES long after pasting into a Word file.

Interesting bits (emphasis mine):

IMS specifications are published solely for the purpose of enabling interoperability among learning products and services used by the education and training communities served by IMS members and are made available under license to Registered Users solely to further that purpose.

I guess this means the standards aren’t meant for public consumption after all, though the end result of the standards are?

Users of the Specification(s) are encouraged to engage directly with IMS as IMS members, including registration of all applications of specifications, in order to enhance the level of interoperability in the education and training industries worldwide.

I read this as: tell us who you are and how you’re using IMS specifications so we can include you in our next press release (you know, the part where we pat ourselves on the back).

Any use of the Specifications(s) or other Materials provided by IMS must be accompanied by the copyright legends provided with full attribution given to IMS.

If this were the case with the HTML, ECMAScript, or XML standards, our documents would be bloated with useless attribution credits. (Side note: I wonder how this affects SCORM, as SCORM uses IMS specifications for packaging? Is anyone who produces a SCORM-based course supposed to pay respect to the Don provide attribution to IMS in their courseware?)

Licensee agrees to publicly and visibly acknowledge and attribute to IMS the Specification(s) upon which products are based to any and all Development Partner(s).

So if Company X uses an IMS specification, they’re supposed to go over to each “Development Partner” — many of whom may be commercial competitors — and let them know?

IMS issues press release for new e-learning interoperability standards

News from the IMS Global Learning Consortium:

The IMS Global Learning Consortium (IMS GLC) today announced the public review of the first phase of the Learning Tools Interoperability (LTI) standards to allow open and seamless integration of educational web applications. Called “Basic LTI or BLTI,” this first standard addresses the most common roadblocks to achieving a seamless experience for teachers and students.

Reading this press release, I couldn’t help but notice two things:

  1. There’s an awful lot of chest-puffing and self-congratulation going on here — people’s credentials are thrown around an awful lot, and over half of the press release is filled “Statements of Support.”
  2. There’s no hyperlink to the standards that are being announced, nor is there any information about how you can read the new standard proposal, whether you can try it yourself, how long it’s open for review, etc.

If the new standards are written as poorly as this press release, it’s going to be 1,000 pages of useless spec. All filler, no killer.

Gotchas in Internet Explorer 8

Internet Explorer 8 (IE8) is at Release Candidate 1, which means it will be released very shortly. IE8 is a brand-new browser and will represent a considerable shift from IE7/IE6; it will follow standards more closely and will offer much improved CSS 2.1 support. However, because of some of these changes, it is also widely understood that IE8 might break websites that have relied on IE-specific hacks targeted at previous versions of Internet Explorer.

To their credit, the IE development team has been very candid about the changes and have posted a number of blogs and documents aimed at helping web developers prepare for IE8. I was looking over one such page and thought I’d point out what I consider to be some of the biggest ‘gotchas’ so far.

Setting Unsupported CSS Values

Trying to detect support for a specific CSS value through a JavaScript try/catch statement will no longer generate an exception, which means you can’t rely on JavaScript to detect support for specific CSS values anymore.

Assigning CSS values that were unsupported in IE7 but are supported in IE8 Standards Mode will not generate exceptions in IE8 Compatibility View. Some sites use these exceptions to determine if a particular value for a CSS property is supported or not.


try {
   elm.style.display = "table-cell";
} catch(e) {
   // This executes in IE7,
   // but not IE8, regardless of mode
}

Malformed HTML

IE8 will not be as forgiving of malformed HTML markup. This is a great new ‘feature’ in terms of ensuring people (and software) are less sloppy with their markup, but this will certainly cause many, many problems for hundreds of thousands of old, poorly written websites.

Parser error correction for malformed HTML has changed in IE8 Standards Mode. Pages depending on the way IE7 performs error correction may encounter issues as a result.


<ul>
    <li>1.1
        <ul>
            <li>1.1.1</li>
    </li> <!-- Closes 1.1 in IE8, but not IE7 -->
            <li>1.1.2</li>
        </ul>
    </li>
</ul> 

Working with an Element’s Class

Like the malformed HTML ‘feature’, this is another great improvement in IE that will also cause many, many headaches. You see, for years IE wouldn’t let developers use the standard setAttribute("class") method for specifying a class name via JavaScript. Instead, IE required developers to use the proprietary setAttribute("className"). This means that it became commonplace for scripts to check for IE then use class for non-IE browsers and className for IE. Now, you’ll still need to make that check for older versions of IE but find a way to use class for IE8. <sarcasm>This will be fun.</sarcasm>

Don’t get me wrong — I’m excited that IE will finally behave like other browsers in this regard — but it also means that so long as IE6 and IE7 are still around, we’ll have to jump through more hoops to handle class names.

In IE7, “className” had to be used as the attribute name to set and retrieve the class of an element. This has been fixed for standards-compliance in IE8 Standards Mode. Using the old approach will create an attribute named “className” that has no affect on the actual class assigned to an element.


return elm.getAttribute("className");

SOLUTION: Use the standardized name, “class”, instead of “className”.


return elm.getAttribute("class");

CSS Expressions

One of the common hacks for IE’s shortcoming with CSS support has been to use IE’s proprietary CSS expressions, which are basically JavaScript statements embedded in place of a CSS value. While this practice has been frowned upon by most in-the-know web developers, it still wound up being heavily utilized as an ‘easy fix’ type of hack.

IE8 will no longer support CSS expressions. This will make it behave more like other browsers, but will cause problems for those who have relied on CSS expression hacks. Fortunately, it should be relatively easy to move your CSS expressions into your page’s JavaScript as suggested by Microsoft.

Support for CSS Expressions has been removed in IE8 Standards Mode.


/* CSS */
#main {
    background-color: expression(
        (new Date()).getHours()%2 ? "#000" : "#fff"
    );
}

SOLUTION: Refactor to utilize either improved CSS support or DHTML logic.


/* Script */
var elm = document.getElementById("main");
if((new Date()).getHours()%2) {
    elm.style.backgroundColor = "#000";
} else {
    elm.style.backgroundColor = "#fff";
} 

On the whole, I’m excited about the changes IE8 will bring, although it will undoubtedly require site revisions for anyone who uses JavaScript extensively in their projects.

You can read the original Microsoft blog post here.

Obfuscating email addresses, revisited

A while back, I posted my method for defeating spambots that harvest email addresses. This post is an update to that original method. It explores cleaner, less obtrusive code approaches and more accessible/usable HTML markup.

If you’re impatient and want to jump to some working examples, here you go:

The other “solutions”

So how do you prevent spambots from harvesting your email address? Well, there are a gazillion suggestions out on the interwebs, and unfortunately most of them stink because they require JavaScript, and because they often use illegible or invalid markup. For instance, this example — which was created by an email address obfuscator ranked high in Google searches — uses character entities to render the text completely illegible:

<a href="&#x6d;&#97;&#000105;&#108;&#116;&#000111;&#58;&#000116;&#x68;&#x69;&#000115;&#64;&#x73;&#x74;&#000105;&#x6e;&#x6b;&#x73;&#x2e;&#00099;&#x6f;&#109;"

This method has been popular for a number of years, but has some serious flaws. First of all, how do you know if you even have the right address in there? Secondly, what’s to stop a spambot from reading character entities? I imagine it would be as easy as reading ASCII or UTF. GONG!

Here’s another popular approach, premised on the notion that spambots look for any links using a mailto: protocol:

<script type="text/javascript">
   function emailme(user, domain, suffix){
      var str = 'mai' + 'lto:' + user + '@' + domain + '.' + suffix;
      window.location.replace(str);
   }
</script>;
<a href="javascript:emailme('this','stinks','com')">this@stinks.com</a>

There are multiple problems with this approach. The first problem is that it doesn’t use mailto: in the markup. This means if JavaScript is disabled, the link is completely useless. It also breaks the sematics of the links.

The second problem is that the JavaScript is inline and therefore obtrusive. JavaScript should not be mingling with your markup… it’s bad form! Any link that starts with javascript: is troublesome in my book.

Lastly, the whole address is still contained in the text of the page. If a spambot is sophisticated enough to look for mailto: protocols, it’s probably sophisticated enough to use RegEx to search for text that uses both @ and a period (.) without spaces.

There are other solutions out there, too, but they all require invalid markup, semantically incorrect markup, or flat-out removal of the email hyperlink. I want a solution that remains clickable when JavaScript is disabled, and doesn’t get all screwy with the markup. These don’t fit the bill. There’s another way.

A cleaner solution

My solution is simple: use an invalid email address. No, really! An invalid address with some extra touches and some unobtrusive JavaScript will work wonders. Here’s how to use it, step-by step:

Step one: Create your markup using a slightly altered address

Begin with a real address, then modify it to include some dummy text. For instance, the address sales@visitwaikiki.com would be rewritten salesnotspam@visitwaikiki.com. The spambot will harvest the address salesnotspam@visitwaikiki.com, which won’t work when the spammers try to use it.

The markup should look like this:


<a href="mailto:salesnotspam@visitwaikiki.com">sales@visitwaikiki.com</a>

There’s an obvious flaw here: The email address is still written in plain text between the ‘a’ tags. We’ll need to use alternate text — if you want to avoid spambots, NEVER use the real address as the visible text in an email hyperlink.

Using something such as sales AT visitwaikiki DOT com is also probably a bad idea, simply because zealous spambot authors can look for that very common pattern and manage to parse the email address. You’re best off using a different phrase, such as:

<a href="mailto:janenotspam@visitwaikiki.com">Contact Jane.</a>
<a href="mailto:salesnotspam@visitwaikiki.com">Email our sales department.</a>

We still have another problem to address: The link works, but it’s using the wrong address! The next step will help with that.

Step two: Improve the markup to make the link more usable when JavaScript is disabled

It’s always a good idea to ensure your visitor can use the email hyperlink when JavaScript is disabled. As it stands, when the visitor clicks the link, their operating system will create an email addressed to the invalid address salesnotspam@visitwaikiki.com. Without JavaScript, we can’t correct the address, but we can let the user know that the address needs to be edited.

<a href="mailto:salesnotspam@visitwaikiki.com?subject=EMAIL ADDRESS NEEDS EDITING&body=Please remove the text 'notspam' from the address before sending your email.">
   Email our sales department.
</a>

The mailto: protocol allows users to tack on additional information using the subject and body options. Whatever is listed after subject will appear in the email’s subject line. Whatever is listed after body will appear in the message’s body. By creatively using these options in the email address, we can clearly instruct the visitor to edit the address as-needed. The code above this paragraph produces the following email when clicked:

To: salesnotspam@visitwaikiki.com
Subject: EMAIL ADDRESS NEEDS EDITING
Message: Please remove the text ‘notspam’ from the address before sending your email.

Is it a pain to have to include the subject and/or body options each time you write an address? Yes. But is it more of a pain than the hundreds of spam emails you might get each week? I doubt it.

We now have a fully-functioning standards-friendly markup-only spam-resistant link. (Yes, I love hyphens. Don’t you?) Next, we’ll improve the experience for the 95% or so of your visitors who have JavaScript enabled.

Step three: Use JavaScript to make the link behave normally for most visitors

Most of your visitors will have JavaScript enabled; let’s take advantage of this and improve their experience. Our primary goal with our script will be to correct the invalid address by removing the dummy text “notspam”. However, since we’re removing the dummy text, we’ll also need to remove the instructions contained in the subject and body options so we don’t confuse the visitor.

Here’s a simple function that scans the page for all email links, then removes the dummy text (assuming all links use the same dummy text), the subject option, and the body option:

onload approach

window.onload = function (){
   var links = document.getElementsByTagName("a");
   for (var i=0; i < links.length; i++){
      if(links[i].href.indexOf("mailto:") !== -1){
         this.href = this.href.split("?")[0].replace("notspam", "");
      }
   }
};

Live demo

This teeny bit of JavaScript executes when the page loads and makes all email links behave as expected. Now we have a fully-functioning standards-friendly spam-resistant email link that also degrades nicely for visitors without JavaScript, and looks/feels completely normal to everyone else.

However, if you’re paranoid like me, you’ll wonder: What if the spambot supports JavaScript and looks for email addresses after the page has loaded? Your email address would be just as vulnerable as it was before.

A quick tweak to the script can help: instead of cleaning the addresses when the page loads, we can choose to only clean an address when the link is clicked.

onclick approach

window.onload = function (){
   var addressCleaner = function (){
      this.href = this.href.split("?")[0].replace("notspam", "");
      this.onclick = function (){};
      this.oncontextmenu = function (){};
   };
   var links = document.getElementsByTagName("a");
   for (var i=0; i < links.length; i++){
      if(links[i].href.indexOf("mailto:") !== -1){
         links[i].onclick = addressCleaner;
         links[i].oncontextmenu = addressCleaner;
      }
   }
};

Live demo

Note: all modern browsers treat a link as ‘clicked’ if you tab to it and hit enter on your keyboard, which means the link remains accessible to those using keyboard navigation and/or screen readers.

Also, notice the oncontextmenu code; when a link is right-clicked, the onclick event isn’t triggered. If a person right-clicks the email address to copy it, they would be copying the invalid version of the address. Using the oncontextmenu event fixes this problem.

You’re done!

You now have a spam-resistant email hyperlink that works whether JavaScript is enabled or not. It adheres to standards (no invalid markup), is semantically correct, and is unobtrusive.

Having said that, you should be aware that this system is not perfect; spammers are very clever, and will always catch up to us. This method is a form of spam resistance, not a foolproof way to defeat all spambots from now until eternity.

While the code you’ve just seen will work fine for most people, there are a few improvements that can be made with the use of a JavaScript framework. If you don’t use a JavaScript framework such as MooTools or jQuery, your journey has ended. If you do use a framework, let’s explore some potential improvements to the system.

Improvements via frameworks

JavaScript frameworks add some impressive tools to our toolbox and provide many conveniences. For this example, I’m going to use MooTools 1.2, but most other frameworks will have similar code that you can adapt for your own needs. Here are some improvements we can make:

  1. Use event handlers instead of direct assignment.
  2. Use a domready event instead of window.onload.
  3. Use CSS selectors and the array:each method

Here’s the improved code, modified to use MooTools 1.2:

window.addEvent("domready", function(){
   var addressCleaner = function (){
      this.href = this.href.split("?")[0].replace("notspam", "");
      this.removeEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   };
   $$("a[href^=mailto:]").each(function (a){
      a.addEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   });
});

Live demo

Explanation of the MooTools framework version

Since some of you may not be familiar with frameworks, so I’ll try and explain the changes I’ve made.

Event handlers

Most JavaScript gurus will tell you that using event handlers is a much more robust approach than using a direct onclick assignment. For starters, adding an onclick event using direct assignment will overwrite any existing onclick event. Using an event handler will ensure the new event will not destroy any existing events, and will simply add the new event to a queue of events.

//Direct assignment
a.onclick = function (){
  //Do something
};

//MooTools event
a.addEvent("click", function (){
   //Do something
});

As you can imagine, if you don’t use a framework, browser support and cross-browser incompatibility issues make event handlers a bit of a pain. This is one of the primary reasons frameworks have become so popular: they take the pain out of cross-browser compatibility.

Change window.onload to a domready event

The domready event is executed earlier than an onload event. domready basically means that all markup has loaded into the browser DOM, even if images and other media haven’t finished downloading yet. onload, by comparison, only fires after everything has finished loading. A MooTools domready event looks like this:

window.addEvent("domready", function (){
   //do something
});

Use CSS selectors and the array:each method

MooTools allows us to replace document.getElementsByTagName with much more targeted CSS-based selector: $$("a[href^=mailto:]"). This selector finds all links on the page whose href attribute begins with mailto:, then places the results in an new array. This means we can ditch two elements of our original script: the call to

document.getElementsByTagName("a")

and the if syntax inside the loop:

if(links[i].href.indexOf("mailto:") !== -1)

Next, we can replace the for loop with an each method, which performs whatever action is specified to each of the items in the array.

myArray.each(function (arrayitem){
   //do something with arrayitem
});

The each array method is native to browsers not named Internet Explorer. Frameworks like MooTools and jQuery bring support for this function to browsers that don’t natively support it.

Now that we’ve got our CSS-based selector working with the each method, we can greatly simplify our code:

window.addEvent("domready", function(){
   var addressCleaner = function (){
      this.href = this.href.split("?")[0].replace("notspam", "");
      this.removeEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   };
   $$("a[href^=mailto:]").each(function (a){
      a.addEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   });
});

Tips

  • You can place the dummy text in any part of your email address, not just the username portion. For instance, you could do sales@visitSOMEWHEREOTHERTHANwaikiki.com, sales@visitwaikiki.commie, etc.
  • It’s probably a good idea to use dummy text other than the common phrase “nospam”; authors of spambot software can easily look for these phrases as keywords and use them to target your address. Get creative with your dummy text, just be sure it’s obvious to a human reader that the text needs to be removed.
  • If you have multiple email addresses on the page, this method requires that you use the same dummy text in all email addresses.
  • Be sure you change the dummy text in the JavaScript function to match whatever text you decide to use!

Known Issues

When JavaScript is disabled and someone copies/pastes the email address instead of clicking it, they will be copying the invalid version of the address. To minimize problems, you can write the address in a hard-to-miss way, such as using all caps for the dummy text (salesNOTSPAM@visitwaikiki.com). This will be an extremely small percentage of users, so I wouldn’t worry too much; if they’re savvy enough to disable JavaScript and use copy/paste for email addresses, they’ll probably read the address, too.

This email address obfuscation method has been successfully tested in the following browser/OS combinations:

  • Firefox 3.0 (Mac OS X, Windows Vista)
  • Safari 3.2.1 (Mac OS X, Windows Vista)
  • Internet Explorer 6 (Windows XP)
  • Internet Explorer 7 (Windows Vista)
  • Internet Explorer 8b1 (Windows 7 beta)
  • Opera 9.6 (Mac OS X, Windows Vista)
    • One issue in Opera: The contextmenu menu event doesn’t trigger correctly when right-clicking

Fixed-width layouts

While working on a recent web project at work, I wondered if I should go for a fixed-width layout or stick with my preference for fluid layouts. Fixed-width layouts are certainly easier to manage, but they just feel so… rigid. With the boom in larger monitors, I also wondered if fluid sites start presenting a problem due to being too wide. I decided to check around the web to see what others are doing.

The search

In my (very quick and unscientific) research, I visited 150 popular sites, including news sites, shopping sites, software company sites, and the personal sites of well-regarded web designers and developers. I purposely avoided blogs that use canned themes.

When visiting the site, I determined the page’s width by reducing the browser window’s width until the horizontal scrollbar appeared. In some cases, I didn’t use the front page of the site, as these are sometimes standalone pages that use a completely different layout than the site’s actual content.

The results

Of the 150 sites I visited, only thirteen used fluid layouts — a whopping 8.6%.

A number of those thirteen sites weren’t completely fluid, as they either break at smaller sizes or have fixed-width sidebars, but I included them in the list since they aren’t using a fixed-width wrapper.

I was genuinely surprised about the number of fixed-width sites; a sizable chunk of these sites belong to (or were designed by) well-known web design gurus. I had assumed a number of them would use some sort of min-width/max-width CSS trickery, but I was wrong — only a very few sites (less than 3.5%) used this approach.

I was also surprised to see that the vast majority of the fixed-width sites were between 900 and 1000 pixels wide, a size that was unthinkable a mere 5 years ago.

Observations

  • Most of the fixed-width sites fell between 900 and 1000 pixels wide, and were usually centered in the browser
  • 20 sites were 800 pixels wide or less
  • 14 sites were between 800 and 900 pixels wide
  • 100 sites were between 900 and 1050 pixels wide
  • 3 sites were over 1050 pixels wide
  • the widest site was time.com at over 1100 pixels, although the layout uses a fixed-width wrapper wider than the content itself (the content was closer to 1000 pixels).

Sites visited

I picked sites somewhat randomly, using a combination of Alexa.com rankings, sites I frequent, sites my co-workers like to visit (hello, www.bebe.com), sites belonging to famous companies (McDonalds, Target, etc.) and sites belonging to well-known web designers and developers. I know I left out a number of good sites, but this was a very quick-and-dirty project.

Fluid layouts

  1. allinthehead.com (Drew McClellan)
  2. clearleft.com
  3. craigslist.org
  4. danwebb.net
  5. dean.edwards.name
  6. drupal.org (partially broken due to floating elements overlaying other elements)
  7. gmail.com
  8. htmldog.com (Patrick Griffiths)
  9. joeclark.org
  10. meyerweb.com (Eric Meyer)
  11. molly.com (Molly Holzschlag)
  12. people.opera.com/howcome (HÃ¥kon Wium Lie)
  13. wikipedia.org

Fixed-width layouts

  1. 24ways.org
  2. 37signals.com
  3. 456bereastreet.com (Roger Johansson)
  4. about.com
  5. adactio.com (Jeremy Keith)
  6. adobe.com
  7. alexa.com
  8. alistapart.com
  9. amazon.com
  10. americanexpress.com
  11. andybudd.com
  12. anthropologie.com
  13. aol.com
  14. apartmenttherapy.com
  15. apple.com
  16. authenticjobs.com
  17. bankofamerica.com
  18. barackobama.com
  19. barnesandnoble.com
  20. bbc.co.uk
  21. bebe.com
  22. bestbuy.com
  23. blogger.com
  24. borders.com
  25. boxofchocolates.ca (Derek Featherstone)
  26. burgerking.com
  27. cameronmoll.com
  28. cartoonnetwork.com
  29. cbs.com
  30. chase.com
  31. clagnut.com (Richard Rutter)
  32. cnet.com
  33. cnn.com
  34. comcast.com
  35. comcast.net
  36. crateandbarrel.com
  37. danbenjamin.com
  38. dean.edwards.name
  39. dell.com
  40. deviantart.com
  41. dictionary.com
  42. digg.com
  43. directv.com
  44. dojotoolkit.com
  45. dustindiaz.com
  46. ebay.com
  47. espn.com (MLB page)
  48. facebook.com
  49. fastcompany.com
  50. fedex.com
  51. flickr.com
  52. fox.com
  53. friendster.com
  54. gamespot.com
  55. go.com
  56. guardian.co.uk
  57. guitarhero.com
  58. happycog.com
  59. haveamint.com
  60. hicksdesign.co.uk
  61. home.live.com
  62. hulu.com
  63. iht.com
  64. ikea.com
  65. imdb.org
  66. jasonsantamaria.com
  67. jeffcroft.com
  68. jetblue.com
  69. jquery.com
  70. kaiserpermanente.com
  71. latimes.com
  72. linkedin.com
  73. livejournal.com
  74. m-w.com
  75. macys.com
  76. markboulton.com
  77. mcdonalds.com
  78. mediatemple.net
  79. mezzoblue.com (Dave Shea)
  80. microsoft.com
  81. mootools.net
  82. mozilla.com
  83. msn.com
  84. myspace.com
  85. nbc.com
  86. neopets.com
  87. netflix.com
  88. newegg.com
  89. nfl.com
  90. ning.com
  91. nintendo.com
  92. npr.org
  93. nytimes.com
  94. opera.com
  95. oreilly.com
  96. paypal.com
  97. pbs.org
  98. quirksmode.com (Peter-Paul Koch)
  99. reuters.com
  100. rockband.com
  101. rollingstone.com
  102. secondlife.com
  103. sfgate.com
  104. shauninman.com
  105. si.com (MLB page)
  106. simonwillison.net
  107. simplebits.com (Dan Cederholm)
  108. sitepoint.com
  109. skype.com
  110. snook.ca (Jonathan Snook)
  111. sony.com
  112. stuffandnonsense.co.uk (Andy Clarke)
  113. target.com
  114. techcrunch.com
  115. theonion.com
  116. time.com
  117. tivo.com
  118. twitter.com
  119. typepad.com
  120. ups.com
  121. urbanoutfitters.com
  122. usaa.com
  123. usps.com
  124. veerle.duoh.com (Veerle Pieters)
  125. virgin.com
  126. wait-til-i.com (Christian Heilmann)
  127. wamu.com
  128. washingtonpost.com
  129. weather.com
  130. wellsfargo.com
  131. whitehouse.gov
  132. williamssonoma.com
  133. wordpress.com
  134. yahoo.com
  135. yelp.com
  136. youtube.com
  137. zeldman.com (Jeffrey Zeldman)

Dealing with Internet Explorer in your JavaScript Code

It’s almost the end of 2008, and thanks to the hard work of web standardistas, browser vendors, and JavaScript framework developers, cross-browser JavaScript code is much less of an issue than it used to be. Even Microsoft is feeling the love — the upcoming Internet Explorer 8 will be a (mostly) clean break from legacy Internet Explorer releases and will behave much more like Firefox, Safari (WebKit) and Opera. …And they rejoiced.

So why is it that when I look under the hood of some recently produced web pages (learning management systems, courses produced by e-learning rapid development tools, general web pages, etc.), the pages’ JavaScript often includes incredibly out-of-date and bad-practice Internet Explorer detection? Check out these samples I randomly copied:


_ObjBrowser.prototype.Init = function() {
    var $nBrowserChar    = "";
    var $nBrowserStart   = 0 ;
    if ( this.$strUA.indexOf("MSIE") >= 0 ){
        this.$nBrowser = BROWSER_IE ;
        this.$nBrowserVersion = "";
        $nBrowserStart   = this.$strUA.indexOf("MSIE")+5
        $nBrowserChar    = this.$strUA.charAt($nBrowserStart);
        while ( $nBrowserChar != ";" ){
            if ( ( $nBrowserChar >= '0' && $nBrowserChar <= '9' ) || $nBrowserChar == '.' )
                this.$nBrowserVersion += $nBrowserChar ;
            $nBrowserStart++;
            $nBrowserChar     = this.$strUA.charAt($nBrowserStart);
        };
        this.$nBrowserVersion = parseInt( parseFloat( this.$nBrowserVersion ) * 100 ) ;
    } else if ( this.$strUA.indexOf("Mozilla") >= 0 ){
        this.$nBrowser        = BROWSER_MOZILLA ;
        this.$nBrowserVersion = parseInt ( (this.$strUA.substring( this.$strUA.indexOf("/") + 1,  this.$strUA.indexOf("/") + 5  )) * 100 );
    }
};

or even these simpler yet equally problematic sniffers:

UserAgent detection

if (navigator.appName &&
    navigator.appName.indexOf("Microsoft") != -1 &&
    navigator.userAgent.indexOf("Windows") != -1 &&
    navigator.userAgent.indexOf("Windows 3.1") == -1) {
        //Do something
}

Bad object detection

if (document.all) {
    //Do something for Internet Explorer 4
} else if (document.layers) {
    //Do something for Netscape Navigator 4
} else {
    //Do something for other browsers
}

These examples are BAD BAD BAD. Why? Well, there are a million web articles explaining the topic, but I’ll give you a quick rundown.

You shouldn’t test for specific browsers, but for specific functionality instead

In most cases, browser detection is being used because the developer is making an assumption that a particular browser does or doesn’t have a specific feature. The problem is that browsers change over time, and detecting for a browser based on assumptions can come back to bite you. Even the prickly Internet Explorer gets updates from time to time. Case in point: The versions of IE6 in Windows 2000 and Windows XP have a different JavaScript engine (JScript version) than IE6 running in Windows XP Service Pack 3. This means a general IE6 detection script might not lead you down the path you expected.

If you test for features instead, your code will be more future-compatible and less likely to break.

if(document.getElementById){
   //It is safe to use document.getElementById()
} else {
   //document.getElementbyId() is not supported in this browser
}

When hacks for Internet Explorer are required

Nowadays, most browsers offer roughly the same features and adhere to W3C standards. But, as we know, our friend Internet Explorer is… different. If you must use hacks custom code for Internet Explorer, know your options (and please, for the love of… something… don’t use ActiveX controls. Just don’t.).

Use cleaner IE detection

The old User-Agent sniffing approach has been abused for years. Internet Explorer 7 (Windows XP SP2) uses the following User-Agent to identify itself:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)

Yes, you read that correctly: IE declares itself a Mozilla browser. Opera’s User-Agent can be changed on the fly, allowing a user to set it to Opera, IE, or Mozilla! This all renders User-Agent detection useless. So what are your other IE detection options?

Conditional comments. Conditional comments are a proprietary Microsoft mechanism that is often used to include IE-specific CSS files in a page. For our discussion, you could conceivably use conditional comments to include a JavaScript file containing IE hacks in your page. You could also do a quick and dirty hack like this:


<!--[if IE]>
   <script type="text/javascript">
      var isIE = true;
   </script>
<![endif]-->

But, as you can see, it’s a clunky solution and isn’t inline with the other JavaScript, which makes it a less-than-ideal option.

Conditional compilation. This method is THE method of choice right now. Dean Edwards described this gem of an approach on his blog post Sniff. As far as I can tell, it is the cleanest and most reliable way to detect IE.

var isMSIE = /*@cc_on!@*/false;

Simple, isn’t it? No User-Agents, no 20 lines of conditional statements and string searches.

How does this code work? Internet Explorer uses JScript, a proprietary form of JavaScript (ECMAScript) developed by Microsoft. JScript has a feature called conditional compilation, which allows us to add JScript-specific JavaScript inside a specially formatted comment. Other JavaScript compilers will ignore this code, thinking it’s a comment, while JScript will recognize it and process it.

/*@cc_on begins the comment, and @*/ ends the comment. When any non-IE browser sees var isMSIE = /*@cc_on!@*/false;, it actually sees it as var isMSIE = <strong>false</strong>;. Internet Explorer’s JScript will add the exclamation point contained in the comment, meaning IE will see var isMSIE = <strong>!false</strong>;. Remember, an exclamation point reverses a boolean’s value, so !false is equivalent to true.

As Dean pointed out, there is an even simpler way to write this (though it’s slightly less legible):

var isMSIE/*@cc_on=1@*/;

If a variable is not assigned a value when it is declared, by default it will return ‘falsy‘ (null). Thus a non-IE browser will see this line as var isMSIE; while Internet Explorer’s JScript will see it as var isMSIE=1; (assigning a 1 makes the variable ‘truthy‘).

I prefer to shorten it a bit more by removing the MS from isMSIE, making it simply isIE. Note: it’s a best practice to give booleans a name that implies what the boolean stands for, usually in a quasi-question format. Using plain old var IE doesn’t tell us the variable is a boolean, and doesn’t tell us what question is being answered by the boolean’s value. var isIE does.


var isIE/*@cc_on=1@*/;
if(isIE){
   //Do something. I suggest downloading Firefox.  ^_^
}

Avoid forking your code for hacks whenever possible.

If your code can be written in a way that satisfies all browsers, do it that way, even if it veers to the left of official web standards — just be sure to document your decision in the comments. For example, using the standards-friendly setAttribute for specifying class names will fail in IE, while direct assignment will work in all major browsers.

//standards-friendly, but fails in IE
element.setAttribute("class", "myClass");

//direct assignment, works in all major browsers
element.className = "myClass";

In this case, I’m advocating using defacto standards that aren’t codified in the W3C standards, but are supported in every major browser. Some standardistas will balk, but this is a much easier way to maintain code; it’s a bad idea to use unnecessary conditional statements simply to prove that IE doesn’t always follow standards. We know, we don’t like it either, but get over it. If a non-standard but universally supported alternative is available, your extra “see I told you so” code will be pure bloat, increasing file size, bandwidth requirements, and browser processing requirements.

Don’t get me wrong: standards are important. Very important. But they only go so far; if we stuck to codified standards on principle, no one would be using AJAX today! That’s right, xmlhttprequest — the heart of AJAX — was created by Microsoft, then copied by other browsers. It is not an official standard, but it is universally supported, and is used by tens of millions of web site visitors every day.

If you’re not comfortable with non-standard code littered throughout your project, abstract it into a function so you can easily modify it later.

Old way:

element.className = "myClass";

Abstracted function:

function setClass(targetElement, name){
   /*
      not using setAttribute here because setAttribute
      won't work with class names in IE
   */
   targetElement.className = name;
}

setClass("element", "myClass");

Now whenever you need to set a class, you won’t worry about IE and can simply use your class function. If you decide to change how you assign the class name, you can just change it in the function and not have to dig around your code looking for every instance of direct assignment (className =).

Note: Many JavaScript frameworks, such as jQuery and MooTools, provide this type of support function for you. These frameworks protect you from needing to know about browser inconsistencies, and free you to focus on your application. If the above example were rewritten to use MooTools, it would simply be

element.addClass("myClass");

There are many ways to approach the problem, and all without using nasty browser detection scripts. It pays to know your options.

Use abstractions for forking code

Getting back to IE detection, if you aren’t using a framework and need to handle browser inconsistencies on your own, it’s a good idea to use support functions. These can encapsulate the browser inconsistencies and isolate them from the rest of your code. This makes your code more maintainable and readable.

Case in point: Internet Explorer will not allow you to dynamically assign a name value to an <input> element. This requires an IE-specific hack.

You would normally use the following W3C-compliant code:

var input = document.createElement("input");
input.setAttribute("name", "myInput");

In Internet Explorer, we’re forced to do this:

var input = document.createElement("<input name='myInput'>");

In your project, you’d have to use conditional statements each time you need to create an input element.

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

if(isIE){
   var input1 = document.createElement("<input name='myInput1'>");
} else {
   var input1 = document.createElement("input");
   input.setAttribute("name", "myInput1");
}
input1.setAttribute("id", "myInput1");

//Later on in your code...
if(isIE){
   var input2 = document.createElement("<input name='myInput2'>");
} else {
   var input2 = document.createElement("input");
   input.setAttribute("name", "myInput2");
}
input2.setAttribute("id", "myInput2");

What a pain. The best way to deal with this is to wrap the code in a function like so

function createInput(parentElement, id){

   //Make sure everything is supported. Error-checking is divine.
   if(!parentElement || !id || !document.createElement || !parentElement.createElement){ return false; }

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

   if(isIE) {
      input = parentElement.createElement("<input name='" +id +"'>");
   } else {
      input = parentElement.createElement("input");
      input.setAttribute("name", id);
   }

   input.setAttribute("id", id);
   return input;

}

As you can see, the code is now easily reusable throughout our project, contains better support for IE detection, and also contains feature detection ensuring our function won’t throw any errors. To use the code in the project, you’d simply write:

var input1 = createInput(document, "myInput1");
var input2 = createInput(document, "myInput2");

In closing

I hope you’ve found this post helpful, and I especially hopes it helps spread the word about using best practices and cleaner code techniques. I’ll be the first to say I’m no programming expert, so feel free to add a comment if you have suggestions for improvements. 🙂

PS: If you have to write a conditional statement, follow Crockford’s advice and wrap it in curly braces.

Introducing PDFObject

Update March 2011: PDFObject source code has been updated and moved to GitHub.

I recently worked on an e-learning course that required embedding some PDFs into an HTML file. PDF embedding piqued my curiosity, and has become something of a pet project. It sounds simpler than it is. No, scratch that… it is pretty simple. Stoopid simple. The problem is that there’s a lot of bad info about embedding PDFs floating around the www, especially regarding using the embed element.

Note to readers: embed bad, object good.

Unlike embedding SWFs, embedding a PDF is a breeze if you use the object element, and the object element has the added bonus of being totally standards-compliant.

As I tinkered on my new pet project, I decided it would be nice to have a JavaScript script that could dynamically embed PDFs as easily as SWFObject allows SWF embedding. I managed to whip up a script, and decided to name it PDFObject. (I know, I know… what a creative name!) As you may have inferred from the name, the concept and functionality is pretty similar to SWFObject. Like SWFObject, it’s also standards-friendly, and uses DOM scripting techniques to write a standard object element to the page.

One of the perks of using this script is that it makes working with PDF Open parameters much easier, similar to how SWFObject makes working with flashvars easier.

I’ve built a simple website for my PDFObject project named — drumroll, please — http://pdfobject.com. The most time-consuming part of this project has actually been building the website; it includes instructions, a ton of examples, and a compatibility table detailing results from browser/OS testing, including Mac OS X 10.5, Win XP, Vista, and Ubuntu. The site also features a section devoted to PDF embedding using standard HTML code without JavaScript (gotta promote standards!), and a handy code generator that makes writing the code as easy as filling out a form.

The site purposely does not include support or contact information; while I enjoy making things that help people out, I already know I don’t have the time to handle support issues. Sorry!

Anyway, PDFObject is completely free (as-is, no warranties) and my gift to the community. I hope some of you find it useful. 🙂