Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit a19d1057cab92e505a1ba3984bc67b6a2b66c5a8 1 parent fe41ba7
Marina Glancy marinaglancy authored
4 grade/grading/form/rubric/edit.php
@@ -44,8 +44,8 @@
44 44 $PAGE->set_title(get_string('definerubric', 'gradingform_rubric'));
45 45 $PAGE->set_heading(get_string('definerubric', 'gradingform_rubric'));
46 46
47   -$mform = new gradingform_rubric_editrubric(null, array('areaid' => $areaid, 'context' => $context, 'allowdraft' => !$controller->has_active_instances()));
48   -$data = $controller->get_definition_for_editing();
  47 +$mform = new gradingform_rubric_editrubric(null, array('areaid' => $areaid, 'context' => $context, 'allowdraft' => !$controller->has_active_instances()), 'post', '', array('class' => 'gradingform_rubric_editform'));
  48 +$data = $controller->get_definition_for_editing(true);
49 49 $returnurl = optional_param('returnurl', $manager->get_management_url(), PARAM_LOCALURL);
50 50 $data->returnurl = $returnurl;
51 51 $mform->set_data($data);
25 grade/grading/form/rubric/edit_form.php
@@ -58,8 +58,8 @@ public function definition() {
58 58
59 59 // rubric completion status
60 60 $choices = array();
61   - $choices[gradingform_controller::DEFINITION_STATUS_DRAFT] = get_string('statusdraft', 'grading');
62   - $choices[gradingform_controller::DEFINITION_STATUS_READY] = get_string('statusready', 'grading');
  61 + $choices[gradingform_controller::DEFINITION_STATUS_DRAFT] = html_writer::tag('span', get_string('statusdraft', 'core_grading'), array('class' => 'status draft'));
  62 + $choices[gradingform_controller::DEFINITION_STATUS_READY] = html_writer::tag('span', get_string('statusready', 'core_grading'), array('class' => 'status ready'));
63 63 $form->addElement('select', 'status', get_string('rubricstatus', 'gradingform_rubric'), $choices)->freeze();
64 64
65 65 // rubric editor
@@ -81,6 +81,27 @@ public function definition() {
81 81 }
82 82
83 83 /**
  84 + * Setup the form depending on current values. This method is called after definition(),
  85 + * data submission and set_data().
  86 + * All form setup that is dependent on form values should go in here.
  87 + *
  88 + * We remove the element status if there is no current status (i.e. rubric is only being created)
  89 + * so the users do not get confused
  90 + */
  91 + public function definition_after_data() {
  92 + $form = $this->_form;
  93 + $el = $form->getElement('status');
  94 + if (!$el->getValue()) {
  95 + $form->removeElement('status');
  96 + } else {
  97 + $vals = array_values($el->getValue());
  98 + if ($vals[0] == gradingform_controller::DEFINITION_STATUS_READY) {
  99 + $this->findButton('saverubric')->setValue(get_string('save', 'gradingform_rubric'));
  100 + }
  101 + }
  102 + }
  103 +
  104 + /**
84 105 * Form vlidation.
85 106 * If there are errors return array of errors ("fieldname"=>"error message"),
86 107 * otherwise true if ok.
56 grade/grading/form/rubric/js/rubriceditor.js
@@ -12,6 +12,10 @@ M.gradingform_rubriceditor.init = function(Y, options) {
12 12 }
13 13 M.gradingform_rubriceditor.disablealleditors()
14 14 Y.on('click', M.gradingform_rubriceditor.clickanywhere, 'body', null)
  15 + YUI().use('event-touch', function (Y) {
  16 + Y.one('body').on('touchstart', M.gradingform_rubriceditor.clickanywhere);
  17 + Y.one('body').on('touchend', M.gradingform_rubriceditor.clickanywhere);
  18 + })
15 19 M.gradingform_rubriceditor.addhandlers()
16 20 };
17 21
@@ -35,6 +39,7 @@ M.gradingform_rubriceditor.disablealleditors = function() {
35 39 // it switches this element to edit mode. If rubric button is clicked it does nothing so the 'buttonclick'
36 40 // function is invoked
37 41 M.gradingform_rubriceditor.clickanywhere = function(e) {
  42 + if (e.type == 'touchstart') return
38 43 var el = e.target
39 44 // if clicked on button - disablecurrenteditor, continue
40 45 if (el.get('tagName') == 'INPUT' && el.get('type') == 'submit') {
@@ -48,7 +53,7 @@ M.gradingform_rubriceditor.clickanywhere = function(e) {
48 53 el = el.get('parentNode')
49 54 }
50 55 if (el) {
51   - if (el.one('textarea').getStyle('display') == 'none') {
  56 + if (el.one('textarea').hasClass('hiddenelement')) {
52 57 M.gradingform_rubriceditor.disablealleditors()
53 58 M.gradingform_rubriceditor.editmode(el, true, focustb)
54 59 }
@@ -61,19 +66,19 @@ M.gradingform_rubriceditor.clickanywhere = function(e) {
61 66 // switch the criterion description or level to edit mode or switch back
62 67 M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) {
63 68 var ta = el.one('textarea')
64   - if (!editmode && ta.getStyle('display') == 'none') return;
65   - if (editmode && ta.getStyle('display') == 'block') return;
66   - var pseudotablink = '<a href="#" class="pseudotablink">&nbsp;</a>',
  69 + if (!editmode && ta.hasClass('hiddenelement')) return;
  70 + if (editmode && !ta.hasClass('hiddenelement')) return;
  71 + var pseudotablink = '<input type="text" size="1" class="pseudotablink"/>',
67 72 taplain = ta.get('parentNode').one('.plainvalue'),
68 73 tbplain = null,
69   - tb = el.one('input[type=text]')
  74 + tb = el.one('.score input[type=text]')
70 75 // add 'plainvalue' next to textarea for description/definition and next to input text field for score (if applicable)
71 76 if (!taplain) {
72   - ta.get('parentNode').append('<div class="plainvalue"><span class="textvalue">&nbsp;</span>'+pseudotablink+'</div>')
  77 + ta.get('parentNode').append('<div class="plainvalue">'+pseudotablink+'<span class="textvalue">&nbsp;</span></div>')
73 78 taplain = ta.get('parentNode').one('.plainvalue')
74 79 taplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
75 80 if (tb) {
76   - tb.get('parentNode').append('<div class="plainvalue"><span class="textvalue">&nbsp;</span>'+pseudotablink+'</div>')
  81 + tb.get('parentNode').append('<span class="plainvalue">'+pseudotablink+'<span class="textvalue">&nbsp;</span></span>')
77 82 tbplain = tb.get('parentNode').one('.plainvalue')
78 83 tbplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
79 84 }
@@ -90,20 +95,33 @@ M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) {
90 95 }
91 96 taplain.one('.textvalue').set('innerHTML', value)
92 97 if (tb) tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
  98 + // hide/display textarea, textbox and plaintexts
  99 + taplain.removeClass('hiddenelement')
  100 + ta.addClass('hiddenelement')
  101 + if (tb) {
  102 + tbplain.removeClass('hiddenelement')
  103 + tb.addClass('hiddenelement')
  104 + }
93 105 } else {
94 106 // if we need to show the input fields, set the width/height for textarea so it fills the cell
95   - var width = parseFloat(ta.get('parentNode').getComputedStyle('width')),
96   - height
97   - if (el.hasClass('level')) height = parseFloat(el.getComputedStyle('height')) - parseFloat(el.one('.score').getComputedStyle('height'))
98   - else height = parseFloat(ta.get('parentNode').getComputedStyle('height'))
99   - ta.setStyle('width', Math.max(width,50)+'px').setStyle('height', Math.max(height,20)+'px')
100   - }
101   - // hide/display textarea, textbox and plaintexts
102   - taplain.setStyle('display', editmode ? 'none' : 'block')
103   - ta.setStyle('display', editmode ? 'block' : 'none')
104   - if (tb) {
105   - tbplain.setStyle('display', editmode ? 'none' : 'inline-block')
106   - tb.setStyle('display', editmode ? 'inline-block' : 'none')
  107 + try {
  108 + var width = parseFloat(ta.get('parentNode').getComputedStyle('width')),
  109 + height
  110 + if (el.hasClass('level')) height = parseFloat(el.getComputedStyle('height')) - parseFloat(el.one('.score').getComputedStyle('height'))
  111 + else height = parseFloat(ta.get('parentNode').getComputedStyle('height'))
  112 + ta.setStyle('width', Math.max(width,50)+'px')
  113 + ta.setStyle('height', Math.max(height,20)+'px')
  114 + }
  115 + catch (err) {
  116 + // this browser do not support 'computedStyle', leave the default size of the textbox
  117 + }
  118 + // hide/display textarea, textbox and plaintexts
  119 + taplain.addClass('hiddenelement')
  120 + ta.removeClass('hiddenelement')
  121 + if (tb) {
  122 + tbplain.addClass('hiddenelement')
  123 + tb.removeClass('hiddenelement')
  124 + }
107 125 }
108 126 // focus the proper input field in edit mode
109 127 if (editmode) { if (tb && focustb) tb.focus(); else ta.focus() }
12 grade/grading/form/rubric/lang/en/gradingform_rubric.php
@@ -53,12 +53,18 @@
53 53 $string['regradeoption1'] = 'Mark for regrade';
54 54 $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.';
55 55 $string['rubric'] = 'Rubric';
  56 +$string['rubricmapping'] = 'Score to grade mapping rules';
  57 +$string['rubricmappingexplained'] = 'The minimum possible score for this rubric is <b>{$a->minscore} points</b> and it will be converted to the minimum grade available in this module (which is zero unless the scale is used).
  58 + The maximum score <b>{$a->maxscore} points</b> will be converted to the maximum grade.<br />
  59 + Intermediate scores will be converted respectively and rounded to the nearest available grade.<br />
  60 + If a scale is used instead of a grade, the score will be converted to the scale elements as if they were consecutive integers.';
56 61 $string['rubricnotcompleted'] = 'Please choose something for each criterion';
57 62 $string['rubricoptions'] = 'Rubric options';
58 63 $string['rubricstatus'] = 'Current rubric status';
  64 +$string['save'] = 'Save';
59 65 $string['saverubric'] = 'Save rubric and make it ready';
60 66 $string['saverubricdraft'] = 'Save as draft';
61   -$string['scorepostfix'] = '{$a} points';
  67 +$string['scorepostfix'] = '{$a}points';
62 68 $string['showdescriptionstudent'] = 'Display rubric description to those being graded';
63 69 $string['showdescriptionteacher'] = 'Display rubric description during evaluation';
64 70 $string['showremarksstudent'] = 'Show remarks to those being graded';
@@ -66,6 +72,4 @@
66 72 $string['showscoreteacher'] = 'Display points for each level during evaluation';
67 73 $string['sortlevelsasc'] = 'Sort order for levels:';
68 74 $string['sortlevelsasc0'] = 'Descending by number of points';
69   -$string['sortlevelsasc1'] = 'Ascending by number of points';
70   -$string['statusdraft'] = 'Draft';
71   -$string['statusready'] = 'Ready';
  75 +$string['sortlevelsasc1'] = 'Ascending by number of points';
46 grade/grading/form/rubric/lib.php
@@ -360,9 +360,10 @@ public function get_options() {
360 360 /**
361 361 * Converts the current definition into an object suitable for the editor form's set_data()
362 362 *
  363 + * @param boolean $addemptycriterion whether to add an empty criterion if the rubric is completely empty (just being created)
363 364 * @return stdClass
364 365 */
365   - public function get_definition_for_editing() {
  366 + public function get_definition_for_editing($addemptycriterion = false) {
366 367
367 368 $definition = $this->get_definition();
368 369 $properties = new stdClass();
@@ -378,6 +379,8 @@ public function get_definition_for_editing() {
378 379 $properties->rubric = array('criteria' => array(), 'options' => $this->get_options());
379 380 if (!empty($definition->rubric_criteria)) {
380 381 $properties->rubric['criteria'] = $definition->rubric_criteria;
  382 + } else if (!$definition && $addemptycriterion) {
  383 + $properties->rubric['criteria'] = array('addcriterion' => 1);
381 384 }
382 385
383 386 return $properties;
@@ -481,7 +484,8 @@ public function render_preview(moodle_page $page) {
481 484 $output = $this->get_renderer($page);
482 485 $criteria = $this->definition->rubric_criteria;
483 486 $options = $this->get_options();
484   - $rubric = $output->display_rubric($criteria, $options, self::DISPLAY_PREVIEW, 'rubric');
  487 + $rubric = $output->display_rubric_mapping_explained($this->get_min_max_score());
  488 + $rubric .= $output->display_rubric($criteria, $options, self::DISPLAY_PREVIEW, 'rubric');
485 489
486 490 return $rubric;
487 491 }
@@ -590,6 +594,28 @@ public static function sql_search_where($token) {
590 594
591 595 return array($subsql, $params);
592 596 }
  597 +
  598 + /**
  599 + * Calculates and returns the possible minimum and maximum score (in points) for this rubric
  600 + *
  601 + * @return array
  602 + */
  603 + public function get_min_max_score() {
  604 + if (!$this->is_form_available()) {
  605 + return null;
  606 + }
  607 + $returnvalue = array('minscore' => 0, 'maxscore' => 0);
  608 + foreach ($this->get_definition()->rubric_criteria as $id => $criterion) {
  609 + $scores = array();
  610 + foreach ($criterion['levels'] as $level) {
  611 + $scores[] = $level['score'];
  612 + }
  613 + sort($scores);
  614 + $returnvalue['minscore'] += $scores[0];
  615 + $returnvalue['maxscore'] += $scores[sizeof($scores)-1];
  616 + }
  617 + return $returnvalue;
  618 + }
593 619 }
594 620
595 621 /**
@@ -717,19 +743,7 @@ public function get_grade() {
717 743 global $DB, $USER;
718 744 $grade = $this->get_rubric_filling();
719 745
720   - $minscore = 0;
721   - $maxscore = 0;
722   - foreach ($this->get_controller()->get_definition()->rubric_criteria as $id => $criterion) {
723   - $scores = array();
724   - foreach ($criterion['levels'] as $level) {
725   - $scores[] = $level['score'];
726   - }
727   - sort($scores);
728   - $minscore += $scores[0];
729   - $maxscore += $scores[sizeof($scores)-1];
730   - }
731   -
732   - if ($maxscore <= $minscore) {
  746 + if (!($scores = $this->get_controller()->get_min_max_score()) || $scores['maxscore'] <= $scores['minscore']) {
733 747 return -1;
734 748 }
735 749
@@ -745,7 +759,7 @@ public function get_grade() {
745 759 foreach ($grade['criteria'] as $id => $record) {
746 760 $curscore += $this->get_controller()->get_definition()->rubric_criteria[$id]['levels'][$record['levelid']]['score'];
747 761 }
748   - return round(($curscore-$minscore)/($maxscore-$minscore)*($maxgrade-$mingrade), 0) + $mingrade; // TODO mapping
  762 + return round(($curscore-$scores['minscore'])/($scores['maxscore']-$scores['minscore'])*($maxgrade-$mingrade), 0) + $mingrade;
749 763 }
750 764
751 765 /**
22 grade/grading/form/rubric/renderer.php
@@ -164,7 +164,7 @@ public function level_template($mode, $options, $elementname = '{NAME}', $criter
164 164 $leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper'));
165 165 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
166 166 $definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
167   - $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '4', 'value' => $level['score']));
  167 + $score = html_writer::empty_tag('input', array('type' => 'text', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
168 168 } else {
169 169 if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
170 170 $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 @@ public function level_template($mode, $options, $elementname = '{NAME}', $criter
181 181 if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN && $level['checked']) {
182 182 $leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levelid]', 'value' => $level['id']));
183 183 }
184   - $score = html_writer::tag('span', $score, array('id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-score'));
  184 + $score = html_writer::tag('span', $score, array('id' => '{NAME}-criteria-{CRITERION-id}-levels-{LEVEL-id}-score', 'class' => 'scorevalue'));
185 185 $definitionclass = 'definition';
186 186 if (isset($level['error_definition'])) {
187 187 $definitionclass .= ' error';
@@ -453,4 +453,22 @@ public function display_regrade_confirmation($elementname, $changelevel, $value)
453 453 $html .= html_writer::end_tag('div');
454 454 return $html;
455 455 }
  456 +
  457 + /**
  458 + * Generates and returns HTML code to display information box about how rubric score is converted to the grade
  459 + *
  460 + * @param array $scores
  461 + * @return string
  462 + */
  463 + public function display_rubric_mapping_explained($scores) {
  464 + $html = '';
  465 + if (!$scores) {
  466 + return $html;
  467 + }
  468 + $html .= $this->box(
  469 + html_writer::tag('h4', get_string('rubricmapping', 'gradingform_rubric')).
  470 + html_writer::tag('div', get_string('rubricmappingexplained', 'gradingform_rubric', (object)$scores))
  471 + , 'generalbox rubricmappingexplained');
  472 + return $html;
  473 + }
456 474 }
8 grade/grading/form/rubric/rubriceditor.php
@@ -184,14 +184,14 @@ protected function prepare_data($value = null, $withvalidation = false) {
184 184 // when adding new criterion copy the number of levels and their scores from the last criterion
185 185 if (!empty($value['criteria'][$lastid]['levels'])) {
186 186 foreach ($value['criteria'][$lastid]['levels'] as $lastlevel) {
187   - $criterion['levels']['NEWID'+($i++)]['score'] = $lastlevel['score'];
  187 + $criterion['levels']['NEWID'.($i++)]['score'] = $lastlevel['score'];
188 188 }
189 189 } else {
190   - $criterion['levels']['NEWID'+($i++)]['score'] = 0;
  190 + $criterion['levels']['NEWID'.($i++)]['score'] = 0;
191 191 }
192 192 // add more levels so there are at least 3 in the new criterion. Increment by 1 the score for each next one
193   - for ($i; $i<3; $i++) {
194   - $criterion['levels']['NEWID'+$i]['score'] = $criterion['levels']['NEWID'+($i-1)]['score'] + 1;
  193 + for ($i=$i; $i<3; $i++) {
  194 + $criterion['levels']['NEWID'.$i]['score'] = $criterion['levels']['NEWID'.($i-1)]['score'] + 1;
195 195 }
196 196 // set other necessary fields (definition) for the levels in the new criterion
197 197 foreach (array_keys($criterion['levels']) as $i) {
11 grade/grading/form/rubric/styles.css
@@ -45,6 +45,10 @@
45 45
46 46 */
47 47
  48 +.gradingform_rubric_editform .status {font-weight:normal;text-transform:uppercase;font-size:60%;padding:0.25em;border:1px solid #EEE;-moz-border-radius:5px;}
  49 +.gradingform_rubric_editform .status.ready {background-color:#e7f1c3;border-color:#AAEEAA;}
  50 +.gradingform_rubric_editform .status.draft {background-color:#f3f2aa;border-color:#EEEE22;}
  51 +
48 52 .gradingform_rubric.editor .criterion .controls,
49 53 .gradingform_rubric .criterion .description,
50 54 .gradingform_rubric .criterion .levels,
@@ -75,7 +79,8 @@
75 79 .gradingform_rubric .plainvalue.empty {font-style: italic; color: #AAA;}
76 80
77 81 .gradingform_rubric.editor .criterion .levels .level .delete {position:absolute;right:0;bottom:0;}
78   -.gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;}
  82 +.gradingform_rubric .criterion .levels .level .score {font-style:italic;color:#575;font-weight: bold;margin-top:5px;nowrap:nowrap;}
  83 +.gradingform_rubric .criterion .levels .level .score .scorevalue {padding-right:5px;}
79 84
80 85 /* Make invisible the buttons 'Move up' for the first criterion and 'Move down' for the last, because those buttons will make no change */
81 86 .gradingform_rubric.editor .criterion.first .controls .moveup input,
@@ -110,6 +115,10 @@
110 115 .gradingform_rubric .criterion .levels .level .definition.error,
111 116 .gradingform_rubric .criterion .levels .level .score.error {background:#FFDDDD;}
112 117
  118 +/* special classes for elements created by rubriceditor.js */
  119 +.gradingform_rubric.editor .hiddenelement {display:none;}
  120 +.gradingform_rubric.editor .pseudotablink {background-color:transparent;border:0px solid;height:1px;width:1px;color:transparent;padding:0;margin:0;position:relative;float:right;}
  121 +
113 122 /**
114 123 *
115 124 */

0 comments on commit a19d105

Please sign in to comment.
Something went wrong with that request. Please try again.