# HG changeset patch # User sergey # Date 1385331571 -14400 # Node ID 833e663796c4ca98e5a3daa1fde42ec22b56140d # Parent 248f95c1762aaaa75158831bcabfec44d6079151 TTView: added view variable to pass rendering context between controls TTView: display function renamed to display_for WebResource: resources now marked with roles for searching a desired resource by a role in the resource chain diff -r 248f95c1762a -r 833e663796c4 Lib/IMPL/Web/Application/Resource.pm --- a/Lib/IMPL/Web/Application/Resource.pm Mon Nov 18 01:25:35 2013 +0400 +++ b/Lib/IMPL/Web/Application/Resource.pm Mon Nov 25 02:19:31 2013 +0400 @@ -21,12 +21,13 @@ 'IMPL::Web::Application::ResourceInterface' => undef ], props => [ - request => PROP_RO, + request => PROP_RO, application => PROP_RO, parent => PROP_RO, model => PROP_RO, id => PROP_RO, location => PROP_RO, + role => PROP_RO | PROP_LIST ] }; @@ -50,6 +51,16 @@ # либо остается не заданным $this->location( $args{location} || eval { $this->parent->location->Child( $this->id ) } ); + + if (my $role = $args{role}) { + if (ref($role) eq 'ARRAY') { + $this->role($role); + } elsif (not ref($role)) { + $this->role(split(/\s+/, $role)); + } else { + die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); + } + } } sub InvokeHttpVerb { @@ -102,21 +113,6 @@ } -sub PrepareEnvironment { - my ($this) = @_; - - my @stack; - my $env = {}; - - for ( my $res = $this ; $res ; $res = $res->parent ) { - push @stack, $res if $res->can('SetupEnvironment'); - } - - map $_->SetupEnvironment($env), reverse @stack; - - return $env; -} - # это реализация по умолчанию, базируется информации о ресурсах, содержащийся # в контракте. sub FetchChildResource { @@ -175,6 +171,32 @@ return $delegate->Invoke(@_) if eval { $delegate->can('Invoke') }; } +sub Seek { + my ($this, $role) = @_; + + my @roles; + + if (ref($role) eq 'ARRAY') { + @roles = @{$role}; + } elsif (not ref($role)) { + @roles = split(/\s+/, $role); + } else { + die ArgumentException->new( role => 'A invalid value is provided, expected ARRAY or SCALAR'); + } + + + for(my $r = $this; $r; $r = $r->parent) { + return $r if $r->HasRole(@roles); + } + return; +} + +sub HasRole { + my ($this, @roles) = @_; + my %cache = map { $_, 1 } @{$this->role}; + return scalar(grep not($cache{$_}), @roles) ? 0 : 1; +} + 1; __END__ @@ -272,6 +294,20 @@ автоматически (только для ресурсов имеющих родителя). Следует заметить, что адрес ресурса не содержит параметров запроса, а только путь. +=head2 C<[get,list]role> + +Список ролей ресурса. Роль это условный маркер, который позволяет определить +функции выполняемые ресурсом, например контейнер, профиль пользователя и т.п. + +Используется при построении цепочек навигации, а также при поиске с использованием +метода C. + +=head2 C + +Ищет ресурс в цепочке родителей (включая сам ресурс) с подходящими ролями. + +Роли могут быть переданы в виде массива или строки, где роли разделены пробелами + =head2 C<[get]FetchChildResource($id)> Возвращает дочерний ресурс, по его идентификатору. diff -r 248f95c1762a -r 833e663796c4 Lib/IMPL/Web/Handler/View.pm --- a/Lib/IMPL/Web/Handler/View.pm Mon Nov 18 01:25:35 2013 +0400 +++ b/Lib/IMPL/Web/Handler/View.pm Mon Nov 25 02:19:31 2013 +0400 @@ -10,7 +10,8 @@ Factory => 'IMPL::Web::View::ObjectFactory', HttpResponse => 'IMPL::Web::HttpResponse', Loader => 'IMPL::Code::Loader', - ViewResult => '-IMPL::Web::ViewResult' + ViewResult => '-IMPL::Web::ViewResult', + Security => 'IMPL::Security' }, base => [ 'IMPL::Object' => undef, @@ -40,24 +41,25 @@ my $result = $next ? $next->($action) : undef; - my ($model,$view,$template); + my ($model,$template); if( ref $result and eval { $result->isa(ViewResult) } ) { $model = $result->model; - $view = $result; $template = $result->template; } else { $model = $result; - $view = ViewResult->new(model => $model); + $result = ViewResult->new(model => $model); } my $vars = { - view => $view, + result => $result, request => sub { $action }, app => $action->application, - context => $action->context, - env => _cached($action->context->{environment}), location => $action->context->{resourceLocation}, - layout => $this->layout + resource => $action->context->{resource}, + layout => $this->layout, + document => {}, + session => sub { Security->context }, + user => sub { Security->principal } }; my %responseParams = ( @@ -70,25 +72,15 @@ ) ); - $responseParams{status} = $view->status if $view->status; - $responseParams{cookies} = $view->cookies if ref $view->cookies eq 'HASH'; - $responseParams{headers} = $view->headers if ref $view->headers eq 'HASH'; + $responseParams{status} = $result->status if $result->status; + $responseParams{cookies} = $result->cookies if ref $result->cookies eq 'HASH'; + $responseParams{headers} = $result->headers if ref $result->headers eq 'HASH'; return HttpResponse->new( %responseParams ); } -sub _cached { - my $arg = shift; - - return $arg unless ref $arg eq 'CODE'; - - return sub { - ref $arg eq 'CODE' ? $arg = &$arg() : $arg; - } -} - sub SelectView { my ($this,$action) = @_; diff -r 248f95c1762a -r 833e663796c4 Lib/IMPL/Web/View/TTContext.pm --- a/Lib/IMPL/Web/View/TTContext.pm Mon Nov 18 01:25:35 2013 +0400 +++ b/Lib/IMPL/Web/View/TTContext.pm Mon Nov 25 02:19:31 2013 +0400 @@ -23,6 +23,9 @@ BEGIN { no strict 'refs'; + # modules is a global (for the whole document) templates cache + # tt_cache is a local (for the current context only) templtes cache + # view is a special variable, which will be cloned and passed to the nested context foreach my $prop (qw( root base @@ -35,6 +38,7 @@ modules aliases id + view )) { my $t = $prop; @@ -75,7 +79,7 @@ } sub find_template { - my ($this,$name) = @_; + my ($this,$name, $nothrow) = @_; my $cache = $this->tt_cache; @@ -129,10 +133,12 @@ } } - $this->throw(Template::Constants::ERROR_FILE, "$name: not found"); + $this->throw(Template::Constants::ERROR_FILE, "$name: not found") + unless $nothrow; + return; } -sub display { +sub display_for { my $this = shift; my $model = shift; my ($template, $args); @@ -203,23 +209,41 @@ ); } +# обеспечивает необходимый уровень изоляции между контекстами +# $code - код, который нужно выполнить в новом контексте +# $env - хеш с переменными, которые будут переданы в новый контекст +# в процессе будет создан клон корневого контекста, со всеми его свойствами +# затем новый контекст будет локализован и в него будут добавлены новые переменные из $env +# созданный контекст будет передан параметром в $code sub invoke_environment { my ($this,$code,$env) = @_; $env ||= {}; my $ctx = ($this->root || $this)->clone(); - + + my @includes = @{$this->includes || []}; + + if ($this->base) { + unshift @includes, $this->base; + } + + my $view = $this->view; + $view = ref $view eq 'HASH' ? { %{$view} } : {}; + + hashApply($view, delete $env->{view}); + my $out = eval { $ctx->localise( hashApply( { + includes => \@includes, aliases => $this->aliases || {}, root => $this->root || $ctx, modules => $this->modules || {}, cache => TypeKeyedCollection->new(), - display => sub { - $ctx->display(@_); + display_for => sub { + $ctx->display_for(@_); }, render => sub { $ctx->render(@_); @@ -227,7 +251,8 @@ display_model => sub { $ctx->display_model(@_); }, - tt_cache => {} + tt_cache => {}, + view => $view }, $env ) @@ -244,6 +269,16 @@ return $out; } +# использует указанный шаблон для создания фрагмента документа +# шаблон может быть как именем, так и хешем, содержащим информацию +# о шаблоне. +# отдельно следует отметить, что данный метод создает новый контекст +# для выполнения шаблона в котором задает переменные base, parent, id +# а также создает переменные для строковых констант из labels +# хеш с переменными $args будет передан самому шаблону в момент выполнения +# если у шаблона указан класс элемента управления, то при выполнении шаблона +# будет создан экземпляр этого класса и процесс выполнения шаблона будет +# делегирован методу Render этого экземпляра. sub render { my ($this,$template,$args) = @_; @@ -276,7 +311,7 @@ } if (my $class = $info->{class}) { - $class->new($ctx,$info->{template},$args)->Render($args); + $class->new($ctx,$info->{template},$args)->Render({}); } else { return $ctx->include($info->{template},$args); } @@ -313,7 +348,7 @@ } sub find_template_for { - my ($this,$model) = @_; + my ($this,$model, $nothrow) = @_; my $type = typeof($model); @@ -331,8 +366,9 @@ my $sclass = shift @isa; (my $name = $sclass) =~ s/:+/_/g; + my ($shortName) = ($sclass =~ m/(\w+)$/); - $template = $this->find_template("templates/$name"); + $template = $this->find_template("templates/$name",1) || $this->find_template("templates/$shortName",1); if ($template) { $this->cache->Set($sclass,$template); @@ -343,7 +379,9 @@ } } - + $this->throw(Template::Constants::ERROR_FILE, "can't find a template for the model " . typeof($model)) + unless $nothrow; + return; } diff -r 248f95c1762a -r 833e663796c4 Lib/IMPL/Web/View/TTControl.pm --- a/Lib/IMPL/Web/View/TTControl.pm Mon Nov 18 01:25:35 2013 +0400 +++ b/Lib/IMPL/Web/View/TTControl.pm Mon Nov 25 02:19:31 2013 +0400 @@ -20,12 +20,19 @@ our $AUTOLOAD_REGEX = qr/^[a-z]/; sub CTOR { - my ($this,$context,$template) = @_; + my ($this,$context,$template,$args) = @_; $this->context($context) or die ArgException->new(context => 'A context is required'); $this->template($template) or die ArgException->new(template => 'A template is required'); + + if (ref $args eq 'HASH') { + while(my ($key, $value) = each %$args) { + next if grep $_ eq $key, qw(context template); + $this->$key($value); + } + } } sub _PopulateMethods { diff -r 248f95c1762a -r 833e663796c4 Lib/IMPL/Web/View/TTView.pm --- a/Lib/IMPL/Web/View/TTView.pm Mon Nov 18 01:25:35 2013 +0400 +++ b/Lib/IMPL/Web/View/TTView.pm Mon Nov 25 02:19:31 2013 +0400 @@ -1,11 +1,13 @@ package IMPL::Web::View::TTView; use strict; -use IMPL::lang qw(hashMerge); +use JSON; +use IMPL::lang qw(hashMerge is); use IMPL::Const qw(:prop); use IMPL::declare { require => { - Context => 'IMPL::Web::View::TTContext' + Context => 'IMPL::Web::View::TTContext', + Loader => 'IMPL::Code::Loader' }, base => [ 'IMPL::Object' => undef, @@ -44,12 +46,11 @@ return shift->render( $layout, hashMerge( - $args, { content => sub { $ctx->invoke_environment( sub { - return shift->display_model($model,$template,$args); + return shift->display_model($model,$template); }, { base => $this->viewBase @@ -67,7 +68,7 @@ } else { return $ctx->invoke_environment( sub { - return shift->display_model($model,$template,$args); + return shift->display_model($model,$template); }, { base => $this->viewBase @@ -75,15 +76,26 @@ ); } },hashMerge( - { - includes => scalar($this->includes), - tt_ext => $this->tt_ext, - document => {}, - debug => sub { - warn @_; + $this->globals, + hashMerge( + $args, + { + includes => scalar($this->includes), + tt_ext => $this->tt_ext, + debug => sub { + warn @_; + }, + is => sub { + return is(shift,shift); + }, + import => sub { + return Loader->safe->Require(shift); + }, + toJSON => sub { + return JSON->new()->utf8->pretty->encode(shift); + } } - }, - $this->globals + ) ) ); }