197 lines
5.0 KiB
TypeScript
197 lines
5.0 KiB
TypeScript
export interface Type<T> extends Function {
|
|
new (...args: any[]): T;
|
|
}
|
|
|
|
export abstract class ValueElement<T, O> extends HTMLElement {
|
|
static observedAttributes = ["value"];
|
|
|
|
private _value: T | null = null;
|
|
|
|
get value(): T | null {
|
|
return this._value;
|
|
}
|
|
|
|
set value(value: T | null) {
|
|
if (value) {
|
|
this.setAttribute("value", JSON.stringify(value));
|
|
} else if (this.hasAttribute("value")) {
|
|
this.removeAttribute("value");
|
|
}
|
|
}
|
|
|
|
options: O | null;
|
|
|
|
constructor(value: T | null = null, options: O | null = null) {
|
|
super();
|
|
if (value) {
|
|
this.value = value;
|
|
}
|
|
this.options = options;
|
|
this.onInit();
|
|
}
|
|
|
|
onInit() {}
|
|
|
|
beforeRefresh() {}
|
|
|
|
onRefresh() {}
|
|
|
|
abstract buildHTML(): string;
|
|
|
|
refresh() {
|
|
const rawValue = this.getAttribute("value");
|
|
if (rawValue) {
|
|
this._value = JSON.parse(rawValue);
|
|
} else {
|
|
this._value = null;
|
|
}
|
|
if (this.className) {
|
|
this.classList = this.className;
|
|
}
|
|
this.beforeRefresh();
|
|
this.innerHTML = this.buildHTML();
|
|
this.onRefresh();
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.refresh();
|
|
}
|
|
|
|
attributeChangedCallback(name: string, oldValue: any, newValue: any) {
|
|
setTimeout(() => this.refresh());
|
|
}
|
|
}
|
|
|
|
export interface ListOptions<T> {
|
|
onClick?: (element: ListItemElement<T>) => void;
|
|
onAdd?: (element: ListItemElement<T>) => void;
|
|
onRemove?: (element: ListItemElement<T>) => void;
|
|
}
|
|
|
|
export abstract class ListItemElement<T> extends ValueElement<T, ListOptions<T>> {
|
|
abstract getHref(value: T): string;
|
|
|
|
abstract getContent(value: T): string;
|
|
|
|
className = "list-group-item list-group-item-action";
|
|
|
|
onInit(): void {
|
|
this.handleClick = this.handleClick.bind(this);
|
|
this.handleRemoveClick = this.handleRemoveClick.bind(this);
|
|
this.handleAddClick = this.handleAddClick.bind(this);
|
|
}
|
|
|
|
buildHTML(): string {
|
|
if (!this.value) {
|
|
return "";
|
|
}
|
|
const href = this.getHref(this.value);
|
|
const content = this.getContent(this.value);
|
|
return `
|
|
<a href="${href}"
|
|
class="d-flex justify-content-between align-items-start text-decoration-none">
|
|
<span>
|
|
<span class="text-success me-2 ${this.options?.onAdd ? "" : "d-none"}" style="color: green;" data-add>
|
|
+
|
|
</span>
|
|
${content}
|
|
</span>
|
|
<span>
|
|
<span class="text-danger ms-2 ${this.options?.onRemove ? "" : "d-none"}" data-remove>
|
|
✕
|
|
</span>
|
|
</span>
|
|
</a>
|
|
`;
|
|
}
|
|
|
|
onRefresh(): void {
|
|
this.addEventListener("click", this.handleClick);
|
|
if (this.options?.onAdd) {
|
|
this.querySelector<HTMLElement>("[data-add]")?.addEventListener("click", this.handleAddClick);
|
|
} else {
|
|
this.querySelector("[data-add]")?.remove();
|
|
}
|
|
if (this.options?.onRemove) {
|
|
this.querySelector<HTMLElement>("[data-remove]")?.addEventListener("click", this.handleRemoveClick);
|
|
} else {
|
|
this.querySelector("[data-remove]")?.remove();
|
|
}
|
|
}
|
|
|
|
handleClick(event: MouseEvent) {
|
|
if (this.value && this.options?.onClick) {
|
|
this.options.onClick(this);
|
|
}
|
|
}
|
|
|
|
handleAddClick(event: MouseEvent) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (this.value && this.options?.onAdd) {
|
|
this.options.onAdd(this);
|
|
}
|
|
}
|
|
|
|
handleRemoveClick(event: MouseEvent) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
if (this.value && this.options?.onRemove) {
|
|
this.options.onRemove(this);
|
|
}
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this.removeEventListener("click", this.handleClick);
|
|
this.querySelector<HTMLElement>("[data-add]")?.removeEventListener("click", this.handleAddClick);
|
|
this.querySelector<HTMLElement>("[data-remove]")?.removeEventListener("click", this.handleRemoveClick);
|
|
}
|
|
}
|
|
|
|
export abstract class ListElement<T, C extends ListItemElement<T> = ListItemElement<T>> extends ValueElement<
|
|
T[],
|
|
ListOptions<T>
|
|
> {
|
|
static observedAttributes = ["value", "onadd", "onremove", "onclick"];
|
|
|
|
abstract childType: Type<C>;
|
|
|
|
className = "list-group";
|
|
|
|
buildHTML(): string {
|
|
return "";
|
|
}
|
|
|
|
beforeRefresh() {
|
|
if (this.hasAttribute("onclick")) {
|
|
const onclick = new Function("element", this.getAttribute("onclick")!) as ListOptions<T>["onClick"];
|
|
if (!this.options) {
|
|
this.options = {};
|
|
}
|
|
this.options.onClick = onclick;
|
|
}
|
|
if (this.hasAttribute("onadd")) {
|
|
const onadd = new Function("element", this.getAttribute("onadd")!) as ListOptions<T>["onAdd"];
|
|
if (!this.options) {
|
|
this.options = {};
|
|
}
|
|
this.options.onAdd = onadd;
|
|
}
|
|
if (this.hasAttribute("onremove")) {
|
|
const onremove = new Function("element", this.getAttribute("onremove")!) as ListOptions<T>["onRemove"];
|
|
if (!this.options) {
|
|
this.options = {};
|
|
}
|
|
this.options.onRemove = onremove;
|
|
}
|
|
}
|
|
|
|
onRefresh() {
|
|
if (this.value) {
|
|
for (const item of this.value) {
|
|
this.appendChild(new this.childType(item, this.options));
|
|
}
|
|
}
|
|
}
|
|
}
|