X

VanX: The 1.2kB Official VanJS Extension

📣 VanX 0.4.0 brings lots of new features →

VanX is the official extension of VanJS, which provides handy utility functions. VanX makes VanJS more ergonomic for certain use cases and its developer experience closer to other popular UI frameworks. Like VanJS, VanX is also ultra-lightweight, with just 1.2kB in the gzipped minified bundle.

Installation


VanX is published as NPM package vanjs-ext. Run the following command to install the package:

npm install vanjs-ext

Add this line to your script to import the package:

import * as vanX from "vanjs-ext"

You can also import individual utility functions you're going to use:

import { <functions you want to use> } from "vanjs-ext"

Alternatively, you can import VanX from CDN via a <script type="text/javascript"> tag:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/van-x.nomodule.min.js"></script>

https://cdn.jsdelivr.net/npm/[email protected]/dist/van-x.nomodule.js can be used for the non-minified version.

Note that: VanJS needs to be imported via a <script type="text/javascript"> tag for VanX to work properly.

To get TypeScript support for <script> tag integration, download van-x-0.6.1.d.ts and add the following code at the top of your .ts file:

import type * as vanXType from "./van-x-0.6.1.d.ts"

declare const vanX: typeof vanXType

vanX.reactive: Reactive Object to Hold Many Individual States


vanX.reactive provides an ergonomic way to define a single reactive object where each of its individual fields corresponds to an underlying State object. For instance:

const obj = vanX.reactive({a: 1, b: 2})

defines a reactive object with the following underlying state fields:

{a: van.state(1), b: van.state(2)}

The reactive objects defined by vanX.reactive can be deeply nested. For instance:

const obj = vanX.reactive({
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
})

defines a reactive object with the following underlying state fields:

{
  a: van.state(1),
  b: van.state({
    c: van.state(2),
    d: van.state(3),
  }),
}

Getting and setting values of the underlying states can be simply done by getting / setting the fields of the reactive object. For instance, obj.b.c is equivalent to what you would have to write obj.b.val.c.val had the underlying state object been accessed.

A practical example

Now, let's take a look at a practice example on how vanX.reactive can help group multiple states into a single reactive object in your application:

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"),
  )
}

Demo:

Try on jsfiddle

Note that, not only you can set the value of each individual leaf field, you can also set the entire object of the name field, as what's being done in the onclick handler of the Reset button:

button({onclick: () => data.name = {first: "Tao", last: "Xin"}}, "Reset")

⚠️ Caveat: 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.

⚠️ Caveat: 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.

API reference: vanX.reactive

SignaturevanX.reactive(obj) => <the created reactive object>
DescriptionConverts the input object obj into a reactive object.
Parameters
  • obj - Can be a plain object or an object of an existing JavaScript class. obj can have deeply nested fields. The original obj shouldn't be accessed anymore after the vanX.reactive(obj) call.
ReturnsThe created reactive object.

⚠️ Caveat: The passed-in obj object shouldn't have any State fields. Doing so will result in states of other State objects, which is invalid in VanJS.

Calculated fields

You can specify calculated fields (similar to derived states in VanJS) via vanX.calc. The example above can be rewritten to the code below:

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"),
  )
}

Demo:

Try on jsfiddle

⚠️ Caveat: Avoid self-referencing when specify calculated fields. For instance, the code below:

const data = vanX.reactive({
  name: {first: "Tao", last: "Xin"},
  fullName: vanX.calc(() => `${data.name.first} ${data.name.last}`),
})

will lead to ReferenceError as data variable is not yet defined when the calculation function is being executed. As shown in the example above, it's recommended to define calculated fields in a separate reactive object.

API reference: vanX.calc

SignaturevanX.calc(f) => <the created calculated field>
DescriptionCreates a calculated field for a reactive object based on the calculation functionf.
Parameters
  • f - The calculation function.
ReturnsThe created calculated field.

Get the underlying State object

