app change

This commit is contained in:
Tsanie Lily 2023-04-11 17:34:13 +08:00
parent c38e486d7d
commit cc3d11f617
14 changed files with 385 additions and 98 deletions

View File

@ -38,7 +38,7 @@
&+* { &+* {
flex: 1 1 auto; flex: 1 1 auto;
font-weight: 400; font-weight: 400;
font-size: $mediumSize; font-size: var(--font-size);
padding-left: 8px; padding-left: 8px;
padding-right: 6px; padding-right: 6px;
align-self: center; align-self: center;

View File

@ -98,7 +98,7 @@ $listMaxHeight: 210px;
line-height: $headerHeight; line-height: $headerHeight;
padding: 0 4px; padding: 0 4px;
font-weight: 400; font-weight: 400;
font-size: $smallSize; font-size: var(--font-smaller-size);
color: var(--color); color: var(--color);
} }
}*/ }*/
@ -106,7 +106,7 @@ $listMaxHeight: 210px;
>.drop-text { >.drop-text {
flex: 1 1 auto; flex: 1 1 auto;
cursor: pointer; cursor: pointer;
font-size: $mediumSize; font-size: var(--font-size);
// line-height: $headerHeight; // line-height: $headerHeight;
padding: 0 6px; padding: 0 6px;
overflow: hidden; overflow: hidden;
@ -120,7 +120,7 @@ $listMaxHeight: 210px;
cursor: initial; cursor: initial;
&::placeholder { &::placeholder {
font-size: $smallSize; font-size: var(--font-smaller-size);
font-style: italic; font-style: italic;
} }
} }
@ -240,7 +240,7 @@ $listMaxHeight: 210px;
list-style: none; list-style: none;
max-height: $listMaxHeight; max-height: $listMaxHeight;
overflow-y: auto; overflow-y: auto;
font-size: $mediumSize; font-size: var(--font-size);
@include scrollbar(); @include scrollbar();
&.filtered>li:first-child { &.filtered>li:first-child {

View File

@ -25,7 +25,17 @@
} }
} }
>input[type="checkbox"] { &.radiobox-wrapper {
.check-box-inner {
box-sizing: border-box;
border-radius: 8px;
width: 16px;
height: 16px;
}
}
>input[type="checkbox"],
>input[type="radio"] {
display: none; display: none;
&:checked+.check-box-inner { &:checked+.check-box-inner {

View File

@ -21,8 +21,8 @@
--row-selected-bg-color: #e6f2fb; --row-selected-bg-color: #e6f2fb;
--text-disabled-color: gray; --text-disabled-color: gray;
--font-size: .8125rem; --row-height: 36px;
--line-height: 36px; --line-height: 24px;
--header-line-height: 26px; --header-line-height: 26px;
--text-indent: 8px; --text-indent: 8px;
@ -39,7 +39,7 @@
--header-padding: 4px 12px 4px 8px; --header-padding: 4px 12px 4px 8px;
--spacing-s: 4px; --spacing-s: 4px;
--spacing-cell: 0 4px 0 8px; --spacing-cell: 6px 4px 6px 8px;
} }
&:focus, &:focus,
@ -49,9 +49,11 @@
&, &,
input[type="text"], input[type="text"],
textarea,
.drop-wrapper>.drop-header>.drop-text, .drop-wrapper>.drop-header>.drop-text,
.drop-wrapper>.drop-box>.drop-list { .drop-wrapper>.drop-box>.drop-list {
font-size: var(--font-size); font-size: var(--font-size);
font-family: var(--font-family);
} }
>.grid-sizer { >.grid-sizer {
@ -86,7 +88,7 @@
>div { >div {
line-height: var(--header-line-height); line-height: var(--header-line-height);
min-height: var(--line-height); min-height: var(--row-height);
display: flex; display: flex;
align-items: center; align-items: center;
padding: var(--header-padding); padding: var(--header-padding);
@ -228,12 +230,11 @@
white-space: pre; white-space: pre;
} }
>input[type="text"] { >input[type="text"],
>textarea {
border: none; border: none;
box-sizing: border-box; box-sizing: border-box;
height: calc(var(--line-height) + 2px);
width: 100%; width: 100%;
text-indent: var(--text-indent);
padding: 0; padding: 0;
&:focus, &:focus,
@ -246,6 +247,20 @@
} }
} }
>input[type="text"] {
height: var(--row-height);
text-indent: var(--text-indent);
}
>textarea {
resize: none;
line-height: var(--line-height);
display: block;
padding: var(--spacing-cell);
white-space: nowrap;
@include scrollbar();
}
.checkbox-wrapper { .checkbox-wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -260,7 +275,7 @@
} }
.drop-wrapper { .drop-wrapper {
height: calc(var(--line-height) + 2px); height: var(--row-height);
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -275,11 +290,11 @@
} }
>.drop-box { >.drop-box {
top: calc(var(--line-height) + 2px); top: calc(var(--row-height) + 2px);
&.slide-up { &.slide-up {
top: unset; top: unset;
bottom: calc(var(--line-height) + 2px); bottom: calc(var(--row-height) + 2px);
} }
} }
} }
@ -287,7 +302,6 @@
.col-icon { .col-icon {
display: flex; display: flex;
cursor: pointer; cursor: pointer;
height: var(--line-height);
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative; position: relative;

View File

@ -43,7 +43,7 @@
} }
>.tooltip-content { >.tooltip-content {
font-size: $smallSize; font-size: var(--font-smaller-size);
line-height: 1rem; line-height: 1rem;
white-space: normal; white-space: normal;
overflow: hidden; overflow: hidden;

View File

@ -1,8 +1,3 @@
// dimension
$mediumSize: .875rem; // 14px
$smallSize: .8125rem; // 13px
$tinySize: .75rem; // 12px
// animation // animation
@keyframes loading-spinner { @keyframes loading-spinner {
0% { 0% {
@ -28,10 +23,15 @@ $tinySize: .75rem; // 12px
--disabled-box-color: #d9d9d9; --disabled-box-color: #d9d9d9;
--hover-color: #eee; --hover-color: #eee;
--link-color: #1890ff; --link-color: #1890ff;
--primary-color: rgb(123,28,33); --primary-color: rgb(123, 28, 33);
--loading-bg-color: hsla(0, 0%, 100%, .4); --loading-bg-color: hsla(0, 0%, 100%, .4);
--loading-fore-color: rgba(0, 0, 0, .2); --loading-fore-color: rgba(0, 0, 0, .2);
--border-radius: 2px; --border-radius: 2px;
--text-indent: 4px; --text-indent: 4px;
--font-size: .8125rem;
--font-smaller-size: .75rem;
--font-larger-size: .875rem;
--font-family: "Franklin Gothic Book", "San Francisco", "Segoe UI", "Open Sans", "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei UI", sans-serif;
} }

View File

@ -1,7 +1,11 @@
import CustomerCommunication from "./app/communications/customer"; import CustomerCommunication from "./app/communications/customer";
import InternalComment from "./app/communications/internal"; import InternalComment from "./app/communications/internal";
import Popup, { showAlert, showConfirm } from "./ui/popup";
export { export {
CustomerCommunication, CustomerCommunication,
InternalComment InternalComment,
Popup,
showAlert,
showConfirm
} }

View File

@ -5,7 +5,7 @@ import { nullOrEmpty } from "../../utility/strings";
import { formatUrl, isEmail, isPhone } from "../../utility"; import { formatUrl, isEmail, isPhone } from "../../utility";
import { setTooltip } from "../../ui/tooltip"; import { setTooltip } from "../../ui/tooltip";
import { createIcon } from "../../ui/icon"; import { createIcon } from "../../ui/icon";
import { createCheckbox } from "../../ui/checkbox"; import { createCheckbox, createRadiobox } from "../../ui/checkbox";
import { createBox } from "./lib"; import { createBox } from "./lib";
import { createPopup, showAlert, showConfirm } from "../../ui/popup"; import { createPopup, showAlert, showConfirm } from "../../ui/popup";
import Grid from "../../ui/grid"; import Grid from "../../ui/grid";
@ -13,9 +13,7 @@ import Contact from "./contact";
class NoteCol extends Grid.GridColumn { class NoteCol extends Grid.GridColumn {
static create() { static create() {
const wrapper = createElement('div', div => { const wrapper = createElement('div', 'contact-wrapper',
div.style.width = '100px';
},
createElement('div', 'contact-name'), createElement('div', 'contact-name'),
createElement('div', 'contact-note') createElement('div', 'contact-note')
); );
@ -41,6 +39,8 @@ class CustomerCommunication {
#message; #message;
#data = {}; #data = {};
#gridContact; #gridContact;
#gridWo;
#gridFollower;
constructor(opt) { constructor(opt) {
this.#option = opt ?? {}; this.#option = opt ?? {};
@ -107,7 +107,7 @@ class CustomerCommunication {
} }
#createContactItem(c) { #createContactItem(c) {
if (c.OptOut || c.OptOut_BC) { if (c.OptOut || c.OptOut_BC || c.selected === false) {
return null; return null;
} }
const mp = String(c.MobilePhone).trim(); const mp = String(c.MobilePhone).trim();
@ -160,6 +160,17 @@ class CustomerCommunication {
this.#enter.disabled = flag === true; this.#enter.disabled = flag === true;
} }
/**
* @param {boolean} flag
*/
set recordReadonly(flag) {
this.#option.recordReadonly = flag;
if (this.#container == null) {
return;
}
this.#container.querySelector('.button-edit-contacts').style.display = flag === true ? 'none' : '';
}
get followers() { get followers() {
return [...this.#followers.children].map(el => { return [...this.#followers.children].map(el => {
const span = el.querySelector('span'); const span = el.querySelector('span');
@ -167,6 +178,7 @@ class CustomerCommunication {
}); });
} }
set followers(followers) { set followers(followers) {
this.#data.followers = followers;
this.#followers.replaceChildren(); this.#followers.replaceChildren();
if (followers?.length > 0) { if (followers?.length > 0) {
this.#container.querySelector('.follower-bar').style.display = ''; this.#container.querySelector('.follower-bar').style.display = '';
@ -243,7 +255,7 @@ class CustomerCommunication {
createElement('button', button => { createElement('button', button => {
button.className = 'roundbtn button-edit-contacts'; button.className = 'roundbtn button-edit-contacts';
button.style.backgroundColor = 'rgb(1, 199, 172)'; button.style.backgroundColor = 'rgb(1, 199, 172)';
if (readonly === true) { if (readonly === true || option.recordReadonly) {
button.style.display = 'none'; button.style.display = 'none';
} }
button.appendChild(createIcon('fa-solid', 'user-edit')); button.appendChild(createIcon('fa-solid', 'user-edit'));
@ -286,10 +298,14 @@ class CustomerCommunication {
} }
if (typeof option.onSave === 'function') { if (typeof option.onSave === 'function') {
const result = option.onSave(item, true); const result = option.onSave(item, true);
if (result !== false) { if (typeof result?.then === 'function') {
this.#gridContact.reload(); return result.then(r => {
this.#gridContact.source = r.filter(c => c.Id >= 0);
this.#gridWo.source = r.filter(c => c.Id < 0);
return r;
});
} }
return result; return false;
} }
} }
}); });
@ -321,11 +337,18 @@ class CustomerCommunication {
) )
); );
pop.show(container).then(() => { pop.show(container).then(() => {
const selectedCol = { const selectedCol = This => {
key: 'selected', return {
type: Grid.ColumnTypes.Checkbox, key: 'selected',
width: 50, type: Grid.ColumnTypes.Checkbox,
enabled: item => !item.OptOut && !item.OptOut_BC width: 50,
enabled: item => !item.OptOut && !item.OptOut_BC,
onchanged: function () {
if (typeof option.onChanged === 'function') {
option.onChanged(This.#gridContact.source.concat(This.#gridWo.source));
}
}
}
}; };
const iconCol = { const iconCol = {
key: 'type', key: 'type',
@ -342,7 +365,7 @@ class CustomerCommunication {
align: 'center', align: 'center',
iconType: 'fa-light' iconType: 'fa-light'
}; };
const createEditCol = grid => { const createEditCol = (This) => {
return { return {
key: 'edit', key: 'edit',
...buttonCol, ...buttonCol,
@ -353,17 +376,23 @@ class CustomerCommunication {
const edit = new Contact({ const edit = new Contact({
contact: this, contact: this,
onSave: item => { onSave: item => {
const exists = grid.source.some(s => s !== this && s.Name === item.Name && s.MobilePhone === item.MobilePhone); 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);
if (exists) { if (exists) {
showAlert(r('editContact', 'Edit Contact'), r('contactUniqueRequired', 'Contact name and contact mobile must be a unique combination.'), 'warn'); showAlert(r('editContact', 'Edit Contact'), r('contactUniqueRequired', 'Contact name and contact mobile must be a unique combination.'), 'warn');
return false; return false;
} }
if (typeof option.onSave === 'function') { if (typeof option.onSave === 'function') {
const result = option.onSave(item); const result = option.onSave(item);
if (result !== false) { if (typeof result?.then === 'function') {
grid.refresh(); return result.then(r => {
This.#gridContact.source = r.filter(c => c.Id >= 0);
This.#gridWo.source = r.filter(c => c.Id < 0);
return r;
});
} }
return result; return false;
} }
} }
}); });
@ -378,7 +407,7 @@ class CustomerCommunication {
grid.allowHtml = true; grid.allowHtml = true;
grid.headerVisible = false; grid.headerVisible = false;
grid.columns = [ grid.columns = [
selectedCol, selectedCol(this),
iconCol, iconCol,
nameCol, nameCol,
{ key: 'Email', width: 180 }, { key: 'Email', width: 180 },
@ -391,23 +420,45 @@ class CustomerCommunication {
tooltip: r('delete', 'Delete'), tooltip: r('delete', 'Delete'),
events: { events: {
onclick: function () { onclick: function () {
showConfirm(r('remoteContact', 'Remove Contact'), r('removeFromCustomer', 'You are removing {name} from customer record.\n\nDo you want to Continue?').replace('{name}', this.Name), [ showConfirm(
{ key: 'continue', text: r('continue', 'Continue') }, r('remoteContact', 'Remove Contact'),
{ key: 'cancel', text: r('cancel', 'Cancel') } createElement('div', null,
]).then(result => { createElement('div', div => div.innerText = r('removeFrom', 'Remove {name} from').replace('{name}', this.Name)),
if (result === 'continue') { createElement('div', div => {
if (typeof option.onDelete === 'function') { div.style.display = 'flex';
option.onDelete(result, this, true); div.style.justifyContent = 'center';
div.style.marginTop = '10px';
},
createRadiobox({
name: 'remove-type',
label: r('customerRecord', 'Customer Record'),
checked: true,
className: 'radio-customer-record'
}),
createRadiobox({
name: 'remove-type',
label: r('workOrder', 'Work Order')
})
)
),
[
{ 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;
}
} }
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;
}
}
});
} }
} }
} }
@ -432,7 +483,7 @@ class CustomerCommunication {
gridWo.allowHtml = true; gridWo.allowHtml = true;
gridWo.headerVisible = false; gridWo.headerVisible = false;
gridWo.columns = [ gridWo.columns = [
selectedCol, selectedCol(this),
iconCol, iconCol,
nameCol, nameCol,
{ key: 'Email', width: 180 }, { key: 'Email', width: 180 },
@ -449,9 +500,9 @@ class CustomerCommunication {
{ key: 'continue', text: r('continue', 'Continue') }, { key: 'continue', text: r('continue', 'Continue') },
{ key: 'cancel', text: r('cancel', 'Cancel') } { key: 'cancel', text: r('cancel', 'Cancel') }
]).then(result => { ]).then(result => {
if (result === 'continue') { if (result?.key === 'continue') {
if (typeof option.onDelete === 'function') { if (typeof option.onDelete === 'function') {
option.onDelete(result, this); option.onDelete(result.key, this);
} }
const index = gridWo.source.indexOf(this); const index = gridWo.source.indexOf(this);
if (index >= 0) { if (index >= 0) {
@ -478,6 +529,7 @@ class CustomerCommunication {
}); });
gridWo.extraRows = workOrderOnly.filter(c => !nullOrEmpty(c.Notes)).length; gridWo.extraRows = workOrderOnly.filter(c => !nullOrEmpty(c.Notes)).length;
gridWo.source = workOrderOnly; gridWo.source = workOrderOnly;
this.#gridWo = gridWo;
}); });
}); });
}) })
@ -512,7 +564,116 @@ class CustomerCommunication {
button.appendChild(createIcon('fa-solid', 'pen')); button.appendChild(createIcon('fa-solid', 'pen'));
setTooltip(button, r('editFollower', 'Edit Followers')); setTooltip(button, r('editFollower', 'Edit Followers'));
button.addEventListener('click', () => { button.addEventListener('click', () => {
// TODO: const pop = createPopup(
createElement('div', div => {
div.style.display = 'flex';
div.style.alignItems = 'center';
div.append(
createElement('div', div => {
div.className = 'popup-move';
div.style.flex = '1 1 auto';
div.innerText = r('editContacts', 'Edit Contacts') + '\n' + r('followers', 'Followers');
}),
createElement('button', button => {
button.style.flex = '0 0 auto';
button.style.backgroundColor = 'rgb(1, 199, 172)';
button.style.marginRight = '10px';
button.className = 'roundbtn button-add-follower';
button.appendChild(createIcon('fa-solid', 'user-plus', {
width: '16px',
height: '16px'
}));
button.addEventListener('click', () => {
/*const add = new Contact({
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');
return false;
}
if (typeof option.onSave === 'function') {
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);
return r;
});
}
return false;
}
}
});
add.show(container);
*/
});
setTooltip(button, r('addFollower', 'Add Follower'))
})
)
}),
createElement('div', null,
createElement('div', div => {
div.style.fontWeight = 'bold';
div.innerText = r('contactFromRecord', 'Contacts from Customer Record');
}),
createElement('div', div => {
div.className = 'followers-record';
div.style.maxHeight = '400px';
div.style.width = '660px';
})
)
);
pop.show(container).then(() => {
const grid = new Grid();
grid.height = 0;
grid.allowHtml = true;
grid.headerVisible = false;
grid.columns = [
{
key: 'type',
type: Grid.ColumnTypes.Icon,
width: 50,
filter: c => String(c.ContactPreference) === '0' ? 'comment-lines' : 'envelope',
className: 'icon-contact-type',
iconType: 'fa-light'
},
{ key: 'Name', width: 160 },
{ key: 'Email', width: 180 },
{ key: 'MobilePhone', width: 130 },
{
key: 'delete',
type: Grid.ColumnTypes.Icon,
width: 40,
align: 'center',
iconType: 'fa-light',
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;
}
}
});
}
}
}
];
grid.init(pop.container.querySelector('.followers-record'));
grid.source = this.#data.followers;
this.#gridFollower = grid;
});
}); });
}) })
) )

