Die nächste Etappe des Adventskalenders widmet sich einer Weiterentwicklung, die unmittelbar spürbar wird, sobald man mehr Shortlinks nutzt. Die bisherigen Interaktionsmuster der Overview-Ansicht waren auf Einzeloperationen ausgerichtet und erlaubten nur sehr begrenzte Effizienzgewinne, wenn viele Objekte gleichzeitig bearbeitet werden sollten. Heute wird dieses Paradigma bewusst aufgebrochen und durch ein Zusammenspiel neuer UI-Konzepte ersetzt, das erstmals echte Mehrfachoperationen, kontextsensitives Arbeiten und eine deutlich stringenter gestaltete Benutzerführung ermöglicht.
Die Quelltexte zu diesem Artikel befinden sich auf GitHub unter der folgenden URL: https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-08


Im Zentrum der Änderungen stehen drei Aspekte: erstens die Einführung einer echten Mehrfachauswahl, die bisher schlicht fehlte und damit die Grundlage für alle neuen Massenoperationen bildet. Zweitens die kontextabhängige Action-Leiste, die nur dann erscheint, wenn sie relevant ist, und sich nahtlos in den Arbeitsfluss einfügt. Drittens eine sichtbar verbesserte Interaktionstiefe, die sich unter anderem in konsistenten visuellen Hinweisen, reduzierten Refresh-Zyklen sowie in der Möglichkeit zeigt, auch komplexere Bearbeitungsschritte – wie das Setzen oder Entfernen von Ablaufdaten – für eine beliebige Menge von Einträgen gleichzeitig vorzunehmen.
Diese Verbesserungen sind nicht als Sammlung isolierter Features zu verstehen, sondern als koordinierter Schritt hin zu einer reiferen UI-Architektur. Sie zielen auf eine Situation, die in produktiven Umgebungen häufig auftritt: Benutzer verwalten nicht einzelne, sondern Dutzende oder Hunderte von Shortlinks. Jede Optimierung, die hier Reibungsverluste reduziert, wirkt unmittelbar auf die Arbeitsgeschwindigkeit und ‑Qualität. Genau an dieser Stelle entfaltet dieser Schritt seine Wirkung: Die Anwendung wird nicht nur schneller bedienbar, sondern auch passgenauer für professionelle Nutzungsszenarien.
Die Mehrfachauswahl als Fundament für Mass-Grid-Operationen
Der Übergang von einer Einzel- zu einer Mehrfachauswahl markiert eine grundlegende Veränderung der Interaktionslogik der Overview-Ansicht. Bislang war die Arbeit mit Shortlinks auf isolierte Operationen beschränkt, die stets einen sequentiellen Ablauf hatten: Ein Eintrag wurde ausgewählt, bearbeitet, bestätigt – und der Vorgang begann von vorne. Diese Struktur war funktional, erwies sich jedoch als Hemmschuh, sobald in realen Anwendungsszenarien mit größerer Anzahl an Einträgen gearbeitet wurde.
Mit der Einführung der Mehrfachauswahl wird dieser Flaschenhals aufgelöst. Das Grid transformiert sich von einer linearen Liste zu einem Arbeitsraum, in dem mehrere Objekte gleichzeitig in den Fokus gerückt werden können. Diese Fähigkeit ist nicht nur eine ergonomische Verbesserung, sondern bildet auch das technische und konzeptionelle Fundament für alle nachfolgenden Massenoperationen. Erst die Möglichkeit, beliebige Mengen an Shortlinks gleichzeitig zu markieren, macht kontextsensitives Arbeiten überhaupt möglich.
Die technische Verankerung dieser Mehrfachauswahl erfolgt zunächst an einer sehr unscheinbaren Stelle der Grid-Konfiguration. Die Overview-Ansicht wechselt explizit vom Single- in den Multi-Selection-Modus, wodurch das Grid intern in der Lage ist, mehrere Einträge parallel als ausgewählt zu führen:
private void configureGrid() {
logger().info("configureGrid..");
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COMPACT);
grid.setHeight("70vh");
grid.setSelectionMode(Grid.SelectionMode.MULTI);
Mit dieser Umstellung wird der Selektionsmechanismus des Vaadin-Grids von Grund auf erweitert. Während im Single-Selection-Modus der Fokus technisch immer auf genau einem Eintrag liegt, erlaubt Grid.SelectionMode.MULTI gleichzeitig beliebig viele Selektionen. Entscheidend ist dabei, dass sich für den Benutzer das grundlegende Interaktionsmuster nicht ändert: Die Auswahl erfolgt weiterhin über bekannte Gesten wie Klicks, gegebenenfalls ergänzt durch Tastaturinteraktionen. Der Unterschied liegt in der darunterliegenden Repräsentation – das Grid verwaltet statt eines einzelnen aktiven Elements nun eine Menge markierter Shortlinks.
Damit diese Mehrfachauswahl nicht nur visuell wirksam ist, sondern sich auch funktional niederschlägt, wird sie über einen Selection-Listener in die restliche Oberfläche eingebunden:
grid.addSelectionListener(event -> {
var all = event.getAllSelectedItems();
boolean hasSelection = !all.isEmpty();
bulkBar.setVisible(hasSelection);
if (hasSelection) {
int count = all.size();
String label = count == 1 ? "link selected" : "links selected";
selectionInfo.setText(count + " " + label + " on page " + currentPage);
} else {
selectionInfo.setText("");
}
bulkDeleteBtn.setEnabled(hasSelection);
bulkSetExpiryBtn.setEnabled(hasSelection);
bulkClearExpiryBtn.setEnabled(hasSelection);
});
Hier wird die abstrakte Mehrfachauswahl in einen konkreten UI-Zustand umgesetzt. Die Methode getAllSelectedItems() liefert die aktuell im Grid markierten Shortlinks als Menge zurück. Aus dieser wird ein boolescher Aggregatzustand hasSelection abgeleitet, der steuert, ob die nachgelagerte Bulk-Leiste überhaupt sichtbar ist und ob die zugehörigen Aktionsschaltflächen aktiviert werden. Gleichzeitig dient die Größe der Auswahl als Grundlage für eine unmittelbare Rückmeldung an den Benutzer: Anzahl und Kontext der Selektion werden in einer Textkomponente zusammengefasst, die die aktuelle Seite sowie die Anzahl der markierten Links sichtbar macht. Auf diese Weise wird die Mehrfachauswahl nicht nur intern verwaltet, sondern auch in eine klar nachvollziehbare, zustandsbasierte Interaktion umgesetzt.
Besonders relevant ist, dass die Mehrfachauswahl nahtlos in das bestehende UI-Modell integriert wurde. Die Auswahl erfolgt über etablierte Muster wie Checkbox-Klicken oder Tastenkombinationen, sodass Benutzer intuitiv, ohne Umgewöhnung, sofort produktiv arbeiten können. Gleichzeitig bleibt die visuelle Rückmeldung präzise: Ausgewählte Zeilen werden klar hervorgehoben, und Änderungen in der Selektion aktualisieren unmittelbar die darauffolgenden kontextabhängigen UI-Bestandteile.
Damit bildet die Mehrfachauswahl die notwendige Basisschicht, auf der Bulk-Operationen wie Löschen, Ablaufzeiten setzen oder entfernen sowie weitere zukünftige Funktionen sicher und konsistent implementiert werden können. Sie ist der architektonische Drehpunkt, um die Anwendung eines rein eintragsorientierten Werkzeugs in ein skalierbares Management-Werkzeug zu überführen, das für professionelle Nutzungsszenarien ausgelegt ist.
Die Bulk-Action-Leiste als kontextsensitives Steuerzentrum
Mit der Einführung der Mehrfachauswahl entsteht erstmals die Möglichkeit, mehrere Shortlinks gleichzeitig zu bearbeiten. Diese Fähigkeit allein genügt jedoch nicht, um einen konsistenten und ergonomisch hochwertigen Massenbearbeitungsprozess zu etablieren. Erst durch die Bulk-Action-Leiste, die sich dynamisch auf Basis der aktuellen Auswahl ein- oder ausblendet, wird die Overview-Ansicht zum echten Interaktionszentrum für massenhafte Operationen.
Der zentrale Gedanke dieser Leiste besteht darin, dass Werkzeuge nur dann sichtbar werden, wenn sie semantisch relevant sind. Solange kein Eintrag ausgewählt ist, bleibt die Oberfläche aufgeräumt und schlank. Sobald jedoch eine oder mehrere Zeilen selektiert wurden, öffnet sich der Funktionsraum für alle Operationen, die sich auf eine Menge von Objekten beziehen. Diese Dynamik folgt dem Prinzip der kontextsensitiven Verdichtung: Die UI präsentiert nicht alle Aktionsmöglichkeiten gleichzeitig, sondern nur diejenigen, die im jeweiligen Arbeitsmoment sinnvoll und möglich sind.
Die Bulk-Leiste ist darüber hinaus nicht nur eine visuelle Ergänzung, sondern verfügt über eine klar strukturierte interne Logik, die sich bereits auf der Feldebene der View niederschlägt. In der Overview-Ansicht werden die wesentlichen UI-Bausteine für Massenoperationen explizit als eigene Komponenten deklariert:
private final Button bulkDeleteBtn = new Button("Delete selected links…");
private final Button bulkSetExpiryBtn = new Button("Set expiry for selected…");
private final Button bulkClearExpiryBtn = new Button("Clear expiry for selected");
private final Span selectionInfo = new Span();
private final HorizontalLayout bulkBar = new HorizontalLayout();
Mit dieser Struktur wird deutlich, dass die Bulk-Leiste als eigenständiges UI-Element verstanden wird, das über eigene Buttons sowie eine separate Informationskomponente verfügt. Die eigentliche Einbindung erfolgt unmittelbar nach der Suchleiste, sodass die Massenaktionen visuell wie funktional in unmittelbarer Nähe zur globalen Navigation und Filterung liegen:
public OverviewView() {
add(new H2("URL Shortener – Overview"));
initDataProvider();
add(buildSearchBar());
add(buildBulkBar());
configureGrid();
addListeners();
addShortCuts();
}
Auf diese Weise entsteht eine vertikale Struktur, in der Suche, Bulk-Leiste und Grid bewusst aufeinanderfolgen: Zuerst wird der Datenraum gefiltert, anschließend werden die selektierten Elemente über die Bulk-Leiste kontextualisiert, bevor im Grid die eigentliche Detailinteraktion stattfindet.
Das Verhalten der Bulk-Leiste selbst ist in einer dedizierten Fabrikmethode gekapselt, die sowohl das visuelle Erscheinungsbild als auch den initialen Funktionszustand definiert:
private Component buildBulkBar() {
bulkDeleteBtn.addThemeVariants(LUMO_ERROR, LUMO_TERTIARY_INLINE);
bulkSetExpiryBtn.addThemeVariants(LUMO_TERTIARY_INLINE);
bulkClearExpiryBtn.addThemeVariants(LUMO_TERTIARY_INLINE);
bulkDeleteBtn.getElement().setProperty("title", "Delete all selected short links");
bulkSetExpiryBtn.getElement().setProperty("title", "Set the same expiry for all selected links");
bulkClearExpiryBtn.getElement().setProperty("title", "Remove expiry for all selected links");
bulkDeleteBtn.setEnabled(false);
bulkSetExpiryBtn.setEnabled(false);
bulkClearExpiryBtn.setEnabled(false);
bulkBar.removeAll();
selectionInfo.getStyle().set("opacity", "0.7");
selectionInfo.getStyle().set("font-size", "var(--lumo-font-size-s)");
selectionInfo.getStyle().set("margin-right", "var(--lumo-space-m)");
bulkBar.add(selectionInfo,
bulkDeleteBtn,
bulkSetExpiryBtn,
bulkClearExpiryBtn);
bulkBar.setWidthFull();
bulkBar.setDefaultVerticalComponentAlignment(Alignment.CENTER);
bulkBar.setVisible(false);
return bulkBar;
}
In dieser Methode wird die semantische Rolle der einzelnen Elemente konkretisiert. Die Theme-Varianten heben insbesondere den Lösch-Button hervor, der mit einem Fehler-Theme versehen ist und sich damit klar von weniger riskanten Operationen absetzt. Die Tooltips tragen zusätzlich zur Erklärbarkeit der Aktionen bei, indem sie den Zweck der jeweiligen Schaltfläche präzise beschreiben. Alle Buttons starten in einem deaktivierten Zustand; erst die Selektion im Grid – wie im vorherigen Kapitel gezeigt – schaltet sie gezielt frei.
Die Layout-Definition macht deutlich, dass die Bulk-Leiste als vollbreites, horizontal ausgerichtetes Steuerzentrum vorgesehen ist. Der selectionInfo-Bereich wird optisch leicht zurückgenommen, dient aber als permanenter Kontextanker, der dem Benutzer zeigt, in welchem Massenszenario er sich aktuell befindet. Die Sichtbarkeit der gesamten Leiste wird zunächst auf false gesetzt und ausschließlich durch den Selektionszustand des Grids kontrolliert. Auf diese Weise wird sichergestellt, dass die Bulk-Leiste nur dann sichtbar wird, wenn sie funktional tatsächlich benötigt wird. Sie informiert zunächst präzise über den Status der Selektion, indem sie die Anzahl der ausgewählten Shortlinks sowie die aktuelle Seite ausgibt. Anschließend werden gezielt jene Schaltflächen freigeschaltet, die auf dieser Grundlage aktiv ausgeführt werden können. Dieser Mechanismus verhindert Fehlbedienungen und sorgt gleichzeitig dafür, dass der Benutzer unmittelbar erkennt, welche Aktionen derzeit durchgeführt werden können.
Bulk-Delete: Sichere und skalierbare Löschoperationen im Grid
Mit dem Übergang zu Massenoperationen gehört das gleichzeitige Löschen mehrerer Shortlinks zu den zentralen Anwendungsfällen. Die bisherige UI war klar auf das Löschen einzelner Einträge ausgelegt, was in vielen professionellen Nutzungsszenarien nicht ausreichte. Besonders bei administrativen Tätigkeiten, bei Migrationen oder bei der Bereinigung von Testdaten ergibt sich eine natürliche Notwendigkeit, viele Einträge in einem einzigen Arbeitsschritt zu entfernen. Genau an dieser Stelle setzt die neue Bulk-Delete-Funktion an.
Der entscheidende Anspruch an diese Funktion besteht darin, zwei Aspekte gleichermaßen sicherzustellen: einerseits maximale Effizienz beim Umgang mit vielen Einträgen, andererseits hohe Robustheit gegen unbeabsichtigte oder fehlerhafte Eingaben. Das System soll schnell arbeiten, aber niemals leichtfertig Daten löschen. Die Umsetzung folgt daher einem zweistufigen Interaktionsmuster, das bewusst gestaltet wurde, um sowohl die Geschwindigkeit der Massenbearbeitung zu ermöglichen als auch die notwendige Sicherheit der Operation zu gewährleisten.
Das Bulk-Delete beginnt stets mit der Mehrfachauswahl im Grid. Sobald eine Menge von Shortlinks markiert wurde, wird die Bulk-Action-Leiste aktiviert und stellt den Benutzerinnen und Benutzern die Option „Delete selected links…“ zur Verfügung. Erst wenn diese Aktion bewusst gewählt wird, öffnet sich ein eigener Bestätigungsdialog, der mehr tut, als nur eine einfache Rückfrage zu stellen: Er zeigt Beispiel-Shortcodes der ausgewählten Einträge an, wodurch der Löschvorgang für den Benutzer transparent und überprüfbar wird. Diese Vorschau ist besonders bei größeren Datenmengen wichtig, da sie hilft, die Richtigkeit der Auswahl zu validieren, ohne die gesamte Liste der ausgewählten Elemente durchlesen zu müssen.

