Template Toolkit

(русская редакция)

[ Пособия ] [ Руководство ] [ Модули ] [ Библиотеки ] [ Утилиты ] [ Вопросы ] [ Релиз ] [ Perl-ресурсы ]
 
Поиск
Template Toolkit | Руководство | Представления

Представления

[ ◄ Внутренности ] [ Ссылки ► ]
Представления Template Toolkit (экспериментальная возможность).

Оглавление

ОПИСАНИЕ

Индекс ] [ Руководство ] [ Наверх ]

Этот раздел описывает динамические представления: мощную, но экспериментальную новую возможность версии Template Toolkit.

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

Вы можете использовать представления чтобы реализовать индивидуальные "шкурки" для приложений или наборов контента. Вы можете использовать их для упрощения представления общих объектов и типов данных. Вы можете даже использовать их для автоматизации представления комплексных структур данных, таких как сгенерированные в XML::DOM деревья или схожих. Вы позволяете итератору выполнять вызовы, а представление будет отвечать за представление. Иными словами, у вас есть независящий от представления, "прыгающий обход структуры" (Structure Shy Traversal) с использованием шаблонов.

В общем, представления могут использоваться рядом различных способов для решения различных задач. Они элегантно решают ряд проблем, которые в противном случае были бы сложными или замысловатыми, и делают некоторые вещи, которые были сложными, простыми.

В настоящий момент, это еще очень сырая возможность. Синтакс директив и лежаший в основе API вероятнее всего значительно изменятся в следующих версиях. Будьте крайне осторожны при создании своих многомиллионных решений для Интернет-коммерции на базе этой возможности.

Представления как сборщики/поставщики шаблонов

Директива VIEW открывает определение представления и включает имя, по которому можно ссылаться на представление. Определение представления продолжается до соответствующей директивы END.

    [% VIEW myview %]
       ...
    [% END %]

Первое назначение представления - служить в качестве сборщика и поставщика шаблонов. У представления можно вызывать метод include(), чтобы эффективно выполнить действие аналогичное директиве INCLUDE. В качестве первого аргумента передается имя шаблона, далее следуют любые локальные определения переменных шаблона.

    [% myview.include('header', title='The Title') %]
    # эквивалентно
    [% INCLUDE header  title='The Title' %] 

Представление принимает ряд опций конфигурации, которые используются для управления различными аспектами их поведения. Опции 'prefix' и 'suffix' можно указать, чтобы определить фиксированные префикс и суффикс к имени каждого шаблона.

    [% VIEW myview
         prefix = 'my/'
         suffix = '.tt2' ;
       END
    %]

Теперь вызов

    [% myview.include('header', title='The Title') %]

эквивалентен

    [% INCLUDE my/header.tt2  title='The Title' %]

Представления предоставляют метод AUTOLOAD, который создает соответствие между именами методов и методом include(). Так, следующие вызовы эквивалентны:

    [% myview.include('header', title='Hello World') %]
    [% myview.include_header(title='Hello World') %]
    [% myview.header(title='Hello World') %]

Локальные определения BLOCK

Определение VIEW может включать определения BLOCK, которые остаются локальными в представлении. Запрос к определенному шаблону вернет блок BLOCK, если он определен, в предпочтение к любому другому шаблону с таким же именем.

    [% BLOCK foo %]
       public foo block
    [% END %]
    [% VIEW plain %]
       [% BLOCK foo %]
       plain foo block
       [% END %]
    [% END %]
    [% VIEW fancy %]
       [% BLOCK foo %]
       fancy foo block
       [% END %]
    [% END %]
    [% INCLUDE foo %]	    # публичный блок foo
    [% plain.foo %]	    # блок foo представления plain
    [% fancy.foo %]	    # блок foo представления fancy

В дополнение к определениям BLOCK, VIEW может содержать любые другие директивы шаблона. Блок с полным определением VIEW обрабатывается для инициализации представления, но вывод не генерируется (это может измениться в следующих версиях и вывод будет сохранятся в элементе 'output', доступном далее как [% view.output %]). Тем не менее, директивы, имеющие побочные эффекты, такие как обновление переменной, будут иметь заметные последствия.

