Mercurial > pub > Impl
comparison Lib/IMPL/Web/Handler/TTView.pm @ 229:47f77e6409f7
heavily reworked the resource model of the web application:
*some ResourcesContraact functionality moved to Resource
+Added CustomResource
*Corrected action handlers
author | sergey |
---|---|
date | Sat, 29 Sep 2012 02:34:47 +0400 |
parents | c8fe3f84feba |
children | 3cebcf6fdb9b |
comparison
equal
deleted
inserted
replaced
228:431db7034a88 | 229:47f77e6409f7 |
---|---|
2 use strict; | 2 use strict; |
3 | 3 |
4 use List::Util qw(first); | 4 use List::Util qw(first); |
5 use IMPL::lang qw(:declare :constants); | 5 use IMPL::lang qw(:declare :constants); |
6 use IMPL::declare { | 6 use IMPL::declare { |
7 require => { | 7 require => { |
8 Factory => 'IMPL::Object::Factory' | 8 Factory => 'IMPL::Object::Factory', |
9 }, | 9 HttpResponse => 'IMPL::Web::HttpResponse' |
10 base => { | 10 }, |
11 'IMPL::Object' => undef, | 11 base => [ |
12 'IMPL::Object::Autofill' => '@_', | 12 'IMPL::Object' => undef, |
13 'IMPL::Object::Serializable' => undef | 13 'IMPL::Object::Autofill' => '@_', |
14 } | 14 'IMPL::Object::Serializable' => undef |
15 ], | |
16 | |
17 props => [ | |
18 contentType => PROP_GET | PROP_OWNERSET, | |
19 contentCharset => PROP_GET | PROP_OWNERSET, | |
20 loader => PROP_GET | PROP_OWNERSET, | |
21 selectors => PROP_GET | PROP_LIST | PROP_OWNERSET, | |
22 defaultDocument => PROP_ALL, | |
23 indexResource => PROP_ALL, | |
24 _selectorsCache => PROP_ALL, | |
25 _classTemplates => PROP_ALL | |
26 ] | |
15 }; | 27 }; |
16 | 28 |
17 BEGIN { | |
18 public property contentType => PROP_GET | PROP_OWNERSET; | |
19 public property loader => PROP_GET | PROP_OWNERSET; | |
20 public property selectors => PROP_GET | PROP_LIST | PROP_OWNERSET; | |
21 public property defaultDocument => PROP_ALL; | |
22 public property indexResource => PROP_ALL; | |
23 private property _selectorsCache => PROP_ALL; | |
24 private property _classTemplates => PROP_ALL; | |
25 } | |
26 | |
27 sub CTOR { | 29 sub CTOR { |
28 my ($this) = @_; | 30 my ($this) = @_; |
29 | 31 |
30 $this->indexResource('index') unless $this->indexResource; | 32 $this->indexResource('index') unless $this->indexResource; |
31 } | 33 } |
32 | 34 |
33 sub Invoke { | 35 sub Invoke { |
34 my ($this,$action,$next) = @_; | 36 my ( $this, $action, $next ) = @_; |
35 | 37 |
36 my $result = $next ? $next->($action) : undef; | 38 my $result = $next ? $next->($action) : undef; |
37 | 39 |
38 my $vars = { | 40 my $vars = { |
39 data => $result, | 41 data => $result, |
40 action => $action, | 42 action => $action, |
41 app => $action->application, | 43 app => $action->application, |
42 LoadFactory => sub { | 44 LoadFactory => sub { |
43 my $class = shift; | 45 my $class = shift; |
44 | 46 |
45 my $module = $class; | 47 my $module = $class; |
46 | 48 |
47 $module =~ s/::/\//g; | 49 $module =~ s/::/\//g; |
48 $module .= ".pm"; | 50 $module .= ".pm"; |
49 | 51 |
50 require $module; | 52 require $module; |
51 return Factory->new($class); | 53 return Factory->new($class); |
52 } | 54 } |
53 }; | 55 }; |
54 | 56 |
55 my $doc = $this->loader->document( | 57 my $doc = |
56 $this->SelectView($action,ref $result), | 58 $this->loader->document( $this->SelectView( $action, ref $result ), |
57 $vars | 59 $vars ); |
60 | |
61 return HttpResponse->new( | |
62 type => $this->contentType, | |
63 charset => $this->contentCharset, | |
64 body => $doc->Render($vars) | |
58 ); | 65 ); |
59 | |
60 $action->response->contentType($this->contentType); | |
61 | |
62 my $hout = $action->response->streamBody; | |
63 | |
64 print $hout $doc->Render($vars); | |
65 } | 66 } |
66 | 67 |
67 sub SelectView { | 68 sub SelectView { |
68 my ($this,$action,$class) = @_; | 69 my ( $this, $action, $class ) = @_; |
69 | 70 |
70 my @path = split /\//, $action->query->path_info(), -1; | 71 my @path = split /\//, $action->query->path_info(), -1; |
71 | 72 |
72 shift @path; # remove always empty leading segment | 73 shift @path; # remove always empty leading segment |
73 | 74 |
74 my $last = pop @path; | 75 my $last = pop @path; |
75 $last =~ s/\.\w+$//; | 76 $last =~ s/\.\w+$//; |
76 $last ||= $this->indexResource; | 77 $last ||= $this->indexResource; |
77 push @path,$last; | 78 push @path, $last; |
78 | 79 |
79 $this->BuildCache unless $this->_selectorsCache; | 80 $this->BuildCache unless $this->_selectorsCache; |
80 my $cache = $this->_selectorsCache; | 81 my $cache = $this->_selectorsCache; |
81 | 82 |
82 @path = reverse @path; | 83 @path = reverse @path; |
83 | 84 |
84 foreach my $subclass ( $class ? (_GetHierarchy($class), '-default') : '-plain') { | 85 foreach |
85 my @results; | 86 my $subclass ( $class ? ( _GetHierarchy($class), '-default' ) : '-plain' ) |
86 push @results, { result => $this->_classTemplates->{$subclass}, level => 0 } if $this->_classTemplates->{$subclass}; | 87 { |
87 if ($cache->{$subclass}) { | 88 my @results; |
88 my $alternatives = [ { selector => $cache->{$subclass}, immediate => 1 } ]; | 89 push @results, |
89 $alternatives = $this->MatchAlternatives($_,$alternatives,\@results) foreach @path; | 90 { result => $this->_classTemplates->{$subclass}, level => 0 } |
90 } | 91 if $this->_classTemplates->{$subclass}; |
91 | 92 if ( $cache->{$subclass} ) { |
92 if (@results) { | 93 my $alternatives = |
93 @results = sort { $b->{level} <=> $a->{level} } @results; | 94 [ { selector => $cache->{$subclass}, immediate => 1 } ]; |
94 return (shift @results)->{result}; | 95 $alternatives = |
95 } | 96 $this->MatchAlternatives( $_, $alternatives, \@results ) |
96 } | 97 foreach @path; |
97 | 98 } |
98 return $this->defaultDocument; | 99 |
100 if (@results) { | |
101 @results = sort { $b->{level} <=> $a->{level} } @results; | |
102 return ( shift @results )->{result}; | |
103 } | |
104 } | |
105 | |
106 return $this->defaultDocument; | |
99 } | 107 } |
100 | 108 |
101 sub _GetHierarchy { | 109 sub _GetHierarchy { |
102 my ($class) = @_; | 110 my ($class) = @_; |
103 return unless $class; | 111 return unless $class; |
104 | 112 |
105 no strict 'refs'; | 113 no strict 'refs'; |
106 | 114 |
107 return $class, map { _GetHierarchy($_) } @{"${class}::ISA"}; | 115 return $class, map { _GetHierarchy($_) } @{"${class}::ISA"}; |
108 } | 116 } |
109 | 117 |
110 sub BuildCache { | 118 sub BuildCache { |
111 my ($this) = @_; | 119 my ($this) = @_; |
112 | 120 |
113 my @selectors; | 121 my @selectors; |
114 | 122 |
115 my $cache = $this->_selectorsCache({}); | 123 my $cache = $this->_selectorsCache( {} ); |
116 $this->_classTemplates({}); | 124 $this->_classTemplates( {} ); |
117 | 125 |
118 foreach my $selector ($this->selectors) { | 126 foreach my $selector ( $this->selectors ) { |
119 if (not ref $selector) { | 127 if ( not ref $selector ) { |
120 | 128 |
121 my ($path,$data) = split(/\s*=>\s*/, $selector); | 129 my ( $path, $data ) = split( /\s*=>\s*/, $selector ); |
122 | 130 |
123 my @path = split(/\s+/,$path); | 131 my @path = split( /\s+/, $path ); |
124 | 132 |
125 my $class; | 133 my $class; |
126 | 134 |
127 # if this selector has a class part | 135 # if this selector has a class part |
128 if ($path[$#path] =~ m/^\@(.*)/) { | 136 if ( $path[$#path] =~ m/^\@(.*)/ ) { |
129 $class = $1; | 137 $class = $1; |
130 pop @path; | 138 pop @path; |
131 } else { | 139 } |
132 $class = '-default'; | 140 else { |
133 } | 141 $class = '-default'; |
134 | 142 } |
135 #if this selector has a path | 143 |
136 if (@path) { | 144 #if this selector has a path |
137 @path = reverse @path; | 145 if (@path) { |
138 my $last = pop @path; | 146 @path = reverse @path; |
139 my $t = ( $cache->{$class} ||= {} ); | 147 my $last = pop @path; |
140 my $level = 1; | 148 my $t = ( $cache->{$class} ||= {} ); |
141 foreach my $prim (@path ) { | 149 my $level = 1; |
142 $t = ($t->{$prim}->{next} ||= {}); | 150 foreach my $prim (@path) { |
143 $level ++; | 151 $t = ( $t->{$prim}->{next} ||= {} ); |
152 $level++; | |
144 } | 153 } |
145 $t->{$last}->{level} = $level; | 154 $t->{$last}->{level} = $level; |
146 $t->{$last}->{data} = $data; | 155 $t->{$last}->{data} = $data; |
147 | 156 |
148 } else { | 157 } |
149 # we dont have a selector, only class | 158 else { |
150 | 159 |
151 $this->_classTemplates->{$class} = $data; | 160 # we dont have a selector, only class |
152 } | 161 |
153 | 162 $this->_classTemplates->{$class} = $data; |
154 } | 163 } |
155 } | 164 |
165 } | |
166 } | |
156 } | 167 } |
157 | 168 |
158 sub MatchAlternatives { | 169 sub MatchAlternatives { |
159 my ($this,$segment,$alternatives,$results) = @_; | 170 my ( $this, $segment, $alternatives, $results ) = @_; |
160 | 171 |
161 my @next; | 172 my @next; |
162 | 173 |
163 foreach my $alt (@$alternatives) { | 174 foreach my $alt (@$alternatives) { |
164 while (my ($selector,$match) = each %{$alt->{selector}} ) { | 175 while ( my ( $selector, $match ) = each %{ $alt->{selector} } ) { |
165 | 176 |
166 | |
167 my $context = { | 177 my $context = { |
168 vars => \%{ $alt->{vars} || {} }, | 178 vars => \%{ $alt->{vars} || {} }, |
169 selector => $match->{next} | 179 selector => $match->{next} |
170 }; | 180 }; |
171 | 181 |
172 if ($selector =~ s/^>//) { | 182 if ( $selector =~ s/^>// ) { |
173 $context->{immediate} = 1; | 183 $context->{immediate} = 1; |
174 } | 184 } |
175 | 185 |
176 if (my ($name,$rx) = ($selector =~ m/^\{(?:(\w+)\:)?(.*)\}$/) ) { | 186 if ( my ( $name, $rx ) = |
187 ( $selector =~ m/^\{(?:(\w+)\:)?(.*)\}$/ ) ) | |
188 { | |
189 | |
177 #this is a regexp | 190 #this is a regexp |
178 | 191 |
179 if ( my @captures = ($segment =~ m/($rx)/) ) { | 192 if ( my @captures = ( $segment =~ m/($rx)/ ) ) { |
180 $context->{success} = 1; | 193 $context->{success} = 1; |
181 | 194 |
182 if ($name) { | 195 if ($name) { |
183 $context->{vars}->{$name} = \@captures; | 196 $context->{vars}->{$name} = \@captures; |
184 } | 197 } |
185 } | 198 } |
186 } else { | 199 } |
200 else { | |
201 | |
187 #this is a segment name | 202 #this is a segment name |
188 if ($segment eq $selector) { | 203 if ( $segment eq $selector ) { |
189 $context->{success} = 1; | 204 $context->{success} = 1; |
190 } | 205 } |
191 } | 206 } |
192 | 207 |
193 # test if there were a match | 208 # test if there were a match |
194 if (delete $context->{success}) { | 209 if ( delete $context->{success} ) { |
195 if (my $data = $match->{data}) { | 210 if ( my $data = $match->{data} ) { |
211 | |
196 # interpolate data | 212 # interpolate data |
197 $data =~ s/{(\w+)(?:\:(\d+))?}/ | 213 $data =~ s/{(\w+)(?:\:(\d+))?}/ |
198 my ($name,$index) = ($1,$2 || 0); | 214 my ($name,$index) = ($1,$2 || 0); |
199 | 215 |
200 if ($context->{vars}{$name}) { | 216 if ($context->{vars}{$name}) { |
201 $context->{vars}{$name}[$index]; | 217 $context->{vars}{$name}[$index]; |
202 } else { | 218 } else { |
203 ""; | 219 ""; |
204 } | 220 } |
205 /gex; | 221 /gex; |
206 | 222 |
207 push @$results, { level => $match->{level}, result => $data }; | 223 push @$results, |
224 { level => $match->{level}, result => $data }; | |
208 } | 225 } |
209 push @next, $context if $context->{selector}; | 226 push @next, $context if $context->{selector}; |
210 } else { | 227 } |
228 else { | |
229 | |
211 #repeat current alternative if it's not required to be immediate | 230 #repeat current alternative if it's not required to be immediate |
212 push @next, { | 231 push @next, |
232 { | |
213 selector => { $selector, $match }, | 233 selector => { $selector, $match }, |
214 vars => $alt->{vars} | 234 vars => $alt->{vars} |
215 } unless $alt->{immediate}; | 235 } |
236 unless $alt->{immediate}; | |
216 } | 237 } |
217 } | 238 } |
218 } | 239 } |
219 | 240 |
220 return \@next; | 241 return \@next; |
221 } | 242 } |
222 | 243 |
223 1; | 244 1; |
224 | 245 |