Controlling Captivate – Bookmarking

We’ve read numerous posts on the internet concerning the way Captivate bookmarks a learner’s location in a course. Since you don’t have the option the change CP’s behavior out of the box, we’ll show you how to override the default SCORM bookmarking behavior in Captivate 6, 7 and 8.

Captivate uses the SCORM value cmi.suspend_data to hold the last visited location and seems to completely ignore the value stored in the cmi.lesson_location. Since we really don’t know how Captivate uses this value, and because we’ll need to use this value for comparison, we’ll manipulate the SCORM_SetBookmark as well as the SCORM_SetDataChunk functions which are located in the scormdriver.js file. For this example we will be demonstrating the fix for SCORM 1.2, but we’ll give you the edited SCORM 2004 functions as well.

Captivate normally returns the user to the last page visited when re-accessing a SCO. This is because Captivate writes the lesson location and suspend data on every slide. Contrary to popular belief, it doesn’t matter if you have “send data on every slide” selected or not in your SCORM settings, Captivate will write this data. When the learner returns to the SCO, they are returned to the last visited slide. For example; If the user has accessed slide 15, but backs up to slide 5 and exits the SCO, they will be returned to Slide 5 when resuming the SCO. To override this behavior we’ll add some JavaScript to the functions that record the bookmark.

To start, let’s open the scormdriver.js (see locations below) file in Dreamweaver and find the first function that handles the bookmarks; SCORM_SetBookmark(). We have copied and pasted the contents of the scormdriver.js file in http://jsbeautifier.org/ to “unminify” the contents and copied the result back into our scromdriver.js file. This will make the file readable and much easier to edit and only adds 53kb to the file size.

Location for SCORM 1.2:

C:\Program Files\Adobe\Adobe Captivate X x64\Templates\Publish\LMS\Standard\SCORM\Default\1_2\scormdriver.js

Location for SCORM 2004:

C:\Program Files\Adobe\Adobe Captivate X x64\Templates\Publish\LMS\Standard\SCORM\Default\2004\scormdriver.js

function SCORM_SetBookmark(strBookmark) {
    WriteToDebug("In SCORM_SetBookmark strBookmark=" + strBookmark);
    SCORM_ClearErrorInfo();
    return SCORM_CallLMSSetValue("cmi.core.lesson_location", strBookmark);
}

In the SCORM_SetBookmark() function we’ll need to add a bit of code to compare the current bookmark to the value of the argument strBookmark that Captivate is sending in to the function. First we’ll get the current bookmark by adding this line of JavaScript in the function:

var getBM = SCORM_GetBookmark().split("_");

Because the value of the current bookmark is a string with a format of “Slide_5”, we need to get the numeric value of the slide by splitting the string into an array and getting the value of the 2nd index of the array, which is the value after the underscore character which we use as the separator. We also need to turn that result into a number with which we can do a mathematical comparison with the next line:

getBM = parseInt(getBM[1]);

Now that we have the current bookmark we’ll also need to parse the value of the argument strBookmark the same way. Since the variable argument is the same for both versions of SCORM the JavaScript is the same.

var sentBM = strBookmark.split("_"); sentBM = parseInt(sentBM [1]);

Now we can compare what the bookmark currently is to the value that Captivate is trying to write with a simple if/else statement. This statement checks to see if the value of the current (old) bookmark is equal to or greater than the bookmark Captivate is trying to write. If it is, we will return a value of “true” to trick Captivate into thinking the operation was successful. If not, we will write a new bookmark.

if (getBM >= sentBM)
{
	return true
}
else
{
	return SCORM_CallLMSSetValue("cmi.core.lesson_location", strBookmark);
}

The final edited function will look like this:

function SCORM_SetBookmark(strBookmark) {
    WriteToDebug("In SCORM_SetBookmark strBookmark=" + strBookmark);
    SCORM_ClearErrorInfo();
	
    var getBM = SCORM_GetBookmark().split("_");
    getBM = parseInt(getBM[1]);
	
    var sentBM = strBookmark.split("_");
    sentBM = parseInt(sentBM  [1]);
	
	if (getBM >= sentBM)
	{
		return true;
	}
	else
	{
		return SCORM_CallSetValue("cmi.core.lesson_location", strBookmark);
	}
}

Now that we have edited the function that sets the cmi.core.lesson_location, we still need to override the suspend data, which is what Captivate really uses when the user resumes a SCO. We are going to parse the “bookmark” from the string value held in the cmi.suspend_data. We’ll do this in the SCORM_SetDataChunk() function in which the suspend data is written. The first step is to get the slide number from the lesson location so we can do a comparison the same way we did in the SCORM_SetBookmark() function. We’ll do this in a slightly different way though since the argument sent into the function is not just a slide number, but a whole array of data including progress, slide type, variable data, and more.

var getBM = SCORM_GetBookmark().split("_");
getBM = parseInt(getBM[1]);
	
var cp = document.getElementById('Captivate');
var thisSlide = cp.cpEIGetValue('m_VarHandle.cpInfoCurrentSlide');

Note: Captivate writes the suspend_data as a string, but separates all of the data with “%24”. When using this character set as the separator when converting the string into an array, the user’s last location is held in the first index of the array. Because the values are sequenced with the values in the example below, we would need to write and parse a lookup table to manipulate the suspend_data. Instead we’ll just write some code to determine whether or not to write suspend_data.

Sample suspend data lesson location examples:

Slides 1-26 = A1A thru Z1A
Slides 27-52 = a1A thru z1A
Slides 53-62 = 01A thru 91A

Since the suspend data contains much more than just the users location in the SCO, we still want to allow Captivate to write the rest of the suspend data. We will do this by parsing the suspend data, separating the bookmark from the rest of the data, and then combining that with the “old” bookmark data.

This line of code will get all of the new data Captivate is sending following the 1st instance of the %24 separator, which is the data after the bookmark data.

var newData = strData.substring(strData.indexOf('%24'));

This next line of code will get the bookmark value that is currently stored in the suspend_data.

var oldData = SCORM_GetDataChunk().split('%24');

Now we’ll concatenate the two values and write the new suspend_data.

return SCORM_CallLMSSetValue("cmi.suspend_data", oldData[0]+newData);

Next we need to encapsulate the existing function with our new code in another if/else statement. Our completed function will now look like this:

function SCORM_SetDataChunk(strData) {
	
	var getBM = SCORM_GetBookmark().split("_");
    getBM = parseInt(getBM[1]);
	
	var cp = document.getElementById('Captivate');
	var thisSlide = cp.cpEIGetValue('m_VarHandle.cpInfoCurrentSlide');
	
	if (getBM == thisSlide)
	{	
		WriteToDebug("In SCORM_SetDataChunk");
		SCORM_ClearErrorInfo();
		
		if (USE_STRICT_SUSPEND_DATA_LIMITS == true) {
			if (strData.length > 4096) {
				WriteToDebug("SCORM_SetDataChunk - suspend_data too large (4096 character limit for SCORM 1.2)");
				return false;
			} else {
				return SCORM_CallLMSSetValue("cmi.suspend_data", strData);
			}
		} else {
			return SCORM_CallLMSSetValue("cmi.suspend_data", strData);
		}
	}
	else
	{
		var newData = strData.substring(strData.indexOf('%24'))
		var oldData = SCORM_GetDataChunk().split('%24');
		
		return SCORM_CallLMSSetValue("cmi.suspend_data", oldData[0]+newData);
	}	
}

By implementing this bit of JavaScript you’ll now have a choice in how bookmarking works in Captivate. Add the code to have the user returned to the point of their furthest progress or do nothing to have the user returned to their last visited slide.

You can also edit Captivates template files to have this “fix” applied to all of your courses.

Sign up now to obtain completed scormdriver.js files for SCORM 1.2 and 2004! You’ll also get access to all of TLC Media Design’s goodies and updates.

Thanks for reading this latest blog in the Controlling Captivate series.