Forms, switch product
announcements

GolemUI v2.1.1: validation and repeater fixes, and a deprecation schedule

GolemUI v2.1.1 is released. It is a patch release after v2.1.0, with no new widgets. Most of it is bug fixes, but a few behaviours change, so read Upgrading before you update.

A hidden input no longer makes the form invalid. In 2.1.0 its error still counted, so a submit button disabled with $formIsInvalid stayed disabled because of a field the user could not see. Now the error leaves $errors, and $formIsInvalid and isFormValid update at once. Check the box below, click in the VAT number, then click outside it. The error shows and Pay is disabled. Now uncheck the box:

The other validation fixes:

  • A cleared optional string is valid. With validator: { minLength: 3 }, a user who types a letter and deletes it gets no error. A string validator without required treats '' like undefined.
  • uniqueItems is checked. The array validator compares the items by value, with a default message and a messages.uniqueItems override.
  • Injected issues update isFormValid at once, so a server error on an input marks the form invalid with no other validation.
  • A language change translates the errors on screen. The form validates again each input that has an error, so its message and $errors use the new language.
  • A submit validates once. One click gives one VALIDATE_ALL action, so each validator runs once.

See Validators.

A length or count limit now goes in the validator. The field lets the user type, paste or add past the limit, shows that the value is over it, and marks the form invalid. No widget blocks input.

  • The maxLength prop of the textarea and the markdown editor has no effect, and it is deprecated. Use validator.maxLength, which controls the counter and the error.
  • The limit prop of the repeater disables the Add button with no message, and it is deprecated. Use validator.maxItems. The Add button stays enabled, so the user can add a row and then remove another one.

This repeater takes at most two contacts. Add a third one, and the error shows at once. Remove one, and the error goes away:

The JSON Schema converter follows the same rule: maxItems goes in the validator, and the repeater gets no limit.

gui-tags and the Tags widget now skip a tag that the value already holds. They show no error, because the value does not change. Type the same tag twice:

To let the user add the same tag more than once, set allowDuplicates: true in a form, or the allow-duplicates attribute on the element.

The input checks only the tags that the user adds. A value from a server can still hold ['vip', 'vip']. To reject it, use validator: { uniqueItems: true }.

Pasting also works better. A paste goes in at the caret and replaces the selected text, and a paste that adds no tag keeps the text in the input. Escape in an empty input can now close a dialog that holds the field.

A row has state besides its data: its touched state, its errors, the issues your app injects and the prop overrides your app sets by path. In 2.1.0 this state stayed at its position, so after a removal the next row received the state of the removed one. Now the Remove button reports the row it removed, and the state moves with the rows, also in nested repeaters.

Take the rows [Alice, Bob], and an app that makes the name of Bob read-only:

event.callback({
type: 'OVERRIDE_WIDGET_PROP',
payload: { path: 'people.1.name', prop: 'readonly', value: true },
});

The user removes Alice. Bob is now row 0, and his name is still read-only. A row that the user adds later is not read-only.

Two cases stay at their positions: setData with a new array, because the form can’t know which new row was which old row, and an error that a widget reports itself, such as a rejected date. See State of a row.

A custom repeater reports the removal with onRowRemoved(rows, index) in React and Vue, or rowRemoved(rows, index) on the adapter in Angular and Lit. See Input widgets.

An invalid form definition now gives a health error instead of an exception, so your app can show its own message.

  • Code 22 now also covers no definition at all (null, for example when a backend returns no form), a repeater without a template, and a widget function that throws. The form calls a row function once without a row when it builds the form, so read $item with ?..
  • Code 60 is new. A validator failed to run: a JSON form names a custom rule that is not registered, or a custom validator throws or runs an async check. The message has the path of the input, and the form does not submit. The error stays until you pass a new config.

