line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
26
|
|
|
26
|
|
2098591
|
use strict; |
|
26
|
|
|
|
|
40
|
|
|
26
|
|
|
|
|
596
|
|
2
|
26
|
|
|
26
|
|
97
|
use warnings; |
|
26
|
|
|
|
|
29
|
|
|
26
|
|
|
|
|
1009
|
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Template::Pure; |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.030'; |
7
|
|
|
|
|
|
|
|
8
|
26
|
|
|
26
|
|
11959
|
use Mojo::DOM58; |
|
26
|
|
|
|
|
480510
|
|
|
26
|
|
|
|
|
679
|
|
9
|
26
|
|
|
26
|
|
184
|
use Scalar::Util; |
|
26
|
|
|
|
|
33
|
|
|
26
|
|
|
|
|
826
|
|
10
|
26
|
|
|
26
|
|
11307
|
use Template::Pure::ParseUtils; |
|
26
|
|
|
|
|
267
|
|
|
26
|
|
|
|
|
658
|
|
11
|
26
|
|
|
26
|
|
9477
|
use Template::Pure::Filters; |
|
26
|
|
|
|
|
42
|
|
|
26
|
|
|
|
|
614
|
|
12
|
26
|
|
|
26
|
|
8239
|
use Template::Pure::DataContext; |
|
26
|
|
|
|
|
49
|
|
|
26
|
|
|
|
|
556
|
|
13
|
26
|
|
|
26
|
|
8293
|
use Template::Pure::DataProxy; |
|
26
|
|
|
|
|
36
|
|
|
26
|
|
|
|
|
544
|
|
14
|
26
|
|
|
26
|
|
106
|
use Template::Pure::EncodedString; |
|
26
|
|
|
|
|
24
|
|
|
26
|
|
|
|
|
363
|
|
15
|
26
|
|
|
26
|
|
8174
|
use Template::Pure::Iterator; |
|
26
|
|
|
|
|
43
|
|
|
26
|
|
|
|
|
607
|
|
16
|
26
|
|
|
26
|
|
13615
|
use Storable qw(dclone); |
|
26
|
|
|
|
|
57437
|
|
|
26
|
|
|
|
|
114176
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub new { |
19
|
50
|
|
|
50
|
1
|
65570
|
my ($proto, %args) = @_; |
20
|
50
|
|
33
|
|
|
256
|
my $class = ref($proto) || $proto; |
21
|
|
|
|
|
|
|
|
22
|
50
|
|
50
|
|
|
242
|
my $template = delete($args{template}) || $class->template || die "Can't find a template"; |
23
|
50
|
|
100
|
|
|
198
|
my $directives = delete($args{directives}) || [$class->directives]; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my $self = bless +{ |
26
|
|
|
|
|
|
|
filters => delete($args{filters}) || +{}, |
27
|
|
|
|
|
|
|
directives => $directives, |
28
|
50
|
|
50
|
|
|
570
|
components => delete($args{components}) || +{}, |
|
|
|
100
|
|
|
|
|
29
|
|
|
|
|
|
|
%args, |
30
|
|
|
|
|
|
|
}, $class; |
31
|
|
|
|
|
|
|
|
32
|
50
|
|
|
|
|
181
|
my ($dom, @directives) = $self->_prepare_dom($template); |
33
|
50
|
|
|
|
|
74
|
unshift @directives, @{$self->{directives}}; |
|
50
|
|
|
|
|
221
|
|
34
|
|
|
|
|
|
|
|
35
|
50
|
|
|
|
|
84
|
$self->{dom} = $dom; |
36
|
50
|
|
|
|
|
78
|
$self->{directives} = \@directives; |
37
|
50
|
|
|
|
|
399
|
return $self; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub _process_pi { |
41
|
6
|
|
|
6
|
|
11
|
my ($self, %params) = @_; |
42
|
6
|
|
|
|
|
13
|
my ($target, %attrs) = $self->parse_processing_instruction($params{node}->tree->[1]); |
43
|
6
|
|
|
|
|
11
|
my $ctx = delete $attrs{ctx}; |
44
|
6
|
|
|
|
|
8
|
my $src = delete $attrs{src}; |
45
|
|
|
|
|
|
|
|
46
|
6
|
100
|
|
|
|
17
|
if($target eq 'pure-include') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
47
|
3
|
|
|
|
|
15
|
$params{node}->replace("<span id='include-$params{cnt}'>include placeholder</span>"); |
48
|
3
|
|
|
|
|
632
|
my @include_directives; |
49
|
3
|
100
|
|
|
|
16
|
if($ctx) { |
|
|
50
|
|
|
|
|
|
50
|
1
|
|
|
|
|
6
|
@include_directives = ("#include-$params{cnt}" => +{ $ctx => ['^.' => "/$src"]}); |
51
|
|
|
|
|
|
|
} elsif(%attrs) { |
52
|
2
|
|
|
|
|
6
|
$attrs{$src} = "/$src"; |
53
|
|
|
|
|
|
|
@include_directives = ( |
54
|
|
|
|
|
|
|
"#include-$params{cnt}" => [ |
55
|
|
|
|
|
|
|
\%attrs, |
56
|
|
|
|
|
|
|
'^.' => sub { |
57
|
2
|
|
|
2
|
|
3
|
my ($t, $dom, $data) = @_; |
58
|
2
|
|
|
|
|
6
|
return $data->{$src}; |
59
|
|
|
|
|
|
|
}, |
60
|
2
|
|
|
|
|
18
|
]); |
61
|
|
|
|
|
|
|
} else { |
62
|
0
|
|
|
|
|
0
|
@include_directives = ("^#include-$params{cnt}", $src) |
63
|
|
|
|
|
|
|
} |
64
|
3
|
|
|
|
|
3
|
push @{$params{directives}}, @include_directives; |
|
3
|
|
|
|
|
10
|
|
65
|
|
|
|
|
|
|
} elsif($target eq 'pure-wrapper') { |
66
|
1
|
|
|
|
|
4
|
$params{node}->following('*')->first->attr('data-pure-wrapper-id'=>"wrapper-$params{cnt}"); |
67
|
1
|
|
|
|
|
221
|
$params{node}->remove; |
68
|
1
|
50
|
|
|
|
95
|
if($ctx) { |
|
|
0
|
|
|
|
|
|
69
|
1
|
|
|
|
|
11
|
push @{$params{directives}}, ( |
70
|
|
|
|
|
|
|
"^*[data-pure-wrapper-id=wrapper-$params{cnt}]", +{ $ctx => ['^.' => "/$src"]}, |
71
|
1
|
|
|
1
|
|
3
|
"*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef }, |
72
|
1
|
|
|
|
|
1
|
); |
73
|
|
|
|
|
|
|
} elsif(%attrs) { |
74
|
0
|
|
|
|
|
0
|
$attrs{$src} = "/$src"; |
75
|
0
|
|
|
|
|
0
|
push @{$params{directives}}, ( |
76
|
|
|
|
|
|
|
"^*[data-pure-wrapper-id=wrapper-$params{cnt}]", [\%attrs, '^.' => "$src"], |
77
|
0
|
|
|
0
|
|
0
|
"*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef }, |
78
|
0
|
|
|
|
|
0
|
); |
79
|
|
|
|
|
|
|
} else { |
80
|
0
|
|
|
|
|
0
|
push @{$params{directives}}, ( |
81
|
|
|
|
|
|
|
"^*[data-pure-wrapper-id=wrapper-$params{cnt}]", $src, |
82
|
0
|
|
|
0
|
|
0
|
"*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef }, |
83
|
0
|
|
|
|
|
0
|
); |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
} elsif($target eq 'pure-filter') { |
86
|
1
|
|
|
|
|
4
|
$params{node}->following('*')->first->attr('data-pure-filter-id'=>"filter-$params{cnt}"); |
87
|
1
|
|
|
|
|
174
|
$params{node}->remove; |
88
|
1
|
50
|
|
|
|
100
|
if($ctx) { |
|
|
50
|
|
|
|
|
|
89
|
0
|
|
|
|
|
0
|
push @{$params{directives}}, ( |
90
|
|
|
|
|
|
|
"^*[data-pure-filter-id=filter-$params{cnt}]", +{ $ctx => ['^.' => sub { |
91
|
0
|
|
|
0
|
|
0
|
my ($t, $dom, $data) = @_; |
92
|
0
|
|
|
|
|
0
|
$t->data_at_path($data, "/$src")->($dom); |
93
|
|
|
|
|
|
|
} ]}, |
94
|
0
|
|
|
0
|
|
0
|
"*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef }, |
95
|
0
|
|
|
|
|
0
|
); |
96
|
|
|
|
|
|
|
} elsif(%attrs) { |
97
|
1
|
|
|
|
|
12
|
push @{$params{directives}}, ( |
98
|
|
|
|
|
|
|
"^*[data-pure-filter-id=filter-$params{cnt}]", [\%attrs, '^.' => sub { |
99
|
1
|
|
|
1
|
|
2
|
my ($t, $dom, $data) = @_; |
100
|
1
|
|
|
|
|
6
|
$t->data_at_path($data, "/$src")->($dom); |
101
|
|
|
|
|
|
|
} ], |
102
|
1
|
|
|
1
|
|
3
|
"*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef }, |
103
|
1
|
|
|
|
|
2
|
); |
104
|
|
|
|
|
|
|
} else { |
105
|
0
|
|
|
|
|
0
|
push @{$params{directives}}, ( |
106
|
|
|
|
|
|
|
"^*[data-pure-filter-id=filter-$params{cnt}]", sub { |
107
|
0
|
|
|
0
|
|
0
|
my ($t, $dom, $data) = @_; |
108
|
0
|
|
|
|
|
0
|
$t->data_at_path($data, $src)->($dom); |
109
|
|
|
|
|
|
|
}, |
110
|
0
|
|
|
0
|
|
0
|
"*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef }, |
111
|
0
|
|
|
|
|
0
|
); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
} elsif($target eq 'pure-overlay') { |
114
|
1
|
|
|
|
|
4
|
$params{node}->following('*')->first->attr('data-pure-overlay-id'=>"overlay-$params{cnt}"); |
115
|
1
|
|
|
|
|
371
|
$params{node}->remove; |
116
|
|
|
|
|
|
|
|
117
|
1
|
|
|
|
|
111
|
push @{$params{directives}}, ( |
|
1
|
|
|
|
|
7
|
|
118
|
|
|
|
|
|
|
"^*[data-pure-overlay-id=overlay-$params{cnt}]", [ +{%attrs, src=>$src }, '^.' => 'src'], |
119
|
|
|
|
|
|
|
# "*[data-pure-overlay-id=overlay-$params{cnt}]\@data-pure-overlay-id", sub { undef }, |
120
|
|
|
|
|
|
|
); |
121
|
|
|
|
|
|
|
} else { |
122
|
0
|
|
|
|
|
0
|
warn "Encountering processing instruction $target that I can't process"; |
123
|
|
|
|
|
|
|
} |
124
|
6
|
|
|
|
|
6
|
$params{cnt}++; |
125
|
6
|
|
|
|
|
25
|
return %params; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
8
|
|
|
8
|
0
|
27
|
sub components { shift->{components} } |
129
|
10
|
|
|
10
|
0
|
2334
|
sub initialized_components { shift->{initialized_components} } |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub initialize_component { |
132
|
8
|
|
|
8
|
0
|
12
|
my ($self, $name, %params) = @_; |
133
|
8
|
|
50
|
|
|
21
|
return ($self->components->{$name} || die "No Component $name")->($self, %params); |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub _process_components { |
137
|
8
|
|
|
8
|
|
16
|
my ($self, %params) = @_; |
138
|
|
|
|
|
|
|
my %fields = ( |
139
|
8
|
50
|
|
|
|
15
|
%{$params{node}->attr||+{}}, |
140
|
|
|
|
|
|
|
parent => $params{component_current_parent}[-1]||undef, |
141
|
|
|
|
|
|
|
node => $params{node}, |
142
|
8
|
|
100
|
|
|
9
|
container => $self, |
143
|
|
|
|
|
|
|
); |
144
|
|
|
|
|
|
|
|
145
|
8
|
|
|
|
|
115
|
my $component_id = $params{component_name}.'-'.$params{cnt}; |
146
|
|
|
|
|
|
|
my $component = $self->{initialized_components}{$component_id} |
147
|
8
|
|
|
|
|
21
|
= $self->initialize_component($params{component_name}, %fields); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$params{component_current_parent}[-1]->add_child($component) |
150
|
8
|
100
|
|
|
|
75
|
if $params{component_current_parent}[-1]; |
151
|
|
|
|
|
|
|
|
152
|
8
|
|
|
|
|
7
|
push @{$params{component_current_parent}}, $component; |
|
8
|
|
|
|
|
11
|
|
153
|
8
|
|
|
|
|
17
|
$params{node}->attr('data-pure-component-id'=>$component_id); |
154
|
|
|
|
|
|
|
|
155
|
8
|
50
|
|
|
|
145
|
%params = $component->on_process_components($self, %params) |
156
|
|
|
|
|
|
|
if $component->can('on_process_components'); |
157
|
|
|
|
|
|
|
|
158
|
8
|
|
|
|
|
5
|
push @{$params{directives}}, ( |
|
8
|
|
|
|
|
35
|
|
159
|
|
|
|
|
|
|
"^*[data-pure-component-id=$component_id]", |
160
|
|
|
|
|
|
|
$component->prepare_render_callback ); |
161
|
|
|
|
|
|
|
|
162
|
8
|
|
|
|
|
10
|
$params{cnt}++; |
163
|
8
|
|
|
|
|
37
|
return %params; |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub _process_node { |
167
|
886
|
|
|
886
|
|
1304
|
my ($self, %params) = @_; |
168
|
886
|
100
|
|
|
|
1364
|
if($params{node}->type eq 'pi') { |
169
|
6
|
|
|
|
|
47
|
%params = $self->_process_pi(%params); |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
886
|
|
|
|
|
6244
|
my $component_name; |
173
|
886
|
100
|
100
|
|
|
1372
|
if(($component_name) = (($params{node}->tag||'') =~m/^pure\-(.+)?/)) { |
174
|
8
|
100
|
|
|
|
99
|
$params{component_current_parent} = [] unless defined $params{component_current_parent}; |
175
|
8
|
|
|
|
|
10
|
$params{component_name} = $component_name; |
176
|
8
|
|
|
|
|
24
|
%params = $self->_process_components(%params); |
177
|
8
|
|
|
|
|
17
|
delete $params{component_name}; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
$params{node}->child_nodes->each(sub { |
180
|
758
|
|
|
758
|
|
19658
|
%params = $self->_process_node(%params, node=>$_); |
181
|
886
|
|
|
|
|
8829
|
}); |
182
|
|
|
|
|
|
|
|
183
|
886
|
100
|
100
|
|
|
16201
|
pop @{$params{component_current_parent}} if defined $params{component_current_parent} && $component_name; |
|
8
|
|
|
|
|
9
|
|
184
|
|
|
|
|
|
|
|
185
|
886
|
|
|
|
|
3497
|
return %params; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub _prepare_dom { |
189
|
50
|
|
|
50
|
|
74
|
my ($self, $template) = @_; |
190
|
50
|
|
|
|
|
74
|
my @directives = (); |
191
|
50
|
|
|
|
|
254
|
my $dom = Mojo::DOM58->new($template); |
192
|
50
|
|
|
|
|
28341
|
my %params = (cnt=>0, node=>$dom, directives=>\@directives); |
193
|
50
|
|
|
|
|
173
|
my $nodes = $dom->child_nodes; |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
$nodes->each(sub { |
196
|
128
|
|
|
128
|
|
775
|
%params = $self->_process_node(%params, node=>$_); |
197
|
50
|
|
|
|
|
4297
|
}); |
198
|
|
|
|
|
|
|
|
199
|
50
|
|
|
|
|
149
|
return ($dom, @{$params{directives}}); |
|
50
|
|
|
|
|
217
|
|
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
54
|
|
|
54
|
0
|
5826
|
sub clone_dom { return dclone(shift->{dom}) } |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub render { |
205
|
54
|
|
|
54
|
1
|
650
|
my ($self, $data_proto, $extra_directives) = @_; |
206
|
54
|
|
|
|
|
267
|
$data_proto = Template::Pure::DataProxy->new($data_proto, self=>$self); |
207
|
54
|
50
|
|
|
|
140
|
$extra_directives = [] unless $extra_directives; |
208
|
|
|
|
|
|
|
|
209
|
54
|
|
|
|
|
187
|
my $dom = $self->clone_dom; |
210
|
|
|
|
|
|
|
|
211
|
54
|
|
|
|
|
200
|
return $self->process_dom($dom, $data_proto, $extra_directives)->to_string; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
sub process_dom { |
215
|
54
|
|
|
54
|
1
|
91
|
my ($self, $dom, $data_proto, $extra_directives) = @_; |
216
|
|
|
|
|
|
|
return $self->_process_dom_recursive( |
217
|
|
|
|
|
|
|
$data_proto, |
218
|
|
|
|
|
|
|
$dom, |
219
|
54
|
|
|
|
|
109
|
@{$self->{directives}}, |
220
|
54
|
50
|
|
|
|
71
|
@{$extra_directives||[]}, |
|
54
|
|
|
|
|
257
|
|
221
|
|
|
|
|
|
|
); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
16
|
|
|
16
|
0
|
68
|
sub default_filters { Template::Pure::Filters->all } |
225
|
201
|
|
|
201
|
1
|
458
|
sub escape_html { Template::Pure::Filters::escape_html($_[1]) } |
226
|
56
|
|
|
56
|
1
|
4449
|
sub encoded_string { Template::Pure::EncodedString->new($_[1]) } |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
my %match_specs = (); |
229
|
245
|
|
100
|
245
|
0
|
219
|
sub parse_match_spec { return %{ $match_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_match_spec($_[1])} } } |
|
245
|
|
|
|
|
1044
|
|
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
my %data_specs = (); |
232
|
173
|
|
100
|
173
|
0
|
133
|
sub parse_data_spec { return %{ $data_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_data_spec($_[1])} } } |
|
173
|
|
|
|
|
662
|
|
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
my %data_templates = (); |
235
|
11
|
|
100
|
11
|
0
|
11
|
sub parse_data_template { return @{ $data_templates{$_[1]} ||= [Template::Pure::ParseUtils::parse_data_template($_[1])] } } |
|
11
|
|
|
|
|
56
|
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
my %processing_instruction_specs = (); |
238
|
|
|
|
|
|
|
sub parse_processing_instruction { |
239
|
6
|
|
50
|
6
|
0
|
35
|
return @{ $processing_instruction_specs{$_[1]} ||= [Template::Pure::ParseUtils::parse_processing_instruction($_[1])] }; |
|
6
|
|
|
|
|
42
|
|
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
my %itr_specs = (); |
243
|
17
|
|
100
|
17
|
0
|
15
|
sub parse_itr_spec { return %{ $itr_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_itr_spec($_[1])} } } |
|
17
|
|
|
|
|
108
|
|
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
sub data_at_path { |
246
|
7
|
|
|
7
|
1
|
2433
|
my ($self, $data, $path) = @_; |
247
|
7
|
|
|
|
|
15
|
my %data_spec = $self->parse_data_spec($path); |
248
|
|
|
|
|
|
|
|
249
|
7
|
50
|
66
|
|
|
46
|
unless(Scalar::Util::blessed($data) and $data->isa('Template::Pure::DataContext') ) { |
250
|
7
|
|
|
|
|
18
|
$data = Template::Pure::DataContext->new($data, $self->{root_data}); |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
7
|
|
|
|
|
18
|
return $self->_value_from_data($data, %data_spec); |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub at_or_die { |
257
|
28
|
|
|
28
|
0
|
34
|
my ($self, $dom, $css) = @_; |
258
|
28
|
100
|
|
|
|
75
|
my $new = $css eq '.' ? $dom : $dom->at($css); |
259
|
28
|
50
|
|
|
|
2866
|
die "$css is not a matching path" unless defined $new; |
260
|
28
|
|
|
|
|
72
|
return $new; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub find_or_die { |
264
|
188
|
|
|
188
|
0
|
181
|
my ($self, $dom, $css) = @_; |
265
|
188
|
|
|
|
|
428
|
my $collection = $dom->find($css); |
266
|
188
|
100
|
|
|
|
56925
|
die "Match specification '$css' produces no nodes on $dom" unless $collection->size; |
267
|
187
|
|
|
|
|
799
|
return $collection; |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub _process_dom_recursive { |
271
|
117
|
|
|
117
|
|
4890
|
my ($self, $data_proto, $dom, @directives) = @_; |
272
|
|
|
|
|
|
|
|
273
|
117
|
100
|
|
|
|
312
|
$self->{root_data} = $data_proto unless exists $self->{root_data}; |
274
|
|
|
|
|
|
|
|
275
|
117
|
|
|
|
|
667
|
my $data = Template::Pure::DataContext->new($data_proto, $self->{root_data}); |
276
|
|
|
|
|
|
|
|
277
|
117
|
|
|
|
|
280
|
($data, @directives) = $self->_process_directive_instructions($dom, $data, @directives); |
278
|
|
|
|
|
|
|
|
279
|
117
|
|
|
|
|
264
|
while(@directives) { |
280
|
233
|
|
|
|
|
10481
|
my $directive = shift @directives; |
281
|
|
|
|
|
|
|
|
282
|
233
|
100
|
100
|
|
|
780
|
if(ref($directive)||'' eq 'CODE') { |
283
|
1
|
|
|
|
|
6
|
$directive->($self, $dom, $data); |
284
|
1
|
|
|
|
|
508
|
next; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
232
|
100
|
|
|
|
471
|
if($directive =~/\=\{/g) { |
288
|
|
|
|
|
|
|
$directive = join '', map { |
289
|
1
|
100
|
|
|
|
6
|
ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_; |
|
2
|
|
|
|
|
15
|
|
290
|
|
|
|
|
|
|
} $self->parse_data_template($directive); |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
232
|
|
|
|
|
387
|
my %match_spec = $self->parse_match_spec($directive); |
294
|
232
|
|
|
|
|
345
|
my $action_proto = shift @directives; |
295
|
|
|
|
|
|
|
|
296
|
232
|
50
|
|
|
|
465
|
$dom = $dom->root if $match_spec{absolute}; |
297
|
|
|
|
|
|
|
|
298
|
232
|
100
|
100
|
|
|
1801
|
if($match_spec{mode} eq 'filter') { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
299
|
1
|
|
|
|
|
13
|
$self->_process_dom_filter($dom, $data, $match_spec{css}, $action_proto); |
300
|
|
|
|
|
|
|
} elsif((ref($action_proto)||'') eq 'HASH') { |
301
|
24
|
|
|
|
|
31
|
$self->_process_sub_data($dom, $data, \%match_spec, %{$action_proto}); |
|
24
|
|
|
|
|
88
|
|
302
|
|
|
|
|
|
|
} elsif((ref($action_proto)||'') eq 'ARRAY') { |
303
|
17
|
|
|
|
|
50
|
$self->process_sub_directives($dom, $data->value, $match_spec{css}, @{$action_proto}); |
|
17
|
|
|
|
|
51
|
|
304
|
|
|
|
|
|
|
} elsif((ref($action_proto)||'') eq 'CODE') { |
305
|
21
|
|
|
|
|
55
|
$self->_process_code($dom, $data, $action_proto, %match_spec); |
306
|
|
|
|
|
|
|
} elsif(Scalar::Util::blessed($action_proto)) { |
307
|
10
|
|
|
|
|
37
|
$self->_process_obj($dom, $data, $action_proto, %match_spec); |
308
|
|
|
|
|
|
|
} else { |
309
|
159
|
|
|
|
|
400
|
my $value_proto = $self->_value_from_action_proto($dom, $data, $action_proto, %match_spec); |
310
|
159
|
|
|
|
|
526
|
$self->_process_value_proto($dom, $data, $value_proto, %match_spec); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
|
314
|
115
|
|
|
|
|
15491
|
return $dom; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
sub _process_value_proto { |
318
|
186
|
|
|
186
|
|
405
|
my ($self, $dom, $data, $value_proto, %match_spec) = @_; |
319
|
186
|
100
|
100
|
|
|
1334
|
if( |
|
|
100
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
320
|
|
|
|
|
|
|
Scalar::Util::blessed($value_proto) && |
321
|
|
|
|
|
|
|
($value_proto->isa('Template::Pure') || $value_proto->can('TO_HTML')) |
322
|
|
|
|
|
|
|
) { |
323
|
9
|
|
|
|
|
27
|
$self->_process_obj($dom, $data, $value_proto, %match_spec); |
324
|
|
|
|
|
|
|
} elsif((ref($value_proto)||'') eq 'CODE') { |
325
|
2
|
|
|
|
|
8
|
$self->_process_code($dom, $data, $value_proto, %match_spec); |
326
|
|
|
|
|
|
|
} else { |
327
|
175
|
|
|
|
|
405
|
$self->_process_match_spec($dom, $value_proto, %match_spec); |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
sub _process_obj { |
332
|
19
|
|
|
19
|
|
66
|
my ($self, $dom, $data, $obj, %match_spec) = @_; |
333
|
19
|
|
|
|
|
29
|
my $css = $match_spec{css}; |
334
|
|
|
|
|
|
|
|
335
|
19
|
100
|
|
|
|
113
|
if($obj->isa(ref $self)) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
336
|
15
|
100
|
|
|
|
30
|
if($css eq '.') { |
337
|
12
|
|
|
|
|
33
|
my $value = $self->_value_from_template_obj($dom, $data, $obj, %match_spec); |
338
|
12
|
|
|
|
|
113
|
$self->_process_mode($dom, $value, %match_spec); |
339
|
|
|
|
|
|
|
} else { |
340
|
3
|
|
|
|
|
17
|
my $collection = $self->find_or_die($dom,$css); |
341
|
|
|
|
|
|
|
$collection->each(sub { |
342
|
|
|
|
|
|
|
|
343
|
4
|
|
|
4
|
|
123
|
my $content; |
344
|
4
|
100
|
|
|
|
12
|
if($match_spec{target} eq 'content') { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
345
|
3
|
|
|
|
|
8
|
$content = $self->encoded_string($_->content); |
346
|
|
|
|
|
|
|
} elsif($match_spec{target} eq 'node') { |
347
|
1
|
|
|
|
|
3
|
$content = $self->encoded_string($_->to_string); |
348
|
0
|
|
|
|
|
0
|
} elsif(my $attr = ${$match_spec{target}}) { |
349
|
0
|
|
|
|
|
0
|
$content = $_->attr($attr); |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
4
|
|
|
|
|
15
|
my $new_data = Template::Pure::DataProxy->new( |
353
|
|
|
|
|
|
|
$data->value, |
354
|
|
|
|
|
|
|
content => $self->encoded_string($content)); |
355
|
|
|
|
|
|
|
|
356
|
4
|
|
|
|
|
14
|
my $value = $self->encoded_string($obj->render($new_data)); |
357
|
|
|
|
|
|
|
|
358
|
4
|
|
|
|
|
23
|
$self->_process_mode($_, $value, %match_spec); |
359
|
3
|
|
|
|
|
18
|
}); |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
} elsif($obj->can('TO_HTML')) { |
362
|
3
|
100
|
|
|
|
8
|
if($css eq '.') { |
363
|
1
|
|
|
|
|
2
|
my $value = $obj->TO_HTML($self, $dom, $data->value); |
364
|
1
|
|
|
|
|
6
|
$self->_process_mode($dom, $value, %match_spec); |
365
|
|
|
|
|
|
|
} else { |
366
|
2
|
|
|
|
|
6
|
my $collection = $self->find_or_die($dom,$css);; |
367
|
|
|
|
|
|
|
$collection->each(sub { |
368
|
3
|
|
|
3
|
|
107
|
my $value = $obj->TO_HTML($self, $_, $data->value); |
369
|
3
|
|
|
|
|
38
|
$self->_process_mode($_, $value, %match_spec); |
370
|
2
|
|
|
|
|
11
|
}); |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} elsif($obj->isa('Mojo::DOM58')) { |
373
|
1
|
|
|
|
|
5
|
$self->_process_match_spec($dom, $obj, %match_spec); |
374
|
|
|
|
|
|
|
} else { |
375
|
0
|
|
|
|
|
0
|
die "Can't process object of type $obj."; |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub _value_from_action_proto { |
380
|
178
|
|
|
178
|
|
363
|
my ($self, $dom, $data, $action_proto, %match_spec) = @_; |
381
|
178
|
100
|
50
|
|
|
373
|
if(ref \$action_proto eq 'SCALAR') { |
|
|
50
|
|
|
|
|
|
382
|
165
|
|
|
|
|
283
|
return $self->_value_from_scalar_action($data, $action_proto); |
383
|
|
|
|
|
|
|
} elsif((ref($action_proto)||'') eq 'SCALAR') { |
384
|
13
|
|
|
|
|
26
|
return $self->_value_from_dom($dom, $$action_proto); |
385
|
|
|
|
|
|
|
} else { |
386
|
0
|
|
|
|
|
0
|
die "I encountered an action I don't know what to do with: $action_proto"; |
387
|
|
|
|
|
|
|
} |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub _value_from_scalar_action { |
391
|
165
|
|
|
165
|
|
169
|
my ($self, $data, $action_proto) = @_; |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
## If a $action_proto contains a ={ with no | first OR it contains a ={ and no | |
394
|
|
|
|
|
|
|
## That means it is a string with placeholders |
395
|
|
|
|
|
|
|
|
396
|
165
|
|
|
|
|
225
|
my $first_pipe = index($action_proto, '|'); |
397
|
165
|
|
|
|
|
161
|
my $first_open = index($action_proto, '={'); |
398
|
|
|
|
|
|
|
|
399
|
165
|
100
|
100
|
|
|
682
|
if( |
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
400
|
|
|
|
|
|
|
( |
401
|
|
|
|
|
|
|
($first_open >= 0) && |
402
|
|
|
|
|
|
|
($first_open < $first_pipe) |
403
|
|
|
|
|
|
|
) || ( |
404
|
|
|
|
|
|
|
($first_open >= 0) && |
405
|
|
|
|
|
|
|
($first_pipe == -1) |
406
|
|
|
|
|
|
|
) |
407
|
|
|
|
|
|
|
) { |
408
|
|
|
|
|
|
|
my @parts = map { |
409
|
10
|
100
|
|
|
|
24
|
ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_; |
|
31
|
|
|
|
|
121
|
|
410
|
|
|
|
|
|
|
} $self->parse_data_template($action_proto); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# If the last part is a literal AND it has trailing filters |
413
|
|
|
|
|
|
|
# we need to process the filters. And deal with all the special cases... |
414
|
10
|
100
|
100
|
|
|
65
|
if(Scalar::Util::blessed($parts[-1]) && (index("$parts[-1]", '|') >0) ) { |
415
|
3
|
|
|
|
|
6
|
my $last = substr "$parts[-1]", 0, index("$parts[-1]", '|'); |
416
|
3
|
|
|
|
|
18
|
$last=~s/\s+$//; |
417
|
3
|
|
|
|
|
13
|
my %data_spec = $self->parse_data_spec(pop @parts); |
418
|
3
|
|
|
|
|
13
|
my $return = join('', @parts, $last); |
419
|
3
|
|
|
|
|
5
|
foreach my $filter (@{$data_spec{filters}}) { |
|
3
|
|
|
|
|
8
|
|
420
|
3
|
|
|
|
|
8
|
$return = $self->_apply_data_filter($return, $data, $filter); |
421
|
|
|
|
|
|
|
} |
422
|
3
|
|
|
|
|
16
|
return $return; |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
7
|
|
|
|
|
62
|
return join('', @parts); |
426
|
|
|
|
|
|
|
} else { |
427
|
155
|
|
|
|
|
252
|
my %data_spec = $self->parse_data_spec($action_proto); |
428
|
155
|
100
|
|
|
|
354
|
if(defined(my $literal = $data_spec{literal})) { |
429
|
2
|
|
|
|
|
5
|
return $literal; |
430
|
|
|
|
|
|
|
} else { |
431
|
153
|
|
|
|
|
438
|
return $self->_value_from_data($data, %data_spec); |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub _process_code { |
437
|
23
|
|
|
23
|
|
51
|
my ($self, $dom, $data, $code, %match_spec) = @_; |
438
|
23
|
|
|
|
|
29
|
my $css = $match_spec{css}; |
439
|
23
|
100
|
|
|
|
44
|
if($css eq '.') { |
440
|
8
|
|
|
|
|
21
|
my $value = $self->_call_coderef($code, $dom, $data->value); |
441
|
8
|
|
|
|
|
29
|
$self->_process_value_proto($dom, $data, $value, %match_spec); |
442
|
|
|
|
|
|
|
} else { |
443
|
15
|
|
|
|
|
28
|
my $collection = $self->find_or_die($dom,$css); |
444
|
|
|
|
|
|
|
$collection->each(sub { |
445
|
19
|
|
|
19
|
|
237
|
my $value = $self->_call_coderef($code, $_, $data->value); |
446
|
19
|
|
|
|
|
146
|
my %local_match_spec = (%match_spec, css=>'.'); |
447
|
19
|
|
|
|
|
72
|
$self->_process_value_proto($_, $data, $value, %local_match_spec); |
448
|
15
|
|
|
|
|
70
|
}); |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub _call_coderef { |
453
|
27
|
|
|
27
|
|
31
|
my ($self, $code, $dom, $value) = @_; |
454
|
27
|
|
|
|
|
61
|
return $self->$code($dom, $value); |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
sub _value_from_template_obj { |
458
|
15
|
|
|
15
|
|
35
|
my ($self, $dom, $data, $template, %match_spec) = @_; |
459
|
15
|
|
|
|
|
33
|
my $content = $self->_value_from_dom($dom, \%match_spec); |
460
|
15
|
|
|
|
|
290
|
my $new_data = Template::Pure::DataProxy->new( |
461
|
|
|
|
|
|
|
$data->value, |
462
|
|
|
|
|
|
|
content => $self->encoded_string($content)); |
463
|
|
|
|
|
|
|
|
464
|
15
|
|
|
|
|
40
|
return $self->encoded_string($template->render($new_data)); |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
sub _process_directive_instructions { |
468
|
117
|
|
|
117
|
|
266
|
my ($self, $dom, $data, @directives) = @_; |
469
|
117
|
100
|
100
|
|
|
491
|
if( (ref($directives[0])||'') eq 'HASH') { |
470
|
9
|
|
|
|
|
12
|
my %map = %{shift(@directives)}; |
|
9
|
|
|
|
|
34
|
|
471
|
9
|
|
|
|
|
11
|
my %new_data; |
472
|
9
|
|
|
|
|
20
|
foreach my $key (keys %map) { |
473
|
19
|
|
|
|
|
1059
|
$new_data{$key} = $self->_value_from_action_proto($dom, $data, $map{$key}); |
474
|
|
|
|
|
|
|
} |
475
|
9
|
|
|
|
|
1107
|
$data = Template::Pure::DataContext->new(\%new_data); |
476
|
|
|
|
|
|
|
} |
477
|
117
|
|
|
|
|
374
|
return ($data, @directives); |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
sub _process_sub_data { |
481
|
24
|
|
|
24
|
|
44
|
my ($self, $dom, $data, $match_spec, %action) = @_; |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# I don't know what it means to match repeat on attribes or append/prepent |
484
|
|
|
|
|
|
|
# right now, so just doing match on the CSS and welcome specifications for |
485
|
|
|
|
|
|
|
# this behavior. |
486
|
|
|
|
|
|
|
|
487
|
24
|
|
|
|
|
36
|
my $css = $match_spec->{css}; |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# Pull out any sort or filters |
490
|
24
|
100
|
|
|
|
56
|
my $sort_cb = exists $action{order_by} ? delete $action{order_by} : undef; |
491
|
24
|
100
|
|
|
|
47
|
my $grep_cb = exists $action{grep} ? delete $action{grep} : undef; |
492
|
24
|
50
|
|
|
|
46
|
my $filter_cb = exists $action{filter} ? delete $action{filter} : undef; |
493
|
24
|
50
|
|
|
|
40
|
my $display_fields = exists $action{display_fields} ? delete $action{display_fields} : undef; |
494
|
24
|
100
|
|
|
|
40
|
my $following_directives = exists $action{directives} ? delete $action{directives} : undef; |
495
|
24
|
|
|
|
|
321
|
my ($sub_data_proto, $sub_data_action) = %action; |
496
|
|
|
|
|
|
|
|
497
|
24
|
100
|
|
|
|
71
|
if(index($sub_data_proto,'<-') > 0) { |
498
|
|
|
|
|
|
|
|
499
|
17
|
100
|
|
|
|
46
|
if(ref \$sub_data_action eq 'SCALAR') { |
500
|
5
|
|
|
|
|
5
|
my $new_match_spec = '.'; |
501
|
5
|
100
|
|
|
|
37
|
$new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append'; |
502
|
5
|
50
|
|
|
|
11
|
$new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend'; |
503
|
5
|
50
|
|
|
|
48
|
$new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter'; |
504
|
5
|
50
|
|
|
|
9
|
$new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node'; |
505
|
5
|
|
|
|
|
12
|
$sub_data_action = [ $new_match_spec => $sub_data_action ]; |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
|
508
|
17
|
100
|
|
|
|
44
|
if(ref $sub_data_action eq 'CODE') { |
509
|
2
|
|
|
|
|
2
|
my $new_match_spec = '.'; |
510
|
2
|
50
|
|
|
|
5
|
$new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append'; |
511
|
2
|
100
|
|
|
|
4
|
$new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend'; |
512
|
2
|
50
|
|
|
|
4
|
$new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter'; |
513
|
2
|
50
|
|
|
|
4
|
$new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node'; |
514
|
2
|
|
|
|
|
4
|
$sub_data_action = [ $new_match_spec => $sub_data_action ]; |
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
|
517
|
17
|
100
|
|
|
|
56
|
if(Scalar::Util::blessed($sub_data_action)) { |
518
|
2
|
|
|
|
|
3
|
my $new_match_spec = '.'; |
519
|
2
|
50
|
|
|
|
5
|
$new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append'; |
520
|
2
|
50
|
|
|
|
6
|
$new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend'; |
521
|
2
|
50
|
|
|
|
4
|
$new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter'; |
522
|
2
|
100
|
|
|
|
5
|
$new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node'; |
523
|
2
|
|
|
|
|
5
|
$sub_data_action = [ $new_match_spec => $sub_data_action ]; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
17
|
50
|
|
|
|
40
|
die "Action for '$sub_data_proto' must be an arrayref of new directives" |
527
|
|
|
|
|
|
|
unless ref $sub_data_action eq 'ARRAY'; |
528
|
|
|
|
|
|
|
|
529
|
17
|
|
|
|
|
38
|
my ($new_key, $itr_data_spec) = $self->parse_itr_spec($sub_data_proto); |
530
|
17
|
|
|
|
|
64
|
my $itr_data_proto = $self->_value_from_data($data, %$itr_data_spec); |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
## For now if the found value is undef, we second it along ti be trimmed |
533
|
|
|
|
|
|
|
## this behavior might be tweaked as examples of usage arise, also for now |
534
|
|
|
|
|
|
|
## we just pass through an empty iterator instead of considering it undef |
535
|
|
|
|
|
|
|
## ie [] is not considered like undef for now... |
536
|
|
|
|
|
|
|
|
537
|
17
|
100
|
|
|
|
39
|
return $self->_process_match_spec($dom, $itr_data_proto, %$match_spec) |
538
|
|
|
|
|
|
|
if $self->_value_is_undef($itr_data_proto); |
539
|
|
|
|
|
|
|
|
540
|
16
|
|
|
|
|
15
|
my %options; |
541
|
16
|
50
|
|
|
|
41
|
if($display_fields) { |
542
|
0
|
|
|
|
|
0
|
$options{display_fields} = $display_fields; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
16
|
100
|
|
|
|
26
|
if($sort_cb) { |
546
|
2
|
50
|
|
|
|
8
|
if(ref(\$sort_cb) eq 'SCALAR') { |
547
|
0
|
|
|
|
|
0
|
my %sub_data_spec = $self->parse_data_spec($sort_cb); |
548
|
0
|
|
|
|
|
0
|
my $value = $self->_value_from_data($data, %sub_data_spec); |
549
|
0
|
|
|
|
|
0
|
$sort_cb = $value; |
550
|
|
|
|
|
|
|
} |
551
|
2
|
50
|
|
|
|
4
|
die "the 'sort' key must point to an anonymous subroutine" unless ref($sort_cb) eq 'CODE'; |
552
|
2
|
|
|
|
|
5
|
$options{sort} = $sort_cb; |
553
|
|
|
|
|
|
|
} |
554
|
16
|
100
|
|
|
|
30
|
if($grep_cb) { |
555
|
1
|
50
|
|
|
|
3
|
if(ref(\$grep_cb) eq 'SCALAR') { |
556
|
1
|
|
|
|
|
2
|
my %sub_data_spec = $self->parse_data_spec($grep_cb); |
557
|
1
|
|
|
|
|
3
|
my $value = $self->_value_from_data($data, %sub_data_spec); |
558
|
1
|
|
|
|
|
2
|
$grep_cb = $value; |
559
|
|
|
|
|
|
|
} |
560
|
1
|
50
|
|
|
|
4
|
die "the 'grep' key must point to an anonymous subroutine" unless ref($grep_cb) eq 'CODE'; |
561
|
1
|
|
|
|
|
1
|
$options{grep} = $grep_cb; |
562
|
|
|
|
|
|
|
} |
563
|
16
|
50
|
|
|
|
32
|
if($filter_cb) { |
564
|
0
|
0
|
|
|
|
0
|
if(ref(\$filter_cb) eq 'SCALAR') { |
565
|
0
|
|
|
|
|
0
|
my %sub_data_spec = $self->parse_data_spec($filter_cb); |
566
|
0
|
|
|
|
|
0
|
my $value = $self->_value_from_data($data, %sub_data_spec); |
567
|
0
|
|
|
|
|
0
|
$filter_cb = $value; |
568
|
|
|
|
|
|
|
} |
569
|
0
|
0
|
|
|
|
0
|
die "the 'sort' key must point to an anonymous subroutine" unless ref($filter_cb) eq 'CODE'; |
570
|
0
|
|
|
|
|
0
|
$options{filter} = $filter_cb; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
|
573
|
16
|
50
|
|
|
|
34
|
$options{display_fields} = $display_fields if $display_fields; |
574
|
|
|
|
|
|
|
|
575
|
16
|
|
|
|
|
81
|
my $iterator = Template::Pure::Iterator->from_proto($itr_data_proto, $self, \%options); |
576
|
|
|
|
|
|
|
|
577
|
16
|
50
|
|
|
|
42
|
if($css eq '.') { |
578
|
0
|
|
|
|
|
0
|
$self->_process_iterator($dom, $new_key, $iterator, @{$sub_data_action}); |
|
0
|
|
|
|
|
0
|
|
579
|
|
|
|
|
|
|
} else { |
580
|
16
|
|
|
|
|
35
|
my $collection = $self->find_or_die($dom,$css); |
581
|
|
|
|
|
|
|
$collection->each(sub { |
582
|
17
|
|
|
17
|
|
185
|
$self->_process_iterator($_, $new_key, $iterator, @{$sub_data_action}); |
|
17
|
|
|
|
|
60
|
|
583
|
16
|
|
|
|
|
78
|
}); |
584
|
|
|
|
|
|
|
} |
585
|
|
|
|
|
|
|
} else { |
586
|
7
|
|
|
|
|
15
|
my %sub_data_spec = $self->parse_data_spec($sub_data_proto); |
587
|
7
|
|
|
|
|
24
|
my $value = $self->_value_from_data($data, %sub_data_spec); |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
## If the value is undefined, we dont' continue... should we remove all this...? |
590
|
7
|
100
|
33
|
|
|
40
|
if(ref $sub_data_action eq 'ARRAY') { |
|
|
50
|
|
|
|
|
|
591
|
4
|
|
|
|
|
4
|
$self->process_sub_directives($dom, $value, $css, @{$sub_data_action}); |
|
4
|
|
|
|
|
10
|
|
592
|
|
|
|
|
|
|
} elsif(Scalar::Util::blessed($sub_data_action) && $sub_data_action->isa(ref $self)) { |
593
|
3
|
|
|
|
|
6
|
my $new_data = Template::Pure::DataContext->new($value); |
594
|
3
|
|
|
|
|
12
|
my $new_value = $self->_value_from_template_obj($dom, $new_data, $sub_data_action, %$match_spec); |
595
|
3
|
|
|
|
|
27
|
$self->_process_match_spec($dom, $new_value, %$match_spec); |
596
|
|
|
|
|
|
|
} else { |
597
|
0
|
|
|
|
|
0
|
die "Don't know how to process $value on $css for $sub_data_action"; |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
} |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
## Todo... not sure if this is right or useful |
602
|
23
|
100
|
|
|
|
4650
|
if($following_directives) { |
603
|
1
|
|
|
|
|
2
|
$self->process_sub_directives($dom, $data, $css, @{$following_directives}); |
|
1
|
|
|
|
|
2
|
|
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
sub _process_iterator { |
608
|
17
|
|
|
17
|
|
35
|
my ($self, $dom, $key, $iterator, @actions) = @_; |
609
|
17
|
|
|
|
|
1809
|
my $template = dclone($dom); |
610
|
17
|
|
|
|
|
61
|
while(my $datum = $iterator->next) { |
611
|
41
|
|
|
|
|
45
|
$datum = $$datum; |
612
|
41
|
|
|
|
|
70
|
my $new_dom = Mojo::DOM58->new($template); |
613
|
41
|
|
|
|
|
8391
|
my $new_data; |
614
|
41
|
100
|
|
|
|
75
|
if($key eq '.') { |
615
|
2
|
|
|
|
|
7
|
$new_data = Template::Pure::DataProxy |
616
|
|
|
|
|
|
|
->new($datum, i=>$iterator); |
617
|
|
|
|
|
|
|
} else { |
618
|
39
|
|
|
|
|
74
|
$new_data = +{ |
619
|
|
|
|
|
|
|
$key => $datum, |
620
|
|
|
|
|
|
|
i => $iterator, |
621
|
|
|
|
|
|
|
}; |
622
|
|
|
|
|
|
|
} |
623
|
41
|
|
|
|
|
87
|
$self->_process_dom_recursive($new_data, $new_dom->descendant_nodes->[0], @actions); |
624
|
41
|
|
|
|
|
178
|
$dom->replace($new_dom); |
625
|
|
|
|
|
|
|
} |
626
|
17
|
|
|
|
|
45
|
$dom->remove; |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
sub process_sub_directives { |
630
|
22
|
|
|
22
|
0
|
46
|
my ($self, $dom, $data, $css, @directives) = @_; |
631
|
22
|
100
|
|
|
|
44
|
if($css eq '.') { |
632
|
2
|
|
|
|
|
4
|
$self->_process_dom_recursive($data, $dom, @directives); |
633
|
|
|
|
|
|
|
} else { |
634
|
20
|
|
|
|
|
39
|
my $collection = $self->find_or_die($dom,$css); |
635
|
|
|
|
|
|
|
$collection->each(sub { |
636
|
20
|
|
|
20
|
|
156
|
$self->_process_dom_recursive($data, $_, @directives); |
637
|
20
|
|
|
|
|
106
|
}); |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
sub _value_from_dom { |
642
|
28
|
|
|
28
|
|
27
|
my $self = shift; |
643
|
28
|
|
|
|
|
31
|
my $dom = shift; |
644
|
28
|
100
|
|
|
|
62
|
my %match_spec = ref $_[0] ? %{$_[0]} : $self->parse_match_spec($_[0]); |
|
15
|
|
|
|
|
43
|
|
645
|
|
|
|
|
|
|
|
646
|
28
|
100
|
|
|
|
71
|
$dom = $dom->root if $match_spec{absolute}; |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
## TODO, perhaps this could do a find instead of at and return |
649
|
|
|
|
|
|
|
## a collection, which populates an iterator if requested? |
650
|
|
|
|
|
|
|
|
651
|
28
|
100
|
|
|
|
202
|
if($match_spec{target} eq 'content') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
652
|
|
|
|
|
|
|
#return $self->encoded_string($self->at_or_die($dom, $match_spec{css})->content); |
653
|
|
|
|
|
|
|
# Not sure if there is a more effecient way to make this happen... |
654
|
10
|
|
|
|
|
41
|
return Mojo::DOM58->new($self->at_or_die($dom, $match_spec{css})->content); |
655
|
|
|
|
|
|
|
} elsif($match_spec{target} eq 'node') { |
656
|
|
|
|
|
|
|
## When we want a full node, with HTML tags, we encode the string |
657
|
|
|
|
|
|
|
## since I presume they want a copy not escaped. T 'think' this is |
658
|
|
|
|
|
|
|
## the commonly desired thing and you can always apply and escape_html filter |
659
|
|
|
|
|
|
|
## yourself when you don't want it. |
660
|
|
|
|
|
|
|
#return $self->encoded_string($self->at_or_die($dom, $match_spec{css})->to_string); |
661
|
17
|
|
|
|
|
36
|
return $self->at_or_die($dom, $match_spec{css}); |
662
|
1
|
|
|
|
|
6
|
} elsif(my $attr = ${$match_spec{target}}) { |
663
|
|
|
|
|
|
|
## TODO not sure what if any encoding we need here. |
664
|
1
|
|
|
|
|
4
|
return $self->at_or_die($dom, $match_spec{css})->attr($attr); |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
} |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
sub _value_from_data { |
669
|
207
|
|
|
207
|
|
352
|
my ($self, $data, %data_spec) = @_; |
670
|
207
|
|
|
|
|
559
|
my $value = $data->at(%data_spec)->value; |
671
|
207
|
|
|
|
|
450
|
foreach my $filter (@{$data_spec{filters}}) { |
|
207
|
|
|
|
|
337
|
|
672
|
13
|
|
|
|
|
28
|
$value = $self->_apply_data_filter($value, $data, $filter); |
673
|
|
|
|
|
|
|
} |
674
|
207
|
|
|
|
|
655
|
return $value; |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
sub _apply_data_filter { |
678
|
16
|
|
|
16
|
|
36
|
my ($self, $value, $data, $filter) = @_; |
679
|
16
|
|
|
|
|
29
|
my ($name, @args) = @$filter; |
680
|
16
|
100
|
|
|
|
23
|
@args = map { ref $_ ? $self->_value_from_data($data, %$_) : $_ } @args; |
|
10
|
|
|
|
|
29
|
|
681
|
16
|
|
|
|
|
38
|
return $self->filter($name, $value, @args); |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
sub _process_dom_filter { |
685
|
1
|
|
|
1
|
|
2
|
my ($self, $dom, $data, $css, $cb) = @_; |
686
|
1
|
50
|
|
|
|
3
|
if($css eq '.') { |
687
|
0
|
|
|
|
|
0
|
$cb->($self, $dom, $data); |
688
|
|
|
|
|
|
|
} else { |
689
|
1
|
|
|
|
|
3
|
my $collection = $self->find_or_die($dom,$css); |
690
|
|
|
|
|
|
|
$collection->each(sub { |
691
|
1
|
|
|
1
|
|
8
|
$cb->($self, $_, $data); |
692
|
1
|
|
|
|
|
5
|
}); |
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
sub _process_match_spec { |
697
|
180
|
|
|
180
|
|
366
|
my ($self, $dom, $value, %match_spec) = @_; |
698
|
180
|
100
|
|
|
|
267
|
if($match_spec{css} eq '.') { |
699
|
49
|
|
|
|
|
112
|
$self->_process_mode($dom, $value, %match_spec); |
700
|
|
|
|
|
|
|
} else { |
701
|
131
|
|
|
|
|
230
|
my $collection = $self->find_or_die($dom,$match_spec{css}); |
702
|
|
|
|
|
|
|
$collection->each(sub { |
703
|
132
|
|
|
132
|
|
1260
|
$self->_process_mode($_, $value, %match_spec); |
704
|
130
|
|
|
|
|
573
|
}); |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
} |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
sub _value_is_undef { |
709
|
347
|
|
|
347
|
|
333
|
my ($self, $value) = @_; |
710
|
347
|
100
|
|
|
|
503
|
return 1 if !defined($value); |
711
|
335
|
100
|
100
|
|
|
989
|
return 1 if Scalar::Util::blessed($value) && $value->isa('Template::Pure::UndefObject'); |
712
|
334
|
|
|
|
|
814
|
return 0; |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
sub _process_mode { |
717
|
201
|
|
|
201
|
|
446
|
my ($self, $dom, $value, %match_spec) = @_; |
718
|
|
|
|
|
|
|
|
719
|
201
|
|
|
|
|
228
|
my $mode = $match_spec{mode}; |
720
|
201
|
|
|
|
|
166
|
my $target = $match_spec{target}; |
721
|
201
|
|
|
|
|
358
|
my $safe_value = $self->escape_html($value); |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
## This behavior may be tweaked in the future. |
724
|
201
|
100
|
|
|
|
343
|
if($self->_value_is_undef($safe_value)) { |
725
|
12
|
50
|
|
|
|
35
|
if($target eq 'node') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
726
|
0
|
|
|
|
|
0
|
return $dom->remove; |
727
|
|
|
|
|
|
|
} elsif($target eq 'content') { |
728
|
9
|
100
|
100
|
|
|
29
|
if( ($mode eq 'append') or ($mode eq 'prepend')) { |
729
|
|
|
|
|
|
|
# Don't remove anything since there's not a target mode here |
730
|
|
|
|
|
|
|
# just stuff we wanted to add to the start or end. |
731
|
4
|
|
|
|
|
13
|
return; |
732
|
|
|
|
|
|
|
} else { |
733
|
5
|
|
|
|
|
16
|
return $dom->remove; # TODO, or should this remove just the content..? |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
} elsif(my $attr = $$target) { |
736
|
3
|
|
|
|
|
9
|
return delete $dom->attr->{$attr}; |
737
|
|
|
|
|
|
|
} |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
|
740
|
189
|
100
|
|
|
|
331
|
if($mode eq 'replace') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
741
|
161
|
100
|
|
|
|
276
|
if($target eq 'content') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
742
|
129
|
50
|
|
|
|
179
|
$dom->content($safe_value) unless $self->_value_is_undef($safe_value); |
743
|
|
|
|
|
|
|
} elsif($target eq 'node') { |
744
|
21
|
|
|
|
|
55
|
$dom->replace($safe_value); |
745
|
|
|
|
|
|
|
} elsif(my $attr = $$target) { |
746
|
11
|
|
|
|
|
29
|
$dom->attr($attr=>$safe_value); |
747
|
|
|
|
|
|
|
} else { |
748
|
0
|
|
|
|
|
0
|
die "Don't understand target of $target"; |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
} elsif($mode eq 'append') { |
751
|
23
|
100
|
|
|
|
54
|
if($target eq 'content') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
752
|
13
|
|
|
|
|
33
|
$dom->append_content($safe_value); |
753
|
|
|
|
|
|
|
} elsif($target eq 'node') { |
754
|
5
|
|
|
|
|
55
|
$dom->append($safe_value); |
755
|
|
|
|
|
|
|
} elsif(my $attr = $$target) { |
756
|
5
|
|
100
|
|
|
11
|
my $current_attr = $dom->attr($attr)||''; |
757
|
5
|
|
|
|
|
68
|
$dom->attr($attr=>"$current_attr$safe_value" ); |
758
|
|
|
|
|
|
|
} else { |
759
|
0
|
|
|
|
|
0
|
die "Don't understand target of $target"; |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
} elsif($mode eq 'prepend') { |
762
|
5
|
100
|
|
|
|
12
|
if($target eq 'content') { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
763
|
3
|
|
|
|
|
9
|
$dom->prepend_content($safe_value); |
764
|
|
|
|
|
|
|
} elsif($target eq 'node') { |
765
|
1
|
|
|
|
|
4
|
$dom->prepend($safe_value); |
766
|
|
|
|
|
|
|
} elsif(my $attr = $$target) { |
767
|
1
|
|
50
|
|
|
4
|
my $current_attr = $dom->attr($attr)||''; |
768
|
1
|
|
|
|
|
15
|
$dom->attr($attr=> "$safe_value$current_attr" ); |
769
|
|
|
|
|
|
|
} else { |
770
|
0
|
|
|
|
|
0
|
die "Don't understand target of $target"; |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
} else { |
773
|
0
|
|
|
|
|
0
|
die "Not sure how to handle mode '$mode'"; |
774
|
|
|
|
|
|
|
} |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
sub filter { |
778
|
16
|
|
|
16
|
1
|
25
|
my ($self, $name, $data, @args) = @_; |
779
|
|
|
|
|
|
|
my %filters = ( |
780
|
|
|
|
|
|
|
$self->default_filters, |
781
|
16
|
|
|
|
|
29
|
%{$self->{filters}} |
|
16
|
|
|
|
|
135
|
|
782
|
|
|
|
|
|
|
); |
783
|
|
|
|
|
|
|
|
784
|
16
|
|
50
|
|
|
63
|
my $filter = $filters{$name} || |
785
|
|
|
|
|
|
|
die "Filter $name does not exist"; |
786
|
|
|
|
|
|
|
|
787
|
16
|
|
|
|
|
44
|
return $filter->($self, $data, @args); |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
1; |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
=head1 NAME |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
Template::Pure - Perlish Port of pure.js and more |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
=head1 SYNOPSIS |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
use Template::Pure; |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
my $html = q[ |
801
|
|
|
|
|
|
|
<html> |
802
|
|
|
|
|
|
|
<head> |
803
|
|
|
|
|
|
|
<title>Page Title</title> |
804
|
|
|
|
|
|
|
</head> |
805
|
|
|
|
|
|
|
<body> |
806
|
|
|
|
|
|
|
<section id="article"> |
807
|
|
|
|
|
|
|
<h1>Header</h1> |
808
|
|
|
|
|
|
|
<div>Story</div> |
809
|
|
|
|
|
|
|
</section> |
810
|
|
|
|
|
|
|
<ul id="friendlist"> |
811
|
|
|
|
|
|
|
<li>Friends</li> |
812
|
|
|
|
|
|
|
</ul> |
813
|
|
|
|
|
|
|
</body> |
814
|
|
|
|
|
|
|
</html> |
815
|
|
|
|
|
|
|
]; |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
my $pure = Template::Pure->new( |
818
|
|
|
|
|
|
|
template=>$html, |
819
|
|
|
|
|
|
|
directives=> [ |
820
|
|
|
|
|
|
|
'head title' => 'meta.title', |
821
|
|
|
|
|
|
|
'#article' => [ |
822
|
|
|
|
|
|
|
'h1' => 'header', |
823
|
|
|
|
|
|
|
'div' => 'content', |
824
|
|
|
|
|
|
|
], |
825
|
|
|
|
|
|
|
'ul li' => { |
826
|
|
|
|
|
|
|
'friend<-user.friends' => [ |
827
|
|
|
|
|
|
|
'.' => '={friend}, #={i.index}', |
828
|
|
|
|
|
|
|
], |
829
|
|
|
|
|
|
|
}, |
830
|
|
|
|
|
|
|
], |
831
|
|
|
|
|
|
|
); |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
my $data = +{ |
834
|
|
|
|
|
|
|
meta => { |
835
|
|
|
|
|
|
|
title => 'Travel Poetry', |
836
|
|
|
|
|
|
|
created_on => '1/1/2000', |
837
|
|
|
|
|
|
|
}, |
838
|
|
|
|
|
|
|
header => 'Fire', |
839
|
|
|
|
|
|
|
content => q[ |
840
|
|
|
|
|
|
|
Are you doomed to discover that you never recovered from the narcoleptic |
841
|
|
|
|
|
|
|
country in which you once stood? Where the fire's always burning, but |
842
|
|
|
|
|
|
|
there's never enough wood? |
843
|
|
|
|
|
|
|
], |
844
|
|
|
|
|
|
|
user => { |
845
|
|
|
|
|
|
|
name => 'jnap', |
846
|
|
|
|
|
|
|
friends => [qw/jack jane joe/], |
847
|
|
|
|
|
|
|
}, |
848
|
|
|
|
|
|
|
}; |
849
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
print $pure->render($data); |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
Results in: |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
<html> |
855
|
|
|
|
|
|
|
<head> |
856
|
|
|
|
|
|
|
<title>Travel Poetry</title> |
857
|
|
|
|
|
|
|
</head> |
858
|
|
|
|
|
|
|
<body> |
859
|
|
|
|
|
|
|
<section id="article"> |
860
|
|
|
|
|
|
|
<h1>Fire</h1> |
861
|
|
|
|
|
|
|
<div> |
862
|
|
|
|
|
|
|
Are you doomed to discover that you never recovered from the narcoleptic |
863
|
|
|
|
|
|
|
country in which you once stood? Where the fire'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 |