Delete-Controller und Bestätigungsdialog implementieren
Delete-Controller und Bestätigungsdialog implementieren
~8 Min. Lesezeit Zuletzt aktualisiert am 9. August 2026
Kapitel 8 hat bereits MassDelete für mehrere ausgewählte Zeilen gebaut. Für das Löschen einer einzelnen Zeile direkt aus der Actions-Spalte braucht es einen eigenen Delete-Controller plus einen clientseitigen Bestätigungsdialog, der ohne eigenes JavaScript auskommt - Magento_Ui bringt beides bereits mit.
Delete-Link in der Actions-Spalte
Die generische Actions-Spaltenklasse aus Kapitel 6 erzeugt standardmäßig nur einen Edit-Link. Ein eigener Column-Renderer ergänzt den Delete-Link mit Bestätigungsdialog - das volle Custom-Renderer-Muster vertieft Kapitel 19; hier vorab die konkrete Actions-Klasse für Kundenstimmen:
<?php
declare(strict_types=1);
namespace Mironsoft\Testimonial\Ui\Component\Listing\Column;
use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;
/**
* Renders edit and delete links for each testimonial grid row.
*/
class TestimonialActions extends Column
{
private const URL_PATH_EDIT = 'mironsoft_testimonial/testimonial/edit';
private const URL_PATH_DELETE = 'mironsoft_testimonial/testimonial/delete';
/**
* @param ContextInterface $context Rendering context.
* @param UiComponentFactory $uiComponentFactory Factory for nested UI components.
* @param UrlInterface $urlBuilder Builds admin URLs for the row actions.
* @param array<string, mixed> $components Nested UI components.
* @param array<string, mixed> $data Additional UI Component data configuration.
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
private readonly UrlInterface $urlBuilder,
array $components = [],
array $data = [],
) {
parent::__construct($context, $uiComponentFactory, $components, $data);
}
/**
* Adds edit/delete URLs to every grid row of this column.
*
* @param array<string, mixed> $dataSource Raw grid data source.
* @return array<string, mixed>
*/
public function prepareDataSource(array $dataSource): array
{
if (!isset($dataSource['data']['items'])) {
return $dataSource;
}
foreach ($dataSource['data']['items'] as &$item) {
$itemId = (int) $item['testimonial_id'];
$item[$this->getData('name')]['edit'] = [
'href' => $this->urlBuilder->getUrl(self::URL_PATH_EDIT, ['id' => $itemId]),
'label' => __('Edit'),
];
$item[$this->getData('name')]['delete'] = [
'href' => $this->urlBuilder->getUrl(self::URL_PATH_DELETE, ['id' => $itemId]),
'label' => __('Delete'),
'confirm' => [
'title' => __('Delete Testimonial'),
'message' => __('Are you sure you want to delete testimonial "%1"?', $item['customer_name']),
],
];
}
return $dataSource;
}
}Die confirm-Konfiguration im Array reicht bereits vollständig aus, um clientseitig Magento_Ui/js/modal/confirm auszulösen, bevor der Delete-Link tatsächlich aufgerufen wird - kein eigenes JavaScript nötig.
<actionsColumn name="actions" class="Mironsoft\Testimonial\Ui\Component\Listing\Column\TestimonialActions">
<settings>
<indexField>testimonial_id</indexField>
<resizeEnabled>false</resizeEnabled>
<sortable>false</sortable>
</settings>
</actionsColumn>Delete-Controller
<?php
declare(strict_types=1);
namespace Mironsoft\Testimonial\Controller\Adminhtml\Testimonial;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Mironsoft\Testimonial\Api\TestimonialRepositoryInterface;
/**
* Deletes a single testimonial identified via the id request parameter.
*/
class Delete extends Action
{
public const ADMIN_RESOURCE = 'Mironsoft_Testimonial::delete';
/**
* @param Context $context Backend action context.
* @param TestimonialRepositoryInterface $testimonialRepository Loads/deletes testimonials.
*/
public function __construct(
Context $context,
private readonly TestimonialRepositoryInterface $testimonialRepository,
) {
parent::__construct($context);
}
/**
* Deletes the requested testimonial and redirects back to the grid.
*
* @return ResultInterface
*/
public function execute(): ResultInterface
{
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
$id = (int) $this->getRequest()->getParam('id');
if ($id) {
try {
$testimonial = $this->testimonialRepository->getById($id);
$this->testimonialRepository->delete($testimonial);
$this->messageManager->addSuccessMessage(__('You deleted the testimonial.'));
} catch (NoSuchEntityException $exception) {
$this->messageManager->addErrorMessage(__('This testimonial no longer exists.'));
}
}
return $resultRedirect->setPath('mironsoft_testimonial/testimonial/index');
}
}delete() muss dafür noch auf TestimonialRepositoryInterface ergänzt werden - eine dünne Methode, die intern $this->resource->delete($testimonial) aufruft, analog zu save() aus Kapitel 16.
Auffällig: ADMIN_RESOURCE zeigt hier bewusst auf Mironsoft_Testimonial::delete, nicht auf den allgemeinen ::testimonial-Knoten aus Kapitel 14 - diese Trennung wird in Kapitel 25 offiziell in der ACL nachgezogen.
Tipp: Bei einer einzelnen Zeile führt der Bestätigungsdialog zu genau einem GET-Request auf die Delete-URL nach Bestätigung - eine unbeabsichtigte Massenlöschung durch Doppelklick ist damit praktisch ausgeschlossen, solange der Dialog nicht versehentlich weggelassen wird.