diff --git a/grade/report/grader/ajax.php b/grade/report/grader/ajax.php deleted file mode 100644 index 18c7d1cfc30..00000000000 --- a/grade/report/grader/ajax.php +++ /dev/null @@ -1,582 +0,0 @@ - diff --git a/grade/report/grader/ajaxlib.php b/grade/report/grader/ajaxlib.php index 6f19b73d424..ab3b98cefef 100644 --- a/grade/report/grader/ajaxlib.php +++ b/grade/report/grader/ajaxlib.php @@ -25,6 +25,7 @@ require_once($CFG->dirroot . '/grade/report/grader/lib.php'); * Class providing an API for the grader report building and displaying. * @uses grade_report * @package gradebook + * @todo MDL-21562 Look at this class + its methods and try to work out what is still required */ class grade_report_grader_ajax extends grade_report_grader { @@ -131,6 +132,7 @@ class grade_report_grader_ajax extends grade_report_grader { /** * Builds and return the HTML rows of the table (grades headed by student). + * @todo MDL-21562 Is this still used anywhere * @return string HTML */ function get_studentshtml() { @@ -154,6 +156,9 @@ class grade_report_grader_ajax extends grade_report_grader { /** * Given a userid, and provided the gtree is correctly loaded, returns a complete HTML row for this user. * + * @todo MDL-21562 Apparently not used anywhere please check + * @todo MDL-21562 Calls to JavaScript function `set_row` will no longer work + * and need to be replaced * @param object $user * @return string */ diff --git a/grade/report/grader/functions.js b/grade/report/grader/functions.js deleted file mode 100755 index 8b0e2adfd6d..00000000000 --- a/grade/report/grader/functions.js +++ /dev/null @@ -1,32 +0,0 @@ -/** highlight/unset the row of a table **/ -function set_row(idx) { - var table = document.getElementById('user-grades'); - var rowsize = table.rows[idx].cells.length; - for (var i = 0; i < rowsize; i++) { - if (table.rows[idx].cells[i]) { - if (table.rows[idx].cells[i].className.search(/hmarked/) != -1) { - table.rows[idx].cells[i].className = table.rows[idx].cells[i].className.replace(' hmarked', ''); - } else { - table.rows[idx].cells[i].className += ' hmarked'; - } - } - } -} - -function yui_set_row(e) { - set_row(this.parentNode.rowIndex); -} - -/** highlight/unset the column of a table **/ -function set_col(idx) { - var table = document.getElementById('user-grades'); - for (var i = 1; i < table.rows.length; i++) { - if (table.rows[i].cells[idx]) { - if (table.rows[i].cells[idx].className.search(/vmarked/) != -1) { - table.rows[i].cells[idx].className = table.rows[i].cells[idx].className.replace(' vmarked', ''); - } else { - table.rows[i].cells[idx].className += ' vmarked'; - } - } - } -} diff --git a/grade/report/grader/grader.js b/grade/report/grader/grader.js deleted file mode 100644 index 38c61c55496..00000000000 --- a/grade/report/grader/grader.js +++ /dev/null @@ -1,141 +0,0 @@ -YAHOO.namespace("graderreport"); - -YAHOO.graderreport.init = function() { - // attach event listener to the table for mouseover and mouseout - var table = document.getElementById('user-grades'); - YAHOO.util.Event.on(table, 'mouseover', YAHOO.graderreport.mouseoverHandler); - YAHOO.util.Event.on(table, 'mouseout', YAHOO.graderreport.mouseoutHandler); - - // Make single panel that can be dynamically re-rendered wit the right data. - YAHOO.graderreport.panelEl = new YAHOO.widget.Panel("tooltipPanel", { - - draggable: false, - visible: false, - close: false, - preventcontextoverlap: true, - underlay: 'none' - }); - - YAHOO.graderreport.panelEl.render(table); - -}; - -YAHOO.graderreport.mouseoverHandler = function (e) { - - var tempNode = ''; - var searchString = ''; - var tooltipNode = ''; - - // get the element that we just moved the mouse over - var elTarget = YAHOO.util.Event.getTarget(e); - - // if it was part of the yui panel, we don't want to redraw yet - searchString = /fullname|itemname|feedback/; - if (elTarget.className.search(searchString) > -1) { - return false; - } - - // move up until we are in the actual cell, not any other child div or span - while (elTarget.id != 'user-grades') { - if(elTarget.nodeName.toUpperCase() == "TD") { - break; - } else { - elTarget = elTarget.parentNode; - } - } - - // only make a tooltip for cells with grades - if (elTarget.className.search('grade cell') > -1) { - - // each time we go over a new cell, we need to put it's tooltip into a div to stop it from - // popping up on top of the panel. - - // don't do anything if we have already made the tooltip div - var makeTooltip = true; - for (var k=0; k < elTarget.childNodes.length; k++) { - if (typeof(elTarget.childNodes[k].className) != 'undefined') { - if (elTarget.childNodes[k].className.search('tooltipDiv') > -1) { - makeTooltip = false; - } - } - } - - // if need to, make the tooltip div and append it to the cell - if (makeTooltip) { - - tempNode = document.createElement("div"); - tempNode.className = "tooltipDiv"; - tempNode.innerHTML = elTarget.title; - elTarget.appendChild(tempNode); - elTarget.title = null; - } - - // Get the tooltip div - elChildren = elTarget.childNodes; - for (var m=0; m < elChildren.length; m++) { - if (typeof(elChildren[m].className) != 'undefined') { - if (elChildren[m].className.search('tooltipDiv') > -1) { - tooltipNode = elChildren[m]; - break; - } - } - } - //build and show the tooltip (if not empty) - if(tooltipNode.innerHTML) - { - YAHOO.graderreport.panelEl.setBody(tooltipNode.innerHTML); - YAHOO.graderreport.panelEl.render(elTarget); - YAHOO.graderreport.panelEl.show(); - } - } -}; - -// only hide the overlay if the mouse has not moved over it -YAHOO.graderreport.mouseoutHandler = function (e) { - - var classVar = ''; - var searchString = ''; - var newTargetClass = ''; - var newTarget = YAHOO.util.Event.getRelatedTarget(e); - - // deals with an error if the mouseout event is over the lower scrollbar - try { - classVar = newTarget.className; - } catch (err) { - YAHOO.graderreport.panelEl.hide() - return false; - } - - // if we are over any part of the panel, do not hide - // do this by walking up the DOM till we reach table level, looking for panel tag - while ((typeof(newTarget.id) == 'undefined') || (newTarget.id != 'user-grades')) { - - try { - newTargetClass = newTarget.className; - } catch (err) { - // we've gone over the scrollbar again - YAHOO.graderreport.panelEl.hide() - return false; - } - searchString = /yui-panel|grade cell/; - if (newTargetClass.search(searchString) > -1) { - // we're in the panel so don't hide it - return false; - } - - if (newTarget.nodeName.toUpperCase() == "HTML") { - // we missed the user-grades table altogether by moving down off screen to read a long one - YAHOO.graderreport.panelEl.hide() - break; - } - - newTarget = newTarget.parentNode; - } - - // no panel so far and we went up to the - YAHOO.graderreport.panelEl.hide() - -}; - - -YAHOO.util.Event.onDOMReady(YAHOO.graderreport.init); diff --git a/grade/report/grader/index.php b/grade/report/grader/index.php index e1193c9c176..336afdedf67 100644 --- a/grade/report/grader/index.php +++ b/grade/report/grader/index.php @@ -110,17 +110,6 @@ $reportname = get_string('modulename', 'gradereport_grader'); // Initialise the grader report object $report = new grade_report_grader($courseid, $gpr, $context, $page, $sortitemid); -$PAGE->requires->yui2_lib('event'); -$PAGE->requires->yui2_lib('json'); -$PAGE->requires->yui2_lib('connection'); -$PAGE->requires->yui2_lib('dragdrop'); -$PAGE->requires->yui2_lib('element'); -$PAGE->requires->yui2_lib('container'); -$PAGE->requires->js('/grade/report/grader/functions.js'); -$PAGE->requires->js('/grade/report/grader/grader.js'); -$PAGE->requires->js('/lib/overlib/overlib.js', true); -$PAGE->requires->js('/lib/overlib/overlib_cssstyle.js', true); - if ($report->get_pref('enableajax')) { $report = new grade_report_grader_ajax($courseid, $gpr, $context, $page, $sortitemid); } @@ -172,7 +161,7 @@ if (!empty($studentsperpage)) { $reporthtml = $report->get_grade_table(); // print submit button -if ($USER->gradeediting[$course->id] && ($report->get_pref('showquickfeedback') || $report->get_pref('quickgrading')) && !$report->get_pref('enableajax')) { +if ($USER->gradeediting[$course->id] && ($report->get_pref('showquickfeedback') || $report->get_pref('quickgrading'))) { echo '
'; echo '
'; 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.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(''); + 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('')); + } + } +}; +/** + * 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: