/** * Wrapper to the SCORM API provided by Chamilo * The complete set of functions and variables are in this file to avoid unnecessary file * accesses. * Only event triggers and answer data are inserted into the final document. * @author Yannick Warnier - inspired by the ADLNet documentation on SCORM content-side API * @package scorm.js */ /** * Initialisation of the SCORM API section. * Find the SCO functions (startTimer, computeTime, etc in the second section) * Find the Chamilo-proper functions (checkAnswers, etc in the third section) */ var _debug = true; var findAPITries = 0; var _apiHandle = null; //private variable var errMsgLocate = "Unable to locate the LMS's API implementation"; var _NoError = 0; var _GeneralException = 101; var _ServerBusy = 102; var _InvalidArgumentError = 201; var _ElementCannotHaveChildren = 202; var _ElementIsNotAnArray = 203; var _NotInitialized = 301; var _NotImplementedError = 401; var _InvalidSetValue = 402; var _ElementIsReadOnly = 403; var _ElementIsWriteOnly = 404; var _IncorrectDataType = 405; var startTime; var exitPageStatus; /** * Gets the API handle right into the local API object and ensure there is only one. * Using the singleton pattern to ensure there's only one API object. * @return object The API object as given by the LMS */ var API = new function() { if (_apiHandle == null) { _apiHandle = getAPI(); } return _apiHandle; } /** * Finds the API on the LMS side or gives up giving an error message * @param object The window/frame object in which we are searching for the SCORM API * @return object The API object recovered from the LMS's implementation of the SCORM API */ function findAPI(win) { while((win.API == null) && (win.parent != null) && (win.parent != win)) { findAPITries++; if (findAPITries>10) { alert("Error finding API - too deeply nested"); return null; } win = win.parent } return win.API; } /** * Gets the API from the current window/frame or from parent objects if not found * @return object The API object recovered from the LMS's implementation of the SCORM API */ function getAPI() { //window is the global/root object of the current window/frame var MyAPI = findAPI(window); //look through parents if any if ((MyAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) { MyAPI = findAPI(window.opener); } //still not found? error message if (MyAPI == null) { alert("Unable to find SCORM API adapter.\nPlease check your LMS is considering this page as SCORM and providing the right JavaScript interface.") } return MyAPI; } /** * Handles error codes (prints the error if it has a description) * @return int Error code from LMS's API */ function errorHandler() { if (API == null) { alert("Unable to locate the LMS's API. Cannot determine LMS error code"); return; } var errCode = API.LMSGetLastError().toString(); if (errCode != _NoError) { if (errCode == _NotImplementedError) { var errDescription = "The LMS doesn't support this feature"; if (_debug) { errDescription += "\n"; errDescription += api.LMSGetDiagnostic(null); } addDebug(errDescription); } else { var errDescription = API.LMSGetErrorString(errCode); if (_debug) { errDescription += "\n"; errDescription += api.LMSGetDiagnostic(null); } addDebug(errDescription); } } return errCode; } function addDebug(message) { if (_debug && window.console) { console.log(message); } } function addDebugTable(message) { if (_debug && window.console) { console.table(message); } } /** * Calls the LMSInitialize method of the LMS's API object * @return string The string value of the LMS returned value or false if error (should be "true" otherwise) */ function doLMSInitialize() { if (API == null) { alert(errMsgLocate + "\nLMSInitialize failed"); return false; } var result = API.LMSInitialize(""); if (result.toString() != "true") { var err = errorHandler(); } return result.toString(); } /** * Calls the LMSFinish method of the LMS's API object * @return string The string value of the LMS return value, or false if error (should be "true" otherwise) */ function doLMSFinish() { if (API == null) { alert(errMsgLocate + "\nLMSFinish failed"); return false; } else { var result = API.LMSFinish(''); if (result.toString() != "true") { var err = errorHandler(); } } return result.toString(); } /** * Calls the LMSGetValue method * @param string The name of the SCORM parameter to get * @return string The value returned by the LMS */ function doLMSGetValue(name) { if (API == null) { alert(errMsgLocate + "\nLMSGetValue was not successful."); return ''; } else { var value = API.LMSGetValue(name); var errCode = API.LMSGetLastError().toString(); if (errCode != _NoError) { // an error was encountered so display the error description var errDescription = API.LMSGetErrorString(errCode); addDebug("LMSGetValue(" + name + ") failed. \n" + errDescription) return ''; } return value.toString(); } } /** * Calls the LMSSetValue method of the API object * @param string The name of the SCORM parameter to set * @param string The value to set the parameter to * @return void */ function doLMSSetValue(name, value) { if (API == null) { alert("Unable to locate the LMS's API Implementation.\nLMSSetValue was not successful."); return; } else { var result = API.LMSSetValue(name, value); if (result.toString() != "true") { var err = errorHandler(); } } return; } /** * Calls the LMSCommit method */ function doLMSCommit() { if (API == null) { alert(errMsgLocate + "\nLMSCommit was not successful."); return "false"; } else { var result = API.LMSCommit(""); if (result != "true") { var err = errorHandler(); } } return result.toString(); } /** * Calls GetLastError() */ function doLMSGetLastError() { if (API == null) { alert(errMsgLocate + "\nLMSGetLastError was not successful."); //since we can't get the error code from the LMS, return a general error return _GeneralError; } return API.LMSGetLastError().toString(); } /** * Calls LMSGetErrorString() */ function doLMSGetErrorString(errorCode) { if (API == null) { alert(errMsgLocate + "\nLMSGetErrorString was not successful."); } return API.LMSGetErrorString(errorCode).toString(); } /** * Calls LMSGetDiagnostic() */ function doLMSGetDiagnostic(errorCode) { if (API == null) { alert(errMsgLocate + "\nLMSGetDiagnostic was not successful."); } return API.LMSGetDiagnostic(errorCode).toString(); } /** * Initialise page values */ function loadPage() { var result = doLMSInitialize(); if (result) { var status = doLMSGetValue("cmi.core.lesson_status"); if (status == "not attempted") { doLMSSetValue("cmi.core.lesson_status", "incomplete"); } exitPageStatus = false; startTimer(); } } /** * Starts the local timer */ function startTimer() { startTime = new Date().getTime(); } /** * Calculates the total time and sends the result to the LMS */ function computeTime() { if (startTime != 0) { var currentDate = new Date().getTime(); var elapsedSeconds = ( (currentDate - startTime) / 1000 ); var formattedTime = convertTotalSeconds(elapsedSeconds); } else { formattedTime = "00:00:00.0"; } doLMSSetValue( "cmi.core.session_time", formattedTime ); } /** * Formats the time in a SCORM time format */ function convertTotalSeconds(ts) { var sec = (ts % 60); ts -= sec; var tmp = (ts % 3600); //# of seconds in the total # of minutes ts -= tmp; //# of seconds in the total # of hours // convert seconds to conform to CMITimespan type (e.g. SS.00) sec = Math.round(sec*100)/100; var strSec = new String(sec); var strWholeSec = strSec; var strFractionSec = ""; if (strSec.indexOf(".") != -1) { strWholeSec = strSec.substring(0, strSec.indexOf(".")); strFractionSec = strSec.substring(strSec.indexOf(".") + 1, strSec.length); } if (strWholeSec.length < 2) { strWholeSec = "0" + strWholeSec; } strSec = strWholeSec; if (strFractionSec.length) { strSec = strSec + "." + strFractionSec; } if ((ts % 3600) != 0) var hour = 0; else var hour = (ts / 3600); if ((tmp % 60) != 0) var min = 0; else var min = (tmp / 60); if ((new String(hour)).length < 2) hour = "0" + hour; if ((new String(min)).length < 2) min = "0" + min; var rtnVal = hour + ":" + min + ":" + strSec; return rtnVal } /** * Handles the use of the back button (saves data and closes SCO) */ function doBack() { checkAnswers(true); doLMSSetValue( "cmi.core.exit", "suspend" ); computeTime(); exitPageStatus = true; var result; result = doLMSCommit(); result = doLMSFinish(); } /** * Handles the closure of the current SCO before an interruption. This is only useful if the LMS * deals with the cmi.core.exit, cmi.core.lesson_status and cmi.core.lesson_mode *and* the SCO * sends some kind of value for cmi.core.exit, which is not the case here (yet). */ function doContinue(status) { // Reinitialize Exit to blank doLMSSetValue( "cmi.core.exit", "" ); var mode = doLMSGetValue( "cmi.core.lesson_mode" ); if ( mode != "review" && mode != "browse" ) { doLMSSetValue( "cmi.core.lesson_status", status ); } computeTime(); exitPageStatus = true; var result; result = doLMSCommit(); result = doLMSFinish(); } /** * handles the recording of everything on a normal shutdown */ function doQuit() { checkAnswers(); computeTime(); exitPageStatus = true; var result; result = doLMSCommit(); result = doLMSFinish(); } /** * Called upon unload event from body element */ function unloadPage(status) { if (!exitPageStatus) { // doQuit( status ); } } /** * Checks the answers on the test formular page */ function checkAnswers(interrupted) { var tmpScore = 0; var status = 'not attempted'; var scoreMax = 0; addDebug('Number of questions: '+ questions.length); for (var i=0; i < questions.length; i++) { if (questions[i] != undefined && questions[i] != null) { var idQuestion = questions[i]; var type = questions_types[idQuestion]; var interactionScore = 0; var interactionAnswers = ''; var interactionCorrectResponses = ''; var interactionType = ''; addDebug('idQuestion: ' +idQuestion + ', Type: ' +type); addDebug('questions_answers: '); addDebugTable(questions_answers[idQuestion]); addDebug('questions_answers_ponderation: '); addDebugTable(questions_answers_ponderation[idQuestion]); addDebug('questions_answers_correct: '); addDebugTable(questions_answers_correct[idQuestion]); switch (type) { case 'mcma': interactionType = 'choice'; var myScore = 0; for(var j = 0; j< questions_answers[idQuestion].length;j++) { var idAnswer = questions_answers[idQuestion][j]; var answer = document.getElementById('question_'+(idQuestion)+'_multiple_'+(idAnswer)); if (answer.checked) { interactionAnswers += idAnswer+'__|';// changed by isaac flores myScore += questions_answers_ponderation[idQuestion][idAnswer]; } } interactionScore = myScore; scoreMax += questions_score_max[idQuestion]; addDebug("Score: "+myScore); break; case 'mcua': interactionType = 'choice'; var myScore = 0; for (var j = 0; j= mastery_score) { status = 'passed'; } else { status = 'failed'; } addDebug('student_score: ' + tmpScore); addDebug('mastery_score: ' + mastery_score); addDebug('cmi.core.score.max: ' + scoreMax); addDebug('cmi.core.lesson_status: ' + status); doLMSSetValue('cmi.core.lesson_status', status); if (interrupted && (status != 'completed') && (status != 'passed')) { doLMSSetValue('cmi.core.exit', 'suspended'); } return false; //do not submit the form } (function($){ //Shuffle all rows, while keeping the first column //Requires: Shuffle $.fn.shuffleRows = function(){ return this.each(function(){ var main = $(/table/i.test(this.tagName) ? this.tBodies[0] : this); var firstElem = [], counter=0; main.children().each(function(){ firstElem.push(this.firstChild); }); main.shuffle(); main.children().each(function(){ this.insertBefore(firstElem[counter++], this.firstChild); }); }); } /* Shuffle is required */ $.fn.shuffle = function() { return this.each(function(){ var items = $(this).children(); return (items.length) ? $(this).html($.shuffle(items)) : this; }); } $.shuffle = function(arr) { for( var j, x, i = arr.length; i; j = parseInt(Math.random() * i), x = arr[--i], arr[i] = arr[j], arr[j] = x ); return arr; } })(jQuery); /* * Assigns any event handler to any element * @param object Element on which the event is added * @param string Name of event * @param string Function to trigger on event * @param boolean Capture the event and prevent */ function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); return true; } else if(elm.attachEvent) { var r = elm.attachEvent('on' + evType, fn); return r; } else { elm['on' + evType] = fn; } }