Can't set default values for paragraphs in node forms

Created on 24 October 2024, about 2 months ago

Problem/Motivation

In Drupal 10, when preloading Paragraph field items in a form using hook_form_alter or hook_entity_prepare_form, two issues occur:

1. Clicking "Add more" replaces existing preloaded Paragraph items with a new item, instead of appending it. No matter how many paragraphs you preload it reduce form to 2 elements.
2. If no new items are added, the preloaded Paragraphs are lost upon saving.

Steps to reproduce

Preload Paragraph field items using hook_form_alter or hook_entity_prepare_form.
Load the form and confirm the preloaded values are shown.
Click "Add more" and observe the replacement of existing paragraphs.
Save the form without adding new items, and notice that the preloaded paragraphs are not saved.

Here is my code

function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Form\FormStateInterface $form_state) {
  if ($entity->getEntityTypeId() === 'node' && $entity->bundle() === 'order') {
    $preloaded_values = \Drupal::service('tempstore.private')->get('my_module')->get('order_preload');

    if ($preloaded_values) {
      // Preloading paragraphs for field_order_lines.
      $order_lines = [];
      foreach ($preloaded_values['field_order_lines'] as $line) {
        $paragraph = Paragraph::create([
          'type' => 'order_line',
          'field_product' => $line['field_product'],
          'field_qty' => $line['field_qty'],
          'field_price' => $line['field_price'],
          'field_list_price' => $line['field_list_price'],
          'field_product_alt_name' => $line['field_product_alt_name'],
        ]);
        $order_lines[] = $paragraph;
      }

      $entity->set('field_order_lines', $order_lines);
    }
  }
}

Also I've tried using hook_form_alter, same behavior

/**
 * Implements hook_form_FORM_ID_alter() to preload values into the order form.
 */
function mk_generate_form_node_order_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Check if there are preloaded values in the session.
  $preloaded_values = \Drupal::service('tempstore.private')->get('mk_generate')->get('order_preload');

  if ($preloaded_values) {
    // Preload the title.
    $form['title']['#default_value'] = $preloaded_values['title'];
    $node = $form_state->getFormObject()->getEntity();
    
    // Preload the order lines.
    if (isset($preloaded_values['field_order_lines']) && !empty($preloaded_values['field_order_lines'])) {
      $entity_form_display = \Drupal::service('entity_type.manager')
      ->getStorage('entity_form_display')
      ->load('node.order.default');
  
      $paragraph_order_lines_form_display = \Drupal::service('entity_type.manager')
        ->getStorage('entity_form_display')
        ->load('paragraph.order_line.default');

      $widget = $entity_form_display->getRenderer('field_order_lines');
      $widget_state = $widget->getWidgetState($form['#parents'], 'field_order_lines', $form_state);  
      $items = $node->get('field_order_lines');
      $items->setValue([]);
      $paragraphs = [];
      foreach ($preloaded_values['field_order_lines'] as $index => $line) {
        $paragraph = Paragraph::create([
          'type' => 'order_line',
        ]);
        $widget_state['paragraphs'][$index] = [
          'entity' => $paragraph,
          'display' => $paragraph_order_lines_form_display,
          'mode' => 'edit',
          'original_delta' => $index + 1
        ];
        $paragraphs[] = $paragraph;
        $items->appendItem($paragraph);
      }
      
      $widget_state['items_count'] = count($preloaded_values['field_order_lines']);
      $widget_state['real_item_count'] = count($preloaded_values['field_order_lines']);
      $widget->setWidgetState($form['#parents'], 'field_order_lines', $form_state, $widget_state);
      $form['field_order_lines'] = $widget->form($items, $form, $form_state);
      $node->set('field_order_lines', $paragraphs);

      foreach ($preloaded_values['field_order_lines'] as $index => $line) {
        $form['field_order_lines']['widget'][$index]['subform']['field_product']['widget'][0]['target_id']['#default_value'] = \Drupal::entityTypeManager()->getStorage('node')->load($line['field_product']);
        $form['field_order_lines']['widget'][$index]['subform']['field_qty']['widget'][0]['value']['#default_value'] = $line['field_qty'];
        $form['field_order_lines']['widget'][$index]['subform']['field_price']['widget'][0]['value']['#default_value'] = $line['field_price'];
        $form['field_order_lines']['widget'][$index]['subform']['field_list_price']['widget'][0]['value']['#default_value'] = $line['field_list_price'];
        $form['field_order_lines']['widget'][$index]['subform']['field_product_alt_name']['widget'][0]['value']['#default_value'] = $line['field_product_alt_name'];
      }
    }
    // Preload field_date (assuming it's a single value date field).
    if (isset($preloaded_values['field_date'])) {
      $form['field_date']['widget'][0]['value']['#default_value'] = new DrupalDateTime($preloaded_values['field_date']); //'2024-10-23'; //$preloaded_values['field_date'];
    }

    // Preload field_customer (assuming it's an entity reference field).
    if (isset($preloaded_values['field_customer'])) {
      $form['field_customer']['widget'][0]['target_id']['#default_value'] = \Drupal::entityTypeManager()->getStorage('node')->load($preloaded_values['field_customer']);
    }

    $form_state->setRebuild(FALSE);
    // Clear the preloaded values from the session after they've been used.
    \Drupal::service('tempstore.private')->get('mk_generate')->delete('order_preload');
  }
}

hook_form_alter is more complex because you have to render the widget form and set widget state, but exactly same issue occurs in both cases.

๐Ÿ› Bug report
Status

Active

Version

1.18

Component

Code

Created by

๐Ÿ‡ฆ๐Ÿ‡ทArgentina dariogcode

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024