diff Lib/IMPL/Web/View/TTFactory.pm @ 301:aeeb57a12046

*IMPL::Web::View: templates inheritance support
author cin
date Mon, 25 Mar 2013 02:04:18 +0400
parents bf3af33b9003
children b5d5793f348e
line wrap: on
line diff
--- 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");
 }