In-Portal Developers Guide

This is a wiki-based Developers Guide for In-Portal Open Source CMS. The purpose of this guide is to provide advanced users, web developers and programmers with documentation on how to expand, customize and improve the functionality and the code the In-Portal software. Please consider contributing to our documentation writing effort.

K4:Особенности работы с тэгами

From In-Portal Developers Guide

Jump to: navigation, search

Работа с шаблонами Работа с шаблонами
Статьи в этой категории

Параметры по умолчанию в теле тэга "RenderElement"

Если в теле тэга RenderElement используются необязательные параметры, которые не были переданы в теле вызова RenderElement, необходимо указать значение по умолчанию непереданных параметров.К примеру на шаблоне присутствует следующий вызов тэга RenderElement:

<inp2:m_RenderElement prefix="test" name="inp_edit_options" field="TestOptions" />

в котором параметры is_last, onchange, has_empty, empty_value, style не передаются, однако в теле RenderElement :

<inp2:m_DefineElement name="inp_edit_options" is_last="" onchange="" has_empty="0" empty_value="" style="">
	<tr class="<inp2:m_odd_even odd="edit-form-odd" even="edit-form-even"/>">
		<inp2:m_RenderElement name="inp_edit_field_caption" prefix="$prefix" field="$field" title="$title" is_last="$is_last"/>
		<td class="control-cell">
			<select tabindex="<inp2:m_get param="tab_index"/>" name="<inp2:{$prefix}_InputName field="$field"/>" id="<inp2:{$prefix}_InputName field="$field"/>" onchange="<inp2:m_Param name="onchange"/>">
			  <inp2:m_if check="{$prefix}_FieldOption" field="$field" option="use_phrases">
			     <inp2:{$prefix}_PredefinedOptions field="$field" block="inp_option_phrase" selected="selected" has_empty="$has_empty" empty_value="$empty_value"/>
			  <inp2:m_else/>
			  <inp2:{$prefix}_PredefinedOptions field="$field" block="inp_option_item" selected="selected" has_empty="$has_empty" empty_value="$empty_value"/>
			</inp2:m_if>
			</select>
		</td>
		<inp2:m_RenderElement name="inp_edit_error" pass_params="1"/>
		<inp2:m_if check="{$prefix}_DisplayOriginal" pass_params="1">
		 	<inp2:m_RenderElement prefix="$prefix" field="$field" name="inp_original_label"/>
		</inp2:m_if>
	</tr>
</inp2:m_DefineElement>

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


Зачем может понадобиться передавать все параметры одного тэга в другой ?

Предположим имеется тэг PrintList (или любой другой тэг, вызывающий в своем теле RenderElement непосредственно в своем теле, либо на уровне PHP), который использует параметры изначально переданные в тэг PrintList. Как раз для таких случаев необходимо передать все параметры "внешнего" тэга во "внутренний". Как это сделать описывается далее в этой статье.


Как передавать все (pass_params) или только некоторые параметры от одного тэга в другой ?

Передача всех параметров из одного тега в другой.

Для передачи всех параметров из одного тега в другой, необходимо передать параметр pass_params. Рассмотрим тело тэга DefineElement для отрисовки блока inp_edit_options (см. пункт 1.). Внутри тела тэга вызываются тэги RenderElement для блока inp_edit_error и тэг if. В обоих тегах передается параметр pass_params="1", таким образом в тэги RenderElement и if передаются все параметры тэга изначально переданные в RenderElement блока inp_edit_options. На уровне PHP параметр pass_params можно передать в метод ParseBlock у объекта Application, или любой другой метод в конечном итоге вызывающий метод ParseBlock у объекта Parser.

Передача некоторых параметров из одного тега в другой.

Любой метод обработчика тэгов (или просто тэг), вызывается с обязательным параметром params. Параметр params это массив, где ключ это название параметра, а значение ключа, соответственно значение параметра. Таким образом из массива params доступны все параметры переданные тэгу. Если возникает необходимость передать часть параметров одного тэга в другой на уровне PHP, для начала необходимо получить нужные параметры из массива params, далее сформировать новый массив с нужными параметрами и вызвать желанный тэг с новым набором параметров. Под вызовом тэга, подразумевается вызов любого метода, который в свою очередь вызывает (напрямую, или через промежуточные методы) метод ParseBlock у объекта Parser.

На уровне шаблонов, параметры одного тэга в другой передаются в теле тэгов DefineElement. В теле тэга DefineElement для отрисовки блока inp_edit_options (см. пункт 1.). Вызывается тэг RenderElement для отрисовки блока inp_edit_field_caption:

<inp2:m_RenderElement name="inp_edit_field_caption" prefix="$prefix" field="$field" title="$title" is_last="$is_last"/>

В RenderElement для inp_edit_field_caption передаются параметры prefix, field, title, is_last. Значения этих параметров на момент обработки тела DefineElement будут хранится в переменных $prefix, $field, $title.


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

Как не стоит называть тег. Название тэга и название фактического метода класса.

Вызов тэга на шаблоне формеруется следующим образом: "<inp2:" + префикс + "_" + "." + special + название фактического метода класса + список параметров + "/>".

Image:Infobox Icon.gif Также следует обратить внимание на некоторые, описанные ниже, приёмы, которые использовались при написании события "<inp2:" это

пространство имен special передается по мере необходимости, символ "." разделяет префикс и special, если нету special, то и символ "."

передавать не надо. "/>" этим символом завершается вызов тэга

Из этого логично, что:

  • в название фактического метода класса не должно присутствовать символа "_", так-как этим символом разделяется префикс и сам тэг.
  • в остальном, название тэгов полностью соответствуют правилам создания названия методов в PHP.


Почему нужно написать свой тэг, а не переписать тэг Field ?

Тзга kDBTagProcessor::Field используется для получения значений полей как для конкретно отдельной "сущности", так и для текущей "сущности" списка. Механизм работы тэга абстрактно выглядит следующим образом: тег получает текущий экземпляр конкретной "сущности" и возвращает значение метода GetField у "сущности", либо метод GetDBField, в случае переданного в тэг параметра "db". Все переданные параметры в тэг, передаются в метод "сущности" GetField и далее в метод Format у соответствующего форматера. Таким образом прямо из шаблона можно получить отформатированное значения поля "сущности" в любом из доступных форматов.

Image:Infobox Icon.gif Значение параметра "db" может быть любым

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


Когда нужно писать префикс внутри объявления блока, а когда не надо.

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

<inp2:m_DefineElement name="element">
   <!-- В данном случае тэгу "Field" не надо передавать префикс и спешл -->
   <inp2:Field name="FieldName" />
   <br />
   <!-- Вызовая тэги обработчика тегов для префикса "m", без передачи префикса не обойтись  -->
   <inp2:m_Get name="MyVar" />
</inp2:m_DefineElement>
 
<inp2:myprerfix.myspecial_PrintList render_as="element" />


Метод prepareTagParams и его использование.

Для того чтобы, каждый раз не передавать в новый тэг такие параметры как: Prefix, Special, PrefixSpecial, предусмотрен метод prepareTagParams. Предположим на шаблоне имеется тэг PrintRecords, который печатает ряд записей:

<inp2:myprefix.myspecial_PrintRecords render_as="record_element"/>

На уровне PHP, тэгу для отрисовки записей, может понадобиться передать параметры Prefix, Special, PrefixSpecial, т.к. в блоке для отрисовки записей могут вызываться другие тэги использующие вышеупомянутые параметры:

/**
 * Enter description here...
 *
 * @param Array $params
 * @return string
 */
function PrintRecords($params)
{
   $output = '';
 
    if (!array_key_exists('render_as', $params)) {
      return $output;
    }
 
    $sql = 'SELECT ... FROM ... WHERE';
    $records = $this->Conn->GetCol($sql); 	
 
    $pass_params = $this->prepareTagParams($params);
    $pass_params['name'] = $params['render_as'];
 
    foreach ($records as $record) {
       $pass_params['value'] = $record;
       $output .= $this->Application->ParseBlock($pass_params);	
    }
 
    return $output;
}


Некоторые особенности использования методов PrintList/PrintList2.

Как правильно получить экземляр kDBList внутри метода класса.

Для получения объекта списка в обработчике тэгов с текущими префикс/special, используется метод GetList. После получения объекта рекомендуется сразу установить указатель текущей позиции списка на самое начало т.к. не известно какая запись в списке текущая на момент получения списка.

$list =& $this->GetList($params);
/* @var $list kDBList */
 
$list->GoFirst();

Для получения любого списка в любом классе используют метод recallObject у Application. В recallObject необходимо передать псевдо имя нужного списка вторым параметром. После, рекомендуется установить указатель текущей позиции списка на самое начало.

$list =& $this->Application->recallObject('myprefix', 'myprefix_List' , $params);
/* @var $list kDBList */
 
$list->GoFirst();


Самый первый момент инициализации объекта kDBList.

В K4 все инициализированные обьекты хранятся в фабрике классов. Обьект попадает в фабрику классов при самой первой инициализации, при всех последующих попытках инициализировать объект, фабрика классов вернет ссылку на уже ранее созданный объект. К примеру при вызове следующего PrintList, фабрика классов проверит факт наличия объекта с префиксом "myprefix" и special "myspecial", вернет ссылку нв этот объект, если объект уже ранее был инициализирован, или же создаст его и вернет ссылку, если объект запрашивается впервые.

На шаблоне со следующим содержанием:

<inp2:m_include t="header"/>
   <inp2:myprefix.myspecial_PrintList render_as="element" per_page="20"/>
<inp2:m_include t="footer"/>

тэг PrintLisе вызовет метод GetList для получения объекта kDBList. В результате в фабрике классов появится объект kDBList с префиксом "myprefix", со special "myspecial" и с атрибутом per_page="20". Тэг PrintList отоброзит до 20 записей на страницу. Далее содержание шаблона станет более интелектуальным, до 20 записей на страницу будут отображаться, только в том случае если в списке имеется хотябы одна запись:

<inp2:m_include t="header"/>
   <inp2:m_if check="myprefix.myspecial_TotalRecords" />
       <inp2:myprefix.myspecial_PrintList render_as="element" per_page="20"/>
    </inp2:m_if>
<inp2:m_include t="footer"/>

Позже, во время тестирования обнаружится, что на шаблоне вместо 20 записей на страницу, отображается только 10 записей. При отладке кода, метод GetList вернет объект kDBList, с значением атрибута per_page равным 10. При более глубоком процессе отладки, выяснится, что фабрика класов на момент обработки тэга PrintList уже содержит запрашиваемый kDBList с атрибутом per_page="10" и передача per_page="20" в тэг PrintList желаемого результата не приносит. После изучения закладки "Stack Trace" в Zend Studio (в все том же процессе отладки) ситуация прояснится: тэг TotalRecords запрашивает kDBList первее тэга PrintList, при этом в фабрике класов создается kDBList с атрибутом per_page="10" (значение по умолчанию). Именно этот kDBList и получает в итоге тэг PrintList. Очевидно, что для отображения 20 записей на страницу необходимо передать параметр per_page="20" в тэг TotalRecords:

<inp2:m_include t="header"/>
    <inp2:m_if check="myprefix.myspecial_TotalRecords" per_page="20"/>
       <inp2:myprefix.myspecial_PrintList render_as="element" />
    </inp2:m_if>	
<inp2:m_include t="footer"/>

Совсем не объязательно искать тэг, первый инициализирущий kDBList. Имеется возможность "вручную" осуществить инициализацию объекта kDBList, используя тэг InitList. Помимо очевидных параметров тегу InitList необходимо передать параметр list_name, по значению параметра list_name далее будет определятся нужный kDBList. После вызова тэга InitList, во все последующие тэги использующие необходимый kDBList, необходимо передать параметр list_name:

<inp2:m_include t="header"/>
   <inp2:myprefix.myspecial_InitList list_name="mylist" per_page="20" />
   <inp2:m_if check="myprefix.myspecial_TotalRecords" list_name="mylist"/>
      <inp2:myprefix.myspecial_PrintList render_as="element" list_name="mylist"/>
   </inp2:m_if>
<inp2:m_include t="footer"/>


Печать одного списка, внутри второго. Оба списка с одинаковым префиксом.

Предположим в систеие имеются категории товаров. Каждая категория может в себе содержать подкатегории. Для вывода списка категорий на текущем уровне в системе имеется тэг PrintCategories вызывающий метод PrintList и фильтрующий список подкатегорий по переданной категории. На шаблоне тэг PrintCategories используется для вывода полного списка категорий товаров рекурсивно.Однако в результате список подкатегорий всегда одинаковый. Чтобы подкатегории выводились правильно, необходимо внутренниму тэгу PrintCategories передать параметр requery="requery", тогда внутренний PrintCategories будет загружать список подкатегорий по переданной категории. Если параметр requery не передать, то список подкатегорий будет фильтроваться по самой первой категории.

<inp2:m_DefineElement name="element">	
     // неправильный вариант	
     <inp2:myprefix.myspecial_PrintCategories render_as="element" parent_cat_id="$parent_cat_id"/>
  </inp2:m_DefineElement>	
 
   <inp2:myprefix.myspecial_PrintCategories render_as="element" parent_cat_id="0" />
<inp2:m_DefineElement name="element">	
     // правильный вариант	
     <inp2:myprefix.myspecial_PrintCategories render_as="element" parent_cat_id="$parent_cat_id" requery="requery"/>
  </inp2:m_DefineElement>	
 
   <inp2:myprefix.myspecial_PrintCategories render_as="element" parent_cat_id="0" />


Метод ProcessParsedTag у объекта Application. Плюсы и минусы.

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

Заметки редактора