77
|
1 package IMPL::Web::TT::Document;
|
49
|
2 use strict;
|
|
3 use warnings;
|
|
4
|
75
|
5 use base qw(IMPL::DOM::Document IMPL::Object::Disposable);
|
49
|
6 use Template::Context;
|
|
7 use Template::Provider;
|
|
8 use IMPL::Class::Property;
|
|
9 use File::Spec;
|
77
|
10 use Scalar::Util qw(blessed);
|
107
|
11 use IMPL::Web::TT::Collection;
|
|
12 use IMPL::Web::TT::Control;
|
49
|
13
|
|
14 BEGIN {
|
77
|
15 private property _provider => prop_all;
|
|
16 private property _context => prop_all;
|
|
17 public property template => prop_get | owner_set;
|
|
18 public property presenter => prop_all, { validate => \&_validatePresenter };
|
108
|
19 private property _controlClassMap => prop_all;
|
49
|
20 }
|
|
21
|
|
22 our %CTOR = (
|
75
|
23 'IMPL::DOM::Document' => sub { nodeName => 'document' }
|
49
|
24 );
|
|
25
|
107
|
26 sub CTOR {
|
|
27 my ($this) = @_;
|
|
28
|
108
|
29 $this->_controlClassMap({});
|
|
30 $this->registerControlClass( Control => 'IMPL::Web::TT::Control' );
|
|
31 $this->appendChild( $this->Create(body => 'IMPL::Web::TT::Collection') );
|
|
32 $this->appendChild( $this->Create(head => 'IMPL::Web::TT::Collection') );
|
|
33 }
|
|
34
|
|
35 sub CreateControl {
|
|
36 my ($this,$name,$class,$args) = @_;
|
|
37
|
|
38 $args = {} unless ref $args eq 'HASH';
|
|
39
|
|
40 if (my $info = $this->_controlClassMap->{$class}) {
|
|
41 my %nodeArgs = (%{$info->{args}},%$args);
|
|
42 $nodeArgs{controlClass} = $class;
|
|
43
|
|
44 return $this->Create($name,$info->{type},\%nodeArgs);
|
|
45 } else {
|
|
46 die new IMPL::Exception('A control is\'t registered', $class, $name);
|
|
47 }
|
107
|
48 }
|
|
49
|
77
|
50 sub provider {
|
49
|
51 my ($this,%args) = @_;
|
|
52
|
77
|
53 if (my $provider = $this->_provider) {
|
49
|
54 return $provider;
|
|
55 } else {
|
77
|
56 return $this->_provider(new Template::Provider(
|
49
|
57 \%args
|
|
58 ));
|
|
59 }
|
|
60 }
|
|
61
|
77
|
62 sub context {
|
49
|
63 my ($this) = @_;
|
|
64
|
77
|
65 if (my $ctx = $this->_context) {
|
49
|
66 return $ctx;
|
|
67 } else {
|
77
|
68 return $this->_context (
|
49
|
69 new Template::Context(
|
|
70 VARIABLES => {
|
77
|
71 document => $this,
|
|
72 this => $this,
|
|
73 render => sub {
|
|
74 $this->_process(@_);
|
108
|
75 }
|
49
|
76 },
|
|
77 TRIM => 1,
|
|
78 RECURSION => 1,
|
77
|
79 LOAD_TEMPLATES => [$this->provider]
|
49
|
80 )
|
|
81 )
|
|
82 }
|
|
83 }
|
|
84
|
108
|
85 sub registerControlClass {
|
|
86 my ($this, $controlClass, $type, $args) = @_;
|
|
87
|
|
88 $type ||= 'IMPL::Web::TT::Control';
|
|
89
|
|
90 die new IMPL::InvalidArgumentException("A controlClass must be a single word",$controlClass) unless $controlClass =~ /^\w+$/;
|
107
|
91
|
108
|
92 eval "require $type; 1;" or die new IMPL::Exception("Failed to load a module",$type,"$@") unless ref $type or $INC{$type};
|
|
93
|
|
94 die new IMPL::InvalidArgumentException("A type must be subclass of IMPL::DOM::Node",$type) unless $type->isa('IMPL::DOM::Node');
|
|
95
|
|
96 $this->_controlClassMap->{$controlClass} = {
|
|
97 controlClass => $controlClass,
|
|
98 type => $type,
|
|
99 args => ref $args eq 'HASH' ? $args : {}
|
|
100 };
|
107
|
101 }
|
|
102
|
|
103 sub _getControls {
|
|
104 my ($this) = @_;
|
|
105
|
|
106 my ($node) = $this->selectNodes('controls');
|
|
107 return $node;
|
|
108 }
|
|
109
|
77
|
110 sub _validatePresenter {
|
|
111 my ($this,$value) = @_;
|
|
112
|
|
113 die new IMPL::InvalidArgumentException("A view object is required") unless blessed($value) and $value->isa('Template::View');
|
|
114 }
|
|
115
|
|
116 sub LoadFile {
|
49
|
117 my ($this,$filePath,$encoding) = @_;
|
|
118
|
|
119 die new IMPL::InvalidArgumentException("A filePath parameter is required") unless $filePath;
|
|
120
|
|
121 $encoding ||= 'utf8';
|
|
122
|
77
|
123 $this->_context(undef);
|
|
124 $this->_provider(undef);
|
49
|
125
|
|
126 my ($vol,$dir,$fileName) = File::Spec->splitpath($filePath);
|
|
127
|
|
128 my $inc = File::Spec->catpath($vol,$dir,'');
|
|
129
|
77
|
130 $this->provider(
|
49
|
131 ENCODING => $encoding,
|
|
132 INTERPOLATE => 1,
|
|
133 PRE_CHOMP => 1,
|
|
134 POST_CHOMP => 1,
|
|
135 INCLUDE_PATH => $inc
|
|
136 );
|
|
137
|
77
|
138 $this->template($this->context->template($fileName));
|
49
|
139 }
|
|
140
|
97
|
141 sub AddVar {
|
|
142 my ($this,$name,$value) = @_;
|
|
143
|
|
144 $this->context->stash->set($name,$value);
|
|
145 }
|
|
146
|
77
|
147 sub title {
|
|
148 $_[0]->template->title;
|
49
|
149 }
|
|
150
|
|
151 sub Render {
|
|
152 my ($this) = @_;
|
|
153
|
77
|
154 return $this->template->process($this->context);
|
|
155 }
|
|
156
|
|
157 # Формирует представление для произвольных объектов
|
|
158 sub _process {
|
|
159 my ($this,@items) = @_;
|
|
160
|
|
161 my @result;
|
|
162
|
|
163 foreach my $item (@items) {
|
|
164 if (blessed($item) and $item->isa('IMPL::Web::TT::Control')) {
|
|
165 push @result, $item->Render();
|
|
166 } elsif(blessed($item)) {
|
|
167 if ($this->presenter) {
|
|
168 push @result, $this->presenter->print($item);
|
|
169 } else {
|
|
170 push @result, $this->toString;
|
|
171 }
|
|
172 } else {
|
|
173 push @result, $item;
|
|
174 }
|
|
175 }
|
|
176
|
107
|
177 return join '',@result;
|
49
|
178 }
|
|
179
|
108
|
180 our $AUTOLOAD;
|
|
181 sub AUTOLOAD {
|
|
182 my $this = shift;
|
|
183 my ($method) = ($AUTOLOAD =~ /(\w+)$/);
|
|
184
|
|
185 if($method =~ /^create(\w+)/) {
|
|
186 my ($name,$args) = @_;
|
|
187 return $this->CreateControl($name,$1,$args);
|
|
188 }
|
|
189
|
|
190 my @result = $this->selectNodes($method);
|
|
191
|
|
192 return $result[0] if @result;
|
|
193 return;
|
|
194 }
|
|
195
|
|
196 sub as_list {
|
|
197 $_[0]->childNodes;
|
|
198 }
|
|
199
|
49
|
200 sub Dispose {
|
|
201 my ($this) = @_;
|
|
202
|
77
|
203 $this->template(undef);
|
|
204 $this->_context(undef);
|
|
205 $this->_provider(undef);
|
49
|
206
|
108
|
207 $this->supercall::Dispose();
|
49
|
208 }
|
|
209
|
|
210 1;
|
|
211 __END__
|
|
212 =pod
|
|
213
|
77
|
214 =head1 NAME
|
|
215
|
|
216 C<IMPL::Web::TT::Document> - Документ, позволяющий строить представление по шаблону
|
|
217
|
49
|
218 =head1 SYNOPSIS
|
|
219
|
75
|
220 =begin code
|
|
221
|
49
|
222 // create new document
|
77
|
223 my $doc = new IMPL::Web::TT::Document;
|
49
|
224
|
|
225 // load template
|
|
226 $doc->loadFile('Templates/index.tt');
|
|
227
|
|
228 // render file
|
|
229 print $doc->Render();
|
|
230
|
75
|
231 =end code
|
|
232
|
49
|
233 =head1 DESCRIPTION
|
|
234
|
77
|
235 C<use base qw(IMPL::DOM::Document)>
|
|
236
|
49
|
237 Документ, основанный на шаблоне Template::Toolkit. Позволяет загрузить шаблон,
|
|
238 и сформировать окончательный документ. Является наследником C<IMPL::DOM::Node>,
|
|
239 т.о. может быть использован для реализации DOM модели.
|
|
240
|
|
241 Внутри шаблона переменная C<document> ссылается на объект документа. По этой
|
|
242 причине образуется циклическая ссылка между объектами шаблона и документом, что
|
|
243 требует вызова метода C<Dispose> для освобождения документа.
|
|
244
|
|
245 =head1 METHODS
|
|
246
|
77
|
247 =over
|
49
|
248
|
77
|
249 =item C<CTOR()>
|
49
|
250
|
77
|
251 Создает новый экземпляр документа, свойство C<nodeName> устанавливается в 'C<document>'
|
49
|
252
|
77
|
253 =item C<$doc->LoadFile($fileName,$encoding)>
|
49
|
254
|
|
255 Загружает шаблон из файла C<$fileName>, используя кодировку C<$encoding>. Если
|
|
256 кодировка не указана, использует utf-8.
|
|
257
|
|
258 =item C<$doc->Render()>
|
|
259
|
|
260 Возвращает данные построенные на основе загруженного шаблона.
|
|
261
|
|
262 =item C<$doc->Dispose()>
|
|
263
|
|
264 Освобождает ресурсы и помечает объект как освобожденный.
|
|
265
|
|
266 =back
|
|
267
|
76
|
268 =head1 DOM
|
|
269
|
108
|
270 Документ представляет собой DOM документ, состоящий из узлов, которые представляют собой данные
|
|
271 для отображения. Для форматированого вывода используется C<template>.
|
|
272
|
|
273 В качестве элементов документа могут присутсвовать специальные объекты C<IMPL::Web::TT::Control>,
|
|
274 которые внутри содержат шаблон для форматирования собственного содержимого.
|
|
275
|
|
276
|
|
277
|
|
278 Документ предоставляет ряд фнукций для работы с элементами управления.
|
|
279
|
|
280 =head1 TEMPLATE
|
|
281
|
77
|
282 =begin code html
|
76
|
283
|
108
|
284 [% CALL document.registerClass( 'Table', 'My::TableClass', template => 'tables/pretty.tt' ) %]
|
|
285 [% CALL document.registerClass( 'Form' )%]
|
|
286
|
|
287 [% table = document.сreateTable('env') %]
|
76
|
288
|
|
289 [% FOEACH item in document.result %]
|
|
290 [% table.rows.Add( item.get('name','value') ) %]
|
|
291 [% END %]
|
|
292
|
108
|
293 [% form = document.createForm('login') %]
|
77
|
294 [% form.template = 'LOGIN_FORM'%]
|
|
295
|
|
296 [% FOREACH item IN document.childNodes %]
|
|
297 [%render(item)%]
|
76
|
298 [% END %]
|
|
299
|
77
|
300 [% BLOCK LOGIN_FORM %]
|
|
301 <form method="POST" action='/login.pl'>
|
|
302 user: [% render(this.item('name')) %] password: [% render(this.item('password')) %] <input type="submit"/>
|
|
303 </form>
|
|
304 [% END %]
|
76
|
305
|
77
|
306 =end code html
|
76
|
307
|
49
|
308 =cut
|