mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 22:08:20 +01:00
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
This commit is contained in:
parent
fe41ba7489
commit
a19d1057ca
@ -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);
|
||||
|
@ -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
|
||||
@ -80,6 +80,27 @@ class gradingform_rubric_editrubric extends moodleform {
|
||||
$form->closeHeaderBefore('buttonar');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"),
|
||||
|
@ -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 = '<a href="#" class="pseudotablink"> </a>',
|
||||
if (!editmode && ta.hasClass('hiddenelement')) return;
|
||||
if (editmode && !ta.hasClass('hiddenelement')) return;
|
||||
var pseudotablink = '<input type="text" size="1" class="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('<div class="plainvalue"><span class="textvalue"> </span>'+pseudotablink+'</div>')
|
||||
ta.get('parentNode').append('<div class="plainvalue">'+pseudotablink+'<span class="textvalue"> </span></div>')
|
||||
taplain = ta.get('parentNode').one('.plainvalue')
|
||||
taplain.one('.pseudotablink').on('focus', M.gradingform_rubriceditor.clickanywhere)
|
||||
if (tb) {
|
||||
tb.get('parentNode').append('<div class="plainvalue"><span class="textvalue"> </span>'+pseudotablink+'</div>')
|
||||
tb.get('parentNode').append('<span class="plainvalue">'+pseudotablink+'<span class="textvalue"> </span></span>')
|
||||
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() }
|
||||
|
@ -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 <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).
|
||||
The maximum score <b>{$a->maxscore} points</b> will be converted to the maximum grade.<br />
|
||||
Intermediate scores will be converted respectively and rounded to the nearest available grade.<br />
|
||||
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';
|
||||
$string['sortlevelsasc1'] = 'Ascending by number of points';
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user