Search code examples
aurelia

Validate input that derives from custom element in to DynamicForm


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. :)


Solution

  • 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.