Сохранение формы в 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 .= '<div class="test_messages">';
        $output .= '<strong>Сообщения:</strong>';
        $messages = variable_get('jft_messages',array());
        foreach ($messages as $index => $message) {
                $output .= '<p>' .$message .'</p>';
        }
        $output .= '</div>';
       
        //выведем форму
        $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 '<p>' .$form_state['values']['message'] .'</p>';
        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 '<p>' .$form_state['values']['message'] .'</p>';
                exit();
        }
} //function _jft_form_submit

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

Рабочие исходники — прилагаются.

Прикрепленный файлРазмер
jf_test.tar.gz8.82 кб
Гость (гость)
Аватар пользователя Гость

Спасибо за информацию. Очень помогла))

Jamshid (гость)
Аватар пользователя Jamshid

Спасибо, за статья. Мне такая проблема. Я сам написал AJAX внутри тема. Но в AJAX e я получаю целая страница. Как можно решит эту проблему.

С телефона (гость)
Аватар пользователя С телефона

В статье эта проблема рассматривается. Найдите слова "не всю страницу", там написано, что делать. Подробнее пока не могу написать - с телефона неудобно.
Если надо подробнее, скажите, через пару дней смогу ответить.

Павел (гость)
Аватар пользователя Павел

Можно ли сделать не сохранение введенного параметра, а что-то наподобие превью. Т.е. при перезагрузке, на странице находится лишь информация данная по-умолчанию. А после, отправки данных из формы информация изменяется (вместо дефолтной пропечатывается новая, и так каждый раз по субмиту). После перезагрузки страницы - снова деволтное значение (которое формируется из данных аргумента в адресе страницы).
Спасибо.

graker
Аватар пользователя graker

Можно, конечно. Надо только в скрипте вместо аппенда нового содержимого к старому сделать замену старого содержимого новым (напр. .html(responseText)).
А в остальном это скорее вопрос изначальной генерации страницы, к аяксу особого отношения не имеет.

Павел (гость)
Аватар пользователя Павел

Спасибо, так работает. Вопрос же по генерации старницы возник из-за того, что данные накапливаются в базе данных. Для работы через аякс в этом случае необходимо именно таким образом сохранять, чтобы потом выводить на страницу? Извините за ламерские вопросы.

graker
Аватар пользователя graker

Нет, как мы сохраняем и загружаем данные - к аяксу вообще отношения не имеет, важно, что мы передаем браузеру при сабмите формы.

Павел (гость)
Аватар пользователя Павел

Есть следующая проблема. При загрузке с помощью AJAX теряется CSS-оформление данных и их связка с JS-скриптом, которые вызываются только при первой загрузке всей страницы. Т.е. речь наверное идет об организации процесса Lazy-load. На Drupal.org нашел некоторые ссылки по этой теме, но не могу это применить (за основу взял пример этого поста и разместил выводимую после сабмита информацию в табах на jQuery).

Ссылки:
jQuery plugin to lazy load other plugins. Small and easy to use.

Improve performance with JavaScript lazy loading.

Что можно сделать для решения данной проблемы?

Павел (гость)
Аватар пользователя Павел

Также, существует модуль AJAX load Но, автоматом он не включился в решение задачи, а с документацией на английском тяжеловато.

graker
Аватар пользователя graker

Сходу не понять, надо разбираться, а у меня сейчас времени нет. Создайте тему на drupal.ru, так быстрее помогут.

Гость
Аватар пользователя Гость

Спасибо за статью.

Подскажите, как сделать, чтобы при сабмите в форме сохранялось выбранное значение? Сейчас оно сбрасывается.

graker
Аватар пользователя graker

Речь о сохранении значения или о показе сохраненного ранее при выводе формы? Что за выбранное значение - селект? Что значит сбрасывается? Выражайте свои мысли яснее, пожалуйста :)

Если речь о сохранении значения - сохранять надо в функции сабмита формы.
Если речь о выводе установленного ранее значения в форме - надо пользоваться свойством #default_value.

Гость (гость)
Аватар пользователя Гость

Речь о показе выбранного и установленного после самбита значения в select и checkbox полях формы.

Использовал:

'#default_value' => array_search(variable_get('jft_messages', $message), $options_for_select)

Но в данном случае не срабатывает. Разве сама форма при использовании АЯКса перезагружается для того, чтобы воспользоваться функцией variable_get или !_POST при показе дефолтного значения?

Уже думается, что это делать надо в JS.

Спасибо за ответ.

graker
Аватар пользователя graker

Так если вы сабмиттите форму через аякс, просто не сбрасывайте ее при сабмите - тогда выбранные до сабмита значения останутся на месте.

И - нет, конечно форма не перезагружается, если вы в JS не скажете ее перезагружать.

Гость (гость)
Аватар пользователя Гость

Да! Разобрался:)

Гость (гость)
Аватар пользователя Гость

Спасибо, очень полезная статья

Гость (гость)
Аватар пользователя Гость

Скажите пожалуйста, чем такой способ предпочтительней использования свойства ahah в API форм? Заранее спасибо за ответ.

graker
Аватар пользователя graker

Данный пример сам по себе можно реализовать и на свойстве AHAH, совершенно верно. Но использование этого свойства ограничивает задачи, которые мы можем с его помощью решить. Например, если мы хотим выполнить собственный JS при сабмите формы.

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

Гость (гость)
Аватар пользователя Гость

А модулем ahah helper вы не пользовались? Мне он кажется очень удобным, при необходимости изменять форму не перегружая страницу. Мне кажется ваш способ годится если вы динамически передаете текстовые, или графические данные. Но если вам необходимо добавить некий элемент формы, и при этом нужно сделать так чтобы форма считала его "родным" при обработке данных, ручками писать все это довольно затруднительно.

Модуль ahah helper решает эту проблему. У него своя понятная апишка, и он делает всю грязную работу за нас. Кстати он изменяет Drupal.behaviors и поэтому у нас появляется возможность повесить на события свои обработчики. О, еще одна приятность, по-моему этот модуль вошел в ядро семерки.

graker
Аватар пользователя graker

Пользовался немножко.
Просто так вышло, что мне было нужно написать довольно увесистый JS, в котором есть и передача данных, и изменение содержимого страницы сразу в нескольких местах, и динамическая подгрузка целых форм на страницу.
В общем, проще было сразу делать руками. Тем более что сабмитовая часть на jQuery Form, как видно из примера выше, делается все равно в две строчки.

Гость (гость)
Аватар пользователя Гость

У Вас на примере используется форма из двух элементов написаная в ручную, и сохранение данных в своей переменной Drupal...
Можно ли как то прикрутить этот же скрипт для сохранения "типа материала"... к примеру нужно реализовать форму добавления материала "Page" во всплывающем окне (facebox например)... и с помощью AJAX провести валидацию и запись в базу...

graker
Аватар пользователя graker

Для этой задачи должен быть модуль. Названия не помню, поищите.

Навскидку: Ajax, Ajax Dialogs, Modal Frame API посмотрите.

Гость (гость)
Аватар пользователя Гость

Меня смущает еще один момент. Вы при использовании Drupal.behaviors не используете класс типа mymodule-proccess(проверку на то что behaviors уже прикреплен к форме с определенным селектором). Мне кажется это может вызывать в определенных случаях повторения сабмитов формы. Или это так задумано, и я неверно что-то понимаю.

graker
Аватар пользователя graker

Все правильно вы говорите, это косяк примера. Двойные сабмиты могут получиться, если на этой же странице другой скрипт будет вызывать Drupal.attachBehaviors для какого-нибудь обновленного материала. Например, voteupdown.

По-хорошему, в этом примере behaviors использовать не обязательно, можно просто $(document).ready().

Luciuz (гость)
Аватар пользователя Luciuz

сделал все как у вас в примере даже после замены нв $(document).ready() формы всеравно размножаются -- загружаются в div.test_messages, при сабмите отправляются все

Luciuz (гость)
Аватар пользователя Luciuz

ложная тревога, все работает как часы

SplasH (гость)
Аватар пользователя SplasH

Кстати, решение по сабмиту формы через AJAX есть и для Drupal 7 :)

graker
Аватар пользователя graker

Спасибо :)

Отправить комментарий

CAPTCHA
Пройдите, пожалуйста, проверку. Она нужна, чтобы отличать людей от спам-роботов. А если не хотите проходить эту проверку регулярно — зарегистрируйтесь.
Image CAPTCHA
Введите цифры, изображенные на картинке (без пробелов).