Sometimes, it's desirable to get the underlying State objects for fields in a reactive object. This can be achieved with vanX.stateFields. The example above can be modified to use the underlying state field instead of the binding function for Full name:

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"),
  )
}

Demo:

Try on jsfiddle

Note that, stateFields only gets the underlying state fields for one layer of the reactive object. For instance, to get the state field for First name, you need to write:

vanX.stateFields(vanX.stateFields(data).name.val).first

API reference: vanX.stateFields

SignaturevanX.stateFields(obj) => <an object for all underlying state fields of obj>
DescriptionGiven a reactive object obj, returns an object for all the underlying state fields of obj. For instance, if obj is {a: 1, b: 2}, {a: van.state(1), b: van.state(2)} will be returned.
Parameters
  • obj - The input reactive object.
ReturnsAn object for all the underlying state fields of obj.

Get the raw field value without registering the dependency

Requires VanX 0.3.0 or later.

Similar to the rawVal property of VanJS states. You can use vanX.raw for getting the raw field value without registering the dependency. For instance:

data.s = vanX.calc(() => vanX.raw(data).a + data.b)

will make data.s updated when data.b changes, but data.s won't be updated when data.a changes. The same effect goes to derived states and side effects registered via van.derive as well as State-derived DOM nodes.

Note that, vanX.raw can access deeply nested fields without registering the dependency (this requires VanX 0.4.0 or later). For instance, you can use vanX.raw(data).a.a to access the field data.a.a without registering the dependency.

API reference: vanX.raw

SignaturevanX.raw(obj) => <an object for getting the field values of obj without registering the dependency>
DescriptionGiven a reactive object obj, returns an object whose field values equal to the field values of obj, but accessing its fields won't register the dependency.
Parameters
  • obj - The input reactive object.
ReturnsAn object with which you can get the field values of obj without registering the dependency.

Add reactivity to existing JavaScript classes

It's possible to add reactivity to objects of existing JavaScript classes with the help of vanX.reactive. For instance, the code below adds the reactivity to a Person object:

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"),
  )
}

Demo:

Try on jsfiddle

⚠️ Caveat: Once an object is turned reactive with vanX.reactive, you shouldn't access the original object anymore. Doing so will create the same issue as aliasing.

⚠️ Caveat: 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: #156.

vanX.noreactive: exemption from reactivity conversion

Requires VanX 0.6.0 or later.

Sometimes it's desirable to exempt certain fields from being converted into reactive objects. For instance, for the reactive array below:

const data = vanX.reactive([
  vanX.noreactive(new ArrayBuffer(8)),
  vanX.noreactive(new ArrayBuffer(16)),
])

we will treat the ArrayBuffers in data 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 can't be correctly converted into reactive objects. ArrayBuffer is one example as wrapping it around a Proxy will cause problems.

Below is the whole example that illustrates how vanX.noreactive helps a reactive array of ArrayBuffer being used in the application:

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")),
)

Try on jsfiddle

API reference: vanX.noreactive

SignaturevanX.noreactive(obj) => <the object exempted from reactivity conversion>
DescriptionMarks an object so that it won't be converted into a reactive object.
Parameters
  • obj - The input object which you want to exempt from being converted into a reactive object.
ReturnsThe object exempted from reactivity conversion.

A comprehensive example

You can refer to this file for a comprehensive demo of all the features regarding to reactive objects discussed above. You can preview the app via CodeSandbox.

vanX.list: Reactive List that Minimizes Re-rendering on Updates


vanX.list 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 Array for non-keyed input, or a plain object for keyed input.

Let's first take a look at some simple examples.

Array for non-keyed input:

const items = vanX.reactive([1, 2, 3])
return vanX.list(ul, items, v => li(v))

Plain object for keyed input:

const items = vanX.reactive({a: 1, b: 2, c: 3})
return vanX.list(ul, items, v => li(v))

In both examples, <ul><li>1</li><li>2</li><li>3</li></ul> will be returned.

