verbb / formie

The most user-friendly forms plugin for Craft CMS.
Other
95 stars 72 forks source link

How to disable range rule for checkboxes? #1700

Open mattcdavis1 opened 8 months ago

mattcdavis1 commented 8 months ago

Question

Previously I was able to disable range validation for checkboxes via the below code:

Event::on(Submission::class, Submission::EVENT_DEFINE_RULES, function(SubmissionRulesEvent $event) {
    // exclude range rules
    $modifiedRules = [];
    foreach ($event->rules as $rule) {
        if (empty($rule['range'])) {
            $modifiedRules[] = $rule;
        }
    }
    $event->rules = $modifiedRules;

    return true;
});

However, now the rules being passed in to this event are just a subset of the rules being applied. The range rule is now associated directly with the element. I can loop through layout elements to access these rules but I don't see a way to set the rules so that the range rule is omitted.

The thing I'm trying to accomplish is to add a text field when an "Other" checkbox is checked where the user can write-in a value. This works fine on the FE with a few lines of javascript and a bit of HTML customization but fails backend validation due to the range rule. I'm running into this issue as part of an update from Craft 3 to Craft 4. The functionality works fine in Craft 3.

Additional context

No response

mattcdavis1 commented 8 months ago

The below code works but doesn't seem like an ideal solution

Event::on(Submission::class, Submission::EVENT_DEFINE_RULES, function (SubmissionRulesEvent $event) {
  $submission = $event->sender;

  // exclude range rules (for custom checkbox values)
  $fieldLayout = $submission->getFieldLayout();
  $layoutElements = $fieldLayout->getVisibleCustomFieldElements($submission);

  foreach ($layoutElements as $layoutElement) {
    $field = $layoutElement->getField();

    if ($field instanceof Checkboxes) {
      $defaultValues = [];
      foreach ($field->options as $option) {
        $defaultValues[] = $option['value'];
      }

      foreach ($field->getValue($submission) as $value) {
        if (!in_array($value, $defaultValues)) {
          $field->options[] = [
            'label' => '',
            'value' => $value,
            'isDefault' => false,
          ];
        }
      }
    }
  }

  return true;
});
engram-design commented 8 months ago

The mechanism for validation for fields did change a bit in Craft itself from 3 to 4 where they moved things from Element::defineRules() to Element::afterValidate().

As such, any rules you register via Submission::EVENT_DEFINE_RULES for a field will be too early in the process. In the Element::afterValidate() they will be overridden. The docs are still correct that this is an appropriate event to register your own rules, but they aren't for overriding any.

What you're after is tricky, as it goes against using an options-based field, where you can only pick from the allowed options. I can certainly see a feature request for adding the ability to add an arbitrary "Other" value to the field, but that's non-trivial at the moment.

There are Twig functions to add dynamic options to the available options for the field, but that's not really going to work in your use-case. I think what you have in the second post is certainly valid, albeit a little lengthy. I would need to introduce a new event to modify the getElementValidationRules() function for fields to simplify this for you.

mattcdavis1 commented 8 months ago

@engram-design - got it, thanks. Can you update the label on this from "Question" to "Feature Request"? The feature would be to allow both fixed and variable options for checklist groups and dropdowns.

For my purposes, resolving the validation issue was the primary challenge, because the data immediately gets sent in a notification email. However, it would be nice to also resolve the other issue associated with this functionality which is to show the user-entered value in the submission detail of the CMS. The above solution does not accommodate this (not sure if the user-entered data is still available at this point or not).

engram-design commented 8 months ago

Being able to manipulate an options-based field comes up increasingly often. Mostly it's because people want to populate a field that looks like a dropdown with an option, and then expect that to "just work". But that fundamentally goes against the reason of having a field that's limited to certain options.

There's plenty of options to look at:

Option 2 is my preference so far, as I think it's cleaner and makes sense. Consider this a feature request, but keen to see what others say.

mattcdavis1 commented 8 months ago

I think "other" makes sense in the Checkbox rendering but for a dropdown I think just a standard Combobox implementation would be best. There could be an "Allow user input" option in the form settings for dropdown field types and for checkboxes a flag on the option to add an input field when selected. Was initially thinking the same setting for both options but for checkboxes a single "Other" field wouldn't accommodate the potential for multiple checkbox items to accept user input when selected.

Hmm - just realizing that there may be a need for multiple "other" type fields for checkboxes so maybe it would be best to indicate this functionality on the option itself.

engram-design commented 8 months ago

For (single) Dropdown fields, we probably need another option to render it as a ComboBox or a ListBox. Although from most examples of ComboBoxes I've seen, they still don't allow arbitrary values, they're just a glorified autocomplete. I'm still unsure how to handle that, or if we should. Having an "other" field isn't a pattern I've seen.

That also ties into adding autocomplete behaviour to a Dropdown field, which would be a good option. We need to hunt down a lightweight JS control though (none I've found are amazing).

just realizing that there may be a need for multiple "other" type fields for checkboxes so maybe it would be best to indicate this functionality on the option itself.

I wasn't thinking to offer multiple "other" controls within the one field, what did you mean by this one?

Sorry this has skewed into a larger convo!

mattcdavis1 commented 8 months ago

@engram-design - see example here of the "Preferred Platform" checkbox. I could see where there would be potential for more than one checkbox to be used in this way within the same group. Probably not a huge use case but would be nice to be able to accommodate this.

https://capture.dropbox.com/TPioyZwNnf3Ek7F3

engram-design commented 8 months ago

Ah, gotcha, appreciate the example! I'll take that on board.