Third time lucky!

Over the last 12 months or so Aurelia has had two different approaches to validation offered as official packages. The first was innovative and reasonably effective, but the code-base was a bit messy, and the original team-member dropped off the map, leaving the team in a tricky situation.

The decision was then made to create a simple interface so that developers could plug in any back-end validation they liked, and an example was created using the Validate.js library. This survived for a few months, but there were many issues related to the underlying engine that made it unsuitable as a solution for the long-term.

Other libraries exist, including Treacherous, which I wrote about here, and this is still available as an option. Now, after many hours of meetings with Jeremy Danyow of the Aurelia core team, we’ve managed to pull in all of the ideas and use-cases learned from these other systems into what we hope is one of the most complete validation solutions out there, let-alone the best for Aurelia.

So let’s take the new version for a spin!

Note that this is example written in Typescript, but if you’re daft enough to sacrifice all that nice type-checking and intellisense and want to use ES6, all you need to do is remove the :type from things (e.g. firstname: string = ''; becomes firstname = ''; ) and change constructor(private controller: ValidationController) { to say constructor(controller) { this.controller = controller;

The documentation will all be on the Aurelia Hub within the next few hours, but let’s create a really simple example of its use on a form. We’ll start with a simple CLI project, and bodge a simple example into the main form.

First, don’t forget to add aurelia-validation as a plugin in our main.ts, as this is needed even if you’re not using the UI bindings:

.plugin('aurelia-validation'); 1 . plugin ( 'aurelia-validation' ) ;

Next, let’s setup the ValidationController and create some rules in our main VM:

import {inject, NewInstance} from 'aurelia-framework'; import {ValidationRules, ValidationController} from "aurelia-validation"; @inject(NewInstance.of(ValidationController)) export class App { message = ''; firstname: string = ''; lastname: string = ''; constructor(private controller: ValidationController) { ValidationRules .ensure((m: App) => m.lastname).displayName("Surname").required() .ensure((m: App) => m.firstname).displayName("First name").required() .on(this); } validateMe() { this.controller .validate() .then(v => { if (v.length === 0) this.message = "All is good!"; else this.message = "You have errors!"; }) } } 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 import { inject , NewInstance } from 'aurelia-framework' ; import { ValidationRules , ValidationController } from "aurelia-validation" ; @ inject ( NewInstance . of ( ValidationController ) ) export class App { message = '' ; firstname : string = '' ; lastname : string = '' ; constructor ( private controller: ValidationController ) { ValidationRules . ensure ( ( m : App ) = > m . lastname ) . displayName ( "Surname" ) . required ( ) . ensure ( ( m : App ) = > m . firstname ) . displayName ( "First name" ) . required ( ) . on ( this ) ; } validateMe ( ) { this . controller . validate ( ) . then ( v = > { if ( v . length === 0 ) this . message = "All is good!" ; else this . message = "You have errors!" ; } ) } }

Now we have to do two things in our view. We have to tell the validator which fields we would like to link to the UI for validation, and we’d like to display the list of validation errors on-screen:

<template> <div> <p> Firstname: <input value.bind="firstname & validate"/></p> <p> Lastname: <input value.bind="lastname & validate"/></p> </div> <button click.delegate="validateMe()">Validate</button> <h3>Latest validation result: ${message}</h3> <ul> <li repeat.for="error of controller.errors"> ${error.message} </li> </ul> </template> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <div> <p> Firstname: <input value . bind = "firstname & validate" /> </p> <p> Lastname: <input value . bind = "lastname & validate" /> </p> </div> <button click . delegate = "validateMe()" > Validate </button> <h3> Latest validation result: ${message} </h3> <ul> <li repeat . for = "error of controller.errors" > ${error.message} </li> </ul> </template>

OK, so now we have an extremely unexciting form, but it’s actually pretty powerful. When the form opens the error list is empty, but as we tab over each field the validation engine applies our rules against the associated property (which it found following the Binding), but it only applied the rule when the field lost focus.

It is possible to have the validation fire on every change, on blur, or only manually, and this can be set at a ValidationController level and on a per-field basis.

So we now have everything to test and display errors.

So what if we want to have the errors displayed on a per-field basis?

Well this is highly solution-centric, as the validation system doesn’t know if you want to use Bootstrap 3.0 errors, roll-your-own, or simply change the colour of a field. Even if you simply want to change colour, what class would you like assigning to the input element?

So we now have the option of adding as many validation renders as we like. They take a really simply set of parameters, and you can perform as much DOM manipulation as you like, including making your renders conditional based on information regarding the rule or the associated input element.

Here’s a very simple one to add a class to an element, and to add a DIV element with the message:

SimpleValidationRenderer.ts

SimpleValidationRenderer.ts import {ValidationError, RenderInstruction} from 'aurelia-validation'; export class SimpleValidationRenderer { render(instruction: RenderInstruction) { console.log(instruction); for (let {error, elements} of instruction.unrender) { for (let element of elements) { this.remove(element, error); } } for (let {error, elements} of instruction.render) { for (let element of elements) { this.add(element, error); } } } private add(element: Element, error: ValidationError) { element.classList.add('has-error'); // add error div const message = document.createElement('div'); message.className = 'validation-message-div'; message.textContent = error.message; message.id = `validation-message-${error.id}`; element.parentNode.insertBefore(message, element.nextSibling); } private remove(element: Element, error: ValidationError) { // remove error div const message = element.parentElement.querySelector(`#validation-message-${error.id}`); if (message) { element.parentElement.removeChild(message); element.classList.remove('has-error'); } } } 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 import { ValidationError , RenderInstruction } from 'aurelia-validation' ; export class SimpleValidationRenderer { render ( instruction : RenderInstruction ) { console . log ( instruction ) ; for ( let { error , elements } of instruction . unrender ) { for ( let element of elements ) { this . remove ( element , error ) ; } } for ( let { error , elements } of instruction . render ) { for ( let element of elements ) { this . add ( element , error ) ; } } } private add ( element : Element , error : ValidationError ) { element . classList . add ( 'has-error' ) ; // add error div const message = document . createElement ( 'div' ) ; message . className = 'validation-message-div' ; message . textContent = error . message ; message . id = ` validation - message - $ { error . id } ` ; element . parentNode . insertBefore ( message , element . nextSibling ) ; } private remove ( element : Element , error : ValidationError ) { // remove error div const message = element . parentElement . querySelector ( ` # validation - message - $ { error . id } ` ) ; if ( message ) { element . parentElement . removeChild ( message ) ; element . classList . remove ( 'has-error' ) ; } } }

Let’s add a bit of styling to make it look as though a skilled UI designer created it (hur hur)

.has-error { background: #fff0f0; } .validation-message-div { display: inline; font-size: x-small; left: -6px; top: -8px; position: relative; padding: 2px; background: red; color: white; font-family: 'Arial', sans-serif; border-radius: 5px; overflow: hidden; white-space: nowrap ; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 .has-error { background : #fff0f0 ; } .validation-message-div { display : inline ; font-size : x-small ; left : -6px ; top : -8px ; position : relative ; padding : 2px ; background : red ; color : white ; font-family : 'Arial' , sans-serif ; border-radius : 5px ; overflow : hidden ; white-space : nowrap ; }

We now need to tell our controller in App.ts that it should call this renderer whenever a validation result changes:

constructor(private controller:ValidationController) { this.controller.addRenderer(new SimpleValidationRenderer()); } 1 2 3 constructor ( private controller: ValidationController ) { this . controller . addRenderer ( new SimpleValidationRenderer ( ) ) ; }

(and don’t forget to add <require from="./SimpleValidationRenderer.css"></require> to your App.html template)

Brilliant!

This is obviously a very basic introduction to the validation module, but it demonstrates the basics of adding some very simple field validation.

After lunch (Australian time) I’ll show how to create a simple replacement list of errors that lets you click on an error and have it focus the corresponding input field.