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   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 module.
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             Cpath> is different from Cpath_info>.
82              
83             =head2 EMBEDDING IMAGES IN EMAILS
84              
85             If you pass a value named C, which should be an empty hash
86             reference, all the images C attributes will be rewritten using
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 is treated this way. You
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 with C, so
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 provides a
123             C method.
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 method
186             on it to get the translations.
187              
188             =head2 DEBUG TOOLS
189              
190             If you set C in the engine stanza, the specification
191             will run a check (using the L's
192             C method) against the template to see if you have elements
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 forms.
209              
210             The token C
is reserved for forms. It can be a single
211             L object or an arrayref of
212             L objects.
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, 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, or through
    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;