Skip to content

Classes and instances

The biblo is the class registry and the instance store. A class gives:

  • cells: the data of each instance. Each cell has an expression, and optionally a type (the class of a child instance) and bindings.
  • methods: the behavior, for example render, hydrate, dehydrate, splash, flow and deref.
  • extends: the parent class. The cells and the methods resolve through the extends chain, and the most specific one wins. A cycle in the chain stops at the first class that repeats.

instantiate(b, store, className, parentId?, bindings?) makes an instance. The instance gets an ID and a scope (self, parent and children). Its root node is a container with one slot for each cell. All nodes evaluate before the end of the function.

  • In a cell expression, ref("self", ...) and ref("parent", ...) become node IDs. A fn parameter with the name self stays a parameter.
  • A typed cell makes a child instance of its class. Its bindings are in the scope of the child: { value: ref("parent", "key") } binds the cell value of the child to the cell key of the parent.
  • A binding of the caller replaces a cell. A binding for a typed cell makes it a plain cell.

destroyInstance(b, store, id) is the inverse. It destroys the children first, then removes the nodes and detaches the instance from its parent. The readers of the removed cells become none in one epoch.

renameClass(b, from, to) renames a class. Its subclasses, the typed cells that use it and its live instances get the new name. moveChild(b, childId, index) changes the place of a child among the children of its parent.

updateClass(b, store, cls) registers a new version of a class, and the live instances follow it. For each instance of the class or of a subclass:

  • A cell that still has the old expression of the class gets the new expression.
  • A cell with an edit of its own, or a binding, keeps it.
  • A new cell is added to each instance, and a removed cell is removed.

Change the label of the class below. The instances without their own label follow the class. Change the label of one instance: that instance keeps its label after the next change of the class.

A class and three instances
instanceits own labeltext = concat(label, " × ", count)
A"star × 1"
B"star × 1"
C"star × 1"

A trait gives methods to each class with a set of cells, whatever its extends chain:

registerTrait(b, {
name: "Point",
requires: ["x", "y"],
methods: { summary: app("summaryView", ref("self", "classRef"), app("concat", lit("("), /* ... */ lit(")"))) },
});

Each cell and each typed cell (kid:Point) gets a unique prime, and the fingerprint of a class is the product of its primes. A trait applies when its fingerprint divides the fingerprint of the class (resolveTraits). Only the most specific traits count. When two of them give the same method, the method is ambiguous, and no trait gives it.

resolveMethods puts the traits between the root of the extends chain and the other classes of the chain. Thus a trait changes a default of Top, and a class changes a trait. The page level of detail shows the traits of the summaries.

classNodeOps(b) makes the NodeOps of a store from the classes. The splash, flow, deref and targets of a node come from the class that owns the node, through the extends chain. The targets op gives the nodes that a read goes through. A class with its own deref gives its own targets too, thus its reads stay reactive. The class Top gives the defaults, and each class can change them. Give the result to nodeStore:

const b = biblo();
const store = nodeStore({ nodeOps: classNodeOps(b), ops: standardOps });

The node layer knows nothing of biblo. This factory gives the class semantics to the node layer without a dependency in the wrong direction.