Captivate Progress bar using Javascript

One of the things that’s constantly being asked for by clients and students alike is a Captivate progress bar. I know there’s one already built in to the Skin Editor, which takes a simple click to turn on or off. You can even disable it on Quiz slides. You can even customise it to a certain extent.

One thing about progress bars is that you can use them to navigate backwards or forwards. Videos from YouTube and Vimeo, audio files, you name it, they all use a similar concept. And we’re well used to the concept.

But there are some scenarios when allowing a learner to navigate around a project needs to be controlled. If you have mission-critical content that simply must be seen before proceeding forward – perhaps there’s a competency at stake, then allowing the standard progress bar to be visible in the interface is just not going to work.

One way around that is to use a Table of Contents, enabling the setting to only allow navigation to previously viewed slides. But you would not have the simple visual experience of a progress bar.  And despite having a few styling options, there’s not a huge amount you can do to create a visually stimulating experience.

It is possible to build a similar Captivate progress bar using Advanced Actions, but I find that approach so clunky and time consuming.

So I decided to share a couple of tips on how to build a Captivate progress bar using Javascript, and employing an undocumented function that allows you to control a Captivate multistate object from an external Javascript file. Totally cool and nerdy, I know, but so easy to repurpose, reuse and restyle.

On with the show…

The purpose of this exercise is to create a graphical progress bar that would replace the standard Captivate progress bar which is controlled by the Skin Editor.

Why? Because the standard progress bar has limitations for styling, and allows users to seek forward or backward in a project when sometimes this is not appropriate for the learning outcome.

The components of this exercise are as follows:

  • A multistate object showing a graphical progression in 10 steps.
  • A Javascript file which controls the display of the multistate object.
  • The use of Captivate System Variables.
Captivate Progress Bar Project
Captivate Progress bar 20%
Captivate Progress bar state
Captivate Progress bar incompleted_style
Captivate Progress bar incompleted style

Step 1: Create a smart shape to use as the multistate object

Select a rectangular smart shape from the button bar dropdown.

Click and drag to create a rectangular shape.

Make one with a border and background. This should be wide enough to contain 10 individual ‘steps’, each representing 10% progress of the project.

Click ‘State View’ in the Properties panel to enter the smart object’s filmstrip.

Lock the shape’s size and position in the ‘Normal’ state.(this is important, as things could move unexpectedly later on if you try to scale or reposition the progress bar.

Right-mouse-click on the ‘Normal’ state and choose ‘duplicate state’ from the dropdown list.

Name this state ‘step_0’. This represents the stage in the project where zero progress has been made.

Add a smart shape to this state (this will represent a stage of completion).

Make the shape about 1/10th the width of the background.

Position the shape at the left hand side and vertical centre of the full width background shape.

Create Object Styles

Create an object style for the shape to represent an ‘incompleted’ stage. This makes it easier for you to make global changes down the track if you want to re-style.

Repeat the process to create an object style to represent a ‘completed’ stage.

Select the small smartshape that you made and duplicate it 9 times so you have a total of 10 shapes.

Distribute them evenly horizontally across the background. 

Make sure all the shapes have the ‘incompleted’ style applied to them.

Duplicate and Rename states

Right-mouse-click and duplicate the ‘step_0’ state, name it ‘step_10’. This will represent 10% progress.

Change the style of the first shape in the series to the ‘completed’ style you created earlier.

The rest of the shapes on this state remain styled with the ‘incomplete’ style.

Repeat the process for the rest of the steps so that each successive state is named incrementally in units of 10, and each shape has the ‘completed’ style applied to it as progress is made.

For instance, the example below shows a state name ‘step_80’, and the first 8 shapes have the ‘completed’ style applied to them.

Captivate Progress bar completed style
Captivate Progress bar 80%
Captivate Progress bar object name

Give the Captivate progress bar a unique name

Exit the state view of the multistate object and position it on your slide.

Note that although it would make sense to have this progress bar placed on a Master Slide for easier implementation, it simply won’t work.

Position the multistate object on the slide and now give it a unique name.

Be aware that I’m using a very specific naming convention that must be applied to the progress bar multistate object on each slide that it is used.

On each frame that implements the progress bar, the multistate object should be named using the prefix ‘progress_’ and the suffix which is the slide number.

For example ‘progress_5’ for the bar on the 5th slide.

Step 2 : Create a Javascript Document

You can create a Javascript document with any basic text editor. You can use Notepad, Text Edit, Dreamweaver, or anything that lets you create a simple text document. Personally I prefer Sublime Text, but that’s just a personal preference.

Because we’re going to include this document at runtime while we’re testing, create a ‘publish’ folder where you’ll be publishing to.

Inside that folder, create another called ‘js’.

Open your text application and create a new file.

Save it in the ‘js’ folder you created a moment ago, and name the file ‘progressbar.js’.

Include the Javascript file

Although the Javascript we’re going to use isn’t overly complex and we could easily include it in the first frame script of our Captivate file, it’s just not as efficient and maintainable as having a separate file. So the first thing to do is to include it in the first slide of the Captivate file.

Go to the first slide, find the Properties tab, and making sure that you have nothing selected on the slide.

Select ‘Execute Javascript’ from the dropdown list underneath the ‘On Enter’ label. This means that as soon as the Captivate published document loads in a browser, this code will be triggered.

Note that you can’t see the results without publishing, and the only publish option here is HTML5. In other words, publishing as SWF won’t work!

Captivate Progress bar javascript file
Captivate Progress bar actions
Captivate Progress bar state view
Captivate Progress bar include javascript

When the ‘On Enter’ action is applied, you’ll see a button labelled ‘Script Window’.

Click the button to launch a small text editor. Be very careful here, as the code syntax needs to be absolutely accurate for this to work. Essentially, you’re going to write a line of code that uses JQuery (a Javascript Library included in all published Captivate projects) to load your script just inside the closing body tag of the published HTML. It’s going to look for the javascript file you created earlier which is in a folder a level up from the published index file.

I’ve used 3 lines below for readability. You can click and copy this code.

$("body").append("");

Inside the double quotes, type : <script src=’../js/progressbar.js’></script>

Note that the src element points to a file that is a folder level above your index file, so if this is not the case for you, point the src to wherever your javascript file is being hosted.

Step 3 : Write Javascript code!

If you’re unfamiliar with Javascript, I’m hoping this doesn’t scare you off right away. However, if you really want to get Captivate humming and you’re bored with the clunkiness of Captivate’s Advanced Actions, Javascript is the only way to travel!

Note that you can also combine Javascript with Advanced Actions, which can be really useful.

This script has a few independent functions that control the following aspects:

  • Initialisation
  • Getting and / or setting Captivate variables
  • Calculating current progress and rounding numbers.
  • Changing the state of the multistate progress bar.
  • Triggering a function when the slide changes.

Open up your code editor with the progressbar.js file open.

Note that best practice would mean that you’d be in the habit of commenting your code.

In Javascript, single-line comments are like this:

// this comment won’t be functional 

and multiline comments are like this:

/* This is a multiline comment. 
Particularly useful for more indepth explanations. */

Declare global variables

Start off by declaring the two global variables we’ll be using. We will create functions that set these values in just a moment.

// Captivate Total Slide Count : cpInfoSlideCount
var cpSlideCount
// Captivate Current Slide Number: cpInfoCurrentSlide
var cpCurrentSlide;

Getter & Setter functions

Create getter and setter functions that allow you to get the total number of slides in the Captivate project, and also what the current slide number is.

In reality, these two functions can be used anywhere you want to get or set both System and user-defined variables.

Please note that some System variables may be got, but not set.

/* ----------------------------------------- */
// Captivate Variable Getter/Setter
/* ----------------------------------------- */

function setCpVariable(p_var,p_val){
	console.log("setCpVariable: "+p_var+" : "+p_val);
	window.cpAPIInterface.setVariableValue(p_var,p_val);
}

function getCpVariable(p_var){
	var cpVar = window.cpAPIInterface.getVariableValue(p_var);
	console.log("getCpVariable: "+p_var+" : "+cpVar);
	return cpVar;	
}

Initialise

Create the initialisation function and then call it.

What this does is to use the getter/setter functions we just made, and assigns a value to the cpSlideCount variable.

Because it’s a global variable, we’ll be able to access it from anywhere in the code.

This function uses the getter function ‘getCpVariable’ defined above

/* ----------------------------------------- */
// Initialisation 
/* ----------------------------------------- */
function initialiseProgress(){
	console.log(":: initialiseProgress ::");
	// get the total number of slides
	cpSlideCount = getCpVariable("cpInfoSlideCount");
}
// Call the initialisation function
initialiseProgress();

Do the Math

Create a function that uses the total number of slides, the current slide number, and some funky maths to get a number that rounds down to the nearest 10.

Math.floor() rounds the percentage of the current slide to total slides.
Math.round with the extra multiplier cuts the result into a 10 unit bitesize!

Since we’ve got 10 stages in our progress bar, that’ll work out perfectly!

/* ----------------------------------------- */
// Calculate percentage of progress
/* ----------------------------------------- */
function calculateProgress(){
	console.log(":: calculateProgress :: ");
	cpCurrentSlide = getCpVariable("cpInfoCurrentSlide");
	var progress = Math.round(Math.floor((cpCurrentSlide/cpSlideCount)*100)/10)*10;
	return progress;
}

Change the Object State

The last stage is to use the number we calculated before, and use it to change the state of the progress bar in the current frame.

Note that cp.changeState is an undocumented function, but probably one of the most useful ones that I’ve ever come across.

Only one more piece left before we can test!

/* ----------------------------------------- */
// Captivate Multistate Object State Changer
/* ----------------------------------------- */
function progressBarStateChange(){
	console.log(":: msoStateChange :: ");
	// get the rounded down value of progress
	var currentProgress = calculateProgress()
	// change the state of the progress bar multistate object in Captivate. 
        // (undocumented function)
        cp.changeState("progress_"+cpCurrentSlide,"step_"+currentProgress); 
}

Create an Event Listener

OK this is the last chunk of code.

It’s used to make sure that every time Captivate enters a new frame, the functions that calculate the progress and change the state are fired off.

/* ----------------------------------------- */ 
// Captivate Event Listeners
/* ----------------------------------------- */
// executed when user enters a slide
window.cpAPIEventEmitter.addEventListener(
	"CPAPI_SLIDEENTER",
	function(e){
		// update the progress bar
		progressBarStateChange();
	}
);

Captivate Progress bar publish button

Step 5 : Publish your project

Publish your project in the folder you created earlier, and then browse it.

Note that using a local server environment is recommended here – something like MAMP on Mac/Windows, or WAMP or XAMPP on Windows.

Captivate Progress bar publish settings

Resources

Here’s the Javascript code that you can copy and paste. There are also a couple of download links where you can get the Captivate Source files (2019) and the Javascript code.

/* 
This file is stored in a js folder, on the same level as the published folder.
This script hooked in first slide of captivate project with:
$("body").append("");
Dependencies:
In Captivate, a naming convention applied to the progress bar multistate object.
on each frame that implements the progress bar, the multistate object should be named using the 
prefix 'progress_' and the suffix which is the slide number.
ie progress_15 for the bar on the 15th slide. 
Add a javascript action to the end of the script and call the function that starts initialises 
the progress bar : initialiseProgress();
From that moment, the Captivate enterFrame event listener will automatically trigger 
the progressBarStateChange() function, 
which calculates the percentage of progress, rounds down to blocks of 10, 
and triggers the change of the progress bar.
More info at https://jrdesign.ccom.au/blog

$("body").append("");
initialiseProgress();

*/
console.log("JProgress Bar Javascript included");

// Captivate Total Slide Count : cpInfoSlideCount
var cpSlideCount
// Captivate Current Slide Number: cpInfoCurrentSlide
var cpCurrentSlide;


/* ----------------------------------------- */
// Initialisation 
/* ----------------------------------------- */
function initialiseProgress(){
	console.log(":: initialiseProgress ::");
	// get the total number of slides
	cpSlideCount = getCpVariable("cpInfoSlideCount");
}

/* ----------------------------------------- */
// Captivate Multistate Object State Changer
/* ----------------------------------------- */
function progressBarStateChange(){
	console.log(":: msoStateChange :: ");
	// get the rounded down value of progress
	var currentProgress = calculateProgress()
	// change the state of the progress bar multistate object 
        // in Captivate. (undocumented function)
	cp.changeState("progress_"+cpCurrentSlide,"step_"+currentProgress);
}

/* ----------------------------------------- */
// Calculate percentage of progress
/* ----------------------------------------- */
function calculateProgress(){
	console.log(":: calculateProgress :: ");
	cpCurrentSlide = getCpVariable("cpInfoCurrentSlide");
	var progress = Math.round(Math.floor((cpCurrentSlide/cpSlideCount)*100)/10)*10;
	return progress;
}
/* ----------------------------------------- */
// GETTERS AND SETTERS
/* ----------------------------------------- */
/* ----------------------------------------- */
// Get current slide number
/* ----------------------------------------- */
function getCurrentSlide(){
	return getCpVariable("cpInfoCurrentSlide");
}
/* ----------------------------------------- */
// Captivate Variable Getter/Setter
/* ----------------------------------------- */

function setCpVariable(p_var,p_val){
	console.log("setCpVariable: "+p_var+" : "+p_val);
	window.cpAPIInterface.setVariableValue(p_var,p_val);
}

function getCpVariable(p_var){
	var cpVar = window.cpAPIInterface.getVariableValue(p_var);
	console.log("getCpVariable: "+p_var+" : "+cpVar);
	return cpVar;	
}

/* ----------------------------------------- */ 
// Captivate Event Listeners
/* ----------------------------------------- */
// executed when user enters a slide
window.cpAPIEventEmitter.addEventListener(
	"CPAPI_SLIDEENTER",
	function(e){
		// update the progress bar
		progressBarStateChange();
	}
);

/* ----------------------------------------- */ 
// Boot up the Code
/* ----------------------------------------- */
initialiseProgress();

Access Tutorial Assets

If you’re a JRD Customer or JRD Registered Student, you can access special information and download the tutorial assets here.

What’s in there?

The source files for the eLearning project shown in the link.

Includes both the Captivate 2019 and the Javascript files.

Useful Links

Thank you!

Thanks to James Fitzroy Photography for the loan of his image. James is an incredible photographer who manages to capture the real essence of the Australian Outback. If you like this image, you should check out his website and his facebook page.