SCORM security (two kinds of SCORM people)

I’ve had a flurry of emails and messages regarding my SCORM cheat the past few days, and have received feedback from a number of well-regarded SCORM aficionados, some of whom contributed to the standard and helped make SCORM what it is today. This is wonderful, I’m very happy to hear from everyone, especially regarding such an engaging topic.

But as I hear more from these seasoned SCORM pros, I’ve made (what I believe to be) an interesting observation: there is a sharp division between die-hard SCORM developers and casual users. I suppose I’ve felt this way for a long time, but it’s really coming into focus this week. Let me try to define the camps.

  • Die-hard SCORM developers (aka scormmies). The scormmie is a person who understands what SCO roll-up means, and can hand-code an entire manifest. A scormmie thinks the word metadata is sexy. This person believes a course should be designed to use SCORM from the start, complete with sequencing and interaction tracking; if the course isn’t running in an LMS, it won’t function without being loaded into some kind of SCORM player or test suite. Scormmies get angry if their LMS hasn’t implemented the entire SCORM spec.
  • Casual users (aka shruggies). The shruggie is a person who doesn’t care about multi-SCO courses. Shruggies don’t want to be bothered by the technical details, and use rapid e-learning development tools to build courses, freeing them from needing to know any of the technical mumbo-jumbo. Metawhat? “SCORM… yeah, that’s one of the publishing options in [insert product name here], right? So it will work with my LMS?”

The e-learning market has changed significantly

Over the last week I’ve mostly heard from scormmies who make comments such as ‘well, if a developer knows what they’re doing, they’d never make their course that vulnerable to begin with!‘ and ‘a developer should never design a course to only require a completion and score… that’s asking for trouble.

The problem with this line of reasoning is that the e-learning landscape has changed dramatically since SCORM was first conceived; the scormmie used to be the majority. Now, with the proliferation of e-learning development tools and LMSs, the scormmie is a minority. Most “e-learning developers” are not programmers by trade, and are not familiar with the very complicated and intimidating SCORM spec. They use tools that do the heavy lifting for them.

If you survey most e-learning development tools (which is a booming market), the courses they publish are almost exclusively single-SCO courses that only use the simplest core SCORM functionality: completion status, lesson location (bookmarking), score, and suspend_data. These products are designed to create courses that work without SCORM, which means they only add the minimal SCORM code needed to get the course running on an LMS; all other logic is generally handled internally. They certainly don’t use sequencing and navigation or cmi.interactions.

LMS vendors generally advise customers to buy these off-the-shelf tools to build their courses. E-learning conferences are packed with tool vendors and advertisements selling the virtues of a ‘no technical expertise required’ tool. At work I sometimes get calls from vendors trying to sell me the latest and greatest tool.

The majority of courses are no longer developed by scormmies

All of this leads to one point: I think some of the SCORM guys have lost touch with the current market and don’t realize just how much of a problem a simple SCORM cheat like mine could be. Sure, it probably wouldn’t work on courses developed by seasoned scormmies because multi-SCO courses that utilize interactions are much too complicated for my itty-bitty script to tackle… but courses developed by mainstream development tools are easy targets. Ducks in a barrel. So long as the API is JavaScript and unprotected, a script like mine can bypass the SCO completely and set the course to complete before the learner even gets past the table of contents. The only way to figure out if someone cheated is to run a completion report and look for unusual patterns, which is highly unlikely in most corporate environments. As a friend noted the other day, there are many more script kiddies who can write cheats like mine now than there were when SCORM was first proposed.

Who gets the blame for the vulnerability?

Can the tool makers be blamed? Maybe, but hey, their #1 priority is satisfying the needs of the community, and the community wants quick, easy, and ‘can run on a CD-Rom’. Could the vendors have implemented more sophisticated SCORM mechanisms? Yes. However, everyone chooses the path of least resistance (and least development dollars), and we all know SCORM development is not a walk in the park. I’ve been using SCORM for five years and still avoid most of the complicated stuff because it’s … well … complicated.

The community at large (aka the shruggies) has bought into the notion that SCORM is the standard for e-learning. This is what the scormmies wanted, and it made the most sense for everyone involved, even the tool vendors. But how many people knew about the security vulnerabilities in the JavaScript-based API? A lot: the SCORM authors, the ADL, LMS vendors, tool vendors, and a number of prominent SCORM developers. Did any of these people warn the end clients of the risks? Maybe, but I personally have never been warned of any SCORM security issues in my five odd years of SCORM work. I’ve never been told “don’t use SCORM for that because it isn’t secure.”

Why didn’t anyone act?

I wasn’t privy to the early conversations, but I’ve been told that SCORM developers have said “don’t use SCORM for high-stakes assessments” from the very beginning, circa 2000. If this is the case, why has nothing been done to improve SCORM’s security? It’s only been about nine years. Did convenience beat out security in the race to implement the standard?

I get the impression that the scormmies (and remember, my term scormmie just means a person that works with SCORM, not necessarily an official representative) felt no one would bother trying to hack the system, and that a well-built course would be so difficult to cheat that it would be easier to simply take the course. With today’s simplistic single-SCO courseware tools, I don’t think this is a valid argument anymore.

I’ve also heard from scormmies that we’re still fine, because everyone knows SCORM shouldn’t be used for high-stakes training. I think a significant number of corporate, military and government trainers would disagree with that assessment, because the LMS salesperson never mentioned it. Neither did the e-learning development tool vendor. Oh, and that instructional designer we hired out of college? She’s heard of SCORM but has no clue how it works. Isn’t it safe since you have to log into the LMS with a password? There’s a padlock icon and an https protocol… that means it’s secure, right?

Nope.

Simple-SCO courses are used for all kinds of sensitive training nowadays. Compliance training alone is huge these days and can be found in examples from almost every simple-SCO tool vendor. As a colleague recently remarked, “it’s all low stakes until someone’s attorney gets involved”.

No hard feelings!

I would like to point out that I am not targeting anyone in particular, have no animosity towards anyone, and have the utmost respect for the scormmies and what they do (I’m half-scormmie myself). I’m an optimist with a very critical eye, and this post is intended as constructive criticism… criticism intended to cause positive change.

It simply became apparent to me that at some point the scormmie community dropped the ball and got complacent; it seems as though the whole community assumed no one would bother to hack a course. Well, I did. And I used public documentation to do it. It took two hours while I was flying on an airplane, and I’m not the sharpest tack in the box. I’m sorry if my cheat script caused a stir (and if this blog post makes some people uncomfortable) but we need to talk about this issue. Now.

What’s the solution?

OK, we’ve covered enough of the criticisms and the importance of working towards a solution… I’m ready to let it rest. Let’s finish on a positive note: SCORM uses existing technology and standards, and if multinational banks can protect billions of dollars from cyber-criminals using standard web technology, we should be able to secure our courseware, too. I personally think we should be able to figure something out in the next couple of months and that it ideally shouldn’t require much work to implement — no need to wait until SCORM 2.0 comes out!

Here are some suggestions I’ve heard:

  • using a secure web service to handle important duties such as processing completions and scores
  • rolling up SCOs in a way that forces the LMS to analyze multiple SCOs before setting pass/fail (a second ‘dummy’ SCO could be used if the course is a single-SCO course)
  • using form posts to submit the completions (the form post would contain a unique encrypted key that must match a key on the LMS)

Personally, I’m especially interested in ideas that don’t require modifications to LMS implementations and might only involve a strategic re-organizing of a SCO’s manifest or SCORM code. Perhaps using a SCO roll-up can become a security best practice, even if the course only uses one SCO? That type of simple solution would be ideal since it wouldn’t require modifications to an LMS or SCORM spec — it would only require a broad marketing effort to get the word out to all SCORM developers and toolmakers.

I would love to hear other ideas, as I feel we can probably come up with any number of workable solutions.ร‚ย ร‚ย  Please add to the discussion! Remember, these need to be solutions that can be implemented easily and by the single-SCO type of courseware tools flooding the e-learning market.

By the way, while we’re at it, can we improve accessibility in our e-learning, too? ๐Ÿ˜‰

Advertisements

Cheating in SCORM

I’m always surprised how little people talk about cheating in e-learning; maybe it’s a fear of revealing just how easy it can be. The fact is, SCORM — the most common communication standard in e-learning — is fairly easy to hack. It uses a public JavaScript-based API that is easy to tap into and feed false data, and because it’s a standard, you know exactly what methods and properties are available in the API. It doesn’t matter what vendor or product produced the course (Articulate, Adobe, etc.)… if it uses SCORM, it’s vulnerable.

I’ve whipped up a proof-of-concept bookmarklet that when clicked will set your SCORM course to complete with a score of 100 (works with both SCORM 1.2 and 2004).

This bookmarklet isn’t guaranteed to work with all courses… it’s just a demonstration of what’s possible, and could be made much more sophisticated by someone highly motivated to cheat.

As e-learning continues to boom, we should be looking into ways of making courses more secure and more difficult to hack. I believe higher security should be achievable with current web technologies. For instance, how about requiring any score or completion data to be accompanied by a unique encrypted security key? Then no external script could inject false data because it wouldn’t have the required security key.

I don’t think cheating is a problem at the moment, but we should be proactive and implement better security before it becomes a problem.

Update #1: For those who are curious, the bookmarklet has been successfully tested in a few LMSs and test environments, but I won’t be revealing which ones. For those interested in the tech specs, the bookmarklet is an anonymous JavaScript function with no global variables. It was error-checked in JS Lint then compressed with the ‘shrink variables’ option enabled, which means it’s pretty hard to decipher. If you’re interested in seeing the uncompressed code, post a comment below with your email and I’ll consider sending a copy.

Update #2: The bookmarklet has been taken down. I am no longer distributing the code, though you’re welcome to write your own.

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

SCORM resources

I recently emailed a shortlist of good SCORM development resources to a colleague, and figured I should probably post a list here, too. This is a quickie list, and I’m sure I’m leaving someone out. If you know of any resources I’ve missed, please add a link in the comments. This list is presented in no particular order.

  • Claude Ostyn’s site. He passed away in 2007, so there haven’t been any updates since then. His materials present a nice overview including lots of examples.
  • The ADL website. Their SCORM Documentation Suite is the official documentation. Must-have for any SCORM developer.
  • Redbird DevNet [link no longer available]. They have a nice tutorial/walk-through of SCORM 2004 architecture.
  • Rustici Software. A for-profit business that works almost exclusively with SCORM. They’ve posted some helpful resources on their site.
  • Aaron Silvers has been writing about SCORM (especially with regards to Flash) for a long time.
  • adlCommunity. A site dedicated to advancing ADL’s technologies. There are some good resources for SCORM developers, including an overview of SCORM written by the late Philip Dodds, one of the chief architects of SCORM.
  • Academic ADL Co-Lab. An offshoot of the ADL that offers SCORM resources and training for would-be SCORM developers. Also hosts Joe Nelson’s custom SCORM JavaScript framework LibSCORM (“a boilerplate template that implements common SCO Tracking and Communication functionality”), which some may find useful.

Of course, I also have a few SCORM odds and ends on my site you may find useful:

For those of you who don’t know, SCORM 2.0 is in the works, and is being handled by Learning Education Training Systems Interoperability (LETSI). SCORM 2.0 is still in the formative stages and won’t be ready for a few years (minimum), but you can certainly join the conversation and help mold SCORM 2.0 by visiting the LETSI site.

Update: LETSI is no longer tasked with SCORM 2.0, which will remain with the ADL. LETSI will continue to work on e-learning technology standards, including a potential replacement for SCORM.

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)

SCORM 2.0: high-level solutions or low-level tools?

Matt Wilcox posted an interesting argument about the development of the CSS3 standard; I think the central points of the argument can be applied to SCORM and where we’re potentially headed with SCORM 2.0.

After explaining some of the shortcomings of the current approach taken by the CSS3 Working Group, Matt writes:

What does CSS need to overcome these problems? First let me say what I think it really does not need. It does not need more ill thought out modules that provide half-baked solutions to explicitly defined problems and take a full decade to mature. We do not need the Working Group to study individual problem cases and propose a pre-packaged "solution" that either misses the point, is fundamentally wrong, or is inflexible. […]

The crux of the issue is that W3C seem to try providing high-level "solutions" instead of low-level tools. It’s a limiting ideology. With the CSS3 WG strategy as it’s been over the last decade, they would have to look at all of the problem points I proposed above, and come up with a module to provide a solution to each. But by giving [designers low-level tool functionality], we designers can build our own solutions to all of them, and innumerable other issues we have right now, and in the future.

As with the discussion regarding ECMAScript “Harmony”, I think LETSI should take a look at the meat of this argument and apply it to SCORM 2.0. Test cases are important for SCORM moving forward, but we can’t try to predict every issue a course developer might encounter — the possibilities are too numerous, and as we learned with Web 2.0, we can’t predict what technology (esp. browser capabilities) will be dominant in 5 years. What we can do is provide a loose framework or toolset that gives developers the flexibility to build their courses the way they want. This system would ensure interoperability by standardizing the tools and data management across LMSs.

You can read Matt’s post here: The Fundamental Problems of CSS3 by Matt Wilcox (via Dion @ Ajaxian)

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. ๐Ÿ™‚