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