**The Croquet Virtual DOM Framework** Last Modified: 2021-05-19 # Introduction The framework uses Croquet for the network communication part and the UI part is based on the document object model of the web browser. ## The Software Architecture of Croquet Applications written using Croquet have a clear separation of "model" and "view" and should be written with this in mind. As in common usage, the model refers to the logic part of the application. However, in the case of Croquet, it is more strictly expected to satisfy the constraint that "when a model is in a state S and receives a message M, the state S' after processing the message will be the same state that can be reliably reproduced from S and M, no matter what computer the model is running on." In order to satisfy this restriction there are some innovations, such as patching `Math.random()`, so that developers can write, in a natural way, "message handlers" that specify the processing for each message of a given type. The view mediates between such a model and the user. It displays the state of the model, and when a user performs any input action the view sends a message to the equivalent model for all users in the session, so that the model will change to the same new state for all users. The model is expected to be in the same state, but the views may be different for each client, because of, for example, differences in screen size, or because an operation that a user is doing may be visible for that user, before being sent to the model. The code describing the behavior of the view is allowed to read out the state of the model. Using an analogy with network programming in the server-client model, the view's code is the client program, but it is like being able to read out the state of the server directly from that program. This also contributes to the descriptiveness of Croquet. In other words, we follow the principle that "common information among clients should be stored in the model and other information should be stored in the view". ## Virtual DOM Although it is not impossible to build an application with the basic Croquet mechanisms described above, constructing a view using the Document Object Model (DOM) provided by the browser involves a laborious process of repeatedly writing trivial pieces of code. In a nutshell, given that the information held by the DOM elements used to display on the screen is also, in most cases, what needs to be shared among participating clients, we can reduce coding effort by keeping the DOM information used by the view in the model as virtual DOM elements, so that the handlers for user operations can be written just once in the model, and the system can automatically generate and update the actual DOM elements. The Croquet Virtual DOM Framework is implemented based on this perspective. ## How Behaviors are Described Applications are defined not simply as state, but also in terms of the "behaviors" that change that state. In Croquet's Virtual DOM Framework, the basic idea is to treat code as data, with users able to change applications' behaviors with code alterations at runtime. This shared authoring environment can also be disabled if no code changes are needed. In any case, the code can also be treated as data, which contributes to the simplicity of implementation. As shown in the example below, in this framework behaviors are written in accordance with certain conventions. Although the grammar includes the "class" keyword, these are actually class extensions, inspired by the language features known as traits and expanders. Simply put, they are collections of methods that do not change the virtual DOM element itself but act as external parts, adding behaviors that can be invoked using a special `call()` syntax. ## Concepts, Terminology ### Model As described above, a model is an object that is used for defining the logic part of an application. It causes state changes by receiving external messages. The word model may refer to the entire logic of the application, but when described as `Model`, it may also refer to individual instances of classes that inherit from the `Croquet.Model` class. In other words, it is normal for the logic part of an application to be made up of multiple `Model` classes, and to use multiple instances of those classes. ### View The view displays the state of the model on the screen, and also accepts input from the user and sends messages to the model. As with `Model`, when referring to `View` this can mean multiple instances of classes that each inherit from `Croquet.View`. ### Client Basically, a browser-based Croquet application. The client participates in a certain Croquet session. The clients participating in that session all share a model with identical state. ### Reflector A server on the network that is responsible for delivering messages from a view to all the clients participating in the session. ### Virtual DOM Element In the Croquet Virtual DOM Framework, instances of the `Element` class are created in the model and mimic the interface of a real DOM. For the part of the application that must be the same across multiple clients, consistency is maintained using updates to the properties of these elements. ### Virtual DOM View When an instance of `Element` is displayed on the screen, a view object called `ElementView` is created. `ElementView` holds a reference to an actual DOM element, and when its corresponding `Element` changes, the `ElementView` is responsible for making the DOM element reflect the change. Also, when an event fires in the actual DOM element, the `ElementView` sends it to the model as a Croquet message. ### Actual DOM Element What a virtual DOM view holds onto, and what the browser actually displays on the screen. ### Virtual DOM Element Reference A reference (pointer) to a Virtual DOM Element is internally represented by a data type called `ElementRef`. ElementRef introduces a simple indirection to accommodate a slippage between the view and the model. For example, imagine that a group of users are using a graphical collaborative application. And suppose that a user sees an element on screen and tries to move the element. By the time the request arrives to the model, the object may have been already deleted by another user. Such model code needs to look up the actual object from the information sent from the view. A ElementRef serves for this need. ### expander Behavior defined as a set of methods that can be "installed" in `Element` or `ElementView`. These are JavaScript classes written according to specific conventions. # Example 1 (Counter) Let's start with a simple example. First of all, I would like to refer to `examples/counter.js` and `counter.html` in the `app/` directory. As mentioned above, code is also data, but at the moment, it is more efficient to use a normal code editor when writing code. However, as you can see in `counter.js`, you need to write your code in accordance with certain conventions. ## Invocation First, copy the Croquet SDK file into a directory called `croquet. ~~~~~~~~ # mkdir -p croquet; curl -L -o croquet/croquet-latest.min.js https://unpkg.com/@croquet/croquet@0.5.0 ~~~~~~~~ The `-L` option specifies to follow redirection. You may copy the file from `https://unpkg.com/@croquet/croquet@0.5.0/pub/croquet.min.js`. Run `server.js` in the top directory from a terminal using `node`. ~~~~~~~~ # node server.js & ~~~~~~~~ If you don't have node.js installed but have Python 2 or Python 3, you can use the equivalent server written in Python. ~~~~~~~~ # python server.py & ~~~~~~~~ Both server programs use port 8000 by default so you can access an example at this location: ~~~~~~~~ http://localhost:8000/apps/counter.html ~~~~~~~~ If the fourth argument of `makeMain()` is a string, a random session name prefixed by `?q=` is attached to the URL. You can enter the same session from another browser by accessing this extended URL. ## File Organization Here we refer to a file like `counter.js` as a library file. A library is configured as follows: ```text <expander definition>... <function definition>... <class definition>... <export statement with "expanders", "functions" and "classes" properties> ``` In `counter.js` there is only one class definition: `Counter`. ~~~~~~~~ JavaScript class Counter { init() { this.addEventListener("click", "reset"); if (this._get("count") === undefined) { this._set("count", 0); this.future(1000).call("Counter", "next"); } console.log("Counter.init"); } next() { let c = this._get("count") + 1; this._set("count", c); this.value = "" + c; this.future(1000).call("Counter", "next"); } reset() { let c = 0; this._set("count", c); this.value = "" + c; } } function beCounter(parent, json) { let text = parent.createElement("TextElement"); text.style.setProperty("width", "200px"); text.style.setProperty("height", "50px"); text.setDefault("serif", 12); parent.appendChild(text); text.setCode(parent.getLibrary("counter.Counter")); } export const counter = { expanders: [Counter], functions: [beCounter] }; ~~~~~~~~ I'll explain it line by line below. ~~~~~~~~ JavaScript class Counter { ~~~~~~~~ This defines an expander named `Counter`. The keyword "class" is used to start a definition so that the syntax checker can handle the description. In other words, the syntax to write an expander is exactly JavaScript. Code written in a library file is processed as string data, so it is not allowed to refer to "free variables" from outside the expander. However, if you need access to other expanders' code, use the initialization-function `library` argument as described below. ~~~~~~~~ JavaScript init() { ~~~~~~~~ `init()` is the name of a special method which is executed automatically when the expander is "installed" on an element. (However, the expander may be edited in the authoring tool, and in that case, `init()` is called on the existing object each time the expander is edited). ~~~~~~~~ JavaScript this.addEventListener("click", "reset"); ~~~~~~~~ This adds an event handler for when a DOM click event occurs on the virtual DOM element where this expander is installed. Internally, a click handler is added to the actual DOM element, and when the user clicks, it is delivered to this expander. `"reset"` is the name of the method in the expander to be involved. You may explicitly specify the expander and the method name in the manner of: ~~~~~~~~ JavaScript this.addEventListener("click", "Counter.reset"); ~~~~~~~~ However, it is very rare to invoke a method from a different expander from where `addEventListener` is called, so the common case is just to specify the method name. Note that the `this` variable in the expander method refers to the virtual DOM element where it is installed, not to the expander itself. ~~~~~~~~ JavaScript if (this._get("count") === undefined) { this._set("count", 0); } ~~~~~~~~ As described above, `init()` may be called multiple times for already existing elements. Therefore, when you initialize a variable in `init()` and you want to keep the value even if the code changes a little while the same element survives, you should initialize it only when the value is not yet set. Checking against `undefined` here implements that. On the other hand, calling `addEventListener` for a virtual DOM element with the same arguments multiple times does not register multiple handlers. Here, the property `count` is defined for the virtual DOM element. `_get()` and `_set()` are methods to read and write the properties of a virtual DOM element. A virtual DOM element can hold only JSONizable values. Were they made readable and writable using simple dot notation, that would invite inadvertently storing inappropriate values, such as functions that have free variables. Instead, one must use the verbose `_get()` and `_set()` notation, chosen so as not to clash with internal property names, and must keep in mind that only JSONizable values can be stored. ~~~~~~~~ JavaScript this.future(1000).call("Counter", "next"); ~~~~~~~~ `future()` is like `setTimeout()`, with an argument specifying milliseconds of logical time. Based on the heartbeat sent by the reflector, all participating clients invoke the specified method after the specified time has elapsed. Normally, this logical time is synchronized with real time, so the elapsed real time is also about 1000 wall-clock milliseconds. In this case, the method is `call()`, meaning that the `next` method of the expander installed with the name `Counter` will be invoked. In the near future it is planned to make it possible to write just `this.future(1000).next()`, implicitly specifying that the method is on the same expander, as with other calls to expander methods. ~~~~~~~~ JavaScript next() { let c = this._get("count") + 1; this._set("count", c); this.value = "" + c; this.future(1000).call("Counter", "next"); } ~~~~~~~~ As shown above, `next()` is executed 1000 milliseconds after the first call to `init()`. After updating the `count` property with `_get()` and `_set()`, a stringified version is written to the special property `this.value` which is compatible with a DOM text element. (The fact that `this` is a text element is specified by `beCounter()`, shown below). The last line of `next` calls `next()` itself again. Like `requestAnimationFrame()` or setTimeout, this is a standard tactic for performing processing at a regular interval. ~~~~~~~~ JavaScript reset() { let c = 0; this._set("count", c); this.value = "" + c; } ~~~~~~~~ This is the method called when a click event occurs, as specified by `init()`. The `count` property is set to 0, and the `value` of the `text` element is set to "0". ~~~~~~~~ JavaScript function beCounter(parent, json, persistentData) { ~~~~~~~~ This is the function to create the virtual DOM element used by the application. As an analogy, you can think of it as embodying the exploratory work of interactively creating elements and setting styles and properties in an interface builder. `parent` is a virtual DOM element which is to be the parent of this "application". You can ignore `json` for the time being. ~~~~~~~~ JavaScript let text = parent.createElement("TextElement"); ~~~~~~~~ Virtual DOM elements are created using the `createElement()` of a virtual DOM element, instead of `document.createElement()`. The basic design policy is to use `div` elements for almost anything, so that as far as possible "everything is made of the same kind of object". However, since elements that provide some special features cannot be replaced by `div`, there are text elements, video elements, iframe elements, and canvas elements, which can be created by specifying a type in the argument. In this example, we are creating a virtual text element to display the string (though in this case there would be no problem with using a `div`, since this text does not have to be collaboratively editable). ~~~~~~~~ JavaScript text.style.setProperty("width", "200px"); text.style.setProperty("height", "50px"); ~~~~~~~~ A virtual DOM element has basic properties such as `style` and `classList`, which can be used to set the CSS style. How to set CSS classes will be explained later. ~~~~~~~~ JavaScript parent.appendChild(text); ~~~~~~~~ Use `appendChild()` to add elements created in this way to the DOM tree. ~~~~~~~~ JavaScript text.setCode("counter.Counter"); ~~~~~~~~ Then, we use the method `setCode` to set the behavior of the element, specifying the path to the expander called "Counter" within the "counter" library registered in the library created in the HTML file. You may call `setCode` with the actual code string of the expander, as opposed to the library path: ~~~~~~~~ JavaScript text.setCode(parent.getLibrary("counter.Counter")); ~~~~~~~~ The latter form is useful when the expander may be modified at runtime, but the common case is the former style. ~~~~~~~~ JavaScript export const counter = { expanders: [Counter], functions: [beCounter] }; ~~~~~~~~ A library file like `counter.js` exports objects with properties `expanders` and `functions`. The above `parent.getLibrary("counter.Counter")`, or `setCode("counter.Counter")` means to get the `Counter` expander from the library exported as `counter`. The `counter.html` that calls it looks like this: ~~~~~~~~ HTML <html> <head> <meta charset="UTF-8"> <script type="text/javascript" charset="UTF-8" src="croquet/croquet-latest.min.js"></script> <script type="module"> import {makeMain, Library} from "./src/framework.js"; import {counter} from "./examples/counter.js"; let library = new Library(); library.addLibrary("counter", counter); window.onload = makeMain("counter.beCounter", {autoSleep: false, appId: "io.croquet.vdom.counter", tps: 1}, library, "counter"); </script> </head> <body style="touch-action: none"> <div id="croquet-root" style="width: 100%; height: 100%"></div> </body> </html> ~~~~~~~~ Picking out some details: ~~~~~~~~ HTML <script type="text/javascript" charset="UTF-8" src="croquet/croquet-latest.min.js"></script> ~~~~~~~~ This loads the Croquet SDK file copied into the `croquet` directory. Alternatively, you can load a stable version of the SDK from `https://unpkg.com/@croquet/croquet@0.5.0`. ~~~~~~~~ JavaScript import {makeMain, Library} from "./src/framework.js"; import {counter} from "./examples/counter.js"; let library = new Library(); library.addLibrary("counter", counter); ~~~~~~~~ Importing `makeMain` from `main.js` and `Library` from `library.js` is the same for any application as long as it follows this pattern. Import your own library, `counter.js`, and register it to an instance of `Library`. The following line: ~~~~~~~~ JavaScript window.onload = makeMain("counter.beCounter", {autoSleep: false, appId: "io.croquet.vdom.counter", tps: 1}, library, "counter"); ~~~~~~~~ creates the function to be called as the `onload` handler, specifying the function registered as `counter.beCounter` as the entry point, with the `autoSleep` session option (described in the [Croquet SDK Document](https://croquet.io/sdk/docs)), and the `library` object that was created above. The last argument specifies the name of the session if it is a string, or if falsy will cause the page access to automatically create a new shared session and add a distinguishing session ID to the URL. # Development and Debugging of a New Application We are deliberately avoiding complicated JavaScript bundling mechanisms, transpilation and such machinery. Instead, we pursue a development style in which you can just edit a file and reload the page to try it out immediately. To write a new application, you can create a new file like `counter.js` above, and also make your own copy of `counter.html` to call it. ## `isLocal` flag Much of application development is about making visual adjustments and other model behavior checks, with no need to test out the sharing aspects, so working with just one client is enough. To simplify such a development cycle, if the query parameter `?isLocal` is specified in the URL, the application will be executed with the reflector's role replaced by a local emulation. In the case of `counter.html`: ~~~~~~~~ http://localhost:8000/counter.html?isLocal ~~~~~~~~ will allow the application's code to be run exactly as is, but without the need for a network. ## debugger At present, there is a problem in that code written as an expander goes through a string representation before being evaluated and executed, which means that a browser debugger cannot locate the original source location. The good news is that by inserting a `debugger` statement the debugger can be started, or if output is generated using `console.log` then clicking on the reported line numbers in the output allows reference to the code in a "VM..." pseudo file. In the future we would like to improve the situation, such as by allowing expander code to be inserted without going through a string representation whenever possible. # Example 2 (Drawing) As an example of an application with view code, here is a simple drawing program `drawing.js` and `drawing.html`. ~~~~~~~~ JavaScript class DrawModel { init() { this._set("color", "black"); this.subscribe(this.id, "line", "line"); this.subscribe(this.sessionId, "color", "color"); this.subscribe(this.sessionId, "clear", "clear"); this.subscribe(this.id, "pointerUp", "savePersistentData"); } ~~~~~~~~ This model has `color` and `lines` (described below) as state properties, and calls `subscribe()` to communicate with the view. The first and second arguments of `subscribe()` are the scope and the message name, which together constitute a key agreed among the objects interested in these messages. The third argument of `subscribe()` defines the method to be invoked when a message arrives. In the case of "line" messages, we want only the view object that knows about this particular model to send "line" messages. This is achieved by using the model's `this.id` as the scope; in effect, this declares that "only this model/view pair is interested in this message". The "color" message, by contrast, comes from a distinct virtual DOM element, and is to be received by all objects used in the session. Therefore as scope we use `this.sessionId`, a shared value that all the session's objects have access to. Croquet has a persistent-data mechanism to support code evolution. In essence, the developer has freedom to define a high-level application state description. When a new version of code is used, it can reconstruct the essential state from that data. In this drawing example, `lines` is deemed essential while `color` is not, and only the former is stored in the persistent data. Also, whenever a pointerup message is sent from any view, the model saves the persistent data. ~~~~~~~~ JavaScript line(data) { if (!this._get("lines")) { this._set("lines", []); } ~~~~~~~~ The "line" message is sent from the view to the model when the user makes a pointer movement on the screen to draw a line. A case to watch out for in Croquet is when a new client joins after other people have already performed some actions, such as - in this application - drawing some lines. This requires that the model hold information about past line segments, so here it is stored in a property called "lines". The property could be created in `init()`, but let's try creating it when the information for the first line segment arrives. ~~~~~~~~ JavaScript let line = {...data, color: this._get("color")}; this._get("lines").push(line); ~~~~~~~~ The object `{command: "line", from: {x,y}, to: {x,y}}` is sent as `data`. We augment it with `color`, and add the result to the "lines" property. ~~~~~~~~ JavaScript this.publish(this.id, "drawLine", line); } ~~~~~~~~ And now we send this object in a `drawLine` message, this time from the model to the view. It is important to remember that only JSONizable objects can be sent as messages. ~~~~~~~~ JavaScript color(color) { this._set("color", color); } ~~~~~~~~ When a "color" message is received, its value is stored in a property ready for the next "line" message. The `savePersistentData()` calls the Croquet API to create the persistent data, and `loadPersistentData()` fills in the `lines` property from the data. Persistent data could be arbitrarily complex, but it is good practice to keep it to a minimum. ~~~~~~~~ JavaScript class DrawView { init() { this.addEventListener("pointerdown", "pointerDown"); ~~~~~~~~ `DrawView` is an expander installed in the view that corresponds to the above `DrawModel`. A view expander can also register event handlers for DOM events using `addEventListener`. As of 2021, almost all common browsers support DOM PointerEvents. We strongly recommend that an application exclusively use pointer events, and not handle mouse events or touch events. ~~~~~~~~ JavaScript this.subscribe(this.model.id, "drawLine", "drawLine"); ~~~~~~~~ In the last line of `DrawModel.line()`, a message was sent using `this.publish(this.id, "drawLine", line);` with the model's `id` as the scope and "drawLine" as the name. The model sends a `cleared` message when it receives a `clear` message and resets the `lines` state. The view responds to the message and clears the canvas content. ~~~~~~~~ JavaScript this.subscribe(this.model.id, "cleared", "clear"); ~~~~~~~~ An `ElementView` stores the corresponding `Element` object itself as `this.model`, and can read values (including `id`) from the model freely. To receive "drawLine" messages, the `DrawView`'s `subscribe` specifies the same scope and the same name. This is how the sending and receiving of the "drawLine" message for a particular `DrawModel`/`DrawView` pair is set up. ~~~~~~~~ JavaScript this.initDraw(); } ~~~~~~~~ This calls the method `initDraw()`, which is defined by the same expander. ~~~~~~~~ JavaScript initDraw() { if (!this.model._get("lines")) {return;} this.model._get("lines").forEach(data => { this.drawLine(data); }); } ~~~~~~~~ The role of `initDraw()` is to make sure that the screen of a participant joining a session that was already running shows correctly any lines that have already been drawn. Since reading values from model objects is allowed, the virtual DOM view accesses the "lines" property from the object stored as `this.model`. If a value exists for the "lines" property, each line is drawn using `drawLine()` as if the participant had been in the session since the start. `drawLine()` is an expander method, but remember that a method within the same expander can be called using `this.`. The call `this.drawLine(data)` is equivalent to `this.call("DrawView", "drawLine", data)`, because the call itself appears in a `DrawView` expander method. ~~~~~~~~ JavaScript pointerDown(evt) { ~~~~~~~~ This is the method registered in `init()` as an event handler. ~~~~~~~~ JavaScript if (evt.buttons !== 1) {return;} this.setPointerCapture(evt.pointerId); ~~~~~~~~ We want to track pointermove events only between the pointer button being pressed and being released, and during that time we need to handle them even if the pointer leaves the DOM element's display area, and to handle a pointerup even if it occurs outside the area. Therefore, pointermove and pointerup handlers are set up afresh on pointerdown and released on pointerup. The `ElementView` class has `setPointerCapture()` method that utilizes the browser's pointer capture feature. The `pointerMove` method handles the drawing action. ~~~~~~~~ JavaScript if (this.lastPoint) { let p = {x: evt.offsetX, y: evt.offsetY}; this.publish(this.model.id, "line", {command: "line", from: this.lastPoint, to: p}); this.lastPoint = p; } ~~~~~~~~ In this `pointerMove`, if the view finds a property `lastPoint` it publishes a message "line" so as to draw a line from `lastPoint` to the current pointermove event's `(offsetX, offsetY)`. The scope is `this.model.id`, so as usual it will only be delivered to objects that specify the same scope. Finally, `lastPoint` is updated to the new pointer position. In the case of views, you can store arbitrary objects in properties, using the standard `this.propertyname` syntax. However, accidentally using a property name that is also used by the system will cause problems, so we may take some action on this in the future. ~~~~~~~~ JavaScript pointerUp(_evt) { this.removeEventListener("pointermove", "pointerMove"); this.removeEventListener("pointerup", "pointerUp"); this.lastPoint = null; this.releaseAllPointerCapture(); this.publish(this.model.id, "pointerUp"); } ~~~~~~~~ In `pointerUp`, the handlers temporarily added are removed and `lastPoint` is also cleared. The `releaseAllPointerCapture()` is a convenience method to invoke the browser's `releasePointerCapture()` to clear captured pointers. ~~~~~~~~ JavaScript let offsetX = evt.offsetX; let offsetY = evt.offsetY; this.lastPoint = {x: offsetX, y: offsetY}; this.addEventListener("pointermove", this.pointerMove); this.addEventListener("pointerup", this.pointerup); ~~~~~~~~ Still in `pointerDown`, we set `lastPoint` to the place where the pointerdown happened. In addition, we register two functions as transient event handlers until the pointerup occurs. What should be noted here is the actual type of events being sent. The events delivered through the virtual DOM system are "cooked", with objects containing only somewhat abstract values in a way that absorbs client differences as much as possible. You can actually register event handlers directly with the real DOM elements, and, by doing so, you can handle actual "uncooked" DOM events. Either the program should use values chosen specifically to avoid inconsistencies, such as always using `offsetX`, or call `this.cookEvent()` to convert an event to a virtual event. ~~~~~~~~ JavaScript drawLine(data) { let ctx = this.dom.getContext("2d"); ctx.lineWidth = 2; ctx.strokeStyle = data.color; ctx.beginPath(); ctx.moveTo(data.from.x, data.from.y); ctx.lineTo(data.to.x, data.to.y); ctx.stroke(); } ~~~~~~~~ Using the data in a "drawLine" message sent from the model, draw on the actual DOM element stored in `this.dom`. ~~~~~~~~ JavaScript class Color { init() { this.addEventListener("click", "color"); this.style.setProperty("background-color", "black"); } ~~~~~~~~ This file defines another expander, called `Color`. It is installed in a certain model. In `init()`, the method `color` of the expander is specified to be called when a click event occurs, and the virtual-DOM style property `background-color` is set to `black`. ~~~~~~~~ JavaScript randomColor() { let h = Math.floor(Math.random() * 256); let s = "100%"; let l = "80%"; return `hsl(${h}, ${s}, ${l})`; } ~~~~~~~~ `randomColor()` is used to get a random color from a hue ring. Because this `Math.random()` is executed as model code, it is guaranteed that however many times clients call it, they will all be obtaining exactly the same sequence of values. ~~~~~~~~ JavaScript color() { let color = this.randomColor(); this.style.setProperty("background-color", color); this.publish(this.sessionId, "color", color); } ~~~~~~~~ The `color` method is executed when this virtual DOM element is clicked. `this.randomColor()` calls the `randomColor()` method defined by the same expander, and the `background-color` that was set to `black` in `init()` is replaced with the new color. The new color is then also published in a "color" message, using `this.sessionId` as the scope. `sessionId` is shared across the whole application, so any object in the application can subscribe to messages that have this scope. As shown above, in this example all `DrawModel`s subscribe to these "color" messages, and respond by changing their "color" property. ~~~~~~~~ JavaScript function beDrawing(parent, json, persistentData) { let top = parent.createElement(); let canvas = parent.createElement("CanvasElement"); canvas.setCode("drawing.DrawModel"); canvas.setViewCode("drawing.DrawView"); let color = parent.createElement(); color.setCode("drawing.Color"); ~~~~~~~~ `beDrawing`, like the earlier `beCounter`, can be thought of as code that simplifies the process of assembling the DOM tree interactively. The `canvas` variable is assigned a `CanvasElement` value, and `color` a `div` element. The `CanvasElement`'s model code is set to `DrawModel` code obtained from `library`, and its view code to the library's `DrawView`. Because `color` is a common behavior across all clients, it is only given model code. ~~~~~~~~ JavaScript color.style.setProperty("width", "60px"); color.style.setProperty("height", "60px"); color.style.setProperty("border-radius", "50%"); ~~~~~~~~ The styles of individual elements can also be set in this way. ~~~~~~~~ JavaScript color.domId = "color"; color.setStyleClasses(` #color:hover { border: 2px dotted white; } #color { border: 2px solid white; }`); ~~~~~~~~ In addition, you can use a special method called `setStyleClasses` to pass as a string a CSS block with CSS selectors. The property `domId`, renamed because the property `id` is already reserved by Croquet, sets the DOM `id` property. The CSS block specifies the style for that id, with and without the `:hover` pseudo-class. ~~~~~~~~ JavaScript top.style.setProperty("display", "flex"); top.style.setProperty("width", "550px"); top.appendChild(canvas); top.appendChild(color); parent.appendChild(top); if (persistentData) { canvas.call("DrawModel", "loadPersistentData", persistentData); } return parent; ~~~~~~~~ These elements are made into a tree structure with `appendChild()` and added to `parent`. ~~~~~~~~ JavaScript export const drawing = { expanders: [DrawModel, DrawView, Color, ClearButton], functions: [beDrawing] }; ~~~~~~~~ Everything is registered in the library `drawing`. `beDrawing()` will be called through `makeMain` in drawing.html. # Example 3 (Video Chat) In `apps/video-chat.html` and `examples/video-chat.js` you can find an example of video chat using SkyWay, a service provided by NTT. The file `skyway/skyway-latest.js` is a SkyWay library. To run this example, get an API key from [SkyWay](https://webrtc.ecl.ntt.com/developer.html) and create a file called `skyway/key.js` using the actual key you obtained. ~~~~~~~~ JavaScript export const key = 'abcd0123-abcd-aaaa-0000-012312012abc'; ~~~~~~~~ In the video chat example, it's important to note that _almost nothing shown on the screen is being shared through Croquet_. The connection status, layout of the individual videos, etc. are also different for each client. That's why `examples/video-chat.js` only defines a view expander which, when notification of a new participant arrives from SkyWay, creates a new actual video element and sets its `srcObject` property. Another thing to note is that you cannot do JavaScript asynchronous processing in the model, because it would cause the timing of model execution to differ between participating clients. In this example, the SkyWay library and `key.js` are loaded with dynamic import, and even if such values are to be used purely by model code, it is essential that they be loaded by view-side code, and then sent to the model using messages. # Example 4 (Text Chat) In `apps/text-chat.html` and `examples/text-chat.js` you can find an example of a simple text chat. A notable feature introduced in the example is a way to specify the CSS class strings to control the appearance of the application. The initialization function `beChat` calls the `setStyleClasses()` method on `chat` element. The selectors matches with the `domId` property of the virtual DOM element that will become the `id` property of the actual DOM element, and the CSS class names added by `classList.add()`. The `setStyleClasses()` method specifies a CSS string that persists until as long as the element stays in the DOM scene tree. For a certain kind of applications, such CSS strings can be attached to different elements instead of having one large string for the top-level element. In this particular case, however, where many similar elements with the same class will be added and removed from the DOM scene tree, it is more concise to just have a single description at the top level.