From 0ed54343b7c746fcf6279a4e63db991c377a6c7c Mon Sep 17 00:00:00 2001 From: Dan Ghost Date: Wed, 15 Jan 2025 16:17:08 +0000 Subject: [PATCH] New: expanded use of `cmi.interactions` data elements to include more context for reporting purposes (#321) --- js/adapt-stateful-session.js | 25 +++++++++++++++---------- js/scorm/wrapper.js | 33 +++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/js/adapt-stateful-session.js b/js/adapt-stateful-session.js index 4f5733d..4454139 100644 --- a/js/adapt-stateful-session.js +++ b/js/adapt-stateful-session.js @@ -216,22 +216,27 @@ export default class StatefulSession extends Backbone.Controller { offlineStorage.set('objectiveStatus', id, completionStatus); } - onQuestionRecordInteraction(questionView) { + onQuestionRecordInteraction(view) { if (!this.shouldRecordInteractions) return; if (!this.scorm.isSupported('cmi.interactions._count')) return; - // View functions are deprecated: getResponseType, getResponse, isCorrect, getLatency - const questionModel = questionView.model; - const responseType = (questionModel.getResponseType ? questionModel.getResponseType() : questionView.getResponseType()); + const model = view.model; + const responseType = model.getResponseType(); // If responseType doesn't contain any data, assume that the question // component hasn't been set up for cmi.interaction tracking if (_.isEmpty(responseType)) return; + const modelId = model.get('_id'); const id = this._uniqueInteractionIds - ? `${this.scorm.getInteractionCount()}-${questionModel.get('_id')}` - : questionModel.get('_id'); - const response = (questionModel.getResponse ? questionModel.getResponse() : questionView.getResponse()); - const result = (questionModel.isCorrect ? questionModel.isCorrect() : questionView.isCorrect()); - const latency = (questionModel.getLatency ? questionModel.getLatency() : questionView.getLatency()); - offlineStorage.set('interaction', id, response, result, latency, responseType); + ? `${this.scorm.getInteractionCount()}-${modelId}` + : modelId; + const response = model.getResponse(); + const result = model.isCorrect(); + const latency = model?.getLatency?.() ?? view.getLatency(); + const correctResponsesPattern = model.getInteractionObject()?.correctResponsesPattern; + const objectiveIds = Adapt?.scoring?.getSubsetsByModelId(modelId) + .filter(set => set.type !== 'adapt') + .map(({ id }) => id); + const description = model.get('body'); + offlineStorage.set('interaction', id, response, result, latency, responseType, correctResponsesPattern, objectiveIds, description); } onContentObjectCompleteChange(model) { diff --git a/js/scorm/wrapper.js b/js/scorm/wrapper.js index 6a23f43..71360a7 100644 --- a/js/scorm/wrapper.js +++ b/js/scorm/wrapper.js @@ -358,7 +358,7 @@ class ScormWrapper { })); } - recordInteraction(id, response, correct, latency, type) { + recordInteraction(id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description) { if (!this.isChildSupported('cmi.interactions.n.id') || !this.isSupported('cmi.interactions._count')) return; switch (type) { case 'choice': @@ -634,7 +634,7 @@ class ScormWrapper { this.setValueIfChildSupported(`${cmiPrefix}.time`, this.getCMITime()); } - recordInteractionScorm2004(id, response, correct, latency, type) { + recordInteractionScorm2004(id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description) { id = id.trim(); const cmiPrefix = `cmi.interactions.${this.getInteractionCount()}`; this.setValue(`${cmiPrefix}.id`, id); @@ -642,10 +642,27 @@ class ScormWrapper { this.setValue(`${cmiPrefix}.learner_response`, response); this.setValue(`${cmiPrefix}.result`, correct ? 'correct' : 'incorrect'); if (latency !== null && latency !== undefined) this.setValue(`${cmiPrefix}.latency`, this.convertToSCORM2004Time(latency)); + if (correctResponsesPattern?.length) { + correctResponsesPattern.forEach((response, index) => { + this.setValue(`${cmiPrefix}.correct_responses.${index}.pattern`, response); + }); + } + if (objectiveIds?.length) { + objectiveIds.forEach((id, index) => { + this.setValue(`${cmiPrefix}.objectives.${index}.id`, id); + }); + } + if (description) { + const maxLength = this.maxCharLimitOverride ?? 250; + // strip HTML + description = $(`

${description}

`).text(); + if (description.length > maxLength) description = description.substr(0, maxLength).trim(); + this.setValue(`${cmiPrefix}.description`, description); + } this.setValue(`${cmiPrefix}.timestamp`, this.getISO8601Timestamp()); } - recordInteractionMultipleChoice(id, response, correct, latency, type) { + recordInteractionMultipleChoice(id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description) { if (this.isSCORM2004()) { response = response.replace(/,|#/g, '[,]'); } else { @@ -653,10 +670,10 @@ class ScormWrapper { response = this.checkResponse(response, 'choice'); } const scormRecordInteraction = this.isSCORM2004() ? this.recordInteractionScorm2004 : this.recordInteractionScorm12; - scormRecordInteraction.call(this, id, response, correct, latency, type); + scormRecordInteraction.call(this, id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description); } - recordInteractionMatching(id, response, correct, latency, type) { + recordInteractionMatching(id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description) { response = response.replace(/#/g, ','); if (this.isSCORM2004()) { response = response.replace(/,/g, '[,]').replace(/\./g, '[.]'); @@ -664,10 +681,10 @@ class ScormWrapper { response = this.checkResponse(response, 'matching'); } const scormRecordInteraction = this.isSCORM2004() ? this.recordInteractionScorm2004 : this.recordInteractionScorm12; - scormRecordInteraction.call(this, id, response, correct, latency, type); + scormRecordInteraction.call(this, id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description); } - recordInteractionFillIn(id, response, correct, latency, type) { + recordInteractionFillIn(id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description) { let maxLength = this.isSCORM2004() ? 250 : 255; maxLength = this.maxCharLimitOverride ?? maxLength; if (response.length > maxLength) { @@ -675,7 +692,7 @@ class ScormWrapper { this.logger.warn(`ScormWrapper::recordInteractionFillIn: response data for ${id} is longer than the maximum allowed length of ${maxLength} characters; data will be truncated to avoid an error.`); } const scormRecordInteraction = this.isSCORM2004() ? this.recordInteractionScorm2004 : this.recordInteractionScorm12; - scormRecordInteraction.call(this, id, response, correct, latency, type); + scormRecordInteraction.call(this, id, response, correct, latency, type, correctResponsesPattern, objectiveIds, description); } getObjectiveCount() {