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:

Trix reactivity is built by tight integration with the companion reactive framework Trax.

Table of contents

  1. Overview
    1. At a glance
    2. Three APIs called Trix
      1. trix()
      2. trix(domNode)
      3. domNode.trix( ...children )
    3. Higher level components
    4. Trax integration for reactivity
    5. Usage
    6. Source code
  2. Tutorial
    1. The example from the overview
    2. Updating the list of child nodes
    3. Updating the list of child nodes (optimized)
    4. APP component
    5. Child nodes as arrays
    6. Node attributes
    7. Adding reactivity with Trax
    8. Capturing UI events
    9. Checkbox
    10. A better Radio component
    11. Selector component
    12. Maintaining locality
    13. Memory cleanup
    14. Blank canvas
  3. Trix APIs
    1. trix( )
    2. trix( node )
    3. trix( ...children )
  4. Sample applications
    1. TodoMVC
    2. Unit converter
    3. Javascript REPL
  5. Acknowledgements

Overview

At a glance

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

  1. 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.
  2. 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.

  3. 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...

Higher level 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);
                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

Overview example
                
                    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.

Changing the 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);
                
                    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 :-)

APP 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);
                
                    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.

Child node arrays
                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.

Attributes
                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.

Trax
                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, ...

UI events
                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.

Try playing around with this a bit:

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"

Checkbox
                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.

Radio 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, 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.

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

Locality
                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.

Improved locality
                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:

Locality using 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.

Memory leak
                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:

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.

Memory leak fixed
                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 :-)

Have fun
                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!