You can add, update, and delete entries in the reactive object items, 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 Array example:

++items[0]
delete items[1]
items.push(4)

the rendered UI elements will be updated to <ul><li>2</li><li>3</li><li>4</li></ul>.

For keyed object, the following changes will produce the same result:

++items.a
delete items.b
items.d = 4

In addition, for Array-based input items, you can call shift, unshift and splice 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:

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)

dom will become <ul><li>4</li><li>5</li><li>3</li></ul>.

API Reference: vanX.list

SignaturevanX.list(container, items, itemFunc) => <the root element of the created DOM tree>
DescriptionCreates a DOM tree for a list of UI elements based on the input reactive object items.
Parameters
  • container - Can be a tag function or a DOM element for the container element of the list of UI elements.
    • If container is a tag function, such as van.tags.ul it means we want to create a <ul> element as the container of the list. Indeed, any function that returns a DOM element can be passed as the container argument.
    • (requires VanX 0.4.0 or later) If container 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 div({class: "item-list"}) for <div class="item-list">.
  • items - A reactive object that holds the data for the list. Can be an Array (for non-keyed input) or a plain object (for keyed input).
  • itemFunc - The function ((v, deleter, k) => Node) that is used to generate the UI element (or rarely, text node) for each list item. The function takes the following parameters:
    • v - A State object corresponding to each list item. You can directly use it as a State-based property / child node, read its value for building the UI element, and/or set its value in some event handlers.
    • deleter - a function (() => void) that can be used in the event handler to delete the entire item. Typically the deleter function can be used as the onclick handler of a deletion button.
    • k - (requires VanX 0.2.0 or later) the key of the corresponding list item, which is the index if items is an Array or the property key if items is a plain object.
ReturnsThe root element of the created DOM tree.

A simplified TODO App

Now, let's take a look at a practical example: The Fully Reactive TODO App in VanJS by Example page can be re-implemented with the help of vanX.list. We can see how a 40+ lines of code is simplified to just over 10 lines:

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}, "❌"),
    )),
  )
}

Demo:

Try on jsfiddle

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 vanX.reactive.

Holes in the array

Deleting items in the reactive array will create holes inside the array, which is an uncommon situation in JavaScript. Basically, if we execute the following code:

const a = [1, 2, 3]
delete a[1]

a will become [1, empty, 3]. Note that, empty is different from undefined. When we do:

for (const key in a)

or use higher-order functions like map or filter, holes will be skipped in the enumeration.

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: [1, 2, 3, 4, 5], and the 3rd item is deleted by the user. If we allow holes, the array will become [1, 2, empty, 4, 5]. 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 [1, 2, 4, 5], then we need 3 DOM updates:

  1. 3rd DOM element: 3 -> 4
  2. 4th DOM element: 4 -> 5
  3. Remove the 5th DOM element.

In the TODO app above, we are calling vanX.compact which recursively removes holes in all arrays of the input reactive object before serializing items to the JSON string via JSON.stringify. This is because holes are turned into null values in the result JSON string and cause problems when the JSON string is deserialized (See a detailed explanation here).

⚠️ Caveat: Because of holes in the reactive array, the length property can't reliable tell the number of items in the array. You can use Object.keys(items).length instead as in the example below.

vanX.replace: Update, Insert, Delete and Reorder Items in Batch


In addition to updating the items object one item at a time, we also provide the vanX.replace function that allows you to update, insert, delete and reorder items in batch. The vanX.replace function takes a reactive object - obj, and a replacement object (or a replacement function) - replacement, as its input parameters. vanX.replace is responsible for updating the obj object as well as UI elements bound to it based on the new data provided by replacement. Let's take a look at a few examples:

// 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}]))

API reference: vanX.replace

