Skip to content

Commit

Permalink
Adds <template is=“literal-shadow”>
Browse files Browse the repository at this point in the history
  • Loading branch information
stephband committed Aug 4, 2024
1 parent b89b79d commit fcc9604
Show file tree
Hide file tree
Showing 11 changed files with 979 additions and 299 deletions.
67 changes: 26 additions & 41 deletions literal-html/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,54 @@
A `literal-html` template may be placed anywhere in your HTML. It is designed to
make it easy to mix islands of dynamically rendered content into static content.
**/
A `literal-html` template is replaced in the DOM with it's own rendered content.
import noop from 'fn/noop.js';
import Signal from 'fn/signal.js';
import element, { getInternals as Internals } from 'dom/element.js';
import assignDataset from '../modules/dom/assign-dataset.js';
import requestData from '../modules/request-data.js';
import DOMRenderer from '../modules/template.js';
import { printError } from '../modules/print.js';
Note that templates declared as shadow roots with the `shadowrootmode="open"` or
`shadowrootmode="closed"` attribute cannot also be `is="literal-html"` templates:
the HTML parser picks them up and treats them as shadows before the custom
element registry can upgrade them: they cannot be enhanced, sadly. However this
library provides another template, `<template is="literal-shadow">`
**/

const assign = Object.assign;
const rpath = /^(\.+|https?:\/)?\//;
const robject = /^(\{|\[)/;

const onerror = window.DEBUG ?
(e, element) => element.replaceWith(print(e)) :
noop ;
import Signal from 'fn/signal.js';
import element from 'dom/element.js';
import assignDataset from '../modules/dom/assign-dataset.js';
import requestData from '../modules/request-data.js';
import DOMRenderer from '../modules/template.js';
import { printError } from '../modules/print.js';


/* Lifecycle */

// tag, template, lifecycle, properties, log
export default element('<template is="literal-html">', {
construct: function() {
const internals = Internals(this);
construct: function(shadow, internals) {
internals.initialised = false;
internals.pushed = false;
internals.data = Signal.of();
internals.renderer = DOMRenderer.fromTemplate(this, this.parentElement);
},

connect: function(shadow) {
const internals = Internals(this);

connect: function(shadow, internals) {
// If already initialised do nothing
if (internals.initialised) { return; }
internals.initialised = true;

// Observe signal listens to signal value changes and calls fn() on next
// tick, as well as immediately if signal already has value
Signal.observe(internals.data, () => {
const { data, renderer } = internals;
// Observe signal listens to signal value changes and calls fn()
// immediately if signal already has value, and on next tick after
// signal mutates
Signal.observe(internals.data, (data) => {
const { renderer } = internals;

if (!data.value) return;
const fragment = renderer.push(data.value);
if (!data) return;
const fragment = renderer.push(data);

// Replace DOM content on first push
if (!internals.pushed) {
internals.pushed = true;
this.replaceWith(fragment);
}
if (internals.pushed) return;
internals.pushed = true;
this.replaceWith(fragment);
});

// If src or data was not set use data found in dataset
Expand Down Expand Up @@ -109,21 +105,10 @@ export default element('<template is="literal-html">', {
},


/**
data-*=""
If there is no `src` or `data` attribute literal populates `data` object
from the dataset properties. So `data-count="3"` may be used in the template
with `${ data.count }`.
(If the template _does_ have a `src` attribute, the dataset proper can still
be accessed in (the usual way)[https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset],
with `${ element.dataset.count }`.)
**/

/**
.data
The `data` property may be set with a JS object or array.
The `data` property may be set to an object.
Getting the `data` property returns the object currently being rendered.
Sort of. The returned data object is actually a _proxy_ of the set object.
Expand Down
86 changes: 57 additions & 29 deletions literal-html/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,44 @@ <h3>#template-7</h3>
<body>
<h1>Tests</h1>

<div class="test">
<!--article class="test">
<template is="literal-html" shadowrootmode>
<h2>is="literal-html" shadowrootmode="open"</h2>
<style>
p {
padding: 8px;
background-color: plum;
}
</style>
<p>I am in the shadow DOM, and here is the defautl slot: "<slot></slot>".</p>
</template>
HELLO
</article-->

<!--article class="test">
<template shadowrootmode="closed">
<h2>is="literal-html" shadowrootmode="open"</h2>
<style>
p {
padding: 8px;
background-color: plum;
}
</style>
<p>I am in the shadow DOM.</p>
</template>
</article-->


<article class="test">
<template is="literal-html">
<h2>is="literal-html"</h2>
<p>Test attribute renderers.</p>
<p id="${ '✓' }">String property <code>.id="✓"</code></p>
${ include('#template-7', data) }
</template>
</div>
</article>

<table class="striped-table x-bleed test">
<!--table class="striped-table x-bleed test">
<thead>
<tr>
<th style="width: 60%;">Expression</th>
Expand Down Expand Up @@ -267,23 +295,23 @@ <h2>is="literal-html"</h2>
</tbody>
</table>
<div class="test">
<article class="test">
<template is="literal-html" data-state="1" data-value="Hello" data-template="1">
${ window.data = data, include('#template-' + data.template, data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-state="1" data-value="Hello">
<h2>is="literal-html"</h2>
<p hidden="${ data.state }">Render counts in ().</p>
<p title="${ data.state }" class="${ data.state }">(${ this.renderCount }) ${ data.state }</p>
${ include('#template-1', data) }
${ include('#template-2', data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-state="1" data-value="Hello" data-template="2">
<h2>is="literal-html"</h2>
<p>Render counts in ().</p>
Expand All @@ -295,9 +323,9 @@ <h2>is="literal-html"</h2>
${ include('#template-' + data.template, data) }
${ include('#template-2', data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-plonk="1" data-value="Hello" data-template="1">
<h2>Cued renders</h2>
<p>Asynchronous stopable stream of mutations to <code>data.template</code>.
Expand All @@ -312,9 +340,9 @@ <h2>Cued renders</h2>
<pre>(${ this.renderCount }) data.template = ${ data.template }</pre>
${ include('#template-' + ((data.template) % 2 + 1), data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-plonk="1" data-value="Hello" data-template="1">
<h2>Uncued renders</h2>
<p>Asynchronous stopable stream of include()s. From clicks. Not
Expand All @@ -328,9 +356,9 @@ <h2>Uncued renders</h2>
.map((e) => include('#template-' + (data.template = ((data.template - 1) % 2) + 1), data))
}
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-plonk="1" data-value="Hello" data-template="1">
<h2>is="literal-html"</h2>
<p>Asynchronous Stream of Promises of include()s. Render counts in ().</p>
Expand All @@ -341,9 +369,9 @@ <h2>is="literal-html"</h2>
.map((e) => request().then(include('#template-' + ((++data.template) % 2 + 1), data)))
}
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-value="Hello World">
<h2>is="literal-html"</h2>
<p>Tests whether <code>data.value</code> is rendered into the correct
Expand All @@ -354,9 +382,9 @@ <h2>is="literal-html"</h2>
<p>An intervening paragraph.</p>
(${ this.renderCount }) data.value = ${ data.value }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-default="2" data-value="Hello">
<h2>is="literal-html"</h2>
<input class="masked" type="radio" name="test-2" value="1" id="test-2-value-1" checked="$ { data.default == element.value }" />
Expand All @@ -370,27 +398,27 @@ <h2>is="literal-html"</h2>
${ include('#template-' + data.default, data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html">
<h2>Array.map(include())</h2>
<p></p>
${ [{ value: 1 }, { value: 2 }, { value: 3 }].map(include('#template-1')) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html">
<h2>Compile and render errors</h2>
${ include('#template-3', data) }
${ include('#template-4', data) }
${ include('#template-5', data) }
${ include('#template-6', data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html" data-value="Hello World">
<h2>Print data</h2>
<p>Print data to the document with <code>print(data)</code>. Note
Expand All @@ -400,9 +428,9 @@ <h2>Print data</h2>
to an existing property triggers a fresh render.</p>
${ print(data) }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html">
<h2>location</h2>
<p>Includes a template based on the location #hash</p>
Expand All @@ -415,9 +443,9 @@ <h2>location</h2>
<pre>(${ this.renderCount }) location.identifier = ${ window.data = data, location.identifier }</pre>
${ location.identifier ? include('#template-' + location.identifier, data) : '' }
</template>
</div>
</article>
<div class="test">
<article class="test">
<template is="literal-html">
<h2>location</h2>
<p>Includes a template based on the location pathname</p>
Expand All @@ -430,5 +458,5 @@ <h2>location</h2>
<pre>(${ this.renderCount }) location.identifier = ${ location.pathname }</pre>
${ location.pathname ? include('#template-' + /\w+$/.exec(location.pathname)[0], data) : '' }
</template>
</div>
</article-->
</body>
37 changes: 37 additions & 0 deletions literal-shadow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

## `<template is="literal-shadow">`

A `literal-shadow` template is replaced in the DOM with its own rendered content.
HTML `<template>`s are allowed pretty much anywhere in a document, so
`literal-shadow` templates enable you to freely mix islands of dynamically
rendered content into your HTML.


### Register `literal-shadow`

Importing `./build/literal-shadow/module.js` from the [repository](https://github.com/stephband/literal/)
registers `<template is="literal-shadow">` as a customised built-in template
element. (Support is polyfilled in Safari, who [refuse to implement customised built-ins](https://github.com/WebKit/standards-positions/issues/97])).

```html
<script src="https://stephen.band/literal/build/literal-shadow/module.js" type="module"></script>
```


### Author a `literal-shadow` template

```html
<article>
<template is="literal-shadow">
<p>${ events('pointermove', body).map((e) => round(e.pageX)) }px</p>
</template>
</article>
```
<div class="demo-block block">
<article>
<template is="literal-shadow">
<p>${ events('pointermove', body).map((e) => round(e.pageX)) }px</p>
</template>
</article>
</div>

Loading

0 comments on commit fcc9604

Please sign in to comment.