File Coverage

blib/lib/Template/Pure.pm
Criterion Covered Total %
statement 409 451 90.6
branch 185 238 77.7
condition 66 80 82.5
subroutine 66 72 91.6
pod 7 20 35.0
total 733 861 85.1


line stmt bran cond sub pod time code
1 26     26   2098591 use strict;
  26         40  
  26         596  
2 26     26   97 use warnings;
  26         29  
  26         1009  
3              
4             package Template::Pure;
5              
6             our $VERSION = '0.030';
7              
8 26     26   11959 use Mojo::DOM58;
  26         480510  
  26         679  
9 26     26   184 use Scalar::Util;
  26         33  
  26         826  
10 26     26   11307 use Template::Pure::ParseUtils;
  26         267  
  26         658  
11 26     26   9477 use Template::Pure::Filters;
  26         42  
  26         614  
12 26     26   8239 use Template::Pure::DataContext;
  26         49  
  26         556  
13 26     26   8293 use Template::Pure::DataProxy;
  26         36  
  26         544  
14 26     26   106 use Template::Pure::EncodedString;
  26         24  
  26         363  
15 26     26   8174 use Template::Pure::Iterator;
  26         43  
  26         607  
16 26     26   13615 use Storable qw(dclone);
  26         57437  
  26         114176  
17              
18             sub new {
19 50     50 1 65570 my ($proto, %args) = @_;
20 50   33     256 my $class = ref($proto) || $proto;
21              
22 50   50     242 my $template = delete($args{template}) || $class->template || die "Can't find a template";
23 50   100     198 my $directives = delete($args{directives}) || [$class->directives];
24            
25             my $self = bless +{
26             filters => delete($args{filters}) || +{},
27             directives => $directives,
28 50   50     570 components => delete($args{components}) || +{},
      100        
29             %args,
30             }, $class;
31              
32 50         181 my ($dom, @directives) = $self->_prepare_dom($template);
33 50         74 unshift @directives, @{$self->{directives}};
  50         221  
34              
35 50         84 $self->{dom} = $dom;
36 50         78 $self->{directives} = \@directives;
37 50         399 return $self;
38             }
39              
40             sub _process_pi {
41 6     6   11 my ($self, %params) = @_;
42 6         13 my ($target, %attrs) = $self->parse_processing_instruction($params{node}->tree->[1]);
43 6         11 my $ctx = delete $attrs{ctx};
44 6         8 my $src = delete $attrs{src};
45              
46 6 100       17 if($target eq 'pure-include') {
    100          
    100          
    50          
47 3         15 $params{node}->replace("<span id='include-$params{cnt}'>include placeholder</span>");
48 3         632 my @include_directives;
49 3 100       16 if($ctx) {
    50          
50 1         6 @include_directives = ("#include-$params{cnt}" => +{ $ctx => ['^.' => "/$src"]});
51             } elsif(%attrs) {
52 2         6 $attrs{$src} = "/$src";
53             @include_directives = (
54             "#include-$params{cnt}" => [
55             \%attrs,
56             '^.' => sub {
57 2     2   3 my ($t, $dom, $data) = @_;
58 2         6 return $data->{$src};
59             },
60 2         18 ]);
61             } else {
62 0         0 @include_directives = ("^#include-$params{cnt}", $src)
63             }
64 3         3 push @{$params{directives}}, @include_directives;
  3         10  
65             } elsif($target eq 'pure-wrapper') {
66 1         4 $params{node}->following('*')->first->attr('data-pure-wrapper-id'=>"wrapper-$params{cnt}");
67 1         221 $params{node}->remove;
68 1 50       95 if($ctx) {
    0          
69 1         11 push @{$params{directives}}, (
70             "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", +{ $ctx => ['^.' => "/$src"]},
71 1     1   3 "*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef },
72 1         1 );
73             } elsif(%attrs) {
74 0         0 $attrs{$src} = "/$src";
75 0         0 push @{$params{directives}}, (
76             "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", [\%attrs, '^.' => "$src"],
77 0     0   0 "*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef },
78 0         0 );
79             } else {
80 0         0 push @{$params{directives}}, (
81             "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", $src,
82 0     0   0 "*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef },
83 0         0 );
84             }
85             } elsif($target eq 'pure-filter') {
86 1         4 $params{node}->following('*')->first->attr('data-pure-filter-id'=>"filter-$params{cnt}");
87 1         174 $params{node}->remove;
88 1 50       100 if($ctx) {
    50          
89 0         0 push @{$params{directives}}, (
90             "^*[data-pure-filter-id=filter-$params{cnt}]", +{ $ctx => ['^.' => sub {
91 0     0   0 my ($t, $dom, $data) = @_;
92 0         0 $t->data_at_path($data, "/$src")->($dom);
93             } ]},
94 0     0   0 "*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef },
95 0         0 );
96             } elsif(%attrs) {
97 1         12 push @{$params{directives}}, (
98             "^*[data-pure-filter-id=filter-$params{cnt}]", [\%attrs, '^.' => sub {
99 1     1   2 my ($t, $dom, $data) = @_;
100 1         6 $t->data_at_path($data, "/$src")->($dom);
101             } ],
102 1     1   3 "*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef },
103 1         2 );
104             } else {
105 0         0 push @{$params{directives}}, (
106             "^*[data-pure-filter-id=filter-$params{cnt}]", sub {
107 0     0   0 my ($t, $dom, $data) = @_;
108 0         0 $t->data_at_path($data, $src)->($dom);
109             },
110 0     0   0 "*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef },
111 0         0 );
112             }
113             } elsif($target eq 'pure-overlay') {
114 1         4 $params{node}->following('*')->first->attr('data-pure-overlay-id'=>"overlay-$params{cnt}");
115 1         371 $params{node}->remove;
116              
117 1         111 push @{$params{directives}}, (
  1         7  
118             "^*[data-pure-overlay-id=overlay-$params{cnt}]", [ +{%attrs, src=>$src }, '^.' => 'src'],
119             # "*[data-pure-overlay-id=overlay-$params{cnt}]\@data-pure-overlay-id", sub { undef },
120             );
121             } else {
122 0         0 warn "Encountering processing instruction $target that I can't process";
123             }
124 6         6 $params{cnt}++;
125 6         25 return %params;
126             }
127              
128 8     8 0 27 sub components { shift->{components} }
129 10     10 0 2334 sub initialized_components { shift->{initialized_components} }
130              
131             sub initialize_component {
132 8     8 0 12 my ($self, $name, %params) = @_;
133 8   50     21 return ($self->components->{$name} || die "No Component $name")->($self, %params);
134             }
135              
136             sub _process_components {
137 8     8   16 my ($self, %params) = @_;
138             my %fields = (
139 8 50       15 %{$params{node}->attr||+{}},
140             parent => $params{component_current_parent}[-1]||undef,
141             node => $params{node},
142 8   100     9 container => $self,
143             );
144              
145 8         115 my $component_id = $params{component_name}.'-'.$params{cnt};
146             my $component = $self->{initialized_components}{$component_id}
147 8         21 = $self->initialize_component($params{component_name}, %fields);
148              
149             $params{component_current_parent}[-1]->add_child($component)
150 8 100       75 if $params{component_current_parent}[-1];
151              
152 8         7 push @{$params{component_current_parent}}, $component;
  8         11  
153 8         17 $params{node}->attr('data-pure-component-id'=>$component_id);
154              
155 8 50       145 %params = $component->on_process_components($self, %params)
156             if $component->can('on_process_components');
157              
158 8         5 push @{$params{directives}}, (
  8         35  
159             "^*[data-pure-component-id=$component_id]",
160             $component->prepare_render_callback );
161              
162 8         10 $params{cnt}++;
163 8         37 return %params;
164             }
165              
166             sub _process_node {
167 886     886   1304 my ($self, %params) = @_;
168 886 100       1364 if($params{node}->type eq 'pi') {
169 6         47 %params = $self->_process_pi(%params);
170             }
171              
172 886         6244 my $component_name;
173 886 100 100     1372 if(($component_name) = (($params{node}->tag||'') =~m/^pure\-(.+)?/)) {
174 8 100       99 $params{component_current_parent} = [] unless defined $params{component_current_parent};
175 8         10 $params{component_name} = $component_name;
176 8         24 %params = $self->_process_components(%params);
177 8         17 delete $params{component_name};
178             }
179             $params{node}->child_nodes->each(sub {
180 758     758   19658 %params = $self->_process_node(%params, node=>$_);
181 886         8829 });
182              
183 886 100 100     16201 pop @{$params{component_current_parent}} if defined $params{component_current_parent} && $component_name;
  8         9  
184              
185 886         3497 return %params;
186             }
187              
188             sub _prepare_dom {
189 50     50   74 my ($self, $template) = @_;
190 50         74 my @directives = ();
191 50         254 my $dom = Mojo::DOM58->new($template);
192 50         28341 my %params = (cnt=>0, node=>$dom, directives=>\@directives);
193 50         173 my $nodes = $dom->child_nodes;
194              
195             $nodes->each(sub {
196 128     128   775 %params = $self->_process_node(%params, node=>$_);
197 50         4297 });
198            
199 50         149 return ($dom, @{$params{directives}});
  50         217  
200             }
201              
202 54     54 0 5826 sub clone_dom { return dclone(shift->{dom}) }
203              
204             sub render {
205 54     54 1 650 my ($self, $data_proto, $extra_directives) = @_;
206 54         267 $data_proto = Template::Pure::DataProxy->new($data_proto, self=>$self);
207 54 50       140 $extra_directives = [] unless $extra_directives;
208              
209 54         187 my $dom = $self->clone_dom;
210            
211 54         200 return $self->process_dom($dom, $data_proto, $extra_directives)->to_string;
212             }
213              
214             sub process_dom {
215 54     54 1 91 my ($self, $dom, $data_proto, $extra_directives) = @_;
216             return $self->_process_dom_recursive(
217             $data_proto,
218             $dom,
219 54         109 @{$self->{directives}},
220 54 50       71 @{$extra_directives||[]},
  54         257  
221             );
222             }
223              
224 16     16 0 68 sub default_filters { Template::Pure::Filters->all }
225 201     201 1 458 sub escape_html { Template::Pure::Filters::escape_html($_[1]) }
226 56     56 1 4449 sub encoded_string { Template::Pure::EncodedString->new($_[1]) }
227              
228             my %match_specs = ();
229 245   100 245 0 219 sub parse_match_spec { return %{ $match_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_match_spec($_[1])} } }
  245         1044  
230              
231             my %data_specs = ();
232 173   100 173 0 133 sub parse_data_spec { return %{ $data_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_data_spec($_[1])} } }
  173         662  
233              
234             my %data_templates = ();
235 11   100 11 0 11 sub parse_data_template { return @{ $data_templates{$_[1]} ||= [Template::Pure::ParseUtils::parse_data_template($_[1])] } }
  11         56  
236              
237             my %processing_instruction_specs = ();
238             sub parse_processing_instruction {
239 6   50 6 0 35 return @{ $processing_instruction_specs{$_[1]} ||= [Template::Pure::ParseUtils::parse_processing_instruction($_[1])] };
  6         42  
240             }
241              
242             my %itr_specs = ();
243 17   100 17 0 15 sub parse_itr_spec { return %{ $itr_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_itr_spec($_[1])} } }
  17         108  
244              
245             sub data_at_path {
246 7     7 1 2433 my ($self, $data, $path) = @_;
247 7         15 my %data_spec = $self->parse_data_spec($path);
248              
249 7 50 66     46 unless(Scalar::Util::blessed($data) and $data->isa('Template::Pure::DataContext') ) {
250 7         18 $data = Template::Pure::DataContext->new($data, $self->{root_data});
251             }
252              
253 7         18 return $self->_value_from_data($data, %data_spec);
254             }
255              
256             sub at_or_die {
257 28     28 0 34 my ($self, $dom, $css) = @_;
258 28 100       75 my $new = $css eq '.' ? $dom : $dom->at($css);
259 28 50       2866 die "$css is not a matching path" unless defined $new;
260 28         72 return $new;
261             }
262              
263             sub find_or_die {
264 188     188 0 181 my ($self, $dom, $css) = @_;
265 188         428 my $collection = $dom->find($css);
266 188 100       56925 die "Match specification '$css' produces no nodes on $dom" unless $collection->size;
267 187         799 return $collection;
268             }
269              
270             sub _process_dom_recursive {
271 117     117   4890 my ($self, $data_proto, $dom, @directives) = @_;
272              
273 117 100       312 $self->{root_data} = $data_proto unless exists $self->{root_data};
274              
275 117         667 my $data = Template::Pure::DataContext->new($data_proto, $self->{root_data});
276              
277 117         280 ($data, @directives) = $self->_process_directive_instructions($dom, $data, @directives);
278              
279 117         264 while(@directives) {
280 233         10481 my $directive = shift @directives;
281              
282 233 100 100     780 if(ref($directive)||'' eq 'CODE') {
283 1         6 $directive->($self, $dom, $data);
284 1         508 next;
285             }
286              
287 232 100       471 if($directive =~/\=\{/g) {
288             $directive = join '', map {
289 1 100       6 ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_;
  2         15  
290             } $self->parse_data_template($directive);
291             }
292              
293 232         387 my %match_spec = $self->parse_match_spec($directive);
294 232         345 my $action_proto = shift @directives;
295              
296 232 50       465 $dom = $dom->root if $match_spec{absolute};
297              
298 232 100 100     1801 if($match_spec{mode} eq 'filter') {
    100 100        
    100 100        
    100          
    100          
299 1         13 $self->_process_dom_filter($dom, $data, $match_spec{css}, $action_proto);
300             } elsif((ref($action_proto)||'') eq 'HASH') {
301 24         31 $self->_process_sub_data($dom, $data, \%match_spec, %{$action_proto});
  24         88  
302             } elsif((ref($action_proto)||'') eq 'ARRAY') {
303 17         50 $self->process_sub_directives($dom, $data->value, $match_spec{css}, @{$action_proto});
  17         51  
304             } elsif((ref($action_proto)||'') eq 'CODE') {
305 21         55 $self->_process_code($dom, $data, $action_proto, %match_spec);
306             } elsif(Scalar::Util::blessed($action_proto)) {
307 10         37 $self->_process_obj($dom, $data, $action_proto, %match_spec);
308             } else {
309 159         400 my $value_proto = $self->_value_from_action_proto($dom, $data, $action_proto, %match_spec);
310 159         526 $self->_process_value_proto($dom, $data, $value_proto, %match_spec);
311             }
312             }
313              
314 115         15491 return $dom;
315             }
316              
317             sub _process_value_proto {
318 186     186   405 my ($self, $dom, $data, $value_proto, %match_spec) = @_;
319 186 100 100     1334 if(
    100 66        
      100        
320             Scalar::Util::blessed($value_proto) &&
321             ($value_proto->isa('Template::Pure') || $value_proto->can('TO_HTML'))
322             ) {
323 9         27 $self->_process_obj($dom, $data, $value_proto, %match_spec);
324             } elsif((ref($value_proto)||'') eq 'CODE') {
325 2         8 $self->_process_code($dom, $data, $value_proto, %match_spec);
326             } else {
327 175         405 $self->_process_match_spec($dom, $value_proto, %match_spec);
328             }
329             }
330              
331             sub _process_obj {
332 19     19   66 my ($self, $dom, $data, $obj, %match_spec) = @_;
333 19         29 my $css = $match_spec{css};
334              
335 19 100       113 if($obj->isa(ref $self)) {
    100          
    50          
336 15 100       30 if($css eq '.') {
337 12         33 my $value = $self->_value_from_template_obj($dom, $data, $obj, %match_spec);
338 12         113 $self->_process_mode($dom, $value, %match_spec);
339             } else {
340 3         17 my $collection = $self->find_or_die($dom,$css);
341             $collection->each(sub {
342              
343 4     4   123 my $content;
344 4 100       12 if($match_spec{target} eq 'content') {
    50          
    0          
345 3         8 $content = $self->encoded_string($_->content);
346             } elsif($match_spec{target} eq 'node') {
347 1         3 $content = $self->encoded_string($_->to_string);
348 0         0 } elsif(my $attr = ${$match_spec{target}}) {
349 0         0 $content = $_->attr($attr);
350             }
351              
352 4         15 my $new_data = Template::Pure::DataProxy->new(
353             $data->value,
354             content => $self->encoded_string($content));
355              
356 4         14 my $value = $self->encoded_string($obj->render($new_data));
357              
358 4         23 $self->_process_mode($_, $value, %match_spec);
359 3         18 });
360             }
361             } elsif($obj->can('TO_HTML')) {
362 3 100       8 if($css eq '.') {
363 1         2 my $value = $obj->TO_HTML($self, $dom, $data->value);
364 1         6 $self->_process_mode($dom, $value, %match_spec);
365             } else {
366 2         6 my $collection = $self->find_or_die($dom,$css);;
367             $collection->each(sub {
368 3     3   107 my $value = $obj->TO_HTML($self, $_, $data->value);
369 3         38 $self->_process_mode($_, $value, %match_spec);
370 2         11 });
371             }
372             } elsif($obj->isa('Mojo::DOM58')) {
373 1         5 $self->_process_match_spec($dom, $obj, %match_spec);
374             } else {
375 0         0 die "Can't process object of type $obj.";
376             }
377             }
378              
379             sub _value_from_action_proto {
380 178     178   363 my ($self, $dom, $data, $action_proto, %match_spec) = @_;
381 178 100 50     373 if(ref \$action_proto eq 'SCALAR') {
    50          
382 165         283 return $self->_value_from_scalar_action($data, $action_proto);
383             } elsif((ref($action_proto)||'') eq 'SCALAR') {
384 13         26 return $self->_value_from_dom($dom, $$action_proto);
385             } else {
386 0         0 die "I encountered an action I don't know what to do with: $action_proto";
387             }
388             }
389              
390             sub _value_from_scalar_action {
391 165     165   169 my ($self, $data, $action_proto) = @_;
392              
393             ## If a $action_proto contains a ={ with no | first OR it contains a ={ and no |
394             ## That means it is a string with placeholders
395              
396 165         225 my $first_pipe = index($action_proto, '|');
397 165         161 my $first_open = index($action_proto, '={');
398            
399 165 100 100     682 if(
      100        
      66        
400             (
401             ($first_open >= 0) &&
402             ($first_open < $first_pipe)
403             ) || (
404             ($first_open >= 0) &&
405             ($first_pipe == -1)
406             )
407             ) {
408             my @parts = map {
409 10 100       24 ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_;
  31         121  
410             } $self->parse_data_template($action_proto);
411              
412             # If the last part is a literal AND it has trailing filters
413             # we need to process the filters. And deal with all the special cases...
414 10 100 100     65 if(Scalar::Util::blessed($parts[-1]) && (index("$parts[-1]", '|') >0) ) {
415 3         6 my $last = substr "$parts[-1]", 0, index("$parts[-1]", '|');
416 3         18 $last=~s/\s+$//;
417 3         13 my %data_spec = $self->parse_data_spec(pop @parts);
418 3         13 my $return = join('', @parts, $last);
419 3         5 foreach my $filter (@{$data_spec{filters}}) {
  3         8  
420 3         8 $return = $self->_apply_data_filter($return, $data, $filter);
421             }
422 3         16 return $return;
423             }
424            
425 7         62 return join('', @parts);
426             } else {
427 155         252 my %data_spec = $self->parse_data_spec($action_proto);
428 155 100       354 if(defined(my $literal = $data_spec{literal})) {
429 2         5 return $literal;
430             } else {
431 153         438 return $self->_value_from_data($data, %data_spec);
432             }
433             }
434             }
435              
436             sub _process_code {
437 23     23   51 my ($self, $dom, $data, $code, %match_spec) = @_;
438 23         29 my $css = $match_spec{css};
439 23 100       44 if($css eq '.') {
440 8         21 my $value = $self->_call_coderef($code, $dom, $data->value);
441 8         29 $self->_process_value_proto($dom, $data, $value, %match_spec);
442             } else {
443 15         28 my $collection = $self->find_or_die($dom,$css);
444             $collection->each(sub {
445 19     19   237 my $value = $self->_call_coderef($code, $_, $data->value);
446 19         146 my %local_match_spec = (%match_spec, css=>'.');
447 19         72 $self->_process_value_proto($_, $data, $value, %local_match_spec);
448 15         70 });
449             }
450             }
451              
452             sub _call_coderef {
453 27     27   31 my ($self, $code, $dom, $value) = @_;
454 27         61 return $self->$code($dom, $value);
455             }
456              
457             sub _value_from_template_obj {
458 15     15   35 my ($self, $dom, $data, $template, %match_spec) = @_;
459 15         33 my $content = $self->_value_from_dom($dom, \%match_spec);
460 15         290 my $new_data = Template::Pure::DataProxy->new(
461             $data->value,
462             content => $self->encoded_string($content));
463            
464 15         40 return $self->encoded_string($template->render($new_data));
465             }
466              
467             sub _process_directive_instructions {
468 117     117   266 my ($self, $dom, $data, @directives) = @_;
469 117 100 100     491 if( (ref($directives[0])||'') eq 'HASH') {
470 9         12 my %map = %{shift(@directives)};
  9         34  
471 9         11 my %new_data;
472 9         20 foreach my $key (keys %map) {
473 19         1059 $new_data{$key} = $self->_value_from_action_proto($dom, $data, $map{$key});
474             }
475 9         1107 $data = Template::Pure::DataContext->new(\%new_data);
476             }
477 117         374 return ($data, @directives);
478             }
479              
480             sub _process_sub_data {
481 24     24   44 my ($self, $dom, $data, $match_spec, %action) = @_;
482              
483             # I don't know what it means to match repeat on attribes or append/prepent
484             # right now, so just doing match on the CSS and welcome specifications for
485             # this behavior.
486              
487 24         36 my $css = $match_spec->{css};
488            
489             # Pull out any sort or filters
490 24 100       56 my $sort_cb = exists $action{order_by} ? delete $action{order_by} : undef;
491 24 100       47 my $grep_cb = exists $action{grep} ? delete $action{grep} : undef;
492 24 50       46 my $filter_cb = exists $action{filter} ? delete $action{filter} : undef;
493 24 50       40 my $display_fields = exists $action{display_fields} ? delete $action{display_fields} : undef;
494 24 100       40 my $following_directives = exists $action{directives} ? delete $action{directives} : undef;
495 24         321 my ($sub_data_proto, $sub_data_action) = %action;
496              
497 24 100       71 if(index($sub_data_proto,'<-') > 0) {
498              
499 17 100       46 if(ref \$sub_data_action eq 'SCALAR') {
500 5         5 my $new_match_spec = '.';
501 5 100       37 $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append';
502 5 50       11 $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend';
503 5 50       48 $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter';
504 5 50       9 $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node';
505 5         12 $sub_data_action = [ $new_match_spec => $sub_data_action ];
506             }
507              
508 17 100       44 if(ref $sub_data_action eq 'CODE') {
509 2         2 my $new_match_spec = '.';
510 2 50       5 $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append';
511 2 100       4 $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend';
512 2 50       4 $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter';
513 2 50       4 $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node';
514 2         4 $sub_data_action = [ $new_match_spec => $sub_data_action ];
515             }
516              
517 17 100       56 if(Scalar::Util::blessed($sub_data_action)) {
518 2         3 my $new_match_spec = '.';
519 2 50       5 $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append';
520 2 50       6 $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend';
521 2 50       4 $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter';
522 2 100       5 $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node';
523 2         5 $sub_data_action = [ $new_match_spec => $sub_data_action ];
524             }
525              
526 17 50       40 die "Action for '$sub_data_proto' must be an arrayref of new directives"
527             unless ref $sub_data_action eq 'ARRAY';
528              
529 17         38 my ($new_key, $itr_data_spec) = $self->parse_itr_spec($sub_data_proto);
530 17         64 my $itr_data_proto = $self->_value_from_data($data, %$itr_data_spec);
531              
532             ## For now if the found value is undef, we second it along ti be trimmed
533             ## this behavior might be tweaked as examples of usage arise, also for now
534             ## we just pass through an empty iterator instead of considering it undef
535             ## ie [] is not considered like undef for now...
536              
537 17 100       39 return $self->_process_match_spec($dom, $itr_data_proto, %$match_spec)
538             if $self->_value_is_undef($itr_data_proto);
539              
540 16         15 my %options;
541 16 50       41 if($display_fields) {
542 0         0 $options{display_fields} = $display_fields;
543             }
544              
545 16 100       26 if($sort_cb) {
546 2 50       8 if(ref(\$sort_cb) eq 'SCALAR') {
547 0         0 my %sub_data_spec = $self->parse_data_spec($sort_cb);
548 0         0 my $value = $self->_value_from_data($data, %sub_data_spec);
549 0         0 $sort_cb = $value;
550             }
551 2 50       4 die "the 'sort' key must point to an anonymous subroutine" unless ref($sort_cb) eq 'CODE';
552 2         5 $options{sort} = $sort_cb;
553             }
554 16 100       30 if($grep_cb) {
555 1 50       3 if(ref(\$grep_cb) eq 'SCALAR') {
556 1         2 my %sub_data_spec = $self->parse_data_spec($grep_cb);
557 1         3 my $value = $self->_value_from_data($data, %sub_data_spec);
558 1         2 $grep_cb = $value;
559             }
560 1 50       4 die "the 'grep' key must point to an anonymous subroutine" unless ref($grep_cb) eq 'CODE';
561 1         1 $options{grep} = $grep_cb;
562             }
563 16 50       32 if($filter_cb) {
564 0 0       0 if(ref(\$filter_cb) eq 'SCALAR') {
565 0         0 my %sub_data_spec = $self->parse_data_spec($filter_cb);
566 0         0 my $value = $self->_value_from_data($data, %sub_data_spec);
567 0         0 $filter_cb = $value;
568             }
569 0 0       0 die "the 'sort' key must point to an anonymous subroutine" unless ref($filter_cb) eq 'CODE';
570 0         0 $options{filter} = $filter_cb;
571             }
572              
573 16 50       34 $options{display_fields} = $display_fields if $display_fields;
574              
575 16         81 my $iterator = Template::Pure::Iterator->from_proto($itr_data_proto, $self, \%options);
576            
577 16 50       42 if($css eq '.') {
578 0         0 $self->_process_iterator($dom, $new_key, $iterator, @{$sub_data_action});
  0         0  
579             } else {
580 16         35 my $collection = $self->find_or_die($dom,$css);
581             $collection->each(sub {
582 17     17   185 $self->_process_iterator($_, $new_key, $iterator, @{$sub_data_action});
  17         60  
583 16         78 });
584             }
585             } else {
586 7         15 my %sub_data_spec = $self->parse_data_spec($sub_data_proto);
587 7         24 my $value = $self->_value_from_data($data, %sub_data_spec);
588              
589             ## If the value is undefined, we dont' continue... should we remove all this...?
590 7 100 33     40 if(ref $sub_data_action eq 'ARRAY') {
    50          
591 4         4 $self->process_sub_directives($dom, $value, $css, @{$sub_data_action});
  4         10  
592             } elsif(Scalar::Util::blessed($sub_data_action) && $sub_data_action->isa(ref $self)) {
593 3         6 my $new_data = Template::Pure::DataContext->new($value);
594 3         12 my $new_value = $self->_value_from_template_obj($dom, $new_data, $sub_data_action, %$match_spec);
595 3         27 $self->_process_match_spec($dom, $new_value, %$match_spec);
596             } else {
597 0         0 die "Don't know how to process $value on $css for $sub_data_action";
598             }
599             }
600              
601             ## Todo... not sure if this is right or useful
602 23 100       4650 if($following_directives) {
603 1         2 $self->process_sub_directives($dom, $data, $css, @{$following_directives});
  1         2  
604             }
605             }
606              
607             sub _process_iterator {
608 17     17   35 my ($self, $dom, $key, $iterator, @actions) = @_;
609 17         1809 my $template = dclone($dom);
610 17         61 while(my $datum = $iterator->next) {
611 41         45 $datum = $$datum;
612 41         70 my $new_dom = Mojo::DOM58->new($template);
613 41         8391 my $new_data;
614 41 100       75 if($key eq '.') {
615 2         7 $new_data = Template::Pure::DataProxy
616             ->new($datum, i=>$iterator);
617             } else {
618 39         74 $new_data = +{
619             $key => $datum,
620             i => $iterator,
621             };
622             }
623 41         87 $self->_process_dom_recursive($new_data, $new_dom->descendant_nodes->[0], @actions);
624 41         178 $dom->replace($new_dom);
625             }
626 17         45 $dom->remove;
627             }
628              
629             sub process_sub_directives {
630 22     22 0 46 my ($self, $dom, $data, $css, @directives) = @_;
631 22 100       44 if($css eq '.') {
632 2         4 $self->_process_dom_recursive($data, $dom, @directives);
633             } else {
634 20         39 my $collection = $self->find_or_die($dom,$css);
635             $collection->each(sub {
636 20     20   156 $self->_process_dom_recursive($data, $_, @directives);
637 20         106 });
638             }
639             }
640              
641             sub _value_from_dom {
642 28     28   27 my $self = shift;
643 28         31 my $dom = shift;
644 28 100       62 my %match_spec = ref $_[0] ? %{$_[0]} : $self->parse_match_spec($_[0]);
  15         43  
645              
646 28 100       71 $dom = $dom->root if $match_spec{absolute};
647              
648             ## TODO, perhaps this could do a find instead of at and return
649             ## a collection, which populates an iterator if requested?
650              
651 28 100       202 if($match_spec{target} eq 'content') {
    100          
    50          
652             #return $self->encoded_string($self->at_or_die($dom, $match_spec{css})->content);
653             # Not sure if there is a more effecient way to make this happen...
654 10         41 return Mojo::DOM58->new($self->at_or_die($dom, $match_spec{css})->content);
655             } elsif($match_spec{target} eq 'node') {
656             ## When we want a full node, with HTML tags, we encode the string
657             ## since I presume they want a copy not escaped. T 'think' this is
658             ## the commonly desired thing and you can always apply and escape_html filter
659             ## yourself when you don't want it.
660             #return $self->encoded_string($self->at_or_die($dom, $match_spec{css})->to_string);
661 17         36 return $self->at_or_die($dom, $match_spec{css});
662 1         6 } elsif(my $attr = ${$match_spec{target}}) {
663             ## TODO not sure what if any encoding we need here.
664 1         4 return $self->at_or_die($dom, $match_spec{css})->attr($attr);
665             }
666             }
667              
668             sub _value_from_data {
669 207     207   352 my ($self, $data, %data_spec) = @_;
670 207         559 my $value = $data->at(%data_spec)->value;
671 207         450 foreach my $filter (@{$data_spec{filters}}) {
  207         337  
672 13         28 $value = $self->_apply_data_filter($value, $data, $filter);
673             }
674 207         655 return $value;
675             }
676              
677             sub _apply_data_filter {
678 16     16   36 my ($self, $value, $data, $filter) = @_;
679 16         29 my ($name, @args) = @$filter;
680 16 100       23 @args = map { ref $_ ? $self->_value_from_data($data, %$_) : $_ } @args;
  10         29  
681 16         38 return $self->filter($name, $value, @args);
682             }
683              
684             sub _process_dom_filter {
685 1     1   2 my ($self, $dom, $data, $css, $cb) = @_;
686 1 50       3 if($css eq '.') {
687 0         0 $cb->($self, $dom, $data);
688             } else {
689 1         3 my $collection = $self->find_or_die($dom,$css);
690             $collection->each(sub {
691 1     1   8 $cb->($self, $_, $data);
692 1         5 });
693             }
694             }
695              
696             sub _process_match_spec {
697 180     180   366 my ($self, $dom, $value, %match_spec) = @_;
698 180 100       267 if($match_spec{css} eq '.') {
699 49         112 $self->_process_mode($dom, $value, %match_spec);
700             } else {
701 131         230 my $collection = $self->find_or_die($dom,$match_spec{css});
702             $collection->each(sub {
703 132     132   1260 $self->_process_mode($_, $value, %match_spec);
704 130         573 });
705             }
706             }
707              
708             sub _value_is_undef {
709 347     347   333 my ($self, $value) = @_;
710 347 100       503 return 1 if !defined($value);
711 335 100 100     989 return 1 if Scalar::Util::blessed($value) && $value->isa('Template::Pure::UndefObject');
712 334         814 return 0;
713             }
714              
715              
716             sub _process_mode {
717 201     201   446 my ($self, $dom, $value, %match_spec) = @_;
718              
719 201         228 my $mode = $match_spec{mode};
720 201         166 my $target = $match_spec{target};
721 201         358 my $safe_value = $self->escape_html($value);
722              
723             ## This behavior may be tweaked in the future.
724 201 100       343 if($self->_value_is_undef($safe_value)) {
725 12 50       35 if($target eq 'node') {
    100          
    50          
726 0         0 return $dom->remove;
727             } elsif($target eq 'content') {
728 9 100 100     29 if( ($mode eq 'append') or ($mode eq 'prepend')) {
729             # Don't remove anything since there's not a target mode here
730             # just stuff we wanted to add to the start or end.
731 4         13 return;
732             } else {
733 5         16 return $dom->remove; # TODO, or should this remove just the content..?
734             }
735             } elsif(my $attr = $$target) {
736 3         9 return delete $dom->attr->{$attr};
737             }
738             }
739              
740 189 100       331 if($mode eq 'replace') {
    100          
    50          
741 161 100       276 if($target eq 'content') {
    100          
    50          
742 129 50       179 $dom->content($safe_value) unless $self->_value_is_undef($safe_value);
743             } elsif($target eq 'node') {
744 21         55 $dom->replace($safe_value);
745             } elsif(my $attr = $$target) {
746 11         29 $dom->attr($attr=>$safe_value);
747             } else {
748 0         0 die "Don't understand target of $target";
749             }
750             } elsif($mode eq 'append') {
751 23 100       54 if($target eq 'content') {
    100          
    50          
752 13         33 $dom->append_content($safe_value);
753             } elsif($target eq 'node') {
754 5         55 $dom->append($safe_value);
755             } elsif(my $attr = $$target) {
756 5   100     11 my $current_attr = $dom->attr($attr)||'';
757 5         68 $dom->attr($attr=>"$current_attr$safe_value" );
758             } else {
759 0         0 die "Don't understand target of $target";
760             }
761             } elsif($mode eq 'prepend') {
762 5 100       12 if($target eq 'content') {
    100          
    50          
763 3         9 $dom->prepend_content($safe_value);
764             } elsif($target eq 'node') {
765 1         4 $dom->prepend($safe_value);
766             } elsif(my $attr = $$target) {
767 1   50     4 my $current_attr = $dom->attr($attr)||'';
768 1         15 $dom->attr($attr=> "$safe_value$current_attr" );
769             } else {
770 0         0 die "Don't understand target of $target";
771             }
772             } else {
773 0         0 die "Not sure how to handle mode '$mode'";
774             }
775             }
776              
777             sub filter {
778 16     16 1 25 my ($self, $name, $data, @args) = @_;
779             my %filters = (
780             $self->default_filters,
781 16         29 %{$self->{filters}}
  16         135  
782             );
783            
784 16   50     63 my $filter = $filters{$name} ||
785             die "Filter $name does not exist";
786            
787 16         44 return $filter->($self, $data, @args);
788             }
789              
790             1;
791              
792             =head1 NAME
793              
794             Template::Pure - Perlish Port of pure.js and more
795              
796             =head1 SYNOPSIS
797              
798             use Template::Pure;
799              
800             my $html = q[
801             <html>
802             <head>
803             <title>Page Title</title>
804             </head>
805             <body>
806             <section id="article">
807             <h1>Header</h1>
808             <div>Story</div>
809             </section>
810             <ul id="friendlist">
811             <li>Friends</li>
812             </ul>
813             </body>
814             </html>
815             ];
816              
817             my $pure = Template::Pure->new(
818             template=>$html,
819             directives=> [
820             'head title' => 'meta.title',
821             '#article' => [
822             'h1' => 'header',
823             'div' => 'content',
824             ],
825             'ul li' => {
826             'friend<-user.friends' => [
827             '.' => '={friend}, #={i.index}',
828             ],
829             },
830             ],
831             );
832              
833             my $data = +{
834             meta => {
835             title => 'Travel Poetry',
836             created_on => '1/1/2000',
837             },
838             header => 'Fire',
839             content => q[
840             Are you doomed to discover that you never recovered from the narcoleptic
841             country in which you once stood? Where the fire's always burning, but
842             there's never enough wood?
843             ],
844             user => {
845             name => 'jnap',
846             friends => [qw/jack jane joe/],
847             },
848             };
849              
850             print $pure->render($data);
851              
852             Results in:
853              
854             <html>
855             <head>
856             <title>Travel Poetry</title>
857             </head>
858             <body>
859             <section id="article">
860             <h1>Fire</h1>
861             <div>
862             Are you doomed to discover that you never recovered from the narcoleptic
863             country in which you once stood? Where the fire&#39;s always burning, but
864             there&#39;s never enough wood?
865             </div>
866             </section>
867             <ul id="friendlist">
868             <li>jack, #1</li>
869             <li>jane, #2</li>
870             <li>joe, #3</li>
871             </ul>
872             </body>
873             </html>
874              
875             =head1 DESCRIPTION
876              
877             B<NOTE> WARNING: Early access module. Although we have a lot of test cases and this is the
878             third redo of the code I've not well tested certain features (such as using an object as
879             a data context) and other parts such as the way we handle undefined values (or empty
880             iterators) are still 'first draft'. Code currently is entirely unoptimized. Additionally the
881             documenation could use another detailed review, and we'd benefit from some 'cookbook' style docs.
882             Nevertheless its all working well enough that I'd like to publish it so I can start using it
883             more widely and hopefully some of you will like what you see and be inspired to try and help
884             close the gaps.
885              
886             B<NOTE> UPDATE (version 0.015): The code is starting to shape up and at this point I'm started to commit to
887             things that pass the current test case should still pass in the future unless breaking changes
888             are absolutely required to move the project forward. Main things to be worked out is if the
889             rules around handling undef values and when we have an object as the loop iterator has not
890             been as well tested as it should be.
891              
892             B<NOTE> UPDATE (version 0.023): Error messaging is tremendously improved and a number of edge case
893             issues have worked out while working on the Catalyst View adaptor (not on CPAN at the time of this
894             writing). Main blockers before I can consider this stable include lots of performance tuning,
895             completion of a working Catalyst view adaptor, and refactoring of the way we use the L<Mojo::DOM58>
896             parser so that parsers are plugable. I also need to refactor how processing instructions are
897             handled so that its not a pile of inlined code (ideally you should be able to write your own
898             processing instructions). I feel commited to the existing test suite and documented
899             API.
900              
901             L<Template::Pure> HTML/XML Templating system, inspired by pure.js L<http://beebole.com/pure/>, with
902             some additions and modifications to make it more Perlish and to be more suitable
903             as a server side templating framework for larger scale needs instead of single page
904             web applications.
905              
906             The core concept is you have your templates in pure HTML and create CSS style
907             matches to run transforms on the HTML to populate data into the template. This allows you
908             to have very clean, truely logicless templates. This approach can be useful when the HTML designers
909             know little more than HTML and related technologies. It helps promote separation of concerns
910             between your UI developers and your server side developers. Over the long term the separate
911             and possibilities for code reuse can lead to an easier to maintain system.
912              
913             The main downside is that it can place more work on the server side developers, who have to
914             write the directives unless your UI developers are able and willing to learn the minimal Perl
915             required for that job. Also since the CSS matching directives can be based on the document
916             structure, it can lead to onerous tight binding between yout document structure and the layout/display
917             logic. For example due to some limitations in the DOM parser, you might have to add some extra markup
918             just so you have a place to match, when you have complex and deeply nested data.
919              
920             Additionally many UI designers already are familiar with some basic templating systems and
921             might really prefer to use that so that they can maintain more autonomy and avoid the additional
922             learning curve that L<Template::Pure> will requires (most people seem to find its a bit more
923             effort to learn off the top compared to more simple systems like Mustache or even L<Template::Toolkit>.
924              
925             Although inspired by pure.js L<http://beebole.com/pure/> this module attempts to help mitigate some
926             of the listed possible downsides with additional features that are a superset of the original
927             pure.js specification. For example you may include templates inside of templates as includes or even
928             overlays that provide much of the same benefit that template inheritance offers in many other
929             popular template frameworks. These additional features are intended to make it more suitable as a general
930             purpose server side templating system.
931              
932             =head1 CREATING TEMPLATE OBJECTS
933              
934             The first step is to create a L<Template::Pure> object:
935              
936             my $pure = Template::Pure->new(
937             template=>$html,
938             directives=> \@directives);
939              
940             L<Template::Pure> has two required parameters:
941              
942             =over 4
943              
944             =item template
945              
946             This is a string that is an HTML template that can be parsed by L<Mojo::DOM58>
947              
948             =item directives
949              
950             An arrayref of directives, which are commands used to transform the template when
951             rendering against data. For more on directives, see L</DIRECTIVES>
952              
953             =back
954              
955             L<Template::Pure> has a third optional parameter, 'filters', which is a hashref of
956             user created filters. For more see L<Template::Pure::Filters> and L</FILTERS>.
957              
958             Once you have a created object, you may call the following methods:
959              
960             =over 4
961              
962             =item render ($data, ?\@extra_directives?)
963              
964             Render a template with the given '$data', which may be a hashref or an object with
965             fields that match data paths defined in the directions section (see L</DIRECTIVES>)
966              
967             Returns a string. You may pass in an arrayref of extra directives, which are executed
968             just like directives defined at instantiation time (although future versions of this
969             distribution may offer optimizations to directives known at create time). These optional
970             added directives are executed after the directives defined at create time.
971              
972             Since we often traverse the $data structure as part of rendering a template, we usually call
973             the current path the 'data context'. We always track the base or root context and you can
974             always return to it, as you will later see in the L</DIRECTIVES> section.
975              
976             =item process_dom ($data, ?\@extra_directives?)
977              
978             Works just like 'render', except we return a L<Mojo::DOM58> object instead of a string directly.
979             Useful if you wish to retrieve the L<Mojo::DOM58> object for advanced, custom tranformations.
980              
981             =item data_at_path ($data, $path)
982              
983             Given a $data object, returns the value at the defined $path. Useful in your coderef actions
984             (see below) when you wish to grab data from the current data context but wish to avoid
985             using $data implimentation specific lookup.
986              
987             =item escape_html ($string)
988              
989             Given a string, returns a version of it that has been properly HTML escaped. Since we do
990             such escaping automatically for most directives you won't need it a lot, but could be useful
991             in a coderef action. Can also be called as a filter (see L</FILTERS>).
992              
993             =item encoded_string ($string)
994              
995             As mentioned we automatically escape values to help protect you against HTML injection style
996             attacked, but there might be cases when you don't wish this protection. Can also be called
997             as a filter (see L</FILTERS>).
998              
999             =back
1000              
1001             There are other methods in the code but please consider all that stuff part of my 'black box'
1002             and only reach into it if you are willing to suffer possible breakage on version changes.
1003              
1004             =head1 DIRECTIVES
1005              
1006             Directives are instructions you prepare against a template, upon which later we render
1007             data against. Directives are ordered and are excuted in the order defined. The general
1008             form of a directive is C<CSS Match> => C<Action>, where action can be a path to fetch data
1009             from, more directives, a coderef, etc. The main idea is that the CSS matches
1010             a node in the HTML template, and an 'action' is performed on that node. The following actions are allowed
1011             against a match specification:
1012              
1013             =head2 Scalar - Replace the value indicated by the match.
1014              
1015             my $html = qq[
1016             <div>
1017             Hello <span id='name'>John Doe</span>!
1018             </div>
1019             ];
1020              
1021             my $pure = Template::Pure->new(
1022             template => $html,
1023             directives => [
1024             '#name' => 'fullname',
1025             ]);
1026              
1027             my %data = (
1028             fullname => 'H.P Lovecraft');
1029              
1030             print $pure->render(\%data);
1031              
1032             Results in:
1033              
1034             <div>
1035             Hello <span id='name'>H.P Lovecraft</span>!
1036             </div>
1037              
1038             In this simple case the value of the CSS match '#name' is replaced by the value 'fullname'
1039             indicated at the current data context (as you can see the starting context is always the
1040             root, or top level data object.)
1041              
1042             If instead of a hashref the rendered data context is an object, we look for a method
1043             matching the name of the indicated path. If there is no matching method or key, we generate
1044             an exception.
1045              
1046             If there is a key matching the requested data path as indicated by the directive, but the associated
1047             value is undef, then the matching node (tag included) is removed. If there is no matching key,
1048             this raises an error.
1049              
1050             B<NOTE>: Remember that you can use dot notation in your action value to indicate a path on the
1051             current data context, for example:
1052              
1053             my %data = (
1054             identity => {
1055             first_name => 'Howard',
1056             last_name => 'Lovecraft',
1057             });
1058              
1059             my $pure = Template::Pure->new(
1060             template => $html,
1061             directives => [ '#last_name' => 'identity.last_name']
1062             );
1063              
1064             In this case the value of the node indicated by '#last_name' will be set to 'Lovecraft'.
1065              
1066             B<NOTE>: If your scalar action returns a L<Template::Pure> object, it will render as if
1067             it was an object action as described below L</Object - Set the match value to another Pure Template>.
1068              
1069             For example:
1070              
1071             my $wrapper_html = qq[
1072             <section>Example Wrapped Stuff</section>];
1073              
1074             my $wrapper = Template::Pure->new(
1075             template=>$wrapper_html,
1076             directives=> [
1077             'section' => 'content',
1078             ]);
1079              
1080             my $template => qq[
1081             <html>
1082             <head>
1083             <title>Title Goes Here!</title>
1084             </head>
1085             <body>
1086             <p>Hi Di Ho!</p>
1087             </body>
1088             </html>
1089             ];
1090              
1091             my @directives = (
1092             title => 'title | upper',
1093             body => 'info',
1094             );
1095              
1096             my $pure = Template::Pure->new(
1097             template => $template,
1098             directives => \@directives);
1099              
1100             my $data = +{
1101             title => 'Scalar objects',
1102             info => $wrapper,
1103             };
1104              
1105             ok my $string = $pure->render($data);
1106              
1107             Results in:
1108              
1109             <html>
1110             <head>
1111             <title>SCALAR OBJECTS</title>
1112             </head>
1113             <body>
1114             <section>
1115             <p>Hi Di Ho!</p>
1116             </section></body>
1117             </html>
1118              
1119             This feature is currently only active for scalar actions but may be extended to other action
1120             types in the future.
1121              
1122             B<NOTE> If your scalar action returns a coderefence, we process it as if the
1123             scalar action was itself a code reference. See L<\'Coderef - Programmatically replace the value indicated'>.
1124              
1125             =head2 ScalarRef - Set the value to the results of a match
1126              
1127             There may be times when you want to set the value of something to an existing
1128             value in the current template:
1129              
1130             my $html = qq[
1131             <html>
1132             <head>
1133             <title>Welcome Page</title>
1134             </head>
1135             <body>
1136             <h1>Page Title</h1>
1137             </body>
1138             </html>
1139             ];
1140              
1141             my $pure = Template::Pure->new(
1142             template => $html,
1143             directives => [
1144             'h1#title' => \'/title',
1145             ]);
1146              
1147             print $pure->render({});
1148              
1149             Results in:
1150              
1151             <html>
1152             <head>
1153             <title>Welcome Page</title>
1154             </head>
1155             <body>
1156             <h1>Welcome Page</h1>
1157             </body>
1158             </html>
1159              
1160             B<NOTE> Since directives are processed in order, this means that you can
1161             reference the rendered value of a previous directive via this alias.
1162              
1163             B<NOTE> The match runs against the current selected node, as defined by the last
1164             successful match. If you need to match a value from the root of the DOM tree you
1165             can use the special '/' syntax on your CSS match, as shown in the above example,
1166             or:
1167              
1168             directives => [
1169             'h1#title' => \'/title',
1170             ]);
1171              
1172              
1173             =head2 Coderef - Programmatically replace the value indicated
1174              
1175             my $html = qq[
1176             <div>
1177             Hello <span id='name'>John Doe</span>!
1178             </div>
1179             ];
1180              
1181             my $pure = Template::Pure->new(
1182             template => $html,
1183             directives => [
1184             '#name' => sub {
1185             my ($instance, $dom, $data) = @_;
1186             return $instance->data_at_path($data, 'id.first_name') .' '.
1187             $instance->data_at_path($data, 'id.last_name') ;
1188             },
1189             ]
1190             );
1191              
1192             my %data = (
1193             id => {
1194             first_name => 'Howard',
1195             last_name => 'Lovecraft',
1196             });
1197              
1198             print $pure->render(\%data);
1199              
1200              
1201             Results in:
1202              
1203             <div>
1204             Hello <span id='name'>Howard Lovecraft</span>!
1205             </div>
1206              
1207             For cases where the display logic is complex, you may use an anonymous subroutine to
1208             provide the matched value. This anonymous subroutine receives the following three
1209             arguments:
1210              
1211             $instance: The template instance
1212             $dom: The DOM Node at the current match (as a L<Mojo::DOM58> object).
1213             $data: Data reference at the current context.
1214              
1215             Your just need to return the value desired which will substitute for the matched node's
1216             current value.
1217              
1218             B<NOTE>: Please note in the above example code that we used 'data_at_path' rather than
1219             dereferenced the $data scalar directly. This is required since internally we wrap your
1220             $data in helper objects, so you can't be 100% certain of the actual structure. In general
1221             using this method wouldbe a good idea anyway since it lets you achieve an API that is
1222             complete independent of your actual data structure (this way if you later change from a
1223             simple hashref to an object, your code wouldn't break.
1224              
1225             =head3 Coderef - No match specification
1226              
1227             Sometimes you may wish to have highly customized transformations, ones that are
1228             not directly attached to a match specification. In those cases you may pass a
1229             match specification without a CSS match:
1230              
1231             my $html = q[
1232             <html>
1233             <head>
1234             <title>Page Title</title>
1235             </head>
1236             <body>
1237             <p>foo</p>
1238             <p>baz</p>
1239             <div id="111"></div>
1240             </body>
1241             </html>
1242             ];
1243              
1244             my $pure = Template::Pure->new(
1245             template=>$html,
1246             directives=> [
1247             sub {
1248             my ($template, $dom, $data) = @_;
1249             $dom->at('#111')->content("coderef");
1250             },
1251             'p' => sub {
1252             my ($template, $dom, $data) = @_;
1253             return $template->data_at_path($data, $dom->content)
1254             }
1255             ]);
1256              
1257             my $data = +{
1258             foo => 'foo is you',
1259             baz => 'baz is raz',
1260             };
1261              
1262             Renders as:
1263              
1264             <html>
1265             <head>
1266             <title>Page Title</title>
1267             </head>
1268             <body>
1269             <p>foo is you</p>
1270             <p>baz is raz</p>
1271             <div id="111">coderef</div>
1272             </body>
1273             </html>
1274              
1275             =head2 Arrayref - Run directives under a new DOM root
1276              
1277             Somtimes its handy to group a set of directives under a given node. For example:
1278              
1279             my $html = qq[
1280             <dl id='contact'>
1281             <dt>Phone</dt>
1282             <dd class='phone'>(xxx) xxx-xxxx</dd>
1283             <dt>Email</dt>
1284             <dd class='email'>aaa@email.com</dd>
1285             </dl>
1286             ];
1287              
1288             my $pure = Template::Pure->new(
1289             template => $html,
1290             directives => [
1291             '#contact' => [
1292             '.phone' => 'contact.phone',
1293             '.email' => 'contact.email',
1294             ],
1295             );
1296              
1297             my %data = (
1298             contact => {
1299             phone => '(212) 387-9509',
1300             email => 'jjnapiork@cpan.org',
1301             }
1302             );
1303              
1304             print $pure->render(\%data);
1305              
1306             Results in:
1307              
1308             <dl id='contact'>
1309             <dt>Phone</dt>
1310             <dd class='phone'>(212) 387-9509</dd>
1311             <dt>Email</dt>
1312             <dd class='email'>jjnapiork@cpan.org'</dd>
1313             </dl>
1314              
1315             For this simple case you could have made it more simple and avoided the nested directives, but
1316             in a complex template with a lot of organization you might find this leads to more readable and
1317             concise directives. It can also promote reusability.
1318              
1319             =head2 Hashref - Move the root of the Data Context
1320              
1321             Just like it may be valuable to move the root DOM context to an inner node, sometimes you'd
1322             like to move the root of the current Data context to an inner path point. This can result in cleaner
1323             templates with less repeated syntax, as well as promote reusability. In order to do this you
1324             use a Hashref whose key is the path under the data context you wish to move to and who's value
1325             is an Arrayref of new directives. These new directives can be any type of directive as already
1326             shown or later documented.
1327              
1328             my $html = qq[
1329             <dl id='contact'>
1330             <dt>Phone</dt>
1331             <dd class='phone'>(xxx) xxx-xxxx</dd>
1332             <dt>Email</dt>
1333             <dd class='email'>aaa@email.com</dd>
1334             </dl>
1335             ];
1336              
1337             my $pure = Template::Pure->new(
1338             template => $html,
1339             directives => [
1340             '#contact' => {
1341             'contact' => [
1342             '.phone' => 'phone',
1343             '.email' => 'email',
1344             ],
1345             },
1346             ]
1347             );
1348              
1349             my %data = (
1350             contact => {
1351             phone => '(212) 387-9509',
1352             email => 'jjnapiork@cpan.org',
1353             }
1354             );
1355              
1356             print $pure->render(\%data);
1357              
1358             Results in:
1359              
1360             <dl id='contact'>
1361             <dt>Phone</dt>
1362             <dd class='phone'>(212) 387-9509</dd>
1363             <dt>Email</dt>
1364             <dd class='email'>jjnapiork@cpan.org'</dd>
1365             </dl>
1366              
1367             In addition to an arrayref of new directives, you may assign the new DOM and Data context
1368             directly to a template object (see L</Object - Set the match value to another Pure Template>
1369             For example:
1370              
1371             my $contact_include = Template::Pure->new(
1372             template => q[
1373             <dl>
1374             <dt>Name</dt>
1375             <dd class='name'>First Last</dd>
1376             <dt>Email</dt>
1377             <dd class='email'>Email@email.com</dd>
1378             </dl>
1379             ],
1380             directives => [
1381             '.name' => 'fullname',
1382             '.email' => 'email',
1383              
1384              
1385             my $pure = Template::Pure->new(
1386             template => $html,
1387             directives => [
1388             '#contact' => {
1389             'contact' => $contact_include;
1390             },
1391             ]
1392             );
1393              
1394             print $pure->render({
1395             person => {
1396             contact => {
1397             fullname => 'John Doe',
1398             email => 'jd@email.com'.
1399             }
1400             },
1401             });
1402              
1403             This lets you isolate the data structure of your includes to improve reuse and clarity.
1404              
1405             =head2 Hashref - Create a Loop
1406              
1407             Besides moving the current data context, setting the value of a match spec key to a
1408             hashref can be used to perform loops over a node, such as when you wish to create
1409             a list:
1410              
1411             my $html = qq[
1412             <ol>
1413             <li class='name'>
1414             <span class='first-name'>John</span>
1415             <span class='last-name'>Doe</span>
1416             </li>
1417             </ol>
1418             ];
1419              
1420             my $pure = Template::Pure->new(
1421             template => $html,
1422             directives => [
1423             '#name' => {
1424             'name<-names' => [
1425             '.first-name' => 'name.first',
1426             '.last-name' => 'name.last',
1427             ],
1428             },
1429             ]
1430             );
1431              
1432             my %data = (
1433             names => [
1434             {first => 'Mary', last => 'Jane'},
1435             {first => 'Jared', last => 'Prex'},
1436             {first => 'Lisa', last => 'Dig'},
1437             ]
1438             );
1439              
1440             print $pure->render(\%data);
1441              
1442             Results in:
1443              
1444             <ol id='names'>
1445             <li class='name'>
1446             <span class='first-name'>Mary</span>
1447             <span class='last-name'>Jane</span>
1448             </li>
1449             <li class='name'>
1450             <span class='first-name'>Jared</span>
1451             <span class='last-name'>Prex</span>
1452             </li>
1453             <li class='name'>
1454             <span class='first-name'>Lisa</span>
1455             <span class='last-name'>Dig</span>
1456             </li>
1457             </ol>
1458              
1459             The indicated data path must be either an ArrayRef, a Hashref, or an object that provides
1460             an iterator interface (see below).
1461              
1462             For each item in the array we render the selected node against that data and
1463             add it to parent node. So the originally selected node is completely replaced by a
1464             collection on new nodes based on the data. Basically just think you are repeating over the
1465             node value for as many times as there is items of data.
1466              
1467             In the case the referenced data is explicitly set to undefined, the full node is
1468             removed (the matched node, not just the value).
1469              
1470             =head3 Special value injected into a loop
1471              
1472             When you create a loop we automatically add a special data key called 'i' which is an object
1473             that contains meta data on the current state of the loop. Fields that can be referenced are:
1474              
1475             =over 4
1476              
1477             =item current_value
1478              
1479             An alias to the current value of the iterator.
1480              
1481             =item index
1482              
1483             The current index of the iterator (starting from 1.. or from the first key in a hashref or fields
1484             interator).
1485              
1486             =item max_index
1487              
1488             The last index item, either number or field based.
1489              
1490             =item count
1491              
1492             The total number of items in the iterator (as a number, starting from 1).
1493              
1494             =item is_first
1495              
1496             Is this the first item in the loop?
1497              
1498             =item is_last
1499              
1500             Is this the last item in the loop?
1501              
1502             =item is_even
1503              
1504             Is this item 'even' in regards to its position (starting with position 2 (the first position, or also
1505             known as index '1') being even).
1506              
1507             =item is_odd
1508              
1509             Is this item 'even' in regards to its position (starting with position 1 (the first position, or also
1510             known as index '0') being odd).
1511              
1512             =back
1513              
1514             =head3 Looping over a Hashref
1515              
1516             You may loop over a hashref as in the following example:
1517              
1518             my $html = qq[
1519             <dl id='dlist'>
1520             <section>
1521             <dt>property</dt>
1522             <dd>value</dd>
1523             </section>
1524             </dl>];
1525              
1526             my $pure = Template::Pure->new(
1527             template => $html,
1528             directives => [
1529             'dl#dlist section' => {
1530             'property<-author' => [
1531             'dt' => 'i.index',
1532             'dd' => 'property',
1533             ],
1534             },
1535             ]
1536             );
1537              
1538             my %data = (
1539             author => {
1540             first_name => 'John',
1541             last_name => 'Napiorkowski',
1542             email => 'jjn1056@yahoo.com',
1543             },
1544             );
1545              
1546             print $pure->render(\%data);
1547              
1548             Results in:
1549              
1550             <dl id="dlist">
1551             <section>
1552             <dt>first_name</dt>
1553             <dd>John</dd>
1554             </section>
1555             <section>
1556             <dt>last_name</dt>
1557             <dd>Napiorkowski</dd>
1558             </section>
1559             <section>
1560             <dt>email</dt>
1561             <dd>jjn1056@yahoo.com</dd>
1562             </section>
1563             </dl>
1564              
1565             B<NOTE> This is a good example of a current limitation in the CSS Match Specification that
1566             requires adding a 'section' tag as a fudge to give the look something to target. Future
1567             versions of this distribution may offer additional match syntax to get around this problem.
1568              
1569             B<NOTE> Notice the usage of the special data path 'i.index' which for a hashref or fields
1570             type loop contains the field or hashref key name.
1571              
1572             B<NOTE> Please remember that in Perl Hashrefs are not ordered. If you wish to order your
1573             Hashref based loop please see L</Sorting and filtering a Loop> below.
1574              
1575             =head3 Iterating over an Object
1576              
1577             If the value indicated by the required path is an object, we need that object to provide
1578             an interface indicating if we should iterate like an ArrayRef (for example a L<DBIx::Class::ResultSet>
1579             which is a collection of database rows) or like a HashRef (for example a L<DBIx::Class>
1580             result object which is one row in the returned database query consisting of field keys
1581             and associated values).
1582              
1583             =head4 Objects that iterate like a Hashref
1584              
1585             The object should provide a method called 'display_fields' (which can be overridden with
1586             the key 'display_fields_handler', see below) which should return a list of methods that are used
1587             as 'keys' to provide values for the iterator. Each method return represents one item
1588             in the loop.
1589              
1590             =head4 Objects that iterate like an ArrayRef
1591              
1592             Your object should defined the follow methods:
1593              
1594             =over 4
1595              
1596             =item next
1597              
1598             Returns the next item in the iterator or undef if there are no more items
1599              
1600             =item count
1601              
1602             The number of items in the iterator (counting from 1 for one item)
1603              
1604             =item reset
1605              
1606             Reset the iterator to the starting item.
1607              
1608             =item all
1609              
1610             Returns all the items in the iterator
1611              
1612             =back
1613              
1614             =head3 Sorting a Loop
1615              
1616             You may provide a custom anonymous subroutine to provide a display
1617             specific order to your loop. For simple values such as Arrayrefs
1618             and hashrefs this is simple:
1619              
1620             my $html = qq[
1621             <ol id='names'>
1622             <li class='name'>
1623             <span class='first-name'>John</span>
1624             <span class='last-name'>Doe</span>
1625             </li>
1626             </ol>
1627             ];
1628              
1629             my $pure = Template::Pure->new(
1630             template => $html,
1631             directives => [
1632             '#name' => {
1633             'name<-names' => [
1634             '.first-name' => 'name.first',
1635             '.last-name' => 'name.last',
1636             ],
1637             'order_by' => sub {
1638             my ($pure, $hashref, $a, $b) = @_;
1639             return $a->{last} cmp $b->{last};
1640             },
1641             },
1642             ]
1643             );
1644              
1645             my %data = (
1646             names => [
1647             {first => 'Mary', last => 'Jane'},
1648             {first => 'Jared', last => 'Prex'},
1649             {first => 'Lisa', last => 'Dig'},
1650             ]
1651             );
1652              
1653             print $pure->render(\%data);
1654              
1655             Results in:
1656              
1657             <ol id='names'>
1658             <li class='name'>
1659             <span class='first-name'>Lisa</span>
1660             <span class='last-name'>Dig</span>
1661             </li>
1662             <li class='name'>
1663             <span class='first-name'>Mary</span>
1664             <span class='last-name'>Jane</span>
1665             </li>
1666             <li class='name'>
1667             <span class='first-name'>Jared</span>
1668             <span class='last-name'>Prex</span>
1669             </li>
1670             </ol>
1671              
1672             So you have a key 'order_by' at the same level as the loop action declaration
1673             which is an anonynous subroutine that takes four arguments; the $pure object,
1674             a reference to the data you are sorting (an arrayref or hashref)
1675             followed by the $a and $b items to be compared for example as in:
1676              
1677             my @display = sort { $a->{last} cmp $b->{last} } @list;
1678              
1679             If your iterator is over an object the interface is slightly more complex since
1680             we allow for the object to provide a sort method based on its internal needs.
1681             For example if you have a L<DBIx::Class::Resultset> as your iterator, you may
1682             wish to order your display at the database level:
1683              
1684             'order_by' => sub {
1685             my ($pure, $object) = @_;
1686             return $object->order_by_last_name;
1687             },
1688              
1689             We recommend avoiding implementation specific details when possible (for example
1690             in L<DBIx::Class> use a custom resultset method, not a ->search query.).
1691              
1692             B<NOTE:> if you need more dynamic control over the way sorting works, you can instead
1693             of hard coding an anonymous subroutine, instead use a string that is a path on the
1694             current data context to an subroutine reference.
1695              
1696             =head3 Perform a 'grep' on your loop items
1697              
1698             You may wish for the purposes of display to skip items in your loop. Similar to
1699             'order_by', you may create a 'grep' key that returns either true or false to determine
1700             if an item in the loop is allowed (works like the 'grep' function).
1701              
1702             # Only show items where the value is greater than 10.
1703             'grep' => sub {
1704             my ($pure, $item) = @_;
1705             return $item > 10;
1706             },
1707              
1708             Just like with 'order_by', if your iterator is over an object, you recieve that
1709             object as the argument and are expected to return a new iterator that is properly
1710             filtered:
1711              
1712             'grep' => sub {
1713             my ($pure, $iterator) = @_;
1714             return $iterator->only_over_10;
1715             },
1716              
1717             B<NOTE:> if you need more dynamic control over the way grep works, you can instead
1718             of hard coding an anonymous subroutine, instead use a string that is a path on the
1719             current data context to an subroutine reference.
1720              
1721             =head3 Perform a 'filter' on your loop items
1722              
1723             Lastly you may wish for the purposes of display to perform so sort of tranformation
1724             on the loop item. For example you may wish rename fields or to flatten a
1725             L<DBIx::Class> result from an object to a hashref in order to prevent your template
1726             authors from accidentally modifying th database. In this case you may add a hash
1727             key 'filter' in the same way as you did with 'sort' or 'grep', which is an anonymous
1728             subroutine that gets the template object followed by the interator item reference (or
1729             scalar). You must return a new reference (or scalar). Example:
1730              
1731             'filter' => sub {
1732             my ($pure, $item) = @_;
1733             return + {
1734             fullname => $item->first_name .' '. $item->last_name,
1735             age => $item->age,
1736             };
1737             },
1738              
1739             Recommendation is to keep this as simple as possible rather than to do very heavy
1740             rewriting of the data structure.
1741              
1742             B<NOTE:> if you need more dynamic control over the way filtering works, you can instead
1743             of hard coding an anonymous subroutine, instead use a string that is a path on the
1744             current data context to an subroutine reference.
1745              
1746             B<NOTE> Should you have more than one special key on your iterator loop, the keys are
1747             processed in the following order 'filter', 'grep', 'order_by'.
1748              
1749             =head3 Generating display_fields
1750              
1751             When you are iterating over an object that is like a Hashref, you need
1752             to inform us of how to get the list of field names which should be the
1753             names of methods on your object who's value you wish to display. By default
1754             we look for a method called 'display fields' but you can customize this
1755             in one of two ways. You can set a key 'display_fields' to be the name of
1756             an alternative method:
1757              
1758             directives => [
1759             '#meta' => {
1760             'field<-info' => [
1761             '.name' => 'field.key',
1762             '.value' => 'field.value',
1763             ],
1764             'display_fields' => 'columns',
1765             },
1766             ]
1767              
1768             =head3 Setting the Data Context in the Interator Specification
1769              
1770             In order to simplify usage of the iterator, you may set the current data
1771             context directly in the interator specification. In order to do this you
1772             would set the target iterator variable name to '.' as in the following example:
1773              
1774             my $html = qq[
1775             <ol>
1776             <li>
1777             <span class='priority'>high|medium|low</span>
1778             <span class='title'>title</span>
1779             </li>
1780             </ol>
1781             ];
1782              
1783             my $pure = Template::Pure->new(
1784             template => $html,
1785             directives => [
1786             'ol li' => {
1787             '.<-tasks' => [
1788             '.priority' => 'priority',
1789             '.title' => 'title',
1790             ],
1791             },
1792             ]);
1793              
1794             my %data = (
1795             tasks => [
1796             { priority => 'high', title => 'Walk Dogs'},
1797             { priority => 'medium', title => 'Buy Milk'},
1798             ],
1799             );
1800              
1801             Returns:
1802              
1803             <ol>
1804             <li>
1805             <span class="priority">high</span>
1806             <span class="title">Walk Dogs</span>
1807             </li>
1808             <li>
1809             <span class="priority">medium</span>
1810             <span class="title">Buy Milk</span>
1811             </li>
1812             </ol>
1813              
1814             =head3 Shortcuts on Loops
1815              
1816             If you are doing a simple loop where the match specification is the current
1817             match point in the DOM and there is only going to be one modification you
1818             can just use a scalar data context path for your action:
1819              
1820             my $html = qq[
1821             <ol>
1822             <li>Things to Do...</li>
1823             </ol>
1824             ];
1825              
1826             my $pure = Template::Pure->new(
1827             template => $html,
1828             directives => [
1829             'ol li' => {
1830             'task<-tasks' => 'task',
1831             },
1832             ]);
1833              
1834             You can also use a coderef in the same way:
1835              
1836             my $pure = Template::Pure->new(
1837             template => $html,
1838             directives => [
1839             'ol li' => {
1840             'task<-tasks' => sub {
1841             my ($pure, $dom, $data) = @_;
1842             $pure->data_at_path($data, 'task');
1843             }
1844             }
1845             ]);
1846              
1847             Both the above would return output like the following:
1848              
1849             my %data = (
1850             tasks => [
1851             'Walk Dogs',
1852             'Buy Milk',
1853             ],
1854             );
1855              
1856             my $string = $pure->render(\%data);
1857              
1858             <ol>
1859             <li>Walk Dogs</li>
1860             <li>Buy Milk</li>
1861             </ol>
1862              
1863             Finally you can use an object that is another L<Template::Pure> instance
1864             in which class it will ack as a wrapper on the matched DOM:
1865              
1866             my $pure = Template::Pure->new(
1867             template => q[
1868             <ol>
1869             <li>Items</li>
1870             </ol>
1871             ],
1872             directives => [
1873             '^ol li' => {
1874             'task<-tasks' => Template::Pure->new(
1875             template => q[<span></span>],
1876             directives => [
1877             'span' => 'task',
1878             '.' => [
1879             { inner => \'^span', content => 'content' },
1880             '.' => 'content',
1881             'li+' => 'inner',
1882             ],
1883             ],
1884             ),
1885             }
1886             ]);
1887              
1888             Produces:
1889              
1890             <ol>
1891             <li>Items<span>Walk Dogs</span></li>
1892             <li>Items<span>Buy Milk</span></li>
1893             </ol>
1894              
1895             =head2 Object - Set the match value to another Pure Template
1896              
1897             my $section_html = qq[
1898             <div>
1899             <h2>Example Section Title</h2>
1900             <p>Example Content</p>
1901             </div>
1902             ];
1903              
1904             my $pure_section = Template::Pure->new(
1905             template => $section_html,
1906             directives => [
1907             'h2' => 'title',
1908             'p' => 'story'
1909             ]);
1910              
1911             my $html = qq[
1912             <div class="story">Example Content</div>
1913             ];
1914              
1915             my $pure = Template::Pure->new(
1916             template => $html,
1917             directives => [
1918             'div.story' => $pure_section,
1919             ]);
1920              
1921             my %data = (
1922             title => 'The Supernatural in Literature',
1923             story => $article_text,
1924             );
1925              
1926             print $pure->render(\%data);
1927              
1928             Results in:
1929              
1930             <div class="story">
1931             <div>
1932             <h2>The Supernatural in Literature</h2>
1933             <p>$article_text</p>
1934             </div>
1935             </div>
1936              
1937             When the action is an object it must be an object that conformation
1938             to the interface and behavior of a L<Template::Pure> object. For the
1939             most part this means it must be an object that does a method 'render' that
1940             takes the current data context refernce and returns an HTML string suitable
1941             to become that value of the matched node.
1942              
1943             When encountering such an object we pass the current data context, but we
1944             add one additional field called 'content' which is the value of the matched
1945             node. You can use this so that you can 'wrap' nodes with a template (similar
1946             to the L<Template> WRAPPER directive).
1947              
1948             my $wrapper_html = qq[
1949             <p class="headline">To Be Wrapped</p>
1950             ];
1951              
1952             my $wrapper = Template::Pure->new(
1953             template => $wrapper_html,
1954             directives => [
1955             'p.headline' => 'content',
1956             ]);
1957              
1958             my $html = qq[
1959             <div>This is a test of the emergency broadcasting
1960             network... This is only a test</div>
1961             ];
1962              
1963             my $wrapper = Template::Pure->new(
1964             template => $html,
1965             directives => [
1966             'div' => $wrapper,
1967             ]);
1968              
1969             Results in:
1970              
1971             <div>
1972             <p class="headline">This is a test of the emergency broadcasting
1973             network... This is only a test</p>
1974             </div>
1975              
1976             Lastly you can mimic a type of inheritance using data mapping and
1977             node aliasing:
1978              
1979             my $overlay_html = q[
1980             <html>
1981             <head>
1982             <title>Example Title</title>
1983             <link rel="stylesheet" href="/css/pure-min.css"/>
1984             <link rel="stylesheet" href="/css/grids-responsive-min.css"/>
1985             <link rel="stylesheet" href="/css/common.css"/>
1986             <script src="/js/3rd-party/angular.min.js"></script>
1987             <script src="/js/3rd-party/angular.resource.min.js"></script>
1988             </head>
1989             <body>
1990             <section id="content">...</section>
1991             <p id="foot">Here's the footer</p>
1992             </body>
1993             </html>
1994             ];
1995              
1996             my $overlay = Template::Pure->new(
1997             template=>$overlay_html,
1998             directives=> [
1999             'title' => 'title',
2000             '^title+' => 'scripts',
2001             'body section#content' => 'content',
2002             ]);
2003              
2004             my $page_html = q[
2005             <html>
2006             <head>
2007             <title>The Real Page</title>
2008             <script>
2009             function foo(bar) {
2010             return baz;
2011             }
2012             </script>
2013             </head>
2014             <body>
2015             You are doomed to discover that you never
2016             recovered from the narcolyptic country in
2017             which you once stood; where the fire's always
2018             burning but there's never enough wood.
2019             </body>
2020             </html>
2021             ];
2022              
2023             my $page = Template::Pure->new(
2024             template=>$page_html,
2025             directives=> [
2026             'title' => 'meta.title',
2027             'html' => [
2028             {
2029             title => \'title',
2030             scripts => \'^head script',
2031             content => \'body',
2032             },
2033             '^.' => $overlay,
2034             ]
2035             ]);
2036              
2037             my $data = +{
2038             meta => {
2039             title => 'Inner Stuff',
2040             },
2041             };
2042              
2043             Results in:
2044              
2045             <html>
2046             <head>
2047             <title>Inner Stuff</title><script>
2048             function foo(bar) {
2049             return baz;
2050             }
2051             </script>
2052             <link href="/css/pure-min.css" rel="stylesheet">
2053             <link href="/css/grids-responsive-min.css" rel="stylesheet">
2054             <link href="/css/common.css" rel="stylesheet">
2055             <script src="/js/3rd-party/angular.min.js"></script>
2056             <script src="/js/3rd-party/angular.resource.min.js"></script>
2057             </head>
2058             <body>
2059             <section id="content">
2060             You are doomed to discover that you never
2061             recovered from the narcolyptic country in
2062             which you once stood; where the fire&amp;#39;s always
2063             burning but there&amp;#39;s never enough wood.
2064             </section>
2065             <p id="foot">Here&#39;s the footer</p>
2066             </body>
2067             </html>
2068              
2069             =head2 Object - A Mojo::DOM58 instance
2070              
2071             In the case where you set the value of the action target to an instance of
2072             L<Mojo::DOM58>, we let the value of that perform the replacement indicated by
2073             the match specification:
2074              
2075             my $html = q[
2076             <html>
2077             <head>
2078             <title>Page Title</title>
2079             </head>
2080             <body>
2081             <p class="foo">aaa</a>
2082             </body>
2083             </html>
2084             ];
2085              
2086             my $pure = Template::Pure->new(
2087             template=>$html,
2088             directives=> [
2089             'p' => Mojo::DOM58->new("<a href='localhost:foo'>Foo!</a>"),
2090             ]);
2091              
2092             my $data = +{
2093             title => 'A Shadow Over Innsmouth',
2094             };
2095              
2096             my $string = $pure->render($data);
2097              
2098             Results in:
2099              
2100             <html>
2101             <head>
2102             <title>A Shadow Over Innsmouth/title>
2103             </head>
2104             <body>
2105             <p class="foo"><a href='localhost:foo'>Foo!</a></a>
2106             </body>
2107             </html>
2108              
2109             =head2 Object - Any Object that does 'TO_HTML'
2110              
2111             In addition to using a L<Template::Pure> object as the target action for
2112             a match specification, you may use any object that does a method called
2113             'TO_HTML'. Such a method would expect to recieve the current template
2114             object, the current matched DOM, and the current value of the Data context
2115             as arguments. It should return a string that is used as the replacement
2116             value for the given match specification. For example:
2117              
2118             {
2119             package Local::Example;
2120              
2121             sub new {
2122             my ($class, %args) = @_;
2123             return bless \%args, $class;
2124             }
2125              
2126             sub TO_HTML {
2127             my ($self, $pure, $dom, $data) = @_;
2128             return $dom->attr('class');
2129             }
2130             }
2131              
2132             my $html = q[
2133             <html>
2134             <head>
2135             <title>Page Title</title>
2136             </head>
2137             <body>
2138             <p class="foo">aaa</a>
2139             <p class="bar">bbb</a>
2140             </body>
2141             </html>
2142             ];
2143              
2144             my $pure = Template::Pure->new(
2145             template=>$html,
2146             directives=> [
2147             'title' => 'title',
2148             'p' => Local::Example->new,
2149             ]);
2150              
2151             my $data = +{
2152             title => 'A Shadow Over Innsmouth',
2153             };
2154              
2155             print $pure->render($data);
2156              
2157             Results in:
2158              
2159             <html>
2160             <head>
2161             <title>A Shadow Over Innsmouth</title>
2162             </head>
2163             <body>
2164             <p class="foo">foo</p>
2165             <p class="bar">bar</p>
2166             </body>
2167             </html>
2168              
2169             B<NOTE> For an alternative method see L</PROCESSING INSTRUCTIONS>
2170              
2171             =head2 Using Dot Notation in Directive Data Mapping
2172              
2173             L<Template::Pure> allows you to indicate a path to a point in your
2174             data context using 'dot' notation, similar to many other template
2175             systems such as L<Template>. In general this offers an abstraction
2176             that smooths over the type of reference your data is (an object, or
2177             a hashref) such as to make it easier to swap the type later on as
2178             needs grow, or for testing:
2179              
2180             directives => [
2181             'title' => 'meta.title',
2182             'copyright => 'meta.license_info.copyright_date',
2183             ...,
2184             ],
2185              
2186             my %data = (
2187             meta => {
2188             title => 'Hello World!',
2189             license_info => {
2190             type => 'Artistic',
2191             copyright_date => 2016,
2192             },
2193             },
2194             );
2195              
2196             Basically you use '.' to replace '->' and we figure out if the path
2197             is to a key in a hashref or method on an object for you.
2198              
2199             In the case when the value of a path is explictly undefined, the behavior
2200             is to remove the matching node (the full matching node, not just the value).
2201              
2202             Trying to resolve a key or method that does not exist returns an error.
2203             However its not uncommon for some types of paths to have optional parts
2204             and in these cases its not strictly and error when the path does not exist.
2205             In this case you may prefix 'optional:' to your path part, which will surpress
2206             an error in the case the requested path does not exist:
2207              
2208             directives => [
2209             'title' => 'meta.title',
2210             'copyright => 'meta.license_info.optional:copyright_date',
2211             ...,
2212             ],
2213              
2214             In this case instead of returning an error we treat the path as though it
2215             returned 'undefined' (which means we trim out the matching node).
2216              
2217             In other cases your path might exist, but returns undefined. This can be an
2218             issue if you have following paths (common case when traversing L<DBIx::Class>
2219             relationships...) and you don't want to throw an exception. In this case you
2220             may use a 'maybe:' prefix, which returns undefined and treats the entire remaining
2221             path as undefined:
2222              
2223             directives => [
2224             'title' => 'meta.title',
2225             'copyright => 'meta.maybe:license_info.copyright_date',
2226             ...,
2227             ],
2228              
2229             =head2 Using a Literal Value in your Directive Action
2230              
2231             Generally the action part of your directive will be a path that maps to
2232             a section of the data that is passed to the template at render. However
2233             there can be some cases when its useful to indicate a literal value, particularly
2234             doing template development when you might not have written all the backend code
2235             that generates data. In those cases you may indicate that the action is a
2236             string literal using single or double quotes as in the following example:
2237              
2238             my $html = q[
2239             <html>
2240             <head>
2241             <title>Page Title</title>
2242             </head>
2243             <body>
2244             <p id="literal_q">aaa</a>
2245             <p id="literal_qq">bbb</a>
2246             </body>
2247             </html>
2248             ];
2249              
2250             my $pure = Template::Pure->new(
2251             template=>$html,
2252             directives=> [
2253             title=>'title',
2254             '#literal_q' => "'literal data single quote'",
2255             '#literal_qq' => '"literal data double quote"',
2256              
2257             ]);
2258              
2259             my $data = +{
2260             title => 'A Shadow Over Innsmouth',
2261             };
2262              
2263             Returns on processing:
2264              
2265             <html>
2266             <head>
2267             <title>A Shadow Over Innsmouth</title>
2268             </head>
2269             <body>
2270             <p id="literal_q">literal data single quote'</a>
2271             <p id="literal_qq">literal data double quote</a>
2272             </body>
2273             </html>
2274              
2275             This feature is of limited value since at this time there is no way to indicate
2276             a literal other than a string.
2277              
2278             =head2 Defaults in your Data Context
2279              
2280             By default there will be a key 'self' in your data context which refers to
2281             the current instance of your L<Template::Pure>. This is handy for introspection
2282             and for subclassing:
2283              
2284             {
2285             package Local::Template::Pure::Custom;
2286              
2287             use Moo;
2288             extends 'Template::Pure';
2289              
2290             has 'version' => (is=>'ro', required=>1);
2291              
2292             sub time { return 'Mon Apr 11 10:49:42 2016' }
2293             }
2294              
2295             my $html_template = qq[
2296             <html>
2297             <head>
2298             <title>Page Title</title>
2299             </head>
2300             <body>
2301             <div id='version'>Version</div>
2302             <div id='main'>Test Body</div>
2303             <div id='foot'>Footer</div>
2304             </body>
2305             </html>
2306             ];
2307              
2308             my $pure = Local::Template::Pure::Custom->new(
2309             version => 100,
2310             template=>$html_template,
2311             directives=> [
2312             'title' => 'meta.title',
2313             '#version' => 'self.version',
2314             '#main' => 'story',
2315             '#foot' => 'self.time',
2316             ]
2317             );
2318              
2319             Results in:
2320              
2321             <html>
2322             <head>
2323             <title>A subclass</title>
2324             </head>
2325             <body>
2326             <div id="version">100</div>
2327             <div id="main">XXX</div>
2328             <div id="foot">Mon Apr 11 10:49:42 2016</div>
2329             </body>
2330             </html>
2331              
2332             Creating subclasses of L<Template::Pure> to encapsulate some of the view
2333             data abd view logic should probably be considered a best practice approach.
2334              
2335             B<NOTE> if you create a subclass and want your methods to have access to
2336             and to modify the DOM, you can return a CODEREF:
2337              
2338             {
2339             package Local::Template::Pure::Custom;
2340              
2341             use Moo;
2342             extends 'Template::Pure';
2343              
2344             has 'version' => (is=>'ro', required=>1);
2345              
2346             sub time {
2347             return sub {
2348             my ($self, $dom, $data) = @_;
2349             $dom->attr(foo=>'bar');
2350             return 'Mon Apr 11 10:49:42 2016';
2351             };
2352             }
2353             }
2354              
2355             Such a coderef may return a scalar value, an object or any other type of
2356             data type we can process.
2357              
2358             =head2 Remapping Your Data Context
2359              
2360             If the first element of your directives (either at the root of the directives
2361             or when you create a new directives list under a given node) is a hashref
2362             we take that as special instructions to remap the current data context to
2363             a different structure. Useful for increase reuse and decreasing complexity
2364             in some situations:
2365              
2366             my $html = qq[
2367             <dl id='contact'>
2368             <dt>Phone</dt>
2369             <dd class='phone'>(xxx) xxx-xxxx</dd>
2370             <dt>Email</dt>
2371             <dd class='email'>aaa@email.com</dd>
2372             </dl>
2373             ];
2374              
2375             my $pure = Template::Pure->new(
2376             template => $html,
2377             directives => [
2378             '#contact' => [
2379             {
2380             phone => 'contact.phone',
2381             email => 'contact.email,
2382             }, [
2383             '.phone' => 'phone',
2384             '.email' => 'email',
2385             ],
2386             },
2387             ]
2388             );
2389              
2390             my %data = (
2391             contact => {
2392             phone => '(212) 387-9509',
2393             email => 'jjnapiork@cpan.org',
2394             }
2395             );
2396              
2397             print $pure->render(\%data);
2398              
2399             Results in:
2400              
2401             <dl id='contact'>
2402             <dt>Phone</dt>
2403             <dd class='phone'>(212) 387-9509</dd>
2404             <dt>Email</dt>
2405             <dd class='email'>jjnapiork@cpan.org'</dd>
2406             </dl>
2407              
2408             =head2 Using Placeholders in your Actions
2409              
2410             Sometimes it makes sense to compose your replacement value of several
2411             bits of information. Although you could do this with lots of extra 'span'
2412             tags, sometimes its much more clear and brief to put it all together. For
2413             example:
2414              
2415             my $pure = Template::Pure->new(
2416             template => $html,
2417             directives => [
2418             '#content' => 'Hi ={name}, glad to meet you on=#{today}',
2419             ]
2420             );
2421              
2422             In the case your value does not refer itself to a path, but instead contains
2423             one or more placeholders which are have data paths inside them. These data
2424             paths can be simple or complex, and even contain filters:
2425              
2426             my $pure = Template::Pure->new(
2427             template => $html,
2428             directives => [
2429             '#content' => 'Hi ={name | uc}, glad to meet you on ={today}',
2430             ]
2431             );
2432              
2433             For more on filters see L</FILTERS>
2434              
2435             =head2 Using Placeholders in your Match Specification
2436              
2437             Sometimes you may wish to allow the user that is rendering a template the
2438             ability to influence the match specification. To grant this ability you
2439             may use a placeholder:
2440              
2441             my $html = q[
2442             <html>
2443             <head>
2444             <title>Page Title</title>
2445             </head>
2446             <body>
2447             <p id="story">Some Stuff</p>
2448             <p id="footer">...</p>
2449             </body>
2450             </html>
2451             ];
2452              
2453             my $pure = Template::Pure->new(
2454             template=>$html,
2455             directives=> [
2456             'body ={story_target}' => '={meta.title | upper}: ={story} on ={meta.date}',
2457             '#footer' => '={meta.title} on ={meta.date}',
2458             ]);
2459              
2460             my $data = +{
2461             story_target => '#story',
2462             meta => {
2463             title => 'Inner Stuff',
2464             date => '1/1/2020',
2465             },
2466             story => 'XX' x 10,
2467             };
2468              
2469             =head2 Special indicators in your Match Specification
2470              
2471             In General your match specification is a CSS match supported by the
2472             underlying HTML parser. However the following specials are supported
2473             for needs unique to the needs of templating:
2474              
2475             =over 4
2476              
2477             =item '.': Select the current node
2478              
2479             Used to indicate the current root node. Useful when you have created a match
2480             with sub directives.
2481              
2482             my $pure = Template::Pure->new(
2483             template => $html,
2484             directives => [
2485             'body' => [
2486             ]
2487             ]
2488             );
2489              
2490             =item '/': The root node
2491              
2492             Used when you which to select from the root of the template DOM, not the current
2493             selected node.
2494              
2495             =item '@': Select an attribute within the current node
2496              
2497             Used to update values inside a node:
2498              
2499             my $pure = Template::Pure->new(
2500             template => $html,
2501             directives => [
2502             'h1@class' => 'header_class',
2503             ],
2504             );
2505              
2506             =item '+': Append or prepend a value
2507              
2508             my $pure = Template::Pure->new(
2509             template => $html,
2510             directives => [
2511             '+h1' => 'title',
2512             '#footer+' => 'copyright_date',
2513             ],
2514             );
2515              
2516             The default behavior is for a match to replace the matched node's content. In some
2517             cases you may wish to preserve the template content and instead either add more
2518             content to the front or back of it.
2519              
2520             B<NOTE> Can be combined with '@' to append / prepend to an attribute.
2521              
2522             B<NOTE> Special handling when appending or prepending to a class attribute (we add a
2523             space if there is an existing since that is expected).
2524              
2525             =item '^': Replace current node completely
2526              
2527             Normally we replace, append or prepend to the value of the selected node. Using the
2528             '^' at the front of your match indicates operation should happen on the entire node,
2529             not just the value. Can be combined with '+' for append/prepend.
2530              
2531             =item '|': Run a filter on the current node
2532              
2533             Passed the currently selected node to a code reference. You can run L<Mojo::DOM58>
2534             transforms on the entire selected node. Nothing should be returned from this
2535             coderef.
2536              
2537             'body|' => sub {
2538             my ($template, $dom, $data) = @_;
2539             $dom->find('p')->each( sub {
2540             $_->attr('data-pure', 1);
2541             });
2542             }
2543              
2544             =back
2545              
2546             =head1 FILTERS
2547              
2548             You may filter you data via a provided built in display filter:
2549              
2550             my $pure = Template::Pure->new(
2551             template => $html,
2552             directives => [
2553             '#content' => 'data.content | escape_html',
2554             ]
2555             );
2556              
2557             If a filter takes arguments you may fill those arguments with either literal
2558             values or a 'placeholder' which should point to a path in the current data
2559             context.
2560              
2561             my $pure = Template::Pure->new(
2562             template => $html,
2563             directives => [
2564             '#content' => 'data.content | repeat(#{times}) | escape_html',
2565             ]
2566             );
2567              
2568             You may add a custom filter when you define your template:
2569              
2570             my $pure = Template::Pure->new(
2571             filters => {
2572             custom_filter => sub {
2573             my ($template, $data, @args) = @_;
2574             # Do something with the $data, possible using @args
2575             # to control what that does
2576             return $data;
2577             },
2578             },
2579             );
2580              
2581             An example custom Filter:
2582              
2583             my $pure = Template::Pure->new(
2584             filters => {
2585             custom_filter => sub {
2586             my ($template, $data, @args) = @_;
2587             # TBD
2588             # return $data;
2589             },
2590             },
2591             );
2592              
2593             In general you can use filters to reduce the need to write your action as a coderef
2594             which should make it easier for you to give the job of writing directives / actions
2595             to non programmers.
2596              
2597             See L<Template::Pure::Filters> for all bundled filters.
2598              
2599             =head1 PROCESSING INSTRUCTIONS
2600              
2601             Generally L<Template::Pure> proposes its best to keep your actual HTML templates as simple
2602             and valid as possible, instead putting your transformations and data binding logic into
2603             directives. This leads to a strong separate of responsibilities and prevents your templates
2604             from getting messy. However there are a few situations where we'd like to offer the template
2605             designer some options to control the overall template structure and to encapsulate common
2606             design elements or template rules. For example its common in a website to have some common
2607             layouts that set overall page structure and import common CSS and Javascript libraries. Additionally
2608             its common to have 'snippets' of HTML that are shared across lots of documents (such as common
2609             header or footer elements, or advertizements panels, etc.) You can describe these via directives
2610             but in order to empower designers and reduce your directive complexity L<Template::Pure> allowes
2611             one to insert HTML Processing instructions into your templates that get parsed when the template
2612             object is instantiated and added as additional directives. This allows one to create directives
2613             declaratively in the template, rather than programtically in your code.
2614              
2615             The availability of this feature in no way suggests that one approach or the other is best. You
2616             should determine that based on your team and project needs.
2617              
2618             L<Template::Pure> currently offers the following three processing instructions, and does not
2619             yet offer an API to create your own. This may change in the future.
2620              
2621             B<NOTE> All processing instructions are parsed and evaluated during instantiation of your
2622             template object and all generated directives are adding to the end of your existing ones. As
2623             a result these instructions are run last.
2624              
2625             =head2 Includes
2626              
2627             Allows one to inject a template render into a placeholder spot in the current template. Example:
2628              
2629             my $include_html = qq[
2630             <span id="footer">Copyright&nbsp;</span>];
2631              
2632             my $include = Template::Pure->new(
2633             template=>$include_html,
2634             directives=> [
2635             '#footer+' => 'copyright_year',
2636             ]);
2637              
2638             my $base_html = q[
2639             <html>
2640             <head>
2641             <title>Page Title: </title>
2642             </head>
2643             <body>
2644             <div id='story'>Example Story</div>
2645             <?pure-include src='foot_include' copyright_year='meta.copyright.year'?>
2646             </body>
2647             </html>
2648             ];
2649              
2650             my $base = Template::Pure->new(
2651             template => $base_html,
2652             directives => [
2653             'title+' => 'meta.title',
2654             '#story' => 'story',
2655             ]
2656             );
2657              
2658             print $base->render({
2659             story => 'It was a dark and stormy night...',
2660             foot_include => $include
2661             meta => {
2662             title=>'Dark and Stormy..',
2663             copyright => {
2664             year => 2016,
2665             author=>'jnap'}
2666             }
2667             },
2668             });
2669              
2670             Returns:
2671              
2672             <html>
2673             <head>
2674             <title>Page Title: Dark and Stormy..'</title>
2675             </head>
2676             <body>
2677             <div id='story'>It was a dark and stormy night...</div>
2678             <span id="footer">Copyright 2016</span>
2679             </body>
2680             </html>
2681              
2682             This is basically the same as:
2683              
2684             my $base_html = q[
2685             <html>
2686             <head>
2687             <title>Page Title: </title>
2688             </head>
2689             <body>
2690             <div id='story'>Example Story</div>
2691             <span id='footer'>...</span>
2692             </body>
2693             </html>
2694             ];
2695              
2696             my $base = Template::Pure->new(
2697             template => $base_html,
2698             directives => [
2699             'title+' => 'meta.title',
2700             '#story' => 'story',
2701             '^#footer' => 'foot_include',
2702             ]
2703             );
2704              
2705             print $base->render({
2706             story => 'It was a dark and stormy night...',
2707             foot_include => $include
2708             meta => {
2709             title=>'Dark and Stormy..',
2710             copyright => {
2711             year => 2016,
2712             author=>'jnap'}
2713             }
2714             },
2715             });
2716              
2717             Or alternatively (if you don't want to allow one to alter the include via
2718             processing data):
2719              
2720             my $base_html = q[
2721             <html>
2722             <head>
2723             <title>Page Title: </title>
2724             </head>
2725             <body>
2726             <div id='story'>Example Story</div>
2727             <span id='footer'>...</span>
2728             </body>
2729             </html>
2730             ];
2731              
2732             my $base = Template::Pure->new(
2733             template => $base_html,
2734             directives => [
2735             'title+' => 'meta.title',
2736             '#story' => 'story',
2737             '^#footer' => $include,
2738             ]
2739             );
2740              
2741             print $base->render({
2742             story => 'It was a dark and stormy night...',
2743             meta => {
2744             title=>'Dark and Stormy..',
2745             copyright => {
2746             year => 2016,
2747             author=>'jnap'}
2748             }
2749             },
2750             });
2751              
2752             Basically you set the processing directive and the PI is fully replaced by the referenced
2753             template. Format is like:
2754              
2755             <?pure-include src=$data_path @args?>
2756              
2757             Where 'src' must be a data context path (see L<\Using Dot Notation in Directive Data Mapping>
2758             for more on referencing a data path) that is an instance of L<Template::Pure> and @args are
2759             a list of mappings to import data into the target include from the calling instance current
2760             data context. Alternatively, you may set a data context root instead using 'ctx' as an
2761             argument:
2762              
2763             my $include_html = qq[
2764             <span id="footer">Copyright&nbsp;</span>];
2765              
2766             my $include = Template::Pure->new(
2767             template=>$include_html,
2768             directives=> [
2769             '#footer+' => 'copyright.year',
2770             ]);
2771              
2772             ...
2773             <?pure-include src='foot_include' ctx='meta'?>
2774             ...
2775              
2776             This might be the preferred method when you wish to copy a full section of data to your
2777             target include. You may not combine the 'ctx' method and the named args method.
2778              
2779             If you do not specify a 'ctx' or named args, we default to a context of the root data
2780             context. This probably leaks too much information into your include but is not terrible
2781             for prototyping.
2782              
2783             =head2 Wrapper
2784              
2785             Similar to the include processing instruction, it provides template authors with a declaritive
2786             approach to L</Object - Set the match value to another Pure Template>. Example:
2787              
2788             my $story_section_wrapper_html = qq[
2789             <section>
2790             <h1>story title</h1>
2791             <p>By:&nbsp;</p>
2792             </section>];
2793              
2794             my $story_section_wrapper = Template::Pure->new(
2795             template=>$story_section_wrapper_html,
2796             directives=> [
2797             'h1' => 'title',
2798             'p+' => 'author',
2799             '^p+' => 'content',
2800             ]);
2801              
2802             my $base_html = q[
2803             <html>
2804             <head>
2805             <title>Page Title:&nbsp;</title>
2806             </head>
2807             <body>
2808             <?pure-wrapper src='section_wrapper' ctx='meta'?>
2809             <div id='story'>Example Story</div>
2810             </body>
2811             </html>
2812             ];
2813              
2814             my $base = Template::Pure->new(
2815             template=>$base_html,
2816             directives=> [
2817             'title+' => 'meta.title',
2818             '#story' => 'story,
2819             ]
2820             );
2821              
2822             print $base->render({
2823             story => 'Once Upon a Time...',
2824             section_wrapper => $story_section_wrapper,
2825             meta => {
2826             title=>'Once',
2827             author=>'jnap',
2828             },
2829             });
2830              
2831             Results in:
2832              
2833             <html>
2834             <head>
2835             <title>Page Title:&nbsp;Once</title>
2836             </head>
2837             <body>
2838             <section>
2839             <h1>Once</h1>
2840             <p>By:&nbsp;jnap</p>
2841             <div id='story'>Once Upon a Time</div>
2842             </section>
2843             </body>
2844             </html>
2845              
2846             This processing instructions 'wraps' the following tag node with the template that
2847             is the target of 'src'. Like L</Includes> you may pass data via named parameters or
2848             by setting a new data context, as in the given example.
2849              
2850             Similar approach using directives only:
2851              
2852             my $base = Template::Pure->new(
2853             template=>$base_html,
2854             directives=> [
2855             'title+' => 'meta.title',
2856             '#story' => 'story,
2857             '^#story => $story_section_wrapper,
2858             ]
2859             );
2860              
2861             =head2 Overlay
2862              
2863             An overlay replaces the selected node with the results on another template. Typically
2864             you will pass selected nodes of the original template as directives to the new template.
2865             This can be used to minic features like template inheritance, that exist in other templating
2866             systems. One example:
2867              
2868             my $overlay_html = q[
2869             <html>
2870             <head>
2871             <title>Example Title</title>
2872             <link rel="stylesheet" href="/css/pure-min.css"/>
2873             <link rel="stylesheet" href="/css/grids-responsive-min.css"/>
2874             <link rel="stylesheet" href="/css/common.css"/>
2875             <script src="/js/3rd-party/angular.min.js"></script>
2876             <script src="/js/3rd-party/angular.resource.min.js"></script>
2877             </head>
2878             <body>
2879             </body>
2880             </html>
2881             ];
2882              
2883             my $overlay = Template::Pure->new(
2884             template=>$overlay_html,
2885             directives=> [
2886             'title' => 'title',
2887             'head+' => 'scripts',
2888             'body' => 'content',
2889             ]);
2890              
2891             my $base_html = q[
2892             <?pure-overlay src='layout'
2893             title=\'title'
2894             scripts=\'^head script'
2895             content=\'body'?>
2896             <html>
2897             <head>
2898             <title>Page Title:&nbsp;</title>
2899             <script>
2900             function foo(bar) {
2901             return baz;
2902             }
2903             </script>
2904             </head>
2905             <body>
2906             <div id='story'>Example Story</div>
2907             </body>
2908             </html>
2909             ];
2910              
2911             my $base = Template::Pure->new(
2912             template=>$base_html,
2913             directives=> [
2914             'title+' => 'meta.title',
2915             '#story' => 'story,
2916             ]
2917             );
2918              
2919             print $base->render({
2920             layout => $overlay,
2921             story => 'Once Upon a Time...',
2922             meta => {
2923             title=>'Once',
2924             author=>'jnap',
2925             },
2926             });
2927              
2928             Renders As:
2929              
2930             <html>
2931             <head>
2932             <title>Once</title>
2933             <link rel="stylesheet" href="/css/pure-min.css"/>
2934             <link rel="stylesheet" href="/css/grids-responsive-min.css"/>
2935             <link rel="stylesheet" href="/css/common.css"/>
2936             <script src="/js/3rd-party/angular.min.js"></script>
2937             <script src="/js/3rd-party/angular.resource.min.js"></script>
2938             <script>
2939             function foo(bar) {
2940             return baz;
2941             }
2942             </script>
2943             </head>
2944             <body>
2945             <div id='story'>Once Upon a Time...</div>
2946             </body>
2947             </html>
2948              
2949             The syntax of the processing instruction is:
2950              
2951             <?pure-overlay src='' @args ?>
2952              
2953             Where 'src' is a data path to the template you want to use as the overlay, and @args is
2954             a list of key values which populate the data context of the overlay when you process it.
2955             Often these values will be references to existing nodes in the base template (as in the
2956             examples \'title' and \'body' above) but they can also be used to map values from your
2957             data context in the same way we do so for L</Include> and L</Wrapper>.
2958              
2959             If you were to write this as 'directives only' it would look like:
2960              
2961             my $base = Template::Pure->new(
2962             template=>$base_html,
2963             directives=> [
2964             'title+' => 'meta.title',
2965             '#story' => 'story,
2966             'html' => [
2967             {
2968             title => \'title'
2969             script s=> \'^head script'
2970             content => \'body'
2971             },
2972             '^.' => 'layout',
2973             ],
2974             ]
2975             );
2976              
2977             Please note that although in this example the overlay wrapped over the entire template, it is
2978             not limited to that, rather like the L</Wrapper> processing instruction it just takes the next
2979             tag node following as its overlay target. So you could have more than one overlap in a document
2980             and can overlay sections for those cases where a L</Wrapper> is not sufficently complex.
2981              
2982             =head2 Filter
2983              
2984             A Filter will process the following node on a L<Template::Pure> instance as if that node was the
2985             source for its template. This means that the target source template must be a coderef that builds
2986             a <Template::Pure> object, and not an already instantiated one. For Example:
2987              
2988             my $base_html = q[
2989             <html>
2990             <head>
2991             <title>Title Goes Here...</title>
2992             </head>
2993             <body>
2994             <?pure-filter src=?>
2995             <ul>
2996             <li>One</li>
2997             <li>Two</li>
2998             <li>Three</li>
2999             </ul>
3000             </body>
3001             </html>
3002             ];
3003              
3004             my $base = Template::Pure->new(
3005             template => $base_html,
3006             directives => [
3007             'title' => 'title',
3008             ]
3009             );
3010              
3011             print $base->render({
3012             title => 'Dark and Stormy..',
3013             style => 'red',
3014             filter => sub {
3015             my $dom = shift;
3016             return Template::Pure->new(
3017             template => $dom,
3018             directives => [
3019             'li@class' => 'style'
3020             ]
3021             },
3022             });
3023              
3024             Outputs:
3025              
3026             <html>
3027             <head>
3028             <title>Dark and Stormy..</title>
3029             </head>
3030             <body>
3031             <ul>
3032             <li class='red'>One</li>
3033             <li class='red'>Two</li>
3034             <li class='red'>Three</li>
3035             </ul>
3036             </body>
3037             </html>
3038              
3039             As you can see, its similar to the Wrapper instruction, just instead of the matched template
3040             being passed as the 'content' argument to be used in anther template, it becomes the template.
3041              
3042             =head1 IMPORTANT NOTE REGARDING VALID HTML
3043              
3044             Please note that L<Mojo::DOM58> tends to enforce rule regarding valid HTML5. For example, you
3045             cannot nest a block level element inside a 'P' element. This might at times lead to some
3046             surprising results in your output.
3047              
3048             =head1 ERROR MESSAGES AND DEBUGGING
3049              
3050             Some error messages will use L<Class::MOP> if its available for introspection. Having this installed
3051             will greatly improve your debugging, so I recommend installing it on your development machines (good
3052             change you already have it via L<Moose> anyway). If its not installed we just do a general L<Data::Dumper>
3053             which results in a lot of data that is not easy to read, but suitable for production.
3054              
3055             =head1 AUTHOR
3056            
3057             John Napiorkowski L<email:jjnapiork@cpan.org>
3058            
3059             =head1 SEE ALSO
3060            
3061             L<Mojo::DOM58>, L<HTML::Zoom>. Both of these are approaches to programmatically examining and
3062             altering a DOM.
3063              
3064             L<Template::Semantic> is a similar system that uses XPATH instead of a CSS inspired matching
3065             specification. It has more dependencies (including L<XML::LibXML> and doesn't separate the actual
3066             template data from the directives. You might find this more simple approach appealing,
3067             so its worth alook.
3068              
3069             L<HTML::Seamstress> Seems to also be prior art along these lines but I have trouble following
3070             the code and it seems not active. Might be worth looking at at least for ideas!
3071            
3072             =head1 COPYRIGHT & LICENSE
3073            
3074             Copyright 2016, John Napiorkowski L<email:jjnapiork@cpan.org>
3075            
3076             This library is free software; you can redistribute it and/or modify it under
3077             the same terms as Perl itself.
3078              
3079             =cut