Skip to main content

upload — a drag-and-drop file zone

UiUpload renders a drop area with a prompt, a browse button and a hidden <input type="file">. The event bus wires both paths — dropping files onto the zone and picking them through the button — and fires onUpload as soon as files arrive. There is no separate "start upload" step.

Reach for it when file intake is the point of the screen. For a single inline picker inside an ordinary form, a field of type FILE is the smaller node.

Live — drop image files on the zone, or use "Choose files…". Nothing is sent anywhere: onUpload is an INVOKE trigger whose handler reads the File objects in the browser and patches their names into the line below.

Fields

FieldTypeMeaning
idStringNode id and the zone's DOM id. Also the default multipart part name.
labelStringCaption above the drop zone. Omitted when absent.
hintStringSmall helper text under the zone, e.g. "PNG or JPG, max 5 MB".
nameStringMultipart field name the files are sent under. Defaults to id.
acceptStringHTML accept filter ("image/*", ".pdf,.docx"). Null = any file.
multiplebooleanAllow more than one file per drop/pick. Defaults to false.
buttonLabelStringBrowse-button text. Renderer default: "Browse…".
dropTextStringPrompt inside the zone. Renderer default: "Drag files here or".
onUploadUiTriggerFired when files are dropped or picked. Usually UPLOAD; INVOKE for client-only handling.
cssClassStringExtra CSS class on the zone wrapper.

Building one

// The common case: multipart POST the files, apply the returned page/patch.
UiUpload.of("images", "Product images")
.hint("PNG or JPG, max 5 MB each.")
.accept("image/*")
.multiple()
.dropText("Drag images here or")
.buttonLabel("Choose files…")
.uploadTo("/api/products/42/images");

// Same thing spelled out, with a custom part name and method:
UiUpload.of("contract", "Signed contract")
.name("file")
.accept(".pdf")
.onUpload(UiTrigger.upload("PUT", "/api/contracts/7"));

// Client-only: the handler receives the File objects, no backend involved.
UiUpload.of("preview", "Preview an image")
.accept("image/*")
.onUpload(UiTrigger.invoke("previewImage"));

Notes

UPLOAD is its own behaviour. It does not send the JSON payload an ordinary trigger would. The bus builds a FormData, appends every selected file under name (falling back to the node id, then to "files"), POSTs it as multipart/form-data, and applies the response exactly like APPLY_RESPONSE — so the server answers with a UiPage or a UiPatch.

INVOKE gets the raw files. A client handler receives them on ctx.files as browser File objects, which is enough for a preview, a size check or a fully offline demo. Return a patch to fold the result back into the page, or return nothing if the handler already updated things itself.

Upload fires on arrival, not on submit. The trigger runs on drop and on the hidden input's change. An upload zone sitting inside a form is therefore independent of that form's Save button — do not expect the files to travel with the form's JSON payload.

accept filters the picker, it does not validate. The browser's dialog honours it, drag-and-drop largely does not, and neither is a security boundary. Check type and size on the server and answer with a patch that sets the error.

Feedback is yours to send. The node has no built-in progress bar or file list. Render one by returning a patch that replaces a neighbouring node — a list of uploaded files, a progress, or a plain text as in the specimen above.

See also

  • fieldFieldType.FILE for a single inline picker.
  • form — the surrounding container.
  • fieldgroup — grouping an upload with related inputs.
  • Forms — the wider form story.
  • Triggers & actions — the UPLOAD and INVOKE behaviours.