-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathx.html
375 lines (350 loc) · 58.3 KB
/
x.html
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
<!DOCTYPE html>
<html lang="en"><head>
<link rel="icon" href="/logo.svg">
<title>VanX - The 1.2kB Official VanJS Extension</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/poppins/v20/pxiEyp8kv8JHgFVrJJfedw.ttf) format('truetype');
}
</style>
<link rel="stylesheet" href="/code/w3-v1.css">
<link rel="stylesheet" href="/code/prism-v1.css">
<link rel="stylesheet" href="/vanjs.css">
</head>
<body class="line-numbers" data-prismjs-copy="📋">
<!-- Gurubase Widget -->
<script async src="https://widget.gurubase.io/widget.latest.min.js" id="guru-widget-id" data-widget-id="f0WMAZ_-X7VX2FTx6oGbm_FvuhgEyJTZKjTrEqCDlQ0" data-text="Ask AI" data-bg-color="rgba(244, 67, 54, 0.3)" data-light-mode="true">
</script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Q0NB75RY7E"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-Q0NB75RY7E');
</script>
<script type="text/javascript" src="/code/prism-v1.js" defer></script><script type="text/javascript" src="/code/van-1.5.3.nomodule.min.js" defer></script><script type="text/javascript" src="/code/van-x-0.6.2.nomodule.min.js" defer></script><script type="text/javascript" src="x.js" defer></script>
<!-- Sidebar/menu -->
<nav class="w3-sidebar w3-red w3-collapse w3-top w3-large w3-padding" style="z-index:3;width:280px;font-weight:bold;" id="mySidebar"><br>
<a href="javascript:void(0)" onclick="w3_close()" class="w3-button w3-hide-large w3-display-topleft" style="width:100%;font-size:22px">Close Menu</a>
<div class="w3-container">
<h1 class="w3-padding-16 w3-xxxlarge">
<img src="/logo.svg" alt="VanJS" width="192px" height="192px">
</h1>
</div>
<div id="nav" class="w3-bar-block"><a href="/" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Home</a><a href="/start" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Getting Started</a><a href="/tutorial" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Tutorial</a><a href="/demo" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">VanJS by Example</a><a href="/convert" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">HTML/MD to VanJS</a><a href="/vanui" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">VanUI</a><a href="/minivan" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Mini-Van</a><a href="/ssr" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">SSR & Hydration</a><a href="/x" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white current">X</a><a href="/advanced" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Advanced Topics</a><a href="/media" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">Media Coverage</a><a href="/about" onclick="w3_close()" class="w3-bar-item w3-button w3-hover-white">About</a></div>
</nav>
<!-- Top menu on small screens -->
<header class="w3-container w3-top w3-hide-large w3-red w3-xlarge w3-padding">
<a href="javascript:void(0)" class="w3-button w3-red w3-margin-right" onclick="w3_open()">☰</a>
<span id="title-bar">X</span>
</header>
<!-- Overlay effect when opening sidebar on small screens -->
<div class="w3-overlay w3-hide-large" onclick="w3_close()" style="cursor:pointer" title="close side menu" id="myOverlay"></div>
<!-- !PAGE CONTENT! -->
<div class="w3-main" style="margin-left:300px;">
<div id="page">
<div id="content"><h1 class="w3-xxlarge"><b>VanX</b>: The 1.2kB Official VanJS Extension</h1><div>📣 <a href="https://github.com/vanjs-org/van/discussions/292" class="w3-hover-opacity"><b>VanX</b> 0.4.0 brings lots of new features →</a></div><p><b>VanX</b> is the official extension of <b>VanJS</b>, which provides handy utility functions. <b>VanX</b> makes <b>VanJS</b> more ergonomic for certain use cases and its developer experience closer to other popular UI frameworks. Like <b>VanJS</b>, <b>VanX</b> is also ultra-lightweight, with just 1.2kB in the gzipped minified bundle.</p><h2 class="w3-xxlarge w3-text-red" id="installation"><a class="self-link" href="#installation">Installation</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p><b>VanX</b> is published as NPM package <a href="https://www.npmjs.com/package/vanjs-ext" class="w3-hover-opacity">vanjs-ext</a>. Run the following command to install the package:</p><pre><code class="language-shell">npm install vanjs-ext</code></pre><p>Add this line to your script to import the package:</p><pre><code class="language-js">import * as vanX from "vanjs-ext"</code></pre><p>You can also import individual utility functions you're going to use:</p><pre><code class="language-js">import { <functions you want to use> } from "vanjs-ext"</code></pre><p>Alternatively, you can import <b>VanX</b> from CDN via a <code class="language-js"><script type="text/javascript"></code> tag:</p><pre><code class="language-html"><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/van-x.nomodule.min.js"></script></code></pre><p><code class="symbol">https://cdn.jsdelivr.net/npm/[email protected]/dist/van-x.nomodule.js</code> can be used for the non-minified version.</p><p>Note that: <b>VanJS</b> needs to be <a href="/start" class="w3-hover-opacity">imported</a> via a <code class="language-js"><script type="text/javascript"></code> tag for <b>VanX</b> to work properly.</p><p>To get TypeScript support for <code class="language-html"><script></code> tag integration, download <code class="symbol"><a href="/code/van-x-0.6.2.d.ts" download="van-x-0.6.2.d.ts" style="white-space: nowrap;" title="Download van-x-0.6.2.d.ts">van-x-0.6.2.d.ts</a></code> and add the following code at the top of your <code class="symbol">.ts</code> file:</p><pre><code class="language-ts">import type * as vanXType from "./van-x-0.6.2.d.ts"
declare const vanX: typeof vanXType
</code></pre><h2 class="w3-xxlarge w3-text-red" id="reactive-object"><a class="self-link" href="#reactive-object"><code class="symbol">vanX.reactive</code>: Reactive Object to Hold Many Individual States</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p><code class="symbol">vanX.reactive</code> provides an ergonomic way to define a single reactive object where each of its individual fields corresponds to an underlying <code class="symbol">State</code> object. For instance:</p><pre><code class="language-js">const obj = vanX.reactive({a: 1, b: 2})</code></pre><p>defines a reactive object with the following underlying state fields:</p><pre><code class="language-js">{a: van.state(1), b: van.state(2)}</code></pre><p>The reactive objects defined by <code class="symbol">vanX.reactive</code> can be deeply nested. For instance:</p><pre><code class="language-js">const obj = vanX.reactive({
a: 1,
b: {
c: 2,
d: 3,
},
})</code></pre><p>defines a reactive object with the following underlying state fields:</p><pre><code class="language-js">{
a: van.state(1),
b: van.state({
c: van.state(2),
d: van.state(3),
}),
}</code></pre><p>Getting and setting values of the underlying states can be simply done by getting / setting the fields of the reactive object. For instance, <code class="language-js">obj.b.c</code> is equivalent to what you would have to write <code class="language-js">obj.b.val.c.val</code> had the underlying state object been accessed.</p><h3 class="w3-large w3-text-red" id="a-practical-example"><a class="self-link" href="#a-practical-example">A practical example</a></h3><p>Now, let's take a look at a practice example on how <code class="symbol">vanX.reactive</code> can help group multiple states into a single reactive object in your application:</p><pre><code class="language-js">const Name = () => {
const data = vanX.reactive({name: {first: "Tao", last: "Xin"}})
return span(
"First name: ",
input({type: "text", value: () => data.name.first,
oninput: e => data.name.first = e.target.value}), " ",
"Last name: ",
input({type: "text", value: () => data.name.last,
oninput: e => data.name.last = e.target.value}), " ",
"Full name: ", () => `${data.name.first} ${data.name.last}`, " ",
button({onclick: () => data.name = {first: "Tao", last: "Xin"}}, "Reset"),
)
}
</code></pre><p><b>Demo:</b> <span id="demo-name"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/name">Try on jsfiddle</a></p><p>Note that, not only you can set the value of each individual leaf field, you can also set the entire object of the <code class="symbol">name</code> field, as what's being done in the <code class="symbol">onclick</code> handler of the <code class="symbol">Reset</code> button:</p><pre><code class="language-js">button({onclick: () => data.name = {first: "Tao", last: "Xin"}}, "Reset")</code></pre><p id="caveat-access-sub-field">⚠️ <b>Caveat</b>: Accessing to any sub-field of the reactive object needs to be wrapped inside a binding function. Otherwise, your app won't be reactive to the sub-field changes.</p><p id="caveat-no-aliasing">⚠️ <b>Caveat</b>: DO NOT alias any sub-field of the reactive object into other variables. Doing so will break the dependency detection when the sub-field alias is used in a binding function.</p><h4 class="w3-medium w3-text-red" id="api-reactive"><a class="self-link" href="#api-reactive">API reference: <code class="symbol">vanX.reactive</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.reactive(obj) => <the created reactive object></code></td></tr><tr><td><b>Description</b></td><td>Converts the input object <code class="symbol">obj</code> into a reactive object.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">obj</code></b> - Can be a plain object or an object of an existing JavaScript class. <code class="symbol">obj</code> can have deeply nested fields. The original <code class="symbol">obj</code> shouldn't be accessed anymore after the <code class="language-js">vanX.reactive(obj)</code> call.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The created reactive object.</td></tr></tbody></table><p>⚠️ <b>Caveat</b>: The passed-in <code class="symbol">obj</code> object shouldn't have any <code class="symbol">State</code> fields. Doing so will result in states of other <code class="symbol">State</code> objects, which is <a href="/tutorial#public-interface-of-state-objects" class="w3-hover-opacity">invalid</a> in <b>VanJS</b>.</p><h3 class="w3-large w3-text-red" id="calculated-fields"><a class="self-link" href="#calculated-fields">Calculated fields</a></h3><p>You can specify calculated fields (similar to <a href="/tutorial#derived-state" class="w3-hover-opacity">derived states</a> in <b>VanJS</b>) via <code class="symbol">vanX.calc</code>. The example above can be rewritten to the code below:</p><pre><code class="language-js">const Name = () => {
const data = vanX.reactive({name: {first: "Tao", last: "Xin"}})
const derived = vanX.reactive({fullName: vanX.calc(() => `${data.name.first} ${data.name.last}`)})
return span(
"First name: ",
input({type: "text", value: () => data.name.first,
oninput: e => data.name.first = e.target.value}), " ",
"Last name: ",
input({type: "text", value: () => data.name.last,
oninput: e => data.name.last = e.target.value}), " ",
"Full name: ", () => derived.fullName, " ",
button({onclick: () => data.name = {first: "Tao", last: "Xin"}}, "Reset"),
)
}
</code></pre><p><b>Demo:</b> <span id="demo-name-calc"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/name-calc">Try on jsfiddle</a></p><p id="caveat-no-self-ref">⚠️ <b>Caveat</b>: Avoid self-referencing when specify calculated fields. For instance, the code below:</p><pre><code class="language-js">const data = vanX.reactive({
name: {first: "Tao", last: "Xin"},
fullName: vanX.calc(() => `${data.name.first} ${data.name.last}`),
})</code></pre><p>will lead to <code class="symbol">ReferenceError</code> as <code class="symbol">data</code> variable is not yet defined when the calculation function is being executed. As shown in the <a href="#calculated-fields" class="w3-hover-opacity">example</a> above, it's recommended to define calculated fields in a separate reactive object.</p><h4 class="w3-medium w3-text-red" id="api-calc"><a class="self-link" href="#api-calc">API reference: <code class="symbol">vanX.calc</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.calc(f) => <the created calculated field></code></td></tr><tr><td><b>Description</b></td><td>Creates a calculated field for a reactive object based on the calculation function<code class="symbol">f</code>.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">f</code></b> - The calculation function.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The created calculated field.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="get-the-underlying-state-object"><a class="self-link" href="#get-the-underlying-state-object">Get the underlying <code class="symbol">State</code> object</a></h3><p>Sometimes, it's desirable to get the underlying <code class="symbol">State</code> objects for fields in a reactive object. This can be achieved with <code class="symbol">vanX.stateFields</code>. The example above can be modified to use the underlying state field instead of the binding function for <code class="symbol">Full name</code>:</p><pre><code class="language-js">const Name = () => {
const data = vanX.reactive({name: {first: "Tao", last: "Xin"}})
data.fullName = vanX.calc(() => `${data.name.first} ${data.name.last}`)
return div(
"First name: ",
input({type: "text", value: () => data.name.first,
oninput: e => data.name.first = e.target.value}), " ",
"Last name: ",
input({type: "text", value: () => data.name.last,
oninput: e => data.name.last = e.target.value}), " ",
"Full name: ", vanX.stateFields(data).fullName, " ",
button({onclick: () => data.name = {first: "Tao", last: "Xin"}}, "Reset"),
)
}
</code></pre><p><b>Demo:</b></p><p id="demo-name-state-fields"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/name-state-fields">Try on jsfiddle</a></p><p>Note that, <code class="symbol">stateFields</code> only gets the underlying state fields for one layer of the reactive object. For instance, to get the state field for <code class="symbol">First name</code>, you need to write:</p><pre><code class="language-js">vanX.stateFields(vanX.stateFields(data).name.val).first</code></pre><h4 class="w3-medium w3-text-red" id="api-stateFields"><a class="self-link" href="#api-stateFields">API reference: <code class="symbol">vanX.stateFields</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.stateFields(obj) => <an object for all underlying state fields of obj></code></td></tr><tr><td><b>Description</b></td><td>Given a reactive object <code class="symbol">obj</code>, returns an object for all the underlying state fields of <code class="symbol">obj</code>. For instance, if <code class="symbol">obj</code> is <code class="language-js">{a: 1, b: 2}</code>, <code class="language-js">{a: van.state(1), b: van.state(2)}</code> will be returned.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">obj</code></b> - The input reactive object.</li></ul></td></tr><tr><td><b>Returns</b></td><td>An object for all the underlying state fields of <code class="symbol">obj</code>.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="get-the-raw-field-value"><a class="self-link" href="#get-the-raw-field-value">Get the raw field value without registering the dependency</a></h3><p><i>Requires <b>VanX</b> 0.3.0 or later.</i></p><p>Similar to the <code class="symbol"><a href="/tutorial#api-rawVal" class="w3-hover-opacity">rawVal</a></code> property of <b>VanJS</b> states. You can use <code class="symbol">vanX.raw</code> for getting the raw field value without registering the dependency. For instance:</p><pre><code class="language-js">data.s = vanX.calc(() => vanX.raw(data).a + data.b)</code></pre><p>will make <code class="symbol">data.s</code> updated when <code class="symbol">data.b</code> changes, but <code class="symbol">data.s</code> won't be updated when <code class="symbol">data.a</code> changes. The same effect goes to derived states and side effects registered via <code class="symbol"><a href="/tutorial#api-derive" class="w3-hover-opacity">van.derive</a></code> as well as <code class="symbol">State</code>-derived DOM nodes.</p><p>Note that, <code class="symbol">vanX.raw</code> can access deeply nested fields without registering the dependency <i>(this requires <b>VanX</b> <a href="https://github.com/vanjs-org/van/discussions/292" class="w3-hover-opacity">0.4.0</a> or later)</i>. For instance, you can use <code class="language-js">vanX.raw(data).a.a</code> to access the field <code class="language-js">data.a.a</code> without registering the dependency.</p><h4 class="w3-medium w3-text-red" id="api-raw"><a class="self-link" href="#api-raw">API reference: <code class="symbol">vanX.raw</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.raw(obj) => <an object for getting the field values of obj without registering the dependency></code></td></tr><tr><td><b>Description</b></td><td>Given a reactive object <code class="symbol">obj</code>, returns an object whose field values equal to the field values of <code class="symbol">obj</code>, but accessing its fields won't register the dependency.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">obj</code></b> - The input reactive object.</li></ul></td></tr><tr><td><b>Returns</b></td><td>An object with which you can get the field values of <code class="symbol">obj</code> without registering the dependency.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="add-reactivity-to-existing-javascript-classes"><a class="self-link" href="#add-reactivity-to-existing-javascript-classes">Add reactivity to existing JavaScript classes</a></h3><p>It's possible to add reactivity to objects of existing JavaScript classes with the help of <code class="symbol">vanX.reactive</code>. For instance, the code below adds the reactivity to a <code class="symbol">Person</code> object:</p><pre><code class="language-js">class Person {
constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName }
get fullName() { return `${this.firstName} ${this.lastName}` }
}
const Name = () => {
const person = vanX.reactive(new Person("Tao", "Xin"))
return div(
"First name: ",
input({type: "text", value: () => person.firstName,
oninput: e => person.firstName = e.target.value}), " ",
"Last name: ",
input({type: "text", value: () => person.lastName,
oninput: e => person.lastName = e.target.value}), " ",
"Full name: ", () => person.fullName, " ",
button({onclick: () => (person.firstName = "Tao", person.lastName = "Xin")}, "Reset"),
)
}
</code></pre><p><b>Demo:</b></p><p id="demo-name-existing-class"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/name-existing-class">Try on jsfiddle</a></p><p id="caveat-no-reuse-reactive">⚠️ <b>Caveat</b>: Once an object is turned reactive with <code class="symbol">vanX.reactive</code>, you shouldn't access the original object anymore. Doing so will create the same issue as <a href="#caveat-no-aliasing" class="w3-hover-opacity">aliasing</a>.</p><p id="caveat-native-reactivity">⚠️ <b>Caveat</b>: There might be issues if you try to add reactivity to a class implemented in native code (not in JavaScript), or a class from a 3rd party library. Example: <a href="https://github.com/vanjs-org/van/issues/156" class="w3-hover-opacity">#156</a>.</p><h3 class="w3-large w3-text-red" id="vanx-noreactive-exemption-from-reactivity-conversion"><a class="self-link" href="#vanx-noreactive-exemption-from-reactivity-conversion"><code class="symbol">vanX.noreactive</code>: exemption from reactivity conversion</a></h3><p><i>Requires <b>VanX</b> <a href="https://github.com/vanjs-org/van/discussions/311" class="w3-hover-opacity">0.6.0</a> or later.</i></p><p>Sometimes it's desirable to exempt certain fields from being converted into reactive objects. For instance, for the reactive array below:</p><pre><code class="language-js">const data = vanX.reactive([
vanX.noreactive(new ArrayBuffer(8)),
vanX.noreactive(new ArrayBuffer(16)),
])</code></pre><p>we will treat the <code class="symbol">ArrayBuffer</code>s in <code class="symbol">data</code> as primitive fields instead of further converting them into reactive objects. This feature is essential as the objects of certain native or 3rd party classes <a href="#caveat-native-reactivity" class="w3-hover-opacity">can't be correctly converted into reactive objects</a>. <code class="symbol">ArrayBuffer</code> is one example as wrapping it around a <code class="symbol">Proxy</code> will cause problems.</p><p>Below is the whole example that illustrates how <code class="symbol">vanX.noreactive</code> helps a reactive array of <code class="symbol">ArrayBuffer</code> being used in the application:</p><pre><code class="language-js">const data = vanX.reactive([
vanX.noreactive(new ArrayBuffer(8)),
vanX.noreactive(new ArrayBuffer(16)),
])
const App = () => div(
vanX.list(div, data, v => div(v.val.byteLength)),
div(button({onclick: () => data.push(vanX.noreactive(new ArrayBuffer(24)))}, "Push")),
)
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/noreactive">Try on jsfiddle</a></p><h4 class="w3-medium w3-text-red" id="api-noreactive"><a class="self-link" href="#api-noreactive">API reference: <code class="symbol">vanX.noreactive</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.noreactive(obj) => <the object exempted from reactivity conversion></code></td></tr><tr><td><b>Description</b></td><td>Marks an object so that it won't be converted into a reactive object.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">obj</code></b> - The input object which you want to exempt from being converted into a reactive object.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The object exempted from reactivity conversion.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="a-comprehensive-example"><a class="self-link" href="#a-comprehensive-example">A comprehensive example</a></h3><p>You can refer to this <a href="https://github.com/vanjs-org/van/blob/main/x/examples/reactive/src/main.js" class="w3-hover-opacity">file</a> for a comprehensive demo of all the features regarding to reactive objects discussed above. You can preview the app via <a href="https://codesandbox.io/p/sandbox/github/vanjs-org/van/tree/main/x/examples/reactive?file=%2Fsrc%2Fmain.js%3A1%2C1" class="w3-hover-opacity">CodeSandbox</a>.</p><h2 class="w3-xxlarge w3-text-red" id="reactive-list"><a class="self-link" href="#reactive-list"><code class="symbol">vanX.list</code>: Reactive List that Minimizes Re-rendering on Updates</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p><code class="symbol">vanX.list</code> takes an input reactive object and builds a list of UI elements whose contents are updated whenever any field of the input reactive object changes. The input reactive object can either be an <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array" class="w3-hover-opacity">Array</a></code> for non-keyed input, or a plain object for keyed input.</p><p>Let's first take a look at some simple examples.</p><p><code class="symbol">Array</code> for non-keyed input:</p><pre><code class="language-js">const items = vanX.reactive([1, 2, 3])
return vanX.list(ul, items, v => li(v))
</code></pre><p>Plain object for keyed input:</p><pre><code class="language-js">const items = vanX.reactive({a: 1, b: 2, c: 3})
return vanX.list(ul, items, v => li(v))
</code></pre><p>In both examples, <code class="language-html"><ul><li>1</li><li>2</li><li>3</li></ul></code> will be returned.</p><p>You can add, update, and delete entries in the reactive object <code class="symbol">items</code>, and the rendered UI elements are bound to the changes while minimizing the re-rendering of the DOM tree. For instance, if you do the following changes to the <code class="symbol">Array</code> example:</p><pre><code class="language-js">++items[0]
delete items[1]
items.push(4)
</code></pre><p>the rendered UI elements will be updated to <code class="language-html"><ul><li>2</li><li>3</li><li>4</li></ul></code>.</p><p>For keyed object, the following changes will produce the same result:</p><pre><code class="language-js">++items.a
delete items.b
items.d = 4
</code></pre><p>In addition, for <code class="symbol">Array</code>-based input <code class="symbol">items</code>, you can call <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift" class="w3-hover-opacity">shift</a></code>, <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift" class="w3-hover-opacity">unshift</a></code> and <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice" class="w3-hover-opacity">splice</a></code> as you would normally do to an array. The rendered UI elements are guaranteed to be in sync. For instance, after executing the following code:</p><pre><code class="language-js">const items = vanX.reactive([1, 2, 3])
const dom = vanX.list(ul, items, v => li(v))
items.shift()
items.unshift(4)
items.splice(1, 1, 5)
</code></pre><p><code class="symbol">dom</code> will become <code class="language-html"><ul><li>4</li><li>5</li><li>3</li></ul></code>.</p><h4 class="w3-medium w3-text-red" id="api-list"><a class="self-link" href="#api-list">API Reference: <code class="symbol">vanX.list</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.list(container, items, itemFunc) => <the root element of the created DOM tree></code></td></tr><tr><td><b>Description</b></td><td>Creates a DOM tree for a list of UI elements based on the input reactive object <code class="symbol">items</code>.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">container</code></b> - Can be a <a href="/tutorial#api-tags" class="w3-hover-opacity">tag function</a> or a DOM element for the container element of the list of UI elements.<ul><li>If <code class="symbol">container</code> is a tag function, such as <code class="language-js">van.tags.ul</code> it means we want to create a <code class="language-html"><ul></code> element as the container of the list. Indeed, any function that returns a DOM element can be passed as the <code class="symbol">container</code> argument.</li><li><i>(requires <b>VanX</b> <a href="https://github.com/vanjs-org/van/discussions/292" class="w3-hover-opacity">0.4.0</a> or later)</i> If <code class="symbol">container</code> is a DOM element, it will be used directly as the container of the list. Usually, this is useful to specify the container element with some customized property values, such as <code class="language-js">div({class: "item-list"})</code> for <code class="language-html"><div class="item-list"></code>.</li></ul></li><li><b><code class="symbol">items</code></b> - A reactive object that holds the data for the list. Can be an <code class="symbol">Array</code> (for non-keyed input) or a plain object (for keyed input).</li><li><b><code class="symbol">itemFunc</code></b> - The function (<code class="language-js">(v, deleter, k) => Node</code>) that is used to generate the UI element (or rarely, text node) for each list item. The function takes the following parameters:<ul><li><code class="symbol">v</code> - A <code class="symbol">State</code> object corresponding to each list item. You can directly use it as a <code class="symbol">State</code>-based <a href="/tutorial#state-typed-prop" class="w3-hover-opacity">property</a> / <a href="/tutorial#state-typed-child" class="w3-hover-opacity">child node</a>, read its value for building the UI element, and/or set its value in some event handlers.</li><li><code class="symbol">deleter</code> - a function (<code class="language-js">() => void</code>) that can be used in the event handler to delete the entire item. Typically the <code class="symbol">deleter</code> function can be used as the <code class="symbol">onclick</code> handler of a deletion button.</li><li><code class="symbol">k</code> - <i>(requires <b>VanX</b> 0.2.0 or later)</i> the key of the corresponding list item, which is the index if <code class="symbol">items</code> is an <code class="symbol">Array</code> or the property key if <code class="symbol">items</code> is a plain object.</li></ul></li></ul></td></tr><tr><td><b>Returns</b></td><td>The root element of the created DOM tree.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="a-simplified-todo-app"><a class="self-link" href="#a-simplified-todo-app">A simplified TODO App</a></h3><p>Now, let's take a look at a practical example: The <a href="/demo#todo-app" class="w3-hover-opacity">Fully Reactive TODO App</a> in <a href="/demo" class="w3-hover-opacity">VanJS by Example</a> page can be re-implemented with the help of <code class="symbol">vanX.list</code>. We can see how a 40+ lines of code is simplified to just over 10 lines:</p><pre><code class="language-js">const TodoList = () => {
const items = vanX.reactive(JSON.parse(localStorage.getItem("appState") ?? "[]"))
van.derive(() => localStorage.setItem("appState", JSON.stringify(vanX.compact(items))))
const inputDom = input({type: "text"})
return div(
inputDom, button({onclick: () => items.push({text: inputDom.value, done: false})}, "Add"),
vanX.list(div, items, ({val: v}, deleter) => div(
input({type: "checkbox", checked: () => v.done, onclick: e => v.done = e.target.checked}),
() => (v.done ? del : span)(v.text),
a({onclick: deleter}, "❌"),
)),
)
}
</code></pre><p><b>Demo:</b></p><p id="demo-todo-list"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/todo-list">Try on jsfiddle</a></p><p>You might notice how easy it is to serialize/deserialize a complex reactive object into/from external storage. This is indeed one notable benefit of reactive objects provided by <code class="symbol"><a href="#reactive-object" class="w3-hover-opacity">vanX.reactive</a></code>.</p><h3 class="w3-large w3-text-red" id="holes-in-the-array"><a class="self-link" href="#holes-in-the-array">Holes in the array</a></h3><p>Deleting items in the reactive array will create <a href="https://2ality.com/2015/09/holes-arrays-es6.html" class="w3-hover-opacity">holes</a> inside the array, which is an uncommon situation in JavaScript. Basically, if we execute the following code:</p><pre><code class="language-js">const a = [1, 2, 3]
delete a[1]
</code></pre><p><code class="symbol">a</code> will become <code class="symbol">[1, empty, 3]</code>. Note that, <code class="symbol">empty</code> is different from <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined" class="w3-hover-opacity">undefined</a></code>. When we do:</p><pre><code class="language-js">for (const key in a)</code></pre><p> or use higher-order functions like <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" class="w3-hover-opacity">map</a></code> or <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter" class="w3-hover-opacity">filter</a></code>, holes will be skipped in the enumeration.</p><p>Why do we allow holes in the array? Short answer: to minimize the re-rendering of DOM elements. Let's say if we have a reactive array: <code class="symbol">[1, 2, 3, 4, 5]</code>, and the 3rd item is deleted by the user. If we allow holes, the array will become <code class="symbol">[1, 2, empty, 4, 5]</code>. Based on how DOM elements are bound to the reactive array, only the 3rd element needs to be removed. However, if we don't allow holes, the array will become <code class="symbol">[1, 2, 4, 5]</code>, then we need 3 DOM updates:</p><ol><li>3rd DOM element: <code class="symbol">3</code> -> <code class="symbol">4</code></li><li>4th DOM element: <code class="symbol">4</code> -> <code class="symbol">5</code></li><li>Remove the 5th DOM element.</li></ol><p>In the TODO app above, we are calling <code class="symbol"><a href="#serialization-and-compact" class="w3-hover-opacity">vanX.compact</a></code> which recursively removes holes in all arrays of the input reactive object before serializing <code class="symbol">items</code> to the JSON string via <code class="language-js">JSON.stringify</code>. This is because holes are turned into <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null" class="w3-hover-opacity">null</a></code> values in the result JSON string and cause problems when the JSON string is deserialized (See a <a href="https://github.com/vanjs-org/van/discussions/144#discussioncomment-7342023" class="w3-hover-opacity">detailed explanation here</a>).</p><p id="caveat-array-holes">⚠️ <b>Caveat</b>: Because of holes in the reactive array, the <code class="symbol">length</code> property can't reliable tell the number of items in the array. You can use <code class="language-js">Object.keys(items).length</code> instead as in the <a href="#example-1-sortable-list" class="w3-hover-opacity">example below</a>.</p><h2 class="w3-xxlarge w3-text-red" id="vanx-replace-update-insert-delete-and-reorder-items-in-batch"><a class="self-link" href="#vanx-replace-update-insert-delete-and-reorder-items-in-batch"><code class="symbol">vanX.replace</code>: Update, Insert, Delete and Reorder Items in Batch</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>In addition to updating the <code class="symbol">items</code> object one item at a time, we also provide the <code class="symbol">vanX.replace</code> function that allows you to update, insert, delete and reorder items in batch. The <code class="symbol">vanX.replace</code> function takes a reactive object - <code class="symbol">obj</code>, and a replacement object (or a replacement function) - <code class="symbol">replacement</code>, as its input parameters. <code class="symbol">vanX.replace</code> is responsible for updating the <code class="symbol">obj</code> object as well as UI elements bound to it based on the new data provided by <code class="symbol">replacement</code>. Let's take a look at a few examples:</p><pre><code class="language-js">// Assume we have a few TODO items as following:
const todoItems = vanX.reactive([
{text: "Implement VanX", done: true},
{text: "Test VanX", done: false},
{text: "Write a tutorial for VanX", done: false},
])
// Directly specify the replacement object
const refreshItems = () => vanX.replace(todoItems, [
{text: "Publishing VanX", done: true},
{text: "Refining VanX", done: false},
{text: "Releasing a new version of VanX", done: false},
])
// To delete items in batch
const clearCompleted = () => vanX.replace(todoItems, l => l.filter(v => !v.done))
// To update items in batch
const appendText = () =>
vanX.replace(todoItems, l => l.map(v => ({text: v.text + "!", done: v.done})))
// To reorder items in batch
const sortItems = () =>
vanX.replace(todoItems, l => l.toSorted((a, b) => a.localeCompare(b)))
// To insert items in batch
const duplicateItems = () => vanX.replace(todoItems,
l => l.flatMap(v => [v, {text: v.text + " copy", done: v.done}]))
</code></pre><h4 class="w3-medium w3-text-red" id="api-replace"><a class="self-link" href="#api-replace">API reference: <code class="symbol">vanX.replace</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.replace(obj, replacement) => obj</code></td></tr><tr><td><b>Description</b></td><td>Updates the reactive object <code class="symbol">obj</code> and UI elements bound to it based on the data provided by <code class="symbol">replacement</code>.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">obj</code></b> - The reactive object that you want to update.</li><li><b><code class="symbol">replacement</code></b> - Can be a plain array / object, or a function.<ul><li><i>(requires <b>VanX</b> <a href="https://github.com/vanjs-org/van/discussions/292" class="w3-hover-opacity">0.4.0</a> or later)</i> If <code class="symbol">replacement</code> is a plain array / object, directly update <code class="symbol">obj</code> with the values provided in <code class="symbol">replacement</code>.</li><li>If <code class="symbol">replacement</code> is a function, it will take the current values of <code class="symbol">obj</code> as input and returns the new values of the update. The input parameter of the function depends on the type of <code class="symbol">obj</code>. If <code class="symbol">obj</code> is an array (for non-keyed data), <code class="symbol">replacement</code> will take its values as an array (after eliminating <a href="#holes-in-the-array" class="w3-hover-opacity">holes</a>) and return the updated values as another array. If <code class="symbol">obj</code> is a plain object (for keyed data), <code class="symbol">replacement</code> will take its values as an array of key value pairs (the data you would get with <code class="language-js">Object.entries(items)</code>) and return the updated values as another array of key value pairs.</li></ul></li></ul></td></tr><tr><td><b>Returns</b></td><td><code class="language-ts">obj</code></td></tr></tbody></table><p id="caveat-no-calc-fields-in-replace">⚠️ <b>Caveat</b>: <a href="#calculated-fields" class="w3-hover-opacity">Calculated fields</a> are not allowed in <code class="symbol">obj</code> and <code class="symbol">replacement</code>.</p><h3 class="w3-large w3-text-red" id="example-1-sortable-list"><a class="self-link" href="#example-1-sortable-list">Example 1: sortable list</a></h3><p>Let's look at a sample app that we can build with <code class="symbol">vanX.list</code> and <code class="symbol">vanX.replace</code> - a list that you can add/delete items, sort items in ascending or descending order, and append a string to all items in the list:</p><pre><code class="language-js">const List = () => {
const items = vanX.reactive([])
const inputDom = input({type: "text"})
return div(
div(inputDom, button({onclick: () => items.push(inputDom.value)}, "Add")),
div(() => Object.keys(items).length, " item(s) in total"),
vanX.list(ul, items, (v, deleter) => li(v, " ", a({onclick: deleter}, "❌"))),
div(
button({onclick: () => vanX.replace(items, l => l.toSorted())}, "A -> Z"),
button({onclick: () => vanX.replace(items,
l => l.toSorted((a, b) => b.localeCompare(a)))}, "Z -> A"),
button({onclick: () => vanX.replace(items, l => l.map(v => v + "!"))}, 'Append "!"'),
),
)
}
</code></pre><p><b>Demo:</b></p><p id="demo-example-list1"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/example-list1">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="example-2-an-advanced-sortable-todo-list"><a class="self-link" href="#example-2-an-advanced-sortable-todo-list">Example 2: an advanced sortable TODO list</a></h3><p>Now, let's take a look at a more advanced example - a sortable TODO list, which is implemented with keyed data. i.e.: reactive <code class="symbol">items</code> is a plain object instead of an array. In additional to the addition, deletion, sorting and appending strings that are implemented in the previous example, you can edit an item, mark an item as complete, clear all completed items and duplicate the entire list. Furthermore, the application state is serialized and persisted into <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage" class="w3-hover-opacity">localStorage</a></code> thus the state is preserved across page loads.</p><pre><code class="language-js">const TodoList = () => {
const items = vanX.reactive(JSON.parse(localStorage.getItem("items") ?? "{}"))
van.derive(() => localStorage.setItem("items", JSON.stringify(vanX.compact(items))))
const inputDom = input({type: "text"})
let id = Math.max(0, ...Object.keys(items).map(v => Number(v.slice(1))))
return div(
div(inputDom, button(
{onclick: () => items["k" + ++id] = {text: inputDom.value, done: false}}, "Add")),
div(() => Object.keys(items).length, " item(s) in total"),
vanX.list(div, items, ({val: v}, deleter) => div(
input({type: "checkbox", checked: () => v.done,
onclick: e => v.done = e.target.checked}), " ",
input({
type: "text", value: () => v.text,
style: () => v.done ? "text-decoration: line-through;" : "",
oninput: e => v.text = e.target.value,
}), " ",
a({onclick: deleter}, "❌"),
)),
div(
button({onclick: () => vanX.replace(items, l => l.filter(([_, v]) => !v.done))},
"Clear Completed"),
button({onclick: () => vanX.replace(items, l =>
l.toSorted(([_1, a], [_2, b]) => a.text.localeCompare(b.text)))}, "A -> Z"),
button({onclick: () => vanX.replace(items, l =>
l.toSorted(([_1, a], [_2, b]) => b.text.localeCompare(a.text)))}, "Z -> A"),
button({onclick: () => vanX.replace(items, l =>
l.flatMap(([k1, v1]) => [
[k1, v1],
["k" + ++id, {text: v1.text + " - copy", done: v1.done}],
]))},
"Duplicate List"),
button({onclick: () => Object.values(items).forEach(v => v.text += "!")}, 'Append "!"'),
),
)
}
</code></pre><p><b>Demo:</b></p><p id="demo-example-list2"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/example-list2">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="vanx-list-for-calculated-fields"><a class="self-link" href="#vanx-list-for-calculated-fields"><code class="symbol">vanX.list</code> for calculated fields</a></h3><p><i>Requires <b>VanX</b> <a href="https://github.com/vanjs-org/van/discussions/292" class="w3-hover-opacity">0.4.0</a> or later.</i></p><p><code class="symbol"><a href="#reactive-list" class="w3-hover-opacity">vanX.list</a></code> can take a <a href="#calculated-fields" class="w3-hover-opacity">calculated field</a> as <code class="symbol">items</code> parameter. Whenever the calculated field is updated, <code class="symbol">vanX.replace</code> will be called internally to update the reactive list, as well as all UI elements bound to it. Below is an example which leverages this technique to build a filterable list:</p><pre><code class="language-js">const FilteredCountries = () => {
const countries = [
"Argentina", "Bolivia", "Brazil", "Chile", "Colombia", "Ecuador", "Guyana",
"Paraguay", "Peru", "Suriname", "Uruguay", "Venezuela",
]
const data = vanX.reactive({filter: ""})
const derived = vanX.reactive({
filteredCountries: vanX.calc(
() => countries.filter(c => c.toLowerCase().includes(data.filter.toLowerCase()))),
})
return div(
div("Countries in South America. Filter: ",
input({type: "text", value: () => data.filter, oninput: e => data.filter = e.target.value})),
vanX.list(ul, derived.filteredCountries, v => li(v)),
)
}
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/calc-list">Try on jsfiddle</a></p><h2 class="w3-xxlarge w3-text-red" id="global-app-state-and-serialization"><a class="self-link" href="#global-app-state-and-serialization">Global App State and Serialization</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p><i>Requires <b>VanX</b> <a href="https://github.com/vanjs-org/van/discussions/292" class="w3-hover-opacity">0.4.0</a> or later.</i></p><p>With <b>VanX</b>, it's possible consolidate the entire app state into a single reactive object, as reactive objects can hold states in arbitrary nested hierarchies. Below is the code for an upgraded version of the <a href="#a-simplified-todo-app" class="w3-hover-opacity">TODO App</a> above, which allows the text of the input box together with all TODO items to be persisted in <code class="symbol">localStorage</code>:</p><pre><code class="language-js">const TodoListPlus = () => {
const appState = vanX.reactive(JSON.parse(
localStorage.getItem("appStatePlus") ?? '{"input":"","items":[]}'))
van.derive(() => localStorage.setItem("appStatePlus", JSON.stringify(vanX.compact(appState))))
return div(
input({type: "text", value: () => appState.input, oninput: e => appState.input = e.target.value}),
button({onclick: () => appState.items.push({text: appState.input, done: false})}, "Add"),
vanX.list(div, appState.items, ({val: v}, deleter) => div(
input({type: "checkbox", checked: () => v.done, onclick: e => v.done = e.target.checked}),
() => (v.done ? del : span)(v.text),
a({onclick: deleter}, "❌"),
)),
)
}
</code></pre><p><b>Demo:</b></p><p id="demo-todo-list-plus"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/todo-list-plus">Try on jsfiddle</a></p><p>Note that <a href="#calculated-fields" class="w3-hover-opacity">calculated fields</a> are still recommended to be stored separately, to avoid issues like <a href="#caveat-no-self-ref" class="w3-hover-opacity">self referencing</a> or <a href="#caveat-no-calc-fields-in-replace" class="w3-hover-opacity">calculated fields being replaced</a>.</p><h3 class="w3-large w3-text-red" id="smart-diff-update-in-vanx-replace"><a class="self-link" href="#smart-diff-update-in-vanx-replace">Smart diff / update in <code class="symbol">vanX.replace</code></a></h3><p>When <code class="symbol">vanX.replace</code> updates the reactive object <code class="symbol">obj</code>, it will traverse the entire object tree, do a diff between <code class="symbol">replacement</code> and <code class="symbol">obj</code>, and only update leaf-level fields with different values. Thus, you can call <code class="symbol">vanX.replace</code> to replace the entire app state object, and <b>VanX</b> guarantees at the framework level that the minimum amount updates are applied to the reactive object and thus the DOM tree bound to it.</p><p>For instance, if <code class="symbol">appState</code> in the example above has the following value:</p><pre><code class="language-json">{
"input": "New Item",
"items": [
{"text": "Item 1", "done": true},
{"text": "Item 2", "done": false}
]
}
</code></pre><p>Calling</p><pre><code class="language-js">vanX.replace(appState, {
input: "New Item",
items: [
{text: "Item 1", done: true},
{text: "Item 2", done: true},
]
})
</code></pre><p>will only get the <code class="symbol">done</code> field of 2nd element in <code class="symbol">items</code> updated. i.e.: it's equivalent to <code class="language-js">appState.items[1].done = true</code>.</p><p>Because of the smart diff / update mechanism, it's usually more preferable to use <code class="symbol">vanX.replace</code> instead of direct assignment to update the object-valued reactive fields. i.e.: prefer:</p><pre><code class="language-js">vanX.replace(data.objField, <new value>)</code></pre><p>instead of</p><pre><code class="language-js">data.objField = <new value></code></pre><h3 class="w3-large w3-text-red" id="server-driven-ui-sdui-with-vanx"><a class="self-link" href="#server-driven-ui-sdui-with-vanx">Server-driven UI (SDUI) with <b>VanX</b></a></h3><p>The smart diff / update mechanism in <code class="symbol">vanX.replace</code> enables a new spectrum of modern programming paradigms, such as <a href="https://techitup.io/blog/Build-a-Server-Driven-UI-TFdlnm" class="w3-hover-opacity">server-driven UI</a>, where the server sends the entire global app state to the client via JSON or other forms. <code class="symbol">vanX.replace</code> guarantees only minimum parts of the global app state to be updated, and thus minimum parts of the DOM tree need to be re-rendered.</p><p>Below is a sample Chat app which receives the updates of app state completely from server. Note that with <code class="symbol">vanX.replace</code>, only necessary DOM elements will be re-rendered upon receiving the server events:</p><pre><code class="language-js">const ChatApp = () => {
const appState = vanX.reactive({friends: [], messages: []})
;(async () => {for await (const state of serverStateUpdates()) vanX.replace(appState, state)})()
return div({class: "container"},
div({class: "friend-list"},
vanX.list(ul, appState.friends, ({val: v}) => li(
span({class: () => ["status-indicator", v.online ? "online" : "offline"].join(" ")}), " ",
() => v.name,
)),
),
vanX.list(div({class: "chat-messages"}), appState.messages, s => div({class: "message"}, s)),
)
}
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/x/chat-app">Try on jsfiddle</a></p><p>Note that in the jsfiddle preview link above, we're simulating the server-side state updates. In real-world applications, state updates can be sent from server via <a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events" class="w3-hover-opacity">server-sent events</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/message_event" class="w3-hover-opacity"><code class="symbol">WebSocket</code> messages</a>, or <a href="https://medium.com/cache-me-out/http-polling-and-long-polling-bd3f662a14f" class="w3-hover-opacity">HTTP polling</a>.</p><h3 class="w3-large w3-text-red" id="serialization-and-compact"><a class="self-link" href="#serialization-and-compact">Serialization app state and <code class="symbol">vanX.compact</code></a></h3><p>You can serialize the entire app state into a single string, via <code class="language-js">JSON.stringify</code> or <a href="https://github.com/protobufjs/protobuf.js" class="w3-hover-opacity">protobuf</a>. As mentioned in <a href="#holes-in-the-array" class="w3-hover-opacity">a previous section</a>, holes that might appear in reactive arrays need to be eliminated. <code class="symbol">vanX.compact</code> does exactly that. It traverses the entire object tree of the input reactive object and returns a new object with holes in all encountered arrays eliminated.</p><h4 class="w3-medium w3-text-red" id="api-compact"><a class="self-link" href="#api-compact">API reference: <code class="symbol">vanX.compact</code></a></h4><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">vanX.compact(obj) => <a new object with holes in all arrays eliminated></code></td></tr><tr><td><b>Description</b></td><td>Traverse the entire object tree of the input reactive object <code class="symbol">obj</code> and returns a new object with holes in all encountered arrays eliminated. The input object <code class="symbol">obj</code> remains unchanged.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">obj</code></b> - The input reactive object.</li></ul></td></tr><tr><td><b>Returns</b></td><td>A new object with holes eliminated.</td></tr></tbody></table><h2 class="w3-xxlarge w3-text-red" id="api-index"><a class="self-link" href="#api-index">API Index</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Below is the list of all top-level APIs in <b>VanX</b>:</p><ul><li><code class="symbol"><a href="#api-reactive" class="w3-hover-opacity">vanX.reactive</a></code></li><li><code class="symbol"><a href="#api-calc" class="w3-hover-opacity">vanX.calc</a></code></li><li><code class="symbol"><a href="#api-stateFields" class="w3-hover-opacity">vanX.stateFields</a></code></li><li><code class="symbol"><a href="#api-raw" class="w3-hover-opacity">vanX.raw</a></code></li><li><code class="symbol"><a href="#api-noreactive" class="w3-hover-opacity">vanX.noreactive</a></code></li><li><code class="symbol"><a href="#api-list" class="w3-hover-opacity">vanX.list</a></code></li><li><code class="symbol"><a href="#api-replace" class="w3-hover-opacity">vanX.replace</a></code></li><li><code class="symbol"><a href="#api-compact" class="w3-hover-opacity">vanX.compact</a></code></li></ul></div>
<aside id="toc"><ul><li><a href="#installation" class="w3-hover-opacity">Installation</a></li><li><a href="#reactive-object" class="w3-hover-opacity">vanX.reactive: Reactive Object to Hold Many Individual States</a><ul><li><a href="#a-practical-example" class="w3-hover-opacity">A practical example</a></li><li><a href="#calculated-fields" class="w3-hover-opacity">Calculated fields</a></li><li><a href="#get-the-underlying-state-object" class="w3-hover-opacity">Get the underlying State object</a></li><li><a href="#get-the-raw-field-value" class="w3-hover-opacity">Get the raw field value without registering the dependency</a></li><li><a href="#add-reactivity-to-existing-javascript-classes" class="w3-hover-opacity">Add reactivity to existing JavaScript classes</a></li><li><a href="#vanx-noreactive-exemption-from-reactivity-conversion" class="w3-hover-opacity">vanX.noreactive: exemption from reactivity conversion</a></li><li><a href="#a-comprehensive-example" class="w3-hover-opacity">A comprehensive example</a></li></ul></li><li><a href="#reactive-list" class="w3-hover-opacity">vanX.list: Reactive List that Minimizes Re-rendering on Updates</a><ul><li><a href="#a-simplified-todo-app" class="w3-hover-opacity">A simplified TODO App</a></li><li><a href="#holes-in-the-array" class="w3-hover-opacity">Holes in the array</a></li></ul></li><li><a href="#vanx-replace-update-insert-delete-and-reorder-items-in-batch" class="w3-hover-opacity">vanX.replace: Update, Insert, Delete and Reorder Items in Batch</a><ul><li><a href="#example-1-sortable-list" class="w3-hover-opacity">Example 1: sortable list</a></li><li><a href="#example-2-an-advanced-sortable-todo-list" class="w3-hover-opacity">Example 2: an advanced sortable TODO list</a></li><li><a href="#vanx-list-for-calculated-fields" class="w3-hover-opacity">vanX.list for calculated fields</a></li></ul></li><li><a href="#global-app-state-and-serialization" class="w3-hover-opacity">Global App State and Serialization</a><ul><li><a href="#smart-diff-update-in-vanx-replace" class="w3-hover-opacity">Smart diff / update in vanX.replace</a></li><li><a href="#server-driven-ui-sdui-with-vanx" class="w3-hover-opacity">Server-driven UI (SDUI) with VanX</a></li><li><a href="#serialization-and-compact" class="w3-hover-opacity">Serialization app state and vanX.compact</a></li></ul></li><li><a href="#api-index" class="w3-hover-opacity">API Index</a></li></ul></aside>
</div>
</div>
<script>
// Script to open and close sidebar
const w3_open = () => {
document.getElementById("mySidebar").style.display = "block"
document.getElementById("myOverlay").style.display = "block"
}
const w3_close = () => {
document.getElementById("mySidebar").style.display = "none"
document.getElementById("myOverlay").style.display = "none"
}
const tocDom = document.getElementById("toc")
// Tracks the current toc item
const trackToc = () => {
const allHeadings = [...document.querySelectorAll("h2,h3")]
const currentHeadingIndex = allHeadings.findIndex(h => h.getBoundingClientRect().top >= 0)
let currentHeading
if (currentHeadingIndex < 0) currentHeading = allHeadings[allHeadings.length - 1]; else {
currentHeading = allHeadings[currentHeadingIndex]
if (currentHeadingIndex > 0 && currentHeading.getBoundingClientRect().top > innerHeight)
currentHeading = allHeadings[currentHeadingIndex - 1]
}
for (const e of document.querySelectorAll("#toc li a"))
if (e.href.split("#")[1] === currentHeading?.id) {
e.classList.add("current-heading")
const {top: tocTop, bottom: tocBottom} = tocDom.getBoundingClientRect()
const {top: eTop, bottom: eBottom} = e.getBoundingClientRect()
if (eBottom > tocBottom) tocDom.scrollTop += eBottom - tocBottom
else if (eTop < tocTop) tocDom.scrollTop -= tocTop - eTop
} else
e.classList.remove("current-heading")
}
trackToc()
document.addEventListener("scroll", trackToc)
addEventListener("resize", trackToc)
const copy = e => {
const file = e.previousElementSibling.innerText
const importLine = file.includes("nomodule") ?
`<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/${file}"><\/script>` :
`import van from "https://cdn.jsdelivr.net/gh/vanjs-org/van/public/${file}"`
navigator.clipboard.writeText(importLine)
.then(() => e.querySelector(".tooltip").innerText = "Copied")
.catch(() => e.querySelector(".tooltip").innerText = "Copy failed")
}
const resetTooltip = e => e.querySelector(".tooltip").innerText = "Copy import line"
</script>
<!-- Place this tag in your head or just before your close body tag. -->
<script async src="https://buttons.github.io/buttons.js"></script>
<link rel="prefetch" href="/code/prism-v1.js" as="script"><link rel="prefetch" href="https://www.gstatic.com/charts/loader.js" as="script"><link rel="prefetch" href="/code/diff.min.js" as="script"><link rel="prefetch" href="/code/van-1.5.3.nomodule.min.js" as="script"><link rel="prefetch" href="/code/van-x-0.6.2.nomodule.min.js" as="script"></body></html>