From af78bf03811f2529cf981151aa981b6b62aac1c7 Mon Sep 17 00:00:00 2001 From: Tsanie Lily Date: Fri, 14 Apr 2023 17:40:15 +0800 Subject: [PATCH] sync from work --- index.html | 2 +- lib/app/communications/contact.js | 9 +- lib/app/communications/customer.js | 352 +++++++++++++++++++++-------- lib/app/communications/follower.js | 2 +- lib/app/communications/internal.js | 2 +- lib/app/communications/style.scss | 14 ++ lib/ui.js | 2 +- lib/ui/checkbox.js | 9 +- lib/ui/grid/column.js | 229 +++++++++++++++++++ lib/ui/{ => grid}/grid.d.ts | 2 +- lib/ui/{ => grid}/grid.html | 0 lib/ui/{ => grid}/grid.js | 215 +----------------- lib/ui/popup.js | 47 +++- 13 files changed, 566 insertions(+), 319 deletions(-) create mode 100644 lib/ui/grid/column.js rename lib/ui/{ => grid}/grid.d.ts (98%) rename lib/ui/{ => grid}/grid.html (100%) rename lib/ui/{ => grid}/grid.js (88%) diff --git a/index.html b/index.html index 9559e81..f9aab1a 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,7 @@
  • checkbox
  • tooltip
  • dropdown
  • -
  • grid
  • +
  • grid
  • popup
  • diff --git a/lib/app/communications/contact.js b/lib/app/communications/contact.js index 1ec6d82..1a57c2a 100644 --- a/lib/app/communications/contact.js +++ b/lib/app/communications/contact.js @@ -56,7 +56,7 @@ class Contact { } item.SaveToCustomer = 1; if (typeof this.#option.onSave === 'function') { - return this.#option.onSave.call(this, item, c == null); + return this.#option.onSave.call(this, item, 'customerrecord'); } } }); @@ -69,9 +69,10 @@ class Contact { if (item == null) { return false; } + item.Id = -1; item.SaveToCustomer = 0; if (typeof this.#option.onSave === 'function') { - return this.#option.onSave.call(this, item, c == null); + return this.#option.onSave.call(this, item, 'workorder'); } } }, @@ -162,6 +163,10 @@ class Contact { let contact = this.#option.contact; if (contact == null) { contact = {}; + } else if (contact.OptOut !== opt) { + if (opt !== false || contact.OptOut_BC === false) { + contact.selected = !opt; + } } contact.Name = name; contact.ContactPreference = pref; diff --git a/lib/app/communications/customer.js b/lib/app/communications/customer.js index b3ed7e4..5393582 100644 --- a/lib/app/communications/customer.js +++ b/lib/app/communications/customer.js @@ -165,10 +165,19 @@ class CustomerCommunication { if (this.#container == null) { return; } - this.#container.querySelector('.button-edit-contacts').style.display = flag === true ? 'none' : ''; - this.#container.querySelector('.button-edit-followers').style.display = flag === true ? 'none' : ''; - this.#enter.disabled = flag === true; - this.#container.querySelector('.button-send-message').style.display = flag === true ? 'none' : ''; + const link = this.#container.querySelector('.check-status-link'); + if (flag === true) { + link.classList.add('disabled'); + } else { + link.classList.remove('disabled'); + } + link.querySelector('input').disabled = flag; + const display = flag === true ? 'none' : ''; + this.#container.querySelector('.button-edit-contacts').style.display = display; + this.#container.querySelector('.button-edit-followers').style.display = display; + // this.#enter.disabled = flag === true; + this.#container.querySelector('.message-bar').style.display = display; + // this.#container.querySelector('.button-send-message').style.display = display; } /** @@ -261,6 +270,7 @@ class CustomerCommunication { create() { const option = this.#option; + const readonly = option.readonly; // functions const checkAutoUpdate = createCheckbox({ className: 'check-auto-update', @@ -273,8 +283,12 @@ class CustomerCommunication { r('autoUpdateDisabled', 'Auto Updates Disabled')); } }); + if (option.autoUpdatesVisible === false) { + checkAutoUpdate.style.display = 'none'; + } const checkLink = createCheckbox({ className: 'check-status-link', + enabled: !readonly, checked: option.statusLink, checkedNode: createIcon('fa-regular', 'link'), uncheckedNode: createIcon('fa-regular', 'unlink'), @@ -284,6 +298,9 @@ class CustomerCommunication { r('statusLinkExcluded', 'Status Link Excluded')); } }); + if (option.statusLinkVisible === false) { + checkLink.style.display = 'none'; + } const container = createBox( createElement('div', null, createElement('div', div => div.innerText = r('messages', 'Customer Communication')), @@ -302,6 +319,62 @@ class CustomerCommunication { ] ); // contacts + this.#contacts = this.#createContacts(container, option); + // followers + this.#followers = this.#createFollowers(container, option); + // enter box + const enter = createElement('textarea'); + enter.placeholder = r('typeMessage', 'Enter Message Here'); + enter.maxLength = 3000; + // if (readonly === true) { + // enter.disabled = true; + // } + enter.addEventListener('input', () => { + const val = this.#enter.value; + const s = String(nullOrEmpty(val) ? 0 : val.length) + '/3000'; + this.#container.querySelector('.message-bar .prompt-count').innerText = s; + }); + this.#enter = enter; + container.appendChild( + createElement('div', div => { + div.className = 'message-bar'; + if (readonly === true) { + div.style.display = 'none'; + } + }, + enter, + createElement('div', div => div.style.textAlign = 'right', + createElement('div', 'prompt-count'), + createElement('button', button => { + button.className = 'roundbtn button-send-message'; + button.style.backgroundColor = 'rgb(19, 150, 204)'; + // if (readonly === true) { + // button.style.display = 'none'; + // } + button.appendChild(createIcon('fa-solid', 'paper-plane')); + setTooltip(button, r('sendMessage', 'Send Message')); + button.addEventListener('click', () => { + const val = this.#enter.value; + if (nullOrEmpty(val?.trim())) { + showAlert(r('error', 'Error'), r('messageRequired', 'Please input the message.'), 'warn'); + return; + } + if (typeof this.#option.onAddMessage === 'function') { + this.#option.onAddMessage(this.#enter.value); + } + }) + }) + ) + ) + ); + + const message = createElement('div', 'list-bar'); + this.#message = message; + container.appendChild(message); + return this.#container = container; + } + + #createContacts(container, option) { const readonly = option.readonly; const recordReadonly = option.recordReadonly; const contacts = createElement('div'); @@ -357,7 +430,7 @@ class CustomerCommunication { button.addEventListener('click', () => { const add = new Contact({ company: !nullOrEmpty(this.#data.companyCode), - onSave: (item) => { + onSave: item => { const exists = this.#gridContact.source.some(s => s.Name === item.Name && s.MobilePhone === item.MobilePhone); if (exists) { showAlert(r('addContact', 'Add Contact'), r('contactUniqueRequired', 'Contact name and contact mobile must be a unique combination.'), 'warn'); @@ -367,8 +440,24 @@ class CustomerCommunication { const result = option.onSave(item, true); if (typeof result?.then === 'function') { return result.then(r => { - this.#gridContact.source = r.filter(c => c.Id >= 0); - this.#gridWo.source = r.filter(c => c.Id < 0); + this.#gridContact.source = r.filter(c => c.Id >= 0).map(c => { + if (c.OptOut || c.OptOut_BC) { + return c; + } + if (typeof c.selected === 'undefined') { + c.selected = true; + } + return c; + }); + this.#gridWo.source = r.filter(c => c.Id < 0).map(c => { + if (c.OptOut || c.OptOut_BC) { + return c; + } + if (typeof c.selected === 'undefined') { + c.selected = true; + } + return c; + }); return r; }); } @@ -456,7 +545,7 @@ class CustomerCommunication { const edit = new Contact({ contact: this, company: !nullOrEmpty(This.#data.companyCode), - onSave: item => { + onSave: (item, _op) => { const exists = This.#gridContact.source.some(s => s !== this && s.Name === item.Name && s.MobilePhone === item.MobilePhone) || This.#gridWo.source.some(s => s !== this && s.Name === item.Name && s.MobilePhone === item.MobilePhone); @@ -468,8 +557,24 @@ class CustomerCommunication { const result = option.onSave(item); if (typeof result?.then === 'function') { return result.then(r => { - This.#gridContact.source = r.filter(c => c.Id >= 0); - This.#gridWo.source = r.filter(c => c.Id < 0); + This.#gridContact.source = r.filter(c => c.Id >= 0).map(c => { + if (c.OptOut || c.OptOut_BC) { + return c; + } + if (typeof c.selected === 'undefined') { + c.selected = true; + } + return c; + }); + This.#gridWo.source = r.filter(c => c.Id < 0).map(c => { + if (c.OptOut || c.OptOut_BC) { + return c; + } + if (typeof c.selected === 'undefined') { + c.selected = true; + } + return c; + }); return r; }); } @@ -504,7 +609,10 @@ class CustomerCommunication { showConfirm( r('remoteContact', 'Remove Contact'), createElement('div', null, - createElement('div', div => div.innerText = r('removeFrom', 'Remove {name} from').replace('{name}', this.Name)), + createElement('div', div => { + div.style.paddingLeft = '16px'; + div.innerText = r('removeFrom', 'Remove {name} from').replace('{name}', this.Name); + }), createElement('div', div => { div.style.display = 'flex'; div.style.justifyContent = 'center'; @@ -525,21 +633,22 @@ class CustomerCommunication { [ { key: 'ok', text: r('ok', 'OK') }, { key: 'cancel', text: r('cancel', 'Cancel') } - ]).then(result => { - if (result?.key === 'ok') { - const isRecord = result.popup.container.querySelector('.radio-customer-record>input').checked; - if (typeof option.onDelete === 'function') { - option.onDelete(result.key, this, isRecord); - } - const index = grid.source.indexOf(this); - if (index >= 0) { - const source = grid.source; - source.splice(index, 1); - grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length; - grid.source = source; - } + ] + ).then(result => { + if (result?.key === 'ok') { + const isRecord = result.popup.container.querySelector('.radio-customer-record>input').checked; + if (typeof option.onDelete === 'function') { + option.onDelete(result.key, this, isRecord); } - }); + const index = grid.source.indexOf(this); + if (index >= 0) { + const source = grid.source; + source.splice(index, 1); + grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length; + grid.source = source; + } + } + }); } } } @@ -627,8 +736,12 @@ class CustomerCommunication { ) ) ); - this.#contacts = contacts; - // followers + return contacts; + } + + #createFollowers(container, option) { + const readonly = option.readonly; + const recordReadonly = option.recordReadonly; const followers = createElement('div'); const buttonEditFollower = createElement('button', button => { button.className = 'roundbtn button-edit-followers'; @@ -681,7 +794,7 @@ class CustomerCommunication { } }); add.show(container); - }) + }); } }); setTooltip(button, r('addFollower', 'Add Follower')) @@ -701,6 +814,12 @@ class CustomerCommunication { ) ); pop.show(container).then(() => { + const buttonCol = { + type: Grid.ColumnTypes.Icon, + width: 40, + align: 'center', + iconType: 'fa-light' + }; const grid = new Grid(); grid.height = 0; grid.allowHtml = true; @@ -710,40 +829,132 @@ class CustomerCommunication { key: 'type', type: Grid.ColumnTypes.Icon, width: 50, - filter: c => c.SendText ? 'comment-lines' : 'envelope', + filter: c => c.SendText && c.SendEmail ? 'at' : (c.SendText ? 'comment-lines' : 'envelope'), className: 'icon-contact-type', iconType: 'fa-light' }, { key: 'Name', width: 160 }, { key: 'Email', width: 180 }, { key: 'MobilePhone', width: 130 }, + { + key: 'edit', + ...buttonCol, + text: 'edit', + tooltip: r('edit', 'Edit'), + events: { + onclick: function () { + if (typeof option.onInitFollower === 'function') { + option.onInitFollower().then(data => { + if (typeof data === 'string') { + showAlert(r('customerRecord', 'Customer Record'), data, 'warn'); + return; + } + const contact = data.find(d => d.IID === this.UserIID); + showConfirm( + r('editContactMethod', 'Edit Contact Method'), + createElement('div', 'wrapper-edit-method', + createElement('div', div => { + div.style.display = 'flex'; + div.style.justifyContent = 'center'; + div.style.marginTop = '20px'; + }, + createCheckbox({ + label: r('text', 'Text'), + checked: this.SendText && !nullOrEmpty(contact?.Mobile), + enabled: !nullOrEmpty(contact?.Mobile), + className: 'check-method-text' + }), + createCheckbox({ + label: r('email', 'Email'), + checked: this.SendEmail, + className: 'check-method-email' + }) + ) + ), + [ + { + key: 'ok', + text: r('ok', 'OK'), + trigger: (popup, button) => { + const text = popup.container.querySelector('.check-method-text>input').checked; + const email = popup.container.querySelector('.check-method-email>input').checked; + + if (!text && !email) { + return showConfirm(r('editContactMethod', 'Edit Contact Method'), r('promptRemoveFollower', 'Contact method is required. If you continue, user will be removed as a follower.'), [ + { key: 'update', text: r('updateContactMethod', 'Update Contact Method') }, + { key: 'remove', text: r('removeFollower', 'Remove Follower') } + ], 'question').then(result => { + if (result?.key === 'remove') { + return { + key: result.key, + popup + }; + } + return false; + }); + } + return { + key: button.key, + popup + }; + } + }, + { key: 'cancel', text: r('cancel', 'Cancel') } + ], + null + ).then(result => { + const key = result?.key; + if (key === 'remove') { + if (typeof option.onDeleteFollower === 'function') { + option.onDeleteFollower(result.key, this); + } + const index = grid.source.indexOf(this); + if (index >= 0) { + const source = grid.source; + source.splice(index, 1); + grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length; + grid.source = source; + } + } else if (key === 'ok') { + const text = result.popup.container.querySelector('.check-method-text>input').checked; + const email = result.popup.container.querySelector('.check-method-email>input').checked; + if (typeof option.onChangeFollower === 'function') { + option.onChangeFollower(result.key, this, text, email); + } + this.SendText = text; + this.SendEmail = email; + grid.refresh(); + } + }); + }); + } + } + } + }, { key: 'delete', - type: Grid.ColumnTypes.Icon, - width: 40, - align: 'center', - iconType: 'fa-light', + ...buttonCol, text: 'times', tooltip: r('delete', 'Delete'), events: { onclick: function () { showConfirm( r('deleteFollower', 'Delete Follower'), - r('promptDeleteFollower', 'Do you want to delete this follower?')) - .then(result => { - if (result?.key === 'yes') { - if (typeof option.onDeleteFollower === 'function') { - option.onDeleteFollower(result.key, this); - } - const index = grid.source.indexOf(this); - if (index >= 0) { - const source = grid.source; - source.splice(index, 1); - grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length; - grid.source = source; - } + r('promptDeleteFollower', 'Do you want to delete this follower?') + ).then(result => { + if (result?.key === 'yes') { + if (typeof option.onDeleteFollower === 'function') { + option.onDeleteFollower(result.key, this); } - }); + const index = grid.source.indexOf(this); + if (index >= 0) { + const source = grid.source; + source.splice(index, 1); + grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length; + grid.source = source; + } + } + }); } } } @@ -775,52 +986,7 @@ class CustomerCommunication { ) ) ); - this.#followers = followers; - // enter box - const enter = createElement('textarea'); - enter.placeholder = r('typeMessage', 'Enter Message Here'); - enter.maxLength = 3000; - if (readonly === true) { - enter.disabled = true; - } - enter.addEventListener('input', () => { - const val = this.#enter.value; - const s = String(nullOrEmpty(val) ? 0 : val.length) + '/3000'; - this.#container.querySelector('.message-bar .prompt-count').innerText = s; - }); - this.#enter = enter; - container.appendChild( - createElement('div', 'message-bar', - enter, - createElement('div', div => div.style.textAlign = 'right', - createElement('div', 'prompt-count'), - createElement('button', button => { - button.className = 'roundbtn button-send-message'; - button.style.backgroundColor = 'rgb(19, 150, 204)'; - if (readonly === true) { - button.style.display = 'none'; - } - button.appendChild(createIcon('fa-solid', 'paper-plane')); - setTooltip(button, r('sendMessage', 'Send Message')); - button.addEventListener('click', () => { - const val = this.#enter.value; - if (nullOrEmpty(val?.trim())) { - showAlert(r('error', 'Error'), r('messageRequired', 'Please input the message.'), 'warn'); - return; - } - if (typeof this.#option.onAddMessage === 'function') { - this.#option.onAddMessage(this.#enter.value); - } - }) - }) - ) - ) - ); - - const message = createElement('div', 'list-bar'); - this.#message = message; - container.appendChild(message); - return this.#container = container; + return followers; } load(data, contacts, followers) { diff --git a/lib/app/communications/follower.js b/lib/app/communications/follower.js index 0c0eabf..fbcba0d 100644 --- a/lib/app/communications/follower.js +++ b/lib/app/communications/follower.js @@ -58,7 +58,7 @@ class Follower { caption: r('email', 'Email'), type: Grid.ColumnTypes.Checkbox, width: 70, - enabled: item => !nullOrEmpty(item.ID) + // enabled: item => !nullOrEmpty(item.ID) } ]; grid.init(); diff --git a/lib/app/communications/internal.js b/lib/app/communications/internal.js index 09667de..8ce1ada 100644 --- a/lib/app/communications/internal.js +++ b/lib/app/communications/internal.js @@ -63,7 +63,7 @@ class InternalComment { createElement('button', button => { button.className = 'roundbtn button-send-message'; button.style.backgroundColor = 'rgb(19, 150, 204)'; - if (readonly === true) { + if (readonly === true || this.#option.noMessage === true) { button.style.display = 'none'; } button.appendChild(createIcon('fa-solid', 'paper-plane')); diff --git a/lib/app/communications/style.scss b/lib/app/communications/style.scss index 36bea08..f676e1e 100644 --- a/lib/app/communications/style.scss +++ b/lib/app/communications/style.scss @@ -1,5 +1,13 @@ @import '../../../css/variables/definition.scss'; +.popup-mask .wrapper-edit-method { + width: 100%; + + .checkbox-wrapper { + padding: 0 28px; + } +} + .comm { display: flex; flex-direction: column; @@ -207,6 +215,12 @@ padding: 10px 10px 0; border: none; height: 70px; + resize: none; + + &:focus, + &:focus-visible { + outline: none; + } } >div { diff --git a/lib/ui.js b/lib/ui.js index 584c1da..f30d5ae 100644 --- a/lib/ui.js +++ b/lib/ui.js @@ -4,7 +4,7 @@ import { createIcon, resolveIcon } from "./ui/icon"; import { createCheckbox, createRadiobox, resolveCheckbox } from "./ui/checkbox"; import { setTooltip, resolveTooltip } from "./ui/tooltip"; import Dropdown from "./ui/dropdown"; -import Grid from "./ui/grid"; +import Grid from "./ui/grid/grid"; import Popup from "./ui/popup"; import { createPopup, showAlert, showConfirm } from "./ui/popup"; diff --git a/lib/ui/checkbox.js b/lib/ui/checkbox.js index 01f544e..13b9dba 100644 --- a/lib/ui/checkbox.js +++ b/lib/ui/checkbox.js @@ -63,6 +63,9 @@ function createCheckbox(opts = {}) { if (opts.className) { container.classList.add(opts.className); } + if (opts.enabled === false) { + container.classList.add('disabled'); + } if (opts.checkedNode != null && opts.uncheckedNode != null) { container.classList.add('checkbox-image'); let height = opts.imageHeight; @@ -120,7 +123,11 @@ function resolveCheckbox(container = document.body, legacy) { } else { text = label.innerText; } - label.className = 'checkbox-wrapper'; + if (chk.disabled) { + label.className = 'checkbox-wrapper disabled'; + } else { + label.className = 'checkbox-wrapper'; + } label.replaceChildren(); fillCheckbox(label, 'fa-regular', text); label.insertBefore(chk, label.firstChild); diff --git a/lib/ui/grid/column.js b/lib/ui/grid/column.js new file mode 100644 index 0000000..e9f5ac1 --- /dev/null +++ b/lib/ui/grid/column.js @@ -0,0 +1,229 @@ +import { global } from "../../utility"; +import { nullOrEmpty } from "../../utility/strings"; +import { createElement } from "../../functions"; +import { createIcon } from "../icon"; +import { createCheckbox } from "../checkbox"; +import { setTooltip } from "../tooltip"; +import Dropdown from "../dropdown"; + +class GridColumn { + static create() { return createElement('span') } + + static setValue(element, val) { element.innerText = val } + + static setStyle(element, style) { + for (let css of Object.entries(style)) { + element.style.setProperty(css[0], css[1]); + } + } +} + +class GridInputColumn extends GridColumn { + static get editing() { return true }; + + static createEdit(trigger, col, _parent, vals) { + const input = createElement('input'); + input.setAttribute('type', 'text'); + if (typeof trigger === 'function') { + input.addEventListener('change', trigger); + } + input.addEventListener('input', () => { + if (vals.__editing == null) { + vals.__editing = { + [col.key]: true + } + } else { + vals.__editing[col.key] = true; + } + }); + return input; + } + + static setValue(element, val) { + if (element.tagName !== 'INPUT') { + super.setValue(element, val); + } else { + element.value = val; + } + } + + static getValue(e) { return e.target.value } + + static setEnabled(element, enabled) { element.disabled = enabled === false } +} + +class GridTextColumn extends GridInputColumn { + static createEdit(trigger, col, _parent, vals) { + const input = createElement('textarea'); + if (typeof trigger === 'function') { + input.addEventListener('change', trigger); + } + input.addEventListener('input', () => { + if (vals.__editing == null) { + vals.__editing = { + [col.key]: true + } + } else { + vals.__editing[col.key] = true; + } + }); + return input; + } + + static setValue(element, val, _item, _col, grid) { + if (element.tagName !== 'TEXTAREA') { + super.setValue(element, val); + } else { + element.value = val; + if (val != null) { + const lines = String(val).split('\n').length; + element.style.height = `${lines * grid.lineHeight + 12}px`; + } + } + } +} + +const SymbolDropdown = Symbol.for('ui-dropdown'); + +class GridDropdownColumn extends GridColumn { + static createEdit(trigger, col, parent) { + const drop = new Dropdown({ ...col.dropOptions, parent }); + drop.onselected = trigger; + return drop.create(); + } + + static #getDrop(element) { + const dropGlobal = global[SymbolDropdown]; + if (dropGlobal == null) { + return null; + } + const dropId = element.dataset.dropId; + const drop = dropGlobal[dropId]; + if (drop == null) { + return null; + } + return drop; + } + + static #getSource(item, col) { + let source = col.source; + if (typeof source === 'function') { + source = source(item); + } + return source; + } + + static #setValue(source, element, val) { + const data = source?.find(v => v.value === val); + if (data != null) { + val = data.text; + } + super.setValue(element, val); + } + + static setValue(element, val, item, col) { + if (element.tagName !== 'DIV') { + let source = this.#getSource(item, col); + if (source instanceof Promise) { + source.then(s => this.#setValue(s, element, val)); + } else { + this.#setValue(source, element, val); + } + return; + } + const drop = this.#getDrop(element); + if (drop == null) { + return; + } + if (drop.source == null || drop.source.length === 0) { + let source = this.#getSource(item, col); + if (source instanceof Promise) { + source.then(s => { + drop.source = s; + drop.select(val, true); + }) + return; + } else if (source != null) { + drop.source = source; + } + } + drop.select(val, true); + } + + static getValue(e) { + return e.value; + } + + static setEnabled(element, enabled) { + const drop = this.#getDrop(element); + if (drop == null) { + return; + } + drop.disabled = enabled === false; + } +} + +class GridCheckboxColumn extends GridColumn { + static createEdit(trigger) { + const check = createCheckbox({ + onchange: typeof trigger === 'function' ? trigger : null + }); + return check; + } + + static setValue(element, val) { element.querySelector('input').checked = val } + + static getValue(e) { return e.target.checked } + + static setEnabled(element, enabled) { element.querySelector('input').disabled = enabled === false } +} + +class GridIconColumn extends GridColumn { + static create() { return createElement('span', 'col-icon') } + + static setValue(element, val, item, col, grid) { + let className = col.className; + if (typeof className === 'function') { + className = className.call(col, item); + } + if (className == null) { + element.className = 'col-icon'; + } else { + element.className = `col-icon ${className}`; + } + let type = col.iconType; + if (typeof type === 'function') { + type = type.call(col, item); + } + type ??= 'fa-regular'; + if (element.dataset.type !== type || element.dataset.icon !== val) { + const icon = createIcon(type, val); + // const layer = element.children[0]; + element.replaceChildren(icon); + !nullOrEmpty(col.tooltip) && setTooltip(element, col.tooltip, false, grid.element); + element.dataset.type = type; + element.dataset.icon = val; + } + } + + static setEnabled(element, enabled) { + if (enabled === false) { + element.classList.add('disabled'); + } else { + element.classList.remove('disabled'); + } + const tooltip = element.querySelector('.tooltip-wrapper'); + if (tooltip != null) { + tooltip.style.display = enabled === false ? 'none' : ''; + } + } +} + +export { + GridColumn, + GridInputColumn, + GridTextColumn, + GridDropdownColumn, + GridCheckboxColumn, + GridIconColumn +} \ No newline at end of file diff --git a/lib/ui/grid.d.ts b/lib/ui/grid/grid.d.ts similarity index 98% rename from lib/ui/grid.d.ts rename to lib/ui/grid/grid.d.ts index 65b2319..a5edad8 100644 --- a/lib/ui/grid.d.ts +++ b/lib/ui/grid/grid.d.ts @@ -1,4 +1,4 @@ -import { DropdownOptions } from "./dropdown"; +import { DropdownOptions } from "../dropdown"; interface GridItem { value: any; diff --git a/lib/ui/grid.html b/lib/ui/grid/grid.html similarity index 100% rename from lib/ui/grid.html rename to lib/ui/grid/grid.html diff --git a/lib/ui/grid.js b/lib/ui/grid/grid.js similarity index 88% rename from lib/ui/grid.js rename to lib/ui/grid/grid.js index f34db13..82bdc55 100644 --- a/lib/ui/grid.js +++ b/lib/ui/grid/grid.js @@ -1,11 +1,9 @@ -import { global, isPositive, isMobile, throttle, truncate } from "../utility"; -import { nullOrEmpty } from "../utility/strings"; -import { r } from "../utility/lgres"; -import { createElement } from "../functions"; -import { createIcon } from "./icon"; -import { createCheckbox } from "./checkbox"; -import { setTooltip } from "./tooltip"; -import Dropdown from "./dropdown"; +import { global, isPositive, isMobile, throttle, truncate } from "../../utility"; +import { r } from "../../utility/lgres"; +import { createElement } from "../../functions"; +import { createIcon } from "../icon"; +import { createCheckbox } from "../checkbox"; +import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn } from "./column"; const ColumnChangedType = { Reorder: 'reorder', @@ -41,203 +39,6 @@ function indexOfParent(target) { return Array.prototype.indexOf.call(target.parentElement.children, target); } -class GridColumn { - static create() { return createElement('span') } - - static setValue(element, val) { element.innerText = val } - - static setStyle(element, style) { - for (let css of Object.entries(style)) { - element.style.setProperty(css[0], css[1]); - } - } -} - -class GridInputColumn extends GridColumn { - static get editing() { return true }; - - static createEdit(trigger, _col, _parent, vals) { - const input = createElement('input'); - input.setAttribute('type', 'text'); - if (typeof trigger === 'function') { - input.addEventListener('change', trigger); - } - input.addEventListener('input', () => vals.__editing = true); - return input; - } - - static setValue(element, val) { - if (element.tagName !== 'INPUT') { - super.setValue(element, val); - } else { - element.value = val; - } - } - - static getValue(e) { return e.target.value } - - static setEnabled(element, enabled) { element.disabled = enabled === false } -} - -class GridTextColumn extends GridInputColumn { - static createEdit(trigger, _col, _parent, vals) { - const input = createElement('textarea'); - if (typeof trigger === 'function') { - input.addEventListener('change', trigger); - } - input.addEventListener('input', () => vals.__editing = true); - return input; - } - - static setValue(element, val, _item, _col, grid) { - if (element.tagName !== 'TEXTAREA') { - super.setValue(element, val); - } else { - element.value = val; - if (val != null) { - const lines = String(val).split('\n').length; - element.style.height = `${lines * grid.lineHeight + 12}px`; - } - } - } -} - -const SymbolDropdown = Symbol.for('ui-dropdown'); - -class GridDropdownColumn extends GridColumn { - static createEdit(trigger, col, parent) { - const drop = new Dropdown({ ...col.dropOptions, parent }); - drop.onselected = trigger; - return drop.create(); - } - - static #getDrop(element) { - const dropGlobal = global[SymbolDropdown]; - if (dropGlobal == null) { - return null; - } - const dropId = element.dataset.dropId; - const drop = dropGlobal[dropId]; - if (drop == null) { - return null; - } - return drop; - } - - static #getSource(item, col) { - let source = col.source; - if (typeof source === 'function') { - source = source(item); - } - return source; - } - - static #setValue(source, element, val) { - const data = source?.find(v => v.value === val); - if (data != null) { - val = data.text; - } - super.setValue(element, val); - } - - static setValue(element, val, item, col) { - if (element.tagName !== 'DIV') { - let source = this.#getSource(item, col); - if (source instanceof Promise) { - source.then(s => this.#setValue(s, element, val)); - } else { - this.#setValue(source, element, val); - } - return; - } - const drop = this.#getDrop(element); - if (drop == null) { - return; - } - if (drop.source == null || drop.source.length === 0) { - let source = this.#getSource(item, col); - if (source instanceof Promise) { - source.then(s => { - drop.source = s; - drop.select(val, true); - }) - return; - } else if (source != null) { - drop.source = source; - } - } - drop.select(val, true); - } - - static getValue(e) { - return e.value; - } - - static setEnabled(element, enabled) { - const drop = this.#getDrop(element); - if (drop == null) { - return; - } - drop.disabled = enabled === false; - } -} - -class GridCheckboxColumn extends GridColumn { - static createEdit(trigger) { - const check = createCheckbox({ - onchange: typeof trigger === 'function' ? trigger : null - }); - return check; - } - - static setValue(element, val) { element.querySelector('input').checked = val } - - static getValue(e) { return e.target.checked } - - static setEnabled(element, enabled) { element.querySelector('input').disabled = enabled === false } -} - -class GridIconColumn extends GridColumn { - static create() { return createElement('span', 'col-icon') } - - static setValue(element, val, item, col, grid) { - let className = col.className; - if (typeof className === 'function') { - className = className.call(col, item); - } - if (className == null) { - element.className = 'col-icon'; - } else { - element.className = `col-icon ${className}`; - } - let type = col.iconType; - if (typeof type === 'function') { - type = type.call(col, item); - } - type ??= 'fa-regular'; - if (element.dataset.type !== type || element.dataset.icon !== val) { - const icon = createIcon(type, val); - // const layer = element.children[0]; - element.replaceChildren(icon); - !nullOrEmpty(col.tooltip) && setTooltip(element, col.tooltip, false, grid.element); - element.dataset.type = type; - element.dataset.icon = val; - } - } - - static setEnabled(element, enabled) { - if (enabled === false) { - element.classList.add('disabled'); - } else { - element.classList.remove('disabled'); - } - const tooltip = element.querySelector('.tooltip-wrapper'); - if (tooltip != null) { - tooltip.style.display = enabled === false ? 'none' : ''; - } - } -} - const ColumnTypes = { 0: GridColumn, 1: GridInputColumn, @@ -882,7 +683,7 @@ class Grid { const type = isCheckbox ? GridCheckboxColumn : this.#colTypes[col.key] ?? GridColumn; let element; if (!isCheckbox && selectChanged && typeof type.createEdit === 'function') { - if (vals.__editing && type.editing) { + if (vals.__editing?.[col.key] && type.editing) { val = type.getValue({ target: cell.children[0] }); this.#onRowChanged(null, startIndex + i, col, val, true); } @@ -938,7 +739,7 @@ class Grid { } } }); - if (vals.__editing) { + if (vals.__editing != null) { delete vals.__editing; } }); diff --git a/lib/ui/popup.js b/lib/ui/popup.js index 0a46bb6..fd098b8 100644 --- a/lib/ui/popup.js +++ b/lib/ui/popup.js @@ -1,6 +1,6 @@ import "../../css/popup.scss"; import { createElement } from "../functions"; -import { r } from "../utility"; +import { r, nullOrEmpty } from "../utility"; import { createIcon } from "./icon"; class Popup { @@ -63,7 +63,11 @@ class Popup { if (typeof b.trigger === 'function') { const result = b.trigger(this); if (typeof result?.then === 'function') { - result.then(r => r !== false && close()).catch(() => { }); + result.then(r => { + if (r !== false) { + close(); + } + }).catch(() => { }); } else if (result !== false) { close(); } @@ -147,19 +151,40 @@ export function showAlert(title, message, iconType = 'info', parent = document.b } export function showConfirm(title, content, buttons, iconType = 'question', parent = document.body) { - return new Promise(resolve => { + return new Promise((resolve, reject) => { + const wrapper = createElement('div', 'message-wrapper'); + if (!nullOrEmpty(iconType)) { + wrapper.appendChild(createIcon('fa-solid', iconTypes[iconType] ?? 'question-circle')); + } + wrapper.appendChild(content instanceof HTMLElement ? + content : + createElement('span', span => span.innerText = content)); const popup = new Popup({ title, - content: createElement('div', 'message-wrapper', - createIcon('fa-solid', iconTypes[iconType] ?? 'question-circle'), - createElement('span', null, content) - ), + content: wrapper, buttons: buttons?.map(b => { return { - text: b.text, trigger: p => resolve({ - key: b.key, - popup: p - }) + text: b.text, + trigger: p => { + let result; + if (typeof b.trigger === 'function') { + result = b.trigger(p, b); + if (typeof result?.then === 'function') { + return result.then(r => { + r !== false && resolve(r); + return r; + }); + } + result !== false && resolve(result); + } else { + result = { + key: b.key, + popup: p + }; + resolve(result); + } + return result; + } }; }) ?? [