{"id":15388089,"url":"https://github.com/calebdwilliams/templiteral","last_synced_at":"2025-04-15T18:30:42.126Z","repository":{"id":57376687,"uuid":"108481490","full_name":"calebdwilliams/templiteral","owner":"calebdwilliams","description":"A light-weight tool to reactivly generate and update markup in-browser. Templiteral can be used to manage native data, property and event bindings using familiar syntax without the need for an external compiler or complicated build tools.","archived":false,"fork":false,"pushed_at":"2019-05-03T02:29:24.000Z","size":558,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-29T10:21:56.813Z","etag":null,"topics":["custom-elements-v1","customelements","dom","html","javascript","reactive","template","template-engine","template-literal"],"latest_commit_sha":null,"homepage":"https://codepen.io/calebdwilliams/pen/mXBryE","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/calebdwilliams.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-27T00:51:48.000Z","updated_at":"2020-12-20T23:06:49.000Z","dependencies_parsed_at":"2022-08-29T19:11:16.115Z","dependency_job_id":null,"html_url":"https://github.com/calebdwilliams/templiteral","commit_stats":null,"previous_names":[],"tags_count":52,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebdwilliams%2Ftempliteral","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebdwilliams%2Ftempliteral/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebdwilliams%2Ftempliteral/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/calebdwilliams%2Ftempliteral/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/calebdwilliams","download_url":"https://codeload.github.com/calebdwilliams/templiteral/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248638909,"owners_count":21137730,"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":["custom-elements-v1","customelements","dom","html","javascript","reactive","template","template-engine","template-literal"],"created_at":"2024-10-01T14:55:27.033Z","updated_at":"2025-04-15T18:30:42.106Z","avatar_url":"https://github.com/calebdwilliams.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Templiteral\n\nTempliteral is a light-weight tool to reactively generate and update markup in-browser without the need for any framework or dependencies. Designed to work with the `customElements` spec, Templiteral can be used to manage native data, property and event bindings using familiar syntax without the need for an external compiler or complicated build tools.\n\n[Try templiteral for yourself on CodePen](https://codepen.io/calebdwilliams/pen/mXBryE).\n\n## Installation\n\nInstall with npm or yarn.\n\n```bash\nnpm i templiteral\n# OR\nyarn add templiteral\n```\n\n## How it Works\n\nThe `templiteral` function takes two optional arguments (a location and a context) and returns a function that serves as an ECMAScript 2015 [template literal tag](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), a function that takes in a template literal and returns some other object. Repeated calls to this function will update the previously-inserted DOM nodes if a binding is present.\n\nSo a template literal tag would look like the following:\n\n```javascript\nfunction myTag(strings, ...values) {\n  return strings.map((string, index) =\u003e `${string} values[index]`).join('');\n}\nconst message = 'world';\nmyTag`Hello ${message}`; // Hello world\n```\n\nIf the arguments aren't present, both will default to the call site's `this` and the relevant `shadowRoot` if one is present.\n\nThe templiteral function returns a tag function that serves as a renderer.\n\n## Example\n\n```javascript\n\nThe following example uses ESNext features and ECMAScript modules which might not be supported by all browsers:\n\nimport { templiteral } from 'templiteral';\n\nclass MyEl extends HTMLElement {\n  #templiteral = templiteral;\n\n  constructor() {\n    super();\n    this.attachShadow({ mode: 'open' });\n    this.username = 'templiteral';\n  }\n\n  connectedCallback() {\n    this.render();\n  }\n\n  render() {\n    this.#templiteral()`\n      \u003ch1\u003eHello ${this.username}\u003c/h1\u003e\n      \u003cp\u003eYou're alright.\u003c/p\u003e\n    `;\n  }\n}\n\ncustomElements.define('my-el', MyEl);\n```\n\nNow when an instance of `my-el` is inserted into the DOM, the `render` method will be called and insert the template into the element's shadow root.\n\n## Event bindings\n\nTempliteral provides Angular-style event bindings using the `(\u003ceventName\u003e)=\"${this.eventHandler}\"` syntax.\n\n```html\n\u003cbutton (click)=\"${this.logClickEvent}\"\u003eLog a message\u003c/button\u003e\n```\n\nThis would call the component's `logClickEvent` with the event object as the argument. As you might expect, you can also pass object properties or other arguments in the function invocation as well.\n\n## Property bindings\n\nSimilar to the event bindings above, property bindings use the bracket notation `[\u003cpropertyName\u003e]=\"${this.someProp}\"`.\n\n```html\n\u003cinput type=\"text\" id=\"username\" name=\"username\" [required]=\"${this.isRequired}\" [value]=\"${this.username}\"\u003e\n```\n\n## Component base\n\nTempliteral exports a `Component` abstract class that provides a significant boilerplate for building custom elements. By utilizing the built-in static getter `boundAttributes` which returns an array of property names, you will keep your attribute and property vaules in sync.\n\nIf a bound attribute is a boolean value, the component supports a static getter `booleanAttributes`. Note that all boolean attributes must also be bound attributes.\n\nIf an attribute is also set inside the static `boundProps` array, it will be reflected inside the component itself. The state, property and attribute will all be bound together. A change to one will be reflected in the others.\n\nIn addition, `Component`'s render method will be called when any bound attribute changes. Along with the renderer, a new element method, `html` serves as an alias for `this.templiteral()`:\n\n[See this demo on CodePen.](https://codepen.io/calebdwilliams/pen/qyOGJO?editors=1010#0)\n\n```javascript\nimport { Component } from 'templiteral';\n\nclass HelloWorld extends Component {\n  static get boundAttributes() { return ['who', 'now']; }\n\n  constructor() {\n    super();\n    this.state = {\n      who: this.getAttribute('who'),\n      now: new Date().toLocaleString()\n    };\n    this.interval = setInterval(this.updateTime.bind(this), 100);\n  }\n  \n  onDestroy() {\n    window.clearInterval(this.interval);\n  }\n \n  updateTime() {\n    this.now = new Date().toLocaleString();    \n  }\n \n  render() {\n    this.html`\n      \u003ch1\u003eHello ${this.state.who}\u003c/h1\u003e\n      \u003cp\u003e${this.state.now}\u003c/p\u003e\n    `;\n  }\n}\n \ncustomElements.define('hello-world', HelloWorld);\n```\n\nThe `\u003chello-world who=\"world\"\u003e\u003c/hello-world\u003e` element would now have attributes in sync with the data and would automatically re-render the time every 100 milliseconds.\n\nThe `Component` base class also includes a custom event emitter utility simply called `emit`. `Component.emit` takes two arguments, the first is a string representing the `CustomEvent` name and the second is the detail object on the event for passing information outside of the component. [These events are, by default, composed](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event) so they will bubble through barriers in the shadow DOM if one is attached.\n\n[See this demo on CodePen.](https://codepen.io/calebdwilliams/pen/MBobmZ)\n\n```javascript\nimport { Component } from 'templiteral';\n\nclass EmitExample extends Component {\n  render() {\n    this.html`\n      \u003cbutton (click)=${() =\u003e this.emit('button-clicked', new Date())}\"\u003eWill emit an event called \u003ccode\u003ebutton-clicked\u003c/code\u003e\u003c/button\u003e`;\n  }\n}\n```\n\nOne caveat to using `Component` is that the immediate renderer will not be called until all `boundAttributes` have been defined. Typically this should be done in the `constructor`, `connectedCallback` or `onInit`; however, when you override the `constructor`, `connectedCallback` or `disconnectedCallback` methods, make sure to call the method on `super` first to preserve functionality. `onInit` is a utility method that gets called when the component is done appending content to the DOM.\n\n## Element references\n\nSimilar to React, you can create a simple element reference inside your template with the `ref` attribute: \n\n```html\n\u003cinput type=\"text\" id=\"username\" ref=\"username\"\u003e\n```\n\nand in your component file:\n\n```javascript\nthis.username = this.refs.username.value;\n```\n\nIf you intend to perform some action on element references, it is probably best to use the `onInit` lifecycle method described above.\n\n## Property updated hooks\n\nWhen a property is updated, often a developer will want to be notified. This is accomplished using the `Component.prototype.updatedHooks` API. `updatedHooks` is a [JavaScript Map object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) that takes a property name and a callback function with the argument signature of `value: any, attributeName: string`. This callback _will not_ be called on the initial setting of the property value, but will be called on all subsequent calls to the property's setter.\n\n```javascript\nimport { Component } from 'templiteral'; \nclass MyEl extends Component {\n  static get boundAttributes() { return ['title']; }\n  constructor() {\n    super();\n\n    this.updatedHooks.set('title', this._titleUpdated);\n  }\n\n  render() { /** Omitted */ }\n\n  _titleUpdated(newTitle, attributeName) {\n    /** Omitted */\n  }\n}\n```\n\n### Array templates\n\nLoops are created using the built-in `Array` prototype methods and the use of the `fragment` function. `fragment` takes in a unique key for each element in the array. Normally, the item's index should suffice, but in cases where there will be significant re-rendering, something else might be necessary.\n\n```jsx\n\u003cul\u003e\n  ${this.todos = todo =\u003e this.fragment(todo)`\n    \u003cli\u003e\n      \u003clabel\u003e\n        \u003cinput type=\"checkbox\" (change)=\"${this.done}\"\u003e\n        ${todo.title}\n      \u003c/label\u003e\n    \u003c/li\u003e\n  `}\n\u003c/ul\u003e\n```\n\nThe fragment's `this` methods will still be referenced to the containing component.\n\n## Conditional templates\n\nTo show/hide elements based on some condition, use the condition function. When used in a `Component`, you can use the element's built-in `if` method:\n\n```html\n${this.if(this.showTodos)`\n  \u003cul\u003e\n    ${this.todos = todo =\u003e this.fragment(todo)`\n      \u003cli\u003e\n        \u003clabel\u003e\n          \u003cinput type=\"checkbox\" (change)=\"${this.done}\"\u003e\n          ${todo.title}\n        \u003c/label\u003e\n      \u003c/li\u003e\n    `}\n  \u003c/ul\u003e\n`}\n```\n\n## Styling components\n\nThe previous method of styling components from version 3.x.x has been deprecated in favor of implementing the [constructible style sheets/adopted style sheets proposal](https://github.com/WICG/construct-stylesheets/blob/gh-pages/explainer.md). This package currently consumes the [construct style sheets polyfill](https://www.npmjs.com/package/construct-style-sheets-polyfill) to aid in styling components inside shadow roots.\n\n```javascript\nimport { Component } from 'templiteral';\n\n/** Construct a new style sheet */\nconst exampleStyles = new CSSStyleSheet();\n\n/** Replace the contents of the style sheet */\nexampleStyles.replace('* { color: tomato; }')\n  .then(console.log);\n\nclass StyleExample extends HTMLElement {\n  constructor() {\n    super();\n    this.attachShadow({ mode: 'open' });\n    /** Adopt the style sheet */\n    this.shadowRoot.adoptedStyleSheets = [exampleStyles];\n  }\n\n  render() {\n    this.html`\u003ch1\u003eHello world, this is tomato colored\u003c/h1\u003e`;\n  }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalebdwilliams%2Ftempliteral","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcalebdwilliams%2Ftempliteral","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcalebdwilliams%2Ftempliteral/lists"}