File Coverage

blib/lib/Template/Lace/Components.pm
Criterion Covered Total %
statement 118 130 90.7
branch 33 44 75.0
condition 5 6 83.3
subroutine 22 25 88.0
pod 0 15 0.0
total 178 220 80.9


line stmt bran cond sub pod time code
1             package Template::Lace::Components;
2              
3 1     1   745 use Moo;
  1         3  
  1         10  
4 1     1   1075 use UUID::Tiny;
  1         14481  
  1         245  
5 1     1   14 use JSON::MaybeXS ();
  1         4  
  1         2666  
6              
7             has [qw(handlers component_info ordered_component_keys)] => (is=>'ro', required=>1);
8              
9             around BUILDARGS => sub {
10             my ($orig, $class, @args) = @_;
11             my $args = $class->$orig(@args);
12             my %component_info = $class->get_component_info($args);
13             my @ordered_component_keys = $class->get_component_ordered_keys(%component_info);
14             my %handlers = $class->get_handlers($args, \%component_info, @ordered_component_keys);
15              
16             $args->{handlers} = \%handlers;
17             $args->{component_info} = \%component_info;
18             $args->{ordered_component_keys} = \@ordered_component_keys;
19              
20             return $args;
21             };
22              
23             sub get_component_info {
24 7     7 0 29 my ($class, $args) = @_;
25 7         40 my %component_info = $class->find_components($args->{dom}, $args->{component_handlers});
26 7         31 return %component_info;
27             }
28              
29             sub get_handlers {
30 7     7 0 26 my ($class, $args, $component_info, @ordered_component_keys) = @_;
31 7         18 my %handlers = ();
32 7         22 foreach my $key(@ordered_component_keys) {
33 7         21 my $handler = $class->get_handler($args, %{$component_info->{$key}});
  7         52  
34 7         39 $handlers{$key} = $handler;
35             }
36 7         31 return %handlers;
37             }
38              
39             sub get_handler {
40 7     7 0 43 my ($class, $args, %component_info) = @_;
41 7         23 my $prefix = $component_info{prefix};
42 7         18 my $name = $component_info{name};
43 7         18 my $handler = '';
44 7 100       31 if(ref($args->{component_handlers}{$prefix}) eq 'CODE') {
45 2         8 $handler = $args->{component_handlers}{$prefix}->($name, $args, %{$component_info{attrs}});
  2         18  
46             } else {
47 5         18 $handler = $args->{component_handlers}{$prefix}{$name};
48 5 50       20 $handler = (ref($handler) eq 'CODE') ? $handler->($args, %{$component_info{attrs}}): $handler;
  0         0  
49             }
50              
51             # TODO should this be in the renderer?
52 7 100       206 if($handler->model_class->can('on_component_add')) {
53 2         21 my $dom_content = $args->{dom}->at("[uuid='$component_info{key}']");
54             my %attrs = (
55             $handler->renderer_class
56             ->process_attrs(
57             $handler->model_class,
58             $args->{dom}, # DOM of containing template
59 2         1512 %{$component_info{attrs}}),
  2         20  
60             content=>$dom_content->content,
61             # model=>$self->model
62             );
63 2         1872 my $renderer = $handler->create(%attrs);
64 2         27 $renderer->model->on_component_add($renderer->dom, $args->{dom});
65 2         966 $dom_content->replace($renderer->dom);
66             }
67              
68 7         9293 return $handler;
69             }
70              
71             sub find_components {
72 178     178 0 431 my ($class, $dom, $handlers, $current_container_id, %components) = @_;
73             $dom->child_nodes->each(sub {
74 164         8557 %components = $class->find_component(@_,
75             $handlers,
76             $current_container_id,
77             %components);
78 178         406 });
79 178         6624 return %components;
80             }
81              
82             sub find_component {
83 164     164 0 409 my ($class, $child_dom, $num, $handlers, $current_container_id, %components) = @_;
84 164 100 100     371 if(my ($prefix, $component_name) = (($child_dom->tag||'') =~m/^(.+?)\-(.+)?/)) {
85             ## if uuid exists, that means we already processed it.
86 15 50       273 if($class->is_a_component($handlers, $prefix, $component_name)) {
87 15 100       35 unless($child_dom->attr('uuid')) {
88 7         117 my $uuid = $class->generate_component_uuid($prefix);
89 7         27 $child_dom->attr({'uuid',$uuid});
90 7         181 $components{$uuid} = +{
91             order => (scalar(keys %components)),
92             key => $uuid,
93             $class->setup_component_info($prefix,
94             $current_container_id,
95             $component_name,
96             $child_dom),
97             };
98              
99 7 100       18 push @{$components{$current_container_id}{children_ids}}, $uuid
  6         14  
100             if $current_container_id;
101              
102 7         11 my $old_current_container_id = $current_container_id;
103 7         8 $current_container_id = $uuid;
104            
105 7         23 %components = $class->find_components(
106             $child_dom,
107             $handlers,
108             $current_container_id,
109             %components);
110              
111 7         17 $current_container_id = $old_current_container_id;
112             }
113             }
114             }
115 164         3176 %components = $class->find_components(
116             $child_dom,
117             $handlers,
118             $current_container_id,
119             %components);
120 164         643 return %components;
121             }
122              
123             sub is_a_component {
124 15     15 0 32 my ($class, $handlers, $prefix, $name) = @_;
125 15 50       32 if($handlers->{$prefix}) {
126 15 100       32 if(ref($handlers->{$prefix}) eq 'CODE') {
127 3         10 return 1;
128             } else {
129 12 50       66 return $handlers->{$prefix}{$name} ? 1:0;
130             }
131             } else {
132 0         0 return 0;
133             }
134             }
135              
136             sub generate_component_uuid {
137 7     7 0 14 my ($class, $prefix) = @_;
138 7         16 my $uuid = UUID::Tiny::create_uuid_as_string;
139 7         937 $uuid=~s/\-//g;
140 7         12 return $uuid;
141             }
142              
143             sub setup_component_info {
144 7     7 0 15 my ($class, $prefix, $current_container_id, $name, $dom) = @_;
145 7         14 my %attrs = $class->setup_component_attr($dom);
146 7   100     55 return prefix => $prefix,
147             name => $name,
148             current_container_id => $current_container_id||'',
149             attrs => \%attrs;
150             }
151              
152             sub setup_component_attr {
153 7     7 0 13 my ($class, $dom) = @_;
154             return map {
155 24         142 $_ => $class->attr_value_handler_factory($dom->attr->{$_});
156 7 50       8 } keys %{$dom->attr||+{}};
  7         28  
157             }
158              
159             sub attr_value_handler_factory {
160 27     27 0 336 my ($class, $value) = @_;
161              
162              
163 27 100       99 if(my ($node, $css) = ($value=~m/^\\['"](\@?)(.+)['"]$/)) {
    100          
    100          
    100          
164 2         8 return $class->setup_css_match_handler($node, $css); # CSS match to content DOM
165             } elsif(my $path = ($value=~m/^\$\.(.+)$/)[0]) {
166 3         8 return $class->setup_data_path_hander($path); # is path to data
167             } elsif($value=~/^\{/) {
168 1         5 return $class->setup_hashrefdata_hander($value);
169             }elsif($value=~/^\[/) {
170 1         4 return $class->setup_arrayrefdata_hander($value);
171             } else {
172 20         60 return $value; # is literal or 'passthru' value
173             }
174             }
175              
176             sub setup_arrayrefdata_hander {
177 1     1 0 3 my ($class, $value) = @_;
178 1         5 my $ref = JSON::MaybeXS::decode_json($value);
179             my @array = map {
180 1         3 my $v = $_; $v =~s/^\$\.//;
  2         4  
  2         3  
181 2         4 $class->attr_value_handler_factory($v);
182             } @$ref;
183             return sub {
184 1     1   6 my ($ctx, $dom) = @_;
185 1 50 50     6 return [ map { (ref($_)||'') eq 'CODE' ? $_->($ctx,$dom) : $_ } @array ];
  2         24  
186 1         5 };
187             }
188              
189              
190             sub setup_hashrefdata_hander {
191 1     1 0 2 my ($class, $value) = @_;
192 1         11 my $ref = JSON::MaybeXS::decode_json($value);
193             my %hash = map {
194 1         10 my $v = $ref->{$_};
  1         3  
195 1         4 $_ => $class->attr_value_handler_factory($v);
196             } keys %$ref;
197             return sub {
198 1     1   5 my ($ctx, $dom) = @_;
199 1         6 my %unrolled = map { $_ => $hash{$_}->($ctx,$dom) } keys(%hash);
  1         8  
200 1         6 return \%unrolled;
201 1         5 };
202             }
203              
204             sub setup_css_match_handler {
205 2     2 0 4 my ($class, $node, $css) = @_;
206 2 50       5 if($node) {
207 0     0   0 return sub { my ($view, $dom) = @_; $dom->find($css) };
  0         0  
  0         0  
208             } else {
209 2 50       27 if(my $content = $css=~s/\:content$//) { # hack to CSS to allow match on content
    0          
210 2     2   17 return sub { my ($view, $dom) = @_; $dom->at($css)->content };
  2         8  
  2         10  
211             } elsif(my $nodes = $css=~s/\:nodes$//) {
212 0     0   0 return sub { my ($view, $dom) = @_; $dom->at($css)->descendant_nodes };
  0         0  
  0         0  
213             }else {
214 0     0   0 return sub { my ($view, $dom) = @_; $dom->at($css) };
  0         0  
  0         0  
215             }
216             }
217             }
218              
219             sub setup_data_path_hander {
220 3     3 0 5 my ($class, $path) = @_;
221 3 100       12 my @parts = $path=~m/\./ ? (split('\.', $path)) : ($path);
222             return sub {
223 3     3   12 my ($ctx, $dom) = @_;
224 3         13 foreach my $part(@parts) {
225 9 100       51 if(Scalar::Util::blessed $ctx) {
    50          
226 3         24 $ctx = $ctx->$part;
227             } elsif(ref($ctx) eq 'HASH') {
228 6         22 $ctx = $ctx->{$part};
229             } else {
230 0         0 die "No '$part' in path '$path' for this view";
231             }
232             }
233 3         18 return $ctx;
234 3         17 };
235             }
236              
237             sub get_component_ordered_keys {
238 7     7 0 29 my ($class, %component_info) = @_;
239             return map {
240             $_->{key}
241 7         13 } sort {
242             $a->{order} <=> $b->{order}
243 10         22 } map {
244 7         31 $component_info{$_}
  7         14  
245             } keys %component_info;
246             }
247              
248             1;
249              
250             =head1 NAME
251              
252             Template::Lace::Components - Prepares a Component Hierarchy from a DOM
253              
254             =head1 SYNOPSIS
255              
256             TBD
257              
258             =head1 DESCRIPTION
259              
260             Use by L<Template::Lace::Factory> to create a component hierarchy for a DOM
261             and from the defined component_mappings. Not really end user bits aimed here
262             but you can subclass if you want customized component features. See the main
263             docs in L<Template::Lace> for detailed discussion of Components.
264              
265             =head1 SEE ALSO
266            
267             L<Template::Lace>.
268              
269             =head1 AUTHOR
270              
271             Please See L<Template::Lace> for authorship and contributor information.
272            
273             =head1 COPYRIGHT & LICENSE
274            
275             Please see L<Template::Lace> for copyright and license information.
276              
277             =cut