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&#39;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

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

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

Template::Lace.

AUTHOR

Please See Template::Lace for authorship and contributor information.

COPYRIGHT & LICENSE

Please see Template::Lace for copyright and license information.