Сохранение состояния переменных внутри представления

Представления также могут быть использованы для сохранения значений любых существующих переменных или создания новых в точке определения представления. В отличие от простых метаданных шаблона (META), которые могут содержать статические строковые значения, блок инициализации представления может содержать любые директивы шаблона и генерировать любой вид динамического вывода и/или элементов данных.

    [% VIEW my_web_site %]
       [% view.title   = title or 'My Cool Web Site' %]
       [% view.author  = "$abw.name, $abw.email" %]
       [% view.sidebar = INCLUDE my/sidebar.tt2 %]
    [% END %]

Учтите, что дополнительные элементы данных могут быть указаны в качестве аргументов к директиве VIEW. Все, что не похоже на параметр конфигурации, считается элементом данных. Это, конечно, немного рисковано, потому что вы не знаете, когда новый элемент конфигурации, который пересекается с вашими данными, может быть добавлен.

    [% VIEW my_web_site
	    # опции конфигурации
	    prefix = 'my/'
	    # прочии данные
	    title   = title or 'My Cool Web Site'
	    author  = "$abw.name, $abw.email"
	    sidebar = INCLUDE my/sidebar.tt2
    %]
       ...
    [% END %]

Извне определения представления вы можете получить доступ к переменным представления примерно так:

    [% my_web_site.title %]

Одна важная возможность - эквивалентность простых переменных и шаблонов. Вы можете реализовать элемент представления 'title' как простую переменную, как шаблон, определенный во внешнем файле возможно с автоматически добавляемым префиксом/суффиксом, или как определение локального блока внутри определения [% VIEW %] ... [% END %]. Если вы используете приведенный выше синтакс, представление сделает правильные действия, чтобы вернуть соответствующий вывод.

В конце (END) определения представления (VIEW) оно "запечатывается", чтобы предотвратить случайное обновление любых значений переменных. Если вы попытаетесь изменить значение переменной после директивы END, закрывающей блок определения VIEW, то будет возбуждено исключение 'view'.

    [% TRY;
         my_web_site.title = 'New Title';
       CATCH;
         error;
       END
    %]

Ошибка выше выдаст следующее сообщение:

    view error - cannot update item in sealed view: title

Тоже самое произойдет, если вы передадите параметр переменной представления. Это интерпретируется как попытка обновить переменную и возбудит такое же исключение.

    [% my_web_site.title('New Title') %]    # ошибка view!

Вы можете установить параметр 'silent', чтобы заставить представление игнорировать эти параметры и просто возвращать значение переменной.

    [% VIEW my_web_site
            silent = 1
	    title  = title or 'My Cool Web Site'
	    # ... ;
       END
    %]
    [% my_web_site.title('Blah Blah') %]   # My Cool Web Site

В качестве альтернативы вы можете указать, чтобы представление не запечатывалось, разрешив обновление существующих переменных и определение новых.

    [% VIEW my_web_site
            sealed = 0
	    title  = title or 'My Cool Web Site'
	    # ... ;
       END
    %]
    [% my_web_site.title('Blah Blah') %]   # Blah Blah
    [% my_web_site.title %]                # Blah Blah

Наследование, делегирование и повторное использование

Представления могут наследовать свойства ранее определенных представлений с помощью параметра 'base'. Этот пример показывает как определяется базовый класс-представление, который накладывает префикс 'view/default/' ко всем именам шаблонов.

    [% VIEW my.view.default
            prefix = 'view/default/';
       END
    %]

Так директива:

    [% my.view.default.header(title='Hello World') %]

теперь эквивалентна:

    [% INCLUDE view/default/header title='Hello World' %]

Второе представление может быть определено с указанием этого представления в качестве базового.

    [% VIEW my.view.fancy
            base   = my.view.default
            prefix = 'view/fancy/';
       END
    %]