See the list of codes and When a validator fails to run.

  • Values stay as they are. A Date, a Map or a class instance stays the same object in formEvent and formSubmit.
  • A null parent acts like a missing one. With data: { order: null }, a repeater at order.lines renders no rows, and the form health is ok.
  • Data paths read own keys only. An input at the path toString works. A __proto__ key in the data is plain data, and it never changes a prototype.
  • Hidden data keeps what a visible input uses. A dropdown at country for some customers and a text input at country for the others can share one value.
  • An i18n param keeps its value. params: { count: 3 } hands the translator a number, so plural rules work. An expression param gives its result, also null or undefined, and never the expression text.
  • gui-currency uses the decimals of the currency: 2 for USD, 0 for JPY, 3 for KWD. In 2.1.0 it showed 2 unless you set otherwise.
  • gui-number, gui-currency and gui-slider read a numeric string on the value property as its number, and gui-otp takes a number.
  • gui-select, gui-radiogroup and gui-list: the attribute value="2" selects the number option 2.
  • gui-select and gui-radiogroup: label-field and value-field win over the objects’ own label and value keys.
  • gui-select: a value that matches no option is not submitted, and it blocks the submit.
  • gui-range-slider: a value with its ends reversed reads low end first, and a value with only one end is no value.
  • gui-radiogroup fires gui-blur only when the focus leaves the group.
  • gui-number fires no events for a step stopped at a bound.
  • gui-file-upload with required gives its upload button “Required” as its description, for screen readers.
  • gui-markdown: the toolbar no longer treats an _ inside a word, as in snake_case, as italic.
  • An unknown item renderer uses the default renderer. Several apps can share one JSON form, and one app may not register every renderer the form names. In dev mode the console warns once.
  • setData and setMeta before the form starts are kept in Lit and Angular. The last call replaces the data or meta of the config.
  • React cancels a pending debounced call on unmount, so a dropdown sends no filter event after its widget is removed. Its dropdown also shows a value that no item matches, as the other frameworks do.
  • Vue keeps the exact values of the other rows when a repeater row is removed. Date, undefined and NaN values used to change.
  • Angular lets Enter in a dropdown field submit the form, and binds on-demand widgets before their elements first render.
  • Lit does not throw when a dropdown filter clears before its items load.
  • A removed prop override also removes the prop, when only the override had set it.

DEPRECATIONS.md lists every deprecated API, what to use instead and the release that removes it. All of these still work, and v3.0.0 removes them:

APIUse instead
maxLength prop of the textarea and the markdown editorvalidator.maxLength
limit prop of the repeatervalidator.maxItems
locale-id attribute of gui-numberNothing. It has no effect
The flex layout: type: 'flex', gui.layouts.flex, horizontalFlex and verticalFlexgrid, horizontalGrid, verticalGrid
autoFit, columnGap, rowGap and align props of the grid layoutcolumns, gap, gap, justify

A few changes need your attention when you upgrade:

  • Upgrade every @golemui/* package to 2.1.1 at once.
  • Tags: if your users must be able to add the same tag twice, set allowDuplicates: true.
  • Currency: JPY now shows no decimals and KWD shows 3. To keep a fixed number of decimals, set minimumFractionDigits and maximumFractionDigits.
  • Optional strings: a cleared optional field no longer fails minLength, pattern or format. If the field must not be empty, set required: true.
  • uniqueItems: a validator that sets it now rejects repeated items.
  • JSON Schema: a form you generate again from a schema with maxItems has an enabled Add button past the cap, and an error. A form you generated before keeps its limit, which works until v3.0.0.
  • Select: a value that matches no option now blocks a native form submit.
  • Translators: a param can now be a number, null or undefined. A translator that expects only strings must handle them.
  • Error handling: if you catch exceptions from a broken form definition, listen to formHealth instead.
  • Custom validators: register every custom rule that a JSON form names. A rule that is missing, that throws or that runs an async check now gives code 60.
  • Custom submit widgets: onClick no longer validates. A widget with no form element must call onSubmit after onClick.
  • Custom repeaters: report a removed row with onRowRemoved or rowRemoved. Without it, the state of the rows stays at its position.

The full list of changes is in the changelog.

Previous GolemUI v2.1.0: overlays, sliders, one-time codes and row events
Next You are at the latest post