Clean out the root of your SCORM 2004 package

Anyone who works with SCORM 2004 has seen something like this:

Image of file directory with all schema files at root of directory

With just a little effort, you can make it look like this, and still be perfectly valid:

Image of file directory with all schema files placed in subfolder

SCORM manifests are required to specify a slew of schema files via the schemaLocation attribute. Here’s what you’d typically see:


<manifest identifier="pipwerks-schema-example" version="1.0"
          xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" 
          xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" 
          xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3" 
          xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3" 
          xmlns:imsss="http://www.imsglobal.org/xsd/imsss" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
          xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd 
                              http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlseq_v1p3 adlseq_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlnav_v1p3 adlnav_v1p3.xsd 
                              http://www.imsglobal.org/xsd/imsss imsss_v1p0.xsd">

Notice the structure of the data in the schemaLocation attribute: external URL followed by a space then the local (relative) URL. For example:


http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd

In this example, imscp_v1p1.xsd is at the root of the package, in the same folder as the imsmanifext.xml file. The trick is to create a subfolder in the root of the package, then update schemaLocation to point to the subfolder. I created a subfolder named SCORM-schemas, which you can see in the following code exerpt:


<manifest identifier="pipwerks-schema-example" version="1.0"
          xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" 
          xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" 
          xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3" 
          xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3" 
          xmlns:imsss="http://www.imsglobal.org/xsd/imsss" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
          xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 SCORM-schemas/imscp_v1p1.xsd 
                              http://www.adlnet.org/xsd/adlcp_v1p3 SCORM-schemas/adlcp_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlseq_v1p3 SCORM-schemas/adlseq_v1p3.xsd 
                              http://www.adlnet.org/xsd/adlnav_v1p3 SCORM-schemas/adlnav_v1p3.xsd 
                              http://www.imsglobal.org/xsd/imsss SCORM-schemas/imsss_v1p0.xsd">

Test, test, test! I’ve tested this in SCORM Cloud as well as a couple of real-world LMSs and haven’t encountered any issues. Your mileage may vary depending on your LMS’s SCORM implementation, but this is perfectly valid XML and shouldn’t break in any LMSs — unless the LMS is poorly coded, but that’s a rarity, right? (LOL)

Advertisements

Important Adobe Captivate SCORM template update

Over the last few weeks, I received a few reports that scores were not being saved in the LMS when using my template. Turned out to be a simple oversight on my part, which I have just fixed. Please download the latest version of scorm_support.js (v1.20120328) from GitHub.

Cause and effect

If you’re curious what happened, here’s a quick rundown:

When a SCORM course launches for the first time, the value of cmi.completion_status is "ab-initio". This means the course is a fresh launch with no prior completion attempts, and therefore no historical data in the LMS.

When Captivate launches, it requests a slew of information from the LMS via SCORM_API.GetValue. This includes the usual suspects, such as completion status, suspend data, location, score.raw, score.max, score.min, and score.scaled. However, if the course has never been launched before, suspend_data, location, and the score elements will all be empty (null). If the LMS follows the SCORM spec, it will throw the “element not initialized” error.

In my earlier work on the template, I decided to prevent these “element not initialized” errors by adding some logic to the template, preventing suspend_data, location, and the score elements from being checked when the course status is ab-initio. This was achieved via a regular expression:


if(entryStatus === "ab-initio" && /location|suspend_data|score/g.test(parameter)){
   //prevent action
}

Unfortunately, I overlooked one important detail: when the Captivate course loads, it queries the LMS to see which SCORM fields are supported. This is done by requesting the “._children” CMI elements. For example, cmi.score._children will return the string “scaled,min,max,raw” indicating that cmi.score.scaled, cmi.score.min, cmi.score.max, and cmi.score.raw are supported by the LMS.

See any problems yet?

My regular expression was too broad, and prevented cmi.score._children from being queried, making Captivate believe that cmi.score was not supported. Since Captivate thought cmi.score was not supported, it did the right thing and stopped sending cmi.score data to the LMS.

The solution was to make the regular expression a bit more explicit:


if(entryStatus === "ab-initio" && /location|suspend_data|score\.(raw|min|max|scaled)/g.test(parameter)){
   //prevent action
}

Instead of blocking any GetValue calls requesting “score” data when the course is ab-initio, we now only block GetValue calls that request specific CMI elements: score.raw, score.min, score.max, and score.scaled. Problem solved.

Introducing SWFRightClick

Adobe Captivate currently ships with a 3rd-party JavaScript utility named RightClick.js, which enables the Captivate SWF to detect when a user right-clicks the SWF. While upgrading the Captivate publishing templates, I realized RightClick.js wasn’t built to work with SWFObject 2.x and suffered from a few shortcomings. I modified the Captivate template’s SWFObject code to get around the issue, but marked it down as something to revisit when I have the time.

Now, I’m happy to report I’ve created a replacement for the RightClick.js utility, creatively named SWFRightClick. It uses the same approach to handling right-clicks, but does it with a completely new codebase and a few extra goodies. SWFRightClick is compatible with every edition of SWFObject, and is free to use (MIT license).

Check it out on GitHub. I plan to fold it in to my Captivate publishing templates very soon.

New SCORM 1.2 Template for Adobe Captivate

By popular demand, the SCORM 1.2 edition of my revised SCORM publishing templates for Adobe Captivate 5.x is now available on GitHub.

Instructions can be found here.

While testing the SCORM 1.2 revisions, I noticed Captivate sometimes sends invalid data to the LMS, specifically for cmi.interactions.n.correct_responses.n.pattern, cmi.interactions.n.student_response, and cmi.interactions.n.weighting. I may fix these errors in a future update, but they’re relatively harmless, so I’ll leave them be for now.

Further Tweaks to the Adobe Captivate SCORM Publishing Template

Now that my version of the Adobe Captivate publishing template for SCORM 2004 is on GitHub, it has become a living document, bound to get updates (major and minor) from time to time. For those of you unfamiliar with GitHub, it’s a nifty site for storing code; it provides issues list for tracking bugs, it enables people to leave comments or make code suggestions, and it even lets you copy an entire open-source project with a single click!

Since the code for my templates will remain on GitHub, I highly suggest checking in from time to time to see if the code has been updated. I won’t be posting a blog entry on pipwerks.com for every little edit I make to the code.

Speaking of edits, I made two or three tonight, spurred by an insightful comment from Jimmi Thøgersen.  He noticed a bug or two, and explained some of Saba’s bugginess — thanks Jimmi! If you know of any oddities or bugs, please let me know by posting an issue on GitHub.

Cleaning up Adobe Captivate’s SCORM Publishing Template, Part 7: Giving the Revisions a Home

I decided to post my revised Adobe Captivate publishing template to GitHub, where it can be easily copied, forked, and updated. Currently, the only files are for the Captivate 5.0 and 5.5 templates for SCORM 2004. I hope to add SCORM 1.2 soon, as well as replacing the default ‘standard.htm’ template, which doesn’t use any LMS-related code.

Update: The SCORM 1.2 template is now available.

If you take a look at Default.htm on GitHub, you’ll notice I’ve made a few changes since I wrote my series about editing the templates. I moved a few bits of markup/code around, added some configuration options (such as the ability to turn off centering, turn on logging, and require SCORM when loading), and added a ton of comments to explain some of the new options. Hopefully it’s all self-explanatory.

I also made a small edit to manifest2004.xml, and a few edits to scorm_support.js.

To use these template files, do the following:

  1. Make a backup of your entire publishing folder and put it somewhere safe!
  2. Go to Captivate’s Templates\Publish\SCORM\2004\ folder and replace Default.htm with the new file.
  3. Go to Templates\Publish\SCORM\2004\SCORM_support\ and replace scorm_support.js with the new file.
  4. While you’re in your SCORM_support folder, delete scorm_support.htm and scorm_support.swf, they won’t be used anymore.
  5. Go to Templates\Publish\ and replace manifest2004.xml with the new file.
  6. While you’re still in the Templates\Publish\ folder, replace standard.js with the new file.
  7. Restart Captivate and give it a try!

Find a bug? Think of a good edit for the template? Post a comment here, or better yet, file an issue on GitHub!

The State of Adobe Captivate’s SCORM Support

Hopefully my series on Adobe Captivate’s SCORM publishing templates has shed light on the sorry state of Captivate’s publishing templates; I know it’s easy to play critic, complaining and pointing out deficiencies, but in this case I feel it was constructive and appropriate. However, I would also like to provide some counterbalance and perspective regarding the status of Adobe Captivate and its development team.

First of all, I have met a number of the Adobe Captivate product team in person. To a man, they are all very nice people who are engaged in their work and very interested in making improvements. They are not disinterested slackers. They have reached out to the community to try and find new ways of using Captivate, and have shipped some of the suggestions they received, such as the ability to send data to a custom database, and the SCORM Aggregator.

In case you haven’t heard, Adobe has been going through many changes, and has laid off over two thousand employees in a 4-year span (600 in Dec. 2008, 680 in Dec. 2009, 750 in Nov. 2011). Today’s Adobe is not the same company some of us older folks grew to love in the early 90s; these days, Adobe’s management appears to be much more focused on profit than outstanding products. Adobe has adopted a very aggressive paid-update cycle; the Creative Suite alone has had six updates in 8 years. Captivate has had 5 paid updates in about 6 years. That’s a lot of money to re-purchase something you thought you already bought, and the new version doesn’t even guarantee bug fixes.

I have previously speculated that the Adobe Captivate team hasn’t fixed some of the product’s deficiencies because Adobe can’t sell bug fixes — Adobe focuses instead on shiny new features that are good marketing opportunities. I still stand by that sentiment. If you look around the blogosphere, it’s a common complaint across product lines — Fireworks, Photoshop, Flash, Illustrator, you name it. It’s a disease borne by management and marketing. Bug fixes aren’t sexy, new whiz-bang features are! Why waste your time on bug fixes? The new features will make the product so much easier to sell.

I have not heard a single comment from the Captivate team (or any other Adobe employee) regarding this theory, but one has to wonder if the Captivate team were forced to make concessions to this new management approach — tight deadlines and marketable new features — rather than customer satisfaction. In this environment, I can see how a SCORM publishing template — something that is creaky and old but still works most of the time — becomes a low priority.

I’ve heard rumors that the upcoming Captivate 6 will contain a completely revamped SCORM system that eliminates many of the issues I’ve covered. If this is the case, many of us will surely be elated, perhaps enough to pay for yet another upgrade. This could also help explain why Adobe hasn’t addressed the current SCORM publishing template; a complete overhaul of the existing SCORM system — including the ActionScript code inside the published SWFs — is a considerable amount of work.

I tried my hand at fixing the ActionScript code in the older CP4 projects a couple of years ago. I examined the ActionScript files that shipped with Captivate 4 and also decompiled some published Captivate SWFs (ActionScript 2) to see how they work. My biggest surprise — which makes perfect sense in hindsight — is that there’s a central tracking mechanism within Adobe Captivate that keeps track of location, score, quiz questions, etc. This tracking system has an adapter that can be used to plug in support for other tracking methods, such as SCORM , AICC, Adobe Connect, etc. In general, only the basic tracking details — the common denominators between systems, such as bookmarking and scoring — were used, all else was discarded. Hence the shallow SCORM support, missing features like cmi.interactions.

As such, any major internal changes to SCORM support would require changes to the central tracking system, and runs a high risk of breaking all other tracking (AICC, Adobe Connect, etc.). It makes sense that the Captivate team would take their time, consult with SCORM experts, and proceed with caution. Hopefully by this time next year, no one will need the cleaned up templates I just created, and Captivate 6 will provide some of the most solid SCORM support in the industry. Hopefully!

In closing, I want to reiterate that I stand by my previous criticisms, but also wanted to provide a bit of background and nuance; the world is rarely black and white, and this situation is no different. I root for the Captivate development team, and hope Adobe’s upper management gets their heads out of their wallets and lets their product teams — people who truly care about the products and end users — do what needs to be done rather than what contributes most to the bottom line.

Cleaning up Adobe Captivate’s SCORM Publishing Template, Part 6: Bugs & Scope Creep

For this blog post, I was going to write a summary of the changes to the template and wrap up the series. Instead, I’m looking at ways to increase the template’s flexibility and hot-rod it for some cool other stuff.

First, in case you missed it, here are the first five parts of the series:

  1. Part 1: Introduction
  2. Part 2: HTML
  3. Part 3: JavaScript
  4. Part 4: SCORM
  5. Part 5: Finishing up

This series has been an exercise in refactoring; I walked through the code and continued to refine it until I reached a point where the template performed as I felt it should have; a bare minimum of functionality was met, with a sprinkle of extra goodness.

The problem is, when it comes to refactoring code, you’re never really done! Flaws are found, ideas pop into your head when you’re trying to go to sleep, and suggestions come along, all prompting another look at your work. Bug fixes and new features (aka scope creep) rule the roost. This project is no exception.

Regarding bug fixes, as part of the cleanup process, I added a feature that displays an error message to the learner when the SCORM API isn’t found; this works great when launching the course from an LMS, but as Infosemantics’ Rod Ward pointed out, it prevents the developer from previewing the course locally. Oops. I also noticed that the code I added for centering the SWF makes the ‘no SCORM’ message look strangely off-center, in the rare cases where you’d see it. No worries, it will be fixed.

Regarding new features, there are many ways we can improve the template, or at least make it more flexible. For example:

  • We can add a debug mode that logs SCORM commands to the console; the debug mode could be toggled on or off via the CONFIG object in the HTML file.
  • Some Captivate developers like to use JavaScript with their course; the addition of the SWFObject callback function makes it trickier for developers to know where to place their extra code. We can help by providing a clearly defined space for these developers.
  • We can provide a compressed version of the template’s scorm_support.js file, which reduces file size.
  • We haven’t touched the imsmanifest.xml file, but there are probably some improvements we can make there, as well.

I’m sure there are other possibilities I’m leaving out. Improvement is an ongoing process, and I would love to hear any ideas you might have about improving Captivate’s SCORM 2004 publishing template. Post a comment below, or message me on Twitter.

(Note: I’m not a fuddy-duddy, but I will ignore and sometimes delete off-topic comments; I tend to get a lot of these, and they’re a drag to deal with. If you have general e-learning questions, please post them on the E-Learning Technology and Development Google Group, which I moderate.)

Reminder: all of this work is centered on Captivate 5.5, not older versions. Captivate versions 4 and 5 require a little bit of help from the inside (ActionScript delivered via a second SWF). I might cover that ground in a future post if I have time.

Update: I was mistaken about Captivate 5 — this revised template should work fine with both 5.0 and 5.5.

Continue to Part 7 of the series.

Cleaning up Adobe Captivate’s SCORM Publishing Template, Part 5: Finishing up

In part one of this series, we published a simple Captivate course and examined its file structure. In part two, we cleaned up the HTML file and externalized all JavaScript. In part three, we cleaned up the JavaScript. In part four, we updated the SCORM code. In this installment, we will put the finishing touches on our code and move our files into Captivate’s publishing folder.

In the first four parts of this series, we were working with actual course files; these files need to be converted into template files. Once they’re set up as templates, you can just pop them into your Captivate templates folder and never worry about it again.

Let’s take a quick look at Captivate’s template folder structure:

I’m on a Mac, but the folder structure is the same on a Windows machine.

Our files were gathered from several locations:

  • The Manifest2004 folder contains all of the SCORM 2004 XSD files. We don’t need to edit any of these files.
  • The manifest2004.xml file is the SCORM 2004 imsmanifest.xml template. We don’t need to edit this file, though there are improvements that can be made.
  • The SCORM folder contains a 2004 subfolder, which in turn contains most of the HTML and JavaScript files we’ll need to edit.
  • The standard.js file contains the SWFObject 1.5 library.

Cleanup task #1: Refactor the code to eliminate the captivate.js file

In part two of this series we externalized JavaScript by creating a new file named captivate.js. This was useful while we refactored code, because it helped separate JavaScript from HTML and gave us a clearer picture of what we needed to work on. However, now that we’re trying to insert our edits into a Captivate publishing template, were stuck with the limitations of Captivate’s template and cannot add new files to the template — we can only edit existing files. We will therefore need to make a choice: where shall we place the contents of our new captivate.js file? In the HTML file, in scorm_support.js, or in standard.js?

There’s an easy answer: the HTML file.

Yes, this goes counter to my previous argument about externalizing the JavaScript, but we’re given little choice. The standard.js file is shared with 12 other templates; if you plan to use any of the other templates that ship with Captivate, we’ll need to leave this file alone (yes, that means leaving the SWFObject 1.5 library in your course even though we won’t be using it! Thankfully it has a very small footprint.).

scorm_support.js is a decent place to put our scripts, but it has one huge drawback: Captivate will not write anything to this file when publishing… and therein lies the problem!

When publishing a Captivate project using the SCORM 2004 template, the filename and dimensions are written to the HTML file (SCORM\2004\Default.htm). In fact, Default.htm is also renamed to match your project’s name. scorm_support.js and standard.js are never touched by Captivate; they are static files. In order for our modified template to work, we have to enable Captivate to write project details (project dimensions, filename, colors, etc.) to the HTML file.

The good news is that we significantly cleaned up the JavaScript, so captivate.js only contains a tiny amount of code.

Here are the modified HTML and scorm_support.js files after we get rid of captivate.js. There is much work to be done.

View the result of task #1

Cleanup task #2: Create a configuration object

Web apps normally use configuration objects — objects that contain data about how the app works. Since we know Captivate will write project details in the HTML, it makes sense to create a ‘config’ object.

Technically we could just use global variables, but an object dedicated to configuration information is a very clean way of working.

var CONFIG = {

};

So… what shall we put into this CONFIG object? Let’s start by looking in Default.htm to see what Captivate makes available to us. Anything prefixed with @ is a variable that gets written to the page by Captivate during the publishing process. A quick scan through the page reveals the following:

  • @MOVIETITLE (used in the HTML title element)
  • @MOVIENAME (the SWF filepath)
  • @IsRightClickFunctionalityRequired (an attribute placed in the HTML body element)
  • @SKINCOLOR (an attribute placed in the HTML body element)
  • @FlashPlayerVersion
  • @MOVIEWIDTH
  • @MOVIEHEIGHT
  • @WMODEVALUE (specifies what wmode should be used by Flash Player)

The whole IsRightClickFunctionalityRequired thing is problematic, we’ll get back to that later.

Here’s what we can put into our CONFIG object. I’m renaming a few of them for clarity and/or simplicity:

var CONFIG = {
    MOVIETITLE: "@MOVIETITLE",
    FILEPATH: "@MOVIENAME",
    BGCOLOR: "@SKINCOLOR",
    FPVERSION: "@FlashPlayerVersion",
    WIDTH: "@MOVIEWIDTH",
    HEIGHT: "@MOVIEHEIGHT",
    WMODE: "@WMODEVALUE"
};

We should also specify the name of the element that SWFObject will use to embed the SWF. Captivate normally names this file “CaptivateContent”, but it would be good to list it in the CONFIG object in case we ever need to edit it. (Spoiler: we will be editing it later.)

If you recall, we also have an if/else clause that displays a message to the learner if the SCORM API fails to load. We should place that message in our CONFIG object as well. This way you can quickly and easily reconfigure the message to suit your needs without digging through all of the template files.

var CONFIG = {
    TITLE: "@MOVIETITLE",
    FILEPATH: "@MOVIENAME",
    BGCOLOR: "@SKINCOLOR",
    FPVERSION: "@FlashPlayerVersion",
    WIDTH: "@MOVIEWIDTH",
    HEIGHT: "@MOVIEHEIGHT",
    WMODE: "@WMODEVALUE",
    TARGET: "CaptivateContent",
    NOSCORM: "Sorry, but the course is not available at this time (SCORM API not found). Please try again. If you continue to encounter problems, please contact the course administrator."
};

Cleanup task #3: Refactor the code using the CONFIG object

Now that our CONFIG object is ready to go, we need to replace all of the hard-coded values from our sample course with these configuration items. (Make a backup copy first!)

While we’re at it, we’ll place @MOVIETITLE into the title element, and @SKINCOLOR into our style element. Because of a quirk of the Captivate system, @SKINCOLOR can’t be followed by a semicolon in the CSS.

This will fail (semicolon after SKINCOLOR):

body { background: @SKINCOLOR; text-align: center; }

This will work (no semicolon):

body { text-align: center; background: @SKINCOLOR }

This configuration object enables us to move all other JavaScript out of the HTML and into scorm_support.js; the only JavaScript remaining on the HTML page are user-defined configuration items. Way cool. We have to be careful to reorder the script elements, so that the CONFIG object is created before scorm_support .js loads.

View the result of task #3

Cleanup task #4: Tidy up the HTML file a bit more

There are a few items that need attention in the HTML.

1. Quirks mode. Adobe placed two HTML comments above the doctype, which will trigger Internet Explorer to display the page in quirks mode. Since our page layout is so simple, quirks mode shouldn’t affect us, but it’s still a good practice to avoid placing comments above the doctype.

Current code:

<!-- Copyright [2008] Adobe Systems Incorporated.  All rights reserved -->
<!-- saved from url=(0013)about:internet -->

Before we move the comments, let’s stop and ask: are these comments even necessary? Are they useful? Well, the first comment is an Adobe copyright notice, which is questionable at best. What, exactly, is being copyrighted? The content of the page? Does that include your Captivate handiwork? Is the HTML being copyrighted? I’m not a lawyer, but I’m pretty sure you can’t copyright HTML markup. There is no clarity. Since I’ve competely obliterated what was provided by Adobe, anyway, I’m going to remove the copyright notice. Feel free to put it back if it makes you nervous, just place it *under* the doctype.

The second comment is what’s affectionately called “the mark of the web” (MOTW). Another reason to thank Microsoft. The short explanation is that MOTW enables you to view your Captivate file locally in Internet Explorer by forcing Internet Explorer to treat the page as if it were loading from the internet instead of your hard drive. If you intend to do any local testing with Internet Explorer, you should keep the MOTW in your template. I’m going to cut and paste the version recommended by Microsoft:


<!-- saved from url=(0014)about:internet -->

2. Remove page margins and padding. By default, all HTML pages have padding around the edges of the document, as well as a slight margin. Let’s remove all padding and margins to ensure the SWF comes up to the edge of the browser window. This will remove unsightly gaps when launching the course in the LMS’s popup window.

* { margin: 0; padding: 0; }

You may be asking: “What happens if the window is bigger than the SWF?” Currently, the SWF will be aligned to the top left corner. Centering the SWF is tricky, we’ll come back to that later.

3. Place the noscript block inside CaptivateContent. This isn’t necessary, but it’s a clean way of working. SWFObject will replace fallback content — content that only displays if something isn’t supported, such as JavaScript or Flash Player — with the SWF upon successful embed, leaving just the bare minimum markup in the HTML.

HTML before embed:


<div id="CaptivateContent"><noscript>
      This course requires JavaScript to be enabled in your browser. Please enable JavaScript, then relaunch the course.
      </noscript></div>

HTML after embed:

<div id="CaptivateContent"></div>

View the result of task #4

Cleanup task #5: Fix the right-click issue

As I mentioned earlier, Captivate has a variable named @IsRightClickFunctionalityRequired. This variable is located in the opening body tag.

Here’s how it looks in the HTML template:

If the Captivate SWF requires right-click functionality at any point, @IsRightClickFunctionalityRequired will add an onload event to the body element:

This is problematic for many reasons. First of all, adding an onload event directly to the body is considered a bad practice; inline JavaScript should be avoided whenever possible. It’s also a bad idea because the onload event is likely to overrule any window.onload events we might have in the external JavaScript.

After digging further into the IsRightClickFunctionalityRequired system, I have two additional concerns:

  1. Using this right-click functionality requires disabling accessibility in the Captivate preferences.
  2. The codebase for this functionality comes from a project named RightClick for Flash Player. It was developed before SWFObject 2.x existed, and therefore requires SWFObject 1.5 unless we make some modifications to our code.

Regarding the first concern, unfortunately, there’s nothing we can do about this in the template. It’s a choice you’ll need to make when authoring your Capivate projects. If you don’t like disabling Captivate’s accessibility features, please let Adobe know.

To understand the second concern, I must quickly explain a key difference bewteen SWFObject 1.x and SWFObject 2.x:

In SWFObject 1.5 — the version being used by Captivate — the SWF gets embedded inside the element you specify. For Captivate projects, the HTML winds up looking like this:

SWFObject JavaScript:

var so = new SWFObject("mymovie.swf", "Captivate", "550", "400", "10", "#CCCCCC");
//options here
so.write("CaptivateContent");

HTML before embed:

HTML after embed:

<div id="CaptivateContent"></div>

SWFObject 2.x changes this behavior. It replaces the target element, taking the ID of the element it replaces, unless a new ID is specified in the attributes object.

SWFObject JavaScript:

//SWFObject optional variables
var flashvars = {};
var params = {};
var attributes = { id: "Captivate" };
var callback = function (){ };

swfobject.embedSWF("mymovie.swf", 
                   "CaptivateContent", 
                   "550", 
                   "400", 
                   "10", 
                   false, 
                   flashvars, 
                   params, 
                   attributes, 
                   callback);

HTML before embed:

After embed:

To summarize: SWFObject 1.5 embeds the SWF inside your element, leaving the original element as a wrapper around your SWF, while SWFObject 2.x replaces the original element, leaving no wrapper.

The RightClick for Flash Player codebase requires the SWF to be located inside a wrapper element.

Normally, I’d edit the RightClick for Flash Player codebase to work with SWFObject 2. Unfortunately for me, it’s located in standard.js, which is used by all 13 Captivate publishing templates; if I update the code there, I’d need to replace ALL Captivate publishing templates. Not interested!

The solution is to write a function that creates a new wrapper div in our template before SWFObject is invoked. Of course, we only want to create this extra wrapper if right-click functionality is required.

Add a “rightClickRequired” property to our CONFIG object and populate it with the output of Captivate’s @IsRightClickFunctionalityRequired:

CONFIG.RIGHTCLICKENABLED = '@IsRightClickFunctionalityRequired';

When right-click functionality is required, Captivate will populate CONFIG.RIGHTCLICKENABLED for us:

//Note the use of single quotes
CONFIG.RIGHTCLICKENABLED = 'onload="RightClick.init();"  ';

When right-click functionality is not required, CONFIG.RIGHTCLICKENABLED will contain an empty string.

All we need to do is check to see if CONFIG.RIGHTCLICKENABLED is an empty string; if yes, create a wrapper div, embed the SWF, then initialize the right-click codebase. If no, just embed the SWF as usual.

View the result of task #5

Cleanup task #6: Center the SWF

Oh boy, this one is rough. Centering an element should be achievable through CSS alone, but unfortunately, we have some crazy restrictions and exceptions to deal with.

If you know the dimensions of the element, it’s easy to center it. We know the dimensions of our SWF — it’s provided via @MOVIEWIDTH and @MOVIEHEIGHT — so we’re on easy street, right? Wrong.

As I mentioned earlier when discussing the CSS code for the body background color, Captivate will not write a variable to the page if the variable name is adjoining any other text.

Consider the following CSS:

#Captivate { width: @MOVIEWIDTHpx; height: @MOVIEHEIGHTpx; }

It fails because “px” can’t be adjoining the @MOVIEWIDTH variable. Unfortunately CSS doesn’t allow us to place a space between the number value and the unit type. Assume @MOVIEWIDTH outputs 550 and @MOVIEHEIGHT outputs 400; if a space is provided between the value and the unit, the code is invalid and will fail:

#Captivate { width: 550 px; height: 400 px; }

Captivate is forcing us to use JavaScript to write the CSS values. Uuuuuugly, but there’s no getting around it.

Normally, if you’d like to center an element horizontally, you can just define the width (which we’ll have to do with JS), then set margin as follows:

#Captivate { display: block; margin: 0 auto; }

But to center an element vertically, we have more hoops to jump through, and it prevents us from being able to use the margin: 0 auto approach for horizontal centering. While there are some nice simple technquies out there for new browsers, they fail in older browsers. Most older sites use tables for layout because tables provides an easy and quick way to vertically center an element. But if you’ve read this entire series of posts, you can guess we’re not going to use tables — tables shouldn’t be used for layout! Period.

The most reliable CSS method is to use absolute positioning. This requires knowing the dimensions of the element, so we’ll be using a combination of CSS and JavaScript.

Added to the SWFObject callback function:

//Fix the centering for the SWF
CaptivateSWF.style.marginTop = "-" +(CONFIG.HEIGHT / 2) +"px";
CaptivateSWF.style.marginLeft = "-" +(CONFIG.WIDTH / 2) +"px";

Added to the HTML page’s CSS:

/* JavaScript is used to set pixel values for margin-left and margin-top */
#Captivate { position: absolute; top: 50%; left: 50%; }

We also set the height of body to 100%. Height is inherited from a parent element, so setting 50% height on an element won’t work in some browsers until you specify the parent element’s height.

View the result of task #6

Cleanup task #7: Final massaging!

We’ve done just about everything we can. The last step is to see if we can clean up our code to make it more tidy (and readable), then run it through some syntax checkers to look for bugs. I suggest JSHint.com, a fork of JSLint that doesn’t aim to hurt your feelings.

One change I’ll make is refactoring my CONFIG object to make it easier to read. While I prefer the original syntax (it compresses better, too), many n00bs prefer the more verbose style because they find it less confusing.

var CONFIG = {},
    flashvars = {},
    params = {},
    attributes = {};

CONFIG.TITLE = "@MOVIETITLE";
CONFIG.FILEPATH = "@MOVIENAME";
CONFIG.BGCOLOR = "@SKINCOLOR";
CONFIG.FPVERSION = "@FlashPlayerVersion";
CONFIG.WIDTH = "@MOVIEWIDTH";
CONFIG.HEIGHT = "@MOVIEHEIGHT";
CONFIG.WMODE = "@WMODEVALUE";
CONFIG.RIGHTCLICKENABLED = '@IsRightClickFunctionalityRequired';
CONFIG.TARGET = "Captivate";
CONFIG.NOSCORM = "Sorry, but the course is not available at this time (SCORM API not found). Please try again. If you continue to encounter problems, please contact the course administrator.";

//For SWFObject
params.bgcolor = CONFIG.BGCOLOR;
params.menu = "false";
attributes.name = CONFIG.TARGET;

While I’m cleaning, I’ll reduce the number of vars I introduced into scorm_support.js when adding new functions. This is also where we can do nitpicky stuff like replacing tabs with spaces and ensuring we have semicolons in the right places. Be sure to remove any alerts or console.log calls you may have added for debugging.

I think I’ll make one other change — add SWFObject 2.2 to standard.js. Yes, this means standard.js will contain both SWFObject 1.5 and 2.2, which is a bit odd. However it seems like a safe choice, since some institutions might not like loading SWFObject from a 3rd party CDN such as Google.

I’m also going to compress RightClick in standard.js to reduce file size. Be sure to make a backup copy if you’re following along.

View the result of task #7

Cleanup task #8: Move the files into the Captivate publishing folder

It’s been a long road, but we’re finally ready to make the jump! Let’s replace the existing template files.

You can grab the new code here.

First, make a backup of your entire publishing folder and put it somewhere safe!

Next, rename your HTML file to Default.htm and drag it into Templates\Publish\SCORM\2004\ (Original file was 11.8KB, new file is 1.7KB)

Now copy your scorm_support.js file to Templates\Publish\SCORM\2004\SCORM_support\ replacing the original file. (Original file was 6.7KB, new file is 7.4KB)

While you’re in your SCORM_support folder, go ahead and delete scorm_support.htm (590B) and scorm_support.swf (149B) — they won’t be used anymore.

Update: If you choose to delete these two files, you must edit Manifest2004.xml to reflect the change. Just remove the two lines that mention scorm_support.htm and scorm_support.swf (found at about line 108 in Manifest2004.xml).

Finally, go to Templates\Publish\ and replace standard.js with our updated copy. (Original file was 10KB, new file is 19KB)

Sweet: Even after adding SWFObject 2.2 to standard.js, we were able to shave about 1KB from the total file size! (29.25KB to 28.1KB)

Now restart Captivate and give it a try!

The next installment of the series will provide a quick summary as well as some additional options and considerations. Go to Part 6.