From d445880375bce1462b8f9a8b1a502b34c296d41f Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Mon, 28 Jan 2013 16:18:07 +0100 Subject: [PATCH] =?utf8?q?SL::Presenter=20--=20die=20neue=20Pr=C3=A4sentat?= =?utf8?q?ionsschicht?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- SL/Controller/Base.pm | 99 ++++----------- SL/Presenter.pm | 235 ++++++++++++++++++++++++++++++++++++ SL/Presenter/EscapedText.pm | 100 +++++++++++++++ SL/Template/Plugin/L.pm | 6 +- SL/Template/Plugin/P.pm | 106 ++++++++++++++++ 5 files changed, 469 insertions(+), 77 deletions(-) create mode 100644 SL/Presenter.pm create mode 100644 SL/Presenter/EscapedText.pm create mode 100644 SL/Template/Plugin/P.pm diff --git a/SL/Controller/Base.pm b/SL/Controller/Base.pm index 64ac328bd..d6bfd42e5 100644 --- a/SL/Controller/Base.pm +++ b/SL/Controller/Base.pm @@ -9,6 +9,7 @@ use IO::File; use List::Util qw(first); use SL::Request qw(flatten); use SL::MoreCommon qw(uri_encode); +use SL::Presenter; use Rose::Object::MakeMethods::Generic ( @@ -62,18 +63,6 @@ sub render { $options->{type} = lc($options->{type} || 'html'); $options->{no_layout} = 1 if $options->{type} eq 'js'; - my $source; - if ($options->{inline}) { - $source = \$template; - - } elsif($options->{raw}) { - $source = $template; - - } else { - $source = "templates/webpages/${template}." . $options->{type}; - croak "Template file ${source} not found" unless -f $source; - } - if (!$options->{partial} && !$options->{inline} && !$::form->{header}) { if ($options->{no_layout}) { $::form->{header} = 1; @@ -88,24 +77,15 @@ sub render { } } - my %params = ( %locals, - AUTH => $::auth, - FLASH => $::form->{FLASH}, - FORM => $::form, - INSTANCE_CONF => $::instance_conf, - LOCALE => $::locale, - LXCONFIG => \%::lx_office_conf, - LXDEBUG => $::lxdebug, - MYCONFIG => \%::myconfig, - SELF => $self, - ); - my $output; - if (!$options->{raw}) { - my $parser = $self->_template_obj; - $parser->process($source, \%params, \$output) || croak $parser->error; + if ($options->{raw}) { + $output = $$template; } else { - $output = $$source; + $output = $self->presenter->render( + $template, $options, + %locals, + SELF => $self, + ); } print $output unless $options->{inline} || $options->{no_output}; @@ -129,6 +109,10 @@ sub send_file { $file->close; } +sub presenter { + return SL::Presenter->get; +} + sub controller_name { my $class = ref($_[0]) || $_[0]; $class =~ s/^SL::Controller:://; @@ -233,24 +217,6 @@ sub _dispatch { } } -sub _template_obj { - my ($self) = @_; - - $self->{__basepriv_template_obj} ||= - Template->new({ INTERPOLATE => 0, - EVAL_PERL => 0, - ABSOLUTE => 1, - CACHE_SIZE => 0, - PLUGIN_BASE => 'SL::Template::Plugin', - INCLUDE_PATH => '.:templates/webpages', - COMPILE_EXT => '.tcc', - COMPILE_DIR => $::lx_office_conf{paths}->{userspath} . '/templates-cache', - ERROR => 'templates/webpages/generic/exception.html', - }) || croak; - - return $self->{__basepriv_template_obj}; -} - 1; __END__ @@ -369,16 +335,18 @@ C<$options>, if present, must be a hash reference. All remaining parameters are slurped into C<%locals>. What is rendered and how C<$template> is interpreted is determined by -the options I, I, I and I. +the options I, I, I and I. The +actual rendering is handled by L. If C<< $options->{inline} >> is trueish then C<$template> is a string containing the template code to interprete. Additionally the output will not be sent to the browser. Instead it is only returned to the caller. -If C<< $options->{raw} >> is trueish, the function will treat the input as -already parsed, and will not filter the input through Template. Unlike -C, the input is taked as a reference. +If C<< $options->{raw} >> is trueish, the function will treat the +input as already parsed, and will not filter the input through +Template. This also means that L is not +called either. Unlike C, the input is taken as a reference. If C<< $options->{inline} >> is falsish then C<$template> is interpreted as the name of a template file. It is prefixed with @@ -400,30 +368,8 @@ C<$locals{title}> (the latter only if C<$locals{title}> is trueish). Setting C<< $options->{no_layout} >> to trueish will prevent this. -The template itself has access to the following variables: - -=over 2 - -=item * C -- C<$::auth> - -=item * C
-- C<$::form> - -=item * C -- C<$::locale> - -=item * C -- all parameters from C -with the same name they appear in the file (first level is the -section, second the actual variable, e.g. C, -C etc) - -=item * C -- C<$::lxdebug> - -=item * C -- C<%::myconfig> - -=item * C -- the controller instance - -=item * All items from C<%locals> - -=back +The template itself has access to several variables. These are listed +in the documentation to L. Unless C<< $options->{inline} >> is trueish the function will send the output to the browser. @@ -563,6 +509,11 @@ Returns the name of the currently executing action. If the dispatcher mechanism was used then this is not C but the actual method name the dispatching resolved to. +=item C + +Returns the global presenter object by calling +L. + =back =head2 PRIVATE FUNCTIONS diff --git a/SL/Presenter.pm b/SL/Presenter.pm new file mode 100644 index 000000000..eeeb43fab --- /dev/null +++ b/SL/Presenter.pm @@ -0,0 +1,235 @@ +package SL::Presenter; + +use strict; + +use parent qw(Rose::Object); + +use Carp; +use Template; + +use SL::Presenter::CustomerVendor; +use SL::Presenter::DeliveryOrder; +use SL::Presenter::EscapedText; +use SL::Presenter::Invoice; +use SL::Presenter::Order; +use SL::Presenter::Project; +use SL::Presenter::Record; + +sub get { + $::request->{presenter} ||= SL::Presenter->new; + return $::request->{presenter}; +} + +sub render { + my $self = shift; + my $template = shift; + my ($options, %locals) = (@_ && ref($_[0])) ? @_ : ({ }, @_); + + $options->{type} = lc($options->{type} || 'html'); + + my $source; + if ($options->{inline}) { + $source = \$template; + + } else { + $source = "templates/webpages/${template}." . $options->{type}; + croak "Template file ${source} not found" unless -f $source; + } + + my %params = ( %locals, + AUTH => $::auth, + FLASH => $::form->{FLASH}, + FORM => $::form, + INSTANCE_CONF => $::instance_conf, + LOCALE => $::locale, + LXCONFIG => \%::lx_office_conf, + LXDEBUG => $::lxdebug, + MYCONFIG => \%::myconfig, + PRESENTER => $self, + ); + + my $output; + my $parser = $self->get_template; + $parser->process($source, \%params, \$output) || croak $parser->error; + + return SL::Presenter::EscapedText->new(text => $output, is_escaped => 1); +} + +sub get_template { + my ($self) = @_; + + $self->{template} ||= + Template->new({ INTERPOLATE => 0, + EVAL_PERL => 0, + ABSOLUTE => 1, + CACHE_SIZE => 0, + PLUGIN_BASE => 'SL::Template::Plugin', + INCLUDE_PATH => '.:templates/webpages', + COMPILE_EXT => '.tcc', + COMPILE_DIR => $::lx_office_conf{paths}->{userspath} . '/templates-cache', + ERROR => 'templates/webpages/generic/exception.html', + }) || croak; + + return $self->{template}; +} + +sub escape { + my ($self, $text) = @_; + + return SL::Presenter::EscapedText->new(text => $text); +} + +sub escaped_text { + my ($self, $text) = @_; + + return SL::Presenter::EscapedText->new(text => $text, is_escaped => 1); +} + +1; + +__END__ + +=head1 NAME + +SL::Presenter - presentation layer class + +=head1 SYNOPSIS + + use SL::Presenter; + my $presenter = SL::Presenter->get; + + # Lower-level template parsing: + my $html = $presenter->render( + 'presenter/dir/template.html', + var1 => 'value', + ); + + # Higher-level rendering of certain objects: + use SL::DB::Customer; + + my $linked_customer_name = $presenter->customer($customer, display => 'table-cell'); + + # Render a list of links to sales/purchase records: + use SL::DB::Order; + + my $quotation = SL::DB::Manager::Order->get_first(where => { quotation => 1 }); + my $records = $quotation->linked_records(direction => 'to'); + my $html = $presenter->grouped_record_list($records); + +=head1 CLASS FUNCTIONS + +=over 4 + +=item C + +Returns the global presenter object and creates it if it doesn't exist +already. + +=back + +=head1 INSTANCE FUNCTIONS + +=over 4 + +=item C + +Renders the template C<$template>. Provides other variables than +C does. + +C<$options>, if present, must be a hash reference. All remaining +parameters are slurped into C<%locals>. + +This is the backend function that L +calls. The big difference is that the presenter's L function +always returns the input and never sends anything to the browser while +the controller's function usually sends the result to the +controller. Therefore the presenter's L function does not use +all of the parameters for controlling the output that the controller's +function does. + +What is rendered and how C<$template> is interpreted is determined by +the options I and I. + +If C<< $options->{inline} >> is trueish then C<$template> is a string +containing the template code to interprete. + +If C<< $options->{inline} >> is falsish then C<$template> is +interpreted as the name of a template file. It is prefixed with +"templates/webpages/" and postfixed with a file extension based on +C<< $options->{type} >>. C<< $options->{type} >> can be either C +or C and defaults to C. An exception will be thrown if that +file does not exist. + +The template itself has access to the following variables: + +=over 2 + +=item * C -- C<$::auth> + +=item * C -- C<$::form> + +=item * C -- C<$::locale> + +=item * C -- all parameters from C +with the same name they appear in the file (first level is the +section, second the actual variable, e.g. C, +C etc) + +=item * C -- C<$::lxdebug> + +=item * C -- C<%::myconfig> + +=item * C -- the controller instance + +=item * All items from C<%locals> + +=back + +The function will always return the output and never send anything to +the browser. + +Example: Render a HTML template with a certain title and a few locals + + $presenter->render('todo/list', + title => 'List TODO items', + TODO_ITEMS => SL::DB::Manager::Todo->get_all_sorted); + +Example: Render a string and return its content for further processing +by the calling function. + + my $content = $presenter->render( + '[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]', + { type => 'js' } + ); + +=item C + +Returns an HTML-escaped version of C<$text>. Instead of a string an +instance of the thin proxy-object L is +returned. + +It is safe to call C on an instance of +L. This is a no-op (the same instance will +be returned). + +=item C + +Returns an instance of L. C<$text> is +assumed to be a string that has already been HTML-escaped. + +It is safe to call C on an instance of +L. This is a no-op (the same instance will +be returned). + +=item C + +Returns the global instance of L