Replaced the function-based JavaScript account selector with the AccountSelector class that does things better.

This commit is contained in:
依瑪貓 2023-02-28 23:30:38 +08:00
parent 319f0aed90
commit a31ce3c400
7 changed files with 205 additions and 137 deletions

View File

@ -23,54 +23,54 @@
// Initializes the page JavaScript. // Initializes the page JavaScript.
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
initializeAccountSelectors(); AccountSelector.initialize();
}); });
/** /**
* Initializes the account selectors. * The account selector.
* *
* @private
*/ */
function initializeAccountSelectors() { class AccountSelector {
const selectors = Array.from(document.getElementsByClassName("accounting-account-selector-modal"));
/**
* The entry type
* @type {string}
*/
#entryType;
/**
* The prefix of the HTML ID and class
* @type {string}
*/
#prefix;
/**
* Constructs an account selector.
*
* @param modal {HTMLFormElement} the account selector modal
*/
constructor(modal) {
this.#entryType = modal.dataset.entryType;
this.#prefix = "accounting-account-selector-" + modal.dataset.entryType;
this.#init();
}
/**
* Initializes the account selector.
*
*/
#init() {
const formAccountControl = document.getElementById("accounting-entry-form-account-control"); const formAccountControl = document.getElementById("accounting-entry-form-account-control");
const formAccount = document.getElementById("accounting-entry-form-account"); const formAccount = document.getElementById("accounting-entry-form-account");
formAccountControl.onclick = function () { const more = document.getElementById(this.#prefix + "-more");
const entryForm = document.getElementById("accounting-entry-form"); const btnClear = document.getElementById(this.#prefix + "-btn-clear");
const prefix = "accounting-account-selector-" + entryForm.dataset.entryType; const options = Array.from(document.getElementsByClassName(this.#prefix + "-option"));
const query = document.getElementById(prefix + "-query") const selector1 = this
const more = document.getElementById(prefix + "-more");
const options = Array.from(document.getElementsByClassName(prefix + "-option"));
const btnClear = document.getElementById(prefix + "-btn-clear");
query.value = "";
more.classList.remove("d-none");
filterAccountOptions(prefix);
for (const option of options) {
if (option.dataset.code === formAccount.dataset.code) {
option.classList.add("active");
} else {
option.classList.remove("active");
}
}
if (formAccount.dataset.code === "") {
btnClear.classList.add("btn-secondary");
btnClear.classList.remove("btn-danger");
btnClear.disabled = true;
} else {
btnClear.classList.add("btn-danger");
btnClear.classList.remove("btn-secondary");
btnClear.disabled = false;
}
};
for (const selector of selectors) {
const more = document.getElementById(selector.dataset.prefix + "-more");
const btnClear = document.getElementById(selector.dataset.prefix + "-btn-clear");
const options = Array.from(document.getElementsByClassName(selector.dataset.prefix + "-option"));
more.onclick = function () { more.onclick = function () {
more.classList.add("d-none"); more.classList.add("d-none");
filterAccountOptions(selector.dataset.prefix); selector1.#filterAccountOptions();
}; };
initializeAccountQuery(selector); this.#initializeAccountQuery();
btnClear.onclick = function () { btnClear.onclick = function () {
formAccountControl.classList.remove("accounting-not-empty"); formAccountControl.classList.remove("accounting-not-empty");
formAccount.innerText = ""; formAccount.innerText = "";
@ -88,40 +88,36 @@ function initializeAccountSelectors() {
}; };
} }
} }
}
/** /**
* Initializes the query on the account options. * Initializes the query on the account options.
* *
* @param selector {HTMLDivElement} the selector modal
* @private
*/ */
function initializeAccountQuery(selector) { #initializeAccountQuery() {
const query = document.getElementById(selector.dataset.prefix + "-query"); const query = document.getElementById(this.#prefix + "-query");
const helper = this;
query.addEventListener("input", function () { query.addEventListener("input", function () {
filterAccountOptions(selector.dataset.prefix); helper.#filterAccountOptions();
}); });
} }
/** /**
* Filters the account options. * Filters the account options.
* *
* @param prefix {string} the HTML ID and class prefix
* @private
*/ */
function filterAccountOptions(prefix) { #filterAccountOptions() {
const query = document.getElementById(prefix + "-query"); const query = document.getElementById(this.#prefix + "-query");
const optionList = document.getElementById(prefix + "-option-list"); const optionList = document.getElementById(this.#prefix + "-option-list");
if (optionList === null) { if (optionList === null) {
console.log(prefix + "-option-list"); console.log(this.#prefix + "-option-list");
} }
const options = Array.from(document.getElementsByClassName(prefix + "-option")); const options = Array.from(document.getElementsByClassName(this.#prefix + "-option"));
const more = document.getElementById(prefix + "-more"); const more = document.getElementById(this.#prefix + "-more");
const queryNoResult = document.getElementById(prefix + "-option-no-result"); const queryNoResult = document.getElementById(this.#prefix + "-option-no-result");
const codesInUse = getAccountCodeUsedInForm(); const codesInUse = this.#getAccountCodeUsedInForm();
let shouldAnyShow = false; let shouldAnyShow = false;
for (const option of options) { for (const option of options) {
const shouldShow = shouldAccountOptionShow(option, more, codesInUse, query); const shouldShow = this.#shouldAccountOptionShow(option, more, codesInUse, query);
if (shouldShow) { if (shouldShow) {
option.classList.remove("d-none"); option.classList.remove("d-none");
shouldAnyShow = true; shouldAnyShow = true;
@ -136,9 +132,24 @@ function filterAccountOptions(prefix) {
optionList.classList.remove("d-none"); optionList.classList.remove("d-none");
queryNoResult.classList.add("d-none"); queryNoResult.classList.add("d-none");
} }
} }
/** /**
* Returns the account codes that are used in the form.
*
* @return {string[]} the account codes that are used in the form
*/
#getAccountCodeUsedInForm() {
const accountCodes = Array.from(document.getElementsByClassName("accounting-account-code"));
const formAccount = document.getElementById("accounting-entry-form-account");
const inUse = [formAccount.dataset.code];
for (const accountCode of accountCodes) {
inUse.push(accountCode.value);
}
return inUse
}
/**
* Returns whether an account option should show. * Returns whether an account option should show.
* *
* @param option {HTMLLIElement} the account option * @param option {HTMLLIElement} the account option
@ -146,9 +157,8 @@ function filterAccountOptions(prefix) {
* @param inUse {string[]} the account codes that are used in the form * @param inUse {string[]} the account codes that are used in the form
* @param query {HTMLInputElement} the query element, if any * @param query {HTMLInputElement} the query element, if any
* @return {boolean} true if the account option should show, or false otherwise * @return {boolean} true if the account option should show, or false otherwise
* @private
*/ */
function shouldAccountOptionShow(option, more, inUse, query) { #shouldAccountOptionShow(option, more, inUse, query) {
const isQueryMatched = function () { const isQueryMatched = function () {
if (query.value === "") { if (query.value === "") {
return true; return true;
@ -168,20 +178,78 @@ function shouldAccountOptionShow(option, more, inUse, query) {
return option.classList.contains("accounting-account-in-use") || inUse.includes(option.dataset.code); return option.classList.contains("accounting-account-in-use") || inUse.includes(option.dataset.code);
}; };
return isMoreMatched() && isQueryMatched(); return isMoreMatched() && isQueryMatched();
}
/**
* Returns the account codes that are used in the form.
*
* @return {string[]} the account codes that are used in the form
* @private
*/
function getAccountCodeUsedInForm() {
const accountCodes = Array.from(document.getElementsByClassName("accounting-account-code"));
const formAccount = document.getElementById("accounting-entry-form-account");
const inUse = [formAccount.dataset.code];
for (const accountCode of accountCodes) {
inUse.push(accountCode.value);
} }
return inUse
/**
* Initializes the account selector when it is shown.
*
*/
initShow() {
const formAccount = document.getElementById("accounting-entry-form-account");
const query = document.getElementById(this.#prefix + "-query")
const more = document.getElementById(this.#prefix + "-more");
const options = Array.from(document.getElementsByClassName(this.#prefix + "-option"));
const btnClear = document.getElementById(this.#prefix + "-btn-clear");
query.value = "";
more.classList.remove("d-none");
this.#filterAccountOptions();
for (const option of options) {
if (option.dataset.code === formAccount.dataset.code) {
option.classList.add("active");
} else {
option.classList.remove("active");
}
}
if (formAccount.dataset.code === "") {
btnClear.classList.add("btn-secondary");
btnClear.classList.remove("btn-danger");
btnClear.disabled = true;
} else {
btnClear.classList.add("btn-danger");
btnClear.classList.remove("btn-secondary");
btnClear.disabled = false;
}
}
/**
* The account selectors.
* @type {{debit: AccountSelector, credit: AccountSelector}}
*/
static #selectors = {}
/**
* Initializes the account selectors.
*
*/
static initialize() {
const modals = Array.from(document.getElementsByClassName("accounting-account-selector-modal"));
for (const modal of modals) {
const selector = new AccountSelector(modal);
this.#selectors[selector.#entryType] = selector;
}
this.#initializeTransactionForm();
}
/**
* Initializes the transaction form.
*
*/
static #initializeTransactionForm() {
const entryForm = document.getElementById("accounting-entry-form");
const formAccountControl = document.getElementById("accounting-entry-form-account-control");
const selectors = this.#selectors;
formAccountControl.onclick = function () {
selectors[entryForm.dataset.entryType].initShow();
};
}
/**
* Initializes the account selector for the journal entry form.
*x
*/
static initializeJournalEntryForm() {
const entryForm = document.getElementById("accounting-entry-form");
const formAccountControl = document.getElementById("accounting-entry-form-account-control");
formAccountControl.dataset.bsTarget = "#accounting-account-selector-" + entryForm.dataset.entryType + "-modal";
}
} }

View File

@ -167,7 +167,6 @@ function initializeNewEntryButton(button) {
entryForm.dataset.entryIndex = button.dataset.entryIndex; entryForm.dataset.entryIndex = button.dataset.entryIndex;
formAccountControl.classList.remove("accounting-not-empty"); formAccountControl.classList.remove("accounting-not-empty");
formAccountControl.classList.remove("is-invalid"); formAccountControl.classList.remove("is-invalid");
formAccountControl.dataset.bsTarget = button.dataset.accountModal;
formAccount.innerText = ""; formAccount.innerText = "";
formAccount.dataset.code = ""; formAccount.dataset.code = "";
formAccount.dataset.text = ""; formAccount.dataset.text = "";
@ -181,6 +180,7 @@ function initializeNewEntryButton(button) {
formAmount.value = ""; formAmount.value = "";
formAmount.classList.remove("is-invalid"); formAmount.classList.remove("is-invalid");
formAmountError.innerText = ""; formAmountError.innerText = "";
AccountSelector.initializeJournalEntryForm();
SummaryHelper.initializeNewJournalEntry(button.dataset.entryType); SummaryHelper.initializeNewJournalEntry(button.dataset.entryType);
}; };
} }
@ -225,7 +225,6 @@ function initializeJournalEntry(entry) {
} else { } else {
formAccountControl.classList.add("accounting-not-empty"); formAccountControl.classList.add("accounting-not-empty");
} }
formAccountControl.dataset.bsTarget = entry.dataset.accountModal;
formAccount.innerText = accountCode.dataset.text; formAccount.innerText = accountCode.dataset.text;
formAccount.dataset.code = accountCode.value; formAccount.dataset.code = accountCode.value;
formAccount.dataset.text = accountCode.dataset.text; formAccount.dataset.text = accountCode.dataset.text;
@ -238,6 +237,7 @@ function initializeJournalEntry(entry) {
formSummary.dataset.value = summary.value; formSummary.dataset.value = summary.value;
formSummary.innerText = summary.value; formSummary.innerText = summary.value;
formAmount.value = amount.value; formAmount.value = amount.value;
AccountSelector.initializeJournalEntryForm();
validateJournalEntryForm(); validateJournalEntryForm();
}; };
} }

View File

@ -70,7 +70,7 @@ First written: 2023/2/25
</div> </div>
<div> <div>
<button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="debit" data-entry-index="new" data-account-modal="#accounting-account-selector-debit-modal" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-debit-modal"> <button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="debit" data-entry-index="new" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-debit-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>

View File

@ -19,7 +19,7 @@ account-selector-modal.html: The modal for the account selector
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25 First written: 2023/2/25
#} #}
<div id="accounting-account-selector-{{ entry_type }}-modal" class="modal fade accounting-account-selector-modal" data-prefix="accounting-account-selector-{{ entry_type }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ entry_type }}-modal-label" aria-hidden="true"> <div id="accounting-account-selector-{{ entry_type }}-modal" class="modal fade accounting-account-selector-modal" data-entry-type="{{ entry_type }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ entry_type }}-modal-label" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">

View File

@ -20,7 +20,7 @@ Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25 First written: 2023/2/25
#} #}
{# <ul> For SonarQube not to complain about incorrect HTML #} {# <ul> For SonarQube not to complain about incorrect HTML #}
<li id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}" class="list-group-item list-group-item-action d-flex justify-content-between accounting-entry accounting-currency-{{ currency_index }}-{{ entry_type }}" data-currency-index="{{ currency_index }}" data-entry-type="{{ entry_type }}" data-entry-index="{{ entry_index }}" data-account-modal="#accounting-account-selector-{{ entry_type }}-modal" data-prefix="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}"> <li id="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}" class="list-group-item list-group-item-action d-flex justify-content-between accounting-entry accounting-currency-{{ currency_index }}-{{ entry_type }}" data-currency-index="{{ currency_index }}" data-entry-type="{{ entry_type }}" data-entry-index="{{ entry_index }}" data-prefix="accounting-currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}">
{% if entry_id %} {% if entry_id %}
<input type="hidden" name="currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-eid" value="{{ entry_id }}"> <input type="hidden" name="currency-{{ currency_index }}-{{ entry_type }}-{{ entry_index }}-eid" value="{{ entry_id }}">
{% endif %} {% endif %}

View File

@ -70,7 +70,7 @@ First written: 2023/2/25
</div> </div>
<div> <div>
<button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="credit" data-entry-index="new" data-account-modal="#accounting-account-selector-credit-modal" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-credit-modal"> <button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="credit" data-entry-index="new" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-credit-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>

View File

@ -72,7 +72,7 @@ First written: 2023/2/25
</div> </div>
<div> <div>
<button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="debit" data-entry-index="new" data-account-modal="#accounting-account-selector-debit-modal" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-debit-modal"> <button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="debit" data-entry-index="new" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-debit-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>
@ -112,7 +112,7 @@ First written: 2023/2/25
</div> </div>
<div> <div>
<button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="credit" data-entry-index="new" data-account-modal="#accounting-account-selector-credit-modal" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-credit-modal"> <button class="btn btn-primary accounting-btn-new-entry accounting-currency-{{ currency_index }}-btn-new-entry" type="button" data-currency-index="{{ currency_index }}" data-entry-type="credit" data-entry-index="new" data-bs-toggle="modal" data-bs-target="#accounting-summary-helper-credit-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>