<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Aurelia TypeScript Template</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="style.css" />
</head>
<body aurelia-app="main">
<h1>Loading...</h1>
<!--Import jQuery before materialize.js-->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.20.19/system.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
</html>
export function configure(aurelia) {
aurelia.use.basicConfiguration()
.plugin('aurelia-validation');
aurelia.start().then(() => aurelia.setRoot('app'));
}
System.config({
map: {
'i18next': 'https://unpkg.com/i18next/i18next.min',
'aurelia-binding': 'https://unpkg.com/aurelia-binding/dist/commonjs/aurelia-binding.js',
'aurelia-bootstrapper': 'https://unpkg.com/aurelia-bootstrapper/dist/commonjs/aurelia-bootstrapper.js',
'aurelia-dependency-injection': 'https://unpkg.com/aurelia-dependency-injection/dist/commonjs/aurelia-dependency-injection.js',
'aurelia-dialog': 'https://unpkg.com/aurelia-dialog/dist/commonjs',
'aurelia-event-aggregator': 'https://unpkg.com/aurelia-event-aggregator/dist/commonjs/aurelia-event-aggregator.js',
'aurelia-fetch-client': 'https://unpkg.com/aurelia-fetch-client/dist/commonjs/aurelia-fetch-client.js',
'aurelia-framework': 'https://unpkg.com/aurelia-framework/dist/commonjs/aurelia-framework.js',
'aurelia-history': 'https://unpkg.com/aurelia-history/dist/commonjs/aurelia-history.js',
'aurelia-history-browser': 'https://unpkg.com/aurelia-history-browser/dist/commonjs/aurelia-history-browser.js',
'aurelia-i18n': 'https://unpkg.com/aurelia-i18n/dist/commonjs',
'aurelia-loader': 'https://unpkg.com/aurelia-loader/dist/commonjs/aurelia-loader.js',
'aurelia-loader-default': 'https://unpkg.com/aurelia-loader-default/dist/commonjs/aurelia-loader-default.js',
'aurelia-logging': 'https://unpkg.com/aurelia-logging/dist/commonjs/aurelia-logging.js',
'aurelia-logging-console': 'https://unpkg.com/aurelia-logging-console/dist/commonjs/aurelia-logging-console.js',
'aurelia-metadata': 'https://unpkg.com/aurelia-metadata/dist/commonjs/aurelia-metadata.js',
'aurelia-pal': 'https://unpkg.com/aurelia-pal/dist/commonjs/aurelia-pal.js',
'aurelia-pal-browser': 'https://unpkg.com/aurelia-pal-browser/dist/commonjs/aurelia-pal-browser.js',
'aurelia-path': 'https://unpkg.com/aurelia-path/dist/commonjs/aurelia-path.js',
'aurelia-polyfills': 'https://unpkg.com/aurelia-polyfills/dist/commonjs/aurelia-polyfills.js',
'aurelia-router': 'https://unpkg.com/aurelia-router/dist/commonjs/aurelia-router.js',
'aurelia-route-recognizer': 'https://unpkg.com/aurelia-route-recognizer/dist/commonjs/aurelia-route-recognizer.js',
'aurelia-task-queue': 'https://unpkg.com/aurelia-task-queue/dist/commonjs/aurelia-task-queue.js',
'aurelia-templating': 'https://unpkg.com/aurelia-templating/dist/commonjs/aurelia-templating.js',
'aurelia-templating-binding': 'https://unpkg.com/aurelia-templating-binding/dist/commonjs/aurelia-templating-binding.js',
'aurelia-templating-resources': 'https://unpkg.com/aurelia-templating-resources/dist/commonjs',
'aurelia-templating-router': 'https://unpkg.com/aurelia-templating-router/dist/commonjs',
'aurelia-validation': 'https://unpkg.com/aurelia-validation/dist/commonjs',
},
packages: {
'.': {},
'aurelia-templating-resources': {
main: 'aurelia-templating-resources.js'
},
'aurelia-templating-router': {
main: 'aurelia-templating-router.js'
},
'aurelia-validation': {
main: 'aurelia-validation.js'
},
'aurelia-dialog': {
main: 'aurelia-dialog.js'
},
'aurelia-i18n': {
main: 'aurelia-i18n.js'
},
}
});
{
"compilerOptions": {
"target": "ESNEXT",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
import { Aurelia, PLATFORM } from 'aurelia-framework';
export class App {
message = "App";
public formName = "Support";
public formTemplate = {
"types": [
"text",
"file",
"number",
"email"
],
"Support": {
"description": "This will generate form for Operational Support request",
"fields": [
{
"name": "title",
"element": "input",
"type": "text",
"label": "Title",
"placeHolder": "Provide Short Discription",
"classes": "form-control",
"value": "",
"validation": {
"isValidate": true,
"validationRule": {
"required": true,
"maxLength": 250
}
}
},
{
"name": "email",
"element": "custom-email",
"type": "email",
"label": "Email",
"placeHolder": "Provide email addresses",
"classes": "form-control",
"value": "",
"validation": {
"isValidate": true,
"validationRule": {
"required": true,
"email": true
},
"errors": []
}
}
]
}
};
public processForm() {
//this.controller.validate();
console.log(this.formTemplate[this.formName].fields);
console.log(this);
}
}
<template>
<require from="dynamicform"></require>
<div class="panel panel-primary">
<div class="panel-heading">${pageTitle}</div>
<div class="panel-body">
<dynamic-form form-template.two-way="formTemplate" form-name.two-way="formName" callback.call="processForm()"></dynamic-form>
</div>
</div>
</template>
import { customElement, useView, bindable, bindingMode, inject } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory, ValidateBindingBehavior } from 'aurelia-validation';
@inject(ValidationControllerFactory)
@customElement('custom-input')
@useView('./custominput.html')
export class CustomInput {
@bindable public callback;
@bindable public formItem;
controller = null;
constructor(factory) {
this.controller = factory.createForCurrentScope();
}
bind() {
this.controller.validate();
this.formItem.validation.errors = this.controller.errors;
this.controller.subscribe(this.callback);
}
}
<template>
<input
class="${formItem.classes}"
type="text"
value.two-way="formItem.value & validateOnChangeOrBlur"
placeholder="${formItem.placeHolder}"
name="${formItem.name}" />
<ul if.bind="controller.errors.length > 0" style="list-style-type:none;">
<li class="text-danger" repeat.for="error of controller.errors">${error.message}</li>
</ul>
</template>
import { bindable, bindingMode, inject } from 'aurelia-framework';
import { FormHelper } from './formhelper';
export class DynamicForm {
@bindable public formName: string;
@bindable public formTemplate: Object;
@bindable public callback;
inputItem: HTMLInputElement;
public processFormRequest() {
console.log(this);
}
public bind() {
const forms = Object.keys(this.formTemplate)
.filter(key => key !== "types")
.map(key => this.formTemplate[key]);
for (const form of forms) {
FormHelper.initializeFormRules(form);
};
}
private errorCount: number = 0;
public onValidate = (event: ValidateEvent) => {
this.errorCount = 0;
const forms = Object.keys(this.formTemplate)
.filter(key => key !== "types")
.map(key => this.formTemplate[key]);
for (const form of forms) {
for (const field of form.fields) {
this.errorCount += field.validation.errors.length;
}
}
}
}
<template>
<require from="./custominput"></require>
<require from="./customemail"></require>
<form class="form-horizontal">
<template repeat.for="item of formTemplate[formName].fields">
<div form-name.bind="formName" class="form-group">
<label for="${item.name}" class="col-sm-2 control-label">${item.label}</label>
<div class="col-sm-10" if.bind="item.type === 'text' && item.element === 'input'">
<custom-input form-item.bind="item" callback.bind="onValidate">
</custom-input>
</div>
<div class="col-sm-10" if.bind="item.type === 'email' && item.element === 'custom-email'">
<custom-email form-item.bind="item" callback.bind="onValidate"></custom-email>
</div>
</div>
</template>
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-default pull-right" disabled.bind="errorCount" click.delegate="processFormRequest()">Submit</button>
</div>
</div>
</form>
</template>
import { ValidationRules } from 'aurelia-validation';
export class FormHelper {
private static initializedForms = [];
public static initializeFormRules(form) {
if (this.initializedForms.indexOf(form) > -1) {
return;
}
this.initializedForms.push(form);
for (const field of form.fields) {
if (field.validation.isValidate) {
field.validation.errors = [];
let ruleBuilder = ValidationRules
.ensure("value")
.displayName(field.label);
const rules = Object.keys(field.validation.validationRule)
.map(key => ({key, value: field.validation.validationRule[key]}));
for (const rule of rules) {
ruleBuilder = ruleBuilder[rule.key](rule.value);
}
ruleBuilder.on(field);
}
}
}
}
import { customElement, useView, bindable, bindingMode, inject } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory, Validator } from 'aurelia-validation';
@inject(ValidationControllerFactory)
@customElement('custom-email')
@useView('./customemail.html')
export class CustomEmail {
@bindable public formItem;
@bindable public callback;
controller = null;
constructor(factory) {
this.controller = factory.createForCurrentScope();
}
bind() {
this.controller.validate();
this.formItem.validation.errors = this.controller.errors;
this.controller.subscribe(this.callback);
}
}
<template>
<require from="./email"></require>
<div class="input-group">
<input readonly class="${formItem.classes}" type="text" value.two-way="formItem.value & validateOnChangeOrBlur" placeholder="${formItem.placeHolder}" name="${formItem.name}" />
<span class="input-group-btn">
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#emailModal">
+
</button>
</span>
</div>
<ul if.bind="controller.errors.length > 0" style="list-style-type:none;">
<li class="text-danger" repeat.for="error of controller.errors">${error.message}</li>
</ul>
<email modal-name.bind="'emailModal'" email-address.two-way="formItem.value" modal-value.bind="'+'"></email>
</template>
import { customElement, useView, bindable, bindingMode, inject } from 'aurelia-framework';
import { ValidationRules, ValidationControllerFactory, Validator } from 'aurelia-validation';
@inject(ValidationControllerFactory)
@customElement('email')
@useView('./email.html')
export class Email {
@bindable public modalName: string;
@bindable public modalValue: string;
@bindable public emailAddress: string;
public emailAddresses = [];
public setEmail: string;
public errorMessage: string;
emailController = null;
constructor(factory) {
this.setEmail = '';
this.emailController = factory.createForCurrentScope();
ValidationRules.ensure('setEmail').displayName('Email').required().email().on(this);
}
public bind() {
this.emailController.validate();
}
private joinEmails() {
this.emailAddress = this.emailAddresses.join(";");
}
private isUniqueEmail = (email: string) => {
return (this.emailAddresses.indexOf(email) > -1)
}
public addEmail() {
if(this.isUniqueEmail(this.setEmail))
{
this.errorMessage = "You must provide unique email address.";
return;
}
this.emailAddresses.push(this.setEmail);
this.joinEmails();
this.setEmail = "";
}
public removeEmail(index) {
this.emailAddresses.splice(index, 1);
this.joinEmails();
}
}
<template>
<!-- Modal -->
<div class="modal fade" id="${modalName}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Add Email Address</h4>
</div>
<div class="modal-body">
<div class="input-group">
<input type="text" id="setEmail" name="setEmail" class="form-control" value.bind="setEmail & validateOnChangeOrBlur" />
<span class="input-group-btn">
<button class="btn btn-primary" class.bind="emailController.errors.length > 0 ? 'disabled' : ''" disabled.bind="emailController.errors.length > 0" click.delegate="addEmail()">Add</button>
</span>
</div>
<input type="text" value.bind="emailAddress" hidden />
<span class="text-danger" repeat.for="error of emailController.errors">${error.message}</span>
<span class="text-danger" if.bind="errorMessage">${errorMessage}</span>
<div>
<ul class="list-group" if.bind="emailAddresses.length > 0" style="margin-top: 10px;">
<li class="list-group-item" repeat.for="e of emailAddresses">
${e} <span class="glyphicon glyphicon-remove text-danger pull-right" style="cursor: pointer;" click.delegate="removeEmail($index)"></span>
</li>
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>