changeset 301:aeeb57a12046

*IMPL::Web::View: templates inheritance support
author cin
date Mon, 25 Mar 2013 02:04:18 +0400
parents bf3af33b9003
children 673581380e79
files Lib/IMPL/Web/View/TTControl.pm Lib/IMPL/Web/View/TTDocument.pm Lib/IMPL/Web/View/TTFactory.pm _test/Resources/TTView/My/Org/Base.tt _test/Resources/TTView/My/Org/Derived.tt _test/Resources/TTView/My/Org/Derived2.tt _test/Resources/TTView/My/Org/Panel.tt _test/Resources/TTView/My/Org/TextPreview.tt _test/Resources/TTView/complex.tt _test/Test/Web/View.pm
diffstat 10 files changed, 147 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/Lib/IMPL/Web/View/TTControl.pm	Fri Mar 22 01:05:11 2013 +0400
+++ b/Lib/IMPL/Web/View/TTControl.pm	Mon Mar 25 02:04:18 2013 +0400
@@ -42,7 +42,7 @@
     
     $this->attributes({});
     
-    if(reftype($attrs) eq 'HASH') {
+    if(ref($attrs) eq 'HASH') {
 	    while (my($key,$value) = each %$attrs) {
 	        $this->SetAttribute($key,$value);
 	    }
@@ -80,7 +80,7 @@
     $args = {} unless ref $args eq 'HASH';
     
     return $this->context->include(
-        $this->template,
+        $this->template->block,
         {
             %$args,
             this => $this,
--- a/Lib/IMPL/Web/View/TTDocument.pm	Fri Mar 22 01:05:11 2013 +0400
+++ b/Lib/IMPL/Web/View/TTDocument.pm	Mon Mar 25 02:04:18 2013 +0400
@@ -31,6 +31,7 @@
     
     $this->loader($loader) if $loader;
     $this->layout( $template->layout ) unless $this->layout;
+    $this->title( $template->title ) unless $this->title;
     
     $this->context->stash->update($vars)
         if ref $vars eq 'HASH';
@@ -51,7 +52,7 @@
         my $control = shift;
         
         unless($self) {
-            carp("Cant load control $control outside the rendering procedure");
+            carp("Cant load control $control outside the document rendering process");
             return;
         }
         
@@ -61,10 +62,14 @@
             my $path = $control;
             
             if ( my $template = $self->loader->template($path) ) {
+                
+                $documentContext->localise();
+                my $ctx = _clone_context($documentContext);
+                $documentContext->delocalise();
                
                 $factory = new IMPL::Web::View::TTFactory(
                     $template,
-                    $documentContext,
+                    $ctx,
                     join( '/', splice( @{[split(/\//,$path)]}, 0, -1 ) ),
                     $require
                 );
@@ -114,6 +119,8 @@
 	        return $this->next::method($args);
 	    }
     };
+    
+    my $e = $@;
 
     undef $require;
     undef $documentContext;    
@@ -121,15 +128,11 @@
     undef $self;
     $this->context->delocalise();
     
-    
-    my $e = $@;
     if ($e) {
         die $e;
     } else {
         return $text;
     }
-    
-    
 }
 
 sub _clone_context {
--- a/Lib/IMPL/Web/View/TTFactory.pm	Fri Mar 22 01:05:11 2013 +0400
+++ b/Lib/IMPL/Web/View/TTFactory.pm	Mon Mar 25 02:04:18 2013 +0400
@@ -3,6 +3,7 @@
 
 use Template::Context();
 
+use Carp qw(carp);
 use IMPL::lang qw(:hash);
 use IMPL::Exception();
 use Scalar::Util qw(weaken);
@@ -11,11 +12,13 @@
 use IMPL::Const qw(:prop);
 use IMPL::declare {
     require => {
-        Loader => 'IMPL::Code::Loader'
+        Loader => 'IMPL::Code::Loader',
+        OpException => '-IMPL::InvalidOperationException',
+        ArgException => '-IMPL::InvalidArgumentException'
     },
     base => [
         'IMPL::Object::Factory' => sub {
-            shift;
+            shift->class || 'IMPL::Web::View::TTControl';
         }
     ],
     props => [
@@ -23,17 +26,20 @@
         context => PROP_RW,
         instances => PROP_RW,
         baseLocation => PROP_RW,
-        require => PROP_RO
+        base => PROP_RW,
+        require => PROP_RO, 
+        blocks => PROP_RO,
+        initialized => PROP_RO
     ]
 };
 
 sub CTOR {
-    my ($this,$class,$template,$context,$baseLocation,$require) = @_;
+    my ($this,$template,$context,$baseLocation,$require) = @_;
     
-    die IMPL::ArgumentException("A template is required") unless $template;
+    die ArgException->new("A template is required") unless $template;
     
-    Loader->safe->Require($class)
-        if $class and not ref $class;
+    Loader->safe->Require($this->factory)
+        if $this->factory and not ref $this->factory;
     
     $context ||= new Template::Context();
     
@@ -42,55 +48,107 @@
     $this->baseLocation($baseLocation);
     $this->instances(0);
     $this->require($require);
+    
+    if (my $baseTplName = $template->extends) {
+        $baseTplName =~ s{^\./}{$baseLocation/};
+        
+        my $base = &$require($baseTplName)
+            or die OpException->new("The specified base template isn't found");
+            
+        $this->base($base);
+            
+        $this->blocks(hashMerge($base->blocks, $template->blocks));
+
+        # блок BASE должен меняться в процессе выполнения, в зависимости от
+        # шаблона, который рендерится, по мере перехода в BASE 
+        
+        my @baseStack;
+        
+        $this->blocks->{BASE} = sub {
+            my $ctx = shift;
+            
+            die OpException->new("This tamplate doesn't have a base template")
+                unless $base;
+            
+            push @baseStack, $base;            
+            my $block = $base->template->block;
+            
+            $base = $base->base;
+            
+            my $result = eval {
+                $ctx->process($block);
+            };
+            my $e = $@;
+            
+            $base = pop @baseStack;
+            
+            die $e if $e;
+            return $result;
+        };
+    } else {
+        $this->blocks( $template->blocks );
+    }
+    
+    if(my $blocks = $this->blocks) {
+        while (my ($name,$block) = each %$blocks) {
+            $context->define_block($name,$block);
+        }
+    }
+    
+    $context->stash->update({ require => sub {
+        my ($module) = @_;
+            
+        $module =~ s/^\.\//$baseLocation\//;
+        return $require->($module);
+    }});
 }
 
 sub MergeParameters {
-    my ($this,$refProps) = @_;
+    my $this = shift;
+    my $refProps = shift || {};
+    
+    unless (ref($refProps) eq 'HASH') {
+        carp "Passing control name through the first parameter is deprecated";
+        my $name = $refProps;
+        $refProps = shift;
+        $refProps->{name} ||= $name;
+    }
     
     $refProps->{factory} = $this;
-    
-    my $baseLocation = $this->baseLocation;
-    
     my $ctx = $this->CloneContext();
-    
-    my $require = $this->require;
-    
-    $ctx->stash->update({
-        require => sub {
-            my ($module) = @_;
-            
-            $module =~ s/^\.\//$baseLocation\//;
-            return $require->($module);
-        }        
-    });
-    
+        
     return ($this->template, $ctx, $refProps);
 }
 
 sub CreateObject {
     my $this = shift;
     
-    my $count = $this->instances;
-    
-    unless($count) {
-        # нужно выполнить именно блок INIT шаблона при создании первого экземпляра
-        if (my $init = $this->template->blocks->{INIT}) {
-            $this->context->process($init);
-        }
-        
-        $this->context->visit($this->template->blocks);
-    }
+    $this->InitOnDemand();
     
     my $instance = $this->SUPER::CreateObject(@_);
-    
-    $instance->InitInstance();
-    
+
+    my $count = $this->instances;   
     $count++;
     $this->instances($count);
     
     return $instance;
 }
 
+sub InitOnDemand {
+    my ($this) = @_;
+    
+    unless ($this->initialized) {
+        $this->initialized(1);
+        
+        $this->base->InitOnDemand()
+            if $this->base;
+        
+        if (my $init = $this->template->blocks->{INIT}) {
+            $this->context->process($init);
+        }
+    }
+}
+
 sub CloneContext {
     my ($this) = @_;
     
@@ -104,6 +162,12 @@
     return Template::Context->new($args);
 }
 
+sub Render {
+    my ($this, $args) = @_;
+    
+    return $this->new()->Render($args);
+}
+
 sub save {
     die new IMPL::NotImplementedException("This class doesn't support serialization");
 }
--- a/_test/Resources/TTView/My/Org/Base.tt	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Resources/TTView/My/Org/Base.tt	Mon Mar 25 02:04:18 2013 +0400
@@ -4,6 +4,8 @@
 [% BLOCK FOOTER %]
 base footer
 [% END %]
-[% INCLUDE LABEL %]
-Render
-[% INCLUDE FOOTER %]
\ No newline at end of file
+BEGIN BASE DOCUMENT
+LABEL: [% INCLUDE LABEL %]
+base document body
+FOOTER: [% INCLUDE FOOTER %]
+END OF BASE DOCUMENT
--- a/_test/Resources/TTView/My/Org/Derived.tt	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Resources/TTView/My/Org/Derived.tt	Mon Mar 25 02:04:18 2013 +0400
@@ -1,5 +1,7 @@
-[% META base='./Base' %]
+[% META extends='./Base' %]
 [% BLOCK LABEL %]
 derived label
 [% END %]
-[% INCLUDE $base.RENDER %]
\ No newline at end of file
+BEGIN DERIVED DOCUMENT
+[% INCLUDE BASE %]
+END OF DERIVED DOCUMENT
--- a/_test/Resources/TTView/My/Org/Derived2.tt	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Resources/TTView/My/Org/Derived2.tt	Mon Mar 25 02:04:18 2013 +0400
@@ -1,2 +1,7 @@
-[% META base='./Derived2'%]
-[% BLOCK LABEL %]
\ No newline at end of file
+[% META extends='./Derived'%]
+[% BLOCK LABEL %]
+derived 2 label
+[% END %]
+BEGIN DERIVED 2 DOCUMENT
+[% INCLUDE BASE %]
+END OF DERIVED 2 DOCUMENT
\ No newline at end of file
--- a/_test/Resources/TTView/My/Org/Panel.tt	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Resources/TTView/My/Org/Panel.tt	Mon Mar 25 02:04:18 2013 +0400
@@ -11,7 +11,7 @@
     this.visualClass = this.visualClass || 'classic';
     this.childNodes = [];
     FOREACH text IN this.data;
-        this.childNodes.push( TPreview.new('preview', nodeValue = text ) );
+        this.childNodes.push( TPreview.new(nodeValue = text ) );
     END;
     
 %]
--- a/_test/Resources/TTView/My/Org/TextPreview.tt	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Resources/TTView/My/Org/TextPreview.tt	Mon Mar 25 02:04:18 2013 +0400
@@ -3,6 +3,4 @@
         dojo.require.push("dijit.layout.ContentPane");
     END;
 %]
-[% BLOCK RENDER %]
-    <div data-dojo-type="dijit.layout.ContentPane">$this.nodeValue</div>
-[% END %]
\ No newline at end of file
+<div data-dojo-type="dijit.layout.ContentPane">$this.nodeValue</div>
\ No newline at end of file
--- a/_test/Resources/TTView/complex.tt	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Resources/TTView/complex.tt	Mon Mar 25 02:04:18 2013 +0400
@@ -2,7 +2,7 @@
     META version = 1, title = "my document 2", layout="default";
 
     TPanel = require('My/Org/Panel');
-    this.childNodes = [ TPanel.new('information', data = data ) ];
+    this.childNodes = [ TPanel.new(data = data ) ];
 
 %]
 [% FOREACH node IN this.childNodes() %]
--- a/_test/Test/Web/View.pm	Fri Mar 22 01:05:11 2013 +0400
+++ b/_test/Test/Web/View.pm	Mon Mar 25 02:04:18 2013 +0400
@@ -100,6 +100,7 @@
     assert(defined $doc);
     
     $doc->title('test document');
+    $doc->name('document');
     
     assert($doc->name eq 'document');
     assert($doc->title eq 'test document');
@@ -141,6 +142,22 @@
     assert($text_raw eq $expected_raw, '$doc->Render(): Bad output', "Got:      $text", "Expected: $expected"); 
 };
 
+test TestControlInheritance => sub {
+    my ($this) = @_;
+    
+    my $loader = $this->CreateLoader();
+    my $doc = $loader->document('derived');
+    
+    my $text = $doc->Render();
+    my $expected = read_file($this->GetResourceFile('Resources', 'TTView.Output', 'derived.txt'), binmode => ':utf8' );
+    
+    my ($text_raw,$expected_raw) = ($text, $expected);
+    $text_raw =~ s/\s+//g;
+    $expected_raw =~ s/\s+//g;
+    
+    assert($text_raw eq $expected_raw, '$doc->Render(): Bad output', "Got:      $text", "Expected: $expected");
+};
+
 test TestDocumentsIsolation => sub {
 	my $this = shift;