Last active
February 6, 2025 18:11
-
-
Save Neophen/5faea87df7b881316ad2bb70bdd50aed to your computer and use it in GitHub Desktop.
Phoenix LiveViewHook Class
This file contains 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
import { LiveViewHook } from './live_view_hook' | |
/* | |
* Tracks height of an element when viewport resizes and sets a variable accordingly. | |
* | |
* Used to set the header offset. | |
*/ | |
class HeightTrackerHook extends LiveViewHook { | |
private resizeObserver: ResizeObserver | null = null | |
mounted() { | |
this.resizeObserver = new ResizeObserver(this.setHeightProperty) | |
this.resizeObserver.observe(this.el) | |
} | |
beforeDestroy() { | |
this.resizeObserver?.unobserve(this.el) | |
} | |
private setHeightProperty = ([entry]: ResizeObserverEntry[]) => { | |
document.documentElement.style.setProperty(this.requiredAttr('data-height-var'), `${entry.contentRect.height}px`) | |
} | |
} | |
export default HeightTrackerHook.createViewHook() |
This file contains 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
import { ViewHook } from 'phoenix_live_view' | |
import { requiredAttr, requiredEl } from '../../utils' | |
interface Hook<T> extends ViewHook { | |
instance: T | null | |
} | |
type OnReply = (reply: unknown, ref: number) => unknown | |
export abstract class LiveViewHook { | |
protected el: HTMLElement | |
protected hook: ViewHook | |
mounted(): void {} | |
beforeUpdate(): void {} | |
updated(): void {} | |
beforeDestroy(): void {} | |
destroyed(): void {} | |
disconnected(): void {} | |
reconnected(): void {} | |
constructor(hook: ViewHook) { | |
this.hook = hook | |
this.el = hook.el | |
} | |
protected pushToServer(event: string, payload: object = {}, onReply?: OnReply) { | |
const targetView = this.el.getAttribute('phx-target') ?? this.el.getAttribute('data-target') | |
if (targetView === null || targetView === '') { | |
this.hook.pushEvent(event, payload, onReply) | |
return | |
} | |
this.hook.pushEventTo(targetView, event, payload, onReply) | |
} | |
protected handleEvent<T extends object>(event: string, callback: (payload: T) => void): void { | |
this.hook.handleEvent(event, (payload: object) => callback(payload as T)) | |
} | |
protected requiredAttr = <T = string>(attribute: string): T => { | |
return requiredAttr<T>(this.el, attribute) | |
} | |
protected requiredChild = <T extends HTMLElement>( | |
selector: string, | |
type: { new (): T } = HTMLElement as unknown as { new (): T } | |
): T => { | |
return requiredEl<T>(this.el.querySelector<T>(selector), type) | |
} | |
protected requiredChildren = <T extends HTMLElement>( | |
selector: string, | |
type: { new (): T } = HTMLElement as unknown as { new (): T } | |
): T[] => { | |
const childrenElements = Array.from(this.el.querySelectorAll<T>(selector)) | |
if (childrenElements.every((element) => requiredEl<T>(element, type))) { | |
return childrenElements | |
} | |
throw new Error(`All child elements must be of type: ${type.name}`) | |
} | |
static createViewHook<T extends LiveViewHook>(this: new (hook: ViewHook) => T): Hook<T> { | |
const HookClass = this as unknown as new (hook: ViewHook) => T | |
return { | |
instance: null, | |
mounted() { | |
this.instance = new HookClass(this) | |
this.instance.mounted() | |
}, | |
beforeUpdate() { | |
this.instance?.beforeUpdate() | |
}, | |
updated() { | |
this.instance?.updated() | |
}, | |
beforeDestroy() { | |
this.instance?.beforeDestroy() | |
}, | |
destroyed() { | |
this.instance?.destroyed() | |
this.instance = null | |
}, | |
disconnected() { | |
this.instance?.disconnected() | |
}, | |
reconnected() { | |
this.instance?.reconnected() | |
} | |
} as Hook<T> | |
} | |
} |
This file contains 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 const requiredEl = <T extends HTMLElement>( | |
element: unknown, | |
type: { new (): T } = HTMLElement as unknown as { new (): T } | |
): T => { | |
if (!element) { | |
throw new Error(`Element must exist ${type.name}`) | |
} | |
if (!(element instanceof type)) { | |
throw new Error(`Element must be of type ${type.name}`) | |
} | |
return element | |
} | |
export const requiredAttr = <T = string>(el: Element, attribute: string): T => { | |
const value = el.getAttribute(attribute) | |
if (!value) { | |
throw new Error(`Attribute must exist: ${attribute}`) | |
} | |
return value as unknown as T | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment