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.

by Deniz Akşimşek

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: