all repos — h3 @ 99d1cf241d5e05e0aa19254428cbd53430d211df

A tiny, extremely minimalist JavaScript microframework.

100% test coverage.
h3rald h3rald@h3rald.com
Thu, 23 Apr 2020 22:02:16 +0200
commit

99d1cf241d5e05e0aa19254428cbd53430d211df

parent

8b15c53a9e7d2c4f003adcb15015c9a3fabc0a17

6 files changed, 294 insertions(+), 277 deletions(-)

jump to
M __tests__/h3.js__tests__/h3.js

@@ -6,9 +6,11 @@ const v1 = h3("div", {onclick: () => true});

const v2 = h3("div", {onclick: () => true}); const v3 = h3("div", {onclick: () => false}); const v4 = h3("div"); - expect(v1.equalTo(v2)).toEqual(true); - expect(v1.equalTo(v3)).toEqual(false); - expect(v4.equalTo({ type: "div" })).toEqual(false); + expect(v1.equal(v2)).toEqual(true); + expect(v1.equal(v3)).toEqual(false); + expect(v4.equal({ type: "div" })).toEqual(false); + expect(v1.equal(null, null)).toEqual(true); + expect(v1.equal(null, undefined)).toEqual(false); }); it("should support the creation of empty virtual node elements", () => {

@@ -38,6 +40,7 @@ const invalid2nd = () => h3("div", 1);

const invalid2nd2 = () => h3("div", true, []); const invalid2nd3 = () => h3("div", null, []); const invalidChildren = () => h3("div", ["test", 1, 2]); + const tooManyArgs = () => h3("div", {id: "test"}, "test", "aaa"); expect(empty).toThrowError(/No arguments passed/); expect(invalid1st).toThrowError(/Invalid first argument/); expect(invalid1st2).toThrowError(/Invalid first argument/);

@@ -48,6 +51,7 @@ expect(invalid2nd).toThrowError(/second argument of a VNode constructor/);

expect(invalid2nd2).toThrowError(/Invalid second argument/); expect(invalid2nd3).toThrowError(/Invalid second argument/); expect(invalidChildren).toThrowError(/not a VNode: 1/); + expect(tooManyArgs).toThrowError(/Too many arguments/); }); it("should support the creation of elements with a single, non-array child", () => {
M __tests__/router.js__tests__/router.js

@@ -9,8 +9,11 @@ get hash() {

return hash; }, set hash(value) { + const event = new CustomEvent("hashchange"); + event.oldURL = hash; + event.newURL = value; hash = value; - window.dispatchEvent(new HashChangeEvent("hashchange")); + window.dispatchEvent(event); }, };

@@ -60,16 +63,22 @@ );

}); it("should expose a navigateTo method to navigate to another path", () => { - h3.navigateTo("/c2", {test1: 1, test2: 2}); + h3.navigateTo("/c2", { test1: 1, test2: 2 }); expect(document.body.childNodes[0].childNodes[1].textContent).toEqual( "test2: 2" ); + h3.navigateTo("/c2"); + expect(document.body.childNodes[0].innerHTML).toEqual(""); }); it("should fail if no route matches at startup", async () => { + mockLocation.hash = "#/test"; try { - await h3.init({element: document.body, routes: {"/gasdgasdg": () => false}}); - } catch(e) { + await h3.init({ + element: document.body, + routes: { "/aaa": () => false }, + }); + } catch (e) { expect(e.message).toMatch(/No route matches/); } });
M __tests__/vnode.js__tests__/vnode.js

@@ -48,7 +48,7 @@ });

it("should provide a render method able to render element nodes with attributes and classes", () => { const createElement = jest.spyOn(document, "createElement"); - const vnode = h3("span.test1.test2", { title: "test" }); + const vnode = h3("span.test1.test2", { title: "test", falsy: false }); const node = vnode.render(); expect(createElement).toHaveBeenCalledWith("span"); expect(node.constructor).toEqual(HTMLSpanElement);

@@ -113,10 +113,13 @@ expect(node.childNodes[0].textContent).toEqual("Hello!");

}); it("should provide a redraw method that is able to add new DOM nodes", () => { - const oldvnode = h3("div", h3("span")); + const oldvnode = h3("div#test", h3("span")); + const newvnodeNoChildren = h3("div"); const newvnode = h3("div", [h3("span#a"), h3("span")]); const node = oldvnode.render(); const span = node.childNodes[0]; + oldvnode.redraw({ node: node, vnode: newvnodeNoChildren }); + expect(oldvnode.children.length).toEqual(0); oldvnode.redraw({ node: node, vnode: newvnode }); expect(oldvnode).toEqual(newvnode); expect(oldvnode.children.length).toEqual(2);

@@ -137,6 +140,18 @@ expect(node.childNodes.length).toEqual(1);

expect(span).toEqual(node.childNodes[0]); }); + it("should provide a redraw method that is able to figure out differences in children", () => { + const oldvnode = h3("div", [h3("span", "a"), h3("span"), h3("span", "b")]); + const newvnode = h3("div", [ + h3("span", "a"), + h3("span", "c"), + h3("span", "b"), + ]); + const node = oldvnode.render(); + oldvnode.redraw({ node: node, vnode: newvnode }); + expect(node.childNodes[1].textContent).toEqual("c"); + }); + it("should provide a redraw method that is able to update different attributes", () => { const oldvnode = h3("span", { title: "a", something: "b" }); const newvnode = h3("span", { title: "b", id: "bbb" });

@@ -150,11 +165,11 @@ });

it("should provide a redraw method that is able to update different classes", () => { const oldvnode = h3("span.a.b", { title: "b" }); - const newvnode = h3("span.c", { title: "b" }); + const newvnode = h3("span.a.c", { title: "b" }); const node = oldvnode.render(); oldvnode.redraw({ node: node, vnode: newvnode }); expect(oldvnode).toEqual(newvnode); - expect(node.classList.value).toEqual("c"); + expect(node.classList.value).toEqual("a c"); }); it("should provide redraw method to detect changed nodes if they have different elements", () => {

@@ -211,18 +226,21 @@ it("should provide a redraw method able to detect specific changes to style, data, value, attributes and eventListeners", () => {

const fn = () => false; const oldvnode = h3("input", { style: "margin: auto;", - data: { a: 111, b: 222 }, + data: { a: 111, b: 222, d: 444 }, value: "Test...", title: "test", + label: "test", + onkeydown: () => true, onclick: () => true, onkeypress: () => true, }); const newvnode = h3("input", { - style: "margin: 5px;", - data: { a: 112, c: 333 }, - value: "Test!", + style: false, + data: { a: 111, b: 223, c: 333 }, title: "test #2", + label: "test", placeholder: "test", + onkeydown: () => true, onkeypress: () => false, onhover: () => true, });

@@ -231,13 +249,14 @@ const node = oldvnode.render();

container.appendChild(node); oldvnode.redraw({ node: node, vnode: newvnode }); expect(oldvnode).toEqual(newvnode); - expect(node.style.cssText).toEqual("margin: 5px;"); - expect(node.dataset["a"]).toEqual("112"); + expect(node.style.cssText).toEqual(""); + expect(node.dataset["a"]).toEqual("111"); expect(node.dataset["c"]).toEqual("333"); - expect(node.dataset["b"]).toEqual(undefined); + expect(node.dataset["b"]).toEqual("223"); + expect(node.dataset["d"]).toEqual(undefined); expect(node.getAttribute("title")).toEqual("test #2"); expect(node.getAttribute("placeholder")).toEqual("test"); - expect(node.value).toEqual("Test!"); + expect(node.value).toEqual(""); }); it("should provide a redraw method able to detect changes in child content", () => {
M docs/example/assets/js/h3.jsdocs/example/assets/js/h3.js

@@ -1,4 +1,3 @@

- /** * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> *

@@ -45,8 +44,6 @@ }

} return true; } - const o1 = obj1 || {}; - const o2 = obj2 || {}; function checkProperties(obj1, obj2) { for (const key in obj1) { if (!(key in obj2)) {

@@ -58,7 +55,7 @@ }

} return true; } - return checkProperties(o1, o2) && checkProperties(o2, o1); + return checkProperties(obj1, obj2) && checkProperties(obj2, obj1); }; const selectorRegex = /^([a-z0-9:_=-]+)(#[a-z0-9:_=-]+)?(\..+)?$/i;

@@ -98,14 +95,14 @@ this.from(this.processVNodeObject(vnode));

} } else { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } } else if (args.length === 2) { let [selector, data] = args; if (typeof selector !== "string") { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } this.processSelector(selector);

@@ -137,17 +134,21 @@ } else if (args.length === 3) {

let [selector, props, children] = args; if (typeof selector !== "string") { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } this.processSelector(selector); if (typeof props !== "object" || props === null) { throw new Error( - "[VNode] Invalid second argument provided to VNode constructor." + "[VNode] Invalid second argument passed to VNode constructor." ); } this.processProperties(props); this.processChildren(children); + } else { + throw new Error( + "[VNode] Too many arguments passed to VNode constructor." + ); } }

@@ -166,8 +167,8 @@ this.attributes = data.attributes;

this.classList = data.classList; } - equalTo(obj) { - return equal(this, obj); + equal(a, b) { + return equal(a, b === undefined ? this : b); } processProperties(attrs) {

@@ -181,7 +182,7 @@ this.classList =

attrs.classList && attrs.classList.length > 0 ? attrs.classList : this.classList; - this.attributes = attrs || {}; + this.attributes = attrs; Object.keys(attrs) .filter((a) => a.startsWith("on")) .forEach((key) => {

@@ -295,7 +296,7 @@ }

// Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) redraw(data) { - let { node, vnode } = data || {}; + let { node, vnode } = data; const newvnode = vnode; const oldvnode = this; if (

@@ -379,7 +380,7 @@ Object.keys(oldvnode.eventListeners).forEach((a) => {

if (!newvnode.eventListeners[a]) { node.removeEventListener(a, oldvnode.eventListeners[a]); } else if ( - !equal(newvnode.eventListeners[a] !== oldvnode.eventListeners[a]) + !equal(newvnode.eventListeners[a], oldvnode.eventListeners[a]) ) { node.removeEventListener(a, oldvnode.eventListeners[a]); node.addEventListener(a, newvnode.eventListeners[a]);

@@ -395,82 +396,78 @@ }

// Children var newmap = []; // Map positions of newvnode children in relation to oldvnode children var oldmap = []; // Map positions of oldvnode children in relation to newvnode children - if (newvnode.children) { - function mapChildren(parent1, parent2) { - const map = []; - for (let j = 0; j < parent1.children.length; j++) { - let found = false; - for (let k = 0; k < parent2.children.length; k++) { - if (equal(parent1.children[j], parent2.children[k])) { - map.push(k); - found = true; - break; - } + function mapChildren(parent1, parent2) { + const map = []; + for (let j = 0; j < parent1.children.length; j++) { + let found = false; + for (let k = 0; k < parent2.children.length; k++) { + if (equal(parent1.children[j], parent2.children[k])) { + map.push(k); + found = true; + break; } - // node not in oldvnode - if (!found) { - map.push(-1); + } + // node not in oldvnode + if (!found) { + map.push(-1); + } + } + return map; + } + var newmap = mapChildren(newvnode, oldvnode); + var oldmap = mapChildren(oldvnode, newvnode); + var notFoundInOld = newmap.indexOf(-1); + var notFoundInNew = oldmap.indexOf(-1); + if (equal(newmap, oldmap) && (notFoundInNew >= 0 || notFoundInOld >= 0)) { + // Something changed + for (let i = 0; i < newmap.length; i++) { + if (newmap[i] === -1) { + if (oldvnode.children[i].type === "#text") { + oldvnode.children[i] = newvnode.children[i]; + node.childNodes[i].nodeValue = newvnode.children[i].value; + } else { + oldvnode.children[i].redraw({ + node: node.childNodes[i], + vnode: newvnode.children[i], + }); } } - return map; } - var newmap = mapChildren(newvnode, oldvnode); - var oldmap = mapChildren(oldvnode, newvnode); + } else { var notFoundInOld = newmap.indexOf(-1); var notFoundInNew = oldmap.indexOf(-1); - if (equal(newmap, oldmap) && (notFoundInNew >= 0 || notFoundInOld >= 0)) { - // Something changed - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1) { - if (oldvnode.children[i].type === "#text") { - oldvnode.children[i] = newvnode.children[i]; - node.childNodes[i].nodeValue = newvnode.children[i].value; - } else { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); - } - } + while (notFoundInOld >= 0 || notFoundInNew >= 0) { + // First remove children not found in new map, then add the missing ones. + if (notFoundInNew >= 0) { + // while there are children not found in newvnode, remove them and re-check + node.removeChild(node.childNodes[notFoundInNew]); + oldvnode.children.splice(notFoundInNew, 1); + newmap = mapChildren(newvnode, oldvnode); + oldmap = mapChildren(oldvnode, newvnode); + notFoundInNew = oldmap.indexOf(-1); + notFoundInOld = newmap.indexOf(-1); } - } else { - var notFoundInOld = newmap.indexOf(-1); - var notFoundInNew = oldmap.indexOf(-1); - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - // while there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); - } - if (notFoundInOld >= 0) { - // while there are children not found in oldvnode, add them and re-check - node.insertBefore( - newvnode.children[notFoundInOld].render(), - node.childNodes[notFoundInOld] - ); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); - } + if (notFoundInOld >= 0) { + // while there are children not found in oldvnode, add them and re-check + node.insertBefore( + newvnode.children[notFoundInOld].render(), + node.childNodes[notFoundInOld] + ); + oldvnode.children.splice( + notFoundInOld, + 0, + newvnode.children[notFoundInOld] + ); + newmap = mapChildren(newvnode, oldvnode); + oldmap = mapChildren(oldvnode, newvnode); + notFoundInNew = oldmap.indexOf(-1); + notFoundInOld = newmap.indexOf(-1); } } } // innerHTML - if (oldvnode.$html !== newvnode.$html) { - if (newvnode.children.length === 0 && newvnode.$html) { - node.innerHTML = newvnode.$html; - } + if (newvnode.$html) { + node.innerHTML = newvnode.$html; oldvnode.$html = newvnode.$html; } }

@@ -496,10 +493,6 @@ this.events[event].forEach((i) => {

this.state = { ...this.state, ...i(this.state, data) }; }); } - } - - get(arg) { - return arg ? this.state[arg] : this.state; } on(event, cb) {

@@ -533,7 +526,7 @@ constructor({ element, routes, store, location }) {

this.element = element; this.redraw = null; this.store = store; - this.location = location || window.location; + this.location = location || window.location; if (!routes || Object.keys(routes).length === 0) { throw new Error("[Router] No routes defined."); }

@@ -622,7 +615,9 @@ let { element, routes, modules, preStart, postStart, location } = config;

if (!routes) { // Assume config is a component object, define default route if (typeof config !== "function") { - throw new Error("[h3.init] The specified argument is not a valid configuration object or component function"); + throw new Error( + "[h3.init] The specified argument is not a valid configuration object or component function" + ); } routes = { "/": config }; }

@@ -633,7 +628,7 @@ }

// Initialize store store = new Store(); (modules || []).forEach((i) => { - if (i) i(store); + i(store); }); store.dispatch("$init"); // Initialize router

@@ -670,7 +665,7 @@ throw new Error(

"[h3.state] No application initialized, unable to retrieve current state." ); } - return store.get(); + return store.state; }, });
M docs/js/h3.jsdocs/js/h3.js

@@ -1,4 +1,3 @@

- /** * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> *

@@ -45,8 +44,6 @@ }

} return true; } - const o1 = obj1 || {}; - const o2 = obj2 || {}; function checkProperties(obj1, obj2) { for (const key in obj1) { if (!(key in obj2)) {

@@ -58,7 +55,7 @@ }

} return true; } - return checkProperties(o1, o2) && checkProperties(o2, o1); + return checkProperties(obj1, obj2) && checkProperties(obj2, obj1); }; const selectorRegex = /^([a-z0-9:_=-]+)(#[a-z0-9:_=-]+)?(\..+)?$/i;

@@ -98,14 +95,14 @@ this.from(this.processVNodeObject(vnode));

} } else { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } } else if (args.length === 2) { let [selector, data] = args; if (typeof selector !== "string") { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } this.processSelector(selector);

@@ -137,17 +134,21 @@ } else if (args.length === 3) {

let [selector, props, children] = args; if (typeof selector !== "string") { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } this.processSelector(selector); if (typeof props !== "object" || props === null) { throw new Error( - "[VNode] Invalid second argument provided to VNode constructor." + "[VNode] Invalid second argument passed to VNode constructor." ); } this.processProperties(props); this.processChildren(children); + } else { + throw new Error( + "[VNode] Too many arguments passed to VNode constructor." + ); } }

@@ -166,8 +167,8 @@ this.attributes = data.attributes;

this.classList = data.classList; } - equalTo(obj) { - return equal(this, obj); + equal(a, b) { + return equal(a, b === undefined ? this : b); } processProperties(attrs) {

@@ -181,7 +182,7 @@ this.classList =

attrs.classList && attrs.classList.length > 0 ? attrs.classList : this.classList; - this.attributes = attrs || {}; + this.attributes = attrs; Object.keys(attrs) .filter((a) => a.startsWith("on")) .forEach((key) => {

@@ -295,7 +296,7 @@ }

// Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) redraw(data) { - let { node, vnode } = data || {}; + let { node, vnode } = data; const newvnode = vnode; const oldvnode = this; if (

@@ -379,7 +380,7 @@ Object.keys(oldvnode.eventListeners).forEach((a) => {

if (!newvnode.eventListeners[a]) { node.removeEventListener(a, oldvnode.eventListeners[a]); } else if ( - !equal(newvnode.eventListeners[a] !== oldvnode.eventListeners[a]) + !equal(newvnode.eventListeners[a], oldvnode.eventListeners[a]) ) { node.removeEventListener(a, oldvnode.eventListeners[a]); node.addEventListener(a, newvnode.eventListeners[a]);

@@ -395,82 +396,78 @@ }

// Children var newmap = []; // Map positions of newvnode children in relation to oldvnode children var oldmap = []; // Map positions of oldvnode children in relation to newvnode children - if (newvnode.children) { - function mapChildren(parent1, parent2) { - const map = []; - for (let j = 0; j < parent1.children.length; j++) { - let found = false; - for (let k = 0; k < parent2.children.length; k++) { - if (equal(parent1.children[j], parent2.children[k])) { - map.push(k); - found = true; - break; - } + function mapChildren(parent1, parent2) { + const map = []; + for (let j = 0; j < parent1.children.length; j++) { + let found = false; + for (let k = 0; k < parent2.children.length; k++) { + if (equal(parent1.children[j], parent2.children[k])) { + map.push(k); + found = true; + break; } - // node not in oldvnode - if (!found) { - map.push(-1); + } + // node not in oldvnode + if (!found) { + map.push(-1); + } + } + return map; + } + var newmap = mapChildren(newvnode, oldvnode); + var oldmap = mapChildren(oldvnode, newvnode); + var notFoundInOld = newmap.indexOf(-1); + var notFoundInNew = oldmap.indexOf(-1); + if (equal(newmap, oldmap) && (notFoundInNew >= 0 || notFoundInOld >= 0)) { + // Something changed + for (let i = 0; i < newmap.length; i++) { + if (newmap[i] === -1) { + if (oldvnode.children[i].type === "#text") { + oldvnode.children[i] = newvnode.children[i]; + node.childNodes[i].nodeValue = newvnode.children[i].value; + } else { + oldvnode.children[i].redraw({ + node: node.childNodes[i], + vnode: newvnode.children[i], + }); } } - return map; } - var newmap = mapChildren(newvnode, oldvnode); - var oldmap = mapChildren(oldvnode, newvnode); + } else { var notFoundInOld = newmap.indexOf(-1); var notFoundInNew = oldmap.indexOf(-1); - if (equal(newmap, oldmap) && (notFoundInNew >= 0 || notFoundInOld >= 0)) { - // Something changed - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1) { - if (oldvnode.children[i].type === "#text") { - oldvnode.children[i] = newvnode.children[i]; - node.childNodes[i].nodeValue = newvnode.children[i].value; - } else { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); - } - } + while (notFoundInOld >= 0 || notFoundInNew >= 0) { + // First remove children not found in new map, then add the missing ones. + if (notFoundInNew >= 0) { + // while there are children not found in newvnode, remove them and re-check + node.removeChild(node.childNodes[notFoundInNew]); + oldvnode.children.splice(notFoundInNew, 1); + newmap = mapChildren(newvnode, oldvnode); + oldmap = mapChildren(oldvnode, newvnode); + notFoundInNew = oldmap.indexOf(-1); + notFoundInOld = newmap.indexOf(-1); } - } else { - var notFoundInOld = newmap.indexOf(-1); - var notFoundInNew = oldmap.indexOf(-1); - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - // while there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); - } - if (notFoundInOld >= 0) { - // while there are children not found in oldvnode, add them and re-check - node.insertBefore( - newvnode.children[notFoundInOld].render(), - node.childNodes[notFoundInOld] - ); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); - } + if (notFoundInOld >= 0) { + // while there are children not found in oldvnode, add them and re-check + node.insertBefore( + newvnode.children[notFoundInOld].render(), + node.childNodes[notFoundInOld] + ); + oldvnode.children.splice( + notFoundInOld, + 0, + newvnode.children[notFoundInOld] + ); + newmap = mapChildren(newvnode, oldvnode); + oldmap = mapChildren(oldvnode, newvnode); + notFoundInNew = oldmap.indexOf(-1); + notFoundInOld = newmap.indexOf(-1); } } } // innerHTML - if (oldvnode.$html !== newvnode.$html) { - if (newvnode.children.length === 0 && newvnode.$html) { - node.innerHTML = newvnode.$html; - } + if (newvnode.$html) { + node.innerHTML = newvnode.$html; oldvnode.$html = newvnode.$html; } }

@@ -496,10 +493,6 @@ this.events[event].forEach((i) => {

this.state = { ...this.state, ...i(this.state, data) }; }); } - } - - get(arg) { - return arg ? this.state[arg] : this.state; } on(event, cb) {

@@ -533,7 +526,7 @@ constructor({ element, routes, store, location }) {

this.element = element; this.redraw = null; this.store = store; - this.location = location || window.location; + this.location = location || window.location; if (!routes || Object.keys(routes).length === 0) { throw new Error("[Router] No routes defined."); }

@@ -622,7 +615,9 @@ let { element, routes, modules, preStart, postStart, location } = config;

if (!routes) { // Assume config is a component object, define default route if (typeof config !== "function") { - throw new Error("[h3.init] The specified argument is not a valid configuration object or component function"); + throw new Error( + "[h3.init] The specified argument is not a valid configuration object or component function" + ); } routes = { "/": config }; }

@@ -633,7 +628,7 @@ }

// Initialize store store = new Store(); (modules || []).forEach((i) => { - if (i) i(store); + i(store); }); store.dispatch("$init"); // Initialize router

@@ -670,7 +665,7 @@ throw new Error(

"[h3.state] No application initialized, unable to retrieve current state." ); } - return store.get(); + return store.state; }, });
M h3.jsh3.js

@@ -1,4 +1,3 @@

- /** * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> *

@@ -45,8 +44,6 @@ }

} return true; } - const o1 = obj1 || {}; - const o2 = obj2 || {}; function checkProperties(obj1, obj2) { for (const key in obj1) { if (!(key in obj2)) {

@@ -58,7 +55,7 @@ }

} return true; } - return checkProperties(o1, o2) && checkProperties(o2, o1); + return checkProperties(obj1, obj2) && checkProperties(obj2, obj1); }; const selectorRegex = /^([a-z0-9:_=-]+)(#[a-z0-9:_=-]+)?(\..+)?$/i;

@@ -98,14 +95,14 @@ this.from(this.processVNodeObject(vnode));

} } else { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } } else if (args.length === 2) { let [selector, data] = args; if (typeof selector !== "string") { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } this.processSelector(selector);

@@ -137,17 +134,21 @@ } else if (args.length === 3) {

let [selector, props, children] = args; if (typeof selector !== "string") { throw new Error( - "[VNode] Invalid first argument provided to VNode constructor." + "[VNode] Invalid first argument passed to VNode constructor." ); } this.processSelector(selector); if (typeof props !== "object" || props === null) { throw new Error( - "[VNode] Invalid second argument provided to VNode constructor." + "[VNode] Invalid second argument passed to VNode constructor." ); } this.processProperties(props); this.processChildren(children); + } else { + throw new Error( + "[VNode] Too many arguments passed to VNode constructor." + ); } }

@@ -166,8 +167,8 @@ this.attributes = data.attributes;

this.classList = data.classList; } - equalTo(obj) { - return equal(this, obj); + equal(a, b) { + return equal(a, b === undefined ? this : b); } processProperties(attrs) {

@@ -181,7 +182,7 @@ this.classList =

attrs.classList && attrs.classList.length > 0 ? attrs.classList : this.classList; - this.attributes = attrs || {}; + this.attributes = attrs; Object.keys(attrs) .filter((a) => a.startsWith("on")) .forEach((key) => {

@@ -295,7 +296,7 @@ }

// Updates the current Virtual Node with a new Virtual Node (and syncs the existing DOM Node) redraw(data) { - let { node, vnode } = data || {}; + let { node, vnode } = data; const newvnode = vnode; const oldvnode = this; if (

@@ -379,7 +380,7 @@ Object.keys(oldvnode.eventListeners).forEach((a) => {

if (!newvnode.eventListeners[a]) { node.removeEventListener(a, oldvnode.eventListeners[a]); } else if ( - !equal(newvnode.eventListeners[a] !== oldvnode.eventListeners[a]) + !equal(newvnode.eventListeners[a], oldvnode.eventListeners[a]) ) { node.removeEventListener(a, oldvnode.eventListeners[a]); node.addEventListener(a, newvnode.eventListeners[a]);

@@ -395,82 +396,78 @@ }

// Children var newmap = []; // Map positions of newvnode children in relation to oldvnode children var oldmap = []; // Map positions of oldvnode children in relation to newvnode children - if (newvnode.children) { - function mapChildren(parent1, parent2) { - const map = []; - for (let j = 0; j < parent1.children.length; j++) { - let found = false; - for (let k = 0; k < parent2.children.length; k++) { - if (equal(parent1.children[j], parent2.children[k])) { - map.push(k); - found = true; - break; - } + function mapChildren(parent1, parent2) { + const map = []; + for (let j = 0; j < parent1.children.length; j++) { + let found = false; + for (let k = 0; k < parent2.children.length; k++) { + if (equal(parent1.children[j], parent2.children[k])) { + map.push(k); + found = true; + break; } - // node not in oldvnode - if (!found) { - map.push(-1); + } + // node not in oldvnode + if (!found) { + map.push(-1); + } + } + return map; + } + var newmap = mapChildren(newvnode, oldvnode); + var oldmap = mapChildren(oldvnode, newvnode); + var notFoundInOld = newmap.indexOf(-1); + var notFoundInNew = oldmap.indexOf(-1); + if (equal(newmap, oldmap) && (notFoundInNew >= 0 || notFoundInOld >= 0)) { + // Something changed + for (let i = 0; i < newmap.length; i++) { + if (newmap[i] === -1) { + if (oldvnode.children[i].type === "#text") { + oldvnode.children[i] = newvnode.children[i]; + node.childNodes[i].nodeValue = newvnode.children[i].value; + } else { + oldvnode.children[i].redraw({ + node: node.childNodes[i], + vnode: newvnode.children[i], + }); } } - return map; } - var newmap = mapChildren(newvnode, oldvnode); - var oldmap = mapChildren(oldvnode, newvnode); + } else { var notFoundInOld = newmap.indexOf(-1); var notFoundInNew = oldmap.indexOf(-1); - if (equal(newmap, oldmap) && (notFoundInNew >= 0 || notFoundInOld >= 0)) { - // Something changed - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1) { - if (oldvnode.children[i].type === "#text") { - oldvnode.children[i] = newvnode.children[i]; - node.childNodes[i].nodeValue = newvnode.children[i].value; - } else { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], - }); - } - } + while (notFoundInOld >= 0 || notFoundInNew >= 0) { + // First remove children not found in new map, then add the missing ones. + if (notFoundInNew >= 0) { + // while there are children not found in newvnode, remove them and re-check + node.removeChild(node.childNodes[notFoundInNew]); + oldvnode.children.splice(notFoundInNew, 1); + newmap = mapChildren(newvnode, oldvnode); + oldmap = mapChildren(oldvnode, newvnode); + notFoundInNew = oldmap.indexOf(-1); + notFoundInOld = newmap.indexOf(-1); } - } else { - var notFoundInOld = newmap.indexOf(-1); - var notFoundInNew = oldmap.indexOf(-1); - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - // while there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); - } - if (notFoundInOld >= 0) { - // while there are children not found in oldvnode, add them and re-check - node.insertBefore( - newvnode.children[notFoundInOld].render(), - node.childNodes[notFoundInOld] - ); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); - newmap = mapChildren(newvnode, oldvnode); - oldmap = mapChildren(oldvnode, newvnode); - notFoundInNew = oldmap.indexOf(-1); - notFoundInOld = newmap.indexOf(-1); - } + if (notFoundInOld >= 0) { + // while there are children not found in oldvnode, add them and re-check + node.insertBefore( + newvnode.children[notFoundInOld].render(), + node.childNodes[notFoundInOld] + ); + oldvnode.children.splice( + notFoundInOld, + 0, + newvnode.children[notFoundInOld] + ); + newmap = mapChildren(newvnode, oldvnode); + oldmap = mapChildren(oldvnode, newvnode); + notFoundInNew = oldmap.indexOf(-1); + notFoundInOld = newmap.indexOf(-1); } } } // innerHTML - if (oldvnode.$html !== newvnode.$html) { - if (newvnode.children.length === 0 && newvnode.$html) { - node.innerHTML = newvnode.$html; - } + if (newvnode.$html) { + node.innerHTML = newvnode.$html; oldvnode.$html = newvnode.$html; } }

@@ -496,10 +493,6 @@ this.events[event].forEach((i) => {

this.state = { ...this.state, ...i(this.state, data) }; }); } - } - - get(arg) { - return arg ? this.state[arg] : this.state; } on(event, cb) {

@@ -533,7 +526,7 @@ constructor({ element, routes, store, location }) {

this.element = element; this.redraw = null; this.store = store; - this.location = location || window.location; + this.location = location || window.location; if (!routes || Object.keys(routes).length === 0) { throw new Error("[Router] No routes defined."); }

@@ -622,7 +615,9 @@ let { element, routes, modules, preStart, postStart, location } = config;

if (!routes) { // Assume config is a component object, define default route if (typeof config !== "function") { - throw new Error("[h3.init] The specified argument is not a valid configuration object or component function"); + throw new Error( + "[h3.init] The specified argument is not a valid configuration object or component function" + ); } routes = { "/": config }; }

@@ -633,7 +628,7 @@ }

// Initialize store store = new Store(); (modules || []).forEach((i) => { - if (i) i(store); + i(store); }); store.dispatch("$init"); // Initialize router

@@ -670,7 +665,7 @@ throw new Error(

"[h3.state] No application initialized, unable to retrieve current state." ); } - return store.get(); + return store.state; }, });