4.4.1. AjaxDropdownPreloader

Data Source

Часто на форме редактирования возникает необходимость вывести два выпадающих меню (далее ВМ), где набор опций второго ВМ напрямую зависит от выбранной в первом ВМ опции. Причем требуется динамическое обновление опций зависимого ВМ без отправки (submit) формы на сервер. Вышеописанную функциональность можно самостоятельно реализовать стандартными средствами HTML и Javascript, однако рекомендуется использовать уже написанный JavaScript класс AjaxDropdownPreloader, который её реализует.

При изменении значения в главном ВМ, отсылается AJAX запрос на сервер. Сервер выполняет необходимые расчёты и получает новый набор опций для подчиненного ВМ. Полученный набор опций возвращается в виде XML документа на страницу, которая послала AJAX запрос. Далее происходит обработка полученного XML документа и замена опций подчиненного ВМ средствами JavaScript на стороне клиента.

Параметры инициализации

Для успешной реализации связи между двумя ВМ необходимо создать экземпляр класса AjaxDropdownPreloader, передав ему параметры, описанные в приведённой ниже таблице.

название

описание

$url (string)

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

<field_options>
    <option>ID1</option>
    <option>ID2</option>
</field_options>

Стоит обратить особое внимание на то, что XML документ содержит только ID опций (без текста, который будет виден в зависимом ВМ). Для того, чтобы у опций был и текст нужно изначально заполнить зависимый ВМ всеми возможными опциями. Обычно установка kOptionsFormatter форматера на это поле является вполне достаточным.

$input_mask (string)

Маска для получения любого элемента ввода на форме. Обычно маска получается путём вызова тэга InputName со значением #FIELD# в качестве названия поля объекта:

<inp2:prefix_InputName field="#FIELD#"/>
// вернёт строку вида: prefix[ID][#FIELD#]
$filter_field (string)

Название поля главного ВМ.

$dependend_field (string)

Название поля подчиненного ВМ.

value (int)

Данный параметр позволяет указать на то, какое значение должно быть выбрано в зависимом ВМ после обновления его набора опций. Если его не передать, то автоматически будет выбрано значение, которое было выбрано до получения нового набора опций (только если оно в нём также присутствует).

Настройка шаблона

Для примера рассматривается стандартная форма редактирования с двумя ВМ:

  • MainField - главное ВМ;

  • DependentField - зависимое и ВМ.

При изменении выбранного значения в поле MainField срабатывает событие onchange и через него подгружаются соответствующие опции в поле DependentField. После выполнения приведённых ниже шагов настройку шаблона можно считать завершенной.

  • Добавить элементы ВМ:

<inp2:m_RenderElement name="inp_edit_options" prefix="sample-prefix" field="MainField" title="la_fld_MainField"/>
<inp2:m_RenderElement name="inp_edit_options" prefix="sample-prefix" field="DependentField" title="la_fld_DependentField"/>
  • Создать экземпляр класса AjaxDropdownPreloader:

var DependentFieldPreloader = new AjaxDropdownPreloader(
    '<inp2:m_Link template="dummy" sample-prefix_event="OnQueryDependentXML" pass="m,sample-prefix" filter_value="#FILTER_VALUE#" no_amp="1"/>',
    '<inp2:sample-prefix_InputName field="#FIELD#"/>', 'MainField', 'DependentField'
);
  • Назначить событие onchange для главного ВМ:

addEvent(DependentFieldPreloader.getControl('MainField'), 'change', function() {DependentFieldPreloader.Query()});
  • Отфильтровать значения для зависимого ВМ сразу после загрузки страницы.

Application.setHook('m:OnAfterWindowLoad', function () { DependentFieldPreloader.Query(); });

Весь приведённый выше JavaScript код нужно писать после того, как на форме будут отображены элементы, с которыми он работает. Самое оптимальное для этого место перед подключением шаблона incs/footer. Это наглядно будет показано на ниже приведённом примере.

<script type="text/javascript">
    // javascript code here
</script>

<inp2:m_include t="incs/footer"/>

В данном случае переданный шаблон dummy использоваться не будет (его даже может не существовать), а вся подготовка XML документа будет происходить в событии OnQueryDependentXML.

Настройка обработчика событий

В обработчик событий от префикса sample-prefix необходимо добавить событие OnQueryDependentXML (указанное на шаблоне редактирования), которое в результате своей работы будет возвращать в поток вывода (output stream) XML документ. Возвращаемый XML документ будет в последствии обрабатывается классом AjaxDropdownPreloader и зависимое ВМ будет заполняется опциями на стороне клиента. После выполнения всех ниже приведённых шагов можно считать настройку обработчика событий завершённой.

Добавить тело события OnQueryDependentXML в обработчик событий от префикса sample-prefix:

/**
 * [AJAX] Метод для получения отфильтрованных опций в виде XML документа.
 *
 * @param kEvent $event
 */
function OnQueryDependentXML(&$event)
{
    $event->status = erSTOP;

    $filter_value = $this->Application->GetVar('filter_value');
    if (!$filter_value || ($this->Application->GetVar('ajax') != 'yes')) {
        return ;
    }

    $sql = 'SELECT DependentTable.FieldId
        FROM DependentTable
        WHERE DependentTable.MainId = ' . $filter_value;
    $dependent_ids = $this->Conn->GetCol($sql);

    $xml = '';
    foreach ($dependent_ids as $id) {
        $xml .= '<option>' . $id . '</option>';
    }
    $xml = '<field_options>' . $xml . '</field_options>';

    $this->Application->XMLHeader();
    echo $xml;
}

Событие OnQueryDependentXML необходимо добавить в метод mapPermissions, который обеспечит проверку наличия у пользователя необходимых прав доступа для вызова данного события:

/**
 * Метод связывающий события и права, необходимые для их выполнения.
 *
 */
function mapPermissions()
{
    parent::mapPermissions();

    $permissions = Array (
        'OnQueryDependentXML' => Array ('self' => 'view'),
    );

    $this->permMapping = array_merge($this->permMapping, $permissions);
}

Совет

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

  • В начале события рекомендуется установить статус его выполнения в erSTOP. Это укажет на то, что по окончания выполнения события не нужно показывать содержание переданного шаблона (в данном случае это dummy):

$event->status = erSTOP;
  • В начале события написать код, который позволит игнорировать запросы, которые будут делать поисковые системы:

if ($this->Application->GetVar('ajax') != 'yes') {
    return ;
}

Параметр ajax добавляется автоматически при отправлении каждого AJAX запроса. Если поисковая система где-то найдёт ссылку, в которой указано данное событие, то зайдя на неё тело события выполнено не будет.

  • Перед выводом XML документа на экран необходимо послать браузеру соответствующий заголовок. Сделать это можно при помощи метода Application::XMLHeader:

$this->Application->XMLHeader();

Примечание

Конечно такой заголовок слать не нужно, если не планируется возвращать XML документ.

Использование метода AfterProcess

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

См. также