{"id":13672718,"url":"https://github.com/retrohacker/template","last_synced_at":"2025-04-05T13:02:04.303Z","repository":{"id":66013958,"uuid":"598831295","full_name":"retrohacker/template","owner":"retrohacker","description":"A simple framework for webapps","archived":false,"fork":false,"pushed_at":"2024-04-01T22:24:51.000Z","size":48,"stargazers_count":490,"open_issues_count":2,"forks_count":9,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-03-29T12:02:50.440Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/retrohacker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-07T22:21:28.000Z","updated_at":"2025-03-26T00:24:08.000Z","dependencies_parsed_at":"2024-11-11T10:32:51.767Z","dependency_job_id":"6070e588-1604-4b91-9e4d-a6e74ae4e5ba","html_url":"https://github.com/retrohacker/template","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retrohacker%2Ftemplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retrohacker%2Ftemplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retrohacker%2Ftemplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/retrohacker%2Ftemplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/retrohacker","download_url":"https://codeload.github.com/retrohacker/template/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247339147,"owners_count":20923013,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T09:01:45.316Z","updated_at":"2025-04-05T13:02:04.281Z","avatar_url":"https://github.com/retrohacker.png","language":"TypeScript","readme":"# Template\n\nTemplate is a simple JS framework for creating interactive applications.\n\nIt focuses on using web-native patterns.\n\nCalling it a framework is a bit of an exaggeration, it's a single `class` that\nmanages HTML `\u003ctemplate\u003e`s.\n\nThe entire \"framework\" is here: [./template.ts](./template.ts)\n\n# Usage\n\nYour Hello World example:\n\n```html\n\u003c!DOCTYPE \u003e\n\u003chtml\u003e\n  \u003chead\u003e\u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"app\"\u003e\u003c/div\u003e\n    \u003cscript type=\"module\"\u003e\n      import Template from \"./template.js\";\n      // Create an HTMLTemplateElement from the text representation of HTML\n      const html = Template.createElement(\n        '\u003cdiv\u003e\u003ch1 class=\"message\"\u003eHello Template\u003c/h2\u003e\u003c/div\u003e'\n      );\n      // Create an HTMLStyleElement from the text representation of a style node\n      const css = Template.createStyle(\"\u003cstyle\u003e\u003c/style\u003e\");\n      class HelloWorld extends Template {\n        constructor() {\n          super(html, css);\n        }\n        setMessage(msg) {\n          // We can use getElement to query for child nodes, in this case: class=\"message\"\n          // Anything you want to update during runtime should be stored on \"this\"\n          const message = this.getElement(\".message\");\n          // Update the content of \u003ch1 class=\"message\"\u003e\n          message.innerText = msg;\n        }\n      }\n      // Get the div we want to mount into\n      const app = document.getElementById(\"app\");\n      // Create an instance of our HelloWorld component\n      const helloworld = new HelloWorld();\n      // Mount our component into the dom\n      helloworld.mount(app);\n      // Set our message\n      helloworld.setMessage(\"Hello Template!\");\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n# How it works\n\nTemplate uses HTML\n[`\u003ctemplate\u003e`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)\nto create reusable components.\n\nWe then \"mount\" a `\u003ctemplate\u003e` into an element on the page by creating a\n[shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM).\n\nThe shadow DOM encapsulates the reusable components making sure that the CSS and\nJS from one component can not interfere with another.\n\nUsing `.addChild(selector: string, child: Template)` allows us to nest these\ncomponents. The parent keeps track of all mounted children. When we unmount the\nparent, it will recursively unmount all children.\n\nWe follow React's philosophy of state flowing down. Our components manage their\nown children, configuring the child's state. Children manage updating their own\nDOM to reflect changes in state.\n\nWe also follow React's philosophy of state bubbling up. When a user interacts\nwith a child node, the child node emits an event. The parent receives the event\nand decides what to do.\n\nState management, event propogation, etc. are still early and need more thought.\n\n# Build process\n\nYou'll want to use a bundler like `vite`\n\nFor example:\n\n`index.html`\n\n```html\n\u003cdiv class=\"WelcomeComponent\"\u003e\n  \u003cdiv class=\"new button\"\u003e\n    \u003cdiv class=\"icon\"\u003e\u003c/div\u003e\n    \u003cdiv class=\"title\"\u003eCreate New\u003cbr /\u003eAccount\u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"pair button\"\u003e\n    \u003cdiv class=\"icon\"\u003e\u003c/div\u003e\n    \u003cdiv class=\"title\"\u003ePair Existing\u003cbr /\u003eAccount\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n`index.css`\n\n```css\n.WelcomeComponent {\n  background-color: hsla(0, 0%, 100%, 1);\n  border-radius: 5px;\n  display: flex;\n  flex-direction: row;\n}\n.button {\n  padding: 1em;\n  margin: 0.5em;\n  cursor: pointer;\n  background-color: inherit;\n  transition: background-color linear 0.1s;\n  border-radius: inherit;\n}\n.button:hover {\n  background-color: hsla(0, 0%, 90%, 1);\n  transition: background-color linear 0.1s;\n}\n```\n\n`index.ts`\n\n```typescript\nimport Template from \"template\";\nimport Feather from \"feather-icons\";\nimport html from \"./index.html?raw\";\nimport css from \"./index.css?raw\";\n\nconst template = Template.createElement(html);\nconst style = Template.createStyle(css);\n\nclass Welcome extends Template {\n  constructor() {\n    super(template, style);\n  }\n  mount(host: HTMLElement) {\n    super.mount(host);\n    this.getElement(\".new \u003e .icon\").innerHTML =\n      Feather.icons[\"user-plus\"].toSvg();\n    this.getElement(\".new\").addEventListener(\"click\", () =\u003e {\n      this.emit(\"new\");\n    });\n    this.getElement(\".pair \u003e .icon\").innerHTML =\n      Feather.icons[\"smartphone\"].toSvg();\n    this.getElement(\".pair\").addEventListener(\"click\", () =\u003e {\n      this.emit(\"pair\");\n    });\n    return this;\n  }\n}\n\nexport default Welcome;\n```\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fretrohacker%2Ftemplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fretrohacker%2Ftemplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fretrohacker%2Ftemplate/lists"}