- 1. JET
- 2. Installation
- 3. Design principles:
- 4. Architecture
- 5. Viewmodel
- 6. Features
- 7. Functions
- 8. Support us
JET stands for Jelmer's Easy Templating. I have created this library because I could not find a good library that is very small (jet.min.js is ~8kb! pre-gzip) and that is not bulky with too many features. doT.js came very close, but its syntax was bulky. Try JET here in this repl: https://pennions.github.io/JET/
If you intend to use this commercially, think about becoming a supporter, check Sponsor this project section on this GitHub page.
You can install it using a package manager:
yarn add @pennions/jet
npm install @pennions/jet
Or manually:
Download the latest zip from https://github.com/pennions/JET/releases
Inside the zip file you will find both regular and minified versions.
<script src="js/jet.min.js"></script>
or from a CDN like unpkg:
<script src="https://unpkg.com/browse/@pennions/jet/dist/jet.min.js"></script>
import { render } from 'js/jet.min.js';
Or
import { buildTemplate, interpolateTemplate } from 'js/jet.min.js';
const jet = require('js/jet.min.js');
- Easy to use
- Explicit over implicit (inspired from Python)
- Semantic (easy to remember)
- Clean (write as little syntax as possible)
- Logic free (Single responsibility)
For designing this templating engine I have implemented the View and Viewmodel part of MVVM (Model View ViewModel).
The view is created with help of the viewmodel, which gives a pre-rendered state. Then it can be interpolated (fill in the properties) with the same viewmodel. So as long as your JSON model has the keys that are referenced in this pre-rendered state, you don't need to recompile.
- Partials
- Viewmodel wrappers
- Loops
- Conditionals
A viewmodel is a JSON object.
Example:
const vm = {
todoList: ['task1', 'task2', 'task3'],
shoppingList: {
groceryStore: ['Carrot', 'Melon', 'Potato'],
bakery: {
birthday: 'carrot cake',
daily: ['Bread', 'Cookies']
}
},
isAdmin: false,
username: 'Jet'
}
- Interpolation
- Loops
- Conditionals
- This contradicts Logic Free, see explanation for the reason.
- Partials
You can use the curlybrace (also known as mustachios) to place properties in a template.
JSON model:
const vm = {
...
username: 'Jet'
}
Example template:
<div>
<span>Hello </span>
<span>{{ username }}</span>
</div>
Output:
<div>
<span>Hello </span>
<span>Jet</span>
</div>
JSON model:
const vm = {
...
shoppingList: {
...
bakery: {
...
daily: ['Bread', 'Cookies']
}
},
...
}
Example template:
<div>
<span>Don't forget to buy: </span>
<span>{{ shoppingList.bakery.daily.0 }}</span>
</div>
Output:
<div>
<span>Don't forget to buy: </span>
<span>Bread</span>
</div>
Sometimes you want to html encode properties, like for a code example or for safety reasons.
JSON model:
const vm = {
codeExample: '<div>Hello world!</div>'
}
Example template:
<pre>
<code>
{{! codeExample }}
</code>
</pre>
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<pre>
<code>
<div>Hello world!</div>
</code>
</pre>
To demonstrate this principle let us take a look at a very familiar example of a Todo app.
JSON model:
const vm = {
todoList: ['task1', 'task2', 'task3'],
...
}
Example template:
<ul>
{{% for task in todoList
<li>
{{ task }}
</li>
%}}
</ul>
Supports both for ... in ... as wel as for ... of ...
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<ul>
<li>
task1
</li>
<li>
task2
</li>
<li>
task3
</li>
</ul>
JSON model:
const vm = {
...
shoppingList: {
groceryStore: ['Carrot', 'Melon', 'Potato'],
...
}
Example template:
<ul>
{{% for item in shoppingList.groceryStore
<li>
{{ item }}
</li>
%}}
</ul>
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<ul>
<li>
Carrot
</li>
<li>
Melon
</li>
<li>
Potato
</li>
</ul>
Conditionals contradict the principle of 'Logic Free'. However having conditional renderings is a must for it to be easy, as 'being easy' is the top priority it has been implemented. Therefor the implementation is kept to a bare minimum. The following conditions can be checked:
- if x is y
- if x not y
- if x
This is implemented as a string comparison resolved as:
x.toString().toLowerCase() === y.toString().toLowerCase()
caveat: you need to start your inner template with either a { or a < else it will be included in the comparison.
JSON model:
...
shoppingList: {
bakery: {
...
birthday: 'carrot cake',
...
}
...
}
Example template:
{{~ if shoppingList.bakery.birthday is carrot cake
<div>The cake is not a lie!</div>
~}}
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<div>The cake is not a lie!</div>
JSON model:
...
isAdmin: false
...
Example template:
{{~ if isAdmin not true
<div>Regular user</div>
~}}
{{~ if isAdmin is true
<div>Admin user</div>
~}}
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<div>Regular user</div>
Checks if a property exists in the model and checks if its value resolves to true with a terniary comparison like:
x ? template : ''
Important: 0, false, '' and [ ] results are all counted as false
JSON model:
...
isAdmin: false,
username: 'Jet'
...
Example template:
{{~ if isAdmin
<div>Admin user</div>
~}}
{{~ if username
<div>{{ username }}</div>
~}}
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<div>Jet</div>
Partials are uncompiled or precompiled templates as a string in your viewmodel. JSON model:
const vm = {
todoList: ['task1', 'task2', 'task3'],
shoppingList: {
groceryStore: ['Carrot', 'Melon', 'Potato'],
bakery: {
birthday: 'carrot cake',
daily: ['Bread', 'Cookies']
}
},
isAdmin: false,
username: 'Jet',
partials: {
shoppingList: `<ul> {{% for item in shoppingList.groceryStore <li> {{ item }} </li> %}} </ul>`,
username: `{{~ if username <div>{{ username }}</div> ~}}`
}
}
Example template:
{{# partials.username #}}
{{# partials.shoppingList #}}
Run:
const renderedTemplate = jet.render(template, vm);
Output:
<div>Jet</div>
<ul>
<li> Carrot </li>
<li> Melon </li>
<li> Potato </li>
</ul>
If you want to create reusable partials for example, you need a way to prefix your properties in a template. A viewmodel wrapper provides this.
{{$ from path.to.property
$}}
For example you have a partial with the following template:
<h1>{{ title }}</h1>
And you have a viewmodel with this structure:
const vm = {
book: {
title: 'Epic book'
},
title_partial: '<h1>{{ title }}</h1>'
}
If you would add the partial directly, title will be undefined.
Unless you wrap it like:
{{$ from book
{{# title_partial #}}
$}}
Now it will resolve to:
<h1>{{ book.title }}</h1>
There are three functions exposed in the library:
- buildTemplate
- interpolateTemplate
- render
Build is the pre-render step. It takes a template and a viewmodel and returns a new template with all the property paths resolved.
JSON model:
const vm = {
todoList: ['task1', 'task2', 'task3'],
}
Example template:
<ul>
{{% for task in todoList
<li>
{{ task }}
</li>
%}}
</ul>
Run:
const buildTemplate = jet.buildTemplate(template, vm);
Output:
<ul>
<li>
{{ todoList.0 }}
</li>
<li>
{{ todoList.1 }}
</li>
<li>
{{ todoList.2 }}
</li>
</ul>
This step will fill in the property values and return html. It takes a template from the build step and the viewmodel which was used.
JSON model:
const vm = {
todoList: ['task1', 'task2', 'task3'],
}
Example template:
<ul>
<li>
{{ todoList.0 }}
</li>
<li>
{{ todoList.1 }}
</li>
<li>
{{ todoList.2 }}
</li>
</ul>
Run:
const interpolatedTemplate = jet.interpolateTemplate(template, vm);
Output:
<ul>
<li>
task1
</li>
<li>
task2
</li>
<li>
task3
</li>
</ul>
Render is a function that combines buildTemplate and interpolateTemplate steps.
If you are using this for paid products and services please consider:
- Becoming a supporter on Patreon.com
- Doing a one time donation on Ko-Fi.
- If you want to donate but are not able to do so on these platforms, please contact us at www.pennions.com so we can provide an iDeal link.