-
-
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)); | |
} |
Hi Jeremy I have a small issue: boundaryElement
is always the root container's element (the body
in my case)
Indeed, as I import bootstrap-validation
as a feature in my app, the bootstrap-form
handler is only registered on the root container.
Is there a way to register the bootstrap-validation
resolver on every container so that boundaryElement
is actually set to the element on which the validation-renderer
custom attribute is applied?
Thanks in advance
On line 30, I changed it to this: target.parentNode.insertBefore(message, message.nextSibling);
This appends it right after the input
. If you have your input
in another div
that isn't a form-group, this will put the message right after the input
.
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();
}
}
Consider moving unrender L41 to L53 and wrapping it in an if.
This ensures that the has-error class is only removed after the last error is resolved. Currently if there is more than one error, the has-error class is removed when the first one is resolved.