all repos — h3 @ cfb7b00eb7d90657f690407bf5e692d9bd1f82f6

A tiny, extremely minimalist JavaScript microframework.

Preventing double redrawing; updated tests.
h3rald h3rald@h3rald.com
Sat, 16 May 2020 23:08:01 +0200
commit

cfb7b00eb7d90657f690407bf5e692d9bd1f82f6

parent

e405c51d36c2b18460a352be6ec9e699c3c28e09

M __tests__/h3.js__tests__/h3.js

@@ -410,4 +410,17 @@ jest.spyOn(vnode, "redraw");

h3.redraw(); expect(vnode.redraw).toHaveBeenCalled(); }); + + it("should not redraw while a other redraw is in progress", async () => { + const vnode = h3("div"); + await h3.init({ + routes: { + "/": () => vnode + }, + }); + jest.spyOn(vnode, "redraw"); + h3.redraw(true); + h3.redraw() + expect(vnode.redraw).toHaveBeenCalledTimes(1); + }); });
M __tests__/vnode.js__tests__/vnode.js

@@ -308,4 +308,28 @@ expect(v1).toEqual(v2);

v3.redraw({ node: n3, vnode: v4 }); expect(v3).toEqual(v4); }); + + it("should expose an $onrender special attribute to execute a callback after rendering", async () => { + let test; + const vnode = h3("div", { $onrender: () => (test = 1) }); + await h3.init({ + routes: { + "/": () => vnode, + }, + }); + expect(test).toEqual(1); + const vnode1 = h3("div", h3("div")); + const vnode2 = h3("div", h3("span", { $onrender: () => (test = 2) })); + const node1 = vnode1.render(); + vnode1.redraw({ node: node1, vnode: vnode2 }); + expect(test).toEqual(2); + const vnode3 = h3("div", h3("div", { $onrender: () => (test = 4) })); + const vnode4 = h3("div", [ + h3("div", { $onrender: () => (test = 4) }), + h3("div", { $onrender: () => (test = 5) }), + ]); + const node3 = vnode3.render(); + vnode3.redraw({ node: node3, vnode: vnode4 }); + expect(test).toEqual(5); + }); });
M docs/H3_DeveloperGuide.htmdocs/H3_DeveloperGuide.htm

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

</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco &ndash; May 9, 2020</p> + <p><span class="copy"></span> Fabio Cevasco &ndash; May 16, 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

@@ -70,6 +70,7 @@ this.data = {};

this.id = undefined; this.$key = undefined; this.$html = undefined; + this.$onrender = undefined; this.style = undefined; this.value = undefined; this.children = [];

@@ -159,6 +160,7 @@ this.type = data.type;

this.id = data.id; this.$key = data.$key; this.$html = data.$html; + this.$onrender = data.$onrender; this.style = data.style; this.data = data.data; this.value = data.value;

@@ -176,6 +178,7 @@ processProperties(attrs) {

this.id = this.id || attrs.id; this.$key = attrs.$key; this.$html = attrs.$html; + this.$onrender = attrs.$onrender; this.style = attrs.style; this.value = attrs.value; this.data = attrs.data || {};

@@ -198,6 +201,7 @@ });

delete this.attributes.value; delete this.attributes.$key; delete this.attributes.$html; + delete this.attributes.$onrender; delete this.attributes.id; delete this.attributes.data; delete this.attributes.style;

@@ -295,7 +299,9 @@ node.dataset[key] = this.data[key];

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

@@ -315,7 +321,9 @@ (oldvnode.type === newvnode.type &&

oldvnode.type === "#text" && oldvnode !== newvnode) ) { - node.parentNode.replaceChild(newvnode.render(), node); + const renderedNode = newvnode.render(); + node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; }

@@ -465,10 +473,10 @@ oldvnode.children.splice(notFoundInNew, 1);

} } else { // While there are children not found in oldvnode, add them and re-check - node.insertBefore( - newvnode.children[notFoundInOld].render(), - node.childNodes[notFoundInOld] - ); + const cnode = newvnode.children[notFoundInOld].render(); + node.insertBefore(cnode, node.childNodes[notFoundInOld]); + newvnode.children[notFoundInOld].$onrender && + newvnode.children[notFoundInOld].$onrender(cnode); oldvnode.children.splice( notFoundInOld, 0,

@@ -596,16 +604,20 @@ }

if (!this.route) { throw new Error(`[Router] No route matches '${fragment}'`); } + redrawing = true; this.store.dispatch("$navigation", this.route); // Display View while (this.element.firstChild) { this.element.removeChild(this.element.firstChild); } const vnode = this.routes[this.route.def](); - this.element.appendChild(vnode.render()); + const node = vnode.render(); + this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); this.setRedraw(vnode); window.scrollTo(0, 0); this.store.dispatch("$redraw"); + redrawing = false; }; processPath(); window.addEventListener("hashchange", processPath);

@@ -627,6 +639,7 @@ };

let store = null; let router = null; +let redrawing = false; h3.init = (config) => { let { element, routes, modules, preStart, postStart, location } = config;

@@ -705,13 +718,18 @@ }

return store.dispatch(event, data); }; -h3.redraw = () => { +h3.redraw = (setRedrawing) => { if (!router || !router.redraw) { throw new Error( "[h3.redraw] No application initialized, unable to update." ); } + if (redrawing) { + return; + } + redrawing = true; router.redraw(); + redrawing = setRedrawing || false; }; export default h3;
M docs/js/h3.jsdocs/js/h3.js

@@ -70,6 +70,7 @@ this.data = {};

this.id = undefined; this.$key = undefined; this.$html = undefined; + this.$onrender = undefined; this.style = undefined; this.value = undefined; this.children = [];

@@ -159,6 +160,7 @@ this.type = data.type;

this.id = data.id; this.$key = data.$key; this.$html = data.$html; + this.$onrender = data.$onrender; this.style = data.style; this.data = data.data; this.value = data.value;

@@ -176,6 +178,7 @@ processProperties(attrs) {

this.id = this.id || attrs.id; this.$key = attrs.$key; this.$html = attrs.$html; + this.$onrender = attrs.$onrender; this.style = attrs.style; this.value = attrs.value; this.data = attrs.data || {};

@@ -198,6 +201,7 @@ });

delete this.attributes.value; delete this.attributes.$key; delete this.attributes.$html; + delete this.attributes.$onrender; delete this.attributes.id; delete this.attributes.data; delete this.attributes.style;

@@ -295,7 +299,9 @@ node.dataset[key] = this.data[key];

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

@@ -315,7 +321,9 @@ (oldvnode.type === newvnode.type &&

oldvnode.type === "#text" && oldvnode !== newvnode) ) { - node.parentNode.replaceChild(newvnode.render(), node); + const renderedNode = newvnode.render(); + node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; }

@@ -465,10 +473,10 @@ oldvnode.children.splice(notFoundInNew, 1);

} } else { // While there are children not found in oldvnode, add them and re-check - node.insertBefore( - newvnode.children[notFoundInOld].render(), - node.childNodes[notFoundInOld] - ); + const cnode = newvnode.children[notFoundInOld].render(); + node.insertBefore(cnode, node.childNodes[notFoundInOld]); + newvnode.children[notFoundInOld].$onrender && + newvnode.children[notFoundInOld].$onrender(cnode); oldvnode.children.splice( notFoundInOld, 0,

@@ -596,16 +604,20 @@ }

if (!this.route) { throw new Error(`[Router] No route matches '${fragment}'`); } + redrawing = true; this.store.dispatch("$navigation", this.route); // Display View while (this.element.firstChild) { this.element.removeChild(this.element.firstChild); } const vnode = this.routes[this.route.def](); - this.element.appendChild(vnode.render()); + const node = vnode.render(); + this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); this.setRedraw(vnode); window.scrollTo(0, 0); this.store.dispatch("$redraw"); + redrawing = false; }; processPath(); window.addEventListener("hashchange", processPath);

