vanilla / ebi
A template engine made with HTML.
Requires
- php: >=5.6.0
- symfony/expression-language: ^103.2.8
Requires (Dev)
- bobthecow/faker: dev-master
- symfony/yaml: ^3.2
- vanilla/htmlawed: v2.2.4.1
README
The Ebi template language uses basic HTML and special attributes for a simple yet powerful template language.
The Basics of a Template
In general you write normal HTML. Data is included in the template by including it between {...}
. Other special functionality is added via special template attributes.
Data is included in a template by putting it between {...}
braces. This is known as "interpolation" and there are quite a few options.
Fields
To include a field from your data use its name.
<p>Hello {firstName} {lastName}.</p>
This will include the "firstName" and "lastName" database keys. You can access deeply nested arrays by separating field names with dots.
<p>Hello {user.firstName} {user.lastName}.</p>
You might be tempted to put a dash in your field names. However this will not work as expected because the field names are interpreted as expressions. So for example {is-on} will be interpreted as "is" minus "on"!
Meta
You can add global meta data to all templates which are then accessed by putting an @
sign before a variable name.
<h1>{@title}</h1>
$ebi = new Ebi(...); $ebi->setMeta('title', 'Welcome to the Page'); $ebi->write(...);
The meta array is a good place to put configuration information that is separate from template data. Since it is global to all components you can access it from within any component regardless of scope.
Operators
When writing fields you aren't limited to just field names. A fairly rich expression syntax is supported.
Functions
Functions are called using the functionName()
syntax. Ebi provides a set of default functions that map to PHP's standard library.
Literals
You can include literals in expressions too.
Unescaping Data
All variables are HTML escaped by default. If you want to return unescaped HTML, you can use the unescape function.
<p>{unescape(bodyHtml)}</p>
Template Attributes
Most of Ebi's functionality is accessed using template attributes. These are HTML style attributes that you add to any tag in your template to add logic. All of Ebi's attributes start with an x-
prefix to help you differentiate between Ebi attributes and regular HTML attributes.
The letter "X" was chosen to mean "extended attribute" and was inspired by the same prefix in HTTP headers.
x-if
Only display an element if the condition is true.
<p x-if="empty(items)"> There are no items! </p>
if (empty($props['items'])) { echo "<p>There are no items!</p>"; }
x-else
Add an else element in conjunction with an if element.
<div x-if="signedIn"> Welcome! </div> <div x-else> Sign in to participate. </div>
if ($props['signedIn']) { echo '<div>Welcome!</div>'; } else { echo '<div>Sign in to participate.</div>'; }
x-each
Loop over elements.
<ul x-each="people"> <li>Hi {firstName} {lastName}!</li> </ul>
echo '<ul>'; foreach ($props['people'] as $props1) { echo 'Hi ', $this->escape($props1['firstName']), ' ', $this->escape($props1['lastName']); } echo '</ul>';
x-each x-as
Name the iterator element so that you can still reference the parent.
<ul x-each="comments" x-as="i comment"> <li>{name}: {comment.body} #{i}</li> </ul>
echo '<ul>'; foreach ($props['comments'] as $i1 => $props1) { echo '<li>', $this->escape($props['name']), ': ', $this->escape($props1['body']), ' #', $this->escape($i1) '</li>'; } echo '</ul>';
Tip: If you want to access the key of an array, but still want to access its values without dot syntax then you can use x-as="key this"
.
Iterator Variables
When you specify x-as with an x-each there three special iterator variables you'll have access to: index, first, last.
<ul x-each="this" x-as="i item"> <li id="{i}" data-index="{i.index}" class="{{first: i.first, last: i.last}}">{item}</li> </ul>
So if you specify a key in x-as you can access the special variables as if they were properties of the key. The resulting PHP is a bit more verbose, but straight forward enough.
echo '<ul>'; $count1 = count($props); $index1 = -1; foreach ($props as $i1 => $props1) { $index1++; $first1 = $index1 === 0; $last1 = $index1 === $count1 - 1; echo '<li', $this->attribute('id', $i1), $this->attribute('data-index', $index1), $this->attribute('class', $this->attributeClass(array("first" => $first1, "last" => $last1))), '>', $this->escape($props1), '</li>'; } echo '</ul>';
x-empty
Specify a template when there are no items.
<ul x-each="messages"> <li>{body}</li> <li x-empty>There are no messages.</li> </ul>
echo '<ul>'; if (empty($props['messages'])) { echo '<li>There are no messages.</li>'; } else { foreach ($props['message'] as $i1 => $props1) { echo '<li>', $this->escape($props1['body']), '</li>'; } } echo '</ul>';
x-with
Pass an item into a template.
<div x-with="user"> Hello {name}. </div>
$props1 = $props['user']; echo '<div>', 'Hello ', $this->escape($props1['name']), '</div>';
x-with x-as
You can give an alias to the data referenced with x-with
so that you can still access the parent data within the block. A good use for this is for performing a calculation on some data and assigning it to a variable.
<x x-with="trim(ucfirst(sentence))" x-as="title"><h1 x-if="!empty(title)">{title}</h1></x>
$props1 = trim(ucfirst($props['sentence'])); if (!empty($props1)) { echo $this->escape($props1); }
x-literal
Don't parse templates within a literal.
<code x-literal>Hello <b x-literal>{username}</b></code>
echo '<code>Hello <b x-literal>{username}</b></code>';
x-tag
Sometimes you want to dynamically determine the name of a tag. That's where the the x-tag
attribute comes in.
<x x-tag="'h'~level">{heading}</x>
echo '<h'.$props['level'].'>', $this->escape($props['heading']), '</h'.$props['level'].'>';
Conditional wrappers with x-tag
If your template uses has an x-tag expression that results in an empty string then the tag will not render, but the tag contents will. In this way you can use x-tag do implement conditional wraps.
If an x-tag expression evaluates to true then the tag that it is in will be used.
<p x-tag="true">Hello!</p>
The above example will just render <p>Hello!</p>
. This notation is useful when you have a boolean expression that determines when to render a wrapper.
Template Tags
Most of Ebi's functionality uses special attributes. However, there are a couple of special tags supported.
The <script type="ebi">
Tag
Usually, you write expressions by enclosing them in braces ({..}
). However, braces don't themselves allow brace characters. They also don't allow multi-line expressions. When you have such an expression you can instead enclose it in a <script tpye="ebi">
tag.
<script type="ebi"> join( "|", [1, 2, 3] ) </script>
echo $this->escape(join('|', [1, 2, 3]);
<script x-unescape>
If you don't want to escape the output in an <script>
tag then add the x-unescape
attribute. You don't have to include the type="ebi"
in this case.
<script x-unescape>join('>', [1, 2, 3])</script>
echo join('>', [1, 2, 3]);
<script x-as="...">
You can also use the <script>
tag with an x-as
attribute to create an expression variable that can be used later in the template. You don't have to include the type="ebi"
in this case.
<script x-as="title">trim(ucfirst(sentence))</script> <h1 x-if="!empty(title)">{title}</h1>
$title = trim(ucfirst($props['sentence'])); if (!empty($title) { echo '<h1>', $this->escape($title), '</h1>'; }
The <x>
Tag
Sometimes you will want to use an ebi attribute, but don't want to render an HTML tag. In this case you can use the x
tag which will only render its contents.
<x x-if="signedIn">Welcome back</x>
if ($props['signedIn']) { echo 'Welcome back'; }
Components
Components are a powerful part of Ebi. With components you can make re-usable templates that can be included in other templates. Here are some component basics:
- Each template is a component. You can declare additional components in a template too.
- Components are lowercase. It is recommended that you use dashes to separate words in component names. Make sure to name your template files in lowercase to avoid issues with case sensitive file systems.
- Components are used by declaring an HTML element with the component's name. Components create custom tags!
- You can pass data into components with contributes. If you want to pass all of the current template's data into a component use the
x-with
attribute. - It is strongly recommended you don't name your components with an
x-
prefix as that may be used for future functionality.
x-component
Define a component that can be used later in the template.
<time x-component="long-date" datetime="{date(date, 'c')}">{date(date, 'r')}</time> <long-date date="{dateInserted}" />
$this->register('long-date', function ($props) { echo '<time datetime="', htmlspecialchars(date($props['date'], 'c')), '">', htmlspecialchars(date($props['date'], 'r')), '</time>'; }); $this->render('long-date', ['date' => $props['dateInserted']]);
Components must begin with a capital letter or include a dash or dot. Otherwise they will be rendered as normal HTML tags.
Component Data
By default, components inherit the current scope's data. There are a few more things you can do to pass additional data into a component.
Pass Data Using x-with
If you want to pass data other than the current context into a component you use the x-with
attribute.
<div class="post post-commment" x-component="Comment"> <img src="{author.photoUrl}" /> <a href="author.url">{author.username}</a> <p>{unescape(body)}</p> </div> <Comment x-with="lastComment" />
x-children and x-block
You can define custom content elements within a component with blocks. An unnamed block will use the same tag it's declared in.
<!-- Declare the layout component. --> <html x-component="layout"> <head><title x-children="title" /></head> <body> <h1 x-children="title" /> <div class="content" x-children="content" /> </body> </html> <!-- Use the layout component. --> <layout> <x x-block="title">Hello world!</x> <p x-block="content">When you put yourself out there you will always do well.</p> </layout>
The blocks get inserted into the component when it is used.
<html> <head><title>Hello world!</title> <body> <h1>Hello world!</title> <div class="content"><p>When you put yourself out there you will always do well.</p></div> </body> </html>
Tip: You can use the hasChildren() function to determine if a particular block has been passed to your component.
x-include
Sometimes you want to include a component dynamically. In this case you can use the x-include
attribute.
<div x-component="hello">Hello {name}</div> <div x-component="goodbye">Goodbye {name}</div> <x x-include="salutation" />
$this->register('hello', function ($props) { echo 'Hello ', $this->escape($props['name']); }); $this->register('goodbye', function ($props) { echo 'Goodbye ', $this->escape($props['name']); }); $this->write($props['salutation'], $props);
HTML Utilities
Attribute expressions
When you specify an attribute value as an expression then the attribute will render differently depending on the value.
Examples
The following templates:
<input type="checkbox" checked="{true}" /> <input type="checkbox" checked="{false}" /> <span role="checkbox" aria-checked="{true}" />
Will result in the following output:
<input type="checkbox" checked /> <input type="checkbox" /> <span role="checkbox" aria-checked="true" />
CSS class attributes
When you assign a css class with data you can pass it an array or an object.
Array class attributes
<p class="{['comment', 'is-default']}">Hello</p>
All elements of the array are rendered as separate classes.
<p class="comment is-default">Hello</p>
Object class attributes
<p class="{{comment: true, 'is-default': isDefault }}">Hello</p>
When passing an object as the class attribute the keys define the class names and the values define whether they should be included. In this way you can enable/disable CSS classes with logic from the template's data.
Note the double braces in the above example. The first brace tells us we are using variable interpolation and the second brace wraps the object in JSON notation.
Style attributes
When you assign the style attribute with data you can pass it an associative array or object expression. When you do so the keys and values will be treated as CSS properties and values respectively.
<p style="{{'font-family': ['Open Sans', 'Helvetica', 'sans-serif'], 'font-size': '16px'}}">Hello</p>
The style object will be converted into a string.
<p style="font-family: 'Open Sans','Helvetica','sans-serif'; font-size: 16px">Hello</p>
Notice how the font-family was given an array and correctly converted to CSS.
Boolean style values
There are a few CSS properties that can accept a true or false value:
Whitespace
Whitespace around block level elements is trimmed by default resulting in more compact output of your HTML.
HTML Comments
Any HTML comments that you declare in the template will be added as PHP comments in the compiled template function. This is useful for debugging or for static template generation.
<!-- Do something. --> <p>wut!?</p>
function ($props) { // Do something. echo '<p>wut!?</p>'; };
Using Ebi in Code
The Ebi class is used to compile and render Ebi templates. You should only need one instance of the class to render any number of templates.
Basic Usage
$ebi = new Ebi( new FilesystemLoader('/path/to/templates'), '/path/to/cache' ); $ebi->write('component', $props);
In this example an Ebi object is constructed and a basic component is written to the output.