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