Теперь директива:

    [% my.view.fancy.header(title='Hello World') %]

будет разрешена как:

    [% INCLUDE view/fancy/header title='Hello World' %]

или если такой шаблон не существует, он будет обработан базовым представлением следующим образом:

    [% INCLUDE view/default/header title='Hello World' %]

Когда родительское представление указывается через параметр 'base', представление автоматически делегирует родителю права на получение шаблонов и доступ к определенным пользователем переменным. Также можно определить свое собственное наследование, делегирование и прочие повторно используемые шаблоны явным делегированием в другие представления.

    [% BLOCK foo %]
       public foo block
    [% END %]
    [% VIEW plain %]
       [% BLOCK foo %]
       <plain>[% PROCESS foo %]</plain>
       [% END %]
    [% END %]
    [% VIEW fancy %]
       [% BLOCK foo %]
       [% plain.foo | replace('plain', 'fancy') %]
       [% END %]
    [% END %]
    [% plain.foo %]	# <plain>public foo block</plain>
    [% fancy.foo %]	# <fancy>public foo block</fancy>

Учтите, что обычные директивы INCLUDE/PROCESS/WRAPPER работают совершенно независимо от представлений и всегда будут предпочитать оригинальное, неизмененное имя шаблона локальным определениям внутри представления.

Ссылка на самого себя

Ссылка на объект представления доступна при определении блока VIEW ... END по указанному имени или также по специальному имени 'view' (аналогично 'my $self = shift;' в методе Perl или указателю 'this' в C++ и т.д.). Изначально представление не запечатано, что позволяет определять и изменять любые элементы данных внутри блока VIEW ... END. Представление автоматически запечатывается в конце определения блока, предотвращая в дальнейшем любое изменение данных представления.

(ВНИМАНИЕ: запечатывание может быть опциональным. Так же как и запечатывание представления предотвращает обновления (SEALED), также возможно установить опцию в представлении, чтобы разрешить внешним контекстам обновлять существующие переменные (UPDATE) или даже создавать совершенно новые переменные представления (CREATE)).

    [% VIEW fancy %]
       [% fancy.title  = 'My Fancy Title' %]
       [% fancy.author = 'Frank Open' %]
       [% fancy.col    = { bg => '#ffffff', bar => '#a0a0ff' } %]
    [% END %]

или

    [% VIEW fancy %]
       [% view.title  = 'My Fancy Title' %]
       [% view.author = 'Frank Open' %]
       [% view.col    = { bg => '#ffffff', bar => '#a0a0ff' } %]
    [% END %]

В данном случае не существует никакой разницы ссылаетесь ли вы на представление по его имени, 'fancy', или по общему имени, 'view'. Тем не менее извне блока представления необходимо всегда использовать только присвоенное имя 'fancy':

    [% fancy.title  %]
    [% fancy.author %]
    [% fancy.col.bg %]

Выбор между выбранным именем или 'view' гораздо более важен, когда оно используется в определениях BLOCK внутри VIEW. В общем случае рекомендуется использовать 'view' внутри определения VIEW, так как это гарантирует правильное определение в любой точке в будущем, когда блок будет вызываться. Оригинальное имя представления существует до тех пор пока не будет изменено или повторно использовано, но ссылка на себя через 'view' будет всегда неизмена и правильна.

Возмем в качестве примера следующее определение VIEW:

    [% VIEW foo %]
       [% view.title = 'Hello World' %]
       [% BLOCK header %]
       Title: [% view.title %]
       [% END %]
    [% END %]

Даже если мы переименнуем представление, или создадим новую переменную 'foo', блок header по прежнему будет правильно обращаться к атрибуту 'title' представления, к которому он относится. Когда бы не обрабатывался блок BLOCK представления, переменная 'view' всегда содержит правильную ссылку на объект представления, к которому она относится.

    [% bar = foo %]
    [% foo = { title => "New Foo" } %]  # no problem
    [% bar.header %]                    # => Title: Hello World

Хранение ссылок на внешние представления

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

    [% VIEW plain %]
       ...
    [% END %]
    [% VIEW fancy %]
       [% view.plain = plain %]
       [% BLOCK foo %]
       [% view.plain.foo | replace('plain', 'fancy') %]
       [% END %]
    [% END %]
    [% plain.foo %]	    # => <plain>public foo block</plain>
    [% plain = 'blah' %]    # это не проблема
    [% fancy.foo %]	    # => <fancy>public foo block</fancy>

Использование представления для отображения данных

Другая ключевая роль представления - диспетчер, автоматически применяющий правильный шаблон к частному объекту или элементу данных. Это контролируется методом print().

Вот пример:

    [% VIEW foo %]
       [% BLOCK text %]
          Some text: [% item %]
       [% END %]
       [% BLOCK hash %]
          a hash:
          [% FOREACH key = item.keys.sort -%]
             [% key %] => [% item.$key %]
          [% END -%]
       [% END %]
       [% BLOCK list %]
          a list: [% item.sort.join(', ') %]
       [% END %]
    [% END %]

Теперь можно использовать представление для печати текста, хешей или массивов. Метод print() включает правильный шаблон в зависимости от типа передаваемого ему аргумента (или аргументов).

    [% some_text = 'I read the news today, oh boy.' %]
    [% a_hash    = { house => 'Lords', hall => 'Albert' } %]
    [% a_list    = [ 'sure', 'Nobody', 'really' ] %]
    [% view.print(some_text) %]
			# Some text: I read the news today, oh boy.
    [% view.print(a_hash) %]
			# a hash:
                             hall => Albert
                             house => Lords
    [% view.print(a_list) %]
			# a list: Nobody, really, sure

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

    [% VIEW foo %]
       [% BLOCK Foo_Bar %]
          a Foo::Bar object:
              thingies: [% view.print(item.thingies) %]
               doodahs: [% view.print(item.doodahs)  %]
       [% END %]
    [% END %]
    [% USE fubar = Foo::Bar(...) %]
    [% foo.print(fubar) %]

Обратите внимание на то как объект представления выводит различные элементы внутри объектов ('thingies' и 'doodahs'). Нам не нужно заботиться какой тип данных представляет эти элементы (текст, массив, хеш и т.д.), потому что мы можем позволить представлению заботиться об этом, автоматически отображая тип данных на правильный шаблон.

Представление может содержать собственное определение карты тип => шаблон.

    [% VIEW foo
         map = { TEXT  => 'plain_text',
		 ARRAY => 'show_list',
		 HASH  => 'show_hash',
		 My::Module => 'template_name'
		 default    => 'any_old_data'
               }
    %]
	[% BLOCK plain_text %]
           ...
        [% END %]

        ...
    [% END %]

Он также может содержать элемент карты по умолчанию 'default', определенный как часть хеша 'map', или в виде отдельного параметра.

    [% VIEW foo
         map     = { ... },
         default = 'whatever'
    %]
       ...
    [% END %]

или

    [% VIEW foo %]
       [% view.map     = { ... }
          view.default = 'whatever'
       %]
       ...
    [% END %]

Метод print() содержит еще один магический момент. Если вы передаете ему ссылку на объект, который имеет метод present(), то этот метод будет вызван с аргументом - ссылкой на представление. Это дает любому объекту возможность определить как он должен быть отображен через представление.

    package Foo::Bar;
    ...
    sub present {
	my ($self, $view) = @_;
	return "a Foo::Bar object:\n"
	     . "thingies: " . $view.print($self->{ _THINGIES }) . "\n"
             . "doodahs: " . $view.print($self->{ _DOODAHS }) . "\n";
    }

Объект может свободно погружаться в дебри собственных приватных данных прежде чем вернуть через представление подходящий результат. В более сложном примере, метод present() при обходе дерева может делать обратные вызовы в представление, чтобы получить представление различных узлов дерева. Мы можем не хотеть показывать внутреннюю структуру дерева (потому что это разрушит инкапсуляцию и сделает код представления зависящим от нее), но мы хотим иметь какой-то способ прохождения по дереву и представлению, найденных элементов в особенном стиле.

