File Coverage

blib/lib/Template/Lace/Components.pm
Criterion Covered Total %
statement 117 126 92.8
branch 33 42 78.5
condition 5 6 83.3
subroutine 23 25 92.0
pod 0 15 0.0
total 178 214 83.1


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