Mercurial > pub > Impl
view Lib/IMPL/Web/Handler/TTView.pm @ 218:358f867712b4
sync
author | sergey |
---|---|
date | Mon, 20 Aug 2012 17:24:48 +0400 |
parents | c8fe3f84feba |
children | 47f77e6409f7 |
line wrap: on
line source
package IMPL::Web::Handler::TTView; use strict; use List::Util qw(first); use IMPL::lang qw(:declare :constants); use IMPL::declare { require => { Factory => 'IMPL::Object::Factory' }, base => { 'IMPL::Object' => undef, 'IMPL::Object::Autofill' => '@_', 'IMPL::Object::Serializable' => undef } }; BEGIN { public property contentType => PROP_GET | PROP_OWNERSET; public property loader => PROP_GET | PROP_OWNERSET; public property selectors => PROP_GET | PROP_LIST | PROP_OWNERSET; public property defaultDocument => PROP_ALL; public property indexResource => PROP_ALL; private property _selectorsCache => PROP_ALL; private property _classTemplates => PROP_ALL; } sub CTOR { my ($this) = @_; $this->indexResource('index') unless $this->indexResource; } sub Invoke { my ($this,$action,$next) = @_; my $result = $next ? $next->($action) : undef; my $vars = { data => $result, action => $action, app => $action->application, LoadFactory => sub { my $class = shift; my $module = $class; $module =~ s/::/\//g; $module .= ".pm"; require $module; return Factory->new($class); } }; my $doc = $this->loader->document( $this->SelectView($action,ref $result), $vars ); $action->response->contentType($this->contentType); my $hout = $action->response->streamBody; print $hout $doc->Render($vars); } sub SelectView { my ($this,$action,$class) = @_; my @path = split /\//, $action->query->path_info(), -1; shift @path; # remove always empty leading segment my $last = pop @path; $last =~ s/\.\w+$//; $last ||= $this->indexResource; push @path,$last; $this->BuildCache unless $this->_selectorsCache; my $cache = $this->_selectorsCache; @path = reverse @path; foreach my $subclass ( $class ? (_GetHierarchy($class), '-default') : '-plain') { my @results; push @results, { result => $this->_classTemplates->{$subclass}, level => 0 } if $this->_classTemplates->{$subclass}; if ($cache->{$subclass}) { my $alternatives = [ { selector => $cache->{$subclass}, immediate => 1 } ]; $alternatives = $this->MatchAlternatives($_,$alternatives,\@results) foreach @path; } if (@results) { @results = sort { $b->{level} <=> $a->{level} } @results; return (shift @results)->{result}; } } return $this->defaultDocument; } sub _GetHierarchy { my ($class) = @_; return unless $class; no strict 'refs'; return $class, map { _GetHierarchy($_) } @{"${class}::ISA"}; } sub BuildCache { my ($this) = @_; my @selectors; my $cache = $this->_selectorsCache({}); $this->_classTemplates({}); foreach my $selector ($this->selectors) { if (not ref $selector) { my ($path,$data) = split(/\s*=>\s*/, $selector); my @path = split(/\s+/,$path); my $class; # if this selector has a class part if ($path[$#path] =~ m/^\@(.*)/) { $class = $1; pop @path; } else { $class = '-default'; } #if this selector has a path if (@path) { @path = reverse @path; my $last = pop @path; my $t = ( $cache->{$class} ||= {} ); my $level = 1; foreach my $prim (@path ) { $t = ($t->{$prim}->{next} ||= {}); $level ++; } $t->{$last}->{level} = $level; $t->{$last}->{data} = $data; } else { # we dont have a selector, only class $this->_classTemplates->{$class} = $data; } } } } sub MatchAlternatives { my ($this,$segment,$alternatives,$results) = @_; my @next; foreach my $alt (@$alternatives) { while (my ($selector,$match) = each %{$alt->{selector}} ) { my $context = { vars => \%{ $alt->{vars} || {} }, selector => $match->{next} }; if ($selector =~ s/^>//) { $context->{immediate} = 1; } if (my ($name,$rx) = ($selector =~ m/^\{(?:(\w+)\:)?(.*)\}$/) ) { #this is a regexp if ( my @captures = ($segment =~ m/($rx)/) ) { $context->{success} = 1; if ($name) { $context->{vars}->{$name} = \@captures; } } } else { #this is a segment name if ($segment eq $selector) { $context->{success} = 1; } } # test if there were a match if (delete $context->{success}) { if (my $data = $match->{data}) { # interpolate data $data =~ s/{(\w+)(?:\:(\d+))?}/ my ($name,$index) = ($1,$2 || 0); if ($context->{vars}{$name}) { $context->{vars}{$name}[$index]; } else { ""; } /gex; push @$results, { level => $match->{level}, result => $data }; } push @next, $context if $context->{selector}; } else { #repeat current alternative if it's not required to be immediate push @next, { selector => { $selector, $match }, vars => $alt->{vars} } unless $alt->{immediate}; } } } return \@next; } 1; __END__ =pod =head1 NAME C<IMPL::Web::Handler::TTView> - использует шаблоны для построения представления. =head1 SYNOPSIS =begin code xml <item id="html-view" type="IMPL::Web::Handler::TTView"> <contentType>text/html</contentType> <loader id="tt-loader" type="IMPL::Web::View::TTLoader"> <options type="HASH"> <INCLUDE_PATH type="IMPL::Config::Reference"> <target>IMPL::Config</target> <AppBase>view</AppBase> </INCLUDE_PATH> <INTERPOLATE>1</INTERPOLATE> <POST_CHOMP>1</POST_CHOMP> <ENCODING>utf-8</ENCODING> </options> <ext>.tt</ext> <initializer>global.tt</initializer> <layoutBase>layouts</layoutBase> </loader> <defaultDocument>default</defaultDocument> <selectors type="ARRAY"> <item>@HASH => dump</item> <item>@My::Data::Product => product/info</item> <item>{action:.*} @My::Data::Product => product/{action}</item> </selectors> </item> =end code xml =head1 DESCRIPTION Подбирает шаблон для представления результата, полученного при выполнении следующего обработчика. При выборе используется принцип похожий на селекторы C<CSS>, основывающийся на именах ресурсов и их типах данных. =head1 SELECTORS =begin text [url-template] [class] => template shoes => product/list {action:*.} @My::Data::Product => product/{action} stuff >list => product/list details => product/details =end text =cut