Cargar un formulario de webform en un modal y hacer de que mande todo con Ajax

  • 21 Jun 2019
  • Drupal 7, Bootstrap, Ajax

Objetivo

Tenemos un Drupal 7 con un tema de Bootstrap y lo que se quiere es que al hacer click en un botón se cargue via ajax un formulario de webform, es decir un node. Y a su vez, hacer que este formulario cargado en el modal se envie con ajax.

Dependencias

Desarrollo

Con un módulo personalizado vamos a crear una página (implementando hook_menu()) que nos devuelva el html de solo el formulario en cuestion. En este caso vamos a hacer de que toda url node/%/modal devuelva un html en base a un modal de bootstrap.

/**
* Implements hook_menu().
*/
function custom_functions_menu() {
  $items['node/%/modal'] = array(
    'page callback' => 'custom_functions_ajax_get_ajax',
    'page arguments' => array(1),
    'type' => MENU_CALLBACK,
    'access arguments' => array('access content'),
    'delivery callback' => 'custom_functions_ajax_callback',
  );
  return $items;
}

Con este "punto de menu" le estamos pidiendo a Drupal que cuando se visite una página cuya url coincida con node/%/modal vaya a buscar primero el (objeto) formulario del node en cuestión (custom_functions_ajax_get_ajax) y luego, con ese objeto genere un poco de HTML y lo devuelva inmediatamente al navegador (custom_functions_ajax_callback).

"delivery callback": The function to call to package the result of the page callback function and send it to the browser. Defaults to drupal_deliver_html_page() unless a value is inherited from a parent menu item. Note that this function is called even if the access checks fail, so any custom delivery callback function should take that into account. See drupal_deliver_html_page() for an example.

 

function custom_functions_ajax_get_ajax($nid) {
  $node = node_load($nid);
  $submission = (object) array();
  $enabled = TRUE;
  $preview = FALSE;
  $form = drupal_get_form('webform_client_form_' . $nid, $node, $submission, $enabled, $preview);
  return $form;
}


function custom_functions_ajax_callback($page_callback_result){
  $title = $page_callback_result['#node']->title;
  $close = '<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>';
  $content  = '<div class="modal-header">'.$close.'<h4 class="modal-title">'.$title.'</h4></div>';
  $content .= '<div class="modal-body">'.drupal_render($page_callback_result).'</div>';

  // Generate the settings:
  $settings = FALSE;
  $javascript = drupal_add_js(NULL, NULL);
  if(isset($javascript['settings'], $javascript['settings']['data'])) {
    $settings = '<script type="text/javascript">jQuery.extend(Drupal.settings, ';
    $settings .= drupal_json_encode(call_user_func_array('array_merge_recursive', $javascript['settings']['data']));
    $settings .=  ');</script>';
  }

  print $content.$settings;

}

Ajax para webform

Ya tenemos una url que ya debería devolver html limpito limpito sin la necesidad de haber creado páginas locas con page manager o themes personalizados para sobreescribir los predeterminados por Drupal. Ahora bien, para hacer que el formulario de webform se envie con ajax podriamos instalar un módulo.. pero hoy no. Para que si con dos lineas en un hook_form_alter podemos obtener el mismo resultado! Imaginemos en este caso que el webform se encuentre en el node 123:

function custom_functions_form_alter(&$form, &$form_state, $form_id){

  switch($form_id) {
    case 'webform_client_form_123':
      // ajax
      $form['actions']['submit']['#id'] = 'submit-webform-123';
      $form['actions']['submit']['#ajax'] = array(
        'wrapper'  => 'webform-client-form-123',
        'callback' => 'node_webform_123_ajax_callback',
        'method'   => 'replace',
        'effect'   => 'fade',
      );
      break;
  }
}

function node_webform_123_ajax_callback($form, &$form_state) {
  if (!empty($form_state['executed'])) {

    $status_msg = drupal_get_messages('status');
    drupal_set_message(t("Thank you for blablabla."));

    // borramos todos los campos
    $form['submitted']['intro']['#access'] = FALSE;
    $form['submitted']['name']['#access'] = FALSE;
    $form['submitted']['lastname']['#access'] = FALSE;
    $form['submitted']['email']['#access'] = FALSE;
    $form['submitted']['interests']['#access'] = FALSE;
    // borramos el boton submit
    $form['actions']['submit']['#access'] = FALSE;
    // agregamos el boton para cerrar el modal
    $form['close'] = array(
      '#markup' => '<button type="button" class="btn btn-lg btn-default" data-dismiss="modal">Close</button>',
      '#weight' => 100,
    );
  }
  return $form;
}

El motivo por el cual alteramos el ID del submit es para permitirnos de tener más de un formulario que puedan enviarse a través de ajax en la misma página. De este modo evitamos que haya más de un botón con el mismo id. Luego, en la función node_webform_123_ajax_callback limpiamos el formulario y mostramos un mensaje de agradecimiento si todo salió bien.

Para que todo funcione, es fundamental que se carguen drupal.ajax y jquery.form. Si estuvimos trabajando con theme_preprocess_page() podríamos encontrar un error. Para cargar siempre estas dos librerias podemos crear dentro nuestro template.php algo como esto:

function mitema_preprocess_page(&$vars) {
  drupal_add_library('system', 'drupal.ajax');
  drupal_add_library('system', 'jquery.form');
}

El botón y javascript

El último paso es poner un botón donde más nos guste que llame a /node/123/modal. Como para abrir un modal es necesario js, podríamos hacer que el href sea solo /node/123 y agregar al elemento una clase que identifique que ese link quiere abrirse dentro un modal, como por ejemplo: node-modal. De eso modo, el botón quedaría más o menos así:

<a class="btn node-modal" data-target="#bs-modal" href="/node/123">Abrite en un modal!</a>

El código javascript que atienda el click de este botón y busque su href + /modal podría ser el siguiente:

var nodeModal = $("a.node-modal");
  if (nodeModal.length > 0) {
    nodeModal.on('click', function(e){
      e.preventDefault();
      var $this = $(this);
      var url = $this.attr('href');
      var target = $this.data('target');
      $(target).modal();
      $(target).find('.modal-content').load(url + "/modal", function(){
        Drupal.attachBehaviors();
      })
    })
  }

Ya casi terminamos. Lo único que falta para completar la obra es un mínimo de html que deberá estar presente en todas las páginas donde se encuentre el botón o link que dispare el modal. Esto lo podemos hacer modificando o creando un template (theme hook) suggestions en nuestro tema. Activar theme debug puede ser de mucha ayuda en este punto.

<div class="modal" id="bs-modal" tabindex="-1" role="dialog" aria-labelledby="newsletter" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
    </div>
  </div>
</div>