view Lib/IMPL/Web/View/TTDocument.pm @ 263:0f59b2de72af

*fixed IMPL::DOM::Schema circular module references *modified IMPL::Object::Singleton, added auto-activation *code cleanups, docs
author sergey
date Wed, 09 Jan 2013 05:17:44 +0400
parents 7c517134c42f
children bbc0da7ef90e
line wrap: on
line source

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

use Scalar::Util qw(weaken);
use IMPL::Const qw(:prop);
use IMPL::lang qw(:hash);

use IMPL::declare {
    require => {
        TTFactory => 'IMPL::Web::View::TTFactory',
        TTControl =>  'IMPL::Web::View::TTControl',
        Loader => 'IMPL::Code::Loader'
    },
    base => [
        'IMPL::Web::View::TTControl' => sub {
            my ($template,$contextOpts) = @_;
            'document',
            $template, # template
            Template::Context->new($contextOpts)  # context
        }
    ],
    props => [
        layout => PROP_RW,
        opts => PROP_RO,
        loader => PROP_RW,
        controls => PROP_RO,
        creationArgs => PROP_RO,
        
        # store the stash separately to make require() method to work correctly
        # even when a stash of the context is modified during the processing 
        stash => PROP_RO
    ]
};

BEGIN {
 
}

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);
    $this->creationArgs($vars);
    
    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->RequireControl(@_);
    });
    
    $this->templateVars(context => $vars);
    
    $this->templateVars(document => sub { $self } );
    $this->InitInstance($vars);
}

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

sub RequireControl {
    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} };

            # factory will create a clone of the stash 
            # $opts->{STASH} = $this->stash->clone();
 
            my $ctx = new Template::Context($opts);
            
            $factory = new IMPL::Web::View::TTFactory(
                $template->class || TTControl,
                $template,
                $ctx,
                $opts
            );
            
            if ($template->class) {
                Loader->safe->Require($template->class);
            }
            
            my @parts = split(/\/+/,$control);
            
            $this->controls->{$control} = $factory;
                        
            return $factory;

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

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

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 Создание объектной модели документа. На данном этапе создаются все элементы управления. 

=item 1 Преобразование объектной модели в конечнное представление. На данном этапе происходит
форматирование документа.

=back


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

=over

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

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

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

=item 1 При первом создании элемента управления фабрика инициализируется выполнением блока C<INIT>.

=back

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

=over

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

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

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

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

=back

=head1 MEMBERS

=over

=item C<CTOR($template, $contextOpts, $loader[, $vars])>

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

=over

=item C<$template>

C<Template::Document> шаблон документа.

=item C<$contextOpts>

C<HASH> Параметры контекста C<Template::Context> для документа. Эти параметры будут сохранены
в свойстве C<opts>, а также на их основе будет создан контекст текщего документа. Как правило
эти параметы задаются загрузчиком документа C<IMPL::Web::View::TTLoader>, таким образом, что
C<Template::Stash> создаваемого контекста наследует переменные из контекста загрузчика.

=item C<$loader>

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

=item C<$vars>

C<HASH> Необязательный параметр. переменные которые будут переданы в блок конструктора C<INIT>.
Как правило они используются для передачи данных для построения документа 

=back 

=back

=over

=item C<templateVars($name[,$newValue])>

Получает или задает переменную для шаблона документа. Имя переменнной может быть составным,
например C<'my.var.name'>, см. C<Template::Stash::set()>.

=item C<RequireControl($controlName)>

Загружает фабрику элемента управления, если она уже была загружена, возвращает на нее ссылку.
При загрузки фабрики для нее создается собственный контекст на основе параметров из свойства
C<opts> и ее пространство имен наследуется от пространства имен документа из свойства C<stash>.

=item C<Render($vars)>

Выполняет блок C<renderBlock> документа для получения конечного представления, C<$vars>
содержит переменные для блока.

=item C<RenderContent($vars)>

Выполняет шаблон документа для получения представления содержимого, в отличии от
метода C<Render> не использует обертку указанную в свойстве C<layout>, если обертка
не указана, то эти методы идентичны.

=item C<[get,set]layout>

Обертка, которая будет использована для построения представления документа. В обертке
будет доступна специальная переменная C<content>, при обращении к которой будет B<выполнен>
метод C<RenderContent()> и возвращен результат его работы. Для получения шаблона обертки
используется загрузчик из свойства C<loader>.

=item C<[get]opts>

Параметры контекста, используются для создания контекстов фабрик элементов управления.

=item C<[get]loader>

Загрузчик, используется для загрузки шаблонов фабрик элементов управления и обертки.

=item C<[get]controls>

C<HASH> Коллекция загруженных фабрик элементов управления, ключем является
квалифицированное имя элемента управления.

=item C<[get]stash>

C<Template::Stash> Пространство имен документа, оно используется как родительское
для пространств имен загружаемых фабрик элементов управления. 

=back

=head1 TEMPLATES

=begin text

[%META layout='default'%]
[% BLOCK CTOR;
    section('top','TOP');
    section('bottom','BOTTOM');
    section('client','CLIENT');
END %]
[% BLOCK TOP;
    TMenu = require('my/org/Menu');
    append(TMenu.new());
END %]

=end

=cut