Newer
Older
const doubleNumReg = /.*_(\d+)_(\d+)$/;
class Base {
let found = singleNumRegexp.exec($cell.attr('id'));
return found ? parseInt(found[1]) : 0;
}
let found = singleNumRegexp.exec($cell.attr('class'));
return found ? parseInt(found[1]) : 0;
}
let [col, row] = doubleNumReg.exec($cell.attr('id')).splice(1, 2);
return [parseInt(col), parseInt(row)];
let $discipline = $('#json_discipline');
settings.discipline = {};
let disciplineRaw = $.parseJSON($discipline.html() || '{}');
Object.keys(disciplineRaw).forEach((keyRaw) => {
let key = keyRaw.slice(0, 1).toLowerCase() + keyRaw.slice(1);
settings.discipline[key] = +disciplineRaw[keyRaw];
static parseRateForCell($cell, server) {
return this.parseRate($cell, $cell.text(), server);
return this.parseRate($cell, $cell.children('input').val(), server);
static parseRate($cell, rate, server) {
let def = server ? '-1' : '0';
return parseInt(rate || def);
}
static filterNumbers($input) {
let scoreInputVal = $input.val();
let numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
scoreInputVal = Array.prototype.filter.call(scoreInputVal, chr => chr in numbers).join('');
$input.val(scoreInputVal);
}
}
class Cursor {
constructor() {
this.col = 0;
this.row = 0;
}
update(col, row) {
this.col = col;
this.row = row;
}
get() {
return [this.col, this.row];
}
// TODO: REFACTOR
// нреправильное название parseLocation для функции, которая
// еще и обновляет значения стобца и строки
parseLocation($cell) {
let [col, row] = Base.parsePosition($cell);
this.update(col, row);
}
isEmpty() {
return (this.col === 0) && (this.row === 0);
}
}
class Cell {
constructor() {
this.update(0, 0, 0);
}
update(recordBook, submodule, maxRate) {
this.recordBook = recordBook;
this.submodule = submodule;
this.maxRate = maxRate;
}
}
class CellInfo {
constructor(cursor, cell) {
this.cursor = cursor;
this.cell = cell;
this.$modulesHead = $('#modulesHead');
this.$submodulesHead = $('#submodulesHead');
this.$submodulesMaxRate = $('#submodulesHeadMaxRate');
}
/**
*
* @param $cell выбранная ячейка
* @desc определяет мероприятие, максимальный балл и id студента для ячейки таблицы баллов
* делает это по id ячейки, тексту в заголовке таблицы и id ячейки с именем студента
*/
select($cell) {
this.cursor.parseLocation($cell);
let [col] = this.cursor.get();
let $curSubmoduleMaxRate = this.$submodulesMaxRate.children(`.col_${col}`);
let recordBook = Base.parseid($cell.siblings('.name'));
let submodule = Base.parseid($curSubmoduleMaxRate);
let maxRate = parseInt($curSubmoduleMaxRate.text());
maxRate -= Base.parseRateForCell($cell.siblings('.semester'));
maxRate -= Base.parseRateForCell($cell.prev('.extra'));
this.cell.update(recordBook, submodule, maxRate);
}
reset() {
this.cell.update(0, 0, 0);
}
}
/**
* @desc Класс, контролирующий строку состояния таблицы баллов
* показывает студента, мероприятие и макс. балл, если выделена ячейка
*
*/
class RateInfo {
constructor(cursor, cell) {
this.cursor = cursor;
this.cell = cell;
this.$modulesHead = $('#modulesHead');
this.$submodulesHead = $('#submodulesHead');
this.$submodulesMaxRate = $('#submodulesHeadMaxRate');
this.$cellInfo = $('#cellInfo');
this.$studentInfo = this.$cellInfo.find('#student .value');
this.$submoduleInfo = this.$cellInfo.find('#submodule .value');
this.$rateInfo = this.$cellInfo.find('#maxRate .value');
}
/**
* @desc обновляет имя студента, мероприятие и макс. балл по выбранной ячейке, выбирая текст из столбца и строки таблицы
* @param $cell ячейка, информацию по которой нужно вывести в строке состояния
*
*/
let studentName = $cell.siblings('.name').text();
let submoduleTitle = this.$submodulesHead.children(`.col_${col}`).text();
this.$studentInfo.html(studentName);
this.$submoduleInfo.html(submoduleTitle);
this.$rateInfo.html(this.cell.maxRate);
this.$cellInfo.show();
}
hide() {
this.$cellInfo.hide();
}
}
/**
* @desc класс, управляющий фильтром по группам в таблице баллов
* следит за выпадающим списком групп и скрывает или показывает строки таблицы
*/
class GroupFilter {
constructor(settings, $table, $selector) {
this.settings = settings;
this.$table = $table.children('tbody');
let groupFilter = JSON.parse(localStorage.groupFilter || "{}");
let groupid = groupFilter[this.settings.discipline.id] || 0;
$selector.children('[value=' + groupid + ']').prop('selected', true);
this.filterGroups(groupid);
let $trs = this.$table.children();
$trs.show();
if (groupid) {
$trs.not(`.group_${groupid}`).hide();
}
}
listen($selector) {
$selector.change(() => {
let groupid = parseInt($selector.val());
let groupFilter = JSON.parse(localStorage.groupFilter || "{}");
groupFilter[this.settings.discipline.id] = groupid;
localStorage.groupFilter = JSON.stringify(groupFilter);
/**
* @desc класс, управляющий вводом баллов в таблицу
*/
class Rating {
constructor() {
this.settings = Base.parseSettings();
this.cell = new Cell();
this.cursor = new Cursor();
this.cellInfo = new CellInfo(this.cursor, this.cell);
this.rateInfo = new RateInfo(this.cursor, this.cell);
this.filter = new GroupFilter(this.settings, $('#studentsRate'), $('#groupSelector'));
this.undoManager = new ActionHistory();
this.Direction = {
Up: 0,
Right: 1,
Down: 2,
Left: 3
};
let keyDirs = {
13: this.Direction.Down, // enter
40: this.Direction.Down, // down arrow
38: this.Direction.Up, // up arrow
39: this.Direction.Right, // right arrow
37: this.Direction.Left // left arrow
};
let self = this;
let oldInputVal = '';
// при нажатии на ячейку таблицы передаем фокус элементу input
// обовляем курсор
// обновляем строку статуса
// запоминаем предыдущее значение в нем
$('#studentsRate tbody').on('focusin', '.rate', function () {
$(this).children('input').select();
self.cellInfo.select($(this));
self.rateInfo.show($(this));
$(this).parents('tr').addClass('focus');
oldInputVal = $(this).children('input').val();
}).on('focusout', '.rate', function () {
self.rateInfo.hide($(this));
self.cellInfo.reset();
$(this).parents('tr').removeClass('focus');
}).on('keydown', '.rate', function (event) {
// Разрешаем нажимать только цифры
keyDownOnlyNumber(event);
if (key == 90 && event.ctrlKey === true) {
let didUndo = self.undoManager.undo();
if (!didUndo) {
Popup.error("Еще нельзя отменить");
}
return;
}
if (key == 89 && event.ctrlKey === true) {
let didRedo = self.undoManager.redo();
if (!didRedo) {
Popup.error("Еще нельзя повторить");
}
return;
}
if (key === 27) {
$(this).children('input').val(oldInputVal);
$(this).children('input').blur();
}
if (!(key in keyDirs)) {
return;
}
let direction = keyDirs[key];
let $cell = self.getDesiredCell(direction);
if ($cell.length !== 0) { // Если ячейка найдена
self.selectCell($cell);
$cell = $(this);
let scoreInputVal = $cell.children('input').val();
if (scoreInputVal !== oldInputVal) {
$cell.one('turnOn', () => self.selectCell($cell));
$cell.chlidren('input').blur();
} else {
self.selectCell($cell);
}
}
}).on('change', '.rate', function () {
Base.filterNumbers($(this).children('input'));
self.setRate($(this), oldInputVal);
}).on('change', '.checkbox', function () {
self.cellInfo.select($(this));
self.cellInfo.reset();
}).on('click', '.download button', function () {
$.fileDownload(g_URLdir + 'handler/FileCreator/GenerateFinalForm', {
'disciplineID': self.settings.discipline.id,
'groupID': Base.parseid($(this)),
'stage': parseInt($(this).siblings('select').val()),
/**
* @desc находит соседнюю ячейку в заданном направлении
* @param direction направление движения
* @returns {*}
*/
getDesiredCell(direction) {
let [col, row] = this.cursor.get();
let $cell;
do {
if (direction === this.Direction.Left) col--;
if (direction === this.Direction.Right) col++;
if (direction === this.Direction.Up) row--;
if (direction === this.Direction.Down) row++;
} while ($cell.length && !$cell.hasClass('rate'));
selectCell($cell) {
$cell.children('input').focus();
// Выбрать весь текст в ячейке
setTimeout(() => $cell.children('input').select(), 0);
}
/**
* @desc отправляет выставленный в ячейку балл на сервер и выводит сообщение об ошибке/успехе
* @param $cell
* @param oldInputVal
*/
setRate($cell, oldInputVal) {
let scoreInputVal = $cell.children('input').val();
if (scoreInputVal === oldInputVal) return;
if (!this.settings.discipline.isLocked) {
if (window.confirm("Если выставить первый балл, то невозможно будет редактировать учебную карту дисциплины. Продолжить?")) {
this.settings.discipline.isLocked = 1;
} else {
$cell.children('input').val(oldInputVal);
return;
}
}
let rate = Base.parseRateForCell($cell, true);
let successUndoText;
if (scoreInputVal !== '' && oldInputVal !== '') {
successUndoText = 'Балл исправлен';
} else if (scoreInputVal !== '' && oldInputVal === '') {
successUndoText = 'Балл удален';
errorText = 'Не удалось добавить балл';
} else {
successText = 'Балл удален';
successUndoText = 'Балл добавлен';
errorText = 'Не удалось удалить балл';
}
let self = this;
let oldGrade = Base.parseRate($cell, oldInputVal, true);
let newGrade = rate;
const newGradeDisplay = newGrade === -1? '' : newGrade;
const oldGradeDisplay = oldGrade === -1? '' : oldGrade;
let targetCellId = $cell.attr("id");
let newAction = this.undoManager.addAction(targetCellId,
function (action) { // redo
$targetCell = $("#" + targetCellId);
let $input = $cell.children('input');
$input.turnOff();
$input.val(newGradeDisplay);
$input.turnOn();
self.cellInfo.select($targetCell);
action.deactivate();
self.sendRate($targetCell, 'handler/rating/SetRate', {rate: newGrade},
function () {
action.activate();
},
function ($input) {
$input.val(oldGradeDisplay);
action.remove();
}, successText, errorText);
self.selectCell($targetCell);
}, function (action) { // undo
$targetCell = $("#" + targetCellId);
let $input = $cell.children('input');
$input.turnOff();
$input.val(oldGradeDisplay);
$input.turnOn();
self.cellInfo.select($targetCell);
action.deactivate();
self.sendRate($targetCell, 'handler/rating/SetRate', {rate: oldGrade},
function () {
action.activate();
},
function ($input) {
$input.val(newGradeDisplay);
}, successUndoText, errorText
);
self.selectCell($targetCell);
});
this.sendRate($cell, 'handler/rating/SetRate', {rate},
function () {
newAction.activate()
},
function ($input) {
newAction.remove();
$input.val(oldInputVal);
},
/**
* @desc отправляет выставленную неявку или автомат на экзамене на сервер и выводит сообщение об ошибке/успехе
* @param $cell
*/
setExam($cell) {
let optionVal;
if ($cell.hasClass('absence')) optionVal = 'absence';
if ($cell.hasClass('autopass')) optionVal = 'pass';
if (!$cell.children('input').prop('checked')) optionVal = `drop_${optionVal}`;
let successText;
let errorText;
let option;
switch (optionVal) {
case 'absence':
successText = 'Неявка установлена';
errorText = 'Не удалось установить неявку';
option = 'absence';
break;
case 'drop_absence':
successText = 'Неявка удалена';
errorText = 'Не удалось удалить неявку';
option = 'null';
break;
case 'pass':
successText = 'Автомат установлен';
errorText = 'Не удалось установить автомат';
option = 'pass'
oldOption = 'null';;
break;
case 'drop_pass':
successText = 'Автомат удален';
errorText = 'Не удалось удалить автомат';
option = 'null';
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
let self = this;
let targetCellId = $cell.attr("id");
// let newAction = this.undoManager.addAction(targetCellId,
// function (action) {
// $targetCell = $("#" + targetCellId);
// let $input = $cell.children('input');
// $input.prop("checked", !$input.prop("checked"));
// //self.cellInfo.select($targetCell);
// self.sendRate($targetCell, 'handler/rating/SetExamPeriodOption', {option},
// function () {
// action.activate();
// },
// function ($input) {
// $input.prop("checked", !$input.prop("checked"))
// action.remove();
// }, successText, errorText);
// //self.selectCell($targetCell);
// },
// function (action) {
// $targetCell = $("#" + targetCellId);
// let $input = $cell.children('input');
// $input.prop("checked", !$input.prop("checked"));
// //self.cellInfo.select($targetCell);
// self.sendRate($targetCell, 'handler/rating/SetExamPeriodOption', {option: oldOption},
// function () {
// action.activate();
// },
// function ($input) {
// $input.prop("checked", !$input.prop("checked"))
// action.remove();
// }, successText, errorText);
// //self.selectCell($targetCell);
// });
this.sendRate($cell, 'handler/rating/SetExamPeriodOption', {option},
function () {
//newAction.activate()
},
function ($input) {
//newAction.remove();
$input.prop("checked", !$input.prop("checked"));
},
successText, errorText);
sendRate($cell, url, data, onSuccess, onFail, successText, errorText) {
// Действия необходимые для безопасной обработки в асинхронном режиме
window.onbeforeunload = () => {
return "Запрос на добавление/изменение/удалени баллов еще не обработан!";
};
data.disciplineID = this.settings.discipline.id;
data.submoduleID = this.cell.submodule;
this.recountScores($cell, data.submoduleID);
Popup.success(successText);
onSuccess();
}).fail(jqXHR => {
onFail($input);
const status = parseInt(jqXHR.status);
let message = errorText;
if (status === 400)
message = JSON.parse(jqXHR.responseText).message;
} catch(error) {
if (status !== 0)
Popup.error(message);
window.onbeforeunload = undefined;
let $cells = $cell.parent().children();
let [col, row] = Base.parsePosition($cell);
let examCol = Base.parseClass($(`#exam_${submoduleID}`));
let absenceCol = Base.parseClass($(`#absence_${submoduleID}`));
let autopassCol = Base.parseClass($(`#autopass_${submoduleID}`));
$cells.filter(`#col_row_${absenceCol}_${row}`).children('input').prop('checked', false);
$cells.filter(`#col_row_${autopassCol}_${row}`).children('input').prop('checked', false);
$cells.filter(`#col_row_${examCol}_${row}`).children('input').val('');
$cells.filter(`#col_row_${autopassCol}_${row}`).children('input').prop('checked', false);
$cells.filter(`#col_row_${examCol}_${row}`).children('input').val('');
$cells.filter(`#col_row_${absenceCol}_${row}`).children('input').prop('checked', false);
let $regularCells = $cells.filter('.regular');
let semesterRate = $regularCells.aggregate((init, elem) => init + Base.parseRateForCell($(elem)));
$cells.filter('.semester').text(semesterRate);
let semesterRate = Base.parseRateForCell($cells.filter('.semester'));
let bonusRate = Base.parseRateForCell($cells.filter('.bonus'));
let extraRate = $cells.filter('.extra').aggregate((init, elem) => init + Base.parseRateForCell($(elem)));
let examRate = $cells.filter('.exam').aggregate((init, elem) => Math.max(init, Base.parseRateForCell($(elem))));
let rateResult = semesterRate + bonusRate + extraRate + examRate;
let rateResultText = (rateResult > 100) ? '100+' : rateResult;
$cells.filter('.result').text(rateResultText);
if ($cell.hasClass('extra')) {
let $nextExtraCell = $cell.next('.extra');
let maxExtraRate = this.settings.discipline.type == 'exam' ? 38 : 60;
let curRate = Base.parseRateForCell($cell, true);
let maxVal = maxExtraRate - semesterRate - curRate;
$nextExtraCell.find('input').attr('placeholder', 'макс. ' + maxVal);
} else {
$nextExtraCell.find('input').attr('placeholder', '–').val('');
}
}