Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/calebdwilliams/templiteral
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.
https://github.com/calebdwilliams/templiteral
custom-elements-v1 customelements dom html javascript reactive template template-engine template-literal
Last synced: 3 months ago
JSON representation
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.
- Host: GitHub
- URL: https://github.com/calebdwilliams/templiteral
- Owner: calebdwilliams
- License: mit
- Created: 2017-10-27T00:51:48.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2019-05-03T02:29:24.000Z (almost 6 years ago)
- Last Synced: 2024-10-29T10:21:56.813Z (3 months ago)
- Topics: custom-elements-v1, customelements, dom, html, javascript, reactive, template, template-engine, template-literal
- Language: HTML
- Homepage: https://codepen.io/calebdwilliams/pen/mXBryE
- Size: 545 KB
- Stars: 13
- Watchers: 6
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Templiteral
Templiteral 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.
[Try templiteral for yourself on CodePen](https://codepen.io/calebdwilliams/pen/mXBryE).
## Installation
Install with npm or yarn.
```bash
npm i templiteral
# OR
yarn add templiteral
```## How it Works
The `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.
So a template literal tag would look like the following:
```javascript
function myTag(strings, ...values) {
return strings.map((string, index) => `${string} values[index]`).join('');
}
const message = 'world';
myTag`Hello ${message}`; // Hello world
```If the arguments aren't present, both will default to the call site's `this` and the relevant `shadowRoot` if one is present.
The templiteral function returns a tag function that serves as a renderer.
## Example
```javascript
The following example uses ESNext features and ECMAScript modules which might not be supported by all browsers:
import { templiteral } from 'templiteral';
class MyEl extends HTMLElement {
#templiteral = templiteral;constructor() {
super();
this.attachShadow({ mode: 'open' });
this.username = 'templiteral';
}connectedCallback() {
this.render();
}render() {
this.#templiteral()`
Hello ${this.username}
You're alright.
`;
}
}customElements.define('my-el', MyEl);
```Now 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.
## Event bindings
Templiteral provides Angular-style event bindings using the `()="${this.eventHandler}"` syntax.
```html
Log a message
```This 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.
## Property bindings
Similar to the event bindings above, property bindings use the bracket notation `[]="${this.someProp}"`.
```html
```
## Component base
Templiteral 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.
If a bound attribute is a boolean value, the component supports a static getter `booleanAttributes`. Note that all boolean attributes must also be bound attributes.
If 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.
In 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()`:
[See this demo on CodePen.](https://codepen.io/calebdwilliams/pen/qyOGJO?editors=1010#0)
```javascript
import { Component } from 'templiteral';class HelloWorld extends Component {
static get boundAttributes() { return ['who', 'now']; }constructor() {
super();
this.state = {
who: this.getAttribute('who'),
now: new Date().toLocaleString()
};
this.interval = setInterval(this.updateTime.bind(this), 100);
}
onDestroy() {
window.clearInterval(this.interval);
}
updateTime() {
this.now = new Date().toLocaleString();
}
render() {
this.html`
Hello ${this.state.who}
${this.state.now}
`;
}
}
customElements.define('hello-world', HelloWorld);
```The `` element would now have attributes in sync with the data and would automatically re-render the time every 100 milliseconds.
The `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.
[See this demo on CodePen.](https://codepen.io/calebdwilliams/pen/MBobmZ)
```javascript
import { Component } from 'templiteral';class EmitExample extends Component {
render() {
this.html`
this.emit('button-clicked', new Date())}">Will emit an event calledbutton-clicked
`;
}
}
```One 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.
## Element references
Similar to React, you can create a simple element reference inside your template with the `ref` attribute:
```html
```
and in your component file:
```javascript
this.username = this.refs.username.value;
```If you intend to perform some action on element references, it is probably best to use the `onInit` lifecycle method described above.
## Property updated hooks
When 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.
```javascript
import { Component } from 'templiteral';
class MyEl extends Component {
static get boundAttributes() { return ['title']; }
constructor() {
super();this.updatedHooks.set('title', this._titleUpdated);
}render() { /** Omitted */ }
_titleUpdated(newTitle, attributeName) {
/** Omitted */
}
}
```### Array templates
Loops 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.
```jsx
-
${todo.title}
${this.todos = todo => this.fragment(todo)`
`}
```
The fragment's `this` methods will still be referenced to the containing component.
## Conditional templates
To 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:
```html
${this.if(this.showTodos)`
-
${todo.title}
${this.todos = todo => this.fragment(todo)`
`}
`}
```
## Styling components
The 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.
```javascript
import { Component } from 'templiteral';
/** Construct a new style sheet */
const exampleStyles = new CSSStyleSheet();
/** Replace the contents of the style sheet */
exampleStyles.replace('* { color: tomato; }')
.then(console.log);
class StyleExample extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
/** Adopt the style sheet */
this.shadowRoot.adoptedStyleSheets = [exampleStyles];
}
render() {
this.html`
Hello world, this is tomato colored
`;}
}
```