Closed Tiagoperes closed 3 years ago
@Tiagoperes I adjust your json to use simpleForm and there is some missing close relativs:
{
"_beagleComponent_": "beagle:simpleForm",
"context": {
"id": "form",
"value": {
"data": {
"username": "",
"password": "",
"passwordConfirmation": ""
},
"showFormErrors": false,
"showError": {
"username": false,
"password": false,
"passwordConfirmation": false
}
}
},
"onValidationError": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "showFormErrors",
"value": true
}
],
"onSubmit": [
{
"_beagleAction_": "beagle:alert",
"message": "form submitted!"
}
],
"children": [
{
"_beagleComponent_": "beagle:container",
"children": [
{
"_beagleComponent_": "beagle:text",
"styleId": "title",
"text": "Account"
},
{
"_beagleComponent_": "beagle:container",
"children": [
{
"_beagleComponent_": "beagle:textInput",
"placeholder": "Username",
"value": "@{form.data.username}",
"showError": "@{or(form.showFormErrors, form.showError.username)}",
"error": "@{condition(isEmpty(form.data.username), 'The username is required.', '')}",
"onChange": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "data.username",
"value": "@{onChange.value}"
}
],
"onBlur": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "showError.username",
"value": true
}
]
}
]
},
{
"_beagleComponent_": "beagle:container",
"children": [
{
"_beagleComponent_": "beagle:textInput",
"placeholder": "Password",
"value": "@{form.data.password}",
"showError": "@{or(form.showFormErrors, form.showError.password)}",
"error": "@{condition(lt(length(form.data.password), 6), 'The password must have at least 6 characters.', '')}",
"type": "PASSWORD",
"onChange": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "data.password",
"value": "@{onChange.value}"
}
],
"onBlur": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "showError.password",
"value": true
}
]
},
{
"_beagleComponent_": "beagle:textInput",
"placeholder": "Confirm your password",
"value": "@{form.data.passwordConfirmation}",
"showError": "@{or(form.showFormErrors, form.showError.passwordConfirmation)}",
"error": "@{condition(eq(form.data.password, form.data.passwordConfirmation), '', 'The password and its confirmation must match.')}",
"type": "PASSWORD",
"onChange": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "data.passwordConfirmation",
"value": "@{onChange.value}"
}
],
"onBlur": [
{
"_beagleAction_": "beagle:setContext",
"contextId": "form",
"path": "showError.passwordConfirmation",
"value": true
}
]
}
]
}
]
},
{
"_beagleComponent_": "beagle:container",
"children": [
{
"_beagleComponent_": "beagle:button",
"text": "Previous",
"disabled": true
},
{
"_beagleComponent_": "beagle:button",
"text": "Next",
"onPress": [
{
"_beagleAction_": "beagle:submitForm"
}
]
}
]
}
]
}
Problem
Today we have two default components to create forms:
These two components, the way that they work today, don't support form validation. It is perfectly possible to validade a form with beagle, but it requires custom:components to do so.
We believe form validation to be an essencial part of a front-end framework, and for this reason, we wish to enhance both the SimpleForm and the TextInput so we can support form validation out of the box (without the need for customization).
Also, we don't want to transform the simple form into the old form component. We don't dismiss the idea of creating a less customizable, but more easy to use form, but this would be another component and is not the topic of this epic. What we want to achieve here is to support form validation by modifying the SimpleForm and the TextInput as little as we can.
The
TextInput
componentFirst, we need to be able to tell the
TextInput
it has an error. For this, a new property should be added to the component:error
.error
is a string and will mostly of the time be calculated via an expression (Bindable<string>
). Iferror
isnull
or an empty string, it means the form has no errors.In the example above, the
TextInput
has its property error resolved to the string"The password must have at least 6 characters."
If we only have the
error
property, the initial state of a form would be to show errors everywhere. This would happen because, considering all the fields are empty, all validations will fail. Se the example below:This is not what we want. It is true that all fields have errors, but we shouldn't show them until the user interacts with the field. Most of the times, we want the errors on a field to be showed only after the
onBlur
effect, i.e. after the input loses focus.From this behavior, we take that the
TextInput
should have one more property:showErrors: boolean
. This means we can control wether to show or not the error of a field. To implement the behavior of most validations,showErrors
would startfalse
for everyTextInput
and there would be an action in theonBlur
of eachTextInput
to change itsshowError
fromfalse
totrue
.In summary, the following properties should be added to the component
TextInput
:error
is a text that should be rendered in red, below the text input. It tells the user about the error. This text is visible only ifshowError
is true. When the error text is visible, the border of the input itself should also become red.showError
controls wether to make the error of the input visible or not. The error will be visible only ifshowError
is true.The
SimpleForm
componentTo make the validation work, the SimpleForm must do two things:
We need the first one because a form cannot be submitted with errors. The only thing we need to do is: on the
onSubmit
event, before calling the actiononSubmit
, we check if there are children where the error property is not empty. If there's at least one child with error, the form is not submitted.We need the second one, because, if the user tries, for instance, to submit an empty form (initial state), it won't be possible, because there will be validation errors everywhere. Because no
TextField
has been interacted with yet, in all of themshowError
will be false. This is a terrible behavior, because the form won't get submitted and the user won't know why. To fix this, we need to transform everyshowError
into true after a failed attempt to submit the form.To make this possible, we must add a new event to the form:
onValidationError
, this event is executed every time a form is submitted, but because of a validation error, theonSubmit
event is not run. This makes it possible to implement any behavior the user wishes to implement.In summary, the following property should be added to the component
SimpleForm
:And the following behavior should change:
When the form is submitted, before executing the action provided for
onSubmit
, we should verify if any child of the form haserror
different than null or an empty string. If there's at least one child with error, instead of executingonSubmit
,onValidationError
should be executed.Example
The example below shows a sign up form where it must be typed a username (required), a password (at least 6 characters) and a password confirmation (must be equal to to the password).
The following image shows the form in a state where every field has an error and
showError
resolves totrue
in each one of them:The json that renders the view with the behavior above (click the submit button when all fields are empty):
Final thoughts
This is still more complex than we desire, but it's a first step towards supporting validations natively. Our next step is to create a new component where we can use shortcuts to all these actions and contexts, see an idea below:
Attention: this is just an idea for a future epic, it shouldn't be implemented now.