Captivate 4 variables gone wild

Update (August 2010): I have published a new list of Captivate variables that are specific to Adobe Captivate 5. Check it out.

The folks at Adobe recently published a list of Captivate 4 variables. While it’s a solid list, it’s not really a complete list.

Here is a list of variables I dug up while combing through Captivate 4’s source AS files. I tried to add descriptions where I could, and will continue to revise this list as I get more information. Kudos to Michael at www.cpguru.com for posting his helpful list, too.

Notes:

  • I’ve tested each of these variables in JavaScript to ensure they exist and are accessible, and have weeded out variables that return errors.
  • I’ve also included Captivate 3 variables at the bottom of this page.

Update: Kurt Melander was kind enough to clean up this list and convert it to PDF format for those who’d like to print it out. Download PDF version.

Captivate 4 Variables

Functional variables (perform an action)

Variable Returns Default values Description (if available)
cpAutoPlay boolean true Toggles auto-play on (true) or off (false). Movie auto-plays by default.
cpCmndCC number 0 Toggles closed captioning on (1) or off (0). Replaces rdcmndCC.
cpCmndFastForward number 0 Setting to 1 increases speed of playback.
cpCmndGotoFrameAndResume number -1 Jumps to the specified frame number (NOT slide number) and resumes playback.

Replaces rdcmndGotoFrameAndResume.

cpCmndMute boolean false Toggles audio. Setting to true mutes the audio, setting to false unmutes the audio.

Replaces rdcmndMute.

cpCmndNext number 0 Setting value to 1 will jump to the next slide in project.

Replaces rdcmndNext

cpCmndShowPlaybar number 1 Toggles playbar visibility. Setting to 0 will hide the playbar, setting to 1 will restore it.

Replaces rdcmndHidePlaybar.

cpCmndVolume number 100 Sets audio volume (if applicable). Range is 0-100, where 0 is off and 100 is full volume. Querying cpCmndVolume will return the current volume level.
rdcmndGotoSlide number -1 Jumps to the specified slide number. Takes slide number (0-based index) as argument.
rdcmndPlaybarMoved boolean false

Informational variables (retrieve or set data)

Variable Returns Sample values Description (if available)
CaptivateVersion string 4.0.0 Returns version of Captivate that published the SWF.
cpCmndPlaybarMoved boolean false Replaces rdcmndPlaybarMoved.
cpHasSkinSWF boolean false Returns boolean indicating whether the movie is using a SWF-based skin.
cpInfoAuthor string Philip Hutchison Returns the project author’s name, if available.
cpInfoCompany string pipwerks Returns the project company’s name, if available.
cpInfoCopyright number 2009 Returns the project’s copyright notice, if available.
cpInfoCurrentDate string 11 Returns the day of the month using the client’s system clock.

String(now.getDate())

cpInfoCurrentDateString string 5/11/2009 Returns the calendar date in mm/dd/yyyy format using the client’s system clock.

Returns (now.getMonth() + 1 + “/” + now.getDate() + “/” + now.getFullYear());

cpInfoCurrentDay string 2 Returns the numerical day of the week using the client’s system clock (Sunday is day 1).

Returns String(now.getDay()+ 1);

cpInfoCurrentHour string 19 Returns the current hour using the client’s system clock.

Returns String(now.getHours());

cpInfoCurrentMinutes string 59 Returns the current minute using the client’s system clock.

Returns String(now.getMinutes());

cpInfoCurrentMonth string 5 Returns the numerical month using the client’s system clock (January is 1).

Returns String(now.getMonth() + 1);

cpInfoCurrentSlide number 1 Returns the current slide number. Unlike rdinfoCurrentSlide, cpInfoCurrentSlide uses 1-based indexing, so slide 1 of the project will return 1.

Returns (rdinfoCurrentSlide + 1);

cpInfoCurrentSlideLabel string SlideLabel:0 Returns the label of the slide, if available.
cpInfoCurrentSlideType string NormalSlide Returns a string indicating the current slide’s type. There are currently three possible results: “NormalSlide”, “QuestionSlide”, and “RandomQuestionSlide”
cpInfoCurrentTime string 19:59:27 Returns the current time in HH:MM:SS format using the client’s system clock. Note: uses 24-hour clock.

Returns (now.getHours() + “:” + now.getMinutes() + “:” + now.getSeconds());

cpInfoCurrentYear string 2009 Returns the current year in YYYY format using the client’s system clock.

Returns String(now.getFullYear());

cpInfoDescription string This demonstration will teach you the… Returns the project description, if available.
cpInfoElapsedTimeMS number 1537 Returns the amount of time (in milliseconds) that has elapsed since the movie began playing.

Returns (cpInfoEpochMS – movie.m_StartTime)

cpInfoEmail string hello@world.org Returns the project author’s email address, if available.
cpInfoEpochMS number 1242097167686 Per Adobe [link no longer available]: “returns the number of milliseconds since midnight January 1, 1970, universal time, for the specified Date object. Use this method to represent a specific instant in time when comparing two or more Date objects.”

Returns now.getTime();

cpInfoHasPlaybar number 1 Returns a boolean (in number format) indicating whether the Captivate file was published with playback controls (a playbar).
cpInfoIsStandalone number 0
cpInfoLastVisitedSlide number 0 Returns the index number of the last visited slide. Uses zero-based numbering (0 = slide 1, 1 = slide 2).
cpInfoPercentage number 0
cpInfoPrevSlide number -1 Returns the index number of the slide that comes before the current slide. Uses zero-based numbering (0 = slide 1, 1 = slide 2).
cpInfoProjectName string CaptivateController Returns the project name, if available.
cpInfoWebsite string www.pipwerks.com Returns the website listed in the project properties, if available.
cpMovieHeight number 432 Returns the movie’s original height, regardless of how it has been sized via HTML or CSS.
CPMovieType number 1
cpMovieWidth number 551 Returns the movie’s original width, regardless of how it has been sized via HTML or CSS.
cpQuizInfoAnswerChoice string
cpQuizInfoAttempts number 1
cpQuizInfoLastSlidePointScored number 0
cpQuizInfoMaxAttemptsOnCurrentQuestion number 0
cpQuizInfoNoQuestionsPerQuiz number 0
cpQuizInfoPointsPerQuestionSlide number 0
cpQuizInfoPointsscored number 0
cpQuizInfoQuestionSlideTiming number 0
cpQuizInfoQuestionSlideType string choice
cpQuizInfoQuizPassPercent number 0
cpQuizInfoQuizPassPoints number 0
cpQuizInfoTotalCorrectAnswers number 0
cpQuizInfoTotalProjectPoints number 0
cpQuizInfoTotalQuestionsPerProject number 0
cpQuizInfoTotalQuizPoints number 0
cpQuizInfoTotalUnansweredQuestions number 0
inAutoPlayState boolean false
isCPMovie boolean true Returns a boolean indicating whether this movie was published using Adobe Captivate.
isPreview number 0 Returns a boolean (in number format) indicating whether this is a preview. Primarily used internally by Captivate when previewing a project. Replaces rdIsPreview.
isPreviewSkin number 0 Returns a boolean (in number format) indicating whether this is a skin for a preview. Primarily used internally by Captivate when previewing a project.
loadedFromAggregator boolean false Returns a boolean indicating whether the SWF has been loaded by Captivate’s aggregator.
LocalConnectionInUse boolean false Returns a boolean indicating whether the movie is using LocalConnection. This is primarily used internally by Captivate.
NoOfTOCEntries number -1 Returns a count of entries in the Table of Contents, if available.
pbcBtnTips object [array] Rewind, Back, Play,
Pause, Forward,
Closed Captioning,
Audio On, Audio Off,
Exit, Information,
Dummy for slider,
Table Of Contents,
2x Fast Forward Speed,
4x Fast Forward Speed,
Normal Speed, Print
Returns the label values used in the playback controller’s tool tips.
playbarHeight number 31 Returns the height of the playbar in pixels, if applicable.
playbarPosition number 3 Returns the position of the playbar in pixels, if applicable.
rdIsPreview boolean false Deprecated. Replaced by isPreview.
rdinfoCurrentFrame number 41 Returns the number of the current frame (NOT slide), counting from the beginning of the movie’s timeline.
rdinfoCurrentSlide number 0 Deprecated. Replaced by cpInfoCurrentSlide.
rdinfoCurrentSlideInProject number 0
rdinfoFPS number 30 Returns the SWF’s frames per second rate.
rdinfoFrameCount number 90 Returns the number of frames in the entire SWF.
rdinfoSlideCount number 1
rdinfoSlidesInProject number 1 Returns the number of slides in the project.
rdinfocurrFrame number 2
swfCmtAutoPlay boolean false Returns boolean indicating whether SWF commenting auto-play is enabled. This is only used by the SWF Commenting AIR application.
swfCommenting boolean false Returns boolean indicating whether SWF commenting auto-play is enabled. This is only used by the SWF Commenting AIR application.
tocInitDone boolean true Returns boolean indicating whether the Table of Contents has finished initializing.

Captivate 3 Variables

Functional variables (perform an action)

Variable Returns Default values Description (if available)
rdcmndCC string 0 Toggles closed captioning on (1) or off (0).
rdcmndGotoSlide string -1 Jumps to the specified slide number. Takes slide number (0-based index) as argument.
rdcmndMute string 0 Toggles audio. Setting to 1 mutes the audio, setting to 0 unmutes the audio.
rdcmndNext string 0 Setting value to 1 will jump to the next slide in project.
rdcmndHidePlaybar string 0 Toggles playbar’s visibility

Informational variables (retrieve or set data)

Variable Returns Sample values Description (if available)
CaptivateVersion string 2.0.0 Returns version of Captivate that published the SWF.
rdIsPreview string 0 Returns a boolean (in number format) indicating whether this is a preview. Primarily used internally by Captivate when previewing a project.
rdinfoCurrentFrame string 1 Returns the number of the current frame (NOT slide), counting from the beginning of the movie’s timeline.
rdinfoCurrentSlide string 1 Returns the current slide number using zero-based indexing (slide 1 of the project will return 0).
rdinfoCurrentSlideInProject string 1
rdinfoFPS string 24 Returns the SWF’s frames per second rate.
rdinfoFrameCount string 2889 Returns the number of frames in the entire SWF.
rdinfoHasPlaybar string 1 Returns a boolean (in number format) indicating whether the Captivate file was published with playback controls (a playbar).
rdinfoSlideCount string 13
rdinfoSlidesInProject string 13 Returns the number of slides in the project.
rdinfocurrFrame string 0
rdcmndPlaybarMoved string 0
Advertisements

Gotchas in Internet Explorer 8

Internet Explorer 8 (IE8) is at Release Candidate 1, which means it will be released very shortly. IE8 is a brand-new browser and will represent a considerable shift from IE7/IE6; it will follow standards more closely and will offer much improved CSS 2.1 support. However, because of some of these changes, it is also widely understood that IE8 might break websites that have relied on IE-specific hacks targeted at previous versions of Internet Explorer.

To their credit, the IE development team has been very candid about the changes and have posted a number of blogs and documents aimed at helping web developers prepare for IE8. I was looking over one such page and thought I’d point out what I consider to be some of the biggest ‘gotchas’ so far.

Setting Unsupported CSS Values

Trying to detect support for a specific CSS value through a JavaScript try/catch statement will no longer generate an exception, which means you can’t rely on JavaScript to detect support for specific CSS values anymore.

Assigning CSS values that were unsupported in IE7 but are supported in IE8 Standards Mode will not generate exceptions in IE8 Compatibility View. Some sites use these exceptions to determine if a particular value for a CSS property is supported or not.


try {
   elm.style.display = "table-cell";
} catch(e) {
   // This executes in IE7,
   // but not IE8, regardless of mode
}

Malformed HTML

IE8 will not be as forgiving of malformed HTML markup. This is a great new ‘feature’ in terms of ensuring people (and software) are less sloppy with their markup, but this will certainly cause many, many problems for hundreds of thousands of old, poorly written websites.

Parser error correction for malformed HTML has changed in IE8 Standards Mode. Pages depending on the way IE7 performs error correction may encounter issues as a result.


<ul>
    <li>1.1
        <ul>
            <li>1.1.1</li>
    </li> <!-- Closes 1.1 in IE8, but not IE7 -->
            <li>1.1.2</li>
        </ul>
    </li>
</ul> 

Working with an Element’s Class

Like the malformed HTML ‘feature’, this is another great improvement in IE that will also cause many, many headaches. You see, for years IE wouldn’t let developers use the standard setAttribute("class") method for specifying a class name via JavaScript. Instead, IE required developers to use the proprietary setAttribute("className"). This means that it became commonplace for scripts to check for IE then use class for non-IE browsers and className for IE. Now, you’ll still need to make that check for older versions of IE but find a way to use class for IE8. <sarcasm>This will be fun.</sarcasm>

Don’t get me wrong — I’m excited that IE will finally behave like other browsers in this regard — but it also means that so long as IE6 and IE7 are still around, we’ll have to jump through more hoops to handle class names.

In IE7, “className” had to be used as the attribute name to set and retrieve the class of an element. This has been fixed for standards-compliance in IE8 Standards Mode. Using the old approach will create an attribute named “className” that has no affect on the actual class assigned to an element.


return elm.getAttribute("className");

SOLUTION: Use the standardized name, “class”, instead of “className”.


return elm.getAttribute("class");

CSS Expressions

One of the common hacks for IE’s shortcoming with CSS support has been to use IE’s proprietary CSS expressions, which are basically JavaScript statements embedded in place of a CSS value. While this practice has been frowned upon by most in-the-know web developers, it still wound up being heavily utilized as an ‘easy fix’ type of hack.

IE8 will no longer support CSS expressions. This will make it behave more like other browsers, but will cause problems for those who have relied on CSS expression hacks. Fortunately, it should be relatively easy to move your CSS expressions into your page’s JavaScript as suggested by Microsoft.

Support for CSS Expressions has been removed in IE8 Standards Mode.


/* CSS */
#main {
    background-color: expression(
        (new Date()).getHours()%2 ? "#000" : "#fff"
    );
}

SOLUTION: Refactor to utilize either improved CSS support or DHTML logic.


/* Script */
var elm = document.getElementById("main");
if((new Date()).getHours()%2) {
    elm.style.backgroundColor = "#000";
} else {
    elm.style.backgroundColor = "#fff";
} 

On the whole, I’m excited about the changes IE8 will bring, although it will undoubtedly require site revisions for anyone who uses JavaScript extensively in their projects.

You can read the original Microsoft blog post here.

Lazy loading excanvas.js

excanvas.js is a script that enables developers to use the canvas element in Internet Explorer; since IE doesn’t support canvas or the canvas API, the excanvas script converts (most) canvas commands to Vector Markup Language (VML), the only vector rendering language supported natively in Internet Explorer.

excanvas is designed specifically for Internet Explorer, so most people simply use a conditional comment to load it in IE and avoid loading it in other browsers:


<!--[if IE]>
<script type="text/javascript" src="/scripts/excanvas.js" src="/scripts/excanvas.js">
<![endif]-->

This works fine if you have access to the HTML file that needs to load excanvas, but what if you can’t edit the HTML? This was my predicament with a recent project; I decided to use a lazy loader approach (on-demand loading) and load excanvas.js dynamically.

I started by developing an HTML example page that used the canvas element and had the excanvas.js file hard-coded. Everything worked as planned. I then took out the hard-coded excanvas.js file and replaced it with a JavaScript-based lazy loader. Guess what? It didn’t work.

I scratched my head for a while and did some more testing. The strange thing was that excanvas.js was loading when and where it was supposed to, but the script itself wasn’t firing.

After digging around the excanvas.js source code a bit, I found the problem: the script contains an init function that only gets invoked when the document’s readystate changes. If the document is already loaded, the readystate won’t change and the init function will never fire!

A simple modification to the excanvas.js file fixed the problem:

Original code (starting at line 87 of excanvas.js)


init: function(opt_doc) {
    if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        var doc = opt_doc || document;
        // Create a dummy element so that IE will allow canvas elements to be
        // recognized.
        doc.createElement('canvas');
        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
    }
},

Modified version


init: function(opt_doc) {
    if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        var doc = opt_doc || document;
        // Create a dummy element so that IE will allow canvas elements to be
        // recognized.
        doc.createElement('canvas');

        if(doc.readyState !== "complete"){

            doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));

        } else {

           this.init_(doc);

        }

    }
},

Basically all we’re doing is checking to see if the readystate is already “completed” before attempting to do attachEvent. If the state is completed, we don’t need attachEvent and can just invoke this.init_ directly.

Update: Alternate solution

January 2014. Unfortunately, the authors of ExCanvas haven’t addressed this issue yet. Since it has been over 3 years since I submitted an issue on their project site, I assume it will not be addressed. (At this point I can’t blame them, who wants to support old versions of IE anyway?)

However, there is some good news if you still need to use ExCanvas: Willis at badsyntax.co came up with a different solution that doesn’t require modifying the source code. I recommend using his technique, as it’s cleaner and allows you to use a CDN copy of ExCanvas instead of maintaining your own copy.

Font replacement techniques

Like many other web professionals, I’m tired of the limited font set we have to work with. Gee, should I use Verdana on this site or Georgia? Maybe Arial? Meh. Bor-ing.

The merits and legal implications of CSS3’s proposed @font-face are being hotly debated, which means the proposal is going nowhere fast. Unfortunately, this also means we won’t have native browser support for a wider set of fonts anytime soon. In the meantime our sites (and e-learning courses) will continue to use the same old ho-hum fonts.

Are there any other options? The answer is yes, but none of them are perfect. The most common (and cross-browser) solution is to use what’s referred to as the image-replacement technique; the basic idea is to use a program like Photoshop to create an image containing your text in a nice font, then display the image instead of the original HTML text. Since this requires making a custom image for every line of text, it’s generally only used for headings. Chris Coyier wrote a nice article on the topic about a year ago, and rounded up the most popular techniques at the time.

Some enterprising developers have come up with alternative image replacement techniques that are quite impressive. The most popular (by far) is sIFR, a Flash-based solution that can replace any specified text on your page dynamically, using small SWF files. This solution doesn’t require custom images, is cross-browser, and is extremely flexible. Alas, it also requires JavaScript and Flash Player; if used for a lot of text on the screen, you wind up with a bunch of SWFs floating around, which definitely eats up CPU cycles and slows down older computers.

More recently, there have been interesting attempts at using SVG, canvas, and VML to draw fonts dynamically. One early implementation of this was typeface.js [link no longer available], which appeared to be a great idea but was difficult to use. More recently, Cufon has been getting a lot of buzz. I think it’s very nice and is easy to use, but it also has a few drawbacks, namely what happens if CSS is disabled in the browser:

cufon-screenshot

Image: Cufon in action, but with CSS disabled. Cufon-generated text is blue, the original text is black.

Any text generated by Cufon (and some similar scripts) is treated as an inline image — canvas elements are functional equivalents to images. The original text is hidden using CSS. If CSS is disabled, the original text shows up side-by-side with the generated text. This causes all sorts of problems.

Another drawback is selectability; Cufon and other canvas/VML-based systems have problems making text selectable in all browsers. To be fair, this is a problem with just about every image replacement technique except sIFR, which uses the power of the Flash Player plugin to get around the issue.

I’d like to be able to harness the creative possibilities of typography in my e-learning courses but really wish I didn’t have to jump through all these hoops to do it. Which system will I wind up using? Probably a combination of old-school static images created in Photoshop and sIFR. I feel that sIFR is the best option for my projects because of its greater accessibility and flexibility, but only when I know my target audience has Flash Player. I’m also very impressed by Cufon and hope they manage to take it to the next level to be on-par with sIFR.

Image-Free Progress Bar using MooTools and Canvas

As part of my ongoing experiments with <canvas>, I decided to convert an image-based progress bar to an image-free canvas-based system. I just finished whipping up a proof-of-concept; it uses MooTools to generate the canvas and CSS code. No images were harmed in the making of this progress bar.

More info later (time permitting)

Custom modal windows using canvas and MooTools

In my previous post Fun with canvas and MooTools: a Rectangle class, I explained that I wanted to make a modal window for a project at my workplace. I was interested in using MochaUI, but felt it was a bit heavy for my needs. I started playing with the canvas element (using excanvas.js for Internet Explorer support), and wound up making a useful Rectangle class that can quickly draw shapes in canvas using JavaScript.

Shortly afterward I built a simple modal window class named Modal using MooTools. This class combines a dynamic canvas drawing API (the Rectangle class) with dynamic DOM element generation to create on-demand modal windows using no external images. My goal was to make this about as easy to use as a normal JavaScript alert, prompt or confirm window.

I’ve been playing with it over the last week or so, and while it’s nowhere near perfect (esp. the ‘close’ button), I think it’s good enough for my project at work. I figured I’d post it here in case anyone wants to have a look. Feel free to use it if you like, but remember it comes as-is with no warranties! View Modal.js here.

Note: The code in Modal.js is subject to change!

I’m completely open to suggestions for code improvements (my code still feels ‘hacky’ to me), but I’m not really interested in adding new features at the moment. If you really want a full-featured, well-crafted window system, you should use Greg Houston’s MochaUI.

Modal Examples:

The default

The simplest way to invoke the window is:

var modal = new Modal({ title: "My title", html: "<p>My html code goes here</p>" });

Simple no-frills

