]> _ Git - fluidbook-html5.git/commitdiff
wip #2048 @7
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 23 May 2018 19:13:57 +0000 (21:13 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 23 May 2018 19:13:57 +0000 (21:13 +0200)
_imsmanifest.12.xml [new file with mode: 0644]
_imsmanifest.2004.xml [new file with mode: 0644]
_imsmanifest.xml [deleted file]
js/libs/fluidbook/fluidbook.js
js/libs/scorm/apiwrapper.js
js/libs/scorm/scorm.js

diff --git a/_imsmanifest.12.xml b/_imsmanifest.12.xml
new file mode 100644 (file)
index 0000000..da32021
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<manifest
+        xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
+        xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
+        identifier="MANIFEST-90878C16-EB60-D648-94ED-9651972B5F38"
+        xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd
+                            http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd
+                            http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">
+    <metadata>
+        <schema>ADL SCORM</schema>
+        <schemaversion>1.2</schemaversion>
+    </metadata>
+    <organizations default="$scorm_org">
+        <organization identifier="$scorm_org" structure="hierarchical">
+            <title>$scorm_title</title>
+            <item identifier="$scorm_id" identifierref="$scorm_id" isvisible="true">
+                <title>$scorm_title</title>
+            </item>
+        </organization>
+    </organizations>
+    <resources>
+        <resource type="webcontent" adlcp:scormtype="sco" identifier="$scorm_id" href="index.html">
+            <file href="index.html"/>
+        </resource>
+    </resources>
+</manifest>
\ No newline at end of file
diff --git a/_imsmanifest.2004.xml b/_imsmanifest.2004.xml
new file mode 100644 (file)
index 0000000..60e8b81
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" standalone="no" ?>
+<manifest identifier="MANIFEST-90878C16-EB60-D648-94ED-9651972B5F38" version="1.3"
+          xmlns="http://www.imsglobal.org/xsd/imscp_v1p1"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3"
+          xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3"
+          xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3"
+          xmlns:imsss="http://www.imsglobal.org/xsd/imsss"
+          xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd
+                              http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd
+                              http://www.adlnet.org/xsd/adlseq_v1p3 adlseq_v1p3.xsd
+                              http://www.adlnet.org/xsd/adlnav_v1p3 adlnav_v1p3.xsd
+                              http://www.imsglobal.org/xsd/imsss imsss_v1p0.xsd">
+    <metadata>
+        <schema>ADL SCORM</schema>
+        <schemaversion>2004 4th Edition</schemaversion>
+    </metadata>
+    <organizations default="$scorm_org">
+        <organization identifier="$scorm_org" adlseq:objectivesGlobalToSystem="false">
+            <title>$scorm_title</title>
+            <item identifier="$scorm_id" identifierref="$scorm_id" isvisible="true">
+                <title>$scorm_title</title>
+                <imsss:sequencing>
+                    <imsss:objectives>
+                        <imsss:primaryObjective satisfiedByMeasure="true">
+                            <!-- equates to cmi.scaled_passing_score -->
+                            <imsss:minNormalizedMeasure>0.5</imsss:minNormalizedMeasure>
+                        </imsss:primaryObjective>
+                    </imsss:objectives>
+                </imsss:sequencing>
+            </item>
+        </organization>
+    </organizations>
+    <resources>
+        <resource identifier="$scorm_id" type="webcontent" adlcp:scormType="sco" href="index.html">
+            <file href="index.html"/>
+        </resource>
+    </resources>
+</manifest>
\ No newline at end of file
diff --git a/_imsmanifest.xml b/_imsmanifest.xml
deleted file mode 100644 (file)
index dc87f9a..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>\r
-<manifest xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2" xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2" identifier="MANIFEST-90878C16-EB60-D648-94ED-9651972B5F38" xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">\r
-    <metadata>\r
-        <schema>ADL SCORM</schema>\r
-        <schemaversion>1.2</schemaversion>\r
-    </metadata>\r
-    <organizations default="$scorm_org">\r
-        <organization identifier="$scorm_org" structure="hierarchical">\r
-            <title>$scorm_title</title>\r
-            <item identifier="$scorm_id" identifierref="$scorm_id" isvisible="true">\r
-                <title>$scorm_title</title>\r
-            </item>\r
-        </organization>\r
-    </organizations>\r
-    <resources>\r
-        <resource type="webcontent" adlcp:scormtype="sco" identifier="$scorm_id" href="index.html">\r
-            <file href="index.html"/>\r
-        </resource>\r
-    </resources>\r
-</manifest>
\ No newline at end of file
index 1fad66fa34222a544c7b3db321abb5f85957b25d..9fe416c1171f89eae2b35e83945d5da2e0955166 100644 (file)
@@ -187,7 +187,7 @@ Fluidbook.prototype = {
             return;
         }
         $(doublePage).find('.' + position).remove();
-        var page = '<div class="page ' + position + '" id="page_' + pageNr + '" data-page="' + pageNr + '"><div class="background" page="' + pageNr + '"></div><div class="clinks"></div><div class="texts" highlight=""></div><div class="shade"></div></div>';
+        var page = '<div class="page ' + position + '" id="page_' + pageNr + '" data-page="' + pageNr + '"><div class="background" page="' + pageNr + '"></div><div class="texts" highlight=""></div><div class="clinks"></div><div class="shade"></div></div>';
         $(doublePage).append(page);
     },
     hidePage: function (position) {
index 6afa2c69688e1a557d03c9d627bc7f697cd5fc8c..7ad6a797c1fcd96c931ab316629da801ee34c8f7 100644 (file)
-/*******************************************************************************\r
- **\r
- ** This software is provided "AS IS," without a warranty of any kind.  ALL\r
- ** EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY\r
- ** IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-\r
- ** INFRINGEMENT, ARE HEREBY EXCLUDED.  PAUL BECKWITH AND HIS LICENSORS SHALL NOT BE LIABLE\r
- ** FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR\r
- ** DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES.  IN NO EVENT WILL PAUL BECKWITH OR ITS\r
- ** LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,\r
- ** INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER\r
- ** CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF\r
- ** OR INABILITY TO USE SOFTWARE, EVEN IF CTC  HAS BEEN ADVISED OF THE POSSIBILITY\r
- ** OF SUCH DAMAGES.\r
- **    javascript:\r
- **          var result = doLMSInitialize();\r
- **          if (result != true)\r
- **          {\r
-**             // handle error\r
-**          }\r
- **\r
- *******************************************************************************/\r
-\r
-\r
-var _Debug = false;  // set this to false to turn debugging off\r
-                     // and get rid of those annoying alert boxes.\r
-\r
-// Define exception/error codes\r
-var _NoError = 0;\r
-var _GeneralException = 101;\r
-var _ServerBusy = 102;\r
-var _InvalidArgumentError = 201;\r
-var _ElementCannotHaveChildren = 202;\r
-var _ElementIsNotAnArray = 203;\r
-var _NotInitialized = 301;\r
-var _NotImplementedError = 401;\r
-var _InvalidSetValue = 402;\r
-var _ElementIsReadOnly = 403;\r
-var _ElementIsWriteOnly = 404;\r
-var _IncorrectDataType = 405;\r
-\r
-\r
-// local variable definitions\r
-var apiHandle = null;\r
-var API = null;\r
-var findAPITries = 0;\r
-\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function: doLMSInitialize()\r
- ** Inputs:  None\r
- ** Return:  CMIBoolean true if the initialization was successful, or\r
- **          CMIBoolean false if the initialization failed.\r
- **\r
- ** Description:\r
- ** Initialize communication with LMS by calling the LMSInitialize\r
- ** function which will be implemented by the LMS.\r
- **\r
- *******************************************************************************/\r
-function doLMSInitialize() {\r
-    startTimer();\r
-\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSInitialize was not successful.");\r
-        return "false";\r
-    }\r
+/*global console*/\r
 \r
-    var result = api.LMSInitialize("");\r
+/* ===========================================================\r
 \r
-    if (result.toString() != "true") {\r
-        var err = ErrorHandler();\r
-    }\r
+pipwerks SCORM Wrapper for JavaScript\r
+v1.1.20160322\r
 \r
-    return result.toString();\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSFinish()\r
- ** Inputs:  None\r
- ** Return:  CMIBoolean true if successful\r
- **          CMIBoolean false if failed.\r
- **\r
- ** Description:\r
- ** Close communication with LMS by calling the LMSFinish\r
- ** function which will be implemented by the LMS\r
- **\r
- *******************************************************************************/\r
-function doLMSFinish() {\r
-\r
-    setSessionTime();\r
-\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSFinish was not successful.");\r
-        return "false";\r
-    }\r
-    else {\r
-        // call the LMSFinish function that should be implemented by the API\r
+Created by Philip Hutchison, January 2008-2016\r
+https://github.com/pipwerks/scorm-api-wrapper\r
 \r
-        var result = api.LMSFinish("");\r
-        if (result.toString() != "true") {\r
-            var err = ErrorHandler();\r
-        }\r
+Copyright (c) Philip Hutchison\r
+MIT-style license: http://pipwerks.mit-license.org/\r
+\r
+This wrapper works with both SCORM 1.2 and SCORM 2004.\r
+\r
+Inspired by APIWrapper.js, created by the ADL and\r
+Concurrent Technologies Corporation, distributed by\r
+the ADL (http://www.adlnet.gov/scorm).\r
+\r
+SCORM.API.find() and SCORM.API.get() functions based\r
+on ADL code, modified by Mike Rustici\r
+(http://www.scorm.com/resources/apifinder/SCORMAPIFinder.htm),\r
+further modified by Philip Hutchison\r
+\r
+=============================================================== */\r
+\r
+\r
+var pipwerks = {};                                  //pipwerks 'namespace' helps ensure no conflicts with possible other "SCORM" variables\r
+pipwerks.UTILS = {};                                //For holding UTILS functions\r
+pipwerks.debug = {isActive: true};                //Enable (true) or disable (false) for debug mode\r
+\r
+pipwerks.SCORM = {                                  //Define the SCORM object\r
+    version: null,                               //Store SCORM version.\r
+    handleCompletionStatus: true,                   //Whether or not the wrapper should automatically handle the initial completion status\r
+    handleExitMode: true,                           //Whether or not the wrapper should automatically handle the exit mode\r
+    API: {\r
+        handle: null,\r
+        isFound: false\r
+    },                 //Create API child object\r
+    connection: {isActive: false},                //Create connection child object\r
+    data: {\r
+        completionStatus: null,\r
+        exitStatus: null\r
+    },               //Create data child object\r
+    debug: {}                                  //Create debug child object\r
+};\r
+\r
+\r
+/* --------------------------------------------------------------------------------\r
+   pipwerks.SCORM.isAvailable\r
+   A simple function to allow Flash ExternalInterface to confirm\r
+   presence of JS wrapper before attempting any LMS communication.\r
+\r
+   Parameters: none\r
+   Returns:    Boolean (true)\r
+----------------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.isAvailable = function () {\r
+    return true;\r
+};\r
+\r
+\r
+// ------------------------------------------------------------------------- //\r
+// --- SCORM.API functions ------------------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
 \r
-    }\r
 \r
-    return result.toString();\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSGetValue(name)\r
- ** Inputs:  name - string representing the cmi data model defined category or\r
- **             element (e.g. cmi.core.student_id)\r
- ** Return:  The value presently assigned by the LMS to the cmi data model\r
- **       element defined by the element or category identified by the name\r
- **       input value.\r
- **\r
- ** Description:\r
- ** Wraps the call to the LMS LMSGetValue method\r
- **\r
- *******************************************************************************/\r
-function doLMSGetValue(name) {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSGetValue was not successful.");\r
-        return "";\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.API.find(window)\r
+   Looks for an object named API in parent and opener windows\r
+\r
+   Parameters: window (the browser window object).\r
+   Returns:    Object if API is found, null if no API found\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.API.find = function (win) {\r
+\r
+    var API = null,\r
+        findAttempts = 0,\r
+        findAttemptLimit = 500,\r
+        traceMsgPrefix = "SCORM.API.find",\r
+        trace = pipwerks.UTILS.trace,\r
+        scorm = pipwerks.SCORM;\r
+\r
+    while ((!win.API && !win.API_1484_11) &&\r
+    (win.parent) &&\r
+    (win.parent != win) &&\r
+    (findAttempts <= findAttemptLimit)) {\r
+\r
+        findAttempts++;\r
+        win = win.parent;\r
+\r
     }\r
-    else {\r
-        var value = api.LMSGetValue(name);\r
-        var errCode = api.LMSGetLastError().toString();\r
-        if (errCode != _NoError) {\r
-            // an error was encountered so display the error description\r
-            var errDescription = api.LMSGetErrorString(errCode);\r
-            console.log("LMSGetValue(" + name + ") failed. \n" + errDescription);\r
-            return "";\r
-        }\r
-        else {\r
 \r
-            return value.toString();\r
+    //If SCORM version is specified by user, look for specific API\r
+    if (scorm.version) {\r
+\r
+        switch (scorm.version) {\r
+\r
+            case "2004" :\r
+\r
+                if (win.API_1484_11) {\r
+\r
+                    API = win.API_1484_11;\r
+\r
+                } else {\r
+\r
+                    trace(traceMsgPrefix + ": SCORM version 2004 was specified by user, but API_1484_11 cannot be found.");\r
+\r
+                }\r
+\r
+                break;\r
+\r
+            case "1.2" :\r
+\r
+                if (win.API) {\r
+\r
+                    API = win.API;\r
+\r
+                } else {\r
+\r
+                    trace(traceMsgPrefix + ": SCORM version 1.2 was specified by user, but API cannot be found.");\r
+\r
+                }\r
+\r
+                break;\r
+\r
         }\r
-    }\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSSetValue(name, value)\r
- ** Inputs:  name -string representing the data model defined category or element\r
- **          value -the value that the named element or category will be assigned\r
- ** Return:  CMIBoolean true if successful\r
- **          CMIBoolean false if failed.\r
- **\r
- ** Description:\r
- ** Wraps the call to the LMS LMSSetValue function\r
- **\r
- *******************************************************************************/\r
-function doLMSSetValue(name, value) {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSSetValue was not successful.");\r
-        return;\r
-    }\r
-    else {\r
-        var result = api.LMSSetValue(name, value);\r
-        if (result.toString() != "true") {\r
-            var err = ErrorHandler();\r
+\r
+    } else {                             //If SCORM version not specified by user, look for APIs\r
+\r
+        if (win.API_1484_11) {            //SCORM 2004-specific API.\r
+\r
+            scorm.version = "2004";      //Set version\r
+            API = win.API_1484_11;\r
+\r
+        } else if (win.API) {              //SCORM 1.2-specific API\r
+\r
+            scorm.version = "1.2";       //Set version\r
+            API = win.API;\r
+\r
         }\r
+\r
     }\r
 \r
-    return;\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSCommit()\r
- ** Inputs:  None\r
- ** Return:  None\r
- **\r
- ** Description:\r
- ** Call the LMSCommit function\r
- **\r
- *******************************************************************************/\r
-function doLMSCommit() {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSCommit was not successful.");\r
-        return "false";\r
+    if (API) {\r
+\r
+        trace(traceMsgPrefix + ": API found. Version: " + scorm.version);\r
+        trace("API: " + API);\r
+\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + ": Error finding API. \nFind attempts: " + findAttempts + ". \nFind attempt limit: " + findAttemptLimit);\r
+\r
     }\r
-    else {\r
-        var result = api.LMSCommit("");\r
-        if (result != "true") {\r
-            var err = ErrorHandler();\r
-        }\r
+\r
+    return API;\r
+\r
+};\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.API.get()\r
+   Looks for an object named API, first in the current window's frame\r
+   hierarchy and then, if necessary, in the current window's opener window\r
+   hierarchy (if there is an opener window).\r
+\r
+   Parameters:  None.\r
+   Returns:     Object if API found, null if no API found\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.API.get = function () {\r
+\r
+    var API = null,\r
+        win = window,\r
+        scorm = pipwerks.SCORM,\r
+        find = scorm.API.find,\r
+        trace = pipwerks.UTILS.trace;\r
+\r
+    API = find(win);\r
+\r
+    if (!API && win.parent && win.parent != win) {\r
+        API = find(win.parent);\r
     }\r
 \r
-    return result.toString();\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSGetLastError()\r
- ** Inputs:  None\r
- ** Return:  The error code that was set by the last LMS function call\r
- **\r
- ** Description:\r
- ** Call the LMSGetLastError function\r
- **\r
- *******************************************************************************/\r
-function doLMSGetLastError() {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSGetLastError was not successful.");\r
-        //since we can't get the error code from the LMS, return a general error\r
-        return _GeneralError;\r
+    if (!API && win.top && win.top.opener) {\r
+        API = find(win.top.opener);\r
     }\r
 \r
-    return api.LMSGetLastError().toString();\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSGetErrorString(errorCode)\r
- ** Inputs:  errorCode - Error Code\r
- ** Return:  The textual description that corresponds to the input error code\r
- **\r
- ** Description:\r
- ** Call the LMSGetErrorString function\r
- **\r
- ********************************************************************************/\r
-function doLMSGetErrorString(errorCode) {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSGetErrorString was not successful.");\r
+    //Special handling for Plateau\r
+    //Thanks to Joseph Venditti for the patch\r
+    if (!API && win.top && win.top.opener && win.top.opener.document) {\r
+        API = find(win.top.opener.document);\r
     }\r
 \r
-    return api.LMSGetErrorString(errorCode).toString();\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function doLMSGetDiagnostic(errorCode)\r
- ** Inputs:  errorCode - Error Code(integer format), or null\r
- ** Return:  The vendor specific textual description that corresponds to the\r
- **          input error code\r
- **\r
- ** Description:\r
- ** Call the LMSGetDiagnostic function\r
- **\r
- *******************************************************************************/\r
-function doLMSGetDiagnostic(errorCode) {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSGetDiagnostic was not successful.");\r
+    if (API) {\r
+        scorm.API.isFound = true;\r
+    } else {\r
+        trace("API.get failed: Can't find the API!");\r
     }\r
 \r
-    return api.LMSGetDiagnostic(errorCode).toString();\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function LMSIsInitialized()\r
- ** Inputs:  none\r
- ** Return:  true if the LMS API is currently initialized, otherwise false\r
- **\r
- ** Description:\r
- ** Determines if the LMS API is currently initialized or not.\r
- **\r
- *******************************************************************************/\r
-function LMSIsInitialized() {\r
-    // there is no direct method for determining if the LMS API is initialized\r
-    // for example an LMSIsInitialized function defined on the API so we'll try\r
-    // a simple LMSGetValue and trap for the LMS Not Initialized Error\r
-\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nLMSIsInitialized() failed.");\r
-        return false;\r
+    return API;\r
+\r
+};\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.API.getHandle()\r
+   Returns the handle to API object if it was previously set\r
+\r
+   Parameters:  None.\r
+   Returns:     Object (the pipwerks.SCORM.API.handle variable).\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.API.getHandle = function () {\r
+\r
+    var API = pipwerks.SCORM.API;\r
+\r
+    if (!API.handle && !API.isFound) {\r
+\r
+        API.handle = API.get();\r
+\r
     }\r
-    else {\r
-        var value = api.LMSGetValue("cmi.core.student_name");\r
-        var errCode = api.LMSGetLastError().toString();\r
-        if (errCode == _NotInitialized) {\r
-            return false;\r
-        }\r
-        else {\r
-            return true;\r
+\r
+    return API.handle;\r
+\r
+};\r
+\r
+\r
+// ------------------------------------------------------------------------- //\r
+// --- pipwerks.SCORM.connection functions --------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.connection.initialize()\r
+   Tells the LMS to initiate the communication session.\r
+\r
+   Parameters:  None\r
+   Returns:     Boolean\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.connection.initialize = function () {\r
+\r
+    var success = false,\r
+        scorm = pipwerks.SCORM,\r
+        completionStatus = scorm.data.completionStatus,\r
+        trace = pipwerks.UTILS.trace,\r
+        makeBoolean = pipwerks.UTILS.StringToBoolean,\r
+        debug = scorm.debug,\r
+        traceMsgPrefix = "SCORM.connection.initialize ";\r
+\r
+    trace("connection.initialize called.");\r
+\r
+    if (!scorm.connection.isActive) {\r
+\r
+        var API = scorm.API.getHandle(),\r
+            errorCode = 0;\r
+\r
+        if (API) {\r
+\r
+            switch (scorm.version) {\r
+                case "1.2" :\r
+                    success = makeBoolean(API.LMSInitialize(""));\r
+                    break;\r
+                case "2004":\r
+                    success = makeBoolean(API.Initialize(""));\r
+                    break;\r
+            }\r
+\r
+            if (success) {\r
+\r
+                //Double-check that connection is active and working before returning 'true' boolean\r
+                errorCode = debug.getCode();\r
+\r
+                if (errorCode !== null && errorCode === 0) {\r
+\r
+                    scorm.connection.isActive = true;\r
+\r
+                    if (scorm.handleCompletionStatus) {\r
+\r
+                        //Automatically set new launches to incomplete\r
+                        completionStatus = scorm.status("get");\r
+\r
+                        if (completionStatus) {\r
+\r
+                            switch (completionStatus) {\r
+\r
+                                //Both SCORM 1.2 and 2004\r
+                                case "not attempted":\r
+                                    scorm.status("set", "incomplete");\r
+                                    scorm.set('cmi.success_status', 'unknown');\r
+                                    break;\r
+\r
+                                //SCORM 2004 only\r
+                                case "unknown" :\r
+                                    scorm.status("set", "incomplete");\r
+                                    scorm.set('cmi.success_status', 'unknown');\r
+                                    break;\r
+\r
+                                //Additional options, presented here in case you'd like to use them\r
+                                //case "completed"  : break;\r
+                                //case "incomplete" : break;\r
+                                //case "passed"     : break;    //SCORM 1.2 only\r
+                                //case "failed"     : break;    //SCORM 1.2 only\r
+                                //case "browsed"    : break;    //SCORM 1.2 only\r
+\r
+                            }\r
+\r
+                            //Commit changes\r
+                            scorm.save();\r
+\r
+                        }\r
+\r
+                    }\r
+\r
+                } else {\r
+\r
+                    success = false;\r
+                    trace(traceMsgPrefix + "failed. \nError code: " + errorCode + " \nError info: " + debug.getInfo(errorCode));\r
+\r
+                }\r
+\r
+            } else {\r
+\r
+                errorCode = debug.getCode();\r
+\r
+                if (errorCode !== null && errorCode !== 0) {\r
+\r
+                    trace(traceMsgPrefix + "failed. \nError code: " + errorCode + " \nError info: " + debug.getInfo(errorCode));\r
+\r
+                } else {\r
+\r
+                    trace(traceMsgPrefix + "failed: No response from server.");\r
+\r
+                }\r
+            }\r
+\r
+        } else {\r
+\r
+            trace(traceMsgPrefix + "failed: API is null.");\r
+\r
         }\r
+\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + "aborted: Connection already active.");\r
+\r
     }\r
-}\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function ErrorHandler()\r
- ** Inputs:  None\r
- ** Return:  The current value of the LMS Error Code\r
- **\r
- ** Description:\r
- ** Determines if an error was encountered by the previous API call\r
- ** and if so, displays a message to the user.  If the error code\r
- ** has associated text it is also displayed.\r
- **\r
- *******************************************************************************/\r
-function ErrorHandler() {\r
-    var api = getAPIHandle();\r
-    if (api == null) {\r
-        console.log("Unable to locate the LMS's API Implementation.\nCannot determine LMS error code.");\r
-        return;\r
-    }\r
 \r
-    // check for errors caused by or from the LMS\r
-    var errCode = api.LMSGetLastError().toString();\r
-    if (errCode != _NoError) {\r
-        // an error was encountered so display the error description\r
-        var errDescription = api.LMSGetErrorString(errCode);\r
-\r
-        if (_Debug == true) {\r
-            errDescription += "\n";\r
-            errDescription += api.LMSGetDiagnostic(null);\r
-            // by passing null to LMSGetDiagnostic, we get any available diagnostics\r
-            // on the previous error.\r
+    return success;\r
+\r
+};\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.connection.terminate()\r
+   Tells the LMS to terminate the communication session\r
+\r
+   Parameters:  None\r
+   Returns:     Boolean\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.connection.terminate = function () {\r
+\r
+    var success = false,\r
+        scorm = pipwerks.SCORM,\r
+        exitStatus = scorm.data.exitStatus,\r
+        completionStatus = scorm.data.completionStatus,\r
+        trace = pipwerks.UTILS.trace,\r
+        makeBoolean = pipwerks.UTILS.StringToBoolean,\r
+        debug = scorm.debug,\r
+        traceMsgPrefix = "SCORM.connection.terminate ";\r
+\r
+\r
+    if (scorm.connection.isActive) {\r
+\r
+        var API = scorm.API.getHandle(),\r
+            errorCode = 0;\r
+\r
+        if (API) {\r
+\r
+            if (scorm.handleExitMode && !exitStatus) {\r
+\r
+                if (completionStatus !== "completed" && completionStatus !== "passed") {\r
+                    switch (scorm.version) {\r
+                        case "1.2" :\r
+                            success = scorm.set("cmi.core.exit", "suspend");\r
+                            break;\r
+                        case "2004":\r
+                            success = scorm.set("cmi.exit", "suspend");\r
+                            break;\r
+                    }\r
+\r
+                } else {\r
+                    switch (scorm.version) {\r
+                        case "1.2" :\r
+                            success = scorm.set("cmi.core.exit", "logout");\r
+                            break;\r
+                        case "2004":\r
+                            success = scorm.set("cmi.exit", "normal");\r
+                            break;\r
+                    }\r
+\r
+                }\r
+\r
+            }\r
+\r
+            //Ensure we persist the data\r
+            success = scorm.save();\r
+\r
+            if (success) {\r
+\r
+                switch (scorm.version) {\r
+                    case "1.2" :\r
+                        success = makeBoolean(API.LMSFinish(""));\r
+                        break;\r
+                    case "2004":\r
+                        success = makeBoolean(API.Terminate(""));\r
+                        break;\r
+                }\r
+\r
+                if (success) {\r
+\r
+                    scorm.connection.isActive = false;\r
+\r
+                } else {\r
+\r
+                    errorCode = debug.getCode();\r
+                    trace(traceMsgPrefix + "failed. \nError code: " + errorCode + " \nError info: " + debug.getInfo(errorCode));\r
+\r
+                }\r
+\r
+            }\r
+\r
+        } else {\r
+\r
+            trace(traceMsgPrefix + "failed: API is null.");\r
+\r
         }\r
 \r
-        console.log(errDescription);\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + "aborted: Connection already terminated.");\r
+\r
     }\r
 \r
-    return errCode;\r
-}\r
-\r
-/******************************************************************************\r
- **\r
- ** Function getAPIHandle()\r
- ** Inputs:  None\r
- ** Return:  value contained by APIHandle\r
- **\r
- ** Description:\r
- ** Returns the handle to API object if it was previously set,\r
- ** otherwise it returns null\r
- **\r
- *******************************************************************************/\r
-function getAPIHandle() {\r
-    if (apiHandle == null) {\r
-        apiHandle = getAPI();\r
+    return success;\r
+\r
+};\r
+\r
+\r
+// ------------------------------------------------------------------------- //\r
+// --- pipwerks.SCORM.data functions --------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.data.get(parameter)\r
+   Requests information from the LMS.\r
+\r
+   Parameter: parameter (string, name of the SCORM data model element)\r
+   Returns:   string (the value of the specified data model element)\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.data.get = function (parameter) {\r
+\r
+    var value = null,\r
+        scorm = pipwerks.SCORM,\r
+        trace = pipwerks.UTILS.trace,\r
+        debug = scorm.debug,\r
+        traceMsgPrefix = "SCORM.data.get('" + parameter + "') ";\r
+\r
+    if (scorm.connection.isActive) {\r
+\r
+        var API = scorm.API.getHandle(),\r
+            errorCode = 0;\r
+\r
+        if (API) {\r
+\r
+            switch (scorm.version) {\r
+                case "1.2" :\r
+                    value = API.LMSGetValue(parameter);\r
+                    break;\r
+                case "2004":\r
+                    value = API.GetValue(parameter);\r
+                    break;\r
+            }\r
+\r
+            errorCode = debug.getCode();\r
+\r
+            //GetValue returns an empty string on errors\r
+            //If value is an empty string, check errorCode to make sure there are no errors\r
+            if (value !== "" || errorCode === 0) {\r
+\r
+                //GetValue is successful.\r
+                //If parameter is lesson_status/completion_status or exit status, let's\r
+                //grab the value and cache it so we can check it during connection.terminate()\r
+                switch (parameter) {\r
+\r
+                    case "cmi.core.lesson_status":\r
+                    case "cmi.completion_status" :\r
+                        scorm.data.completionStatus = value;\r
+                        break;\r
+\r
+                    case "cmi.core.exit":\r
+                    case "cmi.exit"     :\r
+                        scorm.data.exitStatus = value;\r
+                        break;\r
+\r
+                }\r
+\r
+            } else {\r
+\r
+                trace(traceMsgPrefix + "failed. \nError code: " + errorCode + "\nError info: " + debug.getInfo(errorCode));\r
+\r
+            }\r
+\r
+        } else {\r
+\r
+            trace(traceMsgPrefix + "failed: API is null.");\r
+\r
+        }\r
+\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + "failed: API connection is inactive.");\r
+\r
     }\r
 \r
-    return apiHandle;\r
-}\r
-\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function findAPI(win)\r
- ** Inputs:  win - a Window Object\r
- ** Return:  If an API object is found, it's returned, otherwise null is returned\r
- **\r
- ** Description:\r
- ** This function looks for an object named API in parent and opener windows\r
- **\r
- *******************************************************************************/\r
-function findAPI(win) {\r
-    while ((win.API == null) && (win.parent != null) && (win.parent != win)) {\r
-        findAPITries++;\r
-        // Note: 7 is an arbitrary number, but should be more than sufficient\r
-        if (findAPITries > 7) {\r
-            console.log("Error finding API -- too deeply nested.");\r
-            return null;\r
+    trace(traceMsgPrefix + " value: " + value);\r
+\r
+    return String(value);\r
+\r
+};\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.data.set()\r
+   Tells the LMS to assign the value to the named data model element.\r
+   Also stores the SCO's completion status in a variable named\r
+   pipwerks.SCORM.data.completionStatus. This variable is checked whenever\r
+   pipwerks.SCORM.connection.terminate() is invoked.\r
+\r
+   Parameters: parameter (string). The data model element\r
+               value (string). The value for the data model element\r
+   Returns:    Boolean\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.data.set = function (parameter, value) {\r
+\r
+    var success = false,\r
+        scorm = pipwerks.SCORM,\r
+        trace = pipwerks.UTILS.trace,\r
+        makeBoolean = pipwerks.UTILS.StringToBoolean,\r
+        debug = scorm.debug,\r
+        traceMsgPrefix = "SCORM.data.set('" + parameter + "') ";\r
+\r
+\r
+    if (scorm.connection.isActive) {\r
+\r
+        var API = scorm.API.getHandle(),\r
+            errorCode = 0;\r
+\r
+        if (API) {\r
+\r
+            switch (scorm.version) {\r
+                case "1.2" :\r
+                    success = makeBoolean(API.LMSSetValue(parameter, value));\r
+                    break;\r
+                case "2004":\r
+                    success = makeBoolean(API.SetValue(parameter, value));\r
+                    break;\r
+            }\r
+\r
+            if (success) {\r
+\r
+                if (parameter === "cmi.core.lesson_status" || parameter === "cmi.completion_status") {\r
+\r
+                    scorm.data.completionStatus = value;\r
+\r
+                }\r
+\r
+            } else {\r
+\r
+                errorCode = debug.getCode();\r
+\r
+                trace(traceMsgPrefix + "failed. \nError code: " + errorCode + ". \nError info: " + debug.getInfo(errorCode));\r
+\r
+            }\r
+\r
+        } else {\r
+\r
+            trace(traceMsgPrefix + "failed: API is null.");\r
+\r
         }\r
 \r
-        win = win.parent;\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + "failed: API connection is inactive.");\r
 \r
     }\r
-    return win.API;\r
-}\r
-\r
-\r
-/*******************************************************************************\r
- **\r
- ** Function getAPI()\r
- ** Inputs:  none\r
- ** Return:  If an API object is found, it's returned, otherwise null is returned\r
- **\r
- ** Description:\r
- ** This function looks for an object named API, first in the current window's\r
- ** frame hierarchy and then, if necessary, in the current window's opener window\r
- ** hierarchy (if there is an opener window).\r
- **\r
- *******************************************************************************/\r
-function getAPI() {\r
-    var theAPI = findAPI(window);\r
-    if ((theAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) {\r
-        theAPI = findAPI(window.opener);\r
+\r
+    trace(traceMsgPrefix + " value: " + value);\r
+\r
+    return success;\r
+\r
+};\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.data.save()\r
+   Instructs the LMS to persist all data to this point in the session\r
+\r
+   Parameters: None\r
+   Returns:    Boolean\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.data.save = function () {\r
+\r
+    var success = false,\r
+        scorm = pipwerks.SCORM,\r
+        trace = pipwerks.UTILS.trace,\r
+        makeBoolean = pipwerks.UTILS.StringToBoolean,\r
+        traceMsgPrefix = "SCORM.data.save failed";\r
+\r
+\r
+    if (scorm.connection.isActive) {\r
+\r
+        var API = scorm.API.getHandle();\r
+\r
+        if (API) {\r
+\r
+            switch (scorm.version) {\r
+                case "1.2" :\r
+                    success = makeBoolean(API.LMSCommit(""));\r
+                    break;\r
+                case "2004":\r
+                    success = makeBoolean(API.Commit(""));\r
+                    break;\r
+            }\r
+\r
+        } else {\r
+\r
+            trace(traceMsgPrefix + ": API is null.");\r
+\r
+        }\r
+\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + ": API connection is inactive.");\r
+\r
     }\r
-    if (theAPI == null) {\r
-        console.log("Unable to find an API adapter");\r
+\r
+    return success;\r
+\r
+};\r
+\r
+\r
+pipwerks.SCORM.status = function (action, status) {\r
+\r
+    var success = false,\r
+        scorm = pipwerks.SCORM,\r
+        trace = pipwerks.UTILS.trace,\r
+        traceMsgPrefix = "SCORM.getStatus failed",\r
+        cmi = "";\r
+\r
+    if (action !== null) {\r
+\r
+        switch (scorm.version) {\r
+            case "1.2" :\r
+                cmi = "cmi.core.lesson_status";\r
+                break;\r
+            case "2004":\r
+                cmi = "cmi.completion_status";\r
+                break;\r
+        }\r
+\r
+        switch (action) {\r
+\r
+            case "get":\r
+                success = scorm.data.get(cmi);\r
+                break;\r
+\r
+            case "set":\r
+                if (status !== null) {\r
+\r
+                    success = scorm.data.set(cmi, status);\r
+\r
+                } else {\r
+\r
+                    success = false;\r
+                    trace(traceMsgPrefix + ": status was not specified.");\r
+\r
+                }\r
+\r
+                break;\r
+\r
+            default      :\r
+                success = false;\r
+                trace(traceMsgPrefix + ": no valid action was specified.");\r
+\r
+        }\r
+\r
+    } else {\r
+\r
+        trace(traceMsgPrefix + ": action was not specified.");\r
+\r
     }\r
-    return theAPI\r
-}\r
 \r
+    return success;\r
+\r
+};\r
+\r
+\r
+// ------------------------------------------------------------------------- //\r
+// --- pipwerks.SCORM.debug functions -------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
+\r
+\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.debug.getCode\r
+   Requests the error code for the current error state from the LMS\r
+\r
+   Parameters: None\r
+   Returns:    Integer (the last error code).\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.debug.getCode = function () {\r
+\r
+    var scorm = pipwerks.SCORM,\r
+        API = scorm.API.getHandle(),\r
+        trace = pipwerks.UTILS.trace,\r
+        code = 0;\r
 \r
-var startTime;\r
+    if (API) {\r
 \r
-function startTimer() {\r
-    startTime = new Date().getTime();\r
-}\r
+        switch (scorm.version) {\r
+            case "1.2" :\r
+                code = parseInt(API.LMSGetLastError(), 10);\r
+                break;\r
+            case "2004":\r
+                code = parseInt(API.GetLastError(), 10);\r
+                break;\r
+        }\r
+\r
+    } else {\r
 \r
+        trace("SCORM.debug.getCode failed: API is null.");\r
 \r
-// call function right before LMSFinish\r
-function setSessionTime() {\r
+    }\r
 \r
-    var currentTime = new Date();\r
+    return code;\r
 \r
-    var endTime = currentTime.getTime()\r
+};\r
 \r
-    var calculatedTime = endTime - startTime;\r
 \r
-    var totalHours = Math.floor(calculatedTime / 1000 / 60 / 60);\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.debug.getInfo()\r
+   "Used by a SCO to request the textual description for the error code\r
+   specified by the value of [errorCode]."\r
 \r
-    calculatedTime = calculatedTime - totalHours * 1000 * 60 * 60\r
+   Parameters: errorCode (integer).\r
+   Returns:    String.\r
+----------------------------------------------------------------------------- */\r
 \r
-    if (totalHours < 1000 && totalHours > 99) {\r
+pipwerks.SCORM.debug.getInfo = function (errorCode) {\r
 \r
-        totalHours = "0" + totalHours;\r
+    var scorm = pipwerks.SCORM,\r
+        API = scorm.API.getHandle(),\r
+        trace = pipwerks.UTILS.trace,\r
+        result = "";\r
 \r
-    } else if (totalHours < 100 && totalHours > 9) {\r
 \r
-        totalHours = "00" + totalHours;\r
+    if (API) {\r
+\r
+        switch (scorm.version) {\r
+            case "1.2" :\r
+                result = API.LMSGetErrorString(errorCode.toString());\r
+                break;\r
+            case "2004":\r
+                result = API.GetErrorString(errorCode.toString());\r
+                break;\r
+        }\r
 \r
-    } else if (totalHours < 10) {\r
+    } else {\r
 \r
-        totalHours = "000" + totalHours;\r
+        trace("SCORM.debug.getInfo failed: API is null.");\r
 \r
     }\r
 \r
+    return String(result);\r
 \r
-    var totalMinutes = Math.floor(calculatedTime / 1000 / 60);\r
+};\r
 \r
-    calculatedTime = calculatedTime - totalMinutes * 1000 * 60;\r
 \r
-    if (totalMinutes < 10) {\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.SCORM.debug.getDiagnosticInfo\r
+   "Exists for LMS specific use. It allows the LMS to define additional\r
+   diagnostic information through the API Instance."\r
 \r
-        totalMinutes = "0" + totalMinutes;\r
+   Parameters: errorCode (integer).\r
+   Returns:    String (Additional diagnostic information about the given error code).\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.SCORM.debug.getDiagnosticInfo = function (errorCode) {\r
+\r
+    var scorm = pipwerks.SCORM,\r
+        API = scorm.API.getHandle(),\r
+        trace = pipwerks.UTILS.trace,\r
+        result = "";\r
+\r
+    if (API) {\r
+\r
+        switch (scorm.version) {\r
+            case "1.2" :\r
+                result = API.LMSGetDiagnostic(errorCode);\r
+                break;\r
+            case "2004":\r
+                result = API.GetDiagnostic(errorCode);\r
+                break;\r
+        }\r
+\r
+    } else {\r
+\r
+        trace("SCORM.debug.getDiagnosticInfo failed: API is null.");\r
 \r
     }\r
 \r
+    return String(result);\r
+\r
+};\r
+\r
+\r
+// ------------------------------------------------------------------------- //\r
+// --- Shortcuts! ---------------------------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
+\r
+// Because nobody likes typing verbose code.\r
+\r
+pipwerks.SCORM.init = pipwerks.SCORM.connection.initialize;\r
+pipwerks.SCORM.get = pipwerks.SCORM.data.get;\r
+pipwerks.SCORM.set = pipwerks.SCORM.data.set;\r
+pipwerks.SCORM.save = pipwerks.SCORM.data.save;\r
+pipwerks.SCORM.quit = pipwerks.SCORM.connection.terminate;\r
+\r
 \r
-    var totalSeconds = Math.floor(calculatedTime / 1000);\r
+// ------------------------------------------------------------------------- //\r
+// --- pipwerks.UTILS functions -------------------------------------------- //\r
+// ------------------------------------------------------------------------- //\r
 \r
-    if (totalSeconds < 10) {\r
 \r
-        totalSeconds = "0" + totalSeconds;\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.UTILS.StringToBoolean()\r
+   Converts 'boolean strings' into actual valid booleans.\r
 \r
+   (Most values returned from the API are the strings "true" and "false".)\r
+\r
+   Parameters: String\r
+   Returns:    Boolean\r
+---------------------------------------------------------------------------- */\r
+\r
+pipwerks.UTILS.StringToBoolean = function (value) {\r
+    var t = typeof value;\r
+    switch (t) {\r
+        //typeof new String("true") === "object", so handle objects as string via fall-through.\r
+        //See https://github.com/pipwerks/scorm-api-wrapper/issues/3\r
+        case "object":\r
+        case "string":\r
+            return (/(true|1)/i).test(value);\r
+        case "number":\r
+            return !!value;\r
+        case "boolean":\r
+            return value;\r
+        case "undefined":\r
+            return null;\r
+        default:\r
+            return false;\r
     }\r
+};\r
 \r
-    var sessionTime = totalHours + ":" + totalMinutes + ":" + totalSeconds;\r
 \r
-    doLMSSetValue("cmi.core.session_time", sessionTime);\r
+/* -------------------------------------------------------------------------\r
+   pipwerks.UTILS.trace()\r
+   Displays error messages when in debug mode.\r
 \r
-}\r
+   Parameters: msg (string)\r
+   Return:     None\r
+---------------------------------------------------------------------------- */\r
 \r
+pipwerks.UTILS.trace = function (msg) {\r
 \r
+    if (pipwerks.debug.isActive) {\r
+\r
+        if (window.console && window.console.log) {\r
+            window.console.log(msg);\r
+        } else {\r
+            //alert(msg);\r
+        }\r
+\r
+    }\r
+};\r
index 4365ec89ad0800b8f929a50ba99dca98fd18326e..c9ec652fe11b9c45bc56c06b44ae1e7aa5baaeac 100644 (file)
@@ -1,26 +1,73 @@
 SCORM = true;\r
+SCORM_INITED = false;\r
+SCORM_START_TIME = null;\r
+SCORM_INTERACTION_TIMESTAMPS = [];\r
+SCORM_CORRECT_ANSWERS = [];\r
+SCORM_ID_TO_N = {};\r
+SCORM_WEIGHTING = 0;\r
+SCORM_QUESTIONS = [];\r
+SCORM_SUCCESS_STATUS = 'unknown';\r
+SCORM_SUCCESS_SCORE = 0;\r
+\r
+var _CMI12 = {\r
+    'location': 'cmi.core.lesson_location',\r
+    'status': "cmi.core.lesson_status",\r
+    'session_time': 'cmi.core.session_time',\r
+    'success_status': '',\r
+};\r
+\r
+var _CMI2004 = {\r
+    'location': 'cmi.location',\r
+    'status': 'cmi.completion_status',\r
+    'session_time': 'cmi.session_time',\r
+    'success_status': 'cmi.success_status',\r
+}\r
+\r
 $(function () {\r
     if (fluidbook) {\r
-        initScormEvents();\r
+        initScorm();\r
     } else {\r
         $(document).on('fluidbookready', function () {\r
-            initScormEvents();\r
+            initScorm();\r
         });\r
     }\r
 });\r
 \r
+function initScorm() {\r
+    if (SCORM_INITED) {\r
+        return;\r
+    }\r
+    SCORM_INITED = true;\r
+    pipwerks.SCORM.init();\r
+    startScormTimer();\r
+    initScormEvents();\r
+    initScormInteractions();\r
+}\r
+\r
+function _cmi(key) {\r
+    var res = null;\r
+    switch (pipwerks.SCORM.version) {\r
+        case "1.2" :\r
+            res = _CMI12[key];\r
+            break;\r
+        case '2004':\r
+            res = _CMI2004[key];\r
+            break;\r
+    }\r
+    if (res == undefined || res == null) {\r
+        res = key;\r
+    }\r
+    return res;\r
+}\r
+\r
 function initScormEvents() {\r
-    doLMSInitialize();\r
+    initScorm();\r
+\r
     $(window).on('unload', function () {\r
-        doLMSFinish();\r
+        finishScorm();\r
     });\r
 \r
-    var currentStatus = getScormValue('cmi.core.lesson_status');\r
-    if (currentStatus != 'passed' && currentStatus != 'completed') {\r
-        setScormValue('cmi.core.lesson_status', 'incomplete');\r
-    }\r
-\r
-    var currentPage = getScormValue('cmi.core.lesson_location');\r
+    var currentPage = getScormValue('location');\r
     try {\r
         if (currentPage != '') {\r
             var e = currentPage.split('_');\r
@@ -29,28 +76,220 @@ function initScormEvents() {
             }\r
         }\r
     } catch (err) {\r
-        fb(err);\r
+        console.log(err);\r
     }\r
 \r
     $(fluidbook).on('changePage', function (e, page) {\r
-        setScormValue('cmi.core.lesson_location', 'page_' + page);\r
-        doLMSCommit();\r
+        setScormValue('location', 'page_' + page);\r
     });\r
 }\r
 \r
+function finishScorm() {\r
+    setSessionTime();\r
+    pipwerks.SCORM.quit();\r
+}\r
+\r
+function startScormTimer() {\r
+    SCORM_START_TIME = new Date();\r
+}\r
+\r
 function scormComplete() {\r
-    setScormValue("cmi.core.lesson_status", "completed");\r
+    setScormValue('status', "completed");\r
     window.close();\r
-    doLMSFinish();\r
+    finishScorm();\r
 }\r
 \r
 function getScormValue(elementName) {\r
-    var result = String(doLMSGetValue(elementName));\r
+    var cmi = _cmi(elementName);\r
+    if (cmi == '') {\r
+        return null;\r
+    }\r
+    var result = pipwerks.SCORM.get(cmi);\r
     return result;\r
 }\r
 \r
 function setScormValue(elementName, value) {\r
-    var result = doLMSSetValue(elementName, value);\r
-    doLMSCommit();\r
+    var cmi = _cmi(elementName);\r
+    if (cmi == '') {\r
+        return false;\r
+    }\r
+    var result = pipwerks.SCORM.set(cmi, value);\r
+    pipwerks.SCORM.save();\r
     return result;\r
+}\r
+\r
+function initScormInteractions() {\r
+    if (fluidbook.datas.scorm_quizdata === '') {\r
+        return;\r
+    }\r
+    var n = 0;\r
+    for (var sheet in fluidbook.datas.scorm_quizdata) {\r
+        var s = fluidbook.datas.scorm_quizdata[sheet];\r
+        for (var l in s) {\r
+            if (l == 0) {\r
+                continue;\r
+            }\r
+            var line = s[l];\r
+            if (line[1] === null || line[1] === '') {\r
+                continue;\r
+            }\r
+            defineScormInteraction(n, line[0], line[2], line[1], line[3], parseFloat(line[4]));\r
+            n++;\r
+        }\r
+    }\r
+    if (SCORM_WEIGHTING > 0) {\r
+        pipwerks.SCORM.set('cmi.score.min', '0');\r
+        pipwerks.SCORM.set('cmi.score.max', SCORM_WEIGHTING);\r
+        SCORM_SUCCESS_SCORE = parseFloat(pipwerks.SCORM.get('cmi.scaled_passing_score'));\r
+\r
+        updateInteractionsScore();\r
+    }\r
+}\r
+\r
+function defineScormInteraction(number, id, type, question, correct_response, weighting) {\r
+\r
+    if (weighting === undefined) {\r
+        weighting = 1;\r
+    }\r
+    if (question === undefined) {\r
+        question = '';\r
+    }\r
+    if (type === undefined) {\r
+        type = 'other';\r
+    }\r
+    if (id === undefined) {\r
+        id = 'Q' + number;\r
+    }\r
+\r
+    SCORM_QUESTIONS[number] = {\r
+        id: id,\r
+        question: question,\r
+        type: type,\r
+        correct_response: correct_response,\r
+        weighting: weighting,\r
+        timestamp: null,\r
+        result: false\r
+    };\r
+    SCORM_ID_TO_N[id] = number;\r
+\r
+    setScormValue('cmi.interactions.' + number + '.id', id);\r
+    setScormValue('cmi.interactions.' + number + '.description', question);\r
+    setScormValue('cmi.interactions.' + number + '.type', type);\r
+    setScormValue('cmi.interactions.' + number + '.correct_responses.0.pattern', correct_response);\r
+    setScormValue('cmi.interactions.' + number + '.weighting', weighting);\r
+\r
+    SCORM_WEIGHTING += weighting;\r
+}\r
+\r
+function getLearnerResponseById(id) {\r
+    return getScormValue('cmi.interactions.' + scormIdToN(id) + '.learner_response');\r
+}\r
+\r
+function scormIdToN(id) {\r
+    return SCORM_ID_TO_N[id];\r
+}\r
+\r
+function displayScormInteraction(id) {\r
+    var n = scormIdToN(id);\r
+    var now = new Date();\r
+    if (SCORM_QUESTIONS[n].timestamp !== null) {\r
+        return;\r
+    }\r
+    SCORM_QUESTIONS[n].timestamp = now;\r
+    setScormValue('cmi.interactions.' + n + '.timestamp', dateToScormTime(now));\r
+}\r
+\r
+function answerScormInteraction(id, learner_response, result, set_latency) {\r
+    var n = scormIdToN(id);\r
+    var currentTime = Math.round(new Date().getTime() / 1000);\r
+    var latency = getScormTimeInterval(SCORM_QUESTIONS[n].timestamp, new Date());\r
+    if (result === undefined) {\r
+        result = (learner_response === SCORM_QUESTIONS[n].correct_response);\r
+    }\r
+\r
+    setScormValue('cmi.interactions.' + n + '.result', result ? 'correct' : 'incorrect');\r
+    setScormValue('cmi.interactions.' + n + '.learner_response', learner_response);\r
+    setScormValue('cmi.interactions.' + n + '.latency', latency);\r
+\r
+    updateInteractionsScore();\r
+}\r
+\r
+function updateInteractionsScore() {\r
+    var score = 0;\r
+    var answered = 0;\r
+    $.each(SCORM_QUESTIONS, function (n, q) {\r
+        var r = getScormValue('cmi.interactions.' + n + '.result');\r
+        if (r == '') {\r
+            return;\r
+        }\r
+        answered++;\r
+        if (r == 'correct') {\r
+            score += q.weighting;\r
+        }\r
+    });\r
+\r
+    var scaled = score / SCORM_WEIGHTING;\r
+\r
+    setScormValue('cmi.score.raw', score);\r
+    setScormValue('cmi.score.scaled', scaled);\r
+    var status = 'unknown'\r
+    if (answered > 0) {\r
+        if (scaled >= SCORM_SUCCESS_SCORE) {\r
+            status = 'passed';\r
+        } else {\r
+            status = 'failed';\r
+        }\r
+    }\r
+    setScormValue('cmi.success_status', status);\r
+}\r
+\r
+function setSessionTime() {\r
+    var currentTime = new Date();\r
+    var sessionTime;\r
+\r
+    var endTime = currentTime.getTime()\r
+    var calculatedTime = endTime - SCORM_START_TIME;\r
+\r
+    if (pipwerks.SCORM.version == '1.2') {\r
+        var totalHours = Math.floor(calculatedTime / 1000 / 60 / 60);\r
+        calculatedTime = calculatedTime - totalHours * 1000 * 60 * 60\r
+        if (totalHours < 1000 && totalHours > 99) {\r
+            totalHours = "0" + totalHours;\r
+        } else if (totalHours < 100 && totalHours > 9) {\r
+            totalHours = "00" + totalHours;\r
+        } else if (totalHours < 10) {\r
+            totalHours = "000" + totalHours;\r
+        }\r
+\r
+        var totalMinutes = Math.floor(calculatedTime / 1000 / 60);\r
+        calculatedTime = calculatedTime - totalMinutes * 1000 * 60;\r
+        if (totalMinutes < 10) {\r
+            totalMinutes = "0" + totalMinutes;\r
+        }\r
+\r
+        var totalSeconds = Math.floor(calculatedTime / 1000);\r
+        if (totalSeconds < 10) {\r
+            totalSeconds = "0" + totalSeconds;\r
+        }\r
+        sessionTime = totalHours + ":" + totalMinutes + ":" + totalSeconds;\r
+    } else {\r
+        sessionTime = Math.round(calculatedTime / 1000);\r
+    }\r
+    setScormValue('session_time', sessionTime);\r
+}\r
+\r
+function dateToScormTime(date) {\r
+    var res = date.toISOString();\r
+    var e = res.split('.');\r
+    e.pop();\r
+    return res.join('.');\r
+}\r
+\r
+function getScormTimeInterval(start, end) {\r
+    var diff = Math.round((end.getTime() - start.getTime()) / 1000);\r
+    var h = Math.floor(diff / 3600);\r
+    diff = diff % 3600;\r
+    var m = Math.floor(diff / 60);\r
+    var s = diff % 60;\r
+    return 'PT' + h + 'H' + m + 'M' + s + 'S';\r
 }
\ No newline at end of file