3.5. Работа с подчинёнными префиксами
Технология главных и подчинённых префиксах впервые появилась в K4 и призвана упростить создание тесно связанных между собой сущностей.
3.5.1. Отличия между главными и подчинёнными префиксами
Подчинённый префикс отличаться от главного тем, что он:
Не может существовать отдельно без главного.
У него упрощена структура конфигурационного файла (многое объявлено в главном конфигурационном файле).
Нет секции в дереве слева.
Записи подчиненного префикса всегда визуально привязаны к редактированию какой-то записи главного префикса.
При создании/изменении записи в административной консоли вызываются события OnCreate и OnUpdate вместо события OnPreSave (как у главного).
3.5.2. Возможное применение
Технология в силу своего удобства все чаще находит применение в повседневной работе. Ниже приведены несколько примеров типичного ее применения:
сущность «город» - главный префикс, сущность «район» - подчиненный префикс;
сущность «дом» - главный префикс, сущность «изображения дома» - подчиненный префикс.
Более сложный пример, в котором присутствуют несколько уровней зависимости:
сущность «пользователь» - главный префикс;
«заказ» (заказы пользователя) - подчиненный префикс;
«товары в заказе» - подчиненный префикс для сущности «заказ».
Примечание
Ниже приведён подробно описанный пример написания всех требуемых файлов для демонстрации того, как реализовано создание и редактирование главного и подчинённого префиксов.
3.5.3. Создание двух связанных таблиц
Ниже приведены запросы к базе данных, при помощи которых будут созданы таблицы, содержащие данные о городах и регионах.
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. Также поставлен индекс на это поле, что часто забывают делать.
3.5.4. Настройка конфигурационных файлов
Конфигурационный файл главного префикса
Префикс:
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вязующей колонки в таблице от починённого префикса, т.е.
|
| ParentTableKey (string) | Название cвязующей колонки в таблице от главного префикса, т.е.
|
| AutoDelete (boolean) | Указывает на то, что делать с подчинёнными записями при удалении главной записи (тоже удалять или оставлять). |
| AutoClone (boolean) | Указывает на то, что делать с подчинёнными записями при клонировании главной записи (тоже клонировать или нет). |
Разделы TitlePresets и Sections не используются для подчинённых префиксов, т.к. они заданы у главного префикса.
3.5.5. Создание шаблонов главного префикса
Шаблон списка главного префикса
Префикс:
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"/>
3.5.6. Редактирование раздела «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"/>
3.5.7. Создание шаблонов подчинённого префикса
Шаблон списка подчинённого префикса
Префикс:
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"/>
3.5.8. Вкладки на форме редактирования подчинённого префикса
В случае, когда на форме редактирования починённого префикса требуется использование вкладок необходимо проделать следующие дополнительные действия.
Обработчик событий главного префикса
Префикс:
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',
),
),