JI-1536 Add prototype for checking xapi statements

set-xapi-data
Frode Petterson 2020-01-15 15:57:16 +01:00
parent 743147698f
commit e3f1ed9a0f
3 changed files with 223 additions and 5 deletions

View File

@ -327,5 +327,6 @@ H5P.XAPIEvent.allowedXAPIVerbs = [
'copied',
'accessed-reuse',
'accessed-embed',
'accessed-copyright'
'accessed-copyright',
'showed-solution'
];

View File

@ -111,9 +111,226 @@ H5P.EventDispatcher.prototype.setActivityStarted = function () {
*/
H5P.xAPICompletedListener = function (event) {
if ((event.getVerb() === 'completed' || event.getVerb() === 'answered') && !event.getVerifiedStatementValue(['context', 'contextActivities', 'parent'])) {
var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']);
if (H5P.opened[contentId] === undefined) {
return;
}
var score = event.getScore();
var maxScore = event.getMaxScore();
var contentId = event.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']);
H5P.setFinished(contentId, score, maxScore);
}
};
(function () {
/**
* Finds a H5P library instance in an array based on the content ID
*
* @param {Array} instances
* @param {number} contentId
* @returns {Object|null} Content instance
*/
function findInstanceInArray(instances, contentId) {
if (instances !== undefined && contentId !== undefined) {
for (var i = 0; i < instances.length; i++) {
if (instances[i].contentId === contentId) {
return instances[i];
}
}
}
}
/**
*
*/
const callOnInstance = function (instance, fun, args) {
if (typeof instance[fun] === 'function') {
return instance[fun].apply(instance, args);
}
else {
console.error('Instance missing ' + fun + '() function');
}
};
/**
* The new default completed listener
*/
H5P.xAPICompletedListener2 = function (e) {
if (typeof this.setXAPIData !== 'function') {
// Does not support our new ways
return H5P.xAPICompletedListener.call(this, e);
}
const verb = e.getVerb();
if (verb !== 'answered' && verb !== 'completed' && verb !== 'showed-solution') {
if (typeof H5P.otherXAPIData === 'function') {
H5P.otherXAPIData(e);
}
return;
}
const contentId = e.getVerifiedStatementValue(['object', 'definition', 'extensions', 'http://h5p.org/x-api/h5p-local-content-id']);
if (!contentId) {
return;
}
const instance = findInstanceInArray(H5P.instances, contentId);
if (!instance) {
return;
}
// Get xAPI data for the instance who triggered the event
const xAPIData = callOnInstance(this, 'getXAPIData');
if (!xAPIData) {
return;
}
H5P.checkXAPIData.call(this, verb, instance, xAPIData);
};
/**
*
*/
H5P.checkXAPIData = function (verb, instance, xAPIData) {
if (verb === 'showed-solution') {
// Create solution based on the xAPIData
callOnInstance(this, 'setXAPIData', [recursive('solution', xAPIData)]);
}
else if (xAPIData.statement.verb.display['en-US'] === 'answered' || xAPIData.statement.verb.display['en-US'] === 'completed') {
// Create feedback based on the statement
callOnInstance(this, 'setXAPIData', [recursive('feedback', xAPIData)]);
}
};
/**
*
*/
const recursive = function (type, xAPIData) {
const data = {
type: type
};
console.log('recursive', type, xAPIData); // TODO: REMOVE
// Create feedback for data
if (xAPIData.statement.object.definition.interactionType === 'choice') {
data[type] = choice(type, xAPIData.statement.object.definition, xAPIData.statement.result);
}
// Create feedback for child data
if (xAPIData.children && xAPIData.children.length) {
data.children = [];
for (let i = 0; i < xAPIData.children.length; i++) {
data.children.push(recursive(type, xAPIData.children[i]));
}
}
return data;
};
/**
*
*/
const choice = function (type, definition, result) {
// Prepare answers
const response = result.response ? result.response.split('[,]') : [];
const correct = definition.correctResponsesPattern
&& definition.correctResponsesPattern.length ? definition.correctResponsesPattern[0].split('[,]') : [];
// If solution was requested
if (type === 'solution') {
// TODO: Disallow if showSolutionsRequiresInput && no response
return {
choices: correct
}
}
// Prepare for feedback
const feedback = {
answers: [],
points: 0,
max: 0
};
// Go through all available options
for (let i = 0; i < definition.choices.length; i++) {
const id = definition.choices[i].id;
// Determine if this was chosen and if it was correct
const isChosen = response.indexOf(id) !== -1;
const isCorrect = correct.indexOf(id) !== -1;
// Determine the weight of this repsonse
const responseWeight = definition.extensions['http://h5p.org/x-api/response-weight']
&& definition.extensions['http://h5p.org/x-api/response-weight'][i] ? definition.extensions['http://h5p.org/x-api/response-weight'][i] : 1;
// Determine how many points to award
const choice = {
points: isChosen ? (isCorrect ? responseWeight : responseWeight * -1) : 0
};
// Determine if feedback should be provided for this choice
if (definition.extensions['http://h5p.org/x-api/choice-feedback']
&& definition.extensions['http://h5p.org/x-api/choice-feedback'][i]) {
choice.feedback = isChosen ? definition.extensions['http://h5p.org/x-api/choice-feedback'][i].chosen : definition.extensions['http://h5p.org/x-api/choice-feedback'][i].notChosen;
}
feedback.answers[id] = choice; // TODO: Find a better ID solution? Needed when randomized.
feedback.points += choice.points;
}
const min = 0;
if (feedback.points < min) {
feedback.points = min;
}
// Determine the weight of this task
const weight = definition.extensions['http://h5p.org/x-api/weight'] ? definition.extensions['http://h5p.org/x-api/weight'] : 1;
// In case no answer is the correct answer
if (!correct.length) {
feedback.max = weight;
if (!response.length) {
feedback.points = feedback.max;
}
}
else {
feedback.max = correct.length;
}
// Determine max score
if (definition.extensions['http://h5p.org/x-api/choice-type'] === 'single'
|| definition.extensions['http://h5p.org/x-api/single-point']) {
feedback.max = weight;
}
// Determine score if this is a single point task
const scaled = (feedback.max === 0 ? 0 : feedback.points / feedback.max);
const success = (100 * scaled) >= (definition.extensions['http://h5p.org/x-api/pass-percentage'] || 100);
if (definition.extensions['http://h5p.org/x-api/single-point']) {
feedback.points = success ? weight : min;
}
// Select feedback text based on the scaled score
if (definition.extensions['http://h5p.org/x-api/overall-feedback']) {
const scaledFlat = Math.floor(scaled * 100);
const overallFeedback = definition.extensions['http://h5p.org/x-api/overall-feedback'];
for (let i = 0; i < overallFeedback.length; i++) {
if (overallFeedback[i].from <= scaledFlat
&& overallFeedback[i].to >= scaledFlat
&& overallFeedback[i].feedback !== undefined
&& overallFeedback[i].feedback.trim().length !== 0) {
feedback.overall = overallFeedback[i].feedback;
}
}
}
// Update statement for consistency?
result.min = min;
result.raw = feedback.points;
result.max = feedback.max;
result.scaled = Math.round(result.raw / result.max * 10000) / 10000;
result.success = success;
return feedback;
}
})();

View File

@ -216,9 +216,6 @@ H5P.init = function (target) {
}
});
// Listen for xAPI events.
H5P.on(instance, 'xAPI', H5P.xAPICompletedListener);
// Auto save current state if supported
if (H5PIntegration.saveFreq !== false && (
instance.getCurrentState instanceof Function ||
@ -382,6 +379,9 @@ H5P.init = function (target) {
this.contentDocument.write('<!doctype html><html class="h5p-iframe"><head>' + H5P.getHeadTags(contentId) + '</head><body><div class="h5p-content" data-content-id="' + contentId + '"/></body></html>');
this.contentDocument.close();
});
// Listen for xAPI events
H5P.externalDispatcher.on('xAPI', H5P.xAPICompletedListener2);
};
/**