Загрузка и отправка формы по AJAX на Drupal 7

    image Недавно мне потребовалось реализовать сабмит формы по AJAX. При этом форма должна загружаться в попапе. Казалось бы задача тривиальная, но оказалось что все же есть подводные камни.
    Задача решается для Drupal 7. В качестве библиотеки для создания popup'ов используется fancybox.

    Сперва нужно подключить необходимые библиотеки:

      $path = drupal_get_path('module', 'simple_ajax_popup');
      drupal_add_js($path . '/misc/fancybox/source/jquery.fancybox.pack.js');  
      drupal_add_css($path . '/misc/fancybox/source/jquery.fancybox.css');
      drupal_add_js($path . '/misc/simple_ajax_popup.js');
      drupal_add_js('misc/jquery.form.js');

    Файл jquery.form.js подключается друпалом каждый раз, когда на странице присутствует форма. Этот файл необходим для отправки формы по AJAX. Поскольку в момент отрисовки страницы этой формы еще нет, то друпал не подключит этот файл и его нужно подключить вручную.

    Затем в хуке меню нужно добавим необходимые колбеки:

    /**
     * Implements hook_menu().
     */

    function simple_ajax_popup_menu() {
      $items = array();
      $items['test'] = array(
        'type' => MENU_SUGGESTED_ITEM,
        'title' => t('Test page'),
        'page callback' => 'simple_ajax_popup_page',
        'access arguments' => array('access content'),
      );
      $items['callback'] = array(
        'type' => MENU_CALLBACK,
        'page callback' => 'simple_ajax_popup',
        'access arguments' => array('access content'),
      );
     
      return $items;
    }


    Первым колбеком станет страница на которой будет ссылка для загрузки формы, а второй колбек будет возвращать форму.

    Для того чтобы вызывать форму, добавляем ссылку на страницу:

    function simple_ajax_popup_page() {
      drupal_add_library('system', 'drupal.ajax');
       return l(
        'AJAX form',
        'callback',
        array(
          'attributes' => array('class' => array('fancybox', 'fancybox.ajax')),
        )
      );        
    }


    Важно не забыть подключить библиотеку друпала для работы с AJAX.

    Еще один важный момент — навесить на событие fancybox'а afterShow функцию для обработки формы на javascript:

    function simpleAjaxPopupFormProcess() {
      Drupal.attachBehaviors('#simple_ajax_popup_form');
      jQuery('#simple_ajax_popup_form').ajaxForm();
    }
     


    Функция отрисовки формы крайне проста:

    /**
     * Return popup with form.
     */

    function simple_ajax_popup() {
      $form = drupal_render(drupal_get_form('simple_ajax_popup_form'));
      print $form;
      drupal_exit();
    }


    Для ответа используется функция ajax_deliver. Функция ajax_command_replace позволяет добавить в ответ AJAX запроса команду, заменяющую контент по указанному врапперу.

    function simple_ajax_popup_form_submit($form, &$form_state) {
      $form_state['rebuild'] = TRUE;
      watchdog('ajax message', $_POST['message']);
      $commands = array();
      $commands[] = ajax_command_replace(
        '#simple-ajax-popup-form',
        '<div class="messages status">Сообщение добавлено в системный журнал.</div>'
      );
      ajax_deliver(array('#type' => 'ajax', '#commands' => $commands));
      drupal_exit();
    }


    Таким образом можно показать сообщение об ошибке:



    Или сообщение об успешной отправке:



    В данном случае форма просто добавляет сообщение в watchdog.

    И напоследок — не забудьте добавить специальный класс кнопке сабмита формы, для того чтобы форму можно было отправлять по AJAX.

      $form['form_wrapper']['submit'] = array(
        '#type' => 'submit',
        '#value' => 'Отправить.',
        '#attributes' => array('class' => array('use-ajax-submit')),
      );


    Полный код примера можно скачать тут

    Реальный проект где используется такая отправка форм через popup — модуль comment abuse.
    Share post

    Comments 15

      +3
      '#value' => t('Отправить.'),
      В функцию t() нужно подставлять текст для перевода на английском языке
      Да и вообще у вас весь код не правильный и подход изначально не тот. Почитайте про модуль ctools, который берет на себя всю работу с формой.
        0
        > '#value' => t('Отправить.'),
        > В функцию t() нужно подставлять текст для перевода на английском языке
        спасибо, поправил. с англ. переписывал для примера.

        >Почитайте про модуль ctools, который берет на себя всю работу с формой.
        можно будет глянуть.
          0
          Нормальный подход. Необязательно делать один попап для одного модуля с помощью CTools (в котором 1000 других вещей), ограничелся практически стандартными возможностями.

          Только exit(); там?
            0
            ограничился*

            Только exit(); там зачем?* — вопрос автору.
              0
              exit чтобы прервать выполнение скрипта и чтобы случайно ничего после принта формы дополнительно не спринтилось.
          0
          Автор спасибо за труд и вклад в drupal :) Я кстати попробую твой модуль вписать в свой проект, у нас пока Flag as Offensive, но заказчик давно просил что-то подобное.
            +1
            Таки посмотрите ctools на примере, как вариант, модуля ajax login/register. Он небольшой, там просто разобраться. Ну и вот тут небольшие примеры.
              0
              Спасибо за более точную наводку! посмотрю обязательно :)
              0
              * Вместо exit() в друпале нужно использовать drupal_exit()
              * 'access callback' => TRUE, — зло. Объявите свой permission или используйте стандартный access content
              * simple_ajax_popup() — В корне неверный подход к работе с формами. Используйте стандартный #ajax из Forms API, тогда отработают стандартный валидатор и сабмит формы, она ребилднется и вернет нужную информацию.
                0
                > 'access callback' => TRUE, — зло.
                это всего лишь пример.

                > simple_ajax_popup() — В корне неверный подход к работе с формами. Используйте стандартный #ajax из Forms API, тогда отработают стандартный валидатор и сабмит формы, она ребилднется и вернет нужную информацию.

                Для моего случая когда формы изначально нет на странице, это почему то не срабатывает. Функция определенная в #ajax не вызывается. Если знаете в чем проблема — буду рад узнать.
                  0
                  > 'access callback' => TRUE, — зло.
                  это всего лишь пример.

                  Кто-то поймет, что это только пример и сделает правильно, но большинство тупо скопирует.

                  Для моего случая когда формы изначально нет на странице, это почему то не срабатывает. Функция определенная в #ajax не вызывается. Если знаете в чем проблема — буду рад узнать.

                  Я не стал заморачиваться с fancybox, сделал просто пример, где форма получается аяксом и обрабатывается аяксом. Код не претендует, но логика должна быть понятна.

                  Вот рабочий пример
                  Модуль
                  function test_menu() {
                    $items = array();
                    $items['test'] = array(
                      'type' => MENU_SUGGESTED_ITEM,
                      'title' => t('Test page'),
                      'page callback' => 'test_page',
                      'access arguments' => array('access content'),
                    );
                    $items['callback'] = array(
                      'type' => MENU_CALLBACK,
                      'page callback' => 'test_callback',
                      'access arguments' => array('access content'),
                    );
                  
                    return $items;
                  }
                  
                  function test_page() {
                    drupal_add_js('misc/jquery.form.js');
                    drupal_add_js(drupal_get_path('module', 'test') . '/test.js');
                    $output = '<div id="form"></div>';
                    return $output . l(t('Get form'), 'callback', array('attributes' => array('id' => 'test-link')));
                  }
                  
                  function test_callback() {
                    print drupal_render(drupal_get_form('test_form'));
                    drupal_exit();
                  }
                  
                  function test_form($form, $form_state) {
                    $form = array();
                    $form['aass'] = array(
                      '#theme' => 'status_messages'
                    );
                    $form['messages'] = array(
                      '#markup' => '',
                    );
                    $form['name'] = array(
                      '#title' => t('Name'),
                      '#type' => 'textfield',
                      '#default_value' => '',
                      '#required' => TRUE,
                    );
                    $form['submit'] = array(
                      '#type' => 'submit',
                      '#value' => t('Submit'),
                    );
                    return $form;
                  }
                  
                  function test_form_validate(&$form, &$form_state) {}
                  
                  function test_form_submit($form, &$form_state) {
                    $form_state['rebuild'] = TRUE;
                    drupal_set_message(t('Ok. All good!'));
                  }
                  

                  test.js
                  (function($) {
                    $(document).ready(function() {
                      var success = function() {
                        $('#test-form').ajaxForm({
                          target: '#form',
                          success: success
                        });
                      };
                      $('#test-link').click(function(event) {
                        $.get(Drupal.settings.basePath + 'callback', function(data) {
                          $('#form').html(data);
                          Drupal.attachBehaviors('#test-form');
                          success();
                        });
                        event.preventDefault();
                      });
                    });
                  })(jQuery);
                  

                    0
                    Спасибо! Отличный пример — все работает. Сегодня уже поздно, завтра постараюсь привести код в порядок и обновить пост.
                0
                Все делается проще:
                в hook_form_alter добавляем для сабмита нужной формы свойство '#ajax', где указываем callback-функцию, враппер и метод. В callback-е просто возвращаем array('#type' => 'ajax', '#commands' => $commands) или html-код;

                  0
                  Хм…, парсер, какашка, теги трет. Не вставилось: pastebin.com/nupf5dFm.
                  0
                  Обновил код и исходники.

                  Only users with full accounts can post comments. Log in, please.