Это известно как "прыгающий обход структуры" (Structure Shy Traversal). Нашему объекту представления не требуются дополнительные знания о внутренней структуре любого набора данных, чтобы обойти его и представить содержащиеся в нем данные. Элементы данных сами через метод present(), могут реализовать внутренние итерации, чтобы направить представление на путь к правильному отображению данных.

В результате вы можете использовать представления чтобы очень сильно упростить отображение таких структур данных, как деревья XML::DOM. Документация к модулю Template::Plugins::XML::DOM содержит пример такого использования. В сущности это выглядит примерно так:

Исходный XML:

    <user name="Andy Wardley">
	<project id="iCan" title="iCan, but theyCan't"/>
	<project id="p45"  title="iDid, but theyDidn't"/>
    </user>

TT представление:

    [% VIEW fancy %]
       [% BLOCK user %]
          User: [% item.name %]
                [% item.content(myview) %]
       [% END %]
       [% BLOCK project %]
            Project: [% project.id %] - [% project.name %]
       [% END %]
    [% END %]

Генерируем представление:

    [% USE dom = XML.DOM %]
    [% fancy.print(dom.parse(xml_source)) %]

Вывод:

          User: Andy Wardley
            Project: iCan - iCan, but theyCan't
            Project: p45 - iDid, but theyDidn't

Сходный подход может применяться во многих других областях. Вот пример из плагина File/Directory.

    [% VIEW myview %]
       [% BLOCK file %]
          - [% item.name %]
       [% END %]

       [% BLOCK directory %]
          * [% item.name %]
            [% item.content(myview) FILTER indent %]
       [% END %]
    [% END %]
    [% USE dir = Directory(dirpath) %]
    [% myview.print(dir) %]

И вот схожий подход используемый при конвертации pod-документации в любой другой формат через шаблон.

    [%	# загружаем плагин Pod и парсим исходный файл в объектную модель Pod
	USE Pod;
	pom = Pod.parse_file(my_pod_file);

	# определяем представление чтобы отобразить все элементы Pod на "pod/html/xxx" шаблоны
	VIEW pod2html
	    prefix='pod/html';
	END;

	# теперь печатаем документ через представление (т.е. как HTML)
	pod2html.print(pom)
    %]

Здесь мы просто отпределяем префикс шаблона для представления, что заставляет представление искать шаблоны 'pod/html/head1', 'pod/html/head2', 'pod/html/over', чтобы представить различные разделы разобранного pod-документа.

Вот ряд примеров в наборе тестов Template Toolkit: t/pod.t и t/view.t, которые могут пролить больше света на это. В подкаталоге дистрибутива 'examples/pod/html' можно посмотреть примеры шаблонов Pod -> HTML.

(Эта документация не является полной, но я не готов завершить ее на 100%, пока синтакс и API не станут стабильными).

АВТОР

Индекс ] [ Руководство ] [ Наверх ]

Энди Уардли (Andy Wardley <abw@andywardley.com>)

http://www.andywardley.com/

ВЕРСИЯ

Индекс ] [ Руководство ] [ Наверх ]

Template Toolkit версия 2.14, дата релиза - 4 октября 2004.

АВТОРСКИЕ ПРАВА

Индекс ] [ Руководство ] [ Наверх ]

  Copyright (C) 1996-2004 Andy Wardley.  All Rights Reserved.
  Copyright (C) 1998-2002 Canon Research Centre Europe Ltd.

Этот модуль является свободно-распространяемым программным обеспечением; вы можете распространять и/или модифицировать его на тех же условиях, что и Perl.

[ ◄ Внутренности ] [ Ссылки ► ]

[ Пособия ] [ Руководство ] [ Модули ] [ Библиотеки ] [ Утилиты ] [ Вопросы ] [ Релиз ] [ Perl-ресурсы ]

http://www.template-toolkit.ru/