LiveViewJS
Front-end framework for back-end developers
Credit 🙌
This is a backend implementation of Phoenix LiveView in Typescript. What the Phoenix folks have built is phenominal and I wanted to use that paradigm in Typescript and make it available to others.
Quick Overview of LiveView Approach
How Phoenix desribes LiveView:
Featured Content Ads
add advertising hereLiveView is an exciting new library which enables rich, real-time user experiences with server-rendered HTML. LiveView powered applications are stateful on the server with bidrectional communication via WebSockets, offering a vastly simplified programming model compared to JavaScript alternatives.
In other words, LiveView takes a very different approach than the popular SPA frameworks like React, Vuejs, and Svelt to building rich, highly interactive web applications. Instead of sending down a bundle of javascript, LiveView apps render an HTML page on the first request and then connect via a persistent socket over which client events are sent and updated received. These events may trigger a state update on the server and a re-calculation of what the page should look like. Instead of reloading the page, the client receives a “diff” of the page via the socket and the page’s DOM is updated. (This video by Pragmatic Studio does an amazing job of explaining how LiveView works.)
The programming paradigm is extremely powerful and productive!
Feedback is a 🎁
I am not an expert on Phoenix Liveview, Elixir, Erlang VMs, etc or really most things. Please feel free to open an issue with questios, bugs, etc.
Featured Content Ads
add advertising hereStatus – β
LiveViewJS is in βeta. The project is still young but the code is stable, tested, and well-documented.
Implemented Phoenix Bindings
The bindings below marked with examples
codebase. Those with ?
, I have not gotten around to testing so not sure if they work. Those marked with
(See Phoenix Bindings Docs for more details)
Binding | Attribute | Supported |
---|---|---|
Params | phx-value-* |
|
Click Events | phx-click |
|
Click Events | phx-click-away |
|
Form Events | phx-change |
|
Form Events | phx-submit |
|
Form Events | phx-feedback-for |
|
Form Events | phx-disable-with |
|
Form Events | phx-trigger-action |
﹖ |
Form Events | phx-auto-recover |
﹖ |
Focus Events | phx-blur |
|
Focus Events | phx-focus |
|
Focus Events | phx-window-blur |
|
Focus Events | phx-window-focus |
|
Key Events | phx-keydown |
|
Key Events | phx-keyup |
|
Key Events | phx-window-keydown |
|
Key Events | phx-window-keyup |
|
Key Events | phx-key |
|
DOM Patching | phx-update |
|
DOM Patching | phx-remove |
﹖ |
JS Interop | phx-hook |
|
Rate Limiting | phx-debounce |
|
Rate Limiting | phx-throttle |
|
Static Tracking | phx-track-static |
LiveViewJS Changesets
Phoenix’s Ecto ORM library and Phoenix LiveView rely on Ecto Changesets to allow filtering, validation, and other logic to be applied to the data. Changesets are a powerful way to apply logic to data and are used in Phoenix’s ORM and LiveView. LiveViewJS uses Changesets to provide a similar API to Phoenix’s though it is NOT a full-blown ORM.
Featured Content Ads
add advertising hereDetailed documentation on LiveViewJS Changesets.
Usage – Show me some code! ⌨️
Step 0 Install LiveViewJS
npm i liveviewjs
Step 1 Implement a LiveViewComponent
LiveViewComponent
LiveViewExternalEventListener
// mount is called before html render on HTTP requests and // Define and render the HTML for your LiveViewComponent
` // Handle events sent back from the client… Events }”>
// when the socket is connected on the phx-join event
mount(params: LiveViewMountParams, session: Partial
// set the default value(s) for the component data
return { brightness: 10 };
};
// This function is called after any context change and
// only diffs are sent back to the page to re-render
render(context: LightContext) {
const { brightness } = context;
return html`
Front Porch Light
};
// may update the state (context) of the component and
// cause a re-render
handleEvent(event: LightEvent, params: never, socket: LiveViewSocket
const ctx: LightContext = { brightness: socket.context.brightness };
switch (event) {
case ‘off’:
ctx.brightness = 0;
break;
case ‘on’:
ctx.brightness = 100;
break;
case ‘up’:
ctx.brightness = Math.min(ctx.brightness + 10, 100);
break;
case ‘down’:
ctx.brightness = Math.max(ctx.brightness – 10, 0);
break;
}
return ctx;
}
import { SessionData } from "express-session";
import {html, BaseLiveViewComponent, LiveViewComponent, LiveViewExternalEventListener, LiveViewMountParams, LiveViewSocket } from "liveviewjs";
// define your component's data shape
export interface LightContext {
brightness: number;
}
// define the component events
export type LightEvent = "on" | "off" | "up" | "down";
// implement your component
export class LightLiveViewComponent extends BaseLiveViewComponent<LightContext, never> implements
LiveViewComponent<LightContext, never>,
LiveViewExternalEventListener<LightContext, LightEvent, never> {
// mount is called before html render on HTTP requests and
// when the socket is connected on the phx-join event
mount(params: LiveViewMountParams, session: Partial<SessionData>, socket: LiveViewSocket<LightContext>) {
// set the default value(s) for the component data
return { brightness: 10 };
};
// Define and render the HTML for your LiveViewComponent
// This function is called after any context change and
// only diffs are sent back to the page to re-render
render(context: LightContext) {
const { brightness } = context;
return html`
<div id="light">
<h1>Front Porch Light h1>
<div class="meter">
<div>${brightness}%div>
<progress id="light_level" value="${brightness}" max="100">
progress>
div>
<button phx-click="off">
Off
button>
<button phx-click="down">
Down
button>
<button phx-click="up">
Up
button>
<button phx-click="on">
On
button>
div>
`
};
// Handle events sent back from the client... Events
// may update the state (context) of the component and
// cause a re-render
handleEvent(event: LightEvent, params: never, socket: LiveViewSocket<LightContext>) {
const ctx: LightContext = { brightness: socket.context.brightness };
switch (event) {
case 'off':
ctx.brightness = 0;
break;
case 'on':
ctx.brightness = 100;
break;
case 'up':
ctx.brightness = Math.min(ctx.brightness + 10, 100);
break;
case 'down':
ctx.brightness = Math.max(ctx.brightness - 10, 0);
break;
}
return ctx;
}
}
Step 2 – Register your LiveViewComponent
s and start the HTTP and Socket server with LiveViewServer
lvServer.registerLiveViewRoutes(lvRouter);
// OR register your route with the server directly
lvServer.registerLiveViewRoute(“/light”, new LightLiveViewComponent());
// then start the server
lvServer.start();”>
// import package import {LiveViewServer} from "liveviewjs"; // create new LiveViewServer const lvServer = new LiveViewServer(); // define your routes by mapping paths to LiveViewComponents const lvRouter: LiveViewRouter = { "/light": new LightLiveViewComponent(); } // AND then passing the router to the server lvServer.registerLiveViewRoutes(lvRouter); // OR register your route with the server directly lvServer.registerLiveViewRoute("/light", new LightLiveViewComponent()); // then start the server lvServer.start();
Other features to be implemented:
- Updating HTML Document Title – Vote for Issue 16
- LiveView Helpers – Vote for Issue 17
- Temporary Assigns – Vote fo