FormBuilder Component
A FormBuilder layout can differ per Vuetify breakpoint, on the form, a row or a column.
Responsive Layout
The demo's base layout is the xs one; sm, md and lg state their own, and xl uses the lg one:
- xs: the base layout
- sm, md, lg: a layout of their own
- xl: the lg layout
Registration
Features
- Breakpoint overrides on the form, a row or a column
- A nested layout resolves its own breakpoints
- Teleport-anchored cells keep their id across breakpoints - see Template-declared content (Teleport)
Responsive Designs
Overrides on each level:
const formBuilder = new FormBuilder();
// Base layout: from xs up to the first breakpoint that states an override
formBuilder
.row({ }, (row) => row
.col({ cols: 12 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'Name', density: 'compact' }))))
.row({ }, (row) => row
.col({ cols: 12 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'Email', density: 'compact' }))));
// Row-level breakpoint: this row only
formBuilder.row({ }, (row) => row
// 2 columns
.col({ cols: 6 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'City' })))
.col({ cols: 6 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'State/Zip' })))
// 3 columns from md upwards
.breakpoint('md', (row) => row
.col({ cols: 4 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'City' })))
.col({ cols: 4 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'State' })))
.col({ cols: 4 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'Zip' })))));
// Column-level breakpoint: this column only
formBuilder.row({ }, (row) => row
.col({ cols: 12 }, (col) => col
// 12 columns wide; 3 from md upwards
.breakpoint('md', (col) => { col.props.cols = 3; return col; })
.component((cmpt) => cmpt
.generic('VTextField', { label: 'First field' })))
.col({ cols: 12 }, (col) => col
// 12 columns wide; 9 from md upwards
.breakpoint('md', (col) => { col.props.cols = 9; return col; })
.component((cmpt) => cmpt
.generic('VTextarea', { label: 'Comments' }))));
// Form-level breakpoint: from xl upwards the form has only these rows; the rows above, with their row and column
// breakpoints, do not render there
formBuilder.breakpoint('xl', (form) => form
.row({ }, (row) => row
.col({ cols: 6 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'Name' })))
.col({ cols: 6 }, (col) => col
.component((cmpt) => cmpt
.generic('VTextField', { label: 'Email' })))));What a breakpoint inherits
Breakpoints are mobile-first: the base layout applies from the smallest size up, and each breakpoint inherits what it does not state from the nearest smaller breakpoint that states it. A form, a row and a column each inherit separately.
A breakpoint states only what changes:
- Props are merged by key.
.breakpoint('sm', (col) => { col.props.cols = 12; return col; })keeps the column'soffsetand classes. - Content is inherited. A column keeps its components and a row its columns, unless the breakpoint adds content; then the added content replaces all of it, as in the row-level example above.
An empty list states that a breakpoint has no content, which differs from stating nothing:
formBuilder.row({ }, (row) => row
.col({ cols: 6 }, (col) => col.component((cmpt) => cmpt.generic('VTextField', { label: 'City' })))
.col({ cols: 6 }, (col) => col.component((cmpt) => cmpt.generic('VTextField', { label: 'Zip' })))
// nothing in this row from md upwards
.breakpoint('md', (bpRow) => { bpRow.columns = []; return bpRow; }));toJSON() keeps the difference: a breakpoint without content serializes without the rows / columns / components key, an empty list serializes as an empty list, and FormBuilder.fromJSON() reads it back into a layout that renders the same at every breakpoint.
The rules are those of ResponsiveRenderOptions in @dynamicforms/vuetify-inputs; its documentation describes them in full, including values that cannot be merged.