@@ -627,6 +639,7 @@ };

let store = null; let router = null; +let redrawing = false; h3.init = (config) => { let { element, routes, modules, preStart, postStart, location } = config;

@@ -705,13 +718,18 @@ }

return store.dispatch(event, data); }; -h3.redraw = () => { +h3.redraw = (setRedrawing) => { if (!router || !router.redraw) { throw new Error( "[h3.redraw] No application initialized, unable to update." ); } + if (redrawing) { + return; + } + redrawing = true; router.redraw(); + redrawing = setRedrawing || false; }; export default h3;
M h3.jsh3.js

@@ -70,6 +70,7 @@ this.data = {};

this.id = undefined; this.$key = undefined; this.$html = undefined; + this.$onrender = undefined; this.style = undefined; this.value = undefined; this.children = [];

@@ -159,6 +160,7 @@ this.type = data.type;

this.id = data.id; this.$key = data.$key; this.$html = data.$html; + this.$onrender = data.$onrender; this.style = data.style; this.data = data.data; this.value = data.value;

@@ -176,6 +178,7 @@ processProperties(attrs) {

this.id = this.id || attrs.id; this.$key = attrs.$key; this.$html = attrs.$html; + this.$onrender = attrs.$onrender; this.style = attrs.style; this.value = attrs.value; this.data = attrs.data || {};

@@ -198,6 +201,7 @@ });

delete this.attributes.value; delete this.attributes.$key; delete this.attributes.$html; + delete this.attributes.$onrender; delete this.attributes.id; delete this.attributes.data; delete this.attributes.style;

@@ -295,7 +299,9 @@ node.dataset[key] = this.data[key];

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

@@ -315,7 +321,9 @@ (oldvnode.type === newvnode.type &&

oldvnode.type === "#text" && oldvnode !== newvnode) ) { - node.parentNode.replaceChild(newvnode.render(), node); + const renderedNode = newvnode.render(); + node.parentNode.replaceChild(renderedNode, node); + newvnode.$onrender && newvnode.$onrender(renderedNode); oldvnode.from(newvnode); return; }

@@ -465,10 +473,10 @@ oldvnode.children.splice(notFoundInNew, 1);

} } else { // While there are children not found in oldvnode, add them and re-check - node.insertBefore( - newvnode.children[notFoundInOld].render(), - node.childNodes[notFoundInOld] - ); + const cnode = newvnode.children[notFoundInOld].render(); + node.insertBefore(cnode, node.childNodes[notFoundInOld]); + newvnode.children[notFoundInOld].$onrender && + newvnode.children[notFoundInOld].$onrender(cnode); oldvnode.children.splice( notFoundInOld, 0,

@@ -596,16 +604,20 @@ }

if (!this.route) { throw new Error(`[Router] No route matches '${fragment}'`); } + redrawing = true; this.store.dispatch("$navigation", this.route); // Display View while (this.element.firstChild) { this.element.removeChild(this.element.firstChild); } const vnode = this.routes[this.route.def](); - this.element.appendChild(vnode.render()); + const node = vnode.render(); + this.element.appendChild(node); + vnode.$onrender && vnode.$onrender(node); this.setRedraw(vnode); window.scrollTo(0, 0); this.store.dispatch("$redraw"); + redrawing = false; }; processPath(); window.addEventListener("hashchange", processPath);

@@ -627,6 +639,7 @@ };

let store = null; let router = null; +let redrawing = false; h3.init = (config) => { let { element, routes, modules, preStart, postStart, location } = config;

@@ -705,13 +718,18 @@ }

return store.dispatch(event, data); }; -h3.redraw = () => { +h3.redraw = (setRedrawing) => { if (!router || !router.redraw) { throw new Error( "[h3.redraw] No application initialized, unable to update." ); } + if (redrawing) { + return; + } + redrawing = true; router.redraw(); + redrawing = setRedrawing || false; }; export default h3;