1

I am trying to add validation to custom element that gets generated in dynamic form component to support page for view. I injected Aurelia-Validation in main.ts, and DynamicForm.ts and instantiated. Below is my code.

CUSTOM ELEMENT:

TS File

import { customElement, useView, bindable, bindingMode, inject } from 'aurelia-framework';

@customElement('custom-input')
@useView('./custominput.html')
export class CustomInput {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) fieldValue: string;
  @bindable public customClass: string;
  @bindable public placeHolder: string;
  @bindable public fieldName: string;
  @bindable public formItem: any;

}

HTML View:

<template>
    <input class="${customClass}" custom-class.bind="customClass" type="text" field-value.bind="fieldValue"
           value.two-way="fieldValue & validateOnChange" placeholder="${placeHolder}" place-holder.bind="placeHolder" 
           id="${fieldName}" field-name.bind="fieldName" form-item.bind="formItem" />
</template>

DynamicForm

TS File:

import { bindable, bindingMode, inject } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory } from 'aurelia-validation';

@inject(ValidationControllerFactory)
export class DynamicForm {

  @bindable public formName: string;
  @bindable public formTemplate: Object;
  @bindable public callback;
  inputItem: HTMLInputElement;

  controller = null;

  constructor(ValidationControllerFactory) {
    this.controller = ValidationControllerFactory.createForCurrentScope();
  }
  public formValidator(element, field) {
      //console.log(element);
  }

  public bind() {
    if (this.formTemplate) {
      this.formTemplate[this.formName].fields.forEach((item, i) => {
        if (item.validation.isValidate === true) {
          ValidationRules.ensure(item.name)
            .displayName(item.name)
            .required()
            .on(this.formTemplate);
        }
      });
      this.controller.validate();
    }
    console.log(this.controller);
  }
}

HTML View:

<template>
  <require from="../../elements/custominput/custominput"></require>
  <form class="form-horizontal">
      <div form-name.bind="formName" class="form-group" repeat.for="item of formTemplate[formName].fields">
          <label for="${item.name}" class="col-sm-2 control-label">${item.label}</label>
          <div class="col-sm-10" if.bind="item.type === 'text' && item.element === 'input'">
              <custom-input router.bind="router" custom-class="${item.classes}" field-value.two-way="item.value"
                            place-holder="${item.placeHolder}" ref="inputItem" item.bind="formValidator(inputItem, item)" 
                            field-name.bind="item.name" form-item.bind="item">
              </custom-input>
          </div>
      </div>
    <div class="form-group">
      <div class="col-sm-12">
        <button type="submit" class="btn btn-default pull-right" click.delegate="callback()">Submit</button>
      </div>
    </div>
  </form>
  <ul if.bind="controller.errors.length > 0">
    <li repeat.for="error of controller.errors">${error}</li>
  </ul>
</template>

Support page:

This page will load DynamicForm

<template>
  <require from="./support.scss"></require>
  <require from="../DynamicForm/dynamicform"></require>
  <div class="panel panel-primary">
    <div class="panel-heading">${pageTitle}</div>
    <div class="panel-body">
      <dynamic-form form-template.two-way="formTemplate" form-name.two-way="formName" callback.call="processForm()"></dynamic-form>
    </div>
  </div>
</template>

When I view the support page in browser, I do not get validation in UI. Not sure if validation is position in in nested components/elements. The view is generated like this custominput element -> DynamicForm -> support page

Plunker link for more information:

Any help is really appreciated. :)

Ray
  • 1,095
  • 3
  • 18
  • 43

1 Answers1

1

Two major issues:

1. Rules shouldn't be stored on fields

Rules are stored on the prototype of an object and pertain to the properties of that object.

You are defining the rules on each individual property, so ultimately it's trying to validate property.property rather than object.property, which doesn't do much as you can see.

You're also declaring the rules every time the form template changes. I basically wouldn't put that logic there; put it closer to where those object come from.

  • If the objects are declared somewhere in your client code, declare the rules in the same module files
  • If the objects come from the server, declare the rules on those objects on the same place where you fetch them, right after you fetched them

Either way, those rule declarations don't belong in a change handler.

2. Bindings are missing

The ValidationController needs to know which object or properties you want to validate. It only knows in either of these cases:

  • Your rules are declared via controller.addObject(obj, rules).

  • Your rules are declared via ValidationRules.[...].on(obj) and the fields in your html template have & validate following them.

There's several pros and cons with either approach, ultimately you should go with one that gives you least resistance. I would probably go for the second approach because things get more entangled if you declare all rules on your controllers directly.

Fred Kleuver
  • 7,797
  • 2
  • 27
  • 38
  • Thanks Fred for your response. I've updated my post and added `validationRule` to `bind`. I am trying to go with no. 2 option from your answer. But for some reason, the validation rules are not getting applied. – Ray Apr 18 '18 at 19:30
  • Mostly wondering why you're binding both the `formTemplate` and the `formName` when you could might as well directly bind the appropriate `form` (or is it a `formTemplate` and should `formTemplate` really be named `formTemplates`?) – Fred Kleuver Apr 18 '18 at 20:55
  • you are correct. There could be multiple forms. I should have named it `formTemplates`. Created plunker as example code. https://plnkr.co/edit/82tg8sLAHx43kGrc0IhO – Ray Apr 18 '18 at 22:55
  • In `app.ts` file you will find `formTemplate` that gets passed to `dynamicForm` and `DynamicForm` has the `validationRule` rule `Attached`method. I believe the problem is in link 37 in `dynamicform.ts` as shown in plunker with nested objects. – Ray Apr 19 '18 at 11:07
  • Looks like, I cannot add `validationRules` in `DynamicForm`. What I did was I added `validationRule` directly in `custominput`, which now works. – Ray Apr 19 '18 at 13:08
  • 1
    I've updated and cleaned up your punkr a little bit to show what I meant: https://plnkr.co/edit/YYgTXN3DN3TNsa95yGpp Note that in app.ts I'm extracting the validation info from your formTemplates and assigning the rules to each field object, with "value" as the property, utilizing your already proper object structure to do everything dynamically. Works like a charm as far as I can see. – Fred Kleuver Apr 19 '18 at 17:46
  • Thanks Fred. Based on your provided solution, how can I achieve same from `DynamicForm.ts`? I would like to keep minimum validation in `app.ts` and only have `DynamicForm` carry rules. Reason, is there could be multiple views similar to app. Having all the rules in one place in `DynamicForm` would centralize all the validation. Since I cannot instantiate property `formTemplate` in `constructor`, I can probably try to do similar in `bind()` method in `DynamicForm.ts`? – Ray Apr 19 '18 at 18:30
  • There is no validation happening in `app.ts`. Just creating and assigning static rules there. The rules are essentially stored on a property called `__rules__` on each `field` object and the `& validate` binding behavior will then try to find those rules. What I'm doing in the constructor of `app.ts` you can do that wherever you like - I'd put it in a separate `ValidationRulesApplicator` class or something. It's generic logic. As for the "when" - just do it before the first call to any `controller.validate()`, obviously. – Fred Kleuver Apr 19 '18 at 18:43
  • To be absolutely clear: in my example, `app.ts` is not "carrying" the rules. The form objects are. As I said in my answer, the best place to perform this logic is as close as possible tho when you're creating/receiving the form template. Where are you defining them in your actual app? – Fred Kleuver Apr 19 '18 at 18:45
  • Understood. Thanks Fred. I will try out your recommendation. – Ray Apr 19 '18 at 18:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/169439/discussion-between-ray-and-fred-kleuver). – Ray Apr 20 '18 at 12:36