// Code goes here
angular.module('myApp',[]);
var Q;
var HobbiesCtrl = (function () {
var setQ = function setQ(q){ Q = q;}
function HobbiesCtrl($scope,$q,businessRules) {
setQ($q)
this.mainValidator = businessRules;
this.data = {};
//fill list of hobbies with one empty item to indicate there are some hobbies to fill in
if (this.data.Hobbies === undefined)
this.data.Hobbies = [{}];
this.notifyCollectionChanged(true);
}
var frequencyOptions = [
{ text: 'Daily', value: "Daily" },
{ text: 'Weekly', value: "Weekly" },
{ text: 'Monthly', value: "Monthly" }
];
Object.defineProperty(HobbiesCtrl.prototype, "hobbyFrequencyOptions", {
/*
Return hobbies frequency options.
*/
get: function () {
return frequencyOptions;
},
enumerable: true,
configurable: true
});
Object.defineProperty(HobbiesCtrl.prototype, "HobbiesCountRule", {
/*
Return hobbies frequency options.
*/
get: function () {
var hobbiesCountRule = this.mainValidator.Rules["Hobbies"];
if (hobbiesCountRule === undefined) hobbiesCountRule = this.mainValidator.Validators["Hobbies"];
return hobbiesCountRule;
},
enumerable: true,
configurable: true
});
/*
Add new hobby to list of hobbies.
*/
HobbiesCtrl.prototype.addHobby = function () {
this.data.Hobbies.push({});
this.notifyCollectionChanged();
};
/*
Remove selected hobby from list of hobbies.
*/
HobbiesCtrl.prototype.removeHobby = function (hobby) {
this.data.Hobbies.splice(this.data.Hobbies.indexOf(hobby), 1);
this.notifyCollectionChanged();
};
/*
Hook function for actions before saving is done.
*/
HobbiesCtrl.prototype.Save = function () {
this.mainValidator.ValidationResult.SetDirty();
var result = this.mainValidator.Validate(this.data);
if (result.HasErrors) return;
alert(angular.toJson(this.data));
};
HobbiesCtrl.prototype.Reset = function () {
this.mainValidator.ValidationResult.SetPristine();
};
/*
Notify that collection was changed. Conditionally call validation for collection.
*/
HobbiesCtrl.prototype.notifyCollectionChanged = function (ignoreValidation) {
this.mainValidator.Children["Hobbies"].RefreshRows(this.data.Hobbies);
if (!ignoreValidation){
this.HobbiesCountRule.Validate(this.data.Hobbies);
}
};
return HobbiesCtrl;
})();
angular.module('myApp',[]).controller("hobbiesCtrl",HobbiesCtrl);
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap-css@3.1.1" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.22/angular.js" data-semver="1.2.22"></script>
<script data-require="underscore.js@1.6.0" data-semver="1.6.0" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script data-require="jquery@*" data-semver="2.1.1" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script data-require="bootstrap@3.1.1" data-semver="3.1.1" src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://rawgit.com/flesler/hashmap/master/hashmap.js"></script>
<script src="https://rawgit.com/rsamec/business-rules-engine/master/dist/business-rules-engine.js"></script>
<script src="app.js"></script>
<script src="br-jsonSchema.js"></script>
<script src="directives.js"></script>
</head>
<body ng-controller="hobbiesCtrl as va">
<br />
<div class="container-fluid">
<div class="col-xs-8">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-2 control-label">Name</label>
<div class="col-xs-4 col-sm-3 col-md-2">
<input type="text" class="form-control" placeholder="FirstName" ng-model="va.data.Person.FirstName" rule="va.mainValidator.Children['Person']" />
<div val-result="va.mainValidator.Children['Person'].Rules['FirstName']"></div>
</div>
<div class="col-xs-4 col-sm-4 col-md-3">
<input type="text" class="form-control" placeholder="LastName" ng-model="va.data.Person.LastName" rule="va.mainValidator.Children['Person']" />
<div val-result="va.mainValidator.Children['Person'].Rules['LastName']"></div>
</div>
</div>
<div class="form-group">
<label class="col-xs-2 control-label">Email</label>
<div class="col-xs-6 col-sm-7 col-md-5">
<input type="text" class="form-control" rule="va.mainValidator.Children['Person'].Children['Contact']" ng-model="va.data.Person.Contact.Email" placeholder="Email">
<div val-result="va.mainValidator.Children['Person'].Children['Contact'].Rules['Email']"></div>
</div>
</div>
<div class="row">
<div class="col-xs-2">
<div class="btn btn-primary" ng-click="va.addHobby()">
Add +
</div>
</div>
<div class="col-xs-10">
<div class="validation-error">{{va.HobbiesCountRule.ErrorMessage}}</div>
</div>
</div>
<div ng-repeat="hobby in va.data.Hobbies">
<div class="form-group">
<label class="col-xs-2 control-label">Name</label>
<div class="col-xs-8 col-sm-7 col-md-5">
<div class="input-group">
<input type="text" class="form-control" ng-model="hobby.HobbyName" placeholder="Name" rule="va.mainValidator.Children['Hobbies'].Rows[$index]" />
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="va.removeHobby(hobby)">X</button>
<!--<i class="fa fa-times"></i>-->
</span>
</div>
<div ng-show="va.mainValidator.Children['Hobbies'].Rows[$index].ValidationResult.HasErrors" class="validation-error">
{{va.mainValidator.Children['Hobbies'].Rows[$index].ValidationResult.ErrorMessage}}
</div>
</div>
</div>
<div class="form-group">
<label class="col-xs-2 control-label">Frequency</label>
<div style="display: table" class="col-xs-4 col-sm-3 col-md-2">
<select class="form-control" ng-model="va.data.Hobbies[$index].Frequency" ng-options="obj.value as obj.text for obj in va.hobbyFrequencyOptions"></select>
</div>
</div>
<div class="form-group">
<div class="col-xs-offset-2 col-xs-8">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="va.data.Hobbies[$index].Paid" />
Is this a paid hobby?</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-offset-2 col-xs-8">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="va.data.Hobbies[$index].Recommendation" />
Would you recommend this hobby to a friend?</label>
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-offset-2 col-xs-8">
<button type="button" class="btn btn-primary" ng-click="va.Save()">Save</button>
</div>
</div>
</form>
</div>
<div class="col-xs-4">
<pre>{{va.data |json}}</pre>
</div>
</div>
</body>
</html>
/* Put your css in here */
.validation-error{
color:darkred;
}
var app = angular.module('myApp');
app.directive('rule', function ($parse) {
return {
require:'ngModel',
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
if (ctrl===undefined) return;
//parse ngModel expression
var lastIndexOf = attrs.ngModel.lastIndexOf('.');
if (lastIndexOf === -1) return;
//set model expression
var parentModel = attrs.ngModel.substr(0,lastIndexOf);
//set property name
var propertyName = attrs.ngModel.substr(lastIndexOf + 1);
//create rule
var rule = scope.$eval(attrs.rule);
//create parent getter
var getter = $parse(parentModel);
//when value changed then validate property
ctrl.$viewChangeListeners.push(function(){
//execute validation
rule.ValidateProperty(getter(scope),propertyName);
//set dirty flag for correct display
rule.ValidationResult.Errors[propertyName].IsDirty = true;
});
}
};
});
app.directive('field', function ($timeout,$parse) {
return {
require:'ngModel',
restrict: 'E',
replace:true,
scope:true,
templateUrl:'field.tpl.html',
link: function (scope, element, attrs, ctrl) {
if (ctrl== undefined) return;
// we can now use our ngModelController builtin methods
// that do the heavy-lifting for us
var inputs = element.find('input');
var inputEl = inputs.eq(0);
// when model change, update our view (just update the input value)
ctrl.$render = function() {
inputEl.val(ctrl.$viewValue);
};
// update the model then the view
var updateModel = function () {
// call $parsers pipeline then update $modelValue
ctrl.$setViewValue(inputEl.val());
// update the local view
ctrl.$render();
}
scope.label = attrs.label;
bindToOnChanged(scope, inputs, attrs, updateModel);
var lastIndexOf = attrs.ngModel.lastIndexOf('.');
var parentModel = attrs.ngModel.substr(0,lastIndexOf);
var propertyName = attrs.ngModel.substr(lastIndexOf + 1);
var rule = scope.$eval(attrs.rule)
var getter = $parse(parentModel);
ctrl.$viewChangeListeners.push(function(){
rule.ValidateProperty(getter(scope),propertyName);
rule.ValidationResult.Errors[propertyName].IsDirty = true;
});
scope.getErrorMessage = function() {
return rule.ValidationResult.Errors[propertyName].ErrorMessage;
}
}
};
});
app.directive('valResult', function () {
return {
restrict: 'A',
scope:{
valResult:'='
},
//template:'<div class="validation-error"></div>'
link: function (scope, element, attrs) {
element.addClass("validation-error");
var errorChanged = function(){
return scope.valResult.ErrorMessage;
}
scope.$watch(errorChanged,function(){
element.html(scope.valResult.ErrorMessage);
},true)
}
};
});
function bindToOnChanged(scope, element, attrs, bindTo) {
//bind to event according type of element
var tagName = element[0].tagName.toLowerCase();
if (tagName === 'select')
{
element.bind("change", function ()
{
scope.$apply(bindTo);
});
}
else if (tagName === 'input')
{
if (attrs.type === 'radio' || attrs.type === 'checkbox')
{
element.bind("click", function ()
{
scope.$apply(bindTo);
})
}
else
{
element.bind("blur", function ()
{
scope.$apply(bindTo);
})
}
}
else if (tagName === 'textarea')
{
element.bind("blur", function ()
{
scope.$apply(bindTo);
})
}
else
{
}
}
angular.module('myApp').factory('businessRules',function(){
var json = {
Person: {
type: "object",
properties: {
FirstName: { type: "string", title: "First name", required: true, maxLength: 15 },
LastName: { type: "string", title: "Last name", required: true, maxLength: 15 },
Contact: {
type: "object",
properties: {
Email: {
type: "string", title: "Email",
required: true,
maxLength: 100,
email: true
}
}
}
}
},
Hobbies: {
type: "array",
items: {
type: "object",
properties: {
HobbyName: { type: "string", title: "HobbyName", required: true, maxLength: 100 },
Frequency: { type: "string", title: "Frequency", enum: ["Daily", "Weekly", "Monthly"] },
Paid: { type: "boolean", title: "Paid" },
Recommedation: { type: "boolean", title: "Recommedation" }
}
},
maxItems: 4,
minItems: 2
}
};
return new FormSchema.JsonSchemaRuleFactory(json).CreateRule("Main");
})
angular.module('myApp').factory('businessRules',function(){
var json = {
Person: {
FirstName: {
rules: {required: true, maxlength: 15}
},
LastName: {
rules: { required: true, maxlength: 15 }
},
Contact: {
Email: {
rules: {
required: true,
maxlength: 100,
email: true
}
}
}
},
Hobbies: [
{
HobbyName: {
rules: { required: true, maxlength: 100 }
},
Frequency: {
rules: { enum: ["Daily", "Weekly", "Monthly"]
}
}},
{ maxItems: 4, minItems: 2}
]
};
return new FormSchema.JQueryValidationRuleFactory(json).CreateRule("Main");
})
angular.module('myApp').factory('businessRules',function(){
var BusinessRules = (function () {
function BusinessRules() {
this.MainValidator = this.createMainValidator().CreateRule("Main");
}
BusinessRules.prototype.createMainValidator = function () {
//create custom validator
var validator = new Validation.AbstractValidator();
validator.ValidatorFor("Person", this.createPersonValidator());
validator.ValidatorFor("Hobbies", this.createItemValidator(), true);
var hobbiesCountFce = function (args) {
args.HasError = false;
args.ErrorMessage = "";
if (this.Hobbies === undefined || this.Hobbies.length < 2) {
args.HasError = true;
args.ErrorMessage = "Come on, speak up. Tell us at least two things you enjoy doing";
args.TranslateArgs = { TranslateId: 'HobbiesCountMin' };
return;
}
if (this.Hobbies.length > 4) {
args.HasError = true;
args.ErrorMessage = "'Do not be greedy. Four hobbies are probably enough!'";
args.TranslateArgs = { TranslateId: 'HobbiesCountMax'};
return;
}
};
validator.Validation({ Name: "Hobbies", ValidationFce: hobbiesCountFce });
return validator;
};
BusinessRules.prototype.createPersonValidator = function () {
//create custom composite validator
var personValidator = new Validation.AbstractValidator();
//create validators
var required = new Validators.RequiredValidator();
var maxLength = new Validators.MaxLengthValidator(15);
//assign validators to properties
personValidator.RuleFor("FirstName", required);
personValidator.RuleFor("FirstName", maxLength);
personValidator.RuleFor("LastName", required);
personValidator.RuleFor("LastName", maxLength);
personValidator.ValidatorFor("Contact", this.createContactValidator());
return personValidator;
};
BusinessRules.prototype.createContactValidator = function () {
//create custom validator
var validator = new Validation.AbstractValidator();
validator.RuleFor("Email", new Validators.RequiredValidator());
validator.RuleFor("Email", new Validators.MaxLengthValidator(100));
validator.RuleFor("Email", new Validators.EmailValidator());
return validator;
};
BusinessRules.prototype.createItemValidator = function () {
//create custom validator
var validator = new Validation.AbstractValidator();
validator.RuleFor("HobbyName", new Validators.RequiredValidator());
return validator;
};
return BusinessRules;
})();
return new BusinessRules().MainValidator;
})