Replaced the function-based JavaScript with the object-oriented AccountForm class for the account form.
This commit is contained in:
parent
50dc79d865
commit
0a658a76e8
@ -24,100 +24,293 @@
|
||||
|
||||
// Initializes the page JavaScript.
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initializeBaseAccountSelector();
|
||||
document.getElementById("accounting-base-code")
|
||||
.onchange = validateBase;
|
||||
document.getElementById("accounting-title")
|
||||
.onchange = validateTitle;
|
||||
document.getElementById("accounting-form")
|
||||
.onsubmit = validateForm;
|
||||
AccountForm.initialize();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the base account selector.
|
||||
* The account form.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function initializeBaseAccountSelector() {
|
||||
const selector = document.getElementById("accounting-base-selector-modal");
|
||||
const base = document.getElementById("accounting-base");
|
||||
const baseCode = document.getElementById("accounting-base-code");
|
||||
const baseContent = document.getElementById("accounting-base-content");
|
||||
const isOffsetNeededControl = document.getElementById("accounting-is-offset-needed-control");
|
||||
const isOffsetNeeded = document.getElementById("accounting-is-offset-needed");
|
||||
const options = Array.from(document.getElementsByClassName("accounting-base-option"));
|
||||
const btnClear = document.getElementById("accounting-btn-clear-base");
|
||||
base.onclick = () => {
|
||||
base.classList.add("accounting-not-empty");
|
||||
for (const option of options) {
|
||||
option.classList.remove("active");
|
||||
}
|
||||
const selected = document.getElementById("accounting-base-option-" + baseCode.value);
|
||||
if (selected !== null) {
|
||||
selected.classList.add("active");
|
||||
}
|
||||
class AccountForm {
|
||||
|
||||
/**
|
||||
* The base account selector
|
||||
* @type {BaseAccountSelector}
|
||||
*/
|
||||
#baseAccountSelector;
|
||||
|
||||
/**
|
||||
* The form element
|
||||
* @type {HTMLFormElement}
|
||||
*/
|
||||
#formElement;
|
||||
|
||||
/**
|
||||
* The control of the base account
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
#baseControl;
|
||||
|
||||
/**
|
||||
* The input of the base account
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
#baseCode;
|
||||
|
||||
/**
|
||||
* The base account
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
#base;
|
||||
|
||||
/**
|
||||
* The error message for the base account
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
#baseError;
|
||||
|
||||
/**
|
||||
* The title
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
#title;
|
||||
|
||||
/**
|
||||
* The error message of the title
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
#titleError;
|
||||
|
||||
/**
|
||||
* The control of the is-offset-needed option
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
#isOffsetNeededControl;
|
||||
|
||||
/**
|
||||
* The is-offset-needed option
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
#isOffsetNeeded;
|
||||
|
||||
/**
|
||||
* Constructs the account form.
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
this.#baseAccountSelector = new BaseAccountSelector(this);
|
||||
this.#formElement = document.getElementById("accounting-form");
|
||||
this.#baseControl = document.getElementById("accounting-base-control");
|
||||
this.#baseCode = document.getElementById("accounting-base-code");
|
||||
this.#base = document.getElementById("accounting-base");
|
||||
this.#baseError = document.getElementById("accounting-base-error");
|
||||
this.#title = document.getElementById("accounting-title");
|
||||
this.#titleError = document.getElementById("accounting-title-error");
|
||||
this.#isOffsetNeededControl = document.getElementById("accounting-is-offset-needed-control");
|
||||
this.#isOffsetNeeded = document.getElementById("accounting-is-offset-needed");
|
||||
this.#formElement.onsubmit = () => {
|
||||
return this.#validateForm();
|
||||
};
|
||||
this.#baseControl.onclick = () => {
|
||||
this.#baseControl.classList.add("accounting-not-empty");
|
||||
this.#baseAccountSelector.onOpen(this.#baseCode.value);
|
||||
};
|
||||
selector.addEventListener("hidden.bs.modal", () => {
|
||||
if (baseCode.value === "") {
|
||||
base.classList.remove("accounting-not-empty");
|
||||
}
|
||||
});
|
||||
for (const option of options) {
|
||||
option.onclick = () => {
|
||||
baseCode.value = option.dataset.code;
|
||||
baseContent.innerText = option.dataset.content;
|
||||
if (["1", "2"].includes(option.dataset.content.substring(0, 1))) {
|
||||
isOffsetNeededControl.classList.remove("d-none");
|
||||
isOffsetNeeded.disabled = false;
|
||||
|
||||
/**
|
||||
* The callback when the base account selector is closed.
|
||||
*
|
||||
*/
|
||||
onBaseAccountSelectorClosed() {
|
||||
if (this.#baseCode.value === "") {
|
||||
this.#baseControl.classList.remove("accounting-not-empty");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base account.
|
||||
*
|
||||
* @param code {string} the base account code
|
||||
* @param text {string} the text for the base account
|
||||
*/
|
||||
setBaseAccount(code, text) {
|
||||
this.#baseCode.value = code;
|
||||
this.#base.innerText = text;
|
||||
if (["1", "2"].includes(code.substring(0, 1))) {
|
||||
this.#isOffsetNeededControl.classList.remove("d-none");
|
||||
this.#isOffsetNeeded.disabled = false;
|
||||
} else {
|
||||
isOffsetNeededControl.classList.add("d-none");
|
||||
isOffsetNeeded.disabled = true;
|
||||
isOffsetNeeded.checked = false;
|
||||
this.#isOffsetNeededControl.classList.add("d-none");
|
||||
this.#isOffsetNeeded.disabled = true;
|
||||
this.#isOffsetNeeded.checked = false;
|
||||
}
|
||||
btnClear.classList.add("btn-danger");
|
||||
btnClear.classList.remove("btn-secondary")
|
||||
btnClear.disabled = false;
|
||||
validateBase();
|
||||
bootstrap.Modal.getInstance(selector).hide();
|
||||
};
|
||||
}
|
||||
btnClear.onclick = () => {
|
||||
baseCode.value = "";
|
||||
baseContent.innerText = "";
|
||||
btnClear.classList.add("btn-secondary")
|
||||
btnClear.classList.remove("btn-danger");
|
||||
btnClear.disabled = true;
|
||||
validateBase();
|
||||
bootstrap.Modal.getInstance(selector).hide();
|
||||
}
|
||||
initializeBaseAccountQuery();
|
||||
this.#validateBase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the query on the base account options.
|
||||
* Clears the base account.
|
||||
*
|
||||
*/
|
||||
clearBaseAccount() {
|
||||
this.#baseCode.value = "";
|
||||
this.#base.innerText = "";
|
||||
this.#validateBase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the form.
|
||||
*
|
||||
* @returns {boolean} true if valid, or false otherwise
|
||||
*/
|
||||
#validateForm() {
|
||||
let isValid = true;
|
||||
isValid = this.#validateBase() && isValid;
|
||||
isValid = this.#validateTitle() && isValid;
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the base account.
|
||||
*
|
||||
* @returns {boolean} true if valid, or false otherwise
|
||||
*/
|
||||
#validateBase() {
|
||||
if (this.#baseCode.value === "") {
|
||||
this.#baseControl.classList.add("is-invalid");
|
||||
this.#baseError.innerText = A_("Please select the base account.");
|
||||
return false;
|
||||
}
|
||||
this.#baseControl.classList.remove("is-invalid");
|
||||
this.#baseError.innerText = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the title.
|
||||
*
|
||||
* @returns {boolean} true if valid, or false otherwise
|
||||
*/
|
||||
#validateTitle() {
|
||||
this.#title.value = this.#title.value.trim();
|
||||
if (this.#title.value === "") {
|
||||
this.#title.classList.add("is-invalid");
|
||||
this.#titleError.innerText = A_("Please fill in the title.");
|
||||
return false;
|
||||
}
|
||||
this.#title.classList.remove("is-invalid");
|
||||
this.#titleError.innerText = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The account form
|
||||
* @type {AccountForm} the form
|
||||
*/
|
||||
static #form;
|
||||
|
||||
static initialize() {
|
||||
this.#form = new AccountForm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The base account selector.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function initializeBaseAccountQuery() {
|
||||
const query = document.getElementById("accounting-base-selector-query");
|
||||
const optionList = document.getElementById("accounting-base-option-list");
|
||||
const options = Array.from(document.getElementsByClassName("accounting-base-option"));
|
||||
const queryNoResult = document.getElementById("accounting-base-option-no-result");
|
||||
query.addEventListener("input", () => {
|
||||
if (query.value === "") {
|
||||
for (const option of options) {
|
||||
class BaseAccountSelector {
|
||||
|
||||
/**
|
||||
* The account form
|
||||
* @type {AccountForm}
|
||||
*/
|
||||
#form;
|
||||
|
||||
/**
|
||||
* The selector modal
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
#modal;
|
||||
|
||||
/**
|
||||
* The query input
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
#query;
|
||||
|
||||
/**
|
||||
* The error message when the query has no result
|
||||
* @type {HTMLParagraphElement}
|
||||
*/
|
||||
#queryNoResult;
|
||||
|
||||
/**
|
||||
* The option list
|
||||
* @type {HTMLUListElement}
|
||||
*/
|
||||
#optionList;
|
||||
|
||||
/**
|
||||
* The options
|
||||
* @type {HTMLLIElement[]}
|
||||
*/
|
||||
#options;
|
||||
|
||||
/**
|
||||
* The button to clear the base account value
|
||||
* @type {HTMLButtonElement}
|
||||
*/
|
||||
#clearButton;
|
||||
|
||||
/**
|
||||
* Constructs the base account selector.
|
||||
*
|
||||
* @param form {AccountForm} the form
|
||||
*/
|
||||
constructor(form) {
|
||||
this.#form = form;
|
||||
this.#modal = document.getElementById("accounting-base-selector-modal");
|
||||
this.#query = document.getElementById("accounting-base-selector-query");
|
||||
this.#optionList = document.getElementById("accounting-base-selector-option-list");
|
||||
// noinspection JSValidateTypes
|
||||
this.#options = Array.from(document.getElementsByClassName("accounting-base-selector-option"));
|
||||
this.#clearButton = document.getElementById("accounting-base-selector-clear");
|
||||
this.#queryNoResult = document.getElementById("accounting-base-selector-option-no-result");
|
||||
this.#modal.addEventListener("hidden.bs.modal", () => {
|
||||
this.#form.onBaseAccountSelectorClosed();
|
||||
});
|
||||
for (const option of this.#options) {
|
||||
option.onclick = () => {
|
||||
this.#form.setBaseAccount(option.dataset.code, option.dataset.content);
|
||||
};
|
||||
}
|
||||
this.#clearButton.onclick = () => {
|
||||
this.#form.clearBaseAccount();
|
||||
};
|
||||
this.#initializeBaseAccountQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the query.
|
||||
*
|
||||
*/
|
||||
#initializeBaseAccountQuery() {
|
||||
this.#query.addEventListener("input", () => {
|
||||
if (this.#query.value === "") {
|
||||
for (const option of this.#options) {
|
||||
option.classList.remove("d-none");
|
||||
}
|
||||
optionList.classList.remove("d-none");
|
||||
queryNoResult.classList.add("d-none");
|
||||
this.#optionList.classList.remove("d-none");
|
||||
this.#queryNoResult.classList.add("d-none");
|
||||
return
|
||||
}
|
||||
let hasAnyMatched = false;
|
||||
for (const option of options) {
|
||||
for (const option of this.#options) {
|
||||
const queryValues = JSON.parse(option.dataset.queryValues);
|
||||
let isMatched = false;
|
||||
for (const queryValue of queryValues) {
|
||||
if (queryValue.includes(query.value)) {
|
||||
if (queryValue.includes(this.#query.value)) {
|
||||
isMatched = true;
|
||||
break;
|
||||
}
|
||||
@ -130,65 +323,36 @@ function initializeBaseAccountQuery() {
|
||||
}
|
||||
}
|
||||
if (!hasAnyMatched) {
|
||||
optionList.classList.add("d-none");
|
||||
queryNoResult.classList.remove("d-none");
|
||||
this.#optionList.classList.add("d-none");
|
||||
this.#queryNoResult.classList.remove("d-none");
|
||||
} else {
|
||||
optionList.classList.remove("d-none");
|
||||
queryNoResult.classList.add("d-none");
|
||||
this.#optionList.classList.remove("d-none");
|
||||
this.#queryNoResult.classList.add("d-none");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the form.
|
||||
* The callback when the base account selector is shown.
|
||||
*
|
||||
* @returns {boolean} true if valid, or false otherwise
|
||||
* @private
|
||||
* @param baseCode {string} the active base code
|
||||
*/
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
isValid = validateBase() && isValid;
|
||||
isValid = validateTitle() && isValid;
|
||||
return isValid;
|
||||
onOpen(baseCode) {
|
||||
for (const option of this.#options) {
|
||||
if (option.dataset.code === baseCode) {
|
||||
option.classList.add("active");
|
||||
} else {
|
||||
option.classList.remove("active");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the base account.
|
||||
*
|
||||
* @returns {boolean} true if valid, or false otherwise
|
||||
* @private
|
||||
*/
|
||||
function validateBase() {
|
||||
const field = document.getElementById("accounting-base-code");
|
||||
const error = document.getElementById("accounting-base-code-error");
|
||||
const displayField = document.getElementById("accounting-base");
|
||||
field.value = field.value.trim();
|
||||
if (field.value === "") {
|
||||
displayField.classList.add("is-invalid");
|
||||
error.innerText = A_("Please select the base account.");
|
||||
return false;
|
||||
}
|
||||
displayField.classList.remove("is-invalid");
|
||||
error.innerText = "";
|
||||
return true;
|
||||
if (baseCode === "") {
|
||||
this.#clearButton.classList.add("btn-secondary")
|
||||
this.#clearButton.classList.remove("btn-danger");
|
||||
this.#clearButton.disabled = true;
|
||||
} else {
|
||||
this.#clearButton.classList.add("btn-danger");
|
||||
this.#clearButton.classList.remove("btn-secondary")
|
||||
this.#clearButton.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the title.
|
||||
*
|
||||
* @returns {boolean} true if valid, or false otherwise
|
||||
* @private
|
||||
*/
|
||||
function validateTitle() {
|
||||
const field = document.getElementById("accounting-title");
|
||||
const error = document.getElementById("accounting-title-error");
|
||||
field.value = field.value.trim();
|
||||
if (field.value === "") {
|
||||
field.classList.add("is-invalid");
|
||||
error.innerText = A_("Please fill in the title.");
|
||||
return false;
|
||||
}
|
||||
field.classList.remove("is-invalid");
|
||||
error.innerText = "";
|
||||
return true;
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ First written: 2023/2/1
|
||||
{% endif %}
|
||||
<div class="form-floating mb-3">
|
||||
<input id="accounting-base-code" type="hidden" name="base_code" value="{{ form.base_code.data|accounting_default }}">
|
||||
<div id="accounting-base" class="form-control accounting-clickable accounting-material-text-field {% if form.base_code.data %} accounting-not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-base-selector-modal">
|
||||
<div id="accounting-base-control" class="form-control accounting-clickable accounting-material-text-field {% if form.base_code.data %} accounting-not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-base-selector-modal">
|
||||
<label class="form-label" for="accounting-base">{{ A_("Base account") }}</label>
|
||||
<div id="accounting-base-content">
|
||||
<div id="accounting-base">
|
||||
{% if form.base_code.data %}
|
||||
{% if form.base_code.errors %}
|
||||
{{ A_("(Unknown)") }}
|
||||
@ -53,7 +53,7 @@ First written: 2023/2/1
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="accounting-base-code-error" class="invalid-feedback">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
|
||||
<div id="accounting-base-error" class="invalid-feedback">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
@ -99,21 +99,21 @@ First written: 2023/2/1
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<ul id="accounting-base-option-list" class="list-group accounting-selector-list">
|
||||
<ul id="accounting-base-selector-option-list" class="list-group accounting-selector-list">
|
||||
{% for base in form.base_options %}
|
||||
<li id="accounting-base-option-{{ base.code }}" class="list-group-item accounting-base-option accounting-clickable" data-code="{{ base.code }}" data-content="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}">
|
||||
<li class="list-group-item accounting-clickable accounting-base-selector-option" data-code="{{ base.code }}" data-content="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}" data-bs-dismiss="modal">
|
||||
{{ base }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p id="accounting-base-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
|
||||
<p id="accounting-base-selector-option-no-result" class="d-none">{{ A_("There is no data.") }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
|
||||
{% if form.base_code.data %}
|
||||
<button id="accounting-btn-clear-base" type="button" class="btn btn-danger">{{ A_("Clear") }}</button>
|
||||
<button id="accounting-base-selector-clear" type="button" class="btn btn-danger" data-bs-dismiss="modal">{{ A_("Clear") }}</button>
|
||||
{% else %}
|
||||
<button id="accounting-btn-clear-base" type="button" class="btn btn-secondary" disabled="disabled">{{ A_("Clear") }}</button>
|
||||
<button id="accounting-base-selector-clear" type="button" class="btn btn-secondary" disabled="disabled" data-bs-dismiss="modal">{{ A_("Clear") }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user