Jak programowo przesyłać formularze internetowe za pomocą Ajax?

8

Pracuję nad implementacją Ajax dla przesyłania formularza internetowego na Drupal 7. Nie byłem w stanie znaleźć hookniczego, co mogłoby zmienić przycisk przesyłania formularza internetowego i dodać „#ajax” w formularzu, więc zajrzałem do modułu Drupal 6, który implementuje tę funkcjonalność z zewnętrznego skryptu.

Postanowiłem więc użyć własnego modułu i kodu JavaScript, aby uruchomić żądanie postu Ajax do niestandardowego wywołania zwrotnego menu, które zdefiniowałem hook_menu()w Drupal 7.

Część JavaScript działa dobrze, ale mam problemy z programowym przesyłaniem formularza internetowego.

Oto mój kod JavaScript:

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
      }
    });
  }
}

A mój kod modułu (na podstawie modułu webform_ajax):

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;
}

Kiedy przesyłam formularz, otrzymuję 500 błędów serwera.

Wydaje mi się, że interfejsy API formularzy D6 i D7 są zupełnie inne i nie jestem pewien, od czego zacząć ten fragment kodu. Próbowałem to debugować, ale nie mogę ustalić, co powoduje 500 błędów.

Używam webform 3, a moduł, który wziąłem, opiera się również na wersji 3 webform, ale dla Drupala 6. Ale oba moduły powinny zapewniać te same funkcje i te same funkcje. Pierwsze obejście: może pochodzić z wartości, które przekazuję, które nie byłyby zgodne z interfejsem API formatu D7.

W moim dzienniku mam:

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).

-- EDYTOWAĆ --

Debuguję teraz linia po linii, na końcu ten kawałek kodu mógłby się stać modułem D7;)

W dokumentacji D7 znalazłem, że argumenty drupal_rebuild_form () zmieniły się z D6 i że $form_statena tym etapie nie mogą być już puste, więc zaktualizowałem kod w ten sposób:

$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);

Teraz próbuję znaleźć odpowiednik drupal_execute (), który już nie istnieje w D7.

- Edycja (2) -

Uruchomiłem go kilka dni temu i wróciłem, aby podzielić się rozwiązaniem, a może otrzymam porady i sugestie dotyczące ulepszeń.

<?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;
}

Aby pójść o krok dalej, chciałbym teraz pobrać błędy z przetworzonego formularza, aby móc odesłać je z powrotem z obiektem json. Jakieś pomysły ?

E. de Saint Chamas
źródło

Odpowiedzi:

4

Robiłem coś podobnego i znalazłem rozwiązanie E. de Saint Chamasa, które w większości dla mnie działa. Musiałem jednak dodać kilka rzeczy:

Najpierw musiałem dodać to do tablicy form_state przed przetworzeniem formularza

'method' => 'post',

Następnie, w dolnej części, kilka zmian w przetwarzaniu formularza i zwracaniu komunikatów o błędach, jeśli takie występują:

  // 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'],
  ));

Nie jestem pewien, czy jest to najlepszy sposób, ale okazało się, że zadziałało to dla mnie. Oczywiście możesz chcieć po prostu wyrenderować komunikaty o błędach i zwrócić w pełni renderowane okno komunikatu o błędzie, a dodatkowo możesz pobrać „komunikat potwierdzenia” z tablicy $ form_state, aby móc kontrolować komunikat o powodzeniu z interfejs sieciowy.

wesnick
źródło
To wspaniale, ale ciągle pojawia się błąd ($ form_state ['wykonana'] = False). I nic nie ma w drupal_get_messages („error”). Zastanawiam się, jak mogę to debugować.
cybertoast
Powinienem wyjaśnić, że próbuję przesłać przez curl, jak curl -vvv -X POST -H "X-Requested-With: XMLHttpRequest" -d 'submitted [nazwa_kontaktu] = moja nazwa% 20 i przesłana [wiadomość_kontaktu] = test% 40przykład. com i przesłane [contact_message] = test% 20message '„ localhost / fubar / 31 ”. Treść zostaje wysłana, a parametr form_state jest zapełniony, ale funkcja drupal_form_build () nie jest wykonywana / przesyłana.
cybertoast
-1

Powiedz mi, czy się mylę, ale ponieważ przesłanie formularza internetowego jest węzłem, dlaczego nie utworzyć bezpośrednio węzła programowo w twoim page callback(z weryfikacją pola (lub może to zrobić przed przesłaniem za pomocą javascript))

To może być coś takiego

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;

Zrobione ! :)

Gueno
źródło
3
W rzeczywistości przesłane formularze internetowe nie są węzłami. Webform przechowuje zgłoszenia we własnych tabelach. Nie możemy więc utworzyć nowego węzła, aby dodać zgłoszenie. Ponadto chciałbym, aby cały proces sprawdzania poprawności formularza internetowego był uruchamiany po uruchomieniu formularza, aby sprawdzał wymagane pola itp.
E. de Saint Chamas,