Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the checkbox optional validator #30

Merged
merged 1 commit into from
Mar 6, 2024

Conversation

seanpdoyle
Copy link
Owner

While <input type="checkbox"> elements do support built-in Constraint Validations like ValidityState.valueMissing, most of the ValidityState properties will always be false. The Constraint Validations API determines the form control's ValidityState.valueMissing property from its required attribute.

When a form requires that a single <input type="checkbox"> choice (like an acknowledgement of terms) is checked, the built-in support works well enough. When a form requires that at least one checkbox in a group of checkboxes is checked, the built-in support can be more strict than expected. For example, if there were multiple <input type="checkbox"> elements with the same [name] attribute, and each element had the [required] attribute, they would all need to be checked to be considered valid.

ConstraintValidations-powered validations support an experimental checkbox: validator option to validate <input type="checkbox"> elements that share the same [name] attribute as a group. To opt-into support, configure the ConstraintValidations instance:

// configure with the constructor
const validations = new ConstraintValidations(element, {
  validators: {
    checkbox: true
  }
})

// configure with the static helper method
ConstraintValidations.connect(element, {
  validators: {
    checkbox: true
  }
})

// configure with a function that accepts a form field element
ConstraintValidations.connect(element, {
  validators: {
    checkbox: (fields) => fields.some(field => field.name === "special[field]")
  }
})

Then, render a group of <input type="checkbox"> elements as [required]:

<fieldset>
  <legend>Multiple [required] checkboxes</legend>

  <%= form.validation_message :multiple_required_checkboxes %>

  <%= form.collection_check_boxes :multiple_required_checkboxes, [
        ["1", "Multiple required checkbox #1"],
        ["2", "Multiple required checkbox #2"]
      ], :first, :second do |builder| %>
    <%= builder.check_box required: true %>
    <%= builder.label %>
  <% end %>
</fieldset>

How it works

To work-around the quirks of built-in support, ConstraintValidations monitors when <input type="checkbox" required> elements are connected to the document.

Once connected, ConstraintValidations removes their [required] attribute, then replaces it with an [aria-required="true"] attribute instead. During form control validation, it utilizes the [aria-required="true"] attributes to determine whether or not the collective group meets the ValidityState.valueMissing criteria.

This technique integrates with other built-in mechanisms like:

  • matching the [aria-invalid="true"] CSS selector
  • matching the :valid CSS selector when valid
  • matching the :user-valid when valid
  • matching the :user-invalid when invalid

However, its deviates from other built-in mechanism. For example:

  • checkboxes will not match the :required CSS selector
  • checkboxes will always match the :optional CSS selector

@seanpdoyle seanpdoyle force-pushed the checkbox-group-validations branch from c2b1980 to 61d7023 Compare March 6, 2024 19:41
While [`<input type="checkbox">` elements *do* support built-in
Constraint Validations][input#client-side_validation] like
[ValidityState.valueMissing][], most of the [`ValidityState` properties
will always be `false`][checkbox#validation]. The Constraint Validations
API determines the form control's `ValidityState.valueMissing` property
from its [required][] attribute.

When a form requires that a *single* `<input type="checkbox">` choice
(like an acknowledgement of terms) is [checked][], the built-in support
works well enough. When a form requires that _at least one_ checkbox in
a *group* of checkboxes is `checked`, the built-in support can be more
strict than expected. For example, if there were multiple `<input
type="checkbox">` elements with the same `[name]` attribute, and each
element had the `[required]` attribute, they would *all need to be
checked* to be considered valid.

`ConstraintValidations`-powered validations support an experimental
`checkbox:` validator option to validate `<input type="checkbox">`
elements that share the same `[name]` attribute as a group. To opt-into
support, configure the `ConstraintValidations` instance:

```js
// configure with the constructor
const validations = new ConstraintValidations(element, {
  validators: {
    checkbox: true
  }
})

// configure with the static helper method
ConstraintValidations.connect(element, {
  validators: {
    checkbox: true
  }
})

// configure with a function that accepts a form field element
ConstraintValidations.connect(element, {
  validators: {
    checkbox: (fields) => fields.some(field => field.name === "special[field]")
  }
})
```

Then, render a group of `<input type="checkbox">` elements as `[required]`:

```erb
<fieldset>
  <legend>Multiple [required] checkboxes</legend>

  <%= form.validation_message :multiple_required_checkboxes %>

  <%= form.collection_check_boxes :multiple_required_checkboxes, [
        ["1", "Multiple required checkbox #1"],
        ["2", "Multiple required checkbox #2"]
      ], :first, :second do |builder| %>
    <%= builder.check_box required: true %>
    <%= builder.label %>
  <% end %>
</fieldset>
```

How it works
---

To work-around the quirks of built-in support, `ConstraintValidations`
monitors when `<input type="checkbox" required>` elements are connected
to the document.

Once connected, `ConstraintValidations` removes their `[required]`
attribute, then replaces it with an `[aria-required="true"]` attribute
instead. During form control validation, it utilizes the
`[aria-required="true"]` attributes to determine whether or not the
collective group meets the `ValidityState.valueMissing` criteria.

This technique integrates with other built-in mechanisms like:

* matching the `[aria-invalid="true"]` CSS selector
* matching the [:valid](https://developer.mozilla.org/en-US/docs/Web/CSS/:invalid) CSS selector when valid
* matching the [:user-valid](https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid) when valid
* matching the [:user-invalid](https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid) when invalid

However, its deviates from other built-in mechanism. For example:

* checkboxes will not match the [:required](https://developer.mozilla.org/en-US/docs/Web/CSS/:required) CSS selector
* checkboxes will always match the [:optional](https://developer.mozilla.org/en-US/docs/Web/CSS/:optional) CSS selector

[input#client-side_validation]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#client-side_validation
[ValidityState.valueMissing]: https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/valueMissing
[checkbox#validation]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#validation
[required]: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required
[checked]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#instance_properties_that_apply_only_to_elements_of_type_checkbox_or_radio
@seanpdoyle seanpdoyle force-pushed the checkbox-group-validations branch from 61d7023 to c520fc1 Compare March 6, 2024 20:18
@seanpdoyle seanpdoyle merged commit 963269a into main Mar 6, 2024
56 checks passed
@seanpdoyle seanpdoyle deleted the checkbox-group-validations branch March 6, 2024 22:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant