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