SignaturevanX.replace(obj, replacement) => obj
DescriptionUpdates the reactive object obj and UI elements bound to it based on the data provided by replacement.
Parameters
  • obj - The reactive object that you want to update.
  • replacement - Can be a plain array / object, or a function.
    • (requires VanX 0.4.0 or later) If replacement is a plain array / object, directly update obj with the values provided in replacement.
    • If replacement is a function, it will take the current values of obj as input and returns the new values of the update. The input parameter of the function depends on the type of obj. If obj is an array (for non-keyed data), replacement will take its values as an array (after eliminating holes) and return the updated values as another array. If obj is a plain object (for keyed data), replacement will take its values as an array of key value pairs (the data you would get with Object.entries(items)) and return the updated values as another array of key value pairs.
Returnsobj

⚠️ Caveat: Calculated fields are not allowed in obj and replacement.

Example 1: sortable list

Let's look at a sample app that we can build with vanX.list and vanX.replace - 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:

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 "!"'),
    ),
  )
}

Demo:

Try on jsfiddle

Example 2: an advanced sortable TODO list

Now, let's take a look at a more advanced example - a sortable TODO list, which is implemented with keyed data. i.e.: reactive items 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 localStorage thus the state is preserved across page loads.

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 "!"'),
    ),
  )
}

Demo:

Try on jsfiddle

vanX.list for calculated fields

Requires VanX 0.4.0 or later.

vanX.list can take a calculated field as items parameter. Whenever the calculated field is updated, vanX.replace 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:

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)),
  )
}

Try on jsfiddle

Global App State and Serialization


Requires VanX 0.4.0 or later.

With VanX, 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 TODO App above, which allows the text of the input box together with all TODO items to be persisted in localStorage:

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}, "❌"),
    )),
  )
}

Demo:

Try on jsfiddle

Note that calculated fields are still recommended to be stored separately, to avoid issues like self referencing or calculated fields being replaced.

Smart diff / update in vanX.replace

When vanX.replace updates the reactive object obj, it will traverse the entire object tree, do a diff between replacement and obj, and only update leaf-level fields with different values. Thus, you can call vanX.replace to replace the entire app state object, and VanX guarantees at the framework level that the minimum amount updates are applied to the reactive object and thus the DOM tree bound to it.

For instance, if appState in the example above has the following value:

{
  "input": "New Item",
  "items": [
    {"text": "Item 1", "done": true},
    {"text": "Item 2", "done": false}
  ]
}

Calling

vanX.replace(appState, {
  input: "New Item",
  items: [
    {text: "Item 1", done: true},
    {text: "Item 2", done: true},
  ]
})

will only get the done field of 2nd element in items updated. i.e.: it's equivalent to appState.items[1].done = true.

Because of the smart diff / update mechanism, it's usually more preferable to use vanX.replace instead of direct assignment to update the object-valued reactive fields. i.e.: prefer:

vanX.replace(data.objField, <new value>)

instead of

data.objField = <new value>

Server-driven UI (SDUI) with VanX

The smart diff / update mechanism in vanX.replace enables a new spectrum of modern programming paradigms, such as server-driven UI, where the server sends the entire global app state to the client via JSON or other forms. vanX.replace 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.

Below is a sample Chat app which receives the updates of app state completely from server. Note that with vanX.replace, only necessary DOM elements will be re-rendered upon receiving the server events:

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)),
  )
}

Try on jsfiddle

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 server-sent events, WebSocket messages, or HTTP polling.

Serialization app state and vanX.compact

You can serialize the entire app state into a single string, via JSON.stringify or protobuf. As mentioned in a previous section, holes that might appear in reactive arrays need to be eliminated. vanX.compact 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.

API reference: vanX.compact

SignaturevanX.compact(obj) => <a new object with holes in all arrays eliminated>
DescriptionTraverse the entire object tree of the input reactive object obj and returns a new object with holes in all encountered arrays eliminated. The input object obj remains unchanged.
Parameters
  • obj - The input reactive object.
ReturnsA new object with holes eliminated.

API Index


Below is the list of all top-level APIs in VanX: