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
Компоненты Компоненты
Статьи в этой категории

Технология главных и подчинённых префиксах впервые появилась в K4 и призвана упростить создание тесно связанных между собой сущностей.

Contents

Отличия между главными и подчинёнными префиксами

Подчинённый префикс отличаться от главного тем, что он:

Возможное применение

Технология в силу своего удобства все чаще находит применение в повседневной работе. Ниже приведены несколько примеров типичного ее применения:

  • сущность "город" - главный префикс, сущность "район" - подчиненный префикс;
  • сущность "дом" - главный префикс, сущность "изображения дома" - подчиненный префикс.

Более сложный пример, в котором присутствуют несколько уровней зависимости:

  • сущность "пользователь" - главный префикс;
  • "заказ" (заказы пользователя) - подчиненный префикс;
  • "товары в заказе" - подчиненный префикс для сущности "заказ".
Image:Infobox Icon.gif Ниже приведён подробно описанный пример написания всех требуемых файлов для демонстрации того, как реализовано создание и редактирование главного и подчинённого префиксов.

Создание двух связанных таблиц

Ниже приведены запросы к базе данных, при помощи которых будут созданы таблицы, содержащие данные о городах и регионах.

CREATE TABLE int_Cities (
  CityId int(11) NOT NULL AUTO_INCREMENT,
  CityTitle varchar(128) DEFAULT NULL,
  PRIMARY KEY (CityId)
);
 
CREATE TABLE int_CityAreas (
 AreaId int(11) NOT NULL AUTO_INCREMENT,
 CityId int(11) NOT NULL,
 AreaTitle varchar(128) DEFAULT NULL,
 PRIMARY KEY (AreaId),
 KEY CityId (CityId)
);

Нужно обратить внимание, что в таблице int_CityAreas создано специальное поле CityId для связи с главной таблицей int_Cities. Также поставлен индекс на это поле, что часто забывают делать.

Настройка конфигурационных файлов

Конфигурационный файл главного префикса

  • Префикс: city.
  • Файл: city_config.php.
$config = Array (
	'Prefix' => 'city',
	'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
	'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
	'EventHandlerClass' => Array ('class' => 'CityEventHandler', 'file' => 'city_eh.php', 'build_event' => 'OnBuild'),
	'TagProcessorClass' => Array ('class' => 'CityTagProcessor', 'file' => 'city_tp.php', 'build_event' => 'OnBuild'),
	'AutoLoad' => true,
 
	'QueryString' => Array (
		1 => 'id',
		2 => 'Page',
		3 => 'event',
		4 => 'mode',
	),
 
	'IDField' => 'CityId',
	'TableName' => TABLE_PREFIX . 'Cities',
	'SubItems' => Array ('area'),
 
	'TitlePresets' => Array (
		'default' => Array (
			'new_status_labels' => Array ('city' => '!la_title_Adding_City!'),
			'edit_status_labels' => Array ('city' => '!la_title_Editing_City!'),
		),
 
		'city_edit' => Array ('prefixes' => Array ('city'), 'format' => "#city_status# '#city_titlefield#' - !la_title_General!"),
		'city_edit_areas' => Array ('prefixes' => Array ('city', 'area_List'), 'format' => "#city_status# '#city_titlefield#' - !la_title_Areas! (#area_recordcount#)"),
 
		'city_area_edit' => Array (
			'prefixes' => Array ('city', 'area'),
			'new_status_labels' => Array ('area' => '!la_title_Adding_Area!'),
			'edit_status_labels' => Array ('area' => '!la_title_Editing_Area!'),
			'new_titlefield' => Array ('area' => '!la_title_New_Area!'),
			'format' => "#city_status# '#city_titlefield#' - #area_status# '#area_titlefield#'"
		),
	),
 
	'Sections' => Array (
		'custom:city' => Array (
			'parent' => 'custom',
			'icon' => 'custom:city',
			'label' => 'la_tab_Cities',
			'url' => Array ('t' => 'custom/city/city_list', 'pass' => 'm'),
			'permissions' => Array ('view', 'add', 'edit', 'delete'),
			'priority' => 1,
			'type' => stTREE
		),
	),
);

Особенности данного конфигурационного файла:

  • Задание подчиненного префикса area строкой кода 'SubItems' => Array('area'),.
  • В ключе массива TitlePresets по мимо стандартных секций default, city_list, city_edit есть еще 2 дополнительные секции, которые описывают список районов города city_edit_areas и форму редактирования района city_area_edit.

