343
|
1 package IMPL::Web::View::TTContext;
|
|
2 use strict;
|
|
3 use Template::Base;
|
348
|
4 use Carp qw(carp);
|
343
|
5
|
348
|
6 use IMPL::Exception();
|
|
7 use IMPL::lang qw(is typeof hashApply);
|
343
|
8 use IMPL::declare {
|
348
|
9 require => {
|
|
10 Document => '-Template::Document',
|
|
11 TypeKeyedCollection => 'IMPL::TypeKeyedCollection',
|
|
12 ArgException => "-IMPL::InvalidArgumentException"
|
|
13 },
|
|
14 base => [
|
343
|
15 'Template::Context' => '@_'
|
348
|
16 ]
|
343
|
17 };
|
|
18
|
347
|
19 BEGIN {
|
348
|
20 no strict 'refs';
|
347
|
21 foreach my $prop (qw(
|
348
|
22 root
|
347
|
23 base
|
|
24 tt_ext
|
348
|
25 tt_cache
|
347
|
26 parent
|
|
27 prefix
|
348
|
28 cache
|
|
29 includes
|
347
|
30 )) {
|
|
31 my $t = $prop;
|
|
32
|
348
|
33 *{__PACKAGE__ . '::' . $prop} = sub {
|
347
|
34 my $this = shift;
|
|
35 return @_ ? $this->stash->set($t, @_) : $this->stash->get($t);
|
|
36 }
|
|
37 }
|
|
38 }
|
|
39
|
343
|
40 sub clone {
|
|
41 my $this = shift;
|
347
|
42 my $params = shift;
|
343
|
43
|
|
44 $this->localise();
|
|
45
|
|
46 my $args = { %{$this} };
|
|
47
|
|
48 $this->delocalise();
|
|
49
|
348
|
50 my $class = ref($this);
|
343
|
51
|
|
52 delete $args->{CONFIG};
|
|
53
|
347
|
54 my $clone = $class->new($args);
|
|
55
|
|
56 $clone->stash->update($params) if $params;
|
|
57
|
|
58 return $clone;
|
345
|
59 }
|
|
60
|
|
61 sub find_template {
|
348
|
62 my ($this,$name) = @_;
|
|
63
|
|
64 my $cache = $this->tt_cache;
|
|
65 $this->tt_cache($cache = {}) unless $cache;
|
345
|
66
|
348
|
67 if(my $tpl = $cache->{$name}) {
|
|
68 return $tpl;
|
|
69 }
|
|
70
|
|
71 my @inc = ($this->base, @{$this->includes || []});
|
|
72
|
345
|
73 my $ext = $this->tt_ext || "";
|
348
|
74
|
|
75 my $file;
|
345
|
76
|
|
77 foreach my $dir (@inc) {
|
348
|
78 $file = $dir ? "$dir/$name" : $name;
|
|
79
|
|
80 my $base = join('/',splice([split(/\/+/,$file)],0,-1));
|
|
81
|
|
82 $file = $ext ? "$file.$ext" : $file;
|
|
83
|
|
84 warn "lookup: $file";
|
|
85
|
345
|
86 my $tt = eval { $this->template($file) };
|
|
87
|
348
|
88 return $cache->{$name} = {
|
|
89 base => $base,
|
|
90 template => $tt,
|
345
|
91 } if $tt;
|
|
92 }
|
|
93
|
|
94 $this->throw(Template::Constants::ERROR_FILE, "$name: not found");
|
|
95 }
|
|
96
|
347
|
97 sub display {
|
|
98 my $this = shift;
|
|
99 my $model = shift;
|
348
|
100 my ($template, $args);
|
347
|
101
|
|
102 if (ref $_[0] eq 'HASH') {
|
|
103 $args = shift;
|
|
104 } else {
|
|
105 $template = shift;
|
|
106 $args = shift;
|
|
107 }
|
|
108
|
348
|
109 my $prefix = $this->prefix;
|
|
110
|
|
111 if (not(($args and delete $args->{'-no-resolve'}) or ref $model)) {
|
|
112 $prefix = $prefix ? "${prefix}.${model}" : $model;
|
|
113 $model = $this->resolve_model($model);
|
|
114 } else {
|
|
115 $prefix = "";
|
|
116 }
|
|
117
|
|
118 $template = $template ? $this->find_template($template) : $this->find_template_for($model);
|
|
119
|
|
120 return $this->render(
|
|
121 $template,
|
|
122 hashApply(
|
|
123 {
|
|
124 prefix => $prefix,
|
|
125 model => $model,
|
|
126 },
|
|
127 $args
|
|
128 )
|
|
129 );
|
|
130 }
|
|
131
|
|
132 sub invoke_environment {
|
|
133 my ($this,$code,$env) = @_;
|
|
134
|
|
135 $env ||= {};
|
|
136
|
|
137 my $out = eval {
|
|
138 $this->localise(
|
|
139 hashApply(
|
|
140 {
|
|
141 root => $this->root || $this,
|
|
142 cache => TypeKeyedCollection->new(),
|
|
143 display => sub {
|
|
144 $this->display(@_);
|
|
145 },
|
|
146 render => sub {
|
|
147 $this->render(@_);
|
|
148 }
|
|
149 },
|
|
150 $env
|
|
151 )
|
|
152 );
|
|
153
|
|
154 &$code($this);
|
|
155 };
|
|
156
|
|
157 my $e = $@;
|
|
158 $this->delocalise();
|
|
159
|
|
160 die $e if $e;
|
|
161
|
|
162 return $out;
|
|
163 }
|
|
164
|
|
165 sub render {
|
|
166 my ($this,$template,$args) = @_;
|
|
167
|
|
168 $args ||= {};
|
|
169
|
|
170 #TODO handle classes
|
|
171
|
|
172 my $base;
|
347
|
173
|
348
|
174 $template = $this->find_template($template) unless ref $template;
|
|
175
|
|
176 if (ref $template eq 'HASH') {
|
|
177 $base = $template->{base};
|
|
178 $template = $template->{template};
|
|
179 } else {
|
|
180 carp "got an invalid template object: $template";
|
|
181 $base = $this->base;
|
|
182 }
|
|
183
|
|
184 return $this->invoke_environment(
|
|
185 sub {
|
|
186 return shift->include($template,$args);
|
|
187 },
|
|
188 {
|
|
189 base => $base,
|
|
190 parent => $this
|
|
191 }
|
|
192 )
|
|
193 }
|
|
194
|
|
195 sub resolve_model {
|
|
196 my ($this,$prefix) = @_;
|
|
197
|
|
198 die ArgException->new(prefix => "the prefix must be specified")
|
|
199 unless defined $prefix;
|
|
200
|
|
201 #TODO handle DOM models
|
|
202
|
|
203 my @comp = map { $_, 0 } grep length($_), split(/\.|\[(\d+)\]/, $prefix);
|
347
|
204
|
348
|
205 return $this->stash->get(['model',0,@comp]);
|
|
206 }
|
|
207
|
|
208 sub find_template_for {
|
|
209 my ($this,$model) = @_;
|
347
|
210
|
348
|
211 my $type = typeof($model);
|
347
|
212
|
348
|
213 return $this->find_template('templates/plain') unless $type;
|
|
214
|
|
215 if (my $template = $this->cache->Get($type)) {
|
|
216 return $template;
|
|
217 } else {
|
|
218
|
|
219 no strict 'refs';
|
|
220
|
|
221 my @isa = $type;
|
|
222
|
|
223 while (@isa) {
|
|
224 my $sclass = shift @isa;
|
|
225
|
|
226 (my $name = $sclass) =~ s/:+/_/g;
|
|
227
|
|
228 $template = $this->find_template("templates/$name");
|
|
229
|
|
230 if ($template) {
|
|
231 $this->cache->Set($sclass,$template);
|
|
232 return $template;
|
|
233 }
|
|
234
|
|
235 push @isa, @{"${sclass}::ISA"};
|
|
236 }
|
|
237
|
|
238 }
|
|
239
|
|
240 return;
|
347
|
241 }
|
|
242
|
345
|
243 1;
|
|
244
|
|
245 __END__
|
|
246
|
|
247 =pod
|
|
248
|
|
249 =head1 NAME
|
|
250
|
|
251 C<IMPL::Web::View::TTContext> - доработанная версия контекста
|
|
252
|
|
253 =head1 DESCRIPTION
|
|
254
|
|
255 Расширяет функции C<Template::Context>
|
|
256
|
|
257 =begin plantuml
|
|
258
|
|
259 @startuml
|
|
260
|
348
|
261 object RootContext {
|
345
|
262 document
|
346
|
263 globals
|
345
|
264 }
|
|
265
|
|
266 object DocumentContext {
|
346
|
267 base
|
|
268 extends
|
|
269 }
|
|
270
|
|
271 object ControlContext {
|
|
272 base
|
|
273 extends
|
345
|
274 }
|
|
275
|
348
|
276 RootContext o-- DocumentContext
|
|
277 RootContext o-- ControlContext
|
345
|
278
|
346
|
279 Document -- DocumentContext
|
|
280 Control - ControlContext
|
345
|
281
|
348
|
282 Loader . RootContext: <<creates>>
|
346
|
283 Loader . Document: <<creates>>
|
|
284 Loader -up- Registry
|
345
|
285
|
|
286 @enduml
|
|
287
|
|
288 =end plantuml
|
|
289
|
|
290 =head1 MEMBERS
|
|
291
|
|
292 =head2 C<[get,set]base>
|
|
293
|
|
294 Префикс пути для поиска шаблонов
|
|
295
|
|
296 =head2 C<template($name)>
|
|
297
|
|
298 Сначала пытается загрузить шаблон используя префикс C<base>, затем без префикса.
|
|
299
|
|
300 =head2 C<clone()>
|
|
301
|
|
302 Создает копию контекста, при этом C<stash> локализуется, таким образом
|
|
303 клонированный контекст имеет собственное пространство имен, вложенное в
|
|
304 пространство родительского контекста.
|
|
305
|
|
306 =cut |