Mercurial > pub > Impl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 300:bf3af33b9003 | 301:aeeb57a12046 |
|---|---|
| 1 package IMPL::Web::View::TTFactory; | 1 package IMPL::Web::View::TTFactory; |
| 2 use strict; | 2 use strict; |
| 3 | 3 |
| 4 use Template::Context(); | 4 use Template::Context(); |
| 5 | 5 |
| 6 use Carp qw(carp); | |
| 6 use IMPL::lang qw(:hash); | 7 use IMPL::lang qw(:hash); |
| 7 use IMPL::Exception(); | 8 use IMPL::Exception(); |
| 8 use Scalar::Util qw(weaken); | 9 use Scalar::Util qw(weaken); |
| 9 | 10 |
| 10 | 11 |
| 11 use IMPL::Const qw(:prop); | 12 use IMPL::Const qw(:prop); |
| 12 use IMPL::declare { | 13 use IMPL::declare { |
| 13 require => { | 14 require => { |
| 14 Loader => 'IMPL::Code::Loader' | 15 Loader => 'IMPL::Code::Loader', |
| 16 OpException => '-IMPL::InvalidOperationException', | |
| 17 ArgException => '-IMPL::InvalidArgumentException' | |
| 15 }, | 18 }, |
| 16 base => [ | 19 base => [ |
| 17 'IMPL::Object::Factory' => sub { | 20 'IMPL::Object::Factory' => sub { |
| 18 shift; | 21 shift->class || 'IMPL::Web::View::TTControl'; |
| 19 } | 22 } |
| 20 ], | 23 ], |
| 21 props => [ | 24 props => [ |
| 22 template => PROP_RW, | 25 template => PROP_RW, |
| 23 context => PROP_RW, | 26 context => PROP_RW, |
| 24 instances => PROP_RW, | 27 instances => PROP_RW, |
| 25 baseLocation => PROP_RW, | 28 baseLocation => PROP_RW, |
| 26 require => PROP_RO | 29 base => PROP_RW, |
| 30 require => PROP_RO, | |
| 31 blocks => PROP_RO, | |
| 32 initialized => PROP_RO | |
| 27 ] | 33 ] |
| 28 }; | 34 }; |
| 29 | 35 |
| 30 sub CTOR { | 36 sub CTOR { |
| 31 my ($this,$class,$template,$context,$baseLocation,$require) = @_; | 37 my ($this,$template,$context,$baseLocation,$require) = @_; |
| 32 | 38 |
| 33 die IMPL::ArgumentException("A template is required") unless $template; | 39 die ArgException->new("A template is required") unless $template; |
| 34 | 40 |
| 35 Loader->safe->Require($class) | 41 Loader->safe->Require($this->factory) |
| 36 if $class and not ref $class; | 42 if $this->factory and not ref $this->factory; |
| 37 | 43 |
| 38 $context ||= new Template::Context(); | 44 $context ||= new Template::Context(); |
| 39 | 45 |
| 40 $this->template($template); | 46 $this->template($template); |
| 41 $this->context($context); | 47 $this->context($context); |
| 42 $this->baseLocation($baseLocation); | 48 $this->baseLocation($baseLocation); |
| 43 $this->instances(0); | 49 $this->instances(0); |
| 44 $this->require($require); | 50 $this->require($require); |
| 51 | |
| 52 if (my $baseTplName = $template->extends) { | |
| 53 $baseTplName =~ s{^\./}{$baseLocation/}; | |
| 54 | |
| 55 my $base = &$require($baseTplName) | |
| 56 or die OpException->new("The specified base template isn't found"); | |
| 57 | |
| 58 $this->base($base); | |
| 59 | |
| 60 $this->blocks(hashMerge($base->blocks, $template->blocks)); | |
| 61 | |
| 62 # блок BASE должен меняться в процессе выполнения, в зависимости от | |
| 63 # шаблона, который рендерится, по мере перехода в BASE | |
| 64 | |
| 65 my @baseStack; | |
| 66 | |
| 67 $this->blocks->{BASE} = sub { | |
| 68 my $ctx = shift; | |
| 69 | |
| 70 die OpException->new("This tamplate doesn't have a base template") | |
| 71 unless $base; | |
| 72 | |
| 73 push @baseStack, $base; | |
| 74 my $block = $base->template->block; | |
| 75 | |
| 76 $base = $base->base; | |
| 77 | |
| 78 my $result = eval { | |
| 79 $ctx->process($block); | |
| 80 }; | |
| 81 my $e = $@; | |
| 82 | |
| 83 $base = pop @baseStack; | |
| 84 | |
| 85 die $e if $e; | |
| 86 return $result; | |
| 87 }; | |
| 88 } else { | |
| 89 $this->blocks( $template->blocks ); | |
| 90 } | |
| 91 | |
| 92 if(my $blocks = $this->blocks) { | |
| 93 while (my ($name,$block) = each %$blocks) { | |
| 94 $context->define_block($name,$block); | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 $context->stash->update({ require => sub { | |
| 99 my ($module) = @_; | |
| 100 | |
| 101 $module =~ s/^\.\//$baseLocation\//; | |
| 102 return $require->($module); | |
| 103 }}); | |
| 45 } | 104 } |
| 46 | 105 |
| 47 sub MergeParameters { | 106 sub MergeParameters { |
| 48 my ($this,$refProps) = @_; | 107 my $this = shift; |
| 108 my $refProps = shift || {}; | |
| 109 | |
| 110 unless (ref($refProps) eq 'HASH') { | |
| 111 carp "Passing control name through the first parameter is deprecated"; | |
| 112 my $name = $refProps; | |
| 113 $refProps = shift; | |
| 114 $refProps->{name} ||= $name; | |
| 115 } | |
| 49 | 116 |
| 50 $refProps->{factory} = $this; | 117 $refProps->{factory} = $this; |
| 51 | |
| 52 my $baseLocation = $this->baseLocation; | |
| 53 | |
| 54 my $ctx = $this->CloneContext(); | 118 my $ctx = $this->CloneContext(); |
| 55 | 119 |
| 56 my $require = $this->require; | |
| 57 | |
| 58 $ctx->stash->update({ | |
| 59 require => sub { | |
| 60 my ($module) = @_; | |
| 61 | |
| 62 $module =~ s/^\.\//$baseLocation\//; | |
| 63 return $require->($module); | |
| 64 } | |
| 65 }); | |
| 66 | |
| 67 return ($this->template, $ctx, $refProps); | 120 return ($this->template, $ctx, $refProps); |
| 68 } | 121 } |
| 69 | 122 |
| 70 sub CreateObject { | 123 sub CreateObject { |
| 71 my $this = shift; | 124 my $this = shift; |
| 72 | 125 |
| 73 my $count = $this->instances; | 126 $this->InitOnDemand(); |
| 74 | 127 |
| 75 unless($count) { | 128 my $instance = $this->SUPER::CreateObject(@_); |
| 76 # нужно выполнить именно блок INIT шаблона при создании первого экземпляра | 129 |
| 130 my $count = $this->instances; | |
| 131 $count++; | |
| 132 $this->instances($count); | |
| 133 | |
| 134 return $instance; | |
| 135 } | |
| 136 | |
| 137 sub InitOnDemand { | |
| 138 my ($this) = @_; | |
| 139 | |
| 140 unless ($this->initialized) { | |
| 141 $this->initialized(1); | |
| 142 | |
| 143 $this->base->InitOnDemand() | |
| 144 if $this->base; | |
| 145 | |
| 77 if (my $init = $this->template->blocks->{INIT}) { | 146 if (my $init = $this->template->blocks->{INIT}) { |
| 78 $this->context->process($init); | 147 $this->context->process($init); |
| 79 } | 148 } |
| 80 | 149 } |
| 81 $this->context->visit($this->template->blocks); | |
| 82 } | |
| 83 | |
| 84 my $instance = $this->SUPER::CreateObject(@_); | |
| 85 | |
| 86 $instance->InitInstance(); | |
| 87 | |
| 88 $count++; | |
| 89 $this->instances($count); | |
| 90 | |
| 91 return $instance; | |
| 92 } | 150 } |
| 93 | 151 |
| 94 sub CloneContext { | 152 sub CloneContext { |
| 95 my ($this) = @_; | 153 my ($this) = @_; |
| 96 | 154 |
| 100 delete $args->{CONFIG}; | 158 delete $args->{CONFIG}; |
| 101 | 159 |
| 102 $this->context->delocalise(); | 160 $this->context->delocalise(); |
| 103 | 161 |
| 104 return Template::Context->new($args); | 162 return Template::Context->new($args); |
| 163 } | |
| 164 | |
| 165 sub Render { | |
| 166 my ($this, $args) = @_; | |
| 167 | |
| 168 return $this->new()->Render($args); | |
| 105 } | 169 } |
| 106 | 170 |
| 107 sub save { | 171 sub save { |
| 108 die new IMPL::NotImplementedException("This class doesn't support serialization"); | 172 die new IMPL::NotImplementedException("This class doesn't support serialization"); |
| 109 } | 173 } |
