# HG changeset patch # User cin # Date 1381420279 -14400 # Node ID 675cd182925595ce4c156c7c93a769ec1c327139 # Parent cfd7570c2af24ebb773ffa8b7da9952fe1045dfc working on TTView: added control classes support diff -r cfd7570c2af2 -r 675cd1829255 Lib/IMPL/Web/View/TTContext.pm --- a/Lib/IMPL/Web/View/TTContext.pm Tue Oct 08 17:40:35 2013 +0400 +++ b/Lib/IMPL/Web/View/TTContext.pm Thu Oct 10 19:51:19 2013 +0400 @@ -13,7 +13,8 @@ Document => '-Template::Document', TypeKeyedCollection => 'IMPL::TypeKeyedCollection', ArgException => '-IMPL::InvalidArgumentException', - Resources => 'IMPL::Resources' + Resources => 'IMPL::Resources', + Loader => 'IMPL::Code::Loader' }, base => [ 'Template::Context' => '@_' @@ -32,6 +33,7 @@ cache includes modules + aliases )) { my $t = $prop; @@ -93,11 +95,17 @@ if $info; } else { if( my $tt = eval { $this->template($file) } ) { + my $class; + if ($class = $tt->class) { + $class = $this->aliases->{$class} || $class; + Loader->safe->Require($class); + } my $info = { base => $base, labels => $this->load_labels($file), template => $tt, - initialized => 0 + initialized => 0, + class => $class }; $this->modules->{$file} = $info; return $cache->{$name} = $info; @@ -188,6 +196,7 @@ $this->localise( hashApply( { + aliases => $this->aliases || {}, root => $this->root || $this, modules => $this->modules || {}, cache => TypeKeyedCollection->new(), @@ -236,7 +245,7 @@ return $this->invoke_environment( sub { my $ctx = shift; - + unless($info->{initialized}) { if(my $init = $info->{template}->blocks->{INIT}) { $info->{initialized} = 1; @@ -247,8 +256,12 @@ $ctx->leave(); } } - - return $ctx->include($info->{template},$args); + + if (my $class = $info->{class}) { + $class->new($this,$info->{template})->Render($args); + } else { + return $ctx->include($info->{template},$args); + } }, hashMerge( $info->{labels} || {}, diff -r cfd7570c2af2 -r 675cd1829255 Lib/IMPL/Web/View/TTControl.pm --- a/Lib/IMPL/Web/View/TTControl.pm Tue Oct 08 17:40:35 2013 +0400 +++ b/Lib/IMPL/Web/View/TTControl.pm Thu Oct 10 19:51:19 2013 +0400 @@ -3,24 +3,22 @@ use IMPL::Const qw(:prop); use IMPL::lang qw(:hash :base); -use Scalar::Util qw(blessed reftype); use IMPL::declare { require => { - TemplateDocument => 'Template::Document', - TTContext => 'IMPL::Web::View::TTContext', - Exception => 'IMPL::Exception', - ArgumentException => '-IMPL::InvalidArgumentException', - OperationException => '-IMPL::InvalidOperationException' + Exception => 'IMPL::Exception', + ArgException => '-IMPL::InvalidArgumentException' }, base => [ 'IMPL::Object' => undef ], props => [ - id => PROP_RO, - attributes => PROP_RW, - context => PROP_RO, - template => PROP_RO, - parents => PROP_RO + context => PROP_RO, + template => PROP_RO, + _stash => PROP_RO, + id => { + get => sub { shift->_stash->get('id') }, + set => sub { shift->_stash->set('id',shift) } + } ] }; @@ -34,128 +32,45 @@ our $AUTOLOAD_REGEX = qr/^[a-z]/; -my %mapSkipAttributes = map { $_, 1 } qw(attributes context); - sub CTOR { - my ($this,$template,$context,$attrs) = @_; - - - $this->template( $template ) or die new IMPL::ArgumentException("A template is required"); - - die IMPL::ArgumentException->new(context => "A context is required, supplied: $context") - unless is($context,TTContext); - - $this->context( $context ); - - $this->attributes({}); - - if(ref($attrs) eq 'HASH') { - while (my($key,$value) = each %$attrs) { - next if $mapSkipAttributes{$key}; - $this->SetAttribute($key,$value); - } - } + my ($this,$context,$template) = @_; - $this->id(_GetNextId()) unless $this->id; -} - -sub GetAttribute { - my ($this,$name) = (shift,shift); - - if (my $method = $this->can($name)) { - unshift @_,$this; - goto &$method; - } else { - return $this->attributes->{$name}; - } -} - -sub SetAttribute { - my $this = shift; - my $name = shift; - - if (my $method = $this->can($name)) { - unshift @_, $this; - goto &$method; - } else { - return $this->attributes->{$name} = shift; - } + $this->context($context) + or die ArgException->new(context => 'A context is required'); + $this->template($template) + or die ArgException->new(template => 'A template is required'); + + $this->_stash($context->stash); } sub Render { - my ($this,$args) = @_; - - $args = {} unless ref $args eq 'HASH'; - - return $this->context->include( - $this->template->block, - { - %$args, - this => $this, - template => $this->template - } - ); -} - -sub GetTemplate { - my ($this,$name) = @_; - - return eval { $this->context->template($name) }; -} - -sub Include { - my ($this,$template, $args) = @_; - - my $tpl = $this->GetTemplate($template) - or die OperationException->new("The specified template isn't found", $template); - - return $this->context->include( - $tpl, - $args - ); + my ($this,$args) = @_; + return $this->context->include($this->template,$args); } -sub HasBlock { - my ($this,$block) = @_; - - $this->GetTemplate ? 1 : 0; +our $AUTOLOAD; +sub AUTOLOAD { + my ($prop) = ($AUTOLOAD =~ m/(\w+)$/); + + die Exception->new("Control doesn't have method '$prop'") unless $prop=~/$AUTOLOAD_REGEX/; + + no strict 'refs'; + + my $method = sub { + if (@_ == 1) { + return shift->_stash->get($prop); + } elsif (@_ == 2) { + return shift->_stash->set($prop,shift); + } else { + return shift->_stash->get([$prop,[@_]]); + } + }; + + *{$AUTOLOAD} = $method; + + goto &$method; } -sub AUTOLOAD { - our $AUTOLOAD; - - my $method = ($AUTOLOAD =~ m/(\w+)$/)[0]; - - return if $method eq 'DESTROY'; - - if ($method =~ /$AUTOLOAD_REGEX/) { - my $this = shift; - - die OperationException->new("can't invoke method '$method' on an unblessed reference") unless blessed $this; - - return @_ ? $this->SetAttribute($method,@_) : $this->GetAttribute($method); - } else { - die OperationException->new("The specified method '$method' doesn't exists"); - } -} - -sub CreateControlFromTemplate { - my ($this,$template,$args) = @_; - - if (not ref($template)) { - return $this->context->stash->get([ - require => [ - $template - ] - ])->new($args); - } else { - return $this->new( - $template, - $this->context->clone(), - $args - ); - } -} 1; @@ -165,117 +80,32 @@ =head1 NAME -C +C расширяет функциональность шаблонов =head1 SYNPOSIS -=begin text +=begin code + +package My::View::Menu; +use IMPL::declare { + base => [ + 'IMPL::Web::View::TTControl' => '@_' + ] +}; -[% - META version = 1; - BLOCK INIT; - # this is a document scope - dojo.modules.push( 'dijit/form/Input' ); - END; - - # local to this block - TPreview = require('My/Org/TextPreview'); - - # init control props - visualClass = this.visualClass || 'classic'; -%] -
- [% FOREACH item IN model %] -
- [% Display(item) %] -
- [% END %] -
+sub Render { + my ($this,$args) = @_; + + $this->PrepareItems($args); + + return $this->next::method($args); +} -=end text +sub PrepareItems + +=end code =head1 DESCRIPTION -Легкая обертка вокруг шаблона, позволяет изолировать пространство имен шаблона, -а также реализовать собственные методы по представлению данных (в случае если -это проще сделать в виде методов класса). - -=head2 BLOCKS - -=head3 META - -Атрибуты C C, C будут перенесены в свойства элемента -управления. - -=head3 INIT - -Данный блок шаблона управления выполняется один раз при создании первого -экземпляра элемента управления, в пространстве имен документа. Может -использоваться для формирования заголовочной части документа, скрипта -подключающего C<js> модули и т.п. - -Выполнение данного блока производится фабрикой элементов управления. - -=head2 TEMPLATE VARS - -Каждый шаблон имеет собственное пространство имен, вложенное в пространство имен -фабрики элементов (которая разделяет пространство имен документа). В шаблоне -могут определяться новые переменные, однако они останутся локальными для блоков. - -Чтобы передать данные между блоками следует использовать ссылку на элемент -управления C<this>. - -=begin text - -[% - BLOCK CTOR; - this.extraCssClass = 'active'; - text = "this text will gone"; - END; -%] - -<div class="$this.extraCssClass">some text $text</div> - -=end text - -В примере выше переменная C<$text> установленная в конструкторе шаблона, при -отображении элемента управления будет неопределена. - -=over - -=item * C<this> - -ссылка на объект элемента управления - -=item * C<component> - -ссылка на текущий шаблон, устанавливается автоматически в методе -C<Template::Context::process>. - -=item * C<template> - -ссылка на шаблон элемента управления, для совместимости с C<TT> - -=back - -=head1 MEMBERS - -=head2 C<[get]context> - -Контекст элемента управления, хранит пременные шаблона. Фабрика элементов -управления создает новый контекст пространство имен которого вложено в -пространство имен документа. - -Контекст следует использовать только при рендеринге документа. - -=head2 C<[get,set]template> - -C<Template::Document> Шаблон элемента управления. - -=head2 C<AUTOLOAD> - -Для удобства работы с шаблоном, элементы управления предоставляю доступ к своим -свойствам через метод C<AUTOLOAD>. Имена свойств должны начинаться со строчной -буквы. =cut \ No newline at end of file diff -r cfd7570c2af2 -r 675cd1829255 Lib/IMPL/Web/View/TTDocument.pm --- a/Lib/IMPL/Web/View/TTDocument.pm Tue Oct 08 17:40:35 2013 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -package IMPL::Web::View::TTDocument; -use strict; - -use Scalar::Util qw(weaken); -use IMPL::Const qw(:prop); -use IMPL::lang qw(:hash is); -use Carp qw(carp); -use mro; -use IMPL::Exception(); -use IMPL::declare { - require => { - TTRegistry => 'IMPL::Web::View::TTRegistry', - TTFactory => 'IMPL::Web::View::TTFactory', - TTControl => 'IMPL::Web::View::TTControl', - Loader => 'IMPL::Code::Loader', - OpException => '-IMPL::InvalidOperationException' - }, - base => [ - 'IMPL::Web::View::TTControl' => sub { - my ($template,$ctx,$vars) = @_; - $ctx ||= Template::Context->new(); - return $template, $ctx, $vars; # context - } - ] -}; - -sub CTOR { - my ($this,$template,$ctx) = @_; - - $this->layout( $template->layout ) unless $this->layout; - $this->title( $template->title ) unless $this->title; -} - -sub Render { - my ($this,$args) = @_; - - my $ctx = $this->context; - - $args ||= {}; - $args->{document} = $this; - $args->{render} = sub { - my ($model,$factory) = @_; - - $factory = $ctx->require($factory) unless is($factory,TTFactory); - - return $factory->new({document => $this, model => $model})->Render(); - }; - - - if ($this->layout) { - my $layout = $this->registry->Require($this->layout)->new(); - - my $next = $this->next::can(); - - return $layout->Render({ - content => sub { $this->$next($args) }, - template => $this->template, - document => $this - }); - } else { - return $this->next::method($args); - } -} - -sub GetTemplate { - my ($this,$name) = @_; - - $this->template->blocks->{$name}; -} - -sub DTOR { - my $this = shift; - - $this->registry->Dispose() if $this->registry; -} - -1; - -__END__ - -=pod - -=head1 NAME - -C<IMPL::Web::View::TTDocument> - документ для построения HTML страницы на основе шаблонов TT. - -=head1 SYNOPSIS - -=begin code - - -=end code - -=head1 DESCRIPTION - -Позволяет строить представления при помощи Template Toolkit, при этом расширяет -шаблоны до элементов управления, чтобы была возмлжность реализации функционала -средствами C<Perl>. - -Структура представления. - -=begin text - - + view - |- document.tt - |-+items - | |- title.tt - | |- item.tt - | - |-+layouts - |- - -=end text - -=begin text - - - -=end text - -=begin plantuml - -@startuml - -object "doc: TTDocument" as doc -object "docCtx: TTContext" as docctx -object "factory: TTFactory" as factory -object "registry: TTRegistry" as registry -object "control: TTControl" as ctl -object "ctlCtx: TTContext" as ctlctx - -doc -up-> docctx -registry --> "0..*" factory -factory .> doc: <<creates>> -factory .up.> ctl: <<creates>> -docctx -up-> registry -ctl -> ctlctx -ctlctx --> registry -ctlctx --> doc - -@enduml - -=end plantuml - -=cut \ No newline at end of file diff -r cfd7570c2af2 -r 675cd1829255 Lib/IMPL/Web/View/TTLoader.pm --- a/Lib/IMPL/Web/View/TTLoader.pm Tue Oct 08 17:40:35 2013 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -package IMPL::Web::View::TTLoader; -use strict; - -use Template::Constants qw(:status); - -use File::Spec(); -use IMPL::Const qw(:prop); -use Scalar::Util qw(weaken); - -use IMPL::declare { - require => { - Provider => 'Template::Provider', - Context => 'IMPL::Web::View::TTContext', - TTRegistry => 'IMPL::Web::View::TTRegistry', - TTFactory => 'IMPL::Web::View::TTFactory', - TTDocument => '-IMPL::Web::View::TTDocument', - Exception => 'IMPL::Exception', - ArgumentException => '-IMPL::InvalidArgumentException', - KeyNotFoundException => '-IMPL::KeyNotFoundException' - }, - base => [ - 'IMPL::Object' => undef, - 'IMPL::Object::Serializable' => undef - ], - props => [ - provider => PROP_RO, - context => PROP_RO, - registry => PROP_RO, - _globals => PROP_RW - ] -}; - -sub save { - my ($this,$context) = @_; - - $context->AddVar($_, $this->$_()) for qw(options provider context ext layoutBase); -} - -sub restore { - my ($class,$data,$surrogate) = @_; - - my %params = @$data; - - my $refOpts = delete $params{options}; - - if ($surrogate){ - $surrogate->callCTOR($refOpts,%params); - } else { - $surrogate = $class->new($refOpts,%params); - } - return $surrogate; -} - -sub CTOR { - my ($this,$refOpts,%args) = @_; - - $refOpts ||= {}; - - # to aviod cyclic references we need to do a copy of $refOpts - $refOpts->{LOAD_TEMPLATES} = Provider->new( { %$refOpts } ); - - my $ctx = Context->new( { %$refOpts } ); - $this->context($ctx); - - $this->_globals(ref $args{globals} eq 'HASH' ? $args{globals} : {}); - - $ctx->tt_ext($args{ext} || '.tt'); - - $this->registry(TTRegitry->new($ctx)); - - weaken($ctx); - weaken($this); - $ctx->stash->update({ - require => sub { - my ($modname) = @_; - - my @inc; - push @inc, $ctx->base if $ctx->base; - - my $ti = $ctx->find_template($name,@inc); - - require $this->registry->Require($ti); - }, - inclue => sub { - my ($name) = @_; - - my @inc; - push @inc, $ctx->base if $ctx->base; - - my $ti = $ctx->find_template($name,@inc); - - return $ctx->include($ti->{template}, {base => $ti->{base}} ); - } - }); - - -} - -sub document { - my ($this,$name,$vars) = @_; - - $vars ||= {}; - - my $factory = $this->registry->Require($name); - - return $factory->new(hashMerge($vars, $this->_globals)); -} - -1; - -__END__ - -=pod - -=head1 NAME - -C<IMPL::Web::View::TTLoader> - предоставляет глобальный контекст для загрузки шаблонов - -=head1 SYNOPSIS - -=begin code - -use IMPL::Web::View::TTLoader(); - -my $loader = new IMPL::Web::View::TTLoader( - { - INCLUDE_PATH => [ - '/my/app/tt', - '/my/app/tt/lib' - ] - }, - ext => '.tt', - globals => { - images => '//cdn.mysite.net/static/images' - } - -); - -my $doc = $loader->document('index'); - -my $html = $doc->Render(); - -=end code - -=head1 DESCRIPTION - -Провайдер для загрузки шаблонов и документов. Имеет собственное пространство -имен, контекст выполнения шаблонов. Контект загрузчика инициализируется -шаблоном, в котором определяются глобальные переменные, которые будут доступны -в документах, однако их изменения будут локализованы. - -Инициализация контекста провайдера происходит при первой загрузке любого -документа. - -=head1 MEMBERS - -=head2 C<CTOR($options,%args)> - -=over - -=item * C<$options> - -Параметры контекста загрузчика, контексты документов и элементов управления -также унаследуют эти свойства. Напрямую передаются в конструктор -C<Template::Context>. - -=item * C<%args> - -Именованные параметы загрузчика. - -=over - -=item * C<ext> - -Расширение, которое будет добавляться к именам шаблонов и документов (если оно -не будет указано явно). - -=item * C<initializer> - -Имя шаблона, который будет использован для инициализации контекста. - -=item * C<globals> - -Глобальные переменнын, которые будут переданы в контекст. - -=item * C<layoutBase> - -Путь к шаблонам для оформления документов. Каждый документ может задавать свой -C<layout> указанный в его C<META> блоке или конструкторе. -См. C<IMPL::View::TTDocument>. - -=back - -=back - -=head2 C<document($docName)> - -Загружает документ с именем C<$docName>. При необходимости к нему будет -добавлено расширение, указанное в свойстве C<ext>. Это единственно полезный -метод провайдера. - -=cut - diff -r cfd7570c2af2 -r 675cd1829255 Lib/IMPL/Web/View/TTRegistry.pm --- a/Lib/IMPL/Web/View/TTRegistry.pm Tue Oct 08 17:40:35 2013 +0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -package IMPL::Web::View::TTRegistry; -use strict; -use mro; - -use IMPL::Const qw(:prop); -use IMPL::declare { - require => { - TTFactory => 'IMPL::Web::View::TTFactory', - TTControl => '-IMPL::Web::View::TTControl' - }, - base => [ - 'IMPL::Object' => undef - ], - props => [ - context => PROP_RW, - _cache => PROP_RW, - ] -}; - -sub CTOR { - my ($this,$context) = @_; - - $this->context($context); - $this->_cache({}); -} - -sub Require { - my ($this,$name) = @_; - - if(my $factory = $this->_cache->{$name}) { - return $factory; - } else { - my $template = $this->loader->template($name) - or die AppException->new("Failed to load a template $name"); - - $factory = TTFactory->new( - $template->class || TTControl, - $template, - $this->context->clone, - $name, - $this - ); - - $this->_cache->{$name} = $factory; - return $factory; - } -} - -1; - -__END__ - -=pod - -=head1 NAME - -C<IMPL::Web::View::Registry> - реестр шаблонов документа. - -=head1 DESCRIPTION - -Используется для реализации функции C<require> внутри шаблонов. - - - -=cut diff -r cfd7570c2af2 -r 675cd1829255 _test/Resources/view/layout/menu.tt --- a/_test/Resources/view/layout/menu.tt Tue Oct 08 17:40:35 2013 +0400 +++ b/_test/Resources/view/layout/menu.tt Thu Oct 10 19:51:19 2013 +0400 @@ -1,2 +1,3 @@ +[% META class='IMPL::Web::View::TTControl' %] <div id="menu"> </div> \ No newline at end of file diff -r cfd7570c2af2 -r 675cd1829255 _test/temp.pl --- a/_test/temp.pl Tue Oct 08 17:40:35 2013 +0400 +++ b/_test/temp.pl Thu Oct 10 19:51:19 2013 +0400 @@ -13,7 +13,8 @@ options => { INCLUDE_PATH => './Resources/view', INTERPOLATE => 1, - RECURSION => 1000 + RECURSION => 1000, + COMPILE_DIR => '/tmp/ttc' }, view => 'site', layout => 'layout', @@ -22,26 +23,38 @@ ] ); +my $model = { + name => 'debugger', + manufacture => { + name => 'DEBUGGERS INC', + address => [ + { + coutry => 'Russuia', + city => 'Moscow' + }, + { + country => 'GB', + city => 'Essex' + } + ] + } +}; + print $view->display( - { - name => 'debugger', - manufacture => { - name => 'DEBUGGERS INC', - address => [ - { - coutry => 'Russuia', - city => 'Moscow' - }, - { - country => 'GB', - city => 'Essex' - } - ] - } - }, + $model, 'product/view', { layout => 'default' } ), "\n"; -print "render page: ",tv_interval($t,[gettimeofday]),"ms\n"; - +print "render page: ",tv_interval($t,[gettimeofday]),"s\n"; + +$t = [gettimeofday]; + +$view->display( + $model, + 'product/view', + { layout => 'default' } +); + +print "2nd render page: ",tv_interval($t,[gettimeofday]),"s\n"; +