/
radio.js
232 lines (171 loc) · 6.61 KB
/
radio.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*-
Passive updates
===============
This package contains functionality to passively receive updates from the server.
@see [up-hungry]
@see [up-poll]
@module up.radio
*/
up.radio = (function() {
const u = up.util
/*-
Configures defaults for passive updates.
@property up.radio.config
@param {Array<string>} [config.hungrySelectors]
An array of CSS selectors that is replaced whenever a matching element is found in a response.
These elements are replaced even when they were not targeted directly.
By default this contains the [`[up-hungry]`](/up-hungry) attribute.
@param {number} [config.pollInterval=30000]
The default [polling](/up-poll] interval in milliseconds.
@param {boolean|string|Function(Element)} [config.pollEnabled=true]
Whether Unpoly will follow instructions to poll fragments, like the `[up-poll]` attribute.
When set to `'auto'` Unpoly will skip polling updates while one of the following applies:
- The browser tab is in the foreground
- The fragment's layer is the [frontmost layer](/up.layer.front).
- We should not [avoid optional requests](/up.network.shouldReduceRequests)
When set to `true`, Unpoly will always allow polling.
When set to `false`, Unpoly will never allow polling.
You may also pass a function that accepts the polling fragment and returns `true`, `false` or `'auto'`.
When an update is skipped due to polling being disabled,
Unpoly will try to poll again after the configured interval.
@stable
*/
const config = new up.Config(() => ({
hungrySelectors: ['[up-hungry]'],
pollInterval: 30000,
pollEnabled: 'auto'
}))
function reset() {
config.reset()
}
/*-
@function up.radio.hungrySelector
@internal
*/
function hungrySelector() {
return config.hungrySelectors.join(',')
}
/*-
Elements with an `[up-hungry]` attribute are updated whenever the server
sends a matching element, even if the element isn't targeted.
Use cases for this are unread message counters or notification flashes.
Such elements often live in the layout, outside of the content area that is
being replaced.
@selector [up-hungry]
@param [up-transition]
The transition to use when this element is updated.
@stable
*/
/*-
Starts [polling](/up-poll) the given element.
The given element does not need an `[up-poll]` attribute.
@function up.radio.startPolling
@param {Element} fragment
The fragment to reload periodically.
@param {number} options.interval
The reload interval in milliseconds.
Defaults to `up.radio.config.pollInterval`.
@param {string} options.url
Defaults to the element's closest `[up-source]` attribute.
@stable
*/
function startPolling(fragment, options = {}) {
up.FragmentPolling.forFragment(fragment).forceStart(options)
}
/*-
Stops [polling](/up-poll) the given element.
@function up.radio.stopPolling
@param {Element} fragment
The fragment to stop reloading.
@stable
*/
function stopPolling(element) {
up.FragmentPolling.forFragment(element).forceStop()
}
function shouldPoll(fragment) {
const setting = u.evalOption(config.pollEnabled, fragment)
if (setting === 'auto') {
return !document.hidden && !up.network.shouldReduceRequests() && up.layer.get(fragment)?.isFront?.()
}
return setting
}
/*-
Elements with an `[up-poll]` attribute are [reloaded](/up.reload) from the server periodically.
### Example
Assume an application layout with an unread message counter.
You can use `[up-poll]` to refresh the counter every 30 seconds:
```html
<div class="unread-count" up-poll>
2 new messages
</div>
```
### Controlling the reload interval
You may set an optional `[up-interval]` attribute to set the reload interval in milliseconds:
```html
<div class="unread-count" up-poll up-interval="10000">
2 new messages
</div>
```
If the value is omitted, a global default is used. You may configure the default like this:
```js
up.radio.config.pollInterval = 10000
```
### Controlling the source URL
The element will be reloaded from the URL from which it was originally loaded.
To reload from another URL, set an `[up-source]` attribute on the polling element:
```html
<div class="unread-count" up-poll up-source="/unread-count">
2 new messages
</div>
```
### Skipping updates on the client
Client-side code may skip an update by preventing an `up:fragment:poll` event
on the polling fragment.
Unpoly will also choose to skip updates under certain conditions,
e.g. when the browser tab is in the background. See `up.radio.config.pollEnabled` for details.
When an update is skipped, Unpoly will try to poll again after the configured interval.
### Skipping updates on the server
When polling a fragment periodically we want to avoid rendering unchanged content.
This saves <b>CPU time</b> and reduces the <b>bandwidth cost</b> for a
request/response exchange to **~1 KB**.
To achieve this we timestamp your fragments with an `[up-time]` attribute to indicate
when the underlying data was last changed. See `[up-time]` for a detailed example.
If the server has no more recent changes, it may skip the update by responding
with an HTTP status `304 Not Modified`.
When an update is skipped, Unpoly will try to poll again after the configured interval.
### Stopping polling
- The fragment from the server response no longer has an `[up-poll]` attribute.
- Client-side code has called `up.radio.stopPolling()` with the polling element.
- Polling was [disabled globally](/up.radio.config#config.pollEnabled).
@selector [up-poll]
@param [up-interval]
The reload interval in milliseconds.
Defaults to `up.radio.config.pollInterval`.
@param [up-source]
The URL from which to reload the fragment.
Defaults to the closest `[up-source]` attribute of an ancestor element.
@stable
*/
up.compiler('[up-poll]', (fragment) => {
up.FragmentPolling.forFragment(fragment).onPollAttributeObserved()
})
/*-
This event is emitted before a [polling](/up-poll) fragment is reloaded from the server.
Listener may prevent the `up:fragment:poll` event to prevent the fragment from being reloaded.
Preventing the event will only skip a single update. It will *not* stop future polling.
@event up:fragment:poll
@param {Element} event.target
The polling fragment.
@param event.preventDefault()
Event listeners may call this method to prevent the fragment from being reloaded.
@experimental
*/
up.on('up:framework:reset', reset)
return {
config,
hungrySelector,
startPolling,
stopPolling,
shouldPoll,
}
})()