Mercurial > pub > Impl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 352:675cd1829255 | 353:feeb3bc4a818 |
|---|---|
| 1 package IMPL::Web::Handler::View; | |
| 2 use strict; | |
| 3 | |
| 4 use Carp qw(carp); | |
| 5 use List::Util qw(first); | |
| 6 use IMPL::lang; | |
| 7 use IMPL::Const qw(:prop); | |
| 8 use IMPL::declare { | |
| 9 require => { | |
| 10 Factory => 'IMPL::Web::View::ObjectFactory', | |
| 11 HttpResponse => 'IMPL::Web::HttpResponse', | |
| 12 Loader => 'IMPL::Code::Loader', | |
| 13 ViewResult => '-IMPL::Web::ViewResult' | |
| 14 }, | |
| 15 base => [ | |
| 16 'IMPL::Object' => undef, | |
| 17 'IMPL::Object::Autofill' => '@_', | |
| 18 'IMPL::Object::Serializable' => undef | |
| 19 ], | |
| 20 | |
| 21 props => [ | |
| 22 contentType => PROP_RO, | |
| 23 contentCharset => PROP_RO, | |
| 24 view => PROP_RO, | |
| 25 layout => PROP_RO, | |
| 26 selectors => PROP_RO, | |
| 27 defaultDocument => PROP_RW, | |
| 28 _selectorsCache => PROP_RW | |
| 29 ] | |
| 30 }; | |
| 31 | |
| 32 sub CTOR { | |
| 33 my ($this) = @_; | |
| 34 | |
| 35 $this->_selectorsCache([ map $this->ParseRule($_), @{$this->selectors || []} ]); | |
| 36 } | |
| 37 | |
| 38 sub Invoke { | |
| 39 my ( $this, $action, $next ) = @_; | |
| 40 | |
| 41 my $result = $next ? $next->($action) : undef; | |
| 42 | |
| 43 my ($model,$view,$template); | |
| 44 if( ref $result and eval { $result->isa(ViewResult) } ) { | |
| 45 $model = $result->model; | |
| 46 $view = $result; | |
| 47 $template = $result->template; | |
| 48 } else { | |
| 49 $model = $result; | |
| 50 $view = ViewResult->new(model => $model); | |
| 51 } | |
| 52 | |
| 53 my $vars = { | |
| 54 view => $view, | |
| 55 request => sub { $action }, | |
| 56 app => $action->application, | |
| 57 context => $action->context, | |
| 58 env => _cached($action->context->{environment}), | |
| 59 location => $action->context->{resourceLocation}, | |
| 60 layout => $this->layout | |
| 61 }; | |
| 62 | |
| 63 $this->view->display( | |
| 64 $model, | |
| 65 $template || $this->SelectView( $action, ref $model ), | |
| 66 $vars | |
| 67 ); | |
| 68 | |
| 69 my %responseParams = ( | |
| 70 type => $this->contentType, | |
| 71 charset => $this->contentCharset, | |
| 72 body => $this->view->display( | |
| 73 $model, | |
| 74 $template || $this->SelectView( $action, ref $model ), | |
| 75 $vars | |
| 76 ) | |
| 77 ); | |
| 78 | |
| 79 $responseParams{status} = $view->status if $view->status; | |
| 80 $responseParams{cookies} = $view->cookies if ref $view->cookies eq 'HASH'; | |
| 81 $responseParams{headers} = $view->headers if ref $view->headers eq 'HASH'; | |
| 82 | |
| 83 return HttpResponse->new( | |
| 84 %responseParams | |
| 85 ); | |
| 86 } | |
| 87 | |
| 88 sub _cached { | |
| 89 my $arg = shift; | |
| 90 | |
| 91 return $arg unless ref $arg eq 'CODE'; | |
| 92 | |
| 93 return sub { | |
| 94 ref $arg eq 'CODE' ? $arg = &$arg() : $arg; | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 sub SelectView { | |
| 99 my ($this,$action) = @_; | |
| 100 | |
| 101 my @path; | |
| 102 | |
| 103 for(my $r = $action->context->{resource}; $r ; $r = $r->parent ) { | |
| 104 unshift @path, { | |
| 105 name => $r->id, | |
| 106 class => typeof($r->model) | |
| 107 }; | |
| 108 } | |
| 109 | |
| 110 @path = map { name => $_}, split /\/+/, $action->query->path_info() | |
| 111 unless (@path); | |
| 112 | |
| 113 return $this->MatchPath(\@path,$this->_selectorsCache) || $this->defaultDocument; | |
| 114 } | |
| 115 | |
| 116 sub ParseRule { | |
| 117 my ($this, $rule) = @_; | |
| 118 | |
| 119 my ($selector,$data) = split /\s+=>\s+/, $rule; | |
| 120 | |
| 121 my @parts; | |
| 122 my $first = 1; | |
| 123 my $weight = 0; | |
| 124 foreach my $part ( split /\//, $selector ) { | |
| 125 # если первым символом является / | |
| 126 # значит путь в селекторе абсолютный и не нужно | |
| 127 # добавлять "любой" элемент в начало | |
| 128 | |
| 129 if($part) { | |
| 130 $weight ++; | |
| 131 push @parts,{ any => 1 } if $first; | |
| 132 } else { | |
| 133 push @parts,{ any => 1 } unless $first; | |
| 134 next; | |
| 135 } | |
| 136 | |
| 137 my ($name,$class) = split /@/, $part; | |
| 138 | |
| 139 if ( my ( $varName, $rx ) = ( $name =~ m/^\{(?:(\w+)\:)?(.*)\}$/ ) ) { | |
| 140 #this is a regexp | |
| 141 | |
| 142 push @parts, { | |
| 143 rx => $rx, | |
| 144 var => $varName, | |
| 145 class => $class, | |
| 146 }; | |
| 147 } else { | |
| 148 push @parts, { | |
| 149 name => length($name) ? $name : undef, | |
| 150 class => $class, | |
| 151 }; | |
| 152 } | |
| 153 } continue { | |
| 154 $first = 0; | |
| 155 } | |
| 156 | |
| 157 return { selector => \@parts, data => $data, weight => $weight }; | |
| 158 } | |
| 159 | |
| 160 sub MatchPath { | |
| 161 my ($this,$path,$rules) = @_; | |
| 162 | |
| 163 $path ||= []; | |
| 164 $rules ||= []; | |
| 165 | |
| 166 my @next; | |
| 167 | |
| 168 foreach my $segment (@$path) { | |
| 169 foreach my $rule (@$rules) { | |
| 170 my @selector = @{$rule->{selector}}; | |
| 171 | |
| 172 my $part = shift @selector; | |
| 173 | |
| 174 # if this rule doesn't have a selector | |
| 175 next unless $part; | |
| 176 | |
| 177 if ($part->{any}) { | |
| 178 #keep the rule for the next try | |
| 179 push @next, $rule; | |
| 180 | |
| 181 $part = shift @selector while $part->{any}; | |
| 182 } | |
| 183 | |
| 184 my $newRule = { | |
| 185 selector => \@selector, | |
| 186 data => $rule->{data}, | |
| 187 weight => $rule->{weight}, | |
| 188 vars => { %{$rule->{vars} || {}} } | |
| 189 }; | |
| 190 | |
| 191 my $success = 1; | |
| 192 if (my $class = $part->{class}) { | |
| 193 $success = isclass($segment->{class},$class); | |
| 194 } | |
| 195 | |
| 196 if($success && (my $name = $part->{name})) { | |
| 197 $success = $segment->{name} eq $name; | |
| 198 } elsif ($success && (my $rx = $part->{rx})) { | |
| 199 if( my @captures = ($segment->{name} =~ m/($rx)/) ) { | |
| 200 $newRule->{vars}->{$part->{var}} = \@captures | |
| 201 if $part->{var}; | |
| 202 } else { | |
| 203 $success = 0; | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 push @next, $newRule if $success; | |
| 208 | |
| 209 } | |
| 210 $rules = [@next]; | |
| 211 undef @next; | |
| 212 } | |
| 213 | |
| 214 my $result = ( | |
| 215 sort { | |
| 216 $b->{weight} <=> $a->{weight} | |
| 217 } | |
| 218 grep { | |
| 219 scalar(@{$_->{selector}}) == 0 | |
| 220 } | |
| 221 @$rules | |
| 222 )[0]; | |
| 223 | |
| 224 if($result) { | |
| 225 my $data = $result->{data}; | |
| 226 $data =~ s/{(\w+)(?:\:(\d+))?}/ | |
| 227 my ($name,$index) = ($1,$2 || 0); | |
| 228 | |
| 229 if ($result->{vars}{$name}) { | |
| 230 $result->{vars}{$name}[$index]; | |
| 231 } else { | |
| 232 ""; | |
| 233 } | |
| 234 /gex; | |
| 235 | |
| 236 return $data; | |
| 237 } else { | |
| 238 return; | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 1; | |
| 243 | |
| 244 __END__ | |
| 245 | |
| 246 =pod | |
| 247 | |
| 248 =head1 NAME | |
| 249 | |
| 250 C<IMPL::Web::Handler::TTView> - использует шаблоны для построения представления. | |
| 251 | |
| 252 =head1 SYNOPSIS | |
| 253 | |
| 254 =begin code xml | |
| 255 | |
| 256 <item id="html-view" type="IMPL::Web::Handler::View"> | |
| 257 <contentType>text/html</contentType> | |
| 258 <view id="tt-loader" type="IMPL::Web::View::TTView"> | |
| 259 <options type="HASH"> | |
| 260 <INCLUDE_PATH type="IMPL::Config::Reference"> | |
| 261 <target>IMPL::Config</target> | |
| 262 <AppBase>view</AppBase> | |
| 263 </INCLUDE_PATH> | |
| 264 <INTERPOLATE>1</INTERPOLATE> | |
| 265 <POST_CHOMP>1</POST_CHOMP> | |
| 266 <ENCODING>utf-8</ENCODING> | |
| 267 </options> | |
| 268 <ext>.tt</ext> | |
| 269 <initializer>global.tt</initializer> | |
| 270 <layoutBase>layouts</layoutBase> | |
| 271 </view> | |
| 272 <defaultDocument>default</defaultDocument> | |
| 273 <selectors type="ARRAY"> | |
| 274 <item>@HASH => dump</item> | |
| 275 <item>@My::Data::Product => product/info</item> | |
| 276 <item>{action:.*} @My::Data::Product => product/{action}</item> | |
| 277 </selectors> | |
| 278 </item> | |
| 279 | |
| 280 =end code xml | |
| 281 | |
| 282 =head1 DESCRIPTION | |
| 283 | |
| 284 Подбирает шаблон для представления результата, полученного при выполнении следующего обработчика. При | |
| 285 выборе используется принцип похожий на селекторы C<CSS>, основывающийся на именах ресурсов и их типах | |
| 286 данных. | |
| 287 | |
| 288 Данный обработчик понимает определенные свойства контекста: | |
| 289 | |
| 290 =over | |
| 291 | |
| 292 =item * C<resourceLocation> | |
| 293 | |
| 294 В данном свойстве может быть передана информация о текущем расположении ресурса, | |
| 295 для которого строится представление. Эта информация будет доступна в шаблоне | |
| 296 через свойство документа C<location>. | |
| 297 | |
| 298 =item * C<environment> | |
| 299 | |
| 300 В данном совойстве контекста передается дополнительная информация об окружении | |
| 301 ресурса, например, которую задали родительские ресурсы. Использование данного | |
| 302 свойства позволяет не загромождать ресурс реализацией функциональности по | |
| 303 поддержке окружения. Это свойство может быть ссылкой на функцию, что позволяет | |
| 304 формировать контекст только по необходимости, при этом указанная функция будет | |
| 305 выполнена только один раз, при первом обращении. | |
| 306 | |
| 307 =back | |
| 308 | |
| 309 =head1 SELECTORS | |
| 310 | |
| 311 =begin text | |
| 312 | |
| 313 syntax::= selector => template | |
| 314 | |
| 315 selector::= ([>]segment-template[@class-name]) | |
| 316 | |
| 317 segment-template::= {'{'name:regular-expr'}'|segment-name} | |
| 318 | |
| 319 name::= \w+ | |
| 320 | |
| 321 segment-name::= \S+ | |
| 322 | |
| 323 class-name::= name[(::name)] | |
| 324 | |
| 325 url-template@class => template | |
| 326 | |
| 327 shoes => product/list | |
| 328 /shop//{action:*.}@My::Data::Product => product/{action} | |
| 329 | |
| 330 stuff >list => product/list | |
| 331 details => product/details | |
| 332 | |
| 333 =end text | |
| 334 | |
| 335 | |
| 336 =cut | |
| 337 |
