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.