'items' => array(),
'users' => array(),
'feedback' => array(),
- 'grades' => array(),
+ 'grades' => array()
);
$jsscales = array();
} else if ($USER->gradeediting[$this->courseid]) {
+ if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
+ $itemcell->attributes['class'] .= ' grade_type_scale';
+ } else if ($item->gradetype == GRADE_TYPE_VALUE) {
+ $itemcell->attributes['class'] .= ' grade_type_value';
+ } else if ($item->gradetype == GRADE_TYPE_TEXT) {
+ $itemcell->attributes['class'] .= ' grade_type_text';
+ }
+
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
$scale = $scalesarray[$item->scaleid];
$gradeval = (int)$gradeval; // scales use only integers
if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
$itemcell->attributes['class'] .= ' grade_type_scale';
- } else if ($item->gradetype != GRADE_TYPE_TEXT) {
+ } else if ($item->gradetype == GRADE_TYPE_VALUE) {
+ $itemcell->attributes['class'] .= ' grade_type_value';
+ } else if ($item->gradetype == GRADE_TYPE_TEXT) {
$itemcell->attributes['class'] .= ' grade_type_text';
}
- if ($enableajax) {
- $canoverride = true;
- if ($item->is_category_item() || $item->is_course_item()) {
- $canoverride = (bool) get_config('moodle', 'grade_overridecat');
- }
- if ($canoverride) {
+ // Only allow edting if the grade is editable (not locked, not in a unoverridable category, etc).
+ if ($enableajax && $grade->is_editable()) {
+ // If a grade item is type text, and we don't have show quick feedback on, it can't be edited.
+ if ($item->gradetype != GRADE_TYPE_TEXT || $showquickfeedback) {
$itemcell->attributes['class'] .= ' clickable';
}
}
$jsarguments['cfg']['ajaxenabled'] = true;
$jsarguments['cfg']['scales'] = array();
foreach ($jsscales as $scale) {
- $jsarguments['cfg']['scales'][$scale->id] = explode(',', $scale->scale);
+ // Trim the scale values, as they may have a space that is ommitted from values later.
+ $jsarguments['cfg']['scales'][$scale->id] = array_map('trim', explode(',', $scale->scale));
}
$jsarguments['cfg']['feedbacktrunclength'] = $this->feedback_trunc_length;
}
// Sort out the field type
- var fieldtype = 'text';
+ var fieldtype = 'value';
if (node.hasClass('grade_type_scale')) {
fieldtype = 'scale';
+ } else if (node.hasClass('grade_type_text')) {
+ fieldtype = 'text';
}
// Create the appropriate field widget
switch (fieldtype) {
this.current = new M.gradereport_grader.classes.scalefield(this.report, node);
break;
case 'text':
+ this.current = new M.gradereport_grader.classes.feedbackfield(this.report, node);
+ break;
default:
this.current = new M.gradereport_grader.classes.textfield(this.report, node);
break;
next = tr.all('.grade').item(0);
}
if (!next) {
- next = this.current.node;
+ return this.current.node;
+ }
+ // Continue on until we find a clickable cell
+ if (!next.hasClass('clickable')) {
+ return this.get_next_cell(next);
}
return next;
};
next = cells.item(cells.size()-1);
}
if (!next) {
- next = this.current.node;
+ return this.current.node;
+ }
+ // Continue on until we find a clickable cell
+ if (!next.hasClass('clickable')) {
+ return this.get_prev_cell(next);
}
return next;
};
next = tr.all('td.cell').item(column);
}
if (!next) {
- next = this.current.node;
+ return this.current.node;
+ }
+ // Continue on until we find a clickable cell
+ if (!next.hasClass('clickable')) {
+ return this.get_above_cell(next);
}
return next;
};
}
next = tr.all('td.cell').item(column);
}
- // next will be null when we get to the bottom of a column
+ if (!next) {
+ return this.current.node;
+ }
+ // Continue on until we find a clickable cell
+ if (!next.hasClass('clickable')) {
+ return this.get_below_cell(next);
+ }
return next;
};
/**
}
// Calculate the final grade for the cell
var finalgrade = '';
+ var scalegrade = -1;
if (!r.finalgrade) {
if (this.report.isediting) {
// In edit mode don't put hyphens in the grade text boxes
}
} else {
if (r.scale) {
- finalgrade = this.scales[r.scale][parseFloat(r.finalgrade)-1];
+ scalegrade = parseFloat(r.finalgrade);
+ finalgrade = this.scales[r.scale][scalegrade-1];
} else {
finalgrade = parseFloat(r.finalgrade).toFixed(info.itemdp);
}
}
if (this.report.isediting) {
- if (args.properties.itemtype == 'scale') {
- info.cell.one('#grade_'+r.userid+'_'+r.itemid).all('options').each(function(option){
- if (option.get('value') == finalgrade) {
- option.setAttribute('selected', 'selected');
- } else {
- option.removeAttribute('selected');
- }
- });
- } else {
- info.cell.one('#grade_'+r.userid+'_'+r.itemid).set('value', finalgrade);
+ var grade = info.cell.one('#grade_'+r.userid+'_'+r.itemid);
+ if (grade) {
+ // This means the item has a input element to update.
+ var parent = grade.ancestor('td');
+ if (parent.hasClass('grade_type_scale')) {
+ grade.all('option').each(function(option) {
+ if (option.get('value') == scalegrade) {
+ option.setAttribute('selected', 'selected');
+ } else {
+ option.removeAttribute('selected');
+ }
+ });
+ } else {
+ grade.set('value', finalgrade);
+ }
+ } else if (info.cell.one('.gradevalue')) {
+ // This means we are updating a value for something without editing boxed (locked, etc).
+ info.cell.one('.gradevalue').set('innerHTML', finalgrade);
}
} else {
// If there is no currently editing field or if this cell is not being currently edited
if (!this.current || info.cell.get('id') != this.current.node.get('id')) {
// Update the value
- info.cell.one('.gradevalue').set('innerHTML',finalgrade);
+ var node = info.cell.one('.gradevalue');
+ var td = node.ancestor('td');
+ // Only scale and value type grades should have their content updated in this way.
+ if (td.hasClass('grade_type_value') || td.hasClass('grade_type_scale')) {
+ node.set('innerHTML', finalgrade);
+ }
} else if (this.current && info.cell.get('id') == this.current.node.get('id')) {
// If we are here the grade value of the cell currently being edited has changed !!!!!!!!!
// If the user has not actually changed the old value yet we will automatically correct it
// otherwise we will prompt the user to choose to use their value or the new value!
if (!this.current.has_changed() || confirm(M.util.get_string('ajaxfieldchanged', 'gradereport_grader'))) {
this.current.set_grade(finalgrade);
- this.current.grade.set('value', finalgrade);
+ if (this.current.grade) {
+ this.current.grade.set('value', finalgrade);
+ }
}
}
}
this.editfeedback = ajax.showquickfeedback;
this.grade = this.report.Y.one('#grade_'+userid+'_'+itemid);
- if (this.grade !== null) {
- for (var i = 0; i < this.report.grades.length; i++) {
- if (this.report.grades[i]['user']==this.userid && this.report.grades[i]['item']==this.itemid) {
+ var i = 0;
+ if (this.grade) {
+ for (i = 0; i < this.report.grades.length; i++) {
+ if (this.report.grades[i]['user'] == this.userid && this.report.grades[i]['item'] == this.itemid) {
this.oldgrade = this.report.grades[i]['grade'];
}
}
// On blur save any changes in the grade field
this.grade.on('blur', this.submit, this);
-
}
// Check if feedback is enabled
// Get the feedback fields
this.feedback = this.report.Y.one('#feedback_'+userid+'_'+itemid);
- if (this.feedback !== null) {
- for(var i = 0; i < this.report.feedback.length; i++) {
- if (this.report.feedback[i]['user']==this.userid && this.report.feedback[i]['item']==this.itemid) {
+ if (this.feedback) {
+ for(i = 0; i < this.report.feedback.length; i++) {
+ if (this.report.feedback[i]['user'] == this.userid && this.report.feedback[i]['item'] == this.itemid) {
this.oldfeedback = this.report.feedback[i]['content'];
}
}
this.feedback.on('blur', this.submit, this);
// Override the default tab movements when moving between cells
- this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.feedback, 'press:9', this, true)); // Handle Tab
- this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.feedback, 'press:13', this)); // Handle the Enter key being pressed
- this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.feedback, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
+ // Handle Tab.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.feedback, 'press:9', this, true));
+ // Handle the Enter key being pressed.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.feedback, 'press:13', this));
+ // Handle CTRL + arrow keys.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.feedback, 'press:37,38,39,40+ctrl', this));
+ if (this.grade) {
+ // Override the default tab movements when moving between cells
+ // Handle Shift+Tab.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9+shift', this));
- if (this.grade !== null) {
// Override the default tab movements for fields in the same cell
- this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();this.grade.focus();}, this.feedback, 'press:9+shift', this));
- this.keyevents.push(this.report.Y.on('key', function(e){if (e.shiftKey) {return;}e.preventDefault();this.feedback.focus();}, this.grade, 'press:9', this));
-
- // Override the default tab movements when moving between cells
- if (this.editfeedback) {
- this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9+shift', this)); // Handle Shift+Tab
- }
+ this.keyevents.push(this.report.Y.on('key',
+ function(e){e.preventDefault();this.grade.focus();},
+ this.feedback,
+ 'press:9+shift',
+ this));
+ this.keyevents.push(this.report.Y.on('key',
+ function(e){if (e.shiftKey) {return;}e.preventDefault();this.feedback.focus();},
+ this.grade,
+ 'press:9',
+ this));
}
}
- } else if (this.grade !== null) {
- this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this)); // Handle Tab and Shift+Tab
+ } else if (this.grade) {
+ // Handle Tab and Shift+Tab.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this));
}
- if (this.feedback !== null) {
- this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this)); // Handle the Enter key being pressed
- this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
+ if (this.grade) {
+ // Handle the Enter key being pressed.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this));
+ // Handle CTRL + arrow keys.
+ this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this));
}
};
/**
* @return {Bool}
*/
M.gradereport_grader.classes.existingfield.prototype.has_changed = function() {
- if (this.editfeedback) {
- return (this.grade.get('value') !== this.oldgrade || this.feedback.get('value') !== this.oldfeedback);
+ if (this.grade) {
+ if (this.grade.get('value') !== this.oldgrade) {
+ return true;
+ }
}
- return (this.grade.get('value') !== this.oldgrade);
+ if (this.editfeedback && this.feedback) {
+ if (this.feedback.get('value') !== this.oldfeedback) {
+ return true;
+ }
+ }
+ return false;
};
/**
* Submits any changes and then updates the fields accordingly
var properties = this.report.get_cell_info([this.userid,this.itemid]);
var values = (function(f){
- var feedback, oldfeedback = null;
- if (f.editfeedback) {
+ var feedback, oldfeedback, grade, oldgrade = null;
+ if (f.editfeedback && f.feedback) {
feedback = f.feedback.get('value');
oldfeedback = f.oldfeedback;
}
+ if (f.grade) {
+ grade = f.grade.get('value');
+ oldgrade = f.oldgrade;
+ }
return {
editablefeedback : f.editfeedback,
- grade : f.grade.get('value'),
- oldgrade : f.oldgrade,
+ grade : grade,
+ oldgrade : oldgrade,
feedback : feedback,
oldfeedback : oldfeedback
};
this.gradespan = node.one('.gradevalue');
this.inputdiv = this.report.Y.Node.create('<div></div>');
this.editfeedback = this.report.ajax.showquickfeedback;
- this.grade = this.report.Y.Node.create('<input type="text" class="text" value="" />');
+ this.grade = this.report.Y.Node.create('<input type="text" class="text" value="" name="ajaxgrade" />');
this.gradetype = 'value';
this.inputdiv.append(this.grade);
if (this.report.ajax.showquickfeedback) {
- this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" />');
+ this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" name="ajaxfeedback" />');
this.inputdiv.append(this.feedback);
}
};
this.set_feedback(this.get_feedback());
}
this.node.replaceChild(this.inputdiv, this.gradespan);
- this.grade.focus();
+ if (this.grade) {
+ this.grade.focus();
+ } else if (this.feedback) {
+ this.feedback.focus();
+ }
this.editable = true;
return this;
};
M.gradereport_grader.classes.textfield.prototype.commit = function() {
// Produce an anonymous result object contianing all values
var result = (function(field){
+ // Editable false lets us get the pre-update values.
field.editable = false;
var oldgrade = field.get_grade();
if (oldgrade == '-') {
if (field.editfeedback) {
oldfeedback = field.get_feedback();
}
+
+ // Now back to editable gives us the values in the edit areas.
field.editable = true;
if (field.editfeedback) {
feedback = field.get_feedback();
*/
M.gradereport_grader.classes.textfield.prototype.get_feedback = function() {
if (this.editable) {
- return this.feedback.get('value');
+ if (this.feedback) {
+ return this.feedback.get('value');
+ } else {
+ return null;
+ }
}
var properties = this.report.get_cell_info(this.node);
if (properties) {
*/
M.gradereport_grader.classes.textfield.prototype.set_feedback = function(value) {
if (!this.editable) {
- this.feedback.set('value', value);
+ if (this.feedback) {
+ this.feedback.set('value', value);
+ }
} else {
var properties = this.report.get_cell_info(this.node);
this.report.update_feedback(properties.userid, properties.itemid, value);
return true;
}
}
- return (this.get_grade() != this.gradespan.get('innerHTML'));
+
+ if (this.grade) {
+ return (this.get_grade() != this.gradespan.get('innerHTML'));
+ } else {
+ return false;
+ }
};
/**
* Attaches the key listeners for the editable fields and stored the event references
var a = this.report.ajax;
// Setup the default key events for tab and enter
if (this.editfeedback) {
- this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9+shift', a)); // Handle Shift+Tab
- this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'press:9', a, true)); // Handle Tab
- this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'press:13', a)); // Handle the Enter key being pressed
+ if (this.grade) {
+ // Handle Shift+Tab.
+ this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9+shift', a));
+ }
+ // Handle Tab.
+ this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.feedback, 'press:9', a, true));
+ // Handle the Enter key being pressed.
+ this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.feedback, 'press:13', a));
} else {
- this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9', a)); // Handle Tab and Shift+Tab
- }
- this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'press:13', a)); // Handle the Enter key being pressed
- // Setup the arrow key events
- this.keyevents.push(this.report.Y.on('key', a.keypress_arrows, this.grade.ancestor('td'), 'down:37,38,39,40+ctrl', a)); // Handle CTRL + arrow keys
- // Prevent the default key action on all fields for arrow keys on all key events!
- // Note: this still does not work in FF!!!!!
- this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl'));
- this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'press:37,38,39,40+ctrl'));
- this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'up:37,38,39,40+ctrl'));
+ if (this.grade) {
+ // Handle Tab and Shift+Tab.
+ this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'press:9', a));
+ }
+ }
+
+ // Setup the arrow key events.
+ // Handle CTRL + arrow keys.
+ this.keyevents.push(this.report.Y.on('key', a.keypress_arrows, this.inputdiv.ancestor('td'), 'down:37,38,39,40+ctrl', a));
+
+ if (this.grade) {
+ // Handle the Enter key being pressed.
+ this.keyevents.push(this.report.Y.on('key', a.keypress_enter, this.grade, 'press:13', a));
+ // Prevent the default key action on all fields for arrow keys on all key events!
+ // Note: this still does not work in FF!!!!!
+ this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'down:37,38,39,40+ctrl'));
+ this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'press:37,38,39,40+ctrl'));
+ this.keyevents.push(this.report.Y.on('key', function(e){e.preventDefault();}, this.grade, 'up:37,38,39,40+ctrl'));
+ }
+};
+
+/**
+ * Feedback field class
+ * This classes gets used in conjunction with the report running with AJAX enabled
+ * and is used to manage a cell that no editable grade, only possibly feedback
+ *
+ * @class feedbackfield
+ * @constructor
+ * @this {M.gradereport_grader.classes.feedbackfield}
+ * @param {M.gradereport_grader.classes.report} report
+ * @param {Y.Node} node
+ */
+M.gradereport_grader.classes.feedbackfield = function(report, node) {
+ this.report = report;
+ this.node = node;
+ this.gradespan = node.one('.gradevalue');
+ this.inputdiv = this.report.Y.Node.create('<div></div>');
+ this.editfeedback = this.report.ajax.showquickfeedback;
+ this.gradetype = 'text';
+ if (this.report.ajax.showquickfeedback) {
+ this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" name="ajaxfeedback" />');
+ this.inputdiv.append(this.feedback);
+ }
};
+/**
+ * Gets the grade for current cell (which will always be null)
+ *
+ * @function
+ * @this {M.gradereport_grader.classes.feedbackfield}
+ * @return {Mixed}
+ */
+M.gradereport_grader.classes.feedbackfield.prototype.get_grade = function() {
+ return null;
+};
+
+/**
+ * Overrides the set_grade function of textfield so that it can ignore the set-grade
+ * for grade cells without grades
+ *
+ * @function
+ * @this {M.gradereport_grader.classes.feedbackfield}
+ * @param {String} value
+ */
+M.gradereport_grader.classes.feedbackfield.prototype.set_grade = function() {
+ return;
+};
+
+/**
+ * Manually extend the feedbackfield class with the properties and methods of the
+ * textfield class that have not been defined
+ */
+for (var i in M.gradereport_grader.classes.textfield.prototype) {
+ if (!M.gradereport_grader.classes.feedbackfield.prototype[i]) {
+ M.gradereport_grader.classes.feedbackfield.prototype[i] = M.gradereport_grader.classes.textfield.prototype[i];
+ }
+}
+
/**
* An editable scale field
*
this.gradespan = node.one('.gradevalue');
this.inputdiv = this.report.Y.Node.create('<div></div>');
this.editfeedback = this.report.ajax.showquickfeedback;
- this.grade = this.report.Y.Node.create('<select type="text" class="text" /><option value="-1">'+M.util.get_string('ajaxchoosescale', 'gradereport_grader')+'</option></select>');
+ this.grade = this.report.Y.Node.create('<select type="text" class="text" name="ajaxgrade" /><option value="-1">'+
+ M.util.get_string('ajaxchoosescale', 'gradereport_grader')+'</option></select>');
this.gradetype = 'scale';
this.inputdiv.append(this.grade);
if (this.editfeedback) {
- this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" />');
+ this.feedback = this.report.Y.Node.create('<input type="text" class="quickfeedback" value="" name="ajaxfeedback"/>');
this.inputdiv.append(this.feedback);
}
var properties = this.report.get_cell_info(node);
--- /dev/null
+@gradereport @gradereport_grader
+Feature: Using the AJAX grading feature of Grader report to update grades and feedback
+ In order to use AJAX grading
+ As a teacher
+ I need to be able to update and verify grades
+
+
+ Background:
+ Given the following "courses" exist:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | t1 |
+ | student1 | Student | 1 | student1@example.com | s1 |
+ | student2 | Student | 2 | student2@example.com | s2 |
+ | student3 | Student | 3 | student3@example.com | s3 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ | student3 | C1 | student |
+ And the following "scales" exist:
+ | name | scale |
+ | Test Scale | Disappointing,Good,Very good,Excellent |
+ And the following "grade categories" exist:
+ | fullname | course |
+ | Grade Cat | C1 |
+ And the following "grade items" exist:
+ | itemname | course | locked | gradetype | gradecategory |
+ | Item 1 | C1 | 0 | value | Grade Cat |
+ | Item VU | C1 | 0 | value | Grade Cat |
+ | Item VL | C1 | 1 | value | Grade Cat |
+ | Item TU | C1 | 0 | text | Grade Cat |
+ | Item TL | C1 | 1 | text | Grade Cat |
+ And the following "grade items" exist:
+ | itemname | course | locked | gradetype | scale | gradecategory |
+ | Item SU | C1 | 0 | scale | Test Scale | Grade Cat |
+ | Item SL | C1 | 1 | scale | Test Scale | Grade Cat |
+ And the following "grade items" exist:
+ | itemname | course | locked | gradetype | gradecategory |
+ | Item 3 | C1 | 0 | value | Grade Cat |
+ And the following config values are set as admin:
+ | grade_report_showaverages | 0 |
+ | grade_report_enableajax | 1 |
+
+
+ @javascript
+ Scenario: Use the grader report without editing, with AJAX on and quick feedback off
+ When the following config values are set as admin:
+ | grade_overridecat | 1 |
+ | grade_report_showquickfeedback | 0 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Grades" node in "Course administration"
+ And I click on student "Student 2" for grade item "Item VU"
+ Then I should see a grade field for "Student 2" and grade item "Item VU"
+ And I should not see a feedback field for "Student 2" and grade item "Item VU"
+ And I set the field "ajaxgrade" to "33"
+ And I press key "13" in the field "ajaxgrade"
+ And I should not see a grade field for "Student 2" and grade item "Item VU"
+ And I should not see a feedback field for "Student 2" and grade item "Item VU"
+ And I click on student "Student 3" for grade item "Item VU"
+ And I set the field "ajaxgrade" to "50"
+ And I press key "13" in the field "ajaxgrade"
+ And I click on student "Student 3" for grade item "Item 1"
+ And I set the field "ajaxgrade" to "80"
+ And I press key "13" in the field "ajaxgrade"
+ And I click on student "Student 3" for grade item "Item SU"
+ And I set the field "ajaxgrade" to "Very good"
+ And I press key "13" in the field "ajaxgrade"
+ And the following should exist in the "user-grades" table:
+ | -1- | -4- | -5- | -9- | -13- |
+ | Student 2 | - | 33.00 | - | 33.00 |
+ | Student 3 | 80.00 | 50.00 | Very good | 133.00 |
+ And I click on student "Student 3" for grade item "Item VL"
+ And I should not see a grade field for "Student 3" and grade item "Item VL"
+ And I should not see a feedback field for "Student 3" and grade item "Item VL"
+ And I click on student "Student 3" for grade item "Item SL"
+ And I should not see a grade field for "Student 3" and grade item "Item SL"
+ And I should not see a feedback field for "Student 3" and grade item "Item SL"
+ And I click on student "Student 3" for grade item "Item TU"
+ And I should not see a grade field for "Student 3" and grade item "Item TU"
+ And I should not see a feedback field for "Student 3" and grade item "Item TU"
+ And I click on student "Student 1" for grade item "Course total"
+ And I should see a grade field for "Student 1" and grade item "Course total"
+ And I should not see a feedback field for "Student 1" and grade item "Course total"
+ And I set the field "ajaxgrade" to "90"
+ And I press key "13" in the field "ajaxgrade"
+ And the following should exist in the "user-grades" table:
+ | -1- | -13- |
+ | Student 1 | 90.00 |
+ And I navigate to "Grader report" node in "Grade administration"
+ And the following should exist in the "user-grades" table:
+ | -1- | -4- | -5- | -9- | -13- |
+ | Student 1 | - | - | - | 90.00 |
+ | Student 2 | - | 33.00 | - | 33.00 |
+ | Student 3 | 80.00 | 50.00 | Very good | 133.00 |
+
+ @javascript
+ Scenario: Use the grader report without editing, with AJAX and quick feedback on
+ When the following config values are set as admin:
+ | grade_overridecat | 1 |
+ | grade_report_showquickfeedback | 1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Grades" node in "Course administration"
+ And I click on student "Student 2" for grade item "Item VU"
+ Then I should see a grade field for "Student 2" and grade item "Item VU"
+ And I should see a feedback field for "Student 2" and grade item "Item VU"
+ And I set the field "ajaxgrade" to "33"
+ And I set the field "ajaxfeedback" to "Student 2 VU feedback"
+ And I press key "13" in the field "ajaxfeedback"
+ And I click on student "Student 3" for grade item "Item VL"
+ And I should not see a grade field for "Student 3" and grade item "Item VL"
+ And I should not see a feedback field for "Student 3" and grade item "Item VL"
+ And I click on student "Student 3" for grade item "Item TU"
+ And I should not see a grade field for "Student 3" and grade item "Item TU"
+ And I should see a feedback field for "Student 3" and grade item "Item TU"
+ And I set the field "ajaxfeedback" to "Student 3 TU feedback"
+ And I press key "13" in the field "ajaxfeedback"
+ And I click on student "Student 2" for grade item "Item SU"
+ And I set the field "ajaxgrade" to "Very good"
+ And I set the field "ajaxfeedback" to "Student 2 SU feedback"
+ And I press key "13" in the field "ajaxfeedback"
+ And I navigate to "Grader report" node in "Grade administration"
+ And the following should exist in the "user-grades" table:
+ | -1- | -5- | -9- | -13- |
+ | Student 2 | 33.00 | Very good | 36.00 |
+ And I click on student "Student 3" for grade item "Item TU"
+ And the field "ajaxfeedback" matches value "Student 3 TU feedback"
+ And I click on student "Student 2" for grade item "Item SU"
+ And the field "ajaxfeedback" matches value "Student 2 SU feedback"
+
+ @javascript
+ Scenario: Use the grader report without editing, with AJAX and quick feedback on, without category override
+ When the following config values are set as admin:
+ | grade_overridecat | 0 |
+ | grade_report_showquickfeedback | 1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Grades" node in "Course administration"
+ And I click on student "Student 2" for grade item "Item VU"
+ Then I should see a grade field for "Student 2" and grade item "Item VU"
+ And I should see a feedback field for "Student 2" and grade item "Item VU"
+ And I set the field "ajaxgrade" to "33"
+ And I press key "13" in the field "ajaxgrade"
+ And I click on student "Student 2" for grade item "Course total"
+ And I should not see a grade field for "Student 3" and grade item "Course total"
+ And I should not see a feedback field for "Student 3" and grade item "Course total"
+ And the following should exist in the "user-grades" table:
+ | -1- | -5- | -13- |
+ | Student 2 | 33.00 | 33.00 |
+
+ @javascript
+ Scenario: Use the grader report with editing, with AJAX and quick feedback on, with category override
+ When the following config values are set as admin:
+ | grade_overridecat | 1 |
+ | grade_report_showquickfeedback | 1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Grades" node in "Course administration"
+ And I turn editing mode on
+ Then I should not see a grade field for "Student 2" and grade item "Item VL"
+ And I should not see a feedback field for "Student 2" and grade item "Item VL"
+ And I should not see a grade field for "Student 2" and grade item "Item TU"
+ And I should see a feedback field for "Student 2" and grade item "Item TU"
+ And I should see a grade field for "Student 2" and grade item "Course total"
+ And I should see a feedback field for "Student 2" and grade item "Course total"
+ And I give the grade "20.00" to the user "Student 2" for the grade item "Item VU"
+ And I click away from student "Student 2" and grade item "Item VU" value
+ And I give the grade "30.00" to the user "Student 2" for the grade item "Item 1"
+ And I give the feedback "Some feedback" to the user "Student 2" for the grade item "Item 1"
+ And I click away from student "Student 2" and grade item "Item 1" feedback
+ And I give the grade "Very good" to the user "Student 2" for the grade item "Item SU"
+ And I click away from student "Student 2" and grade item "Item SU" value
+ And the grade for "Student 2" in grade item "Grade Cat" should match "53.00"
+ And the grade for "Student 2" in grade item "Course total" should match "53.00"
+ And I turn editing mode off
+ And the following should exist in the "user-grades" table:
+ | -1- | -4- | -5- | -9- | -12- | -13- |
+ | Student 2 | 30.00 | 20.00 | Very good | 53.00 | 53.00 |
+ And I click on student "Student 2" for grade item "Item 1"
+ And the field "ajaxfeedback" matches value "Some feedback"
+
+ @javascript
+ Scenario: Use the grader report with editing, with AJAX and quick feedback on, without category override
+ When the following config values are set as admin:
+ | grade_overridecat | 0 |
+ | grade_report_showquickfeedback | 1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Grades" node in "Course administration"
+ And I turn editing mode on
+ Then I should not see a grade field for "Student 2" and grade item "Course total"
+ And I should not see a feedback field for "Student 2" and grade item "Course total"
+ And I give the grade "20.00" to the user "Student 2" for the grade item "Item VU"
+ And I click away from student "Student 2" and grade item "Item VU" value
+ And I give the grade "30.00" to the user "Student 2" for the grade item "Item 1"
+ And I click away from student "Student 2" and grade item "Item 1" value
+ And I give the feedback "Some feedback" to the user "Student 2" for the grade item "Item 1"
+ And I click away from student "Student 2" and grade item "Item 1" feedback
+ And the following should exist in the "user-grades" table:
+ | -1- | -13- |
+ | Student 2 | 50.00 |
+ And I turn editing mode off
+ And the following should exist in the "user-grades" table:
+ | -1- | -4- | -5- | -13- |
+ | Student 2 | 30.00 | 20.00 | 50.00 |
+ And I click on student "Student 2" for grade item "Item 1"
+ And the field "ajaxfeedback" matches value "Some feedback"
--- /dev/null
+<?php
+// This file is part of Stack - http://stack.bham.ac.uk/
+//
+// Stack is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Stack is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Stack. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Behat steps definitions for drag and drop onto image.
+ *
+ * @package gradereport_grader
+ * @category test
+ * @copyright 2015 Oakland University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+
+use Behat\Behat\Context\Step\Given,
+ Behat\Behat\Context\Step\Then,
+ Behat\Mink\Exception\ExpectationException as ExpectationException,
+ Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
+
+/**
+ * Steps definitions related with the drag and drop onto image question type.
+ *
+ * @copyright 2015 Oakland University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_gradereport_grader extends behat_base {
+ /**
+ * Click a given user grade cell.
+ *
+ * @Given /^I click on student "([^"]*)" for grade item "([^"]*)"$/
+ * @param string $student
+ * @param string $itemname
+ * @return Given
+ */
+ public function i_click_on_student_and_grade_item($student, $itemname) {
+ $xpath = $this->get_student_and_grade_cell_selector($student, $itemname);
+
+ return new Given('I click on "' . $this->escape($xpath) . '" "xpath_element"');
+ }
+
+ /**
+ * Remove focus for a grade value cell.
+ *
+ * @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" value$/
+ * @param string $student
+ * @param string $itemname
+ * @return Given
+ */
+ public function i_click_away_from_student_and_grade_value($student, $itemname) {
+ $xpath = $this->get_student_and_grade_value_selector($student, $itemname);
+
+ return new Given('I take focus off "' . $this->escape($xpath) . '" "xpath_element"');
+ }
+
+ /**
+ * Remove focus for a grade value cell.
+ *
+ * @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" feedback$/
+ * @param string $student
+ * @param string $itemname
+ * @return Given
+ */
+ public function i_click_away_from_student_and_grade_feedback($student, $itemname) {
+ $xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
+
+ return new Given('I take focus off "' . $this->escape($xpath) . '" "xpath_element"');
+ }
+
+ /**
+ * Checks grade values with or without a edit box.
+ *
+ * @Then /^the grade for "([^"]*)" in grade item "([^"]*)" should match "([^"]*)"$/
+ * @throws Exception
+ * @throws ElementNotFoundException
+ * @param string $student
+ * @param string $itemname
+ * @param string $value
+ * @return Then
+ */
+ public function the_grade_should_match($student, $itemname, $value) {
+ $xpath = $this->get_student_and_grade_value_selector($student, $itemname);
+
+ $gradefield = $this->getSession()->getPage()->find('xpath', $xpath);
+ if (!empty($gradefield)) {
+ // Get the field.
+ $fieldtype = behat_field_manager::guess_field_type($gradefield, $this->getSession());
+ if (!$fieldtype) {
+ throw new Exception('Could not get field type for grade field "' . $itemname . '"');
+ }
+ $field = behat_field_manager::get_field_instance($fieldtype, $gradefield, $this->getSession());
+ if (!$field->matches($value)) {
+ $fieldvalue = $field->get_value();
+ throw new ExpectationException(
+ 'The "' . $student . '" and "' . $itemname . '" grade is "' . $fieldvalue . '", "' . $value . '" expected' ,
+ $this->getSession()
+ );
+ }
+ } else {
+ // If there isn't a form field, just search for contents.
+ $valueliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($value);
+
+ $xpath = $this->get_student_and_grade_cell_selector($student, $itemname);
+ $xpath .= "[contains(normalize-space(.)," . $valueliteral . ")]";
+
+ $node = $this->getSession()->getDriver()->find($xpath);
+ if (empty($node)) {
+ $locatorexceptionmsg = 'Cell for "' . $student . '" and "' . $itemname . '" with value "' . $value . '"';
+ throw new ElementNotFoundException($this->getSession(), $locatorexceptionmsg, null, $xpath);
+ }
+ }
+ }
+
+ /**
+ * Look for a grade editing field.
+ *
+ * @Then /^I should see a grade field for "([^"]*)" and grade item "([^"]*)"$/
+ * @param string $student
+ * @param string $itemname
+ * @return Then
+ */
+ public function i_should_see_grade_field($student, $itemname) {
+ $xpath = $this->get_student_and_grade_value_selector($student, $itemname);
+
+ return new Then('"' . $this->escape($xpath) . '" "xpath_element" should be visible');
+ }
+
+ /**
+ * Look for a feedback editing field.
+ *
+ * @Then /^I should see a feedback field for "([^"]*)" and grade item "([^"]*)"$/
+ * @param string $student
+ * @param string $itemname
+ * @return Then
+ */
+ public function i_should_see_feedback_field($student, $itemname) {
+ $xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
+
+ return new Then('"' . $this->escape($xpath) . '" "xpath_element" should be visible');
+ }
+
+ /**
+ * Look for a lack of the grade editing field.
+ *
+ * @Then /^I should not see a grade field for "([^"]*)" and grade item "([^"]*)"$/
+ * @param string $student
+ * @param string $itemname
+ * @return Then
+ */
+ public function i_should_not_see_grade_field($student, $itemname) {
+ $xpath = $this->get_student_and_grade_value_selector($student, $itemname);
+
+ return new Then('"' . $this->escape($xpath) . '" "xpath_element" should not exist');
+ }
+
+ /**
+ * Look for a lack of the feedback editing field.
+ *
+ * @Then /^I should not see a feedback field for "([^"]*)" and grade item "([^"]*)"$/
+ * @param string $student
+ * @param string $itemname
+ * @return Then
+ */
+ public function i_should_not_see_feedback_field($student, $itemname) {
+ $xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
+
+ return new Then('"' . $this->escape($xpath) . '" "xpath_element" should not exist');
+ }
+
+ /**
+ * Gets the user id from its name.
+ *
+ * @throws Exception
+ * @param string $name
+ * @return int
+ */
+ protected function get_user_id($name) {
+ global $DB;
+ $names = explode(' ', $name);
+
+ if (!$id = $DB->get_field('user', 'id', array('firstname' => $names[0], 'lastname' => $names[1]))) {
+ throw new Exception('The specified user with username "' . $name . '" does not exist');
+ }
+ return $id;
+ }
+
+ /**
+ * Gets the grade item id from its name.
+ *
+ * @throws Exception
+ * @param string $itemname
+ * @return int
+ */
+ protected function get_grade_item_id($itemname) {
+ global $DB;
+
+ if ($id = $DB->get_field('grade_items', 'id', array('itemname' => $itemname))) {
+ return $id;
+ }
+
+ // The course total is a special case.
+ if ($itemname === "Course total") {
+ if (!$id = $DB->get_field('grade_items', 'id', array('itemtype' => 'course'))) {
+ throw new Exception('The specified grade_item with name "' . $itemname . '" does not exist');
+ }
+ return $id;
+ }
+
+ // Find a category with the name.
+ if ($catid = $DB->get_field('grade_categories', 'id', array('fullname' => $itemname))) {
+ if ($id = $DB->get_field('grade_items', 'id', array('iteminstance' => $catid))) {
+ return $id;
+ }
+ }
+
+ throw new Exception('The specified grade_item with name "' . $itemname . '" does not exist');
+ }
+
+ /**
+ * Gets unique xpath selector for a student/grade item combo.
+ *
+ * @throws Exception
+ * @param string $student
+ * @param string $itemname
+ * @return string
+ */
+ protected function get_student_and_grade_cell_selector($student, $itemname) {
+ $itemid = 'u' . $this->get_user_id($student) . 'i' . $this->get_grade_item_id($itemname);
+ return "//table[@id='user-grades']//td[@id='" . $itemid . "']";
+ }
+
+ /**
+ * Gets xpath for a particular student/grade item grade value cell.
+ *
+ * @throws Exception
+ * @param string $student
+ * @param string $itemname
+ * @return string
+ */
+ protected function get_student_and_grade_value_selector($student, $itemname) {
+ $cell = $this->get_student_and_grade_cell_selector($student, $itemname);
+ return $cell . "//*[contains(@id, 'grade_') or @name='ajaxgrade']";
+ }
+
+ /**
+ * Gets xpath for a particular student/grade item feedback cell.
+ *
+ * @throws Exception
+ * @param string $student
+ * @param string $itemname
+ * @return string
+ */
+ protected function get_student_and_grade_feedback_selector($student, $itemname) {
+ $cell = $this->get_student_and_grade_cell_selector($student, $itemname);
+ return $cell . "//input[contains(@id, 'feedback_') or @name='ajaxfeedback']";
+ }
+
+
+}
return new Given('I set the field "' . $this->escape($fieldstr) . '" to "' . $grade . '"');
}
+ /**
+ * Enters a quick feedback via the gradebook for a specific grade item and user when viewing
+ * the 'Grader report' with editing mode turned on.
+ *
+ * @Given /^I give the feedback "(?P<grade_number>(?:[^"]|\\")*)" to the user "(?P<username_string>(?:[^"]|\\")*)" for the grade item "(?P<grade_activity_string>(?:[^"]|\\")*)"$/
+ * @param string $feedback
+ * @param string $userfullname the user's fullname as returned by fullname()
+ * @param string $itemname
+ * @return Given
+ */
+ public function i_give_the_feedback($feedback, $userfullname, $itemname) {
+ $gradelabel = $userfullname . ' ' . $itemname;
+ $fieldstr = get_string('useractivityfeedback', 'gradereport_grader', $gradelabel);
+
+ return new Given('I set the field "' . $this->escape($fieldstr) . '" to "' . $this->escape($feedback) . '"');
+ }
+
/**
* Changes the settings of a grade item or category or the course.
*