Skip to content

Commit 55ea8f5

Browse files
committed
add e-sse
1 parent 86b8879 commit 55ea8f5

23 files changed

+333
-207
lines changed

src/E/e-form.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const VALIDATION_PATTERNS = {
1414
password: /^.*(?=.{8,})(?=.*[a-zA-Z])(?=.*\d)(?=.*[!#$%&? "]).*$/,
1515
tel: /[0-9]{0,14}$/,
1616
time: /\d\d:\d\d/,
17-
// eslint-disable-next-line no-useless-escape
17+
1818
url: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/
1919
}
2020

src/E/e-if-template.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default class EIfTemplate extends HTMLTemplateElement {
2323
run() {
2424
const expr = this.getAttribute('data-condition-to-display')
2525
if (!expr) {
26-
throw new Error(`<template is="e-if"> must have data-condition-to-display`)
26+
throw new Error('<template is="e-if"> must have data-condition-to-display')
2727
}
2828

2929
// 1. Get inherited lexical state at this <template>

src/E/e-json-map-template.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ export default class EJsonMapTemplate extends HTMLTemplateElement {
6767
return
6868
}
6969

70+
// ---------------------------------------------------------
71+
// EVENT SOURCE MODE
72+
// ---------------------------------------------------------
73+
const eventSourceName = this.getAttribute('data-event-source')
74+
if (eventSourceName) {
75+
const eventSources = window.__EHTML_SERVER_EVENT_SOURCES__
76+
if (!eventSources || !eventSources[eventSourceName]) {
77+
throw new Error(`event source with name "${eventSourceName}" is not defined or not opened yet`)
78+
}
79+
80+
const eventSource = eventSources[eventSourceName]
81+
82+
eventSource.addEventListener('message', event => {
83+
const response = JSON.parse(event.data)
84+
mapToTemplate(this, response)
85+
})
86+
87+
return
88+
}
89+
7090
// ---------------------------------------------------------
7191
// PROGRESS START
7292
// ---------------------------------------------------------

src/E/e-json.js

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export default class EJson extends HTMLElement {
3131
return this.runSocketMode()
3232
}
3333

34+
const eventSourceName = this.getAttribute('data-event-source')
35+
if (eventSourceName) {
36+
return this.runEventSourceMode()
37+
}
38+
3439
const cacheAttr = this.getAttribute('data-cache-from')
3540
if (cacheAttr) {
3641
const cached = this.tryCache()
@@ -44,10 +49,6 @@ export default class EJson extends HTMLElement {
4449

4550
runSocketMode() {
4651
const state = getNodeScopedState(this)
47-
const ajaxIcon = this.resolveIcon()
48-
if (ajaxIcon) {
49-
ajaxIcon.style.display = ''
50-
}
5152

5253
const socketName = this.getAttribute('data-socket')
5354

@@ -72,6 +73,32 @@ export default class EJson extends HTMLElement {
7273
unwrappedChildrenOfParent(this)
7374
}
7475

76+
runEventSourceMode() {
77+
const state = getNodeScopedState(this)
78+
79+
const eventSourceName = this.getAttribute('data-event-source')
80+
81+
const eventSources = window.__EHTML_SERVER_EVENT_SOURCES__
82+
if (!eventSources || !eventSources[eventSourceName]) {
83+
throw new Error(`eventSource "${eventSourceName}" is not defined or not opened yet`)
84+
}
85+
86+
const eventSource = eventSources[eventSourceName]
87+
88+
eventSource.addEventListener('message', event => {
89+
const response = JSON.parse(event.data)
90+
evaluateActionsOnResponse(
91+
this.getAttribute('data-actions-on-response'),
92+
this.getAttribute('data-response-name'),
93+
response,
94+
this,
95+
state
96+
)
97+
})
98+
99+
unwrappedChildrenOfParent(this)
100+
}
101+
75102
tryCache() {
76103
const state = getNodeScopedState(this)
77104
const cacheAttr = this.getAttribute('data-cache-from')

src/E/e-sse-template.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import getNodeScopedState from '#ehtml/getNodeScopedState.js'
2+
import evaluatedStringWithParamsFromState from '#ehtml/evaluatedStringWithParamsFromState.js'
3+
import evaluateActionsOnProgress from '#ehtml/evaluateActionsOnProgress.js'
4+
import evaluateActionsOnOpenConnection from '#ehtml/evaluateActionsOnOpenConnection.js'
5+
import evaluateActionsOnCloseConnection from '#ehtml/evaluateActionsOnCloseConnection.js'
6+
7+
export default class ESse extends HTMLTemplateElement {
8+
constructor() {
9+
super()
10+
this.ehtmlActivated = false
11+
this.eventSourceName = null
12+
}
13+
14+
connectedCallback() {
15+
this.addEventListener(
16+
'ehtml:activated',
17+
this.onEHTMLActivated,
18+
{ once: true }
19+
)
20+
}
21+
22+
disconnectedCallback() {
23+
if (this.eventSourceName) {
24+
window.__EHTML_SERVER_EVENT_SOURCES__[eventSourceName].close()
25+
}
26+
}
27+
28+
onEHTMLActivated() {
29+
if (this.ehtmlActivated) {
30+
return
31+
}
32+
this.ehtmlActivated = true
33+
this.run()
34+
}
35+
36+
run() {
37+
const state = getNodeScopedState(this)
38+
39+
if (!this.hasAttribute('data-src')) {
40+
throw new Error('e-sse must have "data-src" attribute')
41+
}
42+
43+
if (!this.hasAttribute('data-source-name')) {
44+
throw new Error('e-ws must have "data-event-source-name" attribute')
45+
}
46+
47+
const eventSourceUrl = evaluatedStringWithParamsFromState(
48+
this.getAttribute('data-event-source-name'),
49+
state,
50+
this
51+
)
52+
53+
const eventSourceName = evaluatedStringWithParamsFromState(
54+
this.getAttribute('data-event-source-name'),
55+
state,
56+
this
57+
)
58+
59+
this.eventSourceName = eventSourceName
60+
61+
const connectionIconSelector = this.getAttribute('data-connection-icon')
62+
const connectionIcon = connectionIconSelector
63+
? document.querySelector(connectionIconSelector)
64+
: null
65+
66+
if (connectionIcon) {
67+
connectionIcon.style.display = ''
68+
}
69+
70+
const eventSource = new EventSource(eventSourceUrl)
71+
72+
// global EHTML storage
73+
window.__EHTML_SERVER_EVENT_SOURCES__ =
74+
window.__EHTML_SERVER_EVENT_SOURCES__ || {}
75+
76+
window.__EHTML_SERVER_EVENT_SOURCES__[eventSourceName] = eventSource
77+
78+
eventSource.addEventListener('open', event => {
79+
if (connectionIcon) {
80+
connectionIcon.style.display = 'none'
81+
}
82+
83+
if (this.hasAttribute('data-actions-on-open-connection')) {
84+
evaluateActionsOnOpenConnection(
85+
this.getAttribute('data-actions-on-open-connection'),
86+
event,
87+
this,
88+
state
89+
)
90+
}
91+
92+
// Replace <template is="e-ws"> with its content
93+
this.parentNode.replaceChild(
94+
this.content.cloneNode(true),
95+
this
96+
)
97+
})
98+
}
99+
}
100+
101+
customElements.define('e-ws', EWs, { extends: 'template' })

src/E/e-ws-template.js

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,8 @@ export default class EWs extends HTMLTemplateElement {
1313
connectedCallback() {
1414
this.addEventListener(
1515
'ehtml:activated',
16-
this.onEHTMLActivated
17-
)
18-
}
19-
20-
disconnectedCallback() {
21-
this.removeEventListener(
22-
'ehtml:activated',
23-
this.onEHTMLActivated
16+
this.onEHTMLActivated,
17+
{ once: true }
2418
)
2519
}
2620

@@ -39,6 +33,10 @@ export default class EWs extends HTMLTemplateElement {
3933
throw new Error('e-ws must have "data-src" attribute')
4034
}
4135

36+
if (!this.hasAttribute('data-socket-name')) {
37+
throw new Error('e-ws must have "data-socket-name" attribute')
38+
}
39+
4240
const socketUrl = evaluatedStringWithParamsFromState(
4341
this.getAttribute('data-src'),
4442
state,
@@ -51,10 +49,6 @@ export default class EWs extends HTMLTemplateElement {
5149
this
5250
)
5351

54-
if (!socketName) {
55-
throw new Error('e-ws must have "data-socket-name" attribute')
56-
}
57-
5852
const connectionIconSelector = this.getAttribute('data-connection-icon')
5953
const connectionIcon = connectionIconSelector
6054
? document.querySelector(connectionIconSelector)
@@ -72,14 +66,6 @@ export default class EWs extends HTMLTemplateElement {
7266

7367
window.__EHTML_WEB_SOCKETS__[socketName] = socket
7468

75-
if (this.hasAttribute('data-actions-on-progress-start')) {
76-
evaluateActionsOnProgress(
77-
this.getAttribute('data-actions-on-progress-start'),
78-
this,
79-
state
80-
)
81-
}
82-
8369
socket.addEventListener('open', event => {
8470
if (connectionIcon) {
8571
connectionIcon.style.display = 'none'
@@ -99,14 +85,6 @@ export default class EWs extends HTMLTemplateElement {
9985
this.content.cloneNode(true),
10086
this
10187
)
102-
103-
if (this.hasAttribute('data-actions-on-progress-end')) {
104-
evaluateActionsOnProgress(
105-
this.getAttribute('data-actions-on-progress-end'),
106-
this,
107-
state
108-
)
109-
}
11088
})
11189

11290
socket.addEventListener('close', event => {

src/actions/mapToTemplate.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function mapToTemplate(elmSelectorOrElm, obj) {
1010
}
1111

1212
if (!isTemplate(templateElm)) {
13-
throw new Error(`mapToTemplate(): target must be <template>.`)
13+
throw new Error('mapToTemplate(): target must be <template>.')
1414
}
1515

1616
// Allow:
@@ -24,13 +24,13 @@ export default function mapToTemplate(elmSelectorOrElm, obj) {
2424

2525
if (!templateIsNativeOrReusable) {
2626
throw new Error(
27-
`mapToTemplate() works only on native <template>, <template is="e-reusable"> or <template is="e-json-map">.`
27+
'mapToTemplate() works only on native <template>, <template is="e-reusable"> or <template is="e-json-map">.'
2828
)
2929
}
3030

3131
const objName = templateElm.getAttribute('data-object-name')
3232
if (!objName && obj) {
33-
throw new Error(`Mapping template must have data-object-name="…".`)
33+
throw new Error('Mapping template must have data-object-name="…".')
3434
}
3535

3636
const statePatch = {}

src/actions/releaseTemplate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function releaseTemplate(elmSelectorOrElm) {
2020

2121
if (!templateIsNativeOrReusable) {
2222
throw new Error(
23-
`releaseTemplate() works only on native <template> or <template is="e-reusable">.`
23+
'releaseTemplate() works only on native <template> or <template is="e-reusable">.'
2424
)
2525
}
2626

src/evaluateActionsOnCloseConnection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function evaluateActionsOnCloseConnection(string, e, node, state) {
22
// Create a function using the Function constructor
3-
// eslint-disable-next-line no-new-func
3+
44
const func = new Function(
55
'event',
66
'state',

src/evaluateActionsOnOpenConnection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default function evaluateActionsOnOpenConnection(string, e, node, state) {
22
// Create a function using the Function constructor
3-
// eslint-disable-next-line no-new-func
3+
44
const func = new Function(
55
'event',
66
'state',
@@ -30,6 +30,6 @@ export default function evaluateActionsOnOpenConnection(string, e, node, state)
3030
In short: “mutation first → activation second → actions last.”
3131
──────────────────────────────────────────────────────────────────────────────*/
3232
queueMicrotask(() => {
33-
func(node, [e, state])
33+
func(node, [e, state])
3434
})
3535
}

0 commit comments

Comments
 (0)