';
echo '
';
@@ -189,10 +178,4 @@ if ($USER->gradeediting[$course->id] && ($report->get_pref('showquickfeedback')
if (!empty($studentsperpage) && $studentsperpage >= 20) {
echo $OUTPUT->paging_bar(moodle_paging_bar::make($numusers, $report->page, $studentsperpage, $report->pbarurl));
}
-
-echo $OUTPUT->container('tooltip panel', '', 'hiddentooltiproot');
-// Print AJAX code
-if ($report->get_pref('enableajax')) {
- require_once 'ajax.php';
-}
echo $OUTPUT->footer();
diff --git a/grade/report/grader/lib.php b/grade/report/grader/lib.php
index 2330fb7361a..10d70f74ab9 100644
--- a/grade/report/grader/lib.php
+++ b/grade/report/grader/lib.php
@@ -545,7 +545,7 @@ class grade_report_grader extends grade_report {
for ($i = 0; $i < $levels; $i++) {
$fillercell = new html_table_cell();
- $fillercell->add_classes(array('fixedcolumn', 'cell', 'c0', 'topleft'));
+ $fillercell->add_classes(array('fixedcolumn', 'cell', 'topleft'));
$fillercell->text = ' ';
$fillercell->colspan = $colspan;
$row = html_table_row::make(array($fillercell));
@@ -556,7 +556,7 @@ class grade_report_grader extends grade_report {
$headerrow->add_class('heading');
$studentheader = new html_table_cell();
- $studentheader->add_classes(array('header', 'c0'));
+ $studentheader->add_classes(array('header'));
$studentheader->scope = 'col';
$studentheader->header = true;
$studentheader->id = 'studentheader';
@@ -572,7 +572,7 @@ class grade_report_grader extends grade_report {
$sortidnumberlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'idnumber')), get_string('idnumber'));
$idnumberheader = new html_table_cell();
- $idnumberheader->add_classes(array('header', 'c0', 'useridnumber'));
+ $idnumberheader->add_classes(array('header', 'useridnumber'));
$idnumberheader->scope = 'col';
$idnumberheader->header = true;
$idnumberheader->text = $arrows['idnumber'];
@@ -588,13 +588,13 @@ class grade_report_grader extends grade_report {
foreach ($this->users as $userid => $user) {
$userrow = new html_table_row();
+ $userrow->id = 'fixed_user_'.$userid;
$userrow->add_classes(array('r'.$this->rowcount++, $rowclasses[$this->rowcount % 2]));
$usercell = new html_table_cell();
- $usercell->add_classes(array('c0', 'user'));
+ $usercell->add_classes(array('user'));
$usercell->header = true;
$usercell->scope = 'row';
- $usercell->add_action('click', 'yui_set_row');
if ($showuserimage) {
$usercell->text = $OUTPUT->container($OUTPUT->user_picture($user), 'userpic');
@@ -617,10 +617,9 @@ class grade_report_grader extends grade_report {
if ($showuseridnumber) {
$idnumbercell = new html_table_cell();
- $idnumbercell->add_classes(array('header', 'c0', 'useridnumber'));
+ $idnumbercell->add_classes(array('header', 'useridnumber'));
$idnumbercell->header = true;
$idnumbercell->scope = 'row';
- $idnumbercell->add_action('click', 'yui_set_row');
$userrow->cells[] = $idnumbercell;
}
@@ -639,7 +638,7 @@ class grade_report_grader extends grade_report {
* @return array Array of html_table_row objects
*/
public function get_right_rows() {
- global $CFG, $USER, $OUTPUT, $DB;
+ global $CFG, $USER, $OUTPUT, $DB, $PAGE;
$rows = array();
$this->rowcount = 0;
@@ -648,10 +647,19 @@ class grade_report_grader extends grade_report {
$gradetabindex = 1;
$columnstounset = array();
$strgrade = $this->get_lang_string('grade');
+ $strfeedback = $this->get_lang_string("feedback");
$arrows = $this->get_sort_arrows();
+ $jsarguments = array(
+ 'id' => '#fixed_column',
+ 'cfg' => array('ajaxenabled'=>false),
+ 'items' => array(),
+ 'users' => array(),
+ 'feedback' => array()
+ );
+ $jsscales = array();
+
foreach ($this->gtree->get_levels() as $key=>$row) {
- $columncount = 0;
if ($key == 0) {
// do not display course grade category
// continue;
@@ -673,10 +681,8 @@ class grade_report_grader extends grade_report {
$itemmodule = null;
$iteminstance = null;
- $columnclass = 'c' . $columncount++;
if (!empty($element['colspan'])) {
$colspan = $element['colspan'];
- $columnclass = '';
} else {
$colspan = 1;
}
@@ -690,7 +696,7 @@ class grade_report_grader extends grade_report {
// Element is a filler
if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') {
$fillercell = new html_table_cell();
- $fillercell->add_classes(array($columnclass, $type, $catlevel));
+ $fillercell->add_classes(array($type, $catlevel));
$fillercell->colspan = $colspan;
$fillercell->text = ' ';
$fillercell->header = true;
@@ -700,7 +706,7 @@ class grade_report_grader extends grade_report {
// Element is a category
else if ($type == 'category') {
$categorycell = new html_table_cell();
- $categorycell->add_classes(array($columnclass, 'category', $catlevel));
+ $categorycell->add_classes(array('category', $catlevel));
$categorycell->colspan = $colspan;
$categorycell->text = shorten_text($element['object']->get_name());
$categorycell->text .= $this->get_collapsing_icon($element);
@@ -732,7 +738,7 @@ class grade_report_grader extends grade_report {
$headerlink = $this->gtree->get_element_header($element, true, $this->get_pref('showactivityicons'), false);
$itemcell = new html_table_cell();
- $itemcell->add_classes(array($columnclass, $type, $catlevel));
+ $itemcell->add_classes(array($type, $catlevel, 'highlightable'));
if ($element['object']->is_hidden()) {
$itemcell->add_class('hidden');
@@ -742,11 +748,10 @@ class grade_report_grader extends grade_report {
$itemcell->text = shorten_text($headerlink) . $arrow;
$itemcell->header = true;
$itemcell->scope = 'col';
- $itemcell->onclick = 'set_col(this.cellIndex);';
$headingrow->cells[] = $itemcell;
}
-
+
}
$rows[] = $headingrow;
}
@@ -757,11 +762,14 @@ class grade_report_grader extends grade_report {
$scaleslist = array();
$tabindices = array();
- foreach ($this->gtree->get_items() as $item) {
+ foreach ($this->gtree->get_items() as $itemid=>$item) {
+ $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;
$tabindices[$item->id]['feedback'] = $gradetabindex + $numusers;
$gradetabindex += $numusers * 2;
@@ -771,6 +779,7 @@ class grade_report_grader extends grade_report {
if (!empty($scaleslist)) {
$scalesarray = $DB->get_records_list('scale', 'id', $scaleslist);
}
+ $jsscales = $scalesarray;
$rowclasses = array('even', 'odd');
@@ -786,17 +795,21 @@ class grade_report_grader extends grade_report {
unset($hidingaffected);
}
- $columncount = 0;
$itemrow = new html_table_row();
+ $itemrow->id = 'user_'.$userid;
$itemrow->add_class($rowclasses[$this->rowcount % 2]);
+ $jsarguments['users'][$userid] = fullname($user);
+
foreach ($this->gtree->items as $itemid=>$unused) {
$item =& $this->gtree->items[$itemid];
$grade = $this->grades[$userid][$item->id];
$itemcell = new html_table_cell();
+ $itemcell->id = 'u'.$userid.'i'.$itemid;
+
// Get the decimal points preference for this item
$decimalpoints = $item->get_decimals();
@@ -840,15 +853,10 @@ class grade_report_grader extends grade_report {
// $itemcell->add_class('excluded');
}
- $gradetitle = $OUTPUT->container(fullname($user), 'fullname');
- $gradetitle .= $OUTPUT->container($item->get_name(true), 'itemname');
-
- if (!empty($grade->feedback) && !$USER->gradeediting[$this->courseid]) {
- $gradetitle .= $OUTPUT->container(wordwrap(trim(format_string($grade->feedback, $grade->feedbackformat)), 34, '
'), 'feedback');
+ if (!empty($grade->feedback)) {
+ $jsarguments['feedback'][] = array('user'=>$userid, 'item'=>$itemid, 'content'=>wordwrap(trim(format_string($grade->feedback, $grade->feedbackformat)), 34, '
'));
}
- $itemcell->title = s($gradetitle);
-
if ($grade->is_excluded()) {
$itemcell->text .= $OUTPUT->span(get_string('excluded', 'grades'), 'excludedfloater');
}
@@ -900,9 +908,8 @@ class grade_report_grader extends grade_report {
} else {
$nogradestr = $this->get_lang_string('nooutcome', 'grades');
}
- $itemcell->text .= '
';
- $attributes = array('tabindex' => $tabindices[$item->id]['grade']);
+ $itemcell->text .= '
';
+ $attributes = array('tabindex' => $tabindices[$item->id]['grade'], 'id'=>'grade_'.$userid.'_'.$item->id);
$itemcell->text .= html_writer::select($scaleopt, 'grade_'.$userid.'_'.$item->id, $gradeval, array(-1=>$nogradestr), $attributes);;
} elseif(!empty($scale)) {
$scales = explode(",", $scale->scale);
@@ -921,10 +928,10 @@ class grade_report_grader extends grade_report {
} else if ($item->gradetype != GRADE_TYPE_TEXT) { // Value type
if ($this->get_pref('quickgrading') and $grade->is_editable()) {
$value = format_float($gradeval, $decimalpoints);
- $itemcell->text .= '
';
+ $itemcell->text .= '
';
$itemcell->text .= '
';
+ .$userid.'_' .$item->id.'" id="grade_'.$userid.'_'.$item->id.'" value="'.$value.'" />';
} else {
$itemcell->text .= $OUTPUT->span(format_float($gradeval, $decimalpoints), "gradevalue$hidden$gradepass");
}
@@ -934,17 +941,19 @@ class grade_report_grader extends grade_report {
// If quickfeedback is on, print an input element
if ($this->get_pref('showquickfeedback') and $grade->is_editable()) {
- $itemcell->text .= '
';
- $itemcell->text .= '
';
+ $itemcell->text .= '
';
+ $itemcell->text .= '
';
}
} else { // Not editing
$gradedisplaytype = $item->get_displaytype();
- // If feedback present, surround grade with feedback tooltip: Open span here
+ if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
+ $itemcell->add_class('grade_type_scale');
+ } else if ($item->gradetype != GRADE_TYPE_TEXT) {
+ $itemcell->add_class('grade_type_text');
+ }
if ($item->needsupdate) {
$itemcell->text .= $OUTPUT->span(get_string('error'), "gradingerror$hidden$gradepass");
@@ -962,6 +971,29 @@ class grade_report_grader extends grade_report {
$rows[] = $itemrow;
}
+ if ($this->get_pref('enableajax')) {
+ $jsarguments['cfg']['ajaxenabled'] = true;
+ $jsarguments['cfg']['scales'] = array();
+ foreach ($jsscales as $scale) {
+ $jsarguments['cfg']['scales'][$scale->id] = explode(',',$scale->scale);
+ }
+ $jsarguments['cfg']['feedbacktrunclength'] = $this->feedback_trunc_length;
+ $jsarguments['cfg']['feedback'] = $this->feedbacks;
+ }
+ $jsarguments['cfg']['isediting'] = (bool)$USER->gradeediting[$this->courseid];
+ $jsarguments['cfg']['courseid'] = $this->courseid;
+ $jsarguments['cfg']['studentsperpage'] = $this->get_pref('studentsperpage');
+ $jsarguments['cfg']['showquickfeedback'] = (bool)$this->get_pref('showquickfeedback');
+
+ $module = array(
+ 'name' => 'gradereport_grader',
+ 'fullpath' => '/grade/report/grader/module.js',
+ 'requires' => array('base', 'dom', 'event', 'event-mouseenter', 'event-key', 'io', 'json-parse', 'overlay')
+ );
+ $PAGE->requires->js_init_call('M.gradereport_grader.init_report', $jsarguments, false, $module);
+ $PAGE->requires->strings_for_js(array('addfeedback','feedback', 'grade'), 'grades');
+ $PAGE->requires->strings_for_js(array('ajaxchoosescale','ajaxclicktoclose','ajaxerror','ajaxfailedupdate', 'ajaxfieldchanged'), 'gradereport_grader');
+
$rows = $this->get_right_range_row($rows);
$rows = $this->get_right_avg_row($rows, true);
$rows = $this->get_right_avg_row($rows);
@@ -984,6 +1016,7 @@ class grade_report_grader extends grade_report {
$html = '';
+
if ($fixedstudents) {
$fixedcolumntable = new html_table();
$fixedcolumntable->id = 'fixed_column';
@@ -1027,7 +1060,7 @@ class grade_report_grader extends grade_report {
$controlsrow = new html_table_row();
$controlsrow->add_class('controls');
$controlscell = new html_table_cell();
- $controlscell->add_classes(array('header', 'c0', 'controls'));
+ $controlscell->add_classes(array('header', 'controls'));
$controlscell->colspan = $colspan;
$controlscell->text = $this->get_lang_string('controls','grades');
@@ -1050,7 +1083,7 @@ class grade_report_grader extends grade_report {
$rangerow = new html_table_row();
$rangerow->add_classes(array('range', 'r'.$this->rowcount++));
$rangecell = new html_table_cell();
- $rangecell->add_classes(array('header', 'c0', 'range'));
+ $rangecell->add_classes(array('header', 'range'));
$rangecell->colspan = $colspan;
$rangecell->header = true;
$rangecell->scope = 'row';
@@ -1085,7 +1118,7 @@ class grade_report_grader extends grade_report {
$groupavgrow = new html_table_row();
$groupavgrow->add_classes(array('groupavg', 'r'.$this->rowcount++));
$groupavgcell = new html_table_cell();
- $groupavgcell->add_classes(array('header', 'c0', 'range'));
+ $groupavgcell->add_classes(array('header', 'range'));
$groupavgcell->colspan = $colspan;
$groupavgcell->header = true;
$groupavgcell->scope = 'row';
@@ -1100,7 +1133,7 @@ class grade_report_grader extends grade_report {
$avgrow = new html_table_row();
$avgrow->add_classes(array('avg', 'r'.$this->rowcount++));
$avgcell = new html_table_cell();
- $avgcell->add_classes(array('header', 'c0', 'range'));
+ $avgcell->add_classes(array('header', 'range'));
$avgcell->colspan = $colspan;
$avgcell->header = true;
$avgcell->scope = 'row';
@@ -1126,7 +1159,6 @@ class grade_report_grader extends grade_report {
$showuseridnumber = $this->get_pref('showuseridnumber');
- $columncount = 0;
foreach ($this->gtree->items as $itemid=>$unused) {
// emulate grade element
$item =& $this->gtree->get_item($itemid);
diff --git a/grade/report/grader/module.js b/grade/report/grader/module.js
new file mode 100644
index 00000000000..f17cbcfb9e9
--- /dev/null
+++ b/grade/report/grader/module.js
@@ -0,0 +1,1198 @@
+/**
+ * Grader report namespace
+ */
+M.gradereport_grader = {
+ /**
+ * @param {Array} reports An array of instantiated report objects
+ */
+ reports : [],
+ /**
+ * @namespace M.gradereport_grader
+ * @param {Object} reports A collection of classes used by the grader report module
+ */
+ classes : {},
+ /**
+ * @param {Object} tooltip Null or a tooltip object
+ */
+ tooltip : null,
+ /**
+ * Instantiates a new grader report
+ *
+ * @function
+ * @param {YUI} Y
+ * @param {String} id The id attribute of the reports table
+ * @param {Obejct} 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 feedback objects
+ */
+ init_report : function(Y, id, cfg, items, users, feedback) {
+ this.tooltip = this.tooltip || {
+ overlay : null, // Y.Overlay instance
+ /**
+ * Attaches the tooltip event to the provided cell
+ *
+ * @function M.gradereport_grader.tooltip.attach
+ * @this M.gradereport_grader
+ * @param {Y.Node} td The cell to attach the tooltip event to
+ */
+ attach : function(td, report) {
+ td.on('mouseenter', this.show, this, report);
+ },
+ /**
+ * Shows the tooltip: Callback from @see M.gradereport_grader.tooltip#attach
+ *
+ * @function M.gradereport_grader.tooltip.show
+ * @this {M.gradereport_grader.tooltip}
+ * @param {Event} e
+ * @param {M.gradereport_grader.classes.report} report
+ */
+ show : function(e, report) {
+ e.halt();
+
+ var properties = report.get_cell_info(e.target);
+ if (!properties) {
+ return;
+ }
+
+ var content = '
'+properties.username+'
'+properties.itemname+'
';
+ if (properties.feedback) {
+ content += '
'+properties.feedback+'
';
+ }
+
+ properties.cell.on('mouseleave', this.hide, this, properties.cell);
+ properties.cell.addClass('tooltipactive');
+
+ this.overlay = this.overlay || (function(){
+ var overlay = new Y.Overlay({
+ bodyContent : 'Loading',
+ visible : false,
+ zIndex : 2
+ });
+ overlay.render(report.table.ancestor('div'));
+ return overlay;
+ })();
+ this.overlay.set('xy', [e.target.getX()+(e.target.get('offsetWidth')/2),e.target.getY()+e.target.get('offsetHeight')-5]);
+ this.overlay.set("bodyContent", content);
+ this.overlay.show();
+ this.overlay.get('boundingBox').setStyle('visibility', 'visible');
+ },
+ /**
+ * Hides the tooltip
+ *
+ * @function M.gradereport_grader.tooltip.hide
+ * @this {M.gradereport_grader.tooltip}
+ * @param {Event} e
+ * @param {Y.Node} cell
+ */
+ hide : function(e, cell) {
+ cell.removeClass('tooltipactive');
+ this.overlay.hide();
+ this.overlay.get('boundingBox').setStyle('visibility', 'hidden');
+ }
+ };
+ // Create the actual report
+ this.reports[id] = new this.classes.report(Y, id, cfg, items, users, feedback);
+ }
+};
+
+/**
+ * Initialises the JavaScript for the gradebook grader report
+ *
+ * @class report
+ * @constructor
+ * @this {M.gradereport_grader}
+ * @param {YUI} Y
+ * @param {int} id The id of the table to attach the report to
+ * @param {Object} cfg Configuration variables
+ * @param {Array} items An array containing grade items
+ * @param {Array} users An array containing user infomation
+ * @param {Array} feedback An array containing feedback information
+ */
+M.gradereport_grader.classes.report = function(Y, id, cfg, items, users, feedback) {
+ this.Y = Y;
+ this.isediting = (cfg.isediting);
+ this.ajaxenabled = (cfg.ajaxenabled);
+ this.items = items;
+ this.users = users;
+ this.feedback = feedback;
+ this.table = Y.one('#user-grades');
+
+ // Alias this so that we can use the correct scope in the coming
+ // node iteration
+ this.table.all('tr').each(function(tr){
+ // Check it is a user row
+ if (tr.getAttribute('id').match(/^fixed_user_(\d+)$/)) {
+ // Highlight rows
+ tr.all('th.cell').on('click', this.table_highlight_row, this, tr);
+ // Display tooltips
+ tr.all('td.cell').each(function(cell){
+ M.gradereport_grader.tooltip.attach(cell, this);
+ }, this);
+ }
+ }, this);
+
+ // If the fixed table exists then map those rows to highlight the
+ // grades table rows
+ var fixed = this.Y.one(id);
+ if (fixed) {
+ fixed.all('tr').each(function(tr) {
+ if (tr.getAttribute('id').match(/^fixed_user_(\d+)$/)) {
+ tr.all('th.cell').on('click', this.table_highlight_row, this, this.Y.one(tr.getAttribute('id').replace(/^fixed_/, '#')));
+ }
+ }, this);
+ }
+
+ // Highlight columns
+ this.table.all('.highlightable').each(function(cell){
+ cell.on('click', this.table_highlight_column, this, cell);
+ cell.removeClass('highlightable');
+ }, this);
+
+ // 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.feedback = []; // Array containing feedback items
+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
+/**
+ * Highlights a row in the report
+ *
+ * @function
+ * @param {Event} e
+ * @param {Y.Node} tr The table row to hightlight
+ */
+M.gradereport_grader.classes.report.prototype.table_highlight_row = function (e, tr) {
+ tr.all('.cell').toggleClass('hmarked');
+};
+/**
+ * Highlights a cell in the table
+ *
+ * @function
+ * @param {Event} e
+ * @param {Y.Node} cell
+ */
+M.gradereport_grader.classes.report.prototype.table_highlight_column = function(e, cell) {
+ var column = 0;
+ while (cell = cell.previous('.cell')) {
+ column += parseFloat(cell.getAttribute('colspan')) || 1;
+ }
+ this.table.all('.c'+column).toggleClass('vmarked');
+};
+/**
+ * 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 feedback = 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;
+ }
+
+ for (i in this.feedback) {
+ if (this.feedback[i] && this.feedback[i].user == userid && this.feedback[i].item == itemid) {
+ feedback = this.feedback[i].content;
+ break;
+ }
+ }
+
+ return {
+ 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,
+ feedback : feedback,
+ cell : cell
+ };
+};
+/**
+ * Updates or creates the feedback JS structure for the given user/item
+ *
+ * @function
+ * @this {M.gradereport_grader}
+ * @param {Int} userid
+ * @param {Int} itemid
+ * @param {String} newfeedback
+ * @return {Bool}
+ */
+M.gradereport_grader.classes.report.prototype.update_feedback = function(userid, itemid, newfeedback) {
+ for (var i in this.feedback) {
+ if (this.feedback[i].user == userid && this.feedback[i].item == itemid) {
+ this.feedback[i].content = newfeedback;
+ return true;
+ }
+ }
+ this.feedback.push({user:userid,item:itemid,content:newfeedback});
+ return true;
+};
+
+/**
+ * Initalises 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.feedbacktrunclength = cfg.courseid || null;
+ this.studentsperpage = cfg.studentsperpage || null;
+ this.showquickfeedback = cfg.showquickfeedback || false;
+ this.scales = cfg.scales || null;
+ this.existingfields = [];
+
+ if (!M.gradereport_grader.isediting) {
+ report.table.all('.cell.grade').on('makeditable|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 this.existingfield(this, userid, itemid);
+ }
+ }
+ }
+};
+/**
+ * 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.feedbacktrunclength = null; // The length to truncate feedback to
+M.gradereport_grader.classes.ajax.prototype.studentsperpage = null; // The number of students shown per page
+M.gradereport_grader.classes.ajax.prototype.showquickfeedback = null; // True if feedback editing should be shown
+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 = 'text';
+ if (node.hasClass('grade_type_scale')) {
+ fieldtype = 'scale';
+ }
+ // Create the appropriate field widget
+ switch (fieldtype) {
+ case 'scale':
+ this.current = new M.gradereport_grader.classes.scalefield(this.report, node);
+ break;
+ case 'text':
+ default:
+ this.current = new M.gradereport_grader.classes.textfield(this.report, node);
+ break;
+ }
+ this.current.replace().attach_key_events();
+};
+/**
+ * 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) {
+ 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);
+ }
+};
+/**
+ * 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) {
+ next = this.current.node;
+ }
+ 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) {
+ next = this.current.node;
+ }
+ 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) {
+ next = this.current.node;
+ }
+ 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) {
+ next = this.current.node;
+ }
+ return next;
+};
+/**
+ * Submits changes for update
+ *
+ * @function
+ * @this {M.gradereport_grader.classes.ajax}
+ * @param {Object} properties Properties of the cell being editied
+ * @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});
+ }
+ // If feedback is editable and has changed add to the IO queue for it
+ if (values.editablefeedback && values.feedback !== values.oldfeedback) {
+ 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.feedback+'&type=feedback&sesskey='+M.cfg.sesskey,
+ on : {
+ complete : this.submission_outcome
+ },
+ context : this,
+ arguments : {
+ properties : properties,
+ values : values,
+ type : 'feedback'
+ }
+ }),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.str.gradereport_grader.ajaxfailedupdate;
+ message.replace(/\[1\]/, args.type);
+ message.replace(/\[2\]/, M.gradereport_grader.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 refered 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 = '';
+ if (r.scale) {
+ finalgrade = (r.finalgrade)?this.scales[r.scale][parseFloat(r.finalgrade)-1]:'-';
+ } else {
+ finalgrade = (r.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);
+ }
+ } 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);
+ } 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.str.gradereport_grader.ajaxfieldchanged)) {
+ this.current.set_grade(finalgrade);
+ 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('.gradevalue').set('innerHTML',oldgrade);
+ } else if (args.type == 'feedback') {
+ this.report.update_feedback(p.userid, p.itemid, args.values.oldfeedback);
+ }
+ 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.str.gradereport_grader.ajaxerror+' '+M.str.gradereport_grader.ajaxclicktoclose+'
',
+ 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(report, userid, itemid) {
+ this.report = report;
+ this.userid = userid;
+ this.itemid = itemid;
+ this.editfeedback = M.gradereport_grader.ajax.showquickfeedback;
+ this.grade = report.Y.one('#grade_'+userid+'_'+itemid);
+ this.oldgrade = report.Y.one('#oldgrade_'+userid+'_'+itemid);
+
+ // On blur save any changes in the grade field
+ this.grade.on('blur', this.submit, this);
+
+ // Check if feedback is enabled
+ if (this.editfeedback) {
+ // Get the feedback fields
+ this.feedback = report.Y.one('#feedback_'+userid+'_'+itemid);
+ this.oldfeedback = report.Y.one('#oldfeedback_'+userid+'_'+itemid);
+ // On blur save any changes in the feedback field
+ this.feedback.on('blur', this.submit, this);
+
+ // Override the default tab movements when moving between cells
+ this.keyevents.push(report.Y.on('key', this.keypress_tab, this.grade, 'press:9+shift', this)); // Handle Shift+Tab
+ this.keyevents.push(report.Y.on('key', this.keypress_tab, this.feedback, 'press:9', this, true)); // Handle Tab
+ this.keyevents.push(report.Y.on('key', this.keypress_enter, this.feedback, 'press:13', this)); // Handle the Enter key being pressed
+ this.keyevents.push(report.Y.on('key', this.keypress_arrows, this.feedback, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
+
+ // Override the default tab movements for fields in the same cell
+ this.keyevents.push(report.Y.on('key', function(e){e.preventDefault();this.grade.focus();}, this.feedback, 'press:9+shift', this));
+ this.keyevents.push(report.Y.on('key', function(e){if (e.shiftKey) {return;}e.preventDefault();this.feedback.focus();}, this.grade, 'press:9', this));
+ } else {
+ this.keyevents.push(report.Y.on('key', this.keypress_tab, this.grade, 'press:9', this)); // Handle Tab and Shift+Tab
+ }
+ this.keyevents.push(report.Y.on('key', this.keypress_enter, this.grade, 'press:13', this)); // Handle the Enter key being pressed
+ this.keyevents.push(report.Y.on('key', this.keypress_arrows, this.grade, 'press:37,38,39,40+ctrl', this)); // Handle CTRL + arrow keys
+};
+/**
+ * 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.editfeedback = false;
+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) {
+ 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) {
+ 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);
+ 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.editfeedback) {
+ return (this.grade.get('value') !== this.oldgrade.get('value') || this.feedback.get('value') !== this.oldfeedback.get('value'));
+ }
+ return (this.grade.get('value') !== this.oldgrade.get('value'));
+};
+/**
+ * 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 feedback, oldfeedback = null;
+ if (f.editfeedback) {
+ feedback = f.feedback.get('value');
+ oldfeedback = f.oldfeedback.get('value');
+ }
+ return {
+ editablefeedback : f.editfeedback,
+ grade : f.grade.get('value'),
+ oldgrade : f.oldgrade.get('value'),
+ feedback : feedback,
+ oldfeedback : oldfeedback
+ };
+ })(this);
+ if (values.editablefeedback && values.feedback != values.oldfeedback) {
+ this.report.update_feedback(this.userid, this.itemid, values.feedback);
+ }
+ 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.editfeedback = this.report.ajax.showquickfeedback;
+ this.grade = this.report.Y.Node.create('
');
+ this.gradetype = 'value';
+ this.inputdiv.append(this.grade);
+ if (this.report.ajax.showquickfeedback) {
+ this.feedback = this.report.Y.Node.create('
');
+ this.inputdiv.append(this.feedback);
+ }
+};
+/**
+ * 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;
+M.gradereport_grader.classes.textfield.prototype.editfeedback = false;
+/**
+ * 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());
+ if (this.editfeedback) {
+ this.set_feedback(this.get_feedback());
+ }
+ this.node.replaceChild(this.inputdiv, this.gradespan);
+ 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){
+ field.editable = false;
+ var oldgrade = field.get_grade();
+ if (oldgrade == '-') {
+ oldgrade = '';
+ }
+ var feedback = null;
+ var oldfeedback = null;
+ if (field.editfeedback) {
+ oldfeedback = field.get_feedback();
+ }
+ field.editable = true;
+ if (field.editfeedback) {
+ feedback = field.get_feedback();
+ }
+ return {
+ gradetype : field.gradetype,
+ editablefeedback : field.editfeedback,
+ grade : field.get_grade(),
+ oldgrade : oldgrade,
+ feedback : feedback,
+ oldfeedback : oldfeedback
+ };
+ })(this);
+ // Set the changes in stone
+ this.set_grade(result.grade);
+ if (this.editfeedback) {
+ this.set_feedback(result.feedback);
+ }
+ // 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('makeditable|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);
+ }
+};
+/**
+ * Gets the feedback for the current cell
+ * @function
+ * @this {M.gradereport_grader.classes.textfield}
+ * @return {String}
+ */
+M.gradereport_grader.classes.textfield.prototype.get_feedback = function() {
+ if (this.editable) {
+ return this.feedback.get('value');
+ }
+ var properties = this.report.get_cell_info(this.node);
+ if (properties) {
+ return properties.feedback || '';
+ }
+ return '';
+};
+/**
+ * Sets the feedback for the current cell
+ * @function
+ * @this {M.gradereport_grader.classes.textfield}
+ * @param {Mixed} value
+ */
+M.gradereport_grader.classes.textfield.prototype.set_feedback = function(value) {
+ if (!this.editable) {
+ this.feedback.set('value', value);
+ } else {
+ var properties = this.report.get_cell_info(this.node);
+ this.report.update_feedback(properties.userid, properties.itemid, 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 feedback is being edited then it has changed if either grade or feedback have changed
+ if (this.editfeedback) {
+ var properties = this.report.get_cell_info(this.node);
+ return (this.get_grade() != this.gradespan.get('innerHTML') || this.get_feedback() != properties.feedback);
+ }
+ return (this.get_grade() != this.gradespan.get('innerHTML'));
+};
+/**
+ * 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.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
+ } 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'));
+};
+
+/**
+ * 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 = report;
+ this.node = node;
+ this.gradespan = node.one('.gradevalue');
+ this.inputdiv = this.report.Y.Node.create('
');
+ this.editfeedback = this.report.ajax.showquickfeedback;
+ this.grade = this.report.Y.Node.create('
'+M.str.gradereport_grader.ajaxchoosescale+' ');
+ this.gradetype = 'scale';
+ this.inputdiv.append(this.grade);
+ if (this.editfeedback) {
+ this.feedback = this.report.Y.Node.create('
');
+ this.inputdiv.append(this.feedback);
+ }
+ 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('
'+this.scale[i]+' '));
+ }
+ }
+};
+/**
+ * Override + extend the scalefield class with the follwing 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;
+ if (this.editfeedback) {
+ var properties = this.report.get_cell_info(this.node);
+ var feedback = properties.feedback || '';
+ return (gradef != gradec || this.get_feedback() != feedback);
+ }
+ 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];
+ }
+}
\ No newline at end of file
diff --git a/grade/report/grader/styles.php b/grade/report/grader/styles.css
similarity index 69%
rename from grade/report/grader/styles.php
rename to grade/report/grader/styles.css
index 74096c0818e..fcb515d0638 100644
--- a/grade/report/grader/styles.php
+++ b/grade/report/grader/styles.css
@@ -11,10 +11,6 @@ height:20px;
background-color:#f9f9f9;
}
-.grade-report-grader table#user-grades td.overridden {
-background-color:#ddd;
-}
-
.grade-report-grader table#user-grades tr.avg td.cell {
background-color:#efefff;
font-weight:700;
@@ -26,13 +22,11 @@ background-color:#efefef;
white-space:nowrap;
}
-.grade-report-grader table#user-grades tr.even td.overridden {
-background-color:#F3E4C0;
-}
+.grade-report-grader table#user-grades tr td.overridden {background-color:#F3E4C0;}
+.grade-report-grader table#user-grades tr.odd td.overridden {background-color:#EFD9A4;}
-.grade-report-grader table#user-grades tr.odd td.overridden {
-background-color:#EFD9A4;
-}
+.grade-report-grader table#user-grades tr td.ajaxoverridden {background-color:#FFE3A0;}
+.grade-report-grader table#user-grades tr.odd td.ajaxoverridden {background-color:#FFDA83;}
.grade-report-grader table#user-grades tr.even td.excluded {
background-color:#EABFFF;
@@ -53,7 +47,8 @@ font-weight:700;
color:#006400;
}
-.grade-report-grader table#user-grades td.cat,.grade-report-grader table#user-grades td.course {
+.grade-report-grader table#user-grades td.cat,
+.grade-report-grader table#user-grades td.course {
font-weight:700;
}
@@ -133,22 +128,14 @@ border-style:solid;
border-width:0 0 1px 1px;
}
-.grade-report-grader table#user-grades th.categoryitem,.grade-report-grader table#user-grades th.courseitem,.grade-report-grader table#user-grades td.topleft {
+.grade-report-grader table#user-grades th.categoryitem,
+.grade-report-grader table#user-grades th.courseitem,
+.grade-report-grader table#user-grades td.topleft {
vertical-align:top;
border-style:solid;
border-width:0 1px;
}
-/* we don't want 25px height - at all
- * This causes the columns to fall out of line if Static Students Column is enabled
-.grade-report-grader table#user-grades th.category,
-.grade-report-grader table#user-grades th.item,
-.grade-report-grader table#user-grades th.categoryitem,
-.grade-report-grader table#user-grades th.courseitem {
-height: 25px;
-}
-*/
-
.grade-report-grader td,.grade-report-grader th {
border-color:#CECECE;
}
@@ -201,7 +188,8 @@ border:#000 1px dashed;
text-align:right;
}
-.grade-report-grader table#user-grades .hidden,.grade-report-grader table#user-grades .hidden a {
+.grade-report-grader table#user-grades .hidden,
+.grade-report-grader table#user-grades .hidden a {
color:#aaa;
}
@@ -243,7 +231,8 @@ border-width:0 0 1px;
border-top:1px solid #cecece;
}
-.grade-report-grader table#user-grades tr.heading th.categoryitem,.grade-report-grader table#user-grades tr.heading th.courseitem {
+.grade-report-grader table#user-grades tr.heading th.categoryitem,
+.grade-report-grader table#user-grades tr.heading th.courseitem {
border-width:0 0 0 1px;
}
@@ -259,7 +248,9 @@ margin:0;
padding:0;
}
-.grade-report-grader table#user-grades th.categoryitem,.grade-report-grader table#user-grades th.courseitem,.grade-report-grader table td.topleft {
+.grade-report-grader table#user-grades th.categoryitem,
+.grade-report-grader table#user-grades th.courseitem,
+.grade-report-grader table td.topleft {
vertical-align:top;
border-color:#cecece #cecece #000;
border-style:solid;
@@ -397,11 +388,21 @@ clear:none;
overflow-x:scroll;
}
-.grade-report-grader table tr.avg,.grade-report-grader table tr.groupavg td,.grade-report-grader table tr.avg td,.grade-report-grader table tr.groupavg th,.grade-report-grader table tr.avg th,.grade-report-grader table tr.controls_row,.grade-report-grader table tr.controls_row th,.grade-report-grader table tr.range_row,.grade-report-grader table tr.range_row th,div.right_scroller tr {
+.grade-report-grader table tr.avg,
+.grade-report-grader table tr.groupavg td,
+.grade-report-grader table tr.avg td,
+.grade-report-grader table tr.groupavg th,
+.grade-report-grader table tr.avg th,
+.grade-report-grader table tr.controls_row,
+.grade-report-grader table tr.controls_row th,
+.grade-report-grader table tr.range_row,
+.grade-report-grader table tr.range_row th,
+div.right_scroller tr {
height:2em;
}
-.grade-report-grader table#user-grades tr.groupavg td.cell,.grade-report-grader tr.groupavg th.header {
+.grade-report-grader table#user-grades tr.groupavg td.cell,
+.grade-report-grader tr.groupavg th.header {
background-color:#efffef;
}
@@ -439,7 +440,15 @@ font-size:86%;
padding:0;
}
-.grade-report-grader tr.avg,tr.controls,td.controls,th.controls,.grade-report-grader tr.groupavg,tr.range,th.range,td.range,tr.heading th.range {
+.grade-report-grader tr.avg,
+.grade-report-grader tr.controls,
+.grade-report-grader td.controls,
+.grade-report-grader th.controls,
+.grade-report-grader tr.groupavg,
+.grade-report-grader tr.range,
+.grade-report-grader th.range,
+.grade-report-grader td.range,
+.grade-report-grader tr.heading th.range {
height:2em!important;
white-space:nowrap;
}
@@ -458,15 +467,26 @@ heading_name_row th span {
float:right;
}
-.gradestable th.user,.gradestable th.range,.grade-report-grader .flexible th,.grade-report-grader .flexible td,.grade-report-grader .flexible th a,.grade-report-grader .flexible td a,.grade-report-grader .gradestable th.range,.grade-report-grader td {
+.gradestable th.user,
+.gradestable th.range,
+.grade-report-grader .flexible th,
+.grade-report-grader .flexible td,
+.grade-report-grader .flexible th a,
+.grade-report-grader .flexible td a,
+.grade-report-grader .gradestable th.range,
+.grade-report-grader td {
white-space:nowrap;
}
-.grade-report-grader table#user-grades .catlevel1,.grade-report-grader .r1,.grade-report-grader table tr.even td.cell,.grade-report-grader table tr.even th {
+.grade-report-grader table#user-grades .catlevel1,
+.grade-report-grader .r1,
+.grade-report-grader table tr.even td.cell,
+.grade-report-grader table tr.even th {
background-color:#fff;
}
-.grade-report-grader table#user-grades .catlevel3,.grade-report-grader table tr.odd td.cell {
+.grade-report-grader table#user-grades .catlevel3,
+.grade-report-grader table tr.odd td.cell {
background-color:#efefef;
}
@@ -475,42 +495,67 @@ background-color:#efefef;
background-color:#efefef;
}
-.grade-report-grader table#user-grades td.vmarked,.grade-report-grader table#user-grades tr.odd td.vmarked,.grade-report-grader table#user-grades td.vmarked,.grade-report-grader table#user-grades tr.odd td.vmarked,.grade-report-grader table#user-grades tr.even td.vmarked {
+.grade-report-grader table#user-grades td.vmarked,
+.grade-report-grader table#user-grades tr.odd td.vmarked,
+.grade-report-grader table#user-grades td.vmarked,
+.grade-report-grader table#user-grades tr.odd td.vmarked,
+.grade-report-grader table#user-grades tr.even td.vmarked {
background-color:#fc3;
}
-.grade-report-grader table#user-grades td.hmarked,.grade-report-grader table#user-grades tr.odd td.hmarked,.grade-report-grader table#user-grades td.hmarked,.grade-report-grader table#user-grades tr.odd td.hmarked,.grade-report-grader table#user-grades tr.even td.hmarked {
+.grade-report-grader table#user-grades td.hmarked,
+.grade-report-grader table#user-grades tr.odd td.hmarked,
+.grade-report-grader table#user-grades td.hmarked,
+.grade-report-grader table#user-grades tr.odd td.hmarked,
+.grade-report-grader table#user-grades tr.even td.hmarked {
background-color:#ff9;
}
-.grade-report-grader table#user-grades td.hmarked.vmarked,.grade-report-grader table#user-grades tr.odd td.hmarked.vmarked,.grade-report-grader table#user-grades td.hmarked.vmarked,.grade-report-grader table#user-grades tr.even td.hmarked.vmarked,.grade-report-grader table#user-grades tr.odd td.hmarked.vmarked {
+.grade-report-grader table#user-grades td.hmarked.vmarked,
+.grade-report-grader table#user-grades tr.odd td.hmarked.vmarked,
+.grade-report-grader table#user-grades td.hmarked.vmarked,
+.grade-report-grader table#user-grades tr.even td.hmarked.vmarked,
+.grade-report-grader table#user-grades tr.odd td.hmarked.vmarked {
background-color:#fc9;
}
-.grade-report-grader table#user-grades tr.heading,.grade-report-grader table#user-grades .heading td {
+.grade-report-grader table#user-grades tr.heading,
+.grade-report-grader table#user-grades .heading td {
border-style:solid;
border-width:0;
}
-.grade-report-grader table#user-grades td.useridnumber,.grade-report-grader table#user-grades th,.grade-report-grader div.gradeparent,.ie6 form,.grade-report-grader table#user-grades td.ajax {
+.grade-report-grader table#user-grades td.useridnumber,
+.grade-report-grader table#user-grades th,
+.grade-report-grader div.gradeparent,
+.ie6 form,.grade-report-grader table#user-grades td.ajax {
text-align:left;
}
-.grade-report-grader table tr.avg td.cell,.grade-report-grader table#user-grades td.controls,.grade-report-grader table tr.avg,.grade-report-grader table tr.avg td,.grade-report-grader table tr.avg th {
+.grade-report-grader table tr.avg td.cell,
+.grade-report-grader table#user-grades td.controls,
+.grade-report-grader table tr.avg,
+.grade-report-grader table tr.avg td,
+.grade-report-grader table tr.avg th {
background-color:#f3ead8;
}
-.grade-report-grader div.left_scroller tr,.grade-report-grader div.right_scroller tr,.grade-report-grader div.left_scroller td,.grade-report-grader div.right_scroller td,.grade-report-grader div.left_scroller th,.grade-report-grader div.right_scroller th {
+.grade-report-grader div.left_scroller tr,
+.grade-report-grader div.right_scroller tr,
+.grade-report-grader div.left_scroller td,
+.grade-report-grader div.right_scroller td,
+.grade-report-grader div.left_scroller th,
+.grade-report-grader div.right_scroller th {
height:4.5em;
font-size:10px;
}
-.grade-report-grader table th.user,.grade-report-grader table td.useridnumber {
+.grade-report-grader table th.user,
+.grade-report-grader table td.useridnumber {
text-align:left;
vertical-align:middle;
}
-/*
.grade-report-grader .yui-overlay {
background-color: #FFEE69;
border-color: #D4C237 #A6982B #A6982B;
@@ -520,7 +565,6 @@ vertical-align:middle;
padding: 2px 5px;
font-size: 0.7em;
}
-*/
.grade-report-grader .yui-overlay .fullname {
color: #5F3E00;
@@ -577,4 +621,4 @@ position:absolute;
.ie6 .excludedfloater {
font-size:7px;
-}
+}
\ No newline at end of file
diff --git a/lang/en_utf8/gradereport_grader.php b/lang/en_utf8/gradereport_grader.php
index 52f5f859c44..462a1202db5 100644
--- a/lang/en_utf8/gradereport_grader.php
+++ b/lang/en_utf8/gradereport_grader.php
@@ -1,5 +1,10 @@
-text = $cell;
- $this->prepare_event_handlers($mycell);
$cell = $mycell;
}
+ // Prepare all events handlers for this cell
+ $this->prepare_event_handlers($cell);
+
if (isset($table->colclasses[$key])) {
$cell->add_classes(array_unique(html_component::clean_classes($table->colclasses[$key])));
}
@@ -2060,7 +2062,7 @@ class core_renderer extends renderer_base {
* @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
* @return string the HTML to output.
*/
- public function skip_link_target($id = '') {
+ public function skip_link_target($id = null) {
return html_writer::tag('span', array('id' => $id), '');
}
@@ -2072,7 +2074,7 @@ class core_renderer extends renderer_base {
* @param string $id An optional ID
* @return string the HTML to output.
*/
- public function heading($text, $level = 2, $classes = 'main', $id = '') {
+ public function heading($text, $level = 2, $classes = 'main', $id = null) {
$level = (integer) $level;
if ($level < 1 or $level > 6) {
throw new coding_exception('Heading level must be an integer between 1 and 6.');
@@ -2088,7 +2090,7 @@ class core_renderer extends renderer_base {
* @param string $id An optional ID
* @return string the HTML to output.
*/
- public function box($contents, $classes = 'generalbox', $id = '') {
+ public function box($contents, $classes = 'generalbox', $id = null) {
return $this->box_start($classes, $id) . $contents . $this->box_end();
}
@@ -2098,7 +2100,7 @@ class core_renderer extends renderer_base {
* @param string $id An optional ID
* @return string the HTML to output.
*/
- public function box_start($classes = 'generalbox', $id = '') {
+ public function box_start($classes = 'generalbox', $id = null) {
$this->opencontainers->push('box', html_writer::end_tag('div'));
return html_writer::start_tag('div', array('id' => $id,
'class' => 'box ' . renderer_base::prepare_classes($classes)));
@@ -2119,7 +2121,7 @@ class core_renderer extends renderer_base {
* @param string $id An optional ID
* @return string the HTML to output.
*/
- public function container($contents, $classes = '', $id = '') {
+ public function container($contents, $classes = null, $id = null) {
return $this->container_start($classes, $id) . $contents . $this->container_end();
}
@@ -2129,7 +2131,7 @@ class core_renderer extends renderer_base {
* @param string $id An optional ID
* @return string the HTML to output.
*/
- public function container_start($classes = '', $id = '') {
+ public function container_start($classes = null, $id = null) {
$this->opencontainers->push('container', html_writer::end_tag('div'));
return html_writer::start_tag('div', array('id' => $id,
'class' => renderer_base::prepare_classes($classes)));
@@ -2286,7 +2288,7 @@ class core_renderer_cli extends core_renderer {
* @param string $id An optional ID
* @return string A template fragment for a heading
*/
- public function heading($text, $level, $classes = 'main', $id = '') {
+ public function heading($text, $level, $classes = 'main', $id = null) {
$text .= "\n";
switch ($level) {
case 1: