web analytics
Paste your Google Webmaster Tools verification code here

Understanding how to use Aurelia’s Validation Library

aurelia_form_validation_figure_1

Introduction

In the last article we discussed the different binding mechanisms in Aurelia.  In this article we will discuss how to implement field validation using the Aurelia Framework. Input validation has changed a few times during the evolution of Aurelia.  As of Aurelia 1.0, there appears to be no solid documentation on how to perform validation, but this article should fully clarify the current mechanism for both validating input fields and rendering the validation with bootstrap.

 

Bringing in the Validation Libraries

As of 1.0, validation was split up into two main libraries:  aurelia-validation and aurelia-validatejs,  The aurelia-vaidatejs library contains decorators you can place on your bound properties in the view model (e.g. @required, @email).   aurelia-validatejs also contains the class ValidationRules, which allow you to set up rules programmatically on each of your bound properties instead of decorating them with attributes.  The aurelia-validation library contains a few important properties for both executing the validation rules and rendering the input fields. The classes in the aurelia-validation library include the ValidationController, the validateTrigger class, and the validationMessages class.  We will demonstrate the use of these libraries later in this blog.

To install the two validation libraries use jspm at the command prompt.  On the PC, the path to the jspm command is at the %appdata%\npm path.  Below are the two commands

jspm install aurelia-validation

jspm install aurelia-validatejs

If we look inside our config.js file, we see the current supported versions for validation. As of this writing, this blog supports the following libraries:

“aurelia-validatejs”: “npm:aurelia-validatejs@0.7.0”,
“aurelia-validation”: “npm:aurelia-validation@0.12.2”,

 

Adding the plugins to the project

Both these plugins need to be registered with aurelia during the bootstrapping of the project.  In main.js, we’ll add the the plugins to the code:

Listing 1 – main.js containing plugin configuration for validation

1
2
3
4
5
6
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .plugin('aurelia-validatejs')
    .plugin('aurelia-validation')
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .plugin('aurelia-validatejs')
    .plugin('aurelia-validation')

 

 

Decorating the View Model for Validation

There are two ways to set up validation fields in your view model.  Either declaratively through built in decorators or programmatically through ValidationRules.  Let’s examine the declarative method first.  Lets say I want to make sure the city input field is a required field,  I can use the @required decorator above the city property that is bound to the view as shown:

 Listing 2 – SignUp.js View Model containing validation Decorator

1
2
3
4
5
6
7
8
import {required} from 'aurelia-validatejs';
 
export class SignUp{
 
@required
city = null;
 
}
import {required} from 'aurelia-validatejs';

export class SignUp{

@required
city = null;

}

In my view, I will also need to let aurelia know that the city input field is being validated by adding a validate binding behavior inside my form:

1
2
3
4
<div class="form-group row col-xs-4">
<label for="city">City:</label>
<input type="text" class="form-control" id="city" value.bind="city & validate">
</div>
<div class="form-group row col-xs-4">
<label for="city">City:</label>
<input type="text" class="form-control" id="city" value.bind="city & validate">
</div>

 

Using ValidationRules to Programmatically set up Validation

The other way to validate if the city is a required field is through the validation rules.  To set up validation rules, simply set them up for the ViewModel Class as shown.  Import the ValidationRules from the aurelia-validatejs library and set up the rules as a separate function outside the view model class:

 

 Listing 3- SignUp.js View Model Using ValidationRules to validate the city field

1
2
3
4
5
6
7
8
9
10
import {ValidationRules} from 'aurelia-validatejs';
 
export class SignUp{
 
city = null;
 
}
 
ValidationRules.ensure('city').required()
.on(SignUp);
import {ValidationRules} from 'aurelia-validatejs';

export class SignUp{

city = null;

}

ValidationRules.ensure('city').required()
.on(SignUp);

 

Now let’s add a bunch of other fields to our validation to illustrate the chaining of the ensure method. In the sample code below, we check 3 fields to make sure they have content: city, fullname, and email.  We also  added an additional validation to make sure the email was well formed.

Listing 4 – SignUp.js View Model Using ValidationRules to validate a series of fields

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {ValidationRules} from 'aurelia-validatejs';
 
export class SignUp{city = null;
 
fullname = null;
 
email = null;
 
}
 
ValidationRules.ensure('city').required()
.ensure('fullname').required()
.ensure('email').required().email()
.on(Application);
import {ValidationRules} from 'aurelia-validatejs';

export class SignUp{city = null;

fullname = null;

email = null;

}

ValidationRules.ensure('city').required()
.ensure('fullname').required()
.ensure('email').required().email()
.on(Application);

 

Remember we still need to add the validate behavior to the template html for these fields:

1
2
3
4
5
<div class="form-group row ">
<div class="col-xs-6">
<label for="fullname">Name:</label>
<input type="text" class="form-control" id="fullname" value.bind="fullname & validate">
</div>
<div class="form-group row ">
<div class="col-xs-6">
<label for="fullname">Name:</label>
<input type="text" class="form-control" id="fullname" value.bind="fullname & validate">
</div>

 

How to use the ValidationController

So what is the purpose of the validation controller?  It actually serves a few purposes.  Internally it provides a validate method that is called when you lose focus on the field.  It also plays a part on rendering the view when we have an invalid field. We will talk about rendering a bit later.  We can actually force controller to validate by calling validate directly on the controller when we hit the Apply button in our form.  You can actually construct the ValidationController in one of two ways.  One is creating a transient ValidationController through dependency injection and the other is through a ValidationControllerFactory.  In our example, we will use method #1.  Below we use the dependency injection method NewInstance to create a new instance of the ValidationController to be used by our SignUp View Model.  We also assign a local property, this.validationController to be used by any method in our SignUp class.  Note that the validation controller is imported from the aurelia-validation library and not the aurelia-validatejs library.

Listing 5 – SignUp.js ViewModel , Injecting a ValidationController

1
2
3
4
5
6
7
8
9
10
11
12
13
import {inject, NewInstance} from 'aurelia-dependency-injection';
 
import {ValidationController, validateTrigger} from 'aurelia-validation';
 
@inject(<strong>NewInstance</strong>.of(ValidationController))
export class SignUp{
 
constructor(validationController)  {
this.validationController = validationController;
 
}
 
}
import {inject, NewInstance} from 'aurelia-dependency-injection';

import {ValidationController, validateTrigger} from 'aurelia-validation';

@inject(<strong>NewInstance</strong>.of(ValidationController))
export class SignUp{

constructor(validationController)  {
this.validationController = validationController;

}

}

 

We can now use the ValidationController to force a validation on the form when we submit it:

First we should set up the validationcontroller for manual validation.  We can use the validateTrigger enum to do this.  validateTrigger has three modes:  manual, blur, and change.  The default mode for the ValidationController is blur, so when we tab out of a field, the validate method on the ValidationController is called.  change mode will call validate when a bound field changes, and manual mode won’t trigger validate at all.  You need to call validate directly on the controller to trigger the ValidationController in manual mode.  Here is how to set it up.

Listing 6 – SignUp.js ViewModel , Setting up validation trigger behavior

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {inject, NewInstance} from 'aurelia-dependency-injection';
import {ValidationController, validateTrigger} from 'aurelia-validation';
 
@inject(NewInstance.of(ValidationController))
export class SignUp{
 
constructor(validationController)  {
this.validationController = validationController;
 
this.validationController.validateTrigger = validateTrigger.manual;
 
}
 
}
import {inject, NewInstance} from 'aurelia-dependency-injection';
import {ValidationController, validateTrigger} from 'aurelia-validation';

@inject(NewInstance.of(ValidationController))
export class SignUp{

constructor(validationController)  {
this.validationController = validationController;

this.validationController.validateTrigger = validateTrigger.manual;

}

}

Now in order to validate the form, we will call validate inside the submit method of our view model, which is linked to the form.  Note that the validate method, returns a promise, so we need to handle the result asynchronously in the .then  method of the promise.  .then returns an array of validation errors (one error for each failed validation).  If the results return an empty array, then the validate call passed and we can submit our form to the server.

Listing 7 – SignUp.js ViewModel , Handling the validate method on the validation controller

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
 import {inject, NewInstance} from 'aurelia-dependency-injection';
 
 import {ValidationController, validateTrigger} from 'aurelia-validation';
 
@inject(NewInstance.of(ValidationController))
export class SignUp{
 
constructor(validationController)  {
this.validationController = validationController;
 
this.validationController.validateTrigger = validateTrigger.manual;
 
}
 
submitForm() {
 
this.validationController.validate().then(result => {
if (result.length == 0) // successful
{
 
// submit form to the server.
 
}
 
});
 
}
 
}
 import {inject, NewInstance} from 'aurelia-dependency-injection';

 import {ValidationController, validateTrigger} from 'aurelia-validation';

