Mercurial > pub > Impl
diff Lib/IMPL/Web/Handler/View.pm @ 353:feeb3bc4a818
corrected error handling while loading templates
corrected variables lookup in controls
updated handles to use the new view features
author | cin |
---|---|
date | Fri, 11 Oct 2013 15:49:04 +0400 |
parents | Lib/IMPL/Web/Handler/TTView.pm@97628101b765 |
children | 9330835535b9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/Web/Handler/View.pm Fri Oct 11 15:49:04 2013 +0400 @@ -0,0 +1,337 @@ +package IMPL::Web::Handler::View; +use strict; + +use Carp qw(carp); +use List::Util qw(first); +use IMPL::lang; +use IMPL::Const qw(:prop); +use IMPL::declare { + require => { + Factory => 'IMPL::Web::View::ObjectFactory', + HttpResponse => 'IMPL::Web::HttpResponse', + Loader => 'IMPL::Code::Loader', + ViewResult => '-IMPL::Web::ViewResult' + }, + base => [ + 'IMPL::Object' => undef, + 'IMPL::Object::Autofill' => '@_', + 'IMPL::Object::Serializable' => undef + ], + + props => [ + contentType => PROP_RO, + contentCharset => PROP_RO, + view => PROP_RO, + layout => PROP_RO, + selectors => PROP_RO, + defaultDocument => PROP_RW, + _selectorsCache => PROP_RW + ] +}; + +sub CTOR { + my ($this) = @_; + + $this->_selectorsCache([ map $this->ParseRule($_), @{$this->selectors || []} ]); +} + +sub Invoke { + my ( $this, $action, $next ) = @_; + + my $result = $next ? $next->($action) : undef; + + my ($model,$view,$template); + if( ref $result and eval { $result->isa(ViewResult) } ) { + $model = $result->model; + $view = $result; + $template = $result->template; + } else { + $model = $result; + $view = ViewResult->new(model => $model); + } + + my $vars = { + view => $view, + request => sub { $action }, + app => $action->application, + context => $action->context, + env => _cached($action->context->{environment}), + location => $action->context->{resourceLocation}, + layout => $this->layout + }; + + $this->view->display( + $model, + $template || $this->SelectView( $action, ref $model ), + $vars + ); + + my %responseParams = ( + type => $this->contentType, + charset => $this->contentCharset, + body => $this->view->display( + $model, + $template || $this->SelectView( $action, ref $model ), + $vars + ) + ); + + $responseParams{status} = $view->status if $view->status; + $responseParams{cookies} = $view->cookies if ref $view->cookies eq 'HASH'; + $responseParams{headers} = $view->headers if ref $view->headers eq 'HASH'; + + return HttpResponse->new( + %responseParams + ); +} + +sub _cached { + my $arg = shift; + + return $arg unless ref $arg eq 'CODE'; + + return sub { + ref $arg eq 'CODE' ? $arg = &$arg() : $arg; + } +} + +sub SelectView { + my ($this,$action) = @_; + + my @path; + + for(my $r = $action->context->{resource}; $r ; $r = $r->parent ) { + unshift @path, { + name => $r->id, + class => typeof($r->model) + }; + } + + @path = map { name => $_}, split /\/+/, $action->query->path_info() + unless (@path); + + return $this->MatchPath(\@path,$this->_selectorsCache) || $this->defaultDocument; +} + +sub ParseRule { + my ($this, $rule) = @_; + + my ($selector,$data) = split /\s+=>\s+/, $rule; + + my @parts; + my $first = 1; + my $weight = 0; + foreach my $part ( split /\//, $selector ) { + # если первым символом является / + # значит путь в селекторе абсолютный и не нужно + # добавлять "любой" элемент в начало + + if($part) { + $weight ++; + push @parts,{ any => 1 } if $first; + } else { + push @parts,{ any => 1 } unless $first; + next; + } + + my ($name,$class) = split /@/, $part; + + if ( my ( $varName, $rx ) = ( $name =~ m/^\{(?:(\w+)\:)?(.*)\}$/ ) ) { + #this is a regexp + + push @parts, { + rx => $rx, + var => $varName, + class => $class, + }; + } else { + push @parts, { + name => length($name) ? $name : undef, + class => $class, + }; + } + } continue { + $first = 0; + } + + return { selector => \@parts, data => $data, weight => $weight }; +} + +sub MatchPath { + my ($this,$path,$rules) = @_; + + $path ||= []; + $rules ||= []; + + my @next; + + foreach my $segment (@$path) { + foreach my $rule (@$rules) { + my @selector = @{$rule->{selector}}; + + my $part = shift @selector; + + # if this rule doesn't have a selector + next unless $part; + + if ($part->{any}) { + #keep the rule for the next try + push @next, $rule; + + $part = shift @selector while $part->{any}; + } + + my $newRule = { + selector => \@selector, + data => $rule->{data}, + weight => $rule->{weight}, + vars => { %{$rule->{vars} || {}} } + }; + + my $success = 1; + if (my $class = $part->{class}) { + $success = isclass($segment->{class},$class); + } + + if($success && (my $name = $part->{name})) { + $success = $segment->{name} eq $name; + } elsif ($success && (my $rx = $part->{rx})) { + if( my @captures = ($segment->{name} =~ m/($rx)/) ) { + $newRule->{vars}->{$part->{var}} = \@captures + if $part->{var}; + } else { + $success = 0; + } + } + + push @next, $newRule if $success; + + } + $rules = [@next]; + undef @next; + } + + my $result = ( + sort { + $b->{weight} <=> $a->{weight} + } + grep { + scalar(@{$_->{selector}}) == 0 + } + @$rules + )[0]; + + if($result) { + my $data = $result->{data}; + $data =~ s/{(\w+)(?:\:(\d+))?}/ + my ($name,$index) = ($1,$2 || 0); + + if ($result->{vars}{$name}) { + $result->{vars}{$name}[$index]; + } else { + ""; + } + /gex; + + return $data; + } else { + return; + } +} + +1; + +__END__ + +=pod + +=head1 NAME + +C<IMPL::Web::Handler::TTView> - использует шаблоны для построения представления. + +=head1 SYNOPSIS + +=begin code xml + +<item id="html-view" type="IMPL::Web::Handler::View"> + <contentType>text/html</contentType> + <view id="tt-loader" type="IMPL::Web::View::TTView"> + <options type="HASH"> + <INCLUDE_PATH type="IMPL::Config::Reference"> + <target>IMPL::Config</target> + <AppBase>view</AppBase> + </INCLUDE_PATH> + <INTERPOLATE>1</INTERPOLATE> + <POST_CHOMP>1</POST_CHOMP> + <ENCODING>utf-8</ENCODING> + </options> + <ext>.tt</ext> + <initializer>global.tt</initializer> + <layoutBase>layouts</layoutBase> + </view> + <defaultDocument>default</defaultDocument> + <selectors type="ARRAY"> + <item>@HASH => dump</item> + <item>@My::Data::Product => product/info</item> + <item>{action:.*} @My::Data::Product => product/{action}</item> + </selectors> +</item> + +=end code xml + +=head1 DESCRIPTION + +Подбирает шаблон для представления результата, полученного при выполнении следующего обработчика. При +выборе используется принцип похожий на селекторы C<CSS>, основывающийся на именах ресурсов и их типах +данных. + +Данный обработчик понимает определенные свойства контекста: + +=over + +=item * C<resourceLocation> + +В данном свойстве может быть передана информация о текущем расположении ресурса, +для которого строится представление. Эта информация будет доступна в шаблоне +через свойство документа C<location>. + +=item * C<environment> + +В данном совойстве контекста передается дополнительная информация об окружении +ресурса, например, которую задали родительские ресурсы. Использование данного +свойства позволяет не загромождать ресурс реализацией функциональности по +поддержке окружения. Это свойство может быть ссылкой на функцию, что позволяет +формировать контекст только по необходимости, при этом указанная функция будет +выполнена только один раз, при первом обращении. + +=back + +=head1 SELECTORS + +=begin text + +syntax::= selector => template + +selector::= ([>]segment-template[@class-name]) + +segment-template::= {'{'name:regular-expr'}'|segment-name} + +name::= \w+ + +segment-name::= \S+ + +class-name::= name[(::name)] + +url-template@class => template + +shoes => product/list +/shop//{action:*.}@My::Data::Product => product/{action} + +stuff >list => product/list +details => product/details + +=end text + + +=cut +