├── .gitignore ├── package.json ├── readme.md └── src ├── AS2 └── com │ └── pipwerks │ └── SCORM.as ├── AS3 └── com │ └── pipwerks │ └── SCORM.as └── JavaScript └── SCORM_API_wrapper.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scorm-api-wrapper", 3 | "version": "1.1.20160322", 4 | "description": "The pipwerks SCORM API Wrapper ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/pipwerks/scorm-api-wrapper" 12 | }, 13 | "keywords": [ 14 | "SCORM" 15 | ], 16 | "author": "Philip Hutchison (pipwerks)", 17 | "license": "MIT-style license: http://pipwerks.mit-license.org/", 18 | "bugs": { 19 | "url": "https://github.com/pipwerks/scorm-api-wrapper/issues" 20 | }, 21 | "homepage": "https://github.com/pipwerks/scorm-api-wrapper" 22 | } 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Created by Philip Hutchison, January 2008 2 | https://github.com/pipwerks/scorm-api-wrapper 3 | 4 | Copyright (c) Philip Hutchison 5 | MIT-style license: http://pipwerks.mit-license.org/ 6 | 7 | Inspired by APIWrapper.js, which was a demo file created by the ADL and Concurrent Technologies Corporation. 8 | 9 | The SCORM.API.find() and SCORM.API.get() functions are based on ADL code, which was modified by Mike Rustici (http://www.scorm.com), then further modified by Philip Hutchison. 10 | 11 | WARNING: Use at your own risk! These files are provided as-is with no implied warranties or guarantees. 12 | 13 | ##Background 14 | 15 | These wrappers are intended to make your life easier so you don't need to be a SCORM expert to add SCORM support to your e-learning course. 16 | 17 | The SCORM API wrappers are an abstraction layer that makes adding SCORM code to your course a much simpler, less confusing task. They provide simple logic and error-checking for your course's SCORM code, and include some auto-handling, such as setting cmi.exit (aka cmi.core.exit) when exiting a course. 18 | 19 | The SCORM API Wrappers come in three varieties: 20 | 21 | * JavaScript 22 | * ActionScript 2 (AS2) 23 | * ActionScript 3 (AS3) 24 | 25 | All three wrappers are SCORM version-agnostic, and will work with both SCORM 1.2 and SCORM 2004. Both of the ActionScript wrappers require the JavaScript wrapper; the AS wrappers use ExternalInterface to invoke functions contained in the JavaScript wrapper. If the JS wrapper is not present, the ActionScript wrappers will not work. 26 | 27 | The ActionScript wrappers use ExternalInterface to communicate with JavaScript. ExternalInterface will not work in a local environment unless you change your Flash Player security settings. 28 | -------------------------------------------------------------------------------- /src/AS2/com/pipwerks/SCORM.as: -------------------------------------------------------------------------------- 1 | /* 2 | pipwerks SCORM Wrapper for ActionScript 2 3 | v1.1.20111123 4 | 5 | Created by Philip Hutchison, January 2008 6 | https://github.com/pipwerks/scorm-api-wrapper 7 | 8 | Copyright (c) Philip Hutchison 9 | MIT-style license: http://pipwerks.mit-license.org/ 10 | 11 | FLAs published using this file must be published using AS2. 12 | SWFs will only work in Flash Player 8 or higher. 13 | 14 | This wrapper is designed to be SCORM version-neutral (it works 15 | with SCORM 1.2 and SCORM 2004). It also requires the pipwerks 16 | SCORM Wrapper for JavaScript in the course's HTML file. The 17 | wrapper can be downloaded from http://github.com/pipwerks/scorm-api-wrapper/ 18 | 19 | This class uses ExternalInterface. Testing in a local environment 20 | will FAIL unless you set your Flash Player settings to allow local 21 | SWFs to execute ExternalInterface commands. 22 | 23 | Use at your own risk! This class is provided as-is with no implied warranties or guarantees. 24 | */ 25 | 26 | import flash.external.*; 27 | 28 | class com.pipwerks.SCORM { 29 | 30 | private var __connectionActive:Boolean = false, 31 | __debugActive:Boolean = true; //Enable (true) or disable (false) 32 | 33 | 34 | public function SCORM() { 35 | 36 | var is_EI_available:Boolean = ExternalInterface.available, 37 | wrapperFound:Boolean = false, 38 | debugMsg:String = "Initializing SCORM class. Checking dependencies: "; 39 | 40 | if(is_EI_available){ 41 | 42 | debugMsg += "ExternalInterface.available evaluates true. "; 43 | 44 | wrapperFound = Boolean(ExternalInterface.call("pipwerks.SCORM.isAvailable")); 45 | debugMsg += "SCORM.isAvailable() evaluates " +String(wrapperFound) +". "; 46 | 47 | if(wrapperFound){ 48 | 49 | debugMsg += "SCORM class file ready to go! :) "; 50 | 51 | } else { 52 | 53 | debugMsg += "The required JavaScript SCORM API wrapper cannot be found in the HTML document. Course cannot load."; 54 | 55 | } 56 | 57 | } else { 58 | 59 | debugMsg += "ExternalInterface is NOT available (this may be due to an outdated version of Flash Player). Course cannot load."; 60 | 61 | } 62 | 63 | __displayDebugInfo(debugMsg); 64 | 65 | } 66 | 67 | 68 | 69 | // --- public functions --------------------------------------------- // 70 | 71 | 72 | public function set debugMode(status:Boolean):Void { 73 | this.__debugActive = status; 74 | } 75 | 76 | public function get debugMode():Boolean { 77 | return this.__debugActive; 78 | } 79 | 80 | public function connect():Boolean { 81 | __displayDebugInfo("pipwerks.SCORM.connect() called from class file"); 82 | return __connect(); 83 | } 84 | 85 | public function disconnect():Boolean { 86 | __displayDebugInfo("pipwerks.SCORM.disconnect() called from class file"); 87 | return __disconnect(); 88 | } 89 | 90 | public function get(param:String):String { 91 | var str:String = __get(param); 92 | __displayDebugInfo("data returned from LMS: " +str); 93 | return str; 94 | } 95 | 96 | public function set(parameter:String, value:String):Boolean { 97 | return __set(parameter, value); 98 | } 99 | 100 | public function save():Boolean { 101 | return __save(); 102 | } 103 | 104 | 105 | 106 | // --- private functions --------------------------------------------- // 107 | 108 | 109 | private function __connect():Boolean { 110 | 111 | var result:Boolean = false; 112 | if(!__connectionActive){ 113 | 114 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.init")); 115 | result = __stringToBoolean(eiCall); 116 | 117 | if (result){ 118 | __connectionActive = true; 119 | } else { 120 | var errorCode:Number = __getDebugCode(); 121 | if(errorCode){ 122 | var debugInfo:String = __getDebugInfo(errorCode); 123 | __displayDebugInfo("pipwerks.SCORM.init() failed. \n" 124 | +"Error code: " +errorCode +"\n" 125 | +"Error info: " +debugInfo); 126 | } else { 127 | __displayDebugInfo("pipwerks.SCORM.init failed: no response from server."); 128 | } 129 | } 130 | } else { 131 | __displayDebugInfo("pipwerks.SCORM.init aborted: connection already active."); 132 | } 133 | 134 | __displayDebugInfo("__connectionActive: " +__connectionActive); 135 | 136 | return result; 137 | } 138 | 139 | 140 | private function __disconnect():Boolean { 141 | 142 | var result:Boolean = false; 143 | if(__connectionActive){ 144 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.quit")); 145 | result = __stringToBoolean(eiCall); 146 | if (result){ 147 | __connectionActive = false; 148 | } else { 149 | var errorCode:Number = __getDebugCode(); 150 | var debugInfo:String = __getDebugInfo(errorCode); 151 | __displayDebugInfo("pipwerks.SCORM.quit() failed. \n" 152 | +"Error code: " +errorCode +"\n" 153 | +"Error info: " +debugInfo); 154 | } 155 | } else { 156 | __displayDebugInfo("pipwerks.SCORM.quit aborted: connection already inactive."); 157 | } 158 | return result; 159 | } 160 | 161 | 162 | private function __get(parameter:String):String { 163 | 164 | var returnedValue:String = ""; 165 | 166 | if (__connectionActive){ 167 | 168 | returnedValue = String(ExternalInterface.call("pipwerks.SCORM.get", parameter)); 169 | var errorCode:Number = __getDebugCode(); 170 | 171 | //GetValue returns an empty string on errors 172 | //Double-check errorCode to make sure empty string 173 | //is really an error and not field value 174 | if (returnedValue == "" && errorCode != 0){ 175 | var debugInfo:String = __getDebugInfo(errorCode); 176 | __displayDebugInfo("pipwerks.SCORM.get(" +parameter +") failed. \n" 177 | +"Error code: " +errorCode +"\n" 178 | +"Error info: " +debugInfo); 179 | } 180 | } else { 181 | __displayDebugInfo("pipwerks.SCORM.get(" +parameter +") failed: connection is inactive."); 182 | } 183 | return returnedValue; 184 | } 185 | 186 | 187 | private function __set(parameter:String, value:String):Boolean { 188 | 189 | var result:Boolean = false; 190 | if (__connectionActive){ 191 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.set", parameter, value)); 192 | result = __stringToBoolean(eiCall); 193 | if(!result){ 194 | var errorCode:Number = __getDebugCode(); 195 | var debugInfo:String = __getDebugInfo(errorCode); 196 | __displayDebugInfo("pipwerks.SCORM.set(" +parameter +") failed. \n" 197 | +"Error code: " +errorCode +"\n" 198 | +"Error info: " +debugInfo); 199 | } 200 | } else { 201 | __displayDebugInfo("pipwerks.SCORM.set(" +parameter +") failed: connection is inactive."); 202 | } 203 | return result; 204 | } 205 | 206 | 207 | private function __save():Boolean { 208 | 209 | var result:Boolean = false; 210 | if(__connectionActive){ 211 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.save")); 212 | result = __stringToBoolean(eiCall); 213 | if(!result){ 214 | var errorCode:Number = __getDebugCode(); 215 | var debugInfo:String = __getDebugInfo(errorCode); 216 | __displayDebugInfo("pipwerks.SCORM.save() failed. \n" 217 | +"Error code: " +errorCode +"\n" 218 | +"Error info: " +debugInfo); 219 | } 220 | } else { 221 | __displayDebugInfo("pipwerks.SCORM.save() failed: API connection is inactive."); 222 | } 223 | return result; 224 | } 225 | 226 | 227 | // --- debug functions ----------------------------------------------- // 228 | 229 | private function __getDebugCode():Number { 230 | var code:Number = Number(ExternalInterface.call("pipwerks.SCORM.debug.getCode")); 231 | return code; 232 | } 233 | 234 | private function __getDebugInfo(errorCode:Number):String { 235 | var result:String = String(ExternalInterface.call("pipwerks.SCORM.debug.getInfo", errorCode)); 236 | return result; 237 | } 238 | 239 | private function __getDiagnosticInfo(errorCode:Number):String { 240 | var result:String = String(ExternalInterface.call("pipwerks.SCORM.debug.getDiagnosticInfo", errorCode)); 241 | return result; 242 | } 243 | 244 | private function __displayDebugInfo(msg:String):Void { 245 | if(__debugActive){ 246 | trace(msg); 247 | ExternalInterface.call("pipwerks.UTILS.trace", msg); 248 | } 249 | } 250 | 251 | //Purposely not typing the variable 'value' 252 | private function __stringToBoolean(value):Boolean { 253 | 254 | var t:String = typeof value; 255 | 256 | switch(t){ 257 | 258 | case "boolean": return value; 259 | case "number": return !!value; 260 | case "undefined": return null; 261 | case "string": 262 | var str:String = value.toLowerCase(); 263 | return (str === "true" || str === "1"); 264 | 265 | default: return false; 266 | 267 | } 268 | 269 | } 270 | 271 | } // end SCORM class -------------------------------------------------------------------------------- /src/AS3/com/pipwerks/SCORM.as: -------------------------------------------------------------------------------- 1 | /* 2 | pipwerks SCORM Wrapper for ActionScript 3 3 | v1.1.20111123 4 | 5 | Created by Philip Hutchison, January 2008 6 | https://github.com/pipwerks/scorm-api-wrapper 7 | 8 | Copyright (c) Philip Hutchison 9 | MIT-style license: http://pipwerks.mit-license.org/ 10 | 11 | FLAs published using this file must be published using AS3. 12 | SWFs will only work in Flash Player 9 or higher. 13 | 14 | This wrapper is designed to be SCORM version-neutral (it works 15 | with SCORM 1.2 and SCORM 2004). It also requires the pipwerks 16 | SCORM API JavaScript wrapper in the course's HTML file. The 17 | wrapper can be downloaded from http://github.com/pipwerks/scorm-api-wrapper/ 18 | 19 | This class uses ExternalInterface. Testing in a local environment 20 | will FAIL unless you set your Flash Player settings to allow local 21 | SWFs to execute ExternalInterface commands. 22 | 23 | Use at your own risk! This class is provided as-is with no implied warranties or guarantees. 24 | */ 25 | 26 | package com.pipwerks { 27 | 28 | import flash.external.*; 29 | 30 | public class SCORM { 31 | 32 | private var __connectionActive:Boolean = false, 33 | __debugActive:Boolean = true; 34 | 35 | 36 | public function SCORM() { 37 | 38 | var is_EI_available:Boolean = ExternalInterface.available, 39 | wrapperFound:Boolean = false, 40 | debugMsg:String = "Initializing SCORM class. Checking dependencies: "; 41 | 42 | if(is_EI_available){ 43 | 44 | debugMsg += "ExternalInterface.available evaluates true. "; 45 | 46 | wrapperFound = Boolean(ExternalInterface.call("pipwerks.SCORM.isAvailable")); 47 | debugMsg += "SCORM.isAvailable() evaluates " +String(wrapperFound) +". "; 48 | 49 | if(wrapperFound){ 50 | 51 | debugMsg += "SCORM class file ready to go! :) "; 52 | 53 | } else { 54 | 55 | debugMsg += "The required JavaScript SCORM API wrapper cannot be found in the HTML document. Course cannot load."; 56 | 57 | } 58 | 59 | } else { 60 | 61 | debugMsg += "ExternalInterface is NOT available (this may be due to an outdated version of Flash Player). Course cannot load."; 62 | 63 | } 64 | 65 | __displayDebugInfo(debugMsg); 66 | 67 | } 68 | 69 | 70 | 71 | // --- public functions --------------------------------------------- // 72 | 73 | 74 | public function set debugMode(status:Boolean):void { 75 | this.__debugActive = status; 76 | } 77 | 78 | public function get debugMode():Boolean { 79 | return this.__debugActive; 80 | } 81 | 82 | public function connect():Boolean { 83 | __displayDebugInfo("pipwerks.SCORM.connect() called from class file"); 84 | return __connect(); 85 | } 86 | 87 | public function disconnect():Boolean { 88 | return __disconnect(); 89 | } 90 | 91 | public function get(param:String):String { 92 | var str:String = __get(param); 93 | __displayDebugInfo("public function get returned: " +str); 94 | return str; 95 | } 96 | 97 | public function set(parameter:String, value:String):Boolean { 98 | return __set(parameter, value); 99 | } 100 | 101 | public function save():Boolean { 102 | return __save(); 103 | } 104 | 105 | 106 | 107 | // --- private functions --------------------------------------------- // 108 | 109 | 110 | private function __connect():Boolean { 111 | 112 | var result:Boolean = false; 113 | if(!__connectionActive){ 114 | 115 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.init")); 116 | result = __stringToBoolean(eiCall); 117 | 118 | if (result){ 119 | __connectionActive = true; 120 | } else { 121 | var errorCode:int = __getDebugCode(); 122 | if(errorCode){ 123 | var debugInfo:String = __getDebugInfo(errorCode); 124 | __displayDebugInfo("pipwerks.SCORM.init() failed. \n" 125 | +"Error code: " +errorCode +"\n" 126 | +"Error info: " +debugInfo); 127 | } else { 128 | __displayDebugInfo("pipwerks.SCORM.init failed: no response from server."); 129 | } 130 | } 131 | } else { 132 | __displayDebugInfo("pipwerks.SCORM.init aborted: connection already active."); 133 | } 134 | 135 | __displayDebugInfo("__connectionActive: " +__connectionActive); 136 | 137 | return result; 138 | } 139 | 140 | 141 | private function __disconnect():Boolean { 142 | 143 | var result:Boolean = false; 144 | if(__connectionActive){ 145 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.quit")); 146 | result = __stringToBoolean(eiCall); 147 | if (result){ 148 | __connectionActive = false; 149 | } else { 150 | var errorCode:int = __getDebugCode(); 151 | var debugInfo:String = __getDebugInfo(errorCode); 152 | __displayDebugInfo("pipwerks.SCORM.quit() failed. \n" 153 | +"Error code: " +errorCode +"\n" 154 | +"Error info: " +debugInfo); 155 | } 156 | } else { 157 | __displayDebugInfo("pipwerks.SCORM.quit aborted: connection already inactive."); 158 | } 159 | return result; 160 | } 161 | 162 | 163 | private function __get(parameter:String):String { 164 | 165 | var returnedValue:String = ""; 166 | 167 | if (__connectionActive){ 168 | 169 | returnedValue = String(ExternalInterface.call("pipwerks.SCORM.get", parameter)); 170 | var errorCode:int = __getDebugCode(); 171 | 172 | //GetValue returns an empty string on errors 173 | //Double-check errorCode to make sure empty string 174 | //is really an error and not field value 175 | if (returnedValue == "" && errorCode != 0){ 176 | var debugInfo:String = __getDebugInfo(errorCode); 177 | __displayDebugInfo("pipwerks.SCORM.get(" +parameter +") failed. \n" 178 | +"Error code: " +errorCode +"\n" 179 | +"Error info: " +debugInfo); 180 | } 181 | } else { 182 | __displayDebugInfo("pipwerks.SCORM.get(" +parameter +") failed: connection is inactive."); 183 | } 184 | return returnedValue; 185 | } 186 | 187 | 188 | private function __set(parameter:String, value:String):Boolean { 189 | 190 | var result:Boolean = false; 191 | if (__connectionActive){ 192 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.set", parameter, value)); 193 | result = __stringToBoolean(eiCall); 194 | if(!result){ 195 | var errorCode:int = __getDebugCode(); 196 | var debugInfo:String = __getDebugInfo(errorCode); 197 | __displayDebugInfo("pipwerks.SCORM.set(" +parameter +") failed. \n" 198 | +"Error code: " +errorCode +"\n" 199 | +"Error info: " +debugInfo); 200 | } 201 | } else { 202 | __displayDebugInfo("pipwerks.SCORM.set(" +parameter +") failed: connection is inactive."); 203 | } 204 | return result; 205 | } 206 | 207 | 208 | private function __save():Boolean { 209 | 210 | var result:Boolean = false; 211 | if(__connectionActive){ 212 | var eiCall:String = String(ExternalInterface.call("pipwerks.SCORM.save")); 213 | result = __stringToBoolean(eiCall); 214 | if(!result){ 215 | var errorCode:int = __getDebugCode(); 216 | var debugInfo:String = __getDebugInfo(errorCode); 217 | __displayDebugInfo("pipwerks.SCORM.save() failed. \n" 218 | +"Error code: " +errorCode +"\n" 219 | +"Error info: " +debugInfo); 220 | } 221 | } else { 222 | __displayDebugInfo("pipwerks.SCORM.save() failed: API connection is inactive."); 223 | } 224 | return result; 225 | } 226 | 227 | 228 | // --- debug functions ----------------------------------------------- // 229 | 230 | private function __getDebugCode():int { 231 | var code:int = int(ExternalInterface.call("pipwerks.SCORM.debug.getCode")); 232 | return code; 233 | } 234 | 235 | private function __getDebugInfo(errorCode:int):String { 236 | var result:String = String(ExternalInterface.call("pipwerks.SCORM.debug.getInfo", errorCode)); 237 | return result; 238 | } 239 | 240 | private function __getDiagnosticInfo(errorCode:int):String { 241 | var result:String = String(ExternalInterface.call("pipwerks.SCORM.debug.getDiagnosticInfo", errorCode)); 242 | return result; 243 | } 244 | 245 | private function __displayDebugInfo(msg:String):void { 246 | if(__debugActive){ 247 | //trace(msg); 248 | ExternalInterface.call("pipwerks.UTILS.trace", msg); 249 | } 250 | } 251 | 252 | private function __stringToBoolean(value:*):Boolean { 253 | 254 | var t:String = typeof value; 255 | 256 | switch(t){ 257 | 258 | case "string": return (/(true|1)/i).test(value); 259 | case "number": return !!value; 260 | case "boolean": return value; 261 | case "undefined": return null; 262 | default: return false; 263 | 264 | } 265 | 266 | } 267 | 268 | 269 | 270 | } // end SCORM class 271 | } // end package -------------------------------------------------------------------------------- /src/JavaScript/SCORM_API_wrapper.js: -------------------------------------------------------------------------------- 1 | /*global define, module */ 2 | 3 | /* =========================================================== 4 | 5 | pipwerks SCORM Wrapper for JavaScript 6 | v1.1.20180906 7 | 8 | Created by Philip Hutchison, January 2008-2018 9 | https://github.com/pipwerks/scorm-api-wrapper 10 | 11 | Copyright (c) Philip Hutchison 12 | MIT-style license: http://pipwerks.mit-license.org/ 13 | 14 | This wrapper works with both SCORM 1.2 and SCORM 2004. 15 | 16 | Inspired by APIWrapper.js, created by the ADL and 17 | Concurrent Technologies Corporation, distributed by 18 | the ADL (http://www.adlnet.gov/scorm). 19 | 20 | SCORM.API.find() and SCORM.API.get() functions based 21 | on ADL code, modified by Mike Rustici 22 | (http://www.scorm.com/resources/apifinder/SCORMAPIFinder.htm), 23 | further modified by Philip Hutchison 24 | 25 | =============================================================== */ 26 | 27 | (function(root, factory) { 28 | 29 | "use strict"; 30 | 31 | if (typeof define === 'function' && define.amd) { 32 | // AMD. Register as an anonymous module. 33 | define([], factory); 34 | } else if (typeof module === 'object' && module.exports) { 35 | // Node. Does not work with strict CommonJS, but 36 | // only CommonJS-like environments that support module.exports, 37 | // like Node. 38 | module.exports = factory(); 39 | } else { 40 | // Browser globals (root is window) 41 | root.pipwerks = factory(); 42 | } 43 | }(this, function() { 44 | 45 | "use strict"; 46 | 47 | var pipwerks = {}; //pipwerks 'namespace' helps ensure no conflicts with possible other "SCORM" variables 48 | pipwerks.UTILS = {}; //For holding UTILS functions 49 | pipwerks.debug = { isActive: true }; //Enable (true) or disable (false) for debug mode 50 | 51 | pipwerks.SCORM = { //Define the SCORM object 52 | version: null, //Store SCORM version. 53 | handleCompletionStatus: true, //Whether or not the wrapper should automatically handle the initial completion status 54 | handleExitMode: true, //Whether or not the wrapper should automatically handle the exit mode 55 | API: { 56 | handle: null, 57 | isFound: false 58 | }, //Create API child object 59 | connection: { isActive: false }, //Create connection child object 60 | data: { 61 | completionStatus: null, 62 | exitStatus: null 63 | }, //Create data child object 64 | debug: {} //Create debug child object 65 | }; 66 | 67 | /* -------------------------------------------------------------------------------- 68 | pipwerks.SCORM.isAvailable 69 | A simple function to allow Flash ExternalInterface to confirm 70 | presence of JS wrapper before attempting any LMS communication. 71 | 72 | Parameters: none 73 | Returns: Boolean (true) 74 | ----------------------------------------------------------------------------------- */ 75 | 76 | pipwerks.SCORM.isAvailable = function() { 77 | return true; 78 | }; 79 | 80 | 81 | // ------------------------------------------------------------------------- // 82 | // --- SCORM.API functions ------------------------------------------------- // 83 | // ------------------------------------------------------------------------- // 84 | 85 | /* ------------------------------------------------------------------------- 86 | pipwerks.SCORM.API.find(window) 87 | Looks for an object named API in parent and opener windows 88 | 89 | Parameters: window (the browser window object). 90 | Returns: Object if API is found, null if no API found 91 | ---------------------------------------------------------------------------- */ 92 | 93 | pipwerks.SCORM.API.find = function(win) { 94 | 95 | var API = null, 96 | findAttempts = 0, 97 | findAttemptLimit = 500, 98 | traceMsgPrefix = "SCORM.API.find", 99 | trace = pipwerks.UTILS.trace, 100 | scorm = pipwerks.SCORM; 101 | 102 | while ((!win.API && !win.API_1484_11) && 103 | (win.parent) && 104 | (win.parent != win) && 105 | (findAttempts <= findAttemptLimit)) { 106 | 107 | findAttempts++; 108 | win = win.parent; 109 | 110 | } 111 | 112 | //If SCORM version is specified by user, look for specific API 113 | if (scorm.version) { 114 | 115 | switch (scorm.version) { 116 | 117 | case "2004": 118 | 119 | if (win.API_1484_11) { 120 | 121 | API = win.API_1484_11; 122 | 123 | } else { 124 | 125 | trace(traceMsgPrefix + ": SCORM version 2004 was specified by user, but API_1484_11 cannot be found."); 126 | 127 | } 128 | 129 | break; 130 | 131 | case "1.2": 132 | 133 | if (win.API) { 134 | 135 | API = win.API; 136 | 137 | } else { 138 | 139 | trace(traceMsgPrefix + ": SCORM version 1.2 was specified by user, but API cannot be found."); 140 | 141 | } 142 | 143 | break; 144 | 145 | } 146 | 147 | } else { //If SCORM version not specified by user, look for APIs 148 | 149 | if (win.API_1484_11) { //SCORM 2004-specific API. 150 | 151 | scorm.version = "2004"; //Set version 152 | API = win.API_1484_11; 153 | 154 | } else if (win.API) { //SCORM 1.2-specific API 155 | 156 | scorm.version = "1.2"; //Set version 157 | API = win.API; 158 | 159 | } 160 | 161 | } 162 | 163 | if (API) { 164 | 165 | trace(traceMsgPrefix + ": API found. Version: " + scorm.version); 166 | trace("API: " + API); 167 | 168 | } else { 169 | 170 | trace(traceMsgPrefix + ": Error finding API. \nFind attempts: " + findAttempts + ". \nFind attempt limit: " + findAttemptLimit); 171 | 172 | } 173 | 174 | return API; 175 | 176 | }; 177 | 178 | 179 | /* ------------------------------------------------------------------------- 180 | pipwerks.SCORM.API.get() 181 | Looks for an object named API, first in the current window's frame 182 | hierarchy and then, if necessary, in the current window's opener window 183 | hierarchy (if there is an opener window). 184 | 185 | Parameters: None. 186 | Returns: Object if API found, null if no API found 187 | ---------------------------------------------------------------------------- */ 188 | 189 | pipwerks.SCORM.API.get = function() { 190 | 191 | var API = null, 192 | win = window, 193 | scorm = pipwerks.SCORM, 194 | find = scorm.API.find, 195 | trace = pipwerks.UTILS.trace; 196 | 197 | API = find(win); 198 | 199 | if (!API && win.parent && win.parent != win) { 200 | API = find(win.parent); 201 | } 202 | 203 | if (!API && win.top && win.top.opener) { 204 | API = find(win.top.opener); 205 | } 206 | 207 | //Special handling for Plateau 208 | //Thanks to Joseph Venditti for the patch 209 | if (!API && win.top && win.top.opener && win.top.opener.document) { 210 | API = find(win.top.opener.document); 211 | } 212 | 213 | if (API) { 214 | scorm.API.isFound = true; 215 | } else { 216 | trace("API.get failed: Can't find the API!"); 217 | } 218 | 219 | return API; 220 | 221 | }; 222 | 223 | 224 | /* ------------------------------------------------------------------------- 225 | pipwerks.SCORM.API.getHandle() 226 | Returns the handle to API object if it was previously set 227 | 228 | Parameters: None. 229 | Returns: Object (the pipwerks.SCORM.API.handle variable). 230 | ---------------------------------------------------------------------------- */ 231 | 232 | pipwerks.SCORM.API.getHandle = function() { 233 | 234 | var API = pipwerks.SCORM.API; 235 | 236 | if (!API.handle && !API.isFound) { 237 | 238 | API.handle = API.get(); 239 | 240 | } 241 | 242 | return API.handle; 243 | 244 | }; 245 | 246 | 247 | // ------------------------------------------------------------------------- // 248 | // --- pipwerks.SCORM.connection functions --------------------------------- // 249 | // ------------------------------------------------------------------------- // 250 | 251 | 252 | /* ------------------------------------------------------------------------- 253 | pipwerks.SCORM.connection.initialize() 254 | Tells the LMS to initiate the communication session. 255 | 256 | Parameters: None 257 | Returns: Boolean 258 | ---------------------------------------------------------------------------- */ 259 | 260 | pipwerks.SCORM.connection.initialize = function() { 261 | 262 | var success = false, 263 | scorm = pipwerks.SCORM, 264 | completionStatus = scorm.data.completionStatus, 265 | trace = pipwerks.UTILS.trace, 266 | makeBoolean = pipwerks.UTILS.StringToBoolean, 267 | debug = scorm.debug, 268 | traceMsgPrefix = "SCORM.connection.initialize "; 269 | 270 | trace("connection.initialize called."); 271 | 272 | if (!scorm.connection.isActive) { 273 | 274 | var API = scorm.API.getHandle(), 275 | errorCode = 0; 276 | 277 | if (API) { 278 | 279 | switch (scorm.version) { 280 | case "1.2": 281 | success = makeBoolean(API.LMSInitialize("")); 282 | break; 283 | case "2004": 284 | success = makeBoolean(API.Initialize("")); 285 | break; 286 | } 287 | 288 | if (success) { 289 | 290 | //Double-check that connection is active and working before returning 'true' boolean 291 | errorCode = debug.getCode(); 292 | 293 | if (errorCode !== null && errorCode === 0) { 294 | 295 | scorm.connection.isActive = true; 296 | 297 | if (scorm.handleCompletionStatus) { 298 | 299 | //Automatically set new launches to incomplete 300 | completionStatus = scorm.status("get"); 301 | 302 | if (completionStatus) { 303 | 304 | switch (completionStatus) { 305 | 306 | //Both SCORM 1.2 and 2004 307 | case "not attempted": 308 | scorm.status("set", "incomplete"); 309 | break; 310 | 311 | //SCORM 2004 only 312 | case "unknown": 313 | scorm.status("set", "incomplete"); 314 | break; 315 | 316 | //Additional options, presented here in case you'd like to use them 317 | //case "completed" : break; 318 | //case "incomplete" : break; 319 | //case "passed" : break; //SCORM 1.2 only 320 | //case "failed" : break; //SCORM 1.2 only 321 | //case "browsed" : break; //SCORM 1.2 only 322 | 323 | } 324 | 325 | //Commit changes 326 | scorm.save(); 327 | 328 | } 329 | 330 | } 331 | 332 | } else { 333 | 334 | success = false; 335 | trace(traceMsgPrefix + "failed. \nError code: " + errorCode + " \nError info: " + debug.getInfo(errorCode)); 336 | 337 | } 338 | 339 | } else { 340 | 341 | errorCode = debug.getCode(); 342 | 343 | if (errorCode !== null && errorCode !== 0) { 344 | 345 | trace(traceMsgPrefix + "failed. \nError code: " + errorCode + " \nError info: " + debug.getInfo(errorCode)); 346 | 347 | } else { 348 | 349 | trace(traceMsgPrefix + "failed: No response from server."); 350 | 351 | } 352 | } 353 | 354 | } else { 355 | 356 | trace(traceMsgPrefix + "failed: API is null."); 357 | 358 | } 359 | 360 | } else { 361 | 362 | trace(traceMsgPrefix + "aborted: Connection already active."); 363 | 364 | } 365 | 366 | return success; 367 | 368 | }; 369 | 370 | 371 | /* ------------------------------------------------------------------------- 372 | pipwerks.SCORM.connection.terminate() 373 | Tells the LMS to terminate the communication session 374 | 375 | Parameters: None 376 | Returns: Boolean 377 | ---------------------------------------------------------------------------- */ 378 | 379 | pipwerks.SCORM.connection.terminate = function() { 380 | 381 | var success = false, 382 | scorm = pipwerks.SCORM, 383 | exitStatus = scorm.data.exitStatus, 384 | completionStatus = scorm.data.completionStatus, 385 | trace = pipwerks.UTILS.trace, 386 | makeBoolean = pipwerks.UTILS.StringToBoolean, 387 | debug = scorm.debug, 388 | traceMsgPrefix = "SCORM.connection.terminate "; 389 | 390 | 391 | if (scorm.connection.isActive) { 392 | 393 | var API = scorm.API.getHandle(), 394 | errorCode = 0; 395 | 396 | if (API) { 397 | 398 | if (scorm.handleExitMode && !exitStatus) { 399 | 400 | if (completionStatus !== "completed" && completionStatus !== "passed") { 401 | 402 | switch (scorm.version) { 403 | case "1.2": 404 | success = scorm.set("cmi.core.exit", "suspend"); 405 | break; 406 | case "2004": 407 | success = scorm.set("cmi.exit", "suspend"); 408 | break; 409 | } 410 | 411 | } else { 412 | 413 | switch (scorm.version) { 414 | case "1.2": 415 | success = scorm.set("cmi.core.exit", "logout"); 416 | break; 417 | case "2004": 418 | success = scorm.set("cmi.exit", "normal"); 419 | break; 420 | } 421 | 422 | } 423 | 424 | } 425 | 426 | //Ensure we persist the data for 1.2 - not required for 2004 where an implicit commit is applied during the Terminate 427 | success = (scorm.version === "1.2") ? scorm.save() : true; 428 | 429 | if (success) { 430 | 431 | switch (scorm.version) { 432 | case "1.2": 433 | success = makeBoolean(API.LMSFinish("")); 434 | break; 435 | case "2004": 436 | success = makeBoolean(API.Terminate("")); 437 | break; 438 | } 439 | 440 | if (success) { 441 | 442 | scorm.connection.isActive = false; 443 | 444 | } else { 445 | 446 | errorCode = debug.getCode(); 447 | trace(traceMsgPrefix + "failed. \nError code: " + errorCode + " \nError info: " + debug.getInfo(errorCode)); 448 | 449 | } 450 | 451 | } 452 | 453 | } else { 454 | 455 | trace(traceMsgPrefix + "failed: API is null."); 456 | 457 | } 458 | 459 | } else { 460 | 461 | trace(traceMsgPrefix + "aborted: Connection already terminated."); 462 | 463 | } 464 | 465 | return success; 466 | 467 | }; 468 | 469 | 470 | // ------------------------------------------------------------------------- // 471 | // --- pipwerks.SCORM.data functions --------------------------------------- // 472 | // ------------------------------------------------------------------------- // 473 | 474 | 475 | /* ------------------------------------------------------------------------- 476 | pipwerks.SCORM.data.get(parameter) 477 | Requests information from the LMS. 478 | 479 | Parameter: parameter (string, name of the SCORM data model element) 480 | Returns: string (the value of the specified data model element) 481 | ---------------------------------------------------------------------------- */ 482 | 483 | pipwerks.SCORM.data.get = function(parameter) { 484 | 485 | var value = null, 486 | scorm = pipwerks.SCORM, 487 | trace = pipwerks.UTILS.trace, 488 | debug = scorm.debug, 489 | traceMsgPrefix = "SCORM.data.get('" + parameter + "') "; 490 | 491 | if (scorm.connection.isActive) { 492 | 493 | var API = scorm.API.getHandle(), 494 | errorCode = 0; 495 | 496 | if (API) { 497 | 498 | switch (scorm.version) { 499 | case "1.2": 500 | value = API.LMSGetValue(parameter); 501 | break; 502 | case "2004": 503 | value = API.GetValue(parameter); 504 | break; 505 | } 506 | 507 | errorCode = debug.getCode(); 508 | 509 | //GetValue returns an empty string on errors 510 | //If value is an empty string, check errorCode to make sure there are no errors 511 | if (value !== "" || errorCode === 0) { 512 | 513 | //GetValue is successful. 514 | //If parameter is lesson_status/completion_status or exit status, let's 515 | //grab the value and cache it so we can check it during connection.terminate() 516 | switch (parameter) { 517 | 518 | case "cmi.core.lesson_status": 519 | case "cmi.completion_status": 520 | scorm.data.completionStatus = value; 521 | break; 522 | 523 | case "cmi.core.exit": 524 | case "cmi.exit": 525 | scorm.data.exitStatus = value; 526 | break; 527 | 528 | } 529 | 530 | } else { 531 | 532 | trace(traceMsgPrefix + "failed. \nError code: " + errorCode + "\nError info: " + debug.getInfo(errorCode)); 533 | 534 | } 535 | 536 | } else { 537 | 538 | trace(traceMsgPrefix + "failed: API is null."); 539 | 540 | } 541 | 542 | } else { 543 | 544 | trace(traceMsgPrefix + "failed: API connection is inactive."); 545 | 546 | } 547 | 548 | trace(traceMsgPrefix + " value: " + value); 549 | 550 | return String(value); 551 | 552 | }; 553 | 554 | 555 | /* ------------------------------------------------------------------------- 556 | pipwerks.SCORM.data.set() 557 | Tells the LMS to assign the value to the named data model element. 558 | Also stores the SCO's completion status in a variable named 559 | pipwerks.SCORM.data.completionStatus. This variable is checked whenever 560 | pipwerks.SCORM.connection.terminate() is invoked. 561 | 562 | Parameters: parameter (string). The data model element 563 | value (string). The value for the data model element 564 | Returns: Boolean 565 | ---------------------------------------------------------------------------- */ 566 | 567 | pipwerks.SCORM.data.set = function(parameter, value) { 568 | 569 | var success = false, 570 | scorm = pipwerks.SCORM, 571 | trace = pipwerks.UTILS.trace, 572 | makeBoolean = pipwerks.UTILS.StringToBoolean, 573 | debug = scorm.debug, 574 | traceMsgPrefix = "SCORM.data.set('" + parameter + "') "; 575 | 576 | 577 | if (scorm.connection.isActive) { 578 | 579 | var API = scorm.API.getHandle(), 580 | errorCode = 0; 581 | 582 | if (API) { 583 | 584 | switch (scorm.version) { 585 | case "1.2": 586 | success = makeBoolean(API.LMSSetValue(parameter, value)); 587 | break; 588 | case "2004": 589 | success = makeBoolean(API.SetValue(parameter, value)); 590 | break; 591 | } 592 | 593 | if (success) { 594 | 595 | if (parameter === "cmi.core.lesson_status" || parameter === "cmi.completion_status") { 596 | 597 | scorm.data.completionStatus = value; 598 | 599 | } 600 | 601 | } else { 602 | 603 | errorCode = debug.getCode(); 604 | 605 | trace(traceMsgPrefix + "failed. \nError code: " + errorCode + ". \nError info: " + debug.getInfo(errorCode)); 606 | 607 | } 608 | 609 | } else { 610 | 611 | trace(traceMsgPrefix + "failed: API is null."); 612 | 613 | } 614 | 615 | } else { 616 | 617 | trace(traceMsgPrefix + "failed: API connection is inactive."); 618 | 619 | } 620 | 621 | trace(traceMsgPrefix + " value: " + value); 622 | 623 | return success; 624 | 625 | }; 626 | 627 | 628 | /* ------------------------------------------------------------------------- 629 | pipwerks.SCORM.data.save() 630 | Instructs the LMS to persist all data to this point in the session 631 | 632 | Parameters: None 633 | Returns: Boolean 634 | ---------------------------------------------------------------------------- */ 635 | 636 | pipwerks.SCORM.data.save = function() { 637 | 638 | var success = false, 639 | scorm = pipwerks.SCORM, 640 | trace = pipwerks.UTILS.trace, 641 | makeBoolean = pipwerks.UTILS.StringToBoolean, 642 | traceMsgPrefix = "SCORM.data.save failed"; 643 | 644 | 645 | if (scorm.connection.isActive) { 646 | 647 | var API = scorm.API.getHandle(); 648 | 649 | if (API) { 650 | 651 | switch (scorm.version) { 652 | case "1.2": 653 | success = makeBoolean(API.LMSCommit("")); 654 | break; 655 | case "2004": 656 | success = makeBoolean(API.Commit("")); 657 | break; 658 | } 659 | 660 | } else { 661 | 662 | trace(traceMsgPrefix + ": API is null."); 663 | 664 | } 665 | 666 | } else { 667 | 668 | trace(traceMsgPrefix + ": API connection is inactive."); 669 | 670 | } 671 | 672 | return success; 673 | 674 | }; 675 | 676 | 677 | pipwerks.SCORM.status = function(action, status) { 678 | 679 | var success = false, 680 | scorm = pipwerks.SCORM, 681 | trace = pipwerks.UTILS.trace, 682 | traceMsgPrefix = "SCORM.getStatus failed", 683 | cmi = ""; 684 | 685 | if (action !== null) { 686 | 687 | switch (scorm.version) { 688 | case "1.2": 689 | cmi = "cmi.core.lesson_status"; 690 | break; 691 | case "2004": 692 | cmi = "cmi.completion_status"; 693 | break; 694 | } 695 | 696 | switch (action) { 697 | 698 | case "get": 699 | success = scorm.data.get(cmi); 700 | break; 701 | 702 | case "set": 703 | if (status !== null) { 704 | 705 | success = scorm.data.set(cmi, status); 706 | 707 | } else { 708 | 709 | success = false; 710 | trace(traceMsgPrefix + ": status was not specified."); 711 | 712 | } 713 | 714 | break; 715 | 716 | default: 717 | success = false; 718 | trace(traceMsgPrefix + ": no valid action was specified."); 719 | 720 | } 721 | 722 | } else { 723 | 724 | trace(traceMsgPrefix + ": action was not specified."); 725 | 726 | } 727 | 728 | return success; 729 | 730 | }; 731 | 732 | 733 | // ------------------------------------------------------------------------- // 734 | // --- pipwerks.SCORM.debug functions -------------------------------------- // 735 | // ------------------------------------------------------------------------- // 736 | 737 | 738 | /* ------------------------------------------------------------------------- 739 | pipwerks.SCORM.debug.getCode 740 | Requests the error code for the current error state from the LMS 741 | 742 | Parameters: None 743 | Returns: Integer (the last error code). 744 | ---------------------------------------------------------------------------- */ 745 | 746 | pipwerks.SCORM.debug.getCode = function() { 747 | 748 | var scorm = pipwerks.SCORM, 749 | API = scorm.API.getHandle(), 750 | trace = pipwerks.UTILS.trace, 751 | code = 0; 752 | 753 | if (API) { 754 | 755 | switch (scorm.version) { 756 | case "1.2": 757 | code = parseInt(API.LMSGetLastError(), 10); 758 | break; 759 | case "2004": 760 | code = parseInt(API.GetLastError(), 10); 761 | break; 762 | } 763 | 764 | } else { 765 | 766 | trace("SCORM.debug.getCode failed: API is null."); 767 | 768 | } 769 | 770 | return code; 771 | 772 | }; 773 | 774 | 775 | /* ------------------------------------------------------------------------- 776 | pipwerks.SCORM.debug.getInfo() 777 | "Used by a SCO to request the textual description for the error code 778 | specified by the value of [errorCode]." 779 | 780 | Parameters: errorCode (integer). 781 | Returns: String. 782 | ----------------------------------------------------------------------------- */ 783 | 784 | pipwerks.SCORM.debug.getInfo = function(errorCode) { 785 | 786 | var scorm = pipwerks.SCORM, 787 | API = scorm.API.getHandle(), 788 | trace = pipwerks.UTILS.trace, 789 | result = ""; 790 | 791 | 792 | if (API) { 793 | 794 | switch (scorm.version) { 795 | case "1.2": 796 | result = API.LMSGetErrorString(errorCode.toString()); 797 | break; 798 | case "2004": 799 | result = API.GetErrorString(errorCode.toString()); 800 | break; 801 | } 802 | 803 | } else { 804 | 805 | trace("SCORM.debug.getInfo failed: API is null."); 806 | 807 | } 808 | 809 | return String(result); 810 | 811 | }; 812 | 813 | 814 | /* ------------------------------------------------------------------------- 815 | pipwerks.SCORM.debug.getDiagnosticInfo 816 | "Exists for LMS specific use. It allows the LMS to define additional 817 | diagnostic information through the API Instance." 818 | 819 | Parameters: errorCode (integer). 820 | Returns: String (Additional diagnostic information about the given error code). 821 | ---------------------------------------------------------------------------- */ 822 | 823 | pipwerks.SCORM.debug.getDiagnosticInfo = function(errorCode) { 824 | 825 | var scorm = pipwerks.SCORM, 826 | API = scorm.API.getHandle(), 827 | trace = pipwerks.UTILS.trace, 828 | result = ""; 829 | 830 | if (API) { 831 | 832 | switch (scorm.version) { 833 | case "1.2": 834 | result = API.LMSGetDiagnostic(errorCode); 835 | break; 836 | case "2004": 837 | result = API.GetDiagnostic(errorCode); 838 | break; 839 | } 840 | 841 | } else { 842 | 843 | trace("SCORM.debug.getDiagnosticInfo failed: API is null."); 844 | 845 | } 846 | 847 | return String(result); 848 | 849 | }; 850 | 851 | 852 | // ------------------------------------------------------------------------- // 853 | // --- Shortcuts! ---------------------------------------------------------- // 854 | // ------------------------------------------------------------------------- // 855 | 856 | // Because nobody likes typing verbose code. 857 | 858 | pipwerks.SCORM.init = pipwerks.SCORM.connection.initialize; 859 | pipwerks.SCORM.get = pipwerks.SCORM.data.get; 860 | pipwerks.SCORM.set = pipwerks.SCORM.data.set; 861 | pipwerks.SCORM.save = pipwerks.SCORM.data.save; 862 | pipwerks.SCORM.quit = pipwerks.SCORM.connection.terminate; 863 | 864 | 865 | 866 | // ------------------------------------------------------------------------- // 867 | // --- pipwerks.UTILS functions -------------------------------------------- // 868 | // ------------------------------------------------------------------------- // 869 | 870 | 871 | /* ------------------------------------------------------------------------- 872 | pipwerks.UTILS.StringToBoolean() 873 | Converts 'boolean strings' into actual valid booleans. 874 | 875 | (Most values returned from the API are the strings "true" and "false".) 876 | 877 | Parameters: String 878 | Returns: Boolean 879 | ---------------------------------------------------------------------------- */ 880 | 881 | pipwerks.UTILS.StringToBoolean = function(value) { 882 | var t = typeof value; 883 | switch (t) { 884 | //typeof new String("true") === "object", so handle objects as string via fall-through. 885 | //See https://github.com/pipwerks/scorm-api-wrapper/issues/3 886 | case "object": 887 | case "string": 888 | return (/(true|1)/i).test(value); 889 | case "number": 890 | return !!value; 891 | case "boolean": 892 | return value; 893 | case "undefined": 894 | return null; 895 | default: 896 | return false; 897 | } 898 | }; 899 | 900 | 901 | /* ------------------------------------------------------------------------- 902 | pipwerks.UTILS.trace() 903 | Displays error messages when in debug mode. 904 | 905 | Parameters: msg (string) 906 | Return: None 907 | ---------------------------------------------------------------------------- */ 908 | 909 | pipwerks.UTILS.trace = function(msg) { 910 | 911 | if (pipwerks.debug.isActive) { 912 | 913 | if (window.console && window.console.log) { 914 | window.console.log(msg); 915 | } else { 916 | //alert(msg); 917 | } 918 | 919 | } 920 | }; 921 | 922 | return pipwerks; 923 | 924 | })); 925 | --------------------------------------------------------------------------------