Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom element that holds the validation controller instance? #542

Open
jasonhjohnson opened this issue Feb 27, 2020 · 3 comments
Open

Custom element that holds the validation controller instance? #542

jasonhjohnson opened this issue Feb 27, 2020 · 3 comments

Comments

@jasonhjohnson
Copy link

jasonhjohnson commented Feb 27, 2020

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);
@bigopon
Copy link
Member

bigopon commented Feb 28, 2020

@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

@jods4
Copy link
Contributor

jods4 commented Feb 28, 2020

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.

@jasonhjohnson
Copy link
Author

@jods4 Super insightful, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants