A prototyping library for JSON-driven web pages.
A quick example
Let's say you keep a list of things you like in a file called favorites.json
.
It looks like this.
[
"Raindrops on roses",
"Whiskers on kittens",
"Bright copper kettles",
"Warm woolen mittens",
"Brown paper packages tied up with strings"
]
And, you want to display this list on a website. HTML has a
<template>
tag, so let's use that.
<h1>My favorite things</h1>
<ul>
<template data-src="/favorites.json" embed>
<li slot></li>
</template>
</ul>
<script type="module" src="https://unpkg.com/fill-me-in"></script>
The data-src
attribute specifies the data source for this template. The
embed
attribute tells the library to render the template in place.
So, the HTML above produces
My favorite things
- Raindrops on roses
- Whiskers on kittens
- Bright copper kettles
- Warm woolen mittens
- Brown paper packages tied up with strings
Ready to try it out for yourself? Have a look at the demos in the next section.
Demos
Tests
The Render API
The class Render
is a builder API for customizing the render operation.
For example, this expression,
render("#album-template")
.filter(album => album.rating >= 4.5)
.into("#content");
When executed (via into
), does the following:
- Finds the DOM element by the ID album-template
- Fetches JSON from the URL specified in its data-src attribute
- Removes albums that have a rating lower than 4.5
- Renders the remaining albums with the #album-template and inserts it into #content
render(template: string | HTMLTemplateElement): Render<any>
Initialize the Render API with a selector string or HTMLTemplateElement
.
.map<U>(f: T => U): Render<U>
Map over content, transforming it with f.
.mapList<U, V>(this: Render<U[]>, f: (u: U) => V): Render<V[]>
Map over a list, transforming each item into something else.
.reduce<U, V>(this: Render<U[]>, f: (accumulator: V, next: U) => V, initial: V): Render<V[]>
Fold over the content to transform it into something else.
.filter<U>(this: Render<U[]>, predicate: (value: U) => boolean): Render<U[]>
Remove content, keeping only that which matches the predicate.
.withValue<T>(value: T): Render<T>
Specify values statically, instead of from data-src
.
.asFragment(): Promise<DocumentFragment>
Runs the render process with the customizations.
.into(target: string | HTMLElement): Promise<DocumentFragment>
Runs asFragment
and inserts the document fragment into the target, replacing its contents.
.cache(): Promise<Render<A>>
Runs the renderer, and creates a save point for its state.
Common scenarios
Some common scenarios follow.
Lists
A list of albums
{
albums: [
{ name: "Dr. Feelgood", year: 1989, artist: "Mötley Crüe" },
{ name: "Appetite For Destruction", year: 1987, artist: "Guns N' Roses" }
]
}
applied to this template
<template>
<table slot="albums">
<tbody>
<template>
<tr>
<td slot="name"></td>
<td slot="year"></td>
<td slot="artist"></td>
</tr>
</template>
</tbody>
</table>
</template>
produces
<table>
<tbody>
<tr>
<td>Dr. Feelgood</td>
<td>1989</td>
<td>Mötley Crüe</td>
</tr>
<tr>
<td>Appetite For Destruction</td>
<td>1987</td>
<td>Guns N' Roses</td>
</tr>
</tbody>
</table>
Empty lists
The attribute onempty
is a Javascript callback that is run when the indexed
list is empty (i.e. { albums: [] }
).
<div class="or-empty" style="display: none">No albums found.</div>
<template>
<table slot="albums" class="or-empty" onempty="$('.or-empty').toggle()">
<tbody>
<template>
<tr>
<td slot="name"></td>
<td slot="year"></td>
<td slot="artist"></td>
</tr>
</template>
</tbody>
</table>
</template>
becomes
<div class="or-empty">No albums found.</div>
<table class="or-empty" style="display: none">
<tbody>
</tbody>
</table>
Attribute slots
The indexed value sets the src
attribute.
{
pic: "https://example.com/example.jpg"
}
applied to
<img slot-src="pic">
produces
<img src="https://example.com/example.jpg">
Mods (short for modifiers)
HTML has a nested structure that maps to JSON, but sometimes we need more
flexibility. For <img>
, we want to set the value of the src
attribute. For
<a>
we want to set the value of href
and textContent
. The render
function knows how to do all of this because of mods. Mods describe
how to transform a target element by a value.
The default mod sets the textContent
of the target.
function(e) { e.target.textContent = e.value }
You can define your own custom mods. This is a nonsense modifier to set
every target element to "hello", ignoring the passed in value.
function nonsense(e) { e.target.textContent = "hello" }
Pass it to render via withMods
.
renderFragment.withMods(function(mods) { return [nonsense]; });