I have a admin settings form as part of my module. On the form is a button which when clicked scans the system for nodes of a certain criteria, and the displays the number and titles of these particular nodes on the admin settings form using ajax.

The problem is that the first time I click the 'scan' button, the correct results are shown. How ever if I manually delete some of the nodes from the database and then click on the button again, it returns the same results as the first time the button was clicked. No matter how many times I click the button the same initial results are display. However, if I refresh the form (e.g. reload the page) and then click on the button, then the correct, updated results are show.

It is almost as if the results of the ajax function are cached, and only after refreshing a page will it rebuild the results. Is there some way I can stop this happening?

function namecards_admin_form($form, &$form_state) {
  $form['#title'] = 'Namecards module settings';
  $form['#description'] = t('General setting for the Namecards module');
  $form['namecards_scan_for_orphaned_nodes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Scan for orphaned nodes'),
    '#description' => t('This function should be run if organizations or events appear which do not contain any contacts (i.e. they have become orphaned).'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['namecards_scan_for_orphaned_nodes']['scan'] = array(
    '#type' => 'submit',
    '#prefix' => '<div class="namecards-scan-orphan-nodes-button">',
    '#value' => t('Scan'),
    '#suffix' => '</div>',
    '#submit' => array(),
    '#ajax' => array(
      'callback' => 'namecards_scan_for_orphan_nodes_ajax',
      'wrapper' => 'namecards-scan-orphan-nodes-results',
      'method' => 'replace',
      'progress' => array(
        'type' => 'throbber',
        'message' => t('Scanning...'),
      ),
    ),
  );
  $form['namecards_scan_for_orphaned_nodes']['scan_results'] = array(
    '#type' => 'markup',
    '#prefix' => '<div id="namecards-scan-orphan-nodes-results">',
    '#markup' => (isset($form_state['namecards']['scan_results'])) ? $form_state['namecards']['scan_results'] : '',
    '#suffix' => '</div>',
  );
  $form['advanced_options'] = array(
    '#type' => 'fieldset',
    '#title' => t('Advanced options'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['#submit'][] = 'namecards_admin_form_submit';
  return system_settings_form($form);
}

function namecards_scan_for_orphan_nodes_ajax($form, $form_state) {
  $output = '';
  $output .= _namecards_scan_for_orphan_nodes();

  return $output;
}

function _namecards_scan_for_orphan_nodes() {
  $output = '';

  // Find and delete orphaned nodes.
  $node_types_descriptor = array('organization', 'event', 'position', 'department');
  foreach ($node_types_descriptor as $str) {
    $sql = db_select('node', 'n');
    $tbl_alias = $sql->leftJoin('field_data_namecards_namecard_' . $str, 'fdnno', 'n.nid = %alias.namecards_namecard_' . $str . '_nid');
    $results = $sql
      ->fields('n', array('nid', 'title'))
      ->condition('n.type', 'namecards_' . $str)
      ->isNull($tbl_alias . '.entity_id')
      ->execute();

    $orphaned_nids = array();
    $orphaned_titles = array();
    foreach ($results as $result) {
      $orphaned_nids[] = $result->nid;
      $orphaned_titles[] = $result->title;
    }

    if (count($orphaned_nids) > 0) {
      $output .= t('Orphaned :types found: :count ( :titles )', array(':type' => t($str), ':count' => count($orphaned_nids), ':titles' => implode(', ', $orphaned_titles))) . '<br />';
//      $num_deleted = db_delete('node')
//        ->condition('nid', $orphaned_nids, 'IN')
//        ->execute();
//      $output .= 'Orphaned organizations deleted: ' . $num_deleted . '<br/>';
    }
  }

  // Set default message if no results are found.
  if (empty($output)) {
    $output = 'No orphaned nodes found.';
  }

  return $output;
}

Comments

tecjam’s picture

I am also experiencing the same issue with ajax callbacks. The first result stays even if you repeat the callback with a different value.

http://drupal.org/node/1330520

I have not found a solution yet =(

begun’s picture

Finally worked it all out. Special thanks to Randyfay for putting me on the right track (http://randyfay.com/comment/2128#comment-2128). Turns out I had a fundamental misunderstanding on how #ajax worked in forms and in particular the relationship between the submit callback and the ajax callback functions. I was under the impression that one was an alternative to the other (e.g. if the ajax callback was called then the submit call back would not be). But the reality is that the ajax callback complements the submit function.

In my case I was dealing with ajax on a form submit button. The idea was that one clicks the button, then using ajax, certain calculated results are return and placed inside another form element (in this case a markup element).

To achieve the above, what you essentially need to do is rebuild your form as you would normally without ajax. In this case the form submit function ("namecards_scan_for_orphan_nodes_submit") sets the rebuild property to true, so that the form will be rebuilt . There is already logic in place within the form definition function ("namecards_admin_form"), which determines what content should be shown in the markup element. So up until now everything is working without ajax (in fact, by doing this you have already created the default behavior for the form when javascript is disabled).

When the #ajax property is added to the submit button, you are adding an additional function, which basically tells the system not to reload the form, that you just rebuilt, but rather just take an element of that form (in this case "$form['namecards_scan_for_orphaned_nodes']['scan_results']") and put it inside the element defined in the wrapper property. In other words, you are replacing the existing markup element with the rebuild version of the same element.

Hopefully this might help others who might be stumbling with this.

Here is the actual code:

function namecards_admin_form($form, &$form_state) {
  $form['#title'] = 'Namecards module settings';
  $form['#description'] = t('General setting for the Namecards module');
  $form['namecards_scan_for_orphaned_nodes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Scan for orphaned nodes'),
    '#description' => t('This function should be run if organizations or events appear which do not contain any contacts (i.e. they have become orphaned).'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );
  $form['namecards_scan_for_orphaned_nodes']['scan'] = array(
    '#type' => 'submit',
    '#prefix' => '<div class="namecards-scan-orphan-nodes-button">',
    '#value' => t('Scan'),
    '#suffix' => '</div>',
    '#submit' => array('namecards_scan_for_orphan_nodes_submit'),
    '#ajax' => array(
			'callback' => 'namecards_scan_for_orphan_nodes_ajax',
      'wrapper' => 'namecards-scan-orphan-nodes-results',
      'method' => 'replace',
      'progress' => array(
        'type' => 'throbber',
        'message' => t('Scanning...'),
      ),
    ),
  );
  $form['namecards_scan_for_orphaned_nodes']['scan_results'] = array(
    '#type' => 'markup',
    '#prefix' => '<div id="namecards-scan-orphan-nodes-results">',
    '#markup' => (!empty($form_state['values']['scan'])) ? _namecards_scan_for_orphan_nodes() : '',
    '#suffix' => '</div>',
  );
  return system_settings_form($form);
}

function _namecards_scan_for_orphan_nodes() {
  $output = '';

  // Find and delete orphaned nodes.
  $node_types_descriptor = array('organization', 'event', 'position', 'department');
  foreach ($node_types_descriptor as $str) {
    $sql = db_select('node', 'n');
    $tbl_alias = $sql->leftJoin('field_data_namecards_namecard_' . $str, 'fdnno', 'n.nid = %alias.namecards_namecard_' . $str . '_nid');
    $results = $sql
      ->fields('n', array('nid', 'title'))
      ->condition('n.type', 'namecards_' . $str)
      ->isNull($tbl_alias . '.entity_id')
      ->execute();

    $orphaned_nids = array();
    $orphaned_titles = array();
    foreach ($results as $result) {
      $orphaned_nids[] = $result->nid;
      $orphaned_titles[] = $result->title;
    }

    if (count($orphaned_nids) > 0) {
      $output .= t('Orphaned :types found: :count ( :titles )', array(':type' => t($str), ':count' => count($orphaned_nids), ':titles' => implode(', ', $orphaned_titles))) . '<br />';
    }
  }

  // Set default message if no results are found.
  if (empty($output)) {
    $output = t('No orphaned nodes found.');
  }

  return $output;
}

function namecards_scan_for_orphan_nodes_submit($form, &$form_state) {
  $form_state['rebuild'] = TRUE;
}

function namecards_scan_for_orphan_nodes_ajax($form, $form_state) {
  return $form['namecards_scan_for_orphaned_nodes']['scan_results'];
}