-
Notifications
You must be signed in to change notification settings - Fork 0
Templates
Templates are the basis of the eryn engine. There are multiple types of templates, all of which are listed below.
The simplest kind of template.
[|block_of_code_here|]
Normal templates contain a block of JavaScript code. This block of code code is executed, and the result is then rendered. Currently, the supported result types are:
- String
- Number
- Array
- Object
- Buffer since 0.2.0
- null
- undefined
-
if the result is either of type
String
orNumber
, it is rendered as-is -
if the result is either of type
Array
orObject
, it is automatically stringified and rendered as aString
-
if the result is of type
Buffer
, the raw binary data is rendered since 0.2.0 -
if the result is either
null
orundefined
, nothing will be rendered -
if the template contains an object expression (i.e.
{...}
), eryn will automatically surround it with parentheses. This is because the evaluation function will treat it as a block if those parentheses are missing. This is only done if the first character of the template content is{
since 0.2.8 -
if you want to declare a nested block at the beginning of the template content, you wouldn't want the first
{
character to be treated as an object expression. To treat it as a proper block, write dummy code before the opening bracket (e.g./* BLOCK */ {...}
). This way, the content won't start with{
and eryn won't change your code. You only need to do this if the nested block is right at the beginning of the content (e.g. your template looks like[| {......} |]
) since 0.2.8 -
the last two remarks apply to ALL template types since 0.2.8
The execution of the blocks of code is done using the eval
function through a bridge.
Because of this, normal templates offer great flexibility, as they can contain almost any JavaScript code. However, they can also be dangerous if not written properly. See Security Concerns for more info.
Consider the following context:
{
firstName: "Tyler",
lastName: "Bounty",
strings: ["hello", "world"]
}
This:
The context is [|context|]
Hello, [|context.firstName|] [|context.lastName|]!
The last 3 chars of the first string: [| context.strings[0].substring(context.strings[0].length - 3) |]
...will be rendered as:
The context is {"firstName":"Tyler","lastName":"Bounty","strings":["hello","world"]}
Hello, Tyler Bounty!
The last 3 chars of the first string: llo
Like a normal template, except it doesn't render anything.
[|# block_of_code_here|]
Void templates are useful when you want to execute a block of code silently (e.g. when declaring a local variable).
This:
[|# local.x = 5 |]
[| local.x |]
...will be rendered as:
5
However, this:
[| local.x = 5 |]
[| local.x |]
...will be rendered as:
5
5
The void template suppresses the unwanted output from the local variable assignment.
A template that acts like an if
statement.
[|? expression|]
body
[|end|]
Just like normal templates, conditional templates also support JavaScript blocks of code. In this case, the block of code (expression) is executed, and the result is converted to a boolean value. The body
will be only rendered if the result is true
.
-
the body can contain anything, including other templates
-
if the conditional template isn't closed with a body end (
[|end|]
), an exception will be thrown -
if a template starts with
end
but also contains other non-whitespace characters (e.g. the template[|end stuff|]
), an exception will be thrown. A template body end must only contain the end marker, and may also contain whitespace characters (i.e.\r
,\n
,\t
and space)
Else conditional
templates are like else if
, while else
templates are like else
. They are both optional.
The conditional template can be extended with else (conditional) templates. An else conditional template will have its body rendered only if the expression is evaluated true
, and all previous else conditional templates have expressions that were evaluated to false
. The else template is similar, but it doesn't have an expression.
When using else templates, the body end ([|end|]
) should be placed after the final else (conditional) template.
All else (conditional) templates are optional. However, if an else template is included, it must be the last and it must also be ended with the body end ([|end|]
).
[|? expression|]
body
[|:? expression1|]
else if 1
[|:? expression2|]
else if 2
[|:|]
else
[|end|]
The templates marked with
:?
act likeelse if
, while the template marked with:
acts likeelse
. Note how the body end ([|end|]
) is written after the else template.
Consider the following context:
{
name: "Tyler",
renderGreeting: true,
showTitle: false,
randomObject: {
hello: "world",
nums: [10, 20, 30, 40]
}
}
This:
[|? context.renderGreeting|]
Hello, [|context.name|].
[|end|]
[|? context.showTitle|]
Title goes here.
[|end|]
[|? context.randomObject.nums.length >= 4|]
The length is >= 4.
[|end|]
...will be rendered as:
Hello, Tyler.
The length is >= 4.
until 0.3.0 This template type was removed in version 0.3.0
. The information is left here in case older versions are still used.
Like a conditional template, except that it negates the expression.
[|! expression|]
body
[|end|]
The body
will be only rendered if the result is false
.
-
the inverted conditional template can also be extended with
else conditional
andelse
templates. However, there is noelse inverted conditional
-
the body can contain anything, including other templates
-
if the inverted conditional template isn't closed with a body end (
[|end|]
), an exception will be thrown -
if a template starts with
end
but also contains other non-whitespace characters (e.g. the template[|end stuff|]
), an exception will be thrown. A template body end must only contain the end marker, and may also contain whitespace characters (i.e.\r
,\n
,\t
and space)
A template that acts like a forEach
statement.
[|@ iterator : array|]
body
[|end|]
since 0.2.0
[|@ iterator : array ~|]
body
[|end|]
The body will be rendered for each element of the array. During each iteration, the current element is stored in the variable with the name specified by the iterator.
If the loop contains a reverse marker (~
) at the end, all elements/properties will be iterated over in reverse order. since 0.2.0
-
the body can contain anything, including other templates
-
the array must be either of type
Array
orObject
-
if the array is an object, the loop will iterate through the properties. In this case, the iterator will be an object:
{ key: property_key, value: property_value }
since 0.2.0 -
the iterator will be stored in the
local
object (see here) since 0.2.0 -
if the
local
object is modified inside the body, the changes will not always persist (see #23). A shallow copy of the object is made before entering the loop, and is restored at the end of the loop (and at the end of each iteration, in order to instantiate the iterator again). If you want to make sure that those changes will never persist, use theenableDeepCloning
option. This way, any modifications done to thelocal
object inside the loop body will have no effect outside of the body since 0.2.0 -
modifying the iterator inside the body is not guaranteed to also modify the original element. If you want to make sure that modifying the iterator won't have any effect on the original element, use the
cloneIterators
andenableDeepCloning
options since 0.2.0 -
the
context
object will not be backed up, unlike thelocal
object. Therefore, any modifications done to it will persist even after the loop body -
just like conditional templates, loop templates need to be closed with a body end (
[|end|]
). Not closing the template will result in an exception being thrown -
if a template starts with
end
but also contains other non-whitespace characters (e.g. the template[|end stuff|]
), an exception will be thrown. A template body end must only contain the end marker, and may also contain whitespace characters (i.e.\r
,\n
,\t
and space)
Consider the following context:
{
users: [
{
name: "Tyler",
age: 21
},
{
name: "Alex",
age: 22
},
{
name: "Tom",
age: 24
}
]
}
This:
[|@ user : context.users|]
My name is [|user.name|] and I am [|user.age|] years old.
[|end|]
will be rendered as:
My name is Tyler and I am 21 years old.
My name is Alex and I am 22 years old.
My name is Tom and I am 24 years old.
Renders the contents of another file, with optional context and content.
[|% comp_path|]
content
[|end|]
[|% comp_path /|]
[|% comp_path : context|]
content
[|end|]
[|% comp_path : context /|]
There are 2 types of component templates:
-
regular: has content, and must be closed with
[|end|]
-
self-closed: doesn't have content
A component template contains:
-
path: the path to the component
-
context (optional): the context object with which to render the component (
undefined
by default) -
content (optional): the content with which to render the component
The context is a block of JavaScript code. The code will be executed, and the result will be assigned to the context
variable. Think of it like this:
var backup = context;
context = block_of_code;
// ... after the component was rendered
context = backup;
The content of a component can be accessed with a normal template, by using the content
keyword.
[| content |]
-
the
content
can contain anything, including templates -
the
content
only makes sense when talking about regular components. Self-closing components, as well as files rendered using therender
function, have no content -
the
context
object inside thecontent
refers to the parent context -
the
content
keyword inside thecontent
refers to the parent content, NOT the component content (as that would result in an infinite loop) -
just like conditional and loop templates, regular component templates need to be closed with a body end (
[|end|]
). Not closing the template will result in an exception being thrown -
evidently, self-closing component templates must not to be closed with a body end. Doing so may result in an exception being thrown
-
local
objects will not be shared with any components (see here) since 0.2.0 -
if a template starts with
/
but also contains other non-whitespace characters (e.g. the template[|end stuff|]
), it is an exception will be thrown. A body end must only contain the end marker, and may also contain whitespace characters (i.e.\r
,\n
,\t
and space)
Consider the following context:
{
title: {
text: "Hello",
size: 16
}
}
This:
Here are the components:
[|% comp.eryn : context.title|]
This is the content. It can see the context: [|context|]
[|end|]
[|% comp.eryn : {text: "World", size: 32}|]
This is the content. It can see the context: [|context|]
[|end|]
[|% comp2.eryn /|]
comp.eryn
The title is [|context.text|] and has a size of [|context.size|].
[|content|]
comp2.eryn
This is a component with no content or context.
...will be rendered as:
Here are the components:
The title is Hello and has a size of 16.
This is the content. It can see the context: {"text":"Hello","size":16}
The title is World and has a size of 32.
This is the content. It can see the context: {"text":"World","size":32}
This is a component with no content or context.
A template that contains a comment, which is ignored by the compiler.
[|// comment_here //|]
[|// multiline
comment //|]
Comment templates are ignored by the compiler, and don't appear in the compiled output. Therefore, they won't have any impact on the rendering process.
Unlike other template types, comment templates don't end with templateEnd
(default: |]
). Instead, they end with commentTemplateEnd
(default: //|]
). See here why.
This:
Hello, [|// this is a comment //|]
World
[|// this is a multiline
comment
//|]
...will be rendered as:
Hello,
World