minor
#29852
TL Views: Mehrwertige Binärwerte als Datei-Chips (oder Werteliste) mit Mehrfach-Upload, Pflichtfeld, Validierung und accept-Filter; <file-list> entfällt
Eine Anwendung, die in einem Formular mehrere Dateien auf einmal annehmen und per Skript-Kommando verarbeiten will, hatte dafür bisher nur <file-list>, und das ließ den Nutzer bei einem leeren Absenden ohne Hinweis. Gefunden in Pietro (Openium/Pietro#660), tl-layout-view 8.0.0-alpha10.
Problem
Pietro lädt mit einem Dialog Unterlagen hoch: eine Art für alle Dateien, ein Upload-Bereich, ein Knopf "Hochladen", der pro Datei eine Skript-Funktion aufruft. Das Binärfeld <field> (ReactBinaryFieldControl) bindet ein einwertiges Attribut. Ein mehrwertiger Binärwert (<field> eines mehrwertigen Binärattributs, <value-input multiple="true" type="tl.core:Binary">) wurde zur Werteliste mit einem Picker pro Wert, also ohne Mehrfachauswahl und ohne Drop-Zone. <upload-command multiple="true"> ist ein Knopf ohne Formular.
Funktioniert hat nur der Umweg <file-list attribute="files"/> über einem transienten Formularobjekt mit einer Komposition files. Dort fehlten Pflichtfeld-Stern, Validierungsfehler am Feld und ein accept-Filter. Der eigentliche Mangel: Für "mehrere Dateien eingeben" braucht es keine Objekte pro Datei; <file-list> vermischte eine Eingabe-Darstellung (Datei-Chips) mit Kompositions-/Objektlogik.
Ein persistentes mehrwertiges Binärattribut ließ sich zwar im React-Modelleditor anlegen, aber nicht speichern: PrimitiveStorage legt mehrere Werte als JSON-Array in einer Spalte ab und akzeptiert dabei nur Zahlen, Wahrheitswerte, Zeichenketten und Daten ("only numbers, booleans, strings and dates are supported"). tl.core:Binary trug deshalb kein <supports-multiple/>, und der klassische Modelleditor bot "mehrwertig" für Binärwerte nicht an; der React-Modelleditor bot "mehrwertig" für jeden Typ an.
Die Upload-Obergrenze max-upload-size (#29560) galt für jede Datei und für die gesamte Anfrage: Zwei Dateien von je 6 MB wurden bei einer Grenze von 10 MB mit "Es können Dateien bis 10 MB hochgeladen werden" abgelehnt, wenn sie zusammen ausgewählt wurden, nacheinander hochgeladen aber angenommen. Außerdem gab es die Grenze nur anwendungsweit; pro Attribut ließ sie sich nicht festlegen.
Lösung
Die Darstellung ist konsolidiert: Mehrwertige Binärwerte haben die Chip-Darstellung als normales Eingabeelement, die Objekt-/Kompositionslogik gehört in die Anwendung, <file-list> entfällt. Mehrwertige Binärattribute sind persistent speicherbar.
- Datei-Chips für mehrwertige Binärwerte (Default) für <field> eines mehrwertigen Binärattributs und <value-input multiple="true" type="tl.core:Binary">: ein Chip pro Datei (Icon, Name, Größe, Download), im Bearbeitungsmodus Entfernen pro Chip, Hinzufügen mehrerer Dateien über den Datei-Dialog oder per Drag-and-drop (ReactFileChipsControl, Client TLFileChips). Pflichtfeld-Stern und Validierungsfehler ("Das Feld darf nicht leer sein.") stehen am Feld wie bei jedem anderen Feld. In einer Tabellenzelle stehen die Chips direkt in der Zelle (kompakt, umbrechend). Der Client kennt die Upload-Obergrenze des Servers (maxUploadSize) und lehnt eine Auswahl mit einer zu großen Datei vor dem Senden ab; ein fehlgeschlagener Upload zeigt eine übersetzte Meldung am Feld (error), die der nächste erfolgreiche Upload löscht. Alle Upload-Controls teilen diesen Zustandsschlüssel (UploadSupport.ERROR).
- Upload-Obergrenze pro Datei: max-upload-size begrenzt jede einzelne Datei; mehrere gleichzeitig hochgeladene Dateien dürfen jede für sich die Grenze ausnutzen. Der Client prüft nur noch die einzelnen Dateien, das Servlet setzt die Grenze des Containers nur für die Dateigröße (maxFileSize), nicht für die Anfrage, und prüft die Content-Length nicht mehr.
- Upload-Obergrenze pro Attribut: Die Annotation <max-upload-size value="5m"/> (TLMaxUploadSize, an Binärattributen und -typen sowie an einem <value-input>, Attribut gewinnt über Typ) begrenzt die Größe jeder in das Attribut hochgeladenen Datei. Die anwendungsweite Grenze deckelt sie weiterhin; sind beide gesetzt, gilt die kleinere (UploadSupport.effectiveMaxUploadSize). Die Annotation wandert wie <accepted-types> über FieldControlService in die FieldSpec (maxUploadSize) und von dort in Datei-Chips, Binärfeld und Werteliste. Jedes Upload-Control nennt seine Grenze über UploadHandler.maxUploadSize() (Default: die anwendungsweite); das Servlet prüft die Dateien gegen die Grenze des adressierten Controls, und der Client bekommt sie unter dem einen Zustandsschlüssel maxUploadSize (der eigene Schlüssel maxSize des Binärfelds entfällt). Das klassische Formular setzt die Annotation als DataField.setMaxUploadSize.
- Werteliste: Die vorhandene Annotation <binary-display presentation="…"/> hat den zusätzlichen Wert list. Damit wird ein mehrwertiger Binärwert als Liste mit einem Binärfeld pro Wert dargestellt; deren Hinzufügen-Knopf nimmt mehrere Dateien auf einmal an (auch per Drop) und legt pro Datei ein Element an. In einer Tabellenzelle zeigt die Liste die Dateinamen und öffnet die Felder im Dialog. Allgemein: Ein Feld-Provider kann Werte aus hochgeladenen Dateien erzeugen (ReactFieldControlProvider#uploadValues), die Werteliste bietet dann den Upload statt eines leeren Elements an. Im React-UI bedeutet data-item (Default) die Chips, image-upload wird wie data-item behandelt; das klassische UI behandelt list wie data-item.
- Kompakte Felder: Ein Feld-Control erfährt über den Zustand compact (FieldState.compact, gesetzt aus FieldSpec.isCompact()), dass es auf wenig Raum dargestellt wird, etwa in einer Tabellenzelle. Die Datei-Chips nutzen das für ihre Zellen-Darstellung; jedes andere Feld-Control kann es ebenso lesen.
- Persistente mehrwertige Binärattribute: Die Werte eines mehrwertigen Attributs vom Typ tl.core:Binary liegen in der generischen Tabelle BinaryAttributeValue (bzw. BinaryAttributeValueUnversioned für Objekte unversionierter Tabellen), eine Zeile pro Datei: Besitzer (object), Attribut (metaAttribute), Position (sortOrder), Dateiname, Content-Type und Inhalt (Blob). Die Zeilen werden mit dem Besitzer gelöscht. Die Speicherung übernimmt BinaryCollectionStorage (Tabelle konfigurierbar, in der Speicherauswahl für Eigenschaften wählbar), die StorageImplementationFactory für mehrwertige Binärattribute ohne eigene <storage-algorithm>-Annotation wählt; einwertige Binärattribute bleiben bei PrimitiveStorage. Beim Setzen einer neuen Werteliste behalten aus dem Attribut gelesene Dateien ihre Zeile (nur die Position wird aktualisiert), neue Dateien bekommen Zeilen, entfernte werden gelöscht; add/`remove` arbeiten inkrementell. Das klassische Formular bietet für ein mehrwertiges Binärattribut ein Mehrfach-DataField an.
- Modelleditoren: tl.core:Binary trägt <supports-multiple/>, der klassische wie der React-Modelleditor bieten "mehrwertig" für Binärwerte an. Der React-Modelleditor (tl-dev-tools) sperrt "mehrwertig" beim Anlegen eines Attributs für primitive Typen ohne <supports-multiple/> (deklarativ über <dynamic-visibility> am Formulartyp tl.devtools:NewAttribute; ein angehaktes Feld bleibt bedienbar, damit es abgewählt werden kann), und die Anlege-Aktion lehnt einen solchen Wert mit einer Fehlermeldung ab. Das Bearbeitungsformular eines bestehenden Attributs bietet "mehrwertig" weiterhin für jeden Typ an.
- accept-Filter über die vorhandene Annotation <accepted-types value="…"/> (Attribut oder Typ, bzw. Annotation eines <value-input>), für Chips, Werteliste und das einwertige Binärfeld (Hinweis an den Datei-Dialog des Browsers; *.pdf wird zu .pdf).
- <value-input> in einem <form> folgt dem Bearbeitungsmodus des Formulars: bearbeitbar nur, solange das Formular bearbeitet wird; Pflichtfeld-Stern nur dann. edit-mode="ch" nennt stattdessen einen Kanal, dessen Wert true die Eingabe bearbeitbar macht; readonly="true" gewinnt immer.
- Eigene Speicher-Kette: Ein Formular mit <save-action> (<with-transaction><store-form-state/><execute-script .../></with-transaction>) nimmt sich mit <leave-edit-mode/> (#29892) selbst aus dem Bearbeitungsmodus.
- <file-list> entfällt (FileListElement, FileListControl).
- Demo (tl-demo-react):
- "Objektliste" (Tickets mit Kommentaren): Das Modell (Kommentar mit Komposition von Anhang-Objekten) bleibt; die Anhänge werden über <value-input multiple="true" type="tl.core:Binary"> eingegeben. Ein mit #29829 aus dem Kommentar vorbelegter Kanal hält die Dateien; beim Speichern bzw. Anlegen gleicht die konfigurierte Skript-Funktion demoTicketsStoreAttachments die Anhang-Objekte ab (Datei mit gleichem Inhalt und Namen behält ihr Objekt, neue Dateien werden neue Anhänge, entfernte werden gelöscht). Abbrechen füllt die Dateien neu. Im Ansichtsmodus erscheint das Feld nur bei vorhandenen Anhängen.
- "Attribute": Der Demo-Typ demo.react:Demo hat die persistenten mehrwertigen Binärattribute attachments (Chips, <max-upload-size value="5m"/>) und documents (Werteliste mit <accepted-types value="*.pdf, *.txt, *.md"/> und <max-upload-size value="1m"/>), in beiden Tabellen, im Formular, im Detail- und im Anlege-Dialog.
- "Gestaltung", Abschnitt "Ein Wert, deklariert wie ein Attribut": Mehrfach-Dateieingabe als Chips und als Liste mit accept-Filter.
- Doku: Artikel view-layer/forms (Dateien, Speicherung, Zellen-Darstellung, Größenbegrenzung) und view-layer/basics (Bearbeitungsmodus von <value-input>).
Setzt auf #29829 (beschreibbarer, vorbelegter Kanal) und #29892 (<leave-edit-mode/>) auf.
Migration
<file-list attribute="..."/> gibt es nicht mehr. Ersatz:
- Soll der Nutzer nur Dateien eingeben: ein mehrwertiges Binärattribut mit <field attribute="..."/> (die Dateien werden persistent gespeichert), oder ohne Modellattribut <value-input multiple="true" type="tl.core:Binary" value="ch"/> mit einem Kanal und einem Kommando, das die Dateien verarbeitet.
- Sollen die Dateien als eigene Objekte einer Komposition gespeichert werden: die Dateien über einen mit <channel name="files" inputs="obj" init="o -> $o.get(...).map(a -> $a.get(...))"/> vorbelegten Kanal eingeben und beim Speichern per Skript die Objekte abgleichen: <save-action> mit <with-transaction><store-form-state/><execute-script .../></with-transaction><leave-edit-mode/>, <cancel-action> mit <discard-form-state/> und Neubefüllen des Kanals. Vorlage: comment.view.xml / comment-composer.view.xml und demoTicketsStoreAttachments in demoReactConf.config.xml der React-Demo.
- Ein <value-input> innerhalb eines <form> ist nur noch im Bearbeitungsmodus des Formulars bearbeitbar; soll es unabhängig davon bearbeitbar sein, edit-mode auf einen Kanal mit dem Wert true setzen.
- max-upload-size begrenzt nur noch die einzelne Datei; eine Anwendung, die die Größe einer ganzen Mehrfach-Upload-Anfrage begrenzen will, muss das im Container (Jetty multipart-config) tun. Eine kleinere Grenze für ein einzelnes Attribut setzt <max-upload-size value="…"/> am Attribut oder Typ.
Die Tabellen BinaryAttributeValue (BINARY_ATT_VALUE) und BinaryAttributeValueUnversioned (BINARY_ATT_VALUE_UNV) sowie die Annotation <supports-multiple/> an tl.core:Binary legt die automatische Migration Ticket_29852_binary_attribute_values (Modul tl-element) beim Start an. Eine Datenmigration ist nicht nötig: Ein mehrwertiges Binärattribut konnte bisher keinen Wert speichern.