Step 52 – cmi.comments and cmi.comments_from_lms

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.

SCORM 1.2 RTE Specification – Page 3-35

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.

SCORM 1.2 RTE Specification – Page 3-36

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.

This entry was posted in Run Time Environment. Bookmark the permalink.

3 Responses to Step 52 – cmi.comments and cmi.comments_from_lms

  1. Pingback: Step 53 – Delaying the Closing Sequence | VSSCORM

  2. Pingback: Step 54 – Error Handling? | VSSCORM

  3. Ryan says:

    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 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *