ICT:Drupal opener explained: Difference between revisions
No edit summary |
No edit summary |
||
| Line 141: | Line 141: | ||
* The backend storage should be configured as a '''Required''' field to match this UI behavior. | * The backend storage should be configured as a '''Required''' field to match this UI behavior. | ||
== UI Simplification: Hiding the Asset Grid == | |||
To prevent historians from being overwhelmed by the full library list during an upload, we apply a "Clean UI" override. | |||
=== Strategy 1: State Restriction === | |||
By restricting {{code|allowed_media_types}} to a single value in the {{code|MediaLibraryState}}, the left-hand navigation tabs are removed, focusing the user on the current task. | |||
=== Strategy 2: CSS Override === | |||
We hide the existing asset grid using CSS to ensure the '''Upload Form''' is the only interactive element visible. | |||
<source lang="css"> | |||
/* Target the modal specifically when our opener is active */ | |||
[data-opener-id="your_module.heritage_opener"] .media-library-view { | |||
display: none; /* Hides the bottom grid of existing files */ | |||
} | |||
</source> | |||
=== Strategy 3: "Upload Only" Workflow === | |||
If the requirement is to '''never''' browse, ensure the user role only has "Create" permissions and not "Access Media Overview", though this may conflict with other Drupal administrative tasks. | |||
== UI Simplification: Programmatic List Removal == | |||
To avoid CSS conflicts with themes, we use a backend hook to empty the asset grid. This ensures historians only see the '''Upload Form''' and not the "long list" of existing files. | |||
=== 1. View Query Alteration === | |||
Add the following to {{code|your_module.module}}. This intercepts the database query only when our specific opener is active. | |||
<source lang="php"> | |||
function your_module_views_query_alter($view, $query) { | |||
if ($view->id() === 'media_library') { | |||
$state = \Drupal::request()->query->get('media_library_state'); | |||
// If our heritage opener is calling the modal, kill the list results | |||
if (isset($state['opener_id']) && $state['opener_id'] === 'your_module.heritage_opener') { | |||
$query->addWhereExpression(0, '1 = 0'); | |||
} | |||
} | |||
} | |||
</source> | |||
=== 2. Advantages === | |||
* '''Theme Independent''': Works with Gin, Claro, or custom heritage skins. | |||
* '''Performance''': The database returns zero rows, making the modal load faster. | |||
* '''Integrity''': Historians cannot accidentally select an existing asset if the requirement is always a fresh upload. | |||
[[Category:Drupal Development]] | [[Category:Drupal Development]] | ||
[[Category:Heritage Project Documentation]] | [[Category:Heritage Project Documentation]] | ||
Revision as of 10:00, 12 March 2026
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;
}
Data Integrity: Immutable Selection
To maintain strict archival standards, this tool does not provide a "Remove" button.
- Once an asset is linked, it can only be replaced by launching the Media Library again.
- This prevents "orphaned" Digital Asset nodes in the database that lack an associated file.
- The backend storage should be configured as a Required field to match this UI behavior.
UI Simplification: Hiding the Asset Grid
To prevent historians from being overwhelmed by the full library list during an upload, we apply a "Clean UI" override.
Strategy 1: State Restriction
By restricting Template:code to a single value in the Template:code, the left-hand navigation tabs are removed, focusing the user on the current task.
Strategy 2: CSS Override
We hide the existing asset grid using CSS to ensure the Upload Form is the only interactive element visible.
/* Target the modal specifically when our opener is active */
[data-opener-id="your_module.heritage_opener"] .media-library-view {
display: none; /* Hides the bottom grid of existing files */
}
Strategy 3: "Upload Only" Workflow
If the requirement is to never browse, ensure the user role only has "Create" permissions and not "Access Media Overview", though this may conflict with other Drupal administrative tasks.
UI Simplification: Programmatic List Removal
To avoid CSS conflicts with themes, we use a backend hook to empty the asset grid. This ensures historians only see the Upload Form and not the "long list" of existing files.
1. View Query Alteration
Add the following to Template:code. This intercepts the database query only when our specific opener is active.
function your_module_views_query_alter($view, $query) {
if ($view->id() === 'media_library') {
$state = \Drupal::request()->query->get('media_library_state');
// If our heritage opener is calling the modal, kill the list results
if (isset($state['opener_id']) && $state['opener_id'] === 'your_module.heritage_opener') {
$query->addWhereExpression(0, '1 = 0');
}
}
}
2. Advantages
- Theme Independent: Works with Gin, Claro, or custom heritage skins.
- Performance: The database returns zero rows, making the modal load faster.
- Integrity: Historians cannot accidentally select an existing asset if the requirement is always a fresh upload.