Added the JavaScript AccountOption class to object-ize the account options in the journal entry form.
This commit is contained in:
		| @@ -32,7 +32,7 @@ class AccountSelector { | ||||
|      * The line item editor | ||||
|      * @type {JournalEntryLineItemEditor} | ||||
|      */ | ||||
|     #lineItemEditor; | ||||
|     lineItemEditor; | ||||
|  | ||||
|     /** | ||||
|      * Either "debit" or "credit" | ||||
| @@ -66,7 +66,7 @@ class AccountSelector { | ||||
|  | ||||
|     /** | ||||
|      * The options | ||||
|      * @type {HTMLLIElement[]} | ||||
|      * @type {AccountOption[]} | ||||
|      */ | ||||
|     #options; | ||||
|  | ||||
| @@ -76,6 +76,12 @@ class AccountSelector { | ||||
|      */ | ||||
|     #more; | ||||
|  | ||||
|     /** | ||||
|      * Whether to show all accounts | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     #isShowMore = false; | ||||
|  | ||||
|     /** | ||||
|      * Constructs an account selector. | ||||
|      * | ||||
| @@ -83,27 +89,23 @@ class AccountSelector { | ||||
|      * @param debitCredit {string} either "debit" or "credit" | ||||
|      */ | ||||
|     constructor(lineItemEditor, debitCredit) { | ||||
|         this.#lineItemEditor = lineItemEditor | ||||
|         this.lineItemEditor = lineItemEditor | ||||
|         this.#debitCredit = debitCredit; | ||||
|         const prefix = "accounting-account-selector-" + debitCredit; | ||||
|         this.#query = document.getElementById(prefix + "-query"); | ||||
|         this.#queryNoResult = document.getElementById(prefix + "-option-no-result"); | ||||
|         this.#optionList = document.getElementById(prefix + "-option-list"); | ||||
|         // noinspection JSValidateTypes | ||||
|         this.#options = Array.from(document.getElementsByClassName(prefix + "-option")); | ||||
|         this.#options = Array.from(document.getElementsByClassName(prefix + "-option")).map((element) => new AccountOption(this, element)); | ||||
|         this.#more = document.getElementById(prefix + "-more"); | ||||
|         this.#clearButton = document.getElementById(prefix + "-btn-clear"); | ||||
|  | ||||
|         this.#more.onclick = () => { | ||||
|             this.#isShowMore = true; | ||||
|             this.#more.classList.add("d-none"); | ||||
|             this.#filterOptions(); | ||||
|         }; | ||||
|         this.#clearButton.onclick = () => this.#lineItemEditor.clearAccount(); | ||||
|         for (const option of this.#options) { | ||||
|             option.onclick = () => this.#lineItemEditor.saveAccount(option.dataset.code, option.dataset.text, option.classList.contains("accounting-account-is-need-offset")); | ||||
|         } | ||||
|         this.#query.addEventListener("input", () => { | ||||
|             this.#filterOptions(); | ||||
|         }); | ||||
|         this.#query.oninput = () => this.#filterOptions(); | ||||
|         this.#clearButton.onclick = () => this.lineItemEditor.clearAccount(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -112,17 +114,16 @@ class AccountSelector { | ||||
|      */ | ||||
|     #filterOptions() { | ||||
|         const codesInUse = this.#getCodesUsedInForm(); | ||||
|         let shouldAnyShow = false; | ||||
|         let isAnyMatched = false; | ||||
|         for (const option of this.#options) { | ||||
|             const shouldShow = this.#shouldOptionShow(option, this.#more, codesInUse, this.#query); | ||||
|             if (shouldShow) { | ||||
|                 option.classList.remove("d-none"); | ||||
|                 shouldAnyShow = true; | ||||
|             if (option.isMatched(this.#isShowMore, codesInUse, this.#query.value)) { | ||||
|                 option.setShown(true); | ||||
|                 isAnyMatched = true; | ||||
|             } else { | ||||
|                 option.classList.add("d-none"); | ||||
|                 option.setShown(false); | ||||
|             } | ||||
|         } | ||||
|         if (!shouldAnyShow && this.#more.classList.contains("d-none")) { | ||||
|         if (!isAnyMatched) { | ||||
|             this.#optionList.classList.add("d-none"); | ||||
|             this.#queryNoResult.classList.remove("d-none"); | ||||
|         } else { | ||||
| @@ -137,60 +138,26 @@ class AccountSelector { | ||||
|      * @return {string[]} the account codes that are used in the form | ||||
|      */ | ||||
|     #getCodesUsedInForm() { | ||||
|         const inUse = this.#lineItemEditor.form.getAccountCodesUsed(this.#debitCredit); | ||||
|         if (this.#lineItemEditor.accountCode !== null) { | ||||
|             inUse.push(this.#lineItemEditor.accountCode); | ||||
|         const inUse = this.lineItemEditor.form.getAccountCodesUsed(this.#debitCredit); | ||||
|         if (this.lineItemEditor.accountCode !== null) { | ||||
|             inUse.push(this.lineItemEditor.accountCode); | ||||
|         } | ||||
|         return inUse | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns whether an option should show. | ||||
|      * | ||||
|      * @param option {HTMLLIElement} the option | ||||
|      * @param more {HTMLLIElement} the more element | ||||
|      * @param inUse {string[]} the account codes that are used in the form | ||||
|      * @param query {HTMLInputElement} the query element, if any | ||||
|      * @return {boolean} true if the option should show, or false otherwise | ||||
|      */ | ||||
|     #shouldOptionShow(option, more, inUse, query) { | ||||
|         const isQueryMatched = () => { | ||||
|             if (query.value === "") { | ||||
|                 return true; | ||||
|             } | ||||
|             const queryValues = JSON.parse(option.dataset.queryValues); | ||||
|             for (const queryValue of queryValues) { | ||||
|                 if (queryValue.toLowerCase().includes(query.value.toLowerCase())) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             return false; | ||||
|         }; | ||||
|         const isMoreMatched = () => { | ||||
|             if (more.classList.contains("d-none")) { | ||||
|                 return true; | ||||
|             } | ||||
|             return option.classList.contains("accounting-account-in-use") || inUse.includes(option.dataset.code); | ||||
|         }; | ||||
|         return isMoreMatched() && isQueryMatched(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The callback when the account selector is shown. | ||||
|      * | ||||
|      */ | ||||
|     onOpen() { | ||||
|         this.#query.value = ""; | ||||
|         this.#isShowMore = false; | ||||
|         this.#more.classList.remove("d-none"); | ||||
|         this.#filterOptions(); | ||||
|         for (const option of this.#options) { | ||||
|             if (option.dataset.code === this.#lineItemEditor.accountCode) { | ||||
|                 option.classList.add("active"); | ||||
|             } else { | ||||
|                 option.classList.remove("active"); | ||||
|             } | ||||
|             option.setActive(option.code === this.lineItemEditor.accountCode); | ||||
|         } | ||||
|         if (this.#lineItemEditor.accountCode === null) { | ||||
|         if (this.lineItemEditor.accountCode === null) { | ||||
|             this.#clearButton.classList.add("btn-secondary"); | ||||
|             this.#clearButton.classList.remove("btn-danger"); | ||||
|             this.#clearButton.disabled = true; | ||||
| @@ -216,3 +183,137 @@ class AccountSelector { | ||||
|         return selectors; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * An account option | ||||
|  * | ||||
|  */ | ||||
| class AccountOption { | ||||
|  | ||||
|     /** | ||||
|      * The account selector | ||||
|      * @type {AccountSelector} | ||||
|      */ | ||||
|     #selector; | ||||
|  | ||||
|     /** | ||||
|      * The element | ||||
|      * @type {HTMLLIElement} | ||||
|      */ | ||||
|     #element; | ||||
|  | ||||
|     /** | ||||
|      * The account code | ||||
|      * @type {string} | ||||
|      */ | ||||
|     code; | ||||
|  | ||||
|     /** | ||||
|      * The account text | ||||
|      * @type {string} | ||||
|      */ | ||||
|     text; | ||||
|  | ||||
|     /** | ||||
|      * Whether the account is in use | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     #isInUse; | ||||
|  | ||||
|     /** | ||||
|      * Whether line items in the account need offset | ||||
|      * @type {boolean} | ||||
|      */ | ||||
|     isNeedOffset; | ||||
|  | ||||
|     /** | ||||
|      * The values to query against | ||||
|      * @type {string[]} | ||||
|      */ | ||||
|     #queryValues; | ||||
|  | ||||
|     /** | ||||
|      * Constructs the account in the account selector. | ||||
|      * | ||||
|      * @param selector {AccountSelector} the account selector | ||||
|      * @param element {HTMLLIElement} the element | ||||
|      */ | ||||
|     constructor(selector, element) { | ||||
|         this.#selector = selector; | ||||
|         this.#element = element; | ||||
|         this.code = element.dataset.code; | ||||
|         this.text = element.dataset.text; | ||||
|         this.#isInUse = element.classList.contains("accounting-account-is-in-use"); | ||||
|         this.isNeedOffset = element.classList.contains("accounting-account-is-need-offset"); | ||||
|         this.#queryValues = JSON.parse(element.dataset.queryValues); | ||||
|  | ||||
|         this.#element.onclick = () => this.#selector.lineItemEditor.saveAccount(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the account matches the query. | ||||
|      * | ||||
|      * @param isShowMore {boolean} true to show all accounts, or false to show only those in use | ||||
|      * @param codesInUse {string[]} the account codes that are used in the form | ||||
|      * @param query {string} the query term | ||||
|      * @return {boolean} true if the option matches, or false otherwise | ||||
|      */ | ||||
|     isMatched(isShowMore, codesInUse, query) { | ||||
|         return this.#isInUseMatched(isShowMore, codesInUse) && this.#isQueryMatched(query); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the account matches the "in-use" condition. | ||||
|      * | ||||
|      * @param isShowMore {boolean} true to show all accounts, or false to show only those in use | ||||
|      * @param codesInUse {string[]} the account codes that are used in the form | ||||
|      * @return {boolean} true if the option matches, or false otherwise | ||||
|      */ | ||||
|     #isInUseMatched(isShowMore, codesInUse) { | ||||
|         return isShowMore || this.#isInUse || codesInUse.includes(this.code); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the account matches the query term. | ||||
|      * | ||||
|      * @param query {string} the query term | ||||
|      * @return {boolean} true if the option matches, or false otherwise | ||||
|      */ | ||||
|     #isQueryMatched(query) { | ||||
|         if (query === "") { | ||||
|             return true; | ||||
|         } | ||||
|         for (const queryValue of this.#queryValues) { | ||||
|             if (queryValue.toLowerCase().includes(query.toLowerCase())) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets whether the option is shown. | ||||
|      * | ||||
|      * @param isShown {boolean} true to show, or false otherwise | ||||
|      */ | ||||
|     setShown(isShown) { | ||||
|         if (isShown) { | ||||
|             this.#element.classList.remove("d-none"); | ||||
|         } else { | ||||
|             this.#element.classList.add("d-none"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets whether the option is active. | ||||
|      * | ||||
|      * @param isActive {boolean} true if active, or false otherwise | ||||
|      */ | ||||
|     setActive(isActive) { | ||||
|         if (isActive) { | ||||
|             this.#element.classList.add("active"); | ||||
|         } else { | ||||
|             this.#element.classList.remove("active"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -368,18 +368,16 @@ class JournalEntryLineItemEditor { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets the account. | ||||
|      * Saves the selected account. | ||||
|      * | ||||
|      * @param code {string} the account code | ||||
|      * @param text {string} the account text | ||||
|      * @param isNeedOffset {boolean} true if the line items in the account need offset or false otherwise | ||||
|      * @param account {AccountOption} the selected account | ||||
|      */ | ||||
|     saveAccount(code, text, isNeedOffset) { | ||||
|         this.isNeedOffset = isNeedOffset; | ||||
|     saveAccount(account) { | ||||
|         this.isNeedOffset = account.isNeedOffset; | ||||
|         this.#accountControl.classList.add("accounting-not-empty"); | ||||
|         this.accountCode = code; | ||||
|         this.accountText = text; | ||||
|         this.#accountText.innerText = text; | ||||
|         this.accountCode = account.code; | ||||
|         this.accountText = account.text; | ||||
|         this.#accountText.innerText = account.text; | ||||
|         this.#validateAccount(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -37,7 +37,7 @@ First written: 2023/2/25 | ||||
|  | ||||
|         <ul id="accounting-account-selector-{{ debit_credit }}-option-list" class="list-group accounting-selector-list"> | ||||
|           {% for account in account_options %} | ||||
|             <li id="accounting-account-selector-{{ debit_credit }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ debit_credit }}-option {% if account.is_in_use %} accounting-account-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" data-code="{{ account.code }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> | ||||
|             <li id="accounting-account-selector-{{ debit_credit }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ debit_credit }}-option {% if account.is_in_use %} accounting-account-is-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" data-code="{{ account.code }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> | ||||
|               {{ account }} | ||||
|             </li> | ||||
|           {% endfor %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user