Skip to content

Commit

Permalink
declarative event sending
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Nelson committed Apr 14, 2024
1 parent 35c208e commit d595e26
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 7 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,25 @@ There a quite a few more, for the full list see the [sprae README](https://githu

## Sending events

To send events to a LiveState backend, the `sendEvent()` function is provided and able to be called from event handlers in the template. It takes the name of the event to send to the channel, and will convert DOM events as follows:
Sending events to the LiveState channel can be done declaratively or programmatically.

### Declarative event sending

Sprae directives have been added for several events:

* :sendclick
* :sendsubmit
* :sendinput

The value of the attribute for each will specify the event name to send to the LiveState channel, similar to LiveView `phx-*` attributes. Example:

```html
<button :sendclick="add-person">Add Person</button>
```

### Programmatic event sending

To programmatically send events to a LiveState channel, the `sendEvent()` function is provided and able to be called from event handlers in the template. It takes the name of the event to send to the channel, and will convert DOM events as follows:

* submit and input events will send the FormData and prevent the default event behaviour.
* click events will send the dataset of the element (any `data-` attributes).
Expand Down Expand Up @@ -131,9 +149,13 @@ Example:
</body>
```

## Status
## Demo

The `silly_crm.html` shows a working CRUD example. It is a front end to the [silly_crm](http://github.com/superchris/silly_crm) project. To run it:

live-templates should be considered alpha quality.
1. Run the sillycrm project on localhost:4000 (instructions are in README)
2. Run `npm start` in this project
2. Go to http://localhost:8080/silly_crm.html

## Future plans

Expand Down
2 changes: 1 addition & 1 deletion example.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@
<ul>
<li :each="todo in todos" :text="todo"></li>
</ul>
<button @click="sendEvent('bob', event)">butt</button>
<button :onclick="sendEvent('bob', event)">butt</button>
</live-template>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "",
"license": "MIT",
"author": "live-template",
"version": "0.3.0",
"version": "0.4.0",
"type": "module",
"main": "index.js",
"module": "index.js",
Expand Down
77 changes: 77 additions & 0 deletions silly_crm.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<html>

<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js" crossorigin="anonymous"></script>
<script type="importmap">
{
"imports": {
"lit": "https://ga.jspm.io/npm:[email protected]/index.js",
"phx-live-state": "https://ga.jspm.io/npm:[email protected]/build/src/index.js",
"sprae": "https://ga.jspm.io/npm:[email protected]/sprae.js",
"wc-context": "https://ga.jspm.io/npm:[email protected]/core.js"
},
"scopes": {
"https://ga.jspm.io/": {
"@lit/reactive-element": "https://ga.jspm.io/npm:@lit/[email protected]/development/reactive-element.js",
"json-joy/esm/json-patch": "https://ga.jspm.io/npm:[email protected]/esm/json-patch/index.js",
"lit-element/lit-element.js": "https://ga.jspm.io/npm:[email protected]/development/lit-element.js",
"lit-html": "https://ga.jspm.io/npm:[email protected]/development/lit-html.js",
"lit-html/is-server.js": "https://ga.jspm.io/npm:[email protected]/development/is-server.js",
"phoenix": "https://ga.jspm.io/npm:[email protected]/priv/static/phoenix.mjs",
"process": "https://ga.jspm.io/npm:@jspm/[email protected]/nodelibs/browser/process.js",
"reflect-metadata": "https://ga.jspm.io/npm:[email protected]/Reflect.js",
"subscript": "https://ga.jspm.io/npm:[email protected]/subscript.js",
"subscript/justin": "https://ga.jspm.io/npm:[email protected]/justin.js",
"swapdom": "https://ga.jspm.io/npm:[email protected]/inflate.js",
"ulive": "https://ga.jspm.io/npm:[email protected]/dist/ulive.es.js"
}
}
}
</script>
<script type="module">
import './src/live-template.js';
import './simple-field.js';
</script>
</head>

<body>
<live-template url="ws://localhost:4000/live_state" topic="people:all">
<table>
<thead>
<tr>
<th><a :sendclick="sort" data-sort-key="last_name">Last name</a></th>
<th>First name</th>
</tr>
</thead>
<tbody>
<tr :each="person in people">
<td><a :sendclick="edit-person" :data-id="person.id" :text="person.last_name"></a></td>
<td :text="person.first_name"></td>
<td :text="person.birth_date"></td>
</tr>
</tbody>
</table>
<button :sendclick="add-person">Add Person</button>
<dialog :open="editing">
<form :sendsubmit="save-person">
<div>
<label>First name</label>
<input name="first_name" :value="person.first_name"/>
<span :text="errors.first_name"></span>
</div>
<div>
<label>Last name</label>
<input name="last_name" :value="person.last_name"/>
<span :text="errors.last_name"></span>
</div>
<div>
<label>Birth date</label>
<input name="birth_date" type="date" :value="person.birth_date"/>
<span :text="errors.birth_date"></span>
</div>
<button>Save</button>
</form>
</dialog>
</live-template>
</body>

</html>
21 changes: 19 additions & 2 deletions src/live-template.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import LiveState from 'phx-live-state';
import { registerContext, observeContext } from 'wc-context';
import sprae from 'sprae';
import sprae, { directive } from 'sprae';

export class LiveTemplateElement extends HTMLElement {
connectedCallback() {
Expand Down Expand Up @@ -28,7 +28,10 @@ export class LiveTemplateElement extends HTMLElement {
this.liveState.connect();
this.liveState.addEventListener('livestate-change', ({ detail: { state } }) => {
this.buildTemplate();
sprae(this, {...state, sendEvent: (n) => (e) => this.sendEvent(n, e)});
directive.sendclick = this.sendEventDirective('click');
directive.sendsubmit = this.sendEventDirective('submit');
directive.sendinput = this.sendEventDirective('input');
sprae(this, { ...state, sendEvent: (n) => (e) => this.sendEvent(n, e) });
});
}

Expand All @@ -38,6 +41,20 @@ export class LiveTemplateElement extends HTMLElement {
this.replaceChildren(template.content);
}
}

sendEventDirective(eventName) {
return (el, evaluate, state) => {
let removeOldListener;
return () => {
removeOldListener?.();
const handler = (e) => {
this.sendEvent(evaluate, e);
};
removeOldListener = () => el.removeEventListener(eventName, handler);
el.addEventListener(eventName, handler);
}
}
}

sendEvent(eventName, e) {
if (e instanceof SubmitEvent) {
Expand Down
20 changes: 20 additions & 0 deletions test/live-template-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ describe('render template', () => {
expect(pushCall.args[1].foo).to.equal('bar');
expect(pushCall.args[1].bar).to.equal('wuzzle');
});
it('sends events from directivs', async () => {
const el = await fixture(`
<live-template>
<form :sendsubmit="it">
<input name="foo" value="bar" />
<input name="bar" value="wuzzle" />
<button type="submit">save</button>
</form>
</live-template>
`);
setupLiveState(el);
const pushStub = sinon.stub();
el.liveState.pushEvent = pushStub;
const button = el.querySelector('button');
button.click();
const pushCall = pushStub.getCall(0);
expect(pushCall.args[0]).to.equal('it');
expect(pushCall.args[1].foo).to.equal('bar');
expect(pushCall.args[1].bar).to.equal('wuzzle');
});

it('allows for nested templates and fallback content', async () => {
const el = await fixture(`
Expand Down

0 comments on commit d595e26

Please sign in to comment.