Skip to content
Snippets Groups Projects
Commit 3c8b55b6 authored by Tim Hunt's avatar Tim Hunt
Browse files

Fix get_expected_data for the matrix input type.

This requires re-working how input elements can adjust themselves based
on the teacher's model answer, but I think this is an improvement.
parent 343d99c9
No related branches found
No related tags found
No related merge requests found
Showing with 116 additions and 48 deletions
......@@ -260,6 +260,9 @@ class qtype_stack_question extends question_graded_automatically_with_countback
// Finally, store only those values really needed for later.
$session->prune_session($session_length);
$this->session = $session;
// Allow inputs to update themselves based on the model answers.
$this->adapt_inputs();
}
public function apply_attempt_state(question_attempt_step $step) {
......@@ -267,6 +270,18 @@ class qtype_stack_question extends question_graded_automatically_with_countback
$questionvars = new stack_cas_keyval($step->get_qt_var('_questionvars'), $this->options, $this->seed, 't');
$this->session = $questionvars->get_session();
$this->questionnoteinstantiated = $step->get_qt_var('_questionnote');
$this->adapt_inputs();
}
/**
* Give all the input elements a chance to configure themselves given the
* teacher's model answers.
*/
protected function adapt_inputs() {
foreach ($this->inputs as $name => $input) {
$teacheranswer = $this->session->get_value_key($name);
$input->adapt_to_model_answer($teacheranswer);
}
}
public function format_generalfeedback($qa) {
......@@ -286,11 +301,8 @@ class qtype_stack_question extends question_graded_automatically_with_countback
public function get_expected_data() {
$expected = array();
foreach ($this->inputs as $name => $input) {
$expected[$name] = PARAM_RAW;
if ($input->requires_validation()) {
$expected[$name . '_val'] = PARAM_RAW;
}
foreach ($this->inputs as $input) {
$expected += $input->get_expected_data();
}
return $expected;
}
......
......@@ -47,7 +47,7 @@ class qtype_stack_renderer extends qtype_renderer {
$state = $question->get_input_state($name, $response);
$questiontext = str_replace("[[input:{$name}]]",
$input->render($state, $qa->get_qt_field_name($name), $options->readonly, $question->get_session_variable($name)),
$input->render($state, $qa->get_qt_field_name($name), $options->readonly),
$questiontext);
$feedback = $this->input_validation($name, $input->render_validation($state, $qa->get_qt_field_name($name)));
......
......@@ -23,7 +23,7 @@
*/
class stack_algebraic_input extends stack_input {
public function render(stack_input_state $state, $fieldname, $readonly, $teachersanswer) {
public function render(stack_input_state $state, $fieldname, $readonly) {
$attributes = array(
'type' => 'text',
'name' => $fieldname,
......
......@@ -45,42 +45,42 @@ class stack_algebra_input_test extends qtype_stack_testcase {
$el = stack_input_factory::make('algebraic', 'ans1', 'x^2');
$this->assertEquals('<input type="text" name="stack1__ans1" size="15" value="" />',
$el->render(new stack_input_state(stack_input::VALID, '', '', '', ''),
'stack1__ans1', false, ''));
'stack1__ans1', false));
}
public function test_render_zero() {
$el = stack_input_factory::make('algebraic', 'ans1', '0');
$this->assertEquals('<input type="text" name="stack1__ans1" size="15" value="0" />',
$el->render(new stack_input_state(stack_input::VALID, '0', '', '', ''),
'stack1__ans1', false, ''));
'stack1__ans1', false));
}
public function test_render_pre_filled() {
$el = stack_input_factory::make('algebraic', 'test', 'x^2');
$this->assertEquals('<input type="text" name="stack1__test" size="15" value="x+y" />',
$el->render(new stack_input_state(stack_input::VALID, 'x+y', '', '', ''),
'stack1__test', false, ''));
'stack1__test', false));
}
public function test_render_pre_filled_nasty_input() {
$el = stack_input_factory::make('algebraic', 'test', 'x^2');
$this->assertEquals('<input type="text" name="stack1__test" size="15" value="x&lt;y" />',
$el->render(new stack_input_state(stack_input::VALID, 'x<y', '', '', ''),
'stack1__test', false, ''));
'stack1__test', false));
}
public function test_render_max_length() {
$el = stack_input_factory::make('algebraic', 'test', 'x^2');
$this->assertEquals('<input type="text" name="stack1__test" size="15" value="x+y" />',
$el->render(new stack_input_state(stack_input::VALID, 'x+y', '', '', ''),
'stack1__test', false, ''));
'stack1__test', false));
}
public function test_render_disabled() {
$el = stack_input_factory::make('algebraic', 'input', 'x^2');
$this->assertEquals('<input type="text" name="stack1__input" size="15" value="x+1" readonly="readonly" />',
$el->render(new stack_input_state(stack_input::VALID, 'x+1', '', '', ''),
'stack1__input', true, ''));
'stack1__input', true));
}
public function test_render_different_size() {
......@@ -88,7 +88,7 @@ class stack_algebra_input_test extends qtype_stack_testcase {
$el->set_parameter('boxWidth', 30);
$this->assertEquals('<input type="text" name="stack1__input" size="30" value="x+1" />',
$el->render(new stack_input_state(stack_input::VALID, 'x+1', '', '', ''),
'stack1__input', false, ''));
'stack1__input', false));
}
public function test_render_syntaxhint() {
......@@ -96,7 +96,7 @@ class stack_algebra_input_test extends qtype_stack_testcase {
$el->set_parameter('syntaxHint', '[?, ?, ?]');
$this->assertEquals('<input type="text" name="stack1__sans1" size="15" value="[?, ?, ?]" />',
$el->render(new stack_input_state(stack_input::BLANK, '', '', '', ''),
'stack1__sans1', false, ''));
'stack1__sans1', false));
}
public function test_validate_student_response_1() {
......
......@@ -40,7 +40,7 @@ class stack_boolean_input extends stack_input {
);
}
public function render(stack_input_state $state, $fieldname, $readonly, $teachersanswer) {
public function render(stack_input_state $state, $fieldname, $readonly) {
$attributes = array();
if ($readonly) {
......
......@@ -57,21 +57,21 @@ class stack_boolean_input_rendering_test extends question_testcase {
$el = stack_input_factory::make('boolean', 'ans2', stack_boolean_input::T);
$this->assert(new question_contains_select_expectation('stack1__ans2', $this->expected_choices(),
stack_boolean_input::T), $el->render(new stack_input_state(stack_input::VALID, stack_boolean_input::T, '', '', ''),
'stack1__ans2', false, ''));
'stack1__ans2', false));
}
public function test_render_false() {
$el = stack_input_factory::make('boolean', 'ans3', stack_boolean_input::T);
$this->assert(new question_contains_select_expectation('stack1__ans3', $this->expected_choices(),
stack_boolean_input::F), $el->render(new stack_input_state(stack_input::VALID, stack_boolean_input::F, '', '', ''),
'stack1__ans3', false, ''));
'stack1__ans3', false));
}
public function test_render_disabled() {
$el = stack_input_factory::make('boolean', 'input', stack_boolean_input::T);
$this->assert(new question_contains_select_expectation('stack1__ans1', $this->expected_choices(),
stack_boolean_input::NA, false), $el->render(new stack_input_state(stack_input::BLANK, '', '', '', ''),
'stack1__ans1', true, ''));
'stack1__ans1', true));
}
}
......
......@@ -43,7 +43,7 @@ class stack_dropdown_input extends stack_input {
return $choices;
}
public function render(stack_input_state $state, $fieldname, $readonly, $teachersanswer) {
public function render(stack_input_state $state, $fieldname, $readonly) {
$values = $this->get_choices();
if (empty($values)) {
return stack_string('ddl_empty');
......
......@@ -102,6 +102,17 @@ abstract class stack_input {
}
}
/**
* This method gives the input element a change to adapt itself given the
* value of the teachers model answer for this variant of the question.
* For example, the matrix question type uses this to work out how many
* rows and columns it should have.
* @param string $teacheranswer the teacher's model answer for this input.
*/
public function adapt_to_model_answer($teacheranswer) {
// By default, do nothing.
}
/**
* @param string $param a settings parameter name.
* @return bool whether this input type uses this parameter.
......@@ -210,6 +221,20 @@ abstract class stack_input {
return array();
}
/**
* Get the input variable that this input expects to process.
* All the variable names should start with $this->name.
* @return array string input name => PARAM_... type constant.
*/
public function get_expected_data() {
$expected = array();
$expected[$this->name] = PARAM_RAW;
if ($this->requires_validation()) {
$expected[$this->name . '_val'] = PARAM_RAW;
}
return $expected;
}
/**
* @return string the teacher's answer, an example of what could be typed into
* this input as part of a correct response to the question.
......@@ -322,10 +347,9 @@ abstract class stack_input {
* @param string student's current answer to insert into the xhtml.
* @param string $fieldname the field name to use in the HTML for this input.
* @param bool $readonly whether the contro should be displayed read-only.
* @param string the value of the teacher's answer, post randomization. This is needed, e.g. to automatically size the matrix elements.
* @return string HTML for this input.
*/
public abstract function render(stack_input_state $state, $fieldname, $readonly, $teachersanswer);
public abstract function render(stack_input_state $state, $fieldname, $readonly);
/**
* Add this input the MoodleForm, but only used in questiontestform.php.
......
......@@ -22,32 +22,62 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stack_matrix_input extends stack_input {
protected $errors = null;
protected $width;
protected $height;
public function render(stack_input_state $state, $fieldname, $readonly, $teacheranswer) {
$attributes = array(
'type' => 'text',
'name' => $fieldname,
);
public function adapt_to_model_answer($teacheranswer) {
// Work out how big the matrix should be from the INSTANTIATED VALUE of the teacher's answer.
$cs = new stack_cas_casstring('ta:matrix_size(' . $teacheranswer . ')');
$cs->validate('t');
$at1 = new stack_cas_session(array($cs), null, 0);
$at1->instantiate();
if ('' != $at1->get_errors()) {
// If there are errors, don't try to display anything else.
return html_writer::tag('p', $at1->get_errors(), array('id' => 'error', 'class' => 'p'));
$this->errors = $at1->get_errors();
return;
}
$size = $at1->get_value_key('ta');
$dimensions = explode(',', $size);
$height = trim($dimensions[0], '[]');
$width = trim($dimensions[1], '[]');
$this->height = trim($dimensions[0], '[]');
$this->width = trim($dimensions[1], '[]');
}
public function get_expected_data() {
$expected = array();
// All the matrix elements.
for ($i = 0; $i < $this->height; $i++) {
for ($j = 0; $j < $this->width; $j++) {
$expected[$this->name . '_sub_' . $i . '_' . $j] = PARAM_RAW;
}
}
// I am assuming that the valdiation will write one CAS string in a
// hidden input, that is the combination of all the separate inputs.
if ($this->requires_validation()) {
$expected[$this->name . '_val'] = PARAM_RAW;
}
return $expected;
}
public function render(stack_input_state $state, $fieldname, $readonly) {
$attributes = array(
'type' => 'text',
'name' => $fieldname,
);
if ($this->errors) {
// If there are errors, don't try to display anything else.
return html_writer::tag('p', $this->errors, array('id' => 'error', 'class' => 'p'));
}
// Build an empty array.
$firstrow = array_fill(0, $width, '');
$tc = array_fill(0, $height, $firstrow);
$firstrow = array_fill(0, $this->width, '');
$tc = array_fill(0, $this->height, $firstrow);
// Turn the student's answer into a PHP array.
if ('' != $state->contents) {
......@@ -61,24 +91,24 @@ class stack_matrix_input extends stack_input {
// Build the html table to contain these values.
$xhtml = '<table class="matrixtable" style="display:inline; vertical-align: middle;" border="0" cellpadding="1" cellspacing="0"><tbody>';
for ($i=0; $i < $height; $i++) {
for ($i=0; $i < $this->height; $i++) {
$xhtml .= '<tr>';
if($i == 0) {
$xhtml .= '<td style="border-width: 2px 0px 0px 2px; padding-top: 0.5em">&nbsp;</td>';
} elseif ($i == ($height - 1)) {
} elseif ($i == ($this->height - 1)) {
$xhtml .= '<td style="border-width: 0px 0px 2px 2px;">&nbsp;</td>';
} else {
$xhtml .= '<td style="border-width: 0px 0px 0px 2px;">&nbsp;</td>';
}
for ($j=0; $j < $width; $j++) {
for ($j=0; $j < $this->width; $j++) {
$name = $fieldname.'_sub_'.$i.'_'.$j;
$xhtml .= '<td><input type="text" name="'.$name.'" value="'.$tc[$i][$j].'" size="'.$this->parameters['boxWidth'].'" ></td>';
}
if ($i == 0) {
$xhtml .= '<td style="border-width: 2px 2px 0px 0px; padding-top: 0.5em">&nbsp;</td>';
} elseif ($i == ($height - 1)) {
} elseif ($i == ($this->height - 1)) {
$xhtml .= '<td style="border-width: 0px 2px 2px 0px; padding-bottom: 0.5em">&nbsp;</td>';
} else {
$xhtml .= '<td style="border-width: 0px 2px 0px 0px;">&nbsp;</td>';
......
......@@ -37,17 +37,19 @@ class stack_matrix_input_test extends qtype_stack_testcase {
public function test_render_blank() {
$el = stack_input_factory::make('matrix', 'ans1', 'M');
$el->adapt_to_model_answer('matrix([1,2,3],[3,4,5])');
$this->assertEquals('<table class="matrixtable" style="display:inline; vertical-align: middle;" border="0" cellpadding="1" cellspacing="0"><tbody><tr><td style="border-width: 2px 0px 0px 2px; padding-top: 0.5em">&nbsp;</td><td><input type="text" name="ans1_sub_0_0" value="" size="5" ></td><td><input type="text" name="ans1_sub_0_1" value="" size="5" ></td><td><input type="text" name="ans1_sub_0_2" value="" size="5" ></td><td style="border-width: 2px 2px 0px 0px; padding-top: 0.5em">&nbsp;</td></tr><tr><td style="border-width: 0px 0px 2px 2px;">&nbsp;</td><td><input type="text" name="ans1_sub_1_0" value="" size="5" ></td><td><input type="text" name="ans1_sub_1_1" value="" size="5" ></td><td><input type="text" name="ans1_sub_1_2" value="" size="5" ></td><td style="border-width: 0px 2px 2px 0px; padding-bottom: 0.5em">&nbsp;</td></tr></tbody></table>',
$el->render(new stack_input_state(stack_input::VALID, '', '', '', ''),
'ans1', false, 'matrix([1,2,3],[3,4,5])'));
'ans1', false));
}
public function test_render_no_errors_if_garbled() {
// If the teacher does not know the right syntax for a matrix, we should
// not give PHP errors.
$el = stack_input_factory::make('matrix', 'ans1', 'M');
$el->adapt_to_model_answer('[[1,0],[0,1]]');
$this->assertEquals('<p id="error" class="p"><span class="error">The CAS returned the following error(s):</span><span class="stacksyntaxexample">ta:matrix_size([[1,0],[0,1]])</span> caused the following error: The "\$first" argument of the function "\$matrix_size" must be a matrix </p>',
$el->render(new stack_input_state(stack_input::VALID, '', '', '', ''),
'ans1', false, '[[1,0],[0,1]]'));
'ans1', false));
}
}
......@@ -26,7 +26,7 @@
*/
class stack_singlechar_input extends stack_input {
public function render(stack_input_state $state, $fieldname, $readonly, $teachersanswer) {
public function render(stack_input_state $state, $fieldname, $readonly) {
$attributes = array(
'type' => 'text',
......
......@@ -38,20 +38,20 @@ class stack_singlechar_input_test extends basic_testcase {
$el = stack_input_factory::make('singleChar', 'ans1', null);
$this->assertEquals('<input type="text" name="question__ans1" size="1" maxlength="1" value="" />',
$el->render(new stack_input_state(stack_input::BLANK, '', '', '', ''),
'question__ans1', false, ''));
'question__ans1', false));
}
public function test_render_pre_filled() {
$el = stack_input_factory::make('singleChar', 'test', null);
$this->assertEquals('<input type="text" name="question__ans1" size="1" maxlength="1" value="Y" />',
$el->render(new stack_input_state(stack_input::VALID, 'Y', '', '', ''),
'question__ans1', false, ''));
'question__ans1', false));
}
public function test_render_disabled() {
$el = stack_input_factory::make('singleChar', 'input', null);
$this->assertEquals('<input type="text" name="question__stack1" size="1" maxlength="1" value="a" readonly="readonly" />',
$el->render(new stack_input_state(stack_input::VALID, 'a', '', '', ''),
'question__stack1', true, ''));
'question__stack1', true));
}
}
......@@ -44,7 +44,7 @@ class stack_textarea_input_test extends basic_testcase {
$el = stack_input_factory::make('textArea', 'ans1', null);
$this->assertEquals('<textarea name="st_ans1" rows="5" cols="20"></textarea>',
$el->render(new stack_input_state(stack_input::BLANK, '', '', '', ''),
'st_ans1', false, ''));
'st_ans1', false));
}
public function test_render_pre_filled() {
......@@ -52,7 +52,7 @@ class stack_textarea_input_test extends basic_testcase {
$this->assertEquals('<textarea name="st_ans1" rows="5" cols="20">' .
"1\n1/sum([1,3])\nmatrix([1],[2])</textarea>",
$el->render(new stack_input_state(stack_input::VALID, "1\n1/sum([1,3])\nmatrix([1],[2])", '', '', ''),
'st_ans1', false, ''));
'st_ans1', false));
}
public function test_render_pre_syntaxhint() {
......@@ -60,14 +60,14 @@ class stack_textarea_input_test extends basic_testcase {
$this->assertEquals('<textarea name="st_ans1" rows="5" cols="20">' .
"y=?\n z=?</textarea>",
$el->render(new stack_input_state(stack_input::BLANK, '', '', '', ''),
'st_ans1', false, ''));
'st_ans1', false));
}
public function test_render_disabled() {
$el = stack_input_factory::make('textArea', 'input', null);
$this->assertEquals('<textarea name="st_ans1" rows="5" cols="20" readonly="readonly"></textarea>',
$el->render(new stack_input_state(stack_input::BLANK, '', '', '', ''),
'st_ans1', true, ''));
'st_ans1', true));
}
}
......
......@@ -26,7 +26,7 @@ require_once(dirname(__FILE__) . '/../../utils.class.php');
*/
class stack_textarea_input extends stack_input {
public function render(stack_input_state $state, $fieldname, $readonly, $teachersanswer) {
public function render(stack_input_state $state, $fieldname, $readonly) {
// Note that at the moment, $this->boxHeight and $this->boxWidth are only
// used as minimums. If the current input is bigger, the box is expanded.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment