4

I have a custom widget form that I use on a node reference field. The form outputs a list of nodes with a pager (jQuery Infinite Scroll plugin to append next results using the pager links) which can be selected via checkboxes. The results can be filtered via ajax based on a taxonomy term selection and the old results get replaced with new ones using the ajax command $commands[] = ajax_command_replace. So far so good.

The issue comes after the results are filtered using ajax and the infinite scroll (jQuery plugin) tries to call the next page of results (ie: page 2). Using firebug I can see the GET request returns a 200 ok status message but no results are appended to the dom. When I check the logs I see the following errors:

Undefined index: form_build_id in ajax_get_form() (line 320 of /var/www/html/includes/ajax.inc).

http://mywebsite.com/system/ajax?page=2|http://mywebsite.com/create/list|5372||Invalid form POST data.

This is the ajax callback when filtering the form:

    function nodereference_selector_field_widget_form_ajax_callback(&$form, &$form_state) {
      $commands = array();
      // Replace the old results when filtering occurs.
      $commands[] = ajax_command_replace('.form-item-field-list-und', drupal_render_children($form['field_list']));

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

This is the widget form function:

 function nodereference_selector_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element, $values = array()) {
  $all_categories = array(36, 38, 40, 41, 194, 327, 1308, 1335, 1336, 1337);
  $category = nodereference_selector_get_categories();
  $category = array('all' => t('Display All')) + $category;

  $element['itemswrapper'] = array(
    '#weight' => 0, 
    '#suffix' => '<div id="item-area">',
  );

  $form['selectedwrapper'] = array(
    '#weight' => -10, 
    "#markup" => '<div id="selected-items"><span id="instructions">Select items from the list to create your list.</span></div>',  
    );

  $form['tid'] = array(
    '#type' => 'select',
    '#options' => $category,
    '#default_value' => isset($values['tid']) ? $values['tid'] : 'all',
    '#ajax' => array(
        'callback' => 'nodereference_selector_field_widget_form_ajax_callback',
        'progress' => array('type' => 'throbber', 'message' => 'Loading...'),
        'wrapper' => 'item-area',
    ),
    '#weight' => 0, 
   );
  $values = isset($form_state['values']) ? $form_state['values'] : array('tid' => NULL);
  $radios = $field['cardinality'] == 1 && $element['#required'];
  $options = array();
  foreach ($field['settings']['referenceable_types'] as $type) {
    if (is_numeric($type) || !isset($instance['widget']['settings']['image_field_' . $type])) {
      continue;
    }
    $query = db_select('node', 'n');
    $query->addTag('node_access');
    $query->join('field_data_' . $instance['widget']['settings']['image_field_' . $type], 'fd', 'fd.entity_id = n.nid');
    $query->join('taxonomy_index', 'ti', 'ti.nid = n.nid');
    $query->fields('n', array('title', 'nid'));
    $query->fields('fd', array($instance['widget']['settings']['image_field_' . $type] . '_fid'));
    $query->condition('n.type', $type, '=');
    $query->condition('fd.delta', 0, '=');
    $query->condition('n.status', 1, '=');
    $query->condition('tid', $all_categories);
    $query->orderRandom();
    nodereference_selector_filter_query($query, $values);
    $result = $query->extend('PagerDefault')->limit(20)->execute();
    foreach ($result as $node) {
      $options[$node->nid] = theme('image_style', array(
          'style_name'  => $instance['widget']['settings']['image_style'],
          'path'        => file_load($node->{$instance['widget']['settings']['image_field_' . $type] . '_fid'})->uri,
          'title'       => check_plain($node->title),
        )
      );
    }
  }

  $defaults = array();
  if (isset($form_state['build_info']['args'][0]->{$instance['field_name']}[$langcode])) {
    if (!$radios) {
      foreach ($form_state['build_info']['args'][0]->{$instance['field_name']}[$langcode] as $value) {
        $defaults[] = $value['nid'];
      }
    }
    else {
      $defaults = $form_state['build_info']['args'][0]->{$instance['field_name']}[$langcode][0]['nid'];
    }
  }
  $element += array(
    '#type'             => $radios ? 'radios' : 'checkboxes',
    '#options'          => $options,
    '#element_validate' => array('nodereference_selector_validate'),
    '#attributes'       => array(
      'class' => array('clearfix'),
    ),
    '#attached' => array(
      'js'  => array(
        drupal_get_path('module', 'nodereference_selector') . '/js/nodereference_selector.js',
      ),
      'css' => array(
        drupal_get_path('module', 'nodereference_selector') . '/css/nodereference_selector.css',
      ),
    ),
    '#default_value' => $defaults,
  );

  // Attach the pager theme
  $element['pager_pager'] = array('#theme' => 'pager');

  $element["itemswrapper"]['#prefix'] =  "</div>";

  return $element;
}
1

This is a long standing bug (first reported on the 6th of November 2012) in Drupal 7.x and 8.x. You can find the report here. Thanks to the guys that made the userpoints module and @anthony for pointing it out I made a function to handle this issue.

function mymodule_parse_input($form_state, $input) {
    $values = isset($form_state['values']) ? $form_state['values'] : array_map(function() {return NULL;}, $input);
    foreach ($input as $key => $value) {
        if (isset($values[$key])) {
            $_GET[$key] = $values[$key];
        }
        else {
            if (isset($_GET[$key])) {
                $values[$key] = $_GET[$key];
            }
            else if ($value) {
                $values[$key] = $value;
            }
        }
    }

    return $values;
}

At the top of your form function you need to put something like:

if ($_GET['q'] == 'system/ajax') {
    $_GET['q'] = $form_state['#mymodule']['real_url'];
}
else {
    $form_state['#mymodule']['real_url'] = $_GET['q'];
}
$form_values = array(
  'field1' => $field1_default_value,
  'field2' => $field2_default_value,
);
$values = mymodule_parse_input($form_state, $form_values);

You need to fill $form_values with the default values for your form. These values will be passed into the $values variable when the form is rebuilt. Be careful though because the values are unfiltered from the $_GET superglobal. If you don't want any default value for a field then simply pass it as $field1 => NULL and it will work. You can use $values as if it where $form_state['values']. For example the value for field1 would be $values['field1'].

  • Glad that helped you out. – anthony Sep 13 '15 at 5:10
0

Try $form_state['rebuild'] = TRUE;

0

You cannot send a query param to system/ajax without POST data. Without POST data, system/ajax has no idea what you're talking about.

You should be sending the param to a page callback for your form. If you don't have one, create one. This will allow you to paginate using your JQuery Infinite Scroll. However, I notice you have a select for changing the category. If you don't mind this reseting the pagination, then it should work fine.

0

I resolved the issue by manually parsing the pager url. Drupal has bug that messes up pager urls when using ajax (still hasn't been resolved). After an ajax callback is called the pager links will point to system/ajax which will return a blank page.

I added this at the end of the hook_field_widget_form function:

  // If this is an AJAX request, update $_GET['q'] so that table sorting and
  // similar links are using the correct base path.
  if ($_GET['q'] == 'system/ajax') {
    $q = url('node/add/list', array('absolute' => TRUE));
    $_GET['q'] = $q;
  }

And copied a function from the userpoints module to parse the url:

   function hook_selector_parse_input($form_state, $tid = NULL) {
  // Enforce tid if passed in through the URL.
  $values = isset($form_state['values']) ? $form_state['values'] : array('tid' => NULL);
  if (!isset($values['tid'])) {
    if (isset($_GET['tid'])) {
      $values['tid'] = $_GET['tid'];
    }
    elseif ($tid) {
      $values['tid'] = $tid;
    }
  }

  if (isset($values['tid'])) {
    // Add tid argument to GET.
    $_GET['tid'] = $values['tid'];
  }

  return $values;
}
0

If you use ajax pager with form you need

  1. make menu callback in hook_menu

    $items['mymodule/ajax/pager'] = array(
      'page callback'  => 'list_form_updated',
      'type' => MENU_CALLBACK,
      'delivery callback' => 'ajax_deliver',
      'theme callback' => 'ajax_base_page_theme',
    );
    
  2. ajaxify select pager list manually

    jQuery('#list-form nav a').once('pager').each( function(){
    
      var url = jQuery(this).attr('href'); url_param = url.split('?'),
      // get the form_build_id 
      form_build_id = jQuery('form#list input[name="form_build_id"]').attr('value');
    
      Drupal.ajax['pager'] = new Drupal.ajax(false, this, {
        url : window.location.protocol + '//' + window.location.hostname + '/mymodule/nojs/pager' + (typeof url_param[1] != undefined ? '?' + url_param[1] : ''),
        event : 'click'
      });
      // form_build_id for ajax_get_form() in $_POST('form_build_id;');
      Drupal.ajax['pager'].options.data.form_build_id = form_build_id;
    
    });
    
  3. render form element and return it with ajax command

    /**
     * Pager callback for Pager form.
     */
    function list_form_updated($type = 'ajax') {
      // in this point we have right $_POST array to get the form & $_GET for pager
      list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
    
      $form['updated']['list']['#options'] = _list_form_ajax_get_options($form, $form_state);
    
      $wrapper = 'list-form';
      $output = drupal_render($form['updated']);
    
      $commands[] = ajax_command_replace('#' . $wrapper, $output);
    
      return array('#type' => 'ajax', '#commands' => $commands);
    
    }
    

That is. Thnx

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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