From 67f2669510bf92d920fe3c0d3401d42a5f2e6981 Mon Sep 17 00:00:00 2001 From: Ilya Tregubov Date: Wed, 7 Dec 2022 14:15:21 +0300 Subject: [PATCH] MDL-76146 gradereport_grader: Deprecate AJAX grading interface. Its not stable and not consistent. --- grade/report/grader/ajax_callbacks.php | 140 --- .../grader/classes/privacy/provider.php | 5 - grade/report/grader/lang/en/deprecated.txt | 6 + .../grader/lang/en/gradereport_grader.php | 12 +- grade/report/grader/lib.php | 66 +- grade/report/grader/module.js | 1021 ----------------- grade/report/grader/preferences_form.php | 1 - grade/report/grader/settings.php | 3 - .../grader/tests/behat/ajax_grader.feature | 209 ---- .../tests/behat/behat_gradereport_grader.php | 121 +- .../behat_gradereport_grader_deprecated.php | 148 +++ lang/en/deprecated.txt | 2 + lang/en/grades.php | 4 +- lib/upgrade.txt | 1 + 14 files changed, 169 insertions(+), 1570 deletions(-) delete mode 100644 grade/report/grader/ajax_callbacks.php delete mode 100644 grade/report/grader/module.js delete mode 100644 grade/report/grader/tests/behat/ajax_grader.feature diff --git a/grade/report/grader/ajax_callbacks.php b/grade/report/grader/ajax_callbacks.php deleted file mode 100644 index 702580864cd..00000000000 --- a/grade/report/grader/ajax_callbacks.php +++ /dev/null @@ -1,140 +0,0 @@ -. - -/** - * This file receives ajax callbacks for the grader report - * - * @package gradereport_grader - * @copyright 2008 Nicolas Connault - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once '../../../config.php'; -require_once $CFG->libdir.'/gradelib.php'; -require_once $CFG->dirroot.'/grade/lib.php'; -// require_once $CFG->dirroot.'/grade/report/grader/ajaxlib.php'; -// require_once $CFG->dirroot.'/grade/report/grader/lib.php'; - -$courseid = required_param('id', PARAM_INT); // course id -$userid = optional_param('userid', false, PARAM_INT); -$itemid = optional_param('itemid', false, PARAM_INT); -$type = optional_param('type', false, PARAM_ALPHA); -$action = optional_param('action', false, PARAM_ALPHA); -$newvalue = optional_param('newvalue', false, PARAM_TEXT); - -/// basic access checks -if (!$course = $DB->get_record('course', array('id' => $courseid))) { - throw new \moodle_exception('invalidcourseid'); -} -$context = context_course::instance($course->id); -require_login($course); - -switch ($action) { - case 'update': - if (!confirm_sesskey()) { - break; - } - require_capability('moodle/grade:edit', $context); - - if (!empty($userid) && !empty($itemid) && $newvalue !== false && !empty($type)) { - // Save the grade - if (!$grade_item = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$courseid))) { // we must verify course id here! - throw new \moodle_exception('invalidgradeitemid'); - } - - /** - * Code copied from grade/report/grader/lib.php line 187+ - */ - $warnings = array(); - $finalvalue = null; - $finalgrade = null; - $json_object = new stdClass(); - // Pre-process grade - if ($type == 'value' || $type == 'scale') { - if ($grade_item->gradetype == GRADE_TYPE_SCALE) { - if ($newvalue == -1) { // -1 means no grade - $finalgrade = null; - } else { - $finalgrade = $newvalue; - } - } else { - $finalgrade = unformat_float($newvalue); - } - - $errorstr = ''; - // Warn if the grade is out of bounds. - if (is_null($finalgrade)) { - // ok - } else { - $bounded = $grade_item->bounded_grade($finalgrade); - if ($bounded > $finalgrade) { - $errorstr = 'lessthanmin'; - } else if ($bounded < $finalgrade) { - $errorstr = 'morethanmax'; - } - } - - if ($errorstr) { - $userfieldsapi = \core_user\fields::for_name(); - $user = $DB->get_record('user', array('id' => $userid), 'id' . $userfieldsapi->get_sql()->selects); - $gradestr = new stdClass(); - $gradestr->username = fullname($user); - $gradestr->itemname = $grade_item->get_name(); - $json_object->message = get_string($errorstr, 'grades', $gradestr); - $json_object->result = "error"; - - } - - $finalvalue = $finalgrade; - } - - if (!empty($json_object->result) && $json_object->result == 'error') { - echo json_encode($json_object); - die(); - } else { - $json_object->gradevalue = $finalvalue; - - if ($grade_item->update_final_grade($userid, $finalgrade, 'gradebook', false, FORMAT_MOODLE)) { - $json_object->result = 'success'; - $json_object->message = false; - } else { - $json_object->result = 'error'; - $json_object->message = "TO BE LOCALISED: Failure to update final grade!"; - echo json_encode($json_object); - die(); - } - - // Get row data - $sql = "SELECT gg.id, gi.id AS itemid, gi.scaleid AS scale, gg.userid AS userid, finalgrade, gg.overridden AS overridden " - . "FROM {grade_grades} gg, {grade_items} gi WHERE " - . "gi.courseid = ? AND gg.itemid = gi.id AND gg.userid = ?"; - $records = $DB->get_records_sql($sql, array($courseid, $userid)); - $json_object->row = $records; - echo json_encode($json_object); - die(); - } - } else { - $json_object = new stdClass(); - $json_object->result = "error"; - $json_object->message = "Missing parameter to ajax UPDATE callback: \n" . - " userid: $userid,\n itemid: $itemid\n, type: $type\n, newvalue: $newvalue"; - echo json_encode($json_object); - } - - break; -} - - diff --git a/grade/report/grader/classes/privacy/provider.php b/grade/report/grader/classes/privacy/provider.php index 2d4b3883060..431a71761c0 100644 --- a/grade/report/grader/classes/privacy/provider.php +++ b/grade/report/grader/classes/privacy/provider.php @@ -72,7 +72,6 @@ class provider implements $items->add_user_preference('grade_report_studentsperpage', 'privacy:metadata:preference:grade_report_studentsperpage'); $items->add_user_preference('grade_report_showonlyactiveenrol', 'privacy:metadata:preference:grade_report_showonlyactiveenrol'); $items->add_user_preference('grade_report_aggregationposition', 'privacy:metadata:preference:grade_report_aggregationposition'); - $items->add_user_preference('grade_report_enableajax', 'privacy:metadata:preference:grade_report_enableajax'); // There is also one user preference which can be defined on each course. $items->add_user_preference('grade_report_grader_collapsed_categories', 'privacy:metadata:preference:grade_report_grader_collapsed_categories'); @@ -103,10 +102,6 @@ class provider implements case 'grade_report_shownumberofgrades': case 'grade_report_quickgrading': case 'grade_report_showonlyactiveenrol': - case 'grade_report_enableajax': - $prefname = $name; - $transformedvalue = transform::yesno($value); - break; case 'grade_report_meanselection': $prefname = $name; switch ($value) { diff --git a/grade/report/grader/lang/en/deprecated.txt b/grade/report/grader/lang/en/deprecated.txt index 8d30ad7eadd..278741afbc3 100644 --- a/grade/report/grader/lang/en/deprecated.txt +++ b/grade/report/grader/lang/en/deprecated.txt @@ -1,2 +1,8 @@ privacy:metadata:preference:grade_report_showquickfeedback,gradereport_grader useractivityfeedback,gradereport_grader +privacy:metadata:preference:grade_report_enableajax,gradereport_grader +ajaxclicktoclose,gradereport_grader +ajaxerror,gradereport_grader +ajaxfailedupdate,gradereport_grader +ajaxfieldchanged,gradereport_grader +ajaxchoosescale,gradereport_grader diff --git a/grade/report/grader/lang/en/gradereport_grader.php b/grade/report/grader/lang/en/gradereport_grader.php index 5b5bcacaa78..84d4e467f8f 100644 --- a/grade/report/grader/lang/en/gradereport_grader.php +++ b/grade/report/grader/lang/en/gradereport_grader.php @@ -23,11 +23,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['ajaxclicktoclose'] = 'Click this box to remove it'; -$string['ajaxerror'] = 'Error'; -$string['ajaxfailedupdate'] = 'Unable to update [1] for [2]'; -$string['ajaxfieldchanged'] = 'The field you are currently editing has changed, would you like to use the updated value?'; -$string['ajaxchoosescale'] = 'Choose'; $string['eventgradereportviewed'] = 'Grader report viewed'; $string['grader:manage'] = 'Manage the grader report'; $string['grader:view'] = 'View grader report'; @@ -36,7 +31,6 @@ $string['preferences'] = 'Grader report preferences'; $string['privacy:metadata:preference:grade_report_aggregationposition'] = 'Whether the category and course total columns are displayed first or last in the gradebook reports'; $string['privacy:metadata:preference:grade_report_averagesdecimalpoints'] = 'The number of decimal places to display for each average or whether the overall decimal places setting for the category or grade item is used (inherit).'; $string['privacy:metadata:preference:grade_report_averagesdisplaytype'] = 'Whether the average (mean) is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit).'; -$string['privacy:metadata:preference:grade_report_enableajax'] = 'Whether to add a layer of AJAX functionality to the grader report, simplifying and speeding up common operations'; $string['privacy:metadata:preference:grade_report_grader_collapsed_categories'] = 'List of gradebook categories to be collapsed'; $string['privacy:metadata:preference:grade_report_meanselection'] = 'Whether cells with no grade should be included when calculating the average (mean) for each category or grade item'; $string['privacy:metadata:preference:grade_report_quickgrading'] = 'Whether to display a text input box for each grade, allowing many grades to be edited at the same time'; @@ -60,4 +54,10 @@ $string['overriddengrade'] = 'Overridden grade'; // Deprecated since Moodle 4.2. $string['privacy:metadata:preference:grade_report_showquickfeedback'] = 'Whether to display a feedback text input box with a dotted border for each grade, allowing the feedback for many grades to be edited at the same time'; +$string['privacy:metadata:preference:grade_report_enableajax'] = 'Whether to add a layer of AJAX functionality to the grader report, simplifying and speeding up common operations'; $string['useractivityfeedback'] = '{$a} feedback'; +$string['ajaxclicktoclose'] = 'Click this box to remove it'; +$string['ajaxerror'] = 'Error'; +$string['ajaxfailedupdate'] = 'Unable to update [1] for [2]'; +$string['ajaxfieldchanged'] = 'The field you are currently editing has changed, would you like to use the updated value?'; +$string['ajaxchoosescale'] = 'Choose'; diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php index 174f8a0398c..7b3d4298e88 100644 --- a/grade/report/grader/lib.php +++ b/grade/report/grader/lib.php @@ -772,18 +772,9 @@ class grade_report_grader extends grade_report { $strgrade = $this->get_lang_string('gradenoun'); $arrows = $this->get_sort_arrows(); - $jsarguments = array( - 'cfg' => array('ajaxenabled'=>false), - 'items' => array(), - 'users' => array(), - 'grades' => array() - ); - $jsscales = array(); - // Get preferences once. $showactivityicons = $this->get_pref('showactivityicons'); $quickgrading = $this->get_pref('quickgrading'); - $enableajax = $this->get_pref('enableajax'); $showanalysisicon = $this->get_pref('showanalysisicon'); // Get strings which are re-used inside the loop. @@ -912,9 +903,6 @@ class grade_report_grader extends grade_report { $scale = null; if (!empty($item->scaleid)) { $scaleslist[] = $item->scaleid; - $jsarguments['items'][$itemid] = array('id'=>$itemid, 'name'=>$item->get_name(true), 'type'=>'scale', 'scale'=>$item->scaleid, 'decimals'=>$item->get_decimals()); - } else { - $jsarguments['items'][$itemid] = array('id'=>$itemid, 'name'=>$item->get_name(true), 'type'=>'value', 'scale'=>false, 'decimals'=>$item->get_decimals()); } $tabindices[$item->id]['grade'] = $gradetabindex; $gradetabindex += $numusers * 2; @@ -924,7 +912,6 @@ class grade_report_grader extends grade_report { if (!empty($scaleslist)) { $scalesarray = $DB->get_records_list('scale', 'id', $scaleslist); } - $jsscales = $scalesarray; // Get all the grade items if the user can not view hidden grade items. // It is possible that the user is simply viewing the 'Course total' by switching to the 'Aggregates only' view @@ -952,7 +939,6 @@ class grade_report_grader extends grade_report { $itemrow->id = 'user_'.$userid; $fullname = fullname($user, $viewfullnames); - $jsarguments['users'][$userid] = $fullname; foreach ($this->gtree->items as $itemid => $unused) { $item =& $this->gtree->items[$itemid]; @@ -973,15 +959,6 @@ class grade_report_grader extends grade_report { } else { $gradeval = $grade->finalgrade; } - if (!empty($grade->finalgrade)) { - $gradevalforjs = null; - if ($item->scaleid && !empty($scalesarray[$item->scaleid])) { - $gradevalforjs = (int)$gradeval; - } else { - $gradevalforjs = format_float($gradeval, $decimalpoints); - } - $jsarguments['grades'][] = array('user'=>$userid, 'item'=>$itemid, 'grade'=>$gradevalforjs); - } // MDL-11274 // Hide grades in the grader report if the current grader doesn't have 'moodle/grade:viewhidden' @@ -1123,14 +1100,6 @@ class grade_report_grader extends grade_report { $itemcell->attributes['class'] .= ' grade_type_text'; } - // 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, it can't be edited. - if ($item->gradetype != GRADE_TYPE_TEXT) { - $itemcell->attributes['class'] .= ' clickable'; - } - } - if ($item->needsupdate) { $itemcell->text .= $gradepassicon . "" . $error . ""; } else { @@ -1148,14 +1117,6 @@ class grade_report_grader extends grade_report { } } - // Enable keyboard navigation 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, it can't be edited. - if ($item->gradetype != GRADE_TYPE_TEXT) { - $itemcell->attributes['class'] .= ' gbnavigable'; - } - } - if (!empty($this->gradeserror[$item->id][$userid])) { $itemcell->text .= $this->gradeserror[$item->id][$userid]; } @@ -1165,30 +1126,9 @@ class grade_report_grader extends grade_report { $rows[] = $itemrow; } - if ($enableajax) { - $jsarguments['cfg']['ajaxenabled'] = true; - $jsarguments['cfg']['scales'] = array(); - foreach ($jsscales as $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)); - } - - // Student grades are already at $jsarguments['grades'] - } - $jsarguments['cfg']['isediting'] = !empty($USER->editing); - $jsarguments['cfg']['courseid'] = $this->courseid; - $jsarguments['cfg']['studentsperpage'] = $this->get_students_per_page(); - - $module = array( - 'name' => 'gradereport_grader', - 'fullpath' => '/grade/report/grader/module.js', - 'requires' => array('base', 'dom', 'event', 'event-mouseenter', 'event-key', 'io-queue', 'json-parse', 'overlay') - ); - $PAGE->requires->js_init_call('M.gradereport_grader.init_report', $jsarguments, false, $module); - $PAGE->requires->strings_for_js(array('grade'), 'grades'); - $PAGE->requires->strings_for_js(array('ajaxchoosescale', 'ajaxclicktoclose', 'ajaxerror', 'ajaxfailedupdate', 'ajaxfieldchanged'), 'gradereport_grader'); - if (!$enableajax && !empty($USER->editing)) { - $PAGE->requires->js_call_amd('core_form/changechecker', 'watchFormById', ['gradereport_grader']); + if (!empty($USER->editing)) { + $PAGE->requires->js_call_amd('core_form/changechecker', + 'watchFormById', ['gradereport_grader']); } $rows = $this->get_right_range_row($rows); diff --git a/grade/report/grader/module.js b/grade/report/grader/module.js deleted file mode 100644 index 03fd42e88bf..00000000000 --- a/grade/report/grader/module.js +++ /dev/null @@ -1,1021 +0,0 @@ -/** - * Grader report namespace - */ -M.gradereport_grader = { - /** - * @namespace M.gradereport_grader - * @param {Object} reports A collection of classes used by the grader report module - */ - classes : {}, - /** - * Instantiates a new grader report - * - * @function - * @param {YUI} Y - * @param {Object} cfg A configuration object - * @param {Array} An array of items in the report - * @param {Array} An array of users on the report - * @param {Array} An array of student grades - */ - init_report : function(Y, cfg, items, users, grades) { - // Create the actual report - new this.classes.report(Y, cfg, items, users, grades); - } -}; - -/** - * Initialises the JavaScript for the gradebook grader report - * - * The functions fall into 3 groups: - * M.gradereport_grader.classes.ajax Used when editing is off and fields are dynamically added and removed - * M.gradereport_grader.classes.existingfield Used when editing is on meaning all fields are already displayed - * M.gradereport_grader.classes.report Common to both of the above - * - * @class report - * @constructor - * @this {M.gradereport_grader} - * @param {YUI} Y - * @param {Object} cfg Configuration variables - * @param {Array} items An array containing grade items - * @param {Array} users An array containing user information - */ -M.gradereport_grader.classes.report = function(Y, cfg, items, users, grades) { - this.Y = Y; - this.isediting = (cfg.isediting); - this.ajaxenabled = (cfg.ajaxenabled); - this.items = items; - this.users = users; - this.table = Y.one('#user-grades'); - this.grades = grades; - - // If ajax is enabled then initialise the ajax component - if (this.ajaxenabled) { - this.ajax = new M.gradereport_grader.classes.ajax(this, cfg); - } -}; -/** - * Extend the report class with the following methods and properties - */ -M.gradereport_grader.classes.report.prototype.table = null; // YUI Node for the reports main table -M.gradereport_grader.classes.report.prototype.items = []; // Array containing grade items -M.gradereport_grader.classes.report.prototype.users = []; // Array containing user information -M.gradereport_grader.classes.report.prototype.ajaxenabled = false; // True is AJAX is enabled for the report -M.gradereport_grader.classes.report.prototype.ajax = null; // An instance of the ajax class or null -/** - * Builds an object containing information at the relevant cell given either - * the cell to get information for or an array containing userid and itemid - * - * @function - * @this {M.gradereport_grader} - * @param {Y.Node|Array} arg Either a YUI Node instance or an array containing - * the userid and itemid to reference - * @return {Object} - */ -M.gradereport_grader.classes.report.prototype.get_cell_info = function(arg) { - - var userid= null; - var itemid = null; - var cell = null; - var i = null; - - if (arg instanceof this.Y.Node) { - if (arg.get('nodeName').toUpperCase() !== 'TD') { - arg = arg.ancestor('td.cell'); - } - var regexp = /^u(\d+)i(\d+)$/; - var parts = regexp.exec(arg.getAttribute('id')); - userid = parts[1]; - itemid = parts[2]; - cell = arg; - } else { - userid = arg[0]; - itemid = arg[1]; - cell = this.Y.one('#u'+userid+'i'+itemid); - } - - if (!cell) { - return null; - } - - return { - id : cell.getAttribute('id'), - userid : userid, - username : this.users[userid], - itemid : itemid, - itemname : this.items[itemid].name, - itemtype : this.items[itemid].type, - itemscale : this.items[itemid].scale, - itemdp : this.items[itemid].decimals, - cell : cell - }; -}; -/** - * Initialises the AJAX component of this report - * @class ajax - * @constructor - * @this {M.gradereport_grader.ajax} - * @param {M.gradereport_grader.classes.report} report - * @param {Object} cfg - */ -M.gradereport_grader.classes.ajax = function(report, cfg) { - this.report = report; - this.courseid = cfg.courseid || null; - this.studentsperpage = cfg.studentsperpage || null; - this.scales = cfg.scales || null; - this.existingfields = []; - - if (!report.isediting) { - report.table.all('.clickable').on('click', this.make_editable, this); - } else { - for (var userid in report.users) { - if (!this.existingfields[userid]) { - this.existingfields[userid] = []; - } - for (var itemid in report.items) { - this.existingfields[userid][itemid] = new M.gradereport_grader.classes.existingfield(this, userid, itemid); - } - } - // Disable the Update button as we're saving using ajax. - submitbutton = this.report.Y.one('#gradersubmit'); - submitbutton.set('disabled', true); - } -}; -/** - * Extend the ajax class with the following methods and properties - */ -M.gradereport_grader.classes.ajax.prototype.report = null; // A reference to the report class this object will use -M.gradereport_grader.classes.ajax.prototype.courseid = null; // The id for the course being viewed -M.gradereport_grader.classes.ajax.prototype.studentsperpage = null; // The number of students shown per page -M.gradereport_grader.classes.ajax.prototype.current = null; // The field being currently editing -M.gradereport_grader.classes.ajax.prototype.pendingsubmissions = []; // Array containing pending IO transactions -M.gradereport_grader.classes.ajax.prototype.scales = []; // An array of scales used in this report -/** - * Makes a cell editable - * @function - * @this {M.gradereport_grader.classes.ajax} - */ -M.gradereport_grader.classes.ajax.prototype.make_editable = function(e) { - var node = e; - if (e.halt) { - e.halt(); - node = e.target; - } - if (node.get('nodeName').toUpperCase() !== 'TD') { - node = node.ancestor('td'); - } - this.report.Y.detach('click', this.make_editable, node); - - if (this.current) { - // Current is already set! - this.process_editable_field(node); - return; - } - - // Sort out the field type - 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) { - case 'scale': - this.current = new M.gradereport_grader.classes.scalefield(this.report, node); - break; - default: - this.current = new M.gradereport_grader.classes.textfield(this.report, node); - break; - } - this.current.replace().attach_key_events(); - - // Fire the global resized event for the gradereport_grader to update the table row/column sizes. - Y.Global.fire('moodle-gradereport_grader:resized'); -}; -/** - * Callback function for the user pressing the enter key on an editable field - * - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Event} e - */ -M.gradereport_grader.classes.ajax.prototype.keypress_enter = function(e) { - this.process_editable_field(null); -}; -/** - * Callback function for the user pressing Tab or Shift+Tab - * - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Event} e - * @param {Bool} ignoreshift If true and shift is pressed then don't exec - */ -M.gradereport_grader.classes.ajax.prototype.keypress_tab = function(e, ignoreshift) { - e.preventDefault(); - var next = null; - if (e.shiftKey) { - if (ignoreshift) { - return; - } - next = this.get_above_cell(); - } else { - next = this.get_below_cell(); - } - this.process_editable_field(next); -}; -/** - * Callback function for the user pressing an CTRL + an arrow key - * - * @function - * @this {M.gradereport_grader.classes.ajax} - */ -M.gradereport_grader.classes.ajax.prototype.keypress_arrows = function(e) { - e.preventDefault(); - var next = null; - switch (e.keyCode) { - case 37: // Left - next = this.get_prev_cell(); - break; - case 38: // Up - next = this.get_above_cell(); - break; - case 39: // Right - next = this.get_next_cell(); - break; - case 40: // Down - next = this.get_below_cell(); - break; - } - this.process_editable_field(next); -}; -/** - * Processes an editable field an does what ever is required to update it - * - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Y.Node|null} next The next node to make editable (chaining) - */ -M.gradereport_grader.classes.ajax.prototype.process_editable_field = function(next) { - if (this.current.has_changed()) { - var properties = this.report.get_cell_info(this.current.node); - var values = this.current.commit(); - this.current.revert(); - this.submit(properties, values); - } else { - this.current.revert(); - } - this.current = null; - if (next) { - this.make_editable(next, null); - } - - // Fire the global resized event for the gradereport_grader to update the table row/column sizes. - Y.Global.fire('moodle-gradereport_grader:resized'); -}; -/** - * Gets the next cell that is editable (right) - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Y.Node} cell - * @return {Y.Node} - */ -M.gradereport_grader.classes.ajax.prototype.get_next_cell = function(cell) { - var n = cell || this.current.node; - var next = n.next('td'); - var tr = null; - if (!next && (tr = n.ancestor('tr').next('tr'))) { - next = tr.all('.grade').item(0); - } - if (!next) { - return this.current.node; - } - // Continue on until we find a navigable cell - if (!next.hasClass('gbnavigable')) { - return this.get_next_cell(next); - } - return next; -}; -/** - * Gets the previous cell that is editable (left) - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Y.Node} cell - * @return {Y.Node} - */ -M.gradereport_grader.classes.ajax.prototype.get_prev_cell = function(cell) { - var n = cell || this.current.node; - var next = n.previous('.grade'); - var tr = null; - if (!next && (tr = n.ancestor('tr').previous('tr'))) { - var cells = tr.all('.grade'); - next = cells.item(cells.size()-1); - } - if (!next) { - return this.current.node; - } - // Continue on until we find a navigable cell - if (!next.hasClass('gbnavigable')) { - return this.get_prev_cell(next); - } - return next; -}; -/** - * Gets the cell above if it is editable (up) - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Y.Node} cell - * @return {Y.Node} - */ -M.gradereport_grader.classes.ajax.prototype.get_above_cell = function(cell) { - var n = cell || this.current.node; - var tr = n.ancestor('tr').previous('tr'); - var next = null; - if (tr) { - var column = 0; - var ntemp = n; - while (ntemp = ntemp.previous('td.cell')) { - column++; - } - next = tr.all('td.cell').item(column); - } - if (!next) { - return this.current.node; - } - // Continue on until we find a navigable cell - if (!next.hasClass('gbnavigable')) { - return this.get_above_cell(next); - } - return next; -}; -/** - * Gets the cell below if it is editable (down) - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Y.Node} cell - * @return {Y.Node} - */ -M.gradereport_grader.classes.ajax.prototype.get_below_cell = function(cell) { - var n = cell || this.current.node; - var tr = n.ancestor('tr').next('tr'); - var next = null; - if (tr && !tr.hasClass('avg')) { - var column = 0; - var ntemp = n; - while (ntemp = ntemp.previous('td.cell')) { - column++; - } - next = tr.all('td.cell').item(column); - } - if (!next) { - return this.current.node; - } - // Continue on until we find a navigable cell - if (!next.hasClass('gbnavigable')) { - return this.get_below_cell(next); - } - return next; -}; -/** - * Submits changes for update - * - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Object} properties Properties of the cell being edited - * @param {Object} values Object containing old + new values - */ -M.gradereport_grader.classes.ajax.prototype.submit = function(properties, values) { - // Stop the IO queue so we can add to it - this.report.Y.io.queue.stop(); - // If the grade has changed add an IO transaction to update it to the queue - if (values.grade !== values.oldgrade) { - this.pendingsubmissions.push({transaction:this.report.Y.io.queue(M.cfg.wwwroot+'/grade/report/grader/ajax_callbacks.php', { - method : 'POST', - data : 'id='+this.courseid+'&userid='+properties.userid+'&itemid='+properties.itemid+'&action=update&newvalue='+values.grade+'&type='+properties.itemtype+'&sesskey='+M.cfg.sesskey, - on : { - complete : this.submission_outcome - }, - context : this, - arguments : { - properties : properties, - values : values, - type : 'grade' - } - }),complete:false,outcome:null}); - } - // Process the IO queue - this.report.Y.io.queue.start(); -}; -/** - * Callback function for IO transaction completions - * - * Uses a synchronous queue to ensure we maintain some sort of order - * - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {Int} tid Transaction ID - * @param {Object} outcome - * @param {Mixed} args - */ -M.gradereport_grader.classes.ajax.prototype.submission_outcome = function(tid, outcome, args) { - // Parse the response as JSON - try { - outcome = this.report.Y.JSON.parse(outcome.responseText); - } catch(e) { - var message = M.util.get_string('ajaxfailedupdate', 'gradereport_grader'); - message = message.replace(/\[1\]/, args.type); - message = message.replace(/\[2\]/, this.report.users[args.properties.userid]); - - this.display_submission_error(message, args.properties.cell); - return; - } - - // Quick reference for the grader report - var i = null; - // Check the outcome - if (outcome.result == 'success') { - // Iterate through each row in the result object - for (i in outcome.row) { - if (outcome.row[i] && outcome.row[i].userid && outcome.row[i].itemid) { - // alias it, we use it quite a bit - var r = outcome.row[i]; - // Get the cell referred to by this result object - var info = this.report.get_cell_info([r.userid, r.itemid]); - if (!info) { - continue; - } - // 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 - finalgrade = ''; - } else { - // In non-edit mode put a hyphen in the grade cell - finalgrade = '-'; - } - } else { - if (r.scale) { - scalegrade = parseFloat(r.finalgrade); - finalgrade = this.scales[r.scale][scalegrade-1]; - } else { - finalgrade = parseFloat(r.finalgrade).toFixed(info.itemdp); - } - } - if (this.report.isediting) { - 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 - 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); - if (this.current.grade) { - this.current.grade.set('value', finalgrade); - } - } - } - } - } - } - // Flag the changed cell as overridden by ajax - args.properties.cell.addClass('ajaxoverridden'); - } else { - var p = args.properties; - if (args.type == 'grade') { - var oldgrade = args.values.oldgrade; - p.cell.one('input.text').set('value', oldgrade); - } - this.display_submission_error(outcome.message, p.cell); - } - // Check if all IO transactions in the queue are complete yet - var allcomplete = true; - for (i in this.pendingsubmissions) { - if (this.pendingsubmissions[i]) { - if (this.pendingsubmissions[i].transaction.id == tid) { - this.pendingsubmissions[i].complete = true; - this.pendingsubmissions[i].outcome = outcome; - this.report.Y.io.queue.remove(this.pendingsubmissions[i].transaction); - } - if (!this.pendingsubmissions[i].complete) { - allcomplete = false; - } - } - } - if (allcomplete) { - this.pendingsubmissions = []; - } -}; -/** - * Displays a submission error within a overlay on the cell that failed update - * - * @function - * @this {M.gradereport_grader.classes.ajax} - * @param {String} message - * @param {Y.Node} cell - */ -M.gradereport_grader.classes.ajax.prototype.display_submission_error = function(message, cell) { - var erroroverlay = new this.report.Y.Overlay({ - headerContent : '
'+M.util.get_string('ajaxerror', 'gradereport_grader')+' '+M.util.get_string('ajaxclicktoclose', 'gradereport_grader')+'
', - bodyContent : message, - visible : false, - zIndex : 3 - }); - erroroverlay.set('xy', [cell.getX()+10,cell.getY()+10]); - erroroverlay.render(this.report.table.ancestor('div')); - erroroverlay.show(); - erroroverlay.get('boundingBox').on('click', function(){ - this.get('boundingBox').setStyle('visibility', 'hidden'); - this.hide(); - this.destroy(); - }, erroroverlay); - erroroverlay.get('boundingBox').setStyle('visibility', 'visible'); -}; -/** - * A class for existing fields - * This class is used only when the user is in editing mode - * - * @class existingfield - * @constructor - * @param {M.gradereport_grader.classes.report} report - * @param {Int} userid - * @param {Int} itemid - */ -M.gradereport_grader.classes.existingfield = function(ajax, userid, itemid) { - this.report = ajax.report; - this.userid = userid; - this.itemid = itemid; - this.grade = this.report.Y.one('#grade_'+userid+'_'+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']; - } - } - - if (!this.oldgrade) { - // Assigning an empty string makes determining whether the grade has been changed easier - // This value is never sent to the server - this.oldgrade = ''; - } - - // On blur save any changes in the grade field - this.grade.on('blur', this.submit, this); - } - - if (this.grade) { - // Handle Tab and Shift+Tab. - this.keyevents.push(this.report.Y.on('key', this.keypress_tab, this.grade, 'down:9', this)); - // Handle the Enter key being pressed. - this.keyevents.push(this.report.Y.on('key', this.keypress_enter, this.grade, 'up:13', this)); - // Handle CTRL + arrow keys. - this.keyevents.push(this.report.Y.on('key', this.keypress_arrows, this.grade, 'down:37,38,39,40+ctrl', this)); - } -}; -/** - * Attach the required properties and methods to the existing field class - * via prototyping - */ -M.gradereport_grader.classes.existingfield.prototype.userid = null; -M.gradereport_grader.classes.existingfield.prototype.itemid = null; -M.gradereport_grader.classes.existingfield.prototype.grade = null; -M.gradereport_grader.classes.existingfield.prototype.oldgrade = null; -M.gradereport_grader.classes.existingfield.prototype.keyevents = []; -/** - * Handles saving of changed on keypress - * - * @function - * @this {M.gradereport_grader.classes.existingfield} - * @param {Event} e - */ -M.gradereport_grader.classes.existingfield.prototype.keypress_enter = function(e) { - e.preventDefault(); - this.submit(); -}; -/** - * Handles setting the correct focus if the user presses tab - * - * @function - * @this {M.gradereport_grader.classes.existingfield} - * @param {Event} e - * @param {Bool} ignoreshift - */ -M.gradereport_grader.classes.existingfield.prototype.keypress_tab = function(e, ignoreshift) { - e.preventDefault(); - var next = null; - if (e.shiftKey) { - if (ignoreshift) { - return; - } - next = this.report.ajax.get_above_cell(this.grade.ancestor('td')); - } else { - next = this.report.ajax.get_below_cell(this.grade.ancestor('td')); - } - this.move_focus(next); -}; -/** - * Handles setting the correct focus when the user presses CTRL+arrow keys - * - * @function - * @this {M.gradereport_grader.classes.existingfield} - * @param {Event} e - */ -M.gradereport_grader.classes.existingfield.prototype.keypress_arrows = function(e) { - e.preventDefault(); - var next = null; - switch (e.keyCode) { - case 37: // Left - next = this.report.ajax.get_prev_cell(this.grade.ancestor('td')); - break; - case 38: // Up - next = this.report.ajax.get_above_cell(this.grade.ancestor('td')); - break; - case 39: // Right - next = this.report.ajax.get_next_cell(this.grade.ancestor('td')); - break; - case 40: // Down - next = this.report.ajax.get_below_cell(this.grade.ancestor('td')); - break; - } - this.move_focus(next); -}; -/** - * Move the focus to the node - * @function - * @this {M.gradereport_grader.classes.existingfield} - * @param {Y.Node} node - */ -M.gradereport_grader.classes.existingfield.prototype.move_focus = function(node) { - if (node) { - var properties = this.report.get_cell_info(node); - this.report.ajax.current = node; - switch(properties.itemtype) { - case 'scale': - properties.cell.one('select.select').focus(); - break; - case 'value': - default: - properties.cell.one('input.text').focus(); - break; - } - } -}; -/** - * Checks if the values for the field have changed - * - * @function - * @this {M.gradereport_grader.classes.existingfield} - * @return {Bool} - */ -M.gradereport_grader.classes.existingfield.prototype.has_changed = function() { - if (this.grade) { - if (this.grade.get('value') !== this.oldgrade) { - return true; - } - } - return false; -}; -/** - * Submits any changes and then updates the fields accordingly - * - * @function - * @this {M.gradereport_grader.classes.existingfield} - */ -M.gradereport_grader.classes.existingfield.prototype.submit = function() { - if (!this.has_changed()) { - return; - } - - var properties = this.report.get_cell_info([this.userid,this.itemid]); - var values = (function(f){ - var grade, oldgrade = null; - if (f.grade) { - grade = f.grade.get('value'); - oldgrade = f.oldgrade; - } - return { - grade : grade, - oldgrade : oldgrade, - }; - })(this); - - this.oldgrade = values.grade; - - this.report.ajax.submit(properties, values); -}; - -/** - * Textfield class - * This classes gets used in conjunction with the report running with AJAX enabled - * and is used to manage a cell that has a grade requiring a textfield for input - * - * @class textfield - * @constructor - * @this {M.gradereport_grader.classes.textfield} - * @param {M.gradereport_grader.classes.report} report - * @param {Y.Node} node - */ -M.gradereport_grader.classes.textfield = function(report, node) { - this.report = report; - this.node = node; - this.gradespan = node.one('.gradevalue'); - this.inputdiv = this.report.Y.Node.create('
'); - this.grade = this.report.Y.Node.create(''); - this.gradetype = 'value'; - this.inputdiv.append(this.grade); -}; -/** - * Extend the textfield class with the following methods and properties - */ -M.gradereport_grader.classes.textfield.prototype.keyevents = []; -M.gradereport_grader.classes.textfield.prototype.editable = false; -M.gradereport_grader.classes.textfield.prototype.gradetype = null; -M.gradereport_grader.classes.textfield.prototype.grade = null; -M.gradereport_grader.classes.textfield.prototype.report = null; -M.gradereport_grader.classes.textfield.prototype.node = null; -M.gradereport_grader.classes.textfield.prototype.gradespam = null; -M.gradereport_grader.classes.textfield.prototype.inputdiv = null; -/** - * Replaces the cell contents with the controls to enable editing - * - * @function - * @this {M.gradereport_grader.classes.textfield} - * @return {M.gradereport_grader.classes.textfield} - */ -M.gradereport_grader.classes.textfield.prototype.replace = function() { - this.set_grade(this.get_grade()); - this.node.replaceChild(this.inputdiv, this.gradespan); - if (this.grade) { - this.grade.focus(); - } - this.editable = true; - return this; -}; -/** - * Commits the changes within a cell and returns a result object of new + old values - * @function - * @this {M.gradereport_grader.classes.textfield} - * @return {Object} - */ -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 == '-') { - oldgrade = ''; - } - - // Now back to editable gives us the values in the edit areas. - field.editable = true; - return { - gradetype : field.gradetype, - grade : field.get_grade(), - oldgrade : oldgrade, - }; - })(this); - // Set the changes in stone - this.set_grade(result.grade); - // Return the result object - return result; -}; -/** - * Reverts a cell back to its static contents - * @function - * @this {M.gradereport_grader.classes.textfield} - */ -M.gradereport_grader.classes.textfield.prototype.revert = function() { - this.node.replaceChild(this.gradespan, this.inputdiv); - for (var i in this.keyevents) { - if (this.keyevents[i]) { - this.keyevents[i].detach(); - } - } - this.keyevents = []; - this.node.on('click', this.report.ajax.make_editable, this.report.ajax); -}; -/** - * Gets the grade for current cell - * - * @function - * @this {M.gradereport_grader.classes.textfield} - * @return {Mixed} - */ -M.gradereport_grader.classes.textfield.prototype.get_grade = function() { - if (this.editable) { - return this.grade.get('value'); - } - return this.gradespan.get('innerHTML'); -}; -/** - * Sets the grade for the current cell - * @function - * @this {M.gradereport_grader.classes.textfield} - * @param {Mixed} value - */ -M.gradereport_grader.classes.textfield.prototype.set_grade = function(value) { - if (!this.editable) { - if (value == '-') { - value = ''; - } - this.grade.set('value', value); - } else { - if (value == '') { - value = '-'; - } - this.gradespan.set('innerHTML', value); - } -}; -/** - * Checks if the current cell has changed at all - * @function - * @this {M.gradereport_grader.classes.textfield} - * @return {Bool} - */ -M.gradereport_grader.classes.textfield.prototype.has_changed = function() { - // If its not editable it has not changed - if (!this.editable) { - return false; - } - 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 - * against the textfield - * - * @function - * @this {M.gradereport_grader.classes.textfield} - */ -M.gradereport_grader.classes.textfield.prototype.attach_key_events = function() { - var a = this.report.ajax; - // Setup the default key events for tab and enter - if (this.grade) { - // Handle Tab and Shift+Tab. - this.keyevents.push(this.report.Y.on('key', a.keypress_tab, this.grade, 'down: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, 'up: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')); - } -}; - -/** - * An editable scale field - * - * @class scalefield - * @constructor - * @inherits M.gradereport_grader.classes.textfield - * @base M.gradereport_grader.classes.textfield - * @this {M.gradereport_grader.classes.scalefield} - * @param {M.gradereport_grader.classes.report} report - * @param {Y.Node} node - */ -M.gradereport_grader.classes.scalefield = function(report, node) { - this.report = report; - this.node = node; - this.gradespan = node.one('.gradevalue'); - this.inputdiv = this.report.Y.Node.create('
'); - this.grade = this.report.Y.Node.create(''); - this.gradetype = 'scale'; - this.inputdiv.append(this.grade); - var properties = this.report.get_cell_info(node); - this.scale = this.report.ajax.scales[properties.itemscale]; - for (var i in this.scale) { - if (this.scale[i]) { - this.grade.append(this.report.Y.Node.create('')); - } - } -}; -/** - * Override + extend the scalefield class with the following properties - * and methods - */ -/** - * @property {Array} scale - */ -M.gradereport_grader.classes.scalefield.prototype.scale = []; -/** - * Extend the scalefield with the functions from the textfield - */ -/** - * Overrides the get_grade function so that it can pick up the value from the - * scales select box - * - * @function - * @this {M.gradereport_grader.classes.scalefield} - * @return {Int} the scale id - */ -M.gradereport_grader.classes.scalefield.prototype.get_grade = function(){ - if (this.editable) { - // Return the scale value - return this.grade.all('option').item(this.grade.get('selectedIndex')).get('value'); - } else { - // Return the scale values id - var value = this.gradespan.get('innerHTML'); - for (var i in this.scale) { - if (this.scale[i] == value) { - return parseFloat(i)+1; - } - } - return -1; - } -}; -/** - * Overrides the set_grade function of textfield so that it can set the scale - * within the scale select box - * - * @function - * @this {M.gradereport_grader.classes.scalefield} - * @param {String} value - */ -M.gradereport_grader.classes.scalefield.prototype.set_grade = function(value) { - if (!this.editable) { - if (value == '-') { - value = '-1'; - } - this.grade.all('option').each(function(node){ - if (node.get('value') == value) { - node.set('selected', true); - } - }); - } else { - if (value == '' || value == '-1') { - value = '-'; - } else { - value = this.scale[parseFloat(value)-1]; - } - this.gradespan.set('innerHTML', value); - } -}; -/** - * Checks if the current cell has changed at all - * @function - * @this {M.gradereport_grader.classes.scalefield} - * @return {Bool} - */ -M.gradereport_grader.classes.scalefield.prototype.has_changed = function() { - if (!this.editable) { - return false; - } - var gradef = this.get_grade(); - this.editable = false; - var gradec = this.get_grade(); - this.editable = true; - return (gradef != gradec); -}; - -/** - * Manually extend the scalefield 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.scalefield.prototype[i]) { - M.gradereport_grader.classes.scalefield.prototype[i] = M.gradereport_grader.classes.textfield.prototype[i]; - } -} diff --git a/grade/report/grader/preferences_form.php b/grade/report/grader/preferences_form.php index 760564a3f0e..691427877d1 100644 --- a/grade/report/grader/preferences_form.php +++ b/grade/report/grader/preferences_form.php @@ -106,7 +106,6 @@ class grader_report_preferences_form extends moodleform { $preferences['prefgeneral']['aggregationposition'] = array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*', GRADE_REPORT_AGGREGATION_POSITION_FIRST => get_string('positionfirst', 'grades'), GRADE_REPORT_AGGREGATION_POSITION_LAST => get_string('positionlast', 'grades')); - $preferences['prefgeneral']['enableajax'] = $checkbox_default; $preferences['prefshow']['showuserimage'] = $checkbox_default; $preferences['prefshow']['showactivityicons'] = $checkbox_default; diff --git a/grade/report/grader/settings.php b/grade/report/grader/settings.php index d423307a003..ee4ef2b1b08 100644 --- a/grade/report/grader/settings.php +++ b/grade/report/grader/settings.php @@ -46,9 +46,6 @@ if ($ADMIN->fulltree) { array(GRADE_REPORT_MEAN_ALL => get_string('meanall', 'grades'), GRADE_REPORT_MEAN_GRADED => get_string('meangraded', 'grades')))); - $settings->add(new admin_setting_configcheckbox('grade_report_enableajax', get_string('enableajax', 'grades'), - get_string('enableajax_help', 'grades'), 0)); - $settings->add(new admin_setting_configcheckbox('grade_report_showcalculations', get_string('showcalculations', 'grades'), get_string('showcalculations_help', 'grades'), 1)); diff --git a/grade/report/grader/tests/behat/ajax_grader.feature b/grade/report/grader/tests/behat/ajax_grader.feature deleted file mode 100644 index c361b540663..00000000000 --- a/grade/report/grader/tests/behat/ajax_grader.feature +++ /dev/null @@ -1,209 +0,0 @@ -@gradereport @gradereport_grader @javascript -Feature: Using the AJAX grading feature of Grader report to update grades - 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 "custom profile fields" exist: - | datatype | shortname | name | - | text | frog | Favourite frog | - And the following "users" exist: - | username | firstname | lastname | email | idnumber | profile_field_frog | - | teacher1 | Teacher | 1 | teacher1@example.com | t1 | | - | student1 | Student | 1 | student1@example.com | s1 | litle yellow frog | - | student2 | Student | 2 | student2@example.com | s2 | prince frog | - | 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 categories" exist: - | fullname | course | gradecategory | - | Grade Sub Cat | C1 | Grade Cat | - 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 | - | Item 3 | C1 | 0 | value | Grade Cat | - | Calc Item | C1 | 0 | value | Grade Cat | - | Item VUSub | C1 | 0 | value | Grade Sub 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 config values are set as admin: - | grade_report_showaverages | 0 | - | grade_report_enableajax | 1 | - - Scenario: Use the grader report without editing, with AJAX on - When the following config values are set as admin: - | grade_overridecat | 1 | - And I log in as "teacher1" - And I am on "Course 1" course homepage - And I navigate to "View > Grader report" in the course gradebook - And I change window size to "large" - 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 set the field "ajaxgrade" to "33" - And I press the enter key - And I should not see a grade field for "Student 2" and grade item "Item VU" - And I click on student "Student 3" for grade item "Item SU" - And I set the field "ajaxgrade" to "Very good" - And I press the shift tab key - And I press the tab key - And I press the shift tab key - And I click on student "Student 3" for grade item "Item VU" - And I set the field "ajaxgrade" to "50" - And I press the enter key - And I click on student "Student 3" for grade item "Item 1" - And I set the field "ajaxgrade" to "80" - And I press the enter key - And the following should exist in the "user-grades" table: - | -1- | -6- | -7- | -13- | -16- | - | 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 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 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 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 set the field "ajaxgrade" to "90" - And I press the enter key - And the following should exist in the "user-grades" table: - | -1- | -16- | - | Student 1 | 90.00 | - And I navigate to "View > Grader report" in the course gradebook - And the following should exist in the "user-grades" table: - | -1- | -6- | -7- | -13- | -16- | - | Student 1 | - | - | - | 90.00 | - | Student 2 | - | 33.00 | - | 33.00 | - | Student 3 | 80.00 | 50.00 | Very good | 133.00 | - - Scenario: Use the grader report without editing, with AJAX without category override - When the following config values are set as admin: - | grade_overridecat | 0 | - And I log in as "teacher1" - And I am on "Course 1" course homepage - And I navigate to "View > Grader report" in the course gradebook - And I change window size to "large" - 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 set the field "ajaxgrade" to "33" - And I press the enter key - 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 the following should exist in the "user-grades" table: - | -1- | -7- | -16- | - | Student 2 | 33.00 | 33.00 | - - Scenario: Use the grader report with editing, with AJAX with category override - When the following config values are set as admin: - | grade_overridecat | 1 | - And I log in as "teacher1" - And I am on "Course 1" course homepage - And I navigate to "View > Grader report" in the course gradebook - 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 grade 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 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 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- | -6- | -7- | -13- | -15- | -16- | - | Student 2 | 30.00 | 20.00 | Very good | 53.00 | 53.00 | - And I click on student "Student 2" for grade item "Item 1" - - Scenario: Use the grader report with editing, with AJAX without category override - When the following config values are set as admin: - | grade_overridecat | 0 | - And I log in as "teacher1" - And I am on "Course 1" course homepage - And I navigate to "View > Grader report" in the course gradebook - And I turn editing mode on - And I change window size to "large" - And I set "=[[i1]] + [[i3]] + [[gsc]]" calculation for grade item "Calc Item" with idnumbers: - | Item 1 | i1 | - | Item 3 | i3 | - | Grade Sub Cat | gsc | - Then I should not see a grade 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 the following should exist in the "user-grades" table: - | -1- | -15- | -16- | - | Student 2 | 20.00 | 20.00 | - 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 the following should exist in the "user-grades" table: - | -1- | -15- | -16- | - | Student 2 | 80.00 | 80.00 | - And the field "Student 2 Calc Item grade" matches value "30.00" - And I give the grade "5.00" to the user "Student 2" for the grade item "Item 3" - And I click away from student "Student 2" and grade item "Item 3" value - And the following should exist in the "user-grades" table: - | -1- | -15- | -16- | - | Student 2 | 90.00 | 90.00 | - And the field "Student 2 Calc Item grade" matches value "35.00" - And I give the grade "10.00" to the user "Student 2" for the grade item "Item VUSub" - And I click away from student "Student 2" and grade item "Item VUSub" value - And the following should exist in the "user-grades" table: - | -1- | -5- | -15- | -16- | - | Student 2 | 10.00 | 110.00 | 110.00 | - And the field "Student 2 Calc Item grade" matches value "45.00" - And I turn editing mode off - And the following should exist in the "user-grades" table: - | -1- | -4- | -6- | -7- | -11- | -12- | -15- | -16- | - | Student 2 | 10.00 | 30.00 | 20.00 | 5.00 | 45.00 | 110.00 | 110.00 | - And I click on student "Student 2" for grade item "Item 1" - - Scenario: Teacher can see an error when an incorrect grade is given using the grader report with editing and AJAX on - Given I log in as "teacher1" - And I am on "Course 1" course homepage - And I navigate to "View > Grader report" in the course gradebook - And I turn editing mode on - And I give the grade "66.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 - When I give the grade "999.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 - Then I should see "The grade entered for Item VU for Student 2 is more than the maximum allowed" - And I click on "The grade entered for Item VU for Student 2 is more than the maximum allowed" "text" - And I should not see "The grade entered for Item VU for Student 2 is more than the maximum allowed" - And the grade for "Student 2" in grade item "Item VU" should match "66.00" - - Scenario: Teacher can see user custom filed columns as additional user identity - Given the following config values are set as admin: - | showuseridentity | email,profile_field_frog | - And I log in as "teacher1" - And I am on "Course 1" course homepage - When I navigate to "View > Grader report" in the course gradebook - Then I should see "litle yellow frog" in the "student1" "table_row" - And I should see "prince frog" in the "student2" "table_row" - # Sort by the custom profile field. - And I click on "Favourite frog" "link" in the "gradereport-grader-table" "table" - And "student1" "table_row" should appear before "student2" "table_row" - # Now sort by descending. - And I click on "Favourite frog" "link" in the "gradereport-grader-table" "table" - And "student2" "table_row" should appear before "student1" "table_row" diff --git a/grade/report/grader/tests/behat/behat_gradereport_grader.php b/grade/report/grader/tests/behat/behat_gradereport_grader.php index 709c1876540..cd8ed77ae79 100644 --- a/grade/report/grader/tests/behat/behat_gradereport_grader.php +++ b/grade/report/grader/tests/behat/behat_gradereport_grader.php @@ -37,100 +37,6 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException, * @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 - */ - public function i_click_on_student_and_grade_item($student, $itemname) { - $xpath = $this->get_student_and_grade_cell_selector($student, $itemname); - - $this->execute("behat_general::i_click_on", array($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 - */ - public function i_click_away_from_student_and_grade_value($student, $itemname) { - $xpath = $this->get_student_and_grade_value_selector($student, $itemname); - - $this->execute('behat_general::i_take_focus_off_field', array($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 - */ - 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 = behat_context_helper::escape($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 - */ - public function i_should_see_grade_field($student, $itemname) { - $xpath = $this->get_student_and_grade_value_selector($student, $itemname); - - $this->execute('behat_general::should_be_visible', array($this->escape($xpath), 'xpath_element')); - } - - /** - * 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 - */ - public function i_should_not_see_grade_field($student, $itemname) { - $xpath = $this->get_student_and_grade_value_selector($student, $itemname); - - $this->execute('behat_general::should_not_exist', array($this->escape($xpath), 'xpath_element')); - } /** * Gets the user id from its name. @@ -157,6 +63,7 @@ class behat_gradereport_grader extends behat_base { * @return int */ protected function get_grade_item_id($itemname) { + global $DB; if ($id = $DB->get_field('grade_items', 'id', array('itemname' => $itemname))) { @@ -181,30 +88,4 @@ class behat_gradereport_grader extends behat_base { 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']"; - } - } diff --git a/grade/report/grader/tests/behat/behat_gradereport_grader_deprecated.php b/grade/report/grader/tests/behat/behat_gradereport_grader_deprecated.php index 65a3b462651..df9d8fa6ef7 100644 --- a/grade/report/grader/tests/behat/behat_gradereport_grader_deprecated.php +++ b/grade/report/grader/tests/behat/behat_gradereport_grader_deprecated.php @@ -99,4 +99,152 @@ class behat_gradereport_grader_deprecated extends behat_deprecated_base { return $cell . "//input[contains(@id, 'feedback_') or @name='ajaxfeedback']"; } + /** + * Click a given user grade cell. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @Given /^I click on student "([^"]*)" for grade item "([^"]*)"$/ + * @param string $student + * @param string $itemname + */ + public function i_click_on_student_and_grade_item($student, $itemname) { + $this->deprecated_message(['behat_gradereport_grader::i_click_on_student_and_grade_item']); + + $xpath = $this->get_student_and_grade_cell_selector($student, $itemname); + + $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element")); + } + + /** + * Remove focus for a grade value cell. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" value$/ + * @param string $student + * @param string $itemname + */ + public function i_click_away_from_student_and_grade_value($student, $itemname) { + $this->deprecated_message(['behat_gradereport_grader::i_click_away_from_student_and_grade_value']); + + $xpath = $this->get_student_and_grade_value_selector($student, $itemname); + + $this->execute('behat_general::i_take_focus_off_field', array($this->escape($xpath), 'xpath_element')); + } + + /** + * Checks grade values with or without a edit box. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @Then /^the grade for "([^"]*)" in grade item "([^"]*)" should match "([^"]*)"$/ + * @throws Exception + * @throws ElementNotFoundException + * @param string $student + * @param string $itemname + * @param string $value + */ + public function the_grade_should_match($student, $itemname, $value) { + $this->deprecated_message(['behat_gradereport_grader::the_grade_should_match']); + + $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 = behat_context_helper::escape($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. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @Then /^I should see a grade field for "([^"]*)" and grade item "([^"]*)"$/ + * @param string $student + * @param string $itemname + */ + public function i_should_see_grade_field($student, $itemname) { + $this->deprecated_message(['behat_gradereport_grader::i_should_see_grade_field']); + + $xpath = $this->get_student_and_grade_value_selector($student, $itemname); + + $this->execute('behat_general::should_be_visible', array($this->escape($xpath), 'xpath_element')); + } + + /** + * Look for a lack of the grade editing field. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @Then /^I should not see a grade field for "([^"]*)" and grade item "([^"]*)"$/ + * @param string $student + * @param string $itemname + */ + public function i_should_not_see_grade_field($student, $itemname) { + $this->deprecated_message(['behat_gradereport_grader::i_should_not_see_grade_field']); + $xpath = $this->get_student_and_grade_value_selector($student, $itemname); + + $this->execute('behat_general::should_not_exist', array($this->escape($xpath), 'xpath_element')); + } + + /** + * Gets unique xpath selector for a student/grade item combo. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @throws Exception + * @param string $student + * @param string $itemname + * @return string + */ + protected function get_student_and_grade_cell_selector($student, $itemname) { + $this->deprecated_message(['behat_gradereport_grader::get_student_and_grade_cell_selector']); + + $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. + * + * @deprecated since 4.2 - we don't allow ajax edit on grader report anymore. + * @todo MDL-77107 This will be deleted in Moodle 4.6. + * @throws Exception + * @param string $student + * @param string $itemname + * @return string + */ + protected function get_student_and_grade_value_selector($student, $itemname) { + $this->deprecated_message(['behat_gradereport_grader::get_student_and_grade_value_selector']); + + $cell = $this->get_student_and_grade_cell_selector($student, $itemname); + return $cell . "//*[contains(@id, 'grade_') or @name='ajaxgrade']"; + } + } diff --git a/lang/en/deprecated.txt b/lang/en/deprecated.txt index 1e6fee9c3d6..3c72dc71bc6 100644 --- a/lang/en/deprecated.txt +++ b/lang/en/deprecated.txt @@ -72,3 +72,5 @@ confirmaddadmin,core_role confirmdeladmin,core_role showquickfeedback,core_grades showquickfeedback_help,core_grades +enableajax,core_grades +enableajax_help,core_grades diff --git a/lang/en/grades.php b/lang/en/grades.php index 2fcc3c2337b..1b015fd62ec 100644 --- a/lang/en/grades.php +++ b/lang/en/grades.php @@ -176,8 +176,6 @@ $string['editoutcomes'] = 'Edit outcomes'; $string['editscale'] = 'Edit scale'; $string['edittree'] = 'Setup'; $string['editverbose'] = 'Edit {$a->category} {$a->itemmodule} {$a->itemname}'; -$string['enableajax'] = 'Enable AJAX'; -$string['enableajax_help'] = 'Adds a layer of AJAX functionality to the grader report, simplifying and speeding up common operations. Depends on Javascript being switched on at the user\'s browser level.'; $string['enableoutcomes'] = 'Enable outcomes'; $string['enableoutcomes_help'] = 'If enabled, grade items may be graded using one or more scales tied to outcome statements.'; $string['encoding'] = 'Encoding'; @@ -914,3 +912,5 @@ $string['showquickfeedback'] = 'Show quick feedback'; $string['showquickfeedback_help'] = 'If enabled, when editing is turned on, a feedback text input box with a dotted border appears for each grade, allowing the feedback for many grades to be edited at the same time. Changes are saved and highlighted when the update button is clicked. Note that when feedback is edited in the grader report, an overridden flag is set, meaning that the feedback can no longer be changed from within the related activity.'; +$string['enableajax'] = 'Enable AJAX'; +$string['enableajax_help'] = 'Adds a layer of AJAX functionality to the grader report, simplifying and speeding up common operations. Depends on Javascript being switched on at the user\'s browser level.'; diff --git a/lib/upgrade.txt b/lib/upgrade.txt index fdc6317d914..62906ec8483 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -3,6 +3,7 @@ information provided here is intended especially for developers. === 4.2 === +* 'Enable AJAX' interface has been deprecated for grader report * 'Quick feedback' interface has been deprecated for grader report * \single_button constructor signature has been changed to manage more types than just primary buttons. The boolean "primary" parameter has been deprecated and replaced by a more generic type allowing to use