Psatina
A small (1.6k .min.js.br) library for “sprinkling” dynamic behavior on top of server-rendered templates. Built for leisure, not for speed.
Installation
Download psatina.min.js and include it in your HTML.
<script src="psatina.min.js" type="module"></script>
Psatina is small enough that you can paste the minified code into your HTML file:
<script type="module">
var{TEXT_NODE:E,//...
</script>
Usage
Add a dynamic area
Use a <template> element with the attribute p:data.
<template p:data="{ x: 1 }">
...
</template>
Use data in templates
Add JavaScript expressions between the delimiters [| |]
in text or attribute values.
<template p:data="{ x: 1 }">
<data value="[| x |]">[| x |]</data>
</template>
Set properties
To set DOM properties instead of attributes, use the p:set: prefix:
<template p:data="{ x: 1 }">
<input value="[| x |]"> <!-- `value` attribute sets default value -->
<input p:set:value="x"> <!-- `value` property replaces user input -->
</template>
Add event listeners
Use p:on:. Call update() to update the element with new changes.
update() has an optional callback, but this is just a syntactic convenience.
You don’t need to perform data changes inside the callback.
<template p:data="{ x: 1 }">
<input
p:set:value="x"
p:on:input="update(() => x = event.target.value)">
<output>Squared: [| x ** 2 |]</output>
</template>
Loops and conditionals
p:if and p:for:
<template p:data="{ shopping: ['eggs', 'bread', 'baking powder'] }">
<ul p:if="shopping.length">
<li p:for:item="shopping">[| item |]</li>
</ul>
<p p:if="!shopping.length">No items in shopping list.</p>
</template>
Initialize elements
p:init runs code when a real element is created from a template element,
but not when an existing element is mutated. It receives the element as a
variable named element.
<template p:data="{ }">
<input p:init="element.focus()">
</template>
Reference elements
p:ref stores a reference to the element in the data object.
<template p:data="{ }">
<canvas p:ref="myCanvas"></canvas>
<button p:on:click="
myCanvas.getContext('2d').fillRect(0, 0, 100, 100)
">Draw</button>
</template>
Subscopes
Add p:data to an element inside a template to create a new scope that extends the parent’s scope.
This can be useful in combination with p:for: to create a new scope for each item in the list, and p:ref so that elements in each item’s scope can reference each other.
<template p:data="{ items: [1, 2, 3] }">
<div p:for:item="items">
<span p:ref="item">[| item |]</span>
<button p:on:click="yellowFade(item)">Do a thing</button>
</div>
</template>
Custom directives
You can import the templateDirectives map and add your own directives to it.
<script type="module">
import { templateDirectives } from './psatina.js';
templateDirectives.set('my-directive', ({ render, data, value }) => {
return render(data);
});
</script>
The context object passed to each directive has the following properties:
modifiers: An array of strings representing the modifiers of the directive (the parts of the attribute name after the directive, separated by commas).value: The value of the directive.element: The source template element the directive is attached to.data: The data object for the current scope.render: A function that can be used to render the template element into a list of VNodes.