@inject(NewInstance.of(ValidationController))
export class SignUp{

constructor(validationController)  {
this.validationController = validationController;

this.validationController.validateTrigger = validateTrigger.manual;

}

submitForm() {

this.validationController.validate().then(result => {
if (result.length == 0) // successful
{

// submit form to the server.

}

});

}

}

Setting up for Rendering the Validation Form with Bootstrap

All the documentation I found on rendering controls for validation mostly reflect the previous validation scenarios which no longer work with the latest 0.12.2 release of the validation library.  I suspect validation is still a work in progress, but I was able to get rendering to work by piecing together some of the previous implementations along with stepping through the java code of the current rendering scheme.

Here is basically what you need to do:

The first step is to add the bootstrap rendering to your project.  The rendering behavior can be treated as an aurelia feature.  You’ll need to add a folder with two files.  One file, index.js is used to register the rendering feature and the second file bootstrap-form-validation-renderer.js performs all the rendering.  We can group these two files in a folder called bootstrap-validation

Here is index.js used to register the renderer:

Listing 8 -index.js in the bootstrap-validation folder,  Registering the custom bootstrap renderer

1
2
3
4
5
6
7
import {BootstrapFormValidationRenderer} from './bootstrap-form-validation-renderer'
 
export function configure(config) {
  config.container.registerHandler(
    'bootstrap-form',
    container => container.get(BootstrapFormValidationRenderer));
  }
import {BootstrapFormValidationRenderer} from './bootstrap-form-validation-renderer'

export function configure(config) {
  config.container.registerHandler(
    'bootstrap-form',
    container => container.get(BootstrapFormValidationRenderer));
  }

In order to get aurelia to call the configuration in this file, we need to add the bootstrap validation as a feature.  We’ll simply add the feature with the same name as the folder containing the index.js file inside of the configure function in main.js:

Listing 9 -main.js in the configure function,  Registering the custom bootstrap renderer as a feature with aurelia

1
2
3
4
5
6
7
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .plugin('aurelia-validatejs')
    .plugin('aurelia-validation')
    .feature('bootstrap-validation');
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .plugin('aurelia-validatejs')
    .plugin('aurelia-validation')
    .feature('bootstrap-validation');

Finally we need to make sure we tell the form about the bootstrap renderer in our SignUp.html file.  For the form, we’ll use the registered handler name, bootstrap-form and not the name of the feature (bootstrap-validation):

1
2
3
4
5
<form class="form-vertical" validation-renderer = "bootstrap-form"   submit.delegate = "submitForm()">
 
....
 
</form>
<form class="form-vertical" validation-renderer = "bootstrap-form"   submit.delegate = "submitForm()">

....

</form>

Rendering in Bootstrap using a Custom Renderer

Finally we come to the renderer.  The renderer is a sibling of index.js in the bootstrap-validation folder.  Below is the renderer I was able to get working with bootstrap.  How rendering works is that the validation controller has a render function.  The render function gets called automatically by the framework and passes instructions on the element(s) that require rendering and the error message associated with those elements.  The render method contains two structures: render and unrender.  You can use these objects to determine when to render errors and when to reset the form pre-validation respectively.  In the code below we handle both cases: render and unrender.  We know to render when the render array is greater than zero.  We know to unrender when the unrender array is greater than zero.  The reset method on validatecontroller will trigger a render call on the validation controller with the unrender array populated.  A validate call on the validationcontroller, will populate the render object if it requires populating (in other words if an error occured).

(Note: Other Examples of rendering show a @validationRenderer decorator on your custom renderer.  You do not need this decorator anymore for validation.  In fact it causes errors if you use it.)

Listing 10 -bootstrap-form-validation-renderer.js ,  Rendering the signup fields in bootstrap  to display validation and validation messages

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import {inject} from 'aurelia-dependency-injection';
import {validationRenderer} from 'aurelia-validation';
 
// @validationRenderer - don 't use this, it causes errors 
@inject(Element)
 
export class BootstrapFormValidationRenderer {
  constructor(boundaryElement) {
    this.boundaryElement = boundaryElement;
  }
 
  render(instruction) {
    if (instruction.render.length > 0) {
      this.renderBootstrap(instruction);
    }
 
    if (instruction.unrender.length > 0) {
      this.unrenderBootstrap(instruction);
    }
 
  }
 
  renderBootstrap(instruction) {
    // loop through each renderItem and display an error
    instruction.render.forEach(function (renderItem) {
 
    let target = renderItem.elements[0];
    target.errors = [renderItem.error];
    let error = renderItem.error
 
    // add the has-error class to the bootstrap form-group div
    const formGroup = target.querySelector('.form-group') || target.closest('.form-group');
    formGroup.classList.add('has-error');
 
    // add help-block
    const message = document.createElement('span');
    message.classList.add('help-block');
    message.classList.add('validation-error');
    message.textContent = error.message;
    message.error = error;
    formGroup.appendChild(message);
    });
  }
 
  unrenderBootstrap(instruction) {
    // loop through each renderItem and display an error
    instruction.unrender.forEach(function (unrenderItem) {
 
    let target = unrenderItem.elements[0];
    let error = unrenderItem.error;
    target.errors = [];
 
    // remove the has-error class on the bootstrap form-group div
    const formGroup = target.querySelector('.form-group') || target.closest('.form-group');
    formGroup.classList.remove('has-error');
 
    // remove all messages related to the error.
    let messages = formGroup.querySelectorAll('.validation-error');
    let i = messages.length;
    while(i--) {
      let message = messages[i];
      // if (message.error !== error) {
      //   continue;
      // }
      message.error = null;
      message.remove();
    }
      });
  }
}
 
// Polyfill for Element.closest and Element.matches
// https://github.com/jonathantneal/closest/
(function (ELEMENT) {
    ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
 
    ELEMENT.closest = ELEMENT.closest || function closest(selector) {
        var element = this;
 
        while (element) {
            if (element.matches(selector)) {
                break;
            }
 
            element = element.parentElement;
        }
 
        return element;
    };
}(Element.prototype));
import {inject} from 'aurelia-dependency-injection';
import {validationRenderer} from 'aurelia-validation';

// @validationRenderer - don 't use this, it causes errors 
@inject(Element)

export class BootstrapFormValidationRenderer {
  constructor(boundaryElement) {
    this.boundaryElement = boundaryElement;
  }

  render(instruction) {
    if (instruction.render.length > 0) {
      this.renderBootstrap(instruction);
    }

    if (instruction.unrender.length > 0) {
      this.unrenderBootstrap(instruction);
    }

  }

  renderBootstrap(instruction) {
    // loop through each renderItem and display an error
    instruction.render.forEach(function (renderItem) {

    let target = renderItem.elements[0];
    target.errors = [renderItem.error];
    let error = renderItem.error

    // add the has-error class to the bootstrap form-group div
    const formGroup = target.querySelector('.form-group') || target.closest('.form-group');
    formGroup.classList.add('has-error');

    // add help-block
    const message = document.createElement('span');
    message.classList.add('help-block');
    message.classList.add('validation-error');
    message.textContent = error.message;
    message.error = error;
    formGroup.appendChild(message);
    });
  }

  unrenderBootstrap(instruction) {
    // loop through each renderItem and display an error
    instruction.unrender.forEach(function (unrenderItem) {

    let target = unrenderItem.elements[0];
    let error = unrenderItem.error;
    target.errors = [];

    // remove the has-error class on the bootstrap form-group div
    const formGroup = target.querySelector('.form-group') || target.closest('.form-group');
    formGroup.classList.remove('has-error');

    // remove all messages related to the error.
    let messages = formGroup.querySelectorAll('.validation-error');
    let i = messages.length;
    while(i--) {
      let message = messages[i];
      // if (message.error !== error) {
      //   continue;
      // }
      message.error = null;
      message.remove();
    }
      });
  }
}

// Polyfill for Element.closest and Element.matches
// https://github.com/jonathantneal/closest/
(function (ELEMENT) {
	ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;

	ELEMENT.closest = ELEMENT.closest || function closest(selector) {
		var element = this;

		while (element) {
			if (element.matches(selector)) {
				break;
			}

			element = element.parentElement;
		}

		return element;
	};
}(Element.prototype));

Conclusion

Validation in Aurelia does seem to work, but I suspect there will be updates to fix some issues.  It seemed sometimes not all the fields would light up when there was an error, but this could be a bug in the renderer I came up with.  Anyway, I’d appreciate any feedback if others try using the Aurelia Bootstrap Renderer in this article.  We will continue in our next tutorial to talk about integrating Aurelia with LoopBack and MongoDB.

 

Share

Share This Post

Recent Articles

Leave a Reply

*

© 2016 ToDroid. All rights reserved.
Disclaimer: The content on this site is copyrighted. Do not copy the content. The content on this site must not be reproduced anywhere else.