function MediaLibraryWidget::formElement

Same name and namespace in other branches
  1. 9 core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
  2. 8.9.x core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
  3. 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\FieldWidget

Code

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.