all repos — h3 @ 167c47f9b5c1a2a7d350c236160e3ef33f627597

A tiny, extremely minimalist JavaScript microframework.

Added missing tests and regenerated.
h3rald h3rald@h3rald.com
Thu, 04 Jun 2020 21:17:20 +0200
commit

167c47f9b5c1a2a7d350c236160e3ef33f627597

parent

a5d734df7b9a47f8582b475bb7373d5dd69e5395

M __tests__/router.js__tests__/router.js

@@ -22,15 +22,12 @@ hash = value;

window.dispatchEvent(event); }, }; -const $onrender = (node) => { - node.classList.add("test"); -}; const C1 = () => { const parts = h3.route.parts; const content = Object.keys(parts).map((key) => h3("li", `${key}: ${parts[key]}`) ); - return h3("ul.c1", { $onrender }, content); + return h3("ul.c1", content); }; const C2 = () => {

@@ -38,7 +35,7 @@ const params = h3.route.params;

const content = Object.keys(params).map((key) => h3("li", `${key}: ${params[key]}`) ); - return h3("ul.c2", { $onrender }, content); + return h3("ul.c2", content); }; describe("h3 (Router)", () => {

@@ -69,9 +66,6 @@ it("should support the capturing of parts within the current route", (done) => {

const sub = h3.on("$redraw", () => { expect(document.body.childNodes[0].childNodes[1].textContent).toEqual( "b: 2" - ); - expect(document.body.childNodes[0].classList.contains("test")).toEqual( - true ); sub(); done();

@@ -102,34 +96,6 @@ });

} catch (e) { expect(e.message).toMatch(/No route matches/); } - }); - - it("should execute $onrender callback after each navigation", async () => { - let executions = []; - let count = 0; - const c = () => { - return h3( - "div", - { - $onrender: (node) => { - executions.push("test1"); - }, - }, - [ - h3("span", { - $onrender: (node) => { - executions.push("test2"); - }, - }), - ] - ); - }; - expect(executions).toEqual([]); - await h3.init({ - element: document.body, - routes: { "/": c }, - }); - expect(executions).toEqual(["test2", "test1"]); }); it("should execute setup and teardown methods", (done) => {
M __tests__/vnode.js__tests__/vnode.js

@@ -322,4 +322,24 @@ expect(v1).toEqual(v2);

v3.redraw({ node: n3, vnode: v4 }); expect(v3).toEqual(v4); }); + + it("should execute $onrender callbacks whenever a child node is added to the DOM", async () => { + let n; + const $onrender = (node) => { + n = node; + }; + const vn1 = h3("ul", [h3("li")]); + const vn2 = h3("ul", [h3("li"), h3("li.vn2", { $onrender })]); + const n1 = vn1.render(); + vn1.redraw({ node: n1, vnode: vn2 }); + expect(n.classList.value).toEqual("vn2"); + const vn3 = h3("ul", [h3("span.vn3", { $onrender })]); + vn1.redraw({ node: n1, vnode: vn3 }); + expect(n.classList.value).toEqual("vn3"); + vn2.render(); + expect(n.classList.value).toEqual("vn2"); + const rc = () => h3("div.rc", { $onrender }); + await h3.init(rc); + expect(n.classList.value).toEqual("rc"); + }); });
M docs/H3_DeveloperGuide.htmdocs/H3_DeveloperGuide.htm

@@ -8050,7 +8050,7 @@ </ul>

</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco &ndash; May 30, 2020</p> + <p><span class="copy"></span> Fabio Cevasco &ndash; June 4, 2020</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
M docs/example/assets/js/h3.jsdocs/example/assets/js/h3.js

@@ -59,7 +59,6 @@ }

return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; -let $onrenderCallbacks = []; const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax

@@ -189,7 +188,7 @@ ? attrs.classList

: this.classList; this.attributes = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on")) + .filter((a) => a.startsWith("on") && attrs[a]) .forEach((key) => { if (typeof attrs[key] !== "function") { throw new Error(

@@ -302,11 +301,11 @@ // Children

this.children.forEach((c) => { const cnode = c.render(); node.appendChild(cnode); + c.$onrender && c.$onrender(cnode); }); if (this.$html) { node.innerHTML = this.$html; } - this.$onrender && $onrenderCallbacks.push(() => this.$onrender(node)); return node; }

@@ -324,6 +323,7 @@ oldvnode !== newvnode)

) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; }

@@ -416,104 +416,87 @@ });

oldvnode.eventListeners = newvnode.eventListeners; } // 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 ( - parent1.children[j].equal(parent2.children[k]) && - !map.includes(k) - ) { - map.push(k); - found = true; - break; + + function mapChildren(oldvnode, newvnode) { + const maxLength = Math.max( + oldvnode.children.length, + newvnode.children.length + ); + let map = []; + for (let oldIndex = 0; oldIndex < oldvnode.children.length; oldIndex++) { + if (oldIndex >= newvnode.children.length) { + // not found in new node, remove from old + map.push(-3); + } else { + let found = -1; + for (let index = 0; index < oldvnode.children.length; index++) { + if ( + equal(oldvnode.children[oldIndex], newvnode.children[index]) && + !map.includes(index) + ) { + found = index; + break; + } } - } - if (!found) { - map.push(-1); + map.push(found); } } + // other nodes are new, needs to be added + if (maxLength > oldvnode.children.length) { + map = map.concat( + [...Array(maxLength - oldvnode.children.length)].map(() => -2) + ); + } return map; } - let newmap, oldmap, notFoundInNew, notFoundInOld; - const remap = () => { - // Map positions of newvnode children in relation to oldvnode children - newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - oldmap = mapChildren(oldvnode, newvnode); - notFoundInOld = newmap.indexOf(-1); - notFoundInNew = oldmap.indexOf(-1); - }; - remap(); - if (newmap.length === oldmap.length) { - if (equal(newmap, oldmap) && notFoundInNew >= 0) { - // Something changed (some nodes are different at the same position) - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(childMap.length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + for (let i of childMap) { + count++; + let breakFor = false; + if (i === count) { + // Matching nodes; + continue; + } + switch (i) { + case -1: + // different node, check + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - } + break; + case -2: + // add node + oldvnode.children.push(newvnode.children[count]); + const renderedNode = newvnode.children[count].render(); + node.appendChild(renderedNode); + newvnode.children[count].$onrender && + newvnode.children[count].$onrender(renderedNode); + breakFor = true; + break; + case -3: + // remove node + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + breakFor = true; + break; + default: + // Node found, move nodes and remap + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + node.insertBefore(node.childNodes[i], node.childNodes[count]); + breakFor = true; + break; } - } else { - // Nodes in different position (maps have same nodes) - let index = 0; - while (!equal(oldmap, [...Array(oldmap.length).keys()])) { - if (newmap[index] !== index) { - const child = node.childNodes[newmap[index]]; - node.removeChild(child); - node.insertBefore(child, node.childNodes[index]); - const cnode = oldvnode.children[newmap[index]]; - oldvnode.children = oldvnode.children.filter( - (c) => !equal(c, cnode) - ); - oldvnode.children.splice(index, 0, cnode); - remap(); - index = 0; - } else { - index++; - } + if (breakFor) { + break; } } - } else { - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - const childOfNew = - newvnode.children.length > notFoundInNew && - newvnode.children[notFoundInNew]; - const childOfOld = oldvnode.children[notFoundInNew]; - if ( - childOfNew && - childOfOld && - childOfOld.type === childOfNew.type && - childOfNew.children.length === 0 && - childOfNew.children.length === 0 - ) { - // Optimization to avoid removing simple nodes of the same type - oldvnode.children[notFoundInNew].redraw({ - node: node.childNodes[notFoundInNew], - vnode: newvnode.children[notFoundInNew], - }); - } else { - // While there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - } - } else { - // While there are children not found in oldvnode, add them and re-check - const cnode = newvnode.children[notFoundInOld].render(); - node.insertBefore(cnode, node.childNodes[notFoundInOld]); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); - } - remap(); - } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(childMap.length).keys()]; } // $onrender if (!equal(oldvnode.$onrender, newvnode.$onrender)) {

@@ -601,7 +584,6 @@ }

async start() { const processPath = async (data) => { - $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&

@@ -659,8 +641,7 @@ }

const vnode = newRouteComponent(newRouteComponent.state); const node = vnode.render(); this.element.appendChild(node); - $onrenderCallbacks.forEach((cbk) => cbk()); - $onrenderCallbacks = []; + vnode.$onrender && vnode.$onrender(node); this.setRedraw(vnode, newRouteComponent.state); window.scrollTo(0, 0); this.store.dispatch("$redraw");
M docs/js/h3.jsdocs/js/h3.js

@@ -59,7 +59,6 @@ }

return checkProperties(obj1, obj2); // && checkProperties(obj2, obj1); }; -let $onrenderCallbacks = []; const selectorRegex = /^([a-z][a-z0-9:_=-]*)(#[a-z0-9:_=-]+)?(\.[^ ]+)*$/i; // Virtual Node Implementation with HyperScript-like syntax

@@ -189,7 +188,7 @@ ? attrs.classList

: this.classList; this.attributes = attrs; Object.keys(attrs) - .filter((a) => a.startsWith("on")) + .filter((a) => a.startsWith("on") && attrs[a]) .forEach((key) => { if (typeof attrs[key] !== "function") { throw new Error(

@@ -302,11 +301,11 @@ // Children

this.children.forEach((c) => { const cnode = c.render(); node.appendChild(cnode); + c.$onrender && c.$onrender(cnode); }); if (this.$html) { node.innerHTML = this.$html; } - this.$onrender && $onrenderCallbacks.push(() => this.$onrender(node)); return node; }

@@ -324,6 +323,7 @@ oldvnode !== newvnode)

) { const renderedNode = newvnode.render(); node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; }

@@ -416,104 +416,87 @@ });

oldvnode.eventListeners = newvnode.eventListeners; } // 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 ( - parent1.children[j].equal(parent2.children[k]) && - !map.includes(k) - ) { - map.push(k); - found = true; - break; + + function mapChildren(oldvnode, newvnode) { + const maxLength = Math.max( + oldvnode.children.length, + newvnode.children.length + ); + let map = []; + for (let oldIndex = 0; oldIndex < oldvnode.children.length; oldIndex++) { + if (oldIndex >= newvnode.children.length) { + // not found in new node, remove from old + map.push(-3); + } else { + let found = -1; + for (let index = 0; index < oldvnode.children.length; index++) { + if ( + equal(oldvnode.children[oldIndex], newvnode.children[index]) && + !map.includes(index) + ) { + found = index; + break; + } } - } - if (!found) { - map.push(-1); + map.push(found); } } + // other nodes are new, needs to be added + if (maxLength > oldvnode.children.length) { + map = map.concat( + [...Array(maxLength - oldvnode.children.length)].map(() => -2) + ); + } return map; } - let newmap, oldmap, notFoundInNew, notFoundInOld; - const remap = () => { - // Map positions of newvnode children in relation to oldvnode children - newmap = mapChildren(newvnode, oldvnode); - // Map positions of oldvnode children in relation to newvnode children - oldmap = mapChildren(oldvnode, newvnode); - notFoundInOld = newmap.indexOf(-1); - notFoundInNew = oldmap.indexOf(-1); - }; - remap(); - if (newmap.length === oldmap.length) { - if (equal(newmap, oldmap) && notFoundInNew >= 0) { - // Something changed (some nodes are different at the same position) - for (let i = 0; i < newmap.length; i++) { - if (newmap[i] === -1 || oldmap[i] === -1) { - oldvnode.children[i].redraw({ - node: node.childNodes[i], - vnode: newvnode.children[i], + let childMap = mapChildren(oldvnode, newvnode); + let resultMap = [...Array(childMap.length).keys()]; + while (!equal(childMap, resultMap)) { + let count = -1; + for (let i of childMap) { + count++; + let breakFor = false; + if (i === count) { + // Matching nodes; + continue; + } + switch (i) { + case -1: + // different node, check + oldvnode.children[count].redraw({ + node: node.childNodes[count], + vnode: newvnode.children[count], }); - } + break; + case -2: + // add node + oldvnode.children.push(newvnode.children[count]); + const renderedNode = newvnode.children[count].render(); + node.appendChild(renderedNode); + newvnode.children[count].$onrender && + newvnode.children[count].$onrender(renderedNode); + breakFor = true; + break; + case -3: + // remove node + oldvnode.children.splice(count, 1); + node.removeChild(node.childNodes[count]); + breakFor = true; + break; + default: + // Node found, move nodes and remap + const vtarget = oldvnode.children.splice(i, 1)[0]; + oldvnode.children.splice(count, 0, vtarget); + node.insertBefore(node.childNodes[i], node.childNodes[count]); + breakFor = true; + break; } - } else { - // Nodes in different position (maps have same nodes) - let index = 0; - while (!equal(oldmap, [...Array(oldmap.length).keys()])) { - if (newmap[index] !== index) { - const child = node.childNodes[newmap[index]]; - node.removeChild(child); - node.insertBefore(child, node.childNodes[index]); - const cnode = oldvnode.children[newmap[index]]; - oldvnode.children = oldvnode.children.filter( - (c) => !equal(c, cnode) - ); - oldvnode.children.splice(index, 0, cnode); - remap(); - index = 0; - } else { - index++; - } + if (breakFor) { + break; } } - } else { - while (notFoundInOld >= 0 || notFoundInNew >= 0) { - // First remove children not found in new map, then add the missing ones. - if (notFoundInNew >= 0) { - const childOfNew = - newvnode.children.length > notFoundInNew && - newvnode.children[notFoundInNew]; - const childOfOld = oldvnode.children[notFoundInNew]; - if ( - childOfNew && - childOfOld && - childOfOld.type === childOfNew.type && - childOfNew.children.length === 0 && - childOfNew.children.length === 0 - ) { - // Optimization to avoid removing simple nodes of the same type - oldvnode.children[notFoundInNew].redraw({ - node: node.childNodes[notFoundInNew], - vnode: newvnode.children[notFoundInNew], - }); - } else { - // While there are children not found in newvnode, remove them and re-check - node.removeChild(node.childNodes[notFoundInNew]); - oldvnode.children.splice(notFoundInNew, 1); - } - } else { - // While there are children not found in oldvnode, add them and re-check - const cnode = newvnode.children[notFoundInOld].render(); - node.insertBefore(cnode, node.childNodes[notFoundInOld]); - oldvnode.children.splice( - notFoundInOld, - 0, - newvnode.children[notFoundInOld] - ); - } - remap(); - } + childMap = mapChildren(oldvnode, newvnode); + resultMap = [...Array(childMap.length).keys()]; } // $onrender if (!equal(oldvnode.$onrender, newvnode.$onrender)) {

@@ -601,7 +584,6 @@ }

async start() { const processPath = async (data) => { - $onrenderCallbacks = []; const oldRoute = this.route; const fragment = (data &&

@@ -659,8 +641,7 @@ }

const vnode = newRouteComponent(newRouteComponent.state); const node = vnode.render(); this.element.appendChild(node); - $onrenderCallbacks.forEach((cbk) => cbk()); - $onrenderCallbacks = []; + vnode.$onrender && vnode.$onrender(node); this.setRedraw(vnode, newRouteComponent.state); window.scrollTo(0, 0); this.store.dispatch("$redraw");
M h3.jsh3.js

@@ -473,7 +473,8 @@ // add node

oldvnode.children.push(newvnode.children[count]); const renderedNode = newvnode.children[count].render(); node.appendChild(renderedNode); - newvnode.children[count].$onrender && newvnode.children[count].$onrender(renderedNode); + newvnode.children[count].$onrender && + newvnode.children[count].$onrender(renderedNode); breakFor = true; break; case -3:

@@ -640,6 +641,7 @@ }

const vnode = newRouteComponent(newRouteComponent.state); const node = vnode.render(); this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); this.setRedraw(vnode, newRouteComponent.state); window.scrollTo(0, 0); this.store.dispatch("$redraw");