Das Bestätigungsmuster ist darauf ausgelegt, den Löschvorgang zielgerichtet auszulösen, ohne den Arbeitsfluss zu unterbrechen. Sobald die Bestätigung erfolgt ist, werden sämtliche markierten Shortlinks nacheinander über die Client-API gelöscht. Die Architektur erlaubt sowohl parallele als auch sequentielle Löschmuster; die aktuelle Implementierung nutzt bewusst eine sequentielle Abfolge, um Fehler präzise zu erfassen und sie in der UI zurückzumelden. Jede Löschoperation wird ausgewertet, und am Ende erhält der Benutzer eine Verdichtung der Ergebnisse, die sowohl erfolgreiche als auch fehlgeschlagene Löschvorgänge klar dokumentiert.
Diese Kombination aus Effizienz, Sicherheit und Transparenz macht das Bulk-Delete zu einem robusten Werkzeug im professionellen Kontext. Es wird im Folgenden anhand der relevanten Quelltexte konkretisiert und im Detail erläutert, wie Dialoglogik, API-Integration und Rückmeldemechanismen ineinandergreifen, um einen präzise gesteuerten Löschprozess zu realisieren.
Der Einstiegspunkt für das Bulk-Delete ist die in der Overview-Ansicht enthaltene Methode confirmBulkDeleteSelected(). Sie verbindet die aktuelle Mehrfachauswahl des Grids mit der Dialoglogik und bereitet sowohl die Vorschau der zu löschenden Einträge als auch die anschließende Ausführung der Löschoperationen vor:
private void confirmBulkDeleteSelected() {
var selected = grid.getSelectedItems();
if (selected.isEmpty()) {
Notification.show("No entries selected");
return;
}
Dialog dialog = new Dialog();
dialog.setHeaderTitle("Delete " + selected.size() + " short links?");
var exampleCodes = selected.stream()
.map(ShortUrlMapping::shortCode)
.sorted()
.limit(5)
.toList();
if (!exampleCodes.isEmpty()) {
String preview = String.join(", ", exampleCodes);
if (selected.size() > 5) {
preview += ", …";
}
dialog.add(new Text("Examples: " + preview));
} else {
dialog.add(new Text("Delete selected short links?"));
}
Button confirm = new Button("Delete", _ -> {
int success = 0;
int failed = 0;
for (var m : selected) {
try {
boolean ok = urlShortenerClient.delete(m.shortCode());
if (ok) {
success++;
} else {
failed++;
}
} catch (IOException ex) {
logger().error("Bulk delete failed for {}", m.shortCode(), ex);
failed++;
}
}
dialog.close();
grid.deselectAll();
safeRefresh();
Notification.show("Deleted: " + success + " • Failed: " + failed);
});
confirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY, LUMO_ERROR);
Button cancel = new Button("Cancel", _ -> dialog.close());
dialog.getFooter().add(new HorizontalLayout(confirm, cancel));
dialog.open();
}
Bereits der erste Block verdeutlicht das Sicherheitskonzept der Implementierung. Sollte wider Erwarten keine Auswahl vorliegen, wird der Vorgang mit einer klaren Benachrichtigung abgebrochen. Die eigentliche Dialoginstanz erhält einen Header, der die Anzahl der betroffenen Shortlinks explizit anzeigt und damit die Tragweite der Aktion verdeutlicht.
Besonders interessant ist die Art und Weise, wie die Vorschau der zu löschenden Einträge erstellt wird. Aus der Menge der selektierten Shortlinks wird eine sortierte Liste ihrer Shortcodes erstellt, die auf maximal fünf Beispiele begrenzt ist. Diese Begrenzung sorgt dafür, dass der Dialog auch bei sehr großen Selektionen lesbar bleibt, während ein angehängtes Auslassungszeichen deutlich macht, dass weitere Elemente betroffen sind. Dadurch entsteht eine knappe, aber aussagekräftige Darstellung der Auswahl.
Der innere Confirm-Handler führt schließlich den eigentlichen Löschvorgang aus. Für jeden ausgewählten Shortlink wird die Client-API des URL-Shortener-Backends über urlShortenerClient.delete(m.shortCode()) aufgerufen. Erfolgreiche und fehlgeschlagene Operationen werden gezählt; Ausnahmen werden explizit geloggt und als Fehler in der Zählung berücksichtigt. Diese Zählung bildet die Grundlage für die abschließende Benutzerbenachrichtigung, in der die Anzahl der erfolgreich gelöschten und der fehlgeschlagenen Einträge zusammengefasst wird.
Nach Abschluss der Schleife wird der Dialog geschlossen, die Grid-Selektion vollständig aufgehoben und über safeRefresh() eine konsistente Aktualisierung der angezeigten Daten ausgelöst. Die abschließende Notification übermittelt eine präzise, aggregierte Rückmeldung zum Ausgang der Operation und schließt damit den Interaktionszyklus zwischen Auswahl, Bestätigung und Ergebnisdarstellung ab.
Ergänzend lässt sich die Bulk-Delete-Funktion auch über die Tastatur bedienen. Über einen globalen Shortcut kann der Benutzer den Löschdialog direkt auslösen, sofern eine Selektion vorliegt:
private void addShortCuts() {
UI current = UI.getCurrent();
current.addShortcutListener(_ -> {
if (globalSearch.isEnabled()) globalSearch.focus();
},
Key.KEY_K, KeyModifier.META);
current.addShortcutListener(_ -> {
if (!grid.getSelectedItems().isEmpty()) {
confirmBulkDeleteSelected();
}
},
Key.DELETE);
}
Damit wird das Bulk-Delete in den alltäglichen Arbeitsfluss integriert: Die Delete-Taste löst bei bestehender Auswahl denselben Bestätigungsdialog aus wie der Klick auf die entsprechende Schaltfläche in der Bulk-Leiste. Maus- und Tastaturinteraktion führt somit zu demselben Resultat, was die Bedienung für erfahrene Benutzer deutlich beschleunigt, ohne den Sicherheitscharakter der Operation zu kompromittieren.
Bulk-Set-Expiry: Einheitliche Ablaufdaten für mehrere Shortlinks
Mit der Möglichkeit, mehrere Einträge gleichzeitig auszuwählen, eröffnet sich erstmals die Möglichkeit, auch komplexere Metadatenänderungen in einem einzigen Arbeitsschritt vorzunehmen. Eine der funktional bedeutendsten Operationen in diesem Zusammenhang ist das Setzen einheitlicher Ablaufdaten (Expiry-Daten) für eine beliebige Menge von Shortlinks. Dieses Feature ist insbesondere in administrativen Szenarien relevant, etwa wenn eine große Anzahl temporärer Links synchronisiert auslaufen soll oder wenn Test- bzw. Kampagnendaten müssen zentralisiert gesteuert werden.
Die Gestaltung der Bulk-Set-Expiry-Funktion folgt dem Anspruch, sowohl präzise als auch effizient zu sein. Benutzerinnen und Benutzer sollen die Möglichkeit erhalten, Datum und Uhrzeit in einem konsistenten Dialog festzulegen, ohne sich dafür durch jeden Eintrag einzeln navigieren zu müssen. Gleichzeitig muss die Implementierung robust mit fehlerhaften Eingaben umgehen und eine klare Rückmeldung über den Erfolg oder Misserfolg der Operation geben.
Das dialogbasierte Setzen eines gemeinsamen Ablaufdatums folgt einem dreistufigen Interaktionsmuster. Zunächst wählt der Benutzer alle relevanten Shortlinks aus, wodurch die Bulk-Leiste sichtbar wird und die Option „Set expiry for selected…“ zur Verfügung steht. Im zweiten Schritt öffnet sich ein spezialisiert gestalteter Dialog, der Datums- und Uhrzeiteingaben ermöglicht. Erst im dritten Schritt, nach der bewussten Bestätigung, werden die neuen Ablaufdaten für alle markierten Einträge gesetzt und das Grid entsprechend aktualisiert.

