blib/lib/Dancer/Template/TemplateFlute.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 7 | 9 | 77.7 |
branch | n/a | ||
condition | n/a | ||
subroutine | 3 | 3 | 100.0 |
pod | n/a | ||
total | 10 | 12 | 83.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Dancer::Template::TemplateFlute; | ||||||
2 | |||||||
3 | 1 | 1 | 23583 | use strict; | |||
1 | 2 | ||||||
1 | 37 | ||||||
4 | 1 | 1 | 5 | use warnings; | |||
1 | 4 | ||||||
1 | 27 | ||||||
5 | |||||||
6 | 1 | 1 | 424 | use Template::Flute; | |||
0 | |||||||
0 | |||||||
7 | use Template::Flute::Iterator; | ||||||
8 | use Template::Flute::Utils; | ||||||
9 | use Template::Flute::I18N; | ||||||
10 | use Module::Load; | ||||||
11 | |||||||
12 | use Dancer::Config; | ||||||
13 | |||||||
14 | use base 'Dancer::Template::Abstract'; | ||||||
15 | |||||||
16 | our $VERSION = '0.0113'; | ||||||
17 | |||||||
18 | =head1 NAME | ||||||
19 | |||||||
20 | Dancer::Template::TemplateFlute - Template::Flute wrapper for Dancer | ||||||
21 | |||||||
22 | =head1 VERSION | ||||||
23 | |||||||
24 | Version 0.0113 | ||||||
25 | |||||||
26 | =head1 DESCRIPTION | ||||||
27 | |||||||
28 | This class is an interface between Dancer's template engine abstraction layer | ||||||
29 | and the L |
||||||
30 | |||||||
31 | In order to use this engine, use the template setting: | ||||||
32 | |||||||
33 | template: template_flute | ||||||
34 | |||||||
35 | The default template extension is ".html". | ||||||
36 | |||||||
37 | =head2 LAYOUT | ||||||
38 | |||||||
39 | Each layout needs a specification file and a template file. To embed | ||||||
40 | the content of your current view into the layout, put the following | ||||||
41 | into your specification file, e.g. F |
||||||
42 | |||||||
43 | |
||||||
44 | |
||||||
45 | |||||||
46 | |||||||
47 | This replaces the contents of the following block in your HTML | ||||||
48 | template, e.g. F |
||||||
49 | |||||||
50 | |
||||||
51 | Your content | ||||||
52 | |||||||
53 | |||||||
54 | =head2 ITERATORS | ||||||
55 | |||||||
56 | Iterators can be specified explicitly in the configuration file as below. | ||||||
57 | |||||||
58 | engines: | ||||||
59 | template_flute: | ||||||
60 | iterators: | ||||||
61 | fruits: | ||||||
62 | class: JSON | ||||||
63 | file: fruits.json | ||||||
64 | |||||||
65 | =head2 FILTER OPTIONS | ||||||
66 | |||||||
67 | Filter options and classes can be specified in the configuration file as below. | ||||||
68 | |||||||
69 | engines: | ||||||
70 | template_flute: | ||||||
71 | filters: | ||||||
72 | currency: | ||||||
73 | options: | ||||||
74 | int_curr_symbol: "$" | ||||||
75 | image: | ||||||
76 | class: "Flowers::Filters::Image" | ||||||
77 | |||||||
78 | =head2 ADJUSTING URIS | ||||||
79 | |||||||
80 | We automatically adjust links in the templates if the value of | ||||||
81 | C |
||||||
82 | |||||||
83 | =head2 EMBEDDING IMAGES IN EMAILS | ||||||
84 | |||||||
85 | If you pass a value named C |
||||||
86 | reference, all the images C |
||||||
87 | the CIDs, and the reference will be populated with an hashref, as | ||||||
88 | documented in L |
||||||
89 | |||||||
90 | =head2 DISABLE OBJECT AUTODETECTION | ||||||
91 | |||||||
92 | Sometimes you want to pass values to a template which are objects, but | ||||||
93 | don't have an accessor, so they should be treated like hashrefs instead. | ||||||
94 | |||||||
95 | By default, the class C |
||||||
96 | can specify additional classes with the following syntax: | ||||||
97 | |||||||
98 | engines: | ||||||
99 | template_flute: | ||||||
100 | autodetect: | ||||||
101 | disable: | ||||||
102 | - My::Class1 | ||||||
103 | - My::Class2 | ||||||
104 | |||||||
105 | |||||||
106 | The class matching is checked by L |
||||||
107 | any parent class would do. | ||||||
108 | |||||||
109 | =head2 LOCALIZATION | ||||||
110 | |||||||
111 | Templates can be localized using the Template::Flute::I18N module. You | ||||||
112 | can define a class that provides a method which takes as first (and | ||||||
113 | only argument) the string to translate, and returns the translated | ||||||
114 | one. You have to provide the class and the method. If the class is not | ||||||
115 | provided, no localization is done. If no method is specified, | ||||||
116 | 'localize' will be used. The app will crash if the class doesn't | ||||||
117 | provide such method. | ||||||
118 | |||||||
119 | B | ||||||
120 | translate the string>. | ||||||
121 | |||||||
122 | Example configuration, assuming the class C |
||||||
123 | C |
||||||
124 | |||||||
125 | engines: | ||||||
126 | template_flute: | ||||||
127 | i18n: | ||||||
128 | class: MyApp::Lexicon | ||||||
129 | method: try_to_translate | ||||||
130 | |||||||
131 | |||||||
132 | A class could be something like this: | ||||||
133 | |||||||
134 | package MyTestApp::Lexicon; | ||||||
135 | use Dancer ':syntax'; | ||||||
136 | |||||||
137 | sub new { | ||||||
138 | my $class = shift; | ||||||
139 | debug "Loading up $class"; | ||||||
140 | my $self = { | ||||||
141 | dictionary => { | ||||||
142 | en => { | ||||||
143 | 'TRY' => 'Try', | ||||||
144 | }, | ||||||
145 | it => { | ||||||
146 | 'TRY' => 'Prova', | ||||||
147 | }, | ||||||
148 | } | ||||||
149 | }; | ||||||
150 | bless $self, $class; | ||||||
151 | } | ||||||
152 | |||||||
153 | sub dictionary { | ||||||
154 | return shift->{dictionary}; | ||||||
155 | } | ||||||
156 | |||||||
157 | sub try_to_translate { | ||||||
158 | my ($self, $string) = @_; | ||||||
159 | my $lang = session('lang') || var('lang'); | ||||||
160 | return $string unless $lang; | ||||||
161 | return $string unless $self->dictionary->{$lang}; | ||||||
162 | my $tr = $self->dictionary->{$lang}->{$string}; | ||||||
163 | defined $tr ? return $tr : return $string; | ||||||
164 | } | ||||||
165 | |||||||
166 | 1; | ||||||
167 | |||||||
168 | Optionally, you can pass the options to instantiate the class in the | ||||||
169 | configuration. Like this: | ||||||
170 | |||||||
171 | engines: | ||||||
172 | template_flute: | ||||||
173 | i18n: | ||||||
174 | class: MyApp::Lexicon | ||||||
175 | method: localize | ||||||
176 | options: | ||||||
177 | append: 'X' | ||||||
178 | prepend: 'Y' | ||||||
179 | lexicon: 'path/to/po/files' | ||||||
180 | |||||||
181 | This will call | ||||||
182 | |||||||
183 | MyApp::Lexicon->new(append => 'X', prepend => 'Y', lexicon => 'path/to/po/files'); | ||||||
184 | |||||||
185 | when the engine is initialized, and will call the C |
||||||
186 | on it to get the translations. | ||||||
187 | |||||||
188 | =head2 DEBUG TOOLS | ||||||
189 | |||||||
190 | If you set C |
||||||
191 | will run a check (using the L |
||||||
192 | C |
||||||
193 | of the specifications which are not bound to any HTML elements. | ||||||
194 | |||||||
195 | In this case a debug message is issued (so keep in mind that with | ||||||
196 | higher logging level you are not going to see it). | ||||||
197 | |||||||
198 | Example configuration: | ||||||
199 | |||||||
200 | engines: | ||||||
201 | template_flute: | ||||||
202 | check_dangling: 1 | ||||||
203 | |||||||
204 | |||||||
205 | =head2 FORMS | ||||||
206 | |||||||
207 | Dancer::Template::TemplateFlute includes a form plugin L |
||||||
208 | which supports L |
||||||
209 | |||||||
210 | The token C | ||||||
211 | L |
||||||
212 | L |
||||||
213 | |||||||
214 | =head3 Typical usage for a single form. | ||||||
215 | |||||||
216 | =head4 XML Specification | ||||||
217 | |||||||
218 | |
||||||
219 | |||||||
220 | |
||||||
221 | |
||||||
222 | |
||||||
223 | |||||||
224 | |||||||
225 | |||||||
226 | =head4 HTML | ||||||
227 | |||||||
228 | |||||||
229 | |||||||
230 | Info |
||||||
231 | |
||||||
232 | |
||||||
233 | |||||||
234 | |||||||
235 | |||||||
236 | |
||||||
237 | |||||||
238 | |||||||
239 | |||||||
240 | |
||||||
241 | |||||||
242 | |||||||
243 | |||||||
244 | |
||||||
245 | |||||||
246 | |||||||
247 | |||||||
248 | |||||||
249 | |||||||
250 | |||||||
251 | =head4 Code | ||||||
252 | |||||||
253 | any [qw/get post/] => '/register' => sub { | ||||||
254 | my $form = form('registration'); | ||||||
255 | my %values = %{$form->values}; | ||||||
256 | # VALIDATE, filter, etc. the values | ||||||
257 | $form->fill(\%values); | ||||||
258 | template register => {form => $form }; | ||||||
259 | }; | ||||||
260 | |||||||
261 | =head3 Usage example for multiple forms | ||||||
262 | |||||||
263 | =head4 Specification | ||||||
264 | |||||||
265 | |
||||||
266 | |||||||
267 | |
||||||
268 | |
||||||
269 | |
||||||
270 | |||||||
271 | |||||||
272 | |
||||||
273 | |
||||||
274 | |||||||
275 | |||||||
276 | |||||||
277 | =head4 HTML | ||||||
278 | |||||||
279 | Register |
||||||
280 | |||||||
281 | |||||||
282 | Info |
||||||
283 | |
||||||
284 | |
||||||
285 | |||||||
286 | |||||||
287 | |||||||
288 | |
||||||
289 | |||||||
290 | |||||||
291 | |||||||
292 | |
||||||
293 | |||||||
294 | |||||||
295 | |||||||
296 | |
||||||
297 | |||||||
298 | |||||||
299 | |||||||
300 | |||||||
301 | |||||||
302 | Login |
||||||
303 | |||||||
304 | |||||||
305 | Info |
||||||
306 | |
||||||
307 | |
||||||
308 | |||||||
309 | |||||||
310 | |||||||
311 | |
||||||
312 | |||||||
313 | |||||||
314 | |||||||
315 | |
||||||
316 | |||||||
317 | |||||||
318 | |||||||
319 | |||||||
320 | |||||||
321 | |||||||
322 | |||||||
323 | =head4 Code | ||||||
324 | |||||||
325 | any [qw/get post/] => '/multiple' => sub { | ||||||
326 | my $login = form('logintest'); | ||||||
327 | debug to_dumper({params}); | ||||||
328 | if (params->{login}) { | ||||||
329 | my %vals = %{$login->values}; | ||||||
330 | # VALIDATE %vals here | ||||||
331 | $login->fill(\%vals); | ||||||
332 | } | ||||||
333 | else { | ||||||
334 | # pick from session | ||||||
335 | $login->fill; | ||||||
336 | } | ||||||
337 | my $registration = form('registrationtest'); | ||||||
338 | if (params->{register}) { | ||||||
339 | my %vals = %{$registration->values}; | ||||||
340 | # VALIDATE %vals here | ||||||
341 | $registration->fill(\%vals); | ||||||
342 | } | ||||||
343 | else { | ||||||
344 | # pick from session | ||||||
345 | $registration->fill; | ||||||
346 | } | ||||||
347 | template multiple => { form => [ $login, $registration ] }; | ||||||
348 | }; | ||||||
349 | |||||||
350 | =head1 METHODS | ||||||
351 | |||||||
352 | =head2 default_tmpl_ext | ||||||
353 | |||||||
354 | Returns default template extension. | ||||||
355 | |||||||
356 | =head2 render TEMPLATE TOKENS | ||||||
357 | |||||||
358 | Renders template TEMPLATE with values from TOKENS. | ||||||
359 | |||||||
360 | =cut | ||||||
361 | |||||||
362 | sub default_tmpl_ext { | ||||||
363 | return 'html'; | ||||||
364 | } | ||||||
365 | |||||||
366 | sub _i18n_obj { | ||||||
367 | my $self = shift; | ||||||
368 | unless (exists $self->{_i18n_obj}) { | ||||||
369 | my $conf = $self->config; | ||||||
370 | my $localize; | ||||||
371 | if ($conf and exists $conf->{i18n} and exists $conf->{i18n}->{class}) { | ||||||
372 | my $class = $conf->{i18n}->{class}; | ||||||
373 | load $class; | ||||||
374 | my %args; | ||||||
375 | if ($conf->{i18n}->{options}) { | ||||||
376 | # do a shallow copy and pass that | ||||||
377 | %args = %{ $conf->{i18n}->{options} }; | ||||||
378 | } | ||||||
379 | my $obj = $class->new(%args); | ||||||
380 | my $method = $conf->{i18n}->{method} || 'localize'; | ||||||
381 | # store the closure in the object to avoid loading it up each time | ||||||
382 | $localize = sub { | ||||||
383 | my $to_translate = shift; | ||||||
384 | return $obj->$method($to_translate); | ||||||
385 | }; | ||||||
386 | } | ||||||
387 | # provide a common interface with Template::Flute::I18N | ||||||
388 | $self->{_i18n_obj} = Template::Flute::I18N->new($localize); | ||||||
389 | } | ||||||
390 | return $self->{_i18n_obj}; | ||||||
391 | } | ||||||
392 | |||||||
393 | |||||||
394 | sub render ($$$) { | ||||||
395 | my ($self, $template, $tokens) = @_; | ||||||
396 | my (%args, $flute, $html, $name, $value, %parms, %template_iterators, %iterators, $class); | ||||||
397 | |||||||
398 | %args = (template_file => $template, | ||||||
399 | scopes => 1, | ||||||
400 | auto_iterators => 1, | ||||||
401 | values => $tokens, | ||||||
402 | filters => $self->config->{filters}, | ||||||
403 | autodetect => { disable => [qw/Dancer::Session::Abstract/] }, | ||||||
404 | ); | ||||||
405 | |||||||
406 | # determine whether we need to pass an adjust URI to Template::Flute | ||||||
407 | if (my $request = $tokens->{request}) { | ||||||
408 | my $pos = index($request->path, $request->path_info); | ||||||
409 | if ($pos > 0) { | ||||||
410 | $args{uri} = substr($request->path, 0, $pos); | ||||||
411 | } | ||||||
412 | } | ||||||
413 | |||||||
414 | if (my $i18n = $self->_i18n_obj) { | ||||||
415 | $args{i18n} = $i18n; | ||||||
416 | } | ||||||
417 | |||||||
418 | if (my $email_cids = $tokens->{email_cids}) { | ||||||
419 | $args{email_cids} = $email_cids; | ||||||
420 | } | ||||||
421 | |||||||
422 | if ($self->config->{autodetect} && $self->config->{autodetect}->{disable}) { | ||||||
423 | push @{$args{autodetect}{disable}}, | ||||||
424 | @{$self->config->{autodetect}->{disable}}; | ||||||
425 | } | ||||||
426 | |||||||
427 | $flute = Template::Flute->new(%args); | ||||||
428 | |||||||
429 | # process HTML template to determine iterators used by template | ||||||
430 | $flute->process_template(); | ||||||
431 | |||||||
432 | # instantiate iterators where object isn't yet available | ||||||
433 | if (%template_iterators = $flute->template()->iterators) { | ||||||
434 | my $selector; | ||||||
435 | |||||||
436 | for my $name (keys %template_iterators) { | ||||||
437 | if ($value = $self->config->{iterators}->{$name}) { | ||||||
438 | %parms = %$value; | ||||||
439 | |||||||
440 | $class = "Template::Flute::Iterator::$parms{class}"; | ||||||
441 | |||||||
442 | if ($parms{file}) { | ||||||
443 | $parms{file} = Template::Flute::Utils::derive_filename($template, | ||||||
444 | $parms{file}, 1); | ||||||
445 | } | ||||||
446 | |||||||
447 | if ($selector = delete $parms{selector}) { | ||||||
448 | if ($selector eq '*') { | ||||||
449 | $parms{selector} = '*'; | ||||||
450 | } | ||||||
451 | elsif ($tokens->{$selector}) { | ||||||
452 | $parms{selector} = {$selector => $tokens->{$selector}}; | ||||||
453 | } | ||||||
454 | } | ||||||
455 | |||||||
456 | eval "require $class"; | ||||||
457 | if ($@) { | ||||||
458 | die "Failed to load class $class for iterator $name: $@\n"; | ||||||
459 | } | ||||||
460 | |||||||
461 | eval { | ||||||
462 | $iterators{$name} = $class->new(%parms); | ||||||
463 | }; | ||||||
464 | |||||||
465 | if ($@) { | ||||||
466 | die "Failed to instantiate class $class for iterator $name: $@\n"; | ||||||
467 | } | ||||||
468 | |||||||
469 | $flute->specification->set_iterator($name, $iterators{$name}); | ||||||
470 | } | ||||||
471 | } | ||||||
472 | } | ||||||
473 | |||||||
474 | # check for forms | ||||||
475 | if (my @forms = $flute->template->forms()) { | ||||||
476 | if ($tokens->{form}) { | ||||||
477 | $self->_tf_manage_forms($flute, $tokens, @forms); | ||||||
478 | } | ||||||
479 | else { | ||||||
480 | Dancer::Logger::debug('Missing form parameters for forms ' . | ||||||
481 | join(", ", sort map { $_->name } @forms)); | ||||||
482 | } | ||||||
483 | } | ||||||
484 | elsif ($tokens->{form}) { | ||||||
485 | Dancer::Logger::debug('Form passed, but no forms found in the template.'); | ||||||
486 | } | ||||||
487 | |||||||
488 | $html = $flute->process(); | ||||||
489 | |||||||
490 | if ($self->config->{check_dangling}) { | ||||||
491 | if (my @warnings = $flute->specification->dangling) { | ||||||
492 | foreach my $warn (@warnings) { | ||||||
493 | Dancer::Logger::debug('Found dangling element ' | ||||||
494 | . $warn->{type} . ' ' . $warn->{name} | ||||||
495 | . ' (' , $warn->{dump} , ')'); | ||||||
496 | } | ||||||
497 | } | ||||||
498 | } | ||||||
499 | return $html; | ||||||
500 | } | ||||||
501 | |||||||
502 | sub _tf_manage_forms { | ||||||
503 | my ($self, $flute, $tokens, @forms) = @_; | ||||||
504 | |||||||
505 | # simple case: only one form passed and one in the flute | ||||||
506 | if (ref($tokens->{form}) ne 'ARRAY') { | ||||||
507 | my $form_name = $tokens->{form}->name; | ||||||
508 | if (@forms == 1) { | ||||||
509 | my $form = shift @forms; | ||||||
510 | if ($form_name eq 'main' or | ||||||
511 | $form_name eq $form->name) { | ||||||
512 | # Dancer::Logger::debug("Filling the template form with" . Dumper($tokens->{form}->values)); | ||||||
513 | $self->_tf_fill_forms($flute, $tokens->{form}, $form, $tokens); | ||||||
514 | } | ||||||
515 | } | ||||||
516 | else { | ||||||
517 | my $found = 0; | ||||||
518 | foreach my $form (@forms) { | ||||||
519 | # Dancer::Logger::debug("Filling the template form with" . Dumper($tokens->{form}->values)); | ||||||
520 | if ($form_name eq $form->name) { | ||||||
521 | $self->_tf_fill_forms($flute, $tokens->{form}, $form, $tokens); | ||||||
522 | $found++; | ||||||
523 | } | ||||||
524 | } | ||||||
525 | if ($found != 1) { | ||||||
526 | Dancer::Logger::error("Multiple form are not being managed correctly, found $found corresponding forms, but we expected just one!") | ||||||
527 | } | ||||||
528 | } | ||||||
529 | } | ||||||
530 | else { | ||||||
531 | foreach my $passed_form (@{$tokens->{form}}) { | ||||||
532 | foreach my $form (@forms) { | ||||||
533 | if ($passed_form->name eq $form->name) { | ||||||
534 | $self->_tf_fill_forms($flute, $passed_form, $form, $tokens); | ||||||
535 | } | ||||||
536 | } | ||||||
537 | } | ||||||
538 | } | ||||||
539 | } | ||||||
540 | |||||||
541 | |||||||
542 | sub _tf_fill_forms { | ||||||
543 | my ($self, $flute, $passed_form, $form, $tokens) = @_; | ||||||
544 | # arguments: | ||||||
545 | # $flute is the template object. | ||||||
546 | |||||||
547 | # $passed_form is the Dancer::Plugin::Form object we got from the | ||||||
548 | # tokens, which is $tokens->{form} when we have just a single one. | ||||||
549 | |||||||
550 | # $form is the form object we got from the template itself, with | ||||||
551 | # $flute->template->forms | ||||||
552 | |||||||
553 | # $tokens is the hashref passed to the template. We need it for the | ||||||
554 | # iterators. | ||||||
555 | |||||||
556 | my ($iter, $action); | ||||||
557 | for my $name ($form->iterators) { | ||||||
558 | if (ref($tokens->{$name}) eq 'ARRAY') { | ||||||
559 | $iter = Template::Flute::Iterator->new($tokens->{$name}); | ||||||
560 | $flute->specification->set_iterator($name, $iter); | ||||||
561 | } | ||||||
562 | } | ||||||
563 | if ($action = $passed_form->action()) { | ||||||
564 | $form->set_action($action); | ||||||
565 | } | ||||||
566 | $passed_form->fields([map {$_->{name}} @{$form->fields()}]); | ||||||
567 | $form->fill($passed_form->fill()); | ||||||
568 | |||||||
569 | if (Dancer::Config::settings->{session}) { | ||||||
570 | $passed_form->to_session; | ||||||
571 | } | ||||||
572 | } | ||||||
573 | |||||||
574 | |||||||
575 | =head1 SEE ALSO | ||||||
576 | |||||||
577 | L |
||||||
578 | |||||||
579 | =head1 AUTHOR | ||||||
580 | |||||||
581 | Stefan Hornburg (Racke), |
||||||
582 | |||||||
583 | =head1 BUGS | ||||||
584 | |||||||
585 | Please report any bugs or feature requests to C |
||||||
586 | the web interface at L |
||||||
587 | |||||||
588 | =head1 SUPPORT | ||||||
589 | |||||||
590 | You can find documentation for this module with the perldoc command. | ||||||
591 | |||||||
592 | perldoc Template::Flute | ||||||
593 | |||||||
594 | You can also look for information at: | ||||||
595 | |||||||
596 | =over 4 | ||||||
597 | |||||||
598 | =item * RT: CPAN's request tracker | ||||||
599 | |||||||
600 | L |
||||||
601 | |||||||
602 | =item * AnnoCPAN: Annotated CPAN documentation | ||||||
603 | |||||||
604 | L |
||||||
605 | |||||||
606 | =item * CPAN Ratings | ||||||
607 | |||||||
608 | L |
||||||
609 | |||||||
610 | =item * Search CPAN | ||||||
611 | |||||||
612 | L |
||||||
613 | |||||||
614 | =back | ||||||
615 | |||||||
616 | =head1 LICENSE AND COPYRIGHT | ||||||
617 | |||||||
618 | Copyright 2011-2014 Stefan Hornburg (Racke) |
||||||
619 | |||||||
620 | This program is free software; you can redistribute it and/or modify it | ||||||
621 | under the terms of either: the GNU General Public License as published | ||||||
622 | by the Free Software Foundation; or the Artistic License. | ||||||
623 | |||||||
624 | See http://dev.perl.org/licenses/ for more information. | ||||||
625 | |||||||
626 | =cut | ||||||
627 | |||||||
628 | 1; |