view Lib/IMPL/Web/View/TTDocument.pm @ 194:4d0e1962161c

Replaced tabs with spaces IMPL::Web::View - fixed document model, new features (control classes, document constructor parameters)
author cin
date Tue, 10 Apr 2012 20:08:29 +0400
parents 8e8401c0aea4
children 7a920771fd8e
line wrap: on
line source

package IMPL::Web::View::TTDocument;
use strict;

use IMPL::lang qw(:declare :constants);
use IMPL::DOM::Property qw(_dom);
use IMPL::Web::View::TTFactory();
use IMPL::Web::View::TTControl();

use Scalar::Util qw(weaken);


use parent qw(
    IMPL::DOM::Document
    IMPL::Web::View::TTControl
);

BEGIN {
    public _dom property layout => PROP_ALL;
    public property opts => PROP_GET | PROP_OWNERSET;
    public property loader => PROP_ALL;
    public property controls => PROP_GET | PROP_OWNERSET;
    
    # store the stash separately to make require() method to work correctly
    # even when a stash of the context is modified during the processing 
    public property stash => PROP_GET | PROP_OWNERSET; 
}

sub CTOR {
    my ($this,$template,$refOpts,$loader,$vars) = @_;
    
    $this->controls({});
    $this->loader($loader) if $loader;
    
    $this->layout( $template->layout ) unless $this->layout;
    
    $this->opts($refOpts);
    $this->stash($this->context->stash);
    
    my $self = $this;
    weaken($self);
    
    $this->templateVars('require', sub {
        my $doc = $self;
        die new IMPL::Exception("A document is destroyed or invalid") unless $doc;
        $doc->require(@_);
    });
    
    $this->templateVars('document', sub { $self } );
    $this->InitInstance($vars);
}

our %CTOR = (
    'IMPL::Web::View::TTControl' => sub {
        my ($template,$contextOpts) = @_;
        'document',
        $_[0], # template
        new Template::Context($_[1])  # context
    },
    'IMPL::DOM::Document' => sub {
        nodeName => 'document'
    }
);

sub templateVars {
    my $this = shift;
    my $name = shift;
    
    if (@_) {
        return $this->stash->set($name, shift);        
    } else {
        return $this->stash->get($name);
    }
}

sub require {
    my ($this, $control, $nodeProps) = @_;
    
    $nodeProps ||= {};
    $nodeProps->{document} = $this;
    
    if (my $factory = $this->controls->{$control}) {
        return $factory;
    } else {
    
        my $path = $control;
        if ( my $template = $this->loader->template($path) ) {
            my $opts = { %{$this->opts} };

            # avoid propagation of local variables 
            $opts->{STASH} = $this->stash->clone();
 
            my $ctx = new Template::Context($opts);
            
            $factory = new IMPL::Web::View::TTFactory(
                $template->class || typeof IMPL::Web::View::TTControl,
                $template,
                $ctx,
                $opts
            );
            
            my @parts = split(/\/+/,$control);
            
            $this->controls->{$control} = $factory;
                        
            return $factory;

        } else {
            die new IMPL::KeyNotFoundException($control);
        }
    }
}

sub Render {
    my ($this,$args) = @_;
    
    my $output;
    
    if ($this->layout) {
        $output = $this->context->include(
            $this->loader->template($this->layout),
            {
                content => sub { $output ||= $this->RenderContent($args); },
                this => $this,
                template => $this->template
            }
        );
    } else {
        return $this->RenderContent($args);
    }
    
    return $output;
}

sub RenderContent {
    my $this = shift;
    return $this->SUPER::Render(@_);
}


1;

__END__

=pod

=head1 NAME

C<IMPL::Web::View::TTDocument> - документ для построения HTML страницы на основе шаблонов TT.

=head1 SYNOPSIS

=begin code

use IMPL::Web::View::TTDocument();

my $doc = new IMPL::Wbe::View::TTDocument($template,$ttOptions);

return $doc->Render();

=end code

Однако, более предпочтительный способ использовать C<IMPL::Web::View::TTLoader>.

=head1 DESCRIPTION

Документ для представления данных. Документы представляют собой иерархически организованные данные,
элементами данного документа являются данные для отображения, такие как

=over

=item * Объекты из БД

=item * Навигационные цепочки

=item * Меню и т.п. 

=back

Скприт шаблона формирует структуру документа, затем сформированная структура форматируется в готовый документ.
Процесс форматирования объектной модели в готовый документ может быть выполнена как вручную, так и при помощи
вспомогательного шаблона - обертки. Если у шаблона документа указан C<layout> в метаданных, то он будет
использован как шаблон для форматирования объектной модели, вывод самого шаблона будет проигнорирован. Если
обертка не задана, то результатом будет вывод самого скрипта шаблона.

Использование объектной модели документа позволяет решить задачи по созданию элементов управления
контейнеров, у которых может быть сложное содежимое. Примером таких элементов могут быть формы,
внутри форм элементы управления могут группироваться.

=head2 Элементы управления (компоненты)

Документ состоит из узлов, часть которых наследуется от C<IMPL::Web::View::TTControl>, такие узлы называются
элементами управления. Каждый элемент управления имеет собственный контекст, который наследуется от контекста
документа.

=head2 Фабрика элементов управления

Для создания элементов управления используются фабрики. Каждый элемен управления имеет свой шаблон для
форматиорвания содержимого, фабрика связывает шаблон и класс элемента управления, для чего при загрузке
шаблона используется свойство C<type> из метаданных. Фабрика загружается в документ при том только один
раз, повторные загрузки фабрики возвращают уже загруженную. Для загрузки фабрики используется метод
C<require()>. 


=head2 Порядок обработки документа

=over

=item 1 Создается документ при помощи метода C<TTLoader::document()>

=item 1 При создании документа (в конструкторе), происходит выполнение блока C<CTOR>

=item 1 При вызове метода C<TTDocument::Render()> устанавливаются переменные C<this>, C<document>
и шаблон обрабатывается при помощи метода C<process()> контекста документа.

=back

=head2 Загрузка элемента управления

=over

=item 1 C<TInput = require('my/org/input')>

=item 1 Загружает шаблон C<my/org/input.tt>

=item 1 Создает фабрику элементов управления с собственным контекстом, унаследованным от контекст документа.

=item 1 Выполняет шаблон в пространстве имен фабрики

=back

=head2 Создание элемента управления

=over

=item 1 C<< TInput.new('login') >>

=item 1 Если это первый элемент управления, то выполняетя статический конструктор в контексте фабрики

=item 1 Создается новый дочерний контекст к контексту фабрики

=item 1 Создается экземпляр элемента управления

=item 1 Выполняется блок конструктора в контексте элемента управления, параметр C<this> имеет значение
нового экземпляра элемента управления  

=back

=head1 MEMBERS

=over

=item C<CTOR($template, %options)>

Создает экземпляр документа с указанным шаблоном и параметрами, параметры 

=back

=cut