<!DOCTYPE html>
<html ng-app="my-app">
<head>
<link data-require="foundation@*" data-semver="5.0.0" rel="stylesheet" href="//cdn.jsdelivr.net/foundation/5.0.0/css/normalize.css" />
<link data-require="foundation@*" data-semver="5.0.0" rel="stylesheet" href="//cdn.jsdelivr.net/foundation/5.0.0/css/foundation.css" />
<link data-require="foundation@*" data-semver="5.0.0" rel="stylesheet" href="//cdn.jsdelivr.net/foundation/5.0.0/css/foundation.min.css" />
<script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="angular.js@*" data-semver="1.2.9" src="http://code.angularjs.org/1.2.9/angular.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular-animate.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<h1>AngularJS Validation Example</h1>
<form with-errors name="signUpForm" ng-controller="SignUpCtrl as c" ng-submit="c.signup(c.user)">
<fieldset>
<label>Username:</label>
<input name="username" type="text" ng-model="c.user.username" required />
<fielderrors for="username"></fielderrors>
<label>Password:</label>
<input name="password" type="password" ng-model="c.user.password" required />
<fielderrors for="password"></fielderrors>
<button ng-disabled="signUpForm.$invalid">Submit</button>
</fieldset>
</form>
</body>
</html>
var m = angular.module('my-app', []);
m.controller('SignUpCtrl', [
'$scope', '$q', 'setFormErrors',
function($scope, $q, setFormErrors) {
var serverErrors = this.serverErrors = {};
this.signup = function(user) {
fakeUserCreate(user).then(function() {
// Success
}, function(errors) {
// Failed
setFormErrors({
formName: 'signUpForm',
fieldErrors: errors
});
});
};
function fakeUserCreate() {
return $q.reject({username: ['This username is taken']});
}
}
]);
m.factory('setFormErrors', function() {
// Registered withErrors controllers
var withErrorCtrls = [];
// The exposed service
var setFormErrors = function(opts) {
var fieldErrors = opts.fieldErrors;
var ctrl = withErrorCtrls[opts.formName];
Object.keys(fieldErrors).forEach(function(fieldName) {
ctrl.setErrorsFor(fieldName, fieldErrors[fieldName]);
});
};
// Registers withErrors controller by form name (for internal use)
setFormErrors._register = function(formName, ctrl) {
withErrorCtrls[formName] = ctrl;
};
return setFormErrors;
});
m.directive('withErrors', ['setFormErrors', function(setFormErrors) {
return {
restrict: 'A',
require: 'withErrors',
controller: ['$scope', '$element', function($scope, $element) {
var controls = {};
this.addControl = function(fieldName, ctrl) {
controls[fieldName] = ctrl;
};
this.setErrorsFor = function(fieldName, errors) {
if (!(fieldName in controls)) return;
return controls[fieldName].setErrors(errors);
};
this.clearErrorsFor = function(fieldName, errors) {
if (!(fieldName in controls)) return;
return controls[fieldName].clearErrors(errors);
};
}],
link: function(scope, element, attrs, ctrl) {
// Make this form controller accessible to setFormErrors service
setFormErrors._register(attrs.name, ctrl);
}
};
}]);
m.directive('input', function() {
return {
restrict: 'E',
require: ['?ngModel', '?^withErrors'],
scope: true,
link: function(scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0];
var withErrorsCtrl = ctrls[1];
var fieldName = attrs.name;
if (!ngModelCtrl || !withErrorsCtrl) return;
// Watch for model changes and set errors if any
scope.$watch(attrs.ngModel, function() {
if (ngModelCtrl.$dirty && ngModelCtrl.$invalid) {
withErrorsCtrl.setErrorsFor(fieldName, errorMessagesFor(ngModelCtrl));
} else if (ngModelCtrl.$valid) {
withErrorsCtrl.clearErrorsFor(fieldName);
}
});
// Mapping Angular validation errors to a message
var errorMessages = {
required: 'This field is required'
};
function errorMessagesFor(ngModelCtrl) {
return Object.keys(ngModelCtrl.$error).
map(function(key) {
if (ngModelCtrl.$error[key]) return errorMessages[key];
else return null;
}).
filter(function(msg) {
return msg !== null;
});
}
}
}
});
m.directive('fielderrors', function() {
return {
restrict: 'E',
replace: true,
scope: true,
require: ['fielderrors', '^withErrors'],
template:
'<div ng-repeat="error in errors">' +
'<small class="error">{{ error }}</small>' +
'</div>',
controller: ['$scope', function($scope) {
$scope.errors = [];
this.setErrors = function(errors) {
$scope.errors = errors;
};
this.clearErrors = function() {
$scope.errors = [];
};
}],
link: function(scope, element, attrs, ctrls) {
var fieldErrorsCtrl = ctrls[0];
var withErrorsCtrl = ctrls[1];
withErrorsCtrl.addControl(attrs.for, fieldErrorsCtrl);
}
};
});
body {
padding: 5px;
}
input[type] {
margin-bottom: 0;
}
label, button {
margin-top: 1em;
}