Added validation to form: required, minlength, maxlength
diff --git a/views/ngXosLib/xosHelpers/spec/ui/form.test.js b/views/ngXosLib/xosHelpers/spec/ui/form.test.js
index 32e91be..567f145 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/form.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/form.test.js
@@ -102,6 +102,7 @@
       describe('the _getFieldFormat method', () => {
         it('should return string', () => {
           expect(service._getFieldFormat('string')).toEqual('string');
+          expect(service._getFieldFormat(null)).toEqual('string');
         });
         it('should return mail', () => {
           expect(service._getFieldFormat('test@onlab.us')).toEqual('email');
@@ -166,6 +167,7 @@
 
           scope.config = {
             exclude: ['excludedField'],
+            formName: 'testForm',
             actions: [
               {
                 label: 'Save',
@@ -261,6 +263,42 @@
             expect(isolatedScope.ngModel.enabled).toEqual(true);
           });
         });
+
+        describe('the custom validation options', () => {
+          beforeEach(() => {
+            scope.config.fields.first_name.validators = {
+              minlength: 10,
+              maxlength: 15,
+              required: true
+            };
+
+            scope.$digest();
+          });
+
+          it('should validate required', () => {
+            scope.model.first_name = null;
+            scope.$digest();
+
+            expect(isolatedScope.testForm.first_name.$valid).toBeFalsy();
+            expect(isolatedScope.testForm.first_name.$error.required).toBeTruthy();
+          });
+
+          it('should validate minlength', () => {
+            scope.model.first_name = 'short';
+            scope.$digest();
+
+            expect(isolatedScope.testForm.first_name.$valid).toBeFalsy();
+            expect(isolatedScope.testForm.first_name.$error.minlength).toBeTruthy();
+          });
+
+          it('should validate maxlength', () => {
+            scope.model.first_name = 'this is way too long!';
+            scope.$digest();
+
+            expect(isolatedScope.testForm.first_name.$valid).toBeFalsy();
+            expect(isolatedScope.testForm.first_name.$error.maxlength).toBeTruthy();
+          });
+        });
       });
     });
   });
diff --git a/views/ngXosLib/xosHelpers/spec/ui/validation.test.js b/views/ngXosLib/xosHelpers/spec/ui/validation.test.js
index eb92a05..782f03f 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/validation.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/validation.test.js
@@ -32,6 +32,10 @@
 
       let availableErrors = [
         {
+          type: 'required',
+          message: 'Field required'
+        },
+        {
           type: 'email',
           message: 'This is not a valid email'
         },
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form.component.js
index 3a80fc1..715ead3 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form.component.js
@@ -85,7 +85,15 @@
         <ng-form name="vm.{{vm.config.formName || 'form'}}">
           <div class="form-group" ng-repeat="(name, field) in vm.formField">
             <label>{{field.label}}</label>
-            <input ng-if="field.type !== 'boolean'" type="{{field.type}}" name="{{name}}" class="form-control" ng-model="vm.ngModel[name]"/>
+            <input
+              ng-if="field.type !== 'boolean'"
+              type="{{field.type}}"
+              name="{{name}}"
+              class="form-control"
+              ng-model="vm.ngModel[name]"
+              ng-minlength="field.validators.minlength || 0"
+              ng-maxlength="field.validators.maxlength || 2000"
+              ng-required="field.validators.required || false"/>
             <span class="boolean-field" ng-if="field.type === 'boolean'">
               <button
                 class="btn btn-success"
@@ -100,6 +108,7 @@
                 <i class="glyphicon glyphicon-remove"></i>
               </button>
             </span>
+            <!-- <pre>{{vm[vm.config.formName][name].$error | json}}</pre> -->
             <xos-validation errors="vm[vm.config.formName || 'form'][name].$error"></xos-validation>
           </div>
           <div class="form-group" ng-if="vm.config.actions">
@@ -138,7 +147,7 @@
             return;
           }
           this.formField = XosFormHelpers.buildFormStructure(XosFormHelpers.parseModelField(_.difference(Object.keys(model), this.excludedField)), this.config.fields, model);
-        });
+        }, true);
 
       }
     }
@@ -164,7 +173,7 @@
       }
 
       // check if a string is a number
-      if(!isNaN(value)){
+      if(!isNaN(value) && value !== null){
         return 'number';
       }
 
@@ -173,6 +182,11 @@
         return 'email';
       }
 
+      // if null return string
+      if(value === null){
+        return 'string';
+      }
+
       return typeof value;
     };
 
@@ -181,10 +195,11 @@
       customField = customField || {};
 
       return _.reduce(Object.keys(modelField), (form, f) => {
+
         form[f] = {
           label: (customField[f] && customField[f].label) ? `${customField[f].label}:` : LabelFormatter.format(f),
           type: (customField[f] && customField[f].type) ? customField[f].type : this._getFieldFormat(model[f]),
-          validators: {}
+          validators: (customField[f] && customField[f].validators) ? customField[f].validators : {}
         };
 
         if(form[f].type === 'date'){
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation.component.js
index c5ea993..5cb7d08 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation.component.js
@@ -56,6 +56,9 @@
       template: `
         <div>
           <!-- <pre>{{vm.errors.email | json}}</pre> -->
+          <xos-alert config="vm.config" show="vm.errors.required !== undefined && vm.errors.required !== false">
+            Field required
+          </xos-alert>
           <xos-alert config="vm.config" show="vm.errors.email !== undefined && vm.errors.email !== false">
             This is not a valid email
           </xos-alert>
diff --git a/xos/core/xoslib/static/js/vendor/ngXosHelpers.js b/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
index 63eab05..60dd85d 100644
--- a/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
+++ b/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
@@ -78,7 +78,7 @@
       scope: {
         errors: '='
       },
-      template: '\n        <div>\n          <!-- <pre>{{vm.errors.email | json}}</pre> -->\n          <xos-alert config="vm.config" show="vm.errors.email !== undefined && vm.errors.email !== false">\n            This is not a valid email\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.minlength !== undefined && vm.errors.minlength !== false">\n            Too short\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.maxlength !== undefined && vm.errors.maxlength !== false">\n            Too long\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.custom !== undefined && vm.errors.custom !== false">\n            Field invalid\n          </xos-alert>\n        </div>\n      ',
+      template: '\n        <div>\n          <!-- <pre>{{vm.errors.email | json}}</pre> -->\n          <xos-alert config="vm.config" show="vm.errors.required !== undefined && vm.errors.required !== false">\n            Field required\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.email !== undefined && vm.errors.email !== false">\n            This is not a valid email\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.minlength !== undefined && vm.errors.minlength !== false">\n            Too short\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.maxlength !== undefined && vm.errors.maxlength !== false">\n            Too long\n          </xos-alert>\n          <xos-alert config="vm.config" show="vm.errors.custom !== undefined && vm.errors.custom !== false">\n            Field invalid\n          </xos-alert>\n        </div>\n      ',
       transclude: true,
       bindToController: true,
       controllerAs: 'vm',
@@ -497,7 +497,7 @@
         config: '=',
         ngModel: '='
       },
-      template: '\n        <ng-form name="vm.{{vm.config.formName || \'form\'}}">\n          <div class="form-group" ng-repeat="(name, field) in vm.formField">\n            <label>{{field.label}}</label>\n            <input ng-if="field.type !== \'boolean\'" type="{{field.type}}" name="{{name}}" class="form-control" ng-model="vm.ngModel[name]"/>\n            <span class="boolean-field" ng-if="field.type === \'boolean\'">\n              <button\n                class="btn btn-success"\n                ng-show="vm.ngModel[name]"\n                ng-click="vm.ngModel[name] = false">\n                <i class="glyphicon glyphicon-ok"></i>\n              </button>\n              <button\n                class="btn btn-danger"\n                ng-show="!vm.ngModel[name]"\n                ng-click="vm.ngModel[name] = true">\n                <i class="glyphicon glyphicon-remove"></i>\n              </button>\n            </span>\n            <xos-validation errors="vm[vm.config.formName || \'form\'][name].$error"></xos-validation>\n          </div>\n          <div class="form-group" ng-if="vm.config.actions">\n            <button role="button" href=""\n              ng-repeat="action in vm.config.actions"\n              ng-click="action.cb(vm.ngModel)"\n              class="btn btn-{{action.class}}"\n              title="{{action.label}}">\n              <i class="glyphicon glyphicon-{{action.icon}}"></i>\n              {{action.label}}\n            </button>\n          </div>\n        </ng-form>\n      ',
+      template: '\n        <ng-form name="vm.{{vm.config.formName || \'form\'}}">\n          <div class="form-group" ng-repeat="(name, field) in vm.formField">\n            <label>{{field.label}}</label>\n            <input\n              ng-if="field.type !== \'boolean\'"\n              type="{{field.type}}"\n              name="{{name}}"\n              class="form-control"\n              ng-model="vm.ngModel[name]"\n              ng-minlength="field.validators.minlength || 0"\n              ng-maxlength="field.validators.maxlength || 2000"\n              ng-required="field.validators.required || false"/>\n            <span class="boolean-field" ng-if="field.type === \'boolean\'">\n              <button\n                class="btn btn-success"\n                ng-show="vm.ngModel[name]"\n                ng-click="vm.ngModel[name] = false">\n                <i class="glyphicon glyphicon-ok"></i>\n              </button>\n              <button\n                class="btn btn-danger"\n                ng-show="!vm.ngModel[name]"\n                ng-click="vm.ngModel[name] = true">\n                <i class="glyphicon glyphicon-remove"></i>\n              </button>\n            </span>\n            <!-- <pre>{{vm[vm.config.formName][name].$error | json}}</pre> -->\n            <xos-validation errors="vm[vm.config.formName || \'form\'][name].$error"></xos-validation>\n          </div>\n          <div class="form-group" ng-if="vm.config.actions">\n            <button role="button" href=""\n              ng-repeat="action in vm.config.actions"\n              ng-click="action.cb(vm.ngModel)"\n              class="btn btn-{{action.class}}"\n              title="{{action.label}}">\n              <i class="glyphicon glyphicon-{{action.icon}}"></i>\n              {{action.label}}\n            </button>\n          </div>\n        </ng-form>\n      ',
       bindToController: true,
       controllerAs: 'vm',
       controller: ["$scope", "$log", "_", "XosFormHelpers", function controller($scope, $log, _, XosFormHelpers) {
@@ -524,7 +524,7 @@
             return;
           }
           _this.formField = XosFormHelpers.buildFormStructure(XosFormHelpers.parseModelField(_.difference(Object.keys(model), _this.excludedField)), _this.config.fields, model);
-        });
+        }, true);
       }]
     };
   }).service('XosFormHelpers', ["_", "LabelFormatter", function (_, LabelFormatter) {
@@ -549,7 +549,7 @@
       }
 
       // check if a string is a number
-      if (!isNaN(value)) {
+      if (!isNaN(value) && value !== null) {
         return 'number';
       }
 
@@ -558,6 +558,11 @@
         return 'email';
       }
 
+      // if null return string
+      if (value === null) {
+        return 'string';
+      }
+
       return typeof value === 'undefined' ? 'undefined' : _typeof(value);
     };
 
@@ -566,10 +571,11 @@
       customField = customField || {};
 
       return _.reduce(Object.keys(modelField), function (form, f) {
+
         form[f] = {
           label: customField[f] && customField[f].label ? customField[f].label + ':' : LabelFormatter.format(f),
           type: customField[f] && customField[f].type ? customField[f].type : _this2._getFieldFormat(model[f]),
-          validators: {}
+          validators: customField[f] && customField[f].validators ? customField[f].validators : {}
         };
 
         if (form[f].type === 'date') {