Конфигурационный файл подчиненного префикса

  • Префикс: area.
  • Файл: area_config.php.
$config = Array (
	'Prefix' => 'area',
	'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
	'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
	'EventHandlerClass' => Array ('class' => 'AreaEventHandler', 'file' => 'area_eh.php', 'build_event' => 'OnBuild'),
	'TagProcessorClass' => Array ('class' => 'AreaTagProcessor', 'file' => 'area_tp.php', 'build_event' => 'OnBuild'),
	'AutoLoad' => true,
 
	'QueryString' => Array (
		1 => 'id',
		2 => 'Page',
		3 => 'event'
	),
	'IDField' => 'AreaId',
	'TableName' => TABLE_PREFIX . 'CityAreas',
 
	'ParentPrefix' => 'city',
	'ForeignKey'  => 'CityId',
	'ParentTableKey' => 'CityId',
	'AutoDelete' => true,
	'AutoClone' => true,
 
	'Fields' =>	Array (
		'AreaId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
		'CityId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
		'AreaTitle' => Array (
			'type' => 'string',
			'required' => 1, 'not_null' => 1, 'default' => ''
		),
	),
);

Особенности данного конфигурационного файла:

В разделе Fields описано поле CityId для связи с главным префиксом.

'CityId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),

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

'ParentPrefix' => 'city',
'ForeignKey' => 'CityId',
'ParentTableKey' => 'CityId',
'AutoDelete' => true,
'AutoClone' => true,
параметр описание
ParentPrefix (string) Название главного префикса.
ForeignKey (string) Название cвязующей колонки в таблице от починённого префикса, т.е. inp_CityAreas.
ParentTableKey (string) Название cвязующей колонки в таблице от главного префикса, т.е. inp_Cities.
AutoDelete (boolean) Указывает на то, что делать с подчинёнными записями при удалении главной записи (тоже удалять или оставлять).
AutoClone (boolean) Указывает на то, что делать с подчинёнными записями при клонировании главной записи (тоже клонировать или нет).
  • Разделы TitlePresets и Sections не используются для подчинённых префиксов, т.к. они заданы у главного префикса.

Создание шаблонов главного префикса

Шаблон списка главного префикса

  • Префикс: city.
  • Файл: city_list.tpl.
<inp2:m_include t="incs/header"/>
<inp2:m_RenderElement name="combined_header" section="custom:city" prefix="city" pagination="1"/>
 
<!-- ToolBar -->
<table class="toolbar" height="30" cellspacing="0" cellpadding="0" width="100%" border="0">
	<tr>
		<td>
			<table width="100%" cellpadding="0" cellspacing="0">
				<tr>
					<td>
						<script type="text/javascript">
							a_toolbar = new ToolBar();
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'new_item',
									'<inp2:m_phrase label="la_ToolTip_NewCity" escape="1"/>::<inp2:m_phrase label="la_Add" escape="1"/>',
									function() {
										std_precreate_item('city', 'custom/city/city_edit');
									}
								)
							);
 
							function edit()
							{
							  	std_edit_item('city', 'custom/city/city_edit');
							}
 
							a_toolbar.AddButton( 
								new ToolBarButton(
									'edit', 
									'<inp2:m_phrase label="la_ToolTip_Edit" escape="1"/>::<inp2:m_phrase label="la_ShortToolTip_Edit" escape="1"/>',
									edit
								)
							);
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'delete',
									'<inp2:m_phrase label="la_ToolTip_Delete" escape="1"/>',
									function() {
										std_delete_items('city');
									}
								)
							);
 
							a_toolbar.AddButton( new ToolBarSeparator('sep1') );
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'view',
									'<inp2:m_phrase label="la_ToolTip_View" escape="1"/>',
									function(id) {
										show_viewmenu(a_toolbar,'view');
									}
								)
							);
 
							a_toolbar.Render();
						</script>
					</td>
 
					<inp2:m_RenderElement name="search_main_toolbar" prefix="city" grid="Default"/>
				</tr>
			</table>
		</td>
	</tr>
</table>
 
<inp2:m_RenderElement name="grid" PrefixSpecial="city" IdField="CityId" grid="Default" grid_filters="1"/>
 
