Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 5ae7af9

Browse files
committed
✨ Allow saving previous student answer
1 parent df7beb3 commit 5ae7af9

5 files changed

Lines changed: 132 additions & 79 deletions

File tree

runestone/hparsons/hparsons.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,6 @@ def run(self):
228228
# For MChoice its better to insert the qnum into the content before further processing.
229229
# self.updateContent()
230230

231-
# TODO: fix the nested parse
232-
# same as mchoice, different from parsons. i think it is for generating instructions.
233-
# parsons:
234231
self.state.nested_parse(
235232
self.options['instructions'], self.content_offset, hparsons_node
236233
)

runestone/hparsons/js/hparsons.js

Lines changed: 50 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,31 @@ if (hpList === undefined) hpList = {};
2222
export default class HParsons extends RunestoneBase {
2323
constructor(opts) {
2424
super(opts);
25-
if (opts) {
26-
// TODO: what is orig?
27-
var orig = opts.orig; // entire <pre> element that will be replaced by new HTML
28-
this.containerDiv = orig;
29-
this.origElem = $(orig).find(".hparsons")[0];
30-
console.log(this.origElem)
31-
this.useRunestoneServices =
32-
opts.useRunestoneServices || eBookConfig.useRunestoneServices;
33-
this.divid = orig.id;
34-
35-
// The element that is going to be replaced
36-
// Find the question text and store it in .question
37-
this.question = $(orig).find(`.hparsons_question`)[0];
38-
// TODO: idk what this is with shortanswer
39-
this.renderHTML();
40-
this.caption = "hparsons";
41-
this.addCaption("runestone");
42-
this.checkServer("hparsons", true);
43-
44-
// Set the storageId (key for storing data)
45-
var storageId = super.localStorageKey();
46-
this.storageId = storageId;
47-
this.children = this.origElem.childNodes; // this contains all of the child elements of the entire tag...
48-
this.contentArray = [];
49-
HParsons.counter++; // Unique identifier
50-
this.counterId = "hparsons-" + HParsons.counter;
51-
}
25+
var orig = opts.orig;
26+
this.containerDiv = orig;
27+
// the div element that will contain regex-element
28+
this.origElem = $(orig).find(".hparsons")[0];
29+
this.useRunestoneServices =
30+
opts.useRunestoneServices || eBookConfig.useRunestoneServices;
31+
this.divid = orig.id;
32+
33+
34+
// Set the storageId (key for storing data)
35+
var storageId = super.localStorageKey();
36+
this.storageId = storageId;
37+
// this.setLocalStorage({test: 'test1'})
38+
this.children = this.origElem.childNodes; // this contains all of the child elements of the entire tag...
39+
this.contentArray = [];
40+
HParsons.counter++; // Unique identifier
41+
this.counterId = "hparsons-" + HParsons.counter;
42+
43+
// The element that is going to be replaced
44+
// Find the question text and store it in .question
45+
this.renderHTML();
46+
this.origElem.addEventListener('regex-element', (e) => {this.handleRegexElementEvent(e)});
47+
this.caption = "hparsons";
48+
this.addCaption("runestone");
49+
this.checkServer("hparsons", true);
5250
}
5351

5452
renderHTML() {
@@ -57,12 +55,12 @@ export default class HParsons extends RunestoneBase {
5755
// div.id = 'abcd';
5856
// console.log(this.origElem)
5957
let attributes = '';
60-
console.log($(this.origElem).data("textentry"))
6158
let settings = JSON.parse($(this.origElem).children()[0].innerText)
6259
attributes += ' input-type=' + ($(this.origElem).data("textentry") ? 'text' : 'parsons' );
6360
attributes += $(this.origElem).data("hidetests") ? ' hidetests="true"': '';
6461
$(this.origElem).html('<regex-element' + attributes + '></regex-element>');
6562
let regexElement = $(this.origElem).children()[0];
63+
this.regexElement = regexElement;
6664
if ($(this.origElem).data("nostrictmatch")) {
6765
regexElement.unitTestTable.strictMatch = false;
6866
} else {
@@ -89,11 +87,11 @@ export default class HParsons extends RunestoneBase {
8987
if (settings.testcases) {
9088
for (let index in settings.testcases) {
9189
settings.testcases[index].expect = settings.testcases[index].expect.length < 4 ? [] : settings.testcases[index].expect.slice(2, -2).split(', ');
92-
console.log(settings.testcases[index])
9390
}
9491
regexElement.setTestCases(settings.testcases);
9592
}
9693

94+
9795
// tool.parsonsExplanation = toolConfig.parsonsExplanation;
9896
// tool.parsonsData = toolConfig.parsonsData;
9997
regexElement.resetTool();
@@ -105,6 +103,7 @@ export default class HParsons extends RunestoneBase {
105103
// $(this.origElem).replaceWith(document.createElement('regex-element'));
106104
// $(this.elem).innerHTML = `<regex-element input-type='parsons' id="abcd"></regex-element>`;
107105
// console.log(div)
106+
// localStorage.setItem(this.storageId, JSON.stringify({test: 'test'}));
108107
}
109108

110109
checkCurrentAnswer() { }
@@ -136,31 +135,26 @@ export default class HParsons extends RunestoneBase {
136135
// $(this.feedbackDiv).addClass("alert alert-success");
137136
}
138137
setLocalStorage(data) {
139-
// console.log('hparsons, setlocalstorage')
140-
// if (!this.graderactive) {
141-
// let key = this.localStorageKey();
142-
// localStorage.setItem(key, JSON.stringify(data));
143-
// }
138+
localStorage.setItem(this.storageId, JSON.stringify(data));
144139
}
145140
checkLocalStorage() {
146-
console.log('hparsons, checklocalstorage')
147-
var toStore;
148-
if (data == undefined) {
149-
toStore = {
150-
source: this.sourceHash(),
151-
answer: this.answerHash(),
152-
timestamp: new Date(),
153-
};
154-
var adaptiveHash = this.adaptiveHash();
155-
if (adaptiveHash.length > 0) {
156-
toStore.adaptive = adaptiveHash;
141+
// Return what is stored in local storage
142+
var data = localStorage.getItem(this.storageId);
143+
if (data !== null) {
144+
if (data.charAt(0) == "{") {
145+
data = JSON.parse(data);
146+
} else {
147+
data = {};
157148
}
158149
} else {
159-
toStore = data;
150+
data = {};
151+
}
152+
if (data.type != undefined && data.answer != undefined) {
153+
this.regexElement.restoreAnswer(data.type, data.answer)
160154
}
161-
localStorage.setItem(this.storageId, JSON.stringify(toStore));
162155
}
163-
restoreAnswers(data) {
156+
// called when server has data
157+
restoreAnswers(serverData) {
164158
console.log('hparsons, restoreanswers')
165159
// Restore answers from storage retrieval done in RunestoneBase
166160
// sometimes data.answer can be null
@@ -231,6 +225,14 @@ export default class HParsons extends RunestoneBase {
231225
console.log('hparsons, disableinteraction')
232226
// this.jTextArea.disabled = true;
233227
}
228+
229+
handleRegexElementEvent(event) {
230+
if (event.detail['event-type'] == 'parsons-input' || event.detail['event-type'] == 'text-input') {
231+
this.setLocalStorage({'type': event.detail['event-type'].slice(0, -6), 'answer': event.detail['answer']})
232+
// this.setLocalStorage({'event-type': event.detail['event-type'], 'answer': event.detail['answer'][0]})
233+
// this.setLocalStorage({tes: 'tes'})
234+
}
235+
}
234236
}
235237
HParsons.counter = 0;
236238
/*=================================

runestone/hparsons/js/regex-element.js

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3163,7 +3163,7 @@ class ParsonsInput {
31633163
newBlock.parentNode?.removeChild(newBlock);
31643164
if (this.parentElement) {
31653165
this.parentElement.temporaryInputEvent = {
3166-
'event-type': 'parsons',
3166+
'event-type': 'parsons-input',
31673167
action: RegexEvent.ParsonsInputAction.REMOVE,
31683168
position: [endPosition, -1],
31693169
answer: this._getTextArray()
@@ -3177,7 +3177,7 @@ class ParsonsInput {
31773177
}
31783178
if (this.parentElement && firstBlock) {
31793179
this.parentElement.temporaryInputEvent = {
3180-
'event-type': 'parsons',
3180+
'event-type': 'parsons-input',
31813181
action: RegexEvent.ParsonsInputAction.ADD,
31823182
position: [-1, this._getBlockPosition(firstBlock)],
31833183
answer: this._getTextArray(),
@@ -3244,7 +3244,7 @@ class ParsonsInput {
32443244
newBlockCopy.parentNode?.removeChild(newBlockCopy);
32453245
if (this.parentElement) {
32463246
this.parentElement.temporaryInputEvent = {
3247-
'event-type': 'parsons',
3247+
'event-type': 'parsons-input',
32483248
action: RegexEvent.ParsonsInputAction.REMOVE,
32493249
position: [endPosition, -1],
32503250
answer: this._getTextArray()
@@ -3254,7 +3254,7 @@ class ParsonsInput {
32543254
};
32553255
if (this.parentElement) {
32563256
this.parentElement.temporaryInputEvent = {
3257-
'event-type': 'parsons',
3257+
'event-type': 'parsons-input',
32583258
action: RegexEvent.ParsonsInputAction.ADD,
32593259
position: [-1, this._getBlockPosition(newBlockCopy)],
32603260
answer: this._getTextArray(),
@@ -3271,7 +3271,7 @@ class ParsonsInput {
32713271
newBlock.parentNode?.removeChild(newBlock);
32723272
if (this.parentElement) {
32733273
this.parentElement.temporaryInputEvent = {
3274-
'event-type': 'parsons',
3274+
'event-type': 'parsons-input',
32753275
action: RegexEvent.ParsonsInputAction.REMOVE,
32763276
position: [endPosition, -1],
32773277
answer: this._getTextArray(),
@@ -3317,7 +3317,7 @@ class ParsonsInput {
33173317
newBlock.parentNode?.removeChild(newBlock);
33183318
if (this.parentElement) {
33193319
this.parentElement.temporaryInputEvent = {
3320-
'event-type': 'parsons',
3320+
'event-type': 'parsons-input',
33213321
action: RegexEvent.ParsonsInputAction.REMOVE,
33223322
position: [endPosition, -1],
33233323
answer: this._getTextArray(),
@@ -3331,7 +3331,7 @@ class ParsonsInput {
33313331
}
33323332
if (this.parentElement && firstBlock) {
33333333
this.parentElement.temporaryInputEvent = {
3334-
'event-type': 'parsons',
3334+
'event-type': 'parsons-input',
33353335
action: RegexEvent.ParsonsInputAction.ADD,
33363336
position: [-1, this._getBlockPosition(firstBlock)],
33373337
answer: this._getTextArray(),
@@ -3357,7 +3357,7 @@ class ParsonsInput {
33573357
newBlockCopy.parentNode?.removeChild(newBlockCopy);
33583358
if (this.parentElement) {
33593359
this.parentElement.temporaryInputEvent = {
3360-
'event-type': 'parsons',
3360+
'event-type': 'parsons-input',
33613361
action: RegexEvent.ParsonsInputAction.REMOVE,
33623362
position: [endPosition, -1],
33633363
answer: this._getTextArray(),
@@ -3367,7 +3367,7 @@ class ParsonsInput {
33673367
};
33683368
if (this.parentElement) {
33693369
this.parentElement.temporaryInputEvent = {
3370-
'event-type': 'parsons',
3370+
'event-type': 'parsons-input',
33713371
action: RegexEvent.ParsonsInputAction.ADD,
33723372
position: [-1, this._getBlockPosition(newBlockCopy)],
33733373
answer: this._getTextArray(),
@@ -3385,7 +3385,7 @@ class ParsonsInput {
33853385
newBlock.parentNode?.removeChild(newBlock);
33863386
if (this.parentElement) {
33873387
this.parentElement.temporaryInputEvent = {
3388-
'event-type': 'parsons',
3388+
'event-type': 'parsons-input',
33893389
action: RegexEvent.ParsonsInputAction.REMOVE,
33903390
position: [endPosition, -1],
33913391
answer: this._getTextArray(),
@@ -3408,7 +3408,7 @@ class ParsonsInput {
34083408
// console.log(isExpandable);
34093409
if (this.parentElement) {
34103410
this.parentElement.temporaryInputEvent = {
3411-
'event-type': 'parsons',
3411+
'event-type': 'parsons-input',
34123412
action: RegexEvent.ParsonsInputAction.ADD,
34133413
position: [-1, this._getBlockPosition(event.item)],
34143414
answer: this._getTextArray(),
@@ -3441,7 +3441,7 @@ class ParsonsInput {
34413441
newBlock.parentNode?.removeChild(newBlock);
34423442
if (this.parentElement) {
34433443
this.parentElement.temporaryInputEvent = {
3444-
'event-type': 'parsons',
3444+
'event-type': 'parsons-input',
34453445
action: RegexEvent.ParsonsInputAction.REMOVE,
34463446
position: [endPosition, -1],
34473447
answer: this._getTextArray(),
@@ -3480,7 +3480,7 @@ class ParsonsInput {
34803480
}
34813481
if (this.parentElement) {
34823482
this.parentElement.temporaryInputEvent = {
3483-
'event-type': 'parsons',
3483+
'event-type': 'parsons-input',
34843484
action: action,
34853485
position: [this._prevPosition, endposition],
34863486
answer: this._getTextArray(),
@@ -3565,6 +3565,38 @@ class ParsonsInput {
35653565
}
35663566
}
35673567
};
3568+
restoreAnswer(type, answer) {
3569+
if (type != 'parsons' || !Array.isArray(answer)) {
3570+
return;
3571+
}
3572+
console.log('parsons restore');
3573+
console.log(answer);
3574+
this._dropArea.innerHTML = '';
3575+
for (let i = 0; i < answer.length; ++i) {
3576+
if (typeof answer[i] === 'string') {
3577+
const newBlock = document.createElement('div');
3578+
this._dropArea.appendChild(newBlock);
3579+
newBlock.innerText = answer[i];
3580+
newBlock.style.display = 'inline-block';
3581+
newBlock.classList.add('parsons-block');
3582+
newBlock.onclick = () => {
3583+
// clicking the new block generated by clicking an extendable block to remove that block
3584+
// console.log('expandable new block onclick')
3585+
const endPosition = this._getBlockPosition(newBlock);
3586+
newBlock.parentNode?.removeChild(newBlock);
3587+
if (this.parentElement) {
3588+
this.parentElement.temporaryInputEvent = {
3589+
'event-type': 'parsons-input',
3590+
action: RegexEvent.ParsonsInputAction.REMOVE,
3591+
position: [endPosition, -1],
3592+
answer: this._getTextArray()
3593+
};
3594+
}
3595+
};
3596+
this.el.dispatchEvent(new Event('regexChanged'));
3597+
}
3598+
}
3599+
}
35683600
}
35693601

35703602
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -15527,7 +15559,7 @@ class TextInput {
1552715559
shortKey: true,
1552815560
}, (range, context) => {
1552915561
const freeKeyboardEvent = {
15530-
'event-type': 'free-input-keyboard',
15562+
'event-type': 'text-input-keyboard',
1553115563
range: range,
1553215564
keys: ['ctrl', 'c']
1553315565
};
@@ -15541,7 +15573,7 @@ class TextInput {
1554115573
shortKey: true,
1554215574
}, (range, context) => {
1554315575
const freeKeyboardEvent = {
15544-
'event-type': 'free-input-keyboard',
15576+
'event-type': 'text-input-keyboard',
1554515577
range: range,
1554615578
keys: ['ctrl', 'v']
1554715579
};
@@ -15625,6 +15657,16 @@ class TextInput {
1562515657
'background': '#ff99b3'
1562615658
}, 'silent');
1562715659
};
15660+
restoreAnswer(type, answer) {
15661+
// TODO (misplaced): consider removing expandable blocks
15662+
// TODO: add logging to restoring answer
15663+
if (type != 'text' || typeof answer !== 'string') {
15664+
return;
15665+
}
15666+
console.log('text restore');
15667+
console.log(answer);
15668+
this.quill?.setText(answer);
15669+
}
1562815670
}
1562915671

1563015672
// import {Quill} from '../types/Quill';
@@ -16775,13 +16817,15 @@ class RegexElement extends HTMLElement {
1677516817
this.negativeTestStringInput.setText(this.negativeInitialTestString);
1677616818
};
1677716819
logEvent = (eventContent) => {
16778-
({
16820+
const basicEvent = {
1677916821
'student-id': window.regexStudentId || 'stub-id',
1678016822
'course-id': window.regexCourseId || 'stub-course-id',
1678116823
'problem-id': this.problemId,
1678216824
'input-type': this.inputType,
1678316825
'client-timestamp': this._getTimestamp()
16784-
});
16826+
};
16827+
const ev = new CustomEvent('regex-element', { bubbles: true, detail: { ...basicEvent, ...eventContent } });
16828+
this.dispatchEvent(ev);
1678516829
// console.log({...basicEvent, ...eventContent});
1678616830
};
1678716831
_getTimestamp = () => {
@@ -16921,7 +16965,7 @@ class RegexElement extends HTMLElement {
1692116965
this.regexInput.removeFormat();
1692216966
// logging free input event
1692316967
this.temporaryInputEvent = {
16924-
'event-type': 'free-input',
16968+
'event-type': 'text-input',
1692516969
dropped: this.regexInput.droppedText,
1692616970
delta: delta,
1692716971
answer: this.regexInput.getText()
@@ -16995,6 +17039,15 @@ class RegexElement extends HTMLElement {
1699517039
this._testStatusDiv.innerText = '';
1699617040
this.unitTestTable.setError();
1699717041
}
17042+
// restore student answer from outside storage
17043+
restoreAnswer(type, answer) {
17044+
if (type == undefined || answer == undefined) {
17045+
return;
17046+
}
17047+
console.log('regex restore');
17048+
console.log(answer);
17049+
this.regexInput.restoreAnswer(type, answer);
17050+
}
1699817051
}
1699917052
customElements.define('regex-element', RegexElement);
1700017053

0 commit comments

Comments
 (0)