Using JavaScript, PHP and MySQL to Log Captivate Variable Data

Many Captivate developers use a Learning Management System to log E-Learning Interactions.   The variable data in a Captivate project holds very useful information about how the user is progressing in your E-Learning Course.    Definitely Learning Management Systems are great and Adobe’s Prime LMS is excellent but what if you just want to log some simple user variable data and you don’t feel the need for a full LMS.   I am going to show you how to use JavaScript and PHP to capture Captivate variable data into MySQL tables.   Once the data is posted to the MySQL tables it can be reported on using PHP or a PHP Report Generator.

This instructional video will show you how to do this:

As shown in the instructional video the JavaScript $.post command passes the Captivate variables to the PHP program.   The PHP $_REQUEST command receives the Captivate variables into the PHP program where the data is processed and stored in the MySQL table where it can be reported on.

Example JavaScript:

$.post(‘’,{vemail:ls_email, vstartdatetime:v_startdatetime, vtitleno:v_titleno, vincludeintro:v_includeintro, vcompanyid:ls_companyid}, function() {});

Example PHP:


$email = $_REQUEST[“vemail”];

$startdatetime = $_REQUEST[“vstartdatetime”];

$titleno =  $_REQUEST[“vtitleno”];

$includeintro= $_REQUEST[“vincludeintro”];

$companyid= $_REQUEST[“vcompanyid”];

$conn = new mysqli($servername, $username, $password, $dbname);

$sql = “INSERT INTO userlog (email, startdatetime, titleno,includeintro, durationtime) VALUES (‘$email’, ‘$startdatetime’, ‘$titleno’, ‘$includeintro’, 10)”;



Hope you find this implementation useful and it helps you with your Captivate course development.

Showcase Captivate Project:

Feel free the try out these relaxing Captivate Online Meditations at:


-Steve Lewis

Custom Volume Control – Articulate Storyline 3 or Articulate Storyline 360

The latest release of Articulate Storyline 360 or Storyline 3 has number of exciting new features allowing eLearning developers to explore many more creative ways in developing new interactions. Yet, we still have a few things that cannot be achieved using the native options of Articulate Storyline 360. One such, most requested functionality is, the…

A better way to create a custom PDF of lesson variables from captivate

I’ve seen a number of cases where people have tried to use the eLearningBrothers “print.html” technique to create a custom PDF of information gathered during a lesson.   Inevitably it has led to frustration.   In one case,  the administrator forgot to include this custom file in the course module,  and in another,  the amount of text was larger than that allowed in a URL and broke the call.

The following gets past both of these problems

1.  the entire capability is packed into the course source file and will not be inadvertently lost

2. there are no limits on the number or length of variable names

Here’s a simple alternative

1. Add the following code to “On Enter, execute JavaScript”

var printWindow

