Replaced the function-based JavaScript with the object-oriented AccountForm class for the currency form.

This commit is contained in:
依瑪貓 2023-03-12 21:40:25 +08:00
parent 0a658a76e8
commit 2b84f64554

View File

@ -24,152 +24,151 @@
// Initializes the page JavaScript. // Initializes the page JavaScript.
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
document.getElementById("accounting-code") CurrencyForm.initialize();
.onchange = validateCode;
document.getElementById("accounting-name")
.onchange = validateName;
document.getElementById("accounting-form")
.onsubmit = validateForm;
}); });
/** /**
* The asynchronous validation result * The currency form.
* @type {object} *
* @private * @private
*/ */
let isAsyncValid = {}; class CurrencyForm {
/**
* The form.
* @type {HTMLFormElement}
*/
#formElement;
/**
* The code
* @type {HTMLInputElement}
*/
#code;
/**
* The error message of the code
* @type {HTMLDivElement}
*/
#codeError;
/**
* The name
* @type {HTMLInputElement}
*/
#name;
/**
* The error message of the name
* @type {HTMLDivElement}
*/
#nameError;
/**
* Constructs the currency form.
*
*/
constructor() {
this.#formElement = document.getElementById("accounting-form");
this.#code = document.getElementById("accounting-code");
this.#codeError = document.getElementById("accounting-code-error");
this.#name = document.getElementById("accounting-name");
this.#nameError = document.getElementById("accounting-name-error");
this.#code.onchange = () => {
this.#validateCode().then();
};
this.#name.onchange = () => {
this.#validateName();
};
this.#formElement.onsubmit = () => {
this.#validateForm().then((isValid) => {
if (isValid) {
this.#formElement.submit();
}
});
return false;
};
}
/** /**
* Validates the form. * Validates the form.
* *
* @returns {boolean} true if valid, or false otherwise * @returns {Promise<boolean>} true if valid, or false otherwise
* @private
*/ */
function validateForm() { async #validateForm() {
isAsyncValid = {
"code": false,
"_sync": false,
};
let isValid = true; let isValid = true;
isValid = validateCode() && isValid; isValid = await this.#validateCode() && isValid;
isValid = validateName() && isValid; isValid = this.#validateName() && isValid;
isAsyncValid["_sync"] = isValid; return isValid;
submitFormIfAllAsyncValid();
return false;
}
/**
* Submits the form if the whole form passed the asynchronous
* validations.
*
* @private
*/
function submitFormIfAllAsyncValid() {
let isValid = true;
for (const key of Object.keys(isAsyncValid)) {
isValid = isAsyncValid[key] && isValid;
}
if (isValid) {
document.getElementById("accounting-form").submit()
}
} }
/** /**
* Validates the code. * Validates the code.
* *
* @param changeEvent {Event} the change event, if invoked from onchange * @param changeEvent {Event} the change event, if invoked from onchange
* @returns {boolean} true if valid, or false otherwise * @returns {Promise<boolean>} true if valid, or false otherwise
* @private
*/ */
function validateCode(changeEvent = null) { async #validateCode(changeEvent = null) {
const key = "code"; this.#code.value = this.#code.value.trim();
const isSubmission = changeEvent === null; if (this.#code.value === "") {
let hasAsyncValidation = false; this.#code.classList.add("is-invalid");
const field = document.getElementById("accounting-code"); this.#codeError.innerText = A_("Please fill in the code.");
const error = document.getElementById("accounting-code-error");
field.value = field.value.trim();
if (field.value === "") {
field.classList.add("is-invalid");
error.innerText = A_("Please fill in the code.");
return false; return false;
} }
const blocklist = JSON.parse(field.dataset.blocklist); const blocklist = JSON.parse(this.#code.dataset.blocklist);
if (blocklist.includes(field.value)) { if (blocklist.includes(this.#code.value)) {
field.classList.add("is-invalid"); this.#code.classList.add("is-invalid");
error.innerText = A_("This code is not available."); this.#codeError.innerText = A_("This code is not available.");
return false; return false;
} }
if (!field.value.match(/^[A-Z]{3}$/)) { if (!this.#code.value.match(/^[A-Z]{3}$/)) {
field.classList.add("is-invalid"); this.#code.classList.add("is-invalid");
error.innerText = A_("Code can only be composed of 3 upper-cased letters."); this.#codeError.innerText = A_("Code can only be composed of 3 upper-cased letters.");
return false; return false;
} }
const original = field.dataset.original; const original = this.#code.dataset.original;
if (original === "" || field.value !== original) { if (original === "" || this.#code.value !== original) {
hasAsyncValidation = true; const response = await fetch(this.#code.dataset.existsUrl + "?q=" + encodeURIComponent(this.#code.value));
validateAsyncCodeIsDuplicated(isSubmission, key); const data = await response.json();
if (data["exists"]) {
this.#code.classList.add("is-invalid");
this.#codeError.innerText = A_("Code conflicts with another currency.");
return false;
} }
if (!hasAsyncValidation) {
isAsyncValid[key] = true;
field.classList.remove("is-invalid");
error.innerText = "";
} }
this.#code.classList.remove("is-invalid");
this.#codeError.innerText = "";
return true; return true;
} }
/**
* Validates asynchronously whether the code is duplicated.
* The boolean validation result is stored in isAsyncValid[key].
*
* @param isSubmission {boolean} whether this is invoked from a form submission
* @param key {string} the key to store the result in isAsyncValid
* @private
*/
function validateAsyncCodeIsDuplicated(isSubmission, key) {
const field = document.getElementById("accounting-code");
const error = document.getElementById("accounting-code-error");
const url = field.dataset.existsUrl;
const onLoad = function () {
if (this.status === 200) {
const result = JSON.parse(this.responseText);
if (result["exists"]) {
field.classList.add("is-invalid");
error.innerText = A_("Code conflicts with another currency.");
if (isSubmission) {
isAsyncValid[key] = false;
}
return;
}
field.classList.remove("is-invalid");
error.innerText = "";
if (isSubmission) {
isAsyncValid[key] = true;
submitFormIfAllAsyncValid();
}
}
};
const request = new XMLHttpRequest();
request.onload = onLoad;
request.open("GET", url + "?q=" + encodeURIComponent(field.value));
request.send();
}
/** /**
* Validates the name. * Validates the name.
* *
* @returns {boolean} true if valid, or false otherwise * @returns {boolean} true if valid, or false otherwise
* @private
*/ */
function validateName() { #validateName() {
const field = document.getElementById("accounting-name"); this.#name.value = this.#name.value.trim();
const error = document.getElementById("accounting-name-error"); if (this.#name.value === "") {
field.value = field.value.trim(); this.#name.classList.add("is-invalid");
if (field.value === "") { this.#nameError.innerText = A_("Please fill in the name.");
field.classList.add("is-invalid");
error.innerText = A_("Please fill in the name.");
return false; return false;
} }
field.classList.remove("is-invalid"); this.#name.classList.remove("is-invalid");
error.innerText = ""; this.#nameError.innerText = "";
return true; return true;
} }
/**
* The form
* @type {CurrencyForm}
*/
static #form;
/**
* Initializes the currency form.
*
*/
static initialize() {
this.#form = new CurrencyForm();
}
}