Mercurial > pub > Impl
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;