protected function LayoutParagraphsWidget::formMultipleElements in Layout Paragraphs 1.0.x
Builds the main widget form array container/wrapper.
Form elements for individual items are built by formElement().
Overrides WidgetBase::formMultipleElements
File
- src/
Plugin/ Field/ FieldWidget/ LayoutParagraphsWidget.php, line 498
Class
- LayoutParagraphsWidget
- Entity Reference with Layout field widget.
Namespace
Drupal\layout_paragraphs\Plugin\Field\FieldWidgetCode
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$parents = $form['#parents'];
$widget_state = static::getWidgetState($parents, $this->fieldName, $form_state);
$this->wrapperId = trim(Html::getId(implode('-', $parents) . '-' . $this->fieldName . '-wrapper'), '-');
$this->itemFormWrapperId = trim(Html::getId(implode('-', $parents) . '-' . $this->fieldName . '-form'), '-');
$target_bundles = array_keys($this
->getAllowedTypes());
$title = $this->fieldDefinition
->getLabel();
$description = FieldFilteredMarkup::create(\Drupal::token()
->replace($this->fieldDefinition
->getDescription()));
/** @var \Drupal\Core\Entity\ContentEntityInterface $host */
$host = $items
->getEntity();
// Detect if we are translating.
$this
->initIsTranslating($form_state, $host);
// Save items to widget state when the form first loads.
if (!isset($widget_state['items'])) {
$widget_state['items'] = [];
$widget_state['open_form'] = FALSE;
$widget_state['remove_item'] = FALSE;
/** @var \Drupal\entity_reference_revisions\Plugin\Field\FieldType\EntityReferenceRevisionsItem $item */
foreach ($items as $delta => $item) {
if ($paragraph = $item->entity) {
if ($item->entity instanceof ParagraphInterface) {
$langcode = $form_state
->get('langcode');
if (!$this->isTranslating) {
// Set the langcode if we are not translating.
$langcode_key = $item->entity
->getEntityType()
->getKey('langcode');
if ($item->entity
->get($langcode_key)->value != $langcode) {
// If a translation in the given language already exists,
// switch to that. If there is none yet, update the language.
if ($item->entity
->hasTranslation($langcode)) {
$item->entity = $item->entity
->getTranslation($langcode);
}
else {
$item->entity
->set($langcode_key, $langcode);
}
}
}
else {
// Add translation if missing for the target language.
if (!$item->entity
->hasTranslation($langcode)) {
// Get the selected translation of the paragraph entity.
$entity_langcode = $item->entity
->language()
->getId();
$source = $form_state
->get([
'content_translation',
'source',
]);
$source_langcode = $source ? $source
->getId() : $entity_langcode;
// Make sure the source language version is used if available.
// Fetching the translation without this check could lead valid
// scenario to have no paragraphs items in the source version of
// to an exception.
if ($item->entity
->hasTranslation($source_langcode)) {
$entity = $item->entity
->getTranslation($source_langcode);
}
// The paragraphs entity has no content translation source field
// if no paragraph entity field is translatable,
// even if the host is.
if ($item->entity
->hasField('content_translation_source')) {
// Initialise the translation with source language values.
$item->entity
->addTranslation($langcode, $entity
->toArray());
$translation = $item->entity
->getTranslation($langcode);
$manager = \Drupal::service('content_translation.manager');
$manager
->getTranslationMetadata($translation)
->setSource($item->entity
->language()
->getId());
}
}
// If any paragraphs type is translatable do not switch.
if ($item->entity
->hasField('content_translation_source')) {
// Switch the paragraph to the translation.
$item->entity = $item->entity
->getTranslation($langcode);
}
}
}
$widget_state['items'][$delta] = [
'entity' => $paragraph,
'weight' => $delta,
];
}
}
}
// Handle asymmetric translation if field is translatable
// by duplicating items for enabled languages.
if ($items
->getFieldDefinition()
->isTranslatable()) {
$langcode = $this->languageManager
->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
->getId();
foreach ($widget_state['items'] as $delta => $item) {
if (empty($item['entity']) || $item['entity']
->get('langcode')->value == $langcode) {
continue;
}
/* @var \Drupal\Core\Entity\EntityInterface $duplicate */
$duplicate = $item['entity']
->createDuplicate();
$duplicate
->set('langcode', $langcode);
$widget_state['items'][$delta]['entity'] = $duplicate;
}
}
static::setWidgetState($parents, $this->fieldName, $form_state, $widget_state);
$elements = [
'#field_name' => $this->fieldName,
'#required' => $this->fieldDefinition
->isRequired(),
'#title' => $title,
'#description' => $description,
'#attributes' => [
'class' => [
'layout-paragraphs-field',
],
],
'#type' => 'fieldset',
'#parents' => $form['#parents'],
'#id' => $this->wrapperId,
];
for ($delta = 0; $delta < $widget_state['items_count']; $delta++) {
$elements[$delta] = $this
->formSingleElement($items, $delta, [], $form, $form_state);
}
$elements['#after_build'][] = [
$this,
'buildLayouts',
];
// Add logic for new elements Add, if not in a translation context.
if ($this
->allowReferenceChanges()) {
// Button to add new section and other paragraphs.
$elements['add_more'] = [
'actions' => [
'#attributes' => [
'class' => [
'js-hide',
],
],
'#type' => 'container',
],
];
$bundle_info = $this->entityTypeBundleInfo
->getBundleInfo('paragraph');
$options = [];
$types = [
'layout' => [],
'content' => [],
];
$bundle_ids = $target_bundles;
$target_type = $items
->getSetting('target_type');
$definition = $this->entityTypeManager
->getDefinition($target_type);
$storage = $this->entityTypeManager
->getStorage($definition
->getBundleEntityType());
foreach ($bundle_ids as $bundle_id) {
$type = $storage
->load($bundle_id);
$has_layout = count($this
->getAvailableLayoutsByType($type)) > 0;
$path = '';
// Get the icon and pass to Javascript.
if (method_exists($type, 'getIconUrl')) {
$path = $type
->getIconUrl();
}
$options[$bundle_id] = $bundle_info[$bundle_id]['label'];
$types[$has_layout ? 'layout' : 'content'][] = [
'id' => $bundle_id,
'name' => $bundle_info[$bundle_id]['label'],
'image' => $path,
'title' => $this
->t('Create new @name', [
'@name' => $bundle_info[$bundle_id]['label'],
]),
];
}
$elements['add_more']['actions']['type'] = [
'#title' => $this
->t('Choose type'),
'#type' => 'select',
'#options' => $options,
'#attributes' => [
'class' => [
'layout-paragraphs-item-type',
],
],
];
$elements['add_more']['actions']['item'] = [
'#type' => 'submit',
'#host' => $items
->getEntity(),
'#value' => $this
->t('Create New'),
'#submit' => [
[
$this,
'newItemSubmit',
],
],
'#element_validate' => [
[
$this,
'newItemValidate',
],
],
'#limit_validation_errors' => [
array_merge($parents, [
$this->fieldName,
'add_more',
]),
],
'#attributes' => [
'class' => [
'layout-paragraphs-add-item',
],
],
'#ajax' => [
'callback' => [
$this,
'editItemAjax',
],
],
'#name' => trim(implode('_', $parents) . '_' . $this->fieldName . '_add_item', '_'),
'#element_parents' => $parents,
];
// When adding a new element, the widget needs a way to track
// (a) where in the DOM the new element should be added, and
// (b) which method to use the insert the new element
// (i.e. before, after, append).
$elements['add_more']['actions']['dom_id'] = [
'#type' => 'hidden',
'#attributes' => [
'class' => [
'dom-id',
],
],
];
$elements['add_more']['actions']['insert_method'] = [
'#type' => 'hidden',
'#attributes' => [
'class' => [
'insert-method',
],
],
];
// Template for javascript behaviors.
$elements['add_more']['menu'] = [
'#type' => 'inline_template',
'#template' => '
<div class="layout-paragraphs-add-more-menu hidden">
<h4 class="visually-hidden">Add Item</h4>
<div class="layout-paragraphs-add-more-menu__search hidden">
<input type="text" placeholder="{{ search_text }}" />
</div>
<div class="layout-paragraphs-add-more-menu__group">
{% if types.layout %}
<div class="layout-paragraphs-add-more-menu__group--layout">
{% endif %}
{% for type in types.layout %}
<div class="layout-paragraphs-add-more-menu__item paragraph-type-{{type.id}} layout-paragraph">
<a data-type="{{ type.id }}" href="#{{ type.id }}" title="{{ type.title }}">
{% if type.image %}
<img src="{{ type.image }}" alt ="" />
{% endif %}
<div>{{ type.name }}</div>
</a>
</div>
{% endfor %}
{% if types.layout %}
</div>
{% endif %}
{% if types.content %}
<div class="layout-paragraphs-add-more-menu__group--content">
{% endif %}
{% for type in types.content %}
<div class="layout-paragraphs-add-more-menu__item paragraph-type-{{type.id}}">
<a data-type="{{ type.id }}" href="#{{ type.id }}" title="{{ type.title }}">
{% if type.image %}
<img src="{{ type.image }}" alt ="" />
{% endif %}
<div>{{ type.name }}</div>
</a>
</div>
{% endfor %}
{% if types.content %}
</div>
{% endif %}
</div>
</div>',
'#context' => [
'types' => $types,
'search_text' => $this
->t('Search'),
],
];
}
else {
// Add the #isTranslating attribute, if in a translation context.
$elements['is_translating_warning'] = [
'#type' => "html_tag",
'#tag' => 'div',
'#value' => t("This is Translation Context (editing a version not in the original language). <b>No new Layout Sections and Paragraphs can be added</b>."),
'#weight' => -1100,
'#attributes' => [
'class' => [
'is_translating_warning',
],
],
];
$elements['add_more'] = [
'actions' => [
'#isTranslating' => TRUE,
],
];
}
// Add the paragraph edit form if editing.
if ($widget_state['open_form'] !== FALSE) {
$this
->entityForm($elements, $form_state, $form);
}
// Add remove confirmation form if we're removing.
if ($widget_state['remove_item'] !== FALSE) {
$this
->removeForm($elements, $form_state, $form);
}
// Container for disabled / orphaned items.
$elements['disabled'] = [
'#type' => 'fieldset',
'#attributes' => [
'class' => [
'layout-paragraphs-disabled-items',
],
],
'#weight' => 999,
'#title' => $this
->t('Disabled Items'),
'description' => [
'#markup' => '<div class="layout-paragraphs-disabled-items__description">' . $this
->t('Drop items here that you want to keep disabled / hidden, without removing them permanently.') . '</div>',
],
'items' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'layout-paragraphs-disabled-items__items',
],
],
],
];
// Pass widget instance settings to JS.
$elements['#attached']['drupalSettings']['layoutParagraphsWidgets'][$this->wrapperId] = [
'wrapperId' => $this->wrapperId,
'maxDepth' => $this
->getSetting('nesting_depth'),
'requireLayouts' => $this
->getSetting('require_layouts'),
'isTranslating' => $elements["add_more"]["actions"]["#isTranslating"] ?? NULL,
'cardinality' => $this->fieldDefinition
->getFieldStorageDefinition()
->getCardinality(),
'itemsCount' => $this
->activeItemsCount($widget_state['items']),
];
// Add layout_paragraphs_widget library.
$elements['#attached']['library'][] = 'layout_paragraphs/layout_paragraphs_widget';
return $elements;
}