ICT:Drupal opener explained
Custom Media Library Opener for Heritage Digital Assets
In Drupal 11.3.5, a custom "Opener" service allows the Media Library to be used as a standalone modal. This implementation restricts selection to one and only one asset and returns a rendered thumbnail to the parent page, mimicking the "classical" Media Library behavior without the full Field API overhead.
1. Define the Service
Register the opener in your module's your_module.services.yml. You must tag it with media_library.opener so the system can identify it.
services:
your_module.heritage_opener:
class: Drupal\your_module\HeritageAssetOpener
arguments: ['@entity_type.manager']
tags:
- { name: media_library.opener }
2. The Opener Logic
Create src/HeritageAssetOpener.php. This class handles the permission check and generates the AJAX response to inject the thumbnail.
namespace Drupal\your_module;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\media\Entity\Media;
use Drupal\media_library\MediaLibraryOpenerInterface;
use Drupal\media_library\MediaLibraryState;
class HeritageAssetOpener implements MediaLibraryOpenerInterface {
protected $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* Checks if the user can access the media library in this context.
*/
public function checkAccess(MediaLibraryState $state, AccountInterface $account) {
return AccessResult::allowedIfHasPermission($account, 'view media');
}
/**
* Logic executed after clicking "Insert" in the modal.
*/
public function getSelectionResponse(MediaLibraryState $state, array $selected_ids) {
$selected_id = reset($selected_ids); // Force single selection logic
$media = Media::load($selected_id);
$response = new AjaxResponse();
if ($media) {
// Build the thumbnail using the 'media_library' view mode
$view_builder = $this->entityTypeManager->getViewBuilder('media');
$render_array = $view_builder->view($media, 'media_library');
// Inject the thumbnail into the UI
$response->addCommand(new HtmlCommand('#asset-preview-container', $render_array));
// Update a hidden field with the ID for form submission
$response->addCommand(new InvokeCommand('#selected-asset-id', 'val', [$selected_id]));
}
return $response;
}
}
3. Triggering the Modal
To launch the modal from a controller or form, build a link using the MediaLibraryState. Setting media_library_remaining to 1 enforces the single-item limit.
use Drupal\Core\Url;
use Drupal\media_library\MediaLibraryState;
// Create the state for our custom opener
$state = MediaLibraryState::create(
'your_module.heritage_opener', // Our service ID
['image', 'document'], // Allowed types
'image', // Default tab
1 // Quantity limit (1 for single asset)
);
$build['select_button'] = [
'#type' => 'link',
'#title' => $this->t('Select Heritage Asset'),
'#url' => Url::fromRoute('media_library.ui', [], ['query' => $state->all()]),
'#attributes' => [
'class' => ['use-ajax', 'button'],
'data-dialog-type' => 'modal',
'data-dialog-options' => json_encode(['width' => '80%']),
],
'#attached' => ['library' => ['core/drupal.dialog.ajax']],
];
4. Required HTML Placeholders
Ensure your template or form contains these matching IDs:
id="asset-preview-container": Where the thumbnail will appear.id="selected-asset-id": A hidden input to store the ID for the backend.
Configuration: Custom View Mode
To ensure the selected heritage asset matches the project's design, we use a dedicated view mode instead of the generic library thumbnail.
1. UI Configuration Steps
- Go to Structure > Display modes > View modes and add a new "Media" mode named Template:code.
- Navigate to Structure > Media types > [Your Type] > Manage display.
- Enable the Template:code under Custom display settings.
- Configure the layout:
- Hide all fields except the main file/image.
- Set the Format to Thumbnail and select a custom Image Style (e.g., Template:code).
2. Code Implementation
Update the Template:code method in Template:code to call this specific mode:
// Render the media using our project-specific view mode
$render_array = $view_builder->view($media, 'heritage_asset_preview');
3. CSS Styling
You can now target this specific preview in your theme:
.heritage-thumbnail-wrapper [data-drupal-view-mode="heritage_asset_preview"] {
border: 2px solid #a39161; /* Heritage gold border */
box-shadow: 3px 3px 10px rgba(0,0,0,0.2);
max-width: 300px;
}