Mercurial > pub > Impl
view Lib/IMPL/Web/TT/Document.pm @ 234:2530d1bb9638
sync
author | sergey |
---|---|
date | Thu, 11 Oct 2012 20:11:45 +0400 |
parents | 4d0e1962161c |
children |
line wrap: on
line source
package IMPL::Web::TT::Document; use strict; use warnings; use parent qw(IMPL::DOM::Document IMPL::Object::Disposable); use Template::Context; use Template::Provider; use IMPL::Class::Property; use File::Spec; use Scalar::Util qw(blessed); use IMPL::Web::TT::Collection; use IMPL::Web::TT::Control; use Carp; use Encode(); use Data::Dumper; BEGIN { private property _provider => prop_all; private property _context => prop_all; public property cache => prop_all; public property template => prop_get | owner_set; public property presenter => prop_all, { validate => \&_validatePresenter }; public property preprocess => prop_all | prop_list, public property title => prop_all; private property _controlClassMap => prop_all; } our %CTOR = ( 'IMPL::DOM::Document' => sub { nodeName => 'document' } ); sub CTOR { my ($this,%args) = @_; $this->_controlClassMap({}); $this->registerControlClass( Control => 'IMPL::Web::TT::Control' ); $this->appendChild( $this->Create(body => 'IMPL::Web::TT::Collection') ); $this->appendChild( $this->Create(head => 'IMPL::Web::TT::Collection') ); $this->cache($args{cache}) if $args{cache}; $this->preprocess($args{preprocess}) if $args{preprocess}; } sub CreateControl { my ($this,$name,$class,$args) = @_; $args = {} unless ref $args eq 'HASH'; if (my $info = $this->_controlClassMap->{$class}) { my %nodeArgs = (%{$info->{args}},%$args); $nodeArgs{controlClass} = $class; return $this->Create($name,$info->{type},\%nodeArgs); } else { die new IMPL::Exception('A control is\'t registered', $class, $name); } } sub provider { my ($this,%args) = @_; if (my $provider = $this->_provider) { return $provider; } else { return $this->_provider(new Template::Provider( \%args )); } } sub context { my ($this) = @_; if (my $ctx = $this->_context) { return $ctx; } else { return $this->_context ( new Template::Context( VARIABLES => { document => $this, this => $this, render => sub { $this->_process(@_); }, encode => sub { Encode::encode('utf8',shift); }, dump => sub { Dumper(shift); }, as_list => sub { [ map ref($_) eq 'ARRAY' ? @$_ : $_, @_ ] } }, RECURSION => 1, LOAD_TEMPLATES => [$this->provider] ) ) } } sub resolveVar { my ($this,$var) = @_; return $this->context->stash->get($var); } sub registerControlClass { my ($this, $controlClass, $type, $args) = @_; $type ||= 'IMPL::Web::TT::Control'; die new IMPL::InvalidArgumentException("A controlClass must be a single word",$controlClass) unless $controlClass =~ /^\w+$/; eval "require $type; 1;" or die new IMPL::Exception("Failed to load a module",$type,"$@") unless eval { $type->can('new') }; die new IMPL::InvalidArgumentException("A type must be subclass of IMPL::DOM::Node",$type) unless $type->isa('IMPL::DOM::Node'); # resolve template name to a real template $args->{template} = $this->context->template($args->{template}) if $args->{template}; $this->_controlClassMap->{$controlClass} = { controlClass => $controlClass, type => $type, args => ref $args eq 'HASH' ? $args : {} }; } sub require { my ($this,$template) = @_; my $doc = $this->context->template($template); die new IMPL::InvalidOperationException("A specified template isn't a document",$template) unless eval{ $doc -> isa('Template::Document') }; my $controlClass = $doc->class; my $type = $doc->nativeType; my $controlTemplate; my $out = ""; die new IMPL::InvalidOperationException("A specified template isn't a control",$template) unless $controlClass; if (not $this->isControlClass($controlClass)) { if ($doc->template) { $controlTemplate = $doc->blocks()->{$doc->template} || $this->context->template($doc->template); $out = $this->context->include($doc); } else { $controlTemplate = $doc; } $this->registerControlClass($controlClass,$type,{ template => $controlTemplate } ); } return $out; } sub isControlClass { my ($this,$name) = @_; return $this->_controlClassMap->{$name} ? 1 : 0; } sub _getControls { my ($this) = @_; my ($node) = $this->selectNodes('controls'); return $node; } sub _validatePresenter { my ($this,$value) = @_; die new IMPL::InvalidArgumentException("A view object is required") unless blessed($value) and $value->isa('Template::View'); } sub LoadFile { my ($this,$src,$encoding,$includes,$vars) = @_; die new IMPL::InvalidArgumentException("A template parameter is required") unless $src; $includes = [$includes] if $includes and not ref $includes; $encoding ||= 'utf8'; $this->_context(undef); $this->_provider(undef); if (not ref $src) { my ($vol,$dir,$fileName) = File::Spec->splitpath($src); unshift @$includes, File::Spec->catpath($vol,$dir,''); $src = $fileName; } $this->provider( ENCODING => $encoding, INTERPOLATE => 1, PRE_CHOMP => 1, POST_CHOMP => 1, TRIM => 0, COMPILE_EXT => $this->cache ? '.ttc' : undef, COMPILE_DIR => $this->cache, INCLUDE_PATH => $includes ); if ($vars) { while ( my ($var,$val) = each %$vars ) { $this->AddVar($var,$val); } } $this->context->process($_) foreach $this->preprocess; my $template = $this->context->template($src); $this->title($template->title); if ( $template->template ) { $this->context->process($template); $this->template($template->template); } else { $this->template($template); } } sub AddVar { my ($this,$name,$value) = @_; $this->context->stash->set($name,$value); } sub Render { my ($this) = @_; return $this->context->process($this->template); } # Формирует представление для произвольных объектов sub _process { my ($this,@items) = @_; my @result; foreach my $item (@items) { if (blessed($item) and $item->isa('IMPL::Web::TT::Control')) { push @result, $item->Render(); } elsif(blessed($item)) { if ($this->presenter) { push @result, $this->presenter->print($item); } else { push @result, $this->toString; } } else { push @result, $item; } } return join '',@result; } our $AUTOLOAD; sub AUTOLOAD { my $this = shift; my ($method) = ($AUTOLOAD =~ /(\w+)$/); if($method =~ /^create(\w+)/) { my ($name,$args) = @_; return $this->CreateControl($name,$1,$args); } my @result = $this->selectNodes($method); return $result[0] if @result; carp "Looks like you have a mistake, the document doesn't have a such property or child: $method"; return; } sub Dispose { my ($this) = @_; $this->template(undef); $this->_context(undef); $this->_provider(undef); $this->supercall::Dispose(); } 1; __END__ =pod =head1 NAME C<IMPL::Web::TT::Document> - Документ, позволяющий строить представление по шаблону =head1 SYNOPSIS =begin code // create new document my $doc = new IMPL::Web::TT::Document; // load template $doc->loadFile('Templates/index.tt'); // render file print $doc->Render(); =end code =head1 DESCRIPTION C<use parent qw(IMPL::DOM::Document)> Документ, основанный на шаблоне Template::Toolkit. Позволяет загрузить шаблон, и сформировать окончательный документ. Является наследником C<IMPL::DOM::Node>, т.о. может быть использован для реализации DOM модели. Внутри шаблона переменная C<document> ссылается на объект документа. По этой причине образуется циклическая ссылка между объектами шаблона и документом, что требует вызова метода C<Dispose> для освобождения документа. =head1 METHODS =over =item C<CTOR()> Создает новый экземпляр документа, свойство C<nodeName> устанавливается в 'C<document>' =item C<$doc->LoadFile($fileName,$encoding)> Загружает шаблон из файла C<$fileName>, используя кодировку C<$encoding>. Если кодировка не указана, использует utf-8. =item C<$doc->Render()> Возвращает данные построенные на основе загруженного шаблона. =item C<$doc->Dispose()> Освобождает ресурсы и помечает объект как освобожденный. =back =head1 DOM Документ представляет собой DOM документ, состоящий из узлов, которые представляют собой данные для отображения. Для форматированого вывода используется C<template>. В качестве элементов документа могут присутсвовать специальные объекты C<IMPL::Web::TT::Control>, которые внутри содержат шаблон для форматирования собственного содержимого. Документ предоставляет ряд фнукций для работы с элементами управления. =head1 TEMPLATE =begin code html [% CALL document.registerClass( 'Table', 'My::TableClass', template => 'tables/pretty.tt' ) %] [% CALL document.registerClass( 'Form' )%] [% table = document.сreateTable('env') %] [% FOEACH item in document.result %] [% table.rows.Add( item.get('name','value') ) %] [% END %] [% form = document.createForm('login') %] [% form.template = 'LOGIN_FORM'%] [% FOREACH item IN document.childNodes %] [%render(item)%] [% END %] [% BLOCK LOGIN_FORM %] <form method="POST" action='/login.pl'> user: [% render(this.item('name')) %] password: [% render(this.item('password')) %] <input type="submit"/> </form> [% END %] =end code html =cut