function MediaLibraryWidget::formElement
Same name in other branches
- 9 core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
- 8.9.x core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
- 10 core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
Overrides WidgetInterface::formElement
1 call to MediaLibraryWidget::formElement()
- MediaLibraryInceptionWidget::formElement in core/
modules/ media_library/ tests/ modules/ media_library_test_widget/ src/ Plugin/ Field/ FieldWidget/ MediaLibraryInceptionWidget.php - Returns the form for a single field widget.
1 method overrides MediaLibraryWidget::formElement()
- MediaLibraryInceptionWidget::formElement in core/
modules/ media_library/ tests/ modules/ media_library_test_widget/ src/ Plugin/ Field/ FieldWidget/ MediaLibraryInceptionWidget.php - Returns the form for a single field widget.
File
-
core/
modules/ media_library/ src/ Plugin/ Field/ FieldWidget/ MediaLibraryWidget.php, line 308
Class
- MediaLibraryWidget
- Plugin implementation of the 'media_library_widget' widget.
Namespace
Drupal\media_library\Plugin\Field\FieldWidgetCode
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items */
$referenced_entities = $items->referencedEntities();
$view_builder = $this->entityTypeManager
->getViewBuilder('media');
$field_name = $this->fieldDefinition
->getName();
$parents = $form['#parents'];
// Create an ID suffix from the parents to make sure each widget is unique.
$id_suffix = $parents ? '-' . implode('-', $parents) : '';
$field_widget_id = implode(':', array_filter([
$field_name,
$id_suffix,
]));
$wrapper_id = $field_name . '-media-library-wrapper' . $id_suffix;
$limit_validation_errors = [
array_merge($parents, [
$field_name,
]),
];
$settings = $this->getFieldSetting('handler_settings');
$element += [
'#type' => 'fieldset',
'#cardinality' => $this->fieldDefinition
->getFieldStorageDefinition()
->getCardinality(),
// If no target bundles are specified, all target bundles are allowed.
'#target_bundles' => $settings['target_bundles'] ?? [],
'#attributes' => [
'id' => $wrapper_id,
'class' => [
'js-media-library-widget',
],
],
'#pre_render' => [
[
$this,
'preRenderWidget',
],
],
'#attached' => [
'library' => [
'media_library/widget',
],
],
'#theme_wrappers' => [
'fieldset__media_library_widget',
],
];
if ($this->fieldDefinition
->isRequired()) {
$element['#element_validate'][] = [
static::class,
'validateRequired',
];
}
// When the list of allowed types in the field configuration is null,
// ::getAllowedMediaTypeIdsSorted() returns all existing media types. When
// the list of allowed types is an empty array, we show a message to users
// and ask them to configure the field if they have access.
$allowed_media_type_ids = $this->getAllowedMediaTypeIdsSorted();
if (!$allowed_media_type_ids) {
$element['no_types_message'] = [
'#markup' => $this->getNoMediaTypesAvailableMessage(),
];
return $element;
}
$multiple_items = FALSE;
if (empty($referenced_entities)) {
$element['#field_prefix']['empty_selection'] = [
'#markup' => $this->t('No media items are selected.'),
];
}
else {
// @todo Use a <button> link here.
$multiple_items = count($referenced_entities) > 1;
$element['#field_prefix']['weight_toggle'] = [
'#type' => 'html_tag',
'#tag' => 'button',
'#value' => $this->t('Show media item weights'),
'#access' => $multiple_items,
'#attributes' => [
'class' => [
'link',
'js-media-library-widget-toggle-weight',
],
],
];
}
$element['selection'] = [
'#type' => 'container',
'#theme_wrappers' => [
'container__media_library_widget_selection',
],
'#attributes' => [
'class' => [
'js-media-library-selection',
],
],
];
foreach ($referenced_entities as $delta => $media_item) {
if ($media_item->access('view')) {
// @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
$preview = $view_builder->view($media_item, 'media_library');
}
else {
$item_label = $media_item->access('view label') ? $media_item->label() : new FormattableMarkup('@label @id', [
'@label' => $media_item->getEntityType()
->getSingularLabel(),
'@id' => $media_item->id(),
]);
$preview = [
'#theme' => 'media_embed_error',
'#message' => $this->t('You do not have permission to view @item_label.', [
'@item_label' => $item_label,
]),
];
}
$element['selection'][$delta] = [
'#theme' => 'media_library_item__widget',
'#attributes' => [
'class' => [
'js-media-library-item',
],
// Add the tabindex '-1' to allow the focus to be shifted to the next
// media item when an item is removed. We set focus to the container
// because we do not want to set focus to the remove button
// automatically.
// @see ::updateWidget()
'tabindex' => '-1',
// Add a data attribute containing the delta to allow us to easily
// shift the focus to a specific media item.
// @see ::updateWidget()
'data-media-library-item-delta' => $delta,
],
'remove_button' => [
'#type' => 'submit',
'#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
'#value' => $this->t('Remove'),
'#media_id' => $media_item->id(),
'#attributes' => [
'aria-label' => $media_item->access('view label') ? $this->t('Remove @label', [
'@label' => $media_item->label(),
]) : $this->t('Remove media'),
],
'#ajax' => [
'callback' => [
static::class,
'updateWidget',
],
'wrapper' => $wrapper_id,
'progress' => [
'type' => 'throbber',
'message' => $media_item->access('view label') ? $this->t('Removing @label.', [
'@label' => $media_item->label(),
]) : $this->t('Removing media.'),
],
],
'#submit' => [
[
static::class,
'removeItem',
],
],
// Prevent errors in other widgets from preventing removal.
'#limit_validation_errors' => $limit_validation_errors,
],
'rendered_entity' => $preview,
'target_id' => [
'#type' => 'hidden',
'#value' => $media_item->id(),
],
// This hidden value can be toggled visible for accessibility.
'weight' => [
'#type' => 'number',
'#theme' => 'input__number__media_library_item_weight',
'#title' => $this->t('Weight'),
'#access' => $multiple_items,
'#default_value' => $delta,
'#attributes' => [
'class' => [
'js-media-library-item-weight',
],
],
],
];
}
$cardinality_unlimited = $element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED;
$remaining = $element['#cardinality'] - count($referenced_entities);
// Inform the user of how many items are remaining.
if (!$cardinality_unlimited) {
if ($remaining) {
$cardinality_message = $this->formatPlural($remaining, 'One media item remaining.', '@count media items remaining.');
}
else {
$cardinality_message = $this->t('The maximum number of media items have been selected.');
}
// Add a line break between the field message and the cardinality message.
if (!empty($element['#description'])) {
$element['#description'] .= '<br />';
}
$element['#description'] .= $cardinality_message;
}
// Create a new media library URL with the correct state parameters.
$selected_type_id = reset($allowed_media_type_ids);
$remaining = $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining;
// This particular media library opener needs some extra metadata for its
// \Drupal\media_library\MediaLibraryOpenerInterface::getSelectionResponse()
// to be able to target the element whose 'data-media-library-widget-value'
// attribute is the same as $field_widget_id. The entity ID, entity type ID,
// bundle, field name are used for access checking.
$entity = $items->getEntity();
$opener_parameters = [
'field_widget_id' => $field_widget_id,
'entity_type_id' => $entity->getEntityTypeId(),
'bundle' => $entity->bundle(),
'field_name' => $field_name,
];
// Only add the entity ID when we actually have one. The entity ID needs to
// be a string to ensure that the media library state generates its
// tamper-proof hash in a consistent way.
if (!$entity->isNew()) {
$opener_parameters['entity_id'] = (string) $entity->id();
if ($entity->getEntityType()
->isRevisionable()) {
$opener_parameters['revision_id'] = (string) $entity->getRevisionId();
}
}
$state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_media_type_ids, $selected_type_id, $remaining, $opener_parameters);
// Add a button that will load the Media library in a modal using AJAX.
$element['open_button'] = [
'#type' => 'button',
'#value' => $this->t('Add media'),
'#name' => $field_name . '-media-library-open-button' . $id_suffix,
'#attributes' => [
'class' => [
'js-media-library-open-button',
],
],
'#media_library_state' => $state,
'#ajax' => [
'callback' => [
static::class,
'openMediaLibrary',
],
'progress' => [
'type' => 'throbber',
'message' => $this->t('Opening media library.'),
],
],
// Allow the media library to be opened even if there are form errors.
'#limit_validation_errors' => [],
];
// When the user returns from the modal to the widget, we want to shift the
// focus back to the open button. If the user is not allowed to add more
// items, the button needs to be disabled. Since we can't shift the focus to
// disabled elements, the focus is set back to the open button via
// JavaScript by adding the 'data-disabled-focus' attribute.
// @see Drupal.behaviors.MediaLibraryWidgetDisableButton
if (!$cardinality_unlimited && $remaining === 0) {
$triggering_element = $form_state->getTriggeringElement();
if ($triggering_element && ($trigger_parents = $triggering_element['#array_parents']) && end($trigger_parents) === 'media_library_update_widget') {
// The widget is being rebuilt from a selection change.
$element['open_button']['#attributes']['data-disabled-focus'] = 'true';
$element['open_button']['#attributes']['class'][] = 'visually-hidden';
}
else {
// The widget is being built without a selection change, so we can just
// set the item to disabled now, there is no need to set the focus
// first.
$element['open_button']['#disabled'] = TRUE;
$element['open_button']['#attributes']['class'][] = 'visually-hidden';
}
}
// This hidden field and button are used to add new items to the widget.
$element['media_library_selection'] = [
'#type' => 'hidden',
'#attributes' => [
// This is used to pass the selection from the modal to the widget.
'data-media-library-widget-value' => $field_widget_id,
],
];
// When a selection is made this hidden button is pressed to add new media
// items based on the "media_library_selection" value.
$element['media_library_update_widget'] = [
'#type' => 'submit',
'#value' => $this->t('Update widget'),
'#name' => $field_name . '-media-library-update' . $id_suffix,
'#ajax' => [
'callback' => [
static::class,
'updateWidget',
],
'wrapper' => $wrapper_id,
'progress' => [
'type' => 'throbber',
'message' => $this->t('Adding selection.'),
],
],
'#attributes' => [
'data-media-library-widget-update' => $field_widget_id,
'class' => [
'js-hide',
],
],
'#validate' => [
[
static::class,
'validateItems',
],
],
'#submit' => [
[
static::class,
'addItems',
],
],
// We need to prevent the widget from being validated when no media items
// are selected. When a media field is added in a subform, entity
// validation is triggered in EntityFormDisplay::validateFormValues().
// Since the media item is not added to the form yet, this triggers errors
// for required media fields.
'#limit_validation_errors' => !empty($referenced_entities) ? $limit_validation_errors : [],
];
return $element;
}
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.