Lattice Grid Buy a licence

forms and the record editor · v1.107.0

JavaScript Form Builder and Record Editor

Lay a form out as fieldsets, rows and columns, validate it, and bind it to a row. The order below is a form on top and its lines in a grid underneath, edited as one record: change the status and a quantity, and one Save writes both.

Try the record editor Free on localhost

Building…
Loading a live grid…

a form you can submit

Leave the required fields empty and press Save.

Lay a form out as fieldsets, rows and columns, give each field a control that fits it, and check it all when the user presses Save. The form below is live: submit it empty, then fill it in and submit again.

Loading a live form…

Read the form API → Place it in an application layout →

rows and cols

Rows stack, cols sit side by side.

A form is a tree of rows and cols, each nesting the other to any depth. A rows block stacks its children top to bottom. A cols block places them side by side and shares the width equally unless a child sets its own, and the shell module reads the two words the same way. Give a child a percentage width and it takes that share of the row, so three items of 33.3% sit on one line. Below a container width you choose, a cols block stacks into a single column.

what the form gives you

The controls a record needs, and the checks behind them.

Controls

Every control a record needs: text and number inputs, selects and a searchable combo, date, time and date range pickers, colour, rating, chips, a list box, a toggle group, an avatar and a rich-text editor. Each one works from the keyboard, in the four design-system presets and in dark mode. See every control.

Layouts

Fieldsets, rows, cols, spacers and separators. Widths in percent, or in pixels, or flexible. Density levels of compact, standard and comfortable, and a form beside a grid follows that grid’s row height. Columns can wrap, so a row of percentage widths flows onto further lines and each line fills exactly.

Labels where you want them

Labels on the left or on top, at a width you set, or hidden from view while staying the field’s accessible name. Set it for the form, a fieldset or one item. A left label moves to the top when the control would otherwise be squeezed, so a narrow form stays readable.

Number formats and units

A number field shows currency, percent or fixed decimals while it is not focused and stores a plain number. A unit-aware field converts what is typed, so 145 psi into a bar field stores the value in bar, and a temperature scale converts too.

Disabled and read-only

One contract for every control. Disabled leaves the tab order and is skipped by validation. Read-only stays focusable and selectable, refuses every change, and still validates and still appears in the values. Set them per item or for the whole form.

Validation

Required fields, built-in rules for email, number, integer and url, a regular expression, or a function that returns a message. Check on submit, on change or on blur. Failures carry aria-invalid and a message under the field.

Rules that react

visibleWhen, readOnlyWhen and requiredWhen re-run on every committed change, so a field can appear, lock or become required from the value of another.

A stepper for long forms

Give the form a list of steps and it becomes a wizard under a step indicator, horizontal or vertical. Next validates the current step first, Back never does, and the last step reads Finish.

A grid as a field

Put an editable grid inside the form and its rows become the field’s value. Required cells and column rules are checked with the rest of the form, and the message names the row and the column.

Bound to a grid or the data router

Bind a form to a grid row and it builds one item per column when you declare no layout, tracks which items are dirty, and saves through the grid’s own edit path. Bind it to a data router record and it follows the feed without overwriting what a user is typing.

Files and combos

A file drop area accepts drop, paste or pick, enforces type, size and count, and shows progress with retry and remove. A combo pages and searches a server list, takes avatars, icons and dots on its options, and can allow new entries.

Every framework, and htmx

Wrappers for React, Vue, Svelte and Angular. On an htmx page, a server-rendered form is enhanced in place with a data attribute, keeping its field names and its posts, with validation before the post goes out.

Every control

a form from a config

Describe the fields, get the form.

Drop in one script tag, then hand createForm your rows. Read the values with getValue(), write them with setValue(), and listen for change or submit.

<!-- the form module, as a global -->
<script src="https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/form.min.js"></script>

<script>
  var form = LatticeGridForm.createForm(document.querySelector('#deal'), {
    rows: [
      { fieldset: 'Opportunity', cols: [        // cols sit side by side
        { type: 'input', name: 'name', label: 'Name', required: true, width: '33.3%' },
        { type: 'input', name: 'account', label: 'Account', required: true, width: '33.3%' },
        { type: 'combo', name: 'owner', label: 'Owner', options: people, width: '33.3%' }
      ] },
      { type: 'number', name: 'amount', label: 'Amount',
        format: { style: 'currency', currency: 'USD' }, min: 0 },
      { type: 'buttons', align: 'end', items: [
        { name: 'save', label: 'Save', variant: 'primary', buttonType: 'submit' }
      ] }
    ]
  });

  // A submit button validates first, then fires submit with the values.
  form.el.addEventListener('submit', function (e) { save(e.detail.values); });
</script>

form groups

Several forms, one record.

Put a form in each cell of a layout and join them with createFormGroup. The group reads, writes, validates and locks as one form, and binds to a data router record and saves back through it.

var header = LatticeGridForm.createForm(headerCell, { rows: [
  { type: 'input', name: 'name', label: 'Name', value: 'Ada' },
  { type: 'input', name: 'plan', label: 'Plan', value: 'team' }
] });
var details = LatticeGridForm.createForm(detailsCell, { rows: [
  { type: 'input', name: 'email', label: 'Email', value: 'ada@example.com' },
  // This rule watches a field the header form owns.
  { type: 'input', name: 'coupon', label: 'Coupon',
    visibleWhen: { name: 'plan', equals: 'enterprise' } }
] });

var group = LatticeGridForm.createFormGroup([header, details]);
group.getValue();                           // both forms, merged
group.setValue({ email: 'grace@example.com' }); // routed to the details form
group.validate();                           // one answer for every form

The record editor →

docs

Reference and the rest of the product.