public function EntityReferenceLayoutWidget::formElement in Entity Reference with Layout 8
Builds the widget form array for an individual item.
Overrides WidgetInterface::formElement
File
- src/
Plugin/ Field/ FieldWidget/ EntityReferenceLayoutWidget.php, line 497
Class
- EntityReferenceLayoutWidget
- Entity Reference with Layout field widget.
Namespace
Drupal\entity_reference_layout\Plugin\Field\FieldWidgetCode
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$parents = $form['#parents'];
$widget_state = static::getWidgetState($parents, $this->fieldName, $form_state);
$handler_settings = $items
->getSetting('handler_settings');
$layout_bundles = $handler_settings['layout_bundles'] ?? [];
if (empty($widget_state['items'][$delta]['entity'])) {
return [];
}
// Flatten layouts array for use with radio buttons.
$available_layouts = [];
foreach ($handler_settings['allowed_layouts'] as $group) {
foreach ($group as $layout_id => $layout_name) {
$available_layouts[$layout_id] = $layout_name;
}
}
/** @var \Drupal\paragraphs\ParagraphInterface $entity */
$entity = !empty($widget_state['items'][$delta]) ? $widget_state['items'][$delta]['entity'] : NULL;
$options = !empty($widget_state['items'][$delta]['options']) ? $widget_state['items'][$delta]['options'] : [];
$config = !empty($widget_state['items'][$delta]['config']) ? $widget_state['items'][$delta]['config'] : [];
$layout_path = array_merge($parents, [
$this->fieldName,
$delta,
'entity_form',
'layout_selection',
'layout',
]);
if (!($layout = $form_state
->getValue($layout_path))) {
$layout = !empty($widget_state['items'][$delta]['layout']) ? $widget_state['items'][$delta]['layout'] : NULL;
}
$element = [
'#type' => 'container',
'#delta' => $delta,
'#entity' => $entity,
'#layout' => !empty($items[$delta]->layout) ? $items[$delta]->layout : '',
'#region' => !empty($items[$delta]->region) ? $items[$delta]->region : '',
'#layout_options' => $items[$delta]->options ?? [],
'#attributes' => [
'class' => [
'erl-item',
'erl-item--' . ($entity
->isPublished() ? 'published' : 'unpublished'),
],
'id' => [
$this->fieldName . '--item-' . $delta,
],
],
'region' => [
'#type' => 'hidden',
'#attributes' => [
'class' => [
'erl-region',
],
],
'#default_value' => !empty($items[$delta]->region) ? $items[$delta]->region : '',
],
// These properties aren't modified by the main form,
// but are modified when a user edits a specific item.
'entity' => [
'#type' => 'value',
'#value' => $entity,
],
'config' => [
'#type' => 'value',
'#value' => $config,
],
'options' => [
'#type' => 'value',
'#value' => $options,
],
'layout' => [
'#type' => 'value',
'#value' => $layout,
],
'#process' => [],
];
// Add Edit and Remove button, if the current user has appropriate
// permissions.
if ($this->currentUser
->hasPermission('manage entity reference layout sections')) {
$element['actions'] = [
'#type' => 'container',
'#weight' => -1000,
'#attributes' => [
'class' => [
'erl-actions',
],
],
'edit' => [
'#type' => 'submit',
'#name' => 'edit_' . $this->fieldName . '_' . $delta,
'#value' => $this
->t('Edit'),
'#attributes' => [
'class' => [
'erl-edit',
],
],
'#limit_validation_errors' => [
array_merge($parents, [
$this->fieldName,
]),
],
'#submit' => [
[
$this,
'editItemSubmit',
],
],
'#delta' => $delta,
'#ajax' => [
'callback' => [
$this,
'elementAjax',
],
'wrapper' => $this->wrapperId,
'progress' => 'none',
],
'#element_parents' => $parents,
],
'remove' => [
'#type' => 'submit',
'#name' => 'remove_' . $this->fieldName . '_' . $delta,
'#value' => $this
->t('Remove'),
'#attributes' => [
'class' => [
'erl-remove',
],
],
'#limit_validation_errors' => [
array_merge($parents, [
$this->fieldName,
]),
],
'#submit' => [
[
$this,
'removeItemSubmit',
],
],
'#delta' => $delta,
'#ajax' => [
'callback' => [
$this,
'elementAjax',
],
'wrapper' => $this->wrapperId,
'progress' => 'none',
],
'#element_parents' => $parents,
],
];
}
// If this is a new entity, pass the region and parent
// item's weight to the theme.
if (!empty($widget_state['items'][$delta]['is_new'])) {
$element['#is_new'] = TRUE;
$element['#new_region'] = $widget_state['items'][$delta]['new_region'];
$element['#parent_weight'] = $widget_state['items'][$delta]['parent_weight'];
$element['#attributes']['class'][] = 'js-hide';
}
// Build the preview and render it in the form.
$preview = [];
if (isset($entity)) {
$preview_view_mode = $this
->getSetting('preview_view_mode');
$view_builder = $this->entityTypeManager
->getViewBuilder($entity
->getEntityTypeId());
$preview = $view_builder
->view($entity, $preview_view_mode);
$preview['#cache']['max-age'] = 0;
}
$element += [
'preview' => $preview,
];
// Add remove confirmation form if we're removing.
if (isset($widget_state['remove_item']) && $widget_state['remove_item'] === $delta) {
$element['remove_form'] = [
'#prefix' => '<div class="erl-form">',
'#suffix' => '</div>',
'#type' => 'container',
'#attributes' => [
'data-dialog-title' => [
$this
->t('Confirm removal'),
],
],
'message' => [
'#type' => 'markup',
'#markup' => $this
->t('Are you sure you want to permanently remove this <b>@type?</b><br />This action cannot be undone.', [
'@type' => $entity->type->entity
->label(),
]),
],
'actions' => [
'#type' => 'container',
'confirm' => [
'#type' => 'submit',
'#value' => $this
->t('Remove'),
'#delta' => $delta,
'#submit' => [
[
$this,
'removeItemConfirmSubmit',
],
],
'#ajax' => [
'callback' => [
$this,
'elementAjax',
],
'wrapper' => $this->wrapperId,
],
'#element_parents' => $parents,
],
'cancel' => [
'#type' => 'submit',
'#value' => $this
->t('Cancel'),
'#delta' => $delta,
'#submit' => [
[
$this,
'removeItemCancelSubmit',
],
],
'#attributes' => [
'class' => [
'erl-cancel',
'button--danger',
],
],
'#ajax' => [
'callback' => [
$this,
'elementAjax',
],
'wrapper' => $this->wrapperId,
],
'#element_parents' => $parents,
],
],
'#weight' => 1000,
'#delta' => $delta,
];
}
// Add edit form if open.
if (isset($widget_state['open_form']) && $widget_state['open_form'] === $delta) {
$display = EntityFormDisplay::collectRenderDisplay($entity, 'default');
$bundle_label = $entity->type->entity
->label();
$element['entity_form'] = [
'#prefix' => '<div class="erl-form entity-type-' . $entity
->bundle() . '">',
'#suffix' => '</div>',
'#type' => 'container',
'#parents' => array_merge($parents, [
$this->fieldName,
$delta,
'entity_form',
]),
'#weight' => 1000,
'#delta' => $delta,
'#display' => $display,
'#attributes' => [
'data-dialog-title' => [
$entity
->id() ? $this
->t('Edit @type', [
'@type' => $bundle_label,
]) : $this
->t('Create new @type', [
'@type' => $bundle_label,
]),
],
],
];
// Support for Field Group module based on Paragraphs module.
// @todo Remove as part of https://www.drupal.org/node/2640056
if (\Drupal::moduleHandler()
->moduleExists('field_group')) {
$context = [
'entity_type' => $entity
->getEntityTypeId(),
'bundle' => $entity
->bundle(),
'entity' => $entity,
'context' => 'form',
'display_context' => 'form',
'mode' => $display
->getMode(),
];
field_group_attach_groups($element['entity_form'], $context);
if (function_exists('field_group_form_pre_render')) {
$element['entity_form']['#pre_render'][] = 'field_group_form_pre_render';
}
if (function_exists('field_group_form_process')) {
$element['entity_form']['#process'][] = 'field_group_form_process';
}
}
$display
->buildForm($entity, $element['entity_form'], $form_state);
// Add the layout plugin form if applicable.
if (in_array($entity
->bundle(), $layout_bundles)) {
$element['entity_form']['layout_selection'] = [
'#type' => 'container',
'layout' => [
'#weight' => -100,
'#type' => 'radios',
'#title' => $this
->t('Select a layout:'),
'#options' => $available_layouts,
'#default_value' => $layout,
'#attributes' => [
'class' => [
'erl-layout-select',
],
],
'#required' => TRUE,
'#after_build' => [
[
get_class($this),
'processLayoutOptions',
],
],
],
'update' => [
'#type' => 'submit',
'#value' => $this
->t('Update'),
'#name' => 'update_layout',
'#delta' => $element['#delta'],
'#limit_validation_errors' => [
array_merge($parents, [
$this->fieldName,
$delta,
'entity_form',
'layout_selection',
]),
],
'#submit' => [
[
$this,
'editItemSubmit',
],
],
'#attributes' => [
'class' => [
'js-hide',
],
],
'#element_parents' => $parents,
],
];
// Switching layouts should change the layout plugin options form
// with Ajax for users with adequate permissions.
$element['entity_form']['layout_selection']['layout']['#ajax'] = [
'event' => 'change',
'callback' => [
$this,
'buildLayoutConfigurationFormAjax',
],
'trigger_as' => [
'name' => 'update_layout',
],
'wrapper' => 'layout-config',
'progress' => 'none',
];
$element['entity_form']['layout_selection']['update']['#ajax'] = [
'callback' => [
$this,
'buildLayoutConfigurationFormAjax',
],
'wrapper' => 'layout-config',
'progress' => 'none',
];
$element['entity_form']['layout_plugin_form'] = [
'#prefix' => '<div id="layout-config">',
'#suffix' => '</div>',
'#access' => $this->currentUser
->hasPermission('manage entity reference layout sections'),
];
// Add the layout configuration form if applicable.
if (!empty($layout)) {
try {
$layout_instance = $this->layoutPluginManager
->createInstance($layout, $config);
if ($layout_plugin = $this
->getLayoutPluginForm($layout_instance)) {
$element['entity_form']['layout_plugin_form'] += [
'#type' => 'details',
'#title' => $this
->t('Layout Configuration'),
];
$element['entity_form']['layout_plugin_form'] += $layout_plugin
->buildConfigurationForm([], $form_state);
}
} catch (\Exception $e) {
watchdog_exception('Erl, add the layout configuration form', $e);
}
}
// Add the additional options form if applicable.
// This is deprecated and included only for backwards compatibility.
if ($this
->getSetting('always_show_options_form')) {
// Other layout options.
$element['entity_form']['options'] = [
'#type' => 'details',
'#title' => $this
->t('Basic Layout Options'),
'#description' => $this
->t('Classes will be applied to the container for this field item.'),
'#open' => FALSE,
];
$element['entity_form']['options']['container_classes'] = [
'#type' => 'textfield',
'#title' => $this
->t('Custom Classes for Layout Container'),
'#description' => $this
->t('Classes will be applied to the container for this field item.'),
'#size' => 50,
'#default_value' => $options['options']['container_classes'] ?? '',
'#placeholder' => $this
->t('CSS Classes'),
];
$element['entity_form']['options']['bg_color'] = [
'#type' => 'textfield',
'#title' => $this
->t('Background Color for Layout Container'),
'#description' => $this
->t('Background will be applied to the layout container.'),
'#size' => 10,
'#default_value' => $options['options']['bg_color'] ?? '',
'#placeholder' => $this
->t('Hex Code'),
];
}
}
$paragraphs_type = $entity
->getParagraphType();
// @todo: Check translation functionality.
if ($paragraphs_type && $this->currentUser
->hasPermission('edit behavior plugin settings') && (!$this->isTranslating || !$entity
->isDefaultTranslationAffectedOnly()) && ($behavior_plugins = $paragraphs_type
->getEnabledBehaviorPlugins())) {
$element['entity_form']['behavior_plugins'] = [
'#type' => 'details',
'#title' => $this
->t('Behaviors'),
'#element_validate' => [
[
$this,
'validateBehaviors',
],
],
'#entity' => $entity,
];
$has_behavior_form_options = FALSE;
/* @var \Drupal\paragraphs\ParagraphsBehaviorInterface $plugin */
foreach ($behavior_plugins as $plugin_id => $plugin) {
$element['entity_form']['behavior_plugins'][$plugin_id] = [
'#type' => 'container',
];
$subform_state = SubformState::createForSubform($element['entity_form']['behavior_plugins'][$plugin_id], $form, $form_state);
$plugin_form = $plugin
->buildBehaviorForm($entity, $element['entity_form']['behavior_plugins'][$plugin_id], $subform_state);
if (!empty(Element::children($plugin_form))) {
$element['entity_form']['behavior_plugins'][$plugin_id] = $plugin_form;
$has_behavior_form_options = TRUE;
}
}
// No behaviors were added, remove the behavior form.
if (!$has_behavior_form_options) {
unset($element['entity_form']['behavior_plugins']);
}
}
// Add save, cancel, etc.
$element['entity_form'] += [
'actions' => [
'#weight' => 1000,
'#type' => 'container',
'#attributes' => [
'class' => [
'erl-item-form-actions',
],
],
'save_item' => [
'#type' => 'submit',
'#name' => 'save',
'#value' => $this
->t('Save'),
'#delta' => $element['#delta'],
'#limit_validation_errors' => [
array_merge($parents, [
$this->fieldName,
]),
],
'#submit' => [
[
$this,
'saveItemSubmit',
],
],
'#ajax' => [
'callback' => [
$this,
'elementAjax',
],
'wrapper' => $this->wrapperId,
'progress' => 'none',
],
'#element_parents' => $parents,
],
'cancel' => [
'#type' => 'submit',
'#name' => 'cancel',
'#value' => $this
->t('Cancel'),
'#limit_validation_errors' => [],
'#delta' => $element['#delta'],
'#submit' => [
[
$this,
'cancelItemSubmit',
],
],
'#attributes' => [
'class' => [
'erl-cancel',
'button--danger',
],
],
'#ajax' => [
'callback' => [
$this,
'elementAjax',
],
'wrapper' => $this->wrapperId,
'progress' => 'none',
],
'#element_parents' => $parents,
],
],
];
$hide_untranslatable_fields = $entity
->isDefaultTranslationAffectedOnly();
foreach (Element::children($element['entity_form']) as $field) {
if ($entity
->hasField($field)) {
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$field_definition = $entity
->get($field)
->getFieldDefinition();
$translatable = $entity->{$field}
->getFieldDefinition()
->isTranslatable();
// Do a check if we have to add a class to the form element. We need
// those classes (paragraphs-content and paragraphs-behavior) to show
// and hide elements, depending of the active perspective.
// We need them to filter out entity reference revisions fields that
// reference paragraphs, cause otherwise we have problems with showing
// and hiding the right fields in nested paragraphs.
$is_paragraph_field = FALSE;
if ($field_definition
->getType() == 'entity_reference_revisions') {
// Check if we are referencing paragraphs.
if ($field_definition
->getSetting('target_type') == 'paragraph') {
$is_paragraph_field = TRUE;
}
}
if (!$translatable && $this->isTranslating && !$is_paragraph_field) {
if ($hide_untranslatable_fields) {
$element['entity_form'][$field]['#access'] = FALSE;
}
else {
$element['entity_form'][$field]['widget']['#after_build'][] = [
static::class,
'addTranslatabilityClue',
];
}
}
}
}
}
return $element;
}