2

I have a page with a simple view. Above the view there is a menu link that when clicked loads a node form with a required file field. The form is fetched and loaded using ajax_commands into a div on the current page.

The problem is when I submit the form without providing a file for the required file field (intentionally triggering error), form_set_error redirects the user to the menu path originally called to produce the form (sitename-ajax/new-profile), which displays an unrendered ajax response. How do I get the validation response and return it to the target div on the same page - preventing a page redirect? Code pasted below is simplified for reference. Thanks!

In my hook_menu():

$items['sitename-ajax/new-profile'] = array(
  'title' => t('New Profile'),
  'type' => MENU_CALLBACK,
  'page callback' => 'sitename_ajax_new_profile',
  'access arguments' => array('access content'),
);

Page callback:

function sitename_ajax_new_profile() {
  $form = sitename_forms_prepare_form('ps_profile');

  $output = drupal_render($form);

  $commands = array();
  $commands[] = ajax_command_html('#new-form-target', $output);

  $page = array('#type' => 'ajax', '#commands' => $commands);

  ajax_deliver($page);
}

Form helper function:

function sitename_forms_prepare_form($type) {
  global $user;
  module_load_include('inc', 'node', 'node.pages');

  $node = (object) array(
    'uid' => $user->uid,
    'name' => (isset($user->name) ? $user->name : ''),
    'type' => $type,
    'language' => LANGUAGE_NONE
  );

  $output = drupal_get_form($type . '_node_form', $node);
  return $output;
}

2 Answers 2

0

The API page for form_set_error() says (emphasis added):

When a validation error is detected, the validator calls form_set_error() to indicate which element needs to be changed and provide an error message. This causes the Form API to not execute the form submit handlers, and instead to re-display the form to the user with the corresponding elements rendered with an 'error' CSS class (shown as red by default).

I reckon that's probably where your problem lies. Further down the same page is this:

Multistep forms not wanting to validate the whole form can set #limit_validation_errors on buttons to limit validation errors to only certain elements.

This looks like it might help.

0

Thanks, Darvanen. Your lead helped quite a bit.

After reading through a few similar issues and reading up on ajax_commands, I think I found a solution. The trick was adding an #ajax property to my submit button that invoked a callback that checked for any form errors. If any form errors were found, the form on the page needed to be replaced by the original target div - the one that was initially replaced by the current form - which then needed to be replaced by the rebuilt form. A similar process had to be followed to get the $messages presented on the current page. I'm not sure this is the right way (or the 'Drupal' way), but it seems to be working. If anyone has any suggestions, I'd be grateful to hear them!

Here's the module file I was playing around with:

/**
 * Implements hook_init()
 */
function sandbox_ajax_init() {
  drupal_add_library('system', 'drupal.ajax');
  module_load_include('inc', 'node', 'node.pages');
}

function sandbox_ajax_menu() {
  $items = array();

  $items['ajax/sandbox/get'] = array(
    'title' => 'Ajax Callback',
    'page callback' => 'sandbox_ajax_ajax_callback',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'delivery callback' => 'ajax_deliver',
  );

  $items['sandbox-ajax'] = array(
    'title' => 'Sandbox Ajax Page',
    'page callback' => 'sandbox_ajax_page_callback',
    'access arguments' => array('access content'),
    'type' => MENU_NORMAL_ITEM,
  );

  return $items;
}

/**
 * Implements hook_form_alter()
 */
function sandbox_ajax_form_alter(&$form, &$form_state, $form_id) {
  if ($form['#form_id'] == 'sandbox_file_node_form') {
    $form['actions']['submit']['#ajax'] = array(
      'callback' => 'sandbox_ajax_form_submit',
      'wrapper' => 'sandbox-ajax-wrapper',
      'method' => 'replace',
    );
  }
}

/**
 * KEY TO DISPLAYING FORM ERRORS
 *
 * Custom Ajax Submit Callback
 */
function sandbox_ajax_form_submit($form, &$form_state) {
  $commands = array();
  $form_selector = '#sandbox-file-node-form';

  if (form_get_errors()) {

    // Remove old messages
    $commands[] = ajax_command_remove('#messages-wrap');

    // Replace form with the target div for the rebuilt form
    $commands[] = ajax_command_replace($form_selector, '<div id="sandbox-ajax-wrapper"></div>');

    // Add error messages to page
    $commands[] = ajax_command_after('#main-content', '<div id="messages-wrap">' . theme('status_messages') . '</div>');

    // Replace the div with the rebuilt form - errors included :D
    $commands[] = ajax_command_replace('#sandbox-ajax-wrapper', drupal_render($form));


  } else {
    // If we get here, form validation passed. HOWEVER, if error messages were
    // presented before, they're still hanging out in the messages div. Out with
    // the old, in with the new.
    $commands[] = ajax_command_remove('#messages-wrap');
    $commands[] = ajax_command_after('#main-content', '<div id="messages-wrap">' . theme('status_messages') . '</div>');

    // Finally, replace the form wrapper with an empty div with the original
    // target id.
    $commands[] = ajax_command_replace($form_selector, '<div id="sandbox-ajax-wrapper"></div>');
  }


  // Return the page.
  $page = array('#type' => 'ajax', '#commands' => $commands);
  return $page;
}

/**
 * Page callback
 */
function sandbox_ajax_page_callback() {
  $wrapper = array();

  $wrapper['#attached']['js'] = array(
    drupal_get_path('module', 'sandbox_ajax') . '/js/sandbox_ajax.js',
  );

  $wrapper['container'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'id' => 'sandbox-ajax-wrapper',
      'title' => t('Click to load'),
    ),
  );

  $wrapper['container']['content'] = array(
    '#markup' => t('Loading...'),
  );

  return $wrapper;
}


/**
 * Ajax Form Callback
 */
function sandbox_ajax_ajax_callback() {
  $commands = array();
  $output = sandbox_ajax_prep_form('sandbox_file');

  $selector = '#sandbox-ajax-wrapper';
  $html = drupal_render($output);
  $commands[] = ajax_command_replace($selector, $html);

  $page = array('#type' => 'ajax', '#commands' => $commands);
  return $page;
}


/**
 * Form Prep Helper
 */
function sandbox_ajax_prep_form($type) {
  global $user;
  module_load_include('inc', 'node', 'node.pages');

  $node = (object) array(
    'uid' => $user->uid,
    'name' => (isset($user->name) ? $user->name : ''),
    'type' => $type,
    'language' => LANGUAGE_NONE
  );

  $output = drupal_get_form($type . '_node_form', $node);

  return $output;
}

And here's the JS file:

(function ($) {
  Drupal.behaviors.sandbox_ajax = {
    attach: function (context, settings) {
      $('#sandbox-ajax-wrapper').once('sandbox-ajax-wrapper', function() {
        var base = $(this).attr('id');
        var argument = $(this).attr('argument');
        var element_settings = {
          url: Drupal.settings.basePath + 'ajax/sandbox/get',
          event: 'click',
          progress: {
            type: 'throbber'
          }
        };
        Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
        $(this).click();
      });
    }
  }
}(jQuery));

Similar issues for reference:

#ajax['wrapper'] removed by validation errors

AJAX commands and drupal messages

Your Answer

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

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