In part one of this series, we published a simple Captivate course and examined its file structure. In this part, we’ll take an in-depth look at the HTML generated by Captivate (using the SCORM 2004 publishing template) and clean it up as much as we can.
Here’s the default HTML published by Captivate.
I’ll take the high road and avoid the ever-so-easy snarky comments about this code. Suffice to say it needs some serious cleanup; it’s obvious that multiple people with multiple coding styles have worked on this document, and no quality assurance team has run through it to ensure it’s clean and concise. For example, there is an entire block of code commented out at line 141 (commenting it out means it never gets used, and therefore is useless bloat).
There’s also an aborted attempt at using SWFObject from lines 214 to 242 — the object is declared, but never invoked. Never. Did you know this code has been shipping with Captivate for years? I’m amazed no one has bothered to clean it up (and yes, I have reported it to Adobe, but they chose not to act).
As irritated as I get looking at the code, this blog post is not meant to be a rant, so let’s be constructive and get to work cleaning this mess.
Cleanup task #1: Externalize the JavaScript
This document is incredibly hard to read due to all of the <script>
elements and JavaScript code. Let’s start by removing the JavaScript in the document’s <head>
by cutting it out and pasting it into an external JavaScript file.
View the results of task #1:
The new captivate.js file
The slightly cleaner sample.htm file
This single step makes the HTML file much easier to read.
Cleanup task #2: Fix the doctype
Stepping through the HTML, the very first lines of code (lines 3-4) contain a conflict: the doctype says this is an HTML 4 (transitional) document, but the head element contains an XHTML lang declaration.
We’re in an HTML5 world now, so let’s use the HTML5 doctype. It’s 100% compatible with all browsers (even Internet Explorer 6), and all the cool kids use it. We can retain the lang declaration, too, with a few modifications. While we’re at it, let’s update the charset meta tag to the accepted HTML5 syntax.
Cleanup task #3: End the crazy <script>
abuse!
There are six <script>
blocks in our HTML file, and they are all inconsistent. For example, some have a “type” attribute, some have a “language” attribute, some have both, and some have neither. This is a sure sign of multiple authors working on the document over a long period of time.
Going through the <script>
block, we can clean them up as follows:
- Line 8: Since we’re using the HTML5 doctype, we can simplify it to remove the “type” attribute.
- Line 9: Leave as-is.
- Line 10: Remove the “type” and “language” attributes.
- Lines 17-25: This entire block is never invoked and is completely useless. Delete it.
- Lines 37-73: The contents of this block need cleanup, but we’ll handle that later one. For now, just remove the “type” and “language” attributes.
- Lines 74-77: There is no need for a separate
<script>
block here; merge the contents with the previous<script>
block.
Cleanup task #4: Remove inline scripts and styles
The <body>
element contains two inline attributes: a style attribute, and an onunload handler. It’s an accepted best practice to leave your markup as clean as possible by removing inline styles and scripts when possible, so let’s create a new <style> element to hold the CSS, and move the onunload handler to our captivate.js file (just paste window.onunload = Finish;
at the bottom of captivate.js).
If you’re not sleeping yet, you’re probably wondering why creating a <style>
block is better than leaving the style attribute in the <body> tag. For this project, I know I will be adding more CSS declarations to the <style>
block, so we may as well set it up now.
Cleanup task #5: Cure <center>
itis
There are two sets of <center>
elements on the page that can be replaced by adding one line of CSS to the top of the page.
Delete lines 16, 47, 66 and 72. Add text-align: center;
to the body
selector in the <style>
block.
Cleanup task #6: Edit the <noscript>
block
For accessibility and usability purposes, it’s usually a good idea to include a <noscript>
block; this enables you to present a message or fallback content to visitors who have JavaScript disabled in their browsers.
Captivate uses SWFObject — a JavaScript utility — to embed the Captivate SWF. To hedge their bets, Adobe also attempts to embed the SWF in the <noscript>
block using a method that doesn’t require JavaScript. Normally this is a good idea. However, SCORM courses require JavaScript; if JavaScript is disabled, the course won’t be able to communicate with the LMS. Period. End of story.
For this Captivate template, it’s probably best to remove the SWF from the <noscript>
block and leave a message for the learner informing them that they can’t take the course unless JavaScript is enabled. Chances are they can’t get to the popup window containing the course anyway!
Cleanup task #7: Get rid of the <table>
markup
The JavaScript on lines 39-41 and 54-56 generate a <table>
whose sole purpose is to make the SWF vertically centered on the page. For our purposes, this is unnecessary because most courses launch in a popup window that has been sized to fit the course.
Ironically, the <table>
that is meant to make the embedded SWF look nice on the page actually causes a significant problem — the table has cellpadding and cellspacing set to 7, which means the SWF will never extend to the edge of the window. When you’re trying to make a perfectly-sized popup window for your course, this cellpadding and cellspacing will cause scrollbars to appear, driving you bonkers!
Also, it’s widely accepted that tables shouldn’t be used for layout purposes, anyway, so it’s best to get rid of the <table>
.
Our HTML file is starting to look pretty sweet!
Cleanup task #8: Externalize more JavaScript where possible
We externalized JavaScript in steps 1 and 4, let’s try and get the rest of this JavaScript externalized. The only remaining inline JavaScript is contained in lines 22 through 51. Our first task is to determine whether it needs to be inline; will moving it break anything?
- Lines 23 through 36 appear to be safe to move into the document’s
<head>
, which means it’s safe to move to our captivate.js file. Just cut and paste — place the script at the very bottom of captivate.js. - Lines 38 through 47 use SWFObject 1.5 to embed the SWF. We can move this code, but it will need to be wrapped in an onload event or else it will break the page. In a later step, we will upgrade SWFObject to 2.2, which helps us optimize the Captivate template even further.
- Lines 49 and 50 can be moved, but will also need to be wrapped in an onload event.
While moving the SWFObject code, we can also clean it up a bit:
- Line 39 is not necessary because quality = high is Flash Player’s default.
- Line 41 is not necessary because SWFObject is already set to use “Captivate” (the second argument passed on line 38) as the ID for the SWF.
- Line 42 is not necessary because wmode = window is Flash Player’s default.
- Line 45 is not necessary because it does nothing, it’s a completely gibberish line of code. I suspect it was a placeholder put there by a developer years ago, and no one has bothered removing it. It has shipped with Captivate for years.
- Line 46 is useless; the redirect URL is only invoked when two criteria are met: 1) the embed markup is present in the HTML, and 2) the visitor does not have the required version of Flash Player. This Captivate file is using SWFObject to write the embed code. SWFObject has its own check for Flash Player (argument #5 on line 38); if the minimum version specified is not found, SWFObject will not write the embed code into the HTML. This means requirement #1 listed above will never be met, which means the redirect URL will never be invoked.
Update: Upon re-reading this post, I noticed even more issues with the SWFObject code: line 40 (‘name’ is not a param, it’s an attribute) and line 43 (bgcolor param isn’t needed, the color should be specified as the 6th argument in the SWFObject declaration). I won’t update the examples because we’re replacing SWFObject 1.5 with 2.2 in the next part of this series, so the entire SWFObject code block will be replaced.
We can pare it down more later, but this is a great start.
View the updated captivate.js file.
We were able to reduce the sample.htm file from 278 lines of code to 23 lines. Our new captivate.js weighs in at 224 lines of code, but we’ll be taking a sledgehammer to the JavaScript code in part three of this series.
Comments
Kelly Meeker wrote on January 12, 2012 at 9:22 am:
As always, great resources for the technical elearning developer. Thanks so much for sharing!
Eric Wright wrote on January 12, 2012 at 2:27 pm:
Awesome write-up!
Is there going to be a part in this series where we replace the internal SWF tracking code with a custom MMTracking? That code didn't get any better in 5.5, did it?
philip wrote on January 12, 2012 at 2:37 pm:
I won't be digging in to the ActionScript code in this series, but I might in a future post. This series is definitely a building block for getting to that point.
As for SCORM improvements, I'm not holding my breath. Adobe's priority across all products appears to be new features — hot selling points — not bug fixes. Most people won't pay for bug fixes, they expect them for free. (That includes me.)
Jim Leichliter wrote on January 12, 2012 at 8:15 pm:
THANK YOU! THANK YOU! THANK YOU!
For taking the time to fix this. Adobe needed a good kick in the buttox for putting out sloppy schtuff like this. You Rock!
wl wrote on February 13, 2012 at 6:08 am:
Hi Phil,
I need to get this cleanup on 2.1 template kinda quickly 🙂
I'd be happy to pay you for ths work and also give permission to release this to anyone once doneregards
WL
Ron wrote on April 10, 2012 at 8:35 am:
Is this code correct? I had a problem with right-click not working on an interactive slide. I removed the space after "Tempso" and it seemed to work.
<pre>
var tempso = new SWFObject("SCORM_support/scorm_support.swf", "scorm_support", "2", "2", "10", "#CCCCCC");
tempso .addParam("quality", "high");
tempso .addParam("id", "scorm_support");
tempso .addParam("bgcolor","#FFFFFF");
tempso .addParam("menu", "false");
tempso .addParam("movie", "SCORM_support/scorm_support.swf");
tempso .setAttribute("redirectUrl", "http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash");
</pre>
Great series, THANKS!!
philip wrote on April 10, 2012 at 8:50 am:
@ron sorry, nope, completely incorrect — Captivate never uses the tempso <code>object</code>. read the rest of the article for details (esp. Part 3: JavaScript).
As always, great resources for the technical elearning developer. Thanks so much for sharing!
Awesome write-up!
Is there going to be a part in this series where we replace the internal SWF tracking code with a custom MMTracking? That code didn’t get any better in 5.5, did it?
I won’t be digging in to the ActionScript code in this series, but I might in a future post. This series is definitely a building block for getting to that point.
As for SCORM improvements, I’m not holding my breath. Adobe’s priority across all products appears to be new features — hot selling points — not bug fixes. Most people won’t pay for bug fixes, they expect them for free. (That includes me.)
THANK YOU! THANK YOU! THANK YOU!
For taking the time to fix this. Adobe needed a good kick in the buttox for putting out sloppy schtuff like this. You Rock!
Hi Phil,
I need to get this cleanup on 2.1 template kinda quickly 🙂
I’d be happy to pay you for ths work and also give permission to release this to anyone once done
regards
WL
Is this code correct? I had a problem with right-click not working on an interactive slide. I removed the space after “Tempso” and it seemed to work.
Great series, THANKS!!
@ron sorry, nope, completely incorrect — Captivate never uses the tempso
object
. read the rest of the article for details (esp. Part 3: JavaScript).