Skip to content

Instantly share code, notes, and snippets.

@mbroadst
Forked from jdanyow/app.html
Last active August 15, 2020 15:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mbroadst/4bec1022baac829e236e35892408ca67 to your computer and use it in GitHub Desktop.
Save mbroadst/4bec1022baac829e236e35892408ca67 to your computer and use it in GitHub Desktop.
Aurelia Validation using AJV
import {ValidationError} from 'aurelia-validation';
export class AjvValidator {
cache = new Map;
ajv = new Ajv({ v5: true, allErrors: true, format: 'full' });
validateObject(object) {
this.parseSchema(object);
let schemaId = this._schemaId(object);
if (!this.cache.has(schemaId)) {
console.warn('no schema defined for object');
return [];
}
let validate = this.cache.get(schemaId).validate;
let valid = validate(data);
return valid ? [] : validate.errors
.map(({ dataPath, keyword, message, params, schemaPath }) =>
new ValidationError(dataPath, message, object, propertyName));
}
validateProperty(object, propertyName) {
this.parseSchema(object);
let schemaId = this._schemaId(object);
if (!this.cache.has(schemaId)) {
console.warn('no schema defined for object');
return [];
}
if (!this.cache.get(schemaId).hasOwnProperty(propertyName)) {
console.warn('property not defined in schema: ', propertyName);
return [];
}
let definition = this.cache.get(schemaId);
let validate = definition[propertyName];
let valid = validate({ [propertyName]: object[propertyName] });
return valid ? [] : validate.errors
.map(({ dataPath, keyword, message, params, schemaPath }) =>
new ValidationError(dataPath, message, object, propertyName));
}
_schemaId(object) {
return (!object.schema.hasOwnProperty('id')) ?
object.constructor.name : object.schema.id;
}
parseSchema(object) {
let schema = object.schema;
if (schema === undefined || schema === null) {
throw new Error('object lacks schema');
}
let schemaId = this._schemaId(object);
if (this.cache.has(schemaId)) {
// schema has already been parsed
return;
}
if (!schema.hasOwnProperty('properties')) {
throw new Error('only object schemas are current supported');
}
let definition = {};
definition.validate = this.ajv.compile(schema);
// split schema into individual properties
let required = schema.required || [];
for (let property in schema.properties) {
let subSchema = { type: 'object', properties: {} };
subSchema.properties[property] = schema.properties[property];
if (required.indexOf(property) !== -1) subSchema.required = [ property ];
let validator = this.ajv.compile(subSchema);
definition[property] = validator;
}
this.cache.set(schemaId, definition);
}
}
<template>
<form submit.delegate="submit()">
<!--<ul><li repeat.for="error of controller.errors">${error.message}</li></ul>-->
<div class="form-group">
<label class="control-label" for="first">First Name</label>
<input type="text" class="form-control" id="first" placeholder="First Name"
value.bind="firstName & validate">
</div>
<div class="form-group">
<label class="control-label" for="last">Last Name</label>
<input type="text" class="form-control" id="last" placeholder="Last Name"
value.bind="lastName & validate">
</div>
<div class="form-group">
<label class="control-label" for="email">Email</label>
<input type="email" class="form-control" id="email" placeholder="Email"
value.bind="email & validate">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</template>
import {inject} from 'aurelia-dependency-injection';
import {
ValidationControllerFactory,
ValidationController
} from 'aurelia-validation';
import {BootstrapFormRenderer} from './bootstrap-form-renderer';
@inject(ValidationControllerFactory)
export class App {
schema = {
type: 'object',
properties: {
firstName: { type: 'string', "minLength": 1 },
lastName: { type: 'string', "minLength": 1 },
email: { type: 'string', format: 'email', "minLength": 1 }
},
required: [ 'firstName', 'lastName', 'email' ]
}
firstName = '';
lastName = '';
email = '';
controller = null;
constructor(controllerFactory) {
this.controller = controllerFactory.createForCurrentScope();
this.controller.addRenderer(new BootstrapFormRenderer());
}
submit() {
this.controller.validate();
}
}
import {
ValidationRenderer,
RenderInstruction,
ValidationError
} from 'aurelia-validation';
export class BootstrapFormRenderer {
render(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);
}
}
}
add(element, error) {
const formGroup = element.closest('.form-group');
if (!formGroup) {
return;
}
// add the has-error class to the enclosing form-group div
formGroup.classList.add('has-error');
// add help-block
const message = document.createElement('span');
message.className = 'help-block validation-message';
message.textContent = error.message;
message.id = `validation-message-${error.id}`;
formGroup.appendChild(message);
}
remove(element, error) {
const formGroup = element.closest('.form-group');
if (!formGroup) {
return;
}
// remove help-block
const message = formGroup.querySelector(`#validation-message-${error.id}`);
if (message) {
formGroup.removeChild(message);
// remove the has-error class from the enclosing form-group div
if (formGroup.querySelectorAll('.help-block.validation-message').length === 0) {
formGroup.classList.remove('has-error');
}
}
}
}
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
</head>
<body aurelia-app="main" class="container">
<h1>Loading...</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/4.5.0/ajv.min.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/node_modules/requirejs/require.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/config.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/aurelia.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/babel.js"></script>
<script>
require(['aurelia-bootstrapper']);
</script>
</body>
</html>
import {AjvValidator} from './ajv-validator';
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin('aurelia-validation', config =>
config.customValidator(AjvValidator));
aurelia.start().then(() => aurelia.setRoot());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment