orbeon / orbeon-forms

Orbeon Forms is an open source web forms solution. It includes an XForms engine, the Form Builder web-based form editor, and the Form Runner runtime.
http://www.orbeon.com/
GNU Lesser General Public License v2.1
513 stars 220 forks source link

XBL components with built-in validation #3298

Open ebruchez opened 7 years ago

ebruchez commented 7 years ago

We've had multiple use cases for XBL components which must validate their data from the inside. That is, those components have the knowledge of how the data they read and write should be validated. We don't have a good system for that.

Options we have considered:

See also #1193.

avernet commented 7 years ago

With what we have now, one way of doing this is to have the component maintain a valid attribute on the bound element, and contribute a constraint="@valid = 'true'" to the xf:bind in the form. One benefit of this approach is that it is the component itself that becomes invalid, rather than a control inside the component, so we can safely rely on the lhha XBL mode, and the user can add its own validation. However, amongst the downside of this technique, it doesn't allow for the component to supply its own alert message, per RFE #3300.

The validating.xbl, to be deployed in xbl/example/validating, and a reference to that file added to, say, the oxf.fb.toolbox.group.example.uri.*.* property, is an example of this technique.

ebruchez commented 7 years ago

For reference, as a general principle, we would like to keep the unidirectional data flow from model to view. The view communicates changes to the model using events or data binding (which could be seen as dispatching a special event). Having a bind in the view modifying MIPs in the model is therefore not acceptable, unless the MIPs are set using events.

ebruchez commented 4 years ago

Getting back to this from customer with components such as:

Some can get away with a simple regex, but some need a more elaborate algorithm to perform the validation.

Conceptually, validation is a function.

Let's remember that, right now, form validation is done via the error summary and is based on the validity of the controls (via xforms-valid/xforms-invalid). But most controls become valid/invalid via constraints in the model.

  1. Section templates are a major reason for this.
  2. Some XBL components might also do this? (Which? TODO).

Now, given this, we are really close by having components:

What we are missing here is:

Do we care about the XBL component itself being valid/invalid? It seems like it because we had issues with the LHHA mode and seeing duplicate errors in the Error Summary. In addition, conceptually, unlike with section templates, it is the component that is valid/invalid.

Alternatively, we could find a way to export a validation function from the component so that it would apply at the top-level model.

Assuming the following:

Could we implement logic whereby the validity of the XBL component would be impacted by the validity of the mirrored element? In other words, we wouldn't propagate the MIP to the main model, but we would have the external binding AND the internal one contribute to the validity of the control.

ebruchez commented 4 years ago

Another angle is the datatype. Right now, we have datatypes for numbers, for example. But we don't have datatypes for "US SSN", "UK phone number", etc. Should we move more towards a pluggable system of datatypes?

Say we have this. Then a "US SSN" component could simply have for example a type="fr:us-ssn" in its data template, and bind to xxf-type('fr:us-ssn') values. Formatting could be, in a first step, done by the component, like what we do for fr:number.

How would we plug such datatypes?

  1. We could have a custom format and declare a configuration.
  2. We could register this via an XBL component, which would say "Hey, I am the main handler for this datatype, and here is a validation function for it."
ebruchez commented 4 years ago

Concretely, what would the last comment entail?

  1. Add a declarative <constraints> section to the XBL binding.
  2. Add a way to plug a mapping datatype → constraints in an XForms model.
  3. When, in a form, a binding is available for a custom datatype, register a new validation mapping.
  4. Profit!

This would not be visible to Form Builder, so the user cannot change those built-in constraints for a datatype. The user can add other custom constraints as usual.

Q: What would happen if multiple components define <constraints> for the same datatype? A: They could be merged (AND logic), or we could take the first one only, or the last one, or cause an error. It probably should be avoided. Either a different datatype should be used, or one of the bindings should be removed.

Would this take us in the right direction?

Would we still benefit from implementing the "internal mirror instance validation impacts the component validity" part?

ebruchez commented 4 years ago

Suggestions for a <constraints> section:

<xxbl:constraints>
    <xf:constraint value="...">
        <xf:alert lang="en">My custom error message</xf:alert>
        <xf:alert lang="fr">Mon message d'erreur spécifique</xf:alert>
    </xf:constraint>
    <xf:constraint level="warning" value="xxf:max-length(42)">
        <xf:alert lang="en">My custom warning message</xf:alert>
        <xf:alert lang="fr">Mon message d'attention spécifique</xf:alert>
    </xf:constraint>
</xxbl:constraints>
<xbl:template>
    ....

We don't have to support the nested alerts at first and that could be part of #3300.

ebruchez commented 4 years ago

Following discussion with @avernet:

  1. Another way controls could control their own validity is using a new <xxf:setvalid> action.
    • Controls have state, unlike MIPs. And a relevant control could hold, as part of its state, a flag (or possibly even more complex information, including for warnings and alerts), indicating validity.
    • This flag would contribute to the control's validity as usual.
    • This would probably be in addition to a declarative validation function.
  2. This discussion intersects with the notion of masks/formats (#2118). A mask:
    • is declarative
    • can control user entry (although we don't like this)
    • can validate user entry
    • can format user entry
    • can be dependent on a locale/language
    • can be per control/form/global

So my question is how all this intersects.

Point 1 above seems orthogonal.

Point 2 is partly a shortcut to defining:

So I think we could go ahead with this idea of datatype and constraints mapped via XBL.

ebruchez commented 4 years ago

Say a form has an <xf:bind type="fr:us-ssn"/>.

This translates semantically into:

<xf:bind type="fr:us-ssn">
    <xf:constraint value="...">
        <xf:alert lang="en">My custom error message</xf:alert>
        <xf:alert lang="fr">Mon message d'erreur spécifique</xf:alert>
    </xf:constraint>
    <xf:constraint level="warning" value="xxf:max-length(42)">
        <xf:alert lang="en">My custom warning message</xf:alert>
        <xf:alert lang="fr">Mon message d'attention spécifique</xf:alert>
    </xf:constraint>
</xf:bind>

Now, from an implementation perspective:

If the above holds, then it should be pretty eas to do.