CaptivateController Updated to Support Adobe Captivate 5

By popular demand, I’ve updated my CaptivateController to work with Adobe Captivate 5 (CP5). Since this is an open-source project, there’s no upgrade fee. (What? “Adobe” and “no upgrade fee” in the same paragraph?!) I kid, I kid… I’m a kidder.

As you may have heard, Adobe Captivate 5 is a complete re-write of the Adobe Captivate application. As such, there are a few significant changes under-the-hood. For example, Adobe Captivate 5 does not support ActionScript 2, and will only publish to ActionScript 3. The Captivate developers eliminated a few of the old system variables while adding a few new ones. Showing and hiding the playbar now works very reliably (yay!). Most notably, the developers added extra ExternalInterface support via the cpEIGetValue and cpEISetValue callback functions while eliminating the cpSetValue callback, which explains why the previous version of the CaptivateController didn’t work with CP5 SWFs.

As for the updated CaptivateController, it works the same as the previous version. Most of the changes were under-the-hood, so you shouldn’t need to edit any of your code, and should be able to drop this new version on top of your old one. No system restart required!

If you encounter any bugs, please let me know by posting in the comments. I’d also be happy to hear any success stories you may have.

Advertisements

Comparing and cloning objects in JavaScript

Here’s a handy way to determine if two JavaScript objects are identical without using a framework like jQuery or MooTools:


var compare_objects = function (obj1, obj2){

    var parameter_name;
    
    var compare = function(objA, objB, param){
        
        var param_objA = objA[param],
            param_objB = (typeof objB[param] === "undefined") ? false : objB[param];
        
        switch(typeof objA[param]){
            case "object": return (compare_objects(param_objA, param_objB));
            case "function": return (param_objA.toString() === param_objB.toString());
            default: return (param_objA === param_objB);
        }
        
    };
    
    for(parameter_name in obj1){
        if(typeof obj2[parameter_name] === "undefined" || !compare(obj1, obj2, parameter_name)){
            return false;
        }
    }

    for(parameter_name in obj2){
        if(typeof obj1[parameter_name] === "undefined" || !compare(obj1, obj2, parameter_name)){
            return false;
        }        
    }

    return true;

};

Here’s an easy way to clone a JavaScript object:


var clone_object = function (original_obj) {
    var new_obj = {};
    for(var param in original_obj) {
        if(original_obj.hasOwnProperty(param)){
            if(typeof(original_obj[param]) === "object"){
                new_obj[param] = clone_object(original_obj[param]);
            } else {
                new_obj[param] = original_obj[param];
            }
        }
    }
    return new_obj;
};

A real-world example of the two functions:


var object_1 = { fruit: "apple", tree: "dogwood", number: 3 };
var object_2 = { fruit: "apple", tree: "dogwood", number: 3, cartoons: { anime: "robotech", fantasy: "he-man" } };
var object_3 = clone_object(object_2);

console.log("objects 1 and 2 match? " +compare_objects(object_1, object_2));
//displays false;

console.log("objects 2 and 3 match? " +compare_objects(object_2, object_3));
//displays true

Bear in mind both of these functions are recursive, which means the larger the object, the slower the performance.

For Your Reading Pleasure: EasyCaptions

Introducing EasyCaptions: A simple system for adding captions and an interactive transcript to online videos. EasyCaptions uses progressive enhancement to provide the best possible experience for all visitors, regardless of their browser’s JavaScript, HTML5 or Flash support.
Demonstration

Background

I don’t produce much video these days, but as a web surfer I often encounter other people’s videos, and was recently impressed by two video implementations: a TED Talks video page, and an HTML5 video demo produced by Bruce Lawson.

The TED Talks page had a great feature I’d never really seen anywhere else: an interactive transcript of the video that you can read and click. For example, you can click the third sentence of the transcript and the video will jump to that point.

Bruce Lawson’s demo illustrated a dead-simple way to add captions to an HTML5 video using just a wee bit of HTML markup and JavaScript.

Both of these pages shared a unique attribute: they stored complete transcripts of the videos in the markup of the HTML page itself, NOT in an external XML file, as is most commonly seen. What’s the big deal about inline transcripts, you ask? Well, today’s most common captioning options require placing your caption text in an external XML file that loads via ActionScript or JavaScript/AJAX. Both the TED page and Bruce’s demo eschew that approach and place the full transcript in the HTML. This means the transcript is always available to the visitor, regardless of that browser’s support for HTML5, JavaScript or Flash Player.

In both cases, the transcript had been marked up as paragraphs, with extra markup denoting phrases that align with the video’s timecode. The TED site used a tags to mark each phrase, with inline JavaScript triggering the playback of the clicked link. This is functional when JavaScript is enabled, but fails when JavaScript is disabled, leaving you with a ton of bloated markup that doesn’t work:


<p>
<a href="#" class="transcriptLink" onclick="seekVideo(0); return false;">One way to change our genes is to make new ones,</a> 
<a href="#" class="transcriptLink" onclick="seekVideo(2000); return false;">as Craig Venter has so elegantly shown.</a>
</p>

Bruce’s span tags, on the other hand, are semantically sound — a span is a neutral, unobtrusive inline element meant to be a child of a block element such as p. Perfect. But wait, there’s more! Since this was an HTML5 demo, Bruce took advantage of the new data attribute that can be used on just about any HTML element. (The short version is: you can create any custom attribute you want, so long as the name begins with data-.) Bruce decided to create two attributes for each span: data-begin, which indicates (in seconds) when the phrase starts in the video, and data-end, which indicates when that phrase has ended in the video. Simple as can be, and extremely efficient:


<p>
<span data-begin=1 data-end=6>Hi, my name's Dr Archimedes Einstein 
and I'm a Dctor of Science at the University of Science</span>
</p>

Enter: EasyCaptions

I was incredibly inspired by these two pages and decided to combine their features, creating a new system I call EasyCaptions. The goal of EasyCaptions is to make it as simple as possible to add useful captions to your online videos. I want to eliminate the headaches of captioning as well as the excuses (too hard, confusing, etc.).

What EasyCaptions does:

  • Dynamically generates a div under your video that will display caption text (the div‘s style and positioning is completely configurable via CSS).
  • Dynamically makes each span in your transcript clickable, jumping to that point in the video. This is done via progressive enhancement and event delegation, leaving your markup clean and avoiding heavy-handed use of onclick.
  • Works out-of-the-box with HTML5.
  • Includes HTML5 video support detection, enabling you to use a Flash-based fallback if you desire.
  • Provides a hook for Flash-based fallback systems, enabling the captions and transcript to behave identically to the HTML5 version.

What you’ll need to do:

  • Type up the transcript of your video, using <span> tags to wrap each phrase, just like Bruce’s example above.
  • Place the transcript in a container element with a unique ID (such as div id="transcript").
  • Add a teeny bit of JavaScript (about 4 lines).

The final product. Successfully tested in Firefox, Safari, Opera, and Internet Explorer (IE and older versions of the other browsers all use a Flash Fallback if provided).

Peruse the test suite to get an idea of how flexible the system is.

The documentation and downloads are located on the EasyCaptions page.

Rounded corners on images using CSS3

Most browsers do not allow images to be cropped using CSS3’s border-radius. Tim Van Damme recently posted a workaround for this issue. It’s a nice trick, and doesn’t require JavaScript.

It does, however, require an extra span to be added to the page’s markup, which is quite a pain to manually apply to each image. Bram Van Damme (no relation to Tim) posted a simple jQuery script that automate’s Tim’s workaround with minimal effort. It’s a perfect example of using JavaScript for progressive enhancement purposes, as the page is still usable when JavaScript and/or CSS is disabled.

Being a MooTools kind of guy, I decided to whip up a MooTools-flavored version. Enjoy!


window.addEvent("domready", function (){
    $$("img").each(function(img){
        new Element("span", {
            "class": "rounded",
            styles: {
                "background-image": "url(" + img.getProperty('src') + ")",
                height: img.getProperty('height') + "px",
                width: img.getProperty('width') + "px"
            }
        }).wraps(img);
    });
});

Bear in mind this JavaScript relies on your page having some pre-defined CSS:


.rounded {
    -webkit-border-radius: 25px;
    -moz-border-radius: 25px;
    border-radius: 25px;
    display: block;
}
.rounded img { opacity: 0; }

View demo

Notes:

  • The demo page has the code wrapped in a function, which makes the code reusable; it accepts any CSS selector, such as “img”, “#myid img”, “p img”, “img.rounded”, etc.
  • This will only work in browsers that support CSS3 border-radius, which means no IE6/7/8 or older versions of Safari, Firefox, or Opera.

HTML5 Video, minus Ogg

As you’ve probably read somewhere on the interwebs, HTML5 is bringing native video support to browsers. This will enable us to embed a video file directly in our HTML, much like a SWF or image.

Background

You may have also heard that there’s currently a big controversy over what kind of video files will be supported. The defacto standard is MP4/H.264, which is used by Adobe in their Flash video format, and by huge media sites like YouTube. Mozilla, the makers of Firefox, refuse to support the MP4/H.264 standard because it isn’t open-source and free from licensing constraints.

Turns out H.264 is not public domain. Although the company that owns the H.264 patent has temporarily agreed to waive royalty fees for the next decade or so, they reserve the right to charge fees later on. Mozilla says no way, we will only support a video format that is free from licensing issues and has no patent holders (because patent holders can decide to sue some day). Mozilla supports the completely free/open-source Ogg format.

Apple and Adobe, already knee-deep in MP4/H.264 with their Quicktime and Flash video products, vow to press on with H.264.  Google also supports H.264 because YouTube relies on it, and because Google’s new Chrome browser is based on the WebKit project, which has Apple as a main code contributor. In case you haven’t noticed, Apple, Adobe and Google have pretty much cornered the internet video market the past few years, so if they’re throwing their support behind H.264, you can count on it being around for a while. Not to mention that many mobile devices, including the iPhone and most Android phones, have hardware that is designed specifically to support H.264 video, enabling smoother playback and less battery drain than non-dedicated hardware.

(For what it’s worth, Opera is in agreement with Mozilla and supports Ogg. However, not many people seem to pay attention to Opera these days, so they don’t appear to have much influence in this race. Microsoft has endorsed H.264 with it’s upcoming IE9 browser, but it won’t be available for some time.)

The problem

Firefox and Opera are essentially forcing websites to offer two versions of each video: an Ogg version and an MP4 version. In my opinion — and the opinion of many others — this simply will not do. Providing two different video files is not realistic, Ogg’s quality is inferior to H.264, and many computers and mobile devices have direct hardware support for H.264 but not Ogg. In reality, without MP4 support, HTML5 video is rendered useless for most site developers in Firefox and Opera.

The most logical workaround is to code <video> elements to work for MP4 and have a Flash Player-based fallback for older browsers and browsers that only support Ogg. Since the <video> element is designed to allow for fallback content, just like the <object> element, we can do this:


<video id="myvideo" width="480" height="360" controls>
    <source src="/video/file.m4v" type="video/mp4"></source>
    <object data="flash-video-player.swf" type="application/x-shockwave-flash" width="480" height="360">  
        <param value="flash-video-player.swf" name="movie"/>  
        <param value="file=/video/file.m4v" name="flashvars"/>
    </object>
</video>

This works fine in Safari, Chrome, Internet Explorer and older versions of Firefox and Opera that don’t support the <video> element. However, Firefox 3.6 and Opera 10.5 do something very irritating: even though they KNOW their <video> doesn’t support “video/MP4”, they load the <video> element anyway. Which is … like … OMG so duh … because the video can’t possibly play!

If the <video> element is loaded, Firefox and Opera will never load the fallback <object> containing the Flash-based fallback.

Because this behavior cannot be fixed with markup, we’re forced find a scripted workaround (notice that we haven’t used a single bit of JavaScript yet). Thankfully, there’s a pretty straightforward solution: Delete the <video> element in Firefox and Opera.

A Solution

Here’s a simple script that will detect whether HTML 5 video is supported, and if it is, will check to see if MP4 is supported. If HTML5 video is supported but MP4 is NOT supported, the script deletes the specified <video> element but leaves the Flash fallback in its place.


var detectVideoSupport = function (){
    var detect = document.createElement('video') || false;
    this.html5 = detect && typeof detect.canPlayType !== "undefined";
    this.mp4 = this.html5 && (detect.canPlayType("video/mp4") === "maybe" || detect.canPlayType("video/mp4") === "probably");
    this.ogg = this.html5 && (detect.canPlayType("video/ogg") === "maybe" || detect.canPlayType("video/ogg") === "probably");
    return this;
};

var replaceVideoWithObject = function (video_id){    
    if(!video_id){ return false; }
    var video = document.getElementById(video_id);
    if(video){
        var obj = video.getElementsByTagName("object")[0];
        if(obj){
            var obj_copy = obj.cloneNode(true);
            video.parentNode.insertBefore(obj_copy, video);
            video.parentNode.removeChild(video);
        }
    }
};

window.onload = function (){
    var video = detectVideoSupport();
    //Both Opera and Firefox support OGG but lack MP4 support
    if(video.ogg && !video.mp4){
        replaceVideoWithObject("myvideo");
    }
};
</script>

Functioning demo.

A few notes

Tested successfully in:

  • Windows XP: Firefox 3.0, Firefox 3.5.8, Internet Explorer 6, Internet Explorer 8, Google Chrome 4.1.2
  • Windows Vista: Internet Explorer 7
  • Mac OS X (10.6): Firefox 3.6, Safari 4.01, Chrome 5 (beta), Opera 10.1, Opera 10.5b

(Note: IE6, IE7 & IE8 give an unexplained “object required” error in the demo page, but everything works fine. I will investigate as time permits.)

This demo uses the JW Media Player as the Flash fallback video player, but you can use any Flash-based video player on your page.

This demo doesn’t do any Flash Player version detection to ensure the visitor has a supported version of Flash Player. If you need to add Flash Player version detection, you can use SWFObject to embed your Flash file.

Update April 3, 2010: This post was updated to add Opera 10.5 to the list of non-behaving browsers and remove Firefox user agent sniffing.

A new removeClasses utility for MooTools

Note: If you want to skip to the final code (in both MooTools and framework-neutral flavors), it’s at the bottom of this post.

The problem

Readers of this blog know that I enjoy MooTools. Like other JavaScript frameworks, it has many excellent features, including addClass and removeClass functions, which I use all the time. However, when I was working on my CustomInput class the other day, I discovered a major shortcoming of MooTools’ removeClass function — it doesn’t work very well when trying to remove multiple classes (as of MooTools version 1.2.4). In particular, if you specify multiple classes to remove, removeClass will only work if the classes are listed in that element’s className property in the order specified.


//Assuming element has className "hello cruel world"

//Single terms work fine
element.removeClass("world"); //becomes "hello  world"
element.removeClass("hello"); //becomes " cruel world"

//Multiple terms listed in same order as className works fine
element.removeClass("hello cruel"); //becomes " world"

//Multiple terms NOT listed in same order as className fail
element.removeClass("hello world"); //remains "hello cruel world"
element.removeClass("cruel hello"); //remains "hello cruel world"

The cause

A peek at MooTools’ removeClass code reveals the shortcoming:


removeClass: function(className){
   this.className = this.className.replace(new RegExp('(^|\s)' + className + '(?:\s|$)'), '$1');
   return this;
}

The string containing the class names you want to remove is passed as-is; it remains a whole string, and is not broken down into substrings. So "hello world" remains the single string "hello world" instead of two separate strings "hello" and "world".

I tested jQuery’s removeClass function and noticed it doesn’t have the same problem; it will remove each word, no matter what order you specify. Taking a look under the hood reveals a completely different approach to removeClass:


removeClass: function( value ) {
   if ( jQuery.isFunction(value) ) {
      return this.each(function(i) {
         var self = jQuery(this);
         self.removeClass( value.call(this, i, self.attr("class")) );
      });
   }

   if ( (value && typeof value === "string") || value === undefined ) {
      var classNames = (value || "").split(rspace);

      for ( var i = 0, l = this.length; i < l; i++ ) {
         var elem = this[i];

         if ( elem.nodeType === 1 && elem.className ) {
            if ( value ) {
               var className = (" " + elem.className + " ").replace(rclass, " ");
               for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
                  className = className.replace(" " + classNames[c] + " ", " ");
               }
               elem.className = jQuery.trim( className );

            } else {
               elem.className = "";
            }
         }
      }
   }

   return this;
}

The big difference between jQuery and MooTools in this case is that jQuery converts the arguments to an array (split using spaces as a delimiter) then loops through the className property to search for each word in the array, whereas MooTools performs a simple full-string replace using regular expressions.

The search for a solution

My first reaction was to build a MooTools-flavored variation of jQuery’s removeClass code.


Element.implement({
   removeClasses: function (classNames) {
      if(this.className){
         var classNameString = this.className;
         classNames.split(/s+/).each(function (term){
            classNameString = classNameString.replace(term, " ");
         });
         this.className = classNameString.clean();
      }
      return this;
   }
});

It follows the same basic principles of the jQuery version, but uses MooTools’ Array.each and String.clean utility functions. It works well, but I wasn’t thrilled about using a loop. I thought maybe a regular expression would be better suited for the job.

I eventually came up with this:
(purposely verbose to explain what’s happening)


Element.implement({
   removeClasses: function (classNames) {
      if(classNames && this.className){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = this.className.replace(reg, " ");
         //Use MooTools' 'clean' method to remove extraneous spaces
         newClass = newClass.clean();
         //Set element's classname to new cleaned list of classes
         this.className = newClass;
      }
      return this;
   }
});
Notes:

  • I’m no expert on JavaScript speed tests, so for all I know a loop might be quicker. However, a regular expression feels more elegant.
  • I named my utility removesClasses so it doesn’t overwrite MooTools’ built-in utility.

Final MooTools version

Here’s a more concise version:


Element.implement({
   removeClasses: function (classNames) {
      this.className = this.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").clean();
      return this;
   }
});

You can see it in action via the MooShell (apparently the MooTools Shell is no longer online)

Standalone (framework-neutral) version

For those of you who don’t use MooTools, a few small edits will allow you to use this code without relying on any outside JavaScript libraries. First, a verbose version explaining what’s happening:


function removeClass(el, classNames) {
   //Only run if the element is available and supports the className property
   if(el && el.className && classNames){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = el.className.replace(reg, " ");
         //Use regular expression to remove extraneous whitespace between class names
         newClass = newClass.replace(/s+/g, " ");
         //Use regular expression to remove all whitespace at front and end of string
         newClass = newClass.replace(/^s+|s+$/g, "");
         //Set element's classname to new cleaned list of classes
         el.className = newClass;
   }
}

The concise version:


function removeClass(el, classNames) {
   if(el && el.className && classNames){
      el.className = el.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").replace(/s+/g, " ").replace(/^s+|s+$/g, "");
   }
}

Used as follows:


var myelement = document.getElementById("myelement");
removeClass(myelement, "one three");
//<div id="myelement" class="one two three"></div>
//becomes
//<div id="myelement" class="two"></div>

Enjoy!

Successfully tested in Internet Explorer 6 (WinXP), Firefox 3.5 (WinXP), Firefox 3.6 (OS X 10.6.2), Safari 4 (OS X 10.6.2), Opera 10.1 (OS X 10.6.2), Chrome 5 (OS X 10.6.2)

CustomInput Class: Accessible, Custom-Styled Checkboxes and Radio Buttons

I’m a big fan of the Filament Group’s UI work.  They put a lot of thought into their work, and ensure everything they make is not only beautiful, but as accessible and as semantic as possible.

One of my favorite pieces of work by the Filament Group is their approach to stylized checkbox and radio <input> elements, as described in their post Accessible, Custom Designed Checkbox and Radio Button Inputs Styled with CSS (and a dash of jQuery)

The system is remarkably well-constructed, degrades gracefully, and is completely accessible if JavaScript, image loading, or CSS is disabled can’t be loaded. No small feat. Plus it remains semantically sound, with no bloated markup.

Having said all that, I’ve never actually used any of Filment Group’s UI code in my projects because of one little obstacle: they rely on jQuery.  Mind you, I’m not a jQuery hater, but I prefer MooTools and build all my major projects using MooTools. Switching to jQuery for such a small UI customization is not going to happen. I’ve often thought it would be great to build a MooTools version of Filament’s UI examples, but never had the time… until today.

I’m currently working on a new quiz system at work, and decided I’d incorporate Filament’s wonderful stylized checkboxes and radio buttons into my project, which meant it was time to roll up my sleeves and code me some Moo. 

View Demo

I made a couple of changes to the underlying JavaScript to suit a MooTools approach (and add some flexibility with the CSS selectors) but the basic premise is the same as Filament’s demo.

The following JavaScript will style ALL checkbox and radio inputs on your page. Note that CustomInput returns the collection of elements that have been styled.


window.addEvent("domready", function(){ 
    var all_styled_inputs = new CustomInput();
});

If you want to target specific parts of the page or only certain input types, pass a CSS selector as an argument:


window.addEvent("domready", function(){
    //Only style checkboxes
    var styled_checkboxes = new CustomInput("input[type='checkbox']");

    //Only style radios in a div named "walkman"
    var styled_radios = new CustomInput("#walkman input[type='radio']");
});

The CustomInput class has been fully JSLinted, and remains very close to the original jQuery version’s size. When compressed (YUI), it squeezes down to about 1.6kb. It uses ‘dollar-safe’ mode for compatibility with other JS libraries.

It has been successfully tested in the following systems:

Windows XP

  • Internet Explorer 6
  • Firefox 3.5
  • Safari 4
  • Chrome 4
  • Opera 10

Mac OS X (10.6.2)

  • Firefox 3.6
  • Safari 4
  • Opera 10.1
  • Chrome 5 (beta)

View Demo | Download Project Files

Major kudos to Filament Group for sharing their ideas with the world.

Update 3/12/2010: Fixed a typo in the JS file preventing the checkedHover class from being assigned.

Introducing LearnSWFObject.com

I’m happy to introduce you to my latest project, LearnSWFObject.com

It’s been many, many months in the making, and has rudely been put aside a number of times when my life left me no free time to work on it. Happily, I’ve been able to push through the last remaining barriers and get the site out the door. Hopefully someone will find it useful.

I’ve ported my most popular SWFObject tutorials and examples to the new site — updated, of course — and have added a few new tutorials, too. As part of the housecleaning effort, I’ve removed all SWFObject examples and tutorials from pipwerks.com. 301 redirects will re-route the most popular posts to their LearnSWFObject.com equivalent, but the odds and ends have been sent to the compost heap.

I’m most excited about the brand-new code generator (written from scratch) that will write your SWFObject embed code for you. Some notes about the generator:

  • It can write HTML 5, HTML 4 (transitional/strict), and XHTML 1 (transitional/strict) doctypes
  • It provides code for both types of SWFObject embeds: dynamic (JavaScript) and static (markup)
  • It includes a download link that lets you save your generated markup in an HTML file
  • The static publishing option includes an option for a nice, shorter syntax
  • The generator uses progressive enhancement techniques, meaning it’s nicer with JavaScript enabled but fully functional without it

Give it a try!

LearnSWFObject.com will remain a work in progress, as SWFObject itself continues to evolve and people find news ways to break use it in their sites. I will continue to play with the formatting and layout from time to time.

In other SWFObject news, I think I’m allowed to report that SWFObject 2.3 is in the works, and will contain mostly bug fixes and an enhancement or two. Bug reports and feature requests are always welcome at SWFObject’s Google Code site. If you have questions about how to use SWFObject that aren’t answered by the LearnSWFObject.com tutorials, please post them on the SWFObject Google Group.

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.