File Coverage

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   13246 use strict;
  1         2  
  1         28  
4 1     1   3 use warnings;
  1         1  
  1         20  
5              
6 1     1   167 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             use Scalar::Util qw/blessed/;
12              
13             use Dancer::Config;
14              
15             use base 'Dancer::Template::Abstract';
16              
17             our $VERSION = '0.0142';
18              
19             =head1 NAME
20              
21             Dancer::Template::TemplateFlute - Template::Flute wrapper for Dancer
22              
23             =head1 VERSION
24              
25             Version 0.0142
26              
27             =head1 DESCRIPTION
28              
29             This class is an interface between Dancer's template engine abstraction layer
30             and the L module.
31              
32             In order to use this engine, use the template setting:
33              
34             template: template_flute
35              
36             The default template extension is ".html".
37              
38             =head2 LAYOUT
39              
40             Each layout needs a specification file and a template file. To embed
41             the content of your current view into the layout, put the following
42             into your specification file, e.g. F:
43              
44            
45            
46            
47              
48             This replaces the contents of the following block in your HTML
49             template, e.g. F:
50              
51            
52             Your content
53            
54              
55             =head2 ITERATORS
56              
57             Iterators can be specified explicitly in the configuration file as below.
58              
59             engines:
60             template_flute:
61             iterators:
62             fruits:
63             class: JSON
64             file: fruits.json
65              
66             =head2 FILTER OPTIONS
67              
68             Filter options and classes can be specified in the configuration file as below.
69              
70             engines:
71             template_flute:
72             filters:
73             currency:
74             options:
75             int_curr_symbol: "$"
76             image:
77             class: "Flowers::Filters::Image"
78              
79             =head2 ADJUSTING URIS
80              
81             We automatically adjust links in the templates if the value of
82             Cpath> is different from Cpath_info>.
83              
84             =head2 EMBEDDING IMAGES IN EMAILS
85              
86             If you pass a value named C, which should be an empty hash
87             reference, all the images C attributes will be rewritten using
88             the CIDs, and the reference will be populated with an hashref, as
89             documented in L
90              
91             Further options for the CIDs should be passed in an optional value
92             named C. See L for them.
93              
94              
95             =head2 DISABLE OBJECT AUTODETECTION
96              
97             Sometimes you want to pass values to a template which are objects, but
98             don't have an accessor, so they should be treated like hashrefs instead.
99              
100             By default, the class C is treated this way. You
101             can specify additional classes with the following syntax:
102              
103             engines:
104             template_flute:
105             autodetect:
106             disable:
107             - My::Class1
108             - My::Class2
109              
110              
111             The class matching is checked by L with C, so
112             any parent class would do.
113              
114             =head2 LOCALIZATION
115              
116             Templates can be localized using the Template::Flute::I18N module. You
117             can define a class that provides a method which takes as first (and
118             only argument) the string to translate, and returns the translated
119             one. You have to provide the class and the method. If the class is not
120             provided, no localization is done. If no method is specified,
121             'localize' will be used. The app will crash if the class doesn't
122             provide such method.
123              
124             B
125             translate the string>.
126              
127             Example configuration, assuming the class C provides a
128             C method.
129              
130             engines:
131             template_flute:
132             i18n:
133             class: MyApp::Lexicon
134             method: try_to_translate
135              
136              
137             A class could be something like this:
138              
139             package MyTestApp::Lexicon;
140             use Dancer ':syntax';
141              
142             sub new {
143             my $class = shift;
144             debug "Loading up $class";
145             my $self = {
146             dictionary => {
147             en => {
148             'TRY' => 'Try',
149             },
150             it => {
151             'TRY' => 'Prova',
152             },
153             }
154             };
155             bless $self, $class;
156             }
157              
158             sub dictionary {
159             return shift->{dictionary};
160             }
161              
162             sub try_to_translate {
163             my ($self, $string) = @_;
164             my $lang = session('lang') || var('lang');
165             return $string unless $lang;
166             return $string unless $self->dictionary->{$lang};
167             my $tr = $self->dictionary->{$lang}->{$string};
168             defined $tr ? return $tr : return $string;
169             }
170              
171             1;
172              
173             Optionally, you can pass the options to instantiate the class in the
174             configuration. Like this:
175              
176             engines:
177             template_flute:
178             i18n:
179             class: MyApp::Lexicon
180             method: localize
181             options:
182             append: 'X'
183             prepend: 'Y'
184             lexicon: 'path/to/po/files'
185              
186             This will call
187              
188             MyApp::Lexicon->new(append => 'X', prepend => 'Y', lexicon => 'path/to/po/files');
189              
190             when the engine is initialized, and will call the C method
191             on it to get the translations.
192              
193             =head2 DEBUG TOOLS
194              
195             If you set C in the engine stanza, the specification
196             will run a check (using the L's
197             C method) against the template to see if you have elements
198             of the specifications which are not bound to any HTML elements.
199              
200             In this case a debug message is issued (so keep in mind that with
201             higher logging level you are not going to see it).
202              
203             Example configuration:
204              
205             engines:
206             template_flute:
207             check_dangling: 1
208              
209             When the environment is set to C this feature is turned
210             on by default. You can silence the logs by setting:
211              
212             engines:
213             template_flute:
214             disable_check_dangling: 1
215              
216             =head2 FORMS
217              
218             Dancer::Template::TemplateFlute includes a form plugin L,
219             which supports L forms.
220              
221             The token C
is reserved for forms. It can be a single
222             L object or an arrayref of
223             L objects.
224              
225             =head3 Typical usage for a single form.
226              
227             =head4 XML Specification
228              
229            
230            
231            
232            
233            
234            
235            
236              
237             =head4 HTML
238              
239            
240            
241            
Info
242            
243            
  • 244            
    245            
    246            
    247            
  • 248            
    249            
    250            
    251            
  • 252            
    253            
    254            
    255            
  • 256            
    257            
    258            
    259            
    260            
    261              
    262             =head4 Code
    263              
    264             any [qw/get post/] => '/register' => sub {
    265             my $form = form('registration');
    266             my %values = %{$form->values};
    267             # VALIDATE, filter, etc. the values
    268             $form->fill(\%values);
    269             template register => {form => $form };
    270             };
    271              
    272             =head3 Usage example for multiple forms
    273              
    274             =head4 Specification
    275              
    276            
    277            
    278            
    279            
    280            
    281            
    282            
    283            
    284            
    285            
    286            
    287              
    288             =head4 HTML
    289              
    290            

    Register

    291            
    292            
    293            
    Info
    294            
    295            
  • 296            
    297            
    298            
    299            
  • 300            
    301            
    302            
    303            
  • 304            
    305            
    306            
    307            
  • 308            
    309            
    310            
    311            
    312            
    313            

    Login

    314            
    315            
    316            
    Info
    317            
    318            
  • 319            
    320            
    321            
    322            
  • 323            
    324            
    325            
    326            
  • 327            
    328            
    329            
    330            
    331            
    332              
    333              
    334             =head4 Code
    335              
    336             any [qw/get post/] => '/multiple' => sub {
    337             my $login = form('logintest');
    338             debug to_dumper({params});
    339             if (params->{login}) {
    340             my %vals = %{$login->values};
    341             # VALIDATE %vals here
    342             $login->fill(\%vals);
    343             }
    344             else {
    345             # pick from session
    346             $login->fill;
    347             }
    348             my $registration = form('registrationtest');
    349             if (params->{register}) {
    350             my %vals = %{$registration->values};
    351             # VALIDATE %vals here
    352             $registration->fill(\%vals);
    353             }
    354             else {
    355             # pick from session
    356             $registration->fill;
    357             }
    358             template multiple => { form => [ $login, $registration ] };
    359             };
    360              
    361             =head1 METHODS
    362              
    363             =head2 default_tmpl_ext
    364              
    365             Returns default template extension.
    366              
    367             =head2 render TEMPLATE TOKENS
    368              
    369             Renders template TEMPLATE with values from TOKENS.
    370              
    371             =cut
    372              
    373             sub default_tmpl_ext {
    374             return 'html';
    375             }
    376              
    377             sub _i18n_obj {
    378             my $self = shift;
    379             unless (exists $self->{_i18n_obj}) {
    380             my $conf = $self->config;
    381             my $localize;
    382             if ($conf and exists $conf->{i18n} and exists $conf->{i18n}->{class}) {
    383             my $class = $conf->{i18n}->{class};
    384             load $class;
    385             my %args;
    386             if ($conf->{i18n}->{options}) {
    387             # do a shallow copy and pass that
    388             %args = %{ $conf->{i18n}->{options} };
    389             }
    390             my $obj = $class->new(%args);
    391             my $method = $conf->{i18n}->{method} || 'localize';
    392             # store the closure in the object to avoid loading it up each time
    393             $localize = sub {
    394             my $to_translate = shift;
    395             return $obj->$method($to_translate);
    396             };
    397             }
    398             # provide a common interface with Template::Flute::I18N
    399             $self->{_i18n_obj} = Template::Flute::I18N->new($localize);
    400             }
    401             return $self->{_i18n_obj};
    402             }
    403              
    404              
    405             sub render ($$$) {
    406             my ($self, $template, $tokens) = @_;
    407             my (%args, $flute, $html, $name, $value, %parms, %template_iterators, %iterators, $class);
    408              
    409             %args = (template_file => $template,
    410             scopes => 1,
    411             auto_iterators => 1,
    412             values => $tokens,
    413             filters => $self->config->{filters},
    414             autodetect => { disable => [qw/Dancer::Session::Abstract/] },
    415             );
    416              
    417             # determine whether we need to pass an adjust URI to Template::Flute
    418             if (my $request = $tokens->{request}) {
    419             my $pos = index($request->path, $request->path_info);
    420             if ($pos > 0) {
    421             $args{uri} = substr($request->path, 0, $pos);
    422             }
    423             }
    424              
    425             if (my $i18n = $self->_i18n_obj) {
    426             $args{i18n} = $i18n;
    427             }
    428              
    429             if (my $email_cids = $tokens->{email_cids}) {
    430             $args{email_cids} = $email_cids;
    431             # use the 'cids' tokens only if email_cids is defined
    432             if (my $cid_options = $tokens->{cids}) {
    433             $args{cids} = { %$cid_options };
    434             }
    435             }
    436              
    437             if ($self->config->{autodetect} && $self->config->{autodetect}->{disable}) {
    438             push @{$args{autodetect}{disable}},
    439             @{$self->config->{autodetect}->{disable}};
    440             }
    441              
    442             $flute = Template::Flute->new(%args);
    443              
    444             # process HTML template to determine iterators used by template
    445             $flute->process_template();
    446              
    447             # instantiate iterators where object isn't yet available
    448             if (%template_iterators = $flute->template()->iterators) {
    449             my $selector;
    450              
    451             for my $name (keys %template_iterators) {
    452             if ($value = $self->config->{iterators}->{$name}) {
    453             %parms = %$value;
    454            
    455             $class = "Template::Flute::Iterator::$parms{class}";
    456              
    457             if ($parms{file}) {
    458             $parms{file} = Template::Flute::Utils::derive_filename($template,
    459             $parms{file}, 1);
    460             }
    461              
    462             if ($selector = delete $parms{selector}) {
    463             if ($selector eq '*') {
    464             $parms{selector} = '*';
    465             }
    466             elsif ($tokens->{$selector}) {
    467             $parms{selector} = {$selector => $tokens->{$selector}};
    468             }
    469             }
    470              
    471             eval "require $class";
    472             if ($@) {
    473             die "Failed to load class $class for iterator $name: $@\n";
    474             }
    475              
    476             eval {
    477             $iterators{$name} = $class->new(%parms);
    478             };
    479            
    480             if ($@) {
    481             die "Failed to instantiate class $class for iterator $name: $@\n";
    482             }
    483              
    484             $flute->specification->set_iterator($name, $iterators{$name});
    485             }
    486             }
    487             }
    488              
    489             # check for forms
    490             if (my @forms = $flute->template->forms()) {
    491             if ($tokens->{form}) {
    492             $self->_tf_manage_forms($flute, $tokens, @forms);
    493             }
    494             else {
    495             Dancer::Logger::debug('Missing form parameters for forms ' .
    496             join(", ", sort map { $_->name } @forms));
    497             }
    498             }
    499             elsif ($tokens->{form}) {
    500             my $form_name = blessed($tokens->{form}) ? $tokens->{form}->name : $tokens->{form};
    501              
    502             Dancer::Logger::debug("Form $form_name passed, but no forms found in the template $template.");
    503             }
    504              
    505             $html = $flute->process();
    506              
    507             if ($self->config->{check_dangling} or
    508             ($tokens->{settings}->{environment} eq 'development' &&
    509             !$self->config->{disable_check_dangling})) {
    510              
    511             if (my @warnings = $flute->specification->dangling) {
    512             foreach my $warn (@warnings) {
    513             Dancer::Logger::debug('Found dangling element '
    514             . $warn->{type} . ' ' . $warn->{name}
    515             . ' (' , $warn->{dump} , ')');
    516             }
    517             }
    518             }
    519             return $html;
    520             }
    521              
    522             sub _tf_manage_forms {
    523             my ($self, $flute, $tokens, @forms) = @_;
    524              
    525             # simple case: only one form passed and one in the flute
    526             if (ref($tokens->{form}) ne 'ARRAY') {
    527             my $form_name = $tokens->{form}->name;
    528             if (@forms == 1) {
    529             my $form = shift @forms;
    530             if ($form_name eq 'main' or
    531             $form_name eq $form->name) {
    532             # Dancer::Logger::debug("Filling the template form with" . Dumper($tokens->{form}->values));
    533             $self->_tf_fill_forms($flute, $tokens->{form}, $form, $tokens);
    534             }
    535             }
    536             else {
    537             my $found = 0;
    538             foreach my $form (@forms) {
    539             # Dancer::Logger::debug("Filling the template form with" . Dumper($tokens->{form}->values));
    540             if ($form_name eq $form->name) {
    541             $self->_tf_fill_forms($flute, $tokens->{form}, $form, $tokens);
    542             $found++;
    543             }
    544             }
    545             if ($found != 1) {
    546             Dancer::Logger::error("Multiple form are not being managed correctly, found $found corresponding forms, but we expected just one!")
    547             }
    548             }
    549             }
    550             else {
    551             foreach my $passed_form (@{$tokens->{form}}) {
    552             foreach my $form (@forms) {
    553             if ($passed_form->name eq $form->name) {
    554             $self->_tf_fill_forms($flute, $passed_form, $form, $tokens);
    555             }
    556             }
    557             }
    558             }
    559             }
    560              
    561              
    562             sub _tf_fill_forms {
    563             my ($self, $flute, $passed_form, $form, $tokens) = @_;
    564             # arguments:
    565             # $flute is the template object.
    566              
    567             # $passed_form is the Dancer::Plugin::Form object we got from the
    568             # tokens, which is $tokens->{form} when we have just a single one.
    569              
    570             # $form is the form object we got from the template itself, with
    571             # $flute->template->forms
    572              
    573             # $tokens is the hashref passed to the template. We need it for the
    574             # iterators.
    575              
    576             my ($iter, $action);
    577             for my $name ($form->iterators) {
    578             if (ref($tokens->{$name}) eq 'ARRAY') {
    579             $iter = Template::Flute::Iterator->new($tokens->{$name});
    580             $flute->specification->set_iterator($name, $iter);
    581             }
    582             }
    583             if ($action = $passed_form->action()) {
    584             $form->set_action($action);
    585             }
    586             $passed_form->fields([map {$_->{name}} @{$form->fields()}]);
    587             $form->fill($passed_form->fill());
    588              
    589             if (Dancer::Config::settings->{session}) {
    590             $passed_form->to_session;
    591             }
    592             }
    593              
    594              
    595             =head1 SEE ALSO
    596              
    597             L, L
    598              
    599             =head1 AUTHOR
    600              
    601             Stefan Hornburg (Racke),
    602              
    603             =head1 BUGS
    604              
    605             Please report any bugs or feature requests to C, or through
    606             the web interface at L.
    607              
    608             =head1 SUPPORT
    609              
    610             You can find documentation for this module with the perldoc command.
    611              
    612             perldoc Template::Flute
    613              
    614             You can also look for information at:
    615              
    616             =over 4
    617              
    618             =item * RT: CPAN's request tracker
    619              
    620             L
    621              
    622             =item * AnnoCPAN: Annotated CPAN documentation
    623              
    624             L
    625              
    626             =item * CPAN Ratings
    627              
    628             L
    629              
    630             =item * Search CPAN
    631              
    632             L
    633              
    634             =back
    635              
    636             =head1 LICENSE AND COPYRIGHT
    637              
    638             Copyright 2011-2015 Stefan Hornburg (Racke) .
    639              
    640             This program is free software; you can redistribute it and/or modify it
    641             under the terms of either: the GNU General Public License as published
    642             by the Free Software Foundation; or the Artistic License.
    643              
    644             See http://dev.perl.org/licenses/ for more information.
    645              
    646             =cut
    647              
    648             1;