-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathtutorial.html
412 lines (352 loc) · 58.3 KB
/
tutorial.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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
<!DOCTYPE html>
<html lang="en"><head>
<link rel="icon" href="/logo.svg">
<title>VanJS - Tutorial and API Reference</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="tutorial.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 current">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">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">VanJS Tutorial</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>VanJS</b>: Tutorial and API Reference</h1><p></p><div>📣 <a href="https://github.com/vanjs-org/van/discussions/280" class="w3-hover-opacity"><b>VanJS</b>'s API was simplified in 1.4.0, see the release notes and migration guide →</a></div><p></p><blockquote><i>Entia non sunt multiplicanda praeter necessitatem<br>(The best solution is usually the one with the least unnecessary complexity)<br><br>-- Occam's Razor</i></blockquote><p>In this tutorial, we will break down into 3 core functionalities <b>VanJS</b> supports: DOM composition / manipulation, State and State binding.</p><h2 class="w3-xxlarge w3-text-red" id="dom"><a class="self-link" href="#dom">DOM Composition and Manipulation</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><h3 class="w3-large w3-text-red" id="your-first-vanjs-app-a-simple-hello-page"><a class="self-link" href="#your-first-vanjs-app-a-simple-hello-page">Your first VanJS app: a simple <code class="symbol">Hello</code> page</a></h3><p>We will start this tutorial with a simple <code class="symbol">Hello</code> page, with the code below:</p><pre><code class="language-js">const {a, div, li, p, ul} = van.tags
const Hello = () => div(
p("👋Hello"),
ul(
li("🗺️World"),
li(a({href: "https://vanjs.org/"}, "🍦VanJS")),
),
)
van.add(document.body, Hello())
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/hello">Try on jsfiddle</a></p><p>The code should be self-explanatory if you have some familiarity with HTML. Unlike React, everything in the code above is just pure JavaScript, meaning that you are simply calling functions from <code class="symbol">van.js</code> without any transpiling that converts your source code into another form. Reusable UI components built with <b>VanJS</b> can be pure vanilla JavaScript functions as well. Here we capitalize the first letter to follow React conventions.</p><p>Also unlike React, <b>VanJS</b> does not introduce an ad-hoc virtual DOM layer. All the tag functions above directly return the created DOM objects. e.g.: the function call <code class="symbol">p("👋Hello")</code> simply creates an <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLParagraphElement" class="w3-hover-opacity">HTMLParagraphElement</a></code> with <code class="symbol">👋Hello</code> as its <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText" class="w3-hover-opacity">innerText</a></code>, meaning that you can directly interact with your created DOM nodes with native DOM APIs.</p><p>💡 <b>Tip</b>: If you are tired of adding all the tag function names manually in the importing line:</p><pre><code class="language-js">const {a, div, li, p, ul} = van.tags</code></pre><p>we have built a <a href="https://marketplace.visualstudio.com/items?itemName=TaoXin.vanjs-importtag" class="w3-hover-opacity">VS Code extension</a> with the command that can automatically import the tag function at the cursor. You can check out its <a href="https://github.com/vanjs-org/vanjs-importtag" class="w3-hover-opacity">GitHub repo</a> for more details.</p><h3 class="w3-large w3-text-red" id="api-tags"><a class="self-link" href="#api-tags">API reference: <code class="symbol">van.tags</code></a></h3><p><code class="symbol">van.tags</code> is a top-level dynamic object in <b>VanJS</b> implemented with <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" class="w3-hover-opacity">Proxy</a></code>. <code class="symbol">van.tags.<name></code> gets you a function that creates an HTML element with tag name <code class="symbol"><name></code>. A common way of using <code class="symbol">van.tags</code> is like the line below:</p><pre><code class="language-js">const {a, div, p} = van.tags</code></pre><p>With the line, <code class="symbol">a</code>, <code class="symbol">div</code>, <code class="symbol">p</code> are functions that create <code class="language-html"><a></code>, <code class="language-html"><div></code>, <code class="language-html"><p></code> HTML elements respectively.</p><p>We will use <code class="symbol">div</code> function as an example, the API reference for <code class="symbol">div</code> tag function is as below:</p><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">div([props], ...children) => <the created DOM element></code></td></tr><tr><td><b>Description</b></td><td>Creates an <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement" class="w3-hover-opacity">HTMLDivElement</a></code> with <code class="symbol">props</code> as its properties and <code class="symbol">children</code> as its child nodes.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">props</code></b> - optional, a plain JavaScript object whose keys and values are the keys and values of the properties of the created HTML element. Keys should be <code class="symbol">string</code>, and each value can be a primitive (<code class="symbol">string</code>, <code class="symbol">number</code>, <code class="symbol">boolean</code> or <code class="symbol">bigint</code>), <code class="symbol">null</code>, a primitive-valued or <code class="symbol">null</code>-valued <code class="symbol">State</code> object, or a <code class="symbol">function</code> for a <code class="symbol">State</code>-derived property. We will explain the behavior of <a href="#state-typed-prop" class="w3-hover-opacity"><code class="symbol">State</code>-typed</a> and <a href="#state-derived-prop" class="w3-hover-opacity"><code class="symbol">State</code>-derived</a> properties in State Binding section below. For keys like <code class="symbol">on...</code>, the value should be a <code class="symbol">function</code> to represent the event handler.</li><li><b><code class="symbol">children</code></b> - caller can provide 0 or more children as arguments to represent the child nodes of the created HTML element. Each child can be a valid DOM node, a primitive (<code class="symbol">string</code>, <code class="symbol">number</code>, <code class="symbol">boolean</code> or <code class="symbol">bigint</code>), <code class="symbol">null</code>, <code class="symbol">undefined</code>, a primitive-valued or <code class="symbol">null</code>/<code class="symbol">undefined</code>-valued <code class="symbol">State</code> object, a <code class="symbol">function</code> for a <code class="symbol">State</code>-derived child, or 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> of children. <code class="symbol">null</code>/<code class="symbol">undefined</code>-valued children will be ignored. A <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Text" class="w3-hover-opacity">Text node</a></code> will be created for each primitive-typed argument. We will explain the behavior of <a href="#state-typed-child" class="w3-hover-opacity"><code class="symbol">State</code>-typed child</a> and <a href="#state-derived-child" class="w3-hover-opacity"><code class="symbol">State</code>-derived child</a> in State Binding section below. For DOM node, it shouldn't be already connected to a document tree (<code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected" class="w3-hover-opacity">isConnected</a></code> property should be <code class="symbol">false</code>). i.e.: You should not declare an existing DOM node in the current document as the child node of the newly created element.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement" class="w3-hover-opacity">HTMLDivElement</a></code> object just created.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="svg-and-mathml-support"><a class="self-link" href="#svg-and-mathml-support">SVG and MathML Support</a></h3><p>To create HTML elements with custom <code class="symbol"><a href="https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/glossary.html#dt-namespaceURI" class="w3-hover-opacity">namespace URI</a></code>, you can declare tag functions via <code class="language-js">van.tags(<namespaceURI>)</code> (or <code class="language-js">van.tagsNS(<namespaceURI>)</code> before <b>VanJS</b> <a href="https://github.com/vanjs-org/van/discussions/280" class="w3-hover-opacity">1.4.0</a>). Here is an example of composing the SVG DOM tree:</p><pre><code class="language-js">const {circle, path, svg} = van.tags("http://www.w3.org/2000/svg")
const Smiley = () => svg({width: "16px", viewBox: "0 0 50 50"},
circle({cx: "25", cy: "25", "r": "20", stroke: "black", "stroke-width": "2", fill: "yellow"}),
circle({cx: "16", cy: "20", "r": "2", stroke: "black", "stroke-width": "2", fill: "black"}),
circle({cx: "34", cy: "20", "r": "2", stroke: "black", "stroke-width": "2", fill: "black"}),
path({"d": "M 15 30 Q 25 40, 35 30", stroke: "black", "stroke-width": "2", fill: "transparent"}),
)
van.add(document.body, Smiley())
</code></pre><p><b>Demo:</b> <span id="demo-smiley"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/smiley">Try on jsfiddle</a></p><p>Similarly, math formulas can be created with <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/MathML/Element" class="w3-hover-opacity">MathML</a></code> elements:</p><pre><code class="language-js">const {math, mi, mn, mo, mrow, msup} = van.tags("http://www.w3.org/1998/Math/MathML")
const Euler = () => math(
msup(mi("e"), mrow(mi("i"), mi("π"))), mo("+"), mn("1"), mo("="), mn("0"),
)
van.add(document.body, Euler())
</code></pre><p><b>Demo:</b> <span id="demo-euler"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/euler">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="api-tagsns"><a class="self-link" href="#api-tagsns">API reference <code class="symbol">van.tags</code> (for elements with custom namespace URI)</a></h3><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">van.tags(namespaceURI) => <the created tags object for elements with specified namespaceURI></code></td></tr><tr><td><b>Description</b></td><td>Creates a tags <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" class="w3-hover-opacity">Proxy</a></code> object similar to <code class="symbol"><a href="#api-tags" class="w3-hover-opacity">van.tags</a></code> for elements with specified <code class="symbol">namespaceURI</code>.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">namespaceURI</code></b> - a string for the <code class="symbol">namespaceURI</code> property of elements created via tag functions.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The created tags object.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="specifying-options-of-createelement-or-createelementns"><a class="self-link" href="#specifying-options-of-createelement-or-createelementns">Specifying <code class="symbol">options</code> of <code class="symbol">createElement</code> or <code class="symbol">createElementNS</code></a></h3><p><i>Requires <b>VanJS</b> 1.5.3 or later.</i></p><p>You can also specify <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement#options" class="w3-hover-opacity">options</a></code> of the function <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement" class="w3-hover-opacity">document.createElement</a></code> or <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS" class="w3-hover-opacity">document.createElementNS</a></code> in the <code class="symbol">props</code> argument of tag functions. Below is an example where <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement#is" class="w3-hover-opacity">is</a></code> option is specified to create an element with custom behavior:</p><pre><code class="language-js">const {button} = van.tags
class MyButton extends HTMLButtonElement {
connectedCallback() {
this.addEventListener("click", () => alert("MyButton clicked!"))
}
}
customElements.define("my-button", MyButton, {extends: "button"})
const CustomButton = () => button({is: "my-button"}, "Click me")
van.add(document.body, CustomButton())
</code></pre><p><b>Demo:</b> <span id="demo-custom-button"></span></p><h3 class="w3-large w3-text-red" id="api-add"><a class="self-link" href="#api-add">API reference: <code class="symbol">van.add</code></a></h3><p><code class="symbol">van.add</code> function is similar to tag functions described above. Instead of creating a new HTML element with specified properties and children, <code class="symbol">van.add</code> function mutates its first argument (which is an existing <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" class="w3-hover-opacity">Element node</a></code>) by appending 0 or more children with <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild" class="w3-hover-opacity">appendChild</a></code> calls:</p><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">van.add(dom, ...children) => dom</code></td></tr><tr><td><b>Description</b></td><td>Mutates <code class="symbol">dom</code> by appending 0 or more child nodes to it. Returns <code class="symbol">dom</code> for possibly further chaining.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">dom</code></b> - an existing DOM element that we want to append children to.</li><li><b><code class="symbol">children</code></b> - caller can provide 0 or more <code class="symbol">children</code> as arguments to represent the child nodes we want to append to <code class="symbol">dom</code>. Each child can be a valid DOM node, a primitive, <code class="symbol">null</code>, <code class="symbol">undefined</code>, a primitive-valued or <code class="symbol">null</code>/<code class="symbol">undefined</code>-valued <code class="symbol">State</code> object, a <code class="symbol">function</code> for a <code class="symbol">State</code>-derived child, or 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> of children. <code class="symbol">null</code>/<code class="symbol">undefined</code>-valued children will be ignored. A <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Text" class="w3-hover-opacity">Text node</a></code> will be created for each primitive-typed argument. <a href="#state-typed-child" class="w3-hover-opacity"><code class="symbol">State</code>-typed child</a> and <a href="#state-derived-child" class="w3-hover-opacity"><code class="symbol">State</code>-derived child</a> behave the same way as in tag function. For DOM node, it shouldn't be already connected to a document tree (<code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected" class="w3-hover-opacity">isConnected</a></code> property should be <code class="symbol">false</code>). i.e.: You should not append an existing DOM node in the current document to <code class="symbol">dom</code>. If 0 children is provided, this function is a no-op.</li></ul></td></tr><tr><td><b>Returns</b></td><td><code class="symbol">dom</code></td></tr></tbody></table><h3 class="w3-large w3-text-red" id="dom-nodes-already-in-the-document-tree-can-t-be-used-as-children"><a class="self-link" href="#dom-nodes-already-in-the-document-tree-can-t-be-used-as-children">DOM nodes already in the document tree can't be used as <code class="symbol">children</code></a></h3><p>As mentioned in the API reference, if a DOM node is already connected to the document tree, it shouldn't be used as the child node of tag function or <code class="symbol">van.add</code>. The following code is invalid and an <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error" class="w3-hover-opacity">Error</a></code> will be thrown when <code class="symbol">van-<version>.debug.js</code> is being used:</p><pre><code class="language-js">const existing = document.getElementById("some-id")
// Invalid! Existing node can't be used as the child node of tag function.
const dom = div({id: "new-id"}, existing)
// Invalid! Existing node can't be appended to other nodes in `van.add`.
van.add(document.body, existing)</code></pre><h3 class="w3-large w3-text-red" id="fun-dom"><a class="self-link" href="#fun-dom">Functional-style DOM tree building</a></h3><p>Because both tag functions and <code class="symbol">van.add</code> can take <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> arguments and the <code class="symbol">Array</code> arguments can be deeply nested. <b>VanJS</b> enables very ergonomic DOM tree composition in functional-style. See examples below:</p><p>Building a bullet list:</p><pre><code class="language-js">const {li, ul} = van.tags
const List = ({items}) => ul(items.map(it => li(it)))
van.add(document.body, List({items: ["Item 1", "Item 2", "Item 3"]}))
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/list">Try on jsfiddle</a></p><p>Building a table:</p><pre><code class="language-js">const {table, tbody, thead, td, th, tr} = van.tags
const Table = ({head, data}) => table(
head ? thead(tr(head.map(h => th(h)))) : [],
tbody(data.map(row => tr(
row.map(col => td(col)),
))),
)
van.add(document.body, Table({
head: ["ID", "Name", "Country"],
data: [
[1, "John Doe", "US"],
[2, "Jane Smith", "CA"],
[3, "Bob Johnson", "AU"],
],
}))
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/table">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="on-event-handlers"><a class="self-link" href="#on-event-handlers"><code class="symbol">on...</code> event handlers</a></h3><p>In tag functions, you can provide a <code class="symbol">function</code> value for property keys like <code class="symbol">on...</code>. This is a convenient way to specify event handlers. For instance, the code below creates a <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button" class="w3-hover-opacity">button</a></code> that shows an alert whenever clicked:</p><pre><code class="language-js">button({onclick: () => alert("Hello from 🍦VanJS")}, "Hello")</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/onclick">Try on jsfiddle</a></p><p><i>The support of custom event handlers was added in <b>VanJS</b> <a href="https://github.com/vanjs-org/van/discussions/246" class="w3-hover-opacity">1.2.8</a>.</i></p><p>🎉 Congratulations! You have mastered the skills for building and manipulating DOM trees using <b>VanJS</b>'s declarative API, which is incredibly powerful for creating comprehensive applications with elegant code. In the sections below, you will continue to learn how to build reactive applications with state and state binding.</p><p>If your application doesn't rely on state and state binding, you can use the slimmed-down version of <b>VanJS</b> - <a href="/minivan" class="w3-hover-opacity">Mini-Van</a>.</p><h2 class="w3-xxlarge w3-text-red" id="state"><a class="self-link" href="#state">State</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>A <code class="symbol">State</code> object in <b>VanJS</b> represents a value that can be updated throughout your application. A <code class="symbol">State</code> object has a public property <code class="symbol">val</code>, with a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set" class="w3-hover-opacity">custom setter</a> that automatically propagates changes to DOM nodes that are bound to it.</p><p>The code below illustrates how a <code class="symbol">State</code> object can be used:</p><pre><code class="language-js">const {button, div, input, sup} = van.tags
// Create a new state object with init value 1
const counter = van.state(1)
// Log whenever the value of the state is updated
van.derive(() => console.log(`Counter: ${counter.val}`))
// Derived state
const counterSquared = van.derive(() => counter.val * counter.val)
// Used as a child node
const dom1 = div(counter)
// Used as a property
const dom2 = input({type: "number", value: counter, disabled: true})
// Used in a state-derived property
const dom3 = div({style: () => `font-size: ${counter.val}em;`}, "Text")
// Used in a state-derived child
const dom4 = div(counter, sup(2), () => ` = ${counterSquared.val}`)
// Button to increment the value of the state
const incrementBtn = button({onclick: () => ++counter.val}, "Increment")
const resetBtn = button({onclick: () => counter.val = 1}, "Reset")
van.add(document.body, incrementBtn, resetBtn, dom1, dom2, dom3, dom4)
</code></pre><p><b>Demo:</b></p><p id="demo-state"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/state">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="api-state"><a class="self-link" href="#api-state">API reference: <code class="symbol">van.state</code></a></h3><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">van.state(initVal) => <the created State object></code></td></tr><tr><td><b>Description</b></td><td>Creates a <code class="symbol">State</code> object with its init value specified in the argument.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">initVal</code></b> - the init value of the <code class="symbol">State</code> object to be created.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The created <code class="symbol">State</code> object.</td></tr></tbody></table><h3 class="w3-large w3-text-red" id="public-interface-of-state-objects"><a class="self-link" href="#public-interface-of-state-objects">Public interface of <code class="symbol">State</code> objects</a></h3><ul><li>Property <code class="symbol"><b>val</b></code> - the current value of the <code class="symbol">State</code> object. When a new value of this property is set, all <a href="#derived-state" class="w3-hover-opacity">derived states</a> and <a href="#side-effect" class="w3-hover-opacity">side effects</a> registered via <code class="symbol"><a href="#api-derive" class="w3-hover-opacity">van.derive</a></code> and all DOM nodes that are bound to it will be updated accordingly.</li><li>Readonly property <code class="symbol"><b>oldVal</b></code> - the old value of the <code class="symbol">State</code> object prior to the current UI update cycle. This property might be useful for <a href="#stateful-binding" class="w3-hover-opacity">stateful binding</a>.</li><li id="api-rawVal">Readonly property <code class="symbol"><b>rawVal</b></code> - <i>(requires <b>VanJS</b> <a href="https://github.com/vanjs-org/van/discussions/290" class="w3-hover-opacity">1.5.0</a> or later)</i> getting the current value of the <code class="symbol">State</code> object (peeking) without registering the state as a dependency of the binding function for the derived state, side effect or DOM node. For instance, the derived state <code class="language-js">van.derive(() => a.rawVal + b.val)</code> will be updated when <code class="symbol">b</code> changes, but won't be updated when <code class="symbol">a</code> changes.</li></ul><p>The value of a <code class="symbol">State</code> object can be almost anything, primitive, <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object" class="w3-hover-opacity">Object</a></code>, <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>, <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/null" class="w3-hover-opacity">null</a></code>, etc., with 2 ad-hoc exceptions that we made: The value of the <code class="symbol">State</code> object cannot be a DOM node, or another <code class="symbol">State</code> object. Having values in these 2 types carries little semantic information and is more likely a result of coding bugs. Thus we disallow <code class="symbol">State</code> objects to have values in these 2 types. In <code class="symbol">van-{version}.debug.js</code>, an explicit error will be thrown if you try to assign a DOM node or another <code class="symbol">State</code> object as the value of a state.</p><p>See also: <a href="/advanced#why-not-dom-valued-states" class="w3-hover-opacity">Why can't states have DOM node as values?</a></p><h3 class="w3-large w3-text-red" id="state-val-is-immutable"><a class="self-link" href="#state-val-is-immutable"><code class="symbol">State.val</code> is immutable</a></h3><p>While you can update <code class="symbol">State</code> objects by setting the <code class="symbol">val</code> property, you should never mutate the underlying object of <code class="symbol">val</code> itself. Doing so will not trigger the DOM tree update as you would expect and might result in <a href="https://en.wikipedia.org/wiki/Undefined_behavior" class="w3-hover-opacity">undefined behavior</a> due to <a href="https://en.wikipedia.org/wiki/Aliasing_(computing)" class="w3-hover-opacity">aliasing</a>.</p><h3 class="w3-large w3-text-red" id="derived-state"><a class="self-link" href="#derived-state">Derived state</a></h3><p>Derived states can be declared via <code class="symbol">van.derive</code>, as illustrated in the example below:</p><pre><code class="language-js">const {input, span} = van.tags
const DerivedState = () => {
const text = van.state("VanJS")
const length = van.derive(() => text.val.length)
return span(
"The length of ",
input({type: "text", value: text, oninput: e => text.val = e.target.value}),
" is ", length, ".",
)
}
van.add(document.body, DerivedState())
</code></pre><p><b>Demo:</b> <span id="demo-derived-state"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/derived-state">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="api-derive"><a class="self-link" href="#api-derive">API reference: <code class="symbol">van.derive</code></a></h3><table><tbody><tr><td><b>Signature</b></td><td><code class="language-js">van.derive(f) => <the created derived state></code></td></tr><tr><td><b>Description</b></td><td>Creates a derived <code class="symbol">State</code> object based on the derivation function <code class="symbol">f</code>. The <code class="symbol">val</code> of the derived state is always in sync with the result of <code class="symbol">f</code>. i.e.: whenever the <code class="symbol">val</code> of its dependency changes, <code class="symbol">f</code> will be called to update the <code class="symbol">val</code> of the derived state, synchronously.</td></tr><tr><td><b>Parameters</b></td><td><ul><li><b><code class="symbol">f</code></b> - The derivation function, which takes no parameter and returns a single value.</li></ul></td></tr><tr><td><b>Returns</b></td><td>The created derived <code class="symbol">State</code> object.</td></tr></tbody></table><p>Note that: Since <a href="https://github.com/vanjs-org/van/discussions/290" class="w3-hover-opacity"><b>VanJS</b> 1.5.0</a>, we have changed the execution of state derivation from synchronous to asynchronous as an optimization to avoid potentially unnecessary derivations. That is, instead of executing state derivations immediately, the derivations are scheduled to execute as soon as the next event cycle of browser context (i.e.: after the current call stack is cleared, which is equivalent to <code class="language-js">setTimeout(..., 0)</code>). The effect of the asynchronous derivation can be illustrated by the code below:</p><pre><code class="language-js">const a = van.state(1)
const b = van.derive(() => a.val * 2)
a.val = 2
console.log("b.val =", b.val) // Expecting 2
setTimeout(() => console.log("b.val =", b.val), 10) // Expecting 4</code></pre><h3 class="w3-large w3-text-red" id="side-effect"><a class="self-link" href="#side-effect">Side effect</a></h3><p><code class="symbol">van.derive</code> can be used to declare side effects as well. You can discard the return value of <code class="symbol">van.derive</code> if you are not interested. The code below is a modified <code class="symbol">Counter App</code> which logs the counter to console whenever it changes:</p><pre><code class="language-js">const {button, span} = van.tags
const Counter = () => {
const counter = van.state(0)
van.derive(() => console.log("Counter: ", counter.val))
return span(
"❤️ ", counter, " ",
button({onclick: () => ++counter.val}, "👍"),
button({onclick: () => --counter.val}, "👎"),
)
}
van.add(document.body, Counter())
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/effect">Try on jsfiddle</a></p><p>See also: <a href="/advanced#advanced-state-derivation" class="w3-hover-opacity">Advanced state derivation</a></p><h2 class="w3-xxlarge w3-text-red" id="state-binding"><a class="self-link" href="#state-binding">State Binding</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>Once <code class="symbol">State</code> objects are created, we can bind them to DOM nodes in various ways to make your UI reactive to state changes.</p><h3 class="w3-large w3-text-red" id="state-typed-prop"><a class="self-link" href="#state-typed-prop"><code class="symbol">State</code> objects as properties</a></h3><p><code class="symbol">State</code> objects can be used as properties of HTML elements. Similar to <code class="symbol">State</code>-based child nodes, the value of the properties will be always in sync with the value of the respective states. When <code class="symbol">State</code> objects are used as properties, you need to make sure that the values of the states are always valid property values, i.e.: primitives or <code class="symbol">function</code>s (for event handlers).</p><p>The following code demonstrates 2 <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text" class="w3-hover-opacity">text inputs</a></code> whose values are always in sync:</p><pre><code class="language-js">const {input, span} = van.tags
const ConnectedProps = () => {
const text = van.state("")
return span(
input({type: "text", value: text, oninput: e => text.val = e.target.value}),
input({type: "text", value: text, oninput: e => text.val = e.target.value}),
)
}
van.add(document.body, ConnectedProps())
</code></pre><p><b>Demo:</b> <span id="demo-connected-props"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/connected-props">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="state-typed-child"><a class="self-link" href="#state-typed-child"><code class="symbol">State</code> objects as child nodes</a></h3><p><code class="symbol">State</code> objects can be used as child nodes in <code class="symbol"><a href="#api-tags" class="w3-hover-opacity">tag functions</a></code> and <code class="symbol"><a href="#api-add" class="w3-hover-opacity">van.add</a></code>, like the <code class="symbol"><a href="/#code-counter" class="w3-hover-opacity">Counter</a></code> example shown in the home page. For a <code class="symbol">State</code> object used as a child node, its value needs to be primitive (<code class="symbol">string</code>, <code class="symbol">number</code>, <code class="symbol">boolean</code> or <code class="symbol">bigint</code>), and a <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Text" class="w3-hover-opacity">Text node</a></code> will be created for it. The content of the created <code class="symbol">Text node</code> will be always in sync with the value of the state.</p><p>The following code shows how to build a simple timer with this feature:</p><pre><code class="language-js">const {button, span} = van.tags
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const Timer = ({totalSecs}) => {
const secs = van.state(totalSecs)
return span(
secs, "s ",
button({onclick: async () => {
while (secs.val > 0) await sleep(1000), --secs.val
await sleep(10) // Wait briefly for DOM update
alert("⏰: Time is up")
secs.val = totalSecs
}}, "Start"),
)
}
van.add(document.body, Timer({totalSecs: 5}))
</code></pre><p><b>Demo:</b> <span id="demo-timer"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/timer">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="state-derived-prop"><a class="self-link" href="#state-derived-prop"><code class="symbol">State</code>-derived properties</a></h3><p><code class="symbol">State</code>-derived property is a more advanced way to bind a property of an HTML element to one or more underlying <code class="symbol">State</code> objects. To declare a <code class="symbol">State</code>-derived property, you need to provide a function as the value in <code class="symbol">props</code> argument while calling to a <code class="symbol"><a href="#api-tags" class="w3-hover-opacity">tag function</a></code>. The function takes no parameter and return the value of the property. Whenever any dependency of the function changes, the value of the property will be updated accordingly.</p><p>The example below is a live font size and color preview implemented with this feature:</p><pre><code class="language-js">const {input, option, select, span} = van.tags
const FontPreview = () => {
const size = van.state(16), color = van.state("black")
return span(
"Size: ",
input({type: "range", min: 10, max: 36, value: size,
oninput: e => size.val = e.target.value}),
" Color: ",
select({oninput: e => color.val = e.target.value},
["black", "blue", "green", "red", "brown"]
.map(c => option({selected: () => color.val === c}, c)),
),
// The <span> element below has a state-derived property `style`
span({style: () => `font-size: ${size.val}px; color: ${color.val};`}, " Hello 🍦VanJS"),
)
}
van.add(document.body, FontPreview())
</code></pre><p><b>Demo:</b> <span id="demo-font-preview"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/font-preview">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="state-derived-event-handlers"><a class="self-link" href="#state-derived-event-handlers">State-derived <code class="symbol">on...</code> event handlers</a></h3><p>When declaring a <code class="symbol">State</code>-derived property for an <code class="symbol">on...</code> event handler, you should wrap around the binding function with <code class="language-js">van.derive(...)</code> (i.e.: defining an ad-hoc <a href="#derived-state" class="w3-hover-opacity">derived state</a>). Otherwise, the function you provide will be consider as the event handler, rather than the binding function for the <code class="symbol">State</code>-derived property. See the example below:</p><pre><code class="language-js">const {button, option, select, span} = van.tags
const Counter = () => {
const counter = van.state(0)
const action = van.state("👍")
return span(
"❤️ ", counter, " ",
select({oninput: e => action.val = e.target.value},
option({selected: () => action.val === "👍"}, "👍"),
option({selected: () => action.val === "👎"}, "👎"),
), " ",
button({onclick: van.derive(() => action.val === "👍" ?
() => ++counter.val : () => --counter.val)}, "Run"),
)
}
van.add(document.body, Counter())
</code></pre><p><b>Demo:</b> <span id="demo-escape-derived-prop"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/escape-derived-prop">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="state-derived-child"><a class="self-link" href="#state-derived-child"><code class="symbol">State</code>-derived child nodes</a></h3><p>Similarly, you can bind an HTML node to one or more underlying <code class="symbol">State</code> objects. To declare a <code class="symbol">State</code>-derived child node, you need to provide a function as the <code class="symbol">child</code> argument while calling to a <code class="symbol"><a href="#api-tags" class="w3-hover-opacity">tag function</a></code> or <code class="symbol"><a href="#api-add" class="w3-hover-opacity">van.add</a></code>. The function you provide can return a primitive value (a <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Text" class="w3-hover-opacity">Text node</a></code> will be created for it) or a DOM node. The following example illustrates this:</p><pre><code class="language-js">const {input, li, option, select, span, ul} = van.tags
const SortedList = () => {
const items = van.state("a,b,c"), sortedBy = van.state("Ascending")
return span(
"Comma-separated list: ",
input({oninput: e => items.val = e.target.value,
type: "text", value: items}), " ",
select({oninput: e => sortedBy.val = e.target.value},
option({selected: () => sortedBy.val === "Ascending", value: "Ascending"}, "Ascending"),
option({selected: () => sortedBy.val === "Descending", value: "Descending"}, "Descending"),
),
// A State-derived child node
() => sortedBy.val === "Ascending" ?
ul(items.val.split(",").sort().map(i => li(i))) :
ul(items.val.split(",").sort().reverse().map(i => li(i))),
)
}
van.add(document.body, SortedList())
</code></pre><p><b>Demo:</b></p><p id="demo-sorted-list"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/sorted-list">Try on jsfiddle</a></p><p>Note that: Due to the limit of DOM API, the result of the binding function can't be an array of elements. You can wrap the result into a pass-through container (<code class="language-html"><span></code> for inline elements and <code class="language-html"><div></code> for block elements) if multiple elements need to be returned.</p><h3 class="w3-large w3-text-red" id="removing-a-dom-node"><a class="self-link" href="#removing-a-dom-node">Removing a DOM node</a></h3><p>For <code class="symbol">State</code>-derived child nodes, if the binding function returns <code class="symbol">null</code> or <code class="symbol">undefined</code>, the DOM node will removed. Removed DOM node will never be brought back, even when the binding function would return a non-<code class="symbol">null</code>/<code class="symbol">undefined</code> value based on future values of the dependencies.</p><p>The following code illustrates how to build an editable list with this features:</p><pre><code class="language-js">const {a, button, div, input, li, ul} = van.tags
const ListItem = ({text}) => {
const deleted = van.state(false)
return () => deleted.val ? null : li(
text, a({onclick: () => deleted.val = true}, "❌"),
)
}
const EditableList = () => {
const listDom = ul()
const textDom = input({type: "text"})
return div(
textDom, " ",
button({onclick: () => van.add(listDom, ListItem({text: textDom.value}))}, "➕"),
listDom,
)
}
van.add(document.body, EditableList())
</code></pre><p><b>Demo:</b></p><p id="demo-editable-list"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/editable-list">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="stateful-binding"><a class="self-link" href="#stateful-binding">Stateful binding</a></h3><p>While dealing with state updates for <code class="symbol">State</code>-derived child node, a user can choose to, instead of regenerating the new version of the DOM node entirely based on new state values, mutate the existing DOM node that is already connected to the document tree based on all the new values and old values of its dependencies. This feature can be used as an optimization to avoid the entire DOM subtree being completely re-rendered.</p><p>The following code is a snippet of the <a href="/demo#auto-complete" class="w3-hover-opacity">auto-complete application</a> which leverages this feature to optimize:</p><pre><code class="language-js">div({class: "root"}, textarea({onkeydown, oninput}), dom => {
if (dom && candidates.val === candidates.oldVal) {
// If the candidate list doesn't change, we don't need to re-render the
// suggetion list. Just need to change the selected candidate.
dom.querySelector(`[data-index="${selectedIndex.oldVal}"]`)
?.classList?.remove("selected")
dom.querySelector(`[data-index="${selectedIndex.val}"]`)
?.classList?.add("selected")
return dom
}
return SuggestionList({candidates: candidates.val, selectedIndex: selectedIndex.val})
})
</code></pre><p>The piece of code above is building a suggestion list that is reactive to the changes of selection <code class="symbol">candidates</code> and <code class="symbol">selectedIndex</code>. When selection <code class="symbol">candidates</code> change, the <code class="symbol">suggestionList</code> needs to be regenerated. However, if only <code class="symbol">selectedIndex</code> changes, we only need to update the DOM element to indicate that the new candidate is being selected now, which can be achieved by changing the <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList" class="w3-hover-opacity">classList</a></code> of relevant candidate elements.</p><p>To facilitate stateful binding, the binding function takes the <code class="symbol">dom</code> parameter, which is the current version of the DOM node prior to UI updates (or <code class="symbol">undefined</code> when the binding function is first called). The binding function can either return <code class="symbol">dom</code> (which means we don't want to update the DOM node to a new version), a primitive value (a new <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Text" class="w3-hover-opacity">Text node</a></code> will be created for it), or a new DOM node (whose <code class="symbol"><a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected" class="w3-hover-opacity">isConnected</a></code> property should be <code class="symbol">false</code>).</p><h3 class="w3-large w3-text-red" id="polymorphic-binding"><a class="self-link" href="#polymorphic-binding">Polymorphic Binding</a></h3><p>If you use <b>VanJS</b> to build reusable UI components, it might be desirable for your components, just like tag functions, to accept a static value, a <code class="symbol">State</code> object, or a binding function as a property value. For instance, for a reusable <code class="symbol">Button</code> component like that:</p><pre><code class="language-js">const Button = ({color, ...}) = button(
...
)
</code></pre><p>it would be desirable for the <code class="symbol">color</code> property of <code class="symbol">Button</code> component to accept a static value, a <code class="symbol">State</code> object, or a binding function. If the <code class="symbol">color</code> property is used as a DOM node property or as a child node, things can work out of the box, as tag functions and <code class="symbol">van.add</code> support static values, <code class="symbol">State</code> objects, and binding functions in <code class="symbol">props</code> and <code class="symbol">children</code> parameter. However, if the <code class="symbol">color</code> property is used inside a binding function for a <code class="symbol">State</code>-derived property or a <code class="symbol">State</code>-derived child, it would be hard for your component to work with different types of input. Consider the example below:</p><pre><code class="language-js">button({style: () => `background-color: ${color};`},
...
)</code></pre><p>When <code class="symbol">color</code> is a static value, we should use <code class="language-js">${color}</code>. However, when <code class="symbol">color</code> is a state, we should use <code class="language-js">${color.val}</code>, and when <code class="symbol">color</code> is a binding function, we should use <code class="language-js">${color()}</code> . This makes it hard to build reusable UI component that accepts all types of property values.</p><p>To tackle this issue, you can define an ad-hoc value resolver to get value for different types of property inputs. The value resolver can be something like this:</p><pre><code class="language-js">const stateProto = Object.getPrototypeOf(van.state())
const val = v => {
const protoOfV = Object.getPrototypeOf(v ?? 0)
if (protoOfV === stateProto) return v.val
if (protoOfV === Function.prototype) return v()
return v
}
</code></pre><p>Note that we're using <code class="language-js">Object.getPrototypeOf(van.state())</code> (<code class="language-js">van.state()</code> returns a dummy <code class="symbol">State</code> object) to get the <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes" class="w3-hover-opacity">prototype object</a> of <code class="symbol">State</code> objects. It's guaranteed that all <code class="symbol">State</code> objects in <b>VanJS</b> share the same prototype.</p><p>Let's look at a practical example - a reuseable button whose <code class="symbol">color</code>, <code class="symbol">text</code> and <code class="symbol">onclick</code> properties can be a static value, a <code class="symbol">State</code> object, or a binding function:</p><pre><code class="language-js">const {button, span} = van.tags
const stateProto = Object.getPrototypeOf(van.state())
const val = v => {
const protoOfV = Object.getPrototypeOf(v ?? 0)
if (protoOfV === stateProto) return v.val
if (protoOfV === Function.prototype) return v()
return v
}
const Button = ({color, text, onclick}) =>
button({style: () => `background-color: ${val(color)};`, onclick}, text)
const App = () => {
const colorState = van.state("green")
const textState = van.state("Turn Red")
const turnRed = () => {
colorState.val = "red"
textState.val = "Turn Green"
onclickState.val = turnGreen
}
const turnGreen = () => {
colorState.val = "green"
textState.val = "Turn Red"
onclickState.val = turnRed
}
const onclickState = van.state(turnRed)
const lightness = van.state(255)
return span(
Button({color: "yellow", text: "Click Me", onclick: () => alert("Clicked")}), " ",
Button({color: colorState, text: textState, onclick: onclickState}), " ",
Button({
color: () => `rgb(${lightness.val}, ${lightness.val}, ${lightness.val})`,
text: "Get Darker",
onclick: () => lightness.val = Math.max(lightness.val - 10, 0),
}),
)
}
van.add(document.body, App())
</code></pre><p><b>Demo:</b><span id="demo-poly-binding"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/tutorial/poly-binding">Try on jsfiddle</a></p><h2 class="w3-xxlarge w3-text-red" id="the-end"><a class="self-link" href="#the-end">The End</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>🎉 Congratulations! You have completed the entire tutorial of <b>VanJS</b>. Now you can start your journey of building feature-rich applications!</p><p>To learn more, you can:</p><ul><li>check out a list of <a href="/demo" class="w3-hover-opacity">sample applications</a> built with <b>VanJS</b>.</li><li>read the in-depth discussion of a few <a href="/advanced" class="w3-hover-opacity">advanced topics</a>.</li><li>check out how to build a <a href="/ssr" class="w3-hover-opacity">fullstack app</a> with SSR, CSR and hydration.</li><li>check out <a href="/x" class="w3-hover-opacity"><b>VanX</b></a> for more features: reactive list, global app state, server-driven UI, serialization, etc.</li><li>debug complex dependencies in your app via <a href="/graph" class="w3-hover-opacity"><b>VanGraph</b></a>.</li></ul><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>VanJS</b>:</p><ul><li><code class="symbol"><a href="#api-tags" class="w3-hover-opacity">van.tags</a></code></li><li><code class="symbol"><a href="#api-add" class="w3-hover-opacity">van.add</a></code></li><li><code class="symbol"><a href="#api-state" class="w3-hover-opacity">van.state</a></code></li><li><code class="symbol"><a href="#api-derive" class="w3-hover-opacity">van.derive</a></code></li><li><code class="symbol"><a href="ssr#api-hydrate" class="w3-hover-opacity">van.hydrate</a></code></li></ul></div>
<aside id="toc"><ul><li><a href="#dom" class="w3-hover-opacity">DOM Composition and Manipulation</a><ul><li><a href="#your-first-vanjs-app-a-simple-hello-page" class="w3-hover-opacity">Your first VanJS app: a simple Hello page</a></li><li><a href="#api-tags" class="w3-hover-opacity">API reference: van.tags</a></li><li><a href="#svg-and-mathml-support" class="w3-hover-opacity">SVG and MathML Support</a></li><li><a href="#api-tagsns" class="w3-hover-opacity">API reference van.tags (for elements with custom namespace URI)</a></li><li><a href="#specifying-options-of-createelement-or-createelementns" class="w3-hover-opacity">Specifying options of createElement or createElementNS</a></li><li><a href="#api-add" class="w3-hover-opacity">API reference: van.add</a></li><li><a href="#dom-nodes-already-in-the-document-tree-can-t-be-used-as-children" class="w3-hover-opacity">DOM nodes already in the document tree can't be used as children</a></li><li><a href="#fun-dom" class="w3-hover-opacity">Functional-style DOM tree building</a></li><li><a href="#on-event-handlers" class="w3-hover-opacity">on... event handlers</a></li></ul></li><li><a href="#state" class="w3-hover-opacity">State</a><ul><li><a href="#api-state" class="w3-hover-opacity">API reference: van.state</a></li><li><a href="#public-interface-of-state-objects" class="w3-hover-opacity">Public interface of State objects</a></li><li><a href="#state-val-is-immutable" class="w3-hover-opacity">State.val is immutable</a></li><li><a href="#derived-state" class="w3-hover-opacity">Derived state</a></li><li><a href="#api-derive" class="w3-hover-opacity">API reference: van.derive</a></li><li><a href="#side-effect" class="w3-hover-opacity">Side effect</a></li></ul></li><li><a href="#state-binding" class="w3-hover-opacity">State Binding</a><ul><li><a href="#state-typed-prop" class="w3-hover-opacity">State objects as properties</a></li><li><a href="#state-typed-child" class="w3-hover-opacity">State objects as child nodes</a></li><li><a href="#state-derived-prop" class="w3-hover-opacity">State-derived properties</a></li><li><a href="#state-derived-event-handlers" class="w3-hover-opacity">State-derived on... event handlers</a></li><li><a href="#state-derived-child" class="w3-hover-opacity">State-derived child nodes</a></li><li><a href="#removing-a-dom-node" class="w3-hover-opacity">Removing a DOM node</a></li><li><a href="#stateful-binding" class="w3-hover-opacity">Stateful binding</a></li><li><a href="#polymorphic-binding" class="w3-hover-opacity">Polymorphic Binding</a></li></ul></li><li><a href="#the-end" class="w3-hover-opacity">The End</a></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>