8

I'm working on an Ajax implementation for the Webform submit on Drupal 7. I was not able to find any good hook to alter the Webform submit button and add '#ajax' in the form so i had a look to a Drupal 6 module that implements this functionality from an external script.

So i decided to go with my own module and JavaScript code to fire an Ajax post request to a custom menu callback I have defined in hook_menu(), in Drupal 7.

The JavaScript part works fine but I'm having issues trying to submit the Webform programmatically.

Here is my JavaScript code:

function formSubmit(event, formId) {

  event.preventDefault();

  var form = jQuery("#" + formId);
  var postData = form.serialize();
  var nodeId = formId.substring(20);
  var msg = '';

  msg += form.find('#edit-submitted-name').attr('value') ? '' : 'Please enter your name';
  console.log(form.find('#edit-submitted-name').attr('value'));
  console.log(form.find('#edit-submitted-e-mail').attr('value'));

  if(msg) {
    alert(msg);
  } else {
    jQuery.ajax({
      url: Drupal.settings.basePath + 'webform_ajax/' + nodeId,
      fid:formId,
      type: 'POST',
      data: postData,
      success: function(ajaxData) {
        console.log(ajaxData);
        console.log('Hello world');
        // can't get here
      }
    });
  }
}

And my module code (based on webform_ajax module):

function custom_menu() {
  $items = array();
  $items['webform_ajax/%'] = array(
    'page callback' => '_custom_webform_ajax',
    'page arguments' => array(1,2),
    'access callback' => '_custom_webform_ajax_access',
  );
  return $items;
}

function _custom_webform_ajax($nid, $data) {
  //$sid = $_POST['details']['sid'];

  $local_POST = $_POST;
  $form_build_id = $_POST['form_build_id'];

  $form_id = 'webform_client_form_' . $nid;

  $node = node_load($nid);

  $submission = array();
  $form_state = array();

  $form = form_get_cache($form_build_id, $form_state);
  $form_array = drupal_rebuild_form($form_id, $form_state, array($form_state, $node, $submission), $form_build_id);
  $form_state['clicked_button'] = $form_array['actions']['submit'];

  if (is_array($local_POST['submitted'])) {
    foreach ($local_POST['submitted'] as $submit_index => $submit) {
      $form_state['storage']['submitted'][$submit_index] = $submit;
      $form_state['values']['submitted'][$submit_index] = $submit;
    }
  }

  // Clearing empty values from $form_state
  if (is_array($form_state['values']['submitted'])) {
    foreach ($form_state['values']['submitted'] as $value_index => $value) {
      if (!$value) {
        unset($form_state['values']['submitted'][$value_index]);
      }
    }
  }

  // Executing the pressed button action
  drupal_execute($form_id, $form_state, $node, array());

  // Get the HTML for the error messages
  $error_html = theme('status_messages', 'error');

  // Building the resulting form after the processing of the button
  $form_array = drupal_rebuild_form($form_id, $form_state, array($form_state, $node, $submission), $form_build_id);
  $form = drupal_render_form($form_id, $form_array);

  return drupal_json_output(array(
    'message' => $error_html,
    'status' => 'sent',
  ));

}

function _custom_webform_ajax_access() {
  // Todo: Add webform access conditions
  return true;
}

When i submit my form i get 500 server errors.

I guess D6 & D7 form API are quite different and i'm not sure where to start to have this piece of code working. I have tried to debug it but i can't figure out what's generating the 500 errors.

I use webform 3 and the module i took the code also relies on the version 3 of webform but for Drupal 6. But both modules should provide the same functions and same kind of functionalities behind. First workaround: It may come from the values i pass that would not be compatible with D7 form api.

In my log i have :

Argument 1 passed to drupal_array_nested_key_exists() must be an array, null given, called in D:\wamp\www\productionsite\includes\form.inc on line 1986 and defined in drupal_array_nested_key_exists() (line 6296 of D:\wamp\www\productionsite\includes\common.inc).

-- EDIT --

I'm debugging line by line now, at the end this piece of code could worth to become a D7 module ;)

I found in D7 documentation that drupal_rebuild_form() arguments have changed from D6, and that the $form_state can't be empty anymore at this stage, so i updated my code in this way:

$form_state = array('submitted' => false, 'values' => array());
$form = form_get_cache($form_build_id, $form_state);
$form_array = drupal_rebuild_form($form_id, $form_state, $form);

Now i'm trying to find the equivalent of drupal_execute(), which doesn't exists anymore in D7.

-- Edit (2) --

I got it working a few days ago and come back to share the solution, and maybe get some advices and improvements suggestions.

<?php

