| line | stmt | bran | cond | sub | pod | time | code | 
| 1 | 26 |  |  | 26 |  | 2194973 | use strict; | 
|  | 26 |  |  |  |  | 44 |  | 
|  | 26 |  |  |  |  | 604 |  | 
| 2 | 26 |  |  | 26 |  | 83 | use warnings; | 
|  | 26 |  |  |  |  | 28 |  | 
|  | 26 |  |  |  |  | 1022 |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | package Template::Pure; | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | our $VERSION = '0.032'; | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 26 |  |  | 26 |  | 12419 | use Mojo::DOM58; | 
|  | 26 |  |  |  |  | 475834 |  | 
|  | 26 |  |  |  |  | 726 |  | 
| 9 | 26 |  |  | 26 |  | 183 | use Scalar::Util; | 
|  | 26 |  |  |  |  | 32 |  | 
|  | 26 |  |  |  |  | 837 |  | 
| 10 | 26 |  |  | 26 |  | 11885 | use Template::Pure::ParseUtils; | 
|  | 26 |  |  |  |  | 277 |  | 
|  | 26 |  |  |  |  | 664 |  | 
| 11 | 26 |  |  | 26 |  | 9086 | use Template::Pure::Filters; | 
|  | 26 |  |  |  |  | 39 |  | 
|  | 26 |  |  |  |  | 615 |  | 
| 12 | 26 |  |  | 26 |  | 8201 | use Template::Pure::DataContext; | 
|  | 26 |  |  |  |  | 45 |  | 
|  | 26 |  |  |  |  | 552 |  | 
| 13 | 26 |  |  | 26 |  | 8008 | use Template::Pure::DataProxy; | 
|  | 26 |  |  |  |  | 34 |  | 
|  | 26 |  |  |  |  | 552 |  | 
| 14 | 26 |  |  | 26 |  | 104 | use Template::Pure::EncodedString; | 
|  | 26 |  |  |  |  | 23 |  | 
|  | 26 |  |  |  |  | 353 |  | 
| 15 | 26 |  |  | 26 |  | 8130 | use Template::Pure::Iterator; | 
|  | 26 |  |  |  |  | 39 |  | 
|  | 26 |  |  |  |  | 588 |  | 
| 16 | 26 |  |  | 26 |  | 13807 | use Storable qw(dclone); | 
|  | 26 |  |  |  |  | 58456 |  | 
|  | 26 |  |  |  |  | 114454 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | sub new { | 
| 19 | 50 |  |  | 50 | 1 | 71800 | my ($proto, %args) = @_; | 
| 20 | 50 |  | 33 |  |  | 249 | my $class = ref($proto) || $proto; | 
| 21 |  |  |  |  |  |  |  | 
| 22 | 50 |  | 50 |  |  | 236 | my $template = delete($args{template}) || $class->template || die "Can't find a template"; | 
| 23 | 50 |  | 100 |  |  | 189 | my $directives = delete($args{directives}) || [$class->directives]; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | my $self = bless +{ | 
| 26 |  |  |  |  |  |  | filters => delete($args{filters}) || +{}, | 
| 27 |  |  |  |  |  |  | directives => $directives, | 
| 28 | 50 |  | 50 |  |  | 491 | components => delete($args{components}) || +{}, | 
|  |  |  | 100 |  |  |  |  | 
| 29 |  |  |  |  |  |  | %args, | 
| 30 |  |  |  |  |  |  | }, $class; | 
| 31 |  |  |  |  |  |  |  | 
| 32 | 50 |  |  |  |  | 172 | my ($dom, @directives)  = $self->_prepare_dom($template); | 
| 33 | 50 |  |  |  |  | 67 | unshift @directives, @{$self->{directives}}; | 
|  | 50 |  |  |  |  | 203 |  | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 50 |  |  |  |  | 87 | $self->{dom} = $dom; | 
| 36 | 50 |  |  |  |  | 71 | $self->{directives} = \@directives; | 
| 37 | 50 |  |  |  |  | 447 | return $self; | 
| 38 |  |  |  |  |  |  | } | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | sub _process_pi { | 
| 41 | 6 |  |  | 6 |  | 10 | my ($self, %params) = @_; | 
| 42 | 6 |  |  |  |  | 12 | my ($target, %attrs) = $self->parse_processing_instruction($params{node}->tree->[1]); | 
| 43 | 6 |  |  |  |  | 10 | my $ctx = delete $attrs{ctx}; | 
| 44 | 6 |  |  |  |  | 7 | my $src = delete $attrs{src}; | 
| 45 |  |  |  |  |  |  |  | 
| 46 | 6 | 100 |  |  |  | 22 | if($target eq 'pure-include') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 47 | 3 |  |  |  |  | 13 | $params{node}->replace("<span id='include-$params{cnt}'>include placeholder</span>"); | 
| 48 | 3 |  |  |  |  | 616 | my @include_directives; | 
| 49 | 3 | 100 |  |  |  | 18 | if($ctx) { | 
|  |  | 50 |  |  |  |  |  | 
| 50 | 1 |  |  |  |  | 5 | @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 |  | 4 | my ($t, $dom, $data) = @_; | 
| 58 | 2 |  |  |  |  | 4 | return $data->{$src}; | 
| 59 |  |  |  |  |  |  | }, | 
| 60 | 2 |  |  |  |  | 16 | ]); | 
| 61 |  |  |  |  |  |  | } else { | 
| 62 | 0 |  |  |  |  | 0 | @include_directives = ("^#include-$params{cnt}", $src) | 
| 63 |  |  |  |  |  |  | } | 
| 64 | 3 |  |  |  |  | 3 | push @{$params{directives}}, @include_directives; | 
|  | 3 |  |  |  |  | 9 |  | 
| 65 |  |  |  |  |  |  | } elsif($target eq 'pure-wrapper') { | 
| 66 | 1 |  |  |  |  | 3 | $params{node}->following('*')->first->attr('data-pure-wrapper-id'=>"wrapper-$params{cnt}"); | 
| 67 | 1 |  |  |  |  | 224 | $params{node}->remove; | 
| 68 | 1 | 50 |  |  |  | 97 | if($ctx) { | 
|  |  | 0 |  |  |  |  |  | 
| 69 | 1 |  |  |  |  | 9 | push @{$params{directives}}, ( | 
| 70 |  |  |  |  |  |  | "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", +{ $ctx => ['^.' => "/$src"]}, | 
| 71 | 1 |  |  | 1 |  | 2 | "*[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 |  |  |  |  | 3 | $params{node}->following('*')->first->attr('data-pure-filter-id'=>"filter-$params{cnt}"); | 
| 87 | 1 |  |  |  |  | 174 | $params{node}->remove; | 
| 88 | 1 | 50 |  |  |  | 102 | 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 |  |  |  |  | 9 | push @{$params{directives}}, ( | 
| 98 |  |  |  |  |  |  | "^*[data-pure-filter-id=filter-$params{cnt}]", [\%attrs, '^.' => sub { | 
| 99 | 1 |  |  | 1 |  | 2 | my ($t, $dom, $data) = @_; | 
| 100 | 1 |  |  |  |  | 3 | $t->data_at_path($data, "/$src")->($dom); | 
| 101 |  |  |  |  |  |  | } ], | 
| 102 | 1 |  |  | 1 |  | 1 | "*[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 |  |  |  |  | 359 | $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 |  |  |  |  | 8 | $params{cnt}++; | 
| 125 | 6 |  |  |  |  | 25 | return %params; | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 8 |  |  | 8 | 0 | 33 | sub components { shift->{components} } | 
| 129 | 10 |  |  | 10 | 0 | 2456 | sub initialized_components { shift->{initialized_components} } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub initialize_component { | 
| 132 | 8 |  |  | 8 | 0 | 20 | my ($self, $name, %params) = @_; | 
| 133 | 8 |  | 50 |  |  | 18 | return ($self->components->{$name} || die "No Component $name")->($self, %params); | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | sub _process_components { | 
| 137 | 8 |  |  | 8 |  | 19 | my ($self, %params) = @_; | 
| 138 |  |  |  |  |  |  | my %fields = ( | 
| 139 | 8 | 50 |  |  |  | 18 | %{$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 |  |  |  |  | 147 | my $component_id = $params{component_name}.'-'.$params{cnt}; | 
| 146 |  |  |  |  |  |  | my $component = $self->{initialized_components}{$component_id} | 
| 147 | 8 |  |  |  |  | 30 | = $self->initialize_component($params{component_name}, %fields); | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | $params{component_current_parent}[-1]->add_child($component) | 
| 150 | 8 | 100 |  |  |  | 102 | if $params{component_current_parent}[-1]; | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 8 |  |  |  |  | 6 | push @{$params{component_current_parent}}, $component; | 
|  | 8 |  |  |  |  | 11 |  | 
| 153 | 8 |  |  |  |  | 21 | $params{node}->attr('data-pure-component-id'=>$component_id); | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 8 | 50 |  |  |  | 179 | %params = $component->on_process_components($self, %params) | 
| 156 |  |  |  |  |  |  | if $component->can('on_process_components'); | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 8 |  |  |  |  | 7 | push @{$params{directives}}, ( | 
|  | 8 |  |  |  |  | 45 |  | 
| 159 |  |  |  |  |  |  | "^*[data-pure-component-id=$component_id]", | 
| 160 |  |  |  |  |  |  | $component->prepare_render_callback ); | 
| 161 |  |  |  |  |  |  |  | 
| 162 | 8 |  |  |  |  | 12 | $params{cnt}++; | 
| 163 | 8 |  |  |  |  | 51 | return %params; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | sub _process_node { | 
| 167 | 886 |  |  | 886 |  | 1307 | my ($self, %params) = @_; | 
| 168 | 886 | 100 |  |  |  | 1349 | if($params{node}->type eq 'pi') { | 
| 169 | 6 |  |  |  |  | 49 | %params = $self->_process_pi(%params); | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 886 |  |  |  |  | 6236 | my $component_name; | 
| 173 | 886 | 100 | 100 |  |  | 1352 | if(($component_name) = (($params{node}->tag||'') =~m/^pure\-(.+)?/)) { | 
| 174 | 8 | 100 |  |  |  | 130 | $params{component_current_parent} = [] unless defined $params{component_current_parent}; | 
| 175 | 8 |  |  |  |  | 12 | $params{component_name} = $component_name; | 
| 176 | 8 |  |  |  |  | 26 | %params = $self->_process_components(%params); | 
| 177 | 8 |  |  |  |  | 18 | delete $params{component_name}; | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  | $params{node}->child_nodes->each(sub { | 
| 180 | 758 |  |  | 758 |  | 19544 | %params = $self->_process_node(%params, node=>$_); | 
| 181 | 886 |  |  |  |  | 8777 | }); | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 886 | 100 | 100 |  |  | 16165 | pop @{$params{component_current_parent}} if defined $params{component_current_parent} && $component_name; | 
|  | 8 |  |  |  |  | 15 |  | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 886 |  |  |  |  | 3492 | return %params; | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | sub _prepare_dom { | 
| 189 | 50 |  |  | 50 |  | 71 | my ($self, $template) = @_; | 
| 190 | 50 |  |  |  |  | 74 | my @directives = (); | 
| 191 | 50 |  |  |  |  | 286 | my $dom = Mojo::DOM58->new($template); | 
| 192 | 50 |  |  |  |  | 28285 | my %params = (cnt=>0, node=>$dom, directives=>\@directives); | 
| 193 | 50 |  |  |  |  | 160 | my $nodes = $dom->child_nodes; | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | $nodes->each(sub { | 
| 196 | 128 |  |  | 128 |  | 758 | %params = $self->_process_node(%params, node=>$_); | 
| 197 | 50 |  |  |  |  | 4360 | }); | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 50 |  |  |  |  | 141 | return ($dom, @{$params{directives}}); | 
|  | 50 |  |  |  |  | 211 |  | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 | 54 |  |  | 54 | 0 | 5936 | sub clone_dom { return dclone(shift->{dom}) } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | sub render { | 
| 205 | 54 |  |  | 54 | 1 | 473 | my ($self, $data_proto, $extra_directives) = @_; | 
| 206 | 54 |  |  |  |  | 262 | $data_proto = Template::Pure::DataProxy->new($data_proto, self=>$self); | 
| 207 | 54 | 50 |  |  |  | 151 | $extra_directives = [] unless $extra_directives; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 54 |  |  |  |  | 169 | my $dom = $self->clone_dom; | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 54 |  |  |  |  | 201 | return $self->process_dom($dom, $data_proto, $extra_directives)->to_string; | 
| 212 |  |  |  |  |  |  | } | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | sub process_dom { | 
| 215 | 54 |  |  | 54 | 1 | 113 | my ($self, $dom, $data_proto, $extra_directives) = @_; | 
| 216 |  |  |  |  |  |  | return $self->_process_dom_recursive( | 
| 217 |  |  |  |  |  |  | $data_proto, | 
| 218 |  |  |  |  |  |  | $dom, | 
| 219 | 54 |  |  |  |  | 106 | @{$self->{directives}}, | 
| 220 | 54 | 50 |  |  |  | 75 | @{$extra_directives||[]}, | 
|  | 54 |  |  |  |  | 268 |  | 
| 221 |  |  |  |  |  |  | ); | 
| 222 |  |  |  |  |  |  | } | 
| 223 |  |  |  |  |  |  |  | 
| 224 | 16 |  |  | 16 | 0 | 57 | 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 | 4322 | sub encoded_string { Template::Pure::EncodedString->new($_[1]) } | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | my %match_specs = (); | 
| 229 | 245 |  | 100 | 245 | 0 | 231 | sub parse_match_spec { return %{ $match_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_match_spec($_[1])} } } | 
|  | 245 |  |  |  |  | 1043 |  | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | my %data_specs = (); | 
| 232 | 173 |  | 100 | 173 | 0 | 137 | sub parse_data_spec { return %{ $data_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_data_spec($_[1])} } } | 
|  | 173 |  |  |  |  | 657 |  | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | my %data_templates = (); | 
| 235 | 11 |  | 100 | 11 | 0 | 17 | sub parse_data_template { return @{ $data_templates{$_[1]} ||= [Template::Pure::ParseUtils::parse_data_template($_[1])] } } | 
|  | 11 |  |  |  |  | 48 |  | 
| 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 |  |  |  |  | 34 |  | 
| 240 |  |  |  |  |  |  | } | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | my %itr_specs = (); | 
| 243 | 17 |  | 100 | 17 | 0 | 16 | sub parse_itr_spec { return %{ $itr_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_itr_spec($_[1])} } } | 
|  | 17 |  |  |  |  | 83 |  | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | sub data_at_path { | 
| 246 | 7 |  |  | 7 | 1 | 1706 | my ($self, $data, $path) = @_; | 
| 247 | 7 |  |  |  |  | 11 | my %data_spec = $self->parse_data_spec($path); | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 7 | 50 | 66 |  |  | 44 | unless(Scalar::Util::blessed($data) and $data->isa('Template::Pure::DataContext') ) { | 
| 250 | 7 |  |  |  |  | 15 | $data = Template::Pure::DataContext->new($data, $self->{root_data}); | 
| 251 |  |  |  |  |  |  | } | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 7 |  |  |  |  | 19 | return $self->_value_from_data($data, %data_spec); | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | sub at_or_die { | 
| 257 | 28 |  |  | 28 | 0 | 32 | my ($self, $dom, $css) = @_; | 
| 258 | 28 | 100 |  |  |  | 70 | my $new = $css eq '.' ? $dom : $dom->at($css); | 
| 259 | 28 | 50 |  |  |  | 2902 | die "$css is not a matching path" unless defined $new; | 
| 260 | 28 |  |  |  |  | 74 | return $new; | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | sub find_or_die { | 
| 264 | 188 |  |  | 188 | 0 | 197 | my ($self, $dom, $css) = @_; | 
| 265 | 188 |  |  |  |  | 418 | my $collection = $dom->find($css); | 
| 266 | 188 | 100 |  |  |  | 54932 | die "Match specification '$css' produces no nodes on $dom" unless $collection->size; | 
| 267 | 187 |  |  |  |  | 766 | return $collection; | 
| 268 |  |  |  |  |  |  | } | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | sub _process_dom_recursive { | 
| 271 | 117 |  |  | 117 |  | 4416 | my ($self, $data_proto, $dom, @directives) = @_; | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 117 | 100 |  |  |  | 311 | $self->{root_data} = $data_proto unless exists $self->{root_data}; | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 117 |  |  |  |  | 519 | my $data = Template::Pure::DataContext->new($data_proto, $self->{root_data}); | 
| 276 |  |  |  |  |  |  |  | 
| 277 | 117 |  |  |  |  | 287 | ($data, @directives) = $self->_process_directive_instructions($dom, $data, @directives); | 
| 278 |  |  |  |  |  |  |  | 
| 279 | 117 |  |  |  |  | 267 | while(@directives) { | 
| 280 | 233 |  |  |  |  | 10185 | my $directive = shift @directives; | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 233 | 100 | 100 |  |  | 1091 | if(ref($directive)||'' eq 'CODE') { | 
| 283 | 1 |  |  |  |  | 3 | $directive->($self, $dom, $data); | 
| 284 | 1 |  |  |  |  | 486 | next; | 
| 285 |  |  |  |  |  |  | } | 
| 286 |  |  |  |  |  |  |  | 
| 287 | 232 | 100 |  |  |  | 458 | if($directive =~/\=\{/g) { | 
| 288 |  |  |  |  |  |  | $directive = join '', map { | 
| 289 | 1 | 100 |  |  |  | 4 | ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_; | 
|  | 2 |  |  |  |  | 9 |  | 
| 290 |  |  |  |  |  |  | } $self->parse_data_template($directive); | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  |  | 
| 293 | 232 |  |  |  |  | 404 | my %match_spec = $self->parse_match_spec($directive); | 
| 294 | 232 |  |  |  |  | 369 | my $action_proto = shift @directives; | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 232 | 50 |  |  |  | 395 | $dom = $dom->root if $match_spec{absolute}; | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 232 | 100 | 100 |  |  | 1746 | if($match_spec{mode} eq 'filter') { | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 299 | 1 |  |  |  |  | 12 | $self->_process_dom_filter($dom, $data, $match_spec{css}, $action_proto); | 
| 300 |  |  |  |  |  |  | } elsif((ref($action_proto)||'') eq 'HASH') { | 
| 301 | 24 |  |  |  |  | 37 | $self->_process_sub_data($dom, $data, \%match_spec, %{$action_proto}); | 
|  | 24 |  |  |  |  | 87 |  | 
| 302 |  |  |  |  |  |  | } elsif((ref($action_proto)||'') eq 'ARRAY') { | 
| 303 | 17 |  |  |  |  | 47 | $self->process_sub_directives($dom, $data->value, $match_spec{css}, @{$action_proto}); | 
|  | 17 |  |  |  |  | 66 |  | 
| 304 |  |  |  |  |  |  | } elsif((ref($action_proto)||'') eq 'CODE') { | 
| 305 | 21 |  |  |  |  | 61 | $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 |  |  |  |  | 369 | my $value_proto = $self->_value_from_action_proto($dom, $data, $action_proto, %match_spec); | 
| 310 | 159 |  |  |  |  | 573 | $self->_process_value_proto($dom, $data, $value_proto, %match_spec); | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  | } | 
| 313 |  |  |  |  |  |  |  | 
| 314 | 115 |  |  |  |  | 15377 | return $dom; | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | sub _process_value_proto { | 
| 318 | 186 |  |  | 186 |  | 420 | my ($self, $dom, $data, $value_proto, %match_spec) = @_; | 
| 319 | 186 | 100 | 100 |  |  | 1287 | if( | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 320 |  |  |  |  |  |  | Scalar::Util::blessed($value_proto) && | 
| 321 |  |  |  |  |  |  | ($value_proto->isa('Template::Pure') || $value_proto->can('TO_HTML')) | 
| 322 |  |  |  |  |  |  | ) { | 
| 323 | 9 |  |  |  |  | 25 | $self->_process_obj($dom, $data, $value_proto, %match_spec); | 
| 324 |  |  |  |  |  |  | } elsif((ref($value_proto)||'') eq 'CODE') { | 
| 325 | 2 |  |  |  |  | 9 | $self->_process_code($dom, $data, $value_proto, %match_spec); | 
| 326 |  |  |  |  |  |  | } else { | 
| 327 | 175 |  |  |  |  | 392 | $self->_process_match_spec($dom, $value_proto, %match_spec); | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | sub _process_obj { | 
| 332 | 19 |  |  | 19 |  | 47 | my ($self, $dom, $data, $obj, %match_spec) = @_; | 
| 333 | 19 |  |  |  |  | 28 | my $css = $match_spec{css}; | 
| 334 |  |  |  |  |  |  |  | 
| 335 | 19 | 100 |  |  |  | 108 | if($obj->isa(ref $self)) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 336 | 15 | 100 |  |  |  | 38 | if($css eq '.') { | 
| 337 | 12 |  |  |  |  | 30 | my $value = $self->_value_from_template_obj($dom, $data, $obj, %match_spec); | 
| 338 | 12 |  |  |  |  | 100 | $self->_process_mode($dom, $value, %match_spec); | 
| 339 |  |  |  |  |  |  | } else { | 
| 340 | 3 |  |  |  |  | 8 | my $collection = $self->find_or_die($dom,$css); | 
| 341 |  |  |  |  |  |  | $collection->each(sub { | 
| 342 |  |  |  |  |  |  |  | 
| 343 | 4 |  |  | 4 |  | 232 | my $content; | 
| 344 | 4 | 100 |  |  |  | 13 | 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 |  |  |  |  | 4 | $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 |  |  |  |  | 14 | my $new_data = Template::Pure::DataProxy->new( | 
| 353 |  |  |  |  |  |  | $data->value, | 
| 354 |  |  |  |  |  |  | content => $self->encoded_string($content)); | 
| 355 |  |  |  |  |  |  |  | 
| 356 | 4 |  |  |  |  | 16 | my $value = $self->encoded_string($obj->render($new_data)); | 
| 357 |  |  |  |  |  |  |  | 
| 358 | 4 |  |  |  |  | 30 | $self->_process_mode($_, $value, %match_spec); | 
| 359 | 3 |  |  |  |  | 19 | }); | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  | } elsif($obj->can('TO_HTML')) { | 
| 362 | 3 | 100 |  |  |  | 6 | if($css eq '.') { | 
| 363 | 1 |  |  |  |  | 3 | 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 |  | 110 | my $value = $obj->TO_HTML($self, $_, $data->value); | 
| 369 | 3 |  |  |  |  | 41 | $self->_process_mode($_, $value, %match_spec); | 
| 370 | 2 |  |  |  |  | 10 | }); | 
| 371 |  |  |  |  |  |  | } | 
| 372 |  |  |  |  |  |  | } elsif($obj->isa('Mojo::DOM58')) { | 
| 373 | 1 |  |  |  |  | 3 | $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 |  | 351 | my ($self, $dom, $data, $action_proto, %match_spec) = @_; | 
| 381 | 178 | 100 | 50 |  |  | 381 | if(ref \$action_proto eq 'SCALAR') { | 
|  |  | 50 |  |  |  |  |  | 
| 382 | 165 |  |  |  |  | 281 | return $self->_value_from_scalar_action($data, $action_proto); | 
| 383 |  |  |  |  |  |  | } elsif((ref($action_proto)||'') eq 'SCALAR') { | 
| 384 | 13 |  |  |  |  | 29 | 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 |  | 183 | 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 |  |  |  |  | 228 | my $first_pipe = index($action_proto, '|'); | 
| 397 | 165 |  |  |  |  | 145 | my $first_open = index($action_proto, '={'); | 
| 398 |  |  |  |  |  |  |  | 
| 399 | 165 | 100 | 100 |  |  | 658 | 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 |  |  |  | 22 | ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_; | 
|  | 31 |  |  |  |  | 94 |  | 
| 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 |  |  | 49 | if(Scalar::Util::blessed($parts[-1]) && (index("$parts[-1]", '|') >0) ) { | 
| 415 | 3 |  |  |  |  | 5 | my $last = substr "$parts[-1]", 0, index("$parts[-1]", '|'); | 
| 416 | 3 |  |  |  |  | 14 | $last=~s/\s+$//; | 
| 417 | 3 |  |  |  |  | 8 | my %data_spec = $self->parse_data_spec(pop @parts); | 
| 418 | 3 |  |  |  |  | 23 | my $return = join('', @parts, $last); | 
| 419 | 3 |  |  |  |  | 2 | foreach my $filter (@{$data_spec{filters}}) { | 
|  | 3 |  |  |  |  | 5 |  | 
| 420 | 3 |  |  |  |  | 5 | $return = $self->_apply_data_filter($return, $data, $filter); | 
| 421 |  |  |  |  |  |  | } | 
| 422 | 3 |  |  |  |  | 10 | return $return; | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  |  | 
| 425 | 7 |  |  |  |  | 64 | return join('', @parts); | 
| 426 |  |  |  |  |  |  | } else { | 
| 427 | 155 |  |  |  |  | 257 | my %data_spec = $self->parse_data_spec($action_proto); | 
| 428 | 155 | 100 |  |  |  | 339 | if(defined(my $literal = $data_spec{literal})) { | 
| 429 | 2 |  |  |  |  | 5 | return $literal; | 
| 430 |  |  |  |  |  |  | } else { | 
| 431 | 153 |  |  |  |  | 357 | return $self->_value_from_data($data, %data_spec); | 
| 432 |  |  |  |  |  |  | } | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | sub _process_code { | 
| 437 | 23 |  |  | 23 |  | 49 | my ($self, $dom, $data, $code, %match_spec) = @_; | 
| 438 | 23 |  |  |  |  | 32 | my $css = $match_spec{css}; | 
| 439 | 23 | 100 |  |  |  | 45 | if($css eq '.') { | 
| 440 | 8 |  |  |  |  | 22 | my $value = $self->_call_coderef($code, $dom, $data->value); | 
| 441 | 8 |  |  |  |  | 26 | $self->_process_value_proto($dom, $data, $value, %match_spec); | 
| 442 |  |  |  |  |  |  | } else { | 
| 443 | 15 |  |  |  |  | 67 | my $collection = $self->find_or_die($dom,$css); | 
| 444 |  |  |  |  |  |  | $collection->each(sub { | 
| 445 | 19 |  |  | 19 |  | 241 | my $value = $self->_call_coderef($code, $_, $data->value); | 
| 446 | 19 |  |  |  |  | 155 | my %local_match_spec = (%match_spec, css=>'.'); | 
| 447 | 19 |  |  |  |  | 53 | $self->_process_value_proto($_, $data, $value, %local_match_spec); | 
| 448 | 15 |  |  |  |  | 74 | }); | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  | } | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | sub _call_coderef { | 
| 453 | 27 |  |  | 27 |  | 33 | my ($self, $code, $dom, $value) = @_; | 
| 454 | 27 |  |  |  |  | 62 | return $self->$code($dom, $value); | 
| 455 |  |  |  |  |  |  | } | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | sub _value_from_template_obj { | 
| 458 | 15 |  |  | 15 |  | 43 | my ($self, $dom, $data, $template, %match_spec) = @_; | 
| 459 | 15 |  |  |  |  | 35 | my $content = $self->_value_from_dom($dom, \%match_spec); | 
| 460 | 15 |  |  |  |  | 283 | my $new_data = Template::Pure::DataProxy->new( | 
| 461 |  |  |  |  |  |  | $data->value, | 
| 462 |  |  |  |  |  |  | content => $self->encoded_string($content)); | 
| 463 |  |  |  |  |  |  |  | 
| 464 | 15 |  |  |  |  | 45 | return $self->encoded_string($template->render($new_data)); | 
| 465 |  |  |  |  |  |  | } | 
| 466 |  |  |  |  |  |  |  | 
| 467 |  |  |  |  |  |  | sub _process_directive_instructions { | 
| 468 | 117 |  |  | 117 |  | 248 | my ($self, $dom, $data, @directives) = @_; | 
| 469 | 117 | 100 | 100 |  |  | 474 | if( (ref($directives[0])||'') eq 'HASH') { | 
| 470 | 9 |  |  |  |  | 13 | my %map = %{shift(@directives)}; | 
|  | 9 |  |  |  |  | 32 |  | 
| 471 | 9 |  |  |  |  | 11 | my %new_data; | 
| 472 | 9 |  |  |  |  | 17 | foreach my $key (keys %map) { | 
| 473 | 19 |  |  |  |  | 1939 | $new_data{$key} = $self->_value_from_action_proto($dom, $data, $map{$key}); | 
| 474 |  |  |  |  |  |  | } | 
| 475 | 9 |  |  |  |  | 370 | $data = Template::Pure::DataContext->new(\%new_data); | 
| 476 |  |  |  |  |  |  | } | 
| 477 | 117 |  |  |  |  | 367 | return ($data, @directives); | 
| 478 |  |  |  |  |  |  | } | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | sub _process_sub_data { | 
| 481 | 24 |  |  | 24 |  | 45 | 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 |  |  |  |  | 34 | my $css = $match_spec->{css}; | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | # Pull out any sort or filters | 
| 490 | 24 | 100 |  |  |  | 55 | my $sort_cb = exists $action{order_by} ? delete $action{order_by} : undef; | 
| 491 | 24 | 100 |  |  |  | 51 | my $grep_cb = exists $action{grep} ? delete $action{grep} : undef; | 
| 492 | 24 | 50 |  |  |  | 45 | my $filter_cb = exists $action{filter} ? delete $action{filter} : undef; | 
| 493 | 24 | 50 |  |  |  | 43 | my $display_fields = exists $action{display_fields} ? delete $action{display_fields} : undef; | 
| 494 | 24 | 100 |  |  |  | 43 | my $following_directives = exists $action{directives} ? delete $action{directives} : undef; | 
| 495 | 24 |  |  |  |  | 40 | my ($sub_data_proto, $sub_data_action) = %action; | 
| 496 |  |  |  |  |  |  |  | 
| 497 | 24 | 100 |  |  |  | 76 | if(index($sub_data_proto,'<-') > 0) { | 
| 498 |  |  |  |  |  |  |  | 
| 499 | 17 | 100 |  |  |  | 49 | if(ref \$sub_data_action eq 'SCALAR') { | 
| 500 | 5 |  |  |  |  | 6 | my $new_match_spec = '.'; | 
| 501 | 5 | 100 |  |  |  | 10 | $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append'; | 
| 502 | 5 | 50 |  |  |  | 10 | $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend'; | 
| 503 | 5 | 50 |  |  |  | 11 | $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 |  |  |  |  | 7 | $sub_data_action = [ $new_match_spec => $sub_data_action ]; | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  |  | 
| 508 | 17 | 100 |  |  |  | 46 | if(ref $sub_data_action eq 'CODE') { | 
| 509 | 2 |  |  |  |  | 3 | my $new_match_spec = '.'; | 
| 510 | 2 | 50 |  |  |  | 4 | $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append'; | 
| 511 | 2 | 100 |  |  |  | 5 | $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend'; | 
| 512 | 2 | 50 |  |  |  | 5 | $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter'; | 
| 513 | 2 | 50 |  |  |  | 3 | $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node'; | 
| 514 | 2 |  |  |  |  | 8 | $sub_data_action = [ $new_match_spec => $sub_data_action ]; | 
| 515 |  |  |  |  |  |  | } | 
| 516 |  |  |  |  |  |  |  | 
| 517 | 17 | 100 |  |  |  | 53 | if(Scalar::Util::blessed($sub_data_action)) { | 
| 518 | 2 |  |  |  |  | 4 | my $new_match_spec = '.'; | 
| 519 | 2 | 50 |  |  |  | 6 | $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 |  |  |  | 6 | $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node'; | 
| 523 | 2 |  |  |  |  | 4 | $sub_data_action = [ $new_match_spec => $sub_data_action ]; | 
| 524 |  |  |  |  |  |  | } | 
| 525 |  |  |  |  |  |  |  | 
| 526 | 17 | 50 |  |  |  | 43 | die "Action for '$sub_data_proto' must be an arrayref of new directives" | 
| 527 |  |  |  |  |  |  | unless ref $sub_data_action eq 'ARRAY'; | 
| 528 |  |  |  |  |  |  |  | 
| 529 | 17 |  |  |  |  | 42 | my ($new_key, $itr_data_spec) = $self->parse_itr_spec($sub_data_proto); | 
| 530 | 17 |  |  |  |  | 82 | 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 |  |  |  | 47 | return $self->_process_match_spec($dom, $itr_data_proto, %$match_spec) | 
| 538 |  |  |  |  |  |  | if $self->_value_is_undef($itr_data_proto); | 
| 539 |  |  |  |  |  |  |  | 
| 540 | 16 |  |  |  |  | 27 | my %options; | 
| 541 | 16 | 50 |  |  |  | 33 | if($display_fields) { | 
| 542 | 0 |  |  |  |  | 0 | $options{display_fields} = $display_fields; | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  |  | 
| 545 | 16 | 100 |  |  |  | 33 | if($sort_cb) { | 
| 546 | 2 | 50 |  |  |  | 7 | 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 |  |  |  | 7 | die "the 'sort' key must point to an anonymous subroutine" unless ref($sort_cb) eq 'CODE'; | 
| 552 | 2 |  |  |  |  | 4 | $options{sort} = $sort_cb; | 
| 553 |  |  |  |  |  |  | } | 
| 554 | 16 | 100 |  |  |  | 37 | 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 |  |  |  |  | 4 | 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 |  |  |  |  | 2 | $options{grep} = $grep_cb; | 
| 562 |  |  |  |  |  |  | } | 
| 563 | 16 | 50 |  |  |  | 28 | 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 |  |  |  | 37 | $options{display_fields} = $display_fields if $display_fields; | 
| 574 |  |  |  |  |  |  |  | 
| 575 | 16 |  |  |  |  | 88 | my $iterator = Template::Pure::Iterator->from_proto($itr_data_proto, $self, \%options); | 
| 576 |  |  |  |  |  |  |  | 
| 577 | 16 | 50 |  |  |  | 37 | if($css eq '.') { | 
| 578 | 0 |  |  |  |  | 0 | $self->_process_iterator($dom, $new_key, $iterator, @{$sub_data_action}); | 
|  | 0 |  |  |  |  | 0 |  | 
| 579 |  |  |  |  |  |  | } else { | 
| 580 | 16 |  |  |  |  | 40 | my $collection = $self->find_or_die($dom,$css); | 
| 581 |  |  |  |  |  |  | $collection->each(sub { | 
| 582 | 17 |  |  | 17 |  | 194 | $self->_process_iterator($_, $new_key, $iterator, @{$sub_data_action}); | 
|  | 17 |  |  |  |  | 49 |  | 
| 583 | 16 |  |  |  |  | 77 | }); | 
| 584 |  |  |  |  |  |  | } | 
| 585 |  |  |  |  |  |  | } else { | 
| 586 | 7 |  |  |  |  | 14 | 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 |  |  |  |  | 7 | $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 |  |  |  |  | 7 | my $new_data = Template::Pure::DataContext->new($value); | 
| 594 | 3 |  |  |  |  | 19 | 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 |  |  |  | 4661 | 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 |  | 31 | my ($self, $dom, $key, $iterator, @actions) = @_; | 
| 609 | 17 |  |  |  |  | 1893 | my $template = dclone($dom); | 
| 610 | 17 |  |  |  |  | 62 | while(my $datum = $iterator->next) { | 
| 611 | 41 |  |  |  |  | 38 | $datum = $$datum; | 
| 612 | 41 |  |  |  |  | 77 | my $new_dom = Mojo::DOM58->new($template); | 
| 613 | 41 |  |  |  |  | 7704 | my $new_data; | 
| 614 | 41 | 100 |  |  |  | 67 | if($key eq '.') { | 
| 615 | 2 |  |  |  |  | 5 | $new_data = Template::Pure::DataProxy | 
| 616 |  |  |  |  |  |  | ->new($datum, i=>$iterator); | 
| 617 |  |  |  |  |  |  | } else { | 
| 618 | 39 |  |  |  |  | 76 | $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 |  |  |  |  | 171 | $dom->replace($new_dom); | 
| 625 |  |  |  |  |  |  | } | 
| 626 | 17 |  |  |  |  | 46 | $dom->remove; | 
| 627 |  |  |  |  |  |  | } | 
| 628 |  |  |  |  |  |  |  | 
| 629 |  |  |  |  |  |  | sub process_sub_directives { | 
| 630 | 22 |  |  | 22 | 0 | 45 | my ($self, $dom, $data, $css, @directives) = @_; | 
| 631 | 22 | 100 |  |  |  | 48 | if($css eq '.') { | 
| 632 | 2 |  |  |  |  | 6 | $self->_process_dom_recursive($data, $dom, @directives); | 
| 633 |  |  |  |  |  |  | } else { | 
| 634 | 20 |  |  |  |  | 35 | my $collection = $self->find_or_die($dom,$css); | 
| 635 |  |  |  |  |  |  | $collection->each(sub { | 
| 636 | 20 |  |  | 20 |  | 145 | $self->_process_dom_recursive($data, $_, @directives); | 
| 637 | 20 |  |  |  |  | 93 | }); | 
| 638 |  |  |  |  |  |  | } | 
| 639 |  |  |  |  |  |  | } | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | sub _value_from_dom { | 
| 642 | 28 |  |  | 28 |  | 23 | my $self = shift; | 
| 643 | 28 |  |  |  |  | 26 | my $dom = shift; | 
| 644 | 28 | 100 |  |  |  | 56 | my %match_spec = ref $_[0] ? %{$_[0]} : $self->parse_match_spec($_[0]); | 
|  | 15 |  |  |  |  | 44 |  | 
| 645 |  |  |  |  |  |  |  | 
| 646 | 28 | 100 |  |  |  | 67 | $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 |  |  |  | 225 | 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 |  |  |  |  | 28 | 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 |  |  |  |  | 30 | 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 |  |  |  |  | 3 | return $self->at_or_die($dom, $match_spec{css})->attr($attr); | 
| 665 |  |  |  |  |  |  | } | 
| 666 |  |  |  |  |  |  | } | 
| 667 |  |  |  |  |  |  |  | 
| 668 |  |  |  |  |  |  | sub _value_from_data { | 
| 669 | 207 |  |  | 207 |  | 345 | my ($self, $data, %data_spec) = @_; | 
| 670 | 207 |  |  |  |  | 532 | my $value = $data->at(%data_spec)->value; | 
| 671 | 207 |  |  |  |  | 417 | foreach my $filter (@{$data_spec{filters}}) { | 
|  | 207 |  |  |  |  | 311 |  | 
| 672 | 13 |  |  |  |  | 28 | $value = $self->_apply_data_filter($value, $data, $filter); | 
| 673 |  |  |  |  |  |  | } | 
| 674 | 207 |  |  |  |  | 616 | return $value; | 
| 675 |  |  |  |  |  |  | } | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | sub _apply_data_filter { | 
| 678 | 16 |  |  | 16 |  | 20 | my ($self, $value, $data, $filter) = @_; | 
| 679 | 16 |  |  |  |  | 22 | my ($name, @args) = @$filter; | 
| 680 | 16 | 100 |  |  |  | 20 | @args = map { ref $_ ? $self->_value_from_data($data, %$_) : $_ } @args; | 
|  | 10 |  |  |  |  | 27 |  | 
| 681 | 16 |  |  |  |  | 33 | 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 |  |  |  |  | 2 | my $collection = $self->find_or_die($dom,$css); | 
| 690 |  |  |  |  |  |  | $collection->each(sub { | 
| 691 | 1 |  |  | 1 |  | 7 | $cb->($self, $_, $data); | 
| 692 | 1 |  |  |  |  | 5 | }); | 
| 693 |  |  |  |  |  |  | } | 
| 694 |  |  |  |  |  |  | } | 
| 695 |  |  |  |  |  |  |  | 
| 696 |  |  |  |  |  |  | sub _process_match_spec { | 
| 697 | 180 |  |  | 180 |  | 348 | my ($self, $dom, $value, %match_spec) = @_; | 
| 698 | 180 | 100 |  |  |  | 276 | if($match_spec{css} eq '.') { | 
| 699 | 49 |  |  |  |  | 110 | $self->_process_mode($dom, $value, %match_spec); | 
| 700 |  |  |  |  |  |  | } else { | 
| 701 | 131 |  |  |  |  | 219 | my $collection = $self->find_or_die($dom,$match_spec{css}); | 
| 702 |  |  |  |  |  |  | $collection->each(sub { | 
| 703 | 132 |  |  | 132 |  | 1223 | $self->_process_mode($_, $value, %match_spec); | 
| 704 | 130 |  |  |  |  | 560 | }); | 
| 705 |  |  |  |  |  |  | } | 
| 706 |  |  |  |  |  |  | } | 
| 707 |  |  |  |  |  |  |  | 
| 708 |  |  |  |  |  |  | sub _value_is_undef { | 
| 709 | 347 |  |  | 347 |  | 304 | my ($self, $value) = @_; | 
| 710 | 347 | 100 |  |  |  | 494 | return 1 if !defined($value); | 
| 711 | 335 | 100 | 100 |  |  | 976 | return 1 if Scalar::Util::blessed($value) && $value->isa('Template::Pure::UndefObject'); | 
| 712 | 334 |  |  |  |  | 772 | return 0; | 
| 713 |  |  |  |  |  |  | } | 
| 714 |  |  |  |  |  |  |  | 
| 715 |  |  |  |  |  |  |  | 
| 716 |  |  |  |  |  |  | sub _process_mode { | 
| 717 | 201 |  |  | 201 |  | 421 | my ($self, $dom, $value, %match_spec) = @_; | 
| 718 |  |  |  |  |  |  |  | 
| 719 | 201 |  |  |  |  | 212 | my $mode = $match_spec{mode}; | 
| 720 | 201 |  |  |  |  | 158 | my $target = $match_spec{target}; | 
| 721 | 201 |  |  |  |  | 360 | my $safe_value = $self->escape_html($value); | 
| 722 |  |  |  |  |  |  |  | 
| 723 |  |  |  |  |  |  | ## This behavior may be tweaked in the future. | 
| 724 | 201 | 100 |  |  |  | 328 | if($self->_value_is_undef($safe_value)) { | 
| 725 | 12 | 50 |  |  |  | 34 | 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 |  |  |  |  | 14 | return; | 
| 732 |  |  |  |  |  |  | } else { | 
| 733 | 5 |  |  |  |  | 17 | return $dom->remove; # TODO, or should this remove just the content..? | 
| 734 |  |  |  |  |  |  | } | 
| 735 |  |  |  |  |  |  | } elsif(my $attr = $$target) { | 
| 736 | 3 |  |  |  |  | 6 | return delete $dom->attr->{$attr}; | 
| 737 |  |  |  |  |  |  | } | 
| 738 |  |  |  |  |  |  | } | 
| 739 |  |  |  |  |  |  |  | 
| 740 | 189 | 100 |  |  |  | 332 | if($mode eq 'replace') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 741 | 161 | 100 |  |  |  | 262 | if($target eq 'content') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 742 | 129 | 50 |  |  |  | 161 | $dom->content($safe_value) unless $self->_value_is_undef($safe_value); | 
| 743 |  |  |  |  |  |  | } elsif($target eq 'node') { | 
| 744 | 21 |  |  |  |  | 62 | $dom->replace($safe_value); | 
| 745 |  |  |  |  |  |  | } elsif(my $attr = $$target) { | 
| 746 | 11 |  |  |  |  | 31 | $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 |  |  |  | 56 | if($target eq 'content') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 752 | 13 |  |  |  |  | 35 | $dom->append_content($safe_value); | 
| 753 |  |  |  |  |  |  | } elsif($target eq 'node') { | 
| 754 | 5 |  |  |  |  | 46 | $dom->append($safe_value); | 
| 755 |  |  |  |  |  |  | } elsif(my $attr = $$target) { | 
| 756 | 5 |  | 100 |  |  | 10 | my $current_attr = $dom->attr($attr)||''; | 
| 757 | 5 |  |  |  |  | 86 | $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 |  |  |  | 13 | if($target eq 'content') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 763 | 3 |  |  |  |  | 10 | $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 |  |  | 3 | my $current_attr = $dom->attr($attr)||''; | 
| 768 | 1 |  |  |  |  | 16 | $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 | 22 | my ($self, $name, $data, @args) = @_; | 
| 779 |  |  |  |  |  |  | my %filters = ( | 
| 780 |  |  |  |  |  |  | $self->default_filters, | 
| 781 | 16 |  |  |  |  | 25 | %{$self->{filters}} | 
|  | 16 |  |  |  |  | 112 |  | 
| 782 |  |  |  |  |  |  | ); | 
| 783 |  |  |  |  |  |  |  | 
| 784 | 16 |  | 50 |  |  | 55 | my $filter = $filters{$name} || | 
| 785 |  |  |  |  |  |  | die "Filter $name does not exist"; | 
| 786 |  |  |  |  |  |  |  | 
| 787 | 16 |  |  |  |  | 40 | 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's always burning, but | 
| 864 |  |  |  |  |  |  | there'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&#39;s always | 
| 2063 |  |  |  |  |  |  | burning but there&#39;s never enough wood. | 
| 2064 |  |  |  |  |  |  | </section> | 
| 2065 |  |  |  |  |  |  | <p id="foot">Here'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 </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 </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: </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: </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: Once</title> | 
| 2836 |  |  |  |  |  |  | </head> | 
| 2837 |  |  |  |  |  |  | <body> | 
| 2838 |  |  |  |  |  |  | <section> | 
| 2839 |  |  |  |  |  |  | <h1>Once</h1> | 
| 2840 |  |  |  |  |  |  | <p>By: 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: </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 |