Skip to content

Instantly share code, notes, and snippets.

@jleyva
Last active November 25, 2015 12:05
Show Gist options
  • Save jleyva/c1df66f7c3df37496999 to your computer and use it in GitHub Desktop.
Save jleyva/c1df66f7c3df37496999 to your computer and use it in GitHub Desktop.
/**
* Check if the given SCORM is available (time restrictions).
* The remote Web Service check that, but if we are offline maybe we have passed the timeclose date.
*/
isAvailable = function(scorm) {
var timeNow = $mmUtil.timestamp();
if (scorm.timeopen > 0 && scorm.timeopen > timeNow) {
return false;
}
if (scorm.timeclose >0 && timeNow > scorm.timeclose) {
return false;
}
};
/**
* Check if is a valid SCORM 1.2
*/
isValidVersion = function(scorm) {
return scorm.version == 'scorm_12';
};
/**
* Check if the package is downloadable
*/
isDownloadable = function(scorm) {
var downloadsEnabled = typeof scorm.protectpackagedownloads != 'undefined' && scorm.protectpackagedownloads == true;
var hasURL = typeof scorm.packageurl != 'undefined';
return downloadsEnabled && hasURL;
};
/**
* Check if the TOC should be displayed
*/
displayToc = function(scorm) {
return scorm.displaycoursestructure == 1;
};
/**
* Calls the get_scorm_user_data WS
*/
getUserData = function(scormId, attemptId) {
};
/**
* Check if the given SCORM is imcompleted
*/
isIncomplete = function(scorm, attemptId) {
return self.getUserData(scorm.id, attemptId).then(function(data) {
var imcomplete = false;
data.forEach(function(sco) {
if (sco.userdata.len == 0) {
incomplete = true;
return;
} else {
sco.forEach(function(el) {
if (el.element == 'status' && (el.value == 'notattempted' || el.value == 'incomplete' || el.value == 'browsed')) {
incomplete = true;
}
});
}
});
return incomplete;
});
};
/**
* Calls remote WS get_attempt_count
*/
getAttemptCount = function(scormId, userId, ignoreMissing) {
};
/**
* Count the attempts left for the given scorm
*/
countAttemptsLeft = function(scorm) {
if (scorm.maxattempt == 0) {
return $q.when(1);
}
return self.getAttemptCount(scorm.id, $mmSite.getUserId(), false).then(function(result) {
return scorm.maxattempt - result.attemptscount;
});
};
/**
* Call the remote get_scorm_scoes WS for retrieving a list of SCOs
* @param {int} scormId the SCORM id
* @param {str} organization The organization id
* @return {array} A list of SCO objects
*/
getScoes = function(scormId, organization) {
organization = organization || '';
};
/**
* Get the list of a organizations defined in a given SCORM package
* @param {int} scormId the SCORM id
* @return {array} A list of organizations
*/
getOrganizations = function(scormId) {
return self.getScoes(scormId, '').then(function(result) {
var organizations = [];
angular.forEach(result.scoes, function(sco) {
// Is an organization entry?
if (sco.organization == '' && sco.parent == '/' && sco.scormtype == '') {
organizations.push({
identifier: sco.identifier,
title: sco.title,
sortorder: sco.sortorder
});
}
});
return organizations;
});
};
/**
* Get the organization Toc object
*
* @param {int} scormId The SCORM id
* @param {str} organization The organization
* @param {int} attemptId The attempt number (to populate SCO track data)
* @return {array} The toc object
*/
getOrganizationToc = function(scormId, organization, attemptId) {
// First of all, get the track data for all the SCOes in the organization for the given attempt.
// We need this data to display the SCO status in the toc.
return self.getUserData(scormId, attemptId).then(function(data) {
var trackData = {};
// Extract data for each SCO.
angular.forEach(data, function(scoData) {
scoTracks = {};
// Data from array to object.
angular.forEach(scoData.userdata, function(el) {
scoTracks[el.element] = el.value;
});
trackData[scoData.scoid] = scoTracks;
});
self.getScoes(scormId, organization).then(function(result) {
var map = {}, node, root, trackDataBySCO = {};
// First populate trackDataBySCO to index by SCO identifier.
angular.forEAch(result.scoes, function(sco) {
trackDataBySCO[sco.identifier] = trackData[sco.id];
});
for (var i = 0; i < result.scoes.length; i += 1) {
// Keep the reference to the node.
node = result.scoes[i];
node.children = [];
map[node.identifier] = i;
if (node.parent !== "/") {
// Add specific SCO information (related to tracked data).
// Check isvisible attribute.
node.isvisible = typeof trackData[node.id].isvisible != 'undefined' ? trackData[node.id].isvisible : true;
// Check pre-requisites status.
node.prereq = typeof trackData[node.id].prerequisites == 'undefined' || self.evalPrerequisites(trackData[node.id].prerequisites, trackDataBySCO);
// Add status.
node.status = typeof trackData[node.id].status == 'undefined' ? 'notattempted' : trackData[node.id].status;
// typeof exit.
node.exitvar = typeof trackData[node.id].exitvar == 'undefined' ? 'cmi.core.exit' : trackData[node.id].exitvar;
result.scoes[map[node.parent]].children.push(node);
} else {
root = node;
}
}
return root;
});
});
};
evalPrerequisites = function(prerequisites, trackData) {
// This is really a little language parser - AICC_SCRIPT is the reference
// see 2.3.2.5.1. Sequencing/Navigation Today - from the SCORM 1.2 spec.
var element = '';
var stack = [];
var statuses = {
'passed': 'passed',
'completed': 'completed',
'failed': 'failed',
'incomplete': 'incomplete',
'browsed': 'browsed',
'not attempted': 'notattempted',
'p': 'passed',
'c': 'completed',
'f': 'failed',
'i': 'incomplete',
'b': 'browsed',
'n': 'notattempted'
};
var i = 0;
// Expand the amp entities.
prerequisites = prerequisites.replace(/&amp;/gi, '&');
// Find all my parsable tokens.
prerequisites = prerequisites.replace(/(&|\||\(|\)|\~)/gi, '\t$1\t');
// Expand operators.
prerequisites = prerequisites.replace(/&/gi, '&&');
prerequisites = prerequisites.replace(/\|/gi, '||');
// Now - grab all the tokens.
var elements = prerequisites.trim().split('\t');
// Process each token to build an expression to be evaluated.
angular.forEach(elements, function(element) {
element = element.trim();
if (!element) {
return;
}
if (!element.match(/^(&&|\|\||\(|\))$/gi)) {
// Create each individual expression.
// Search for ~ = <> X*{} .
// Sets like 3*{S34, S36, S37, S39}.
var re = /^(\d+)\*\{(.+)\}$/;
// Other symbols.
var reOther = /^(.+)(\=|\<\>)(.+)$/;
if (re.test(element)) {
var matches = element.match(re);
var repeat = matches[1];
var set = matches[2].split(',');
var count = 0;
angular.forEach(set, function(setelement) {
setelement = setelement.trim();
if (typeof trackData[setelement] != 'undefined' &&
(trackData[setelement].status == 'completed' || trackData[setelement].status == 'passed')) {
count++;
}
});
if (count >= repeat) {
element = 'true';
} else {
element = 'false';
}
} else if (element == '~') {
// Not maps ~.
element = '!';
} else if (reOther.test(element)) {
// Other symbols = | <> .
var matches = element.match(reOther);
element = matches[1].trim();
if (typeof trackData[element] != 'undefined') {
value = matches[3].trim().replace(/(\'|\")/gi);
if (typeof statuses[value] != 'undefined') {
value = statuses[value];
}
if (matches[2] == '<>') {
oper = '!=';
} else {
oper = '==';
}
element = '(\'' + trackData[element].status + '\' ' + oper + ' \'' + value + '\')';
} else {
element = 'false';
}
} else {
// Everything else must be an element defined like S45 ...
if (typeof trackData[element] != 'undefined' &&
(trackData[element].status == 'completed' || trackData[element].status == 'passed')) {
element = 'true';
} else {
element = 'false';
}
}
}
stack.push(' ' + element + ' ');
});
return eval(stack.join('') + ';');
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment