Skip to content

Commit 1b4a3f3

Browse files
author
Chris Nelson
committed
added support for custom events
1 parent b40ec78 commit 1b4a3f3

7 files changed

+211
-9
lines changed

auto-complete.html

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<html>
2+
<head>
3+
<!-- Generated by @jspm/generator VSCode Extension - https://github.com/jspm/jspm-vscode -->
4+
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js" crossorigin="anonymous"></script>
5+
<script type="importmap">
6+
{
7+
"imports": {
8+
"@github/combobox-nav": "https://ga.jspm.io/npm:@github/[email protected]/dist/index.js",
9+
"debounce": "https://ga.jspm.io/npm:[email protected]/index.js",
10+
"phx-live-state": "https://ga.jspm.io/npm:[email protected]/build/src/index.js",
11+
"sprae": "https://ga.jspm.io/npm:[email protected]/sprae.js",
12+
"wc-context": "https://ga.jspm.io/npm:[email protected]/core.js"
13+
},
14+
"scopes": {
15+
"https://ga.jspm.io/": {
16+
"json-joy/esm/json-patch": "https://ga.jspm.io/npm:[email protected]/esm/json-patch/index.js",
17+
"phoenix": "https://ga.jspm.io/npm:[email protected]/priv/static/phoenix.mjs",
18+
"process": "https://ga.jspm.io/npm:@jspm/[email protected]/nodelibs/browser/process.js",
19+
"reflect-metadata": "https://ga.jspm.io/npm:[email protected]/Reflect.js",
20+
"subscript": "https://ga.jspm.io/npm:[email protected]/subscript.js",
21+
"swapdom/deflate": "https://ga.jspm.io/npm:[email protected]/deflate.js"
22+
}
23+
}
24+
}
25+
</script>
26+
<script type="module">
27+
import './src/live-template.js';
28+
import './simple-autocomplete.js'
29+
</script>
30+
<script type="module" src="./auto-complete.js"></script>
31+
<style>
32+
auto-complete [aria-selected='true'],
33+
auto-complete [role='option']:hover {
34+
background-color: lavender;
35+
}
36+
37+
auto-complete [aria-disabled='true'] {
38+
color: grey;
39+
}
40+
simple-autocomplete [aria-selected='true'],
41+
simple-autocomplete [role='option']:hover {
42+
background-color: lavender;
43+
}
44+
45+
simple-autocomplete [aria-disabled='true'] {
46+
color: grey;
47+
}
48+
49+
</style>
50+
</head>
51+
<body>
52+
<live-template url="ws://localhost:4000/live_state" topic="people:search" custom-events="autocomplete-commit,autocomplete-search">
53+
<div>
54+
The chosen people:
55+
<ul>
56+
<li :each="person in selected_people" :text="person.first_name + ' ' + person.last_name"></li>
57+
</ul>
58+
</div>
59+
<simple-autocomplete :sendautocomplete-commit="choose-person" :sendautocomplete-search="search" clear-on-select="true">
60+
<input slot="input" type="search" name="query" autocomplete="off" />
61+
<ul slot="list">
62+
<li :each="person in people" :data-person-id="person.id" role="option" :text="person.first_name + ' ' + person.last_name"></li>
63+
</ul>
64+
</simple-autocomplete>
65+
66+
</live-template>
67+
</body>
68+
</html>

importmap.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"env": [
3+
"browser",
4+
"development",
5+
"module"
6+
],
7+
"imports": {
8+
"phx-live-state": "https://ga.jspm.io/npm:[email protected]/build/src/index.js",
9+
"sprae": "https://ga.jspm.io/npm:[email protected]/sprae.js",
10+
"wc-context": "https://ga.jspm.io/npm:[email protected]/core.js"
11+
},
12+
"scopes": {
13+
"https://ga.jspm.io/": {
14+
"json-joy/esm/json-patch": "https://ga.jspm.io/npm:[email protected]/esm/json-patch/index.js",
15+
"phoenix": "https://ga.jspm.io/npm:[email protected]/priv/static/phoenix.mjs",
16+
"process": "https://ga.jspm.io/npm:@jspm/[email protected]/nodelibs/browser/process.js",
17+
"reflect-metadata": "https://ga.jspm.io/npm:[email protected]/Reflect.js",
18+
"subscript": "https://ga.jspm.io/npm:[email protected]/subscript.js",
19+
"subscript/justin": "https://ga.jspm.io/npm:[email protected]/justin.js",
20+
"swapdom": "https://ga.jspm.io/npm:[email protected]/inflate.js",
21+
"ulive": "https://ga.jspm.io/npm:[email protected]/dist/ulive.es.js"
22+
}
23+
}
24+
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "",
44
"license": "MIT",
55
"author": "live-template",
6-
"version": "0.4.1",
6+
"version": "0.5.0",
77
"type": "module",
88
"main": "index.js",
99
"module": "index.js",

signals.html

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<head>
2+
3+
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js" crossorigin="anonymous"></script>
4+
<script type="importmap">
5+
{
6+
"imports": {
7+
"@preact/signals-core": "https://ga.jspm.io/npm:@preact/[email protected]/dist/signals-core.module.js",
8+
"phx-live-state": "https://ga.jspm.io/npm:[email protected]/build/src/index.js",
9+
"solid-js": "https://ga.jspm.io/npm:[email protected]/dist/dev.js"
10+
},
11+
"scopes": {
12+
"https://ga.jspm.io/": {
13+
"json-joy/esm/json-patch": "https://ga.jspm.io/npm:[email protected]/esm/json-patch/index.js",
14+
"phoenix": "https://ga.jspm.io/npm:[email protected]/priv/static/phoenix.mjs",
15+
"process": "https://ga.jspm.io/npm:@jspm/[email protected]/nodelibs/browser/process.js",
16+
"reflect-metadata": "https://ga.jspm.io/npm:[email protected]/Reflect.js",
17+
"subscript": "https://ga.jspm.io/npm:[email protected]/subscript.js",
18+
"wc-context": "https://ga.jspm.io/npm:[email protected]/core.js"
19+
}
20+
}
21+
}
22+
</script>
23+
<script type="module">
24+
import { createSignal } from 'solid-js';
25+
import { signal, effect } from "@preact/signals-core";
26+
import LiveState from 'phx-live-state';
27+
28+
function createLiveSignal(liveState, initialState) {
29+
const stateSignal = signal({people: []});
30+
liveState.addEventListener('state:change', ({ detail: {state} }) => {
31+
stateSignal.value = state;
32+
})
33+
return stateSignal;
34+
}
35+
const liveState = new LiveState({ url: 'ws://localhost:4000/live_state', topic: 'people:all' });
36+
liveState.connect();
37+
const state = createLiveSignal(liveState, {});
38+
effect(() => {
39+
const {people} = state.value;
40+
console.log(state.value);
41+
console.log(`There are ${people?.length} people.`);
42+
});
43+
</script>
44+
</head>
45+
46+
<body>
47+
<h1>hi</h1>
48+
</body>

simple-autocomplete.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Combobox from '@github/combobox-nav';
2+
import debounce from 'debounce';
3+
4+
export class SimpleAutocomplete extends HTMLElement {
5+
constructor() {
6+
super();
7+
this.attachShadow({ mode: 'open' });
8+
this.shadowRoot.innerHTML = `
9+
<slot name="input"></slot>
10+
<slot name="list"></slot>
11+
`;
12+
const inputSlot = this.shadowRoot.querySelector('slot[name="input"]');
13+
const input = inputSlot.assignedElements().length > 0 ? inputSlot.assignedElements()[0] : undefined;
14+
input.addEventListener('input',
15+
debounce((e) => this.dispatchEvent(
16+
new CustomEvent('autocomplete-search', { detail: { query: input.value } })), 300));
17+
const listSlot = this.shadowRoot.querySelector('slot[name="list"]');
18+
const list = listSlot.assignedElements().length > 0 ? listSlot.assignedElements()[0] : undefined;
19+
const combobox = new Combobox(input, list)
20+
// when options appear, start intercepting keyboard events for navigation
21+
combobox.start();
22+
this.addEventListener("combobox-commit", ({detail, target}) => {
23+
if (this.getAttribute('clear-on-select')) {input.value = '';}
24+
target.dispatchEvent(new CustomEvent('autocomplete-commit', {detail, bubbles: true}));
25+
})
26+
}
27+
}
28+
29+
customElements.define('simple-autocomplete', SimpleAutocomplete)

src/live-template.js

+20-8
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,23 @@ export class LiveTemplateElement extends HTMLElement {
2222
}
2323
this.connectLiveState();
2424
}
25+
directive.sendclick = this.sendEventDirective('click');
26+
directive.sendsubmit = this.sendEventDirective('submit');
27+
directive.sendinput = this.sendEventDirective('input');
28+
if (this.getAttribute('custom-events')) {
29+
const customEvents = this.getAttribute('custom-events').split(',');
30+
for (const customEvent of customEvents) {
31+
console.log('adding directive for', customEvent);
32+
directive[`send${customEvent}`] = this.sendEventDirective(customEvent);
33+
}
34+
}
2535
}
2636

2737
connectLiveState() {
2838
this.liveState.connect();
2939
this.liveState.addEventListener('livestate-change', ({ detail: { state } }) => {
40+
console.log('got state', state);
3041
this.buildTemplate();
31-
directive.sendclick = this.sendEventDirective('click');
32-
directive.sendsubmit = this.sendEventDirective('submit');
33-
directive.sendinput = this.sendEventDirective('input');
3442
sprae(this, { ...state, sendEvent: (n) => (e) => this.sendEvent(n, e) });
3543
});
3644
}
@@ -57,18 +65,22 @@ export class LiveTemplateElement extends HTMLElement {
5765
dir.parse = (value) => value;
5866
return dir;
5967
}
60-
68+
6169
sendEvent(eventName, e) {
6270
if (e instanceof SubmitEvent) {
6371
const form = e.target;
6472
const formData = new FormData(form);
6573
const data = Object.fromEntries(formData.entries());
6674
this['liveState'].pushEvent(eventName, data);
6775
} else if (e instanceof InputEvent) {
68-
const form = e.target.form;
69-
const formData = new FormData(form);
70-
const data = Object.fromEntries(formData.entries());
71-
this['liveState'].pushEvent(eventName, data);
76+
const input = e.target;
77+
const payload = {};
78+
payload[input.getAttribute('name')] = input.value;
79+
this['liveState'].pushEvent(eventName, payload);
80+
} else if (e instanceof CustomEvent) {
81+
const detail = e.detail;
82+
const payload = Object.assign({}, e.target.dataset, detail);
83+
this['liveState'].pushEvent(eventName, payload);
7284
} else {
7385
this['liveState'].pushEvent(eventName, e.target.dataset)
7486
}

test/live-template-test.js

+21
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ describe('render template', () => {
8484
expect(pushCall.args[1].bar).to.equal('wuzzle');
8585
});
8686

87+
it('sends custom events with element data', async () => {
88+
const el = await fixture(`
89+
<live-template custom-events="bar,bang">
90+
<span id="foo" data-wuzzle="blarg" :sendbar="bar" :sendbang="bang"></span>
91+
</live-template>
92+
`);
93+
setupLiveState(el);
94+
const pushStub = sinon.stub();
95+
el.liveState.pushEvent = pushStub;
96+
const span = el.querySelector('#foo');
97+
span.dispatchEvent(new CustomEvent('bar', {detail: {bing: 'bong'}, bubbles: true}))
98+
span.dispatchEvent(new CustomEvent('bang', {detail: {wuzzle: 'overwrite'}, bubbles: true}))
99+
const pushCall = pushStub.getCall(0);
100+
expect(pushCall.args[0]).to.equal('bar');
101+
expect(pushCall.args[1].bing).to.equal('bong');
102+
expect(pushCall.args[1].wuzzle).to.equal('blarg');
103+
const secondCall = pushStub.getCall(1);
104+
expect(secondCall.args[0]).to.equal('bang');
105+
expect(secondCall.args[1].wuzzle).to.equal('overwrite');
106+
});
107+
87108
it('allows for nested templates and fallback content', async () => {
88109
const el = await fixture(`
89110
<live-template>

0 commit comments

Comments
 (0)