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 |      * The line item editor | ||||||
|      * @type {JournalEntryLineItemEditor} |      * @type {JournalEntryLineItemEditor} | ||||||
|      */ |      */ | ||||||
|     #lineItemEditor; |     lineItemEditor; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Either "debit" or "credit" |      * Either "debit" or "credit" | ||||||
| @@ -66,7 +66,7 @@ class AccountSelector { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * The options |      * The options | ||||||
|      * @type {HTMLLIElement[]} |      * @type {AccountOption[]} | ||||||
|      */ |      */ | ||||||
|     #options; |     #options; | ||||||
|  |  | ||||||
| @@ -76,6 +76,12 @@ class AccountSelector { | |||||||
|      */ |      */ | ||||||
|     #more; |     #more; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Whether to show all accounts | ||||||
|  |      * @type {boolean} | ||||||
|  |      */ | ||||||
|  |     #isShowMore = false; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Constructs an account selector. |      * Constructs an account selector. | ||||||
|      * |      * | ||||||
| @@ -83,27 +89,23 @@ class AccountSelector { | |||||||
|      * @param debitCredit {string} either "debit" or "credit" |      * @param debitCredit {string} either "debit" or "credit" | ||||||
|      */ |      */ | ||||||
|     constructor(lineItemEditor, debitCredit) { |     constructor(lineItemEditor, debitCredit) { | ||||||
|         this.#lineItemEditor = lineItemEditor |         this.lineItemEditor = lineItemEditor | ||||||
|         this.#debitCredit = debitCredit; |         this.#debitCredit = debitCredit; | ||||||
|         const prefix = "accounting-account-selector-" + debitCredit; |         const prefix = "accounting-account-selector-" + debitCredit; | ||||||
|         this.#query = document.getElementById(prefix + "-query"); |         this.#query = document.getElementById(prefix + "-query"); | ||||||
|         this.#queryNoResult = document.getElementById(prefix + "-option-no-result"); |         this.#queryNoResult = document.getElementById(prefix + "-option-no-result"); | ||||||
|         this.#optionList = document.getElementById(prefix + "-option-list"); |         this.#optionList = document.getElementById(prefix + "-option-list"); | ||||||
|         // noinspection JSValidateTypes |         this.#options = Array.from(document.getElementsByClassName(prefix + "-option")).map((element) => new AccountOption(this, element)); | ||||||
|         this.#options = Array.from(document.getElementsByClassName(prefix + "-option")); |  | ||||||
|         this.#more = document.getElementById(prefix + "-more"); |         this.#more = document.getElementById(prefix + "-more"); | ||||||
|         this.#clearButton = document.getElementById(prefix + "-btn-clear"); |         this.#clearButton = document.getElementById(prefix + "-btn-clear"); | ||||||
|  |  | ||||||
|         this.#more.onclick = () => { |         this.#more.onclick = () => { | ||||||
|  |             this.#isShowMore = true; | ||||||
|             this.#more.classList.add("d-none"); |             this.#more.classList.add("d-none"); | ||||||
|             this.#filterOptions(); |             this.#filterOptions(); | ||||||
|         }; |         }; | ||||||
|         this.#clearButton.onclick = () => this.#lineItemEditor.clearAccount(); |         this.#query.oninput = () => this.#filterOptions(); | ||||||
|         for (const option of this.#options) { |         this.#clearButton.onclick = () => this.lineItemEditor.clearAccount(); | ||||||
|             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(); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -112,17 +114,16 @@ class AccountSelector { | |||||||
|      */ |      */ | ||||||
|     #filterOptions() { |     #filterOptions() { | ||||||
|         const codesInUse = this.#getCodesUsedInForm(); |         const codesInUse = this.#getCodesUsedInForm(); | ||||||
|         let shouldAnyShow = false; |         let isAnyMatched = false; | ||||||
|         for (const option of this.#options) { |         for (const option of this.#options) { | ||||||
|             const shouldShow = this.#shouldOptionShow(option, this.#more, codesInUse, this.#query); |             if (option.isMatched(this.#isShowMore, codesInUse, this.#query.value)) { | ||||||
|             if (shouldShow) { |                 option.setShown(true); | ||||||
|                 option.classList.remove("d-none"); |                 isAnyMatched = true; | ||||||
|                 shouldAnyShow = true; |  | ||||||
|             } else { |             } 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.#optionList.classList.add("d-none"); | ||||||
|             this.#queryNoResult.classList.remove("d-none"); |             this.#queryNoResult.classList.remove("d-none"); | ||||||
|         } else { |         } else { | ||||||
| @@ -137,60 +138,26 @@ class AccountSelector { | |||||||
|      * @return {string[]} the account codes that are used in the form |      * @return {string[]} the account codes that are used in the form | ||||||
|      */ |      */ | ||||||
|     #getCodesUsedInForm() { |     #getCodesUsedInForm() { | ||||||
|         const inUse = this.#lineItemEditor.form.getAccountCodesUsed(this.#debitCredit); |         const inUse = this.lineItemEditor.form.getAccountCodesUsed(this.#debitCredit); | ||||||
|         if (this.#lineItemEditor.accountCode !== null) { |         if (this.lineItemEditor.accountCode !== null) { | ||||||
|             inUse.push(this.#lineItemEditor.accountCode); |             inUse.push(this.lineItemEditor.accountCode); | ||||||
|         } |         } | ||||||
|         return inUse |         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. |      * The callback when the account selector is shown. | ||||||
|      * |      * | ||||||
|      */ |      */ | ||||||
|     onOpen() { |     onOpen() { | ||||||
|         this.#query.value = ""; |         this.#query.value = ""; | ||||||
|  |         this.#isShowMore = false; | ||||||
|         this.#more.classList.remove("d-none"); |         this.#more.classList.remove("d-none"); | ||||||
|         this.#filterOptions(); |         this.#filterOptions(); | ||||||
|         for (const option of this.#options) { |         for (const option of this.#options) { | ||||||
|             if (option.dataset.code === this.#lineItemEditor.accountCode) { |             option.setActive(option.code === this.lineItemEditor.accountCode); | ||||||
|                 option.classList.add("active"); |  | ||||||
|             } else { |  | ||||||
|                 option.classList.remove("active"); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         if (this.#lineItemEditor.accountCode === null) { |         if (this.lineItemEditor.accountCode === null) { | ||||||
|             this.#clearButton.classList.add("btn-secondary"); |             this.#clearButton.classList.add("btn-secondary"); | ||||||
|             this.#clearButton.classList.remove("btn-danger"); |             this.#clearButton.classList.remove("btn-danger"); | ||||||
|             this.#clearButton.disabled = true; |             this.#clearButton.disabled = true; | ||||||
| @@ -216,3 +183,137 @@ class AccountSelector { | |||||||
|         return selectors; |         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 account {AccountOption} the selected account | ||||||
|      * @param text {string} the account text |  | ||||||
|      * @param isNeedOffset {boolean} true if the line items in the account need offset or false otherwise |  | ||||||
|      */ |      */ | ||||||
|     saveAccount(code, text, isNeedOffset) { |     saveAccount(account) { | ||||||
|         this.isNeedOffset = isNeedOffset; |         this.isNeedOffset = account.isNeedOffset; | ||||||
|         this.#accountControl.classList.add("accounting-not-empty"); |         this.#accountControl.classList.add("accounting-not-empty"); | ||||||
|         this.accountCode = code; |         this.accountCode = account.code; | ||||||
|         this.accountText = text; |         this.accountText = account.text; | ||||||
|         this.#accountText.innerText = text; |         this.#accountText.innerText = account.text; | ||||||
|         this.#validateAccount(); |         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"> |         <ul id="accounting-account-selector-{{ debit_credit }}-option-list" class="list-group accounting-selector-list"> | ||||||
|           {% for account in account_options %} |           {% 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 }} |               {{ account }} | ||||||
|             </li> |             </li> | ||||||
|           {% endfor %} |           {% endfor %} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user