Step 48 – LMSFinish(), and Unexpected Closure

In theory, the way that I handle the LMSFinish() API call should be pretty much the same as for Rev 1 of my code. I simply generate an AJAX request to the server which runs a PHP script that carries out a series of actions on the data elements stored in the LMS database (persistent storage). But the use of the cache introduces some wrinkles.

Wrinkle #1: LMSCommit() Not Called Before LMSFinish()

In Rev 1 of the code, all API calls were carried out immediately they were invoked which meant that the LMS database was always up-to-date with the information being set by the course. Therefore, when the course called LMSFinish(), I knew that I was operating on up-to-date values for each of the data elements.

In Rev 2 of the code, values of the data elements are cached in the students’ browsers until the course developer chooses to invoke the LMSCommit() API call. A smart course developer will do this relatively often – certainly after any major change in the values of the data elements. However, I can’t rely on all courses being developed by smart developers. So I need to add a call to LMSCommit() to the top of my LMSFinish() API code to ensure that it’s going to be operating on the final set of data values:

function LMSFinish(dummyString) {

  // commit cached values to the database
  LMSCommit('');

  ...

That’s straightforward enough.

Wrinkle #2: Course Window May Be Closed Unexpectedly

While it would be nice to think that students will always use the navigation buttons that the developer provides in his/her course, you and I know that this isn’t going to happen. Students will sometimes choose to close the course window by clicking on the ‘x’ at the top of the window.

OK – technically, this wrinkle existed in Rev 1 of the code as well, but I just didn’t worry about it as much since I knew that the database would always have an up-to-date copy of the data element values, and thus the course would be able to re-start fairly easily. To handle this with my caching version of the API, I have to make sure that the course invokes LMSFinish() when the browser window is closed for any reason.

In theory, the course developer should try to handle this. As Claude Ostyn says on page 9 of his SCORM Cookbook (with my emphasis):

To ensure that LMSFinish will be called even if the SCO is unloaded expectedly, a properly initialized SCO should be coded to call LMSFinish at the following times, and under the following circumstances:

  • When it no longer needs to communicate with the runtime service. For example, when the user clicks a “submit” button at the end of a test and the user will not be allowed to change responses, a SCO may upload the results of the test with LMSSetValue, then call LMSFinish.
     
  • If LMSFinish has not been called successfully yet, when an onbeforeunload browser event is processed (typically, this will only happen if the browser is Internet Explorer).
     
  • If LMSFinish has not been called successfully yet, when an onunload browser event is processed.
     

However, I can’t rely on course developers always being responsible. So I can help myself out by modifying my rte.php code like this:

<html>
<head>
  <title>VS SCORM</title>
</head>
  <frameset 
    frameborder="0" 
    framespacing="0" 
    border="0" 
    rows="50,*" 
    cols="*" 
    onbeforeunload="API.LMSFinish('');" 
    onunload="API.LMSFinish('');"
  >
  <frame 
    src="api.php?SCOInstanceID=<?php print $SCOInstanceID; ?>" 
	name="API" 
	noresize
  >
  <frame src="../course/index.html" name="course">
</frameset>
</html>

In the frameset tag, you’ll see that I’ve added an onunload() action, and an onbeforeunload() action (lines 11 and 12). In each case, the action calls LMSFinish() to try to make sure that everything is nicely wrapped-up should the student close the course/SCO window in some way other than using the course/SCO navigation buttons.

Why both onbeforeunload() and onunload()? It’s difficult to find a definitive statement on the Internet but, according to what I’ve read, it seems that onbeforeunload() is a Microsoft extension that’s supported in Internet Explorer, and in recent versions of other browsers (Safari, FireFox). However, Opera doesn’t support it (please let me know if I’m wrong), and I’m not sure about Chrome. So, by using both onbeforeunload() and onunload(), I have a pretty good chance of one or both of them being triggered by the frameset closing.

Wrinkle #3: Multiple Calls to LMSFinish()

The solution to wrinkle #2 causes another wrinkle. It’s now possible that LMSFinish() could be called more than once. The SCORM 1.2 RTE specification is clear about this – LMSFinish() should be called once, and only once, at the end of the course/SCO session.

To handle this, I’m going to introduce a JavaScript variable into api.php to act as a ‘flag’:

var flagFinished = false;

Initially set to ‘false’, I’m going to make sure that, when called, LMSFinish() …

  1. Tests to see if flagFinished is set to ‘true’ and, if so, aborts processing.
  2. If flagFinished is set to ‘false’, processes the data element values as usual.
  3. Sets flagFinished to ‘true’ before exiting.

In the next section, I’ll show you how I do that.

So, What Does The Code Look Like?

Even including all of the changes I’ve outlined above, the basic form of the LMSFinish() API function in api.php is little changed from my previous version. It looks like this:

function LMSFinish(dummyString) {

  // already finished - prevent repeat call
  if (flagFinished) {
    return "true";
  }

  // commit cached values to the database
  LMSCommit('');

  // create request object
  var req = createRequest();

  // code to prevent caching
  var d = new Date();

  // set up request parameters - uses GET method
  req.open('GET','finish.php?SCOInstanceID=&code='+d.getTime(),false);

  // submit to the server for processing
  req.send(null);

  // process returned data - error condition
  if (req.status != 200) {
    alert('Problem with AJAX Request in LMSFinish()');
    return "";
  }

  // set finish flag
  flagFinished = true;

  // return to calling program
  return "true";

}

The differences from the Rev 1 code are as follows:

  • Lines 4 through 6 – I’m going to check the JavaScript variable ‘flagFinished’ in api.php to see if LMSFinish() has previously been called. If so, it aborts processing.
     
  • Line 9 – Calls LMSCommit() to make sure that all data element values have been transferred to the LMS database before I invoke the finish.php code, and the course closes.
     
  • Line 30 – I set the flag to show that I’ve successfully finished the LMSFinish() process, and that communication between the SCO and the LMS is now closed.
     

That was a fairly complex topic with a relatively simple resolution. Next time, I’ll look at the other end of the course/SCO session – initialization.

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

1 Response to Step 48 – LMSFinish(), and Unexpected Closure

  1. rahul says:

    Hi Team,
    Could you please tell me, in which conditions “LMS Finish Has already been called” error comes while playing a course.

Leave a Reply

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