changeset 352:675cd1829255

working on TTView: added control classes support
author cin
date Thu, 10 Oct 2013 19:51:19 +0400
parents cfd7570c2af2
children feeb3bc4a818
files Lib/IMPL/Web/View/TTContext.pm Lib/IMPL/Web/View/TTControl.pm Lib/IMPL/Web/View/TTDocument.pm Lib/IMPL/Web/View/TTLoader.pm Lib/IMPL/Web/View/TTRegistry.pm _test/Resources/view/layout/menu.tt _test/temp.pl
diffstat 7 files changed, 109 insertions(+), 665 deletions(-) [+]
line wrap: on
line diff
--- 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} || {},
--- 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<IMPL::Web::View::TTControl>
+C<IMPL::Web::View::TTControl> расширяет функциональность шаблонов
 
 =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';
-%]    
-<div id="$id" class="$visualClass" data-dojo-type="dijit/form/Input">
-    [% FOREACH item IN model %]
-        <div class="itemContainer">
-        [% Display(item) %]
-        </div>
-    [% END %]
-</div>
+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<META> C<layout>, C<title> будут перенесены в свойства элемента
-управления. 
-
-=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
--- 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
--- 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
-
--- 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
--- 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
--- 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";
+