feat(exam-correct): validate user input stub

This commit is contained in:
Sarah Vaupel 2020-01-20 11:11:14 +01:00
parent 36e90102c4
commit 431d004665
3 changed files with 108 additions and 50 deletions

View File

@ -9,13 +9,20 @@ import moment from 'moment';
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_PART_INPUT_ATTR = 'uw-exam-correct--part-input';
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_PARTICIPANT_ATTR = 'exam-correct--participant';
const EXAM_CORRECT_PARTICIPANT_ATTR = 'exam-correct--user';
const INPUT_EMPTY_CLASS = 'input--invalid';
@ -33,7 +40,8 @@ export class ExamCorrect {
_element;
_sendBtn;
_participantInput;
_userInput;
_userInputCandidates;
_partInputs;
constructor(element, app) {
@ -48,40 +56,65 @@ export class ExamCorrect {
this._element = element;
this._app = app;
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}]`)];
if (this._sendBtn)
this._sendBtn.addEventListener('click', this._sendCorrectionHandler.bind(this));
else console.error('ExamCorrect utility could not detect send button!');
if (this._participantInput)
this._participantInput.addEventListener('focusout', this._validateParticipantInput.bind(this));
else throw new Error('ExamCorrect utility could not detect participant input!');
if (this._userInput)
this._userInput.addEventListener('focusout', this._validateUserInput.bind(this));
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() {
this._sendBtn.removeEventListener('click', this._sendCorrectionHandler);
this._participantInput.removeEventListener('change', this._validateParticipantInput);
this._participantInput.removeEventListener('input', this._removeInputEmptyClassHandler);
this._userInput.removeEventListener('change', this._validateUserInput);
this._userInput.removeEventListener('input', this._removeInputEmptyClassHandler);
}
_validateParticipantInput(event) {
console.log('WIP _validateParticipantInput', event);
_validateUserInput() {
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) {
console.log('WIP _sendCorrectionHandler', event);
// refocus participant input element for convenience
this._participantInput.focus();
// refocus user input element for convenience
this._userInput.focus();
const participant = this._participantInput.value;
const user = this._userInput.value;
// abort send if the participant input is empty
if (!participant) {
this._participantInput.classList.add(INPUT_EMPTY_CLASS);
this._participantInput.addEventListener('input', this._removeInputEmptyClassHandler.bind(this), { once: true });
// abort send if the user input is empty
if (!user) {
this._userInput.classList.add(INPUT_EMPTY_CLASS);
this._userInput.addEventListener('input', this._removeInputEmptyClassHandler.bind(this), { once: true });
return;
}
@ -106,9 +139,9 @@ export class ExamCorrect {
const now = moment();
dateTD.appendChild(document.createTextNode(now.format(MOMENT_FORMAT)));
dateTD.setAttribute('date', moment());
const participantTD = document.createElement('TD');
participantTD.appendChild(document.createTextNode(participant));
participantTD.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, participant);
const userTD = document.createElement('TD');
userTD.appendChild(document.createTextNode(user));
userTD.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, user);
const partTDs = this._partInputs.map((input) => {
const partTD = document.createElement('TD');
const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR);
@ -120,7 +153,7 @@ export class ExamCorrect {
const statusDiv = document.createElement('DIV');
statusDiv.classList.add('exam-correct--loading');
statusTD.appendChild(statusDiv);
[dateTD,participantTD,...partTDs, statusTD].forEach((td) => {
[dateTD,userTD,...partTDs, statusTD].forEach((td) => {
td.classList.add('table__td');
correctionRow.appendChild(td);
});
@ -129,41 +162,65 @@ export class ExamCorrect {
// clear input values on validation 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 = {
name: participant,
name: user,
results: results,
op: true,
};
this._app.httpClient.post({
url: url,
headers: headers,
url: EXAM_CORRECT_URL_POST,
headers: EXAM_CORRECT_HEADERS,
body: JSON.stringify(body),
}).then(
(response) => response.json()
).then(
(response) => this._processResponse(response, participant)
(response) => this._processResponse(response, user)
).catch((error) => {
console.error('Error while processing response', error);
});
}
_processResponse(response, participant) {
console.log('WIP ExamCorrect._processResponse', response, participant);
_processResponse(response, user) {
console.log('WIP _processResponse', response, user);
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]) {
const participantElem = row.cells.item(1);
const participantIdent = participantElem && participantElem.getAttribute(EXAM_CORRECT_PARTICIPANT_ATTR);
if (participantIdent === participant) {
const userElem = row.cells.item(1);
const userIdent = userElem && userElem.getAttribute(EXAM_CORRECT_PARTICIPANT_ATTR);
if (userIdent === user) {
let faIcon, ecClass;
switch (response.status) {
// TODO fetch update time from response and replace
@ -171,9 +228,9 @@ export class ExamCorrect {
faIcon = 'fa-check';
ecClass = 'exam-correct--success';
if (response.user) {
participantElem.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, response.user.id);
participantElem.innerHTML = '';
formatUser(participantElem, response.user);
userElem.setAttribute(EXAM_CORRECT_PARTICIPANT_ATTR, response.user.id);
userElem.innerHTML = '';
userAsInnerHTML(userElem, response.user);
}
// TODO replace results with results from response
// TODO set edit button visibility
@ -185,7 +242,7 @@ export class ExamCorrect {
ecClass = 'exam-correct--error';
// TODO show users
if (response.users) {
showUsers(participantElem, response.users);
showUsers(userElem, response.users);
}
break;
case 'failure':
@ -211,10 +268,10 @@ export class ExamCorrect {
_removeInputEmptyClassHandler() {
console.log('removeclass');
if (this._participantInput.value) {
this._participantInput.classList.remove(INPUT_EMPTY_CLASS);
if (this._userInput.value) {
this._userInput.classList.remove(INPUT_EMPTY_CLASS);
} 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;
}
function formatUser(elem, user) {
function userAsInnerHTML(elem, user) {
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']})` : '');
} else {
@ -238,7 +295,7 @@ function showUsers(elem, users) {
elem.innerHTML = '';
if (users) {
for (const user of users) {
formatUser(elem, user);
userAsInnerHTML(elem, user);
elem.innerHTML += '<br/>';
}
} else {

View File

@ -779,4 +779,4 @@
}
}
]
}
}

View File

@ -26,7 +26,8 @@ $newline never
<tr .table__row>
<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
<td .table__td>
^{ptsInput examPartNumber}