web-dev-qa-db-ja.com

ajaxを使用してフォームステップ間でノードフォームデータを保存するにはどうすればよいですか?

Ajaxを使用してノードフォームをマルチステップフォームに変換したい。 ajax対応の送信ボタンと_hook_form_alter_(下記参照)にいくつかのロジックを追加して、インクリメントとデクリメントのステップを処理し、現在のステップに基づいてフォーム項目を表示および非表示にします。前後のステップは、正しい要素をレンダリングするという点で非常に効果的です。

問題は、1つのステップでデータを入力し、前後に移動してから元のステップに戻ると、入力したデータがフォームから失われることです。 FormStateInterface::getValue()を使用してデータを入力したアイテムの値を確認すると、元のステップを離れるときに変更フックが実行されると、データは存在しますが、後続の次の遷移/戻る遷移が行われます。この問題は、すでにフォームにあるデータ(たとえば、既存のノードを編集するとき)にも当てはまりません。

明らかに、受け取った値をどこかに保存する必要がありますが、どこに?そして、どのようにして適切なステップでフォーム要素に復元しますか?

私のコード(最小限の例):

_<?php
/**
 * Implements hook_form_FORM_ID_alter().
 */
function my_module_form_node_ajax_test_form_alter(&$form, $form_state) {
  // Define steps. Each child array is a step, and each string in the
  // a child array is a field that should be present on that step.
  $steps = [
    ['title'],
    ['body'],
  ];
  // Add wrapper.
  $form['#prefix'] = '<div id="ajax-form-wrapper">';
  $form['#suffix'] = '</div>';
  // If the step is not set, this is step 0.
  if (!$form_state->has('step')) {
    $form_state->set('step', 0);
  }
  // Get the current step.
  $step = $form_state->get('step');
  // If we have a triggering element, increment or decrement step as
  // appropriate.
  if ($trigger = $form_state->getTriggeringElement()) {
    switch ($trigger['#name']) {
      case 'next':
        $form_state->set('step', ++$step);
        break;

      case 'back':
        $form_state->set('step', --$step);
        break;
    }
  }
  // Show elements in the current step, hide others. Always show the
  // `actions` element, and all `form_*` elements.      
  foreach (Element::children($form) as $element) {
    $form[$element]['#access'] = (
      in_array($element, $steps[$step])
      || $element == 'actions'
      || strpos($element, 'form') === 0
    );
  }
  // Show the back button if this is not the first step.
  $form['actions']['back'] = $step > 0 ? ajax_button('back') : NULL;
  // Show the next button if this is not the last step.
  $is_last_step = $step == count($steps) - 1;
  $form['actions']['next'] = !$is_last_step ? ajax_button('next') : NULL;
  // Show the submit button if this is the last step.
  $form['actions']['submit']['#access'] = $is_last_step;
}

/**
 * Return a render array for an ajax step button.
 */
function ajax_button($name) {
  return [
    '#type' => 'button',
    '#value' => $name,
    '#name' => $name,
    '#ajax' => [
      'wrapper' => 'ajax-form-wrapper',
    ],
  ];
}
_
5
Xaq

さて、私はそれを理解しました。 (これは、@ 4k4の回答の前でした。これは、私が最終的に得た内容に非常に近いものです)。

送信されたフォームの値を使用してエンティティを構築し、送信ハンドラでフォームを再構築(エンティティの値をフォームに入れる)するように設定する必要がありました。ここに私がそれをした方法があります:

<?php
/**
 * Return a render array for an ajax step button.
 */
function ajax_button($name) {
  return [
    // Changed from 'button', buttons don't invoke submit handlers, 
    // submits do.
    '#type' => 'submit',
    '#value' => $name,
    '#name' => $name,
    // Set the submit handler.
    '#submit' = ['my_module_ajax_form_submit'],
    '#ajax' => [
      'wrapper' => 'ajax-form-wrapper',
    ],
  ];
}

/**
 * Build entity using submitted form data and rebuild form.
 */
function my_module_ajax_form_submit($form, $form_state) {
  // Get the NodeForm object.
  $form_object = $form_state->getFormObject();
  // NodeForm::buildEntity() maps form values into the entity object
  // and returns it.
  $entity = $form_object->buildEntity($form, $form_state);
  // NodeForm::setEntity() stores the entity in the form object which 
  // is used to populate form fields when the form rebuilds.
  $form_object->setEntity($entity);
  // Set the form to rebuild.
  $form_state->setRebuild()
}

NodeForm::buildEntity()::buildForm()のさまざまな実装とその祖先 ContentEntityForm および EntityForm

1
Xaq

このコードは非常に印象的です。フォームの変更フックに数行のマルチステップエンティティフォームを作成することは不可能だと思いました。これをシンプルに保つために、エンティティオブジェクトを使用して値を格納することができます。フォームオブジェクトはステップ間でシリアル化およびキャッシュされ、送信された値からそのようなエンティティを構築するメソッドはエンティティフォームクラスの一部であるためです。

  if ($trigger = $form_state->getTriggeringElement()) {
    $form_object = $form_state->getFormObject();
    $new_entity = $form_object->buildEntity($form, $form_state);
    $form_object->setEntity($new_entity);
3
4k4

Symfonyセッションを使用する必要があると思います

セッション変数を設定する

$session = \Drupal::request()->getSession();
$session->set('mymodule_variable', 'value');

後であなたはこの変数をこのように得ることができます

$session = \Drupal::request()->getSession();
$value = $session->get('mymodule_variable', 'default_value');

Symfonyセッションはphp $ _SESSIONに相当します

0
GiorgosK

これを行う正しい方法は PrivateTempStore を使用するようです。Coreで CommentAdminOverview を表示すると、次のようになります。

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
      ...
      $info = [];
      /** @var \Drupal\comment\CommentInterface $comment */
      foreach ($comments as $comment) {
        $langcode = $comment->language()->getId();
        $info[$comment->id()][$langcode] = $langcode;
      }
      $this->tempStoreFactory
        ->get('comment_multiple_delete_confirm')
        ->set($this->currentUser()->id(), $info);
      $form_state->setRedirect('comment.multiple_delete_confirm');
    }
  }

このページでは、削除してリダイレクトするコメントIDを収集します。コメントの削除は、次のコードを持つcomment.multiple_delete_confirmConfirmDeleteMultiple )で行われます。

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $this->commentInfo = $this->tempStoreFactory->get('comment_multiple_delete_confirm')->get($this->currentUser()->id());
    if (empty($this->commentInfo)) {
      return $this->redirect('comment.admin');
    }
    /** @var \Drupal\comment\CommentInterface[] $comments */
    $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo));
  ...
  }

ビューはPrivateTempStorageとも連携します。これが、保存されていないビューに戻ったときに、変更を再度確認できる理由です。

0