You have the option of not using a window titlebar or ‘close’ button:

var html = "<p>This is a modal window without any title bar.</p>";

var modal = new Modal({
    html: html,
    width: 300,
    height: 200,
    edgeMargin: 1,
    windowRadius: 9,
    opacity: 0.80,
    colors: {
        modalBackground: "#CCC",
        windowBackground: "#999",
        contentBackground: "#EFEFEF"
   },
   showTitleBar: false
});

Public Methods

close() This closes the modal window. Once the window is closed, the elements are destroyed and garbage collected using MooTools’ element.destroy method.

var mymodal = new Modal({
    title: "My title",
    html: "<p><a href='#' onclick='return goAway()'>Close me</a></p>"
});

function goAway(){
    mymodal.close();
    return false;
}

Options

The options available in the Modal class are:

Text

  • title (String) Text content for titlebar. String format, loads into an h1 element.
  • html (String) Text content for the main window content. Loads into a div.
  • padding (Number, default is 12) Indicates how much padding the content div gets, in pixels.
  • font (String, default is “Verdana, Geneva”) Sets CSS style for the title and window content. Can be overridden by inline styles.
  • fontSize (String, default is “small”) Sets CSS style for the window content div. Can be overridden by inline styles.

Size/shape

  • width (Number, default is 350) Width of window before the drop shadow gets added, in pixels.
  • height (Number, default is 200) Width of window before the drop shadow gets added, in pixels.
  • shadowSize (Number, default is 6) Size of drop shadow, in pixels (note: not 100% pixel perfect measurements).
  • titlebarHeight (Number, default is 28) Size of titlebar, in pixels.
  • edgeMargin (Number, default is 1) Size of window chrome between shadow and content area, in pixels.
  • windowRadius (object OR number) Size of window corner radius, in pixels. If a number is specified, all four corners get the same radius. If an object is used, each corner gets the number specified (see below).
    • windowRadius.topLeft (Number, default is 9)
    • windowRadius.topRight (Number, default is 9)
    • windowRadius.bottomLeft (Number, default is 3)
    • windowRadius.bottomRight (Number, default is 3)

Behavior

  • animate (Boolean, default is true) Indicates whether or not to fade out the modal background when dismissing the window.
  • backgroundClickDismissesModal (Boolean, default is true) Indicates whether or not clicking the background will dismiss the modal window.
  • closeButton (Boolean, default is true) Indicates whether or not to include a close button. Only works when showTitleBar is also set to true.
  • showTitleBar (Boolean, default is true) Indicates whether or not the title bar should be rendered.

Colors

  • opacity (Number, default is 0.66) Indicates the opacity level of the background modal div. Number must be between 0 and 1.
  • colors (object) Child properties are used to specify colors for window elements. All color parameters accept standard CSS color conventions, including hex and RGB.
    • colors.modalBackground (Default is #000) Background modal div.
    • colors.windowBackground (Default is #AAA) Window chrome color.
    • colors.windowTitleBar (object) Used to create a gradient background for the window’s title bar. Accepts two properties: top and bottom.
      • colors.windowTitleBar.top (Default is “#F5F5F5”)
      • colors.windowTitleBar.bottom (Default is “#AAA”)
    • colors.contentBackground. (Default is “#F8F8F8”) Color of the canvas element behind the content div.
    • colors.closeButton (object) Used to create a the background and stroke for the closeButton.
      • colors.closeButton.fill (object) Used to create a gradient background for the closeButton.
        • colors.closeButton.fill.top (Default is “#F5F5F5”)
        • colors.closeButton.fill.bottom (Default is “#F36”)
      • colors.closeButton.stroke (object) Used to create a gradient stroke for the closeButton (does not work in Internet Explorer).
        • colors.closeButton.stroke.top (Default is “#FFF”)
        • colors.closeButton.stroke.bottom (Default is “#F00”)

Drawbacks

A system like this is bound to have drawbacks, and the biggest one is probably accessibility. Users with a screen reader or similar device may find the custom modal completely unusable. This is the same problem most RIAs face due to dynamically generated content. I’m considering implementing a check that uses a traditional JavaScript alert, prompt or confirm window in lieu of the custom modal if the user is using a screen reader. The check may or may not be based on whether WAI-ARIA mode is activated, but I have a long way to go before I’m ready for that! (Side note: To see WAI-ARIA in action, check out Google Reader.)

Another drawback is the mingling of my CSS with my JavaScript; in most cases I don’t like using JavaScript to set styles, except to set a classname. However, in this case I want the modal window to be as simple as possible and look nice out-of-the-box without requiring adding CSS files and all that jazz.

Lastly, I’d like to point out that the window doesn’t auto-size to fit the content. However, if the the HTML content doesn’t fit, the main content div will sprout scrollbars, ensuring you still have access to all of the content.

Anyway, this has been a fun experiment for me. It’s taught me a lot about what goes into creating feature-rich and flexible JavaScript widgets. I have nothing but respect for people who make JavaScript-based UI components like the folks behind MochaUI, Dojo, and YUI. It’s a TON of work.

Hawaiian diacriticals

For those of you who haven’t had the pleasure of encountering the Hawaiian language, it’s a very simple but elegant language.  The written form is largely phonetic (cooked up by American missionaries in the 1800s) and makes use of two diacritical marks: the ‘okina, and the macron (also known as the kahako).

An ‘okina usually indicates a glottal stop, which is very important in the pronunciation of Hawaiian words.  The name Hawai‘i is a great example: the ‘okina indicates the name is pronounced hahwhy-ee instead of hahwhy. When you hear a native pronounce the name, there’s usually a very short hard pause between the why and ee syllables.

Unfortunately, the two Hawaiian diacriticals are not used by European languages, which means they’re difficult to accurately represent on a standard US qwerty keyboard. In most printed publications, the authors simply omit the diacriticals altogether — the very reason you usually see the name Hawaii, and not Hawai‘i.

Over the last decade, there has been an attempt by many well-meaning locals (Hawaiian and non-Hawaiian) to use substitute characters when true diacriticals aren’t available. While macrons are usually omitted (they don’t exist in most font sets), the ‘okina is often represented by a foot mark (‘), sometimes (mistakenly) referred to as a straight or neutral single quote mark.

An 'okina. Credit: Wikipedia (http://en.wikipedia.org/wiki/File:Hawaiian_okipona.png) This brings me to one of my pet peeves and the purpose of this post:  misuse of the backtick (`) character. Many of the previously-mentioned well-intentioned folks mistakenly use a backtick to represent an ‘okina, and it drives me absolutely bonkers.

As I mentioned to a friend of mine recently, a proper ‘okina is usually the same as left single quotation mark (‘), depending on the font. The shape of the ‘okina should loosely resemble the number 6. In HTML you can get this character by typing the entity &lsquo;.

Granted, using entities is a pain for most people, and practically impossible in email and other electronic documents.  Substitutions will continue to be made.  I believe a foot mark (‘) is a more accurate depiction of an ‘okina than the backtick (`). It’s also easier to type and looks nicer.

Fun with canvas and MooTools: a Rectangle class

Greg Houston’s uber-cool MochaUI has led me to experiment with the canvas element the last few days.

I first saw MochaUI sometime in 2007. While I was duly impressed, I couldn’t quite find a use for it and filed it away in my bottomless “play with this later” drawer.

Recently at work I realized I needed a good modal window that was more extensible than JavaScript’s built-in confirm and prompt windows. MochaUI looked like a handy way to get slick modal windows into my project, but I soon realized that MochaUI is designed to do much, much more than I need, and therefore is (for my purposes) bloated. So, in typical DIY fashion here at pipwerks, I decided to borrow a page from Greg’s book and make my own MochaUI-inspired modal window using the canvas element, CSS, HTML, and MooTools.

Before I could get into the full modal window code, I needed to understand the canvas element, and learn how to manipulate it. Luckily for me, it isn’t much different than ActionScript’s drawing API. After evaluating what I’d need for my little modal window, I whipped up a MooTools-based JavaScript class that produces canvas rectangles in the blink of an eye. Check out the test suite.

Here’s how you’d instantiate a plain-jane rectangle with no stroke and a pink fill:

var canvas = new Element("canvas", {width:300, height:200}).inject(document.body);
var rect = new Rectangle({
    canvas: canvas
    width: 300,
    height: 200,
    x: 0,
    y:0,
    fill: "#F36"
});

Want rounded corners? Just add a radius parameter:

var rect = new Rectangle({
    canvas: canvas
    width: 300,
    height: 200,
    x: 0,
    y:0,
    fill: "#F36",
    radius: 9
});

As demonstrated by the test suite, the class includes the ability to specify the radius of each corner, the fill type and color (solid or linear gradient), and the stroke weight and color (including linear gradient).

For those of you unfamiliar with the canvas element, there are a host of additional options I chose not to build into my class, including join type for lines, radial gradients, and image backgrounds. For the purposes of my modal window, I didn’t need that stuff and wanted to keep it simple (and smaller file size). Feel free to modify the class for yourself, though.

Sometime in the next day or two, I’ll write about my modal window and provide test links. I’ll also cover the fugly hack required to get Internet Explorer to work with canvas.

Until then…

UPDATE: Read about the Modal class here.

PS: Disclaimers:

  1. If you’re wondering why I didn’t include a sample of the class in action on this page, my WordPress install uses jQuery, which conflicts with MooTools. I didn’t feel like fighting it tonight.
  2. I don’t write JS classes often, so I’m sure mine could use some cleanup. Feel free to make suggestions!

Obfuscating email addresses, revisited

A while back, I posted my method for defeating spambots that harvest email addresses. This post is an update to that original method. It explores cleaner, less obtrusive code approaches and more accessible/usable HTML markup.

If you’re impatient and want to jump to some working examples, here you go:

The other “solutions”

So how do you prevent spambots from harvesting your email address? Well, there are a gazillion suggestions out on the interwebs, and unfortunately most of them stink because they require JavaScript, and because they often use illegible or invalid markup. For instance, this example — which was created by an email address obfuscator ranked high in Google searches — uses character entities to render the text completely illegible:

<a href="&#x6d;&#97;&#000105;&#108;&#116;&#000111;&#58;&#000116;&#x68;&#x69;&#000115;&#64;&#x73;&#x74;&#000105;&#x6e;&#x6b;&#x73;&#x2e;&#00099;&#x6f;&#109;"

This method has been popular for a number of years, but has some serious flaws. First of all, how do you know if you even have the right address in there? Secondly, what’s to stop a spambot from reading character entities? I imagine it would be as easy as reading ASCII or UTF. GONG!

Here’s another popular approach, premised on the notion that spambots look for any links using a mailto: protocol:

<script type="text/javascript">
   function emailme(user, domain, suffix){
      var str = 'mai' + 'lto:' + user + '@' + domain + '.' + suffix;
      window.location.replace(str);
   }
</script>;
<a href="javascript:emailme('this','stinks','com')">this@stinks.com</a>

There are multiple problems with this approach. The first problem is that it doesn’t use mailto: in the markup. This means if JavaScript is disabled, the link is completely useless. It also breaks the sematics of the links.

The second problem is that the JavaScript is inline and therefore obtrusive. JavaScript should not be mingling with your markup… it’s bad form! Any link that starts with javascript: is troublesome in my book.

Lastly, the whole address is still contained in the text of the page. If a spambot is sophisticated enough to look for mailto: protocols, it’s probably sophisticated enough to use RegEx to search for text that uses both @ and a period (.) without spaces.

There are other solutions out there, too, but they all require invalid markup, semantically incorrect markup, or flat-out removal of the email hyperlink. I want a solution that remains clickable when JavaScript is disabled, and doesn’t get all screwy with the markup. These don’t fit the bill. There’s another way.

A cleaner solution

My solution is simple: use an invalid email address. No, really! An invalid address with some extra touches and some unobtrusive JavaScript will work wonders. Here’s how to use it, step-by step:

Step one: Create your markup using a slightly altered address

Begin with a real address, then modify it to include some dummy text. For instance, the address sales@visitwaikiki.com would be rewritten salesnotspam@visitwaikiki.com. The spambot will harvest the address salesnotspam@visitwaikiki.com, which won’t work when the spammers try to use it.

The markup should look like this:


<a href="mailto:salesnotspam@visitwaikiki.com">sales@visitwaikiki.com</a>

There’s an obvious flaw here: The email address is still written in plain text between the ‘a’ tags. We’ll need to use alternate text — if you want to avoid spambots, NEVER use the real address as the visible text in an email hyperlink.

Using something such as sales AT visitwaikiki DOT com is also probably a bad idea, simply because zealous spambot authors can look for that very common pattern and manage to parse the email address. You’re best off using a different phrase, such as:

<a href="mailto:janenotspam@visitwaikiki.com">Contact Jane.</a>
<a href="mailto:salesnotspam@visitwaikiki.com">Email our sales department.</a>

We still have another problem to address: The link works, but it’s using the wrong address! The next step will help with that.

Step two: Improve the markup to make the link more usable when JavaScript is disabled

It’s always a good idea to ensure your visitor can use the email hyperlink when JavaScript is disabled. As it stands, when the visitor clicks the link, their operating system will create an email addressed to the invalid address salesnotspam@visitwaikiki.com. Without JavaScript, we can’t correct the address, but we can let the user know that the address needs to be edited.

<a href="mailto:salesnotspam@visitwaikiki.com?subject=EMAIL ADDRESS NEEDS EDITING&body=Please remove the text 'notspam' from the address before sending your email.">
   Email our sales department.
</a>

The mailto: protocol allows users to tack on additional information using the subject and body options. Whatever is listed after subject will appear in the email’s subject line. Whatever is listed after body will appear in the message’s body. By creatively using these options in the email address, we can clearly instruct the visitor to edit the address as-needed. The code above this paragraph produces the following email when clicked:

To: salesnotspam@visitwaikiki.com
Subject: EMAIL ADDRESS NEEDS EDITING
Message: Please remove the text ‘notspam’ from the address before sending your email.

Is it a pain to have to include the subject and/or body options each time you write an address? Yes. But is it more of a pain than the hundreds of spam emails you might get each week? I doubt it.

We now have a fully-functioning standards-friendly markup-only spam-resistant link. (Yes, I love hyphens. Don’t you?) Next, we’ll improve the experience for the 95% or so of your visitors who have JavaScript enabled.

Step three: Use JavaScript to make the link behave normally for most visitors

Most of your visitors will have JavaScript enabled; let’s take advantage of this and improve their experience. Our primary goal with our script will be to correct the invalid address by removing the dummy text “notspam”. However, since we’re removing the dummy text, we’ll also need to remove the instructions contained in the subject and body options so we don’t confuse the visitor.

Here’s a simple function that scans the page for all email links, then removes the dummy text (assuming all links use the same dummy text), the subject option, and the body option:

onload approach

window.onload = function (){
   var links = document.getElementsByTagName("a");
   for (var i=0; i < links.length; i++){
      if(links[i].href.indexOf("mailto:") !== -1){
         this.href = this.href.split("?")[0].replace("notspam", "");
      }
   }
};

Live demo

This teeny bit of JavaScript executes when the page loads and makes all email links behave as expected. Now we have a fully-functioning standards-friendly spam-resistant email link that also degrades nicely for visitors without JavaScript, and looks/feels completely normal to everyone else.

However, if you’re paranoid like me, you’ll wonder: What if the spambot supports JavaScript and looks for email addresses after the page has loaded? Your email address would be just as vulnerable as it was before.

A quick tweak to the script can help: instead of cleaning the addresses when the page loads, we can choose to only clean an address when the link is clicked.

onclick approach

window.onload = function (){
   var addressCleaner = function (){
      this.href = this.href.split("?")[0].replace("notspam", "");
      this.onclick = function (){};
      this.oncontextmenu = function (){};
   };
   var links = document.getElementsByTagName("a");
   for (var i=0; i < links.length; i++){
      if(links[i].href.indexOf("mailto:") !== -1){
         links[i].onclick = addressCleaner;
         links[i].oncontextmenu = addressCleaner;
      }
   }
};

Live demo

Note: all modern browsers treat a link as ‘clicked’ if you tab to it and hit enter on your keyboard, which means the link remains accessible to those using keyboard navigation and/or screen readers.

Also, notice the oncontextmenu code; when a link is right-clicked, the onclick event isn’t triggered. If a person right-clicks the email address to copy it, they would be copying the invalid version of the address. Using the oncontextmenu event fixes this problem.

You’re done!

You now have a spam-resistant email hyperlink that works whether JavaScript is enabled or not. It adheres to standards (no invalid markup), is semantically correct, and is unobtrusive.

Having said that, you should be aware that this system is not perfect; spammers are very clever, and will always catch up to us. This method is a form of spam resistance, not a foolproof way to defeat all spambots from now until eternity.

While the code you’ve just seen will work fine for most people, there are a few improvements that can be made with the use of a JavaScript framework. If you don’t use a JavaScript framework such as MooTools or jQuery, your journey has ended. If you do use a framework, let’s explore some potential improvements to the system.

Improvements via frameworks

JavaScript frameworks add some impressive tools to our toolbox and provide many conveniences. For this example, I’m going to use MooTools 1.2, but most other frameworks will have similar code that you can adapt for your own needs. Here are some improvements we can make:

  1. Use event handlers instead of direct assignment.
  2. Use a domready event instead of window.onload.
  3. Use CSS selectors and the array:each method

Here’s the improved code, modified to use MooTools 1.2:

window.addEvent("domready", function(){
   var addressCleaner = function (){
      this.href = this.href.split("?")[0].replace("notspam", "");
      this.removeEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   };
   $$("a[href^=mailto:]").each(function (a){
      a.addEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   });
});

Live demo

Explanation of the MooTools framework version

Since some of you may not be familiar with frameworks, so I’ll try and explain the changes I’ve made.

Event handlers

Most JavaScript gurus will tell you that using event handlers is a much more robust approach than using a direct onclick assignment. For starters, adding an onclick event using direct assignment will overwrite any existing onclick event. Using an event handler will ensure the new event will not destroy any existing events, and will simply add the new event to a queue of events.

//Direct assignment
a.onclick = function (){
  //Do something
};

//MooTools event
a.addEvent("click", function (){
   //Do something
});

As you can imagine, if you don’t use a framework, browser support and cross-browser incompatibility issues make event handlers a bit of a pain. This is one of the primary reasons frameworks have become so popular: they take the pain out of cross-browser compatibility.

Change window.onload to a domready event

The domready event is executed earlier than an onload event. domready basically means that all markup has loaded into the browser DOM, even if images and other media haven’t finished downloading yet. onload, by comparison, only fires after everything has finished loading. A MooTools domready event looks like this:

window.addEvent("domready", function (){
   //do something
});

Use CSS selectors and the array:each method

MooTools allows us to replace document.getElementsByTagName with much more targeted CSS-based selector: $$("a[href^=mailto:]"). This selector finds all links on the page whose href attribute begins with mailto:, then places the results in an new array. This means we can ditch two elements of our original script: the call to

document.getElementsByTagName("a")

and the if syntax inside the loop:

if(links[i].href.indexOf("mailto:") !== -1)

Next, we can replace the for loop with an each method, which performs whatever action is specified to each of the items in the array.

myArray.each(function (arrayitem){
   //do something with arrayitem
});

The each array method is native to browsers not named Internet Explorer. Frameworks like MooTools and jQuery bring support for this function to browsers that don’t natively support it.

Now that we’ve got our CSS-based selector working with the each method, we can greatly simplify our code:

window.addEvent("domready", function(){
   var addressCleaner = function (){
      this.href = this.href.split("?")[0].replace("notspam", "");
      this.removeEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   };
   $$("a[href^=mailto:]").each(function (a){
      a.addEvents({
         "click": addressCleaner,
         "contextmenu": addressCleaner
      });
   });
});

Tips

  • You can place the dummy text in any part of your email address, not just the username portion. For instance, you could do sales@visitSOMEWHEREOTHERTHANwaikiki.com, sales@visitwaikiki.commie, etc.
  • It’s probably a good idea to use dummy text other than the common phrase “nospam”; authors of spambot software can easily look for these phrases as keywords and use them to target your address. Get creative with your dummy text, just be sure it’s obvious to a human reader that the text needs to be removed.
  • If you have multiple email addresses on the page, this method requires that you use the same dummy text in all email addresses.
  • Be sure you change the dummy text in the JavaScript function to match whatever text you decide to use!

Known Issues

When JavaScript is disabled and someone copies/pastes the email address instead of clicking it, they will be copying the invalid version of the address. To minimize problems, you can write the address in a hard-to-miss way, such as using all caps for the dummy text (salesNOTSPAM@visitwaikiki.com). This will be an extremely small percentage of users, so I wouldn’t worry too much; if they’re savvy enough to disable JavaScript and use copy/paste for email addresses, they’ll probably read the address, too.

This email address obfuscation method has been successfully tested in the following browser/OS combinations:

  • Firefox 3.0 (Mac OS X, Windows Vista)
  • Safari 3.2.1 (Mac OS X, Windows Vista)
  • Internet Explorer 6 (Windows XP)
  • Internet Explorer 7 (Windows Vista)
  • Internet Explorer 8b1 (Windows 7 beta)
  • Opera 9.6 (Mac OS X, Windows Vista)
    • One issue in Opera: The contextmenu menu event doesn’t trigger correctly when right-clicking

SCORM resources

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

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

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

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

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