View File

@ -13,7 +13,6 @@
--dark-fore-opacity-color: rgba(255, 255, 255, .6); --dark-fore-opacity-color: rgba(255, 255, 255, .6);
--strong-color: #333; --strong-color: #333;
--light-color: #ccc; --light-color: #ccc;
--medium-font-size: .875rem;
} }
.roundbtn { .roundbtn {
@ -179,7 +178,7 @@
>span { >span {
flex: 1 1 auto; flex: 1 1 auto;
color: var(--strong-color); color: var(--strong-color);
font-size: var(--medium-font-size); font-size: var(--font-larger-size);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
padding-right: 10px; padding-right: 10px;
@ -318,11 +317,16 @@
.popup-mask .grid { .popup-mask .grid {
height: 100%; height: 100%;
min-height: 120px;
overflow: hidden; overflow: hidden;
>.grid-body .grid-body-content>.grid-row>td { >.grid-body .grid-body-content>.grid-row>td {
vertical-align: top; vertical-align: top;
.col-icon {
padding: 10px 4px 10px 8px;
}
.icon-contact-type { .icon-contact-type {
cursor: unset; cursor: unset;
@ -335,13 +339,18 @@
} }
} }
.contact-name { .contact-wrapper {
overflow: hidden; width: 100px;
text-overflow: ellipsis; padding: var(--spacing-cell);
}
.contact-note { .contact-name {
color: #999; overflow: hidden;
text-overflow: ellipsis;
}
.contact-note {
color: #999;
}
} }
} }
} }

View File

@ -1,9 +1,9 @@
import { createElement } from "../functions"; import { createElement } from "../functions";
import { createIcon } from "./icon"; import { createIcon } from "./icon";
function fillCheckbox(container, type, label) { function fillCheckbox(container, type, label, charactor = 'check') {
container.appendChild( container.appendChild(
createElement('layer', 'check-box-inner', createIcon(type, 'check')) createElement('layer', 'check-box-inner', createIcon(type, charactor))
); );
if (label instanceof HTMLElement) { if (label instanceof HTMLElement) {
container.appendChild(label); container.appendChild(label);
@ -14,6 +14,33 @@ function fillCheckbox(container, type, label) {
} }
} }
function createRadiobox(opts = {}) {
const container = createElement('label', 'checkbox-wrapper radiobox-wrapper',
createElement('input', input => {
input.setAttribute('type', 'radio');
input.name = opts.name;
if (opts.checked === true) {
input.checked = true;
}
if (opts.enabled === false) {
input.disabled = true;
}
if (opts.customerAttributes != null) {
for (let entry of Object.entries(opts.customerAttributes)) {
input.setAttribute(entry[0], entry[1]);
}
}
if (typeof opts.onchange === 'function') {
input.addEventListener('change', opts.onchange);
}
}));
if (opts.className) {
container.classList.add(opts.className);
}
fillCheckbox(container, opts.type || 'fa-regular', opts.label, 'circle');
return container;
}
function createCheckbox(opts = {}) { function createCheckbox(opts = {}) {
const container = createElement('label', 'checkbox-wrapper', const container = createElement('label', 'checkbox-wrapper',
createElement('input', input => { createElement('input', input => {
@ -131,5 +158,6 @@ function resolveCheckbox(container = document.body, legacy) {
export { export {
createCheckbox, createCheckbox,
resolveCheckbox resolveCheckbox,
createRadiobox
} }

2
lib/ui/grid.d.ts vendored
View File

@ -25,6 +25,7 @@ interface GridColumnType {
2: "Dropdown"; 2: "Dropdown";
3: "Checkbox"; 3: "Checkbox";
4: "Icon"; 4: "Icon";
5: "Text";
} }
interface GridColumnDefinition { interface GridColumnDefinition {
@ -123,6 +124,7 @@ declare var Grid: {
Dropdown: 2, Dropdown: 2,
Checkbox: 3, Checkbox: 3,
Icon: 4, Icon: 4,
Text: 5,
isCheckbox(type: Number): boolean; isCheckbox(type: Number): boolean;
}; };
GridColumn: typeof GridColumn; GridColumn: typeof GridColumn;

View File

@ -223,6 +223,7 @@
type: statusCol, type: statusCol,
enabled: 'enabled' enabled: 'enabled'
}, },
{ key: 'c2c', caption: '多行编辑', type: Grid.ColumnTypes.Text, enabled: 'enabled' },
{ key: 'c3', caption: 'column 3', width: 90 }, { key: 'c3', caption: 'column 3', width: 90 },
{ key: 'c4', caption: 'Note', type: Grid.ColumnTypes.Input }, { key: 'c4', caption: 'Note', type: Grid.ColumnTypes.Input },
{ {
@ -252,11 +253,11 @@
grid.cellDblClicked = (rId, cId) => console.log(`row (${rId}), column (${cId}) double clicked.`); grid.cellDblClicked = (rId, cId) => console.log(`row (${rId}), column (${cId}) double clicked.`);
grid.init(); grid.init();
grid.source = [ grid.source = [
{ c1: 'abc', c2: true, c2a: 'off', c2b: '<font style="color: red; margin-left: 8px">red</font>', c3: 12345, c4: 'another note', enabled: false }, { c1: 'abc', c2: true, c2a: 'off', c2b: '<font style="color: red; margin-left: 8px">red</font>', c2c: 'multiple lines\nline2\nline3\n\nline5', c3: 12345, c4: 'another note' },
{ c1: 'abc2bbbbaaaaa', c2: false, c2a: 'pending', c2b: '<b style="margin-left: 8px">bold</b>', c3: 1225, c4: 'Note note this is note' }, { c1: 'abc2bbbbaaaaa', c2: false, c2a: 'pending', c2b: '<b style="margin-left: 8px">bold</b>', c3: 1225, c4: 'Note note this is note' },
{ c1: 'type', c2: false, c2a: 'broken', c3: 121111 }, { c1: 'type', c2: false, c2a: 'broken', c2c: 'multiple lines\nline2\nline3\n\nline5', c3: 121111 },
{ c1: 'diff', c2: true, c2a: 'running', c3: 124445555555555555 }, { c1: 'diff', c2: true, c2a: 'running', c3: 124445555555555555 },
{ c1: 'diff', c2: true, c2a: 'running', c3: 12499 }, { c1: 'diff', c2: true, c2a: 'running', c3: 12499, enabled: false },
{ c1: 'diff', c2: true, c2a: 'off', c3: 1244445 } { c1: 'diff', c2: true, c2a: 'off', c3: 1244445 }
]; ];
@ -265,7 +266,7 @@
</script> </script>
<style type="text/css"> <style type="text/css">
#grid-sample { #grid-sample {
/* height: 400px; */ height: 500px;
} }
#grid-sample>.grid { #grid-sample>.grid {

View File

@ -54,12 +54,15 @@ class GridColumn {
} }
class GridInputColumn extends GridColumn { class GridInputColumn extends GridColumn {
static createEdit(trigger) { static get editing() { return true };
static createEdit(trigger, _col, _parent, vals) {
const input = createElement('input'); const input = createElement('input');
input.setAttribute('type', 'text'); input.setAttribute('type', 'text');
if (typeof trigger === 'function') { if (typeof trigger === 'function') {
input.addEventListener('change', trigger); input.addEventListener('change', trigger);
} }
input.addEventListener('input', () => vals.__editing = true);
return input; return input;
} }
@ -76,6 +79,29 @@ class GridInputColumn extends GridColumn {
static setEnabled(element, enabled) { element.disabled = enabled === false } 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'); const SymbolDropdown = Symbol.for('ui-dropdown');
class GridDropdownColumn extends GridColumn { class GridDropdownColumn extends GridColumn {
@ -217,7 +243,8 @@ const ColumnTypes = {
1: GridInputColumn, 1: GridInputColumn,
2: GridDropdownColumn, 2: GridDropdownColumn,
3: GridCheckboxColumn, 3: GridCheckboxColumn,
4: GridIconColumn 4: GridIconColumn,
5: GridTextColumn
}; };
class Grid { class Grid {
@ -248,6 +275,7 @@ class Grid {
}; };
virtualCount = 100; virtualCount = 100;
rowHeight = 36; rowHeight = 36;
lineHeight = 24;
extraRows = 0; extraRows = 0;
filterRowHeight = 30; filterRowHeight = 30;
height; height;
@ -274,6 +302,7 @@ class Grid {
Dropdown: 2, Dropdown: 2,
Checkbox: 3, Checkbox: 3,
Icon: 4, Icon: 4,
Text: 5,
isCheckbox(type) { return type === 3 } isCheckbox(type) { return type === 3 }
}; };
@ -715,10 +744,15 @@ class Grid {
if (!this.holderDisabled) { if (!this.holderDisabled) {
const holder = createElement('div', 'grid-hover-holder'); const holder = createElement('div', 'grid-hover-holder');
holder.addEventListener('mousedown', e => { holder.addEventListener('mousedown', e => {
const keyid = e.currentTarget.keyid; const holder = e.currentTarget;
const keyid = holder.keyid;
if (keyid == null) { if (keyid == null) {
return; return;
} }
delete holder.keyid;
if (holder.classList.contains('active')) {
holder.classList.remove('active');
}
return this.#onRowClicked(e, (keyid >>> MaxColumnBit) - this.#startIndex, keyid & MaxColumnMask); return this.#onRowClicked(e, (keyid >>> MaxColumnBit) - this.#startIndex, keyid & MaxColumnMask);
}); });
holder.addEventListener('dblclick', e => this.#onRowDblClicked(e)); holder.addEventListener('dblclick', e => this.#onRowDblClicked(e));
@ -846,8 +880,12 @@ class Grid {
const type = isCheckbox ? GridCheckboxColumn : this.#colTypes[col.key] ?? GridColumn; const type = isCheckbox ? GridCheckboxColumn : this.#colTypes[col.key] ?? GridColumn;
let element; let element;
if (!isCheckbox && selectChanged && typeof type.createEdit === 'function') { if (!isCheckbox && selectChanged && typeof type.createEdit === 'function') {
if (vals.__editing && type.editing) {
val = type.getValue({ target: cell.children[0] });
this.#onRowChanged(null, startIndex + i, col, val, true);
}
element = selected ? element = selected ?
type.createEdit(e => this.#onRowChanged(e, startIndex + i, col, type.getValue(e)), col, this.#refs.bodyContent) : type.createEdit(e => this.#onRowChanged(e, startIndex + i, col, type.getValue(e)), col, this.#refs.bodyContent, vals) :
type.create(col); type.create(col);
cell.replaceChildren(element); cell.replaceChildren(element);
} else { } else {
@ -864,7 +902,7 @@ class Grid {
enabled = item[enabled]; enabled = item[enabled];
} }
} }
type.setValue(element, val, item, col); type.setValue(element, val, item, col, this);
if (typeof type.setEnabled === 'function') { if (typeof type.setEnabled === 'function') {
type.setEnabled(element, enabled); type.setEnabled(element, enabled);
} }
@ -898,6 +936,9 @@ class Grid {
} }
} }
}); });
if (vals.__editing) {
delete vals.__editing;
}
}); });
} }
@ -1322,7 +1363,7 @@ class Grid {
if (overflow) { if (overflow) {
holder.keyid = keyid; holder.keyid = keyid;
holder.innerText = element.innerText; holder.innerText = element.innerText;
const top = this.#refs.bodyContent.offsetTop + target.offsetTop + 1; const top = this.#refs.bodyContent.offsetTop + target.offsetTop;
let left = target.offsetLeft; let left = target.offsetLeft;
let width = holder.offsetWidth; let width = holder.offsetWidth;
if (width > this.#bodyClientWidth) { if (width > this.#bodyClientWidth) {
@ -1412,7 +1453,7 @@ class Grid {
} }
#onRowDblClicked(e) { #onRowDblClicked(e) {
if (e.target.tagName === 'INPUT') { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'LAYER' && e.target.className === 'check-box-inner' || e.target.tagName === 'LABEL' && (e.target.className === 'drop-text' || e.target.className === 'drop-caret')) {
return; return;
} }
const index = this.selectedIndex; const index = this.selectedIndex;
@ -1427,7 +1468,7 @@ class Grid {
} }
} }
#onRowChanged(_e, index, col, value) { #onRowChanged(_e, index, col, value, blur) {
if (this.#currentSource == null) { if (this.#currentSource == null) {
return; return;
} }
@ -1445,8 +1486,14 @@ class Grid {
if (enabled !== false) { if (enabled !== false) {
item[col.key] = value; item[col.key] = value;
row.__changed = true; row.__changed = true;
if (typeof col.onchanged === 'function') { if (blur) {
col.onchanged.call(this, item, value); if (typeof col.oneditend === 'function') {
col.oneditend.call(this, item, value);
}
} else {
if (typeof col.onchanged === 'function') {
col.onchanged.call(this, item, value);
}
} }
} }
} }

View File

@ -60,10 +60,16 @@ class Popup {
const button = createElement('div', 'popup-button'); const button = createElement('div', 'popup-button');
button.innerText = b.text; button.innerText = b.text;
button.addEventListener('click', () => { button.addEventListener('click', () => {
if (typeof b.trigger === 'function' && b.trigger(this) === false) { if (typeof b.trigger === 'function') {
return; const result = b.trigger(this);
if (typeof result?.then === 'function') {
result.then(r => r !== false && close()).catch(() => { });
} else if (result !== false) {
close();
}
} else {
close();
} }
close();
}); });
return button; return button;
})) }))
@ -140,20 +146,25 @@ export function showAlert(title, message, iconType = 'info', parent = document.b
}); });
} }
export function showConfirm(title, message, buttons, iconType = 'question', parent = document.body) { export function showConfirm(title, content, buttons, iconType = 'question', parent = document.body) {
return new Promise(resolve => { return new Promise(resolve => {
const popup = new Popup({ const popup = new Popup({
title, title,
content: createElement('div', 'message-wrapper', content: createElement('div', 'message-wrapper',
createIcon('fa-solid', iconTypes[iconType] ?? 'question-circle'), createIcon('fa-solid', iconTypes[iconType] ?? 'question-circle'),
createElement('span', span => span.innerText = message) createElement('span', null, content)
), ),
buttons: buttons?.map(b => { buttons: buttons?.map(b => {
return { text: b.text, trigger: p => resolve(b.key, p) } return {
text: b.text, trigger: p => resolve({
key: b.key,
popup: p
})
};
}) ?? }) ??
[ [
{ text: r('yes', 'Yes'), trigger: p => resolve('yes', p) }, { text: r('yes', 'Yes'), trigger: p => resolve({ key: 'yes', popup: p }) },
{ text: r('no', 'No'), trigger: p => resolve('no', p) } { text: r('no', 'No'), trigger: p => resolve({ key: 'no', popup: p }) }
] ]
}); });
popup.show(parent); popup.show(parent);