Form validation in Aurelia is actually pretty nice.
You can configure the validation rules in a chainable api and add multiple rules to single fields.
However there is a problem if you want to trigger the validation of an input when another input is filled. A good example are two inputs (a start value and an end value). Of course you want to validate that the start-value is less than the end-value. But if you add the rule to both fields only the edited field will show (and reset) the error message.
Ok… This explaination might be a bit confusing… Let’s consider the following example:
You have two inputs, ‘Start’ and ‘End’. End has to be greater than start. You type ’20’ as start and ’30’ as end. It gonna look something like this:
But suddenly you realize, the start should really be 50. Of course the validation will fail, and the start will be marked as invalid.
If you now change the value of end to 100, the start-input will still be marked as invalid, because only the input you are editing is validated and you haven’t told the validation that the inputs are dependent.
This is only an example of many other use cases. I’m sure you can think of other examples like two dates (I will show this in another blogpost, where I’ll tell you about some problems I had with the Aurelia materialize datepicker), or two inputs where only one should be filled and the other be empty.
Everything results in one problem: Validation of dependent inputs.
There is actually an open issue for Aurelia for dependent validation rules (Issue 204).
But until this is implemented we’ll need a workaround.
For my explaination I will use the code of the two inputs seen in the screenshots above:
1 2 3 4 5 6 7 8 9 |
<sc-content> <md-input md-label="Start" md-value.bind="viewModel.start & validate:BlogModel.validation" md-type="number" md-input-id="val-start" class="col s12"> </md-input> <md-input md-label="End" md-value.bind="viewModel.end & validate:BlogModel.validation" md-type="number" md-input-id="val-end" class="col s12"> </md-input> </sc-content> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
export class BlogModel { viewModel = { start: undefined, end: undefined }; validation; constructor(data) { Object.assign(this.viewModel, data); this.initValidation(); } initValidation() { ValidationRules.customRule('endGreaterStart', (value, object) => { if (!object.start || !object.end) { return true } return parseInt(object.start) < parseInt(object.end); }, '\${$displayName} not valid' ); this.validation = ValidationRules .ensure('start').displayName('Start').required().satisfiesRule('endGreaterStart') .ensure('end').displayName('End').required().satisfiesRule('endGreaterStart') .on(this.viewModel) .rules; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@inject(NewInstance.of(ValidationController)) export default class BlogValidation { BlogModel = new BlogModel(); viewModel; validationController; constructor(validationController) { this.validationController = validationController; this.validationController.addRenderer(new MaterializeFormValidationRenderer()); this.viewModel = this.BlogModel.viewModel; } submit() { this.validationController.validate() .then((result) => { if (result.valid) { console.log('success'); } else { console.log('fail'); } }); } } |
As you can see I have a viewmodel ( BlogModel) that knows how to validate its fields and the Controller BlogValidation that controlls the html snipped above and holds the injected ValidationController.
Now there are multiple ways to make the inputs dependent.
I prefer to react on the change event in the html and manually trigger the validation of the other inputs. This will also keep the example short 🙂
1 2 3 4 5 6 7 8 9 |
<sc-content> <md-input md-label="Start" md-value.bind="viewModel.start & validate:BlogModel.validation" md-type="number" md-input-id="val-start" class="col s12" change.delegate="validationController.validate({object: viewModel, propertyName: 'end'})"> </md-input> <md-input md-label="End" md-value.bind="viewModel.end & validate:BlogModel.validation" md-type="number" md-input-id="val-end" class="col s12" change.delegate="validationController.validate({object: viewModel, propertyName: 'start'})"> </md-input> </sc-content> |
The downside of this is that there will be two validation errors if the validation of one input fails. This also includes the required, so if you enter some number in the start input, the end input will say ‘hey, I also need a number, because I’m required’.
If you want this, perfect, but there are other ways:
1 2 3 4 5 6 7 8 9 |
<sc-content> <md-input md-label="Start" md-value.bind="viewModel.start & validate:BlogModel.validation" md-type="number" md-input-id="val-start" class="col s12" change.delegate="validationController.reset({object: viewModel, propertyName: 'end'})"> </md-input> <md-input md-label="End" md-value.bind="viewModel.end & validate:BlogModel.validation" md-type="number" md-input-id="val-end" class="col s12" change.delegate="validationController.reset({object: viewModel, propertyName: 'start'})"> </md-input> </sc-content> |
All I did here was change the called method from
validationController.validate() to
validationController.reset().
This will remove the validation error of the dependent input. If the validation of the current input fails there will still be an error, but it will be only displayed on the current input.
So this is how you make the validation of two inputs dependent in Aurelia.
All you really wanted to do was trigger the validation of an additional input. And for that you just have to tell your inputs that they should execute the validation or reset the validation of the dependent inputs in the change event.
Thank you for reading!
If I could help you with my post or you have another or even a better solution for the problem, feel free to comment.