An error occurred while loading the file. Please try again.
-
Владислав Яковлев authored2db44224
Forked from
it-lab / grade
Source project has a limited visibility.
common.js 14.87 KiB
let doubleNumReg = /.*_(\d+)_(\d+)$/;
let singleNumReg = /\w_(\d+)$/;
class Base {
static parseID($this) {
singleNumReg.lastIndex = 0;
return +singleNumReg.exec($this.attr('id'))[1];
}
static parsePosition($this) {
doubleNumReg.lastIndex = 0;
return doubleNumReg.exec($this.attr('id')).splice(1, 2);
}
static parseSettings() {
let $hiddenInfo = $('#hidden_div');
let $disciplineID = $('#disciplineID');
let $pageType = $('#pageType');
let settings = $.parseJSON($hiddenInfo.html());
settings.disciplineID = $disciplineID.val();
settings.pageType = $pageType.val();
$hiddenInfo.remove();
$disciplineID.remove();
$pageType.remove();
return settings;
}
}
class Cursor {
constructor() {
this.col = 0;
this.row = 0;
}
update(col, row) {
this.col = col;
this.row = row;
}
get() {
return [this.col, this.row];
}
parseLocation($this) {
let [col, row] = Base.parsePosition($this);
this.update(+col, +row);
}
isEmpty() {
return (this.col == 0) && (this.row == 0);
}
}
class Cell {
constructor() {
this.update(0, 0, 0);
}
update(student, submodule, maxRate) {
this.student = student;
this.submodule = submodule;
this.maxRate = maxRate;
}
}
class RateInfo {
constructor(settings, cursor, cell) {
this.settings = settings;
this.cursor = cursor;
this.cell = cell;
this.$submodulesInfo = $('.RatingTableSubmodulesInfo');
this.$submodulesHead = $('.RatingTableSubmodulesHead');
this.$submodulesMaxRate = $('.RatingTableSubmodulesHeadMaxRate');
this.$modulesHead = $('.RatingTableModulesHead');
this.$tdInfoWrap = $('#tdInfo_wrap');
this.$tdInfo = this.$tdInfoWrap.children('#tdInfo');
this.$studentInfo = this.$tdInfo.children('#student').children('b');
this.$submoduleInfo = this.$tdInfo.children('#submodule').children('b');
this.$rateInfo = this.$tdInfo.children('#maxRate').children('b');
}
show($this) {
var disciplinePassRate = (this.settings.Type === "exam") ? 38 : 60;
var [col] = this.cursor.get();
// Получаем подмодуль
var jCurSubmoduleInfo = this.$submodulesInfo.children(`.col_${col}:first`);
var jCurSubmoduleHead = this.$submodulesHead.children(`.col_${col}:first`);
var submodule = +jCurSubmoduleInfo.attr('id');
var student = parseID($this.siblings('.studentCell'));
var maxRate = +this.$submodulesMaxRate.children(`.col_${col}`).text();
// Проверяем допустимое значение (только для добора)
if ($this.hasClass('additionalCell')) {
var semesterRate = parseInt($this.siblings('.semesterRateResultCell').text());
maxRate = (semesterRate <= disciplinePassRate) ? disciplinePassRate - semesterRate : 0;
}
this.cell.update(student, submodule, maxRate);
let submoduleTitle = (jCurSubmoduleHead.length < 1 && this.$modulesHead.children('.bonus').length > 0)
? 'Бонусные баллы'
: (jCurSubmoduleHead.length == 0) ? 'Добор баллов' : jCurSubmoduleHead.text(); // todo: убрать костыль
let stdName = $this.siblings('.studentCell').text();
this.$tdInfoWrap.show();
this.$studentInfo.html(stdName);
this.$submoduleInfo.html(submoduleTitle);
this.$rateInfo.html(this.cell.maxRate);
};
hide() {
this.$tdInfoWrap.hide();
this.cell.update(0, 0, 0);
};
}
const defaultCellBackground = '#fff';
const focusCellBackground = '#f1f1f1';
class Highlighting {
constructor(settings, cursor) {
this.settings = settings;
this.cursor = cursor;
}
static setRowBackground(color, [col, row]) {
let $cols = $(`td#col_${col}`);
let $rows = $(`tr#row_${row}`);
let $rowStatic = $rows.find('.staticCell');
$cols.filter('.commonCell')
.add($cols.filter('.staticCell'))
.add($rows.find('.commonCell'))
.add($rowStatic)
.children('input')
.add($rowStatic)
.each((_, elem) => $(elem).css('background-color', color));
}
// Ставим подстветку
focus($this) {
this.cursor.parseLocation($this);
Highlighting.setRowBackground(focusCellBackground, this.cursor.get());
$this.children('input').css('background-color', defaultCellBackground);
};
// Убираем подстветку
focusOut() {
Highlighting.setRowBackground(defaultCellBackground, this.cursor.get());
}
}
class GroupFilter {
constructor(settings, $table, groupID) {
this.settings = settings;
this.$table = $table.children('tbody');
this.groupSelected = +groupID;
}
// Скрываем все остальные группы
// 0 - показать все
filterGroups(groupID) {
let $trs = this.$table.children().slice(3);
$trs.show();
if (+groupID !== 0)
$trs.not(`.group_${groupID}`).hide();
}
listen($selector) {
$selector.children('[value=' + this.groupSelected + ']').prop('selected', true);
this.filterGroups(this.groupSelected);
let self = this;
$selector.change(function () {
self.groupSelected = +$(this).val();
if (self.groupSelected >= 0) {
self.filterGroups(self.groupSelected);
let disciplineID = self.settings.disciplineID;
let groupSelected = self.groupSelected;
$.post(URLdir + 'handler/rating/SelectGroup', {disciplineID, groupSelected});
}
});
}
}
class TableAdjuster {
constructor($wrapper, $table) {
this.$wrapper = $wrapper;
this.$table = $table;
}
adjust() {
let tableWidth = this.$table.get(0).scrollWidth;
// check scroll bar
if (tableWidth <= this.$table.width() + 10)
return;
//correct
tableWidth *= 1.1;
let maxWidth = $(window).width() * 0.95;
let newWidth = (tableWidth > maxWidth) ? maxWidth : tableWidth;
this.$wrapper.css('max-width', newWidth);
};
autoResize() {
$(window).resize(() => this.adjust());
}
}
function Rating() {
let settings = Base.parseSettings();
let cell = new Cell();
let cursor = new Cursor();
let rateInfo = new RateInfo(settings, cursor, cell);
let highlighter = new Highlighting(settings, cursor);
let adjuster = new TableAdjuster($('.main_layer'), $('.main_content'));
let filter = new GroupFilter(settings, $('.studentsRate'), settings['GroupID_Filter']);
const Direction = {
Up: 0,
Right: 1,
Down: 2,
Left: 3
};
/**
* @param direction Direction в каком направлении искать следующую ячейку для перемещения фокуса
* @return jQuery клетка, в которую надо переместиться или пустой jquery list, если в этом направлении нет подходящих ячеек
*/
let getDesiredCell = function (direction) {
let [col, row] = cursor.get();
let dx = (direction == Direction.Left) ? -1
: (direction == Direction.Right) ? 1 : 0;
let dy = (direction == Direction.Up) ? -1
: (direction == Direction.Down) ? 1 : 0;
let currentCell;
do {
row += dy;
col += dx;
currentCell = $(`#col_row_${col}_${row}`);
} while (currentCell.length > 0 && currentCell.children('input').attr('disabled') === 'disabled');
return currentCell;
};
let isFocusCell = false; // Стоит фокус на ячейке или нет
let oldRate = 0;
var cancelFlag = false;
let initGeneralCells = function (finalize) {
const keyDirs = {
27: Direction.Down, // esc
13: Direction.Down, // enter
40: Direction.Down, // down arrow
38: Direction.Up, // up arrow
39: Direction.Right, // right arrow
37: Direction.Left // left arrow
};
$('.studentsRate tbody')
.on('mouseenter', '.commonCell', function() {
if (isFocusCell === false)
highlighter.focus($(this));
})
.on('mouseleave', '.commonCell', function () {
if (isFocusCell === false)
highlighter.focusOut();
})
.on('focusin', '.commonCell', function () {
isFocusCell = true;
highlighter.focus($(this));
rateInfo.show($(this));
var value = $(this).children('input').val();
oldRate = (value !== '') ? +value : -1;
})
.on('focusout', '.commonCell', function () {
isFocusCell = false;
if (cancelFlag) {
var str = (oldRate != -1) ? oldRate : '';
$(this).children('input').val(str);
cancelFlag = false;
} else
setRate($(this), +oldRate);
highlighter.focusOut();
rateInfo.hide($(this));
finalize($(this));
})
.on('keydown', '.commonCell input', keyDownOnlyNumber) // В inputCredit (где баллы вводить) разрешаем вводить только цифры
.on('keydown', '.commonCell', function (e) {
cancelFlag = cancelFlag || +e.keyCode === 27; // escape
if (!(e.keyCode in keyDirs)) //
return;
let [col, row] = cursor.get();
let direction = keyDirs[e.keyCode];
let whereToMoveFocus = getDesiredCell(direction);
if (whereToMoveFocus.length > 0) {
highlighter.focusOut();
cursor.update(col, row);
whereToMoveFocus.children('input').focus();
} else
$(this).children('input').blur();
});
};
var postRate = function (rate, $this, oldRate, rateResult, studentID, submoduleID) {
const disciplineID = settings.disciplineID;
let [col, row] = cursor.get();
$.postJSON(URLdir + 'handler/rating/setRate', {studentID, disciplineID, submoduleID, rate})
.success(() => {
let correctRate = (rateResult > 100) ? '100+' : rateResult;
$this.siblings('.rateResultCell').text(correctRate);
// Открываем доступ к след. ячейке добора баллов
if ($this.hasClass('additionalCell')) {
let nextAdditionalCell = $(`#col_row_${col + 1}_${row}`);
let placeholderMaxVal = (rateResult < 60) ? (60 - rateResult) : 0;
if (nextAdditionalCell.hasClass('additionalCell')) {
let placeholderMax = (placeholderMaxVal > 0) ? 'макс. ' + placeholderMaxVal : '---';
nextAdditionalCell.find('input').attr('placeholder', placeholderMax);
}
}
Popup.success('Балл добавлен/изменен');
}).fail((jqXHR) => {
$this.children('input').val(oldRate != -1 ? oldRate : '');
const response = JSON.parse(jqXHR.responseText);
let status = +jqXHR.status;
let errorMsg = 'Не удалось добавить/изменить балл';
switch (status) {
case 400: errorMsg = response.message; break;
case 403: errorMsg = 'Сессия истекла'; break;
case 500:
default: // ничего не делать
break;
}
Popup.error(errorMsg);
if (status == 403)
window.location.replace(URLdir);
});
};
// todo: merge "if" branches
function recountScores(newRate, $this) {
let rateResult = Math.max(0, newRate);
let bonus = +$this.siblings('.bonus').text();
// считаем баллы по строке
if (settings.pageType === 'exam') //(jThis.attr('class').indexOf('attemptCell') >= 0)
{
// страница сессии
rateResult += +$this.siblings('.semesterRateResultCell').text();
rateResult += $this.siblings('.additionalCell').aggregate((init, elem) => +$(elem).children('input').val() + init);
if (newRate === -1)
rateResult += $this.siblings('.attemptCell').not('.autoPass').not('.absenceCell')
.aggregate((init, elem) => Math.max(init, +$(elem).find('input').val()));
} else {
$this.siblings('.commonCell').each(function () { // добавим сумму баллов в соседних ячейках
rateResult += +$(this).children('input').val() || 0;
});
let extraRate = +$this.siblings('.extraCell').text() || 0;
let examRate = +$this.siblings('.examCell').text() || 0;
rateResult += extraRate + examRate;
}
return rateResult + (bonus || 0);
}
/**
* @param {jQuery} $this
* @param {int} oldRate
*/
function setRate($this, oldRate) {
let $scoreInput = $this.children('input');
let inputVal = $scoreInput.val();
// если пустая строка в ячейке, значит ничего не поставлено
let newRate = (inputVal === '') ? -1 : +inputVal;
if (newRate === oldRate)
return;
// блокируем ячейку пока не обработаем коллбек
$scoreInput.turnOff();
var rateResult = recountScores(newRate, $this);
if (newRate <= cell.maxRate)
postRate(newRate, $this, oldRate, rateResult, cell.student, cell.submodule);
else {
let cellRate = (oldRate > cell.maxRate) ? '0' :
(oldRate === -1 ? '' : oldRate);
$scoreInput.val(cellRate);
Popup.error('Текущий балл превышает максимальный для данного модуля');
}
// todo: move to post callback
$scoreInput.turnOn();
}
return {
initGeneralCells,
// + ID - id дисциплины
// + studyGroupID_Filter - studyGroupID для фильтра (Эффект памяти)
settings,
cell,
cursor,
rateInfo,
highlighter,
adjuster,
filter
}
}