Implemented $onrender; updated release
@@ -19,7 +19,7 @@ ### I'm sold! Where can I get it?
Here, look, it's just one file: -<a href="https://raw.githubusercontent.com/h3rald/h3/v0.4.0/h3.js" target="_blank" class="button primary">Download v0.4.0 (Dedicated Denobulan)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.5.0/h3.js" target="_blank" class="button primary">Download v0.5.0 (Experienced El-Aurian)</a> Yes there is also a [NPM package](https://www.npmjs.com/package/@h3rald/h3) if you want to use it with WebPack and similar, but let me repeat: _it's just one file_.
@@ -22,11 +22,12 @@ beforeEach(async () => {
const preStart = () => (preStartCalled = true); const postStart = () => (postStartCalled = true); const C1 = () => { + const $onrender = (node) => node.classList.add("test"); const parts = h3.route.parts; const content = Object.keys(parts).map((key) => h3("li", `${key}: ${parts[key]}`) ); - return h3("ul.c1", content); + return h3("ul.c1", { $onrender }, content); }; const C2 = () => { const params = h3.route.params;@@ -59,6 +60,9 @@ it("should support the capturing of parts within the current route", () => {
mockLocation.hash = "#/c1/1/2/3"; expect(document.body.childNodes[0].childNodes[1].textContent).toEqual( "b: 2" + ); + expect(document.body.childNodes[0].classList.contains("test")).toEqual( + true ); });
@@ -68,6 +68,19 @@ expect(node.childNodes[1].constructor).toEqual(HTMLLIElement);
expect(node.childNodes[0].childNodes[0].data).toEqual("test1"); }); + it("should provide an $onrender special attribute to execute code after the vnode is first rendered", () => { + const addClass = (node) => node.classList.add(node.tagName); + const vnode = h3("div", h3("span", {$onrender: addClass})); + let node = vnode.render(); + expect(node.childNodes[0].classList.contains("SPAN")).toEqual(true); + const vnode2 = h3("div", h3("div", {$onrender: addClass})); + vnode.redraw({node, vnode: vnode2}); + expect(node.childNodes[0].classList.contains("DIV")).toEqual(true); + const vnode3 = h3("div", [h3("div", {$onrender: addClass}), h3("h1", {$onrender: addClass})]); + vnode.redraw({node, vnode: vnode3}); + expect(node.childNodes[1].classList.contains("H1")).toEqual(true); + }); + it("should handle boolean attributes when redrawing", () => { const vnode1 = h3("input", { type: "checkbox", checked: true }); const node = vnode1.render();
@@ -7331,7 +7331,7 @@ <h3>I’m sold! Where can I get it?<a href="#document-top" title="Go to top"></a></h3>
<p>Here, look, it’s just one file:</p> -<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.4.0/h3.js" target="_blank" class="button primary">Download v0.4.0 (Dedicated Denobulan)</a></p> +<p><a href="https://raw.githubusercontent.com/h3rald/h3/v0.5.0/h3.js" target="_blank" class="button primary">Download v0.5.0 (Experienced El-Aurian)</a></p> <p>Yes there is also a <a href="https://www.npmjs.com/package/@h3rald/h3">NPM package</a> if you want to use it with WebPack and similar, but let me repeat: <em>it’s just one file</em>.</p>@@ -7664,8 +7664,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [
h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.4.0"), - h3("div.version-label", "“Dedicated Denobulan“"), + h3("div.version-number", "v0.5.0"), + h3("div.version-label", "“Experienced El-Aurian“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);@@ -8001,7 +8001,7 @@ </ul>
</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco – May 8, 2020</p> + <p><span class="copy"></span> Fabio Cevasco – May 13, 2020</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
@@ -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,@@ -602,7 +610,9 @@ 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");
@@ -44,8 +44,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [
h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.4.0"), - h3("div.version-label", "“Dedicated Denobulan“"), + h3("div.version-number", "v0.5.0"), + h3("div.version-label", "“Experienced El-Aurian“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
@@ -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,@@ -602,7 +610,9 @@ 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");
@@ -114,6 +114,21 @@ * The `classList` attribute can be set to a list of classes to apply to the element (as an alternative to using the element selector shorthand).
* The `data` attribute can be set to a simple object containing [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes). * The special `$key` attribute can be used to guarantee the uniqueness of two VNodes and it will not be translated into an HTML attribute. * The special `$html` attribute can be used to set the `innerHTML` property of the resulting HTML element. Use only if you know what you are doing! +* The special `$onrender` attribute can be set to a function that will executed after the VNode is rendered for the first time. + +The `$html` and the `$onrender` special attributes should be used sparingly, and typically only when interfacing with third-party libraries that need access to the real DOM. + +For example, consider the following code snippet that can be used to initialize the [InscrybMDE](https://github.com/Inscryb/inscryb-markdown-editor) Markdown editor on a textarea element: + +```js +h3("textarea", { + $onrender: (element) => { + const editor = new window.InscrybMDE({ + element + }); + } +}); +``` ### h3.dispatch(event: string, data: any)
@@ -12,7 +12,7 @@ ### I'm sold! Where can I get it?
Here, look, it's just one file: -<a href="https://raw.githubusercontent.com/h3rald/h3/v0.4.0/h3.js" target="_blank" class="button primary">Download v0.4.0 (Dedicated Denobulan)</a> +<a href="https://raw.githubusercontent.com/h3rald/h3/v0.5.0/h3.js" target="_blank" class="button primary">Download v0.5.0 (Experienced El-Aurian)</a> Yes there is also a [NPM package](https://www.npmjs.com/package/@h3rald/h3) if you want to use it with WebPack and similar, but let me repeat: _it's just one file_.
@@ -118,8 +118,8 @@ h3("a.logo.col-sm-1", { href: "#/" }, [
h3("img", { alt: "H3", src: "images/h3.svg" }), ]), h3("div.version.col-sm.col-md", [ - h3("div.version-number", "v0.4.0"), - h3("div.version-label", "“Dedicated Denobulan“"), + h3("div.version-number", "v0.5.0"), + h3("div.version-label", "“Experienced El-Aurian“"), ]), h3("label.drawer-toggle.button.col-sm-last", { for: "drawer-control" }), ]);
@@ -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,@@ -602,7 +610,9 @@ 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");
@@ -1,7 +1,7 @@
{ "name": "@h3rald/h3", - "version": "0.4.0", - "versionName": "Dedicated Denobulan", + "version": "0.5.0", + "versionName": "Experienced El-Aurian", "description": "A tiny, extremely minimalist JavaScript microframework.", "main": "h3.js", "scripts": {