GrFormFile
Binds file uploads to a form field and validation.
Overview
When to take it
- the file is the value of a field — a CV, a scan, an attachment: it is sent by the form, not by the component;
- the file is checked before submission — the size, the type, the number through
validators; - there are several files —
multipleandlimittogether with a list of what has been chosen; - a preview is needed —
previewshows a thumbnail of an image before the submission.
When to take something else
| Need | Take |
|---|---|
| The file leaves for the server at once, with progress | GrFileUpload |
| The image has to be examined | GrImageViewer |
| The value is not a file | GrInput |
The border with GrFileUpload runs along who does the sending. Here a file is an
ordinary v-model value, and it leaves together with the rest of the form; there the
component uploads by itself and shows the progress of every file.
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrFormFile uploadText="Attach contract" placeholder="Signed contract, PDF up to 10 MB" />Examples 3 each with a live demo and its code
A CV that travels with the application
The field only collects the file: it is a value of the model like the name, and nothing is uploaded until the whole application is sent. The
filerule of GrForm checks type and size on submit next to the other rules, whileaccepton the field filters the system dialog.Senior product designer · Dublin or remoteNorthwind Traders · Apply in two minutes Listing photos with a cover
The first photo becomes the cover, so the order belongs to the user:
reorderableadds a handle to each row, and moving one changes the model. A new pick is added to the set — the button says so withadd-text— andlimitchecks the whole set: a seventh photo is refused, not trimmed silently.previewdraws square thumbnails so rows do not jump with the shape of a photo.2-room flat in Alfama · draftCover photo: none yet Drag to reorder — the first one is the cover
No files selectedA receipt already claimed
validateruns after the built-in checks and may ask a server — here whether the receipt was already claimed.v-model:errorsis the one channel for what the checks found and what the server said, and theerrorslot words each issue by its code, with a link to the earlier claim.Taxi, 16 Oct · €24.60Client visit, Lisbon Photo or PDF of the receipt
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
disabled | boolean | undefined | false | Unavailable: choosing, dragging in and removal are switched off. Combined by "or" with `GrFormField` and the form. |
multiple | boolean | undefined | false | A set of several files: the model is an array, and every row has its own removal. |
readonly | boolean | undefined | false | Read-only: the value is visible and leaves with the form, but is not edited. |
invalid | boolean | undefined | false | The visual and ARIA state of an error. |
required | boolean | undefined | false | A required field (`aria-required`). |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The size of the buttons, the icons and the labels. |
placeholder | string | undefined | undefined | The text by the button while nothing is chosen. By default — the translated "No files selected". |
ariaLabel | string | undefined | undefined | The accessible name outside `GrFormField`. |
replace | boolean | undefined | false | A new choice in `multiple` **replaces** the set rather than adding to it — the old behaviour. Without it the set grows, duplicates (the same name, size and modification time) are dropped, and `limit` and the validators check the set as a whole. |
limit | number | undefined | undefined | The maximum number of files in the set. Extra ones are not truncated silently — the set is rejected with an error. |
validators | FileValidator[] | undefined | undefined | Checks of the set (`maxFileSize`, `allowedExtensionsValidator` and so on) — after `accept` and `limit`, before `validate`. A rejected set does not get into the model. |
accept | string | undefined | undefined | The W3C `accept` for the `<input type="file">` plus sugar over `acceptValidator(...)`. |
preview | boolean | undefined | false | Thumbnails for the pictures in the set. Files of other types stay a row. |
validate |
| undefined | Additional (custom) validation on the side of the consumer. |
uploadText | string | undefined | undefined | The label of the choose button while the set is empty. By default — the translated "Upload file". |
changeText | string | undefined | undefined | The label of the choose button when there are files already. By default — the translated "Change file". |
addText | string | undefined | undefined | The label of the choose button in `multiple` when there are files already: a new choice is added to the set. By default — the translated "Add files". |
removeText | string | undefined | undefined | The label of the removal for a single file and for the rows of the set. By default — the translated "Remove". |
clearAllText | string | undefined | undefined | The label of the reset of the whole set with `multiple`. By default — the translated "Clear all". |
reorderable | boolean | undefined | false | The order of the set is set by the user: the row gets a drag handle. Needed where the sequence of the files means something — a gallery, the pages of a document, a queue of attachments. Meaningful only with `multiple`. |
errorsv-model | FileValidationIssue[] | undefined | undefined | The controlled list of errors: `v-model:errors`. If it is set, it is what is shown, and the internal validation does not overwrite it. The errors that came from the server are put here as well. Unset — the component keeps its errors itself. |
modelValuerequiredv-model | File | File[] | null | — | The chosen file or `null`; with `multiple` — an array of files (`v-model`). |
Slots
| Slot | Type | Description |
|---|---|---|
error | { errors: FileValidationIssue[]; } | Your own presentation of the errors instead of the default list. |
Events
| Event | Signature | Description |
|---|---|---|
update:modelValue | (event: "update:modelValue", value: File | File[] | null): void | — |
change | (event: "change", value: File | File[] | null): void | — |
clear | (event: "clear"): void | — |
focus | (event: "focus", event: FocusEvent): void | — |
blur | (event: "blur", event: FocusEvent): void | — |
update:errors | (event: "update:errors", errors: FileValidationIssue[]): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
focus | () => void | Put the focus on the choose-file button. |
blur | () => void | Remove the focus from the choose-file button. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrFormFileError, GrFormFileProps, GrFormFileEmits } from '@feugene/granularity/components/GrFormFile'Filedist/types/components/GrFormFile/config.d.ts
const grFormFileConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrFormFile">;Filedist/types/components/GrFormFile/GrFormFile.vue.d.ts
type GrFormFileError = FileValidationIssue;interface GrFormFileProps {
modelValue: File | File[] | null;
multiple?: boolean;
disabled?: boolean;
readonly?: boolean;
invalid?: boolean;
required?: boolean;
ariaLabel?: string;
accept?: string;
validators?: FileValidator[];
limit?: number;
uploadText?: string;
changeText?: string;
addText?: string;
replace?: boolean;
removeText?: string;
clearAllText?: string;
placeholder?: string;
size?: GrFormFileSize;
preview?: boolean;
reorderable?: boolean;
validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>;interface GrFormFileEmits {
(e: 'update:modelValue', value: File | File[] | null): void;
(e: 'change', value: File | File[] | null): void;
(e: 'clear'): void;
(e: 'update:errors', errors: GrFormFileError[]): void;
(e: 'focus', event: FocusEvent): void;
(e: 'blur', event: FocusEvent): void;
}Filedist/types/components/GrFormFile/defaults.d.ts
interface GrFormFileConfigurableProps {
size: GrComponentSize;
}Filedist/types/components/GrFormFile/grFormFileStyles.d.ts
type GrFormFileSize = GrComponentSize;Filedist/types/components/GrFormFile/safelist.d.ts
const grFormFileSafelist: string[];Filedist/types/components/GrFormFile/index.d.ts
type GrFormFileInstance = ComponentExposed<typeof GrFormFileComponent>;Usage from the library prose
The errors are a controlled value
v-model:errors is a two-way channel: the internal validation writes into it, and the
consumer puts the errors that came from the server into the same place. While the prop is
set, it is stronger than the internal list (the same scheme as sortKey in GrDataTable).
<GrFormFile v-model="files" v-model:errors="errors" accept="application/pdf" multiple :limit="3" />There is one channel: the validation emit duplicated update:errors with the same payload
and has been removed.
The list of errors is declared role="alert" and is linked to the choose button through
aria-describedby — together with the aria-describedby from GrFormField, if the field
is inside one. While there are errors, the button carries aria-invalid. Before that,
“dropped a file of the wrong type” looked to a screen reader like “nothing happened”.
The validation is one for both ways of input
The set of validators is assembled in one place and goes both into the choice through the
dialog and into v-dropzone: two copies of that assembly drift apart at the very first
edit, and dragging starts behaving differently from the dialog.
The order: accept → limit → the consumer’s validators → validate. limit is sugar
over maxCountValidator: extra files are not truncated silently, the set is rejected with
an error, as by any other rule.
A choice in multiple is added to the set
A new choice — through the dialog or by dragging — is appended to the files already
chosen: a gallery and a list of attachments grow rather than lose what was chosen with every
dialog. A repeat of a file already chosen (the same name, size and modification time) is
discarded. The button of a non-empty set is “Add files” (gr.formFile.add, a text of your
own — addText), and starting over is “Clear all”.
The validators see the set as a whole: limit, maxTotalSizeBytesValidator and
validate check what will result after the addition, not the last choice. A rejected
choice leaves the set alone — the previous one remains, together with an error.
Replacing the set on every choice — the old behaviour — is available explicitly: replace.
The button then reads “Change file” again.
The list of files
In multiple every row shows the name and the size, and the remove button names its file
(aria-label) — three “Remove” buttons in a row are indistinguishable to a screen reader.
A row takes the full width of the column: a long name is truncated with an ellipsis (the
full one is in title), the size and “Remove” stand at a shared right edge, and in a narrow
container the button is not pushed out.
Disabled is dimmed with the cursor and with the state of the buttons themselves: an
opacity on the container would dilute both the labels and the names of the files.
The order is set by the user
reorderable adds a drag handle to a row. Needed where the sequence of the files means
something: a gallery, the pages of a document, a queue of attachments. Meaningful only with
multiple.
The mechanics are taken from GrSortableList rather than written
anew: dragging with the pointer, the keyboard (Space picks the row up, the arrows move it,
Esc cancels), auto-scrolling and announcements are already there. The price is honest — its
styles arrive to everyone who takes GrFormFile.
It is the handle that is dragged, not the row: the row holds the remove button, and dragging the whole row would take the click away from it.
Reordering emits update:modelValue and change — like removing and adding. Without the
second the order would remain on the screen only, and the previous one would leave for the
form. With readonly and disabled dragging is forbidden.
Previews of pictures
preview switches thumbnails on: they appear for image/* files, and a file of any other
type stays an ordinary row. A thumbnail is square and is cropped with object-cover —
otherwise the rows of the list would jump in height following the proportions of the shots.
<GrFormFile v-model="gallery" multiple preview accept="image/*" />The alt of a thumbnail is empty: the name of the file stands right beside it, and there is
no point announcing it twice. The object URL lives exactly as long as the file is in the
set — it is revoked as soon as the file has left the set, whatever removed it.
readonly
The set is visible and leaves with the form, but is changed by nothing: not by the
choice dialog, not by dragging, not by the buttons — they are not rendered in this state.
The choose button stays in the tab order and announces aria-readonly: a field has to be
reachable from the keyboard and able to explain why it does not yield.
The difference from disabled: that one switches the button off as well, that is, the field
drops out of the walk entirely.
Inside GrForm
The same constraints can be declared as a rule of the form — beside the rest:
const rules: GrFormRules = {
contract: [{ required: true, file: { accept: '.pdf', maxSizeMb: 1 } }],
}The checking is done by the same validators, so the text of the error does not change — what
changes is the moment: the rule of the field keeps a bad file out of the model at once, the
rule of the form rejects the submit and enters invalid and the scroll to the first error.
The details and the full list of keys — GrForm.md.
The typical division: the constraints in rules and accept on the field as a filter for
the choice dialog.
What is missing
Uploading to a server: GrFormFile is a form field, and the files leave together with it.
Progress and cancellation are covered by GrFileUpload.
Props in this example