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 |