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 |