Copy link to clipboard
Copied
I was wondering if someone out there with JavaScript knowledge could help me out a little...
A coworker came to me with a question about randomizing slides. We're working in Captivate 5.5. The slides are all part of a quiz (but are not question slides). They have a click box. The idea is that if the user clicks the box (or outside the box), it shows a correct (or incorrect) caption and then jumps to a random slide (a new scenario). If the user gets to the end of the slide without choosing anything, their time runs out and they get jumped to a random slide and it counts as an incorrect answer. The user must see all the slides in a random order with no repeats.
I've created an Advanced Action, incorporating the random number widget, and it works. My problem is that the project has 50 slides, and I'm not sure that's very doable with Advanced Actions. As it is, my test with 3 random slides has an action with 4 conditional statements, so I'm not really looking forward to creating one for 50 slides. (I don't even know if Captivate would be able to handle that.)
Is there an (easy-ish) way to accomplish this with JavaScript instead? My vague idea based on my very limited programming knowledge is:
- on entering first captivate slide, create array of the slide numbers of the random slides - 1 (because cpCmndGotoSlide index starts at 0)
so if I had a project where I wanted to randomize slides 3-8, the array would be [2, 3, 4, 5, 6, 7] - shuffle/randomize the array
- set up a variable called slideviews = 0 to keep track of how many slides have been viewed (and therefore which element in the random array to jump to next)
- create a function that I will set to execute when I want to jump to a random slide (either at the end of a slide or upon success/failure of clicking a box)
something like... if slideviews < array.length, cpCmndGotoSlide = array[slideviews]; slideviews++; else, cpCmndGotoSlide = 8 (jump to end slide)
Obviously this is missing stuff (like proper syntax and the necessary things to communicate with captivate)... but is the logic sound? Is there a better way to do this?
Now that I think about it, the array and the variable slideviews would have to be saved if we wanted it to work with bookmarking... I think this project will be SCORM 1.2 in an LMS.
It might also be nice to have a user variable that I can access within the Captivate project and I can put in a caption to show the scenario #. Something like scenario = slideviews (I know this isn't the syntax, from reading Jim Leichliter's JavaScript series on CaptivateDev.com, but it's the basic idea.)
Anyway, any pointers would be very helpful! Thanks.
Edit: I played around a little; for randomization I found some code online, and then I wrote something that would create an array automatically and a function for jumping to a random slide. Here's what I've got so far (not tested in Captivate yet):
var objCP = document.Captivate;
var randomNumbers = [];
// The following creates the values for the array: 3 to 52.
// I want to randomize slides 4-53, using cpCmndGotoSlide (index starting at 0) to jump to them
for(i=3; i<53; i++) {
randomNumbers[i-3] = i;
}
var n = randomNumbers.length;
var tempArr = [];
for (i = 0; i < n-1; i++ ) {
// The following line removes one random element from arr
// and pushes it onto tempArr
tempArr.push(randomNumbers.splice(Math.floor(Math.random()*randomNumbers.length),1)[0]);
}
// Push the remaining item onto tempArr
tempArr.push(randomNumbers[0]);
randomNumbers=tempArr;
var slideViews = 0;
var randomSlide = function() {
if (slideViews < 50) {
objCP.cpEISetValue("cpCmndGotoSlide", randomNumbers[slideViews]);
slideViews++;
objCP.cpEISetValue("scenarioNumber", slideViews);
}
//after all the slides have been seen, jump to the final results slide
else {
objCP.cpEISetValue("cpCmndGotoSlide", 53);
}
};
I doubt this'll work off the bat, but maybe it's a start?
1 Correct answer
I've managed to get things working, so I thought I'd post my (probably a bit ineffecient and unwieldy) code in case people come across this thread and want a starting point:
...
var objCP = document.Captivate;
var randomNumbers = [];
var slideViews = 0;
var resume = 0;var shuffle = function(array) {
var m = array.length, t, j;
while (m) {
j = Math.floor(Math.random() * m--);
t = array;
array= array ;
array= t;
}
};var jumpSlide = function() {
if (slideViews < randomNumbers.length) {
Copy link to clipboard
Copied
Well, I feel a bit silly talking to myself, but I might as well post my progress.
I forgot that comments break JavaScript in Captivate, but once I sorted that out I got things working with this code:
var objCP = document.Captivate;
var randomNumbers = [];
for(i=3; i<53; i++) {
randomNumbers[i-3] = i;
}
objCP.cpEISetValue("array1", randomNumbers);
var n = randomNumbers.length;
var tempArr = [];
for (i = 0; i < n-1; i++ ) {
tempArr.push(randomNumbers.splice(Math.floor(Math.random()*randomNumbers.length),1)[0]);
}tempArr.push(randomNumbers[0]);
randomNumbers=tempArr;
objCP.cpEISetValue("array1randomized", randomNumbers);
var slideViews = 0;
var randomSlide = function() {
if (slideViews < 50) {
objCP.cpEISetValue("cpCmndGotoSlide", randomNumbers[slideViews]);
slideViews++;
objCP.cpEISetValue("scenarioNumber", slideViews);
}
else {
objCP.cpEISetValue("cpCmndGotoSlide", 53);
}
};
Whenever I want to go to the next random slide I execute randomSlide(); and it works. I can watch it cycle through slides 4-53 randomly before jumping to 54, the final slide. (The lines where I set array1 and array1randomized are just so I can put the arrays in debugging captions and follow along. The order of the slides visited matches the order of the randomized array.)
Questions
I've tried researching this, but while I'm rambling here I might as well ask again: is there a way to store the randomized array and slideViews variable in SCORM data and then get those back if the user resumes in the middle?
Is there anything to be gained from putting this code outside the swf? At the moment it executes upon entering the first slide. If I did get resuming in the middle of the course to work, would this be a problem? (IE, if it resumes on slide 40, will Captivate load the first slide so that the function can be called on slide 40?)
Copy link to clipboard
Copied
I've tried researching this, but while I'm rambling here I might as well ask again: is there a way to store the randomized array and slideViews variable in SCORM data and then get those back if the user resumes in the middle?
Adobe Captivate User Variables can round-trip with the SCORM suspend data. Try storing your data in a Cp User Variable... and I would suggest using JSON format so you can easily convert the string into a JavaScript object.
Is there anything to be gained from putting this code outside the swf?
Yes. If you need to make a change, you won't have to recompile and republish your Cp project... you can just update a .js file.
Copy link to clipboard
Copied
Adobe Captivate User Variables can round-trip with the SCORM suspend data. Try storing your data in a Cp User Variable... and I would suggest using JSON format so you can easily convert the string into a JavaScript object.
Thanks! I thought that was the case; I just tested it in SCORM Cloud and I can see that my Captivate variables scenarioNumber, array1, and array1randomized were saved and displayed in my debug captions when I resumed the course. It does stop following the order of array1randomized upon resuming, but that makes sense and is what I expected.
Three more questions (if you don't mind!):
- Are there any tutorials/explanations of the JSON format you could suggest? I'm really, really new to all this (just started teaching myself JavaScript this weekend), and I'd appreciate anything that might help!
- Similarly, are there any tutorials/explanations for using variables that have been stored in SCORM suspend data? I'm assuming you do something like setting the JavaScript variable equal to the stored Captivate variable... but I don't know if there's special syntax for that. I also feel like I need some sort of decision as well-- to create the variable/array if there is no suspend data, or to pull the suspend data if it exists:
"If this Captivate variable array1randomized does not exist, then create the array randomNumbers and randomize it; else set the array randomNumbers equal to the stored Captivate variable array1randomized" and "If Captivate variable scenarioNumber exists, set the JavaScript variable slideViews equal to the stored Captivate variable scenarioNumber; else set the JavaScript variable slideViews equal to 0." - If I put the code outside the swf in a js file, will I be referring to captivate variables that haven't been created yet?
Thanks, and sorry if my questions are confusing or silly!
Copy link to clipboard
Copied
Are there any tutorials/explanations of the JSON format you could suggest? I'm really, really new to all this (just started teaching myself JavaScript this weekend), and I'd appreciate anything that might help!
try http://json.org for the spec. You can also look at http://www.w3schools.com/json/default.asp
Basically, it's a way to serialize and store javascript objects as strings. You can later retrieve those strings and turn them back into JavaScript objects with one line of code. It makes storing the data incredibly easy.
Similarly, are there any tutorials/explanations for using variables that have been stored in SCORM suspend data?
Here's an example of getting/setting Cp variables in JavaScript:
Yes, you will need a bit of logic to figure out if you can pull the suspend data, or if you need to create the random number array from scratch.
If I put the code outside the swf in a js file, will I be referring to captivate variables that haven't been created yet?
Timing is key. If you try to access Captivate before it has had a chance to load in the browser, then you get a null reference error. The best practice is to keep MOST of your code inside of an external .js file (or the standard.js file in the Cp installation directory), and then access the functions in that .js file inside of Captivate. So define your functions in the .js file, then call those functions from Captivate. Personally, I try to keep most of my JS outside of Cp as much as possible. This allows me to use comment blocks so I can document my code.
Copy link to clipboard
Copied
Hello again!
Thanks for the links; I've read them through, and I think I have a better idea of what I need to do. I decided to first get the JavaScript working in a separate file and then work on pulling SCORM data; I figured if I tried to do both at the same time I wouldn't know which one was inevitably going wrong.
I got a little alert function in a .js file to work, using the steps you outlined here: http://captivatedev.com/2011/06/03/captivate-javascript-series-javascript-injection-best-practices/ The problem comes when I try the same thing with the code that I got to work, moving it from the first slide to a .js file included in the .html. I get a null error ('objCP' is null or not an object). I've put the include code as the very last item within the <head> tags, after the standard.js file and such... any idea what I'm doing wrong?
Thanks
Copy link to clipboard
Copied
Well, I think I'm stumped. I reread things and realized I didn't have the "random array" part of my code in a function, so it was executing too soon when it was in a separate file. I put it in a function, and called that on my first slide, but while the swf now loads the randomization no longer works.
I also can't get the resume part to work. I've tried figuring out what's going on in the code in the tutorial (http://captivatedev.com/2012/12/17/display-the-students-name-from-your-lms-using-adobe-captivate-6-x...), but I still haven't been able to write the decision that's necessary. I can see the swf storing Captivate user variables in SCORM suspend data, and I can get those variables upon resuming using .cpEIGetValue, but as soon as I try to introduce a simple decision (if there is suspend data, get it, otherwise create it from scratch), it breaks. I guess I don't know how to state it properly.
Here's one variation I've tried, if anyone has the time to help point out my (no doubt numerous) errors:
var objCP = document.Captivate;
var randomNumbers = [];
var slideViews = "";
var suspendData = objCP.cpEIGetValue("CPscenarioNumber");
// if the suspend data does not exist:
if (suspendData === "") {
// create the array:
for(i = 4; i < 54; i++) {
randomNumbers[i-4] = i;
}
// randomize the array:
var n = randomNumbers.length;
var tempArr = [];
for (i = 0; i < n-1; i++ ) {
tempArr.push(randomNumbers.splice(Math.floor(Math.random()*randomNumbers.length),1)[0]);
}
tempArr.push(randomNumbers[0]);
randomNumbers=tempArr;
// put random array into JSON format:
var JSONrandomNumbers = JSON.stringify(randomNumbers);
// store JSON formatted array as Captivate variable for SCORM suspend data:
objCP.cpEISetValue("CPrandomNumbers", JSONrandomNumbers);
// set slideViews equal to 0 (no random slides have been viewed yet):
slideViews = 0;
}
// if the program is resuming and suspend data exists:
else {
// set randomNumbers and slideViews to stored values:
var JSONrandomNumbers = objCP.cpEIGetValue("CPrandomNumbers");
randomNumbers = JSON.parse(JSONrandomNumbers);
slideViews = objCP.cpEIGetValue("CPscenarioNumber");
};
// function for jumping to a random slide:
var randomSlide = function() {
// if one or more random slide has not been visited yet:
if (slideViews < randomNumbers.length) {
// jump to slide:
objCP.cpEISetValue("cpCmndGotoSlide", randomNumbers[slideViews]-1);
// increment slideViews so next time it jumps to the next element in the array:
slideViews++;
// assign Captivate variable for caption and for saving variable:
objCP.cpEISetValue("CPscenarioNumber", slideViews);
}
// if all random slides have been visited:
else {
// jump to final slide:
objCP.cpEISetValue("cpCmndGotoSlide", 53);
}
};
Thanks!
Copy link to clipboard
Copied
I've managed to get things working, so I thought I'd post my (probably a bit ineffecient and unwieldy) code in case people come across this thread and want a starting point:
var objCP = document.Captivate;
var randomNumbers = [];
var slideViews = 0;
var resume = 0;var shuffle = function(array) {
var m = array.length, t, j;
while (m) {
j = Math.floor(Math.random() * m--);
t = array;
array= array ;
array= t;
}
};var jumpSlide = function() {
if (slideViews < randomNumbers.length) {
objCP.cpEISetValue('cpCmndGotoSlide', randomNumbers[slideViews]-1);
slideViews++;
objCP.cpEISetValue('scenarioNumber', slideViews);
}
else {
objCP.cpEISetValue('cpCmndGotoSlide', 52);
}
};var randomSlide = function() {
if (g_objAPI.LMSGetValue('cmi.comments') === '') {
for(i = 3; i < 53; i++) {
randomNumbers[i-3] = i;
}
shuffle(randomNumbers);
g_objAPI.LMSSetValue('cmi.comments', randomNumbers);
resume++;
jumpSlide();
}
else if (resume === 0){
randomNumbers = g_objAPI.LMSGetValue('cmi.comments').split(',');
slideViews = objCP.cpEIGetValue('scenarioNumber');
resume++;
jumpSlide();
}
else {
jumpSlide();
}
};
The random slide order gets stored in cmi.comments, while the position in the sequence is stored in a Captivate variable called scenarioNumber. I could have stored both in Captivate variables in cmi.suspend_data, but in the end I liked being able to see the numbers when looking at the SCORM data. To get around problems with initialization, I made the creation/restoration of the random numbers part of the function that jumps to a random slide, so nothing is actually executed upon entering the first slide.
Copy link to clipboard
Copied
Nicely done ElaKat, and thanks for sharing your solution with the rest of the community!
Jim Leichliter
Copy link to clipboard
Copied
Hi
I have posted a question based on this thread at http://forums.adobe.com/thread/1364346
Would really love some help if you can spare some time.
Thanks
Luke

