SaveStudentInteractions-ITS/saveStudentInteractions.js

704 lines
26 KiB
JavaScript

// © 2022 Uniwersytet im. Adama Mickiewicza w Poznaniu
// Biblioteka pozwala na zapisywanie różnych informacji o aktywnościach studenta.
var contentFrameReference = null;
var courseWindowReference = null;
const scoReadyTime = getTimeAsStringShort();
var allInteractionsForLinksInitialized = false;
var allInteractionsForQuizInitialized = false;
var allInteractionsForFillInInitialized = false;
var allInteractionsForFillInNumericInitialized = false;
var allInteractionsForDragAndDropInitialized = false;
var quizClicksArr = [];
var interactiondIDList = [];
$(function() {
quizClicksArr = [];
interactiondIDList = [];
let interactionsCountSet = false;
$(window).scroll(function(){
if (!interactionsCountSet) {
const result = prepare(interactionsCountSet, null);
interactionsCountSet = result.interactionsCountSet;
}
});
if ($("a[target='popup']").size() > 0) {
$("a[target='popup']").click(function() {
let interactionId = -1;
const interactionName = $(this).text() + " url:" + $(this).attr("href") + " sco: " + getScoName() + " (" + getScoId() + ")";
const result = prepare(interactionsCountSet, interactionName);
interactionsCountSet = result.interactionsCountSet;
interactionId = result.interactionId;
if (interactionId != -1) {
saveData(interactionId, "clicked", "neutral");
}
});
}
if ($(".el-draganddrop-buttons-container").size() > 0) {
$(".el-draganddrop-buttons-container").each(function() {
let container$ = $(this);
container$.find(".el-draganddrop-checkbutton").click(function() {
let interactionId = -1;
const header = $(this).find(".el-element-header-text") != undefined ? "header: " + $(this).find(".el-element-header-text").text() : "";
const interactionName = "type: matching " + header + " sco: " + getScoName() + " (" + getScoId() + ")";
const result = prepare(interactionsCountSet, interactionName);
interactionsCountSet = result.interactionsCountSet;
interactionId = result.interactionId;
console.log("interactionId dd: " + interactionId);
if (interactionId != -1) {
let d = $(this).closest(".eduexe-library-draganddrop-container");
let answers = "";
let correctness = "correct";
d.find(".el-draganddrop-draggable").each(function() {
let draggableId = $(this).attr("data-drag-id");
let draggableDivQuery = 'div[data-drag-id=\"'.concat(draggableId).concat('\"]');
let draggableDiv = container$.find(draggableDivQuery);
let draggableDivText = draggableDiv.text();
let droppedonDivQuery = 'div[data-droppedon-id=\"'.concat(draggableId).concat('\"]');
let droppedonDiv = container$.find(droppedonDivQuery);
let dropDivQuery = 'div[data-drop-id=\"'.concat(draggableId).concat('\"]');
let dropDiv = container$.find(dropDivQuery);
let dropDivKeywordText = dropDiv.prevAll(".el-draganddrop-keyword:first").text();
if (droppedonDiv.length) {
var droppedonDivPairId = droppedonDiv.attr("data-drop-id");
if (droppedonDivPairId == draggableId) {
// Element draggable upuszczono na właściwy element droppable
draggableDiv.css("border", "2px solid green");
}
else {
// Element draggable upuszczono na niewłaściwy element droppable
draggableDiv.css("border", "2px solid red");
correctness = "incorrect";
}
}
else {
if (dropDiv.length) {
// Element draggable pozostawiono nieupuszczony, ale była do niego para
draggableDiv.css("border", "2px solid red");
correctness = "incorrect";
}
else {
// Element draggable pozostawiono nieupuszczony i nie było do niego pary
draggableDiv.css("border", "2px solid green");
}
}
answers = answers + dropDivKeywordText + "[.]" + draggableDivText + "[,]"; // puste dropDivKeywordText i draggableDivText ?! :(
});
answers = answers.substring(0, answers.length - 3);
saveData(interactionId, answers, correctness);
}
});
});
}
if ($(".eduexe-library-quiz-container form").size() > 0) {
$(".eduexe-library-quiz-container form").click(function() {
let interactionId = -1;
const header = $(this).parent().parent().parent().parent().find(".el-element-header-text") != undefined ? "header: " + $(this).parent().parent().parent().parent().find(".el-element-header-text").text() : "";
const interactionName = "type: quiz " + header
+ " introduction:" + $(this).parent().parent().parent().parent().find("el-quiz-introduction").text() + " question:"
+ $(this).parent().parent().parent().parent().find(".el-quiz-question").text() + " sco: " + getScoName() + " (" + getScoId() + ")";
const result = prepare(interactionsCountSet, interactionName);
interactionsCountSet = result.interactionsCountSet;
interactionId = result.interactionId;
if (interactionId != -1) {
if (quizClicksArr.includes(interactionId)) {
const index = quizClicksArr.indexOf(interactionId);
if (index > -1) {
quizClicksArr.splice(index, 1);
}
let answers = "";
let correctness = "correct";
let correctN = 0;
let k = 0;
const selector = $(this).parent().parent().find(".el-checkbox").size() > 0 ? ".el-checkbox" : ".el-radio";
$(this).find(selector).each(function() {
if ($(this).find("input").attr("data-val") == "true") {
correctN++;
}
});
let answersCorrectN = 0;
const n = $(this).find(selector + " input:checked").size();
$(this).find(selector).each(function() {
if ($(this).find("input").is(":checked")) {
answers += $(this).find("p").text();
if (k < n - 1) {
answers += ";";
}
if ($(this).find("input").attr("data-val") == "false") {
correctness = "incorrect";
} else {
answersCorrectN++;
}
k++;
}
});
if (answers.length == 0) {
correctness = "incorrect";
}
if (correctN != answersCorrectN) {
correctness = "incorrect";
}
saveData(interactionId, answers, correctness);
} else {
quizClicksArr.push(interactionId);
}
}
});
}
if ($(".eduexe-library-fillin-container form input").size() > 0) {
const $input = $('.eduexe-library-fillin-container form input');
$input.each(function() {
const that = $(this);
let typingTimer; //timer identifier
const doneTypingInterval = 500; //time in ms, 0.5 second
that.on('keyup', function(event) {
const keyCode = event.keyCode || event.which;
clearTimeout(typingTimer);
typingTimer = setTimeout(function() {
let $target = $(event.target);
if (keyCode == 9) {
$target = $target.parent().parent().prev().find("input");
}
if ($target) {
interactionsCountSet = doneTypingFillIn($target, interactionsCountSet);
}
if (keyCode == 9) {
let nextInput = $(event.target);
if (nextInput) {
nextInput.focus();
}
}
}, doneTypingInterval);
});
that.on('keydown', function() {
clearTimeout(typingTimer);
});
});
}
if ($(".eduexe-library-numeric-container form input").size() > 0) {
const $inputNumeric = $('.eduexe-library-numeric-container form input');
$inputNumeric.each(function() {
const that = $(this);
let typingTimerNumeric; //timer identifier
const doneTypingIntervalNumeric = 500; //time in ms, 0.5 second
that.on('keyup', function(event) {
const keyCode = event.keyCode || event.which;
clearTimeout(typingTimerNumeric);
typingTimerNumeric = setTimeout(function() {
let $target = $(event.target);
if (keyCode == 9) {
$target = $target.parent().parent().prev().find("input");
}
if ($target) {
interactionsCountSet = doneChangingFillInNumeric(that, interactionsCountSet);
}
if (keyCode == 9) {
let nextInput = $(event.target);
if (nextInput) {
nextInput.focus();
}
}
}, doneTypingIntervalNumeric);
});
that.on('keydown', function() {
clearTimeout(typingTimerNumeric);
});
let typingTimerNumericMouse; //timer identifier
const doneTypingIntervalNumericMouse = 500; //time in ms, 0.5 second
that.on('click', function() {
clearTimeout(typingTimerNumericMouse);
typingTimerNumericMouse = setTimeout(function() {
interactionsCountSet = doneChangingFillInNumeric(that, interactionsCountSet);
}, doneTypingIntervalNumericMouse);
});
let typingTimerNumericMouseWheel; //timer identifier
const doneTypingIntervalNumericMouseWheel = 500; //time in ms, 0.5 second
that.on('wheel', function() {
clearTimeout(typingTimerNumericMouseWheel);
typingTimerNumericMouseWheel = setTimeout(function() {
interactionsCountSet = doneChangingFillInNumeric(that, interactionsCountSet);
}, doneTypingIntervalNumericMouseWheel);
});
});
}
});
function doneChangingFillInNumeric(that, interactionsCountSet) {
let interactionId = -1;
const header = that.parent().parent().parent().parent().parent().parent().find(".el-element-header-text") != undefined ? "header: " + that.parent().parent().parent().parent().parent().parent().find(".el-element-header-text").text() : "";
const interactionName = "type: fill-in numeric " + header + " question: " + that.parent().find(".el-slide-content-header").text() + " sco: " + getScoName() + " (" + getScoId() + ")";
let result = prepare(interactionsCountSet, interactionName);
interactionsCountSet = result.interactionsCountSet;
interactionId = result.interactionId;
if (interactionId != -1) {
let correctness = "correct";
const answerCorrect = that.attr("data-val");
const answerRange = that.attr("data-range");
const userAnswer = that.val();
const answer = that.val();
that.attr("data-userans", userAnswer);
// walidacja odpowiedzi
if (answerCorrect == userAnswer) {
// wpisano dobrą odpowiedź
correctness = "correct";
} else {
if (answerRange !== undefined && Math.abs(answerCorrect - userAnswer) <= answerRange) {
// wpisano niedokładną odpowiedź, ale mieszczącą się w dopuszczalnym zakresie błędu
correctness = "correct";
} else {
// wpisano złą odpowiedź
correctness = "incorrect";
}
}
saveData(interactionId, answer, correctness);
}
}
function doneTypingFillIn(that, interactionsCountSet) {
let interactionId = -1;
const header = that.parent().parent().parent().parent().parent().parent().find(".el-element-header-text") != undefined ? "header: " + that.parent().parent().parent().parent().parent().parent().find(".el-element-header-text").text() : "";
const interactionName = "type: fill-in " + header + " question: " + that.parent().find(".el-slide-content-header").text() + " sco: " + getScoName() + " (" + getScoId() + ")";
const result = prepare(interactionsCountSet, interactionName);
interactionsCountSet = result.interactionsCountSet;
interactionId = result.interactionId;
if (interactionId != -1) {
let correctness = "correct";
let answerCorrect = that.attr("data-val");
let userAnswer = that.val();
const answer = that.val();
// zastosuj małe litery
answerCorrect = answerCorrect.toLowerCase();
userAnswer = userAnswer.toLowerCase();
// walidacja odpowiedzi
const answerCorrectArr = answerCorrect.split(";");
let userAnswerWasCorrect = false;
for (let i = 0; i < answerCorrectArr.length; i++) {
if (answerCorrectArr[i] == userAnswer) {
// wpisano dobrą odpowiedź
userAnswerWasCorrect = true;
break;
}
}
// wpisano złą odpowiedź
if (!userAnswerWasCorrect) {
correctness = "incorrect";
}
saveData(interactionId, answer, correctness);
}
return interactionsCountSet;
}
function getContentFrameReference() {
if (document.getElementById('page-content-frame') != null) {
contentFrameReference = document.getElementById('page-content-frame').contentWindow;
} else {
contentFrameReference = window;
}
}
function getCourseWindowReference() {
if (typeof window.parent.coursejson !== 'undefined') {
courseWindowReference = window.parent;
} else {
courseWindowReference = window;
}
}
/*function countInteractions() {
const isPlainSCORM = courseWindowReference.getParsedCourseJson().scormPlain;
const scosNumber = courseWindowReference.getParsedCourseJson().children.length;
let numberOfInteractions = 0;
for (let i = 1; i <= scosNumber; i++) {
if (isPlainSCORM && i != getScoId()) {
continue;
}
let currentSCOManifest = courseWindowReference.manifestDataCopy.structure['sco_' + i];
let interactions = currentSCOManifest.interactions;
numberOfInteractions += interactions.length;
for (let j = 0; j < interactions.length; j++) {
let inter = interactions[j].id;
if (interactiondIDList.indexOf(inter) == -1) {
interactiondIDList.push(inter);
}
}
}
return numberOfInteractions;
}*/
function countInteractionsFunction() {
console.log("countInteractionsFunction");
let i = 0;
let inter = contentFrameReference.doGetValue("cmi.interactions.0.id");
while (inter != "") {
if (interactiondIDList.indexOf(inter) == -1) {
interactiondIDList.push(inter);
}
i++;
inter = contentFrameReference.doGetValue("cmi.interactions." + i + ".id");
}
return i;
}
function initAllInteractionsForFillInNumeric(interactionsCount) {
if ($(".eduexe-library-numeric-container form input").size() > 0) {
const scoId = getScoId();
const scoTitle = getScoName();
$(".eduexe-library-numeric-container form input").each(function() {
const header = $(this).parent().parent().parent().parent().parent().parent().find(".el-element-header-text") != undefined ? "header: " + $(this).parent().parent().parent().parent().parent().parent().find(".el-element-header-text").text() : "";
const interactionName = "type: fill-in numeric " + header + " question: " + $(this).parent().find(".el-slide-content-header").text() + " sco: " + getScoName() + " (" + getScoId() + ")";
initOtherInteraction(
interactionsCount, interactionName,
scoId, scoTitle, $(this).text(), "numeric"
);
interactionsCount++;
});
// contentFrameReference.doCommit();
allInteractionsForFillInNumericInitialized = true;
}
return interactionsCount;
}
function initAllInteractionsForFillIn(interactionsCount) {
if ($(".eduexe-library-fillin-container form input").size() > 0) {
const scoId = getScoId();
const scoTitle = getScoName();
$(".eduexe-library-fillin-container form input").each(function() {
const header = $(this).parent().parent().parent().parent().parent().parent().find(".el-element-header-text") != undefined ? "header: " + $(this).parent().parent().parent().parent().parent().parent().find(".el-element-header-text").text() : "";
const interactionName = "type: fill-in " + header + " question: " + $(this).parent().find(".el-slide-content-header").text() + " sco: " + getScoName() + " (" + getScoId() + ")";
initOtherInteraction(
interactionsCount, interactionName,
scoId, scoTitle, $(this).text(), "fill-in"
);
interactionsCount++;
});
// contentFrameReference.doCommit();
allInteractionsForFillInInitialized = true;
}
return interactionsCount;
}
function initAllInteractionsForQuiz(interactionsCount) {
if ($(".eduexe-library-quiz-container").size() > 0) {
const scoId = getScoId();
const scoTitle = getScoName();
$(".eduexe-library-quiz-container").each(function() {
const header = $(this).find(".el-element-header-text") != undefined ? "header: " + $(this).find(".el-element-header-text").text() : "";
const introduction = $(this).find("el-quiz-introduction") != undefined ? " introduction:" + $(this).find("el-quiz-introduction").text() : "";
const interactionName = "type: quiz " + header + introduction + " question:" + $(this).find(".el-quiz-question").text() + " sco: " + getScoName() + " (" + getScoId() + ")";
const type = $(this).find(".el-quiz").find(".el-checkbox").size() > 0 ? "multiple-choice" : "single-choice";
initOtherInteraction(
interactionsCount, interactionName,
scoId, scoTitle, $(this).text(), type
);
interactionsCount++;
});
// contentFrameReference.doCommit();
allInteractionsForQuizInitialized = true;
}
return interactionsCount;
}
function initAllInteractionsForDragAndDrop(interactionsCount) {
if ($(".eduexe-library-draganddrop-container").size() > 0) {
const scoId = getScoId();
const scoTitle = getScoName();
$(".eduexe-library-draganddrop-container").each(function() {
const header = $(this).find(".el-element-header-text") != undefined ? "header: " + $(this).find(".el-element-header-text").text() : "";
const interactionName = "type: matching " + header + " sco: " + getScoName() + " (" + getScoId() + ")";
initOtherInteraction(
interactionsCount, interactionName,
scoId, scoTitle, $(this).text(), "matching"
);
interactionsCount++;
});
// contentFrameReference.doCommit();
allInteractionsForDragAndDropInitialized = true;
}
return interactionsCount;
}
function initAllInteractionsForLinks(interactionsCount) {
if ($("a[target='popup']").size() > 0) {
const scoId = getScoId();
const scoTitle = getScoName();
$("a[target='popup']").each(function() {
const interactionName = $(this).text() + " url:" + $(this).attr("href") + " sco: " + getScoName() + " (" + getScoId() + ")";
initOtherInteraction(
interactionsCount, interactionName,
scoId, scoTitle, $(this).prop('outerHTML'), "other"
);
interactionsCount++;
});
// contentFrameReference.doCommit();
allInteractionsForLinksInitialized = true;
}
return interactionsCount;
}
function getCurrentInteractionId(interactionName) {
return interactiondIDList.indexOf(btoa(encodeURIComponent(interactionName)));
}
function prepare(interactionsCountSet, interactionName) {
let interactionsCount = 0;
console.log("interactionsCountSet:" + interactionsCountSet);
if (!interactionsCountSet) {
getContentFrameReference();
getCourseWindowReference();
interactionsCount = countInteractionsFunction();
} else {
interactionsCount = interactiondIDList.length;
}
interactionsCountSet = true;
if (!allInteractionsForLinksInitialized) {
interactionsCount = initAllInteractionsForLinks(interactionsCount);
}
if (!allInteractionsForQuizInitialized) {
interactionsCount = initAllInteractionsForQuiz(interactionsCount);
}
if (!allInteractionsForFillInInitialized) {
interactionsCount = initAllInteractionsForFillIn(interactionsCount);
}
if (!allInteractionsForFillInNumericInitialized) {
interactionsCount = initAllInteractionsForFillInNumeric(interactionsCount);
}
if (!allInteractionsForDragAndDropInitialized) {
interactionsCount = initAllInteractionsForDragAndDrop(interactionsCount);
}
let interactionId = null;
if (interactionName != null) {
interactionId = getCurrentInteractionId(interactionName);
}
const result = {
interactionsCountSet: interactionsCountSet,
interactionId: interactionId
};
return result;
}
function getScoId() {
return "sco_" + $("body").attr("data-sco-id").trim();
}
function getScoName() {
return $(".eduexe-sco-title").first().text().trim();
}
function initOtherInteraction(interactionID, interactionName, scoId, scoTitle, content, type) {
const interEncoded = btoa(encodeURIComponent(interactionName));
if (interactiondIDList.indexOf(interEncoded) == -1) {
interactiondIDList.push(interEncoded);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.id', interEncoded);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.type', "other");
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.timestamp', scoReadyTime);
contentFrameReference.CMI.encodeAndSetValue('cmi.interactions.' + interactionID + '.description', content);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.weighting', 0.0);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.0.id', '0:learning_object');
const scoTitleSafe = btoa(encodeURIComponent(scoTitle));
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.1.id', '1:' + scoTitleSafe);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.2.id', '2:' + scoId);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.3.id', '3:' + type);
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.4.id', '4:');
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.5.id', '5:drawn');
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.6.id', '6:');
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.7.id', '7:');
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.8.id', '8:');
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.9.id', '9:');
contentFrameReference.doSetValue('cmi.interactions.' + interactionID + '.objectives.10.id', '10:');
}
}
function saveData(interactionId, answer, result) {
updateLearnerResponse(interactionId, answer);
updateResult(interactionId, result);
saveInteractionChangeTime(interactionId, answer, result);
saveTimeSpentInInteractions(interactionId);
// contentFrameReference.doCommit();
}
function saveInteractionLatency(interactionId) {
const latencyFromCMI = contentFrameReference.doGetValue("cmi.interactions." + interactionId + ".latency");
if (latencyFromCMI == "") {
const testStartTime = Date.parse(doGetValue("cmi.interactions." + interactionId + ".timestamp") + ".000Z");
const currentTime = contentFrameReference.getTimeAsTimestamp();
const lacencyTime = currentTime - testStartTime;
const lacencyTimeSeconds = Math.round(lacencyTime / 1000);
const latencyFormatted = "PT" + lacencyTimeSeconds + "S";
contentFrameReference.doSetValue('cmi.interactions.' + interactionId + '.latency', latencyFormatted);
}
}
function updateNumberOfAnswerChanges(interactionId) {
const dataOfObjectives8Raw = contentFrameReference.doGetValue("cmi.interactions." + interactionId + ".objectives.8.id");
let numberOfAnswerChanges = dataOfObjectives8Raw.substr(2);
if (numberOfAnswerChanges == "") {
contentFrameReference.doSetValue("cmi.interactions." + interactionId + ".objectives.8.id", "8:1");
} else {
numberOfAnswerChanges = parseInt(numberOfAnswerChanges) + 1;
contentFrameReference.doSetValue("cmi.interactions." + interactionId + ".objectives.8.id", "8:" + numberOfAnswerChanges.toString());
}
}
function updateLearnerResponse(interactionId, answer) {
contentFrameReference.CMI.encodeAndSetValue('cmi.interactions.' + interactionId + '.learner_response', answer);
}
function updateResult(interactionId, result) {
contentFrameReference.doSetValue('cmi.interactions.' + interactionId + '.result', result);
}
function saveInteractionChangeTime(interactionId, answer, result) {
const dataOfObjectives9Raw = contentFrameReference.doGetValue("cmi.interactions." + interactionId + ".objectives.9.id");
const changesArrayStr = dataOfObjectives9Raw.substr(2);
let changesArrayObj = [];
if (changesArrayStr != "") {
changesArrayObj = JSON.parse(contentFrameReference.CMI.eduexeDecodeText(changesArrayStr));
}
if (answer.length > 0 && ((changesArrayObj.length > 0 && changesArrayObj[changesArrayObj.length - 1].answer != answer && answer != "clicked") || changesArrayObj.length == 0)) {
changesArrayObj.push({ "time": contentFrameReference.getTimeAsTimestamp(), "answer": answer, "result": result });
saveInteractionLatency(interactionId);
updateNumberOfAnswerChanges(interactionId);
contentFrameReference.doSetValue("cmi.interactions." + interactionId + ".objectives.9.id", "9:"
+ contentFrameReference.CMI.eduexeEncodeText(JSON.stringify(changesArrayObj)));
}
}
function saveTimeSpentInInteractions(interactionId) {
const currentTime = Date.now();
const intStarted = contentFrameReference.doGetValue('cmi.interactions.' + interactionId + '.timestamp');
const intStartedDate = new Date(intStarted + '.000Z');
let timeSpent = currentTime - intStartedDate.getTime();
timeSpent = Math.round(timeSpent / 1000);
let timeSpentPrevious = contentFrameReference.doGetValue('cmi.interactions.' + interactionId + '.objectives.10.id');
timeSpentPrevious = timeSpentPrevious.substr(3);
if (timeSpentPrevious == "") {
contentFrameReference.doSetValue('cmi.interactions.' + interactionId + '.objectives.10.id', "10:" + timeSpent.toString());
} else {
timeSpent += parseInt(timeSpentPrevious);
contentFrameReference.doSetValue('cmi.interactions.' + interactionId + '.objectives.10.id', "10:" + timeSpent.toString());
}
}
function getTimeAsStringShort() {
const date = new Date();
return dateStr = date.toISOString().split(".")[0];
}