function custom_menu() {
  $items = array();
  $items['webform_ajax/%'] = array(
    'page callback' => '_custom_webform_ajax',
    'page arguments' => array(1,2),
    'access callback' => '_custom_webform_ajax_access',
  );
  return $items;
}

function _custom_webform_ajax($nid, $data) {

  $local_POST = $_POST;
  $form_build_id = $_POST['form_build_id'];

  $form_id = 'webform_client_form_' . $nid;

  $node = node_load($nid);

  $submission = array();
  $form_state = array(
    'submitted' => false, 
    'values' => array(),
    'build_info' => array(
      'args' => array(
        $node,
        array(),
        FALSE
      )
    )
  );

  $form = form_get_cache($form_build_id, $form_state);
  $form_array = drupal_rebuild_form($form_id, $form_state);

  // Add the clicked button before processing the form
  $form_state['clicked_button'] = $form_array['actions']['submit'];

  if (is_array($local_POST['submitted'])) {
    foreach ($local_POST['submitted'] as $submit_index => $submit) {
      $form_state['values']['submitted'][$submit_index] = $submit;
    }
  }

  // Clearing empty values from $form_state
  if (is_array($form_state['values']['submitted'])) {
    foreach ($form_state['values']['submitted'] as $value_index => $value) {
      if (!$value) {
        unset($form_state['values']['submitted'][$value_index]);
      }
    }
  }

  $form_state['values']['details']['nid'] = $nid;

  // Executing the pressed button action
  drupal_build_form($form_id, $form_state);

  return drupal_json_output(array(
    'message' => t('Your submission has been received. Thank you for contacting us.'),
    'status' => 'sent',
  ));  

}

function _custom_webform_ajax_access() {
  // TODO: Add user role / perm check
  return true;
}

To go a step further I would like now to get the errors from the processed form so i could send them back with the json object. Any ideas ?

2 Answers 2

4

I was doing something similar and found E. de Saint Chamas's solution to mostly work for me. However, there were a few things that I needed to add:

First, I had to add this to form_state array before processing form

'method' => 'post',

Then, towards the bottom, some adjustments to process the form and return error messages if any:

  // Prevent the form from redirecting the request
  $form_state['no_redirect'] = TRUE;
  // Executing the pressed button action
  drupal_build_form($form_id, $form_state);
  // See if the form submitted successfully
  if (!$form_state['executed']) {
    // If the form didn't submit successfully, get the errors
    // which are set bu drupal_set_message
    $messages = drupal_get_messages('error');
    $messages = implode('<br />', $messages['error']);
  }
  else {
    // If form submitted successfully, create a nice message.
    $messages = "Thanks for contacting us! We will let you know when the Beta is live!";
  }
  // drupal_json_output seems to confuse some browsers, who want to save as a file 
  print drupal_json_encode(array(
    'message' => $messages,
    'status' => $form_state['executed'],
  ));

I am not sure if this is the best way to do it, but found it worked for me. Of course, you might want to just go ahead and render the error messages and return a fully rendered error message box, and additionally, you could pluck the "confirmation message" from the $form_state array so that you can control the success message from the webform UI.

2
  • This is great, but I keep getting a failure ($form_state['executed'] = False). And there's nothing in drupal_get_messages('error'). Wonder how I can debug this.
    – cybertoast
    Commented Dec 6, 2012 at 15:33
  • I should clarify that i'm trying to submit via curl, like curl -vvv -X POST -H "X-Requested-With: XMLHttpRequest" -d 'submitted[contact_fullname]=my%20name&submitted[contact_email]=test%40example.com&submitted[contact_message]=test%20message' "localhost/fubar/31". The content gets sent and the form_state is populated, but drupal_form_build() does not get executed/submitted.
    – cybertoast
    Commented Dec 6, 2012 at 15:41
-1

Tell me if I am wrong but since a webform submission is a node, why not directly create the node programatically in your page callback (with field validation (or could do before submit using javascript))

It could be something like

if(!function_exists("node_object_prepare"))
{
  include_once(drupal_get_path('module', 'node') . '/node.pages.inc');
}
$node = new stdClass();                                                         
$node->is_new = TRUE;
$node->type = 'YOUR_NODE_TYPE_HERE';                                
node_object_prepare($node);

// then all the fields you need

node_validate($node);
$node = node_submit($node);
node_save($node);
$nid = $node->nid;

Et voilà ! :)

1
  • 3
    Actually webform submissions are not nodes. Webform stores submissions in its own tables. So we cannot have a new node created to add the submission. Moreover i would like to have the whole webform validation workflow being triggered once the form is executed so it checks the required fields, etc... Commented Jul 19, 2011 at 18:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.