validation
validation copied to clipboard
Custom element that holds the validation controller instance?
Is it possible to create an "app-form
" custom element that contains the validation controller instance and handles the validation so parent components can use it without knowing about validation?
I'm considering something like:
parent.vm.html
<app-form validate.bind="true">
<app-form-group>
<app-label slot="label">First Name</app-label>
<app-input slot="input" type="text" value.bind="model.firstName"></app-input>
</app-form-group>
</app-form>
input.element.html
<template>
<input
value.bind="value & validateOnBlur"
/>
</template>
sample.model.ts
export class SampleModel {
firstName: string;
}
ValidationRules.ensure((m: SampleModel) => m.firstName)
.required()
.on(SampleModel);
@jasonhjohnson nice Q. The scope of a ValidationController
created in parent.html
<app-form validate.bind="true">
<app-form-group>
<app-label slot="label">First Name</app-label>
<app-input slot="input" type="text" value.bind="model.firstName"></app-input>
</app-form-group>
</app-form>
will be different with the scope of a ValidationController
created inside app-form.html
itself.
At the moment, we don't support this, but probably there's a way to do it, via a template controller custom attribute with a ValidationController
encapsulated in the scope of app-form
in the above template.
@jods4 requested this feature, in the name of template controller custom element, maybe he can chime in.
cc @fkleuver @EisenbergEffect
It's possible, that's more or less how I do it in my projects. The way I achieved it is rather complex, though.
It's based on two custom elements, data-form
and data-field
, e.g.
<data-form entity.bind='model' validation.bind='rules' with.bind='model'>
<data-field label='First name'>
<input value.bind='firstName' />
</data-field>
<data-field label='Last name'>
<input value.bind='lastName' />
</data-field>
</data-form>
Those elements do a fair bit of presentation work (e.g. creating a localized label that is automatically associated with the inner control, doing the layout, etc.) but that's beside the point.
The first trick is that <data-field>
processes its children (and I won't be able to provide the details out of the top of my head, sorry. It's a fairly advanced / obscure Aurelia api). For a whole set of known elements / attributes, it modifies the template of its contents from
<input value.bind='firstName'>
to
<input value.bind='firstName & validate:"firstName"'>
It does so only when there is no validate
binding behaviour in your template already, which enables you to customize the default validation (e.g. specify a different fieldName if it can't be figured out from the expression or disable validation althogether).
From here, validate
binding behavior is the one actually doing the heavy lifting.
Of course it does all the things you imagine, such as performing validation when the binding changes and putting .invalid
classes on the element accordingly.
There's one trick, which is that the validate
binding finds and is tied to enclosing the <data-form>
. That's how it grabs the validation rules, or how it can find about fields that depend on each others and update when another field is modified. (It also does other services such as tracking if the entity is dirty or not, but again, beside the point).
Sorry I don't have access to the source code right now, and that was the most tricky part.
I did it by having <data-form>
be a template controller and put itself inside the scope/overrides chain under a known key such as $form
. The behaviour would then walk up the scope chain until it finds or form (or nothing). I recall there were issues because of when the children are bound vs when the parent form is bound (basically it's the opposite of what you'd like).
You could also try to solve that problem by having <data-form>
create a nested injection scope that can provide itself, and maybe have the behaviour inject the form. Haven't tried it, if it works it would be simpler than using the binding scope.
@jods4 Super insightful, thanks!