Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Aurelia - bootstrap form validation feature
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));
}
@taz

This comment has been minimized.

Copy link

commented Jun 17, 2016

Consider moving unrender L41 to L53 and wrapping it in an if.

        if (i == 0) {
                formGroup.classList.remove('has-error');
            }

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.

@tkhyn

This comment has been minimized.

Copy link

commented Jul 1, 2016

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

@vincentw56

This comment has been minimized.

Copy link

commented Jul 6, 2016

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.

@WorldMaker

This comment has been minimized.

Copy link

commented Aug 18, 2016

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?

@gheoan

This comment has been minimized.

Copy link

commented Aug 23, 2016

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

@toxicehc

This comment has been minimized.

Copy link

commented Aug 26, 2016

  message.parentElement.removeChild(message);

Tested in IE, which was generating errors before and it works.

@fracz

This comment has been minimized.

Copy link

commented Nov 5, 2016

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');
  });
});
@hosseinakbarian

This comment has been minimized.

Copy link

commented Nov 27, 2018

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();
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.