Сохранение формы в Drupal с помощью AJAX

Сегодня речь пойдет о том, как запрограммировать сохранение формы в Друпале через AJAX. В принципе, для этих целей уже существует модуль Ajax. Но вдруг у нас крайне хитрые потребности, или мы просто не хотим ради сабмита пары форм тащить за собой целый модуль?

В общем, задача такова: создать средствами Друпала простую форму, вывести ее на страницу, а затем — сохранять через AJAX-запросы, выводя на ту же страницу результаты сохранения и ошибки валидации формы, если таковые были. Без обновления страницы, естественно. Для решения задачи будем использовать Form API Друпала и jQuery с плагином Form. Оформим все в виде отдельного модуля.

Рассмотрим решение по порядку. Начнем с создания страницы с формой.

function jf_test_menu() {
    $items = array();
    //страница для теста формы
    $items['jf_test'] = array(
        'title' => 'Тест jQuery Form',
        'page callback' => '_generate_page',
                'access arguments' => array(TRUE),
                'type' => MENU_NORMAL_ITEM,
    );
    return $items;
} //function jf_test_menu

function _generate_page() {
    $output = "";
    //выведем текст
    $output .= '';
    $output .= '<strong>Сообщения:</strong>';
    $messages = variable_get('jft_messages',array());
    foreach ($messages as $index => $message) {
        $output .= '' .$message .'';
    }
    $output .= '';
    //выведем форму
    $output .= drupal_get_form('_jft_form');
    return $output;
} //function generate_page

function _jft_form(&$form_state) {
    $form = array();
    $form['message'] = array(
        '#type' => 'textarea',
        '#title' => 'Введите текст',
        '#required' => TRUE,
    );
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => 'Отправить',
    );
    return $form;
} //function _jft_form

function _jft_form_submit($form,&$form_state) {
    //добавляем сообщение в массив
    $messages = variable_get('jft_messages',array());
    $messages[] = $form_state['values']['message'];
    variable_set('jft_messages',$messages);
} //function _jft_form_submit

Теперь если пользователь зайдет по адресу /jf_test, напишет что-нибудь в форме и нажмет «Отправить», набранный текст будет накапливаться в блоке под надписью «Сообщения». Пока отправка текста происходит обычным сохранением формы с перезагрузкой страницы. Сделаем, чтобы сообщения отправлялись с помощью AJAX.

Прежде всего, нужно добавить скрипт с плагином jQuery Form. Сама библиотека jQuery включена в Друпал и добавится автоматически. Плагин Form тоже включен в состав Друпала, но версия очень старая, а мы скачаем новую и подключим ее при генерации страницы. Заодно сразу добавим js-файл со скриптом, которым будем сохранять форму.

function _generate_page() {
    $output = "";
    //добавим скрипт для сабмита формы и плагин jQuery Form
    drupal_add_js(drupal_get_path('module','jf_test').'/jquery.form.js');
    drupal_add_js(drupal_get_path('module','jf_test').'/jf_test.js');
    //...

Теперь напишем сам скрипт jf_test.js:

Drupal.behaviors.jf_testBehavior = function (context) {
    $("#-jft-form").ajaxForm(successCallback);
    //устанавливаем обработчик сабмита формы
    $("#-jft-form").submit(function () {
        //возвращаем false чтобы предотвратить обычный сабмит
        return false;
    });
}

//callback вызывается после успешного сабмита формы
function successCallback(responseText,statusText) {
    $("div.test_messages").append(responseText);
}

Drupal.behaviors заменяет $(document).ready() для нашего модуля. Подробнее об этом можно почитать в описании Javascript, AJAX, AHAH API. А пока посмотрим на новоиспеченный скрипт. Вызов функции ajaxForm() подготавливает нашу форму к сохранению через AJAX. Аргументом функции является название callback-а, который будет вызван в случае успешного сохранения формы (successCallback). Функция ajaxForm() сама вытащит из формы адрес скрипта для сохранения, установленный Друпалом, так что мы можем об этом не заботиться, и сама же настроит все остальное для сохранения через AJAX при нажатии на кнопку.

Затем мы устанавливаем для события submit, то есть сохранения формы, свой обработчик. В нем возвращается false — для того чтобы избежать обычного сохранения с обновлением страницы. Ну а в функции successCallback мы добавляем в блок «Сообщения» тот текст, который получили от сервера.

Однако, если мы попытаемся выполнить все, что написали, то увидим, что в блок «Сообщения» добавилось не последнее введенное сообщение, а целая страница, сгенерированная Друпалом заново. Поэтому теперь нам нужно позаботиться об адекватных ответах на AJAX-запросы при сохранении формы. То есть, нужно возвращать не всю страницу, а только последнее введенное сообщение. Кроме того, мы бы хотели получать сообщения об ошибках валидации формы, которые возникнут, например, если мы оставим поле ввода сообщения пустым. Для этого напишем валидатор нашей формы, а также немного изменим функцию сохранения:

function _jft_form_validate($form,&$form_state) {
    //мы хотим показать все ошибки Друпала,
    //которые могли возникнуть при валидации формы
    $output = "";
    $output .= theme('status_messages','error');
    if ($output != "") {
        print $output;
        exit();
    }
} //function _jft_form_validate

function _jft_form_submit($form,&$form_state) {
    //...
    //выведем последнее сообщение
    print '' .$form_state['values']['message'] .'';
    exit();
} //function _jft_form_submit

Теперь мы получаем от сервера либо последнее сообщение пользователя, либо ошибку валидации. Окончательный jf_test.js выглядит так:

Drupal.behaviors.jf_testBehavior = function (context) {
    //отмечаем, что наша форма будет сохраняться по AJAX
    //и указываем callback, вызываемый после успешного сабмита
    $("#-jft-form").ajaxForm(successCallback);
    //устанавливаем обработчик сабмита формы
    $("#-jft-form").submit(function () {
        //убираем старые ошибки
        $("div.messages").remove();
        //возвращаем false чтобы предотвратить обычный сабмит
        return false;
    });
}

//callback вызывается после успешного сабмита формы
function successCallback(responseText,statusText) {
    if ($(responseText).hasClass("error")) {
        //есть ошибки при валидации, показываем их в начале формы
        $("#-jft-form").prepend(responseText);
    } else {
        //выводим сообщение
        $("div.test_messages").append(responseText);
        //сбрасываем форму
        $("#-jft-form").resetForm();
    }
}

На всякий случай уточняю: функции ajaxForm и resetForm — это функции из API плагина jQuery Form, без него ничего работать не будет.

Наконец, позаботимся о злоумышленниках, коварно отключивших в своих браузерах JavaScript. Ведь если оставить все как есть, после сохранения формы они будут видеть только текст последнего сообщения и больше ничего. Чтобы все работало нормально и без JavaScript-а, будем проверять способ сохранения формы в функциях валидатора и сохранения. Окончательный текст этих функций:

function _jft_form_validate($form,&$form_state) {
    //проверка, используется ли AJAX для сабмита
    if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
        //мы хотим показать все ошибки Друпала,
        //которые могли возникнуть при валидации формы
        $output = "";
        $output .= theme('status_messages','error');
        if ($output != "") {
            print $output;
            exit();
        }
    }
} //function _jft_form_validate

function _jft_form_submit($form,&$form_state) {
    //добавляем сообщение в массив
    $messages = variable_get('jft_messages',array());
    $messages[] = $form_state['values']['message'];
    variable_set('jft_messages',$messages);
    //выводим последнее сообщение только если для сабмита используется AJAX
    if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
        //выведем последнее сообщение
        print '' .$form_state['values']['message'] .'';
        exit();
    }
} //function _jft_form_submit

Теперь с выключенным JavaScript-ом все будет работать так, как будто его и не было никогда. Задача решена.

Комментарии