INTRODUCTION
This is a step-by-step tutorial on how to use Plift. You'll start using 100% pure HTML templates to learn how to render data using directives. Then you'll learn how to use the builtin processing instructions in your template files.
Finally, I'll show how to create your own custom processing instructions (triggered by custom tags/attributes) and how to bundle them in the form of reusable components/plugins.
HELLO WORLD
Lets start with the classic "Hello World" example.
First our template file, index.html
:
<section>
<h1>Message placeholder</h1>
</section>
Now our script, hello_world.pl
:
### hello_world.pl ###
use Plift;
my $plift = Plift->new;
my %data = (
hello_message => 'Hello World!'
);
my @directives = (
'h1' => 'hello_message'
);
my $html = $plift->render('index', \%data, \@directives );
That will render (not surprisingly) the following text into $html
:
<section>
<h1>Hello World!</h1>
</section>
Ok, now lets explain what $plift->render
did. As you can see, the first argument is the name of the template file, without the .html
extension. The second argument \%data
is a reference to a hash of data to make available for templates. Finally, the third argument \@directives
is a reference to a list of render directives, which are simple rules mapping a CCS selector to a data key.
Our example uses only one render directive, 'h1' => 'hello_message'
, that tells Plift to render the value of the 'hello_message' data key into the <h1>
element. If there were multiple <h1>
elements in the template, all of them would get rendered.
Under the hood, Plift uses XML::LibXML::jQuery to manipulate HTML. The render directive in the example is equivalent to the following code:
my $data_value = $context->get('hello_message');
$root_element->find('h1')->text($data_value);
THE PATH
In the above example Plift used the default config for the 'paths' option, which is the current working dir. In a real world application you usualy pass some pre defined template path.
If you pass an arrayref with multiple paths, Plift will search all of them in order. You can use that funcionality to create a simple way to "extend" the website "theme" or "skin":
my $plift = Plift->new(
paths => [
'/myapp/website-foo/skin/templates/',
'/myapp/share/skin/base/templates/'
]
);
If you ask for the template 'error_pages/404'
(remember, no .html extension), Plift first looks for /myapp/website-foo/skin/templates/error_pages/404.html
then /myapp/share/skin/base/templates/error_pages/404.html
. The first existing file will be used, totally overriding the equivalent file on subsequent paths. There is no "template inheritante" or anything like that.
THE CONTEXT
Before proceding, we must learn about the Plift::Context object. At a high level, you can think of it as a representation of the template that we are procesing. Thats why we create a new context via "template" in Plift, and render it via "render" in Plift::Context. Which gives us a nice syntax:
$document = $plift->template('foo')
->at(\@directives)
->render(\%data);
Semantics aside, a Plift::Context instance is the storage for all things related to the current processing context. It's meant to live only for the duration of that template rendering operation (usualy a web request). In contrast to the actual Plift instance that is meant to live for the whole application life.
The methods "render" in Plift and "process" in Plift are just shortcuts for "template" in Plift, "at" in Plift::Context and "render" in Plift::Context. The code snipped above is equivalent to:
$document = $plift->process('index', \%data, \@directives );
See Plift::Context for the complete reference.
RENDER DIRECTIVES
Render directives are pairs of MATCH => ACTION
values. The match part is a CSS selector that targets the element the action applies to. You can use any selector supported by HTML::Selector::XPath. The action part takes a few variations, as described next.
All render directives and template data are stored in the Plift::Context object. Directives are added using the method "at" in Plift::Context.
Scalar - Interpolate data value
# directive
'.first-name' => 'user.first_name'
# data
user => {
first_name => 'Carlos',
...
}
This is the most common directive, where the selector points to a data point, like '.first-name' =
'user.first_name' >. By default the data value is rendered as the element text content. You can add a some MATCH
modifiers to control where and how the value is interpolated.
- '+': Append or prepend a value
-
'+body' => 'layout.header', 'body+' => 'layout.footer'
The default behavior is for a match to replace the matched node's content. In some cases you may wish to preserve the template content and instead either add more content to the front or back of it.
- '@HTML': Render as HTML
-
'.blog-post-content@HTML' => 'post.content'
The default is to explicitly create XML::LibXML::Text nodes, instructing libxml to automatically escape any special HTML character.
- '@<attr>': Select an attribute within the current node
-
'a.blog-post-link@href' => 'post.url'
Render the value in a node's attribute instead of inner content.
- '^': Replace current node completely
-
'^.user-name' => 'user.fullname'
Normally we replace, append or prepend to the content of the selected node. Using the '^' at the front of your match indicates operation should happen on the entire node, not just the content. Can be combined with '+' for append/prepend.
Arrayref - Run directives under a new DOM root
# directive
'#contact' => [
'.phone' => 'contact.phone',
'.email' => 'contact.email',
]
# data
contact => {
phone => '1234-5678',
email => 'foo@example.com'
}
In the example above, the directives in the arrayref are processed using the '#contact' node as the DOM root instead of document node. These new directives can be any type of directive as already shown or later documented.
Hashref - Move the root of the Data Context
# directive
'#contact' => {
'contact' => [
'.phone' => 'phone',
'.email' => 'email',
]
}
# data
contact => {
phone => '1234-5678',
email => 'foo@example.com'
}
Just like it may be valuable to move the root DOM context to an inner node, sometimes you'd like to also move the root of the current Data context to an inner path point. This can result in cleaner templates with less repeated syntax, as well as promote reusability. In order to do this you use a Hashref whose key is the path under the data context you wish to move to and who's value is an Arrayref of new directives. These new directives can be any type of directive as already shown or later documented.
HashRef - Create a loop
# directive
'li.user' => {
'[users]' => [
'.first-name' => 'first_name',
'.last-name' => 'last_name',
],
}
# data
users => [
{ first_name => 'First 01', last_name => 'Last 01' },
{ first_name => 'First 02', last_name => 'Last 02' },
{ first_name => 'First 03', last_name => 'Last 03' }
]
Besides moving the current data context, setting the value of a match spec key to a hashref can be used to perform loops over a node, such as when you wish to create a list. The only difference to the previous Hashref syntax is the angle brakets []
around the data-point name, to tell Plift this is a loop.
In this case, users
must be an arrayref of hashref. Plift will create a clone of the 'li.user' element, and use that as DOM root for rendering each item of the array.
CodeRef - Programmatically render the element
# directive
'#contact' => sub {
my ($element, $ctx) = @_;
my $text = sprintf "%s (☎ %s)", $ctx->get('contact.email'), $ctx->get('contact.phone');
$element->text($text)->attr( title => $text );
}
# data
contact => {
phone => '1234-5678',
email => 'foo@example.com'
}
Perform any custom processing on the target element. The coderef receives the element and the context object as arguments. The return value of the custom code is ignored.
THE DOT NOTATION
PROCESSING INSTRUCTIONS
ELEMENT REMOVAL
CUSTOM HANDLERS
CREATING REUSABLE COMPONENTS
AUTHOR
Carlos Fernando Avila Gratz <cafe@kreato.com.br>