A reader recently contacted me with a problem that raised some interesting issues. Hopefully, we’ve now resolved the problem but, since the issues might well occur with other SCOs, what I’d like to do is to walk through the process and show how the VSSCORM RTE has to be modified to deal with this situation.
Here’s what was happening. The SCO in question was a technical training course – the subject is unimportant – and it ran OK except for one problem. The SCO author(s) had very helpfully provided a small text area on the SCO screens where a student could write notes and have them saved between sessions – in effect, online ‘PostIt’ notes. The problem was that the notes weren’t being saved to the database when the SCO was closed.
After digging around in the code, it became obvious that the SCO was storing all of the notes on the client side while the SCO was running – probably in a cookie, or maybe in a Javascript variable – until the SCO window was closed at which point it attempted to save them to the LMS database. Here’s the Javascript from the SCO (not the RTE) that seems to be failing.
function getCommentsData() { return scormGetValue("cmi.comments"); } function setCommentsData(sSuspend) { scormSetValue("cmi.comments", sSuspend+""); }
The first thing that to note is that the SCO is using the (optional) cmi.comments data element rather than the (mandatory) cmi.suspend_data element. And, to be fair, that’s what the cmi.comments data element is provided for.
So, as the first step in trying to fix the problem, I’m going to add the optional element cmi.comments and, while I’m at it, the related data element cmi.comments_from_lms.
I’m going to start with the LMSSetValue() function in the api.php file where I have to declare the cmi.comments data element as writeable (line 14):
function LMSSetValue(varname,varvalue) { // not initialized or already finished if ((! flagInitialized) || (flagFinished)) { return "false"; } // is this a writeable data element? if ( (varname=='cmi.core.lesson_location') || (varname=='cmi.core.lesson_status') || (varname=='cmi.core.exit') || (varname=='cmi.core.session_time') || (varname=='cmi.core.score.raw') || (varname=='cmi.core.suspend_data') || (varname=='cmi.comments') ) { // set the requested data, and return success value cache[varname] = varvalue; return "true"; } // not a writeable data element else { // return failure value return "false"; } }
Note that I don’t have to do anything with the cmi.comments_from_lms data element since it’s read-only. Now, in the LMSCommit() function, I make a corresponding change to add cmi.comments to the data being transferred to the LMS (line 24):
function LMSCommit(dummyString) { // not initialized or already finished if ((! flagInitialized) || (flagFinished)) { return "false"; } // create request object var req = createRequest(); // code to prevent caching var d = new Date(); // set up request parameters - uses POST method req.open('POST','commit.php',false); // create a POST-formatted list of cached data elements // include only SCO-writeable data elements var params = 'SCOInstanceID=&code='+d.getTime(); params += "&data[cmi.core.lesson_location]="+urlencode(cache['cmi.core.lesson_location']); params += "&data[cmi.core.lesson_status]="+urlencode(cache['cmi.core.lesson_status']); params += "&data[cmi.core.exit]="+urlencode(cache['cmi.core.exit']); params += "&data[cmi.core.session_time]="+urlencode(cache['cmi.core.session_time']); params += "&data[cmi.core.score.raw]="+urlencode(cache['cmi.core.score.raw']); params += "&data[cmi.suspend_data]="+urlencode(cache['cmi.suspend_data']); params += "&data[cmi.comments]="+urlencode(cache['cmi.comments']); // request headers req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); req.setRequestHeader("Content-length", params.length); req.setRequestHeader("Connection", "close"); // submit to the server for processing req.send(params); // process returned data - error condition if (req.status != 200) { alert('Problem with AJAX Request in LMSCommit()'); return "false"; } // process returned data - OK else { return "true"; } }
Now to look at subs.php where I need to add both of these elements to the list so that they’re initialized when the SCO first starts. Pretty simple – I add the following lines to the initializeSCO() function (omitting most of the other lines for clarity):
function initializeSCO() { ... ... // comments initializeElement('cmi.comments',''); initializeElement('cmi.comments_from_lms',getFromLMS('cmi.comments_from_lms')); ... ... }
cmi.comments starts out as an empty string, but the cmi.comments_from_lms data element needs to be initialized with a value from the LMS. So my final task is to change the getFromLMS() function:
function getFromLMS($varname) { switch ($varname) { ... ... case 'cmi.comments_from_lms': $varvalue = ""; break; ... ... } return $varvalue; }
Now, I’ve added the new data elements and I run the SCO again. No good – the problem is still there. There must be another problem that I haven’t spotted.
Pingback: Step 53 – Delaying the Closing Sequence | VSSCORM
Pingback: Step 54 – Error Handling? | VSSCORM
You may have already addressed this, but you have a bug in your code if implemented above. Line 13 of LMSSetValue should read cmi.suspend_data and not cmi.core.suspend_data.
Thanks again! Keep up the good work 🙂