Template
Template is a simple JS framework for creating interactive applications.
It focuses on using web-native patterns.
Calling it a framework is a bit of an exaggeration, it's a single class
that
manages HTML <template>
s.
The entire "framework" is here: ./template.ts
Usage
Your Hello World example:
<!DOCTYPE >
<html>
<head></head>
<body>
<div id="app"></div>
<script type="module">
import Template from "./template.js";
// Create an HTMLTemplateElement from the text representation of HTML
const html = Template.createElement(
'<div><h1 class="message">Hello Template</h2></div>'
);
// Create an HTMLStyleElement from the text representation of a style node
const css = Template.createStyle("<style></style>");
class HelloWorld extends Template {
constructor() {
super(html, css);
}
setMessage(msg) {
// We can use getElement to query for child nodes, in this case: class="message"
// Anything you want to update during runtime should be stored on "this"
const message = this.getElement(".message");
// Update the content of <h1 class="message">
message.innerText = msg;
}
}
// Get the div we want to mount into
const app = document.getElementById("app");
// Create an instance of our HelloWorld component
const helloworld = new HelloWorld();
// Mount our component into the dom
helloworld.mount(app);
// Set our message
helloworld.setMessage("Hello Template!");
</script>
</body>
</html>
How it works
Template uses HTML
<template>
to create reusable components.
We then "mount" a <template>
into an element on the page by creating a
shadow DOM.
The shadow DOM encapsulates the reusable components making sure that the CSS and JS from one component can not interfere with another.
Using .addChild(selector: string, child: Template)
allows us to nest these
components. The parent keeps track of all mounted children. When we unmount the
parent, it will recursively unmount all children.
We follow React's philosophy of state flowing down. Our components manage their own children, configuring the child's state. Children manage updating their own DOM to reflect changes in state.
We also follow React's philosophy of state bubbling up. When a user interacts with a child node, the child node emits an event. The parent receives the event and decides what to do.
State management, event propogation, etc. are still early and need more thought.
Build process
You'll want to use a bundler like vite
For example:
index.html
<div class="WelcomeComponent">
<div class="new button">
<div class="icon"></div>
<div class="title">Create New<br />Account</div>
</div>
<div class="pair button">
<div class="icon"></div>
<div class="title">Pair Existing<br />Account</div>
</div>
</div>
index.css
.WelcomeComponent {
background-color: hsla(0, 0%, 100%, 1);
border-radius: 5px;
display: flex;
flex-direction: row;
}
.button {
padding: 1em;
margin: 0.5em;
cursor: pointer;
background-color: inherit;
transition: background-color linear 0.1s;
border-radius: inherit;
}
.button:hover {
background-color: hsla(0, 0%, 90%, 1);
transition: background-color linear 0.1s;
}
index.ts
import Template from "template";
import Feather from "feather-icons";
import html from "./index.html?raw";
import css from "./index.css?raw";
const template = Template.createElement(html);
const style = Template.createStyle(css);
class Welcome extends Template {
constructor() {
super(template, style);
}
mount(host: HTMLElement) {
super.mount(host);
this.getElement(".new > .icon").innerHTML =
Feather.icons["user-plus"].toSvg();
this.getElement(".new").addEventListener("click", () => {
this.emit("new");
});
this.getElement(".pair > .icon").innerHTML =
Feather.icons["smartphone"].toSvg();
this.getElement(".pair").addEventListener("click", () => {
this.emit("pair");
});
return this;
}
}
export default Welcome;