Replaced the function-based JavaScript with the object-oriented AccountForm class for the currency form.
This commit is contained in:
		@@ -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();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user