Report Custom SCORM Variables with JavaScript

In this tutorial guest contributor Trihan explains how to set up Captivate user variables and insert JavaScript code to report scores to a SCORM-compliant LMS.  How would this be useful?  Well, it will allow you to use text boxes on slides as a full-blown SCORM-compliant quiz that has multiple possible inputs and partial scoring.

----------

Hi everyone! Trihan here. This is my first (but hopefully not last) contribution to Infosemantics. I hope it will prove to be useful to a lot of people. Without further ado, let's begin!

Captivate has a rather annoying limitation that I can see more than one person on the forums has come up against: you can't add interactive objects to a question slide. For those of us who want questions that are a little more sophisticated, or variable scoring depending on just -how- correct an answer was, there isn't much of a choice. Sure, you could create a normal slide with objects on it and do your partial validation that way, but it's impossible to have those custom scores report to an LMS.

...until now.

Sounds wonderful, right? Well before I go into the method, there are a couple of limitations I know of that I'll outline first.

  • You can't use this in conjunction with suspend data for some reason. When I tried my score calculation slide gave me an "Object doesn't support this property or method" error.
  • I'm not sure whether this will work alongside standard Captivate question slides, since the finish button on the results slide effectively suppresses Captivate's "normal" LMS reporting. I might look into this at a later date.

Let's say we want a question that asks someone to introduce themselves on the phone. Ordinarily, with a short answer question you would put something like "Hi, my name is [name]". But what if your user actually types something like "Hello, you're speaking to [name]."? It's a valid thing to say, they should certainly get a point for it. But by the standard rules of short answer marking, they wouldn't get it. And that's why I decided to try this.

You'll need the following prior to starting:

  • A user variable for each answer in your quiz. If you're using the checkbox widget, you'll need a variable for each possible answer. Make sure the names are almost identical so you can assign the Javascript values with a loop. In my example, I used "s[number of slide the question is on]_txt_Q[number of question]"
  • Some knowledge of regular expressions. It should be easy to understand the example I give here, but for your own purposes you'll need to know exactly what pattern it is you're looking for. For testing regular expressions I cannot recommend enough the wonderful tool The Regex Coach, which allows you to test various strings against your regular expressions and see where they match.
  • Very basic knowledge of Javascript. The code I give here should work for pretty much everyone, but it's only been tested on Moodle and may require some tweaking.

Method

1. Create a blank slide. Add a caption with the question text, and a text input box for the answer (or a radio/checkbox widget for multiple choice questions). Repeat for each question you'd like to have on the slide. 

2. Once all the question slides are created, create another blank slide; it helps to call this "prepare answers" or something similar to make it clear it's for processing only. Set the On Entrance action to "Execute Javascript". Go to your script window and add the following code:

var objCP=document.Captivate;
var answers=[];
for (i=1;i<[number of answers to process in this script];i++){answers[i]=objCP.cpEIGetValue('s1_txt_Q'+i);}
calculate_Grade();
function calculate_Grade(){

var myRegExp='',matchPos,score=objCP.cpEIGetValue('cpQuizInfoPointsscored ');
myRegExp=/(?:Hello|Hi|Hey),? you(?:'re| are) (?:speaking|talking) (?:to|with) [A-Z][a-z]+\.?/i;matchPos=answers[1].search(myRegExp);
if (matchPos != -1){score+=1;}

objCP.cpEISetValue('cpQuizInfoPointsscored',score);
objCP.cpEISetValue('rdcmndNextSlide',1);

That's a pretty simple regular expression for the purposes of the demonstration, but they can be as simple or as complex as you need them to be as long as you know what you're doing with them. If anyone wants I can write a more in-depth regular expressions tutorial at some point, but I don't foresee there being enough interest to warrant going into it in detail just now.

Essentially, what this does is store the answer to the question in an array, then check that answer against a pattern of what input you're looking for. Then it adds the score that part of the question is worth, and assigns the variable's value to cpQuizInfoPointsscored. Finally, it forces a jump to the next slide. 

If you need to use more than one script window (because of the character limit you can only have about 4-5 questions per slide or the Javascript won't run) you need to change the initial setting of the score variable slightly: 

var score = objCP.cpEIGetValue('cpQuizInfoPointsscored'); 

3. Once you've processed all your questions, add this code to the bottom of your last processing slide: 

percent = (score * 100) / objCP.cpEIGetValue('v_MaxScore');
objCP.cpEISetValue('v_Percentage', percent); 

Note that v_MaxScore is a user variable, as is v_Percentage. In fact, anything beginning with v_ or s_ in my code is a user variable. 

4. Create one more slide to show score/results. Add captions for whatever variables you'd like the user to see when they finish. In the On Exit action, choose "Execute Javascript" again and enter the following: 

var CaptivateObj = document.Captivate;
var score = CaptivateObj.cpEIGetValue('cpQuizInfoPointsscored');
var timeMS = CaptivateObj.cpEIGetValue('cpInfoElapsedTimeMS');
var time = timeMS / 1000;
var seconds = (time % 60).toFixed(0);
var minutes = ((time % 3600) / 60).toFixed(0);
var hours = (time / 3600).toFixed(0);
g_objAPI.LMSSetValue('cmi.core.session_time',((hours.length < 2) ? '0' : '') + hours + ':' + ((minutes.length < 2) ? '0' : '') + minutes + ':' + ((seconds.length < 2) ? '0' : '') + seconds);
var intLMSScoreRaw = score;
g_objAPI.LMSSetValue('cmi.core.score.max', 100);
g_objAPI.LMSSetValue('cmi.core.score.raw', intLMSScoreRaw);
if (score >= 85) {
g_objAPI.LMSSetValue('cmi.core.lesson_status', 'passed');
}
else {
g_objAPI.LMSSetValue('cmi.core.lesson_status', 'failed');
}
g_objAPI.LMSCommit('');
g_objAPI.LMSFinish('');
CaptivateObj.cpEISetValue('rdcmndExit',1);

This does a number of things:

  • Calculate the session time based on Captivate's elapsed time in milliseconds and send it to the LMS in proper SCORM format
  • Send the max and raw scores to the LMS. The reason we need to send max score like this is that for all intents and purposes the Captivate project has no scoreable objects and as far as Captivate is concerned the max score is 0.
  • Send the lesson status depending on pass criteria (in this case a score of 85% or higher. I used score because in this case the max score is 100 so the actual score will also be the percentage, but if you have more or less available points it would be better to reuse the percentage variable).
  • Commit the scores and finish processing
  • Force the project to close.

 This essentially sends our own variables to the LMS and then cuts it out before Captivate can send its own to overwrite ours. This has only been tested on my Moodle site but I don't see why it wouldn't work with other systems. It correctly reports score, pass status, and the time taken. 

If anyone has any questions about any of the above or would like to know more about any topic discussed here, just let me know. I'll be more than happy to share whatever knowledge I possess. And if you can see a place where I haven't been as efficient with my code as I could have been, let me know! I'm always looking to improve.

IF YOU LIKE WHAT YOU'VE READ ABOVE, THERE'S PLENTY MORE!

Join more than 2500 other Adobe Captivate users just like yourself and receive regular troubleshooting tips, illustrated tutorials, technical information, and creative solutions to real-world e-learning development issues. (See an example here.) Click the button below to join our community.  It's completely FREE!