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