package Dancer::Template::TemplateFlute;
use strict;
use warnings;
use Template::Flute;
use Template::Flute::Iterator;
use Template::Flute::Utils;
use Template::Flute::I18N;
use Module::Load;
use Dancer::Config;
use base 'Dancer::Template::Abstract';
our $VERSION = '0.0099';
=head1 NAME
Dancer::Template::TemplateFlute - Template::Flute wrapper for Dancer
=head1 VERSION
Version 0.0099
=head1 DESCRIPTION
This class is an interface between Dancer's template engine abstraction layer
and the L<Template::Flute> module.
In order to use this engine, use the template setting:
template: template_flute
The default template extension is ".html".
=head2 LAYOUT
Each layout needs a specification file and a template file. To embed
the content of your current view into the layout, put the following
into your specification file, e.g. F<views/layouts/main.xml>:
<specification>
<value name="content" id="content" op="hook"/>
</specification>
This replaces the contents of the following block in your HTML
template, e.g. F<views/layouts/main.html>:
<div id="content">
Your content
</div>
=head2 ITERATORS
Iterators can be specified explicitly in the configuration file as below.
engines:
template_flute:
iterators:
fruits:
class: JSON
file: fruits.json
=head2 FILTER OPTIONS
Filter options and classes can be specified in the configuration file as below.
engines:
template_flute:
filters:
currency:
options:
int_curr_symbol: "$"
image:
class: "Flowers::Filters::Image"
=head2 LOCALIZATION
Templates can be localized using the Template::Flute::I18N module. You
can define a class that provides a method which takes as first (and
only argument) the string to translate, and returns the translated
one. You have to provide the class and the method. If the class is not
provided, no localization is done. If no method is specified,
'localize' will be used. The app will crash if the class doesn't
provide such method.
B<Be sure to return the argument verbatim if the module is not able to
translate the string>.
Example configuration, assuming the class C<MyApp::Lexicon> provides a
C<try_to_translate> method.
engines:
template_flute:
i18n:
class: MyApp::Lexicon
method: try_to_translate
A class could be something like this:
package MyTestApp::Lexicon;
use Dancer ':syntax';
sub new {
my $class = shift;
debug "Loading up $class";
my $self = {
dictionary => {
en => {
'TRY' => 'Try',
},
it => {
'TRY' => 'Prova',
},
}
};
bless $self, $class;
}
sub dictionary {
return shift->{dictionary};
}
sub try_to_translate {
my ($self, $string) = @_;
my $lang = session('lang') || var('lang');
return $string unless $lang;
return $string unless $self->dictionary->{$lang};
my $tr = $self->dictionary->{$lang}->{$string};
defined $tr ? return $tr : return $string;
}
1;
=head2 FORMS
Dancer::Template::TemplateFlute includes a form plugin L<Dancer::Plugin::Form>,
which supports L<Template::Flute> forms.
The token C<form> is reserved for forms. It can be a single
L<Dancer::Plugin::Form> object or an arrayref of
L<Dancer::Plugin::Form> objects.
=head3 Typical usage for a single form.
=head4 XML Specification
<specification>
<form name="registration" link="name">
<field name="email"/>
<field name="password"/>
<field name="verify"/>
</form>
</specification>
=head4 HTML
<form class="frm-default" name="registration" action="/register" method="POST">
<fieldset>
<div class="reg-info">Info</div>
<ul>
<li>
<label>Email</label>
<input type="text" name="email"/>
</li>
<li>
<label>Password</label>
<input type="text" name="password"/>
</li>
<li>
<label>Confirm password</label>
<input type="text" name="verify" />
</li>
<li>
<input type="submit" value="Register" class="btn-submit" />
</li>
</ul>
</fieldset>
</form>
=head4 Code
any [qw/get post/] => '/register' => sub {
my $form = form('registration');
my %values = %{$form->values};
# VALIDATE, filter, etc. the values
$form->fill(\%values);
template register => {form => $form };
};
=head3 Usage example for multiple forms
=head4 Specification
<specification>
<form name="registrationtest" link="name">
<field name="emailtest"/>
<field name="passwordtest"/>
<field name="verifytest"/>
</form>
<form name="logintest" link="name">
<field name="emailtest_2"/>
<field name="passwordtest_2"/>
</form>
</specification>
=head4 HTML
<h1>Register</h1>
<form class="frm-default" name="registrationtest" action="/multiple" method="POST">
<fieldset>
<div class="reg-info">Info</div>
<ul>
<li>
<label>Email</label>
<input type="text" name="emailtest"/>
</li>
<li>
<label>Password</label>
<input type="text" name="passwordtest"/>
</li>
<li>
<label>Confirm password</label>
<input type="text" name="verifytest" />
</li>
<li>
<input type="submit" name="register" value="Register" class="btn-submit" />
</li>
</ul>
</fieldset>
</form>
<h1>Login</h1>
<form class="frm-default" name="logintest" action="/multiple" method="POST">
<fieldset>
<div class="reg-info">Info</div>
<ul>
<li>
<label>Email</label>
<input type="text" name="emailtest_2"/>
</li>
<li>
<label>Password</label>
<input type="text" name="passwordtest_2"/>
</li>
<li>
<input type="submit" name="login" value="Login" class="btn-submit" />
</li>
</ul>
</fieldset>
</form>
=head4 Code
any [qw/get post/] => '/multiple' => sub {
my $login = form('logintest');
debug to_dumper({params});
if (params->{login}) {
my %vals = %{$login->values};
# VALIDATE %vals here
$login->fill(\%vals);
}
else {
# pick from session
$login->fill;
}
my $registration = form('registrationtest');
if (params->{register}) {
my %vals = %{$registration->values};
# VALIDATE %vals here
$registration->fill(\%vals);
}
else {
# pick from session
$registration->fill;
}
template multiple => { form => [ $login, $registration ] };
};
=head1 METHODS
=head2 default_tmpl_ext
Returns default template extension.
=head2 render TEMPLATE TOKENS
Renders template TEMPLATE with values from TOKENS.
=cut
sub default_tmpl_ext {
return 'html';
}
sub _i18n_sub {
my $self = shift;
unless (exists $self->{_i18n_sub}) {
my $conf = $self->config;
my $localize;
if ($conf and exists $conf->{i18n} and exists $conf->{i18n}->{class}) {
my $class = $conf->{i18n}->{class};
load $class;
my $obj = $class->new;
my $method = $conf->{i18n}->{method} || 'localize';
# store the closure in the object to avoid loading it up each time
$localize = sub {
my $to_translate = shift;
return $obj->$method($to_translate);
};
}
$self->{_i18n_sub} = $localize;
}
return $self->{_i18n_sub};
}
sub render ($$$) {
my ($self, $template, $tokens) = @_;
my (%args, $flute, $html, $name, $value, %parms, %template_iterators, %iterators, $class);
%args = (template_file => $template,
scopes => 1,
auto_iterators => 1,
values => $tokens,
filters => $self->config->{filters},
);
if (my $i18n = $self->_i18n_sub) {
$args{i18n} = Template::Flute::I18N->new($i18n);
}
$flute = Template::Flute->new(%args);
# process HTML template to determine iterators used by template
$flute->process_template();
# instantiate iterators where object isn't yet available
if (%template_iterators = $flute->template()->iterators) {
my $selector;
for my $name (keys %template_iterators) {
if ($value = $self->config->{iterators}->{$name}) {
%parms = %$value;
$class = "Template::Flute::Iterator::$parms{class}";
if ($parms{file}) {
$parms{file} = Template::Flute::Utils::derive_filename($template,
$parms{file}, 1);
}
if ($selector = delete $parms{selector}) {
if ($selector eq '*') {
$parms{selector} = '*';
}
elsif ($tokens->{$selector}) {
$parms{selector} = {$selector => $tokens->{$selector}};
}
}
eval "require $class";
if ($@) {
die "Failed to load class $class for iterator $name: $@\n";
}
eval {
$iterators{$name} = $class->new(%parms);
};
if ($@) {
die "Failed to instantiate class $class for iterator $name: $@\n";
}
$flute->specification->set_iterator($name, $iterators{$name});
}
}
}
# check for forms
if (my @forms = $flute->template->forms()) {
if ($tokens->{form}) {
$self->_tf_manage_forms($flute, $tokens, @forms);
}
else {
Dancer::Logger::debug('Missing form parameters for forms ' .
join(", ", sort map { $_->name } @forms));
}
}
$html = $flute->process();
return $html;
}
sub _tf_manage_forms {
my ($self, $flute, $tokens, @forms) = @_;
# simple case: only one form passed and one in the flute
if (ref($tokens->{form}) ne 'ARRAY') {
my $form_name = $tokens->{form}->name;
if (@forms == 1) {
my $form = shift @forms;
if ($form_name eq 'main' or
$form_name eq $form->name) {
# Dancer::Logger::debug("Filling the template form with" . Dumper($tokens->{form}->values));
$self->_tf_fill_forms($flute, $tokens->{form}, $form, $tokens);
}
}
else {
my $found = 0;
foreach my $form (@forms) {
# Dancer::Logger::debug("Filling the template form with" . Dumper($tokens->{form}->values));
if ($form_name eq $form->name) {
$self->_tf_fill_forms($flute, $tokens->{form}, $form, $tokens);
$found++;
}
}
if ($found != 1) {
Dancer::Logger::error("Multiple form are not being managed correctly, found $found corresponding forms, but we expected just one!")
}
}
}
else {
foreach my $passed_form (@{$tokens->{form}}) {
foreach my $form (@forms) {
if ($passed_form->name eq $form->name) {
$self->_tf_fill_forms($flute, $passed_form, $form, $tokens);
}
}
}
}
}
sub _tf_fill_forms {
my ($self, $flute, $passed_form, $form, $tokens) = @_;
# arguments:
# $flute is the template object.
# $passed_form is the Dancer::Plugin::Form object we got from the
# tokens, which is $tokens->{form} when we have just a single one.
# $form is the form object we got from the template itself, with
# $flute->template->forms
# $tokens is the hashref passed to the template. We need it for the
# iterators.
my ($iter, $action);
for my $name ($form->iterators) {
if (ref($tokens->{$name}) eq 'ARRAY') {
$iter = Template::Flute::Iterator->new($tokens->{$name});
$flute->specification->set_iterator($name, $iter);
}
}
if ($action = $passed_form->action()) {
$form->set_action($action);
}
$passed_form->fields([map {$_->{name}} @{$form->fields()}]);
$form->fill($passed_form->fill());
if (Dancer::Config::settings->{session}) {
$passed_form->to_session;
}
}
=head1 SEE ALSO
L<Dancer>, L<Template::Flute>
=head1 AUTHOR
Stefan Hornburg (Racke), <racke@linuxia.de>
=head1 BUGS
Please report any bugs or feature requests to C<bug-template-flute at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Flute>.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Template::Flute
You can also look for information at:
=over 4
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Dancer-Template-TemplateFlute>
=item * AnnoCPAN: Annotated CPAN documentation
L<http://annocpan.org/dist/Dancer-Template-TemplateFlute>
=item * CPAN Ratings
L<http://cpanratings.perl.org/d/Dancer-Template-TemplateFlute>
=item * Search CPAN
L<http://search.cpan.org/dist/Dancer-Template-TemplateFlute/>
=back
=head1 LICENSE AND COPYRIGHT
Copyright 2011-2014 Stefan Hornburg (Racke) <racke@linuxia.de>.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
=cut
1;