vanilla/ebi

A template engine made with HTML.

v0.3.6 2017-11-15 18:22 UTC

README

Build Status Coverage Packagist Version MIT License CLA

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.

Type Operators
Arithmetic +,-,*,/,%,**
Bitwise &,|,^
Comparison ==,===,!=,!==,<,>,<=,>=,matches
Logical &&,||,!
String Concatenation ~
Array in, not in
Range ..
Ternary cond ? 'yes' : 'no'
cond ?: 'no'
cond ? 'yes'

Functions

Functions are called using the functionName() syntax. Ebi provides a set of default functions that map to PHP's standard library.

Function Description PHP Function
abs Absolute value of a number. abs
arrayColumn Return the values from a single column in the input array. array_column
arrayKeyExists Checks if the given key or index exists in the array. array_key_exists
arrayKeys Return the keys of an array. array_keys
arrayMerge Merge one or more arrays. array_merge
arrayMergeRecursive Merge two or more arrays recursively. array_merge_recursive
arrayReplace Replaces elements from passed arrays into the first array. array_replace
arrayReplaceRecursive Replaces elements from passed arrays into the first array recursively. array_replace_recursive
arrayReverse Return an array with elements in reverse order. array_reverse
arrayValues Return all the values of an array. array_values
base64Encode Encodes data with MIME base64. base64_encode
ceil Round fractions up. ceil
componentExists Checks if the given component name exists.
count Count the number of items in an array. count
empty Check to see if a string or array is empty. empty
floor Round fractions down. floor
formatDate Format a date. date_format
formatNumber Format a number with grouped thousands. number_format
hasChildren Checks if the component has children passed to it. Pass the name of a block to test for that specific child.
htmlEncode Convert special characters to HTML entities. htmlspecialchars
isArray Finds whether a variable is an array. is_string
isBool Finds out whether a variable is a boolean. is_bool
isInt Find whether the type of a variable is integer is_int
isScalar Finds whether a variable is a scalar. is_scalar
isString Find whether the type of a variable is string. is_string
join Join the elements of an array into a string. implode
lcase Lowercase a string. strtolower, mb_strtolower
lcfirst Lowercase the first letter of a word. lcfirst
ltrim Left trim a string. ltrim
max Find highest value. max
min Find the lowest value. min
queryEncode Generate a URL-encoded query string. http_build_query
round Round a number. round
rtrim Right trim a string. rtrim
sprintf Return a formatted string. sprintf
strlen Return the length of a string. strlen, mb_strlen
substr Return a part of a string. substr, mb_substr
trim Trim a string. trim
ucase Uppercase a string. strtoupper, mb_strtoupper
ucfirst Uppercase the first letter of a word. ucfirst
ucwords Uppercase the first letter in each word of a string. ucwords
urlEncode URL-encode according to RFC 3986. rawurlencode

Literals

You can include literals in expressions too.

Type Notation Example
string Enclose in single or double quotes. 'hello'
number Write the number without quotes. 123
array Use JSON notation. [1, 2, 3]
object Use JSON notation without quoted keys. { foo: 'bar' }
boolean Use the true and false constants. true
null Use null constant. null

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.

Value Behavior
true Renders just the attribute.
false, null Won't render the attribute.
aria-* attribute Values of true or false render as string values.
other values render as normal attribute definitions.

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:

Property Value Result
border false border: none
box-shadow false box-shadow: none
display false display: none
visibility true visibility: visible
visibility false visibility: hidden

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.