leongrdic / dataplater
Template engine that uses data-* attributes so your HTML templates look great before rendering
Requires
- php: >=8.0
- ext-dom: *
- ext-libxml: *
- leongrdic/smplang: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^v3.8
- pestphp/pest: ^1.21
README
Dataplater is a templating engine written in PHP that uses HTML data-attributes and keeps templates valid and clean. This makes Dataplater perfect for creating document templates like invoices, contracts, emails, etc. which can be previewed in the browser before rendering.
Features
- 💻 make HTML templates that look great even before rendering
- 💾 use data-attributes to pass data into your template
- ⚡ powerful expression language (SMPLang)
- 📩 automatic escaping
- 👍 UTF-8 support
- 🧮 access to php functions in expressions
- 🔗 include HTML files
- ➿ nested foreach loops
- 🔤 set inner HTML or custom attributes of elements
- 🗑️ delete elements based on conditions
Examples
This is <var data-dp=php.strrev(`emosewa`)></var> <!-- line above becomes: --> This is <var>awesome</var>
I actually created an invoice template for Dataplater that's used in production. The template's source code is here.
Install
composer require leongrdic/dataplater
Requirements
- PHP 8.0+
- DOM & XML extensions
Usage
$dp = new \Le\Dataplater\Dataplater( filename: 'template.html', // path relative to baseDir or an absolute path // OR template: '<html>...</html>', // doesn't have to be a full HTML document // optional: vars: [ 'var' => 'value', ], // global vars baseDir: 'app/templates/', // base directory for templates attr: 'data-custom' // custom base attribute );
When creating a Dataplater object, you can either provide a path to your template file or pass the template as a string. The recommended way to initialize the object is by using PHP8's named arguments.
Optional parameters:
vars
: an array of global vars to pass into the template with keys being the vars' namesbaseDir
: the base directory to use for includes (defaults to.
)attr
: the base attribute name to use for all attributes (defaults todata-dp
)
render()
method
$html = $dp->render([ 'var' => 'local value', 'function' => fn () => 'some value', ]);
The render()
method renders the template and returns the rendered HTML as a string.
The optional parameter is an array of local vars that will override any global vars with the same name.
You can call this method multiple times on the same object with different vars to render multiple variants of the same document.
Attribute reference
If you change the base attribute to e.g. data-custom
(when creating the Dataplater object), use data-custom-foreach
instead of data-dp-foreach
.
All Dataplater attributes will be automatically removed from the template after rendering.
data-dp-include
Value: filename of the HTML template to include.
Content of the included file will be inserted into the document replacing the element with the data-dp-include
attribute.
<template data-dp-include="include.html"></template>
data-dp-if
Value: SMPL expression
If the expression evaluates to true
, the element will be rendered, otherwise it will be removed.
<div data-dp-if=false>this element will always be removed</div> <div data-dp-if="id > 1">this will be rendered if the condition is met</div>
data-dp-foreach
Value: SMPL expression
Additional attributes:
data-dp-key
: name of var in which will be the current element key (optional)data-dp-value
: name of var in which will be the current element value (required)
The expression must evaluate to an array or an iterable object.
Children of the element with the data-dp-foreach
attribute will be copied for each iteration and the data-dp-key
and data-dp-value
vars will be set to the current element key and value to be used in those child elements.
The element containing the data-dp-foreach
attribute will be removed after the loop.
<ul> <template data-dp-foreach=['google','youtube'] data-dp-var=name data-dp-key=id> <li data-dp="id+1 ~ '. ' ~ name"></li> </template> </ul> <template data-dp-foreach=users data-dp-var=user> <a data-dp-href=user.url data-dp=user.name></a> </template>
data-dp-html
Value: SMPL expression
If the expression evaluates to null
, no action will be taken (the element will be rendered without any modifications).
The expression otherwise must evaluate to a string. The string will be inserted into the element as HTML replacing the element's content.
The inserted HTML will have only data-dp
, data-dp-attr
and attribute shortcuts rendered which means that other attributes like data-dp-if
or data-dp-foreach
will be ignored.
<div data-dp-html="'<b>' ~ name ~ '</b>'"></div> <div data-dp-html="'<b data-dp=name></b>'"></div>
Note the quotes around the expression in the second example - the outer quotes are defining content of the HTML attribute and the inner quotes are defining a string within the SMPL expression.
data-dp
Value: SMPL expression
If the expression evaluates to null
, no action will be taken (the element will be rendered without any modifications).
The expression otherwise must evaluate to a string. The string will be inserted into the element as escaped text replacing the element's content.
<span data-dp=message></span> <var data-dp="balance > 0 ? balance : 'empty balance'"></var> <label data-dp=`cool`></label>
Note: HTML interprets the attribute from the last example as: data-dp="`cool`"
, and SMPLang will interpret `cool`
as a string literal. If you wrote data-dp="cool"
instead, SMPLang would look for a var called cool
and return its value.
data-dp-attr
Value: attribute name ; SMPL expression
If the expression evaluates to null
, the attribute won't be set, and it's value will be left as is (if any).
The expression otherwise must evaluate to a string. The string will be escaped and inserted as the attribute value for the desired attribute.
Dataplater also provides a few shortcut for the following attributes: id
class
title
alt
value
href
src
style
using the syntax: data-dp-attribute
.
<span data-dp-attr="title ; message"></span> <!-- or --> <var data-dp-title=message></var> <div data-dp-class="!hidden ? 'show'"></div>
Expression reference
Dataplater uses the SMPLang expression language. Refer to the SMPLang readme for more information about the syntax and supported operators.
Vars from Dataplater are accessible as vars in the SMPL expression.
Dataplater also provides a global object php
which is basically a proxy to all php functions. Use it to access any PHP function from within your expressions:
php.count(users) > 0
php.array_reverse(links)
php.implode('-', php.explode(' ', someText))
Keep in mind you can still pass your own closure/function vars and use them in your templates.
When the expression returns a closure, Dataplater will attempt to call it (without any params) and use the result instead of the closure.
Dataplater wraps all SMPL exceptions in a \Le\Dataplater\ParseException
which gives you access to the line number and the causing HTML element.
Rendering order
- Includes are inserted (single level only)
if
checks are performed (except for those in loops)- Loops are performed (recursively) and their contents are fully rendered (which allows for nested loops; including
if
checks) - HTML insertions are performed
- Text and attribute insertions are performed
Notes
Dataplater heavily relies on the PHP DOM extension and all escaping is done by the DOM extension natively, so passing user-generated data into Dataplater vars should be safe, although if you do use data from untrusted sources, make sure proper validations is done.
Also keep in mind that DOM extension sometimes reformats HTML (like adds attribute brackets, removes closing tags if unnecessary, etc.) and that the code coming out of Dataplater is not going to be indented well.
Any contributions to this project are welcome!