]> _ Git - hf-scorm-package.git/commitdiff
wip #4907 @1
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Tue, 14 Dec 2021 16:37:47 +0000 (17:37 +0100)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Tue, 14 Dec 2021 16:37:47 +0000 (17:37 +0100)
index.html
js/libs/scorm/apiwrapper.js
js/libs/scorm/facade.js [new file with mode: 0644]
js/scormpackage.js

index b81cbd286c70e308687e5e3456da38f730b1fed0..0a6b68512ce5e05c13363ce5bac39db9ea95cfba 100644 (file)
 
 <script src="data.js"></script>
 <script src="js/libs/jquery.min.js"></script>
+<script src="js/libs/scorm/facade.js"></script>
 <script src="js/libs/scorm/apiwrapper.js"></script>
 <script src="js/scormpackage.js"></script>
 </body>
index 8adfc0179653de529a73e334635ddaab34eba14c..16f7aebecaf7725c547f7424cc137d1009fa95ee 100644 (file)
-/*******************************************************************************
- **
- ** This software is provided "AS IS," without a warranty of any kind.  ALL
- ** EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
- ** IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-
- ** INFRINGEMENT, ARE HEREBY EXCLUDED.  PAUL BECKWITH AND HIS LICENSORS SHALL NOT BE LIABLE
- ** FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
- ** DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES.  IN NO EVENT WILL PAUL BECKWITH OR ITS
- ** LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
- ** INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
- ** CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
- ** OR INABILITY TO USE SOFTWARE, EVEN IF CTC  HAS BEEN ADVISED OF THE POSSIBILITY
- ** OF SUCH DAMAGES.
- **    javascript:
- **          var result = doLMSInitialize();
- **          if (result != true)
- **          {
-**             // handle error
-**          }
- **
- *******************************************************************************/
-
-
-var _Debug = false;  // set this to false to turn debugging off
-                     // and get rid of those annoying alert boxes.
-
-// Define exception/error codes
-var _NoError = 0;
-var _GeneralException = 101;
-var _ServerBusy = 102;
-var _InvalidArgumentError = 201;
-var _ElementCannotHaveChildren = 202;
-var _ElementIsNotAnArray = 203;
-var _NotInitialized = 301;
-var _NotImplementedError = 401;
-var _InvalidSetValue = 402;
-var _ElementIsReadOnly = 403;
-var _ElementIsWriteOnly = 404;
-var _IncorrectDataType = 405;
-
-
-// local variable definitions
-var apiHandle = null;
-var API = null;
-var findAPITries = 0;
-
-
-/*******************************************************************************
- **
- ** Function: doLMSInitialize()
- ** Inputs:  None
- ** Return:  CMIBoolean true if the initialization was successful, or
- **          CMIBoolean false if the initialization failed.
- **
- ** Description:
- ** Initialize communication with LMS by calling the LMSInitialize
- ** function which will be implemented by the LMS.
- **
- *******************************************************************************/
-function doLMSInitialize() {
-    startTimer();
-
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSInitialize was not successful.");
-        return false;
+/*global console*/
+
+/* ===========================================================
+
+pipwerks SCORM Wrapper for JavaScript
+v1.1.20160322
+
+Created by Philip Hutchison, January 2008-2016
+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 = 12,
+        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;
     }
 
-    var result = api.LMSInitialize("");
+    //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 (result.toString() != "true") {
-        var err = ErrorHandler();
+    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 result.toString();
-}
-
-/*******************************************************************************
- **
- ** Function doLMSFinish()
- ** Inputs:  None
- ** Return:  CMIBoolean true if successful
- **          CMIBoolean false if failed.
- **
- ** Description:
- ** Close communication with LMS by calling the LMSFinish
- ** function which will be implemented by the LMS
- **
- *******************************************************************************/
-function doLMSFinish() {
-
-    setSessionTime();
-
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSFinish was not successful.");
-        return "false";
+    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;
+
+    API = find(win);
+
+    if (!API && win.parent && win.parent != win) {
+        API = find(win.parent);
     }
-    else {
-        // call the LMSFinish function that should be implemented by the API
 
-        var result = api.LMSFinish("");
-        if (result.toString() != "true") {
-            var err = ErrorHandler();
-        }
+    if (!API && win.top && win.top.opener) {
+        API = find(win.top.opener);
+    }
 
+    //Special handling for Plateau
+    //Thanks to Joseph Venditti for the patch
+    if (!API && win.top && win.top.opener && win.top.opener.document) {
+        API = find(win.top.opener.document);
     }
 
-    return result.toString();
-}
-
-/*******************************************************************************
- **
- ** Function doLMSGetValue(name)
- ** Inputs:  name - string representing the cmi data model defined category or
- **             element (e.g. cmi.core.student_id)
- ** Return:  The value presently assigned by the LMS to the cmi data model
- **       element defined by the element or category identified by the name
- **       input value.
- **
- ** Description:
- ** Wraps the call to the LMS LMSGetValue method
- **
- *******************************************************************************/
-function doLMSGetValue(name) {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSGetValue was not successful.");
-        return "";
+    if (API) {
+        scorm.API.isFound = true;
+    } else {
+        trace("API.get failed: Can't find the API!");
     }
-    else {
-        var value = api.LMSGetValue(name);
-        var errCode = api.LMSGetLastError().toString();
-        if (errCode != _NoError) {
-            // an error was encountered so display the error description
-            var errDescription = api.LMSGetErrorString(errCode);
-            console.error("LMSGetValue(" + name + ") failed. \n" + errDescription);
-            return "";
-        }
-        else {
 
-            return value.toString();
+    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) {
+            if (API.Initialized !== true) {
+                switch (scorm.version) {
+                    case "1.2" :
+                        success = makeBoolean(API.LMSInitialize(""));
+                        break;
+                    case "2004":
+                        success = makeBoolean(API.Initialize(""));
+                        break;
+                }
+            } else {
+                success = true;
+            }
+
+            if (success) {
+                //Double-check that connection is active and working before returning 'true' boolean
+                errorCode = debug.getCode();
+                if (API.Initialized===true || (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");
+                                    scorm.set('cmi.success_status', 'unknown');
+                                    break;
+                                //SCORM 2004 only
+                                case "unknown" :
+                                    scorm.status("set", "incomplete");
+                                    scorm.set('cmi.success_status', 'unknown');
+                                    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
+                                default :
+                                    scorm.status('set', completionStatus);
+                                    scorm.set('cmi.success_status', scorm.get('cmi.success_status'));
+                                    break;
+                            }
+                            //Commit changes
+                            scorm.save();
+                        }
+                    }
+                } 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.");
+
     }
-}
-
-/*******************************************************************************
- **
- ** Function doLMSSetValue(name, value)
- ** Inputs:  name -string representing the data model defined category or element
- **          value -the value that the named element or category will be assigned
- ** Return:  CMIBoolean true if successful
- **          CMIBoolean false if failed.
- **
- ** Description:
- ** Wraps the call to the LMS LMSSetValue function
- **
- *******************************************************************************/
-function doLMSSetValue(name, value) {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSSetValue was not successful.");
-        return;
+    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", "suspend");
+                            break;
+                        case "2004":
+                            success = scorm.set("cmi.exit", "suspend");
+                            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.");
     }
-    else {
-        var result = api.LMSSetValue(name, value);
-        if (result.toString() != "true") {
-            var err = ErrorHandler();
+    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;
+            }
 
-    return;
-}
-
-/*******************************************************************************
- **
- ** Function doLMSCommit()
- ** Inputs:  None
- ** Return:  None
- **
- ** Description:
- ** Call the LMSCommit function
- **
- *******************************************************************************/
-function doLMSCommit() {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSCommit was not successful.");
-        return "false";
+            if (success) {
+                if (parameter === "cmi.core.lesson_status" || parameter === "cmi.completion_status") {
+                    scorm.data.completionStatus = value;
+                } else if (parameter == 'cmi.core.exit' || parameter == 'cmi.exit') {
+                    scorm.data.exitStatus = value;
+                }
+            } 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 + "failed: API connection is inactive.");
     }
-    else {
-        var result = api.LMSCommit("");
-        if (result != "true") {
-            var err = ErrorHandler();
+    trace(traceMsgPrefix + " value: " + value);
+    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 result.toString();
-}
-
-/*******************************************************************************
- **
- ** Function doLMSGetLastError()
- ** Inputs:  None
- ** Return:  The error code that was set by the last LMS function call
- **
- ** Description:
- ** Call the LMSGetLastError function
- **
- *******************************************************************************/
-function doLMSGetLastError() {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSGetLastError was not successful.");
-        //since we can't get the error code from the LMS, return a general error
-        return _GeneralError;
-    }
+    return success;
 
-    return api.LMSGetLastError().toString();
-}
-
-/*******************************************************************************
- **
- ** Function doLMSGetErrorString(errorCode)
- ** Inputs:  errorCode - Error Code
- ** Return:  The textual description that corresponds to the input error code
- **
- ** Description:
- ** Call the LMSGetErrorString function
- **
- ********************************************************************************/
-function doLMSGetErrorString(errorCode) {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSGetErrorString was not successful.");
-    }
+};
 
-    return api.LMSGetErrorString(errorCode).toString();
-}
-
-/*******************************************************************************
- **
- ** Function doLMSGetDiagnostic(errorCode)
- ** Inputs:  errorCode - Error Code(integer format), or null
- ** Return:  The vendor specific textual description that corresponds to the
- **          input error code
- **
- ** Description:
- ** Call the LMSGetDiagnostic function
- **
- *******************************************************************************/
-function doLMSGetDiagnostic(errorCode) {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSGetDiagnostic was not successful.");
-    }
 
-    return api.LMSGetDiagnostic(errorCode).toString();
-}
-
-/*******************************************************************************
- **
- ** Function LMSIsInitialized()
- ** Inputs:  none
- ** Return:  true if the LMS API is currently initialized, otherwise false
- **
- ** Description:
- ** Determines if the LMS API is currently initialized or not.
- **
- *******************************************************************************/
-function LMSIsInitialized() {
-    // there is no direct method for determining if the LMS API is initialized
-    // for example an LMSIsInitialized function defined on the API so we'll try
-    // a simple LMSGetValue and trap for the LMS Not Initialized Error
-
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nLMSIsInitialized() failed.");
-        return false;
-    }
-    else {
-        var value = api.LMSGetValue("cmi.core.student_name");
-        var errCode = api.LMSGetLastError().toString();
-        if (errCode == _NotInitialized) {
-            return false;
+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;
         }
-        else {
-            return true;
+
+        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.");
         }
-    }
-}
-
-/*******************************************************************************
- **
- ** Function ErrorHandler()
- ** Inputs:  None
- ** Return:  The current value of the LMS Error Code
- **
- ** Description:
- ** Determines if an error was encountered by the previous API call
- ** and if so, displays a message to the user.  If the error code
- ** has associated text it is also displayed.
- **
- *******************************************************************************/
-function ErrorHandler() {
-    var api = getAPIHandle();
-    if (api == null) {
-        console.error("Unable to locate the LMS's API Implementation.\nCannot determine LMS error code.");
-        return;
+    } else {
+        trace(traceMsgPrefix + ": action was not specified.");
     }
 
-    // check for errors caused by or from the LMS
-    var errCode = api.LMSGetLastError().toString();
-    if (errCode != _NoError) {
-        // an error was encountered so display the error description
-        var errDescription = api.LMSGetErrorString(errCode);
-
-        if (_Debug == true) {
-            errDescription += "\n";
-            errDescription += api.LMSGetDiagnostic(null);
-            // by passing null to LMSGetDiagnostic, we get any available diagnostics
-            // on the previous error.
-        }
+    return success;
 
-        console.error(errDescription);
-    }
+};
 
-    return errCode;
-}
-
-/******************************************************************************
- **
- ** Function getAPIHandle()
- ** Inputs:  None
- ** Return:  value contained by APIHandle
- **
- ** Description:
- ** Returns the handle to API object if it was previously set,
- ** otherwise it returns null
- **
- *******************************************************************************/
-function getAPIHandle() {
-    if (apiHandle == null) {
-        apiHandle = getAPI();
-    }
 
-    return apiHandle;
-}
-
-
-/*******************************************************************************
- **
- ** Function findAPI(win)
- ** Inputs:  win - a Window Object
- ** Return:  If an API object is found, it's returned, otherwise null is returned
- **
- ** Description:
- ** This function looks for an object named API in parent and opener windows
- **
- *******************************************************************************/
-function findAPI(win) {
-    try {
-        while ((win.API == null) && (win.parent != null) && (win.parent != win)) {
-            findAPITries++;
-            // Note: 7 is an arbitrary number, but should be more than sufficient
-            if (findAPITries > 7) {
-                console.error("Error finding API -- too deeply nested.");
-                return null;
-            }
+// ------------------------------------------------------------------------- //
+// --- 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 () {
 
-            win = win.parent;
+    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;
         }
-        return win.API;
-    } catch (err) {
-        return null;
+    } else {
+        trace("SCORM.debug.getCode failed: API is null.");
     }
-}
-
-
-/*******************************************************************************
- **
- ** Function getAPI()
- ** Inputs:  none
- ** Return:  If an API object is found, it's returned, otherwise null is returned
- **
- ** Description:
- ** This function 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).
- **
- *******************************************************************************/
-function getAPI() {
-    var theAPI = findAPI(window);
-    if ((theAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) {
-        theAPI = findAPI(window.opener);
-    }
-    if (theAPI == null) {
-        console.error("Unable to find an API adapter");
+
+    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 theAPI
-}
-var startTime;
-
-function startTimer() {
-    startTime = new Date().getTime();
-}
-
-
-// call function right before LMSFinish
-function setSessionTime() {
-    var currentTime = new Date();
-    var endTime = currentTime.getTime()
-    var calculatedTime = endTime - startTime;
-    var totalHours = Math.floor(calculatedTime / 1000 / 60 / 60);
-    calculatedTime = calculatedTime - totalHours * 1000 * 60 * 60
-
-    if (totalHours < 1000 && totalHours > 99) {
-        totalHours = "0" + totalHours;
-    } else if (totalHours < 100 && totalHours > 9) {
-        totalHours = "00" + totalHours;
-    } else if (totalHours < 10) {
-        totalHours = "000" + totalHours;
+    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.");
     }
 
-    var totalMinutes = Math.floor(calculatedTime / 1000 / 60);
-    calculatedTime = calculatedTime - totalMinutes * 1000 * 60;
+    return String(result);
+};
 
-    if (totalMinutes < 10) {
-        totalMinutes = "0" + totalMinutes;
-    }
 
-    var totalSeconds = Math.floor(calculatedTime / 1000);
+// ------------------------------------------------------------------------- //
+// --- 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;
+
 
-    if (totalSeconds < 10) {
-        totalSeconds = "0" + totalSeconds;
+// ------------------------------------------------------------------------- //
+// --- 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;
     }
+};
 
-    var sessionTime = totalHours + ":" + totalMinutes + ":" + totalSeconds;
 
-    doLMSSetValue("cmi.core.session_time", sessionTime);
-}
+/* -------------------------------------------------------------------------
+   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) {
+            window.console.log(msg);
+        } else {
+            //alert(msg);
+        }
+    }
+};
diff --git a/js/libs/scorm/facade.js b/js/libs/scorm/facade.js
new file mode 100644 (file)
index 0000000..fd27768
--- /dev/null
@@ -0,0 +1,44 @@
+function SCORMFacade() {
+}
+
+SCORMFacade.prototype = {
+    Initialize: function () {
+        this._log('Init');
+        return true;
+    },
+    Terminate: function () {
+        this._log('Terminate');
+        return finishScorm();
+    },
+    GetValue: function (key) {
+        this._log('Get value ' + key);
+        if(key==='cmi.completion_status'){
+
+        }
+        return getScormValue(key);
+    },
+    SetValue: function (key, value) {
+        this._log('Set value ' + key + ' :: ' + value);
+        if(key===''){
+
+        }
+
+        // TODO save data in module
+    },
+    Commit: function (d) {
+        this._log('Commit');
+        return true;
+    },
+    GetLastError: function () {
+        return 0;
+    },
+    GetErrorString: function () {
+        return '';
+    },
+    GetDiagnostic: function () {
+        return '';
+    },
+    _log: function (log) {
+        console.log('SCORM facade : ' + log);
+    },
+};
\ No newline at end of file
index 0e93cecfcc3f9a2aae5c9bf0a8ffee9a10f7bea5..ce38a76e6b69a0c5f82e6b352d46254cf0214bfa 100644 (file)
 window.savedState = {};
-console.log(window.savedState);
 
+SCORM_INITED = false;
+SCORM_START_TIME = null;
+SCORM_INTERACTION_TIMESTAMPS = [];
+SCORM_CORRECT_ANSWERS = [];
+SCORM_ID_TO_N = {};
+SCORM_WEIGHTING = 0;
+SCORM_QUESTIONS = [];
+SCORM_SUCCESS_STATUS = 'unknown';
+SCORM_SUCCESS_SCORE = 0;
+SCORM_EVENTS_INITED = false;
+SCORM_INTERACTIONS_INITED = false;
+SCORM_LOCATION_INITED = false;
+SCORM_OK = false;
+
+var _CMI12 = {
+    'location': 'cmi.core.lesson_location',
+    'status': "cmi.core.lesson_status",
+    'session_time': 'cmi.core.session_time',
+    'success_status': '',
+    'exit': 'cmi.core.exit',
+    'cmi.score.raw': 'cmi.core.score.raw',
+    'cmi.score.min': 'cmi.core.score.min',
+    'cmi.score.max': 'cmi.core.score.max',
+    'cmi.score.scaled': '',
+};
+
+var _CMI2004 = {
+    'location': 'cmi.location',
+    'status': 'cmi.completion_status',
+    'session_time': 'cmi.session_time',
+    'success_status': 'cmi.success_status',
+    'exit': 'cmi.exit',
+};
+
+
+$(function () {
+    $("header #logo").html(getSpriteIcon('logo'));
+    $("header #tile").html(getSpriteIcon('tile'));
+    initScorm();
+});
+
+function _cmi(key) {
+    var res = null;
+    switch (pipwerks.SCORM.version) {
+        case "1.2" :
+            res = _CMI12[key];
+            break;
+        case '2004':
+            res = _CMI2004[key];
+            break;
+    }
+    if (res == undefined || res == null) {
+        res = key;
+    }
+    return res;
+}
 
-(function (global) {
-    $(function () {
-        $("header #logo").html(getSpriteIcon('logo'));
-        $("header #tile").html(getSpriteIcon('tile'));
-        initScorm();
-    });
+function initScorm() {
+    if (SCORM_INITED) {
+        return;
+    }
 
-})(typeof window === 'undefined' ? this : window);
+    SCORM_INITED = true;
+    try {
+        if (pipwerks.SCORM.init()) {
+            SCORM_OK = true;
+        }
+    } catch (e) {
+
+    }
+
+    try {
+        if (FORCE_SCORM) {
+            SCORM_OK = true;
+        }
+    } catch (e) {
+
+    }
+
+    setContents();
+    initEvents();
+    if (SCORM_OK) {
+        scormExit();
+        startScormTimer();
+        initScormEvents();
+    }
+    window.API = null;
+    window.API_1484_11 = new SCORMFacade();
+    return SCORM_OK;
+}
+
+function scormMarkAsComplete() {
+    if (!SCORM_OK) {
+        return;
+    }
+    scormExit();
+}
+
+
+function finishScorm() {
+    if (!SCORM_OK) {
+        return;
+    }
+    setSessionTime();
+    pipwerks.SCORM.save();
+    if (fluidbook.settings.multilang === '') {
+        pipwerks.SCORM.quit();
+    }
+}
+
+function scormExit() {
+    if (!SCORM_OK) {
+        return;
+    }
+    var v = 'suspend';
+    if (fluidbook.settings.scorm_force_attempts) {
+        if (pipwerks.SCORM.version === '1.2') {
+            v = 'logout';
+        } else {
+            v = 'normal';
+        }
+    }
+
+    setScormValue('exit', v);
+}
 
+function startScormTimer() {
+    SCORM_START_TIME = new Date();
+}
+
+function scormCompleteAndClose() {
+    scormComplete();
+    scormClose();
+}
+
+function scormClose() {
+    parent.close();
+    top.close();
+    window.close();
+}
 
 function initEvents() {
     $(document).on('click', '[data-id]', function () {
@@ -84,7 +213,6 @@ function setContents() {
     $('tbody').html('');
     $.each(DATA.modules, function (k, v) {
         var s = (status[v.id] === undefined) ? {complete_status: 'incomplete', success_status: 'unknown', score: 0, location: {}} : status[v.id];
-        console.log(s);
         var tr = $('<tr data-type="' + v.type + '" data-id="' + v.id + '"><td class="i">' + getSpriteIcon('icon-' + v.type) + '</td></tr>');
         $('tbody').append(tr);
         tr.append('<td class="t">' + v.title + '</td>');
@@ -114,24 +242,30 @@ function setContents() {
     // Using `this` for web workers & supports Browserify / Webpack.
 })(typeof window === 'undefined' ? this : window);
 
-function initScorm() {
-    SCORM = doLMSInitialize();
+
+function initScormEvents() {
+    if (!SCORM_OK || SCORM_EVENTS_INITED) {
+        return;
+    }
+    SCORM_EVENTS_INITED = true;
 
     $(window).on('unload', function () {
-        doLMSFinish();
+        finishScorm();
     });
 
-    try {
-        var l = getScormValue('cmi.location');
-        if (l === null || l === 'null') {
-            window.savedState = {};
-        } else {
-            window.savedState = JSON.parse(l);
-        }
-    } catch (e) {
+    if (SCORM_LOCATION_INITED) {
+        return;
+    }
+    SCORM_LOCATION_INITED = true;
 
+    var currentLocation = getScormValue('location');
+    if (currentLocation !== 'null' && currentLocation !== '') {
+        window.savedState = {};
+    } else {
+        window.savedState = JSON.parse(currentLocation);
     }
 
+
     if (window.savedState === undefined || window.savedState === null || window.savedState === 'null') {
         window.savedState = {};
     }
@@ -139,7 +273,10 @@ function initScorm() {
     initEvents();
     setContents();
 
-    return true;
+
+    setInterval(function () {
+        pipwerks.SCORM.save();
+    }, 5000);
 }
 
 function setSCORMLocation(location) {
@@ -158,19 +295,29 @@ function setSCORMScore(score, max, min) {
 
 
 function getScormValue(elementName) {
-    if (!SCORM) {
+    if (!SCORM_OK) {
         return null;
     }
-    var result = String(doLMSGetValue(elementName));
+    var cmi = _cmi(elementName);
+    if (cmi == '') {
+        return null;
+    }
+    var result = pipwerks.SCORM.get(cmi);
     return result;
 }
 
 function setScormValue(elementName, value) {
-    if (!SCORM) {
+    if (!SCORM_OK) {
         return;
     }
-    var result = doLMSSetValue(elementName, value);
-    doLMSCommit();
+    var cmi = _cmi(elementName);
+    if (cmi == '') {
+        return false;
+    }
+    var result = pipwerks.SCORM.set(cmi, value);
+    if (fluidbook.settings.scorm_immediate_commit) {
+        pipwerks.SCORM.save();
+    }
     return result;
 }
 
@@ -212,4 +359,65 @@ function getSpriteIcon(icon, attrs, dimensions) {
         a.push(k + '="' + v + '"');
     });
     return '<svg ' + a.join(' ') + ' aria-hidden="true"><use xlink:href="#' + icon + '" /></svg>';
+}
+
+
+function setSessionTime() {
+    if (!SCORM_OK) {
+        return;
+    }
+    var currentTime = new Date();
+    var sessionTime;
+
+    var endTime = currentTime.getTime()
+    var calculatedTime = endTime - SCORM_START_TIME;
+
+    if (pipwerks.SCORM.version == '1.2') {
+        var totalHours = Math.floor(calculatedTime / 1000 / 60 / 60);
+        calculatedTime = calculatedTime - totalHours * 1000 * 60 * 60
+        if (totalHours < 1000 && totalHours > 99) {
+            totalHours = "0" + totalHours;
+        } else if (totalHours < 100 && totalHours > 9) {
+            totalHours = "00" + totalHours;
+        } else if (totalHours < 10) {
+            totalHours = "000" + totalHours;
+        }
+
+        var totalMinutes = Math.floor(calculatedTime / 1000 / 60);
+        calculatedTime = calculatedTime - totalMinutes * 1000 * 60;
+        if (totalMinutes < 10) {
+            totalMinutes = "0" + totalMinutes;
+        }
+
+        var totalSeconds = Math.floor(calculatedTime / 1000);
+        if (totalSeconds < 10) {
+            totalSeconds = "0" + totalSeconds;
+        }
+        sessionTime = totalHours + ":" + totalMinutes + ":" + totalSeconds;
+        setScormValue('session_time', sessionTime);
+    } else {
+        setScormValue('session_time', scormSecondsToTimeInterval(calculatedTime / 1000));
+    }
+
+}
+
+function dateToScormTime(date) {
+    var res = date.toISOString();
+    var e = res.split('.');
+    e.pop();
+    return e.join('.');
+}
+
+function getScormTimeInterval(start, end) {
+    var diff = Math.round((end.getTime() - start.getTime()) / 1000);
+    return scormSecondsToTimeInterval(diff);
+}
+
+function scormSecondsToTimeInterval(diff) {
+    var diff = Math.round(diff);
+    var h = Math.floor(diff / 3600);
+    diff = diff % 3600;
+    var m = Math.floor(diff / 60);
+    var s = diff % 60;
+    return 'PT' + h + 'H' + m + 'M' + s + 'S';
 }
\ No newline at end of file