blob: 55f9474c56d8f3b016a7d1fdba54aa62adaaac06 [file] [log] [blame]
type ComponentCtor<Props> = new (props: Props) => Component<Props>;
type ComponentFunc<Props> = (props: Props) => string;
/** Base component class. */
export class Component<Props> {
el!: Element;
constructor(readonly props: Props) {
this.props = props;
}
/** Lifecycle method to generate static templates. */
render(props: Props): string {
throw new Error('Must be overridden.');
}
/**
* Lifecycle method called after `render()`. It's a good opportunity to add
* dynamic content and add event listeners.
*/
afterRender() {}
}
/** Caches the component instances hooked to DOM elements. */
const componentMap = new WeakMap<Element, Component<unknown>>();
function isComponentCtor<Props>(
CtorOrFunc: ComponentCtor<Props>|
ComponentFunc<Props>): CtorOrFunc is ComponentCtor<Props> {
if (CtorOrFunc.prototype instanceof Component) {
return true;
}
return false;
}
/**
* Renders a component to the container element.
* @param CtorOrFunc Can be either a component contructor or a component
* function. The component function is easier to write but does not keep
* track of internal states.
* @param props Component props.
* @param container The container element of the component.
*/
export function renderComponent<Props>(
CtorOrFunc: ComponentCtor<Props>|ComponentFunc<Props>, props: Props,
container: Element) {
if (!isComponentCtor(CtorOrFunc)) {
// Function based component
container.innerHTML = CtorOrFunc(props);
return;
}
let component = componentMap.get(container) as Component<Props>| undefined;
if (!(component instanceof CtorOrFunc)) {
component = new CtorOrFunc(props);
componentMap.set(container, component);
component.el = container;
}
container.innerHTML = component.render(props);
component.afterRender();
}