<!doctype html>
<html ng-app="plunker" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <script>document.write("<base href=\"" + document.location + "\" />");</script>
  <link rel="stylesheet" href="style.css">
  <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap-combined.min.css" rel="stylesheet">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js"></script>
  <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/js/bootstrap.min.js"></script>
  <script src="app.js"></script>
  
</head>
<body ng-controller="MainCtrl">
  <div class='span12'>
    <form name='TestForm' ng-submit='addUser()'>
      <legend>Add User Form</legend>
      <div control-group='TestForm.userName'>
        <label for='userName'>Name</label>
        <input type="text" name="userName" ng-model='user.name' user-name-validation required />
        <form-error key='TestForm.userName.$error.minlength'>User name cannot be less than 9 characters</form-error>
        <form-error key='TestForm.userName.$error.maxlength'>User name cannot be more than 9 characters</form-error>
        <form-error key='TestForm.userName.$error.conflict'>User {{ user.name }} already exists</form-error>
      </div>
      <div control-group='TestForm.userAge'>
        <label for='userAge'>Age</label>
        <input type="text" name="userAge" ng-model='user.age' user-age-validation />
        <form-error key='TestForm.userAge.$error.integer'>Age must be an integer</form-error>
        <form-error key='TestForm.userAge.$error.too_old'>The user is older than 30</form-error>
        <form-error key='TestForm.userAge.$error.too_young'>The user is younger than 5</form-error>
      </div>
      <div control-group='TestForm.usercolor'>
        <label for='usercolor'>Color</label>
        <select name='userColor' ng-model='user.color' ng-options='c for c in colors' required>
          <option value="">Select a color</option>
        </select>
      </div>
      <div class='form-actions'>
        <button class='btn btn-primary' ng-class='{disabled:TestForm.$invalid}'>Add User</button>
        <form-error key='TestForm.$invalid && TestForm.$dirty'>This form has errors</form-error>
      </div>
    </form>
    <hr />
    <h3>Existing Users</h3>
    <table class='table table-bordered'>
      <thead>
        <th>Name</th>
        <th>Age</th>
        <th>Favorite Color</th>
      </thead>
      <tr ng-repeat='u in users'>
        <td>{{ u.name }}</td>
        <td>{{ u.age }}</td>
        <td>{{ u.color }}</td>
      </tr>
    </table>
    <hr />
    <div class="alert alert-block">
      <button type="button" class="close" data-dismiss="alert">×</button>
      <h4>Warning!</h4>
      This may not work in IE8
    </div>
  </div>
</body>
</html>
/* Put your css in here */

var app = angular.module('plunker', ['plunker.mock']);

app.controller('MainCtrl', function($scope, User, Color) {
  $scope.colors = Color.query();
  $scope.user = {};
  
  $scope.addUser = function() {
    if($scope.TestForm.$valid) {
      User.save($scope.user);
      $scope.user = {};
      $scope.users = User.query();
    }
  }
  
  $scope.findUsers = function() {
    $scope.users = User.query();
  }
  
  $scope.findUsers();
});

app.directive('controlGroup', function() {
  return {
    templateUrl: 'p_control-group.html',
    transclude: 'element',
    replace: true,
    scope: { controlGroup: '=controlGroup' },
    link: function(scope) {
      console.log(scope)
    }
  };
});

app.directive('formError', function(){
    return {
      restrict: 'EMCA',
      templateUrl: 'p_form-error.html',
      transclude: 'true',
      replace: true,
      scope: { inputKey : '=key'},
    };
});

app.directive('userNameValidation', function(){
    return {
      restrict: 'A', // only activate on element attribute
      require: '?ngModel', // get a hold of NgModelController
      link: function(scope, element, attrs, ngModel) {
        if(!ngModel) return; // do nothing if no ng-model
 
        var isConflict = function(value) {
          var conflict = false;
          angular.forEach(scope.users, function(u) {
            console.log(value, u, scope.users);
            if(u.name === value) {
              conflict = true;
            }
          });
          return conflict;
        }
        
    		var validator = function(value) {
          ngModel.$setValidity('minlength', !(value.length < 9)); 
          ngModel.$setValidity('maxlength', !(value.length > 9));
          ngModel.$setValidity('conflict', !isConflict(value));
          return value;
  			};
        
        ngModel.$parsers.push(validator);
      }
    };
});

app.directive('userAgeValidation', function(){
    return {
      restrict: 'A', // only activate on element attribute
      require: '?ngModel', // get a hold of NgModelController
      link: function(scope, element, attrs, ngModel) {
        if(!ngModel) return; // do nothing if no ng-model
 
        function AgeValidationSpec(value) {
          var age = value;
          return {
            get isInteger() {
              return /^[0-9]+$/.test(value);
            },
            get isLessThan30() {
              return age <= 30;
            },
            get isOlderThan5() { 
              return age >= 5;
            },
          }
        }
        
      	var validator = function(value) {
          var spec = AgeValidationSpec(value);
          ngModel.$setValidity('integer', spec.isInteger);
          ngModel.$setValidity('too_young', spec.isOlderThan5);
          ngModel.$setValidity('too_old', spec.isLessThan30);
          return value;
  			};
        
        ngModel.$parsers.push(validator);
      }
    };
});

var mock = angular.module('plunker.mock', []);

mock.factory('User', function() {
  var users = [];
  users.push({
    name: 'DEFAULT',
    age: '999',
    color: 'blue'
  })

  return {
    query: function() {
      return users;
    },
    save: function(user) {
      users.push(angular.copy(user));
    }
  }
});

mock.factory('Color', function() {
  return {
    query: function() {
      return ['red', 'blue', 'green'];
    }
  }
});
<h1>lol</h1>
<span class='help-inline' ng-transclude ng-show='inputKey'></span>
<div ng-transclude class='control-group' ng-class=' { error: controlGroup.$invalid && controlGroup.$dirty, success: controlGroup.$valid && controlGroup.$dirty }'></div>