Skip to content

Commit

Permalink
Define Exit Status in Advanced Settings (#184)
Browse files Browse the repository at this point in the history
* Allow Exit Mode to be Defined in Advanced Settings

- Add new fields to advanced settings to define the exit mode when a course is either complete or incomplete
- Override behaviour of Pipwerks SCORM API wrapper so that Spoor sets exit mode in all instances
- Support for SCORM 1.2 and 2004

adaptlearning/adapt_framework#2458
  • Loading branch information
canstudios-matth authored and moloko committed Jul 26, 2019
1 parent 3514a1d commit 1d2c107
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 16 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ The attributes listed below are used in *config.json* to configure **Spoor**, an
>>**_commitOnVisibilityChangeHidden** (boolean): Determines whether or not a "commit" call should be made when the [visibilityState](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState) of the course page changes to `"hidden"`. This functionality helps to ensure that tracking data is saved whenever the user switches to another tab or minimises the browser window - and is only available in [browsers that support the Page Visibility API](http://caniuse.com/#search=page%20visibility). The default is `true`.
>>**_exitStateIfIncomplete** (string): Determines the 'exit state' (`cmi.core.exit` in SCORM 1.2, `cmi.exit` in SCORM 2004) to set if the course hasn't been completed. The default behaviour will cause the exit state to be set to an empty string for SCORM 1.2 courses, or `"suspend"` for SCORM 2004 courses. The default behaviour should be left in place unless you are confident you know what you are doing!
>>**_exitStateIfComplete** (string): Determines the 'exit state' (`cmi.core.exit` in SCORM 1.2, `cmi.exit` in SCORM 2004) to set when the course has been completed. The default behaviour will cause the exit state to be set to an empty string for SCORM 1.2 courses, or `"normal"` for SCORM 2004 courses. The default behaviour should be left in place unless you are confident you know what you are doing! Note: if you are using SCORM 2004, you can set this to `"suspend"` to prevent the LMS from clearing all progress tracking when a previously-completed course is re-launched by the learner.
<div float align=right><a href="#top">Back to Top</a></div>

### Running a course without tracking while Spoor is installed
Expand Down
4 changes: 3 additions & 1 deletion example.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"_timedCommitFrequency": 10,
"_maxCommitRetries": 5,
"_commitRetryDelay": 2000,
"_commitOnVisibilityChangeHidden": true
"_commitOnVisibilityChangeHidden": true,
"_exitStateIfIncomplete": "auto",
"_exitStateIfComplete": "auto"
}
}
}
18 changes: 13 additions & 5 deletions js/adapt-contrib-spoor.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,33 @@ define([

scorm.setVersion(settings._scormVersion || "1.2");

if(settings.hasOwnProperty("_suppressErrors")) {
if(settings._suppressErrors) {
scorm.suppressErrors = settings._suppressErrors;
}

if(settings.hasOwnProperty("_commitOnStatusChange")) {
if(settings._commitOnStatusChange) {
scorm.commitOnStatusChange = settings._commitOnStatusChange;
}

if(settings.hasOwnProperty("_timedCommitFrequency")) {
if(_.isFinite(settings._timedCommitFrequency)) {
scorm.timedCommitFrequency = settings._timedCommitFrequency;
}

if(settings.hasOwnProperty("_maxCommitRetries")) {
if(_.isFinite(settings._maxCommitRetries)) {
scorm.maxCommitRetries = settings._maxCommitRetries;
}

if(settings.hasOwnProperty("_commitRetryDelay")) {
if(_.isFinite(settings._commitRetryDelay)) {
scorm.commitRetryDelay = settings._commitRetryDelay;
}

if ("_exitStateIfIncomplete" in settings) {
scorm.exitStateIfIncomplete = settings._exitStateIfIncomplete;
}

if ("_exitStateIfComplete" in settings) {
scorm.exitStateIfComplete = settings._exitStateIfComplete;
}
} else {
/**
* force use of SCORM 1.2 by default - some LMSes (SABA/Kallidus for instance) present both APIs to the SCO and, if given the choice,
Expand Down
38 changes: 28 additions & 10 deletions js/scorm/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ define ([
* not currently used - but you could include in an error message to show when data was last saved
*/
this.lastCommitSuccessTime = null;
/**
* The exit state to use when course isn't completed yet
*/
this.exitStateIfIncomplete = "auto";
/**
* The exit state to use when the course has been completed/passed
*/
this.exitStateIfComplete = "auto";


this.timedCommitIntervalID = null;
this.retryCommitTimeoutID = null;
Expand All @@ -50,6 +59,10 @@ define ([

this.logger = Logger.getInstance();
this.scorm = pipwerks.SCORM;
/**
* Prevent the Pipwerks SCORM API wrapper's handling of the exit status
*/
this.scorm.handleExitMode = false;

this.suppressErrors = false;

Expand Down Expand Up @@ -80,13 +93,6 @@ define ([
ScormWrapper.prototype.setVersion = function(value) {
this.logger.debug("ScormWrapper::setVersion: " + value);
this.scorm.version = value;
/**
* stop the pipwerks code from setting cmi.core.exit to suspend/logout when targeting SCORM 1.2.
* there doesn't seem to be any tangible benefit to doing this in 1.2 and it can actually cause problems with some LMSes
* (e.g. setting it to 'logout' apparently causes Plateau to log the user completely out of the LMS!)
* It needs to be on for SCORM 2004 though, otherwise the LMS might not restore the suspend_data
*/
this.scorm.handleExitMode = this.isSCORM2004();
};

ScormWrapper.prototype.initialize = function() {
Expand Down Expand Up @@ -303,10 +309,10 @@ define ([

if (this.isSCORM2004()) {
this.scorm.set("cmi.session_time", this.convertToSCORM2004Time(this.endTime.getTime() - this.startTime.getTime()));
}
else {
this.scorm.set("cmi.exit", this.getExitState());
} else {
this.scorm.set("cmi.core.session_time", this.convertToSCORM12Time(this.endTime.getTime() - this.startTime.getTime()));
this.scorm.set("cmi.core.exit", "");
this.scorm.set("cmi.core.exit", this.getExitState());
}

// api no longer available from this point
Expand Down Expand Up @@ -716,5 +722,17 @@ define ([
return response.join(',');
};

ScormWrapper.prototype.getExitState = function() {
var completionStatus = this.scorm.data.completionStatus;
var isIncomplete = completionStatus === 'incomplete' || completionStatus === 'not attempted';
var exitState = isIncomplete ? this.exitStateIfIncomplete : this.exitStateIfComplete;

if (exitState !== 'auto') return exitState;

if (this.isSCORM2004()) return (isIncomplete ? 'suspend' : 'normal');

return '';
};

return ScormWrapper;
});
42 changes: 42 additions & 0 deletions properties.schema
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,48 @@
"inputType": "Text",
"validators": ["required"],
"help": "Sets the 'identifier' attribute in the imsmanifest.xml"
},
"_exitStateIfIncomplete": {
"type": "string",
"required": false,
"default": "auto",
"title": "Exit state if incomplete",
"enum": ["auto", "suspend", "normal", ""],
"inputType": {
"type": "Select",
"options": [
"auto",
"suspend",
"normal",
{
"val": "",
"label": "'' (empty string)"
}
]
},
"validators": [],
"help": "What exit status to use if the course is incomplete."
},
"_exitStateIfComplete": {
"type": "string",
"required": false,
"default": "auto",
"title": "Exit state if complete",
"enum": ["auto", "suspend", "normal", ""],
"inputType": {
"type": "Select",
"options": [
"auto",
"suspend",
"normal",
{
"val": "",
"label": "'' (empty string)"
}
]
},
"validators": [],
"help": "What exit status to use if the course is complete."
}
}
}
Expand Down

0 comments on commit 1d2c107

Please sign in to comment.