Tour of Splice
Splice is a templating language
Splice supports nesting, partial templates, iterators, conditionals, variable assignment, local scope, comments, escape characters, and escapes HTML by default. In its minified form, it is only 3.06 KB uncompressed, and has no dependencies. This website uses Splice clientside to render the sidebar and header.
Download
The Splice engine can can be downloaded here and (minified) here as a JavaScript file.
HTML Templating
Include a script tag in your html file with an id of template
, or a custom id if you prefer. Set the type
attribute to text/x-template
.
<script id="template" type="text/x-template">
<ul>
(:~ each chapters as 'chapter {
<li>
<a class="headerLink" href="#(: chapter.id :)">(: chapter.name :)</a>
</li>
}:)
</ul>
</script>
Make sure to also link to a file containing the Splice engine before linking to JS files which utilize Splice.
<head>
<!-- ... -->
<script src="javascripts/splice.js"></script>
<!-- ... -->
</head>
Call Splice.render
with CSS selectors for your template source and destination elements to render your template in the DOM
Splice.render(yourData, "#template-src", "#destination");
For more control, call Splice.compile
to return a function which returns a string for a given data set.
const evaluatorFn = Splice.compile(templateText);
const finalText = evaluatorFn({
chapters: [{
id: 2,
name: 'The Vanishing Glass',
}, {
id: 3,
name: 'The Letters from No One',
}]
});
Partials
To register your partial template, simply invoke Splice.registerPartial
with the id of your template.
Splice.registerPartial('sidebar_template');
Render a partial in your template:
(:~ partial sidebar_template {}:)
Iteration
To iterate through a collection, invoke each
and pass an array. Within the body of each
, you may reference elements of the collection with $
.
(:~ each baskets {
<p>(: $ :)</p>
}:)
Alternatively, you may refer to elements of your collection using an alias defined with as
.
(:~ each baskets as 'aBasket {
<p>(: aBasket :)</p>
}:)
Invocations may be nested as deeply as you please.
(:~ each valley.o as 'bogs {
(:~ each bogs as 'holes {
(:~ each holes as 'tree {
<p>A rare (: tree :), a rattlin (: tree :)</p>
}:)
}:)
}:)
Conditionals
Splice has two conditional functions: if
and unless
. They both takes one argument and a body which may be evaluated depending on whether the argument is truthy or falsy.
(:~ if isValid {
<p>Access Granted</p>
}:)
(:~ unless isValid {
<p>Access Denied</p>
}:)
Assignment
Assign bindings in the current scope using the assignment function: def
.
(:~ def 'snack meal {}:)
Then use the new binding to access its value within the current scope.
<p>I'm just going to have a (: snack :).</p>
Properties
If a binding references an object, access its properties using dot notation.
(: person.community.region :)
Execution Context
in
takes a scope object and uses it to create different context for evaluating bindings within its body. For instance:
<li>(: person.name :)</li>
<li>(: person.job :)</li>
becomes:
(:~ in person {
<li>(: name :)</li>
<li>(: job :)</li>
}:)
Escaping
By default, text referenced by a binding is HTML-escaped to avoid cross-site scripting. To render unescaped html, use (:!
instead of (:
when evaluating a binding.
(:~ def 'unsafe '<a>docs</a> {}:)
<li>(:! unsafe :)</li>
In addition, any character anywhere in a Splice template may be explicitly escaped by prefixing it with \
. This prevents a character from being evaluated as syntax. For instance, \(:
will render (:
as text. To display a backslash in the template, you must escape it.
<p>A Splice expression begins with \(: and ends with \:).</p>
Browser Compatibility
Splice does not support Internet Explorer.
Splice in Depth
Expressions
Every Splice expression returns a string which is inserted in place within the template at evalution time, even if it is an empty string. Splice expressions begin with (:
and end with :)
There are two kinds of Splice expressions: evaluations and invocations. Evalutions are used to evaluate a binding in scope and return its text content.
Evaluate a binding:
(: id :)
Function invocation begins with (:~
and always contains a body defined by opening and closing brackets {}
which may or may not be evaluated. Function bodies can contain either kind of expression or text.
Evaluate a function:
(:~ if response {
<p>Response received!</p>
(:~ each response.headers {
<li>(: $ :)</li>
}:)
}:)
Scope
Rendering a Splice template requires an object representing the global scope of the template. Within the template, properties of the global scope object can be accessed by bindings. Invoking Splice.compile
returns a function which takes a scope and uses it to evaluate its internal AST.
const template = `
(:~ each pages as 'page {
<p>(: page.title :)</p>
(:~ if page.articles {
<ul>
(:~ each articles as 'article {
<li>(: article :)</li>
}:)
</ul>
}:)
}:)
`
const scope = {
pages: [{
title: 'Unforseen Consequences',
articles: ['When to expect the unexpe....', 'Losing sight...'],
}, {
title: 'New Beginnings',
articles: ['As Simon discovered....', 'Lab test demonstrate...'],
}, {
title: 'A bench in the woods',
articles: false,
}]
}
Splice.compile(template)(scope);
Function bodies define a new scope. Splice uses lexical scoping rules, meaning that inner functions have access to variables defined in the outer scope but not vice-versa.
The exception is that the in
function allows a user to set a new context for evaluating bindings, and in doing so, truncates the scope.
Data Types
Within a Splice template there are two data types that can be created: text and atoms.
Text
Text literals are defined by any set of characters outside of a Splice expression or within a function body not enclosed by (: :)
.
Atoms
Atom literals are defined by prefixing a set of non-whitespace characters with a single quote mark '
within a list of arguments to a function invocation. Atoms are used to pass text to a function invocation.
(:~ def 'pop soda {}:)
All else is either syntax or bindings which can reference either built in functions or data stored in scope. Javascript objects and arrays stored in scope are treated by Splice as 'scope objects'. Strings are treated as text.
Source
The source code is devided in five sections.
Lexer & Parser
Generator
In-template functions
Internal Utility Functions
Public Interface
The in-template functions section will be of most interest if you'd like to implement your own helper functions. The first parameter must reserved for the scope, and the last for the body of the function (an abstract syntax tree).
Source here
const Splice = (function() {
// IN-TEMPLATE HELPER FUNCTIONS
// ============================
//...
// if :: Object, Object, Array{Object} -> String
templateFns.if = (scope, expr, body) => {
const innerScope = Object.assign({}, scope);
return evaluate(expr, innerScope) ? evaluateAll(body, innerScope) : '';
};
//...
}());
JavaScript Interface
Splice.compile
:: String -> [Object] -> String
Returns a function which accepts a scope object and returns a string.
const fn = Splice.compile('<p>(: message :)</p>');
fn({ message: 'Hello, World!' });
// returns "<p>Hello, World!"</p>
Splice.registerPartial
:: String, String
:: String
The first argument sets the name for the partial template that can be used to access it within your Splice template(s). Splice.registerPartial
takes a second optional argument which if present, is used as the partial template input string. If not present, Splice.registerPartial
assumes the name of the partial (argument 1) is also the id of a DOM element containing the partial template.
Splice.registerPartial('sidebar');
const partialTemplate = `
<aside class="sidebar (: chapter.id :)">
<nav>
(:~ each chapter.pages as 'page {
<ul>
<li><a class="pageLink" href="#(: page.id :)">(: page.title :)</a></li>
<ul>
(:~ each page.articles as 'article {
<li><a class="articleLink (: page.id :)" href="#(: article.id :)">(: article.title :)</a></li>
}:)
</ul>
</ul>
}:)
</nav>
</aside>
`
Splice.registerPartial('sidebar', partialTemplate);
See also Template Functions::partial
Splice.render
:: [Object], String, String
The first argument represents the 'scope' for the evaluation of the template. The second argument is used as a CSS selector to identify the script
tag containing the template within the DOM. Likewise the third argument is used as a CSS selector to identify the desination element where the final html generated from the template will be rendered.
Splice.render({ message: 'Hello, World!' }, "#src", "#destination");
Template Functions
each
:: Array{}, "as" Atom -> String
:: Array{} -> String
each
is used to iterate through a collection specified by the first argument. Elements can be referenced within the function body using $
. Appending as 'alias
to the arguments list defines an alias for the current element in the collection. each
evaluates its body with a new scope giving access to the current member of the collection and returns a string of these evaluations concatenated together.
<ul>
(:~ each library as 'book {
<li><p>(: book.knowledge :)</p></li>
}:)
</ul>
comment
:: _ -> String
comment
takes no arguments and returns an empty string. The body provides a space for comments. Any function that always returns an empty string can contain comments in its body.
(:~ comment { This is a comment. }:)
def
:: Atom, b -> String
def
is used to assign variables. It returns an empty string.
(:~ def 'list array {}:)
No we can use list
to reference array
.
<ul>
(:~ each list {
<li><p>(: $ :)</p></li>
}:)
</ul>
if
:: a -> String
The body of if
will be evaluated and returned if the argument to if
is truthy. Otherwise, it returns an empty string.
(:~ if isTrue {
<li><p>(: big :)</p></li>
}:)
in
:: Object -> String
in
limits the scope of its body to the argument that is passed in. It can then evaluate bindings in this new context. For instance, suppose we have a global scope that looks like this:
Splice.compile(html)({
context: { foo: 'Hello, World!' }
})
Instead of using dot notation to access context.foo
, we could use in:
(:~ in context {
<li><p>(: foo :)</p></li>
}:)
partial
:: Array{Object} -> String
Used in conjunction with Splice.registerPartial
to render a partial template.
(:~ each chapters as 'chapter {
(:~ partial sidebar_template {}:)
}:)
See also Splice Functions::partial
unless
:: a -> String
The body of unless
will be evaluated and returned if the argument to unless
is falsy. Otherwise, it returns an empty string.
(:~ unless error {
<li><p>(: headers.contentType :)</p></li>
}:)