feat(exam-correct): validate user input stub

This commit is contained in:
Sarah Vaupel 2020-01-20 11:11:14 +01:00 committed by Gregor Kleen
parent 4cb62f8f91
commit 7f04862a6f
2 changed files with 107 additions and 49 deletions

View File

@ -9,13 +9,20 @@ import moment from 'moment';
const EXAM_CORRECT_URL_POST = 'correct'; const EXAM_CORRECT_URL_POST = 'correct';
const EXAM_CORRECT_HEADERS = {
'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN'),
'Content-Type': HttpClient.ACCEPT.JSON,
'Accept': HttpClient.ACCEPT.JSON,
};
const EXAM_CORRECT_IDENT = 'uw-exam-correct'; const EXAM_CORRECT_IDENT = 'uw-exam-correct';
const EXAM_CORRECT_PART_INPUT_ATTR = 'uw-exam-correct--part-input'; const EXAM_CORRECT_PART_INPUT_ATTR = 'uw-exam-correct--part-input';
const EXAM_CORRECT_SEND_BTN_ID = 'exam-correct__send-btn'; const EXAM_CORRECT_SEND_BTN_ID = 'exam-correct__send-btn';
const EXAM_CORRECT_PARTICIPANT_INPUT_ID = 'exam-correct__participant'; const EXAM_CORRECT_PARTICIPANT_INPUT_ID = 'exam-correct__user';
const EXAM_CORRECT_USER_INPUT_CANDIDATES_ID = 'exam-correct__user-candidates';
const EXAM_CORRECT_INPUT_BODY_ID = 'exam-correct__new'; const EXAM_CORRECT_INPUT_BODY_ID = 'exam-correct__new';
const EXAM_CORRECT_PARTICIPANT_ATTR = 'exam-correct--participant'; const EXAM_CORRECT_PARTICIPANT_ATTR = 'exam-correct--user';
const INPUT_EMPTY_CLASS = 'input--invalid'; const INPUT_EMPTY_CLASS = 'input--invalid';
@ -33,7 +40,8 @@ export class ExamCorrect {
_element; _element;
_sendBtn; _sendBtn;
_participantInput; _userInput;
_userInputCandidates;
_partInputs; _partInputs;
constructor(element, app) { constructor(element, app) {
@ -48,40 +56,65 @@ export class ExamCorrect {
this._element = element; this._element = element;
this._app = app; this._app = app;
this._sendBtn = document.getElementById(EXAM_CORRECT_SEND_BTN_ID); this._sendBtn = document.getElementById(EXAM_CORRECT_SEND_BTN_ID);
this._participantInput = document.getElementById(EXAM_CORRECT_PARTICIPANT_INPUT_ID); this._userInput = document.getElementById(EXAM_CORRECT_PARTICIPANT_INPUT_ID);
this._userInputCandidates = document.getElementById(EXAM_CORRECT_USER_INPUT_CANDIDATES_ID);
this._partInputs = [...this._element.querySelectorAll(`input[${EXAM_CORRECT_PART_INPUT_ATTR}]`)]; this._partInputs = [...this._element.querySelectorAll(`input[${EXAM_CORRECT_PART_INPUT_ATTR}]`)];
if (this._sendBtn) if (this._sendBtn)
this._sendBtn.addEventListener('click', this._sendCorrectionHandler.bind(this)); this._sendBtn.addEventListener('click', this._sendCorrectionHandler.bind(this));
else console.error('ExamCorrect utility could not detect send button!'); else console.error('ExamCorrect utility could not detect send button!');
if (this._participantInput) if (this._userInput)
this._participantInput.addEventListener('focusout', this._validateParticipantInput.bind(this)); this._userInput.addEventListener('focusout', this._validateUserInput.bind(this));
else throw new Error('ExamCorrect utility could not detect participant input!'); else throw new Error('ExamCorrect utility could not detect user input!');
if (!this._userInputCandidates) {
throw new Error('ExamCorrect utility could not detect user input candidate list!');
}
} }
destroy() { destroy() {
this._sendBtn.removeEventListener('click', this._sendCorrectionHandler); this._sendBtn.removeEventListener('click', this._sendCorrectionHandler);
this._participantInput.removeEventListener('change', this._validateParticipantInput); this._userInput.removeEventListener('change', this._validateUserInput);
this._participantInput.removeEventListener('input', this._removeInputEmptyClassHandler); this._userInput.removeEventListener('input', this._removeInputEmptyClassHandler);
} }
_validateParticipantInput(event) { _validateUserInput() {
console.log('WIP _validateParticipantInput', event); const user = this._userInput.value;
if (!user) return;
const body = {
name: user,
results: {},
op: false,
};
this._app.httpClient.post({
url: EXAM_CORRECT_URL_POST,
headers: EXAM_CORRECT_HEADERS,
body: JSON.stringify(body),
}).then(
(response) => response.json()
).then(
(response) => this._processResponse(response, user)
).catch((error) => {
console.error('Error while validating user input', error);
});
} }
_sendCorrectionHandler(event) { _sendCorrectionHandler(event) {
console.log('WIP _sendCorrectionHandler', event); console.log('WIP _sendCorrectionHandler', event);
// refocus participant input element for convenience // refocus user input element for convenience
this._participantInput.focus(); this._userInput.focus();
const participant = this._participantInput.value; const user = this._userInput.value;
// abort send if the participant input is empty // abort send if the user input is empty
if (!participant) { if (!user) {
this._participantInput.classList.add(INPUT_EMPTY_CLASS); this._userInput.classList.add(INPUT_EMPTY_CLASS);
this._participantInput.addEventListener('input', this._removeInputEmptyClassHandler.bind(this), { once: true }); this._userInput.addEventListener('input', this._removeInputEmptyClassHandler.bind(this), { once: true });
return; return;
} }
@ -106,9 +139,9 @@ export class ExamCorrect {
const now = moment(); const now = moment();
dateTD.appendChild(document.createTextNode(now.format(MOMENT_FORMAT))); dateTD.appendChild(document.createTextNode(now.format(MOMENT_FORMAT)));
dateTD.setAttribute('date', moment()); dateTD.setAttribute('date', moment());
const participantTD = document.createElement('TD'); const userTD = document.createElement('TD');
participantTD.appendChild(document.createTextNode(participant)); userTD.appendChild(document.createTextNode(user));
participantTD.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, participant); userTD.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, user);
const partTDs = this._partInputs.map((input) => { const partTDs = this._partInputs.map((input) => {
const partTD = document.createElement('TD'); const partTD = document.createElement('TD');
const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR); const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR);
@ -120,7 +153,7 @@ export class ExamCorrect {
const statusDiv = document.createElement('DIV'); const statusDiv = document.createElement('DIV');
statusDiv.classList.add('exam-correct--loading'); statusDiv.classList.add('exam-correct--loading');
statusTD.appendChild(statusDiv); statusTD.appendChild(statusDiv);
[dateTD,participantTD,...partTDs, statusTD].forEach((td) => { [dateTD,userTD,...partTDs, statusTD].forEach((td) => {
td.classList.add('table__td'); td.classList.add('table__td');
correctionRow.appendChild(td); correctionRow.appendChild(td);
}); });
@ -129,41 +162,65 @@ export class ExamCorrect {
// clear input values on validation success // clear input values on validation success
// TODO only clear input on post success // TODO only clear input on post success
[this._participantInput, ...this._partInputs].forEach(clearInput); [this._userInput, ...this._partInputs].forEach(clearInput);
const url = EXAM_CORRECT_URL_POST;
const headers = {
'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN'),
'Content-Type': HttpClient.ACCEPT.JSON,
'Accept': HttpClient.ACCEPT.JSON,
};
const body = { const body = {
name: participant, name: user,
results: results, results: results,
op: true, op: true,
}; };
this._app.httpClient.post({ this._app.httpClient.post({
url: url, url: EXAM_CORRECT_URL_POST,
headers: headers, headers: EXAM_CORRECT_HEADERS,
body: JSON.stringify(body), body: JSON.stringify(body),
}).then( }).then(
(response) => response.json() (response) => response.json()
).then( ).then(
(response) => this._processResponse(response, participant) (response) => this._processResponse(response, user)
).catch((error) => { ).catch((error) => {
console.error('Error while processing response', error); console.error('Error while processing response', error);
}); });
} }
_processResponse(response, participant) { _processResponse(response, user) {
console.log('WIP ExamCorrect._processResponse', response, participant); console.log('WIP _processResponse', response, user);
if (response) { if (response) {
if (response.status === 'no-op') {
if (response.users) {
// TODO directly replace input value and add attr if list contains only one element
// TODO add event handler on links -> display name as input value and set id as attribute
// TODO how to destroy candidate handlers?
response.users.forEach((userCandidate) => {
const candidateItem = document.createElement('li');
userAsInnerHTML(candidateItem, userCandidate);
candidateItem.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, userCandidate.id);
const acceptCandidateHandler = () => {
console.log('candidate accepted');
this._userInput.value = userCandidate['display-name'] || userCandidate.surname || userCandidate['mat-nr'];
this._userInput.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, userCandidate.id);
// remove all candidates
while (this._userInputCandidates.firstChild) {
this._userInputCandidates.removeChild(this._userInputCandidates.firstChild);
}
};
candidateItem.addEventListener('click', acceptCandidateHandler, { once: true });
this._userInputCandidates.appendChild(candidateItem);
});
}
return;
}
for (let row of [...this._element.rows]) { for (let row of [...this._element.rows]) {
const participantElem = row.cells.item(1); const userElem = row.cells.item(1);
const participantIdent = participantElem && participantElem.getAttribute(EXAM_CORRECT_PARTICIPANT_ATTR); const userIdent = userElem && userElem.getAttribute(EXAM_CORRECT_PARTICIPANT_ATTR);
if (participantIdent === participant) { if (userIdent === user) {
let faIcon, ecClass; let faIcon, ecClass;
switch (response.status) { switch (response.status) {
// TODO fetch update time from response and replace // TODO fetch update time from response and replace
@ -171,9 +228,9 @@ export class ExamCorrect {
faIcon = 'fa-check'; faIcon = 'fa-check';
ecClass = 'exam-correct--success'; ecClass = 'exam-correct--success';
if (response.user) { if (response.user) {
participantElem.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, response.user.id); userElem.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, response.user.id);
participantElem.innerHTML = ''; userElem.innerHTML = '';
formatUser(participantElem, response.user); userAsInnerHTML(userElem, response.user);
} }
// TODO replace results with results from response // TODO replace results with results from response
// TODO set edit button visibility // TODO set edit button visibility
@ -185,7 +242,7 @@ export class ExamCorrect {
ecClass = 'exam-correct--error'; ecClass = 'exam-correct--error';
// TODO show users // TODO show users
if (response.users) { if (response.users) {
showUsers(participantElem, response.users); showUsers(userElem, response.users);
} }
break; break;
case 'failure': case 'failure':
@ -211,10 +268,10 @@ export class ExamCorrect {
_removeInputEmptyClassHandler() { _removeInputEmptyClassHandler() {
console.log('removeclass'); console.log('removeclass');
if (this._participantInput.value) { if (this._userInput.value) {
this._participantInput.classList.remove(INPUT_EMPTY_CLASS); this._userInput.classList.remove(INPUT_EMPTY_CLASS);
} else { } else {
this._participantInput.addEventListener('input', this._removeInputEmptyClassHandler, { once: true }); this._userInput.addEventListener('input', this._removeInputEmptyClassHandler, { once: true });
} }
} }
@ -225,7 +282,7 @@ function clearInput(inputElement) {
inputElement.value = null; inputElement.value = null;
} }
function formatUser(elem, user) { function userAsInnerHTML(elem, user) {
if (user && user['display-name'] && user['surname']) { if (user && user['display-name'] && user['surname']) {
elem.innerHTML += user['display-name'].replace(new RegExp(user['surname']), `<strong>${user['surname']}</strong>`) + (user['mat-nr'] ? ` (${user['mat-nr']})` : ''); elem.innerHTML += user['display-name'].replace(new RegExp(user['surname']), `<strong>${user['surname']}</strong>`) + (user['mat-nr'] ? ` (${user['mat-nr']})` : '');
} else { } else {
@ -238,7 +295,7 @@ function showUsers(elem, users) {
elem.innerHTML = ''; elem.innerHTML = '';
if (users) { if (users) {
for (const user of users) { for (const user of users) {
formatUser(elem, user); userAsInnerHTML(elem, user);
elem.innerHTML += '<br/>'; elem.innerHTML += '<br/>';
} }
} else { } else {

View File

@ -26,7 +26,8 @@ $newline never
<tr .table__row> <tr .table__row>
<td .table__td> <td .table__td>
<td .table__td> <td .table__td>
<input #exam-correct__participant type="text"> <input #exam-correct__user type="text">
<ul #exam-correct__user-candidates>
$forall ExamPart{examPartNumber} <- examParts $forall ExamPart{examPartNumber} <- examParts
<td .table__td> <td .table__td>
^{ptsInput examPartNumber} ^{ptsInput examPartNumber}