-
-
Save jdanyow/ea843c24956cfffff48bb21776291f6a to your computer and use it in GitHub Desktop.
import {inject} from 'aurelia-dependency-injection'; | |
import {validationRenderer} from 'aurelia-validation'; | |
@validationRenderer | |
@inject(Element) | |
export class BootstrapFormValidationRenderer { | |
constructor(boundaryElement) { | |
this.boundaryElement = boundaryElement; | |
} | |
render(error, target) { | |
if (!target || !(this.boundaryElement === target || this.boundaryElement.contains(target))) { | |
return; | |
} | |
// tag the element so we know we rendered into it. | |
target.errors = (target.errors || new Map()); | |
target.errors.set(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); | |
} | |
unrender(error, target) { | |
if (!target || !target.errors || !target.errors.has(error)) { | |
return; | |
} | |
target.errors.delete(error); | |
// 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 {BootstrapFormValidationRenderer} from './bootstrap-form-validation-renderer'; | |
export function configure(config) { | |
config.container.registerHandler( | |
'bootstrap-form', | |
container => container.get(BootstrapFormValidationRenderer)); | |
} |
Why isn't this a package? I get that maybe some people will care to customize the behavior, but certainly seems like a good default should be an npm install
away. Hopefully it's just because you haven't gotten around to it?
On line 52 ChildNode.prototype.remove()
is called, which is not well supported in all browsers and should be polyfilled.
https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
message.parentElement.removeChild(message);
Tested in IE, which was generating errors before and it works.
Sample unit test in TS with little support of JQuery:
import {BootstrapFormValidationRenderer} from "./bootstrap-form-validation-renderer";
import {RenderInstruction, RenderErrorInstruction, ValidationError} from "aurelia-validation";
import "jquery";
describe(BootstrapFormValidationRenderer.name, () => {
let renderer: BootstrapFormValidationRenderer;
let html: JQuery;
class TestRenderInstruction implements RenderInstruction {
kind;
render: RenderErrorInstruction[] = [];
unrender: RenderErrorInstruction[] = [];
addRender(errorId: number, elementSelector: string): TestRenderInstruction {
this.render.push(this.newRenderErrorInstruction(errorId, elementSelector));
return this;
}
addUnrender(errorId: number, elementSelector: string): TestRenderInstruction {
this.unrender.push(this.newRenderErrorInstruction(errorId, elementSelector));
return this;
}
private newRenderErrorInstruction(errorId: number, elementSelector: string) {
return <RenderErrorInstruction>{
error: this.newError(errorId),
elements: [html.find(`${elementSelector} input`)[0]]
}
};
private newError(id: number): ValidationError {
let error = new ValidationError({}, `Error ${id}`, {});
error.id = id;
return error;
}
}
beforeEach(() => {
html = $(`<form>
<div class="form-group first"><input type="text"></div>
<div class="form-group second"><input type="text"></div>
</form>`);
renderer = new BootstrapFormValidationRenderer;
});
it('displays an error', () => {
renderer.render(new TestRenderInstruction().addRender(1, '.first'));
expect(html.find(".first").hasClass('has-error')).toBeTruthy();
expect(html.find(".second").hasClass('has-error')).toBeFalsy();
expect(html.find(".first .help-block").length).toBe(1);
});
it('clears an error', () => {
renderer.render(new TestRenderInstruction().addRender(1, '.first'));
renderer.render(new TestRenderInstruction().addUnrender(1, '.first'));
expect(html.find(".first").hasClass('has-error')).toBeFalsy();
expect(html.find(".first .help-block").length).toBe(0);
});
it('renders many errors', () => {
renderer.render(new TestRenderInstruction().addRender(1, '.first').addRender(2, '.first'));
expect(html.find(".first").hasClass('has-error')).toBeTruthy();
expect(html.find(".first .help-block").length).toBe(2);
});
it('renders errors next to desired fields', () => {
renderer.render(new TestRenderInstruction().addRender(1, '.first').addRender(2, '.second'));
expect(html.find(".first .help-block").text()).toBe('Error 1');
expect(html.find(".second .help-block").text()).toBe('Error 2');
});
it('clears single error', () => {
renderer.render(new TestRenderInstruction().addRender(1, '.first').addRender(2, '.first'));
renderer.render(new TestRenderInstruction().addUnrender(1, '.first'));
expect(html.find(".first").hasClass('has-error')).toBeTruthy();
expect(html.find(".first .help-block").length).toBe(1);
expect(html.find(".first .help-block").text()).toBe('Error 2');
});
});
i want to use BootstrapFormValidationRenderer code in aurelia framework javascript but i have this error TypeError: Object(...) is not a function
at Module../src/bootstrap-form-validation-renderer.js
i have a registration form like this
<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"><span></span>
</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="text" class="form-control" id="email" placeholder="Email"
value.bind="email & validate">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
and a js file this
import {inject, NewInstance} from 'aurelia-dependency-injection';
import {ValidationController, validateTrigger} from 'aurelia-validation';
import {required, email} from 'aurelia-validatejs';
import {BootstrapFormValidationRenderer} from './bootstrap-form-validation-renderer';
@Inject(NewInstance.of(ValidationController), validateTrigger)
export class RegistrationForm {
@required
firstName = '';
@required
lastName = '';
@required
email = '';
constructor(controller) {
this.controller = controller;
controller.validateTrigger = validateTrigger.manual;
}
submit() {
let errors = this.controller.validate();
}
}
On line 30, I changed it to this:
target.parentNode.insertBefore(message, message.nextSibling);
This appends it right after the
input
. If you have yourinput
in anotherdiv
that isn't a form-group, this will put the message right after theinput
.