PDFObject 2.0 released

I’ve just released PDFObject 2.0. Read more on my blog:

https://itgotmethinking.com/2016/04/21/pdfobject-2-0-released/

Advertisements

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.

Complete List of Variables for Adobe Captivate 5

While updating my CaptivateController script I noticed there have been some changes to the Captivate variables available to Captivate developers. I figured I should document them for future reference.

Note that some variables available in CP3 and CP4 are no longer available. The following list should be exhaustive for CP5; variables for previous versions of Captivate have been purposely left off this list. I also purposely left off some publicly accessible (but useless) movieclips and objects.

While most people seem to focus on using Captivate variables natively within a Captivate project via the Variables menu or widgets, my focus has been figuring out how accessible/usable they are via JavaScript. Thus you may find small differences between my list and other people’s lists, and differing opinions regarding the usefulness of some variables and publicly accessible movieclips. Regardless, the items below should be accessible via every method: widgets, JavaScript, and the native Variables support in Adobe Captivate.

If you know of any variables that I missed, please leave a comment. Thanks!

Update: Kurt Melander has kindly converted this list to PDF format if you’d like to download or print it. Thanks, Kurt.

Variable Type Description

CPMovieType

[number] Informational variable. Indicates whether the queried SWF is a skin SWF (0) or the main content SWF (1).
CaptivateVersion [string] Informational variable. Indicates the version of Adobe Captivate that published the SWF
DoNotRegisterRightClickBecauseOfAggregator [boolean] Internal variable, no information available
LocalConnectionInUse [boolean] Internal variable, no information available
NoOfTOCEntries [number] Informational variable. Returns the number of items contained in the project’s Table of Contents

PlaybarProperties

[string]
in Chrome

[XML] in Firefox

Internal variable, no information available.

NOTE: This item is
:XML data type in AS3. Because browsers have different support for native XML data types, values returned from this variable should not be considered cross-browser. Use at your own risk.

__loadbase [string]

Informational variable. Returns project’s root file path.

NOTE: This value can only be obtained when the project uses an external skin.

contentHeight [number] Informational variable. Returns the height of the Captivate main SWF (in pixels)
contentLeft [number] Informational variable. Returns the distance of the Captivate main SWF from the leftmost edge of the project (in pixels).
contentSWF [string]

Informational variable. Returns the file name for the project’s main SWF.

NOTE: This value can only be obtained when the project uses an external skin.

contentTop [number] Informational variable. Returns the distance of the Captivate main SWF from the topmost edge of the project (in pixels).
contentWidth [number] Informational variable. Returns the width of the Captivate main SWF (in pixels)
cpAutoPlay [boolean] Informational variable. Indicates whether the project is set to auto-play.
cpCaptivateSkinSWF [boolean]

Informational variable. Indicates whether the SWF is a skin SWF or primary project SWF.

NOTE: This value can only be obtained when the project uses an external skin.

cpCmndCC [number] Command variable. Setting to 1 will enable captioning. Setting to 0 will disable captioning.
cpCmndFastForward [number] Command variable. Setting to 1 will fast-forward the movie (play the movie at a higher framerate). Setting to 0 will return the movie to the normal playback speed.
cpCmndGotoFrameAndResume [number]

Command variable. Will cause the movie to jump to the specified frame and resume playing (frame numbering begins at 0).

Note: frames are not the same as slides.

cpCmndGotoSlide [number]

Command variable. Will cause the movie to jump to the specified slide (slide numbering starts at 0).

Note: frames are not the same as slides.

cpCmndMute [boolean] Command variable. Setting to 1 will disable (mute) the audio. Setting to 0 will restore it to normal.
cpCmndNext [number] Command variable. Setting to 1 will cause the movie to jump to the next slide. Setting to 0 will do nothing.
cpCmndPlaybarMoved [boolean] Command variable. Internal variable. According to Captivate documentation, "Set to 1 if the playbar has moved."
cpCmndShowPlaybar [number] Command variable. Setting to 1 will cause the movie’s playbar to appear. Setting to 0 will make the palybar disappear.
cpCmndVolume [number] Command variable. Setting to a number will cause the volume to change. The volume ranges from 0 (muted) to 100 (maximum volume).
cpContentLoadStart [boolean]

Informational variable. Indicates whether the main project SWF has started.

NOTE: This value can only be obtained when the project uses an external skin.

cpContentLoaded [boolean]

Informational variable. Indicates whether the main project SWF has loaded.

NOTE: This value can only be obtained when the project uses an external skin.

cpContentPositioned Internal variable. No information available.
cpContentScaled Internal variable. No information available.
cpHasSkinSWF [boolean] Informational variable. Indicates whether the Captivate project uses an external skin.
cpInfoAuthor [string] Informational variable. Returns the project author’s name, as entered in the movie’s properties before publishing.
cpInfoCompany [string] Informational variable. Returns the project company’s name, as entered in the movie’s properties before publishing.
cpInfoCopyright [number] Informational variable. Returns the project’s copyright notice, as entered in the movie’s properties before publishing.
cpInfoCourseID [number] Informational variable. Returns the project’s course ID, as entered in the movie’s properties before publishing.
cpInfoCourseName [string] Informational variable. Returns the project’s course name, as entered in the movie’s properties before publishing.
cpInfoCurrentDate [string] Informational variable. Returns the day portion of the current date.
cpInfoCurrentDateString [string] Informational variable. Returns today’s date (US English format).
cpInfoCurrentDay [string] Informational variable. Returns number indicating day of week (1 = Sunday, 2 = Monday, etc.)
cpInfoCurrentHour [string] Informational variable. Returns the current hour (24 hour clock format).
cpInfoCurrentMinutes [string] Informational variable. Returns the minutes portion of the current time.
cpInfoCurrentMonth [string] Informational variable. Returns the month portion of the current date.
cpInfoCurrentSlide [number] Informational variable. Returns the current slide number. (Uses 1-based index)
cpInfoCurrentSlideLabel [string] Informational variable. Returns the slide label for the current slide, if available.
cpInfoCurrentSlideType [string] Informational variable. Returns the slide type for the current slide.
cpInfoCurrentTime [string] Informational variable. Returns the current time, including seconds (24 hour clock format).
cpInfoCurrentYear [string] Informational variable. Returns the year portion of the current date.
cpInfoDescription [string] Informational variable. Returns the project’s description, as entered in the movie’s properties before publishing.
cpInfoElapsedTimeMS [number] Informational variable. Returns the amount of time (in milliseconds) that has elapsed since the project began playing.
cpInfoEmail [string] Informational variable. Returns the project author’s e-mail address, as entered in the movie’s properties before publishing.
cpInfoEpochMS [number] Informational variable. Returns the current time elapsed (in milliseconds) since January 01, 1970.
cpInfoHasPlaybar [number] Informational variable. Indcates whether the Captivate movie has a playbar. 1=true, 0=false
cpInfoIsStandalone [number] Informational variable. Indicates whether Captivate project is en .exe or .app file (1) or standard SWF (0).
cpInfoLastVisitedSlide [number] Informational variable. Returns the last visited slide number. (Unlike cpInfoCurrentSlide, this variable uses 0-based index)
cpInfoPercentage [number] Informational variable. Returns the current score as a percentage (if available).
cpInfoPrevSlide [number] Informational variable. Returns the number of the slide before the current slide. (Uses 1-based index)
cpInfoProjectName [string] Informational variable. Returns the project’s name, as entered in the movie’s properties before publishing.
cpInfoWebsite [string] Informational variable. Returns the project’s web addess, as entered in the movie’s properties before publishing.
cpLockTOC [number] Command variable. Setting to 1 disables user interaction on the Table of Contents. Setting to 0 re-enables (unlocks) user interaction.
cpMovieHeight [number] Informational variable. Returns the height of the Captivate project, in pixels.
cpMovieWidth [number] Informational variable. Returns the width of the Captivate project, in pixels.
cpMovieXForEmbededPlaybar [number] Informational variable. Returns the x coordinate (left position) of the Captivate project’s toolbar, if available.
cpMovieXForTOC [number] Informational variable. Returns the x coordinate (left position) of the Captivate project’s Table of Contents movieclip, if available.
cpMovieYForEmbededPlaybar [number] Informational variable. Returns the y coordinate (top position) of the Captivate project’s toolbar, if available.
cpMovieYForTOC [number] Informational variable. Returns the y coordinate (top position) of the Captivate project’s Table of Contents movieclip, if available.
cpOrgSWFPath [string]

Informational variable. Provides the file name for the project’s main SWF. Appears to duplicate functionality of cpOrgSWFPath.

NOTE: This value can only be obtained when the project uses an external skin.

cpQuizInfoAnswerChoice [string] Informational variable. Returns the chosen answer for the quiz question.
cpQuizInfoAttempts [number] Informational variable. Returns the number of times the quiz has been attempted.
cpQuizInfoLastSlidePointScored [number] Informational variable. Returns the score for the last visited quiz slide.
cpQuizInfoMaxAttemptsOnCurrentQuestion [number] Informational variable. Returns the number of attempts allowed for this quiz question.
cpQuizInfoNoQuestionsPerQuiz Informational variable. No information available. Best guess: returns the number of questions in the quiz.
cpQuizInfoPassFail [number] Informational variable. Returns the result of the quiz: pass or fail.
cpQuizInfoPointsPerQuestionSlide [number] Informational variable. Returns the number of points for this quiz question.
cpQuizInfoPointsscored [number] Informational variable. Returns the total number of points scored in the project.
cpQuizInfoQuestionSlideTiming [number] Informational variable. Returns the time limit for the current question (in seconds).
cpQuizInfoQuestionSlideType [string] Informational variable. Returns the current question’s type (multiple-choice, true-false, likert, etc.).
cpQuizInfoQuizPassPercent [number] Informational variable. Returns the passing percentage for the quiz.
cpQuizInfoQuizPassPoints [number] Informational variable. Returns the passing points for the quiz.
cpQuizInfoTotalCorrectAnswers [number] Informational variable. Returns the number of correctly answered quiz questions.
cpQuizInfoTotalProjectPoints [number] Informational variable. Returns the total number of points for the project.
cpQuizInfoTotalQuestionsPerProject [number] Informational variable. Returns the total number of questions for the project.
cpQuizInfoTotalQuizPoints [number] Informational variable. Returns the total number of quiz points for the project.
cpQuizInfoTotalUnansweredQuestions [number] Informational variable. Returns the total number of unanswered questions for the project.
endSwfAction [number] Internal variable. No information available.
expired [boolean] Internal variable. No information available. Best guess: Boolean indicating whether time limit has elapsed.
hasProjectFadeOut [boolean] Internal variable. No information available. Best guess: Boolean indicating whether last slide in project is set to fade out.
inAutoPlayState [boolean] Internal variable. No information available. Best guess: Boolean indicating whether project is set to auto-play.
isCPMovie [boolean] Informational variable. Indicates whether the SWF is a Captivate SWF.
isContiniousModeRecording [number] Internal variable. No information available. (Yes, that’s how it was spelled in the code.)
isCustomizable [boolean]

Internal variable. No information available. Best guess: Indicates whether the skin is customizable.

NOTE: This value can only be obtained when the project uses an external skin.

isForceMuteAudio [boolean] Internal variable. No information available. Best guess: Indicates whether the audio was muted by the user.
isPlayBarBtnClicked [boolean] Internal variable. No information available. Best guess: Indicates whether a specific action was performed via clicking the playbar (such as muting audio).
isPreview [number] Internal variable. No information available. Best guess: Indicates whether the SWF is a preview SWF (used when previewing projects within the Captivate authoring environment).
isPreviewForAudioDialog [boolean] Internal variable. No information available. Best guess: Indicates whether the SWF is a preview SWF (used when previewing projects within the Captivate authoring environment).
isPreviewSkin [boolean] Internal variable. No information available. Best guess: Indicates whether the project SWF’s skin is a preview SWF (used when previewing projects within the Captivate authoring environment).
lmsString [string] Internal variable. No information available. Best guess: The text displayed when Captivate initializes an LMS connection (SCORM, AICC, etc.).
loadedFromAggregator [boolean] Internal variable. No information available. Best guess: Indicates whether the Captivate SWF was loaded as part of an aggregator project.
m_quizPoolColl [object] Internal variable. No information available. Best guess: Object containing question pool questions.
movieQuality [string] Internal variable. No information available. Best guess: Indicates quality setting of SWF playback.

movieXML

[string] in Chrome

[XML] in Firefox

Internal variable, no information available.

NOTE: This item is
:XML data type in AS3. Because browsers have different support for native XML data types, values returned from this variable should not be considered cross-browser. Use at your own risk.

needToMuteAudioForAggregator [boolean]

Internal variable. No information available.

NOTE: This value can only be obtained when the project uses an external skin.

passwordPresent [boolean] Internal variable. Indicates whether a password has been supplied.
pbcBtnTips [object] Internal variable. Alias for pbcBtnTips_ENU.
pbcBtnTips_ENU [object] Internal variable (array). Returns list of tooltips used by the playbar buttons.
playbarBarAlign [number]

Internal variable. No information available.

NOTE: This value can only be obtained when the project uses an external skin.

playbarHeight [number] Informational variable. Returns height of playbar, in pixels.
playbarPosition [number]

Internal variable. No information available.

NOTE: This value can only be obtained when the project uses an external skin.

rdIsInLivePreviewMode [boolean] Internal variable. No information available. Best guess: Indicates whether the SWF is a preview SWF (used when previewing projects within the Captivate authoring environment).
rdIsPreview [boolean] Internal variable. No information available. Best guess: Deprecated variable replaced by isPreview
rdIsPreviewInBrowser [boolean] Internal variable. No information available.
rdIsStandalone [boolean] Internal variable. No information available. Best guess: Deprecated variable replaced by cpInfoIsStandalone
rdcmndCC [number] Command variable (deprecated). Alias for cpCmndCC.
rdcmndExit [number] Command variable. According to Captivate documentation, "Exit the movie. Set to 1 to exit." Has never worked for me.
rdcmndGotoFrame [number] Command variable (deprecated). Alias for cpCmndGotoFrame.
rdcmndGotoFrameAndResume [number] Command variable (deprecated). Alias for cpCmndGotoFrameAndResume.
rdcmndGotoSlide [number] Command variable (deprecated). Alias for cpCmndGotoSlide.
rdcmndMute [boolean] Command variable (deprecated). Alias for cpCmndMute.
rdcmndNext [boolean] Command variable (deprecated). Alias for cpCmndNext.
rdcmndNextSlide [number] Command variable (deprecated). Alias for cpCmndNext.
rdcmndPause [number] Command variable. Setting to 1 will cause the movie to stop playing (pause). Setting to 0 will do nothing.
rdcmndPlaybarMoved [boolean] Command variable (deprecated). Alias for cpCmndPlaybarMoved.
rdcmndPrevious [number] Command variable. Setting to 1 will cause the movie to stop playing (pause). Setting to 0 will do nothing.
rdcmndResume [number] Command variable. Setting to 1 will cause the movie to go backwards to the previous slide. Setting to 0 will do nothing.
rdinfoCurrentFrame [number] Informational variable. Returns current frame number using 0-based index.
rdinfoCurrentSlide [number] Informational variable (deprecated). Alias for cpInfoCurrentSlide.
rdinfoCurrentSlideInProject [number] Informational variable. No information available. Best guess: Alias for cpInfoCurrentSlide.
rdinfoFPS [number] Informational variable. Returns the SWF’s frame rate (in seconds).
rdinfoFrameCount [number] Informational variable. Returns the number of frames in the SWF.
rdinfoHasPlaybar [boolean] Informational variable (deprecated). Alias for cpInfoHasPlaybar.
rdinfoSlideCount [number] Informational variable. Returns the number of slides in the Captivate movie.
rdinfoSlidesInProject [number] Informational variable. No information available. Best guess: An unused/deprecated variable.
rdinfocurrFrame [number] Informational variable. No information available. Best guess: Alias for rdinfoCurrentFrame.
skinHeight [number]

Internal variable. No information available.

NOTE: This value can only be obtained when the project uses an external skin.

skinWidth [number]

Internal variable. No information available.

NOTE: This value can only be obtained when the project uses an external skin.

swfCmtAutoPlay [boolean] Internal variable. No information available. Best guess: Indicates whether the SWF will auto-play when in commenting mode.
swfCommenting [boolean] Internal variable. No information available. Best guess: Indicates whether the SWF is in commenting mode.
tocInitDone [boolean] Internal variable. Indicates when the Table of Contents has finished initializing.
waitCount [number] Internal variable. Indicates how long the SWF has been waiting (used for internal timer-related functions).

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.

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.

iframes and cross-domain security

What a pain.

I’m working on an HTML-based course interface that serves up content in an iframe. I had everything working great until I needed to move the content to one domain while hosting the interface on a different domain (kind of a simplified home-brewed CMS approach). BAM! Cross-domain security issues. Course interface dead in the water.

Cross-domain iframe security has been an issue for years and still hasn’t been resolved. Hacks abound, but none are the end-all-be-all solution.

One of the most popular hacks for getting around an iframe’s cross-domain security constraints is the fragment identifier hack. The simple explanation is that you can add data to the end of a page’s URL by using a hash (#, aka pound sign or octothorpe), just like named anchor elements. (Hashes are preferred over querystrings because querystrings would cause the page to reload, while using a hash doesn’t.) You’d then create a JavaScript function that monitors the URL for changes and evaluates whatever new data is in the ‘fragment.’

For example, the iframe myurl.com/index.html could have its URL appended to read myurl.com/index.html#somekindofdata. The parent frame would notice the change and could use conditional code to act on whatever the fragment contains.

The downsides to this approach make it unusable for my e-learning course interface. The biggest downside is that it breaks the browsing history model, rendering the browser’s ‘back’ button practically useless. It also breaks the named anchor functionality, which I use in a number of places. It is limited in scope, requiring all JavaScript to be converted to a string before being added to the URL; this means no native sending/receiving of JavaScript objects, booleans, etc… everything needs to be serialized and deserialized. Which brings me to the last point: this adds to code weight, code complexity, and processing time (especially when using polling to monitor changes to the URL); all three suck are undesirable.

I don’t have a solution I like yet, but I will continue to search and experiment. I was hoping a JavaScript framework like MooTools would have some kind of workaround built-in, but no dice. Other approaches include using Flash hacks and using server-side processing. I can’t use server-side processing since I’m not in control of the primary domain. Flash hacks are a possibility, but I’ve worked hard to ensure this course interface is cross-browser and cross-platform without requiring plugins. *sigh*

Wish me luck, I’ll need it.

Update 11/30/08: I’ve written about the workaround I decided to use; read about the workaround here.

Introducing the pipwerks Captivate Controller JavaScript utility

As alluded to in a previous post, I’ve whipped up a simple JavaScript utility to help you control your Captivate SWFs using JavaScript. Give it a spin.

The biggest selling point for this utility is that it not only contains all of the built-in Captivate ‘variable’ functionality, but it also contains some extra functionality created by chaining some variables together. For instance, Captivate’s built-in rdcmndGotoSlide automatically pauses the SWF when it reaches the desired slide; to get around this limitation, I created gotoSlideAndPlay, which does exactly what you’d expect: jumps to the desired slide, then unpauses the SWF. gotoFrameAndPlay works the same way. There’s also muteWithCaptions, which mutes the SWF’s audio while toggling on captioning, and unmuteWithCaptions, which turns audio back on while disabling captioning.

Using pipwerks.captivate.control() also means you don’t need to know the full rdcmnd phrases and numerical values; they have been replaced with simpler, more Flash-like phrases that are easier to remember and use.

Here is a list of available commands:

  • pause (rdcmndPause)
  • resume (rdcmndResume)
  • next (rdcmndNextSlide)
  • previous (rdcmndPrevious)
  • rewindAndStop (rdcmndRewindAndStop)
  • rewindAndPlay (rdcmndRewindAndPlay)
  • info (rdcmndInfo)
  • showCaptions (rdcmndCC)
  • hideCaptions (rdcmndCC)
  • mute (rdcmndMute)
  • unmute (rdcmndMute)
  • hidePlaybar (rdcmndHidePlaybar)
  • muteWithCaptions (rdcmndMute & rdcmndCC)
  • unmuteWithCaptions (rdcmndMute & rdcmndCC)
  • gotoSlideAndStop (rdcmndGotoSlide)
  • gotoSlideAndPlay (rdcmndGotoSlide & rdcmndResume)
  • gotoFrameAndStop (rdcmndGotoFrame)
  • gotoFrameAndPlay (rdcmndPause & rdcmndGotoFrameAndResume)

The following commands do not currently work when using JavaScript:

  • exit (rdcmndExit)
  • showPlaybar (rdcmndHidePlaybar)

If I can figure out why, I’ll be sure to let you know. 🙂

Detailed instructions for the utility and a download link can be found here. The script has been compressed to minimize bandwidth consumption, and is only 1.8kb. It has also been vetted with Douglas Crockford’s jslint (prior to compression).

Legal disclaimer: As with most of my other utilities, this utility is provided free, as-is, with no guarantees or support.

I hope you enjoy it and find it useful.

Control a Captivate SWF using JavaScript: The basics

Note: This post covers Captivate 2 & 3; Captivate 4 introduced new problems for JavaScript interaction. You can avoid the headache of writing your own code by using the free CaptivateController utility, works with all versions of Captivate.

JavaScript can control the playback of Captivate-generated SWFs. I posted some examples about a year ago (example one, example two), but someone recently reminded me I haven’t posted any instructions or explanations for my examples. Here’s a quickie explanation of how you can control a Captivate-generated SWF using JavaScript.

Please note that this document refers to Captivate 2 & 3, and may not be accurate when Captivate 4 is released.

What can be controlled using JavaScript?

According to the official Adobe docs, Captivate provides the following controls:

  • go previous slide
  • go to next slide
  • pause
  • resume (play/un-pause)
  • rewind to beginning and stop
  • rewind to beginning and play
  • go to a specific frame
  • exit
  • display the information window

I’ve determined there are additional unpublished parameters that can be accessed. A complete list (including the variable name) is located here.

Build a simple example

Step 1: Create the HTML file and embed the Captivate SWF

For this example, we’ll use bare-bones HTML, with SWFObject handling the embed:


<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Control a Captivate SWF with JavaScript</title>
   <script type="text/javascript" src="swfobject.js"></script>
   <script type="text/javascript">
      //Embed the SWF in the HTML
      swfobject.embedSWF("captivate.swf", "captivateSample", "450", "300", "7");
   </script>
</head>
<body>
   <div id="captivateSample"></div>
</body>
</html>

Make sure the SWF you’re embedding is a SWF created using Adobe Captivate, and was published without the “border” property in the Skin settings. My example SWF is named “captivate.swf”; you should replace “captivate.swf” with your Captivate SWF’s filename.

Now place a link in the document; this link will invoke the JavaScript command. For this example, we’ll just be using Captivate’s “rewind and play” command (rdcmndRewindAndPlay), so give the link the text “Click here to rewind and play.” Since this is a fake link, the href value should just contain a hash (#).

<body>
   <p><a href="#">Click here to rewind and play</a></p>
   <div id="captivateSample"></div>
</body>

Step 2: Add the JavaScript

Now that the HTML is in place, all we need to do is add a touch of JavaScript. Add the following JavaScript function to the <head>:


   //Embed the SWF in the HTML
   swfobject.embedSWF("captivate.swf", "captivateSample", "450", "300", "7");

  function rewindAndPlay(){
      var swf = document.getElementById('captivateSample');
      swf.SetVariable('rdcmndRewindAndPlay', 1);
   }

Add a corresponding onclick event to the link in the <body>:

<body>
   <p><a href="#" onclick="rewindAndPlay(); return false;">Click here to rewind and play</a></p>
   <div id="captivateSample"></div>
</body>

Save and test your page; the link should control the Captivate SWF.

How it works

The function

The JavaScript function you just created has two elements.

var swf = document.getElementById('captivateSample');

When we embedded the SWF, we assigned it an ID of “captivateSample”. This means we can use document.getElementById('captivateSample') to ‘grab’ the SWF in the browser DOM and make the SWF an object available to JavaScript. In our case, we return the object as the variable swf. As a JavaScript object, you can now invoke any methods and get/set any properties that are available within that object.

Note: You may find old documentation warning you not to use document.getElementById to get a SWF; in my testing, document.getElementById works fine in all major browsers, including Firefox 2 & 3 (Mac & PC), Internet Explorer 6 & 7, Safari 2, Safari 3 (Mac & PC), and Opera 9.5 (Mac). Also note that I used SWFObject to embed the SWFs in all of my tests, which could have an impact on test results.

swf.SetVariable('rdcmndRewindAndPlay', 1);

Captivate SWFs can be controlled by setting the value of specific ActionScript variables contained inside the Captivate SWF. We can set the value of these ActionScript variables using Flash’s SetVariable method (SetVariable allows JavaScript to set the value of an ActionScript variable without using ExternalInterface).

In this example, we’re setting the value of the Captivate variable rdcmndRewindAndPlay to 1 (boolean, meaning true).

The onclick event

If you’re not familiar with onclick events, all that’s happening is the function rewindAndPlay(); is being invoked when the link gets clicked. The extra code return false; simply instructs the browser to ignore whatever is contained in the href attribute, effectively preventing the browser from following the link. Remember to include the semicolons!

<p><a href="#" onclick="rewindAndPlay(); return false;">Click here to rewind and play</a></p>

Border blues

When a Captivate file is published with the border option enabled, Captivate is actually publishing two SWFs: a skin SWF and the Captivate SWF itself. The skin SWF loads the Captivate SWF into a movieclip named cpSkinLoader_mc. This means we need to dig one level deeper to get to the Captivate SWF. This can be accomplished by appending the prefix cpSkinLoader_mc to the variable name. Here’s an example:


document.getElementById('captivateSample').SetVariable('cpSkinLoader_mc.rdcmndRewindAndPlay', 1);

Expanding the functionality

My older examples (example one, example two) used a custom function that was designed to make controlling Captivate easier. This demonstrated how the developer could create shortcuts that prevented writing the same code over and over, while also providing a way to use simpler syntax, such as ‘pause’ instead of ‘rdcmndPause’. Here’s a really quick overview of my example function:


//Handle the Captivate commands
function control(swfID, command, usesSkin){

   //Get SWF as an object so we can use SetVariable
   var swf = document.getElementById(swfID);

   //Error-checking is good.
   if(!swf){ return false; }

   //Declare our prefix variable in case we need it. Leave as empty string for now.
   var prefix = "";

   //If the Captivate SWF uses a skin, change prefix to include the skin's movieclip name
   if(usesSkin){ prefix = "cpSkinLoader_mc."; }

   //Which command is being invoked?
   switch (command) {
      case "pause": command = "rdcmndPause"; break;
      case "resume": command = "rdcmndResume"; break;
      case "rewindStop": command = "rdcmndRewindAndStop"; break;
      case "rewindPlay": command = "rdcmndRewindAndPlay"; break;
      case "next": command = "rdcmndNextSlide"; break;
      case "prev": command = "rdcmndPrevious"; break;
      case "info": command = "rdcmndInfo"; break;
      case "exit": command = "rdcmndExit"; break;
   }

   swf.SetVariable(prefix + command, 1);

   return false;
}

This function accepts three parameters: swfName (the ID of the Captivate SWF, which is assigned when embedding the SWF), command (what we’re telling the SWF to do), and usesSkin (boolean indicating whether or not this Captivate file uses a skin).

If the SWF doesn’t use a skin:


<a href="#" onclick="return control('captivateSample', 'pause');">Pause</a>

If the SWF does use a skin:


<a href="#" onclick="return control('captivateSample', 'pause', true);">Pause</a>

As I just mentioned, this function allows us to use simpler command names. A switch statement is used to match the simpler command name with the official command name. SetVariable does its thing, then the function returns false, which prevents the browser from following the link in the href attribute.

Everything but the kitchen sink

I’ve written a new Captivate controller utility that expands on the function shown above. It includes every known Captivate control that has been tested to work with JavaScript (there are a couple that appear to only work via ActionScript), and even combines a few existing controls to create new ones, such as combining ‘mute’ with ‘enable captions’ to automatically turn on captioning when muting the movie. I’ll be posting another entry about this new Captivate utility in the next day or two. Stay tuned!

Send Captivate Quiz Data to JavaScript

Adobe Captivate 3 doesn’t have a built-in mechanism for sending quiz results to JavaScript. Here’s a workaround you may find useful.

The plan

The basic premise of this workaround is to hijack Captivate’s ’email report’ functionality, replacing the original email-centered JavaScript with new JavaScript. This approach was also used in an Adobe article named Storing Captivate Test Scores in a Database and Exporting Them To Excel with ColdFusion [link no longer available].

My goals for this project were to:

  • Get all available quiz data from Captivate
  • Put the data into an easy-to-access JavaScript object
  • Remove as much of the original Captivate JavaScript as possible, leaving a cleaner global space
  • Use SWFObject (currently SWFObject 2.1) to embed the Captivate SWF

Here’s a working example. There is a link to a ZIP containing the source files (including a template) at the end of this article.

What data can I get from Captivate?

Captivate is a fickle friend, and only gives us a few pieces of data: status, location, raw score, max score, min score, and time.

Here’s a sample of what Captivate sends to JavaScript when using the email reporting option:


Core Data|"Status","Location","Raw Score","Max Score","Min Score","Time"|
"passed","3","30","30","0","00:00:17"||
Interaction Data|"Date","Time","Interaction ID","Objective ID","Interaction Type",
"Correct Response","Student Response","Result","Weight","Latency"

This string contains two primary elements: the Core Data element, and the Interaction Data element. These elements are separated by two vertical bars (||). The Core Data portion of the string is:


"Status","Location","Raw Score","Max Score","Min Score","Time"|"passed","3","30","30","0","00:00:17"

Notice how the Core Data portion contains headers to the left of the vertical bar (|) and data to the right of the bar. Now look at the Interaction Data portion of the string:


"Date","Time","Interaction ID","Objective ID","Interaction Type",
"Correct Response","Student Response","Result","Weight","Latency"

Notice there is no interaction data, only interaction headers. Although Captivate can track interactions, for some reason the interaction data was not coming across in my tests. The headers were coming across, but the data wasn’t. If any of you figure out why, drop me a line.

For now, we’ll just focus on the Core Data.

Step 1: Create and configure your Captivate file

Create a Captivate quiz containing a few questions (you can also use my example contained in the ‘source files’ ZIP). Make sure all of your questions have the “report answers” option checked.

There are a few important settings to change in your quiz preferences. First of all, go to Quiz > Quiz Preferences. Change your Reporting settings to match the screenshot: “Enable reporting” is checked, “E-mail” is checked, an email address is entered (Captivate checks to see if a well-formed email is found when using this option; you could probably use something fake such as 123@abc.com), “Quiz results only” is checked, and “Report score” is checked.

Captivate 3 Quiz preferences: Reporting (screenshot)

Next go to the Settings preferences. For this example, I unchecked “Allow backward movement” and “Allow user to review quiz.” This is up to you, it doesn’t affect the JavaScript; I just wanted my quiz to be as simple as possible from a UI standpoint.

Captivate 3 Quiz preferences: settings

While on the Settings screen, click the Quiz Result Messages button. A popup will appear; change “Email button text” to whatever you like. For this example, I changed it to “Send quiz results to JavaScript.” You might choose to use something such as “Submit quiz results.” It’s important to note that the quiz results are only sent to JavaScript when the user clicks this button, so make sure your instructions are clear.

Captivate Quiz preferences: Result messages (screenshot)

In the Pass or Fail settings, I set the passing and failing actions to “No Action.” Remember, the quiz results are only sent to JavaScript when the user clicks the “Send quiz results to JavaScript” button.

Captivate 3 Quiz preferences: Pass or Fail (screenshot)

By default, Captivate inserts a “Continue” button on the results page at the end of a quiz. Clicking “Continue” will cause the movie to resume playing (un-pause). Since we don’t want the user to click “Continue” before clicking the “Send quiz results to JavaScript” button, we should get rid of the “Continue” button. However, due to Captivate’s typical quirkiness, it appears you can’t simply delete the “Continue” button. My solution was to make the button transparent with white text, then shrink it as small as possible and stick it off in a corner where no one will click it.

The end result looks like this:

Captivate quiz results screen (screenshot)

Publish the Captivate file. You can uncheck the “Export HTML” options since we’re going to use our own HTML.

Step 2: Prepare the HTML file

Create a blank HTML page. Give it whatever title you like. Embed your SWF using your embed method of choice; I use SWFObject. My Captivate-generated SWF is named “quiz_report-score-only.swf”. Your HTML code should look something like this:


<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>Captivate score in JavaScript</title>
   <script type="text/javascript" src="swfobject.js"></script>
   <script type="text/javascript">
      //Embed the SWF in the HTML
      swfobject.embedSWF("quiz_report-score-only.swf", "capQuiz", "640", "480", "7");
   </script>
</head>
<body>
   <h1>Captivate score in JavaScript</h1>
   <div id="capQuiz">
     <p>If you're seeing this message, you need to install or update your Flash Player</p>
   </div>
</body>
</html>

Your code may look different if you use a different embed method, but the point is to have a clean HTML file with no extraneous JavaScript and no unnecessary markup.

Once you’ve tested your file to make sure the SWF loads and plays correctly, move on to step 3.

Step 3: Add the custom JavaScript

Converter function

I’ve prepared a function (pipwerks.UTILS.convertCaptivateEmailData) that converts Captivate’s ’email’ string to an object. This function has been saved in a compressed (<1kb) external file named pipwerks.UTILS.convertCaptivateEmailData.js. Without getting into the boring details, the function splits the string into arrays then merges the arrays into an object. It contains error-checking, and also does a little housecleaning along the way, such as removing quotes and converting property names to lowercase.

Let’s add a link to pipwerks.UTILS.convertCaptivateEmailData.js in our HTML file so we can take advantage of this function later on:

<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>Captivate score in JavaScript</title>
   <script type="text/javascript" src="pipwerks.UTILS.convertCaptivateEmailData.js"></script>
   <script type="text/javascript" src="swfobject.js"></script>
   <script type="text/javascript">
      //Embed the SWF in the HTML
      swfobject.embedSWF("quiz_report-score-only.swf", "capQuiz", "640", "480", "7");
   </script>
</head>

Replacements for Captivate’s email functions

Next, we’ll replace the ‘report via email’ JavaScript functions invoked by the Captivate SWF. We’ll place these functions in the <head>, just before the SWFObject embed.


<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <title>Captivate score in JavaScript</title>
   <script type="text/javascript" src="pipwerks.UTILS.convertCaptivateEmailData.js"></script>
   <script type="text/javascript" src="swfobject.js"></script>
   <script type="text/javascript">
      function appendEmailBody(){} //nothing to see here, move along!
      function sendMail(){}        //nothing to see here, move along!

      function padMail(strAddress, strSubject, strBody) {
         //The important stuff will go here
      }

      //Embed the SWF in the HTML
      swfobject.embedSWF("quiz_report-score-only.swf", "capQuiz", "640", "480", "7");
   </script>
</head>

As you can see, there are three Captivate-generated JavaScript functions; while we don’t need the first two functions (appendEmailBody and sendMail), they’re included here to ensure the Captivate SWF doesn’t throw any errors by invoking a function that doesn’t exist.

The star of the show is the padMail function. It accepts three parameters, of which we only need the third parameter: strBody. This string was meant to be inserted into the body of the email message, and contains all the important quiz information.

To get the data into a useable JavaScript object, just pass the strBody parameter along to our helper function pipwerks.UTILS.convertCaptivateEmailData; the function will return an object you can use in your course.

function padMail(strAddress, strSubject, strBody) {

   //Forward parameter strBody to our custom function.
   var quiz = pipwerks.UTILS.convertCaptivateEmailData(strBody);

   //Do something with 'quiz' object
   alert("score: " +quiz.rawscore);

}

The quiz object

While I’m using the name ‘quiz’ for this example, you can name the object whatever you prefer; I’m just calling it ‘quiz’ for demonstration purposes.

Object properties

The object contains all of the reporting values that were sent from Captivate. pipwerks.UTILS.convertCaptivateEmailData also creates a new property named accuracy, which is a percentage created using the formula (rawscore/maxscore) * 100. Our final list of properties is:

  • status
  • location
  • rawscore
  • maxscore
  • minscore
  • time
  • accuracy

Remember, these are all of the properties sent by Captivate and were not chosen by me! I didn’t leave anything out; reportable data such as ‘number of attempts’ are not sent over, and I have no idea why.

Accessing the data

Now that we’ve converted the Captivate email string to an object, you can simply access the properties by using the dot syntax. For example, if your object name is captivatedata, you’d access the data like so:

var captivatedata = pipwerks.UTILS.convertCaptivateEmailData(strBody);
alert("score: " +captivatedata.rawscore);

And that’s all there is to it! Here’s another look at the final product, and you can download the source files (including pipwerks.UTILS.convertCaptivateEmailData.js) here.