Trix a Javascript only reactive UI framework
Inspired by solutions like VanJS, SolidJS, Strawberry... Trix is a minimal yet fully functional javascript only reactive UI framework.
Trix was built with the following goals:
- Simple and minimal APIs
- Composable APIs as building blocks
- Trivial to build higher order abstractions and components
- Declarative, not imperative
- Support reactive behavior
- DOM nodes
- DOM node attributes
- DOM node child nodes
- Light weight
- Zero dependency
Trix reactivity is built by tight integration with the companion reactive framework Trax.
Table of contents
- Overview
- At a glance
- Three APIs called Trix
- trix()
- trix(domNode)
- domNode.trix( ...children )
- Higher level components
- Trax integration for reactivity
- Usage
- Source code
- Tutorial
- The example from the overview
- Updating the list of child nodes
- Updating the list of child nodes (optimized)
- APP component
- Child nodes as arrays
- Node attributes
- Adding reactivity with Trax
- Capturing UI events
- Checkbox
- A better Radio component
- Selector component
- Maintaining locality
- Memory cleanup
- Blank canvas
- Trix APIs
- trix( )
- trix( node )
- trix( ...children )
- Sample applications
- TodoMVC
- Unit converter
- Javascript REPL
- Acknowledgements
Overview
At a glance
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
const {H1, P, DIV, SPAN, EM} = trix();
trix('app').trix(
H1("Hello there!"),
P(`Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris iaculis, sapien sed interdum tincidunt,
massa libero pretium nibh, eu egestas velit elit sit amet leo.`),
DIV (
{
style: 'width:10em;height:5em;'
},
SPAN(
"Proin ",
EM({ style:'color:red'}, "non ligula"),
" quis urna egestas imperdiet a non dui."
)
)
);
Can you guess what this code might do?
Click the run button at the top right of the REPL window.
Three APIs called Trix
trix()
to create DOM node factory functions
const {H1, P, DIV, SPAN, TEXT} = trix();
let container = DIV(); // <-- container is a div DOM node
For readability, factory functions use ALL CAPS by convention.
trix( domNode )
to trixify a DOM node
const attachPoint = document.getElementById('attachPoint');
trix(attachPoint); // <-- trixify the node (adds a trix() method to the DOM node)
or let trix() lookup the node by id
trix('#attachPoint'); // <-- lookup + trixify the node
Note, all DOM nodes created using the factory functions are already trixified.
domNode.trix( ...children )
use the trix method added to the DOM node to set its list of child nodes
container.trix("I am a container div");
attachPoint.trix(
H1('This is an application with a container'),
container
);
Higher level components
At its base, Trix is mostly syntactic sugar around the DOM APIs that allows you to write in an 'HTML' readable way.
But as we are working in javascript and not HTML, we have the full power of a turing complete language to build higher level abstractions.
For example, to create a new EMAIL component that highlights the @ symbol in emails...
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
const {P, MARK, ADDRESS} = trix();
let p = P();
let content = p.trix;
trix('app').trix(p);
let EMAIL = (email) => {
let parts = email.split('@');
return ADDRESS([parts[0], MARK('@'),parts[1]]);
}
content(
EMAIL('myself@gmail.com'),
EMAIL('freddy@trixy.com')
)
Expand the code (top-left icon) to see the full context and how the content function was created.
After you go through the tutorial you should understand how this works.
Trax integration for reactivity
Trix uses the companion framework Trax for reactivity. Any API parameter can be a reactive Trax element.
Changes to the Trax elements will immediately be reflected in the UI.
The Trax nodes used in the UI are made Live by Trix
Application state is managed within the Trax elements and only declaratively linked to the DOM nodes.
Logically we end up with a similar paradigm to React Flux: UI event -> update to Trax element -> update to trixified DOM node
Trixified DOM nodes can be sources of UI events and directly mapped to Trax elements.
It's probably a good idea to take a quick look at the Trax overview at this time.
Usage
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trix } from 'https://0x1f528.github.io/Trax/modules/trax.js' // you will likely want this too for reactivity
Source code
The source code is available in github under https://github.com/0x1F528/Trix
Tutorial
Much of what you need to know was already covered in the Overview.
But let's walk through the steps one-by-one using an interactive REPL. There are still some details and edge-cases you will need to be aware of.
The example from the overview
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC); // <-- for UI reactivity ASYNC mode is recommended
const {H1, P, DIV, SPAN, TEXT} = trix();
let container = DIV(); // <-- is a div DOM node
const attachPoint = document.getElementsByTagName('app')[0];
trix(attachPoint); // <-- trixify the node (adds a trix() method to the DOM node)
container.trix("I am a container div"); // <-- set the container's child
attachPoint.trix(
H1('This is an application with a container'),
container
);
Note, the REPL already has a <app></app> tag, so we will use that instead of the attachPoint id from the overview.
Try adding a TEXT( 'Yoohoo' ) node after the container.
Updating the list of child nodes
Going forward we will hide the trix and trax imports in the context. Click the context icon top-left of the REPL window to see it.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
const {P, DIV} = trix();
let app = trix('app').trix(
P('First Line'),
P('Last Line')
);
setTimeout( () =>
app.trix(
P('First Line'),
DIV('In the middle'),
P('Last Line')
), 2000
)
After 2 seconds we change the list of child nodes.
Unfortunately in this case, we are deleting the existing nodes and re-injecting new ones.
Note that DIV('In the middle')
is the same as the two step let d = DIV();
d.trix('In the middle');
or finally DIV().trix('In the middle')
Updating the list of child nodes (optimized)
Changing the child nodes (optimized)
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
const {P, DIV} = trix();
let p1 = P('First Line');
let p2 = P('Last Line');
let app = trix('app').trix(
p1,
p2
);
setTimeout( () =>
app.trix(
p1,
DIV('In the middle'),
p2
), 2000
)
You can't tell from running this, but since we are using the same p1 and p2 DOM nodes when updating the list of child nodes
Trix will move nodes around and not rebuild and replace them.
APP component
Maybe not the most useful, but it makes for beautiful code :-)
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
const {DIV, P} = trix();
let APP = trix('app').trix;
APP(
DIV("I am a div"),
P("And a paragraph with nothing to say :-(")
);
We will use this APP() shortcut going forward; the definition will be hidden in the context.
Child nodes as arrays
As we have seen, the node.trix( ...children ) API can take a list of child nodes.
But it can also take a list of strings (=> Text Nodes), Trax elements, and/or arrays of child nodes.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {P} = trix();
let children = [
P('First Line'),
"Some text ",
[
"and an array of text ",
trax("with some text being wrapped "),
trax(["in"," a"," trax"," element"])
],
P('Last Line')
];
APP(
children
);
In fact, Trix will flatten the list/array of child nodes no matter how deep the arrays are nested.
As long as we use the same DOM nodes they will be rearranged based on the input and wont be destroyed and recreated.
Node attributes
To set a node's attributes, pass a javascript object (name:values) along with the list of child nodes.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {P, DIV} = trix();
APP(
DIV("Some text with ",
P(
"emphasis",
{style: "background-color:pink; font-size:3em;color:red"}
),
{ style: "background-color:blue;color:white;"}
)
);
Adding reactivity with Trax
The Trax reactive framework integrates into Trix.
UI nodes that are defined using Trax nodes are automatically updated when that Trax node updates.
Trax can hold values for text, attributes, and even Trix DOM nodes.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {P, DIV} = trix();
let text = trax("Emphasis"); // trax holds the text
let clazz = trax("bg-yellow"); // trax holds the class attribute
let div = DIV( text, { class: clazz } );
let node = trax(div); // trax holds the DOM node
APP(
node // add the node to the UI
);
setTimeout( () => text( "This is important" ), 2000 ); // change the text
setTimeout( () => clazz( "bg-pink"), 4000 ); // change the background color
setTimeout( () => node( P(text) ), 6000 ); // change from div to p
What happens if you add the line ~~ div.innerHTML = "Something Wonderful" ~~ at the end?
You still have a handle to the DOM nodes you created and can affect them directly using the standard DOM APIs.
How about adding ~~ div.trix("Something Wonderful") ~~ at the end?
Capturing UI events
Trax elements can also be used to capture UI events like clicks, input field updates, ...
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {FIELDSET, LEGEND, DIV, LABEL, INPUT, BUTTON, SPAN, BR} = trix();
let counter = trax(0).fct( (c, v) => (v||c) + 1 );
let my_text = trax('Change me');
APP(
DIV(
FIELDSET(
LEGEND("Inputs"),
LABEL( "Input One: ", { for: "input1" } ),
INPUT(
{
id:"input1",
io: my_text
}
),
BR(),
LABEL( "Input Two: ", { for: "input2" } ),
INPUT(
{
id:"input2",
io: my_text
}
)
),
BR(),BR(),
FIELDSET(
LEGEND("Counter"),
BUTTON(
"Click Me",
{
onclick: counter
}
),
SPAN(" The counter is ", counter )
)
)
);
There is a bit to unravel here.
- The LABEL One is associated with the INPUT one
- Both INPUT fields use the same Trax value for input and output
- Updating one field is immediately reflected in the other
- The "io" attribute is a special synthetic attribute managed by Trix; it corresponds to a combination of the two attributes
{ value: my_text, oninput: my_text }
- The value attribute sets the field to the value of my_text, and the oninput attribute updates the my_text Trax node
- We define a Trax counter element that will increment its value "v" (or initial "c" value) every time it is called; check out the documentation here
- The Click Me BUTTON triggers "counter" every time it is clicked.
- The SPAN shows the counter starting as 1 because displaying the initial counter value is already calling it once => and thus incrementing once
Try playing around with this a bit:
- Click on the two labels and see which INPUT field receives focus
- Update the text in one input field, then in the other
- Click on the increment button
- Try adding code to display the current my_text value in a span after the counter
- While reactive setting of attributes has to be done using Trax, event handling will work using any function; try replacing the onclick handler with your own function
Checkbox
We saw in the previous example that Trix implements a special "io" attribute so that a single Trax instance can be used both for input (aka value) and output (aka onchange).
Similarly, Trix also implements "checkbox" for radio buttons, selectors, and checkboxes: input: "checked", output: "onchange"
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {FIELDSET, LEGEND, LABEL, INPUT, BUTTON, SPAN, BR} = trix();
let CHECKBOX = (id, label, checker, ...args) =>
[
LABEL( {for:id}, label ),
INPUT( { type:"checkbox", checkbox:checker, id:id}, ...args ),
]
let RADIO = (id, label, group, chooser, ...args) =>
[
LABEL( {for:id}, label ),
INPUT( { type:"radio", checkbox:chooser, id:id, name:group}, ...args )
]
let checked = trax(true);
let chosen1 = trax(true);
let chosen2 = trax(false);
let chosen3 = trax(false);
APP(
FIELDSET(
LEGEND("Checkboxes"),
CHECKBOX( "checkbox1", "Checkbox One", checked ),
BR(),
CHECKBOX( "checkbox2", "Checkbox Two", checked ),
BR(),
"Checkboxes are ", SPAN(checked)
),
BR(),
FIELDSET(
LEGEND("Radio buttons"),
RADIO( "radio1", "Radio One", "rb", chosen1 ),
BR(),
RADIO( "radio2", "Radio Two", "rb", chosen2 ),
BR(),
RADIO( "radio3", "Radio Three", "rb", chosen3 ),
)
);
This example also shows how easy it is to create higher level components (aka javascript functions).
The CHECKBOX and RADIO functions create both the DOM node and the accompanying label.
A better Radio component
The previous Radio component is a bit awkward - we had to create a trax instance for each radio button. Let's improve this.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {FIELDSET, LEGEND, LABEL, INPUT, BUTTON, SPAN, BR} = trix();
let RADIO = (id, label, group, chooser, ...args) => {
let choice = trax(chooser, id).fct( (c,i) => c === i );
return [
LABEL( {for:id}, label ),
INPUT( { type:"radio", id:id, name:group, checked:choice, onchange: () => chooser(id) }, ...args )
]
}
let chosen = trax("radio1");
APP(
FIELDSET(
LEGEND("Radio buttons"),
RADIO( "radio1", "Radio One", "rb", chosen ),
BR(),
RADIO( "radio2", "Radio Two", "rb", chosen ),
BR(),
RADIO( "radio3", "Radio Three", "rb", chosen ),
BR(),
"The choice is ", SPAN(chosen),
BR(),
BUTTON(
"Set to Radio Two",
{
onclick: () => chosen("radio2")
}
)
),
);
In this case, instead of using the synthetic Trix checkbox attribute, we use the checked and onchange attributes separately.
The new choice Trax instance returns true/false depending if the chooser equals the current id.
We use this to determine if a particular radio button is checked.
As for the onchange behavior, we added an inline function to set the chooser value to the current id when selected.
An alternate implementation would be to use a form wrapping around the radio buttons and then setting the selection on the form.
Selector component
While we are at it, let's create a SELECTOR component.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {FIELDSET, LEGEND, LABEL, INPUT, BUTTON, SELECT, OPTION, SPAN, BR} = trix();
let SELECTOR = (id, label, selections, selected, ...args) =>
[
LABEL( {for:id}, label ),
SELECT(
{id:id, io: selected},
selections.map( (item) => OPTION( { value: item.value }, item.label )),
...args
)
]
let selected = trax("dog");
APP(
FIELDSET(
LEGEND("Selection"),
SELECTOR(
"pet_selector",
"Select a pet ",
[
{ value:"dog", label: "Dog" },
{ value:"cat", label: "Cat" },
{ value:"hamster", label: "Hamster" },
{ value:"parrot", label: "Parrot" },
{ value:"goldfish", label: "Goldfish" }
],
selected),
BR(),
"Pet is ", SPAN(selected),
BR(),
BUTTON(
"Set to Goldfish",
{
onclick: () => selected("goldfish")
}
)
)
);
Maintaining locality
One of the issues that comes up with large Trix applications is that we can end up with many Trax instances far removed from where they are actually used in the Trix DOM structure.
This makes following the code harder and can become a source of bugs.
A good practice is to keep the application logic together and the UI pieces together. Only that data that is needed by both should bridge across the two areas.
As an overly simplified - and thus not too compelling - example, let's take a string (in the application logic) and show the number of characters in the UI
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {P} = trix();
let str = trax("Lorem ipsum dolor sit amet, consectetur adipiscing elit");
let strlength = trax(str).fct( (s) => s.length );
APP(
P(str),
P(strlength)
);
The strlength value is a derived value only of interest to the UI. The str however is application wide logic and will bridge the application and UI.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {P} = trix();
let str = trax("Lorem ipsum dolor sit amet, consectetur adipiscing elit");
APP(
P(str),
P( trax(str).fct( (s) => s.length ) )
);
In a more complex situation we may want to use an iife:
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {P} = trix();
let str = trax("Lorem ipsum dolor sit amet, consectetur adipiscing elit");
APP(
(() => {
let filtered = trax(str).fct( (s) => s.replace(/[aeiou]/g, "") );
let strlength = trax(filtered).fct( (s) => s.length );
return [
P(str),
P( filtered ),
P( strlength )
];
})()
);
Try updating the code above so that you can change the str and see the output on the fly.
Memory cleanup
In most cases Trix (and Trax) will clean up after itself, but there are situations where memory leaks can occur.
Specifically, when you create Trax instances as part of component functions they may still be "attached" even after the component goes away.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
import { prettyJSON } from 'https://0x1f528.github.io/Trax/modules/trux.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {DIV, P, PRE, BUTTON} = trix();
let COMPONENT = (r, id) => {
let leaf = trax(r).id("leaf-"+id);
return DIV(
P(leaf),
P(leaf.id)
);
}
let root = trax("Some value").id("root");
let components = trax([
COMPONENT(root, 1),
COMPONENT(root, 2)
]);
let subscribers = trax().fct(
() => PRE( prettyJSON(root.subs()) )
)
let remove_component = () => {
components().pop(); // drop one of the components
components.fire(); // fire a refresh of the component list
subscribers.fire(); // fire a refresh of the JSON dependency tree
};
APP(
components,
BUTTON( { onclick: remove_component }, "Remove Component" ),
DIV(subscribers, { class:"bg-pink"})
);
Untangling the example:
- We have a single root Trax instance...
- ...that is wrapped in a new Trax instance leaf for each COMPONENT
- The COMPONENT is a div with two paragraphs: the first the leaf value, the second the leaf id
- We create an array of two COMPONENTs and wrap that in a Trax components instance
- We also create a subs Trax instance that shows the JSON dependency tree of root
- The root dependency tree is pretty printed in the pink div
- Observe the two components and the dependency tree showing two leaf nodes
- Click on the [Remove Component] button to remove the component containing leaf-2 from the array and notify dependents by calling fire()
- Observe that we only have a single components but still two leaf nodes in the dependency tree
Notice how the dependency tree still shows two leaf-1 and leaf-2 nodes even after the component has disappeared. This is a memory leak.
We can fix the memory leak by passing in a cleanup function using the Trix specific cleanup attribute.
When the DIV is removed from the DOM, Trix will also call any cleanup code provided. The Trax prune() method completely unhooks the instance from the dependency tree.
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
import { prettyJSON } from 'https://0x1f528.github.io/Trax/modules/trux.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
const {DIV, P, PRE, BUTTON} = trix();
let COMPONENT = (r, id) => {
let leaf = trax(r).id("leaf-"+id);
return DIV(
P(leaf),
P(leaf.id),
{ cleanup: () => leaf.prune() } // <-- this is the fix!
);
}
let root = trax("Some value").id("root");
let components = trax([
COMPONENT(root, 1),
COMPONENT(root, 2)
]);
let subscribers = trax().fct(
() => PRE( prettyJSON(root.subs()) )
)
let remove_component = () => {
components().pop(); // drop one of the components
components.fire(); // fire a refresh of the component list
subscribers.fire(); // fire a refresh of the JSON dependency tree
};
APP(
components,
BUTTON( { onclick: remove_component }, "Remove Component" ),
DIV(subscribers, { class:"bg-pink"})
);
This time you see that the JSON dependency tree in the pink div no longer contains leaf-2.
Here is a blank canvas for you to try things out :-)
import { trix } from 'https://0x1f528.github.io/Trix/modules/trix.js'
import { trax, Trax } from 'https://0x1f528.github.io/Trax/modules/trax.js'
Trax.onChange(Trax.MODE.ASYNC);
let APP = trix('app').trix;
// a bit of boilerplate to get you started:
const {FIELDSET, LEGEND, LABEL, INPUT, BUTTON, DIV, P, SPAN, BR} = trix();
APP(
DIV("Hello, I am a div")
)
Trix APIs
trix()
Creates a new Trix DOM generator.
trix( ): ( ...children: TDOM[] | string[] ) => TDOM (*)
- Returns
- ( ...children: TDOM Node[] | string[] ) => TDOM
- A function to create trixified DOM (aka TDOM) nodes.
Using JS Proxy, the variable name on the left hand side will be set to a factory function that creates TDOM node of the name of the variable.
For example, (**) DIV = trix() will create a factory function that when called will generate a <div> trixified DOM node and its children.
TEXT is a special DOM generator that creates HTML text nodes.
(*) : TDOM denotes a trixified DOM Node.
(**): Using ALL CAPS for TDOM factory functions is a readability convention.
trix( node )
This adds the trix() method to the DOM node. "It trixifies the node"
trix( node: string | DOM Node, index?: number ): TDOM
- node
- string | DOM node
- If a string is passed in, it will be used to look up a DOM node in the document.
If the string starts with "#" we are looking for a tag of that name;
if the string starts with "." we are looking for a class of that name;
otherwise we are looking for a tag of that name
If a DOM node is passed in, that is the node that will be trixified.
- index?
- number
- For tag name or class name node input, the index is the index of the matching document node to use. Default is 0.
- Returns
- TDOM
- The Trixified DOM Node; the TDOM will have a trix() function.
trix( ...children )
TDOM Method defining the list of children for the node.
TDOM.trix( ...children: TDOM[] | string[] | [ TDOM | string ] | Trax ): TDOM
- ...children
- TDOM[] | string[] | [ TDOM | string ] | Trax
- A trixified node, a string, a Trax element, or an array of the former.
- Returns
- TDOM
- The original TDOM (aka this)
Sample applications
To vette and demo the Trix solution I implemented several test applications.
TodoMVC
This is the Trix implementation of the wonderful TodoMVC initiative.
I used the modern vanillajs TodoMVC implementation as primary guide.
The TodoMVC demo is hosted as a github page and included below in an iFrame.
Unit converter
Inspired by the google unit converter widget it demonstrates some of the benefits of using pub/sub reactive programming integrated into a UI application.
It is a nice example of how using a pure javascript UI framework makes creating components and higher level abstractions easy.
The source code is in github, the demo is hosted as a github page, and included below in an iFrame.
Javascript REPL
This documentation makes extensive use of a REPL implemented in Trix around the codeflask (‡) code editor.
There is standalone demo version hosted on github pages and the source code is here.
Acknowledgements
(‡) The code editor is provided by CodeFlask. The accompanying REPL was built using Trix and Trax.
A special shout out to VanJS for being such an inspiration.
Trix lifts a lot of the ideas from VanJS. The main differences are the specifics of the APIs and the separate Trax reactive framework.
I recommend everyone take a look at the solution AND at the most beautiful, terse, and clean implementation!