Template-Pure
view release on metacpan or search on metacpan
README.mkdn view on Meta::CPAN
<li>jack, #1</li>
<li>jane, #2</li>
<li>joe, #3</li>
</ul>
</body>
</html>
# DESCRIPTION
**NOTE** WARNING: Early access module. Although we have a lot of test cases and this is the
third redo of the code I've not well tested certain features (such as using an object as
a data context) and other parts such as the way we handle undefined values (or empty
iterators) are still 'first draft'. Code currently is entirely unoptimized. Additionally the
documenation could use another detailed review, and we'd benefit from some 'cookbook' style docs.
Nevertheless its all working well enough that I'd like to publish it so I can start using it
more widely and hopefully some of you will like what you see and be inspired to try and help
close the gaps.
**NOTE** UPDATE (version 0.015): The code is starting to shape up and at this point I'm started to commit to
things that pass the current test case should still pass in the future unless breaking changes
are absolutely required to move the project forward. Main things to be worked out is if the
rules around handling undef values and when we have an object as the loop iterator has not
been as well tested as it should be.
**NOTE** UPDATE (version 0.023): Error messaging is tremendously improved and a number of edge case
issues have worked out while working on the Catalyst View adaptor (not on CPAN at the time of this
writing). Main blockers before I can consider this stable include lots of performance tuning,
completion of a working Catalyst view adaptor, and refactoring of the way we use the [Mojo::DOM58](https://metacpan.org/pod/Mojo::DOM58)
parser so that parsers are plugable. I also need to refactor how processing instructions are
handled so that its not a pile of inlined code (ideally you should be able to write your own
processing instructions). I feel commited to the existing test suite and documented
API.
[Template::Pure](https://metacpan.org/pod/Template::Pure) HTML/XML Templating system, inspired by pure.js [http://beebole.com/pure/](http://beebole.com/pure/), with
some additions and modifications to make it more Perlish and to be more suitable
as a server side templating framework for larger scale needs instead of single page
web applications.
The core concept is you have your templates in pure HTML and create CSS style
matches to run transforms on the HTML to populate data into the template. This allows you
to have very clean, truely logicless templates. This approach can be useful when the HTML designers
know little more than HTML and related technologies. It helps promote separation of concerns
between your UI developers and your server side developers. Over the long term the separate
and possibilities for code reuse can lead to an easier to maintain system.
The main downside is that it can place more work on the server side developers, who have to
write the directives unless your UI developers are able and willing to learn the minimal Perl
required for that job. Also since the CSS matching directives can be based on the document
structure, it can lead to onerous tight binding between yout document structure and the layout/display
logic. For example due to some limitations in the DOM parser, you might have to add some extra markup
just so you have a place to match, when you have complex and deeply nested data.
Additionally many UI designers already are familiar with some basic templating systems and
might really prefer to use that so that they can maintain more autonomy and avoid the additional
learning curve that [Template::Pure](https://metacpan.org/pod/Template::Pure) will requires (most people seem to find its a bit more
effort to learn off the top compared to more simple systems like Mustache or even [Template::Toolkit](https://metacpan.org/pod/Template::Toolkit).
Although inspired by pure.js [http://beebole.com/pure/](http://beebole.com/pure/) this module attempts to help mitigate some
of the listed possible downsides with additional features that are a superset of the original
pure.js specification. For example you may include templates inside of templates as includes or even
overlays that provide much of the same benefit that template inheritance offers in many other
popular template frameworks. These additional features are intended to make it more suitable as a general
purpose server side templating system.
# CREATING TEMPLATE OBJECTS
The first step is to create a [Template::Pure](https://metacpan.org/pod/Template::Pure) object:
my $pure = Template::Pure->new(
template=>$html,
directives=> \@directives);
[Template::Pure](https://metacpan.org/pod/Template::Pure) has two required parameters:
- template
This is a string that is an HTML template that can be parsed by [Mojo::DOM58](https://metacpan.org/pod/Mojo::DOM58)
- directives
An arrayref of directives, which are commands used to transform the template when
rendering against data. For more on directives, see ["DIRECTIVES"](#directives)
[Template::Pure](https://metacpan.org/pod/Template::Pure) has a third optional parameter, 'filters', which is a hashref of
user created filters. For more see [Template::Pure::Filters](https://metacpan.org/pod/Template::Pure::Filters) and ["FILTERS"](#filters).
Once you have a created object, you may call the following methods:
- render ($data, ?\\@extra\_directives?)
Render a template with the given '$data', which may be a hashref or an object with
fields that match data paths defined in the directions section (see ["DIRECTIVES"](#directives))
Returns a string. You may pass in an arrayref of extra directives, which are executed
just like directives defined at instantiation time (although future versions of this
distribution may offer optimizations to directives known at create time). These optional
added directives are executed after the directives defined at create time.
Since we often traverse the $data structure as part of rendering a template, we usually call
the current path the 'data context'. We always track the base or root context and you can
always return to it, as you will later see in the ["DIRECTIVES"](#directives) section.
- process\_dom ($data, ?\\@extra\_directives?)
Works just like 'render', except we return a [Mojo::DOM58](https://metacpan.org/pod/Mojo::DOM58) object instead of a string directly.
Useful if you wish to retrieve the [Mojo::DOM58](https://metacpan.org/pod/Mojo::DOM58) object for advanced, custom tranformations.
- data\_at\_path ($data, $path)
Given a $data object, returns the value at the defined $path. Useful in your coderef actions
(see below) when you wish to grab data from the current data context but wish to avoid
using $data implimentation specific lookup.
- escape\_html ($string)
Given a string, returns a version of it that has been properly HTML escaped. Since we do
such escaping automatically for most directives you won't need it a lot, but could be useful
in a coderef action. Can also be called as a filter (see ["FILTERS"](#filters)).
- encoded\_string ($string)
README.mkdn view on Meta::CPAN
]);
my %data = (
title => 'The Supernatural in Literature',
story => $article_text,
);
print $pure->render(\%data);
Results in:
<div class="story">
<div>
<h2>The Supernatural in Literature</h2>
<p>$article_text</p>
</div>
</div>
When the action is an object it must be an object that conformation
to the interface and behavior of a [Template::Pure](https://metacpan.org/pod/Template::Pure) object. For the
most part this means it must be an object that does a method 'render' that
takes the current data context refernce and returns an HTML string suitable
to become that value of the matched node.
When encountering such an object we pass the current data context, but we
add one additional field called 'content' which is the value of the matched
node. You can use this so that you can 'wrap' nodes with a template (similar
to the [Template](https://metacpan.org/pod/Template) WRAPPER directive).
my $wrapper_html = qq[
<p class="headline">To Be Wrapped</p>
];
my $wrapper = Template::Pure->new(
template = $wrapper_html,
directives => [
'p.headline' => 'content',
]);
my $html = qq[
<div>This is a test of the emergency broadcasting
network... This is only a test</div>
];
my $wrapper = Template::Pure->new(
template = $html,
directives => [
'div' => $wrapper,
]);
Results in:
<div>
<p class="headline">This is a test of the emergency broadcasting
network... This is only a test</p>
</div>
Lastly you can mimic a type of inheritance using data mapping and
node aliasing:
my $overlay_html = q[
<html>
<head>
<title>Example Title</title>
<link rel="stylesheet" href="/css/pure-min.css"/>
<link rel="stylesheet" href="/css/grids-responsive-min.css"/>
<link rel="stylesheet" href="/css/common.css"/>
<script src="/js/3rd-party/angular.min.js"></script>
<script src="/js/3rd-party/angular.resource.min.js"></script>
</head>
<body>
<section id="content">...</section>
<p id="foot">Here's the footer</p>
</body>
</html>
];
my $overlay = Template::Pure->new(
template=>$overlay_html,
directives=> [
'title' => 'title',
'^title+' => 'scripts',
'body section#content' => 'content',
]);
my $page_html = q[
<html>
<head>
<title>The Real Page</title>
<script>
function foo(bar) {
return baz;
}
</script>
</head>
<body>
You are doomed to discover that you never
recovered from the narcolyptic country in
which you once stood; where the fire's always
burning but there's never enough wood.
</body>
</html>
];
my $page = Template::Pure->new(
template=>$page_html,
directives=> [
'title' => 'meta.title',
'html' => [
{
title => \'title',
scripts => \'^head script',
content => \'body',
},
'^.' => $overlay,
]
]);
my $data = +{
meta => {
title => 'Inner Stuff',
},
};
Results in:
<html>
<head>
<title>Inner Stuff</title><script>
function foo(bar) {
return baz;
}
</script>
<link href="/css/pure-min.css" rel="stylesheet">
<link href="/css/grids-responsive-min.css" rel="stylesheet">
<link href="/css/common.css" rel="stylesheet">
<script src="/js/3rd-party/angular.min.js"></script>
<script src="/js/3rd-party/angular.resource.min.js"></script>
</head>
<body>
<section id="content">
You are doomed to discover that you never
recovered from the narcolyptic country in
which you once stood; where the fire&#39;s always
burning but there&#39;s never enough wood.
</section>
<p id="foot">Here's the footer</p>
</body>
</html>
## Object - A Mojo::DOM58 instance
In the case where you set the value of the action target to an instance of
[Mojo::DOM58](https://metacpan.org/pod/Mojo::DOM58), we let the value of that perform the replacement indicated by
the match specification:
my $html = q[
<html>
<head>
<title>Page Title</title>
</head>
<body>
<p class="foo">aaa</a>
</body>
</html>
];
my $pure = Template::Pure->new(
template=>$html,
directives=> [
'p' => Mojo::DOM58->new("<a href='localhost:foo'>Foo!</a>"),
]);
my $data = +{
title => 'A Shadow Over Innsmouth',
README.mkdn view on Meta::CPAN
<html>
<head>
<title>Page Title: </title>
</head>
<body>
<?pure-wrapper src='section_wrapper' ctx='meta'?>
<div id='story'>Example Story</div>
</body>
</html>
];
my $base = Template::Pure->new(
template=>$base_html,
directives=> [
'title+' => 'meta.title',
'#story' => 'story,
]
);
print $base->render({
story => 'Once Upon a Time...',
section_wrapper => $story_section_wrapper,
meta => {
title=>'Once',
author=>'jnap',
},
});
Results in:
<html>
<head>
<title>Page Title: Once</title>
</head>
<body>
<section>
<h1>Once</h1>
<p>By: jnap</p>
<div id='story'>Once Upon a Time</div>
</section>
</body>
</html>
This processing instructions 'wraps' the following tag node with the template that
is the target of 'src'. Like ["Includes"](#includes) you may pass data via named parameters or
by setting a new data context, as in the given example.
Similar approach using directives only:
my $base = Template::Pure->new(
template=>$base_html,
directives=> [
'title+' => 'meta.title',
'#story' => 'story,
'^#story => $story_section_wrapper,
]
);
## Overlay
An overlay replaces the selected node with the results on another template. Typically
you will pass selected nodes of the original template as directives to the new template.
This can be used to minic features like template inheritance, that exist in other templating
systems. One example:
my $overlay_html = q[
<html>
<head>
<title>Example Title</title>
<link rel="stylesheet" href="/css/pure-min.css"/>
<link rel="stylesheet" href="/css/grids-responsive-min.css"/>
<link rel="stylesheet" href="/css/common.css"/>
<script src="/js/3rd-party/angular.min.js"></script>
<script src="/js/3rd-party/angular.resource.min.js"></script>
</head>
<body>
</body>
</html>
];
my $overlay = Template::Pure->new(
template=>$overlay_html,
directives=> [
'title' => 'title',
'head+' => 'scripts',
'body' => 'content',
]);
my $base_html = q[
<?pure-overlay src='layout'
title=\'title'
scripts=\'^head script'
content=\'body'?>
<html>
<head>
<title>Page Title: </title>
<script>
function foo(bar) {
return baz;
}
</script>
</head>
<body>
<div id='story'>Example Story</div>
</body>
</html>
];
my $base = Template::Pure->new(
template=>$base_html,
directives=> [
'title+' => 'meta.title',
'#story' => 'story,
]
);
print $base->render({
layout => $overlay,
story => 'Once Upon a Time...',
meta => {
title=>'Once',
author=>'jnap',
},
});
Renders As:
<html>
<head>
<title>Once</title>
<link rel="stylesheet" href="/css/pure-min.css"/>
<link rel="stylesheet" href="/css/grids-responsive-min.css"/>
<link rel="stylesheet" href="/css/common.css"/>
<script src="/js/3rd-party/angular.min.js"></script>
<script src="/js/3rd-party/angular.resource.min.js"></script>
<script>
function foo(bar) {
return baz;
}
</script>
</head>
<body>
<div id='story'>Once Upon a Time...</div>
</body>
</html>
The syntax of the processing instruction is:
<?pure-overlay src='' @args ?>
Where 'src' is a data path to the template you want to use as the overlay, and @args is
a list of key values which populate the data context of the overlay when you process it.
Often these values will be references to existing nodes in the base template (as in the
examples \\'title' and \\'body' above) but they can also be used to map values from your
data context in the same way we do so for ["Include"](#include) and ["Wrapper"](#wrapper).
If you were to write this as 'directives only' it would look like:
my $base = Template::Pure->new(
template=>$base_html,
directives=> [
'title+' => 'meta.title',
'#story' => 'story,
'html' => [
{
title => \'title'
script s=> \'^head script'
content => \'body'
},
'^.' => 'layout',
],
]
);
Please note that although in this example the overlay wrapped over the entire template, it is
not limited to that, rather like the ["Wrapper"](#wrapper) processing instruction it just takes the next
tag node following as its overlay target. So you could have more than one overlap in a document
and can overlay sections for those cases where a ["Wrapper"](#wrapper) is not sufficently complex.
## Filter
A Filter will process the following node on a [Template::Pure](https://metacpan.org/pod/Template::Pure) instance as if that node was the
source for its template. This means that the target source template must be a coderef that builds
a <Template::Pure> object, and not an already instantiated one. For Example:
my $base_html = q[
<html>
<head>
<title>Title Goes Here...</title>
</head>
<body>
<?pure-filter src=?>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</body>
</html>
];
my $base = Template::Pure->new(
template => $base_html,
directives => [
'title' => 'title',
]
);
print $base->render({
title => 'Dark and Stormy..',
style => 'red',
filter => sub {
my $dom = shift;
return Template::Pure->new(
template => $dom,
directives => [
'li@class' => 'style'
]
},
});
Outputs:
<html>
<head>
<title>Dark and Stormy..</title>
</head>
<body>
<ul>
<li class='red'>One</li>
<li class='red'>Two</li>
<li class='red'>Three</li>
</ul>
</body>
</html>
As you can see, its similar to the Wrapper instruction, just instead of the matched template
being passed as the 'content' argument to be used in anther template, it becomes the template.
( run in 1.229 second using v1.01-cache-2.11-cpan-f56aa216473 )