Permalink
Browse files

get rid of answerList

answerList was sort of redundant, and one of the final hang-overs from
the very first version of Numbas.

Now, the process of answering a question looks like this:

* p.storeAnswer(answer) - Save the object `answer` as `p.stagedAnswer`
* p.setStudentAnswer() - `p.stagedAnswer` is used to set whatever
    "submitted answer" properties the part has.
* p.mark() - Mark the part, using `p.rawStudentAnswerAsJME()`, which
    should read the properties set by `setStudentAnswer`

Since this changes the way `storeAnswer` works, old themes will break.
This is worth doing because the answerList format was always hard
to understand.
  • Loading branch information...
christianp committed Aug 15, 2017
1 parent 13ba7ff commit 831c50ad18b4b16dc9884459e80d037afd27a6a7
@@ -165,18 +165,6 @@ Numbas.controls = /** @lends Numbas.controls */ {
job(Numbas.exam.currentQuestion.submit,Numbas.exam.currentQuestion);
},

/** Call when the student has changed their answer to a part
* @param {Array} answerList - student's answer
* @param {partpath} - id of the part being answered
* @see Numbas.Question#doPart
*/
doPart: function( answerList, partRef )
{
job(function() {
Numbas.exam.currentQuestion.doPart(answerList, partRef);
});
},

/* Show steps for a question part
* @param {partpath} partRef - id of the part
* @see Numbas.parts.Part#showSteps
@@ -273,7 +273,7 @@ Numbas.queueScript('marking',['jme','localisation','jme-variables'],function() {
try {
this.tree = jme.compile(this.expr);
} catch(e) {
throw(new Numbas.Error("marking.note.compilation error",{name:name, message:e.message}));
throw(new Numbas.Error("marking.note.compilation error",{name:this.name, message:e.message}));
}
this.vars = jme.findvars(this.tree);
}
@@ -317,6 +317,7 @@ Part.prototype = /** @lends Numbas.parts.Part.prototype */ {
* @param {Boolean} extend_base - Does this script extend the built-in script?
*/
setMarkingScript: function(markingScriptString, extend_base) {
var p = this;
var oldMarkingScript = this.markingScript;

var algo = this.markingScript = new marking.MarkingScript(markingScriptString, extend_base ? oldMarkingScript : undefined);
@@ -408,11 +409,6 @@ Part.prototype = /** @lends Numbas.parts.Part.prototype */ {
*/
stagedAnswer: undefined,

/** Student's last submitted answer - a copy of {@link Numbas.parts.Part.stagedAnswer} taken when they submitted.
* @type {Array.<String>}
*/
answerList: undefined,

/** Has this part been answered?
* @type {Boolean}
*/
@@ -617,9 +613,11 @@ Part.prototype = /** @lends Numbas.parts.Part.prototype */ {
},

/** Update the stored answer from the student (called when the student changes their answer, but before submitting)
* @param {*} answer
* @see {Numbas.parts.Part.stagedAnswer}
*/
storeAnswer: function(answerList) {
this.stagedAnswer = answerList;
storeAnswer: function(answer) {
this.stagedAnswer = answer;
this.setDirty(true);
this.display && this.display.removeWarnings();
},
@@ -671,9 +669,6 @@ Part.prototype = /** @lends Numbas.parts.Part.prototype */ {
: R('part.marking.revealed steps no penalty'));
}

if(this.stagedAnswer) {
this.answerList = util.copyarray(this.stagedAnswer);
}
this.setStudentAnswer();

if(this.doesMarking) {
@@ -872,7 +867,7 @@ Part.prototype = /** @lends Numbas.parts.Part.prototype */ {
*/
getCorrectAnswer: function(scope) {},

/** Save a copy of the student's answer as entered on the page, for use in marking.
/** Save an answer entered by the student, for use in marking.
* @abstract
*/
setStudentAnswer: function() {},
@@ -899,12 +894,13 @@ Part.prototype = /** @lends Numbas.parts.Part.prototype */ {
* @returns {Numbas.marking.finalised_state}
*/
mark: function() {
if(this.answerList==undefined) {
var studentAnswer = this.rawStudentAnswerAsJME();
if(studentAnswer==undefined) {
this.setCredit(0,R('part.marking.nothing entered'));
return;
}

var result = this.mark_answer(this.rawStudentAnswerAsJME());
var result = this.mark_answer(studentAnswer);

var finalised_result = marking.finalise_state(result.states.mark)
this.apply_feedback(finalised_result);
@@ -106,11 +106,14 @@ GapFillPart.prototype = /** @lends Numbas.parts.GapFillPart.prototype */
return new Numbas.jme.types.TList(this.gaps.map(function(g){return g.rawStudentAnswerAsJME()}));
},

storeAnswer: function(answer) {
this.gaps.forEach(function(g,i) {
g.storeAnswer(answer[i]);
})
},

setStudentAnswer: function() {
this.studentAnswer = this.gaps.map(function(g) {
if(g.stagedAnswer) {
g.answerList = util.copyarray(g.stagedAnswer);
}
g.setStudentAnswer();
return g.studentAnswer;
});
@@ -159,7 +162,7 @@ GapFillPart.prototype = /** @lends Numbas.parts.GapFillPart.prototype */
}

};
['loadFromXML','resume','finaliseLoad','loadFromJSON'].forEach(function(method) {
['loadFromXML','resume','finaliseLoad','loadFromJSON','storeAnswer'].forEach(function(method) {
GapFillPart.prototype[method] = util.extend(Part.prototype[method], GapFillPart.prototype[method]);
});
['revealAnswer'].forEach(function(method) {
@@ -256,7 +256,7 @@ JMEPart.prototype = /** @lends Numbas.JMEPart.prototype */
/** Save a copy of the student's answer as entered on the page, for use in marking.
*/
setStudentAnswer: function() {
this.studentAnswer = this.answerList[0];
this.studentAnswer = this.stagedAnswer;
},

/** Get the student's answer as it was entered as a JME data type, to be used in the custom marking algorithm
@@ -183,9 +183,9 @@ MatrixEntryPart.prototype = /** @lends Numbas.parts.MatrixEntryPart.prototype */
/** Save a copy of the student's answer as entered on the page, for use in marking.
*/
setStudentAnswer: function() {
this.studentAnswerRows = parseInt(this.stagedAnswer[0]);
this.studentAnswerColumns = parseInt(this.stagedAnswer[1]);
this.studentAnswer = this.stagedAnswer[2];
this.studentAnswerRows = parseInt(this.stagedAnswer.rows);
this.studentAnswerColumns = parseInt(this.stagedAnswer.columns);
this.studentAnswer = this.stagedAnswer.matrix;
},

/** Get the student's answer as it was entered as a JME data type, to be used in the marking script
@@ -635,14 +635,14 @@ MultipleResponsePart.prototype = /** @lends Numbas.parts.MultipleResponsePart.pr
},

/** Store the student's choices */
storeAnswer: function(answerList)
storeTick: function(answer)
{
this.setDirty(true);
this.display && this.display.removeWarnings();
//get choice and answer
//in MR1_n_2 and MRm_n_2 parts, only the choiceindex matters
var answerIndex = answerList[0];
var choiceIndex = answerList[1];
var answerIndex = answer.answer;
var choiceIndex = answer.choice;

switch(this.settings.displayType)
{
@@ -654,7 +654,7 @@ MultipleResponsePart.prototype = /** @lends Numbas.parts.MultipleResponsePart.pr
}
break;
default:
this.stagedAnswer[answerIndex][choiceIndex] = answerList[2];
this.stagedAnswer[answerIndex][choiceIndex] = answer.ticked;
}
},

@@ -216,7 +216,7 @@ NumberEntryPart.prototype = /** @lends Numbas.parts.NumberEntryPart.prototype */
/** Save a copy of the student's answer as entered on the page, for use in marking.
*/
setStudentAnswer: function() {
this.studentAnswer = this.cleanAnswer(this.answerList[0]);
this.studentAnswer = this.cleanAnswer(this.stagedAnswer);
},

/** Get the student's answer as it was entered as a JME data type, to be used in the custom marking algorithm
@@ -118,7 +118,7 @@ PatternMatchPart.prototype = /** @lends Numbas.PatternMatchPart.prototype */ {
/** Save a copy of the student's answer as entered on the page, for use in marking.
*/
setStudentAnswer: function() {
this.studentAnswer = this.answerList[0];
this.studentAnswer = this.stagedAnswer;
},

/** Get the student's answer as it was entered as a JME data type, to be used in the custom marking algorithm
@@ -587,16 +587,6 @@ Question.prototype = /** @lends Numbas.Question.prototype */
}
},

/** Mark the student's answer to a given part/gap/step.
*/
doPart: function(answerList, partRef)
{
var part = this.getPart(partRef);
if(!part)
throw(new Numbas.Error('question.no such part',{path:partRef}));
part.storeAnswer(answerList);
},

/** Calculate the student's total score for this questoin - adds up all part scores
*/
calculateScore: function()
@@ -162,7 +162,7 @@ var util = Numbas.util = /** @lends Numbas.util */ {
return a.value[0]==b.value[0] && a.value[1]==b.value[1] && a.value[2]==b.value[2];
},
'name': function(a,b) {
return a.name == b.name;
return a.name.toLowerCase() == b.name.toLowerCase();
},
'string': function(a,b) {
return a.value==b.value;
@@ -64,14 +64,7 @@ <h2 id="qunit-userAgent"></h2>
function mark_part(p, answer) {
var answer = answer;
if(p.type=='gapfill') {
p.answerList = p.stagedAnswer = answer;
p.gaps.forEach(function(g,i) {
g.answerList = g.stagedAnswer = answer[i];
});
} else {
p.answerList = p.stagedAnswer = typeof(answer)=='string' ? [answer] : answer;
}
p.storeAnswer(answer);
p.setStudentAnswer();
return p.mark();
}
@@ -180,19 +173,19 @@ <h2 id="qunit-userAgent"></h2>
QUnit.module('Matrix entry');
QUnit.test('Answer is id(2)', function(assert) {
var p = createPartFromJSON({type:'matrix', correctAnswer: 'id(2)'});
var res = mark_part(p,[2,2,[['1','0'],['0','1']]]);
var res = mark_part(p,{rows:2,columns:2,matrix:[['1','0'],['0','1']]});
assert.equal(res.credit,1,'[[1,0],[0,1]] is correct');
var res = mark_part(p,[2,2,[['1','1'],['0','1']]]);
var res = mark_part(p,{rows:2,columns:2,matrix:[['1','1'],['0','1']]});
assert.equal(res.credit,0,'[[1,1],[0,1]] is incorrect');
var res = mark_part(p,[3,3,[['1','0','0'],['0','1','0'],['0','0','0']]]);
var res = mark_part(p,{rows:3,columns:3,matrix:[['1','0','0'],['0','1','0'],['0','0','0']]});
assert.equal(res.credit,0,'[[1,0,0],[0,1,0],[0,0,0]] is incorrect');
assert.ok(res.states.filter(function(s){return s.note=='wrong_size' && s.credit==0}).length>0, '[[1,0,0],[0,1,0],[0,0,0]] fails because wrong size');
});
QUnit.module('Choose one from a list');
QUnit.test('Three choices, first answer is correct', function(assert) {
var p = createPartFromJSON({type:'1_n_2', choices: ['a','b','c'], matrix: [[1],[0],[0]]});
var res = mark_part(p, [[true], [false], [false]]);
var res = mark_part(p, [[true],[false],[false]]);
assert.equal(res.credit,1,'Picking first choice is correct');
var res = mark_part(p, [[false], [true], [false]]);
assert.equal(res.credit,0,'Picking second choice is incorrect');
@@ -265,7 +258,7 @@ <h2 id="qunit-userAgent"></h2>
q.signals.on('ready',function() {
var p = q.getPart('p0');
assert.ok(p,'Part created');
p.answerList = p.stagedAnswer = ['x+2'];
p.storeAnswer('x+2');
p.setStudentAnswer();
q.submit();
@@ -287,10 +280,10 @@ <h2 id="qunit-userAgent"></h2>
var p1 = q.getPart('p1');
var p5 = q.getPart('p5');
p1.stagedAnswer = [2,2,[['2','0'],['0','1']]];
p1.storeAnswer({rows:2,columns:2,matrix:[['2','0'],['0','1']]});
p1.submit();
assert.equal(p1.credit,0.75,'0.75 credit on part b for one cell wrong');
p5.stagedAnswer = '2';
p5.storeAnswer('2');
p5.submit();
assert.equal(p5.credit,1,'Adaptive marking used for part f');
@@ -37,7 +37,7 @@ Numbas.queueScript('display/parts/jme',['display-base','part-display','util','jm
this.correctAnswerLaTeX = Numbas.jme.display.exprToLaTeX(this.correctAnswer,p.settings.answerSimplification,p.question.scope);

ko.computed(function() {
p.storeAnswer([this.studentAnswer()]);
p.storeAnswer(this.studentAnswer());
},this);

/** The student's answer, in LaTeX form
@@ -43,7 +43,11 @@ Numbas.queueScript('display/parts/matrix',['display-base','part-display','util',
var newColumns = this.studentAnswerColumns();
var newMatrix = this.studentAnswer();
if(newRows != oldRows || newColumns != oldColumns || !util.arraysEqual(oldMatrix,newMatrix)) {
p.storeAnswer([this.studentAnswerRows(),this.studentAnswerColumns(),this.studentAnswer()]);
p.storeAnswer({
rows: this.studentAnswerRows(),
columns: this.studentAnswerColumns(),
matrix: this.studentAnswer()
});
}
},this);

@@ -16,7 +16,7 @@ Numbas.queueScript('display/parts/multipleresponse',['display-base','part-displa
function makeTicker(answer,choice) {
var obs = ko.observable(p.ticks[answer][choice]);
ko.computed(function() {
p.storeAnswer([answer,choice,obs()]);
p.storeTick({answer:answer, choice:choice, ticked:obs()});
},p);
return obs;
}
@@ -29,14 +29,14 @@ Numbas.queueScript('display/parts/multipleresponse',['display-base','part-displa
}
ko.computed(function() {
var answer = parseInt(obs());
p.storeAnswer([answer,choice]);
p.storeAnswer({answer:answer, choice:choice});
},p);
return obs;
}
function makeCheckboxTicker(answer,choice) {
var obs = ko.observable(p.ticks[answer][choice]);
ko.computed(function() {
p.storeAnswer([answer,choice,obs()]);
p.storeAnswer({answer:answer, choice:choice, ticked:obs()});
});
return obs;
}
@@ -59,11 +59,11 @@ Numbas.queueScript('display/parts/multipleresponse',['display-base','part-displa
ko.computed(function() {
if(this.studentAnswer()==='') {
oldAnswer = null;
p.storeAnswer([null,0]);
p.storeAnswer({answer:null, choice: 0});
}
var i = parseInt(this.studentAnswer());
if(i!==oldAnswer && !isNaN(i)) {
p.storeAnswer([i,0]);
p.storeAnswer({answer:i, choice:0});
oldAnswer = i;
}
},this);
@@ -26,7 +26,7 @@ Numbas.queueScript('display/parts/numberentry',['display-base','part-display','u
this.correctAnswer = ko.observable(p.settings.displayAnswer);

ko.computed(function() {
p.storeAnswer([this.studentAnswer()]);
p.storeAnswer(this.studentAnswer());
},this);

/** Cleaned-up version of student answer (remove commas and trim whitespace)
@@ -31,7 +31,7 @@ Numbas.queueScript('display/parts/patternmatch',['display-base','part-display','
this.displayAnswer = ko.observable(p.settings.displayAnswer);

ko.computed(function() {
p.storeAnswer([this.studentAnswer()]);
p.storeAnswer(this.studentAnswer());
},this);
}
display.PatternMatchPartDisplay.prototype =

0 comments on commit 831c50a

Please sign in to comment.