【问题标题】:Drupal 7 node_save() causes AJAX to failDrupal 7 node_save() 导致 AJAX 失败
【发布时间】:2026-02-08 03:15:01
【问题描述】:

我创建了自定义节点表单,只附加了两个字段。每个字段都有自己的“保存”AJAX 按钮。单击“保存”按钮,一切都好像是默认节点表单提交。完整代码如下:

/**
 * Form;
 */
function mymodule_custom_form($form, &$form_state) {
  $node = node_load(123);
  $node->langcode = entity_language('node', $node);

  // Store node object in form state
  if (!isset($form_state['node'])) {
    if (!isset($node->title)) {
      $node->title = NULL;
    }
    node_object_prepare($node);
    $form_state['node'] = $node;
  }
  else {
    $node = $form_state['node'];
  }

  // Basic node information.
  // These elements are just values so they are not even sent to the client.
  $properties = array('nid', 'vid', 'uid', 'created', 'type', 'language');
  foreach ($properties as $key) {
    $form[$key] = array(
      '#type' => 'value',
      '#value' => isset($node->$key) ? $node->$key : NULL,
    );
  }

  // Changed must be sent to the client, for later overwrite error checking.
  $form['changed'] = array(
    '#type' => 'hidden',
    '#default_value' => isset($node->changed) ? $node->changed : NULL,
  );

  // TEST 1 field
  field_attach_form('node', $node, $form, $form_state, $node->langcode, array(
    'field_name' => 'field_test_1'
  ));

  // Set the form prefix and suffix to support AJAX
  $form['field_test_1']['#prefix'] = '<div id="wrapper-field-test-1">';
  $form['field_test_1']['#suffix'] = '</div>';

  // the submit button
  $form['field_test_1']['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#name' => 'button-field-test-1',
    '#ajax' => array(
      'callback' => 'mymodule_custom_form_ajax_submit',
      'wrapper' => 'wrapper-field-test-1',
      'method' => 'replace',
      'effect' => 'fade',
    )
  );

  // TEST 2 field
  field_attach_form('node', $node, $form, $form_state, $node->langcode, array(
    'field_name' => 'field_test_2'
  ));

  // Set the form prefix and suffix to support AJAX
  $form['field_test_2']['#prefix'] = '<div id="wrapper-field-test-2">';
  $form['field_test_2']['#suffix'] = '</div>';

  // the submit button
  $form['field_test_2']['save'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#name' => 'button-field-test-2',
    '#ajax' => array(
      'callback' => 'mymodule_custom_form_ajax_submit',
      'wrapper' => 'wrapper-field-test-2',
      'method' => 'replace',
      'effect' => 'fade',
    )
  );

  return $form;
}

/**
 * Form validate;
 */
function mymodule_custom_form_validate($form, &$form_state) {
  $field_name = reset($form_state['triggering_element']['#parents']);

  // Validate only the stuff we need
  $fields = array(
    'field_test_1',
    'field_test_2'
  );
  foreach ($fields as $field => $bundle) {
    if ($field_name != $field) {
      unset($form_state['values'][$field], $form_state['input'][$field]);
    }
  }

  // $form_state['node'] contains the actual entity being edited, but we must
  // not update it with form values that have not yet been validated, so we
  // create a pseudo-entity to use during validation.
  $node = (object) $form_state['values'];
  node_validate($node, $form, $form_state);
  entity_form_field_validate('node', $form, $form_state);
}

/**
 * Form submit;
 */
function mymodule_custom_form_submit($form, &$form_state) {
  // Execute all submit functions
  $node = $form_state['node'];
  entity_form_submit_build_entity('node', $node, $form, $form_state);

  node_submit($node);
  foreach (module_implements('node_submit') as $module) {
    $function = $module . '_node_submit';
    $function($node, $form, $form_state);
  }

  // Save the node
  node_save($node);

  $form_state['values']['nid'] = $node->nid;
  $form_state['nid'] = $node->nid;
}

/**
 * Form ajax submit;
 */
function mymodule_custom_form_ajax_submit($form, &$form_state) {
  $field_name = reset($form_state['triggering_element']['#parents']);

  // validate the form
  drupal_validate_form('mymodule_custom_form', $form, $form_state);
  // if there are errors, return the form to display the error messages
  if (form_get_errors()) {
    $form_state['rebuild'] = TRUE;
    return $form[$field_name];
  }
  // process the form
  mymodule_custom_form_submit($form, $form_state);

  // Show the processing box
  $form[$field_name] = array('#markup' => 'Thanks!');
  $form[$field_name]['#prefix'] = '<div id="wrapper-' . str_replace('_', '-', $field_name) . '">';
  $form[$field_name]['#suffix'] = '</div>';

  // return the confirmation message
  return $form[$field_name];
}

代码完美运行,除了node_save($node) 导致The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved. 错误。

没有错误,如果我删除它。但是我需要保存节点并触发所有的钩子。

【问题讨论】:

    标签: ajax drupal drupal-7


    【解决方案1】:

    我认为这行引起的问题:

     // process the form
     mymodule_custom_form_submit($form, $form_state);
    

    在您的 ajax 函数中,尝试在您的 ajax 函数中使用 node_save。 mymodule_custom_form_submit 是通常使用的常规钩子。似乎是同时进行了几个保存过程。

    【讨论】:

    • 它没有帮助。我的猜测是,当表单第一次构建时,所有field_attach_form 调用都使用$node 对象,该对象在首次提交后更新。此更新可能会导致已附加的字段变旧?
    • 我认为是的,所以尝试评论一些行并调试以确定哪个进程导致您的问题
    【解决方案2】:

    我不知道你是否解决了它,但我一直陷入类似的情况。

    我避免了错误:此页面上的内容已被修改...,正在修改$form_state 中的已更改 值。在您的提交函数中:mymodule_custom_form_submit紧随其后 node_save($node),添加此行:

    $form_state['input']['changed'] = $node->changed;
    

    【讨论】:

      【解决方案3】:

      Drupal 7 完整的 Save & Stay 回调函数,请为表单添加包装器:

      function <MODULE_NAME>_node_ajax_save_callback($form, &$form_state){
      
        // If error, return form.
        if (form_get_errors()) {
          return $form;
        }
      
        node_form_submit($form, $form_state);
        $form['changed']['#value'] = $form_state['node']->changed;
      
        return $form;
      }
      

      【讨论】:

        最近更新 更多