Merge branch 'dev' of github.com:h3rald/h3 into dev
@@ -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" }), ]);@@ -7850,9 +7850,23 @@ <li>The <code>classList</code> attribute can be set to a list of classes to apply to the element (as an alternative to using the element selector shorthand).</li>
<li>The <code>data</code> attribute can be set to a simple object containing <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes">data attributes</a>.</li> <li>The special <code>$key</code> attribute can be used to guarantee the uniqueness of two VNodes and it will not be translated into an HTML attribute.</li> <li>The special <code>$html</code> attribute can be used to set the <code>innerHTML</code> property of the resulting HTML element. Use only if you know what you are doing!</li> +<li>The special <code>$onrender</code> attribute can be set to a function that will executed after the VNode is rendered for the first time.</li> </ul> +<p>The <code>$html</code> and the <code>$onrender</code> special attributes should be used sparingly, and typically only when interfacing with third-party libraries that need access to the real DOM.</p> + +<p>For example, consider the following code snippet that can be used to initialize the <a href="https://github.com/Inscryb/inscryb-markdown-editor">InscrybMDE</a> Markdown editor on a textarea element:</p> + +<pre><code class="js">h3("textarea", { + $onrender: (element) => { + const editor = new window.InscrybMDE({ + element + }); + } +}); +</code></pre> + <a name="h3.dispatch(event:-string,-data:-any)"></a> <h3>h3.dispatch(event: string, data: any)<a href="#document-top" title="Go to top"></a></h3>@@ -8001,7 +8015,7 @@ </ul>
</div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco – May 9, 2020</p> + <p><span class="copy"></span> Fabio Cevasco – May 16, 2020</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
@@ -1,9 +1,9 @@
/** + * H3 v0.5.0 "Experienced El-Aurian" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ const checkProperties = (obj1, obj2) => { for (const key in obj1) {@@ -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" }), ]);
@@ -1,9 +1,9 @@
/** + * H3 v0.5.0 "Experienced El-Aurian" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ const checkProperties = (obj1, obj2) => { for (const key in obj1) {@@ -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" }), ]);
@@ -1,9 +1,9 @@
/** + * H3 v0.5.0 "Experienced El-Aurian" * Copyright 2020 Fabio Cevasco <h3rald@h3rald.com> * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ const checkProperties = (obj1, obj2) => { for (const key in obj1) {@@ -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": {
@@ -6,9 +6,21 @@ const overview = "./docs/md/overview.md";
const app = "./docs/js/app.js"; const tutorial = "./docs/md/tutorial.md"; const package = "./package.json"; +const h3 = "./h3.js"; const pkg = JSON.parse(fs.readFileSync(package, "utf8")); +// Update h3.js + +const h3Data = fs.readFileSync(h3, "utf8"); +const notice = h3Data.match(/\/\*\*((.|\n|\r)+?)\*\//gm)[0]; +const newNotice = notice + .replace(/v\d+\.\d+\.\d+/, `v${pkg.version}`) + .replace(/\"[^"]+\"/, `"${pkg.versionName}"`) + .replace(/Copyright \d+/, `Copyright ${new Date().getFullYear()}`); +fs.writeFileSync(h3, h3Data.replace(notice, newNotice)); + + // Update README.md let readmeData = fs.readFileSync(readme, "utf8"); readmeData = readmeData.replace(/v\d+\.\d+\.\d+/, `v${pkg.version}`);@@ -16,7 +28,7 @@ readmeData = readmeData.replace(
/Download v\d+\.\d+\.\d+ \([^)]+\)/, `Download v${pkg.version} (${pkg.versionName})` ); -readmeData = readmeData.replace(/### Can I download(\n|.)+/gm, ''); +readmeData = readmeData.replace(/### Can I download(\n|.)+/gm, ""); fs.writeFileSync(readme, readmeData); // Remove badges and copy to overview.md