Commit 8d7a0eab authored by Astor Bizard's avatar Astor Bizard
Browse files

Submissions list & VPLs index table sorting switched to front-end. Added max...

Submissions list & VPLs index table sorting switched to front-end. Added max grade & n° of automatic evaluations.
parent 88d9184b
define(["jquery"],function(a){return{makeSortable:function(b,c=[],d,e=0){var f;null===d&&(d=void 0);var g=a("#"+b+" tbody"),h=a("#"+b+" thead th"),i=h.length;h.each(function(){var b=h.index(this);-1==c.indexOf(b)&&-1==c.indexOf(b-i)&&a(this).append('<i class="icon fa fa-fw sortarrow"></i>')}),a("#"+b+" thead .sortarrow").each(function(){var b=a(this),c=b.closest("th"),d=h.index(c);c.css("cursor","pointer").css("user-select","none").click(function(){b.hasClass("sortarrow-active")?1==(f=-f)?b.removeClass("fa-caret-up").addClass("fa-caret-down"):b.removeClass("fa-caret-down").addClass("fa-caret-up"):(a("i.sortarrow-active").removeClass("sortarrow-active fa-caret-down fa-caret-up"),b.addClass("sortarrow-active fa-caret-down"),f=1);var c=1,h=Array.from(g.children("tr")),i=h.splice(-e,e);h.sort(function(b,c){var e=function(b){return a(b).find(".cell.c"+d+" [value]").attr("value")||a(b).find(".cell.c"+d).text()},g=e(b),h=e(c);return f*(""===g||""===h||isNaN(g)||isNaN(h)?g.toString().localeCompare(h):g-h)}).forEach(function(b){a(b).children("td").first().find(":not(:empty)").addBack(":not(:empty)").each(function(){/^[0-9]+$/.test(a(this).html())&&a(this).html(c)}),c++,g.append(b)}),i.forEach(function(a){g.append(a)})}),c.find("a").click(function(a){a.stopPropagation()})}).eq(d).click()}}});
\ No newline at end of file
define(['jquery'], function($){
/**
* Makes an html table sortable by creating clickable column headers and arrows.
* @param tableid The id of the table to make sortable.
* @param nosortcols (optional) An array of column indexes to exclude.
* Negative indexes can be specified to exclude columns counting from the last.
* @param defaultsortcol (optional) The index of the column to sort by default.
* This index is computed after excluding columns. Negative indexes are allowed.
* @param nexcludedlines (optional) The number of lines to ignore at the end of the table.
*/
function makeSortable(tableid, nosortcols=[], defaultsortcol, nexcludedlines=0){
var sortdirection;
if(defaultsortcol === null) {
defaultsortcol = undefined;
}
var table = $('#' + tableid + ' tbody');
var $ths = $('#' + tableid + ' thead th');
var nths = $ths.length;
$ths.each(function(){
// Create sorting arrows except for excluded columns.
var i = $ths.index(this);
if (nosortcols.indexOf(i) == -1 && nosortcols.indexOf(i - nths) == -1) {
$(this).append('<i class="icon fa fa-fw sortarrow"></i>');
}
});
// Setup sort arrows / headers.
$('#' + tableid + ' thead .sortarrow').each(function(){
var $arrow = $(this);
var $th = $arrow.closest('th');
var icol = $ths.index($th);
$th.css('cursor','pointer').css('user-select','none').click(function(){
if(!$arrow.hasClass('sortarrow-active')){
// Change of sorting column: remove old sorting column arrow and setup the new one.
$('i.sortarrow-active').removeClass('sortarrow-active fa-caret-down fa-caret-up');
$arrow.addClass('sortarrow-active fa-caret-down');
sortdirection = 1;
}
else {
// Change sorting direction and arrow display.
sortdirection = -sortdirection;
if (sortdirection == 1) {
$arrow.removeClass('fa-caret-up').addClass('fa-caret-down');
}
else {
$arrow.removeClass('fa-caret-down').addClass('fa-caret-up');
}
}
// Sort rows.
var num = 1;
var rows = Array.from(table.children('tr'));
var endrows = rows.splice(-nexcludedlines, nexcludedlines);
rows.sort(function(a, b){
// Sort according to 'value' attributes or inner text.
var getCellValue = function(tr){
return $(tr).find('.cell.c' + icol + ' [value]').attr('value') || $(tr).find('.cell.c' + icol).text();
};
var v1 = getCellValue(a);
var v2 = getCellValue(b);
return sortdirection *
(v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2));
})
.forEach(function(tr){
// Renumber first column.
var $firstcol = $(tr).children('td').first();
$firstcol.find(':not(:empty)').addBack(':not(:empty)').each(function(){
if(/^[0-9]+$/.test($(this).html())){
$(this).html(num);
}
});
num++;
// Re-insert row inside table.
table.append(tr);
});
// Re-insert excluded rows at the end.
endrows.forEach(function(tr){
table.append(tr);
});
});
// Do not sort upon click on header link.
$th.find('a').click(function(e){
e.stopPropagation();
});
}).eq(defaultsortcol).click();
}
return {
makeSortable : makeSortable
};
});
\ No newline at end of file
......@@ -147,7 +147,7 @@ if ($subinstance->dategraded == 0 || $subinstance->grader == $USER->id || $subin
// Change grade info at parent window.
$text = $submission->get_grade_core();
$grader = fullname( $submission->get_grader( $USER->id ) );
$gradedon = userdate( $submission->get_instance()->dategraded );
$gradedon = $submission->get_gradedon_date_formatted();
$jscript .= 'VPL.updatesublist(' . $submission->get_instance()->id . ',';
$jscript .= '\'' . addslashes( $text ) . '\',';
$jscript .= '\'' . addslashes( $grader ) . '\',';
......
......@@ -25,33 +25,21 @@
require_once(dirname(__FILE__).'/../../config.php');
require_once(dirname(__FILE__).'/locallib.php');
require_once(dirname(__FILE__).'/list_util.class.php');
require_once(dirname(__FILE__).'/vpl_submission.class.php');
$id = required_param( 'id', PARAM_INT ); // Course id.
$instanceselection = optional_param( 'selection', 'all', PARAM_RAW );
$sort = vpl_get_set_session_var( 'sort', '' );
$sortdir = vpl_get_set_session_var( 'sortdir', 'down' );
$instanceselection = vpl_get_set_session_var( 'selection', 'all' );
global $DB, $PAGE, $OUTPUT, $USER;
// Check course existence.
if (! $course = $DB->get_record( "course", array ( 'id' => $id ) )) {
print_error( 'invalidcourseid', '', $id );
}
require_course_login( $course );
// Load strings.
$burl = vpl_rel_url( basename( __FILE__ ), 'id', $id );
$strname = get_string( 'name' ) . ' ';
$strname .= vpl_list_util::vpl_list_arrow( $burl, 'name', $instanceselection, $sort, $sortdir );
$strvpls = get_string( 'modulenameplural', VPL );
$strshortdescription = get_string( 'shortdescription', VPL ) . ' ';
$strshortdescription .= vpl_list_util::vpl_list_arrow( $burl, 'shortdescription', $instanceselection, $sort, $sortdir );
$strstartdate = get_string( 'startdate', VPL ) . ' ';
$strstartdate .= vpl_list_util::vpl_list_arrow( $burl, 'startdate', $instanceselection, $sort, $sortdir );
$strduedate = get_string( 'duedate', VPL ) . ' ';
$strduedate .= vpl_list_util::vpl_list_arrow( $burl, 'duedate', $instanceselection, $sort, $sortdir );
$strnopls = get_string( 'novpls', VPL );
$PAGE->set_url( '/mod/vpl/index.php', array ( 'id' => $id ) );
$PAGE->navbar->add( $strvpls );
......@@ -65,11 +53,8 @@ $event = \mod_vpl\event\course_module_instance_list_viewed::create( $einfo );
$event->trigger();
// Print selection by instance state.
$urlbase = new moodle_url( '/mod/vpl/index.php', array (
'id' => $id,
'sort' => $sort,
'sortdir' => $sortdir
'id' => $id
) );
$urls = array ();
$urlindex = array ();
......@@ -92,9 +77,8 @@ foreach (array (
}
echo $OUTPUT->url_select( $urls, $urlindex [$instanceselection], array () );
if (! $cms = get_coursemodules_in_course( VPL, $course->id, "m.shortdescription, m.startdate, m.duedate" )) {
vpl_redirect( vpl_abs_href( '/course/view.php', 'id', $course->id ),
$strnopls);
if (empty(get_coursemodules_in_course( VPL, $course->id, "m.shortdescription, m.startdate, m.duedate" ))) {
vpl_redirect( vpl_abs_href( '/course/view.php', 'id', $course->id ), get_string( 'novpls', VPL ) );
die();
}
$ovpls = get_all_instances_in_course( VPL, $course );
......@@ -178,23 +162,13 @@ foreach ($vpls as $vpl) {
$grader = $grader && ! $nograde;
$student = $student && ! $nograde;
// The usort of old PHP versions don't call static class functions.
if ($sort > '') {
$corder = new vpl_list_util();
$corder->set_order( $sort, $sortdir == 'down' );
usort( $vpls, array (
$corder,
'cpm'
) );
}
// Generate table.
$table = new html_table();
$table->attributes ['class'] = 'generaltable mod_index';
$table->head = array (
'#',
$strname,
$strshortdescription
get_string( 'name' ),
get_string( 'shortdescription', VPL )
);
$table->align = array (
'left',
......@@ -202,11 +176,11 @@ $table->align = array (
'left'
);
if ($startdate) {
$table->head [] = $strstartdate;
$table->head [] = get_string( 'startdate', VPL );
$table->align [] = 'center';
}
if ($duedate) {
$table->head [] = $strduedate;
$table->head [] = get_string( 'duedate', VPL );
$table->align [] = 'center';
}
if ($grader && ! $nograde) {
......@@ -239,7 +213,7 @@ foreach ($vpls as $vpl) {
if ($grader) {
if ($vpl->has_capability( VPL_GRADE_CAPABILITY )
&& $vpl->get_grade() != 0 && ! $instance->example) {
$info = vpl_list_util::count_graded( $vpl );
$info = $vpl->count_graded();
$totalsubs += $info ['submissions'];
$totalgraded += $info ['graded'];
$url = vpl_rel_url( 'views/submissionslist.php', 'id', $vpl->get_course_module()->id, 'selection', 'allsubmissions' );
......@@ -298,23 +272,21 @@ foreach ($vpls as $vpl) {
$table->data [] = $row;
}
if ($totalsubs > 0) {
$row = array ('', '', '');
if ($startdate) {
$row [] = '';
}
if ($duedate) {
$row [] = '';
}
end( $row );
$row [key( $row )] = get_string( 'total' );
$row = array_fill(0, count($table->head) - 2, '');
$row [1] = get_string( 'total' );
$row [] = $totalsubs;
$row [] = $totalgraded;
$table->data [] = array_fill(0, count($table->head), '');
$table->data [] = $row;
$extrarows = 2;
} else {
$extrarows = 0;
}
$table->id = 'vplindex';
$PAGE->requires->js_call_amd('mod_vpl/sorttable', 'makeSortable', array('vplindex', array(0, 2), null, $extrarows));
echo "<br />";
echo html_writer::table( $table );
$url = new moodle_url( '/mod/vpl/views/checkvpls.php', array ('id' => $id) );
$string = get_string( 'checkall' );
echo html_writer::link($url, $string, array('class' => 'btn btn-secondary'));
echo html_writer::link($url, get_string( 'checkgroups', VPL ), array('class' => 'btn btn-secondary'));
echo $OUTPUT->footer();
......@@ -70,10 +70,24 @@
window.close();
}
var ssubid = "" + subid;
var divnext = opener.document.getElementById('n' + ssubid);
if (divnext) {
location.replace(url + divnext.innerHTML);
}else{
var divgrade = opener.document.getElementById('g' + ssubid);
while (divgrade.parentNode.nodeName !== 'TR' && divgrade.parentNode !== null) {
divgrade = divgrade.parentNode;
}
if (divgrade.parentNode === null) {
window.close();
return;
}
var nextrow = divgrade.parentNode.nextSibling;
if (nextrow === null) {
window.close();
return;
}
var rowhtml = nextrow.innerHTML;
var nextid = rowhtml.match(/user\/view\.php\?(.*?)id=([0-9]+)/)[2];
if (nextid) {
location.replace(url + nextid);
} else {
window.close();
}
};
......
......@@ -90,6 +90,7 @@ $string ['evaluate'] = 'Evaluate';
$string ['evaluateonsubmission'] = 'Evaluate just on submission';
$string ['evaluating'] = 'Evaluating';
$string ['evaluation'] = 'Evaluation';
$string ['evaluations'] = 'Automatic evaluations';
$string ['examples'] = 'Examples';
$string ['execution'] = 'Execution';
$string ['executionfiles'] = 'Execution files';
......@@ -169,6 +170,7 @@ $string ['maxfiles'] = 'Maximum number of files';
$string ['maxfilesexceeded'] = 'Maximum number of files exceeded';
$string ['maxfilesize'] = 'Maximum upload file size';
$string ['maxfilesizeexceeded'] = 'Maximum file size exceeded';
$string ['maxgradeobtained'] = 'Max grade obtained';
$string ['maximumperiod'] = 'Maximum period {$a}';
$string ['maxresourcelimits'] = 'Maximum execution resources limits';
$string ['maxsimilarityoutput'] = 'Maximum output by similarity';
......@@ -263,6 +265,7 @@ $string ['submission'] = 'Submission';
$string ['submissionperiod'] = 'Submission period';
$string ['submissionrestrictions'] = 'Submission restrictions';
$string ['submissions'] = 'Submissions';
$string ['submissionscharts'] = 'Submissions charts';
$string ['submissionselection'] = 'Submission selection';
$string ['submissionslist'] = 'Submissions list';
$string ['submissionview'] = 'Submission view';
......
......@@ -26,7 +26,6 @@
defined('MOODLE_INTERNAL') || die();
require_once(dirname(__FILE__).'/locallib.php');
require_once(dirname(__FILE__).'/list_util.class.php');
require_once($CFG->dirroot.'/course/lib.php');
/**
......@@ -499,10 +498,11 @@ function mod_vpl_get_fontawesome_icon_map() {
'mod_vpl:submissionslist' => 'fa-list-ul',
'mod_vpl:loading' => 'fa-spinner fa-pulse',
'mod_vpl:copy' => 'fa-copy',
'mod_vpl:submissions' => 'fa-bar-chart',
'mod_vpl:submissionscharts' => 'fa-bar-chart',
'mod_vpl:gradercomments' => 'fa-check-square',
'mod_vpl:downloadsubmissions' => 'fa-cloud-download',
'mod_vpl:downloadallsubmissions' => 'fa-history',
'mod_vpl:group' => 'fa-group',
];
}
......
<?php
// This file is part of VPL for Moodle - http://vpl.dis.ulpgc.es/
//
// VPL for Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// VPL for Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with VPL for Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* List utility class
*
* @package mod_vpl
* @copyright 2012 Juan Carlos Rodríguez-del-Pino
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Juan Carlos Rodríguez-del-Pino <jcrodriguez@dis.ulpgc.es>
*/
defined( 'MOODLE_INTERNAL' ) || die();
class vpl_list_util {
static protected $fields; // Field to compare.
static protected $ascending; // Value to return when ascending or descending order.
// Compare two submission fields.
static public function cpm($avpl, $bvpl) {
$a = $avpl->get_instance();
$b = $bvpl->get_instance();
foreach (self::$fields as $field) {
$avalue = $a->$field;
$bvalue = $b->$field;
if ($avalue == $bvalue) {
continue;
} else if ($avalue < $bvalue) {
return self::$ascending;
} else {
return - self::$ascending;
}
}
return 0;
}
/**
* Check and set data to sort return comparation function $field field to compare $descending order
*/
static public function set_order($field, $ascending = true) {
$sortfields = array (
'name' => array (
'name'
),
'shortdescription' => array (
'shortdescription'
),
'startdate' => array (
'startdate',
'duedate',
'name'
),
'duedate' => array (
'duedate',
'startdate',
'name'
),
'automaticgrading' => array (
'automaticgrading',
'duedate',
'name'
)
);
if (isset( $sortfields [$field] )) {
self::$fields = $sortfields [$field];
} else { // Unknow field.
self::$fields = $sortfields ['duedate'];
}
if ($ascending) {
self::$ascending = - 1;
} else {
self::$ascending = 1;
}
}
static public function vpl_list_arrow($burl, $sort, $instanceselection, $selsort, $seldir) {
global $OUTPUT;
$newdir = 'down'; // Dir to go if clicked.
$url = vpl_url_add_param( $burl, 'sort', $sort );
$url = vpl_url_add_param( $url, 'selection', $instanceselection );
if ($sort == $selsort) {
$sortdir = $seldir;
if ($sortdir == 'up') {
$newdir = 'down';
} else if ($sortdir == 'down') {
$newdir = 'up';
} else { // Unknow sortdir.
$sortdir = 'down';
}
$url = vpl_url_add_param( $url, 'sortdir', $newdir );
} else {
$sortdir = 'move';
}
return '<a href="' . $url . '">' . ($OUTPUT->pix_icon( 't/' . $sortdir, get_string( $sortdir ) )) . '</a>';
}
// Count submissions graded.
static public function count_graded($vpl) {
$numsubs = 0;
$numgraded = 0;
$subs = $vpl->all_last_user_submission( 's.dategraded, s.userid' );
if ($vpl->is_group_activity()) { // Fixes group activity userid.
foreach ($subs as $sub) {
$group = $vpl->get_group_members($sub->groupid);
if ( count($group) ) {
$user = reset($group);
$sub->userid = $user->id;
}
}
}
$students = $vpl->get_students();
foreach ($subs as $sub) {
if (isset( $students [$sub->userid] )) {
$numsubs ++;
if ($sub->dategraded > 0) { // Is graded.
$numgraded ++;
}
}
}
return array (
'submissions' => $numsubs,
'graded' => $numgraded
);
}
}
This diff is collapsed.
......@@ -1314,6 +1314,20 @@ class mod_vpl {
return $this->instance->grade;
}
public function get_max_grades() {
global $DB;
if ( $this->is_group_activity() ) {
$field = 'groupid';
} else {
$field = 'userid';
}
$id = $this->get_instance()->id;
$query = "SELECT $field, max({vpl_submissions}.grade) as maxgrade FROM {vpl_submissions}";
$query .= " WHERE {vpl_submissions}.vpl=$id and ({vpl_submissions}.grade is not null)";
$query .= " GROUP BY {vpl_submissions}.$field";
return $DB->get_records_sql( $query );
}
/**
* Format given grade as grade/maxgrade
* @param float $grade
......@@ -1969,4 +1983,33 @@ class mod_vpl {
}
return $ret;
}
public function count_graded() {
$numsubs = 0;
$numgraded = 0;
$subs = $this->all_last_user_submission( 's.dategraded, s.userid' );
if ($this->is_group_activity()) { // Fixes group activity userid.
foreach ($subs as $sub) {
$group = $this->get_group_members($sub->groupid);
if ( count($group) ) {
$user = reset($group);
$sub->userid = $user->id;
}
}
}
$students = $this->get_students();
foreach ($subs as $sub) {
if (isset( $students [$sub->userid] )) {
$numsubs ++;
if ($sub->dategraded > 0) { // Is graded.
$numgraded ++;
}
}
}
return array (
'submissions' => $numsubs,
'graded' => $numgraded
);
}
}
......@@ -718,7 +718,7 @@ class mod_vpl_submission {
if ($this->vpl->get_grade() != 0) {
$ret .= $this->vpl->str_restriction(get_string('grade'), $this->get_grade_core(), true) . '<br>';
if ($detailed) {
$ret .= $this->reduce_grade_string() . '<br>';
$ret .= $this->reduce_grade_string() . '<br>';
$feedback = $this->get_grade_comments();
if ($feedback) {
$div = new vpl_hide_show_div( true );
......@@ -1291,4 +1291,15 @@ class mod_vpl_submission {
}
return $params;
}
public function get_gradedon_date_formatted() {
$date = $this->instance->dategraded;
if ($date > 0) {
$gradedon = userdate( $date, get_string('strftimedatetimeshort', 'langconfig') );
} else {
$gradedon = '';
}
$fullgradedondate = userdate( $date, get_string('strftimedaydatetime', 'langconfig') );
return '<div value="' . $date . '" title="'.$fullgradedondate.'">'. $gradedon. '</div>';
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment