From cedc761e2f66d1730dfd431e1fb78090c454551f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 30 May 2014 10:37:56 +0100 Subject: [PATCH] reverted scormwarpper to previous version --- js/SCORM_API_wrapper.js | 2 +- js/scormWrapper.js | 1939 ++++++++++++++++++++++----------------- 2 files changed, 1108 insertions(+), 833 deletions(-) diff --git a/js/SCORM_API_wrapper.js b/js/SCORM_API_wrapper.js index b3f83f5b..80a8b627 100644 --- a/js/SCORM_API_wrapper.js +++ b/js/SCORM_API_wrapper.js @@ -829,4 +829,4 @@ pipwerks.UTILS.trace = function(msg){ } } -}; +}; \ No newline at end of file diff --git a/js/scormWrapper.js b/js/scormWrapper.js index 80a8b627..ddbdcaea 100644 --- a/js/scormWrapper.js +++ b/js/scormWrapper.js @@ -1,832 +1,1107 @@ -/* =========================================================== - -pipwerks SCORM Wrapper for JavaScript -v1.1.20140217 - -Created by Philip Hutchison, January 2008-2014 -https://github.com/pipwerks/scorm-api-wrapper - -Copyright (c) Philip Hutchison -MIT-style license: http://pipwerks.mit-license.org/ - -This wrapper works with both SCORM 1.2 and SCORM 2004. - -Inspired by APIWrapper.js, created by the ADL and -Concurrent Technologies Corporation, distributed by -the ADL (http://www.adlnet.gov/scorm). - -SCORM.API.find() and SCORM.API.get() functions based -on ADL code, modified by Mike Rustici -(http://www.scorm.com/resources/apifinder/SCORMAPIFinder.htm), -further modified by Philip Hutchison - -=============================================================== */ - - -var pipwerks = {}; //pipwerks 'namespace' helps ensure no conflicts with possible other "SCORM" variables -pipwerks.UTILS = {}; //For holding UTILS functions -pipwerks.debug = { isActive: true }; //Enable (true) or disable (false) for debug mode - -pipwerks.SCORM = { //Define the SCORM object - version: null, //Store SCORM version. - handleCompletionStatus: true, //Whether or not the wrapper should automatically handle the initial completion status - handleExitMode: true, //Whether or not the wrapper should automatically handle the exit mode - API: { handle: null, - isFound: false }, //Create API child object - connection: { isActive: false }, //Create connection child object - data: { completionStatus: null, - exitStatus: null }, //Create data child object - debug: {} //Create debug child object -}; - - - -/* -------------------------------------------------------------------------------- - pipwerks.SCORM.isAvailable - A simple function to allow Flash ExternalInterface to confirm - presence of JS wrapper before attempting any LMS communication. - - Parameters: none - Returns: Boolean (true) ------------------------------------------------------------------------------------ */ - -pipwerks.SCORM.isAvailable = function(){ - return true; -}; - - - -// ------------------------------------------------------------------------- // -// --- SCORM.API functions ------------------------------------------------- // -// ------------------------------------------------------------------------- // - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.API.find(window) - Looks for an object named API in parent and opener windows - - Parameters: window (the browser window object). - Returns: Object if API is found, null if no API found ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.API.find = function(win){ - - var API = null, - findAttempts = 0, - findAttemptLimit = 500, - traceMsgPrefix = "SCORM.API.find", - trace = pipwerks.UTILS.trace, - scorm = pipwerks.SCORM; - - while ((!win.API && !win.API_1484_11) && - (win.parent) && - (win.parent != win) && - (findAttempts <= findAttemptLimit)){ - - findAttempts++; - win = win.parent; - - } - - //If SCORM version is specified by user, look for specific API - if(scorm.version){ - - switch(scorm.version){ - - case "2004" : - - if(win.API_1484_11){ - - API = win.API_1484_11; - - } else { - - trace(traceMsgPrefix +": SCORM version 2004 was specified by user, but API_1484_11 cannot be found."); - - } - - break; - - case "1.2" : - - if(win.API){ - - API = win.API; - - } else { - - trace(traceMsgPrefix +": SCORM version 1.2 was specified by user, but API cannot be found."); - - } - - break; - - } - - } else { //If SCORM version not specified by user, look for APIs - - if(win.API_1484_11) { //SCORM 2004-specific API. - - scorm.version = "2004"; //Set version - API = win.API_1484_11; - - } else if(win.API){ //SCORM 1.2-specific API - - scorm.version = "1.2"; //Set version - API = win.API; - - } - - } - - if(API){ - - trace(traceMsgPrefix +": API found. Version: " +scorm.version); - trace("API: " +API); - - } else { - - trace(traceMsgPrefix +": Error finding API. \nFind attempts: " +findAttempts +". \nFind attempt limit: " +findAttemptLimit); - - } - - return API; - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.API.get() - Looks for an object named API, first in the current window's frame - hierarchy and then, if necessary, in the current window's opener window - hierarchy (if there is an opener window). - - Parameters: None. - Returns: Object if API found, null if no API found ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.API.get = function(){ - - var API = null, - win = window, - scorm = pipwerks.SCORM, - find = scorm.API.find, - trace = pipwerks.UTILS.trace; - - if(win.parent && win.parent != win){ - API = find(win.parent); - } - - if(!API && win.top.opener){ - API = find(win.top.opener); - } - - //Special handling for Plateau - //Thanks to Joseph Venditti for the patch - if(!API && win.top.opener && win.top.opener.document) { - API = find(win.top.opener.document); - } - - if(API){ - scorm.API.isFound = true; - } else { - trace("API.get failed: Can't find the API!"); - } - - return API; - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.API.getHandle() - Returns the handle to API object if it was previously set - - Parameters: None. - Returns: Object (the pipwerks.SCORM.API.handle variable). ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.API.getHandle = function() { - - var API = pipwerks.SCORM.API; - - if(!API.handle && !API.isFound){ - - API.handle = API.get(); - - } - - return API.handle; - -}; - - - -// ------------------------------------------------------------------------- // -// --- pipwerks.SCORM.connection functions --------------------------------- // -// ------------------------------------------------------------------------- // - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.connection.initialize() - Tells the LMS to initiate the communication session. - - Parameters: None - Returns: Boolean ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.connection.initialize = function(){ - - var success = false, - scorm = pipwerks.SCORM, - completionStatus = scorm.data.completionStatus, - trace = pipwerks.UTILS.trace, - makeBoolean = pipwerks.UTILS.StringToBoolean, - debug = scorm.debug, - traceMsgPrefix = "SCORM.connection.initialize "; - - trace("connection.initialize called."); - - if(!scorm.connection.isActive){ - - var API = scorm.API.getHandle(), - errorCode = 0; - - if(API){ - - switch(scorm.version){ - case "1.2" : success = makeBoolean(API.LMSInitialize("")); break; - case "2004": success = makeBoolean(API.Initialize("")); break; - } - - if(success){ - - //Double-check that connection is active and working before returning 'true' boolean - errorCode = debug.getCode(); - - if(errorCode !== null && errorCode === 0){ - - scorm.connection.isActive = true; - - if(scorm.handleCompletionStatus){ - - //Automatically set new launches to incomplete - completionStatus = scorm.status("get"); - - if(completionStatus){ - - switch(completionStatus){ - - //Both SCORM 1.2 and 2004 - case "not attempted": scorm.status("set", "incomplete"); break; - - //SCORM 2004 only - case "unknown" : scorm.status("set", "incomplete"); break; - - //Additional options, presented here in case you'd like to use them - //case "completed" : break; - //case "incomplete" : break; - //case "passed" : break; //SCORM 1.2 only - //case "failed" : break; //SCORM 1.2 only - //case "browsed" : break; //SCORM 1.2 only - - } - - } - - } - - } else { - - success = false; - trace(traceMsgPrefix +"failed. \nError code: " +errorCode +" \nError info: " +debug.getInfo(errorCode)); - - } - - } else { - - errorCode = debug.getCode(); - - if(errorCode !== null && errorCode !== 0){ - - trace(traceMsgPrefix +"failed. \nError code: " +errorCode +" \nError info: " +debug.getInfo(errorCode)); - - } else { - - trace(traceMsgPrefix +"failed: No response from server."); - - } - } - - } else { - - trace(traceMsgPrefix +"failed: API is null."); - - } - - } else { - - trace(traceMsgPrefix +"aborted: Connection already active."); - - } - - return success; - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.connection.terminate() - Tells the LMS to terminate the communication session - - Parameters: None - Returns: Boolean ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.connection.terminate = function(){ - - var success = false, - scorm = pipwerks.SCORM, - exitStatus = scorm.data.exitStatus, - completionStatus = scorm.data.completionStatus, - trace = pipwerks.UTILS.trace, - makeBoolean = pipwerks.UTILS.StringToBoolean, - debug = scorm.debug, - traceMsgPrefix = "SCORM.connection.terminate "; - - - if(scorm.connection.isActive){ - - var API = scorm.API.getHandle(), - errorCode = 0; - - if(API){ - - if(scorm.handleExitMode && !exitStatus){ - - if(completionStatus !== "completed" && completionStatus !== "passed"){ - - switch(scorm.version){ - case "1.2" : success = scorm.set("cmi.core.exit", "suspend"); break; - case "2004": success = scorm.set("cmi.exit", "suspend"); break; - } - - } else { - - switch(scorm.version){ - case "1.2" : success = scorm.set("cmi.core.exit", "logout"); break; - case "2004": success = scorm.set("cmi.exit", "normal"); break; - } - - } - - } - - //Ensure we persist the data - success = scorm.save(); - - if(success){ - - switch(scorm.version){ - case "1.2" : success = makeBoolean(API.LMSFinish("")); break; - case "2004": success = makeBoolean(API.Terminate("")); break; - } - - if(success){ - - scorm.connection.isActive = false; - - } else { - - errorCode = debug.getCode(); - trace(traceMsgPrefix +"failed. \nError code: " +errorCode +" \nError info: " +debug.getInfo(errorCode)); - - } - - } - - } else { - - trace(traceMsgPrefix +"failed: API is null."); - - } - - } else { - - trace(traceMsgPrefix +"aborted: Connection already terminated."); - - } - - return success; - -}; - - - -// ------------------------------------------------------------------------- // -// --- pipwerks.SCORM.data functions --------------------------------------- // -// ------------------------------------------------------------------------- // - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.data.get(parameter) - Requests information from the LMS. - - Parameter: parameter (string, name of the SCORM data model element) - Returns: string (the value of the specified data model element) ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.data.get = function(parameter){ - - var value = null, - scorm = pipwerks.SCORM, - trace = pipwerks.UTILS.trace, - debug = scorm.debug, - traceMsgPrefix = "SCORM.data.get(" +parameter +") "; - - if(scorm.connection.isActive){ - - var API = scorm.API.getHandle(), - errorCode = 0; - - if(API){ - - switch(scorm.version){ - case "1.2" : value = API.LMSGetValue(parameter); break; - case "2004": value = API.GetValue(parameter); break; - } - - errorCode = debug.getCode(); - - //GetValue returns an empty string on errors - //If value is an empty string, check errorCode to make sure there are no errors - if(value !== "" || errorCode === 0){ - - //GetValue is successful. - //If parameter is lesson_status/completion_status or exit status, let's - //grab the value and cache it so we can check it during connection.terminate() - switch(parameter){ - - case "cmi.core.lesson_status": - case "cmi.completion_status" : scorm.data.completionStatus = value; break; - - case "cmi.core.exit": - case "cmi.exit" : scorm.data.exitStatus = value; break; - - } - - } else { - - trace(traceMsgPrefix +"failed. \nError code: " +errorCode +"\nError info: " +debug.getInfo(errorCode)); - - } - - } else { - - trace(traceMsgPrefix +"failed: API is null."); - - } - - } else { - - trace(traceMsgPrefix +"failed: API connection is inactive."); - - } - - trace(traceMsgPrefix +" value: " +value); - - return String(value); - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.data.set() - Tells the LMS to assign the value to the named data model element. - Also stores the SCO's completion status in a variable named - pipwerks.SCORM.data.completionStatus. This variable is checked whenever - pipwerks.SCORM.connection.terminate() is invoked. - - Parameters: parameter (string). The data model element - value (string). The value for the data model element - Returns: Boolean ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.data.set = function(parameter, value){ - - var success = false, - scorm = pipwerks.SCORM, - trace = pipwerks.UTILS.trace, - makeBoolean = pipwerks.UTILS.StringToBoolean, - debug = scorm.debug, - traceMsgPrefix = "SCORM.data.set(" +parameter +") "; - - - if(scorm.connection.isActive){ - - var API = scorm.API.getHandle(), - errorCode = 0; - - if(API){ - - switch(scorm.version){ - case "1.2" : success = makeBoolean(API.LMSSetValue(parameter, value)); break; - case "2004": success = makeBoolean(API.SetValue(parameter, value)); break; - } - - if(success){ - - if(parameter === "cmi.core.lesson_status" || parameter === "cmi.completion_status"){ - - scorm.data.completionStatus = value; - - } - - } else { - - trace(traceMsgPrefix +"failed. \nError code: " +errorCode +". \nError info: " +debug.getInfo(errorCode)); - - } - - } else { - - trace(traceMsgPrefix +"failed: API is null."); - - } - - } else { - - trace(traceMsgPrefix +"failed: API connection is inactive."); - - } - - return success; - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.data.save() - Instructs the LMS to persist all data to this point in the session - - Parameters: None - Returns: Boolean ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.data.save = function(){ - - var success = false, - scorm = pipwerks.SCORM, - trace = pipwerks.UTILS.trace, - makeBoolean = pipwerks.UTILS.StringToBoolean, - traceMsgPrefix = "SCORM.data.save failed"; - - - if(scorm.connection.isActive){ - - var API = scorm.API.getHandle(); - - if(API){ - - switch(scorm.version){ - case "1.2" : success = makeBoolean(API.LMSCommit("")); break; - case "2004": success = makeBoolean(API.Commit("")); break; - } - - } else { - - trace(traceMsgPrefix +": API is null."); - - } - - } else { - - trace(traceMsgPrefix +": API connection is inactive."); - - } - - return success; - -}; - - -pipwerks.SCORM.status = function (action, status){ - - var success = false, - scorm = pipwerks.SCORM, - trace = pipwerks.UTILS.trace, - traceMsgPrefix = "SCORM.getStatus failed", - cmi = ""; - - if(action !== null){ - - switch(scorm.version){ - case "1.2" : cmi = "cmi.core.lesson_status"; break; - case "2004": cmi = "cmi.completion_status"; break; - } - - switch(action){ - - case "get": success = scorm.data.get(cmi); break; - - case "set": if(status !== null){ - - success = scorm.data.set(cmi, status); - - } else { - - success = false; - trace(traceMsgPrefix +": status was not specified."); - - } - - break; - - default : success = false; - trace(traceMsgPrefix +": no valid action was specified."); - - } - - } else { - - trace(traceMsgPrefix +": action was not specified."); - - } - - return success; - -}; - - -// ------------------------------------------------------------------------- // -// --- pipwerks.SCORM.debug functions -------------------------------------- // -// ------------------------------------------------------------------------- // - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.debug.getCode - Requests the error code for the current error state from the LMS - - Parameters: None - Returns: Integer (the last error code). ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.debug.getCode = function(){ - - var scorm = pipwerks.SCORM, - API = scorm.API.getHandle(), - trace = pipwerks.UTILS.trace, - code = 0; - - if(API){ - - switch(scorm.version){ - case "1.2" : code = parseInt(API.LMSGetLastError(), 10); break; - case "2004": code = parseInt(API.GetLastError(), 10); break; - } - - } else { - - trace("SCORM.debug.getCode failed: API is null."); - - } - - return code; - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.debug.getInfo() - "Used by a SCO to request the textual description for the error code - specified by the value of [errorCode]." - - Parameters: errorCode (integer). - Returns: String. ------------------------------------------------------------------------------ */ - -pipwerks.SCORM.debug.getInfo = function(errorCode){ - - var scorm = pipwerks.SCORM, - API = scorm.API.getHandle(), - trace = pipwerks.UTILS.trace, - result = ""; - - - if(API){ - - switch(scorm.version){ - case "1.2" : result = API.LMSGetErrorString(errorCode.toString()); break; - case "2004": result = API.GetErrorString(errorCode.toString()); break; - } - - } else { - - trace("SCORM.debug.getInfo failed: API is null."); - - } - - return String(result); - -}; - - -/* ------------------------------------------------------------------------- - pipwerks.SCORM.debug.getDiagnosticInfo - "Exists for LMS specific use. It allows the LMS to define additional - diagnostic information through the API Instance." - - Parameters: errorCode (integer). - Returns: String (Additional diagnostic information about the given error code). ----------------------------------------------------------------------------- */ - -pipwerks.SCORM.debug.getDiagnosticInfo = function(errorCode){ - - var scorm = pipwerks.SCORM, - API = scorm.API.getHandle(), - trace = pipwerks.UTILS.trace, - result = ""; - - if(API){ - - switch(scorm.version){ - case "1.2" : result = API.LMSGetDiagnostic(errorCode); break; - case "2004": result = API.GetDiagnostic(errorCode); break; - } - - } else { - - trace("SCORM.debug.getDiagnosticInfo failed: API is null."); - - } - - return String(result); - -}; - - -// ------------------------------------------------------------------------- // -// --- Shortcuts! ---------------------------------------------------------- // -// ------------------------------------------------------------------------- // - -// Because nobody likes typing verbose code. - -pipwerks.SCORM.init = pipwerks.SCORM.connection.initialize; -pipwerks.SCORM.get = pipwerks.SCORM.data.get; -pipwerks.SCORM.set = pipwerks.SCORM.data.set; -pipwerks.SCORM.save = pipwerks.SCORM.data.save; -pipwerks.SCORM.quit = pipwerks.SCORM.connection.terminate; - - - -// ------------------------------------------------------------------------- // -// --- pipwerks.UTILS functions -------------------------------------------- // -// ------------------------------------------------------------------------- // - - -/* ------------------------------------------------------------------------- - pipwerks.UTILS.StringToBoolean() - Converts 'boolean strings' into actual valid booleans. - - (Most values returned from the API are the strings "true" and "false".) - - Parameters: String - Returns: Boolean ----------------------------------------------------------------------------- */ - -pipwerks.UTILS.StringToBoolean = function(value){ - var t = typeof value; - switch(t){ - //typeof new String("true") === "object", so handle objects as string via fall-through. - //See https://github.com/pipwerks/scorm-api-wrapper/issues/3 - case "object": - case "string": return (/(true|1)/i).test(value); - case "number": return !!value; - case "boolean": return value; - case "undefined": return null; - default: return false; - } -}; - - - -/* ------------------------------------------------------------------------- - pipwerks.UTILS.trace() - Displays error messages when in debug mode. - - Parameters: msg (string) - Return: None ----------------------------------------------------------------------------- */ - -pipwerks.UTILS.trace = function(msg){ - - if(pipwerks.debug.isActive){ - - if(window.console && window.console.log){ - console.log(msg); - } else { - //alert(msg); - } - - } -}; \ No newline at end of file +define (function(require) { + + /* + IMPORTANT: This wrapper uses the Pipwerks SCORM wrapper and should therefore support both SCORM 1.2 and 2004. Ensure any changes support both versions. + */ + + var ScormWrapper = function() + { + /* configuration */ + this.setCompletedWhenFailed = true;// this only applies to SCORM 2004 + /** + * whether to commit each time there's a change to lesson_status or not + */ + this.commitOnStatusChange = true; + /** + * how frequently (in minutes) to commit automatically. set to 0 to disable. + */ + this.timedCommitFrequency = 10; + /** + * how many times to retry if a commit fails + */ + this.maxCommitRetries = 5; + /** + * time (in milliseconds) to wait between retries + */ + this.commitRetryDelay = 1000; + + /** + * prevents commit from being called if there's already a 'commit retry' pending. + */ + this.commitRetryPending = false; + /** + * how many times we've done a 'commit retry' + */ + this.commitRetries = 0; + /** + * not currently used - but you could include in an error message to show when data was last saved + */ + this.lastCommitSuccessTime = null; + + this.timedCommitIntervalID = null; + this.retryCommitTimeoutID = null; + this.logOutputWin = null; + this.startTime = null; + this.endTime = null; + + this.lmsConnected = false; + this.finishCalled = false; + + this.logger = Logger.getInstance(); + this.scorm = pipwerks.SCORM; + + this.registeredViews = []; + + if (window.__debug) + this.showDebugWindow(); + + /* + stop pipwerks from auto-setting the SCO to incomplete, otherwise we can never let the course know when it is being run for the first time.... + */ + this.scorm.handleCompletionStatus = false; + /** + * and also stop it from setting cmi.core.exit to suspend/logout. there doesn't seem to be any tangible benefit to doing this... + * it can actually cause problems with some LMSes (e.g. setting 'logout' apparently causes Plateau to log out completely!) + * you can always switch it back on for an individual course if you think it's necessary. + */ + this.scorm.handleExitMode = false; + }; + + // static + ScormWrapper.instance = null; + + /******************************* public methods *******************************/ + + // static + ScormWrapper.getInstance = function() + { + if (ScormWrapper.instance == null) + ScormWrapper.instance = new ScormWrapper(); + return ScormWrapper.instance; + }; + + ScormWrapper.prototype.getVersion = function() + { + return this.scorm.version; + }; + + ScormWrapper.prototype.setVersion = function(value) + { + this.scorm.version = value; + }; + + ScormWrapper.prototype.registerView = function(_view) + { + this.registeredViews[this.registeredViews.length] = _view; + }; + + ScormWrapper.prototype.updateViews = function() + { + for (var i = 0; i < this.registeredViews.length; i++) { + this.registeredViews[i].update(this); + } + }; + + ScormWrapper.prototype.initialize = function() + { + this.lmsConnected = this.scorm.init(); + + if (this.lmsConnected) + { + this.startTime = new Date(); + + this.initTimedCommit(); + } + else + { + this.handleError("Course could not connect to the LMS"); + } + + return this.lmsConnected; + }; + + ScormWrapper.prototype.setIncomplete = function() + { + this.setValue(this.isSCORM2004() ? "cmi.completion_status" : "cmi.core.lesson_status", "incomplete"); + + if(this.commitOnStatusChange) this.commit(); + }; + + ScormWrapper.prototype.setCompleted = function() + { + this.setValue(this.isSCORM2004() ? "cmi.completion_status" : "cmi.core.lesson_status", "completed"); + + if(this.commitOnStatusChange) this.commit(); + }; + + ScormWrapper.prototype.setPassed = function() + { + if (this.isSCORM2004()) + { + this.setValue("cmi.completion_status", "completed"); + this.setValue("cmi.success_status", "passed"); + } + else + { + this.setValue("cmi.core.lesson_status", "passed"); + } + + if(this.commitOnStatusChange) this.commit(); + }; + + ScormWrapper.prototype.setFailed = function() + { + if (this.isSCORM2004()) + { + this.setValue("cmi.success_status", "failed"); + + if(this.setCompletedWhenFailed) + this.setValue("cmi.completion_status", "completed"); + } + else + { + this.setValue("cmi.core.lesson_status", "failed"); + } + + if(this.commitOnStatusChange) this.commit(); + }; + + ScormWrapper.prototype.getStatus = function() + { + var status = this.getValue(this.isSCORM2004() ? "cmi.completion_status" : "cmi.core.lesson_status"); + + switch(status.toLowerCase())// workaround for some LMSes (e.g. Arena) not adhering to the all-lowercase rule + { + case "passed": + case "completed": + case "incomplete": + case "failed": + case "browsed": + case "not attempted": + case "not_attempted":// mentioned in SCORM 2004 docs but not sure it ever gets used + case "unknown": //the SCORM 2004 version if not attempted + return status; + break; + default: + this.handleError("ScormWrapper::getStatus: invalid lesson status '" + status + "' received from LMS"); + return null; + } + }; + + ScormWrapper.prototype.getScore = function() + { + return this.getValue(this.isSCORM2004() ? "cmi.score.raw" : "cmi.core.score.raw"); + }; + + ScormWrapper.prototype.setScore = function(_score, _minScore, _maxScore) + { + if (this.isSCORM2004()) + { + this.setValue("cmi.score.raw", _score) && this.setValue("cmi.score.min", _minScore) && this.setValue("cmi.score.max", _maxScore) && this.setValue("cmi.score.scaled", _score / 100); + } + else + { + this.setValue("cmi.core.score.raw", _score); + + if(this.isSupported("cmi.core.score.min")) this.setValue("cmi.core.score.min", _minScore); + + if(this.isSupported("cmi.core.score.max")) this.setValue("cmi.core.score.max", _maxScore); + } + }; + + ScormWrapper.prototype.getLessonLocation = function() + { + return this.getValue(this.isSCORM2004() ? "cmi.location" : "cmi.core.lesson_location"); + }; + + ScormWrapper.prototype.setLessonLocation = function(_location) + { + this.setValue(this.isSCORM2004() ? "cmi.location" : "cmi.core.lesson_location", _location); + }; + + ScormWrapper.prototype.getSuspendData = function() + { + return this.getValue("cmi.suspend_data"); + }; + + ScormWrapper.prototype.setSuspendData = function(_data) + { + this.setValue("cmi.suspend_data", _data); + }; + + ScormWrapper.prototype.getStudentName = function() + { + return this.getValue(this.isSCORM2004() ? "cmi.learner_name" : "cmi.core.student_name"); + }; + + ScormWrapper.prototype.commit = function() + { + this.logger.debug("ScormWrapper::commit"); + + if (this.lmsConnected) + { + if (this.commitRetryPending) + { + this.logger.debug("ScormWrapper::commit: skipping this commit call as one is already pending.") + } + else + { + if (this.scorm.save()) + { + this.commitRetries = 0; + this.lastCommitSuccessTime = new Date(); + } + else + { + if (this.commitRetries <= this.maxCommitRetries && !this.finishCalled) + { + this.commitRetries++; + this.initRetryCommit(); + } + else + { + var _errorCode = this.scorm.debug.getCode(); + + var _errorMsg = "Course could not commit data to the LMS"; + _errorMsg += "\nError " + _errorCode + ": " + this.scorm.debug.getInfo(_errorCode); + _errorMsg += "\nLMS Error Info: " + this.scorm.debug.getDiagnosticInfo(_errorCode); + + this.handleError(_errorMsg); + } + } + } + } + else + { + this.handleError("Course is not connected to the LMS"); + } + }; + + ScormWrapper.prototype.finish = function() + { + this.logger.debug("ScormWrapper::finish"); + + if (this.lmsConnected && !this.finishCalled) + { + this.finishCalled = true; + + if(this.timedCommitIntervalID != null) + { + window.clearInterval(this.timedCommitIntervalID); + } + + if(this.commitRetryPending) + { + window.clearTimeout(this.retryCommitTimeoutID); + this.commitRetryPending = false; + } + + if (this.logOutputWin && !this.logOutputWin.closed) + { + this.logOutputWin.close(); + } + + this.endTime = new Date(); + + if (this.isSCORM2004()) + { + this.scorm.set("cmi.session_time", this.convertMilliSecondsToSCORM2004Time(this.endTime.getTime() - this.startTime.getTime())); + this.scorm.set("cmi.exit", "normal"); + } + else + { + this.scorm.set("cmi.core.session_time", this.convertMilliSecondsToSCORMTime(this.endTime.getTime() - this.startTime.getTime())); + this.scorm.set("cmi.core.exit", ""); + } + + this.commit(); + + // api no longer available from this point + this.lmsConnected = false; + + if (!this.scorm.quit()) + { + this.handleError("Course could not finish"); + } + } + else + { + this.handleError("Course is not connected to the LMS"); + } + }; + + ScormWrapper.prototype.recordInteraction = function(strID, strResponse, strCorrect, strLatency, scormInteractionType) + { + if(this.isSupported("cmi.interactions._count")) + { + if (scormInteractionType == "choice") + { + var responseIdentifiers = new Array(); + var answers = strResponse.split("#"); + + for (var i = 0; i < answers.length; i++) + { + responseIdentifiers.push(new ResponseIdentifier(answers[i], answers[i])); + } + + this.recordMultipleChoiceInteraction(strID, responseIdentifiers, strCorrect, null, null, null, strLatency, null); + } + else if (scormInteractionType == "matching") + { + var matchingResponses = new Array(); + var sourceTargetPairs = strResponse.split("#"); + var sourceTarget = null; + + for (var i = 0; i < sourceTargetPairs.length; i++) + { + sourceTarget = sourceTargetPairs[i].split("."); + matchingResponses.push(new MatchingResponse(sourceTarget[0], sourceTarget[1])); + } + + this.recordMatchingInteraction(strID, matchingResponses, strCorrect, null, null, null, strLatency, null); + } + } + else + { + this.logger.info("ScormWrapper::recordInteraction: cmi.interactions are not supported by this LMS..."); + } + } + + /****************************** private methods ******************************/ + ScormWrapper.prototype.getValue = function(_property) + { + this.logger.debug("ScormWrapper::getValue: _property=" + _property); + + if(this.finishCalled) + { + this.logger.debug("ScormWrapper::getValue: ignoring request as 'finish' has been called"); + return; + } + + if (this.lmsConnected) + { + var _value = this.scorm.get(_property); + var _errorCode = this.scorm.debug.getCode(); + var _errorMsg = ""; + + if (_errorCode !== 0) + { + if (_errorCode === 403) + { + this.logger.warn("ScormWrapper::getValue: data model element not initialized"); + } + else + { + _errorMsg += "Course could not get " + _property; + _errorMsg += "\nError Info: " + this.scorm.debug.getInfo(_errorCode); + _errorMsg += "\nLMS Error Info: " + this.scorm.debug.getDiagnosticInfo(_errorCode); + + this.handleError(_errorMsg); + } + } + this.logger.debug("ScormWrapper::getValue: returning " + _value); + return _value + ""; + } + else + { + this.handleError("Course is not connected to the LMS"); + } + }; + + ScormWrapper.prototype.setValue = function(_property, _value) + { + this.logger.debug("ScormWrapper::setValue: _property=" + _property + " _value=" + _value); + + if(this.finishCalled) + { + this.logger.debug("ScormWrapper::setValue: ignoring request as 'finish' has been called"); + return; + } + + if (this.lmsConnected) + { + var _success = this.scorm.set(_property, _value); + var _errorCode = this.scorm.debug.getCode(); + var _errorMsg = ""; + + if (!_success) + { + /* + * Some LMSes have an annoying tendency to return false from a set call even when it actually worked fine... + * So we need to throw an error only if there was a valid error code. Grr. + * http://www.madcrew.se/wordpress/wp-content/uploads/2008/09/stupid_final_01.jpg + */ + if(_errorCode !== 0) + { + _errorMsg += "Course could not set " + _property + " to " + _value; + _errorMsg += "\nError Info: " + this.scorm.debug.getInfo(_errorCode); + _errorMsg += "\nLMS Error Info: " + this.scorm.debug.getDiagnosticInfo(_errorCode); + + this.handleError(_errorMsg); + } + else + { + this.logger.warn("ScormWrapper::setValue: LMS reported that the 'set' call failed but then said there was no error!"); + } + } + + return _success; + } + else + { + this.handleError("Course is not connected to the LMS"); + } + }; + + /** + * used for checking any data field that is not 'LMS Mandatory' to see whether + * the LMS we're running on supports it or not. + * Note that the way this check is being performed means it wouldn't work for any element that is + * 'write only', but so far we've not had a requirement to check for any optional elements that are. + */ + ScormWrapper.prototype.isSupported = function(_property) + { + this.logger.debug("ScormWrapper::isSupported: _property=" + _property); + + if(this.finishCalled) + { + this.logger.debug("ScormWrapper::isSupported: ignoring request as 'finish' has been called"); + return; + } + + if (this.lmsConnected) + { + var _value = this.scorm.get(_property); + var _errorCode = this.scorm.debug.getCode(); + + return (_errorCode === 401 ? false : true); + } + else + { + this.handleError("Course is not connected to the LMS"); + return false; + } + }; + + ScormWrapper.prototype.initTimedCommit = function() + { + this.logger.debug("ScormWrapper::initTimedCommit"); + + if(this.timedCommitFrequency > 0) + { + var delay = this.timedCommitFrequency * (60 * 1000); + this.timedCommitIntervalID = window.setInterval(delegate(this, this.commit), delay); + } + }; + + ScormWrapper.prototype.initRetryCommit = function() + { + this.logger.debug("ScormWrapper::initRetryCommit"); + + this.commitRetryPending = true;// stop anything else from calling commit until this is done + + this.retryCommitTimeoutID = window.setTimeout(delegate(this, this.doRetryCommit), this.commitRetryDelay); + }; + + ScormWrapper.prototype.doRetryCommit = function() + { + this.logger.debug("ScormWrapper::doRetryCommit"); + + this.commitRetryPending = false; + + this.commit(); + }; + + ScormWrapper.prototype.handleError = function(_msg) + { + this.logger.error(_msg); + + if ((!this.logOutputWin || this.logOutputWin.closed) && confirm("An error has occured:\n\n" + _msg + "\n\nPress 'OK' to view debug information to send to technical support.")) + this.showDebugWindow(); + }; + + ScormWrapper.prototype.createValidIdentifier = function(str) + { + str = this.trim(new String(str)); + + if (_.indexOf(str.toLowerCase(), "urn:") === 0) + { + str = str.substr(4); + } + + // URNs may only contain the following characters: letters, numbers - ( ) + . : = @ ; $ _ ! * ' % + // if anything else is found, replace it with _ + str = str.replace(/[^\w\-\(\)\+\.\:\=\@\;\$\_\!\*\'\%]/g, "_"); + + return str; + }; + + ScormWrapper.prototype.createResponseIdentifier = function(strShort, strLong) + { + + if (strShort.length != 1 || strShort.search(/\w/) < 0) + { + strShort = ""; + } + else + { + strShort = strShort.toLowerCase(); + } + + strLong = this.createValidIdentifier(strLong); + + return new ResponseIdentifier(strShort, strLong); + }; + + ScormWrapper.prototype.recordInteraction12 = function(strID, strResponse, bCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, scormInteractionType, strAlternateResponse, strAlternateCorrectResponse) + { + var bResult; + var bTempResult; + var interactionIndex; + var strResult; + + // in SCORM 1.2, add a new interaction rather than updating an old one, because some LMS vendors have misinterpreted the "write only" rule regarding interactions to mean "write once" + interactionIndex = this.getValue("cmi.interactions._count"); + + if (interactionIndex === "") + { + interactionIndex = 0; + } + + if (bCorrect === true || bCorrect == "correct") + { + strResult = "correct"; + } + else if (bCorrect == "false" || bCorrect == "wrong") + { + strResult = "wrong"; + } + else if (bCorrect == "unanticipated") + { + strResult = "unanticipated"; + } + else if (bCorrect == "neutral") + { + strResult = "neutral"; + } + + bResult = this.setValue("cmi.interactions." + interactionIndex + ".id", strID); + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".type", scormInteractionType); + + bTempResult = this.setValue("cmi.interactions." + interactionIndex + ".student_response", strResponse); + + if (bTempResult === false) + { + bTempResult = this.setValue("cmi.interactions." + interactionIndex + ".student_response", strAlternateResponse); + } + + bResult = bResult && bTempResult; + + if (strCorrectResponse !== undefined && strCorrectResponse !== null && strCorrectResponse !== "") + { + bTempResult = this.setValue("cmi.interactions." + interactionIndex + ".correct_responses.0.pattern", strCorrectResponse); + if (bTempResult === false) + { + bTempResult = this.setValue("cmi.interactions." + interactionIndex + ".correct_responses.0.pattern", strAlternateCorrectResponse); + } + + bResult = bResult && bTempResult; + } + + if (strResult !== undefined && strResult !== null && strResult !== "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".result", strResult); + } + + // ignore the description parameter in SCORM 1.2, there is nothing we can do with it + + if (intWeighting !== undefined && intWeighting !== null && intWeighting !== "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".weighting", intWeighting); + } + + if (intLatency !== undefined && intLatency !== null && intLatency !== "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".latency", this.convertMilliSecondsToSCORMTime(intLatency)); + } + + if (strLearningObjectiveID !== undefined && strLearningObjectiveID !== null && strLearningObjectiveID !== "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".objectives.0.id", strLearningObjectiveID); + } + + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".time", this.convertDateToCMITime(dtmTime)); + + return bResult; + }; + + ScormWrapper.prototype.recordInteraction2004 = function(strID, strResponse, bCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, scormInteractionType) + { + + var bResult; + var interactionIndex; + var strResult; + + bCorrect = new String(bCorrect); + + interactionIndex = this.getValue("cmi.interactions._count"); + + if (interactionIndex == "") + { + interactionIndex = 0; + } + + if (bCorrect == true || bCorrect == "true" || bCorrect == "correct") + { + strResult = "correct"; + } + else if (bCorrect == "false" || bCorrect == "wrong") + { + strResult = "incorrect"; + } + else if (bCorrect == "unanticipated") + { + strResult = "unanticipated"; + } + else if (bCorrect == "neutral") + { + strResult = "neutral"; + } + else + { + strResult = ""; + } + + strID = this.createValidIdentifier(strID); + + bResult = this.setValue("cmi.interactions." + interactionIndex + ".id", strID); + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".type", scormInteractionType); + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".learner_response", strResponse); + + if (strResult != undefined && strResult != null && strResult != "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".result", strResult); + } + + if (strCorrectResponse != undefined && strCorrectResponse != null && strCorrectResponse != "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".correct_responses.0.pattern", strCorrectResponse); + } + + if (strDescription != undefined && strDescription != null && strDescription != "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".description", strDescription); + } + + // ignore the description parameter in SCORM 1.2, there is nothing we can do with it + + if (intWeighting != undefined && intWeighting != null && intWeighting != "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".weighting", intWeighting); + } + + if (intLatency != undefined && intLatency != null && intLatency != "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".latency", this.convertMilliSecondsToSCORM2004Time(intLatency)); + } + + if (strLearningObjectiveID != undefined && strLearningObjectiveID != null && strLearningObjectiveID != "") + { + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".objectives.0.id", strLearningObjectiveID); + } + + bResult = bResult && this.setValue("cmi.interactions." + interactionIndex + ".timestamp", this.convertDateToISO8601Timestamp(dtmTime)); + + return bResult; + }; + + ScormWrapper.prototype.recordMultipleChoiceInteraction = function(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID) + { + var _responseArray = null; + var _correctResponseArray = null; + + if (response.constructor == String) + { + _responseArray = new Array(this.createResponseIdentifier(response, response)); + } + else if (response.constructor == ResponseIdentifier) + { + _responseArray = new Array(response); + } + else if (response.constructor == Array || response.constructor.toString().search("Array") > 0) + { + _responseArray = response; + } + else if (window.console && response.constructor.toString() == "(Internal Function)" && response.length > 0) + { + _responseArray = response; + } + else + { + this.handleError("ScormWrapper::recordMultipleChoiceInteraction: response is not in the correct format"); + return false; + } + + if (correctResponse != null && correctResponse != undefined && correctResponse != "") + { + if (correctResponse.constructor == String) + { + _correctResponseArray = new Array(this.createResponseIdentifier(correctResponse, correctResponse)); + } + else if (correctResponse.constructor == ResponseIdentifier) + { + _correctResponseArray = new Array(correctResponse); + } + else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0) + { + _correctResponseArray = correctResponse; + } + else if (window.console && correctResponse.constructor.toString() == "(Internal Function)" && correctResponse.length > 0) + { + _correctResponseArray = correctResponse; + } + else + { + this.handleError("ScormWrapper::recordMultipleChoiceInteraction: correct response is not in the correct format"); + return false; + } + } + else + { + _correctResponseArray = new Array(); + } + + var dtmTime = new Date(); + + var strResponse = ""; + var strResponseLong = ""; + + var strCorrectResponse = ""; + var strCorrectResponseLong = ""; + + for (var i = 0; i < _responseArray.length; i++) + { + if (strResponse.length > 0) {strResponse += this.isSCORM2004() ? "[,]" : ",";} + if (strResponseLong.length > 0) {strResponseLong += ",";} + + strResponse += this.isSCORM2004() ? _responseArray[i].Long : _responseArray[i].Short; + strResponseLong += _responseArray[i].Long; + } + + for (var i = 0; i < _correctResponseArray.length; i++) + { + if (strCorrectResponse.length > 0) {strCorrectResponse += this.isSCORM2004() ? "[,]" : ",";} + if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} + + strCorrectResponse += this.isSCORM2004() ? _correctResponseArray[i].Long : _correctResponseArray[i].Short; + strCorrectResponseLong += _correctResponseArray[i].Long; + } + + if (this.isSCORM2004()) + return this.recordInteraction2004(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, "choice"); + + return this.recordInteraction12(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, "choice", strResponse, strCorrectResponse); + }; + + ScormWrapper.prototype.recordMatchingInteraction = function(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID) + { + var _responseArray = null; + var _correctResponseArray = null; + + if (response.constructor == MatchingResponse) + { + _responseArray = new Array(response); + } + else if (response.constructor == Array || response.constructor.toString().search("Array") > 0) + { + _responseArray = response; + } + else if (window.console && response.constructor.toString() == "(Internal Function)" && response.length > 0) + { + _responseArray = response; + } + else + { + this.handleError("ScormWrapper::recordMatchingInteraction: response is not in the correct format"); + return false; + } + + if (correctResponse != null && correctResponse != undefined) + { + if (correctResponse.constructor == MatchingResponse) + { + _correctResponseArray = new Array(correctResponse); + } + else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0) + { + _correctResponseArray = correctResponse; + } + else if (window.console && correctResponse.constructor.toString() == "(Internal Function)" && correctResponse.length > 0) + { + _correctResponseArray = correctResponse; + } + else + { + this.handleError("ScormWrapper::recordMatchingInteraction: correct response is not in the correct format"); + return false; + } + } + else + { + _correctResponseArray = new Array(); + } + + var dtmTime = new Date(); + + var strResponse = ""; + var strResponseLong = ""; + + var strCorrectResponse = ""; + var strCorrectResponseLong = ""; + + for (var i = 0; i < _responseArray.length; i++) + { + if (strResponse.length > 0) {strResponse += ",";} + if (strResponseLong.length > 0) {strResponseLong += this.isSCORM2004() ? "[,]" : ",";} + + strResponse += _responseArray[i].Source.Short + "." + _responseArray[i].Target.Short; + strResponseLong += _responseArray[i].Source.Long + (this.isSCORM2004() ? "[.]" : ".") + _responseArray[i].Target.Long; + } + + for (var i = 0; i < _correctResponseArray.length; i++) + { + if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} + if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += this.isSCORM2004() ? "[,]" : ",";} + + strCorrectResponse += _correctResponseArray[i].Source.Short + "." + _correctResponseArray[i].Target.Short; + strCorrectResponseLong += _correctResponseArray[i].Source.Long + (this.isSCORM2004() ? "[.]" : ".") + _correctResponseArray[i].Target.Long; + } + + if (this.isSCORM2004()) + return this.recordInteraction2004(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, "matching"); + + return this.recordInteraction12(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, "matching", strResponse, strCorrectResponse); + }; + + ScormWrapper.prototype.showDebugWindow = function() + { + + if (this.logOutputWin && !this.logOutputWin.closed) { + this.logOutputWin.close(); + } + + this.logOutputWin = window.open("log_output.html", "Log", "width=600,height=300,status=no,scrollbars=yes,resize=yes,menubar=yes,toolbar=yes,location=yes,top=0,left=0"); + + if (this.logOutputWin) + this.logOutputWin.focus(); + + return; + }; + + ScormWrapper.prototype.convertMilliSecondsToSCORMTime = function(value) + { + var h; + var m; + var s; + var ms; + var cs; + var CMITimeSpan; + + ms = value % 1000; + + s = ((value - ms) / 1000) % 60; + + m = ((value - ms - (s * 1000)) / 60000) % 60; + + h = (value - ms - (s * 1000) - (m * 60000)) / 3600000; + + if (h == 10000) + { + h = 9999; + + m = (value - (h * 3600000)) / 60000; + if (m == 100) + { + m = 99; + } + m = Math.floor(m); + + s = (value - (h * 3600000) - (m * 60000)) / 1000; + if (s == 100) + { + s = 99; + } + s = Math.floor(s); + + ms = (value - (h * 3600000) - (m * 60000) - (s * 1000)); + } + + cs = Math.floor(ms / 10); + + CMITimeSpan = this.zeroPad(h, 4) + ":" + this.zeroPad(m, 2) + ":" + this.zeroPad(s, 2); + CMITimeSpan += "." + cs; + + if (h > 9999) + { + CMITimeSpan = "9999:99:99"; + + CMITimeSpan += ".99"; + } + + return CMITimeSpan; + }; + + ScormWrapper.prototype.convertDateToCMITime = function(_value) + { + var h; + var m; + var s; + + dtmDate = new Date(_value); + + h = dtmDate.getHours(); + m = dtmDate.getMinutes(); + s = dtmDate.getSeconds(); + + return this.zeroPad(h, 2) + ":" + this.zeroPad(m, 2) + ":" + this.zeroPad(s, 2); + }; + + ScormWrapper.prototype.convertMilliSecondsToSCORM2004Time = function(_value) + { + var str = ""; + var cs; + var s; + var m; + var h; + var d; + var mo; // assumed to be an "average" month (a leap year every 4 years) = ((365*4) + 1) / 48 = 30.4375 days per month + var y; + + var HUNDREDTHS_PER_SECOND = 100; + var HUNDREDTHS_PER_MINUTE = HUNDREDTHS_PER_SECOND * 60; + var HUNDREDTHS_PER_HOUR = HUNDREDTHS_PER_MINUTE * 60; + var HUNDREDTHS_PER_DAY = HUNDREDTHS_PER_HOUR * 24; + var HUNDREDTHS_PER_MONTH = HUNDREDTHS_PER_DAY * (((365 * 4) + 1) / 48); + var HUNDREDTHS_PER_YEAR = HUNDREDTHS_PER_MONTH * 12; + + cs = Math.floor(_value / 10); + + y = Math.floor(cs / HUNDREDTHS_PER_YEAR); + cs -= (y * HUNDREDTHS_PER_YEAR); + + mo = Math.floor(cs / HUNDREDTHS_PER_MONTH); + cs -= (mo * HUNDREDTHS_PER_MONTH); + + d = Math.floor(cs / HUNDREDTHS_PER_DAY); + cs -= (d * HUNDREDTHS_PER_DAY); + + h = Math.floor(cs / HUNDREDTHS_PER_HOUR); + cs -= (h * HUNDREDTHS_PER_HOUR); + + m = Math.floor(cs / HUNDREDTHS_PER_MINUTE); + cs -= (m * HUNDREDTHS_PER_MINUTE); + + s = Math.floor(cs / HUNDREDTHS_PER_SECOND); + cs -= (s * HUNDREDTHS_PER_SECOND); + + if (y > 0) + str += y + "Y"; + if (mo > 0) + str += mo + "M"; + if (d > 0) + str += d + "D"; + + // check to see if we have any time before adding the "T" + if ((cs + s + m + h) > 0 ) + { + + str += "T"; + + if (h > 0) + str += h + "H"; + + if (m > 0) + str += m + "M"; + + if ((cs + s) > 0) + { + str += s; + + if (cs > 0) + str += "." + cs; + + str += "S"; + } + } + + if (str == "") + str = "0S"; + + str = "P" + str; + + return str; + }; + + ScormWrapper.prototype.convertDateToISO8601Timestamp = function(_value) + { + var str; + + dtm = new Date(_value); + + var y = dtm.getFullYear(); + var mo = dtm.getMonth() + 1; + var d = dtm.getDate(); + var h = dtm.getHours(); + var m = dtm.getMinutes(); + var s = dtm.getSeconds(); + + mo = this.zeroPad(mo, 2); + d = this.zeroPad(d, 2); + h = this.zeroPad(h, 2); + m = this.zeroPad(m, 2); + s = this.zeroPad(s, 2); + + str = y + "-" + mo + "-" + d + "T" + h + ":" + m + ":" + s; + + return str; + }; + + ScormWrapper.prototype.zeroPad = function(intNum, intNumDigits) + { + var strTemp; + var intLen; + var i; + + strTemp = new String(intNum); + intLen = strTemp.length; + + if (intLen > intNumDigits) + { + strTemp = strTemp.substr(0, intNumDigits); + } + else + { + for (i = intLen; i < intNumDigits; i++) + strTemp = "0" + strTemp; + } + + return strTemp; + }; + + ScormWrapper.prototype.trim = function(str) + { + return str.replace(/^\s*|\s*$/g, ""); + }; + + ScormWrapper.prototype.isSCORM2004 = function() + { + return this.scorm.version == "2004"; + }; + + function delegate(obj, func) + { + return function() {return func.apply(obj, arguments); }; + } + + return ScormWrapper; +}); \ No newline at end of file