From a19d1057cab92e505a1ba3984bc67b6a2b66c5a8 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Fri, 18 Nov 2011 15:35:24 +0800 Subject: [PATCH] MDL-30270, MDL-30269: rubric interface/usability improvements: - In rubric editor the line 'Current rubric status' is hidden if there is no status yet - If present the style of the status is the same as on manage.php page - For newly created rubric 'Add criterion' button is pre-pressed automatically - Changed JavaScript to work for Mac browsers default settings and for IPad - MDL-30269: added explanation message about score to grade mapping - fixed bug with non-javascript 'Add criterion' behaviour --- grade/grading/form/rubric/edit.php | 4 +- grade/grading/form/rubric/edit_form.php | 25 +++++++++- grade/grading/form/rubric/js/rubriceditor.js | 56 ++++++++++++++-------- .../form/rubric/lang/en/gradingform_rubric.php | 12 +++-- grade/grading/form/rubric/lib.php | 46 +++++++++++------- grade/grading/form/rubric/renderer.php | 22 ++++++++- grade/grading/form/rubric/rubriceditor.php | 8 ++-- grade/grading/form/rubric/styles.css | 11 ++++- 8 files changed, 134 insertions(+), 50 deletions(-) diff --git a/grade/grading/form/rubric/edit.php b/grade/grading/form/rubric/edit.php index d9390bb1e8e..09daca91e49 100644 --- a/grade/grading/form/rubric/edit.php +++ b/grade/grading/form/rubric/edit.php @@ -44,8 +44,8 @@ $PAGE->set_url(new moodle_url('/grade/grading/form/rubric/edit.php', array('area $PAGE->set_title(get_string('definerubric', 'gradingform_rubric')); $PAGE->set_heading(get_string('definerubric', 'gradingform_rubric')); -$mform = new gradingform_rubric_editrubric(null, array('areaid' => $areaid, 'context' => $context, 'allowdraft' => !$controller->has_active_instances())); -$data = $controller->get_definition_for_editing(); +$mform = new gradingform_rubric_editrubric(null, array('areaid' => $areaid, 'context' => $context, 'allowdraft' => !$controller->has_active_instances()), 'post', '', array('class' => 'gradingform_rubric_editform')); +$data = $controller->get_definition_for_editing(true); $returnurl = optional_param('returnurl', $manager->get_management_url(), PARAM_LOCALURL); $data->returnurl = $returnurl; $mform->set_data($data); diff --git a/grade/grading/form/rubric/edit_form.php b/grade/grading/form/rubric/edit_form.php index 3bae7837899..64cf8f936b3 100644 --- a/grade/grading/form/rubric/edit_form.php +++ b/grade/grading/form/rubric/edit_form.php @@ -58,8 +58,8 @@ class gradingform_rubric_editrubric extends moodleform { // rubric completion status $choices = array(); - $choices[gradingform_controller::DEFINITION_STATUS_DRAFT] = get_string('statusdraft', 'grading'); - $choices[gradingform_controller::DEFINITION_STATUS_READY] = get_string('statusready', 'grading'); + $choices[gradingform_controller::DEFINITION_STATUS_DRAFT] = html_writer::tag('span', get_string('statusdraft', 'core_grading'), array('class' => 'status draft')); + $choices[gradingform_controller::DEFINITION_STATUS_READY] = html_writer::tag('span', get_string('statusready', 'core_grading'), array('class' => 'status ready')); $form->addElement('select', 'status', get_string('rubricstatus', 'gradingform_rubric'), $choices)->freeze(); // rubric editor @@ -81,6 +81,27 @@ class gradingform_rubric_editrubric extends moodleform { } /** + * Setup the form depending on current values. This method is called after definition(), + * data submission and set_data(). + * All form setup that is dependent on form values should go in here. + * + * We remove the element status if there is no current status (i.e. rubric is only being created) + * so the users do not get confused + */ + public function definition_after_data() { + $form = $this->_form; + $el = $form->getElement('status'); + if (!$el->getValue()) { + $form->removeElement('status'); + } else { + $vals = array_values($el->getValue()); + if ($vals[0] == gradingform_controller::DEFINITION_STATUS_READY) { + $this->findButton('saverubric')->setValue(get_string('save', 'gradingform_rubric')); + } + } + } + + /** * Form vlidation. * If there are errors return array of errors ("fieldname"=>"error message"), * otherwise true if ok. diff --git a/grade/grading/form/rubric/js/rubriceditor.js b/grade/grading/form/rubric/js/rubriceditor.js index de3dd192fe2..1d5d5248d87 100644 --- a/grade/grading/form/rubric/js/rubriceditor.js +++ b/grade/grading/form/rubric/js/rubriceditor.js @@ -12,6 +12,10 @@ M.gradingform_rubriceditor.init = function(Y, options) { } M.gradingform_rubriceditor.disablealleditors() Y.on('click', M.gradingform_rubriceditor.clickanywhere, 'body', null) + YUI().use('event-touch', function (Y) { + Y.one('body').on('touchstart', M.gradingform_rubriceditor.clickanywhere); + Y.one('body').on('touchend', M.gradingform_rubriceditor.clickanywhere); + }) M.gradingform_rubriceditor.addhandlers() }; @@ -35,6 +39,7 @@ M.gradingform_rubriceditor.disablealleditors = function() { // it switches this element to edit mode. If rubric button is clicked it does nothing so the 'buttonclick' // function is invoked M.gradingform_rubriceditor.clickanywhere = function(e) { + if (e.type == 'touchstart') return var el = e.target // if clicked on button - disablecurrenteditor, continue if (el.get('tagName') == 'INPUT' && el.get('type') == 'submit') { @@ -48,7 +53,7 @@ M.gradingform_rubriceditor.clickanywhere = function(e) { el = el.get('parentNode') } if (el) { - if (el.one('textarea').getStyle('display') == 'none') { + if (el.one('textarea').hasClass('hiddenelement')) { M.gradingform_rubriceditor.disablealleditors() M.gradingform_rubriceditor.editmode(el, true, focustb) } @@ -61,19 +66,19 @@ M.gradingform_rubriceditor.clickanywhere = function(e) { // switch the criterion description or level to edit mode or switch back M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) { var ta = el.one('textarea') - if (!editmode && ta.getStyle('display') == 'none') return; - if (editmode && ta.getStyle('display') == 'block') return; - var pseudotablink = ' ', + if (!editmode && ta.hasClass('hiddenelement')) return; + if (editmode && !ta.hasClass('hiddenelement')) return; + var pseudotablink = '', taplain = ta.get('parentNode').one('.plainvalue'), tbplain = null, - tb = el.one('input[type=text]') + tb = el.one('.score input[type=text]') // add 'plainvalue' next to textarea for description/definition and next to input text field for score (if applicable) if (!taplain) { - ta.get('parentNode').append('
 '+pseudotablink+'
') + ta.get('parentNode').append('
'+pseudotablink+' 
') taplain = ta.get('parentNode').one('.plainvalue') taplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere) if (tb) { - tb.get('parentNode').append('
 '+pseudotablink+'
') + tb.get('parentNode').append(''+pseudotablink+' ') tbplain = tb.get('parentNode').one('.plainvalue') tbplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere) } @@ -90,20 +95,33 @@ M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) { } taplain.one('.textvalue').set('innerHTML', value) if (tb) tbplain.one('.textvalue').set('innerHTML', tb.get('value')) + // hide/display textarea, textbox and plaintexts + taplain.removeClass('hiddenelement') + ta.addClass('hiddenelement') + if (tb) { + tbplain.removeClass('hiddenelement') + tb.addClass('hiddenelement') + } } else { // if we need to show the input fields, set the width/height for textarea so it fills the cell - var width = parseFloat(ta.get('parentNode').getComputedStyle('width')), - height - if (el.hasClass('level')) height = parseFloat(el.getComputedStyle('height')) - parseFloat(el.one('.score').getComputedStyle('height')) - else height = parseFloat(ta.get('parentNode').getComputedStyle('height')) - ta.setStyle('width', Math.max(width,50)+'px').setStyle('height', Math.max(height,20)+'px') - } - // hide/display textarea, textbox and plaintexts - taplain.setStyle('display', editmode ? 'none' : 'block') - ta.setStyle('display', editmode ? 'block' : 'none') - if (tb) { - tbplain.setStyle('display', editmode ? 'none' : 'inline-block') - tb.setStyle('display', editmode ? 'inline-block' : 'none') + try { + var width = parseFloat(ta.get('parentNode').getComputedStyle('width')), + height + if (el.hasClass('level')) height = parseFloat(el.getComputedStyle('height')) - parseFloat(el.one('.score').getComputedStyle('height')) + else height = parseFloat(ta.get('parentNode').getComputedStyle('height')) + ta.setStyle('width', Math.max(width,50)+'px') + ta.setStyle('height', Math.max(height,20)+'px') + } + catch (err) { + // this browser do not support 'computedStyle', leave the default size of the textbox + } + // hide/display textarea, textbox and plaintexts + taplain.addClass('hiddenelement') + ta.removeClass('hiddenelement') + if (tb) { + tbplain.addClass('hiddenelement') + tb.removeClass('hiddenelement') + } } // focus the proper input field in edit mode if (editmode) { if (tb && focustb) tb.focus(); else ta.focus() } diff --git a/grade/grading/form/rubric/lang/en/gradingform_rubric.php b/grade/grading/form/rubric/lang/en/gradingform_rubric.php index 0d78aa62c6b..d7f605c0e65 100644 --- a/grade/grading/form/rubric/lang/en/gradingform_rubric.php +++ b/grade/grading/form/rubric/lang/en/gradingform_rubric.php @@ -53,12 +53,18 @@ $string['regradeoption0'] = 'Do not mark for regrade'; $string['regradeoption1'] = 'Mark for regrade'; $string['restoredfromdraft'] = 'NOTE: The last attempt to grade this person was not saved properly so draft grades have been restored. If you want to cancel these changes use the \'Cancel\' button below.'; $string['rubric'] = 'Rubric'; +$string['rubricmapping'] = 'Score to grade mapping rules'; +$string['rubricmappingexplained'] = 'The minimum possible score for this rubric is {$a->minscore} points and it will be converted to the minimum grade available in this module (which is zero unless the scale is used). + The maximum score {$a->maxscore} points will be converted to the maximum grade.
+ Intermediate scores will be converted respectively and rounded to the nearest available grade.
+ If a scale is used instead of a grade, the score will be converted to the scale elements as if they were consecutive integers.'; $string['rubricnotcompleted'] = 'Please choose something for each criterion'; $string['rubricoptions'] = 'Rubric options'; $string['rubricstatus'] = 'Current rubric status'; +$string['save'] = 'Save'; $string['saverubric'] = 'Save rubric and make it ready'; $string['saverubricdraft'] = 'Save as draft'; -$string['scorepostfix'] = '{$a} points'; +$string['scorepostfix'] = '{$a}points'; $string['showdescriptionstudent'] = 'Display rubric description to those being graded'; $string['showdescriptionteacher'] = 'Display rubric description during evaluation'; $string['showremarksstudent'] = 'Show remarks to those being graded'; @@ -66,6 +72,4 @@ $string['showscorestudent'] = 'Display points for each level to those being grad $string['showscoreteacher'] = 'Display points for each level during evaluation'; $string['sortlevelsasc'] = 'Sort order for levels:'; $string['sortlevelsasc0'] = 'Descending by number of points'; -$string['sortlevelsasc1'] = 'Ascending by number of points'; -$string['statusdraft'] = 'Draft'; -$string['statusready'] = 'Ready'; \ No newline at end of file +$string['sortlevelsasc1'] = 'Ascending by number of points'; \ No newline at end of file diff --git a/grade/grading/form/rubric/lib.php b/grade/grading/form/rubric/lib.php index 6aa0d988540..ff24e85d665 100644 --- a/grade/grading/form/rubric/lib.php +++ b/grade/grading/form/rubric/lib.php @@ -360,9 +360,10 @@ class gradingform_rubric_controller extends gradingform_controller { /** * Converts the current definition into an object suitable for the editor form's set_data() * + * @param boolean $addemptycriterion whether to add an empty criterion if the rubric is completely empty (just being created) * @return stdClass */ - public function get_definition_for_editing() { + public function get_definition_for_editing($addemptycriterion = false) { $definition = $this->get_definition(); $properties = new stdClass(); @@ -378,6 +379,8 @@ class gradingform_rubric_controller extends gradingform_controller { $properties->rubric = array('criteria' => array(), 'options' => $this->get_options()); if (!empty($definition->rubric_criteria)) { $properties->rubric['criteria'] = $definition->rubric_criteria; + } else if (!$definition && $addemptycriterion) { + $properties->rubric['criteria'] = array('addcriterion' => 1); } return $properties; @@ -481,7 +484,8 @@ class gradingform_rubric_controller extends gradingform_controller { $output = $this->get_renderer($page); $criteria = $this->definition->rubric_criteria; $options = $this->get_options(); - $rubric = $output->display_rubric($criteria, $options, self::DISPLAY_PREVIEW, 'rubric'); + $rubric = $output->display_rubric_mapping_explained($this->get_min_max_score()); + $rubric .= $output->display_rubric($criteria, $options, self::DISPLAY_PREVIEW, 'rubric'); return $rubric; } @@ -590,6 +594,28 @@ class gradingform_rubric_controller extends gradingform_controller { return array($subsql, $params); } + + /** + * Calculates and returns the possible minimum and maximum score (in points) for this rubric + * + * @return array + */ + public function get_min_max_score() { + if (!$this->is_form_available()) { + return null; + } + $returnvalue = array('minscore' => 0, 'maxscore' => 0); + foreach ($this->get_definition()->rubric_criteria as $id => $criterion) { + $scores = array(); + foreach ($criterion['levels'] as $level) { + $scores[] = $level['score']; + } + sort($scores); + $returnvalue['minscore'] += $scores[0]; + $returnvalue['maxscore'] += $scores[sizeof($scores)-1]; + } + return $returnvalue; + } } /** @@ -717,19 +743,7 @@ class gradingform_rubric_instance extends gradingform_instance { global $DB, $USER; $grade = $this->get_rubric_filling(); - $minscore = 0; - $maxscore = 0; - foreach ($this->get_controller()->get_definition()->rubric_criteria as $id => $criterion) { - $scores = array(); - foreach ($criterion['levels'] as $level) { - $scores[] = $level['score']; - } - sort($scores); - $minscore += $scores[0]; - $maxscore += $scores[sizeof($scores)-1]; - } - - if ($maxscore <= $minscore) { + if (!($scores = $this->get_controller()->get_min_max_score()) || $scores['maxscore'] <= $scores['minscore']) { return -1; } @@ -745,7 +759,7 @@ class gradingform_rubric_instance extends gradingform_instance { foreach ($grade['criteria'] as $id => $record) { $curscore += $this->get_controller()->get_definition()->rubric_criteria[$id]['levels'][$record['levelid']]['score']; } - return round(($curscore-$minscore)/($maxscore-$minscore)*($maxgrade-$mingrade), 0) + $mingrade; // TODO mapping + return round(($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*($maxgrade-$mingrade), 0) + $mingrade; } /** diff --git a/grade/grading/form/rubric/renderer.php b/grade/grading/form/rubric/renderer.php index 8086833f696..f037263a97f 100644 --- a/grade/grading/form/rubric/renderer.php +++ b/grade/grading/form/rubric/renderer.php @@ -164,7 +164,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base { $leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper')); if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) { $definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4')); - $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '4', 'value' => $level['score'])); + $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score'])); } else { if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) { $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition'])); @@ -181,7 +181,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base { if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN && $level['checked']) { $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levelid]', 'value' => $level['id'])); } - $score = html_writer::tag('span', $score, array('id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-score')); + $score = html_writer::tag('span', $score, array('id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-score', 'class' => 'scorevalue')); $definitionclass = 'definition'; if (isset($level['error_definition'])) { $definitionclass .= ' error'; @@ -453,4 +453,22 @@ class gradingform_rubric_renderer extends plugin_renderer_base { $html .= html_writer::end_tag('div'); return $html; } + + /** + * Generates and returns HTML code to display information box about how rubric score is converted to the grade + * + * @param array $scores + * @return string + */ + public function display_rubric_mapping_explained($scores) { + $html = ''; + if (!$scores) { + return $html; + } + $html .= $this->box( + html_writer::tag('h4', get_string('rubricmapping', 'gradingform_rubric')). + html_writer::tag('div', get_string('rubricmappingexplained', 'gradingform_rubric', (object)$scores)) + , 'generalbox rubricmappingexplained'); + return $html; + } } diff --git a/grade/grading/form/rubric/rubriceditor.php b/grade/grading/form/rubric/rubriceditor.php index 7fb23316db1..92cd3be7f35 100644 --- a/grade/grading/form/rubric/rubriceditor.php +++ b/grade/grading/form/rubric/rubriceditor.php @@ -184,14 +184,14 @@ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input { // when adding new criterion copy the number of levels and their scores from the last criterion if (!empty($value['criteria'][$lastid]['levels'])) { foreach ($value['criteria'][$lastid]['levels'] as $lastlevel) { - $criterion['levels']['NEWID'+($i++)]['score'] = $lastlevel['score']; + $criterion['levels']['NEWID'.($i++)]['score'] = $lastlevel['score']; } } else { - $criterion['levels']['NEWID'+($i++)]['score'] = 0; + $criterion['levels']['NEWID'.($i++)]['score'] = 0; } // add more levels so there are at least 3 in the new criterion. Increment by 1 the score for each next one - for ($i; $i<3; $i++) { - $criterion['levels']['NEWID'+$i]['score'] = $criterion['levels']['NEWID'+($i-1)]['score'] + 1; + for ($i=$i; $i<3; $i++) { + $criterion['levels']['NEWID'.$i]['score'] = $criterion['levels']['NEWID'.($i-1)]['score'] + 1; } // set other necessary fields (definition) for the levels in the new criterion foreach (array_keys($criterion['levels']) as $i) { diff --git a/grade/grading/form/rubric/styles.css b/grade/grading/form/rubric/styles.css index 4428ac3921e..41a5d104cd8 100644 --- a/grade/grading/form/rubric/styles.css +++ b/grade/grading/form/rubric/styles.css @@ -45,6 +45,10 @@ */ +.gradingform_rubric_editform .status {font-weight:normal;text-transform:uppercase;font-size:60%;padding:0.25em;border:1px solid #EEE;-moz-border-radius:5px;} +.gradingform_rubric_editform .status.ready {background-color:#e7f1c3;border-color:#AAEEAA;} +.gradingform_rubric_editform .status.draft {background-color:#f3f2aa;border-color:#EEEE22;} + .gradingform_rubric.editor .criterion .controls, .gradingform_rubric .criterion .description, .gradingform_rubric .criterion .levels, @@ -75,7 +79,8 @@ .gradingform_rubric .plainvalue.empty {font-style: italic; color: #AAA;} .gradingform_rubric.editor .criterion .levels .level .delete {position:absolute;right:0;bottom:0;} -.gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;} +.gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;nowrap:nowrap;} +.gradingform_rubric .criterion .levels .level .score .scorevalue {padding-right:5px;} /* Make invisible the buttons 'Move up' for the first criterion and 'Move down' for the last, because those buttons will make no change */ .gradingform_rubric.editor .criterion.first .controls .moveup input, @@ -110,6 +115,10 @@ .gradingform_rubric .criterion .levels .level .definition.error, .gradingform_rubric .criterion .levels .level .score.error {background:#FFDDDD;} +/* special classes for elements created by rubriceditor.js */ +.gradingform_rubric.editor .hiddenelement {display:none;} +.gradingform_rubric.editor .pseudotablink {background-color:transparent;border:0px solid;height:1px;width:1px;color:transparent;padding:0;margin:0;position:relative;float:right;} + /** * */ -- 2.11.4.GIT