NAME
Template::Lace::DOM - DOM searching and tranformation engine
SYNOPSIS
sub process_dom {
my ($self, $dom) = @_;
$dom->body($self->body);
}
DESCRIPTION
Template::Lace::DOM is a subclass of Mojo::DOM58 that exists to abstract the DOM engine used by Template::Lace as well as to provide some helper methods intended to make the most common types of transformations on your DOM easier.
The helper API described here is one of the more 'under consideration / development' parts of Template::Lace since without a lot of usage in the wild its a bit hard to be sure exactly what type of helpers and in what form are most useful. Take the follower API with regard to the fact I will change things if necessary.
GENERAL HELPER METHODS
This class defines the following methods for general use
clone
Uses Storable dclone
to clone the current DOM.
ctx
Execute a DOM under a given data context, return DOM. Example
my $dom = Template::Lace::DOM->new(qq[
<section>
<p id='story'>...</p>
</section>
]);
$dom->ctx(sub {
my ($self, $data) = @_;
$_->at('#story')->content($data);
}, "Don't look down")
->... # more commands on $dom;
Returns:
<section>
<p id='story'>Don't look down'</p>
</section>
Isolate running transformaions on a DOM to explicit data. Makes it easier to create reusable snips of transformations.
overlay
Overlay the current DOM with a new one. Examples a coderef that should return the new DOM and any additional arguments you want to pass to the coderef. Example;
my $dom = Template::Lace::DOM->new(qq[
<h1 id="title">HW</h1>
<section id="body">Hello World</section>
</html>
]);
$dom->overlay(sub {
my ($dom, $now) = @_; # $dom is also localized to $_
my $new_dom = Template::Lace::DOM->new(qq[
<html>
<head>
<title>PAGE_TITLE</title>
</head>
<body>
STUFF
</body>
</html>
]);
$new_dom->title($dom->at('#title')->content)
->body($dom->at('#body'))
->at('head')
->append_content("<meta startup='$now'>");
return $new_dom;
}, scalar(localtime));
Returns example:
<html>
<head>
<title>HW</title>
<meta startup="Fri Apr 21 15:45:49 2017"></head>
<body>Hello World</body>
</html>
Useful to encapsulate a lot of the work when you want to apply a standard layout to a web page or section there of.
=wrap_with
Makes it easier to wrap a current DOM with a 'layout' DOM. Layout DOM replaces original. Example
my $master = Template::Lace::DOM->new(qq[
<html>
<head>
<title></title>
</head>
<body id="content">
</body>
</html>
]);
my $inner = Template::Lace::DOM->new(qq[
<h1>Hi</h1>
<p>This is a test of the emergency broadcasting networl</p>
]);
$inner->wrap_with($master)
->title('Wrapped');
print $inner;
Returns:
<html>
<head>
<title>Wrapped</title>
</head>
<body id="content">
<h1>Hi</h1>
<p>This is a test of the emergency broadcasting networl</p>
</body>
</html>
By default we match the wrapping DOM ($master in the given example) at the '#content' id for the template. You can specify an alternative match point by passing it as a second argument to wrap_with
.
repeat
Repeat a match as in a loop. Example:
my $dom = Template::Lace::DOM->new("<ul><li>ITEMS</li></ul>");
my @items = (qw/aaa bbb ccc/);
$dom->at('li')
->repeat(sub {
my ($li, $item, $index) = @_;
# $li here is DOM that represents the original '<li>ITEMS</li>'
# each repeat gets that (as a lone of the original) and you can
# modify it.
$li->content($item);
return $li;
}, @items);
print $dom->to_string;
Returns:
<ul>
<li>aaa</li>
<li>bbb</li>
<li>ccc</li>
<ul>
Basically you have a coderef that gets a cloned copy of the matched DOM and you need to return a new DOM that replaces it. Generally you might just modify the current comment (as in the given example) but you are permitted to replace the DOM totally.
You might want to see "LIST HELPERS" and "fill" as well.
smart_content
Like content
but when called on a tag that does not have content (like input
) will attempt to 'do the right thing'. For example it will put the value into the 'value' attribute of the input
tag.
Returns the original DOM.
NOTE We also html escape values here, since this is usually the safest thing.
NOTE Possibly magical method that will need lots of fixes.
fill
Used to 'fill' a DOM node with data inteligently by matching hash keys or methods to classes (or ids) and creating repeat loops when the data contains an arrayref.
fill
will recursively descend the data structure you give it and match hash keys to tag ids or tag classes and then arrayrefs to tag classes only (since ids can't be repeated).
Useful to rapidly fill data into a DOM if you don't mind the structual binding between classes/ids and your data. Examples:
my $dom = Template::Lace::DOM->new(q[
<section>
<ul id='stuff'>
<li></li>
</ul>
<ul id='stuff2'>
<li>
<a class='link'>Links</a> and Info:
<span class='info'></span>
</li>
</ul>
<ol id='ordered'>
<li></li>
</ol>
<dl id='list'>
<dt>Name</dt>
<dd id='name'></dd>
<dt>Age</dt>
<dd id='age'></dd>
</dl>
</section>
]);
$dom->fill({
stuff => [qw/aaa bbb ccc/],
stuff2 => [
{ link=>'1.html', info=>'one' },
{ link=>'2.html', info=>'two' },
{ link=>'3.html', info=>'three' },
],
ordered => [qw/11 22 33/],
list => {
name=>'joe',
age=>'32',
},
});
Produces:
<section>
<ul id="stuff">
<li>aaa</li><li>bbb</li><li>ccc</li>
</ul>
<ul id="stuff2">
<li>
<a class="link">1.html</a> and Info:
<span class="info">one</span>
</li><li>
<a class="link">2.html</a> and Info:
<span class="info">two</span>
</li><li>
<a class="link">3.html</a> and Info:
<span class="info">three</span>
</li>
</ul>
<ol id="ordered">
<li>11</li><li>22</li><li>33</li>
</ol>
<dl id="list">
<dt>Name</dt>
<dd id="name">joe</dd>
<dt>Age</dt>
<dd id="age">32</dd>
</dl>
</section>
In addition we also match and fill form elements based on the name
attribute, even automatically unrolling arrayrefs of hashrefs correctly for select
and input[type='radio']
. HOWEVER you must first match a form element (by id or class), for example:
my $dom = Template::Lace::DOM->new(q[
<section>
<form id='login'>
<input type='text' name='user' />
<input type='checkbox' name='toggle'/>
<input type='radio' name='choose' />
<select name='cars'>
<option value='value1'>Value</option>
</select>
</form>
</section>]);
$dom->at('html')
->fill(+{
login => +{
user => 'Hi User',
toggle => 'on',
choose => [
+{id=>'id1', value=>1},
+{id=>'id2', value=>2, selected=>1},
],
cars => [
+{ value=>'honda', content=>'Honda' },
+{ value=>'ford', content=>'Ford', selected=>1 },
+{ value=>'gm', content=>'General Motors' },
],
},
});
print $dom;
Would return:
<section>
<form id="login">
<input name="user" type="text" value="Hi User">
<input name="toggle" type="checkbox" value="on">
<input id="id1" name="choose" type="radio" value="1">
<input id="id2" name="choose" selected="on" type="radio" value="2">
<select name="cars">
<option value="honda">Honda</option>
<option selected="on" value="ford">Ford</option>
<option value="gm">General Motors</option>
</select>
</form>
</section>
This is done because lookup by name
globally would impact performance and return too many false positives.
In general fill
will try to do the right thing, even coping with list tags such as ol
, ul
and input type tags (including select
and Radio input tags) correctly. You maye find it more magical than you like. Also using this introduces a required structural binding between you Model class and the ids and classes of tags in your templates. You might find this a great convention or fragile binding depending on your outlook.
You might want to see "LIST HELPERS" as well.
for
Syntax sugar that combines 'find' and 'fill'. So:
$dom->find($match)
->each(sub $_->fill($spec));
Can be written as:
$dom->for($match, $spec);
Might save a bit of space.
append_style_uniquely
append_script_uniquely
append_link_uniquely
Appends a style, script or link tag to the header 'uniquely' (that is we don't append it if its already there). The means used to determine uniqueness is first to check for an exising id attribute, and then in the case of scripts we look at the src tag, or the href tag for a link.
You need to add the id attributes yourself and be consistent. In the future we may add some type of md5 checksum on content when that exists.
Useful when you have a lot of components that need supporting scripts or styles and you want to make sure you only add the required supporting code once.
Examples:
$dom->append_style_uniquely(qq[
<style id='four'>
body h4 { border: 1px }
</style>]);
NOTE This should be the entire tag element.
append_css_href_uniquely
append_js_src_uniquely
Similar to the previous group of helpers "append_link_uniquely", etc. but instead of taking the entire tag this just wants a URI which is either the src attribute for a script
tag, or the href attribute of a link
tag. Useful for quickly adding common assets to your pages. URIs are added uniquely so you don't have to worry about checking for the presence it first.
$dom->append_js_src_uniquely('/js/common1.js')
->append_js_src_uniquely('/js/common2.js')
->append_js_src_uniquely('/js/common2.js')
Would render similar to:
<html>
<head>
<title>Wrapped</title>
<script src="/js/common1.js" type="text/javascript"></script>
<script src="/js/common2.js" type="text/javascript"></script>
</head>
<body id="content">
<h1>Hi</h1>
<p>This is a test of the emergency broadcasting networl</p>
</body>
</html>
We append these to the last node inside the head
element content.
do
NOTE: Helper is evolving and may change.
Allows you to run a list of CSS matches at once. For example:
my $dom = Template::Lace::DOM->new(q[
<section>
<h2>title</h2>
<ul id='stuff'>
<li></li>
</ul>
<ul id='stuff2'>
<li>
<a class='link'>Links</a> and Info:
<span class='info'></span>
</li>
</ul>
<ol id='ordered'>
<li></li>
</ol>
<dl id='list'>
<dt>Name</dt>
<dd id='name'></dd>
<dt>Age</dt>
<dd id='age'></dd>
</dl>
<a>Link</a>
</section>
]);
$dom->do(
'section h2' => 'Wrathful Hound',
'#stuff', [qw/aaa bbbb ccc/],
'#stuff2', [
{ link=>'1.html', info=>'one' },
{ link=>'2.html', info=>'two' },
{ link=>'3.html', info=>'three' },
],
'#ordered', sub { $_->fill([qw/11 22 33/]) },
'#list', +{
name=>'joe',
age=>'32',
},
'a@href' => 'localhost://aaa.html',
);
Returns:
<section>
<h2>Wrathful Hound</h2>
<ul id="stuff">
<li>aaa</li><li>bbbb</li><li>ccc</li></ul>
<ul id="stuff2">
<li>
<a class="link" href="localhost://aaa.html">1.html</a> and Info:
<span class="info">one</span>
</li><li>
<a class="link" href="localhost://aaa.html">2.html</a> and Info:
<span class="info">two</span>
</li><li>
<a class="link" href="localhost://aaa.html">3.html</a> and Info:
<span class="info">three</span>
</li></ul>
<ol id="ordered">
<li>11</li><li>22</li><li>33</li></ol>
<dl id="list">
<dt>Name</dt>
<dd id="name">joe</dd>
<dt>Age</dt>
<dd id="age">32</dd>
</dl>
<a href="localhost://aaa.html">Link</a>
</section>
Takes a list of pairs where the first item in the pair is a match specification and the second is an action to take on it. The match specification is basically just a CSS match with one added feature to make it easier to fill values into attributes, if the match specification ends in @attr
the action taken is to fill that attribute.
Additionally if the action is a simple, scalar value we automatically HTML escape it for you
NOTE if you want to set content or attributes on the DOM that ->do is run on you can use '.' as the match specification.
tt
Lets you fill the matching node's content via <Template::Tiny>. This of course violates the 'pure no logic templates' but its here as an escapt hatch should you find the transformation are very awkward. Also its probably the best way to fill variable into an inline script.
my $dom = Template::Lace::DOM->new(
qq[<span>Hello [% name%]! It is a [% weather %] day!</span>]);
$dom->at('span')
->tt(name=>'John',
weather=>'great');
print $dom; # prints '<span>Hello John! It is a great day!</span>'
ATTRIBUTE HELPERS
The following methods are intended to make setting standard attributes on HTML tags easier. All methods return the DOM node instance of the tag making it easier to chain several calls.
target
src
href
id
action
method
size
headers
formaction
enctype
alt
colspan
value
Example
$dom->at('form')
->id('#login_form')
->action('/login')
->method('POST');
checked
selected
hidden
multiple
These attribute helpers have a special feature, since its basically a boolean attribute will check the passed value for its truth state, setting the attribute value to 'on' when true, but NOT setting the attribute at all if its false.
class
This attribute helper has a special shortcup to make it easier to programmtically set several classes based on a property. If your argument is a hashref, all the keys whose values are true will be added. For example:
my $dom = Template::Lace::DOM->new('<html><div>aaa</div></html>');
$dom->at('div')->class({ completed=>1, selected=>0});
print $dom;
Returns:
<html><div class="completed">aaa</div></html>
If you instead use an arrayref, all the classes are just added.
Useful to reduce some boilerplate.
add_class
Works just like "class" except the classes are added to any ones that are already there.
UNIQUE TAG HELPERS
Helpers to access tags that are 'unique', typically only appearing on a page once. Can accept a coderef, reference or scalar value. All return the original DOM for ease of chaining.
html
head
title
body
Examples:
my $data = +{
intro_title => "Things Todo...",
status => {
active_items => 2,
competed_items => 10,
late_items => 0,
},
items => [
'walk dogs',
'buy milk',
],
};
my $dom = Template::Lace::DOM->new(qq[
<html>
<head>
<title>TITLE</title>
</head>
<body>
<h1 id='intro_title'>TITLE</h1>
<dl>
</dl>
<dt>Active</dt>
<dd id='active_items'>0</dd>
<dt>Completed</dt>
<dd id='completed_items'>0</dd>
<dt>Late</dt>
<dd id='late_items'>0</dd>
</dl>
<ol>
<li class='items'>ITEMS</li>
</ol>
</body>
</html>
]);
$dom->title($data->{intro_title})
->head(sub {
$_->append_content('<meta description="a page" />');
$_->append_content('<link href="/css/core.css" />');
})->body($data);
print $dom->to_string;
Returns
<html>
<head>
<title>Things Todo...</title>
</head>
<body>
<h1 id='intro_title'>Things Todo...</h1>
<dl>
</dl>
<dt>Active</dt>
<dd id='active_items'>2</dd>
<dt>Completed</dt>
<dd id='completed_items'>10</dd>
<dt>Late</dt>
<dd id='late_items'>0</dd>
</dl>
<ol>
<li class='items'>walk dog</li>
<li class='items'>buy milk</li>
</ol>
</body>
</html>
Under the hood we use "fill" and smart_content as well as repeat as necessary. More magic for less code but at some code in performance and possible support / code understanding.
LIST TAG HELPERS
Helpers to make populating data into list type tags easier. All return the original DOM to make chaining easier.
my $dom = Template::Lace::DOM->new(q[
<section>
<ul id='stuff'>
<li></li>
</ul>
<ul id='stuff2'>
<li>
<a class='link'>Links</a> and Info:
<span class='info'></span>
</li>
</ul>
<ol id='ordered'>
<li></li>
</ol>
<dl id='list'>
<dt>Name</dt>
<dd id='name'></dd>
<dt>Age</dt>
<dd id='age'></dd>
</dl>
</section>
]);
$dom->ul('#stuff', [qw/aaa bbbb ccc/]);
$dom->ul('#stuff2', [
{ link=>'1.html', info=>'one' },
{ link=>'2.html', info=>'two' },
{ link=>'3.html', info=>'three' },
]);
$dom->ol('#ordered', [qw/11 22 33/]);
$dom->dl('#list', {
name=>'joe',
age=>'32',
});
ul
ol
Both helper make it easier to populate an array reference of data into list tags.
Example:
my $dom = Template::Lace::DOM->new(q[
<section>
<ul id='stuff'>
<li></li>
</ul>
<ul id='stuff2'>
<li>
<a class='link'>Links</a> and Info:
<span class='info'></span>
</li>
</ul>
<ol id='ordered'>
<li></li>
</ol>
<dl id='list'>
<dt>Name</dt>
<dd id='name'></dd>
<dt>Age</dt>
<dd id='age'></dd>
</dl>
</section>
]);
$dom->ul('#stuff', [qw/aaa bbbb ccc/]);
$dom->ul('#stuff2', [
{ link=>'1.html', info=>'one' },
{ link=>'2.html', info=>'two' },
{ link=>'3.html', info=>'three' },
]);
$dom->ol('#ordered', [qw/11 22 33/]);
Returns:
<section>
<ul id="stuff">
<li>aaa</li>
<li>bbbb</li>
<li>ccc</li>
</ul>
<ul id="stuff2">
<li>
<a class="link">1.html</a> and Info:
<span class="info">one</span>
</li>
<li>
<a class="link">2.html</a> and Info:
<span class="info">two</span>
</li>
<li>
<a class="link">3.html</a> and Info:
<span class="info">three</span>
</li>
</ul>
<ol id="ordered">
<li>11</li>
<li>22</li>
<li>33</li>
</ol>
</section>
dl
This helper will take either an arrayref or hashref and attempt to 'do the right thing'. Example:
my $dom = Template::Lace::DOM->new(q[
<dl id='hashref'>
<dt>Name</dt>
<dd id='name'></dd>
<dt>Age</dt>
<dd id='age'></dd>
</dl>
<dl id='arrayref'>
<dt class='term'></dt>
<dd class='value'></dd>
</dl>
]);
$dom->dl('#hashref', +{
name=>'John',
age=> '48'
});
$dom->dl('#arrayref', [
+{ term=>'Name', value=> 'John'},
+{ term=>'Age', value=> 42 },
+{ term=>'email', value=> [
'jjn1056@gmail.com',
'jjn1056@yahoo.com']},
]);
Returns:
<dl id="hashref">
<dt>Name</dt>
<dd id="name">John</dd>
<dt>Age</dt>
<dd id="age">48</dd>
</dl>
<dl id="arrayref">
<dt class="term">Name</dt>
<dd class="value">John</dd>
<dt class="term">Age</dt>
<dd class="value">42</dd>
<dt class="term">email</dt>
<dd class="value">jjn1056@gmail.com</dd>
<dd class="value">jjn1056@yahoo.com</dd>
</dl>
select
The select
tag for the purposes of filling its options
is treated as a type of list tag.
my $dom = Template::Lace::DOM->new(q[
<form>
<select name='cars'>
<option>Example</options>
</select>
</form>]);
$dom->select('cars', [
+{ value=>'honda', content=>'Honda' },
+{ value=>'ford', content=>'Ford', selected=>1 },
+{ value=>'gm', content=>'General Motors' },
]);
print $dom;
Returns:
<select name="cars">
<option value="honda">Honda</option>
<option selected="on" value="ford">Ford</option>
<option value="gm">General Motors</option>
</select>
Please note that match 'id' is on the name
attribute of the select
tag, not of the id
attribute as it is on other list helper types.
You can also populate option groups as in the following:
my $dom = Template::Lace::DOM->new(q[
<select name='jobs'>
<optgroup label='Example'>
<option>Example</option>
</optgroup>
</select>]);
$dom->select('jobs', [
+{
label=>'Easy',
options => [
+{ value=>'slacker', content=>'Slacker' },
+{ value=>'couch_potato', content=>'Couch Potato' },
],
},
+{
label=>'Hard',
options => [
+{ value=>'digger', content=>'Digger' },
+{ value=>'brain', content=>'Brain Surgeon' },
],
},
]);
print $dom;
Would return:
<select name="jobs">
<optgroup label="Easy">
<option value="slacker">Slacker</option>
<option value="couch_potato">Couch Potato</option>
</optgroup>
<optgroup label="Hard">
<option value="digger">Digger</option>
<option value="brain">Brain Surgeon</option>
</optgroup>
</select>
radio
List helper for a radio input type. Example
my $dom = Template::Lace::DOM->new("
<form>
<input type='radio' name='choose' />
</form>");
$dom->radio('choose',[
+{id=>'id1', value=>1},
+{id=>'id2', value=>2, selected=>1},
]);
print $dom;
Returns;
<form>
<input id="id1" name="choose" type="radio" value="1">
<input id="id2" name="choose" type="radio" value="2" selected="on">
</form>
optgroup
Populates an optgroup tag:
my $dom = Template::Lace::DOM->new(q[
<form>
<select name='states'>
<optgroup id='usa_states' label='USA States'>
<option class="state">Example</option>
</optgroup>
</select>
</form>
]);
$dom->optgroup('#usa_states')->fill({
state => [
+{ value=>'ny', content=>'New York' },
+{ value=>'tx', content=>'Texas' },
]
});
Returns:
<form>
<select name="states">
<optgroup id="usa_states" label="USA States">
<option class="state" value="ny">New York</option>
<option class="state" value="tx">Texas</option>
</optgroup>
</select>
</form>
GENERAL TAG HELPERS
Helpers to work with common tags. All return the original DOM to make chaining easier.
form
Form tag helper. Example:
$dom->form('#login', sub {
$_->action('login.html');
});
SEE ALSO
AUTHOR
Please See Template::Lace for authorship and contributor information.
COPYRIGHT & LICENSE
Please see Template::Lace for copyright and license information.