Deja Vu: A small JS framework for doodads

Deja Vu is a set of small libraries (each minfies and compresses to under a kilobyte, the whole bundle, uncompressed in any form is less than 6kb) under a meant to make it pleasant to make small JS doodads. There are currently 4 libraries:

deja

deja makes patching changes into the DOM concise:

        deja.attach(deja.q1("#app"), {
            "#my-app-title": {t: "Deja Example"},
            "#my-app-button": { on: { click: () => { console.log("clicked!"); } } },
        }
        
It also makes it easy to interact with DOM elements:

    const el = deja.attach({
        title: "#my-app-title",
        color: ["#my-app-title", "style.background-color"]
    })
    el.title = "new title";
    el.color = "blue";
         
Finally, deja makes it easy to use html template tags to build out more dynamic pages.

    <div id="todos"> </div>

    <template id="todo">
    <div class="todo">
    <input type="check" name="completed" />
    <input type="text" name="description"/>
    </div>
    </template>
            

    const todos = deja.q1("#todos");
    const addTodo = (description) => { 
        const newTodo = deja.from("#todo");
        const me = deja.attach(newTodo, {
            root: ".todo",
            "[name=description]": {t: description },
            "[name=completed]": { on: { click() { me.$root.remove() } } }
        });
        todos.append(newTodo);
    }
             

doptics

Doptics (or dom-optics) is a utility focused on answering the question of what the "default" value to get and set on a given DOM node should be. For the most part, this is mapped to `innerText`, but for form inputs and `output`, it's mapped to `value`. Doptics also lets you define your own lens types.


    doptics.add("number", (x) => x.toString(), (x) => parseNumber(x));
    doptics.add("date", (x) => x.toString(), (x) => x && new Date(...x.split("-")));
        

chalk

Chalk is a join-capable tuplespace. It's meant to facilitate communication between parts of a larger program. You can wait on a single message:


    const board = chalk();
    board.msg("sending", "a", "message");
    board.when("sending a $message", ({ message }) => {
        console.log("Got: " + message);
    });
    

You can wait for only the -first- message to match a pattern


    board.msg("only", "sent", "once");
    board.once("only sent once", () => {
        console.log("Got the message, now this handler will be removed");
    });

Being join-capable means you can wait for multiple message patterns. If you want to, you can also require those messages to share values.


    board.when("open $door with $key", "have $key", ({ door, key }) => {
        door.open(key);
    });

    let myKey = {};
    board.msg("have", myKey);
    let myDoor = { state: "closed", open(key) {
        if (key === myKey) {
            myDoor.state = "open";
        }
    }} 
    board.msg("open", myDoor, "with", myKey);

vu

vu is a list/table-managing wrapper around Deja. It provides a setup for managing a list of data wrapped up in an object that exposes methods for working with the list that sync changes to the DOM as well.


const board = chalk();
let tests_ui = vu({
    el: deja.q1("#suites"), 
    data: [],
    item({suite, tests}, suite_ui, become) {
        let results = [];
        for (const [name, t] of Object.entries(tests)) {
            try {
                t(); 
                results.push({ passed: true, name });
            } catch(ex) {
                results.push({ passed: false, name, reason: ex.message });
            }
        }
        become("#test-suite", {
            ".suite-name": {t: suite+": "},
            results: ".test-results",
        });
        let results_ui = vu({ el: suite_ui.$results, data: results, 
            item(result, _, become) {
                become("#test-result", {
                    ".test-name": {t: result.name},
                    ".test-pass-fail": { 
                        t: (result.passed ? "Passed" : "Failed: " + result.reason)
                    }
                }) 
            }}
        );
        board.when("filter is $filter", (_) => {
            results_ui.filter(r => r.name.includes(_.filter));
        });
    }
});

deja.attach("body", {
    "#search": { on: { input(e) { 
        board.msg("filter", "is", e.target.value);
    }}},
});

globalThis.test = (suite, tests) => {
    tests_ui.push({suite, tests});
}

// For global scope
globalThis.assert = (cond, msg) => { if (!cond) throw new Error(msg); };

Examples

All of the examples below are written with the expectation that you can view and read the source on them.