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,171 +24,335 @@
|
|||||||
|
|
||||||
// Initializes the page JavaScript.
|
// Initializes the page JavaScript.
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
initializeBaseAccountSelector();
|
AccountForm.initialize();
|
||||||
document.getElementById("accounting-base-code")
|
|
||||||
.onchange = validateBase;
|
|
||||||
document.getElementById("accounting-title")
|
|
||||||
.onchange = validateTitle;
|
|
||||||
document.getElementById("accounting-form")
|
|
||||||
.onsubmit = validateForm;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the base account selector.
|
* The account form.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function initializeBaseAccountSelector() {
|
class AccountForm {
|
||||||
const selector = document.getElementById("accounting-base-selector-modal");
|
|
||||||
const base = document.getElementById("accounting-base");
|
/**
|
||||||
const baseCode = document.getElementById("accounting-base-code");
|
* The base account selector
|
||||||
const baseContent = document.getElementById("accounting-base-content");
|
* @type {BaseAccountSelector}
|
||||||
const isOffsetNeededControl = document.getElementById("accounting-is-offset-needed-control");
|
*/
|
||||||
const isOffsetNeeded = document.getElementById("accounting-is-offset-needed");
|
#baseAccountSelector;
|
||||||
const options = Array.from(document.getElementsByClassName("accounting-base-option"));
|
|
||||||
const btnClear = document.getElementById("accounting-btn-clear-base");
|
/**
|
||||||
base.onclick = () => {
|
* The form element
|
||||||
base.classList.add("accounting-not-empty");
|
* @type {HTMLFormElement}
|
||||||
for (const option of options) {
|
*/
|
||||||
option.classList.remove("active");
|
#formElement;
|
||||||
}
|
|
||||||
const selected = document.getElementById("accounting-base-option-" + baseCode.value);
|
/**
|
||||||
if (selected !== null) {
|
* The control of the base account
|
||||||
selected.classList.add("active");
|
* @type {HTMLDivElement}
|
||||||
}
|
*/
|
||||||
};
|
#baseControl;
|
||||||
selector.addEventListener("hidden.bs.modal", () => {
|
|
||||||
if (baseCode.value === "") {
|
/**
|
||||||
base.classList.remove("accounting-not-empty");
|
* The input of the base account
|
||||||
}
|
* @type {HTMLInputElement}
|
||||||
});
|
*/
|
||||||
for (const option of options) {
|
#baseCode;
|
||||||
option.onclick = () => {
|
|
||||||
baseCode.value = option.dataset.code;
|
/**
|
||||||
baseContent.innerText = option.dataset.content;
|
* The base account
|
||||||
if (["1", "2"].includes(option.dataset.content.substring(0, 1))) {
|
* @type {HTMLDivElement}
|
||||||
isOffsetNeededControl.classList.remove("d-none");
|
*/
|
||||||
isOffsetNeeded.disabled = false;
|
#base;
|
||||||
} else {
|
|
||||||
isOffsetNeededControl.classList.add("d-none");
|
/**
|
||||||
isOffsetNeeded.disabled = true;
|
* The error message for the base account
|
||||||
isOffsetNeeded.checked = false;
|
* @type {HTMLDivElement}
|
||||||
}
|
*/
|
||||||
btnClear.classList.add("btn-danger");
|
#baseError;
|
||||||
btnClear.classList.remove("btn-secondary")
|
|
||||||
btnClear.disabled = false;
|
/**
|
||||||
validateBase();
|
* The title
|
||||||
bootstrap.Modal.getInstance(selector).hide();
|
* @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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
btnClear.onclick = () => {
|
|
||||||
baseCode.value = "";
|
/**
|
||||||
baseContent.innerText = "";
|
* The callback when the base account selector is closed.
|
||||||
btnClear.classList.add("btn-secondary")
|
*
|
||||||
btnClear.classList.remove("btn-danger");
|
*/
|
||||||
btnClear.disabled = true;
|
onBaseAccountSelectorClosed() {
|
||||||
validateBase();
|
if (this.#baseCode.value === "") {
|
||||||
bootstrap.Modal.getInstance(selector).hide();
|
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 {
|
||||||
|
this.#isOffsetNeededControl.classList.add("d-none");
|
||||||
|
this.#isOffsetNeeded.disabled = true;
|
||||||
|
this.#isOffsetNeeded.checked = false;
|
||||||
|
}
|
||||||
|
this.#validateBase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
}
|
}
|
||||||
initializeBaseAccountQuery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the query on the base account options.
|
* The base account selector.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function initializeBaseAccountQuery() {
|
class BaseAccountSelector {
|
||||||
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"));
|
* The account form
|
||||||
const queryNoResult = document.getElementById("accounting-base-option-no-result");
|
* @type {AccountForm}
|
||||||
query.addEventListener("input", () => {
|
*/
|
||||||
if (query.value === "") {
|
#form;
|
||||||
for (const option of options) {
|
|
||||||
option.classList.remove("d-none");
|
/**
|
||||||
}
|
* The selector modal
|
||||||
optionList.classList.remove("d-none");
|
* @type {HTMLDivElement}
|
||||||
queryNoResult.classList.add("d-none");
|
*/
|
||||||
return
|
#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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let hasAnyMatched = false;
|
this.#clearButton.onclick = () => {
|
||||||
for (const option of options) {
|
this.#form.clearBaseAccount();
|
||||||
const queryValues = JSON.parse(option.dataset.queryValues);
|
};
|
||||||
let isMatched = false;
|
this.#initializeBaseAccountQuery();
|
||||||
for (const queryValue of queryValues) {
|
}
|
||||||
if (queryValue.includes(query.value)) {
|
|
||||||
isMatched = true;
|
/**
|
||||||
break;
|
* Initializes the query.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#initializeBaseAccountQuery() {
|
||||||
|
this.#query.addEventListener("input", () => {
|
||||||
|
if (this.#query.value === "") {
|
||||||
|
for (const option of this.#options) {
|
||||||
|
option.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
this.#optionList.classList.remove("d-none");
|
||||||
|
this.#queryNoResult.classList.add("d-none");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let hasAnyMatched = false;
|
||||||
|
for (const option of this.#options) {
|
||||||
|
const queryValues = JSON.parse(option.dataset.queryValues);
|
||||||
|
let isMatched = false;
|
||||||
|
for (const queryValue of queryValues) {
|
||||||
|
if (queryValue.includes(this.#query.value)) {
|
||||||
|
isMatched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isMatched) {
|
||||||
|
option.classList.remove("d-none");
|
||||||
|
hasAnyMatched = true;
|
||||||
|
} else {
|
||||||
|
option.classList.add("d-none");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMatched) {
|
if (!hasAnyMatched) {
|
||||||
option.classList.remove("d-none");
|
this.#optionList.classList.add("d-none");
|
||||||
hasAnyMatched = true;
|
this.#queryNoResult.classList.remove("d-none");
|
||||||
} else {
|
} else {
|
||||||
option.classList.add("d-none");
|
this.#optionList.classList.remove("d-none");
|
||||||
|
this.#queryNoResult.classList.add("d-none");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback when the base account selector is shown.
|
||||||
|
*
|
||||||
|
* @param baseCode {string} the active base code
|
||||||
|
*/
|
||||||
|
onOpen(baseCode) {
|
||||||
|
for (const option of this.#options) {
|
||||||
|
if (option.dataset.code === baseCode) {
|
||||||
|
option.classList.add("active");
|
||||||
|
} else {
|
||||||
|
option.classList.remove("active");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasAnyMatched) {
|
if (baseCode === "") {
|
||||||
optionList.classList.add("d-none");
|
this.#clearButton.classList.add("btn-secondary")
|
||||||
queryNoResult.classList.remove("d-none");
|
this.#clearButton.classList.remove("btn-danger");
|
||||||
|
this.#clearButton.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
optionList.classList.remove("d-none");
|
this.#clearButton.classList.add("btn-danger");
|
||||||
queryNoResult.classList.add("d-none");
|
this.#clearButton.classList.remove("btn-secondary")
|
||||||
|
this.#clearButton.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the form.
|
|
||||||
*
|
|
||||||
* @returns {boolean} true if valid, or false otherwise
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function validateForm() {
|
|
||||||
let isValid = true;
|
|
||||||
isValid = validateBase() && isValid;
|
|
||||||
isValid = validateTitle() && isValid;
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 %}
|
{% endif %}
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
<input id="accounting-base-code" type="hidden" name="base_code" value="{{ form.base_code.data|accounting_default }}">
|
<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>
|
<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.data %}
|
||||||
{% if form.base_code.errors %}
|
{% if form.base_code.errors %}
|
||||||
{{ A_("(Unknown)") }}
|
{{ A_("(Unknown)") }}
|
||||||
@ -53,7 +53,7 @@ First written: 2023/2/1
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
<div class="form-floating mb-3">
|
||||||
@ -99,21 +99,21 @@ First written: 2023/2/1
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</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 %}
|
{% 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 }}
|
{{ base }}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</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>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
|
||||||
{% if form.base_code.data %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user