Last active
February 6, 2019 02:07
-
-
Save jinjor/08cf386c9992ed693ecd45f82f3bbab5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export abstract class DomSync<T, Context extends unknown[]> { | |
constructor( | |
private root: Element | Document | ShadowRoot, | |
private selector?: string | |
) {} | |
abstract readonly class: string; | |
protected identify(obj: T): string | number { | |
const o = obj as any; | |
if (o && (typeof o.id === "string" || typeof o.id === "number")) { | |
return String(o.id).trim(); | |
} | |
throw new Error("Cannot identify the object."); | |
} | |
protected abstract create(obj: T, ...context: Context): Element; | |
protected abstract update(el: Element, obj: T, ...context: Context): void; | |
listenToRoot(type: string, callback: (e: Event) => void): () => void { | |
this.root.addEventListener(type, callback); | |
return function() { | |
this.root.removeEventListener(type, callback); | |
}; | |
} | |
listenToChild( | |
type: string, | |
match: (el: Element) => boolean, | |
callback: (e: Event, element: Element) => void, | |
unmatchedCallback?: (e: Event) => void | |
): () => void { | |
const handle = (e: Event) => { | |
let target = e.target as HTMLElement; | |
while (target && target !== e.currentTarget) { | |
if (match(target)) { | |
callback(e, target); | |
return; | |
} else { | |
target = target.parentElement; | |
} | |
} | |
if (unmatchedCallback) { | |
unmatchedCallback(e); | |
} | |
}; | |
return this.listenToRoot(type, handle); | |
} | |
listenToContainer( | |
type: string, | |
callback: (e: Event, element: Element) => void, | |
unmatchedCallback?: (e: Event) => void | |
): () => void { | |
return this.listenToChild( | |
type, | |
el => el.classList.contains(this.class), | |
callback, | |
unmatchedCallback | |
); | |
} | |
listenToItem( | |
type: string, | |
callback: (e: Event, element: Element, id: string | number) => void, | |
unmatchedCallback?: (e: Event) => void | |
): () => void { | |
return this.listenToChild( | |
type, | |
el => el.classList.contains(this.class), | |
(e, el: HTMLElement) => { | |
callback(e, el, el.dataset.id || +el.dataset["id:number"]); | |
}, | |
unmatchedCallback | |
); | |
} | |
private containerElement(): Node { | |
if (this.selector) { | |
return this.root.querySelector(this.selector); | |
} | |
return this.root; | |
} | |
private elementId(objectId: string | number): string { | |
return this.class + "_" + objectId; | |
} | |
private objectId(obj: T): string | number { | |
return this.identify(obj); | |
} | |
add(obj: T, ...context: Context): Element | null { | |
const objectId = this.objectId(obj); | |
const elementId = this.elementId(objectId); | |
const el = this.create(obj, ...context); | |
if (!el) { | |
return null; | |
} | |
this.update(el, obj, ...context); | |
if (el.id) { | |
throw new Error("Element id should not be set."); | |
} | |
el.id = elementId; | |
el.classList.add(this.class); | |
el.setAttribute( | |
typeof objectId !== "string" ? `data-id:${typeof objectId}` : "data-id", | |
String(objectId) | |
); | |
this.containerElement().appendChild(el); | |
return el; | |
} | |
addAll(objs: T[], ...context: Context): Element[] { | |
const els = []; | |
for (let obj of objs) { | |
const el = this.add(obj, ...context); | |
if (el) { | |
els.push(el); | |
} | |
} | |
return els; | |
} | |
modify(obj: T, ...context: Context): Element | null { | |
const elementId = this.elementId(this.objectId(obj)); | |
const el = this.root.querySelector("#" + elementId); | |
if (el) { | |
this.update(el, obj, ...context); | |
return el; | |
} | |
return null; | |
} | |
remove(id: string): void { | |
const el = this.root.querySelector("#" + this.elementId(id)); | |
if (el) { | |
el.remove(); | |
} | |
} | |
sync(id: string, obj: T | null, ...context: Context): Element | null { | |
if (obj) { | |
const el = this.modify(obj, ...context); | |
if (!el) { | |
this.add(obj, ...context); | |
} | |
return el; | |
} else { | |
this.remove(id); | |
return null; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment