comparison lib/IMPL/Web/View/TTContext.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::View::TTContext;
2 use strict;
3 use Template::Base;
4 use Carp qw(carp);
5 use File::Spec();
6 use IMPL::Resources::Format qw(FormatMessage);
7 use IMPL::Resources::Strings();
8
9 use IMPL::Exception();
10 use IMPL::lang qw(is typeof hashApply hashMerge);
11 use IMPL::declare {
12 require => {
13 Document => '-Template::Document',
14 TypeKeyedCollection => 'IMPL::TypeKeyedCollection',
15 ArgException => '-IMPL::InvalidArgumentException',
16 Resources => 'IMPL::Resources',
17 Loader => 'IMPL::Code::Loader',
18 MetadataBase => '-IMPL::Web::View::Metadata::BaseMeta',
19 Metadata => 'IMPL::Web::View::Metadata::ObjectMeta',
20 StringMap => 'IMPL::Resources::StringLocaleMap'
21 },
22 base => [
23 'Template::Context' => '@_'
24 ]
25 };
26
27 BEGIN {
28 no strict 'refs';
29 # modules is a global (for the whole document) templates cache
30 # tt_cache is a local (for the current context only) templtes cache
31 foreach my $prop (qw(
32 root
33 base
34 tt_ext
35 tt_cache
36 parent
37 prefix
38 cache
39 includes
40 modules
41 aliases
42 id
43 metadata
44 model
45 templateInfo
46 )) {
47 my $t = $prop;
48
49 *{__PACKAGE__ . '::' . $prop} = sub {
50 my $this = shift;
51 return @_ ? $this->stash->set($t, @_) : $this->stash->get($t);
52 }
53 }
54 }
55
56 sub clone {
57 my $this = shift;
58 my $params = shift;
59
60 $this->localise();
61
62 my $args = { %{$this} };
63
64 $this->delocalise();
65
66 my $class = ref($this);
67
68 delete $args->{CONFIG};
69
70 my $clone = $class->new($args);
71
72 $clone->stash->update($params) if $params;
73
74 return $clone;
75 }
76
77 sub get_next_id {
78 my ($this) = @_;
79
80 my $id = $this->stash->get('document.nextId') || 0;
81 $this->stash->set('document.nextId', $id + 1);
82 return "w-$id";
83 }
84
85 sub find_template {
86 my ($this,$name, $nothrow) = @_;
87
88 my $cache = $this->tt_cache;
89
90 $this->tt_cache($cache = {}) unless $cache;
91
92 if(my $tpl = $cache->{$name}) {
93 return $tpl;
94 }
95
96 my @inc = ($this->base, @{$this->includes || []});
97 #my @inc = @{$this->includes || []};
98
99 my $ext = $this->tt_ext || "";
100
101 #warn "find: $name";
102
103 my $file;
104
105 foreach my $dir (@inc) {
106 $file = $dir ? "$dir/$name" : $name;
107
108 my @parts = split(/\/+/,$file);
109
110 my $templateName = pop @parts;
111
112 my $base = join('/',@parts);
113
114 $file = $ext ? "$file.$ext" : $file;
115
116 #warn " file: $file";
117
118 if (exists($this->modules->{$file})) {
119 my $info = $this->modules->{$file};
120 return $cache->{$name} = $info
121 if $info;
122 } else {
123 if( my $tt = eval { $this->template($file) } ) {
124 #warn " found: $file";
125 my $class;
126 if ($class = $tt->class) {
127 $class = $this->aliases->{$class} || $class;
128 Loader->safe->Require($class);
129 }
130 my $info = {
131 base => $base,
132 name => $templateName,
133 template => $tt,
134 initialized => 0,
135 class => $class,
136 file => $file
137 };
138 $this->modules->{$file} = $info;
139 return $cache->{$name} = $info;
140 } else {
141 my $err = $@;
142
143 #warn " not found: $err";
144
145 for(my $t = $err; is($t,'Template::Exception'); $t = $t->info ) {
146 die $err unless $t->type eq Template::Constants::ERROR_FILE;
147 }
148 $this->modules->{$file} = undef;
149 }
150 }
151 }
152
153 $this->throw(Template::Constants::ERROR_FILE, "$name: not found")
154 unless $nothrow;
155 return;
156 }
157
158 sub display_for {
159 my $this = shift;
160 my $path = shift;
161 my ($template, $args);
162
163 if (ref $_[0] eq 'HASH') {
164 $args = shift;
165 } else {
166 $template = shift;
167 $args = shift;
168 }
169
170 my $prefix = $this->prefix;
171
172 my $info;
173 my $meta = $this->resolve_model($path,$args)
174 or return "[not found '$path']";
175
176 $info->{prefix} = join('.', grep($_, $prefix, $path));
177 $info->{model} = $meta->model;
178 $info->{metadata} = $meta;
179
180 $template ||= $info->{template};
181 $template = $template ? $this->find_template($template) : $this->find_template_for($info->{metadata});
182
183 return $this->render(
184 $template,
185 hashApply(
186 $info,
187 $args
188 )
189 );
190 }
191
192 sub display_model {
193 my $this = shift;
194 my $model = shift;
195 my ($template, $args);
196
197 if (ref $_[0] eq 'HASH') {
198 $args = shift;
199 } else {
200 $template = shift;
201 $args = shift;
202 }
203
204 #copy
205 $args = { %{$args || {}} };
206
207 $args->{prefix} = join('.',grep($_,$this->prefix,$args->{path}))
208 unless defined $args->{prefix};
209
210 if (is($model,MetadataBase)) {
211 $args->{model} = $model->model;
212 $args->{metadata} = $model;
213 } else {
214 $args->{model} = $model;
215 $args->{metadata} = Metadata->GetMetadataForModel($model);
216 }
217
218 $template = $template ? $this->find_template($template) : $this->find_template_for($args->{metadata});
219
220 return $this->render(
221 $template,
222 $args
223 );
224 }
225
226 # обеспечивает необходимый уровень изоляции между контекстами
227 # $code - код, который нужно выполнить в новом контексте
228 # $env - хеш с переменными, которые будут переданы в новый контекст
229 # в процессе будет создан клон корневого контекста, со всеми его свойствами
230 # затем новый контекст будет локализован и в него будут добавлены новые переменные из $env
231 # созданный контекст будет передан параметром в $code
232 sub invoke_environment {
233 my ($this,$code,$env) = @_;
234
235 $env ||= {};
236
237 my $ctx = ($this->root || $this)->clone();
238
239 my @includes = @{$this->includes || []};
240
241 if ($this->base) {
242 unshift @includes, $this->base;
243 }
244
245 my $out = eval {
246 $ctx->localise(
247 hashApply(
248 {
249 includes => \@includes,
250 aliases => $this->aliases || {},
251 root => $this->root || $ctx,
252 modules => $this->modules || {},
253 cache => TypeKeyedCollection->new(),
254 display_for => sub {
255 $ctx->display_for(@_);
256 },
257 render => sub {
258 $ctx->render(@_);
259 },
260 display_model => sub {
261 $ctx->display_model(@_);
262 },
263 tt_cache => {},
264 labels => sub {
265 $ctx->load_labels(@_);
266 }
267 },
268 $env
269 )
270 );
271
272 &$code($ctx);
273 };
274
275 my $e = $@;
276 $ctx->delocalise();
277
278 die $e if $e;
279
280 return $out;
281 }
282
283 # использует указанный шаблон для создания фрагмента документа
284 # шаблон может быть как именем, так и хешем, содержащим информацию
285 # о шаблоне.
286 # отдельно следует отметить, что данный метод создает новый контекст
287 # для выполнения шаблона в котором задает переменные base, parent, id
288 # а также создает переменные для строковых констант из labels
289 # хеш с переменными $args будет передан самому шаблону в момент выполнения
290 # если у шаблона указан класс элемента управления, то при выполнении шаблона
291 # будет создан экземпляр этого класса и процесс выполнения шаблона будет
292 # делегирован методу Render этого экземпляра.
293 sub render {
294 my ($this,$template,$args) = @_;
295
296 $args ||= {};
297
298 my $info = ref $template ? $template : $this->find_template($template);
299
300 if (ref($info) ne 'HASH') {
301 carp "got an invalid template object: $info (" . ref($info) . ")";
302 $info = {
303 template => $info,
304 base => $this->base,
305 initialized => 1
306 };
307 }
308
309 return $this->invoke_environment(
310 sub {
311 my $ctx = shift;
312
313 unless($info->{initialized}) {
314 if(my $init = $info->{template}->blocks->{INIT}) {
315 $info->{initialized} = 1;
316 eval {
317 $ctx->visit($info->{template}->blocks);
318 $ctx->include($init);
319 };
320 $ctx->leave();
321 }
322 }
323
324 if (my $class = $info->{class}) {
325 $class->new($ctx,$info->{template},$args)->Render({});
326 } else {
327 return $ctx->include($info->{template},$args);
328 }
329 },
330 {
331 base => $info->{base},
332 parent => $this,
333 id => $this->get_next_id,
334 templateInfo => $info
335 }
336 )
337 }
338
339 sub resolve_model {
340 my ($this,$prefix) = @_;
341
342 die ArgException->new(prefix => "the prefix must be specified")
343 unless defined $prefix;
344
345 my $meta = $this->metadata;
346 unless($meta) {
347 $meta = Metadata->GetMetadataForModel($this->model);
348 $this->metadata($meta);
349 }
350
351 foreach my $part (grep length($_), split(/\.|\[(\d+)\]/, $prefix)) {
352 last unless $meta;
353 if ($part =~ /^\d+$/) {
354 $meta = $meta->GetItem($part);
355 } else {
356 $meta = $meta->GetProperty($part);
357 }
358 }
359
360 return $meta;
361 }
362
363 sub find_template_for {
364 my ($this,$meta, $nothrow) = @_;
365
366 die ArgException->new(meta => 'An invalid metadata is supplied')
367 unless is($meta,MetadataBase);
368
369 return $this->find_template($meta->template)
370 if ($meta->template);
371
372 my $type = $meta->modelType;
373
374 return $this->find_template('templates/plain') unless $type;
375
376 if (my $template = $this->cache->Get($type)) {
377 return $template;
378 } else {
379
380 no strict 'refs';
381
382 my @isa = $type;
383
384 while (@isa) {
385 my $sclass = shift @isa;
386
387 (my $name = $sclass) =~ s/:+/_/g;
388 my ($shortName) = ($sclass =~ m/(\w+)$/);
389
390 $template = $this->find_template("templates/$name",1) || $this->find_template("templates/$shortName",1);
391
392 if ($template) {
393 $this->cache->Set($sclass,$template);
394 return $template;
395 }
396
397 #todo $meta->GetISA to implement custom hierachy
398 push @isa, @{"${sclass}::ISA"};
399 }
400
401 }
402 $this->throw(Template::Constants::ERROR_FILE, "can't find a template for the model $type")
403 unless $nothrow;
404
405 return;
406 }
407
408 sub get_real_file {
409 my ($this,$fname) = @_;
410
411 return unless length $fname;
412
413 my @path = split(/\/+/,$fname);
414
415 foreach my $provider (@{$this->load_templates || []}) {
416 foreach my $dir (@{$provider->paths || []}) {
417 my $realName = File::Spec->catfile($dir,@path);
418 return $realName if -f $realName;
419 }
420 }
421 }
422
423 sub load_labels {
424 my ($this,$data) = @_;
425
426 die ArgException->new("A hash reference is required")
427 unless ref($data) eq 'HASH';
428
429 my $stringMap = StringMap->new($data);
430
431 $this->stash->update({
432 map {
433 my $id = $_;
434 $id,
435 sub {
436 $stringMap->GetString($id,@_);
437 };
438 } keys %$data
439 });
440
441 my $ti = $this->templateInfo || {};
442
443 if (my $fullName = $this->get_real_file($ti->{file})) {
444 my ($vol,$dir,$fname) = File::Spec->splitpath($fullName);
445
446 my $name = $this->templateInfo->{name};
447
448 my $localePath = File::Spec->catpath($vol, File::Spec->catdir($dir,'locale'),'');
449
450 $stringMap->name($name);
451 $stringMap->paths($localePath);
452 }
453 return;
454 }
455
456 1;
457
458 __END__
459
460 =pod
461
462 =head1 NAME
463
464 C<IMPL::Web::View::TTContext> - доработанная версия контекста
465
466 =head1 DESCRIPTION
467
468 Расширяет функции C<Template::Context>
469
470 =begin plantuml
471
472 @startuml
473
474 object RootContext {
475 document
476 globals
477 }
478
479 object DocumentContext {
480 base
481 extends
482 }
483
484 object ControlContext {
485 base
486 extends
487 }
488
489 RootContext o-- DocumentContext
490 RootContext o-- ControlContext
491
492 Document -- DocumentContext
493 Control - ControlContext
494
495 Loader . RootContext: <<creates>>
496 Loader . Document: <<creates>>
497 Loader -up- Registry
498
499 @enduml
500
501 =end plantuml
502
503 =head1 MEMBERS
504
505 =head2 C<[get,set]base>
506
507 Префикс пути для поиска шаблонов
508
509 =head2 C<template($name)>
510
511 Сначала пытается загрузить шаблон используя префикс C<base>, затем без префикса.
512
513 =head2 C<clone()>
514
515 Создает копию контекста, при этом C<stash> локализуется, таким образом
516 клонированный контекст имеет собственное пространство имен, вложенное в
517 пространство родительского контекста.
518
519 =cut