Can't set default values for paragraphs in node forms

Created on 24 October 2024, 8 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

  • Issue created by @dariogcode
  • ๐Ÿ‡ฆ๐Ÿ‡ทArgentina dariogcode
  • ๐Ÿ‡ต๐Ÿ‡ญPhilippines ambot112

    Seems you missed the 'target_id' and 'target_revision_id` when setting the field value.

       $entity->set('field_order_lines', [
                'target_id' => $paragraph->id(),
                'target_revision_id' =>$paragraph->getRevisionId(),
              ]);
       $entity->save();
    
  • ๐Ÿ‡ฆ๐Ÿ‡ทArgentina dariogcode

    Thanks @ambot112 but it is supposed that field_order_lines is a multivalue field, means can have several paragraphs, and also we don't have id here because those aren't already crated paragraph, Iยดm just pre populating form.

Production build 0.71.5 2024