There was a time when it was common to assemble your own PC.
You chose your MotherBoards, Graphic Cards and so on.
Those times are gone for the vast majority of us...
But nothing prevents us to do the same from or with our favorites libraries.
I am very found of the Model/View/Update a.k.a Sam Pattern.
There are great libraries that take this approach ELM, HyperApp, AppRun, Meiosis (more a pattern than a library)...
But none of them satisfied me entirely, so I, decided to assemble one which fits my needs, and easily used by my colleagues who 'hate' Javascript and don't know nothing about node.js
For that I used : SinuousJS and Unistore and I create MVU.js
I created a very simple project with vite.js, and I included the following lines in the main script
import htm from 'sinuous/htm';
import { observable, subscribe } from 'sinuous/observable';
import { render, context } from './src/render';
import createStore from 'unistore';
const r = context()
function html() {
return htm.apply(r, arguments);
}
const MVU = { render, html, observable, subscribe, createStore };
window.MVU = MVU;
The main goal was to make the View a function of the State (or Model) and nothing else.
Here is the demo of the traditional Counter
const {html, render, observable, createStore} = MVU;
const State = observable({});
const Model = createStore({
counter : 0
});
const inc = Model.action ((model, value = 1) => {
return ({counter : model.counter + value});
})
Model.subscribe(store => State(store));
function View () {
let state = State();
return html `
<h1>Counter ${state.counter} </h1>
<button id="incer">INC</button>
`
}
function startApp () {
Model.setState();
render(View, document.body);
document.getElementById("incer")
.addEventListener("click", () => inc(1));
}
startApp()
You can test it here : MVUCounter
See how the counter is updated, while the button is not recreated and so, we don't loose the 'click listener' on it...thanks to SinuousJS.
Unistore allows to 'react' and 'call' subscribers whenever the Model changes.
In this model, there is a clean separation from the Model and the View.
The actions are centralized in one unique point and not messing around all your code
Let's refactor a bit :
import { State } from "./state.js"
import { Actions } from "./actions.js"
import { Model } from "./model.js"
const {html, render} = MVU;
function View () {
let state = State();
return html `
<h1>Counter ${state.counter || 0} </h1>
<button onclick=${() => Actions.inc(1)}>INC</button>
`
}
render(View, document.body);
You can test it here SAMCounter
Go and assemble yours (or not), and tell us why you created it :-)