<script type="text/javascript">
	Grids['city'].SetDependantToolbarButtons( new Array('edit', 'delete') );
</script>
 
<inp2:m_include t="incs/footer"/>

Шаблон списка стандартный и не содержит каких-либо особенностей.

Шаблон редактирования главного префикса

  • Префикс: city.
  • Файл: city_edit.tpl.
<inp2:adm_SetPopupSize width="570" height="540"/>
<inp2:m_include t="incs/header"/>
 
<inp2:m_RenderElement name="combined_header" section="custom:city" prefix="city" title_preset="city_edit" tab_preset="Default"/>
 
<!-- ToolBar -->
<table class="toolbar" height="30" cellspacing="0" cellpadding="0" width="100%" border="0">
	<tr>
		<td>
			<script type="text/javascript">
				a_toolbar = new ToolBar();
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'select',
						'<inp2:m_phrase label="la_ToolTip_Save" escape="1"/>',
						function() {
							submit_event('city','<inp2:city_SaveEvent/>');
						}
					)
				);
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'cancel',
						'<inp2:m_phrase label="la_ToolTip_Cancel" escape="1"/>',
						function() {
							cancel_edit('city','OnCancelEdit','<inp2:city_SaveEvent/>','<inp2:m_Phrase label="la_FormCancelConfirmation" escape="1"/>');
						}
					)
				);
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'reset_edit',
						'<inp2:m_phrase label="la_ToolTip_Reset" escape="1"/>',
						function() {
							reset_form('city', 'OnReset', '<inp2:m_Phrase label="la_FormResetConfirmation" escape="1"/>');
						}
					)
				);
 
				a_toolbar.AddButton( new ToolBarSeparator('sep1') );
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'prev',
						'<inp2:m_phrase label="la_ToolTip_Prev" escape="1"/>',
						function() {
							go_to_id('city', '<inp2:city_PrevId/>');
						}
					)
				);
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'next',
						'<inp2:m_phrase label="la_ToolTip_Next" escape="1"/>',
						function() {
							go_to_id('city', '<inp2:city_NextId/>');
						}
					)
				);
 
				a_toolbar.Render();
 
				<inp2:m_if check="city_IsSingle">
					a_toolbar.HideButton('prev');
					a_toolbar.HideButton('next');
					a_toolbar.HideButton('sep1');
				<inp2:m_else/>
					<inp2:m_if check="city_IsLast">
						a_toolbar.DisableButton('next');
					</inp2:m_if>
					<inp2:m_if check="city_IsFirst">
						a_toolbar.DisableButton('prev');
					</inp2:m_if>
				</inp2:m_if>
			</script>
		</td>
 
		<inp2:m_RenderElement name="ml_selector" prefix="city"/>
	</tr>
</table>
 
<inp2:city_SaveWarning name="grid_save_warning"/>
<inp2:city_ErrorWarning name="form_error_warning"/>
<div id="scroll_container">
	<table class="edit-form">
		<inp2:m_RenderElement name="inp_id_label" prefix="city" field="CityId" title="la_fld_Id"/>
		<inp2:m_RenderElement name="inp_edit_box" prefix="city" field="CityTitle" title="la_fld_Title" />
	</table>
</div>
 
<inp2:m_include t="incs/footer"/>

Редактирование раздела "EditTabPresets"

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

Для реализации этого нужно:

  • В файле city_config.php описать секцию EditTabPresets:
'EditTabPresets' => Array (
	'Default' => Array (
		Array ('title' => 'la_tab_General', 't' => 'custom/city/city_edit', 'priority' => 1),
		Array ('title' => 'la_tab_Areas', 't' => 'custom/city/city_edit_areas', 'priority' => 2),
	),
),

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

  • На шаблонах city_edit.tpl, city_edit_areas.tpl нужно передать дополнительный параметр tab_preset при использовании блока combined_header вверху шаблона. Его значение нужно установить равным "Default" (или то, что было ранее определено). Например использование блока combined_header в шаблоне city_edit.tpl будет выглядеть следующим образом:
<inp2:m_RenderElement name="combined_header" prefix="city" section="custom:city" title_preset="city_edit" tab_preset="Default"/>

Создание шаблонов подчинённого префикса

Шаблон списка подчинённого префикса

  • Префикс: area.
  • Файл: city_edit_areas.tpl.
<inp2:adm_SetPopupSize width="570" height="540"/>
<inp2:m_include t="incs/header"/>
<inp2:m_RenderElement name="combined_header" section="custom:city" prefix="city" title_preset="city_edit_areas" tab_preset="Default" pagination_prefix="area" pagination="1"/>
 
<!-- ToolBar -->
<table class="toolbar" height="30" cellspacing="0" cellpadding="0" width="100%" border="0">
	<tr>
		<td>
			<table width="100%" cellpadding="0" cellspacing="0">
				<tr>
					<td>
						<script type="text/javascript">
							a_toolbar = new ToolBar();
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'select',
									'<inp2:m_phrase label="la_ToolTip_Save" escape="1"/>',
									function() {
										submit_event('city', '<inp2:city_SaveEvent/>');
									}
								)
							);
 
							a_toolbar.AddButton( 
								new ToolBarButton(
									'cancel', 
									'<inp2:m_phrase label="la_ToolTip_Cancel" escape="1"/>', 
									function() {
										cancel_edit('city','OnCancelEdit','<inp2:city_SaveEvent/>','<inp2:m_Phrase label="la_FormCancelConfirmation" escape="1"/>');
									}
								)
							);
 
							a_toolbar.AddButton( new ToolBarSeparator('sep1') );
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'prev',
									'<inp2:m_phrase label="la_ToolTip_Prev" escape="1"/>',
									function() {
										go_to_id('city', '<inp2:city_PrevId/>');
									}
								)
							);
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'next',
									'<inp2:m_phrase label="la_ToolTip_Next" escape="1"/>',
									function() {
										go_to_id('city', '<inp2:city_NextId/>');
									}
								)
							);
 
							a_toolbar.AddButton( new ToolBarSeparator('sep2') );
 
							<!-- Start Area Buttons -->
 
							function edit()
							{
								std_edit_temp_item('area', 'custom/city/city_area_edit');
							} 
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'new_item',
									'<inp2:m_phrase label="la_ToolTip_New_Area" escape="1"/>',
									function() {
										std_new_item('area', 'custom/city/city_area_edit')
									}
								)
							);
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'edit',
									'<inp2:m_phrase label="la_ToolTip_Edit" escape="1"/>',
									edit
								)
							);
 
							a_toolbar.AddButton(
								new ToolBarButton(
									'delete',
									'<inp2:m_phrase label="la_ToolTip_Delete" escape="1"/>',
									function() {
										std_delete_items('area')
									}
								)
							);
 
							a_toolbar.AddButton( 
								new ToolBarButton(
									'view', 
									'<inp2:m_phrase label="la_ToolTip_View" escape="1"/>',
									function(id) {
										show_viewmenu(a_toolbar, 'view');
									}
								)
							);	
 
							<!-- End Area Buttons -->
 
							a_toolbar.Render();
 
							<inp2:m_if check="city_IsSingle">
								a_toolbar.HideButton('prev');
								a_toolbar.HideButton('next');
								a_toolbar.HideButton('sep1');
							<inp2:m_else/>
								<inp2:m_if check="city_IsLast">
									a_toolbar.DisableButton('next');
								</inp2:m_if>
								<inp2:m_if check="city_IsFirst">
									a_toolbar.DisableButton('prev');
								</inp2:m_if>
							</inp2:m_if>
						</script>
					</td>
				</tr>
			</table>
		</td>
	</tr>
</table>
 
<inp2:m_RenderElement name="grid" PrefixSpecial="area" IdField="AreaId" grid="Default"/>
<script type="text/javascript">
Grids['area'].SetDependantToolbarButtons( new Array('edit','delete') );
</script>
 
<inp2:m_include t="incs/footer"/>

Данный шаблон создается на основе шаблона для редактирования главного префикса city (есть все кнопки формы редактирования) и стандартного списка для подчиненного префикса area с кнопками для вызова формы создания, формы редактирования и удаления районов.

Особенности данного файла:

  • При вызове блока combined_header нужно передать параметры title_preset="city_edit_areas" и tab_preset="Default":
<inp2:m_RenderElement name="combined_header" section="custom:city" prefix="city" title_preset="city_edit_areas" tab_preset="Default" />

Шаблон редактирования подчинённого префикса

  • Префикс: area.
  • Файл: city_area_edit.tpl.
<inp2:adm_SetPopupSize width="750" height="570"/>
<inp2:m_include t="incs/header" body_properties="" />
<inp2:m_RenderElement name="combined_header" prefix="city" section="custom:city" title_preset="city_area_edit"/>
 
<!-- ToolBar -->
<table class="toolbar" height="30" cellspacing="0" cellpadding="0" width="100%" border="0">
	<tr>
		<td>
			<script type="text/javascript">
				a_toolbar = new ToolBar();
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'select',
						'<inp2:m_phrase label="la_ToolTip_Save" escape="1"/>',
						function() {
							submit_event('area', '<inp2:area_SaveEvent/>');
						}
					)
				);
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'cancel',
						'<inp2:m_phrase label="la_ToolTip_Cancel" escape="1"/>',
						function() {
							cancel_edit('area','OnCancel','<inp2:area_SaveEvent/>','<inp2:m_Phrase label="la_FormCancelConfirmation" escape="1"/>');
						}
					)
				);
 
				a_toolbar.AddButton(
					new ToolBarButton(
						'reset_edit',
						'<inp2:m_phrase label="la_ToolTip_Reset" escape="1"/>',
						function() {
							reset_form('area', 'OnReset', '<inp2:m_Phrase label="la_FormResetConfirmation" escape="1"/>');
						}
					)
				);
 
				a_toolbar.Render();
			</script>
		</td>
	</tr>
</table>
 
<inp2:area_SaveWarning name="grid_save_warning"/>
<inp2:area_ErrorWarning name="form_error_warning"/>
 
<div id="scroll_container">
	<table class="edit-form">
		<inp2:m_RenderElement name="inp_edit_hidden" prefix="area" field="CityId"/>
		<inp2:m_RenderElement name="inp_id_label" prefix="area" field="AreaId" title="la_fld_Id"/>
		<inp2:m_RenderElement name="inp_edit_box" prefix="area" field="Title" title="la_fld_Title" />
	</table>
</div>
 
<inp2:m_include t="incs/footer"/>

Особенности данного файла:

  • При вызове блока combined_header нужно передать параметр title_preset="city_area_edit".
<inp2:m_RenderElement name="combined_header" prefix="city" section="custom:city" title_preset="city_area_edit"/>
  • Через скрытое поле формы объявлено поле CityId, в которое записываеться Id текущего города.
<inp2:m_RenderElement name="inp_edit_hidden" prefix="area" field="CityId"/>

Вкладки на форме редактирования подчинённого префикса

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

Обработчик событий главного префикса

  • Префикс: city.
  • Файл: city_eh.php.

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

/**
 * Saves edited item in temp table and goes
 * to passed tabs, by redirecting to it with OnPreSave event
 *
 * @param kEvent $event
 */
function OnPreSaveAndGoToTab(&$event)
{
	$event->CallSubEvent('OnPreSave');
	if ($event->status == erSUCCESS) {
		$area_id = $this->Application->GetVar('area_id');
		if (is_numeric($area_id)) {
			$event->SetRedirectParam('area_id', $area_id);
		}
 
		$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
	}
}

Конфигурационный файл главного префикса (2)

  • Префикс: city.
  • Файл: city_config.php.

Набор вкладок (ключ EditTabPresets), который будет использоваться на форме редактирования подчинённого префикса нужно будет определить в unit config главного префикса (т.е. также как и ключ TitlePresets).

'EditTabPresets' => Array (
	'AreaEdit' => Array (
		Array ('title' => 'la_tab_General', 't' => 'custom/city/area_edit', 'priority' => 1),
		Array ('title' => 'la_tab_Additional', 't' => 'custom/city/area_edit_additional', 'priority' => 2),
	),
);

Конфигурационный файл подчиненного префикса (2)

  • Префикс: area.
  • Файл: area_config.php.

В конфигурационном файле подчинённого префикса нужно добавить hook, который при наличии данных подчинённого префикса в момент выполнения события OnPreSave у главного префикса будет их также сохранять.

'Hooks' => Array (
	Array (
		'Mode' => hAFTER,
		'Conditional' => true,
		'HookToPrefix' => '#PARENT#',
		'HookToSpecial' => '*',
		'HookToEvent' => Array ('OnPreSave'),
		'DoPrefix' => '',
		'DoSpecial' => '*',
		'DoEvent' => 'OnPreSaveSubItem',
	),
),
Image:Tipbox Icon.gif Событие OnPreSaveSubItem доступно только начиная с Core v 4.3.2.