Der Einstiegspunkt in diese Funktionalität ist die Methode openBulkSetExpiryDialog(), die direkt aus der Bulk-Action-Leiste ausgelöst wird. Sie kapselt sowohl die dialogbasierte Erfassung der Ablaufzeit als auch die spätere Weitergabe an die Server-API:
private void openBulkSetExpiryDialog() {
var selected = grid.getSelectedItems();
if (selected.isEmpty()) {
Notification.show("No entries selected");
return;
}
Dialog dialog = new Dialog();
dialog.setHeaderTitle("Set expiry for " + selected.size() + " short links");
DatePicker date = new DatePicker("Date");
TimePicker time = new TimePicker("Time");
time.setStep(Duration.ofMinutes(15));
HorizontalLayout body = new HorizontalLayout(date, time);
body.setAlignItems(Alignment.END);
dialog.add(body);
Button cancel = new Button("Cancel", _ -> dialog.close());
Button apply = new Button("Apply", _ -> {
if (date.getValue() == null) {
Notification.show("Please select a date");
return;
}
var localTime = Optional.ofNullable(time.getValue()).orElse(LocalTime.of(0, 0));
var zdt = ZonedDateTime.of(date.getValue(), localTime, ZoneId.systemDefault());
Instant expiresAt = zdt.toInstant();
int success = 0;
int failed = 0;
for (ShortUrlMapping m : selected) {
try {
boolean ok = urlShortenerClient.edit(
m.shortCode(),
m.originalUrl(),
expiresAt
);
if (ok) success++;
else failed++;
} catch (IOException ex) {
logger().error("Bulk set expiry failed for {}", m.shortCode(), ex);
failed++;
}
}
dialog.close();
grid.deselectAll();
safeRefresh();
Notification.show("Updated expiry: " + success + " • Failed: " + failed);
});
apply.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
dialog.getFooter().add(new HorizontalLayout(cancel, apply));
dialog.open();
}
Bereits der Kopfbereich der Methode enthält eine Sicherheitsabfrage, die ein versehentliches Öffnen des Dialogs ohne gültige Selektion verhindert. Die Dialogkonfiguration selbst ist bewusst minimalistisch gehalten, konzentriert sich jedoch auf die zentralen Eingabeelemente: ein DatePicker für das Datum und ein TimePicker für die Uhrzeit. Die Schrittweite des TimePickers ist auf 15 Minuten eingestellt und entspricht damit einem praxisnahen Kompromiss zwischen Präzision und Bedienbarkeit.
Die zentrale Logik beginnt mit der Validierung der Datumsangabe. Ist kein Datum ausgewählt, wird die Ausführung abgebrochen und eine entsprechende Benachrichtigung ausgegeben. Wird ein Datum angegeben, erzeugt die Methode daraus ein ZonedDateTime, das anschließend in ein Instant überführt wird. Dieses Instant repräsentiert die neue Ablaufzeit, die allen selektierten Shortlinks zugewiesen werden soll.
Der darauffolgende Schleifenblock illustriert die Schnittstelle zum Backend. Für jeden Shortlink wird die Edit-Operation aufgerufen:
urlShortenerClient.edit(m.shortCode(), m.originalUrl(), expiresAt);
Diese Operation ist bewusst so gestaltet, dass nur die Ablaufzeit verändert wird, während der ursprüngliche URL-Wert bestehen bleibt. Fehler werden individuell behandelt und sowohl in der UI als auch im Logging berücksichtigt. Die Summierung der erfolgreichen und fehlgeschlagenen Updates führt am Ende des Vorgangs zu einer aggregierten Rückmeldung, die dem Benutzer unmittelbar mitteilt, wie erfolgreich die Massenoperation verlaufen ist.
Schließlich sorgt grid.deselectAll() in Kombination mit safeRefresh() dafür, dass sowohl der UI-Zustand als auch die Grid-Daten konsistent aktualisiert werden. Der Benutzer wird damit ohne manuelle Zwischenschritte in einen sauber zurückgesetzten Arbeitszustand versetzt, während die abschließende Notification.show(...) die Ergebnisse der Operation zusammenfasst. Die Bulk-Set-Expiry-Funktion zeigt die Zusammenspielpunkte zwischen UI, Server-API und Fehlerbehandlung auf und erläutert, wie dieser Mechanismus die Verwaltung großer Linkmengen erheblich vereinfacht.
Cheers Sven