Master / detail

Материал из Eludia
Перейти к: навигация, поиск

Содержание

Когда [форма редактирования|draw_form] содержит десяток-другой select'ов, довольно часто оказывается так, что при выборе значения в одном из них должны перерисовываться один или несколько остальных.

Пример

Сразу опишем условия модельной задачи, в рамках которой пойдёт дальнейшее описание.

Допустим, в нашей БД имеется таблица рабочих групп groups и (как всегда) таблица пользователей системы users со ссылкой id_group на groups.

Мы верстаем форму со списками выбора id_group и id_user:

{
 name => 'id_group',
 type => 'select',
 values => $data -> {groups},
 empty => '[Выберите группу]',
},
{
 name => 'id_user',
 type => 'select',
 values => $data -> {users},
 empty => '[Выберите пользователя]',
},

данные для справочников извлекается так:

add_vocabularies ($data,
 groups => {},
 users  => {},
)

Наша цель -- сделать так, чтобы во втором select'е показывались только те пользователи, которые относятся к выбранной группе.

Шаг 1. Устанавливаем зависимость

Прежде всего, дадим системе знать, что id_user как-то зависит от id_group. Как именно -- уточним позже. А пока просто заменим первое определение на

{
 name => 'id_group',
 type => 'select',
 values => $data -> {groups},
 empty  => '[Выберите группу]',
 detail => 'id_user', # <-- вот она где прописана
},

Смотрим, что получилось. На первый взгляд, всё работает по-прежнему. Однако, если присмотреться внимательно, cтанет заметно, что при выборе строки в первом списке второй немного подмигивает и передёргивается.

Что происходит?

Включив любой HTTP-сниффер, нетрудно заметить, что теперь при любом изменении id_group на сервер отправляется запрос со специальными параметрами, которые не встречаются при обычной работе приложения. Вот они:

  • __only_field -- имя единственного поля, которое будет перерисовываться;
  • __only_form -- имя формы, которая его содержит.

В ответ сервер присылает HTML-страницу, всё содержимое которой сводится к onload-скрипту. А скрипт этот перерисовывает заказанное поле, подменяя его innerHTML на новый.

Откуда берётся HTML для обновлённого поля? Ровно оттуда же, откуда для целой страницы (его формирует [$_SKIN] по результатам работы get_item_of...), только из целого экрана вырезается единственное поле и обрамляется в скрипт.

Как сервер понимает, что ему нужно выдать не полную страницу, а обновляющий скрипт? По наличию параметров __only_form/__only_field.

Если вы когда-либо имели дело с JSF2, то тамошнее частичное обновление через AJAX -- прямой аналог того, о чём написано в этой статье.

Шаг 2. Корректируем выборку данных

Всё это интересно, но прогресс пока невелик: в $data -> {users} по-прежнему полный список пользователей, а не состав выбранной группы. Ну так отфильтруем же его. Что нам нужно? id_group? Посмотрим, что там приходит вместе с параметрами __only_form/__only_field... Так и есть: _id_group, как и в do_update, например. Значит, так и пишем:

[add_vocabularies] ($data,
 groups => {},
 users  => {filter => "id_group=$_REQUEST{_id_group}"},
)

Стоп! Легко сказать: "id_group=$_REQUEST{_id_group}". Но ведь $_REQUEST {_id_group} может быть пустым. Тогда получится битый SQL.

Шаг 3. Учитываем пустые значения

Давайте спокойно разберёмся, какие вообще возможны варианты get_item_of:

  1. вызов для отрисовки полного экрана:
    1. существующей записи:
    2. новой записи
      1. в режиме __read_only
      2. в режиме __edit
  2. вызов ради перерисовки id_user;

По поводу этого обилия возможностей можно написать соответствующий развесистый if, но можно сделать проще и красивее: прогарантировать корректное значение $_REQUEST{_id_group} во всех случаях.

Явное значение параметра фильтра

Допустим, то или иное значение $_REQUEST {_id_group} пришло в запросе. Этот случай обладает наивысшим приоритетом: клиент знает, какой id_group он хочет. В частности, он может хотеть id_group=0 -- это клик по пустой строке в списке групп. Заказывая такое, он должен получить пустой список users: ведь у вас нет users с id_group=0, (не IS NULL, а =0) не правда ли? А может быть, есть -- тогда их должны найти и найдут. Так или иначе, если $_REQUEST {_id_group} задан (не истинен -- а именно задан! Поскольку значение '0' необходимо уважать), то необходимо использовать его. Итак, начало нашей строки кода будет иметь вид:

defined $_REQUEST {_id_group} or $_REQUEST {_id_group} = ...

или, если вы уверены, что ваш код будет исполняться на Perl не ранее 5.10, то

$_REQUEST {_id_group} //= ...

Значение, сохранённое в БД

А если $_REQUEST {_id_group} всё-таки не определён? Тогда имеет смысл использовать значение из записи БД. Если её извлекли, как всегда, в переменную $data, то

defined $_REQUEST {_id_group} or $_REQUEST {_id_group} = $data -> {id_group}
$_REQUEST {_id_group} //= $data -> {id_group}

Это сгодится и для __read_only-режима, и для первого показа в __edit-режиме, пока форму ещё не трогали. Однако...

Значение по умолчанию

...запись может быть совсем новой, с пустым полем id_group. На этот случай надо использовать такое значение, чтобы оно было корректно, но никаких дочерних записей бы не нашлось:

defined $_REQUEST {_id_group} or $_REQUEST {_id_group} = $data -> {id_group} || -1;
$_REQUEST {_id_group} //= $data -> {id_group} || -1;

Вот теперь -- всё. Форма с master / detail-списками будет корректно работать во всех режимах.

Résumé

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

1. Уточнение $_REQUEST {_id_group} в get_item_of:

defined $_REQUEST {_id_group} or $_REQUEST {_id_group} = $data -> {id_group} || -1;

2. Фильтрация справочника users с учётом уточнённого $_REQUEST {_id_group} в get_item_of:

users  => {filter => "id_group=$_REQUEST{_id_group}"},

3. Объявление связи справочников в draw_item_of:

detail => 'id_user',

Дополнительные сведения

Множественное влияние

Изменение одного списка выбора может требовать перерисовки не одного, а нескольких подчинённых списков. В этом случае всё обстоит ровно так же, как описано выше, только опция detail имеет векторное значение:

detail => ['id_user', 'id_project', ...],

Косвенная и множественная зависимость

Список, упомянутый как detail, сам может иметь опцию detail. Цепочки detail-зависимостей могут быть столь длинными, сколько у вас полей на форме. Кроме того, один список может упоминаться как detail сразу у нескольких.

Но тут возникает одна небольшая проблема. Дело в том, что в вышеописанном рецепте при перерисовке подчинённого списка на сервер приходит значение только того параметра, который связан с непосредственно "кликнутым" списком. Если id_user зависит не только от id_group, но ещё и от id_role, то при выборе группы параметр $_REQUEST {_id_group} будет опредеён, а вот $_REQUEST {_id_role} -- нет. Хотя переключатель роли могли затронуть прошлым кликом и его значение отличается от $data -> {id_role}.

Но это не беда: достаточно указать зависимости опцией master:

master => ['id_group', 'id_role'],
Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
Разработчику
Администратору
Инструменты