View on GitHub

Tech, Games and Whisky

The Sebr's Blog

spui

|

They say every reader has a book inside them (or something along those lines). I think every web developer has a ui framework in themselves. And it is part of a rite of passage to create that ui library to put it up on JS Benchmark Framework or on TodoMVC and then to get on with your life.

I can now say i have completed this chore/task/rite of passage. I am now fulfilled, completed and enlightened. Here I present:

SPUI

Directly from the documentation:

SPUI is a library using an hyperscript syntax that helps create dom ui and automatically update when model changes. SPUI is fully DOM based (NOT vdom).

This is my take at creating a web framework. I wanted something that would feel like when I write Mithril.js code using m.prop. But that would NOT be vdom based.

Small aparte

For Stingray we had a lot of Mithril code written. Sometimes, we would run into performance problem since our virtual views were really big to generate. It would happen especially with controls that would update multiple times per second like spinner and such.

Granted we used an old version of mithril (0.24) it still could become a problem for the kind of application we were building. Also it put a lot of burden on how you needed to write your render code. You had to try to never allocate memory, try not to create new closures or else this could mean hell in terms of performance.

That said Mithril was a million times better than angular :)

Back to SPUI

That is why, I wanted to create a DOM based library. The view would get created once and then you would use specific constructs (streams and listeners) to automatically udpate the DOM when your model would change.

To give you a taste of SPUI here is the Getting Started example taken directly from the documentation page:

Getting Started

SPUI is basically an hyperscript function named h and a stream api to notify the DOM when model changes. The best way to dive into SPUI is to look at a dead simple TODO application example:

First we create a stream that will store whatever the user types in an <input> field:

const newTitle = sp.valueStream('');

Then we create an ObservableArray that will store our list of Todo models. When this list is mutated (if we add or remove from it) the DOM will be notified and updated accordingly:

const todos = new sp.ObservableArray();

Now let’s add a function to add new todos to our list:

function addTodo() {
    if (newTitle()) {
        todos.push(createTodo(newTitle()));
        newTitle('');
    }
};

Notice how our stream newTitle is a getter/setter function. When called without argument it returns its backing value. When called with a single argument, it updates its backing value and notify all listeners about it.

We then add an helper function to create new todo:

function createTodo (title: string, done = false) {
    return {
        title: sp.valueStream(title),
        done: sp.valueStream(done)
    };
}

Notice how title and done are implemented as streams: this means modifying thoses values will trigger a notification that will keep the DOM up to date.

It is now time to create a view using the hyperscript h function:

const view = h('div', { id: 'todoapp'}, [
    h('div', {class: 'header'}, [
        h('h3', {}, 'todo express'),
        h('input', { type: 'text', 
                    // `newTitle` is used as a getter here. Each time it will be change
                    // the 'value' property will be updated as well.
                    value: newTitle, 
                    placeholder: 'what is up?', 
                    // `newTitle` is used as a setter each time a new character is typed. 
                    // `targetAttr` is just an helper to extract the 'value` property
                    // from the event target.
                    oninput: sp.targetAttr('value', newTitle) 
        }),
        // You can hook on to any dom events by adding an attribute prefixed with `on`
        h('span', { class: 'addBtn', onclick: addTodo }, 'Add'),
    ]),
    // This is how SPUI handles list of elements: we bind the ObservableArray to an Element list:
    sp.elementList('ul', {}, todos, (listNode: HTMLElement, todo: Todo) => {
        // This is the function that is called any time a new DOM element needs to be constructed 
        // for a new Todo object.
        return h('li', { class: { checked: todo.done }, 
                         onclick: () => todo.done(!todo.done()) }, [
            todo.title,
            // ObservableArray has a nifty remove function that will remove the Todo from the list
            // and notify the DOM about it:
            h('span', {class: 'close', onclick: () => todos.remove(todo)}, 'x')
        ]);
    })
]);

All that is left is to hook the view on to the body of the Document. Since the result of h is an HTMLElement it is as easy as this:

document.body.appendChild(view);

And that’s it! We now have a todo application (like a million other ones):

spui

Check here to see the complete source of the examples.

More examples

SPUI itself is only 3 concepts h to create DOM and valueStream to create listenable model object and ObservableArray to store multiple models. That is it.

The following page showcases the basic usages of the SPUI API. It is all very interactive though super bootstrappy-ugly.

What is next

You can get SPUI on npm.

Or on github.

The current version is unit tested but not really battle tested. I will keep iterating on it and try to make it better but I am glad just to have taken it out of my system!