Statefull components in plain JS - No Signals , no Hooks
Published: 2023-04-04 21:27:17

> 
Why on earth use 'hooks' or 'signal' to update a simple counter as we have all seen as examples in hyped frameworks ?

Use simple tools for simple tasks.

MVU is one of those simple tools.

The entire source is here (Thanks to Eckehard Fiedler creator of DML): 

```js
var _base
var _baseStack = []

function selectBase(ID) {
	// Save old base
	_baseStack.push(_base)
	if (_baseStack.length > 100) {
		alert("DML error: _baseStackOverflow in bushBase()")
		_baseStack = []
	}
	// select new base, either ID or element
	if (typeof(ID) === 'string')
		_base = document.getElementById(ID)

	_base = ID
	return _base
}

function sb(ID) {
	selectBase(ID)
} // Alias shortcut

/*------------------------------------------------------
  read curent _base
  ------------------------------------------------------*/
function getBase() {
	return _base
}

/*------------------------------------------------------
   get current stack position
  ------------------------------------------------------*/
function DMLgetSP() {
	return _baseStack.length
}

/*------------------------------------------------------
  set Stackpointer for stored value
  ------------------------------------------------------*/
function DMLsetSP(SP, msg = "DMLsetSP") {
	if (SP > _baseStack.length)
		alert("Error in " + msg + ", Stack pointer below desired SP")
	else
		while (_baseStack.length > SP)
			_base = _baseStack.pop()
	return _baseStack.length
}

/*------------------------------------------------------
   check, if current position is equal to chk
   chk is the stacklength before push, gives stack mismatch alert
  ------------------------------------------------------*/
function DMLchkSP(oldCnt = 0, txt = "Missing unselectBase()") {
	if (DMLgetSP() != oldCnt)
		alert("DML error: _baseStack size mismatch - " + txt + ", before: " + oldCnt + ", after: " + DMLgetSP())
	return _baseStack.length
}

/*------------------------------------------------------
  Stack-Prüfung für Einzelfunktionen
  f: ()=>{return new construct(xyz) }
  ------------------------------------------------------*/
function checkSP(f, txt) {
	let sp = DMLgetSP()
	let ret = f()
	DMLchkSP(sp, txt)
	return ret
}

/*------------------------------------------------------
   restore last base from stack, returns Stack position.
   if cnt set, unselectBase is called cnt times
   oldCnt is provided for test purpose. If SP after unselectBase
   is different, error message is displayed
  ------------------------------------------------------*/
function unselectBase(cnt = 1, oldCnt = -1, msg = "unselectBase") {
	for (let i = 0; i < cnt; i++) {
		if (_baseStack.length <= 0) {
			alert("DML error: _baseStack empty in popBase()")
			break
		} else
			_base = _baseStack.pop() // restore old stack

		if (oldCnt >= 0) {
			if (DMLgetSP() != oldCnt)
				alert("DML error: _baseStack size mismatch - " + msg + ", before: " + oldCnt + ", after: " + DMLgetSP())
		}
	}
	// if chk, check Stacklength after pop == chk
	return _baseStack.length
}


/* ----------------------------------------------------------------------------
   Check, if node is element or string
   create span from string
   if c is object, return Object only
   ----------------------------------------------------------------------------*/
function chk_node(c) {
	if (typeof(c) == "string") {
		let ret = create("span", null, null)
		ret.innerHTML = c
		return ret
		//  return textNode(c)
	} else return c
}


function setAttributes(el, attrib) {
	if (typeof(attrib) == "string") // Check for string attribute          
		attrib = {
			"style": attrib
		} // Convert strings to {"style",attrib}

	// set attributes
	if (typeof(attrib) == "object") {
		// Slpit JSON, set attributes individually
		Object.keys(attrib).forEach(function(key) {
			let val = attrib[key]
			if (key != "style") {
				el.setAttribute(key, val); // Normal attributes
			} else { // set Style parameters individually
				let ar = val.split(';'); // Split style Elements
				ar.forEach(function(pair) {
					if (pair) { // If not empty    
						let kv = pair.split(":")
						if (kv.length == 2) {
							let p = kv[0].trim()
							let v = kv[1].trim()
							el.style.setProperty(p, v); // Set property
						}
					}
				})
			}
		})
	}
	return (el)
}

/*------------------------------------------------------
   general short cut function. Creates an element and appends
   to existing object. Type is string like 'div'
  ------------------------------------------------------*/
function createAt(obj, typ, attrib) {
	let ret = document.createElement(typ)
	setAttributes(ret, attrib)
	obj.appendChild(ret)
	return ret
}


// ----------------------------------------------------------------------------
// create object with content and attributes. Content can be text or object
// attrib is an JSON-object {"id":"test", "class": myclass}
// ----------------------------------------------------------------------------
function create(typ, attrib, c) {
	let el = document.createElement(typ)
	let cIsArray = (Array.isArray(c))
	if (c) {
		if (typeof(c) == 'number') {
			c = String(c)
		}

		if (typeof(c) == 'string') {
			el.innerHTML = c
		} else {
			if (cIsArray) {
				el.append(...c)
			} else {
				el.appendChild(c);
			}

		}
	}
	if (attrib) {
		setAttributes(el, attrib)
	}
	return el
}


// ----------------------------------------------------------------------------
// Append object at current base
// ----------------------------------------------------------------------------
function appendBase(c) {
	let e = chk_node(c)
	if (_base) _base.appendChild(e)
	else {
		// if (document.body) document.body.append(e)
		if (document.body) document.body.appendChild(e)
		else {
			console.log("null Body found: " + c.textContent)
			return
		}
	}
	return (e)
}
// ----------------------------------------------------------------------------
// Append object at current base without check for String
// ----------------------------------------------------------------------------
function _appendBase(c) {

	if (_base)
		_base.appendChild(c)
	else
		document.body.append(c)
	return (c)
}

// ----------------------------------------------------------------------------
// make: create element with content and appendBase at current base
// attributes is an JSON-object {"id":"test", "class": myclass}
// ----------------------------------------------------------------------------
function make(typ, attrib, c) {
	return appendBase(create(typ, attrib, c))
}

// wrapper on make
const m = (type) => (content, attribs) => {
	let elt = make(type, attribs, content)
	elt.name = type
	return elt
}

function div(s, attrib) {
	return make("div", attrib, s)
}

const dom = (base = div("", null)) => selectBase(base)
const udom = unselectBase;

function _html(html) {
	var template = document.createElement('template');
	html = html.trim();
	template.innerHTML = html;
	const elt = template.content.firstChild
	appendBase(elt);
	return elt
}

function html(template) {
	const text = typeof template == "string" ? [template] : [template[0]]
	for (let i = 1; i < arguments.length; i++) {
		text.push(arguments[i], template[i])
	}
	return _html(text.join(''))
}
const MVU = {
	m,
	dom,
	udom,
	html
}

export {
	MVU
}
```

Here is a demo a famous the famous statefull counters :

```js

import { MVU } from "./mvu.js"

const {dom, udom, html} = MVU;

const qs = (elt, tag) => elt.querySelector(tag);

function Counter (start = 0) {
  let c = start;
  const incValue = () => qs(view, 'span').innerText = ++c;
  const view = html`
    <div>
      Value : <span>${c}</span>
      <button>INC</button>
    </div>`
    qs(view, "button").onclick = incValue;
  return view
}

function applyStyle (elt, bgcolor = "deeppink") {
   elt.style = 
    `border-bottom:1px solid white;
     background:${bgcolor};
     color:white;
     padding:10px;`
}

function App () {
   const app = dom ()
     const c1 = Counter (3);
     const c2 = Counter (12);
     const c3 = Counter (4);
   udom ();
   [c1,c2,c3].forEach(c => applyStyle(c));
   app.style.width = "300px";
   return app;
}

document.body.append(App())
```
You can test it here [StateFullCounters](https://flems.io/#0=N4IgtglgJlA2CmIBcAWADAOgIwDYA0IAZhAgM7IDaoAdgIZiJIgYAWALmLCAQMYD21NvEHIQAHhYAmAHwAdagAIFAZTa0hhAK6xYC-mAAOA4W1IKAtEup8FpCAHM6sUngXWFLPnwDWpeWIB6KTlqeW4QUngEHjYIAXImLFRJEABfPBp6RnAAN00MACtyXgEhESYc2gAnBQB9ACNaSPlKmoam+FVaHm8FAF4FCgBdeXktahi4xUjotgAhDoAKAEkAEQBKBWBRtgCAlVoc+AU+WCgFRubQtnbIrp6MA01SFkXb+HWdiEIFN8vOtQPBDUexsFgKaQKLBoNCbbbXWRqBBVNiLWQgVYAWQAMgp4FUqnwqkg6v97t4APJHKqEWB8ADuCgginqzxYC0ii0+IE+CJuZMBvQGwx2qR2e1sUXgMTc8EZ-1c8AgYPxCjWJxqUoYgi+P0WbAAngZ4HxCCsNv0+gMAOSkNhVZn2a28xGI979BRQPg8TTatgYezwNgAUQQfrmBuWUHNLuu7oGax2VSDmiqine8jFoWo40mAls9RjWx2M2l8yWa15qQUEoAgrAIE1bJ4UT62KNqAEAFTmXt9-sDwdD4cjkfyJTJ2jnH3JwSkjrjiyj5cr1dDrsBMaaCaxfOB8ucuFJlNp+dXLPybtr683seKJT7vSp2dsWyChRGOy77NKW9--8bluO5TAoWLYvuygAApcsW1zJmwqbpgK3TeBgwKgiwmYdle-64Wui6RK+5JGMyQg1IQRJvkS8DnJUsCaPAi54cxy6ATm255ooYGEVBixQa4YCkPYHrotxQZQeiR7XN8vxQRCZ4AihaHCBhLrtki+KouiwYEpRzIKOiCgANQKIJwkmeirjkh+fCkaq9RRAynrwHYybnBJPI7FEVyumw9IsCQxx-B05LKSCYLyVBam+fGCmhUYBhcseCGnu8oXoWCWHZjhLG5QOi56Cw0reK4Mkzi+NlfiBEBmPAACOmi0LobA2DwLDeAVbW9DVCgqm+KEZeCDkUcmH5sq49gQEcZh2ihpk1WA6htQoTWaUxeUbb2bG5t+oE4l1vGnFAADCc4DGgrhsAAHq+AzopiNV2CCCjbqWMQcvASU8rBiIyYsYEQdBmwAIQDEdp1sNFGkomiGI4niunEnF752AAXsckCkItbDLZYhkmddr4WdwFzwCN8AkvjJxnBDxkGSTtCEGRlMgHTAPiUDanwYhyNKYNWWXj2m0bYu5LmJBVQAD-jMJhCSzUwbMujsDjN437CIuhAklyfTSMA3OntQcp6PE9qaDEixXQaqObFmv7C3l20cbtbXFbxhCXTdUmIggr6kAYHrs2wvFqb7CjwR6ZpqWBB3Qf7nuQ8lPPwQLnZCw7LEFcmdrUQosBNK+-wKIQhJgP1PSuAb1BmNZn7KlMGAFWVc6Ea4r1Su9HRMmYPBNQg05zrEDB+Pe1MnXOPUGISOTQDRxeUUIdpjVUn7wBgao-HJjNkS91cdwejGjz1UDfIQ+ImIqiOmS5pC0IG3eejVBj5waNHrRnzFO8B+bt7MH2LDwM6UJXDgyAeYLAAkhIiRAL-MsH1JI-TYCNX4YcIAejQAAbiZAoMQehBBYIgEZIy3t1J-TSoKMKGEcHnRIb5VaMNRLw3xISJG5C5rwEMIaJkigEr-25FDRE9RJwdT5NWbyh9fJuiLgMNhDwEowQlNnFqo0jrlxETsX6epQGvmkDQxBvk-pBxDgoUGY8IYCPUvQrScNcTMKJCSWRvQ0YYwWktcEeNWYmTMnTSypNyYszptonxDMmb4gCSZIxnMNFsAvDE8U+wyrtVcK7HoKhBSDRWqEmoCVLSFXUfaE8SEQoUP5tQC82UuxLg-tU5cBVjpFQrkyH41goDHB6lqEwGo3wOhBJ1ScQhbAGFoIoEufAy52h6fYJuPweAPz4PUAoZZK6FIUBSBZZYTjUFgAaAqNS9kji-pxPJtQWmfR4CQv6hpjSmgAZsK09MJmOgQfCXyYcI4DB4P0z66J-bDN8dQbQsBXAAp0FDeCGBmRGyqAACQACrww+dEqu4cgzRIlCilKighA3QAHJ8Fabc0UeJnDHGRTwVOQEjmEVrGwe0EBWSL0WFEVw6g6X1AuXqK5JozSsodOy3JPy6W9O+hKepxV541Eec9Xl9LNADKUAqxV0SZX1A9C89SroIiGgQOiEkKronVlFQIakfshX2DMC1YsWqDQ6u4Cq8piIJSERWrSvlcqXK6l+Fym5Kq7l3RAPMxZMRnlov2MoWABhlQKAAFLKApDi1wzqVXurMMyE+M8oCNR0Dsvkayg3+m8PAA0pBFi+owCNYM3RXg7SmIsQtBpaHqTDnRD0KqKD1pGHyTRvx60mP9XaG1jFvrqskUIWAGBqWutlUIOtRbXB0XWFgiUeKqiLV0Mmxe0TERiJJVsGs+xnWqEHR+aoWQyKpuoOm6AWbtlbqbUGFaNQBh0Qnc-ZUixrQYOdEusNb7TXHtDBwkwI9R2ImqOWokla2qLBrQIRYQyIBVEbaOv6CGkN7olMsZpfBXwcIMFwhVd7R1h28DkD0aHX0NmsUgSSRGNWxD1KRyhEV7mSGQ-R15D6A4DFIxQNAQwMB0rAElLtHH72vjIzxnIFAsACaEyJ0DYmogTu1WvQiEs+DGhRAaeD87F37pUFxwkWnDR0d8rExTFn6OpAsVZmzook6niZbAKs2F077JvAVQMUKmrNiJK+NsxdnYNwUMdL5ZhhnEqA3OYZ5xaAGGNJekeCrLXwCujVWIz1A1lnXrCo0bSZpmrzhAQtChrTputO-Dz15Dkuy+TSxYgbLpGhZVO-l6q3kPoGF6H0foMCfPgOoeAgG-T6iNGpSdbKU2LHgq1tlalA0YHi4lk6AUzgzaDFzFZKcykdkvPsarh3zD7b0F8k46yZT0mVOCfgghOmxZdVNxe69jqlE6b3FkxxsWvkotl4NnZ9gqofpF2N8bzB-dfNsEA0BdXokXu2Em6IeD51IH4ZApkDTI6aKQLMEojvVcpXVwbM6rlzb5ckkhYcogeh676Ew-WvkjZMGNgwocH08GWKQWsBJaAGg9IsbnVRecQq5zznT5y1J-XOfohjXr8s3Ol-c60AKwAOSqM6GXiJZkDFUJMwloi9ukM5fLs0iubRSqdOx5TkL8RwoRXoA1xLIia9lwAzngvefscRMp5bwhowYAD+cu9O7ncjvoz7hLfv6kkGjOcjBwfDfboc9JPUvqZeTbdYy5lj2+VqViciqIFKAdVPxzUk7tZI+XvO-ml1T4CSdP+Cd0v+zCcgV95e-+0uOsPuOB89qJz8VnMl3qd4mx3hLcr6tmPTK1LiJlxKP6tO+v1HxQ2z03o6eCAwCvqABoJ8rZn9ExfG-l+r82Ev+nO+9-t6n+tj40S59h61-EU4a86T2FhiC3QcxV-z23FAcJPQQTNLNgV7O7QQCxKuA1ZPREZFQ-XbYvZvPZcvSfavDZdQOvCqIuK7MEPgOVQqcVZBXXJ5RApA6pVvfMWoG-Tvb2FPX4UfaJcfG-aPdbIPa4cRaJC-LfK-ffP3fXWAlZfXcpPHMgj+E7RaQtEkAbIbKLP0BQHAm7N7GLKvagruDA8qBvBcYvDdFyYHRQUHHFcHC7SHdEGHZAOHFyBHVwJHFHNHEkMATHWw3HA7UQjOCgxQCQz6UnHPelCnRBZFVQzkaQknFrHw+oCnVzbKfYekIXSPGofMTw+QW7JeMuAYFnD4foSEABJQtgMnelUgO5SEbvXDWAW6UyWgQtdIvI+oFwE2cAxOdg0ojAOgBgD0LlRzRQKIdsBA9wx+HIRYWotPdVZFTw2GdNXxFVRNSIpI02dfVI34aRPo2Gf5QFdYQoyUP+JYf4XkZI18TQL0eY2BTuSIePbMWDdMdgTgRYS4lzRBVoXqPDfOAZbrE-enYI4bMMZna0IQQwJ4+Ab9HYG4j0G4wTB0YTRdHYH45+IbCFagKFO3XEAYG4045-auEosoqEv4-rHI8tRDO0FgqAHYQIz6LoiEuCFZLo1Oc4jwDgWAfUR4obEhXYh4m6Nok3B434mQ+5QVSZQyAAfkGExKGyGAUBJAoCFKED4yGE7URGQUWFQQ9CwAIRwUfXsE31MGYxYAISIUbW+0eDZFLSqDVL9FIAoAgCGEugZMlPNLzw6LqBuPpJukKFsmoA-WdEiOZMxAADUABVNVHYMAPAHYA4oM64fYsZUMxEG41ONLIwFERBb0n0zMcIN6b8BIEANAJANAcwSQAAVhwCQCwDSAyBABaOyEKGKBAFuzKDYFEHkAgEMH8z3UTIUGrFGTLnRAwACDADyArPRA7GZOABDJemHJuOrAGETJRImFmLqjMDSK6MujvnWK6IwAanxANGUH3iJH1CXKnOpNe23B3gGLUHjL0XhCUDDm136hRFOKUGZOZB4C9KagYn53WNnMWBnjlFcFtCGWoGdFhKhVhRAI9CIR4FvLqKXk-MZCRNpIAAMCocF00QhFUlAxAGUWpqBpBlgcVjpAh0KBBkKUKnz6JjgSQxBflMKAASYAHgVIQICiwi1CgIJC+C0eBQd8qC6wkAfC0IHkDAAQZHCAVJAYB84ihicC5FKCqk4LfMZbbZI9BAX4Bci4ewfgOkJ9emJ4FeW1KSBVFcgdRSu6Ni2ClfKoVpKocwFfWlMZQsgwK6WwU4aAeQgKIQcChVRoHoewQkf-JAai+oVS04IkVINyu8wK4kfyZUeAEKk9GAR0QstAOyjBVi8pakivAOGCc80KtElaBLGnMZX4XkFC5kngLAD0A8u7GoRYAAZjJKKtmJ4EkDKrwIqt+CwDY2iuKqqqasPNVEWBQFqqUHDLLi5DcooBKrwAaomqqoEwrSrQAUyJyufg3NU1uQGsWpU0HQwCuygBY3piqphDsvRDcoCIS1OPKS4P9B4JvwFwSy5FjBTP3imHTKwCqqQCqqLPSEyAYFEH61R3CGrJMFECv1giUA8u8C8uaoAIUAAGJJAAAxeGqquG8CtS+xCK1yzWUocwQgegEgA0EkL0-EKAYZWgZMggVMp60QV6yQFIVIC00srIUQRM8q89NIIAA)