const singleNumRegexp = /\w_(\d+)/; const doubleNumReg = /.*_(\d+)_(\d+)$/; class Base { static parseid($cell) { singleNumRegexp.lastIndex = 0; let found = singleNumRegexp.exec($cell.attr('id')); return found ? parseInt(found[1]) : 0; } static parseClass($cell) { singleNumRegexp.lastIndex = 0; let found = singleNumRegexp.exec($cell.attr('class')); return found ? parseInt(found[1]) : 0; } static parsePosition($cell) { doubleNumReg.lastIndex = 0; let [col, row] = doubleNumReg.exec($cell.attr('id')).splice(1, 2); return [ parseInt(col), parseInt(row) ]; } static parseSettings() { let $discipline = $('#json_discipline'); let settings = {}; settings.discipline = {}; let disciplineRaw = $.parseJSON($discipline.html() || '{}'); Object.keys(disciplineRaw).forEach((keyRaw) => { let key = keyRaw.slice(0, 1).toLowerCase() + keyRaw.slice(1); if (keyRaw == "ID") key = 'id'; let value = disciplineRaw[keyRaw]; settings.discipline[key] = value; }); $discipline.remove(); return settings; } static parseRate($cell, server) { let def = server ? '-1' : '0'; if ($cell.hasClass('static')) { return parseInt($cell.text() || def); } else { return parseInt($cell.children('input').val() || 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]; } 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(student, submodule, maxRate) { this.student = student; 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'); } select($cell) { this.cursor.parseLocation($cell); let [col] = this.cursor.get(); let $curModuleHead = this.$modulesHead.children(`.col_${col}`); let $curSubmoduleHead = this.$submodulesHead.children(`.col_${col}`); let $curSubmoduleMaxRate = this.$submodulesMaxRate.children(`.col_${col}`); let student = Base.parseid($cell.siblings('.name')); let submodule = Base.parseid($curSubmoduleMaxRate); let maxRate = parseInt($curSubmoduleMaxRate.text()); if ($cell.hasClass('extra')) { maxRate -= Base.parseRate($cell.siblings('.semester')); maxRate -= Base.parseRate($cell.prev('.extra')); maxRate = Math.max(0, maxRate); } this.cell.update(student, submodule, maxRate); } reset() { this.cell.update(0, 0, 0); } } 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'); } show($cell) { let [col] = this.cursor.get(); 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(); } } 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); this.listen($selector); } 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); this.filterGroups(groupid); }); } } 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.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 = ''; $('#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); let key = event.keyCode; // esc 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); } else { $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.setExam($(this)); self.cellInfo.reset(); }).on('click', '.download button', function () { $.fileDownload(URLdir + 'handler/FileCreator/GenerateFinalForm', { httpMethod: 'POST', data: { 'disciplineID': self.settings.discipline.id, 'groupID': Base.parseid($(this)), 'stage': parseInt($(this).siblings('select').val()), }, }); }); } 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++; $cell = $(`#col_row_${col}_${row}`); } while ($cell.length && !$cell.hasClass('rate')); return $cell; } selectCell($cell) { $cell.children('input').focus(); // Выбрать весь текст в ячейке setTimeout(() => $cell.children('input').select(), 0); } setRate($cell, oldInputVal) { let scoreInputVal = $cell.children('input').val(); if (scoreInputVal == oldInputVal) return; let rate = Base.parseRate($cell, true); let successText; let errorText; if (scoreInputVal != '' && oldInputVal != '') { successText = 'Балл обновлен'; errorText = 'Не удалось обновить балл'; } else if (scoreInputVal != '' && oldInputVal == '') { successText = 'Балл добавлен'; errorText = 'Не удалось добавить балл'; } else { successText = 'Балл удален'; errorText = 'Не удалось удалить балл'; } this.sendRate($cell, 'handler/rating/SetRate', { rate }, $input => $input.val(oldInputVal), successText, errorText); } 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'; break; case 'drop_pass': successText = 'Автомат удален'; errorText = 'Не удалось удалить автомат'; option = 'null'; break; } this.sendRate($cell, 'handler/rating/SetExamPeriodOption', { option }, $input => $input.prop("checked", !$input.prop("checked")), successText, errorText); } sendRate($cell, url, data, reset, successText, errorText) { let $input = $cell.children('input'); $input.turnOff(); data.studentID = this.cell.student; data.disciplineID = this.settings.discipline.id; data.submoduleID = this.cell.submodule; $.postJSON(URLdir + url, data).success(() => { this.recountScores($cell, data.submoduleID); Popup.success(successText); }).fail(jqXHR => { reset($input); let status = parseInt(jqXHR.status); try { if (status != 400) throw null; let message = JSON.parse(jqXHR.responseText).message; if (!message) throw null; Popup.error(message); } catch (error) { Popup.error(errorText); } }).always(() => $input.turnOn()); } recountScores($cell, submoduleID) { let $сells = $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}`)); if ($cell.hasClass('exam')) { $сells.filter(`#col_row_${absenceCol}_${row}`).children('input').prop('checked', false); $сells.filter(`#col_row_${autopassCol}_${row}`).children('input').prop('checked', false); } if ($cell.hasClass('absence')) { $сells.filter(`#col_row_${examCol}_${row}`).children('input').val(''); $сells.filter(`#col_row_${autopassCol}_${row}`).children('input').prop('checked', false); } if ($cell.hasClass('autopass')) { $сells.filter(`#col_row_${examCol}_${row}`).children('input').val(''); $сells.filter(`#col_row_${absenceCol}_${row}`).children('input').prop('checked', false); } let $regularCells = $сells.filter('.regular'); if ($regularCells.length) { let semesterRate = $regularCells.aggregate((init, elem) => init + Base.parseRate($(elem))); $сells.filter('.semester').text(semesterRate); } let semesterRate = Base.parseRate($сells.filter('.semester')); let bonusRate = Base.parseRate($сells.filter('.bonus')); let extraRate = $сells.filter('.extra').aggregate((init, elem) => init + Base.parseRate($(elem))); let examRate = $сells.filter('.exam').aggregate((init, elem) => Math.max(init, Base.parseRate($(elem)))); let rateResult = semesterRate + bonusRate + extraRate + examRate; let rateResultText = (rateResult > 100) ? '100+' : rateResult; $сells.filter('.result').text(rateResultText); if ($cell.hasClass('extra')) { let $nextExtraCell = $cell.next('.extra'); if ($nextExtraCell.length) { let maxExtraRate = this.settings.discipline.type == 'exam' ? 38 : 60; let curRate = Base.parseRate($cell, true); let maxVal = maxExtraRate - semesterRate - curRate; if (maxVal > 0 && curRate != -1) { $nextExtraCell.find('input').attr('placeholder', 'макс. ' + maxVal); } else { $nextExtraCell.find('input').attr('placeholder', '–').val(''); } } } } } $(() => new Rating());