function writeHeader(txt) {
printWindow.document.write("<H1>"+txt+"</H1> <hr>")

function writeName(txt) {

function writeValue(txt) {

function openPrint() {

function printVars() {"", "_blank");

sty=` <style type="text/css">
        body {
          font-family: Sans-Serif;
          padding-top: 10px;
          padding-left: 10px;
            z-index:: 1;
          font-family: Sans-Serif;
            color: #666666;
            padding-left: 22px;
        h1 {
            font-family: Sans-Serif;
            padding-left: 19px;
            margin-top: 27px;
        h3 {
          font-family: Sans-Serif;
            color: #444444;
          padding-left: 20px;
        p {
            -webkit-margin-before: 0.0em;
            -webkit-margin-after: 0.0em;
          font-family: Sans-Serif;
            color: #666666;
            padding-left: 20px;
        hr {
            display: block;
            -webkit-margin-before: 0.5em;
            -webkit-margin-after: 0.5em;
            -webkit-margin-start: auto;
            -webkit-margin-end: auto;
            border-style: inset;
            border-width: 1px;
var d = new Date();
var curr_date = d.getDate();
var curr_month = d.getMonth() +1;
var curr_year = d.getFullYear();
printWindow.document.write('<div id="date">'+curr_month+"-"+curr_date+"-"+curr_year+'</div>');

2. On the slide you want to create the PDF, add the following code to “on enter, execute javascript”

function fillWindow() {
  writeHeader("any text for your header")

  writeName("any text here for a name")
  writeValue("any text here for a value")



// add more name/value pairs here
// this is the last line in the function


Just fill in the variable names and values as you need to and create this function


3. On the slide you want to create the PDF, create a button that has an action “on success, execute javascript”

the code for the button is simply:


That’s it.

When you press your “print variables” button,  another window will open with a simple format, already set to print.  you can set the print function to save as PDF and you’re on your way.

Note  – this solution uses a quoting capability only available in the latest versions of Javascript, so if you need to support IE11,  some small changes need to be made.

Create a List of All Your Slide Titles (Exporting a Table of Contents)

Earlier today, I saw this discussion:

In essence, ruthg84 needs to get a list of all the slide titles in several of her files. I responded on that thread but am showing my solution here too because I can include screen shots here.

I took a quick stab at this and have created a solution that works, though it takes a couple of steps. I hope others of you may find a quicker way, but this works and doesn’t take long.

  • In the first slide of your Captivate file, set the On Enter action to Execute JavaScript and enter this in the JavaScript window:

2017-07-15 19_55_17-Get slide titles.cptx_ 2017-07-15 19_55_25-JavaScript

var titles = ”;
var title = ”;
     function() {
           titles = titles + ‘Slide ‘ + window.cpAPIInterface.getVariableValue(‘cpInfoCurrentSlide’) + ‘: ‘;
           title = window.cpAPIInterface.getVariableValue(‘cpInfoCurrentSlideLabel’);
           if (title == ”) { titles += ‘(No slide title)’} else {titles += title };
           titles += ”;

  • Make sure in your Skin Editor that your playbar is turned on and the Next button is available.

    2017-07-15 19_56_36- 2017-07-15 19_56_55-

  • Publish your file (Preview won’t work). I tried it in HTML5 but Flash should work too.
  • Launch your published file in the browser.
  • Turn on your Development Tools in your browser. In Chrome, you can do this by hitting Ctrl+Shift+I or going to the browser’s options and turning it on there.
    2017-07-15 19_58_41-
  • In your Development Tools, go to the Console. Here you will see at least the first slide’s title listed.

2017-07-15 20_03_23-INDEX~1.HTM

  • Use your Playbar Next button and go to each screen, quickly if you like, just as most learners do (haha). There is no need to wait for the slide to finish.
  • Each time you hit the Next button, you’ll see in the Console the current slide title added. If a slide does not have a title, the words (No slide title) are added.
  • When you get to the last slide, copy the last console line and paste it into a text editor or into Word.
    2017-07-15 20_04_33-INDEX~1.HTM
  • Find and replace each occurrence of @@ with a carriage return.
  • Now you have a complete list.

Example: I tried this with a file I created in which there are six slides. After I got to the sixth slide after publishing, I copied this out of the Console window:

Slide 1: Introduction@@Slide 2: Hello There@@Slide 3: (No slide title)@@Slide 4: Slide Four@@Slide 5: Test@@Slide 6: Summary@@

I copied the line into a text editor (Word would work too) and after replacing all @@ with a carriage return, now have a nice list:

Slide 1: Introduction
Slide 2: Hello There
Slide 3: (No slide title)
Slide 4: Slide Four
Slide 5: Test
Slide 6: Summary

I hope this helps anyone who needs to make a quick list of all slide titles.

Captivate popup manager with 508 accessibility support

A common e-learning interaction is a set of buttons that activate the display of a particular set of images and text. Often, the items are displayed as an overlay on top of the button set, and a semitransparent grey background is added to de-emphasize the selection buttons. When the learner is done with the displayed information, they click on either the background or a specific “close” button. This is commonly referred to as a “Lightbox.”

Captivate has all of the functionality needed to implement these kinds of interactions through the use of Advanced Actions but the process used to create and manage the popup can be cumbersome and brittle. As an alternative, we have developed a JavaScript popup manger that simplifies the process of display, and state management given certain shape naming conventions. Our popup manager also addresses a key navigation issue for the visually impaired, which is discussed at the end of the article.

The complete Captivate 10 source and the published version of this interaction are available here.

Creating a popup: Naming conventions

The Captivate timeline gives a very compact way of describing all of the elements on the page.

  • The buttons are specifically named to help with tab ordering (discussed later)
  • All elements associated with the popup are given ID names that start with a prefix that identifies the particular popup interaction, in this case “animal”. Elements that are part of the popup start out hidden, which is done for each element in the property tab by clicking on the eye next to the element name to add the red strikeout:

The popup manager

Our popup manager is added to the slide using the “on enter, execute Javascript” function. The code is provided below:

var currentButton
function popupManager(popupID, stateName) {
console.log(popupID, stateName);
let clearMarker = (stateName == "clear") || 
                  (stateName == "close") || 
                  (stateName == "hide")
if (!clearMarker) {
  currentButton = document.activeElement
// get elements
popupElements = $("[id^='" + popupID + "'].cp-frameset").map(function (i, j) {
// hide all popup elements to start
for (element in popupElements) {
  elemID = popupElements[element]
// set popup state
 if (clearMarker) {
for (element in popupElements) {
  elemID = popupElements[element]
  cp.changeState(elemID, stateName);;
$("[id^='" + popupID + "_Text']").focus()

The popup manager is called using the function:

function(popup_ID, desired_state) 

where the popup_ID is the prefix used on each popup element’s ID (in this case “animal”) and the “desired state” is the state name you have used when creating each element’s visual states.

The popup manager performs the following:

  • Show all elements that are associated with the popup
  • Set the element state
  • Set focus to the popup element designated as “Text” (discussed later)

When called with “hide”, “close”, or “clear” as the desired state:

  • Close the popup and set focus back to the button that initiated the interaction (discussed later)

Opening a popup

The buttons use the following JavaScript to open the popup:


where “animal” is the popup shape prefix as seen in the timeline and “dog” is the desired state of shapes in the popup. This is added to the button as the action “on success, execute Javascript”. We typically disable the “continue playing project” option on our buttons.

Configuring popup shapes

In our case, we have five shapes associated with the popup. All will be shown and hidden together since they all have the same prefix on their name.

After we construct these shapes, they are hidden and locked.

Two of the shapes have visual states, the “animal_Text” and “animal_image” shapes. We name the states so that the image of the dog and text associated with the dog have the same state name ( um, “dog” ).

Any shape with the popup name prefix and the designated state name will be set before being displayed.

Two other shapes, animal_exit and animal_grey will enable the learner to close the popup. They are both smartshapes that are set to be buttons.

  • The animal_grey shape is really just a black rectangle that covers the entire screen. It is set with an opacity of 70% to create the grey transparency effect. Since it is also a button, it initially is created with to other states, “Rollover” and “Down”. We set these states to have the same image as the Normal state: Black with 70% opacity. It is also set to display behind the other elements in the popup.
  • The animal_exit shape is always set to display on top of all other elements. You can place this anywhere on the page, but it will be required to have such a shape to support our 508 accessibility needs (discussed below)

Both of these shapes use the following JavaScript to close the popup, added under the action “on success, execute JavaScript”:


In both cases, we disable the “continue playing the project” option.

The use of JavaScript and the popup manger gives a lot of flexibility when developing popups. Adding additional elements to the popup, and adding/removing additional buttons is very simple. Entire popup interactions can be copied with a few modifications to prefixes.

Accessibility and 508 compliance

Designing courses that are accessible to individuals with disabilities is a complex topic, and a complete explanation is beyond the scope of this article. The fundamental needs for folks that visually impaired include the ability to use a text-to-voice screen reader to understand what is on the page, and a rational structure to the page so that keyboard-based navigation can be used to tab through the elements on the page.

As you might imagine, popups are really a problem when it comes to keyboard navigation, as there is a natural concept of navigating through buttons in a menu, but the jump to and back from the popup is not “linear” and there is no inherent support in Captivate for this capability.

There are a set of Internet standards that have been developed to help screen readers deal with these concepts, called the “ARIA” standards. However, Captivate does not provide any support for these, and there is no simple way to post-process the captivate published file to integrate ARIA tagging.

We have addressed the issue of 508 compliance in popups by controlling “focus”. One element in the popup has a special designation as “Text”. When the popup is opened, the browser’s focus is shifted to this element. Screen readers will then read the text within that element.

The Text element is designated by creating a shape that has the popup prefix followed by the characters “_Text”. In the above case, the shape is “animal_Text”. If for some reason additional characters are added to the end of this ID, it doesn’t matter. We tend to avoid using text caption elements, as we have found that screen readers seem to have problems with those. Smartshapes with text seem to work best.

The “exit” element in the popup is always defined last, and is set to display on top of all other elements in the popup. After the screen reader is done with the text element, the next element in the tab order will be the exit element. The popup manger is designed such that when the popup is closed, focus then shifts back to the button that initiated the action, enabling further navigation though the buttons.

Building for 508

Building for accessibility can tremendously complicate the creation of course material, nearly doubling the development time in some cases. The following are key steps needed to make the popup interaction more 508 friendly:

  • Tab order: Once the buttons are built, it is necessary to select the entire slide and set the tab order for the buttons. Giving the buttons named IDs makes this a lot easier!
  • Exit button accessibility text: The popup exit button is typically just a symbol, like an “X”. Accessibility text will be read when the shape is in focus. Something like “Return to list” is used.

Testing Accessibility Compliance

For folks that do not do accessibility-enabled projects very often, it is possible to get a relatively good assessment of the basic compliance with tab-order and accessibility text availability using just Microsoft Windows 10. The built-in browser, Microsoft “Edge” is actually excellent at displaying the tab focus information, and Microsoft’s built-in screen reader “Narrator” can be used to test pages in Edge for accessibility text.

For more information on Narrator, you can see

Using Narrator is easier than you think. The most important commands are:

  • ctrl-win-enter start and stop narrator
  • caps lock + F1 narrator help
  • caps lock + left/right arrow keys move between items and read

In Edge, the current tab location will be visible with dashed lines around the item.


For more information, contact sdwarwick <at> eLearningOcean <dot> com

Goto slide by slide name & goto slide containing shape with name in javascript

There are many cases where you have interactions that require jumping to another slide in the module.  The classic case is a “menu” slide with a number of options, where selecting an option transfers the learner to a particular lesson or detail slide.  This is also the basis of how adaptive learning is actually implemented.

Captivate provides you a way to jump to a particular slide using advanced actions.  Once you point to a particular slide, the advanced action jump will follow that slide even if it is moved or other slides are added before or after.  This is a well known capability that has been part of captivate for a long time.

There are some cases where having  the ability to perform these kinds of jumping functions in javascript is an advantage.

  •  It is sometimes easier to develop more complex branching decisions in javascript than directly in advanced actions
  •  There are cases where one wants to use a named object rather than a slide name to identify which slide to go to
  •  If slides are deleted, captivate removes the “jump to slide” action and replaces it with a “continue”  action.

Jump to slide by slide name

The following shows how to implement jump functions in Javascript.  First, we  define a Javascript function that gets added to the menu  slide,   and then each button is set up to call this function with the name of the slide that it wants to have the user jump to.

The javascript function is:

function gotoSlideNamed(aName) {
   var cpObjectList = Object.getOwnPropertyNames(cp.D);   // get all of the captivate-defined objects
   var findSlideLabels = function ( acc, val ) {   //  extract objects that have slide names 
         if ( typeof cp.D[val].lb == "undefined" || cp.D[val].lb == "" ) return acc;
          acc[cp.D[val].lb ] = cp.D[val].from ;   // pick up the frame number 
          return acc
   var cpSlideObjects = cpObjectList.reduce ( findSlideLabels , {} )   // run the function on all of the captivate objects
   cpCmndGotoFrameAndResume = cpSlideObjects[aName];  // set the "next frame" variable to the start frame of the object
   return ;
This script  is  added to the menu slide using the “on entry execute Javascript” slide-level action.
Now, for each button,  you simply add the following “on success execute Javascript” action:
gotoSlideNamed("action_1")   // action_1 is just a made-up name,  you put the name of your desired slide there..
Now, any change in the module will not impact where you jump to.
It is common that once a learner completes a subsection, they are taken back to the menu.   In that case, we use the same function to make sure that the return jump is also robust.   Naming the menu slide “top_menu”   we add a “go back” button with the javascript:
Note that we tend not to use names with spaces in them just for safety.

Jump to slide by name of shape

There are some cases where you’d rather jump to a slide that happens to have an element on it with a specific name.   In this case,  there is another function we use that works for named elements rather than slide names:

function gotoSlideWithShapeName(aName) {
  var theSlide = cp.D[aName].apsn;    // get parent slide name
  cpCmndGotoFrameAndResume = cp.D[theSlide].from   // set next frame to the start frame number of that slide 

If you have a shape on the target slide with the id “section_3”  you can have a button that simply calls the javascript function:


With these two functions,  branching paths can be accomplished in Javascript, which can be an advantage in cases where you have complex branching scenarios.

For more information,  contact

( edited 6/11)

Capture Adobe Captivate Data in Real Time

This demo course and post explains how to use a free Firebase account to collect and display all user data live in real time.

Play with the live example here (click the launch course button, upper right):

Watch a video of how I made it here:

Read more and get the JavaScript here:

I will be presenting a similar concept on how to use Google (and other) Analytics as free LMSs at Learning DevCamp June 6th.