Skip to content

Commit

Permalink
updated readme, added docs directory and changeset.md
Browse files Browse the repository at this point in the history
  • Loading branch information
floodfx committed Feb 10, 2022
1 parent 61722b9 commit 140e5a8
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 24 deletions.
59 changes: 35 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
## LiveViewJS

*Front-end framework for back-end developers*
### *Front-end framework for back-end developers*

### Credit 🙌
This is a port of [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html) to Typescript / Javascript. What the Phoenix folks have built is phenominal and I wanted to use that paradigm in Typescript and make it available to others.
This is a backend implementation of [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html) 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.

### Approach 📐
### Quick Overview of LiveView Approach
How Phoenix desribes LiveView:
> LiveView 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.
* **Reuse Phoenix Client Libraries and app.js code** - The Phoenix team has done a ton of heavy lifting on the client that we can just use. We also benefit from fixes and improvements in the future. [See `src/client/liveview.ts` for client code.]
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](https://online.pragmaticstudio.com/courses/liveview-pro/modules/4) by Pragmatic Studio does an amazing job of explaining how LiveView works.)

* **Reuse Phoenix socket message protocol** - The Phoenix team already figured out a great protocol to send events to/from the server. We just implemented a different backend.
The programming paradigm is extremely powerful and productive!

* **Follow component API design (i.e. `mount`, `render` etc), reimplemented with Typescript (so even more type-safe)** - Components in LiveViewJS follow the `mount`, `render`, `handleEvent`, and `handleInfo` API defined in Phoenix. Again, no need to invent a new API.
### 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.

### Status - ****
This is still in ⍺lpha territory. You probably shouldn't put this into production just yet. But side-projects / internal apps could work. 🧱
### Status - **β**
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 ✅ are working and tested and most of them have example usage in the `examples` codebase. Those with `?`, I have not gotten around to testing so not sure if they work. Those marked with ❌ are not yet implemented and known not to work.

(See [Phoenix Bindings Docs](https://hexdocs.pm/phoenix_live_view/bindings.html) for more details)

| Binding | Attribute | Supported |
Expand Down Expand Up @@ -44,16 +49,14 @@ This is still in ⍺lpha territory. You probably shouldn't put this into produc
| JS Interop | `phx-hook` ||
| Rate Limiting | `phx-debounce` ||
| Rate Limiting | `phx-throttle` ||
| Static Tracking | `phx-track-static` | |
| Static Tracking | `phx-track-static` | |

Other features to be implemented:
* [Updating HTML Document Title](https://hexdocs.pm/phoenix_live_view/live-layouts.html#updating-the-html-document-title) - Vote for [Issue 16](https://github.com/floodfx/liveviewjs/issues/16)
* [LiveView Helpers](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html) - Vote for [Issue 17](https://github.com/floodfx/liveviewjs/issues/17)
* [Temporary Assigns](https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-assigns) - Vote for [Issue 18](https://github.com/floodfx/liveviewjs/issues/18)
* [Build in Flash Message Support](https://hexdocs.pm/phoenix_live_view/0.17.6/Phoenix.LiveView.html#put_flash/3) - Vote for [Issue 19](https://github.com/floodfx/liveviewjs/issues/19)
* [File Uploads](https://hexdocs.pm/phoenix_live_view/uploads.html) - Vote for [Issue 20](https://github.com/floodfx/liveviewjs/issues/20)
#### LiveViewJS Changesets
Phoenix's Ecto ORM library and Phoenix LiveView rely on [Ecto Changesets](https://hexdocs.pm/ecto/Ecto.Changeset.html) 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.

### Show me some code! ⌨️
Detailed documentation on [LiveViewJS Changesets](docs/changesets.md).

### Usage - Show me some code! ⌨️

**Step 0** Install LiveViewJS
`npm i liveviewjs`
Expand Down Expand Up @@ -162,11 +165,15 @@ lvServer.registerLiveViewRoute("/light", new LightLiveViewComponent());
// then start the server
lvServer.start();
```
#### Other features to be implemented:
* [Updating HTML Document Title](https://hexdocs.pm/phoenix_live_view/live-layouts.html#updating-the-html-document-title) - Vote for [Issue 16](https://github.com/floodfx/liveviewjs/issues/16)
* [LiveView Helpers](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html) - Vote for [Issue 17](https://github.com/floodfx/liveviewjs/issues/17)
* [Temporary Assigns](https://hexdocs.pm/phoenix_live_view/dom-patching.html#temporary-assigns) - Vote for [Issue 18](https://github.com/floodfx/liveviewjs/issues/18)
* [Build in Flash Message Support](https://hexdocs.pm/phoenix_live_view/0.17.6/Phoenix.LiveView.html#put_flash/3) - Vote for [Issue 19](https://github.com/floodfx/liveviewjs/issues/19)
* [File Uploads](https://hexdocs.pm/phoenix_live_view/uploads.html) - Vote for [Issue 20](https://github.com/floodfx/liveviewjs/issues/20)

### Welcome your feedback, issues, and PRs
I am not an expert on Phoenix Liveview, Elixir, Erlang VMs, etc or most things. Please feel free to open an issue with questios, bugs, etc.

### Commands
### NPM Commands
`npm i` - install the deps

`npm run build` - builds the framework, client, and examples (server)
Expand All @@ -181,11 +188,15 @@ I am not an expert on Phoenix Liveview, Elixir, Erlang VMs, etc or most things.
**Credit** These examples are adapted from an amazing [Phoenix Video / Code Course](https://online.pragmaticstudio.com/courses/liveview-starter/steps/15) authored by the folks at [Pragmatic Studio](https://pragmaticstudio.com/).

Run `npm run examples` then point your browser to:
* `http://localhost:4444/light` - control a fictional porch light with some buttons [See `src/examples/light_liveview.ts`]
* `http://localhost:4444/license` - calculate the per seat costs to license a SaaS product [See `src/examples/license_liveview.ts`]
* `http://localhost:4444/sales-dashboard` - show the aggregated sales stats updated every second [See `src/examples/sales_dashboard_liveview.ts`]
* `http://localhost:4444/search` - search for business by zip code (e.g. 80204) [See `src/examples/live-search/*`]
* `http://localhost:4444/autocomplete` - search for businesses by city with automatically suggested names [See `src/examples/autocomplete/*`]
* `http://localhost:4444/` - Shows the index of all the examples

### More Details on the Approach to Building LiveViewJS 📐

* **Reuse Phoenix Client Libraries and app.js code** - The Phoenix team has done a ton of heavy lifting on the client that we can just use. We also benefit from fixes and improvements in the future. [See `src/client/liveview.ts` for client code.]

* **Reuse Phoenix socket message protocol** - The Phoenix team already figured out a great protocol to send events to/from the server. We just implemented a different backend.

* **Follow component API design (i.e. `mount`, `render` etc), reimplemented with Typescript (so even more type-safe)** - Components in LiveViewJS follow the `mount`, `render`, `handleEvent`, and `handleInfo` API defined in Phoenix. Again, no need to invent a new API.

### Thanks!
Thanks to [@ogrodnek](https://github.com/ogrodnek) for the early support, feedback, and the idea to reuse the Phoenix client code instead of reinventing!
84 changes: 84 additions & 0 deletions docs/changesets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
## Changesets

Changesets are used to track creation and mutation of data models in LiveView projects and synchronize them with HTML Forms and the user input that drives them.

Changeset are built on top of [Zod](https://github.com/colinhacks/zod) which is a "Typescript-first schema validation with static type inference" library.

### Define the Schema

First, we need to define a schema for the data model:
```typescript
import { z } from "zod";

// custom validation
const phoneRegex = /^\d{3}[\s-.]?\d{3}[\s-.]?\d{4}$/

// Use Zod to define the schema for the Volunteer model
// More on Zod - https://github.com/colinhacks/zod
export const VolunteerSchema = z.object({
id: z.string().default(nanoid),
name: z.string().min(2).max(100),
phone: z.string().regex(phoneRegex, 'Should be a valid phone number'),
checked_out: z.boolean().default(false),
})
```

### Infer the Data Model Type based on the Schema
```typescript
// infer the Volunteer model from the Zod Schema
export type Volunteer = z.infer<typeof VolunteerSchema>;
```

### Generate a Changeset Helper and use it to validate the data model
```typescript
import { newChangesetFactory } from "liveviewjs";

// generate changeset helper
export const changeset = newChangesetFactory<Volunteer>(VolunteerSchema)

// implement a mutating changeset function
export const createVolunteer = (newVolunteer: Partial<Volunteer>): LiveViewChangeset<Volunteer> => {
const result = changeset({}, newVolunteer, 'create');
if (result.valid) {
const v = result.data as Volunteer;
// Update your database or other data store
volunteers[v.id] = v;
}
return result;
}
```

## More on Changeset Helpers

The `newChangesetFactory` function generates a changeset helper function that can be used to validate and mutate data models defined by the schema and inferred type.

The API for generate changeset helpers is pretty straight forward. It takes 3 arguments:
* `existing: Partial<T>` the current data model - which can be an empty object if you are creating a new record
* `newData: Partial<T>` the new data to apply to the data model
* `action?: string` which is an optional string describing the changeset action (Note: we keep the action unset for an empty changeset but must be set for form validations to be visible.)

You can see the implementation of the changeset helper factory in the `src/server/component/changeset.ts` file.

When executed, the changeset helper function returns a `LiveViewChangeset<T>` object which has the following properties:
* `valid: boolean` - does the resulting merged data model pass all validation rules?
* `changes: Partial<T>` - just the parts of the model that have been mutated
* `data: T` - the data model after the changeset has been applied (which may be invalid or valid depending on the validations)
* `errors: { [Property in keyof T]?: string | undefined; }` - an object of error messages if the changeset is invalid keyed by field name(s) of the data model
* `action?: string` - the changeset action (or undefined if unset)

### Use with HTML Forms and Form Validation
Changesets are used in concert with HTML Forms to gather user input, validate that input, update the data model or send back error messages for the user to correct.

Example of using a changeset helper with a form text input field including error messages:
```typescript
<div class="field">
${text_input<Volunteer>(changeset, "name", { placeholder: "Name", autocomplete: "off", phx_debounce: 1000 })}
${error_tag(changeset, "name")}
</div>
```

### Empty Changeset
Empty changesets are used to initialize a form with and can be created by calling `changset({}, {})`.

### Detailed Example
See `src/examples/volunteers/*` for working, detailed example of how changesets work and how to use them including with forms.

0 comments on commit 140e5a8

Please sign in to comment.