spui
01 Nov 2017 | spui ui web domThey 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):
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!