Commit d1625e52 authored by Konstantin Schulz's avatar Konstantin Schulz
Browse files

fixed solution review for drag text exercises in test module

parent f27a514c
import StatementBase from 'src/app/models/xAPI/StatementBase';
export class TestResultMC {
public statement: StatementBase;
public innerHTML: string;
constructor(init?: Partial<TestResultMC>) {
Object.assign(this, init);
}
}
......@@ -15,7 +15,6 @@
<ion-content padding>
<ion-grid>
<!-- <ion-row><ion-col><ion-button (click)="test()">Test</ion-button></ion-col></ion-row>-->
<!-- for easy navigation during development -->
<ion-row *ngIf="HelperService.isDevMode; else production">
<ion-col>
......@@ -140,7 +139,7 @@
</ion-button>
</ion-row>
<!-- step 6: show results -->
<div *ngIf="currentState == TestModuleState.showResults">
<div *ngIf="currentState == TestModuleState.showResults && currentExerciseIndex === exerciseCount - 1">
<ion-row>
<ion-col>
<h4>{{'UNIT_EVALUATION_HEADER' | translate}}</h4>
......
......@@ -12,6 +12,7 @@ import Activity from 'src/app/models/xAPI/Activity';
import LanguageMap from 'src/app/models/xAPI/LanguageMap';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import Context from 'src/app/models/xAPI/Context';
import {TestResultMC} from 'src/app/models/testResultMC';
declare var H5P: any;
declare var $: any;
......@@ -44,8 +45,6 @@ export class TestPage implements OnDestroy, OnInit {
}), new ExercisePart({
startIndex: 0,
durationSeconds: 300,
// TODO: DELETE DRAG_TEXT_1
// 'drag_text_1',
exercises: ['fill_blanks_1', 'multi_choice_1', 'multi_choice_2',
'multi_choice_3', 'multi_choice_4', 'multi_choice_5', 'multi_choice_6',
'multi_choice_7', 'multi_choice_8', 'fill_blanks_2', 'fill_blanks_3',
......@@ -101,7 +100,9 @@ export class TestPage implements OnDestroy, OnInit {
public h5pDragTextString = 'H5P.DragText';
public h5pIframeString = '#h5p-iframe-1';
public h5pMultiChoiceString = 'H5P.MultiChoice';
public h5pRetryClassString = '.h5p-question-try-again';
public h5pRowIDstring = '#h5p-row';
public h5pShowSolutionClassString = '.h5p-question-show-solution';
public h5pTextInputClassString = '.h5p-text-input';
public hideClassString = 'hide';
public knownCount: [number, number];
......@@ -124,6 +125,16 @@ export class TestPage implements OnDestroy, OnInit {
this.translate.get('DATA_ALREADY_SENT').subscribe(value => this.dataAlreadySentMessage = value);
}
addScore(allTestIndices: number[], exercisePartIndex: number) {
const relevantTestIndices = allTestIndices.filter(
x => this.currentExerciseParts[exercisePartIndex].startIndex <= x && (!this
.currentExerciseParts[exercisePartIndex + 1] || x < this.currentExerciseParts[exercisePartIndex + 1].startIndex));
const correctlySolved = relevantTestIndices.filter((i) => {
return this.vocService.currentTestResults[i].statement.result.score.scaled === 1;
});
this.results.push([correctlySolved.length, relevantTestIndices.length]);
}
adjustStartIndices() {
[...Array(this.currentExerciseParts.length).keys()].forEach((index: number) => {
if (index === 0) {
......@@ -134,14 +145,17 @@ export class TestPage implements OnDestroy, OnInit {
});
}
addScore(allTestIndices: number[], exercisePartIndex: number) {
const relevantTestIndices = allTestIndices.filter(
x => this.currentExerciseParts[exercisePartIndex].startIndex <= x && (!this
.currentExerciseParts[exercisePartIndex + 1] || x < this.currentExerciseParts[exercisePartIndex + 1].startIndex));
const correctlySolved = relevantTestIndices.filter((i) => {
return this.vocService.currentTestResults[i].result.score.scaled === 1;
});
this.results.push([correctlySolved.length, relevantTestIndices.length]);
adjustTimer(newIndex: number, review: boolean) {
if (!review) {
const metaIndex: number = this.currentExerciseParts.map(x => x.startIndex).indexOf(newIndex);
if (metaIndex > -1 && this.currentExerciseParts[metaIndex].durationSeconds > 0) {
this.removeTimer(false);
this.initTimer(this.currentExerciseParts[metaIndex].durationSeconds);
}
}
if (newIndex === this.currentExerciseParts[this.currentExerciseParts.length - 1].startIndex) {
this.removeTimer(true);
}
}
analyzeResults() {
......@@ -150,7 +164,7 @@ export class TestPage implements OnDestroy, OnInit {
// pretest
let relevantTestIndices = allTestIndices.filter(x => x < this.currentExerciseParts[2].startIndex);
let correctlySolved: number[] = relevantTestIndices.filter((i) => {
return this.vocService.currentTestResults[i].result.score.scaled === 1;
return this.vocService.currentTestResults[i].statement.result.score.scaled === 1;
});
this.results.push([correctlySolved.length, relevantTestIndices.length]);
// text comprehension
......@@ -158,10 +172,10 @@ export class TestPage implements OnDestroy, OnInit {
// exercises
relevantTestIndices = allTestIndices.filter(
x => this.currentExerciseParts[3].startIndex <= x && x < this.currentExerciseParts[4].startIndex);
correctlySolved = relevantTestIndices.map(i => this.vocService.currentTestResults[i].result.score.raw);
correctlySolved = relevantTestIndices.map(i => this.vocService.currentTestResults[i].statement.result.score.raw);
const scoreObserved: number = correctlySolved.length ? correctlySolved.reduce((x, y) => x + y) : 0;
const scoreExpected: number = relevantTestIndices.length ? relevantTestIndices.map(
i => this.vocService.currentTestResults[i].result.score.max).reduce((x, y) => x + y) : 0;
i => this.vocService.currentTestResults[i].statement.result.score.max).reduce((x, y) => x + y) : 0;
this.results.push([scoreObserved, scoreExpected]);
// posttest
this.addScore(allTestIndices, 4);
......@@ -182,6 +196,34 @@ export class TestPage implements OnDestroy, OnInit {
return await HelperService.currentPopover.present();
}
finishExercise(event: XAPIevent) {
document.querySelector(this.h5pRowIDstring).classList.add(this.hideClassString);
// dirty hack to wait for the solutions being processed by H5P
setTimeout(() => {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
if (iframe) {
const iframeDoc: Document = iframe.contentWindow.document;
const solutionButton: HTMLButtonElement = iframeDoc.body.querySelector(this.h5pShowSolutionClassString);
if (solutionButton) {
solutionButton.click();
}
const inner: string = iframeDoc.documentElement.innerHTML;
this.vocService.currentTestResults[this.currentExerciseIndex] = new TestResultMC({
statement: event.data.statement,
innerHTML: inner
});
const knownCheckbox: HTMLInputElement = iframeDoc.body.querySelector('#known');
if (knownCheckbox) {
this.knownCount = [this.knownCount[0] + (knownCheckbox.checked ? 1 : 0), this.knownCount[1] + 1];
}
}
if (!this.didTimeRunOut) {
this.continue();
}
document.querySelector(this.h5pRowIDstring).classList.remove(this.hideClassString);
}, 200);
}
getCurrentExerciseName() {
const targetPartIndex: number = this.getCurrentExercisePartIndex();
return this.currentExerciseParts[targetPartIndex]
......@@ -194,12 +236,20 @@ export class TestPage implements OnDestroy, OnInit {
|| this.currentExerciseParts[i + 1].startIndex > this.currentExerciseIndex));
}
hideRetryButton() {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
const iframeDoc: Document = iframe.contentWindow.document;
// hide the retry button during review
const retryButton: HTMLButtonElement = iframeDoc.documentElement.querySelector(this.h5pRetryClassString);
if (retryButton) {
retryButton.style.display = 'none';
}
}
public initH5P(exerciseType: string) {
// dirty hack to get H5P going without explicit button click on the new page
setTimeout(() => {
H5P.jQuery('.h5p-container').empty().h5p({
// TODO: CHANGE TO MINIFIED
// frameJs: 'assets/dist/js/h5p-standalone-frame.js',
frameJs: 'assets/dist/js/h5p-standalone-frame.min.js',
frameCss: 'assets/dist/styles/h5p.css',
h5pContent: 'assets/h5p/' + exerciseType
......@@ -299,11 +349,12 @@ export class TestPage implements OnDestroy, OnInit {
return;
}
const learnerId: string = this.vocService.currentTestResults[Object.keys(this.vocService.currentTestResults)[0]]
.actor.account.name;
.statement.actor.account.name;
const fileUrl: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiFilePath'] + '/' + learnerId;
HelperService.currentError = null;
HelperService.isLoading = true;
this.http.post(fileUrl, JSON.stringify(this.vocService.currentTestResults)).subscribe(async () => {
this.http.post(fileUrl, JSON.stringify(Object.keys(this.vocService.currentTestResults).map(
i => this.vocService.currentTestResults[i].statement))).subscribe(async () => {
HelperService.isLoading = false;
this.wasDataSent = true;
const toast = await this.toastCtrl.create({
......@@ -331,17 +382,7 @@ export class TestPage implements OnDestroy, OnInit {
}
// results are only available when a task has been completed/answered, not in the "attempted" or "interacted" stages
if (event.data.statement.verb.id === HelperService.config['xAPIverbIDanswered'] && event.data.statement.result) {
this.vocService.currentTestResults[this.currentExerciseIndex] = event.data.statement;
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
if (iframe) {
const knownCheckbox: HTMLInputElement = iframe.contentWindow.document.body.querySelector('#known');
if (knownCheckbox) {
this.knownCount = [this.knownCount[0] + (knownCheckbox.checked ? 1 : 0), this.knownCount[1] + 1];
}
}
if (!this.didTimeRunOut) {
this.continue();
}
this.finishExercise(event);
}
});
H5P.externalDispatcher.on('domChanged', (event: any) => {
......@@ -358,16 +399,7 @@ export class TestPage implements OnDestroy, OnInit {
}
showNextExercise(newIndex: number, review: boolean = false) {
if (!review) {
const metaIndex: number = this.currentExerciseParts.map(x => x.startIndex).indexOf(newIndex);
if (metaIndex > -1 && this.currentExerciseParts[metaIndex].durationSeconds > 0) {
this.removeTimer(false);
this.initTimer(this.currentExerciseParts[metaIndex].durationSeconds);
}
}
if (newIndex === this.currentExerciseParts[this.currentExerciseParts.length - 1].startIndex) {
this.removeTimer(true);
}
this.adjustTimer(newIndex, review);
const currentExercisePart: ExercisePart = this.currentExerciseParts[this.getCurrentExercisePartIndex()];
const maxProgress: number = currentExercisePart.exercises.length;
this.progressBarValue = (newIndex - currentExercisePart.startIndex) / maxProgress;
......@@ -375,7 +407,7 @@ export class TestPage implements OnDestroy, OnInit {
const currentExerciseName = this.getCurrentExerciseName();
if (currentExerciseName.startsWith(this.nonH5Pstring)) {
document.querySelector(this.h5pRowIDstring).classList.add(this.hideClassString);
if (!review && newIndex === this.currentExerciseParts[this.currentExerciseParts.length - 1].startIndex) {
if (newIndex === this.currentExerciseParts[this.currentExerciseParts.length - 1].startIndex) {
this.analyzeResults();
H5P.externalDispatcher.off('xAPI');
this.updateUI();
......@@ -384,6 +416,17 @@ export class TestPage implements OnDestroy, OnInit {
}
this.currentState = review ? TestModuleState.showSolutions : TestModuleState.inProgress;
document.querySelector(this.h5pRowIDstring).classList.remove(this.hideClassString);
if (review && this.vocService.currentTestResults[this.currentExerciseIndex]) {
const id = this.vocService.currentTestResults[this.currentExerciseIndex].statement.context.contextActivities.category[0].id;
// handle the drag text exercise solutions
if (id.indexOf(this.h5pDragTextString) > -1) {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
const iframeDoc: Document = iframe.contentWindow.document;
iframeDoc.documentElement.innerHTML = this.vocService.currentTestResults[this.currentExerciseIndex].innerHTML;
this.hideRetryButton();
return;
}
}
const fileName: string = currentExerciseName.split('_').slice(-1) + '_' + this.translate.currentLang + '.json';
let exerciseType = currentExerciseName.split('_').slice(0, 2).join('_');
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'],
......@@ -419,9 +462,9 @@ export class TestPage implements OnDestroy, OnInit {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
if (iframe) {
if (this.vocService.currentTestResults[this.currentExerciseIndex]) {
const oldActivity: Activity = this.vocService.currentTestResults[this.currentExerciseIndex].object as Activity;
const oldContext: Context = this.vocService.currentTestResults[this.currentExerciseIndex].context;
const oldResponse: string = this.vocService.currentTestResults[this.currentExerciseIndex].result.response;
const oldActivity: Activity = this.vocService.currentTestResults[this.currentExerciseIndex].statement.object as Activity;
const oldContext: Context = this.vocService.currentTestResults[this.currentExerciseIndex].statement.context;
const oldResponse: string = this.vocService.currentTestResults[this.currentExerciseIndex].statement.result.response;
const singleResponses: string[] = oldResponse.split('[,]');
if (oldContext.contextActivities.category[0].id.indexOf(this.h5pMultiChoiceString) > -1) {
const oldChosen: { description: LanguageMap, id: string }[] = oldActivity.definition.choices.filter(
......@@ -439,46 +482,14 @@ export class TestPage implements OnDestroy, OnInit {
input.value = singleResponses[index];
});
} else if (oldContext.contextActivities.category[0].id.indexOf(this.h5pDragTextString) > -1) {
if (false) {
const dragClass = '.ui-draggable';
const dropClass = '.ui-droppable';
const solutionContainer = '<div class="h5p-drag-show-solution-container" style="display: none;"></div>';
// const startDragElement = element.all(by.css(dragClass));
// const endDragElement = element.all(by.css(dropClass));
// browser.actions().dragAndDrop(startDragElement, endDragElement).perform().then(); // .perform();
// setTimeout(() => {
// }, 250);
const draggables: HTMLCollection = $(dragClass, iframe.contentWindow.document);
const dropZones: HTMLCollection = $(dropClass, iframe.contentWindow.document);
$(dropZones).each((index: number, dropZone: HTMLDivElement) => {
if (singleResponses[index]) {
// H5P.DragText.drop($(draggables[index]), dropZone);
// $(draggables[index]).appendTo(dropZone);
}
// $(dropZone).append(solutionContainer);
// ('<div></div>').addClass('h5p-drag-show-solution-container');
});
// tslint:disable-next-line:prefer-for-of
// for (let i = 0; i < dropZones.length; i++) {
// console.log(dropZones[i]);
// }
const b = 0;
// EMPTY ZONE
// <div aria-dropeffect="none" aria-label="Drop Zone 2. Drop Zone 2 is empty." tabindex="-1" class="ui-droppable" style="width: 69px;"></div>
// FILLED ZONE >> first element in empty drop zone
// <div aria-dropeffect="none" aria-label="Drop Zone 3. Drop Zone 3 contains draggable scribo." tabindex="0" class="ui-droppable" style="width: 69px;"><div role="button" aria-grabbed="false" tabindex="-1" class="ui-draggable h5p-drag-dropped" aria-label="Draggable scribo. 1 of 3 draggables. Draggable is grabbed." style="position: relative; touch-action: none; left: 0px; top: 0px;">scribo</div></div>
// SOLUTION CONTAINER >> second element in empty drop zone
// <div class="h5p-drag-show-solution-container" style="display: none;"></div>
// UNTOUCHED DRAGGABLE
// <div role="button" aria-grabbed="false" tabindex="0" class="ui-draggable" aria-label="Draggable facere. 2 of 3 draggables. " style="position: relative; touch-action: none; left: 0px; top: 0px;">facere</div>
}
// this case is handled elsewhere because we cannot fake the drag text exercises easily
}
}
const checkButton: HTMLButtonElement = iframe.contentWindow.document.body.querySelector(this.h5pCheckButtonClassString);
if (checkButton) {
// prevent the check button from jumping to the next exercise
checkButton.click();
this.hideRetryButton();
}
}
}
......@@ -487,27 +498,4 @@ export class TestPage implements OnDestroy, OnInit {
// dirty hack to trigger ngIf evaluation & data bindings
(document.querySelector('#refreshUI') as HTMLLinkElement).click();
}
test() {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
// H5P.jQuery.fn.on('select', (event: any) => {
// console.log(event);
// });
H5P.externalDispatcher.on('*', (event: any) => {
console.log(event);
});
if (iframe) {
const dragClass = '.ui-draggable';
const dropClass = '.ui-droppable';
// const draggables: HTMLCollection = $(dragClass, iframe.contentWindow.document);
// const dropZones: HTMLCollection = $(dropClass, iframe.contentWindow.document);
const draggables: NodeList = iframe.contentWindow.document.querySelectorAll(dragClass);
const dropZones: NodeList = iframe.contentWindow.document.querySelectorAll(dropClass);
// H5P.externalDispatcher.trigger('before-select', draggables[0]);
H5P.externalDispatcher.trigger('start');
H5P.externalDispatcher.trigger('select', {target: dropZones[0], element: dragClass[0]});
// H5P.externalDispatcher.trigger('select', {target: dropZones[0], element: draggables[0]});
// H5P.externalDispatcher.trigger('drop', {target: dropZones[0], element: draggables[0]});
}
}
}
......@@ -4,7 +4,7 @@ import {VocabularyCorpus} from 'src/app/models/enum';
import {Vocabulary} from 'src/app/models/vocabulary';
import {Sentence} from 'src/app/models/sentence';
import {HelperService} from 'src/app/helper.service';
import StatementBase from "src/app/models/xAPI/StatementBase";
import {TestResultMC} from 'src/app/models/testResultMC';
@Injectable({
providedIn: 'root'
......@@ -12,7 +12,7 @@ import StatementBase from "src/app/models/xAPI/StatementBase";
export class VocabularyService {
currentReferenceVocabulary: VocabularyCorpus = VocabularyCorpus.bws;
currentSentences: Sentence[] = [];
currentTestResults: { [exerciseIndex: number]: StatementBase } = {};
currentTestResults: { [exerciseIndex: number]: TestResultMC } = {};
// desiredMatchingPercentage: number = 50;
desiredSentenceCount = 10;
frequencyUpperBound = 500;
......
......@@ -31,4 +31,4 @@
"correctAnswer": "Correct answer:",
"feedbackHeader": "Feedback",
"scoreBarLabel": "You got :num out of :total points"
}
\ No newline at end of file
}
......@@ -35,10 +35,6 @@
<!-- H5P integration, can be called using the global H5P variable -->
<script type="text/javascript" src="assets/dist/js/h5p-standalone-main.min.js"></script>
<!-- <script type="text/javascript" src="assets/dist/js/h5p-standalone-main.js"></script>-->
<!-- <script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>-->
</head>
<body>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment