Full Code of netceteragroup/valdr for AI

master 2a6174f18402 cached
74 files
169.6 KB
43.5k tokens
11 symbols
1 requests
Download .txt
Repository: netceteragroup/valdr
Branch: master
Commit: 2a6174f18402
Files: 74
Total size: 169.6 KB

Directory structure:
gitextract_m3qzmqq7/

├── .bowerrc
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── demo/
│   ├── core/
│   │   ├── custom-validator.html
│   │   ├── list.html
│   │   ├── load-constraints.html
│   │   └── simple.html
│   ├── demo-api.js
│   ├── demoConstraints.json
│   ├── index.html
│   └── message/
│       ├── angular-translate.html
│       ├── angular-validation.html
│       ├── message-template.tpl.html
│       └── simple.html
├── js.prefix
├── js.suffix
├── karma.conf.js
├── package.json
├── server.js
└── src/
    ├── core/
    │   ├── valdr-service.js
    │   ├── valdr-service.spec.js
    │   ├── valdrEnabled-directive.js
    │   ├── valdrEnabled-directive.spec.js
    │   ├── valdrFormGroup-directive.js
    │   ├── valdrFormGroup-directive.spec.js
    │   ├── valdrFormItem-directive.js
    │   ├── valdrFormItem-directive.spec.js
    │   ├── valdrType-directive.js
    │   ├── valdrType-directive.spec.js
    │   ├── valdrUtil-service.js
    │   ├── valdrUtil-service.spec.js
    │   └── validators/
    │       ├── digitsValidator.js
    │       ├── digitsValidator.spec.js
    │       ├── emailValidator.js
    │       ├── emailValidator.spec.js
    │       ├── futureAndPastSharedValidator.js
    │       ├── futureValidator.js
    │       ├── futureValidator.spec.js
    │       ├── hibernateEmailValidator.js
    │       ├── hibernateEmailValidator.spec.js
    │       ├── hibernateUrlValidator.js
    │       ├── hibernateUrlValidator.spec.js
    │       ├── maxLengthValidator.js
    │       ├── maxLengthValidator.spec.js
    │       ├── maxValidator.js
    │       ├── maxValidator.spec.js
    │       ├── minLengthValidator.js
    │       ├── minLengthValidator.spec.js
    │       ├── minValidator.js
    │       ├── minValidator.spec.js
    │       ├── pastValidator.js
    │       ├── pastValidator.spec.js
    │       ├── patternValidator.js
    │       ├── patternValidator.spec.js
    │       ├── requiredValidator.js
    │       ├── requiredValidator.spec.js
    │       ├── sizeValidator.js
    │       ├── sizeValidator.spec.js
    │       ├── urlValidator.js
    │       └── urlValidator.spec.js
    ├── message/
    │   ├── valdrMessage-directive.js
    │   ├── valdrMessage-directive.spec.js
    │   ├── valdrMessage-service.js
    │   └── valdrMessage-service.spec.js
    ├── valdr.js
    └── valdr.spec.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .bowerrc
================================================
{
  "directory": "bower_components"
}

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
bower_components
dist
coverage
demo/js
npm-debug.log
*.iml
.idea/


================================================
FILE: .jshintrc
================================================
{
  "predef": [
    "describe",
    "it",
    "xit",
    "expect",
    "angular",
    "beforeEach",
    "spyOn",
    "inject",
    "module",
    "xdescribe",
    "ddescribe",
    "moment",
    "jasmine"
  ],
  "esnext": false,
  "bitwise": true,
  "curly": true,
  "eqeqeq": true,
  "immed": true,
  "indent": 2,
  "latedef": false,
  "newcap": true,
  "noarg": true,
  "quotmark": "single",
  "undef": true,
  "unused": true,
  "strict": false,
  "trailing": true,
  "smarttabs": true,
  "browser": true,
  "node": true
}


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - "0.10"

before_install:
  - npm install -g grunt-cli karma-cli bower

before_script:
  - export DISPLAY=:99.0
  - sh -e /etc/init.d/xvfb start

script: grunt

================================================
FILE: CHANGELOG.md
================================================
# Changelog
## 1.1.6 - 2016-09-14
- Fix: min length validator and size validator validate only length [#107](https://github.com/netceteragroup/valdr/pull/107)
- Fix: Fixed memory leak in valdrMessage-directive [#105](https://github.com/netceteragroup/valdr/pull/105)
- Fix: Unable to validate multiselect dropdowns [#101](https://github.com/netceteragroup/valdr/issues/101)

## 1.1.5 - 2015-09-22
- Don't throw exception if an input has no name and valdr is disabled [#92](https://github.com/netceteragroup/valdr/pull/92)

## 1.1.4 - 2015-08-11
- Fix: valdrMessage does not add messages of angular validation directives [#90](https://github.com/netceteragroup/valdr/issues/90)

## 1.1.3 - 2015-06-22
- Support to validate non-input/-select/-textarea widgets [#83](https://github.com/netceteragroup/valdr/issues/83) [documentation](https://github.com/netceteragroup/valdr#applying-validation-to-custom-input-widgets)

## 1.1.2 - 2015-05-03
- Support dynamically added and removed form items, see [#62](https://github.com/netceteragroup/valdr/issues/62)
- Support showing validation messages for AngularJS built-in validators, see [#58](https://github.com/netceteragroup/valdr/issues/58)

## 1.1.1 - 2015-01-04
- introduce ```valdrEnabled``` directive to conditionally enable/disable validation, see [#54](https://github.com/netceteragroup/valdr/issues/54)
- fix bug that after removing constraints from valdr with valdr.removeConstraints(), the validity state of previously validated form items was not reset. see [#55](https://github.com/netceteragroup/valdr/issues/55)

## 1.1.0 - 2014-12-09
- added new valdrFormGroup directive which sets validity state for a group of form items and is responsible for adding and removing validation messages if valdr-message is loaded, see [#11](https://github.com/netceteragroup/valdr/issues/11), fixes [#44](https://github.com/netceteragroup/valdr/issues/44), fixes [#48](https://github.com/netceteragroup/valdr/issues/48)
- support multiple aliases for constraint names, see [#30](https://github.com/netceteragroup/valdr/issues/30)
- use the latest regular expression to validate e-mail addresses used in AngularJS, see [#33](https://github.com/netceteragroup/valdr/issues/33)
- use new $validators pipeline from AngularJS 1.3 instead of $parsers and $formatters for validation, see [#35](https://github.com/netceteragroup/valdr/issues/35)
- renamed no-valdr-message to valdr-no-message, see [#42](https://github.com/netceteragroup/valdr/issues/42)
- use ng-show in the default message template instead of ng-if, fixes [#49](https://github.com/netceteragroup/valdr/issues/49)

**BREAKING CHANGES**
- valdr now requires AngularJS 1.3.x.
- Before 1.1.0 a class named ```form-group``` was used to group multiple form items and add overall validity state. In 1.1.0
the new directive ```valdr-form-group``` was introduced for this purpose. All valdr validated form fields register with
the next parent element with the ```valdr-form-group```directive (if present). The directive sets the form groups validity
(```ng-valid```, ```ng-invalid```) and the class ```valdr-invalid-dirty-touched-group``` if one of the form items is
invalid, has been changed and the user blurred out of the form item. Besides that, if ```valdr-messages```
is used to add validation messages, the ```valdr-form-group``` directive is the element in the DOM which adds and
removes validation messages for all form items in the group.
- the attribute ```no-valdr-message``` was renamed to ```valdr-no-message``` to disable message adding for individual
form items

## 1.0.2 - 2014-11-18
- added new option ```valdr-no-validate``` to disable valdr validation on specific form elements, see [#41](https://github.com/netceteragroup/valdr/pull/41)
- validity is now set for each valdr validator on ngModel controller, see [#37](https://github.com/netceteragroup/valdr/issues/37)
- NOTE: this will be the last valdr version with support for AngularJS versions below 1.3

## 1.0.1 - 2014-07-16
- Revalidate field with $modelValue instead of $viewValue on constraint change event, fixes [#34](https://github.com/netceteragroup/valdr/pull/34)

## 1.0.0 - 2014-06-27
- added support for isolated scopes around valdr-message directive, closes [#32](https://github.com/netceteragroup/valdr/issues/32)

## 0.2.0 - 2014-05-03
- add hibernateEmail validator, closes [#26](https://github.com/netceteragroup/valdr/issues/26)
- add hibernateUrl validator, closes [#27](https://github.com/netceteragroup/valdr/issues/27)
- add support for <textarea> elements, closes [#22](https://github.com/netceteragroup/valdr/issues/22)
- support form groups with multiple levels, closes [#29](https://github.com/netceteragroup/valdr/issues/29)
- add min/max validators for numbers
- prefixed all internal validator services with valdr to avoid name collisions

## 0.1.1 - 2014-04-17
- add support for <select> elements, closes [#20](https://github.com/netceteragroup/valdr/issues/20)


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guide


## Commit messages

Follow the [AngularJS conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit).


================================================
FILE: Gruntfile.js
================================================
'use strict';

module.exports = function (grunt) {

  require('load-grunt-tasks')(grunt);

  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    language: grunt.option('lang') || 'en',

    meta: {
      banner: '/**\n * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
      ' * <%= pkg.homepage %>\n' +
      ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>\n' +
      ' * License: <%= pkg.license %>\n */\n'
    },

    build_dir: 'dist',

    files: {

      core: [
        'src/valdr.js',
        'src/core/valdrUtil-service.js',
        'src/core/validators/requiredValidator.js',
        'src/core/validators/minValidator.js',
        'src/core/validators/maxValidator.js',
        'src/core/validators/sizeValidator.js',
        'src/core/validators/emailValidator.js',
        'src/core/validators/urlValidator.js',
        'src/core/validators/digitsValidator.js',
        'src/core/validators/futureAndPastSharedValidator.js',
        'src/core/validators/pastValidator.js',
        'src/core/validators/futureValidator.js',
        'src/core/validators/patternValidator.js',
        'src/core/validators/minLengthValidator.js',
        'src/core/validators/maxLengthValidator.js',
        'src/core/validators/hibernateEmailValidator.js',
        'src/core/valdr-service.js',
        'src/core/valdrFormGroup-directive.js',
        'src/core/valdrType-directive.js',
        'src/core/valdrFormItem-directive.js',
        'src/core/valdrEnabled-directive.js'
      ],

      message: [
        'src/message/valdrMessage-service.js',
        'src/message/valdrMessage-directive.js'
      ],

      test: ['src/**/*.spec.js']
    },

    jshint: {

      options: {
        jshintrc: true
      },

      all: ['Gruntfile.js', '<%= files.core %>', '<%= files.message %>', '<%= files.test %>'],

      test: {
        files: {
          src: ['<%= files.test %>']
        }
      }
    },

    concat: {

      banner: {
        options: {
          banner: '<%= meta.banner %>'
        },
        src: '<%= concat.core.dest %>',
        dest: '<%= concat.core.dest %>'
      },

      core: {
        src: ['js.prefix', '<%= files.core %>', 'js.suffix'],
        dest: '<%= build_dir %>/valdr.js'
      },

      message: {
        src: ['js.prefix', '<%= files.message %>', 'js.suffix'],
        dest: '<%= build_dir %>/valdr-message.js'
      }

    },

    uglify: {
      core: {
        files: {
          '<%= build_dir %>/valdr.min.js': '<%= concat.core.dest %>'
        }
      },
      message: {
        files: {
          '<%= build_dir %>/valdr-message.min.js': '<%= concat.message.dest %>'
        }
      }
    },

    karma: {
      'unit': {
        configFile: 'karma.conf.js',
        singleRun: true,
        browsers: ['PhantomJS']
      },

      'chrome-unit': {
        configFile: 'karma.conf.js',
        singleRun: true,
        browsers: ['Chrome']
      },

      'firefox-unit': {
        configFile: 'karma.conf.js',
        singleRun: true,
        browsers: ['Firefox']
      }
    },

    copy: {

      demo: {
        files: [{
          src: '*.js',
          dest: 'demo/js/',
          cwd: 'dist/',
          expand: true
        }]
      }
    },

    watch: {
      livereload: {
        options: {
          livereload: true
        },
        files: ['src/**/*.js'],
        tasks: ['concat:core', 'concat:message', 'concat:banner', 'uglify:core', 'uglify:message', 'copy:demo']
      }
    },

    express: {
      server: {
        options: {
          port: 3005,
          bases: '.',
          server: __dirname + '/server.js'
        }
      }
    }
  });

  grunt.registerTask('test', ['jshint:all', 'karma:unit']);
  grunt.registerTask('default', ['test']);

  // Advanced test tasks
  grunt.registerTask('test-chrome', ['karma:chrome-unit']);
  grunt.registerTask('test-firefox', ['karma:firefox-unit']);
  grunt.registerTask('test-all', ['karma']);

  grunt.registerTask('build', [
    'jshint:all',
    'karma:unit',
    'concat:core',
    'concat:message',
    'concat:banner',
    'uglify:core',
    'uglify:message'
  ]);

  // For development purpose.
  grunt.registerTask('dev', [
    'build',
    'copy:demo',
    'watch:livereload'
  ]);
  grunt.registerTask('server', [
    'express',
    'express-keepalive'
  ]);
};


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014 Netcetera AG

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# valdr
[![Build Status](https://travis-ci.org/netceteragroup/valdr.svg?branch=master)](https://travis-ci.org/netceteragroup/valdr) [![Coverage Status](https://coveralls.io/repos/netceteragroup/valdr/badge.svg?branch=master)](https://coveralls.io/r/netceteragroup/valdr?branch=master) [![Code Climate](https://codeclimate.com/github/netceteragroup/valdr.svg)](https://codeclimate.com/github/netceteragroup/valdr) [![NPM version](https://badge.fury.io/js/valdr.svg)](http://badge.fury.io/js/valdr) [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/netceteragroup/valdr/blob/master/LICENSE) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/netceteragroup/valdr?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

A model centric approach to AngularJS form validation. Learn more on the [valdr website](http://netceteragroup.github.io/valdr/).

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!--**Table of Contents**  *generated with [DocToc](http://doctoc.herokuapp.com/)*-->

  - [Why valdr](#why-valdr)
  - [Install](#install)
  - [Getting started](#getting-started)
  - [Constraints JSON](#constraints-json)
  - [Built-In Validators](#built-in-validators)
    - [size](#size)
    - [minLength / maxLength](#minlength--maxlength)
    - [min / max](#min--max)
    - [required](#required)
    - [pattern](#pattern)
    - [email](#email)
    - [digits](#digits)
    - [url](#url)
    - [future / past](#future--past)
  - [Adding custom validators](#adding-custom-validators)
  - [Applying validation to custom input widgets](#applying-validation-to-custom-input-widgets)
  - [Showing validation messages](#showing-validation-messages-with-valdr-messages)
    - [Message template](#message-template)
    - [CSS to control visibility](#css-to-control-visibility)
    - [Integration of angular-translate](#integration-of-angular-translate)
    - [Show messages for AngularJS built-in validators](#show-messages-for-angularjs-built-in-validators)
  - [Conditionally enable/disable validation](#conditionally-enabledisable-validation)
  - [Wire up your back-end](#wire-up-your-back-end)
    - [Using JSR303 Bean Validation with Java back-ends](#using-jsr303-bean-validation-with-java-back-ends)
    - [Using DataAnnotation with C# back-ends](#using-dataannotation-with-c-back-ends)
  - [Develop](#develop)
  - [Support](#support)
  - [License](#license)
  - [Credits](#credits)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Why valdr
Defining the validation rules on every input field with the default AngularJS validators pollutes the markup with your
business rules and makes them pretty hard to maintain. This is especially true for larger applications, where you may
have recurring models like persons or addresses in different forms, but require slightly different markup and therefore
can't just reuse an existing form via ng-include or a form directive. What's even worse: Usually you already have
the validation logic in your back-end, but there is no easy way to share this information with the UI.

valdr solves these issues by extracting the validation constraints from the markup to a JSON structure. You define
the constraints per type and field, tell valdr that a form (or just a part of the form) belongs to a certain type and
valdr automatically validates the form fields.

valdr gives you the following advantages:
- cleaner form markup
- reusable validation constraints for your models -> easier to maintain
- possibility to generate the constraints from the models you already have in your back-end (see [valdr-bean-validation](https://github.com/netceteragroup/valdr-bean-validation) for Java)
- an easy way to display validation messages per form field
- seamless integration with [angular-translate](https://github.com/angular-translate/angular-translate) for i18n of the validation messages

## Install

#### [Bower](http://bower.io)

```bash
bower install --save valdr
```

#### [NPM](http://www.npmjs.com)

```bash
npm install valdr
```

## Getting started

1) Add valdr.js to your index.html

2) Add it as a dependency to your apps module:

```javascript
angular.module('yourApp', ['valdr']);
```

3) Define the constraints:
```javascript
yourApp.config(function(valdrProvider) {
  valdrProvider.addConstraints({
    'Person': {
      'lastName': {
        'size': {
          'min': 2,
          'max': 10,
          'message': 'Last name must be between 2 and 10 characters.'
        },
        'required': {
          'message': 'Last name is required.'
        }
      },
      'firstName': {
        'size': {
          'min': 2,
          'max': 20,
          'message': 'First name must be between 2 and 20 characters.'
        }
      }
    }
});
```

4) Add the ```valdr-type``` directive in your form on any parent element of the input fields that you want to validate.
The **important** thing is, that the attribute ```name``` on the input field matches the field name in the constraints JSON.

```html
<form name="yourForm" novalidate valdr-type="Person">
    <label for="lastName">Last Name</label>
    <input type="text"
           id="lastName"
           name="lastName"
           ng-model="person.lastName">

    <label for="firstName">First Name</label>
    <input type="text"
           id="firstName"
           name="firstName"
           ng-model="person.firstName">
</form>
```
That's it. valdr will now automatically validate the fields with the defined constraints and set the ```$validity``` of these form items.
All violated constraints will be added to the ```valdrViolations``` array on those form items.

## Constraints JSON
The JSON object to define the validation rules has the following structure:

```javascript
  TypeName {
    FieldName {
      ValidatorName {
        <ValidatorArguments>
        message: 'error message'
      }
    }
  }
```
- **TypeName** The type of object (string)
- **FieldName** The name of the field to validate (string)
- **ValidatorName** The name of the validator (string) see below in the Built-In Validators section for the default validators
- **ValidatorArguments** arguments which are passed to the validator, see below for the optional and required arguments for the built-in validators
- **Message** the message which should be shown if the validation fails (can also be a message key if angular-translate is used)

Example:
```javascript
  "Person": {
    "firstName": {
      "size": {
        "min": 2,
        "max": 20,
        "message": "First name must be between 2 and 20 characters."
      }
    }
  },
  "Address": {
    "email": {
      "email": {
        "message": "Must be a valid E-Mail address."
      }
    },
    "zipCode": {
      "size": {
        "min": "4",
        "max": "6",
        "message": "Zip code must be between 4 and 6 characters."
      }
    }
  }
```

## Built-In Validators

### size
Checks the minimal and maximal length of the value.

Arguments:
- **min** The minimal string length (number, optional, default 0)
- **max** The maximal string length (number, optional)

### minLength / maxLength
Checks that the value is a string and not shorter / longer than the specified number of characters.

Arguments:
- **number** The minimal / maximal string length (number, required)

### min / max
Checks that the value is a number above/below or equal to the specified number.

Arguments:
- **value** The minimal / maximal value of the number (number, required)

### required
Marks the field as required.

### pattern
Validates the input using the specified regular expression.

Arguments:
- **value** The regular expression (string/RegExp, required)

### email
Checks that the field contains a valid e-mail address. It uses the same regular expression as AngularJS is using for e-mail validation.

### digits
Checks that the value is a number with the specified number of integral/fractional digits.

Arguments:
- **integer** The integral part of the number (number, required)
- **fraction** The fractional part of the number (number, required)

### url
Checks that the value is a valid URL. It uses the same regular expression as AngularJS for the URL validation.

### future / past
Check that the value is a date in the future/past. *NOTE* These validators require that [Moment.js](http://momentjs.com) is loaded.

## Adding custom validators
Implementing custom validation logic is easy, all you have to do is implement a validation service/factory and register it in the valdrProvider.

1) Custom validator:
```javascript
yourApp.factory('customValidator', function () {
  return {
    name: 'customValidator', // this is the validator name that can be referenced from the constraints JSON
    validate: function (value, arguments) {
      // value: the value to validate
      // arguments: the validator arguments
      return value === arguments.onlyValidValue;
    }
  };
});
```
2) Register it:
```javascript
yourApp.config(function (valdrProvider) {
  valdrProvider.addValidator('customValidator');
}
```

3) Use it in constraints JSON:
```javascript
yourApp.config(function (valdrProvider) {
  valdrProvider.addConstraints({
    'Person': {
      'firstName': {
        'customValidator': {
          'onlyValidValue': 'Tom',
          'message': 'First name has to be Tom!'
        }
      }
    }
  });
}
```

## Applying validation to custom input widgets
valdr applies validation to ```<input>```, ```<textarea>``` and ```<select>``` HTML elements automatically if those
elements are within a ```valdr-type``` block and there is ```ng-model``` bound to them. If you implemented your own
input widget or used one provided by a library (e.g. ui-select), you can still benefit from valdr validation applied to
that input widget. All you need is to decorate it with the ```enableValdrValidation``` directive. In addition to this,
if you would like to make use of [validation messages](#showing-validation-messages-with-valdr-messages), add the
```enableValdrMessage``` directive to the input widget:
```html
<form name="yourForm" novalidate valdr-type="Person">
    <div valdr-form-group>
        <label for="bestFriend">Best Friend</label>
        <my-select
            id="bestFriend"
            name="bestFriend"
            ng-model="person.bestFriend"
            enable-valdr-validation
            enable-valdr-message>
           <!-- other my-select elements -->
        </my-select>
    </div>
</form>
```

## Showing validation messages with ```valdr-messages```
valdr sets the AngularJS validation states like ```$valid```, ```$invalid``` and ```$error``` on all validated form
elements and forms by default. Additional information like the violated constraints and the messages configured in the
constraints JSON are always set as ```valdrViolations``` array on the individual form items.
With this information, you can either write your own logic to display the validation messages, or use valdr-messages to
automatically show the messages next to the invalid form items.

To enable this behaviour, include ```valdr-message.js``` in your page (which is included in the bower package) and make
use of the ```valdr-form-group``` directive. This directive marks the element, where the valdr messages will be
appended. The ```valdr-form-group``` can wrap multiple valdr validated form items. Each form item has to have at least
one surrounding ```valdr-form-group``` to automatically show validation messages.

### Message template
The default message template to be used by valdr-messages can be overridden by configuring the ```valdrMessageProvider```:

```javascript
valdrMessageProvider.setTemplate('<div class="valdr-message">{{ violation.message }}</div>');

// or

valdrMessageProvider.setTemplateUrl('/partials/valdrMesssageTemplate.html');
```

The following variables will be available on the scope of the message template:
- ```violations``` - an array of all violations for the current form field
- ```violation``` - the first violation, if multiple constraints are violated it will be the one that is first declared in the JSON structure
- ```formField``` - the invalid form field

### CSS to control visibility
valdr sets some CSS classes on elements with the ```valdr-form-group``` directive and on the message elements which are
automatically added by ```valdr-messages```. These classes allow you to control the visibility of the validation
messages.

To change the CSS class names used by valdr, you can inject ```valdrClasses```and override the following values:

```javascript
{
  // added on all elements with valdr-form-group directive
  formGroup: 'form-group',
  // added on valdr-form-group and on valdr-messages if all of the form items are valid
  valid: 'ng-valid',
  // added on valdr-form-group and on valdr-messages if one of the form items is invalid
  invalid: 'ng-invalid',
  // added on valdr-messages if the form item this message is associated with is dirty
  dirty: 'ng-dirty',
  // added on valdr-messages if the form item this message is associated with is pristine
  pristine: 'ng-pristine',
  // added on valdr-messages if the form item this message is associated with has been blurred
  touched: 'ng-touched',
  // added on valdr-messages if the form item this message is associated with has not been blurred
  untouched: 'ng-untouched',
  // added on valdr-form-group if one of the contained items is currently invalid, dirty and has been blurred
  invalidDirtyTouchedGroup: 'valdr-invalid-dirty-touched-group'
}
```

Using CSS like the following, the messages can be shown only on inputs which the user changed, blurred and are invalid:

```css
.valdr-message {
  display: none;
}
.valdr-message.ng-invalid.ng-touched.ng-dirty {
  display: inline;
  background: red;
}
```

### Integration of angular-translate
To support i18n of the validation messages, valdr has built-in support for [angular-translate](https://github.com/angular-translate/angular-translate).

Instead of adding the message directly to the constraints, use a message key and add the translations to the ```$translateProvider```.
In the translations, the constraint arguments and the field name can be used with placeholders as in the following
example:

```javascript
valdrProvider.addConstraints({
  'Person': {
    'lastName': {
      'size': {
        'min': 5,
        'max': 20,
        'message': 'message.size'
      }
    }
  }
});

$translateProvider.translations('en', {
  'message.size': '{{fieldName}} must be between {{min}} and {{max}} characters.',
  'Person.lastName': 'Last name'
});

$translateProvider.translations('de', {
  'message.size': 'Zwischen {{min}} und {{max}} Zeichen sind im Feld {{fieldName}} erlaubt.',
  'Person.lastName': 'Nachname'
});
```

#### Custom field name translation keys
By default the translation key for the field name is put together as ```Type.Fieldname```, but
sometimes your translations might be a bit more complicated and you need more freedom when
generating your field names translation key. For example you would like to use an article in
another language to make it sound more natural.

This can be achieved by defining a `valdrFieldNameKeyGenerator` function like this:

```javascript
module.factory('valdrFieldNameSuffix', function() {
  return function(violation) {
    return violation.type + '.' + violation.field + '.' + violation.validator + 'Name';
  }
});

valdrProvider.addConstraints({
  'Person': {
    'lastName': {
      'required': { 'message': 'message.required' }
    }
  }
});

$translateProvider.translations('en', {
  'message.required': 'Please enter {{fieldName}}.',
  'Person.lastName.requiredName' : 'the Last name'
  }
});

$translateProvider.translations('de', {
  'message.required': 'Bitte geben sie {{fieldName}} ein.',
  'Person.lastName.requiredName': 'den Nachnamen'
});
```

### Show messages for AngularJS built-in validators
valdr also allows to show messages for the AngularJS built-in validators like ```required``` and ```number```.
To enable this, set ```valdrMessage.angularMessagesEnabled``` to true in the run phase:

```javascript
valdrMessage.angularMessagesEnabled = true;
```

After that, the messages can be registered by validator name in the ```valdrMessageProvider```:

```javascript
valdrMessage.addMessages({
  'required': 'This field is required.',
  'number': 'Not a valid number.'
});
```

To register messages for specific fields, the type and the field name can be prepended:
```javascript
valdrMessage.addMessages({
  'Person.lastName.required': 'This last name is required.',
  'Person.age.number': 'The age has to be a number.'
});
```

When ```angular-translate``` is loaded it is also possible to register message keys instead of the messages.

A complete example of mixed valdr and AngularJS validation can be found on the [demo page](demo/message/angular-validation.html).

## Conditionally enable/disable validation
The ```valdrEnabled``` directive allows to dynamically enable and disable the validation with valdr. All form elements
in a child node of an element with the 'valdr-enabled' directive will be affected by this.

Usage:
```html
<div valdr-enabled="isValidationEnabled()">
  <input type="text" name="name" ng-model="mymodel.field">
</div>
```
If multiple valdr-enabled directives are nested, the one nearest to the validated form element will take precedence.

## Wire up your back-end

To load the validation constraints from your back-end, you can configure the ```valdrProvider``` in a ```config```
function like this:

```javascript
yourApp.config(function(valdrProvider) {
  valdrProvider.setConstraintUrl('/api/constraints');
});
```

### Using JSR303 Bean Validation with Java back-ends

If you have a Java back-end and use Bean Validation (JSR 303) annotations, check out the [valdr-bean-validation](https://github.com/netceteragroup/valdr-bean-validation)
project. It parses Java model classes for Bean Validation constraints and extracts their information into a JSON document
to be used by valdr. This allows to apply the exact same validation rules on the server and on the AngularJS client.

### Using DataAnnotation with C# back-ends

If you have a c# back-end and use DataAnnotation, check out the [valdr-dotnet](https://github.com/netceteragroup/valdr-dotnet)
project. It parses C# classes for DataAnnotation attributes and extracts their information into into a JSON document
to be used by valdr. This allows to apply the exact same validation rules on the server and on the AngularJS client.

## Develop

Clone and install dependencies:

```bash
git clone https://github.com/netceteragroup/valdr.git
cd valdr
npm install
```

Start live-reload
```bash
grunt dev
```

Then start the dev server
```bash
grunt server
```

Open [http://localhost:3005/demo](http://localhost:3005/demo) in your browser.

## Support

[Ask a question on Stack Overflow](http://stackoverflow.com/questions/ask?tags=valdr) and tag it with [`valdr`](http://stackoverflow.com/tags/valdr).

## License

[MIT](http://opensource.org/licenses/MIT) © Netcetera AG

## Credits
Thanks a lot to [all contributors](https://github.com/netceteragroup/valdr/graphs/contributors) and the guys who brainstormed the name for this project over a dinner on [mount Rigi](https://maps.google.com/maps?q=Hotel+Rigi+Kaltbad&hl=en&cid=7481422441262508040&gl=US&hq=Hotel+Rigi+Kaltbad&t=m&z=16) with us.
* [Björn Mosler](https://github.com/bjorm)
* [Roland Weiss](https://github.com/rolego), father of 'valdr'
* [Jason Brazile](https://github.com/jbrazile)


================================================
FILE: bower.json
================================================
{
  "author": "Netcetera AG",
  "name": "valdr",
  "description": "A model centric approach to AngularJS form validation",
  "homepage": "https://github.com/netceteragroup/valdr",
  "repository": {
    "type": "git",
    "url": "git://github.com/netceteragroup/valdr"
  },
  "devDependencies": {
    "angular": "1.3.3",
    "angular-mocks": "1.3.3",
    "angular-translate": "2.4.2",
    "jasmine": "1.3.0",
    "momentjs": "2.8.3"
  }
}


================================================
FILE: demo/core/custom-validator.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Demo</title>
</head>
<body ng-app="demoApp">

<div ng-controller="TestController">

  <h1>Custom Validator Demo</h1>

  <form name="demoForm" novalidate valdr-type="Person">
      <div valdr-form-group>
        <label for="lastName">Last Name</label>
        <input type="text"
               id="lastName"
               name="lastName"
               ng-model="person.lastName">
        <span>$valid {{ demoForm.lastName.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="firstName">First Name</label>
        <input type="text"
               id="firstName"
               name="firstName"
               ng-model="person.firstName">
        <span>$valid {{ demoForm.firstName.$valid }}</span>
      </div>

  </form>

  <pre>demoForm.$valid: {{ demoForm.$valid }}</pre>

  <h3>demoForm</h3>
  <pre>{{ demoForm | json }}</pre>

  <h3>constraints</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="../js/valdr.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr']);

  demoApp.factory('customValidator', function () {
    return {
      name: 'customValidator',
      validate: function (value) {
        return value === 'Hanueli';
      }
    };
  });

  demoApp.config(function (valdrProvider) {

    valdrProvider.addValidator('customValidator');

    valdrProvider.addConstraints({
      'Person': {
        'lastName': {
          'required': {
            'message': 'This field is required.'
          }
        },
        'firstName': {
          'customValidator': {
            'message': 'First name must be Hanueli.'
          }
        }
      }
    });

  });

  demoApp.controller('TestController', function ($scope, valdr) {
    $scope.person = {};

    $scope.$watch(valdr.getConstraints, function (newConstraints) {
      $scope.constraints = newConstraints;
    });
  });
</script>
</body>
</html>


================================================
FILE: demo/core/list.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Demo</title>

  <style>
    .demo-is-invalid label {
      color: red;
    }
    .demo-is-valid label {
      color: green;
    }
    .demo-is-invalid input {
      background-color: red;
    }
  </style>
</head>
<body ng-app="demoApp">

<div ng-controller="TestController">

  <h1>List Demo</h1>

  <form name="demoForm" novalidate>

    <div valdr-type="Person">

      <h4>Person</h4>
      <div valdr-form-group>
        <label for="firstName">First Name</label>
        <input type="text"
               id="firstName"
               name="firstName"
               ng-model="person.firstName">
        <span>$valid {{ demoForm.firstName.$valid }}</span>
      </div>
    </div>

    <div ng-form="addressForm" valdr-type="Address" ng-repeat="address in person.addresses">
      <h4>Address {{ $index + 1 }} <button ng-click="removeAddress(address)" >remove</button></h4>
      <div valdr-form-group>
        <label for="zipCode">Zip Code</label>
        <input type="text"
               id="zipCode"
               name="zipCode"
               ng-model="address.zipCode">
        <span>$valid {{ addressForm.zipCode.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="email">E-Mail</label>
        <input type="text"
               id="email"
               name="email"
               ng-model="address.email">
        <span>$valid {{ addressForm.email.$valid }}</span>
      </div>
      <pre>{{ addressForm | json }}</pre>
    </div>

  </form>

  <button ng-click="addAddress()">Add address</button>
  <pre>demoForm.$valid: {{ demoForm.$valid }}</pre>

  <h3>demoForm</h3>
  <pre>{{ demoForm | json }}</pre>

  <h3>constraints</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="../js/valdr.min.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr']);

  demoApp.config(function (valdrProvider) {
    valdrProvider.addConstraints({
      'Person': {
        'firstName': {
          'size': {
            'min': 2,
            'max': 20,
            'message': 'First name must be between 2 and 20 characters.'
          }
        }
      },
      'Address': {
        'email': {
          'email': {
            'message': 'Must be a valid E-Mail address.'
          }
        },
        'zipCode': {
          'size': {
            'min': '4',
            'max': '6',
            'message': 'Zip code must be between 4 and 6 characters.'
          }
        }
      }
    });
  });

  demoApp.controller('TestController', function ($scope, valdr) {
    // use custom classes on the surrounding elements
    valdr.setClasses({
      valid: 'demo-is-valid',
      invalid: 'demo-is-invalid'
    });

    $scope.person = {
      addresses: []
    };

    $scope.constraints = valdr.getConstraints();

    $scope.addAddress = function () {
      $scope.person.addresses.push({});
    };

    $scope.removeAddress = function(address) {
      var index = $scope.person.addresses.indexOf(address);
      $scope.person.addresses.splice(index, 1);
    };
  });
</script>
</body>
</html>


================================================
FILE: demo/core/load-constraints.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Demo</title>
</head>
<body ng-app="demoApp">

<div ng-controller="TestController">

  <h1>Async Demo</h1>

  <form name="demoForm" novalidate>

    <div valdr-type="Person">

      <div valdr-form-group ng-form>
        <label for="lastName">Last Name</label>
        <input type="text"
               id="lastName"
               name="lastName"
               ng-model="person.lastName">
        <span>$valid {{ demoForm.lastName.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="firstName">First Name</label>
        <input type="text"
               id="firstName"
               name="firstName"
               ng-model="person.firstName">
        <span>$valid {{ demoForm.firstName.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="zipCode">Zip Code</label>
        <input type="text"
               id="zipCode"
               name="zipCode"
               ng-model="person.zipCode">
        <span>$valid {{ demoForm.zipCode.$valid }}</span>
      </div>

    </div>

  </form>

  <pre>demoForm.$valid: {{ demoForm.$valid }}</pre>

  <h3>demoForm</h3>
  <pre>{{ demoForm | json }}</pre>

  <h3>constraints</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="../js/valdr.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr']);

  demoApp.config(function(valdrProvider) {
    valdrProvider.setConstraintUrl('/api/constraints');
  });

  demoApp.controller('TestController', function ($scope, valdr) {
    $scope.person = {};

    $scope.$watch(valdr.getConstraints, function (newConstraints) {
      $scope.constraints = newConstraints;
    });
  });
</script>
</body>
</html>


================================================
FILE: demo/core/simple.html
================================================
<!DOCTYPE html>
<html>

<head>
  <title>Demo</title>
</head>

<body ng-app="demoApp">

<div ng-controller="TestController">

  <h1>Simple Demo</h1>

  <form name="demoForm" novalidate valdr-enabled="isValdrEnabled">

    <div valdr-type="Person">

      <h4>Person</h4>
      <div valdr-form-group valdr-enabled="true">
        <label for="lastName">Last Name</label>
        <input type="text"
               id="lastName"
               name="lastName"
               ng-model="person.lastName">
        <span>$valid {{ demoForm.lastName.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="firstName">First Name</label>
        <input type="text"
               id="firstName"
               name="firstName"
               ng-model="person.firstName">
        <span>$valid {{ demoForm.firstName.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="age">Age</label>
        <input type="number"
               id="age"
               name="age"
               ng-model="person.age">
        <span>$valid {{ demoForm.age.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="gender">Gender</label>
        <select id="gender"
               name="gender"
               ng-options="gender as gender.label for gender in genders"
               ng-model="person.gender">
          <option value=""></option>
        </select>
        <span>$valid {{ demoForm.gender.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="bio">Bio</label>
        <textarea
               id="bio"
               name="bio"
               ng-model="person.bio"
               rows="3"></textarea>
        <span>$valid {{ demoForm.bio.$valid }}</span>
      </div>

      <div valdr-form-group>
        <label for="homepage">Homepage</label>
        <input type="text"
               id="homepage"
               name="homepage"
               ng-model="person.homepage">
        <span>$valid {{ demoForm.homepage.$valid }}</span>
      </div>

      <h4>Address</h4>
      <div valdr-type="Address">
        <div valdr-form-group>
          <label for="zipCode">Zip Code</label>
          <input type="text"
                 id="zipCode"
                 name="zipCode"
                 ng-model="person.address.zipCode">
          <span>$valid {{ demoForm.zipCode.$valid }}</span>
        </div>

        <div valdr-form-group>
          <label for="email">E-Mail</label>
          <input type="text"
                 id="email"
                 name="email"
                 ng-model="person.address.email">
          <span>$valid {{ demoForm.email.$valid }}</span>
        </div>
      </div>
    </div>

  </form>

  <h3>Settings</h3>

  <button ng-click="addAddressConstraints()">Add address constraints</button>

  <div>
    <label for="enabled">Enable/Disable valdr (except last name)</label>
    <input id="enabled" type="checkbox" ng-model="isValdrEnabled">
  </div>

  <button ng-click="removeConstraints()">Remove all constraints</button>

  <pre>demoForm.$valid: {{ demoForm.$valid }}</pre>

  <h3>demoForm</h3>
  <pre>{{ demoForm | json }}</pre>

  <h3>constraints</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="../js/valdr.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr']);

  demoApp.config(function(valdrProvider) {
    valdrProvider.addConstraints({
      'Person': {
        'lastName': {
          'size': {
            'min': 2,
            'max': 10,
            'message': 'Last name must be between 2 and 10 characters.'
          },
          'required': {
            'message': 'Last name is required.'
          }
        },
        'firstName': {
          'size': {
            'min': 2,
            'max': 20,
            'message': 'First name must be between 2 and 20 characters.'
          }
        },
        'age': {
          'min': {
            value: '21',
            message: 'Must be 21 years old.'
          },
          'required': {
            message: 'Age is required.'
          }
        },
        'gender': {
          'required': {
            message: 'Gender is required.'
          }
        },
        'bio': {
          'size': {
            max: 20,
            message: 'Bio can not be longer than 20 characters.'
          }
        },
        'homepage': {
          'url': {
            message: 'Must be a valid URL.'
          }
        }
      }
    });
  });

  demoApp.controller('TestController', function ($scope, valdr) {
    $scope.person = {};

    $scope.genders = [
      { label: 'Male', value: 1 },
      { label: 'Female', value: 2 }
    ];
    $scope.constraints = valdr.getConstraints();

    $scope.isValdrEnabled = true;

    $scope.removeConstraints = function () {
      valdr.removeConstraints('Person');
    };

    $scope.addAddressConstraints = function () {

      valdr.addConstraints({
        'Address': {
          'email': {
            'email': {
              'message': 'Must be a valid E-Mail address.'
            }
          },
          'zipCode': {
            'pattern': {
              'value': /^\d{4}$/,
              'message': 'Zip code must be 4 digits.'
            }
          }
        }
      });
      $scope.constraints = valdr.getConstraints();
    };

  });
</script>
</body>
</html>


================================================
FILE: demo/demo-api.js
================================================
module.exports = function (app) {

  var url = require('url'),
      fs = require('fs');

  var getConstraints = function (req, res) {
    function answer(code, data) {
      res.writeHead(code,{
        'Content-Type':'application/json;charset=utf-8',
        'Access-Control-Allow-Origin':'*',
        'Access-Control-Allow-Headers':'X-Requested-With'
      });
      res.end(data);
    }

    fs.readFile('./demo/demoConstraints.json', function(err, data) {
      if (err) answer(404, '');
      else answer(200, data);
    });
  };

  app.get('/api/constraints', getConstraints);

};

================================================
FILE: demo/demoConstraints.json
================================================
{
  "Person": {
    "lastName": {
      "size": {
        "min": 2,
        "max": 10,
        "message": "javax.validation.constraints.Size.message"
      }
    },
    "firstName": {
      "size": {
        "min": 2,
        "max": 20,
        "message": "javax.validation.constraints.Size.message"
      }
    },
    "zipCode": {
      "size": {
        "min": 4,
        "max": 4,
        "message": "javax.validation.constraints.Size.message"
      }
    }
  }
}

================================================
FILE: demo/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>valdr demos</title>
</head>
<body>

<h2>Core</h2>
<ul>
  <li><a href="core/simple.html">Simple</a></li>
  <li><a href="core/list.html">List</a></li>
  <li><a href="core/load-constraints.html">Load constraints</a></li>
  <li><a href="core/custom-validator.html">Custom validator</a></li>
</ul>

<h2>Validation Messages</h2>
<ul>
  <li><a href="message/simple.html">Simple</a></li>
  <li><a href="message/angular-validation.html">Messages for AngularJS validators</a></li>
  <li><a href="message/angular-translate.html">i18n with angular-translate</a></li>
</ul>

</body>
</html>


================================================
FILE: demo/message/angular-translate.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Demo</title>
</head>
<body ng-app="demoApp">

<div ng-controller="TestController">

  <h1>angular-translate Demo</h1>

  <ul>
    <li><a href="" ng-click="changeLanguage('en')">english</a></li>
    <li><a href="" ng-click="changeLanguage('de')">german</a></li>
  </ul>

  <form name="demoForm" novalidate>

    <div valdr-type="Person">

      <h4>Person</h4>
      <div valdr-form-group>
        <label for="lastName">Last Name</label>
        <div class="some-class">
          <input type="text"
                 id="lastName"
                 name="lastName"
                 ng-model="person.lastName">
        </div>

        <pre>{{ demoForm.lastName.valdrViolations | json }}</pre>
      </div>

      <div valdr-form-group>
        <label for="firstName">First Name</label>
        <input type="text"
               id="firstName"
               name="firstName"
               ng-model="person.firstName">
      </div>

    </div>
  </form>

  <h3>constraints</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="/bower_components/angular-translate/angular-translate.js"></script>
<script src="../js/valdr.js"></script>
<script src="../js/valdr-message.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr', 'pascalprecht.translate']);

  demoApp.config(function(valdrProvider, valdrMessageProvider, $translateProvider) {

    valdrProvider.addConstraints({
      'Person': {
        'lastName': {
          'required': {
            'message': 'message.required'
          },
          'size': {
            'min': 5,
            'max': 20,
            'message': 'message.size'
          }
        },
        'firstName': {
          'size': {
            'min': 3,
            'max': 10,
            'message': 'message.size'
          }
        }
      }
    });

    $translateProvider.translations('en', {
      'message.required': '{{fieldName}} is required.',
      'message.size': '{{fieldName}} must be between {{min}} and {{max}} characters.',
      'Person.lastName': 'Last name',
      'Person.firstName': 'First name'
    });

    $translateProvider.translations('de', {
      'message.required': '{{fieldName}} ist ein Pflichtfeld.',
      'message.size': 'Zwischen {{min}} und {{max}} Zeichen sind im Feld {{fieldName}} erlaubt.',
      'Person.lastName': 'Nachname',
      'Person.firstName': 'Vorname'
    });

    $translateProvider.preferredLanguage('en');
  });

  demoApp.controller('TestController', function ($scope, valdr, $translate) {
    $scope.person = {};
    $scope.constraints = valdr.getConstraints();

    $scope.changeLanguage = function (key) {
      $translate.use(key);
    };
  });
</script>
</body>
</html>


================================================
FILE: demo/message/angular-validation.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Demo</title>

  <style>
    .valdr-message {
        display: inline;
        background: red;
    }

    input.ng-invalid {
      background: red;
    }
    .form-group.ng-invalid label {
      color: red;
    }
  </style>
</head>
<body ng-app="demoApp">

<div ng-controller="TestController" ng-class="{ 'form-submitted': formSubmitted }">

  <h1>AngularJS validation messages</h1>

  <p>This example shows that by using valdrMessage it is possible to register validation messages
    for both AngularJS built-in and valdr validators.</p>

  <p> The last name field has AngularJS and valdr validators, while the age field is of type number and therefore
    is automatically validated by the AngularJS number validator.</p>

  <form name="demoForm" novalidate>

    <div valdr-type="Person">

      <h4>Person</h4>
      <div valdr-form-group>
        <label for="lastName">Last Name</label>
        <input type="text"
               id="lastName"
               name="lastName"
               required
               ng-minlength="2"
               ng-model="person.lastName">
      </div>

      <div valdr-form-group>
        <label for="age">Age</label>
        <input type="number"
               id="age"
               name="age"
               ng-model="person.age">
      </div>

    </div>

  </form>

  <pre>demoForm.$valid = {{demoForm.$valid}}</pre>

  <h3>constraints:</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="/bower_components/angular-translate/angular-translate.js"></script>
<script src="../js/valdr.js"></script>
<script src="../js/valdr-message.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr', 'pascalprecht.translate']);

  demoApp.config(function(valdrProvider) {

    valdrProvider.addConstraints({
      'Person': {
        'lastName': {
          'size': {
            'max': 4,
            'message': 'Last name max is 4 characters.'
          }
        }
      }
    });
  });

  demoApp.config(function (valdrMessageProvider, $translateProvider) {
    $translateProvider.translations('en', {
      'message.required': '{{fieldName}} is required.',
      'message.number': '{{fieldName}} must be a number.',
      'lastname.specific': '{{fieldName}} min is 2 characters.',
      'Person.lastName': 'Last name',
      'Person.age': 'Age'
    });

    $translateProvider.preferredLanguage('en');

    valdrMessageProvider.addMessages({
      'required': 'message.required',
      'number': 'message.number',
      'Person.lastName.minlength': 'lastname.specific'
    });

  });

  demoApp.run(function (valdrMessage) {
    valdrMessage.angularMessagesEnabled = true;
  });

  demoApp.controller('TestController', function ($scope, valdr) {

    $scope.person = {};
    $scope.genders = [
      { label: 'Male', value: 1 },
      { label: 'Female', value: 2 }
    ];
    $scope.constraints = valdr.getConstraints();

    $scope.showAllErrors = function () {
        $scope.formSubmitted = true;
    }
  });
</script>
</body>
</html>


================================================
FILE: demo/message/message-template.tpl.html
================================================
<div class="my-violations">
  <p class="some-custom-class" ng-repeat="violation in violations">
    {{ violation.message }} {{ violation | json }}
  </p>
</div>

================================================
FILE: demo/message/simple.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>Demo</title>

  <style>
    .valdr-message {
        display: none;
    }
    .valdr-message.ng-invalid.ng-dirty.ng-touched, .form-submitted .valdr-message.ng-invalid {
        display: inline;
        background: red;
    }
    input.ng-invalid.ng-dirty.ng-touched, .form-submitted input.ng-invalid {
      background: red;
    }
    .valdr-invalid-dirty-touched-group label, .form-submitted .form-group.ng-invalid label {
      color: red;
    }
  </style>
</head>
<body ng-app="demoApp">

<div ng-controller="TestController" ng-class="{ 'form-submitted': formSubmitted }">

  <h1>Message Demo</h1>

  <form name="demoForm2" novalidate>

    <div valdr-type="Person">

      <h4>Person</h4>
      <div valdr-form-group>
        <label for="lastName">Last Name</label>
        <input type="text"
               id="lastName"
               name="lastName"
               ng-model="person.lastName">
      </div>

      <div valdr-form-group>
        <label for="firstName">First Name</label>
        <input type="text"
               id="firstName"
               name="firstName"
               ng-model="person.firstName"
               valdr-no-message>
        <span><em>Error message disabled for this field.</em></span>
      </div>

      <div valdr-form-group>
        <label for="age">Age</label>
        <input type="number"
               id="age"
               name="age"
               ng-model="person.age">
      </div>

      <div valdr-form-group>
        <label for="gender">Gender</label>
        <select id="gender"
                name="gender"
                ng-options="gender as gender.label for gender in genders"
                ng-model="person.gender">
          <option value=""></option>
        </select>
      </div>

      <label for="showZipCity">Show Zip/City </label>
      <input id="showZipCity" name="showZipCity" ng-model="showZipCity" type="checkbox">

      <div valdr-form-group>
        <div ng-if="showZipCity">
            <label for="zip">Zip / City</label>
            <input type="text"
               id="zip"
               name="zip"
               ng-model="person.zip">
            <input type="text"
               id="city"
               name="city"
               ng-model="person.city">
        </div>
      </div>

    </div>

  </form>

  <button ng-click="showAllErrors()">Show all errors</button>

  <h3>constraints</h3>
  <pre>{{ constraints | json }}</pre>

</div>

<script src="/bower_components/angular/angular.js"></script>
<script src="../js/valdr.js"></script>
<script src="../js/valdr-message.js"></script>
<script src="http://localhost:35729/livereload.js"></script>

<script>
  var demoApp = angular.module('demoApp', ['valdr']);

  demoApp.config(function(valdrProvider) {

    valdrProvider.addConstraints({
      'Person': {
        'lastName': {
          'required': {
            'message': 'Last name is required.'
          },
          'size': {
            'min': 2,
            'max': 10,
            'message': 'Last name must be between 2 and 10 characters.'
          }
        },
        'firstName': {
          'size': {
            'min': 2,
            'max': 20,
            'message': 'First name must be between 2 and 20 characters.'
          }
        },
        'age': {
          'min': {
            value: '21',
            message: 'Person must be 21 years old.'
          }
        },
        'gender': {
          'required': {
            'message': 'Gender is required.'
          }
        },
        'zip': {
          'size': {
              'min': 4,
              'max': 6,
              'message': 'ZIP must be between 4 and 6 characters.'
          }
        },
        'city': {
          'size': {
              'min': 1,
              'max': 10,
              'message': 'City must be between 1 and 10 characters.'
          }
        }
      }
    });
  });

  demoApp.controller('TestController', function ($scope, valdr) {
    $scope.person = {};
    $scope.genders = [
      { label: 'Male', value: 1 },
      { label: 'Female', value: 2 }
    ];
    $scope.constraints = valdr.getConstraints();

    $scope.showAllErrors = function () {
        $scope.formSubmitted = true;
    }
  });
</script>
</body>
</html>


================================================
FILE: js.prefix
================================================
(function (window, document) {
'use strict';


================================================
FILE: js.suffix
================================================
})(window, document);

================================================
FILE: karma.conf.js
================================================
module.exports = function (config) {
  config.set({

    basePath: '',

    frameworks: ['jasmine'],

    files: [
      'bower_components/angular/angular.js',
      'bower_components/angular-translate/angular-translate.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'bower_components/momentjs/moment.js',
      'src/valdr.js',
      'src/**/*.js',
      'src/**/*.spec.js'
    ],

    exclude: [],

    reporters: ['progress', 'coverage', 'coveralls'],

    port: 9876,

    colors: true,

    // LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],

    captureTimeout: 60000,

    singleRun: false,

    preprocessors: {
      'src/**/!(*.spec)+(.js)': ['coverage']
    },

    coverageReporter: {
      type: 'lcov',
      dir: 'coverage/'
    },

    plugins: [
      'karma-jasmine',
      'karma-chrome-launcher',
      'karma-firefox-launcher',
      'karma-phantomjs-launcher',
      'karma-coverage',
      'karma-coveralls'
    ]
  });
};


================================================
FILE: package.json
================================================
{
  "name": "valdr",
  "version": "1.1.6",
  "description": "A model centric approach to AngularJS form validation",
  "homepage": "https://github.com/netceteragroup/valdr",
  "repository": {
    "type": "git",
    "url": "git://github.com/netceteragroup/valdr"
  },
  "scripts": {
    "postinstall": "bower install"
  },
  "author": {
    "name": "Netcetera AG"
  },
  "license": "MIT",
  "devDependencies": {
    "bower": "~1.3.12",
    "karma": "~0.12.31",
    "karma-jasmine": "~0.1.5",
    "karma-chrome-launcher": "~0.1.7",
    "karma-firefox-launcher": "~0.1.4",
    "karma-phantomjs-launcher": "0.1.4",
    "karma-coverage": "~0.2.7",
    "karma-coveralls": "~0.1.4",
    "grunt": "~0.4.5",
    "grunt-karma": "~0.8.2",
    "grunt-contrib-copy": "~0.7.0",
    "grunt-contrib-clean": "~0.6.0",
    "grunt-contrib-concat": "~0.5.0",
    "grunt-contrib-uglify": "~0.7.0",
    "grunt-contrib-jshint": "~0.10.0",
    "grunt-contrib-watch": "~0.6.1",
    "load-grunt-tasks": "~2.0.0",
    "express": "~3.4.8",
    "grunt-express": "~1.2.1"
  }
}


================================================
FILE: server.js
================================================
var express = require('express');

var app = express();
var server = require('http').createServer(app);

app.configure(function () {
    app.use(express.logger('dev'));
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    app.use(express.errorHandler());
    app.use(express.static(__dirname));
    app.use(app.router);
    require('./demo/demo-api')(app, __dirname);
});

module.exports = server;

// Override: Provide an "use" used by grunt-express.
module.exports.use = function () {
    app.use.apply(app, arguments);
};


================================================
FILE: src/core/valdr-service.js
================================================
angular.module('valdr')

  .provider('valdr', function () {

    var constraints = {}, validators = {}, constraintUrl, constraintsLoading, constraintAliases = {},
      validatorNames = [
        'valdrRequiredValidator',
        'valdrSizeValidator',
        'valdrMinLengthValidator',
        'valdrMaxLengthValidator',
        'valdrMinValidator',
        'valdrMaxValidator',
        'valdrEmailValidator',
        'valdrUrlValidator',
        'valdrDigitsValidator',
        'valdrFutureValidator',
        'valdrPastValidator',
        'valdrPatternValidator',
        'valdrHibernateEmailValidator'
      ];

    var addConstraints = function (newConstraints) {
      angular.extend(constraints, newConstraints);
    };

    this.addConstraints = addConstraints;

    var removeConstraints = function (constraintNames) {
      if (angular.isArray(constraintNames)) {
        angular.forEach(constraintNames, function (name) {
          delete constraints[name];
        });
      } else if (angular.isString(constraintNames)) {
        delete constraints[constraintNames];
      }
    };

    this.removeConstraints = removeConstraints;

    this.setConstraintUrl = function (url) {
      constraintUrl = url;
    };

    this.addValidator = function (validatorName) {
      validatorNames.push(validatorName);
    };

    this.addConstraintAlias = function (valdrName, alias) {
      if(!angular.isArray(constraintAliases[valdrName])) {
        constraintAliases[valdrName] = [];
      }
      constraintAliases[valdrName].push(alias);
    };

    this.$get =
      ['$log', '$injector', '$rootScope', '$http', 'valdrEvents', 'valdrUtil', 'valdrClasses',
        function ($log, $injector, $rootScope, $http, valdrEvents, valdrUtil, valdrClasses) {

          // inject all validators
          angular.forEach(validatorNames, function (validatorName) {
            var validator = $injector.get(validatorName);
            validators[validator.name] = validator;

            // register validator with aliases
            if(angular.isArray(constraintAliases[validator.name])) {
              angular.forEach(constraintAliases[validator.name], function (alias) {
                validators[alias] = validator;
              });
            }

          });

          // load constraints via $http if constraintUrl is configured
          if (constraintUrl) {
            constraintsLoading = true;
            $http.get(constraintUrl).then(function (response) {
              constraintsLoading = false;
              addConstraints(response.data);
              $rootScope.$broadcast(valdrEvents.revalidate);
            })['finally'](function () {
              constraintsLoading = false;
            });
          }

          var constraintsForType = function (type) {
            if (valdrUtil.has(constraints, type)) {
              return constraints[type];
            } else if (!constraintsLoading) {
              $log.warn('No constraints for type \'' + type + '\' available.');
            }
          };

          return {
            /**
             * Validates the value of the given type with the constraints for the given field name.
             *
             * @param typeName the type name
             * @param fieldName the field name
             * @param value the value to validate
             * @returns {*}
             */
            validate: function (typeName, fieldName, value) {

              var validResult = { valid: true },
                typeConstraints = constraintsForType(typeName);

              if (valdrUtil.has(typeConstraints, fieldName)) {
                var fieldConstraints = typeConstraints[fieldName],
                  fieldIsValid = true,
                  validationResults = [],
                  violations = [];

                angular.forEach(fieldConstraints, function (constraint, validatorName) {
                  var validator = validators[validatorName];

                  if (angular.isUndefined(validator)) {
                    $log.warn('No validator defined for \'' + validatorName +
                      '\'. Can not validate field \'' + fieldName + '\'');
                    return validResult;
                  }

                  var valid = validator.validate(value, constraint);
                  var validationResult = {
                    valid: valid,
                    value: value,
                    field: fieldName,
                    type: typeName,
                    validator: validatorName
                  };
                  angular.extend(validationResult, constraint);

                  validationResults.push(validationResult);
                  if (!valid) {
                    violations.push(validationResult);
                  }
                  fieldIsValid = fieldIsValid && valid;
                });

                return {
                  valid: fieldIsValid,
                  violations: violations.length === 0 ? undefined : violations,
                  validationResults: validationResults.length === 0 ? undefined : validationResults
                };
              } else {
                return validResult;
              }
            },
            addConstraints: function (newConstraints) {
              addConstraints(newConstraints);
              $rootScope.$broadcast(valdrEvents.revalidate);
            },
            removeConstraints: function (constraintNames) {
              removeConstraints(constraintNames);
              $rootScope.$broadcast(valdrEvents.revalidate);
            },
            getConstraints: function () {
              return constraints;
            },
            setClasses: function (newClasses) {
              angular.extend(valdrClasses, newClasses);
              $rootScope.$broadcast(valdrEvents.revalidate);
            }
          };
        }];
  });

================================================
FILE: src/core/valdr-service.spec.js
================================================
describe('valdr', function () {

  var valdr, $rootScope, valdrEvents, valdrClasses, sizeValidator, requiredValidator,
    personConstraints = {
      'Person': {
        'firstName': {
          'size': {
            'min': 0,
            'max': 10,
            'message': 'size'
          }
        }
      }
    },
    addressConstraints = {
      'Address': {
        'street': {
          'required': {
            'message': 'required'
          }
        }
      }
    };

  beforeEach(module('valdr'));

  beforeEach(inject(
    function (_valdr_, _$rootScope_, _valdrEvents_, _valdrClasses_, _valdrSizeValidator_, _valdrRequiredValidator_) {
    valdr = _valdr_;
    $rootScope = _$rootScope_;
    valdrEvents = _valdrEvents_;
    valdrClasses = _valdrClasses_;
    sizeValidator = _valdrSizeValidator_;
    requiredValidator = _valdrRequiredValidator_;
  }));

  describe('addConstraints()', function () {

    it('should add initial constraints', function () {
      // when
      valdr.addConstraints(personConstraints);

      // then
      expect(valdr.getConstraints()).toEqual(personConstraints);
    });

    it('should extend initial constraints', function () {
      // when
      valdr.addConstraints(personConstraints);
      valdr.addConstraints(addressConstraints);

      // then
      expect(valdr.getConstraints().Person).toEqual(personConstraints.Person);
      expect(valdr.getConstraints().Address).toEqual(addressConstraints.Address);
    });

    it('should broadcast event when constraints change', function () {
      // given
      spyOn($rootScope, '$broadcast');

      // when
      valdr.addConstraints(personConstraints);

      // then
      expect($rootScope.$broadcast).toHaveBeenCalledWith(valdrEvents.revalidate);
    });
  });

  describe('removeConstraints()', function () {

    it('should remove single constraint', function () {
      // given
      valdr.addConstraints(personConstraints);
      valdr.addConstraints(addressConstraints);

      // when
      valdr.removeConstraints('Person');

      // then
      expect(valdr.getConstraints()).toEqual(addressConstraints);
    });

    it('should remove multiple constraints', function () {
      // given
      valdr.addConstraints(personConstraints);
      valdr.addConstraints(addressConstraints);

      // when
      valdr.removeConstraints(['Person', 'Address']);


      // then
      expect(valdr.getConstraints()).toEqual({});
    });

    it('should broadcast event when constraints change', function () {
      // given
      valdr.addConstraints(personConstraints);
      spyOn($rootScope, '$broadcast');

      // when
      valdr.removeConstraints('Person');

      // then
      expect($rootScope.$broadcast).toHaveBeenCalledWith(valdrEvents.revalidate);
    });
  });

  describe('validate()', function () {

    it('should not validate if no constraints are defined', function () {
      // when
      var validationResult = valdr.validate('Person', 'firstName', 'Hanueli');

      // then
      expect(validationResult.valid).toBe(true);
      expect(validationResult.violations).toBeUndefined();
      expect(validationResult.validationResults).toBeUndefined();
    });

    it('should validate with correct validator', function () {
      // given
      valdr.addConstraints(personConstraints);
      spyOn(sizeValidator, 'validate').andCallThrough();

      // when
      var validationResult = valdr.validate('Person', 'firstName', 'Hanueli');

      // then
      expect(validationResult.valid).toBe(true);
      expect(validationResult.violations).toBeUndefined();
      expect(validationResult.validationResults.length).toBe(1);
      expect(sizeValidator.validate).toHaveBeenCalled();
    });

    it('should return true if no validator is available for a constraint', function () {
      // given
      valdr.addConstraints({
        'Person': {
          'firstName': {
            'ThereIsNoValidatorForThisConstraint': {}
          }
        }
      });

      // when
      var validationResult = valdr.validate('Person', 'firstName', 'Hanueli');

      // then
      expect(validationResult.valid).toBe(true);
    });

    it('should return invalid state and message if validation fails', function () {
      // given
      valdr.addConstraints(personConstraints);
      spyOn(sizeValidator, 'validate').andCallThrough();

      // when
      var validationResult = valdr.validate('Person', 'firstName', 'Hanueli with a name that is too long');

      // then
      expect(sizeValidator.validate).toHaveBeenCalled();
      expect(validationResult.valid).toBe(false);
      expect(validationResult.violations[0].message).toBe('size');
      expect(validationResult.violations[0].field).toBe('firstName');
      expect(validationResult.violations[0].type).toBe('Person');
      expect(validationResult.violations[0].value).toBe('Hanueli with a name that is too long');
      expect(validationResult.violations[0].max).toBe(10);
      expect(validationResult.violations[0].min).toBe(0);
    });

    it('should return invalid state and message if multiple validations fail', function () {
      // given
      valdr.addConstraints({
        'Person': {
          'firstName': {
            'size': {
              'min': 2,
              'max': 10,
              'message': 'size'
            },
            'required': {
              'message': 'required'
            }
          }
        }
      });
      spyOn(sizeValidator, 'validate').andCallThrough();
      spyOn(requiredValidator, 'validate').andCallThrough();

      // when
      var validationResult = valdr.validate('Person', 'firstName', undefined);

      // then
      expect(sizeValidator.validate).toHaveBeenCalled();
      expect(requiredValidator.validate).toHaveBeenCalled();
      expect(validationResult.valid).toBe(false);
      expect(validationResult.violations[0].message).toBe('required');
    });

  });

  describe('setClasses()', function () {

    it('should add the given classes to the valdrClasses and broadcast an event', function () {
      // given
      spyOn($rootScope, '$broadcast');
      var newClass = 'is-valid';
      expect(valdrClasses.valid).toBe('ng-valid');
      expect(valdrClasses.invalid).toBe('ng-invalid');

      // when
      valdr.setClasses({ valid: newClass });

      // then
      expect($rootScope.$broadcast).toHaveBeenCalledWith(valdrEvents.revalidate);
      expect(valdrClasses.valid).toBe(newClass);
      expect(valdrClasses.invalid).toBe('ng-invalid');

      // cleanup to prevent side-effects
      valdr.setClasses({ valid: 'ng-valid' });
    });

  });
});

describe('valdrProvider', function () {

  it('should load the constraints via $http', function () {
    // given
    var $httpBackend, apiUrl = '/api/validation';

    // when
    module('valdr');
    module(function (valdrProvider) {
      valdrProvider.setConstraintUrl(apiUrl);
    });
    inject(function (_$httpBackend_) {
      $httpBackend = _$httpBackend_;
      $httpBackend.expect('GET', apiUrl).respond(200, {});
    });

    /*jshint unused:false */
    inject(function (valdr) {
      // injecting the valdr service triggers the loading and therefore the GET to the apiUrl
      $httpBackend.flush();

      // then
      $httpBackend.verifyNoOutstandingExpectation();
      $httpBackend.verifyNoOutstandingRequest();
    });
  });

  it('should support to use aliases for constraint names', function () {
    // given
    module('valdr');
    module(function (valdrProvider) {
      valdrProvider.addConstraints({
        'Person': {
          'firstName': {
            'sizeBetween': {
              'min': 0,
              'max': 10
            },
            'secondSizeBetween': {
              'min': 10,
              'max': 20
            }
          }
        }
      });
      valdrProvider.addConstraintAlias('size', 'sizeBetween');
      valdrProvider.addConstraintAlias('size', 'secondSizeBetween');
    });


    inject(function (valdr, valdrSizeValidator) {
      // when
      spyOn(valdrSizeValidator, 'validate').andCallThrough();
      var validationResult = valdr.validate('Person', 'firstName', 'Hanueli');

      // then
      expect(validationResult.valid).toBe(false);
      expect(validationResult.violations).toBeDefined();
      expect(validationResult.violations.length).toBe(1);
      expect(validationResult.validationResults.length).toBe(2);
      expect(valdrSizeValidator.validate).toHaveBeenCalled();
    });
  });

  describe('custom validators', function () {

    it('should allow to add custom validators', function () {
      // given
      module('valdr');

      module(function ($provide) {
        $provide.factory('customValidator', function () {
          return {
            name: 'custom',
            validate: function (value) {
              return value === 'Hanueli';
            }
          };
        });
      });

      module(function (valdrProvider) {
        valdrProvider.addValidator('customValidator');
        valdrProvider.addConstraints({
          'Person': {
            'firstName': {
              'custom': {}
            }
          }
        });
      });

      inject(function (valdr, customValidator) {
        // when
        spyOn(customValidator, 'validate').andCallThrough();
        var validationResult = valdr.validate('Person', 'firstName', 'Hanueli');

        // then
        expect(validationResult.valid).toBe(true);
        expect(validationResult.violations).toBeUndefined();
        expect(customValidator.validate).toHaveBeenCalled();
      });
    });

  });

});

================================================
FILE: src/core/valdrEnabled-directive.js
================================================
angular.module('valdr')

/**
 * This directive allows to dynamically enable and disable the validation with valdr.
 * All form elements in a child node of an element with the 'valdr-enabled' directive will be affected by this.
 *
 * Usage:
 *
 * <div valdr-enabled="isValidationEnabled()">
 *   <input type="text" name="name" ng-model="mymodel.field">
 * </div>
 *
 * If multiple valdr-enabled directives are nested, the one nearest to the validated form element
 * will take precedence.
 */
  .directive('valdrEnabled', ['valdrEvents', function (valdrEvents) {
    return  {
      controller: ['$scope', '$attrs', function($scope, $attrs) {
        $scope.$watch($attrs.valdrEnabled, function () {
          $scope.$broadcast(valdrEvents.revalidate);
        });

        this.isEnabled = function () {
          var evaluatedExpression = $scope.$eval($attrs.valdrEnabled);
          return evaluatedExpression === undefined ? true : evaluatedExpression;
        };
      }]
    };
  }]);


================================================
FILE: src/core/valdrEnabled-directive.spec.js
================================================
describe('valdrEnabled directive', function () {

  // VARIABLES

  var $scope, $compile, element, valdr, ngModelController, template,
    personConstraints = {
      'Person': {
        'firstName': {
          'required': {
            'message': 'first name is required'
          }
        }
      }
    };

  // TEST UTILITIES

  function compileValdrEnabledTemplate() {
    element = $compile(angular.element(template))($scope);
    $scope.$digest();
    ngModelController = element.find('input').controller('ngModel');
  }

  beforeEach(function () {
    module('valdr');
  });

  beforeEach(inject(function ($rootScope, _$compile_, _valdr_) {
    $compile = _$compile_;
    valdr = _valdr_;

    template =
      '<form valdr-type="Person" valdr-enabled="isValdrEnabled()">' +
        '<input type="text" name="firstName" ng-model="person.firstName">' +
      '</form>';

    $scope = $rootScope.$new();

    $scope.person = { };
    $scope.valdrEnabled = true;
    $scope.isValdrEnabled = function () {
      return $scope.valdrEnabled;
    };

    valdr.addConstraints(personConstraints);
  }));

  // TEST

  it('should validate if valdrEnabled is true', function () {
    // given
    $scope.valdrEnabled = true;

    // when
    compileValdrEnabledTemplate();

    // then
    expect(ngModelController.$valid).toBe(false);
  });

  it('should not validate if valdrEnabled is false', function () {
    // given
    $scope.valdrEnabled = false;

    // when
    compileValdrEnabledTemplate();

    // then
    expect(ngModelController.$valid).toBe(true);
  });

  it('should validate if no expression is provided', function () {
    // given
    template =
      '<form valdr-type="Person" valdr-enabled>' +
        '<input type="text" name="firstName" ng-model="person.firstName">' +
      '</form>';

    // when
    compileValdrEnabledTemplate();

    // then
    expect(ngModelController.$valid).toBe(false);
  });

  it('should remove errors when constraints are removed', function () {
    // given
    compileValdrEnabledTemplate();

    // when
    valdr.removeConstraints('Person');

    // then
    expect(ngModelController.$valid).toBe(true);
  });

  it('should remove errors when valdr gets disabled', function () {
    // given
    compileValdrEnabledTemplate();

    // when
    $scope.$apply(function () {
      $scope.valdrEnabled = false;
    });

    // then
    expect(ngModelController.$valid).toBe(true);
  });

});

================================================
FILE: src/core/valdrFormGroup-directive.js
================================================
/**
 * This directive adds the validity state to a form group element surrounding valdr validated input fields.
 * If valdr-messages is loaded, it also adds the validation messages as last element to the element this this
 * directive is applied on.
 */
var valdrFormGroupDirectiveDefinition =
  ['valdrClasses', 'valdrConfig', function (valdrClasses, valdrConfig) {
    return  {
      restrict: 'EA',
      link: function (scope, element) {
        if (valdrConfig.addFormGroupClass) {
          element.addClass(valdrClasses.formGroup);
        }
      },
      controller: ['$scope', '$element', function ($scope, $element) {

        var formItems = [],
          messageElements = {};

        /**
         * Checks the state of all valdr validated form items below this element.
         * @returns {Object} an object containing the states of all form items in this form group
         */
        var getFormGroupState = function () {

          var formGroupState = {
            // true if an item in this form group is currently dirty, touched and invalid
            invalidDirtyTouchedGroup: false,
            // true if all form items in this group are currently valid
            valid: true,
            // contains the validity states of all form items in this group
            itemStates: []
          };

          angular.forEach(formItems, function (formItem) {
            if (formItem.$touched && formItem.$dirty && formItem.$invalid) {
              formGroupState.invalidDirtyTouchedGroup = true;
            }

            if (formItem.$invalid) {
              formGroupState.valid = false;
            }

            var itemState = {
              name: formItem.$name,
              touched: formItem.$touched,
              dirty: formItem.$dirty,
              valid: formItem.$valid
            };

            formGroupState.itemStates.push(itemState);
          });

          return formGroupState;
        };

        /**
         * Updates the classes on this element and the valdr message elements based on the validity states
         * of the items in this form group.
         * @param formGroupState the current state of this form group and its items
         */
        var updateClasses = function (formGroupState) {
          // form group state
          $element.toggleClass(valdrClasses.invalidDirtyTouchedGroup, formGroupState.invalidDirtyTouchedGroup);
          $element.toggleClass(valdrClasses.valid, formGroupState.valid);
          $element.toggleClass(valdrClasses.invalid, !formGroupState.valid);

          // valdr message states
          angular.forEach(formGroupState.itemStates, function (itemState) {
            var messageElement = messageElements[itemState.name];
            if (messageElement) {
              messageElement.toggleClass(valdrClasses.valid, itemState.valid);
              messageElement.toggleClass(valdrClasses.invalid, !itemState.valid);
              messageElement.toggleClass(valdrClasses.dirty, itemState.dirty);
              messageElement.toggleClass(valdrClasses.pristine, !itemState.dirty);
              messageElement.toggleClass(valdrClasses.touched, itemState.touched);
              messageElement.toggleClass(valdrClasses.untouched, !itemState.touched);
            }
          });
        };

        $scope.$watch(getFormGroupState, updateClasses, true);

        this.addFormItem = function (ngModelController) {
          formItems.push(ngModelController);
        };

        this.removeFormItem = function (ngModelController) {
          var index = formItems.indexOf(ngModelController);
          if (index >= 0) {
            formItems.splice(index, 1);
          }
        };

        this.addMessageElement = function (ngModelController, messageElement) {
          $element.append(messageElement);
          messageElements[ngModelController.$name] = messageElement;
        };

        this.removeMessageElement = function (ngModelController) {
          if (messageElements[ngModelController.$name]) {
            messageElements[ngModelController.$name].remove();
            delete messageElements[ngModelController.$name];
          }
        };

      }]
    };
  }];

angular.module('valdr')
  .directive('valdrFormGroup', valdrFormGroupDirectiveDefinition);


================================================
FILE: src/core/valdrFormGroup-directive.spec.js
================================================
describe('valdrFormGroup directive', function () {

  // VARIABLES

  var $scope, $compile, element, valdr, valdrClasses, valdrConfig, ngModelController,
    personConstraints = {
      'Person': {
        'firstName': {
          'size': {
            'min': 0,
            'max': 10,
            'message': 'size'
          }
        },
        'lastName': {
          'size': {
            'min': 0,
            'max': 10,
            'message': 'size'
          }
        }
      }
    };

  var formGroupTemplate =
      '<form valdr-type="Person" valdr-form-group>' +
        '<input type="text" name="firstName" valdr-no-message ng-model="person.firstName">' +
        '<input type="text" name="lastName" valdr-no-message  ng-model="person.lastName">' +
      '</form>';

  // TEST UTILITIES

  function compileTemplate(template) {
    element = $compile(angular.element(template))($scope);
    $scope.$digest();
  }

  function compileFormGroupTemplate() {
    compileTemplate(formGroupTemplate);
    ngModelController = element.find('input').controller('ngModel');
  }

  beforeEach(function () {
    module('valdr');
  });

  beforeEach(inject(function ($rootScope, _$compile_, _valdr_, _valdrClasses_, _valdrConfig_) {
    $compile = _$compile_;
    valdr = _valdr_;
    valdrClasses = _valdrClasses_;
    valdrConfig = _valdrConfig_;

    $scope = $rootScope.$new();
    $scope.person = { };
    valdr.addConstraints(personConstraints);
  }));


  describe('valdrFormGroup', function () {

    beforeEach(function () {
      compileFormGroupTemplate();
    });

    describe('form-group class', function () {

      it ('should add form group class by default', function () {
        expect(element.hasClass(valdrClasses.formGroup)).toBe(true);
      });

      it ('should not add form group class if option is disabled in valdrConfig', function () {
        // given
        valdrConfig.addFormGroupClass = false;

        // when
        compileFormGroupTemplate();

        // then
        expect(element.hasClass(valdrClasses.formGroup)).toBe(false);
      });

    });

    it('should not set valid and invalidDirtyTouchedGroup classes if all items are valid', function () {
      expect(element.hasClass(valdrClasses.valid)).toBe(true);
      expect(element.hasClass(valdrClasses.invalid)).toBe(false);
      expect(element.hasClass(valdrClasses.invalidDirtyTouchedGroup)).toBe(false);
    });

    it('should not set invalid class if an item is not valid', function () {
      // given
      $scope.person.firstName = 'This name is too long for the constraints.';

      // when
      $scope.$digest();

      // then
      expect(element.hasClass(valdrClasses.invalid)).toBe(true);
      expect(element.hasClass(valdrClasses.valid)).toBe(false);
      expect(element.hasClass(valdrClasses.invalidDirtyTouchedGroup)).toBe(false);
    });

    it('should add invalidDirtyTouchedGroup class if an input is dirty, touched and invalid', function () {
      // given
      $scope.person.firstName = 'This name is too long for the constraints.';
      ngModelController.$invalid = true;
      ngModelController.$dirty = true;
      ngModelController.$touched = true;

      // when
      $scope.$digest();

      // then
      expect(element.hasClass(valdrClasses.invalid)).toBe(true);
      expect(element.hasClass(valdrClasses.valid)).toBe(false);
      expect(element.hasClass(valdrClasses.invalidDirtyTouchedGroup)).toBe(true);
    });

    it('should be valid if no form items are registered', function () {
      // given
      var template = '<form valdr-type="Person" valdr-form-group></form>';

      // when
      compileTemplate(template);

      // then
      expect(element.hasClass(valdrClasses.valid)).toBe(true);
      expect(element.hasClass(valdrClasses.invalid)).toBe(false);
      expect(element.hasClass(valdrClasses.invalidDirtyTouchedGroup)).toBe(false);
    });
  });

});

================================================
FILE: src/core/valdrFormItem-directive.js
================================================
/**
 * This controller is used if no valdrEnabled parent directive is available.
 */
var nullValdrEnabledController = {
  isEnabled: function () {
    return true;
  }
};

/**
 * This controller is used if no valdrFormGroup parent directive is available.
 */
var nullValdrFormGroupController = {
  addFormItem: angular.noop,
  removeFormItem: angular.noop
};

/**
 * This directive adds validation to all input and select fields as well as to explicitly enabled elements which are
 * bound to an ngModel and are surrounded by a valdrType directive. To prevent adding validation to specific fields,
 * the attribute 'valdr-no-validate' can be added to those fields.
 */
var valdrFormItemDirectiveDefinitionFactory = function (restrict) {
    return ['valdrEvents', 'valdr', 'valdrUtil', function (valdrEvents, valdr, valdrUtil) {
      return {
        restrict: restrict,
        require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup', '?^valdrEnabled'],
        link: function (scope, element, attrs, controllers) {

          var valdrTypeController = controllers[0],
            ngModelController = controllers[1],
            valdrFormGroupController = controllers[2] || nullValdrFormGroupController,
            valdrEnabled = controllers[3] || nullValdrEnabledController,
            valdrNoValidate = attrs.valdrNoValidate,
            fieldName = attrs.name;

          /**
           * Don't do anything if
           * - this is an <input> that's not inside of a valdr-type block
           * - there is no ng-model bound to input
           * - there is the 'valdr-no-validate' attribute present
           */
          if (!valdrTypeController || !ngModelController || angular.isDefined(valdrNoValidate)) {
            return;
          }

          valdrFormGroupController.addFormItem(ngModelController);

          if (valdrUtil.isEmpty(fieldName) && valdrEnabled.isEnabled()) {
            console.warn('Form element with ID "' + attrs.id + '" is not bound to a field name.');
          }

          var updateNgModelController = function (validationResult) {

            if (valdrEnabled.isEnabled()) {
              var validatorTokens = ['valdr'];

              // set validity state for individual valdr validators
              angular.forEach(validationResult.validationResults, function (result) {
                var validatorToken = valdrUtil.validatorNameToToken(result.validator);
                ngModelController.$setValidity(validatorToken, result.valid);
                validatorTokens.push(validatorToken);
              });

              // set overall validity state of this form item
              ngModelController.$setValidity('valdr', validationResult.valid);
              ngModelController.valdrViolations = validationResult.violations;

              // remove errors for valdr validators which no longer exist
              angular.forEach(ngModelController.$error, function (value, validatorToken) {
                if (validatorTokens.indexOf(validatorToken) === -1 && valdrUtil.startsWith(validatorToken, 'valdr')) {
                  ngModelController.$setValidity(validatorToken, true);
                }
              });
            } else {
              angular.forEach(ngModelController.$error, function (value, validatorToken) {
                if (valdrUtil.startsWith(validatorToken, 'valdr')) {
                  ngModelController.$setValidity(validatorToken, true);
                }
              });
              ngModelController.valdrViolations = undefined;
            }
          };

          var validate = function (modelValue) {
            var validationResult = valdr.validate(valdrTypeController.getType(), fieldName, modelValue);
            updateNgModelController(validationResult);
            return valdrEnabled.isEnabled() ? validationResult.valid : true;
          };

          ngModelController.$validators.valdr = validate;

          scope.$on(valdrEvents.revalidate, function () {
            validate(ngModelController.$modelValue);
          });

          scope.$on('$destroy', function () {
            valdrFormGroupController.removeFormItem(ngModelController);
          });

        }
      };
    }];
  },
  valdrFormItemElementDirectiveDefinition = valdrFormItemDirectiveDefinitionFactory('E'),
  valdrFormItemAttributeDirectiveDefinition = valdrFormItemDirectiveDefinitionFactory('A');

angular.module('valdr')
  .directive('input', valdrFormItemElementDirectiveDefinition)
  .directive('select', valdrFormItemElementDirectiveDefinition)
  .directive('textarea', valdrFormItemElementDirectiveDefinition)
  .directive('enableValdrValidation', valdrFormItemAttributeDirectiveDefinition);


================================================
FILE: src/core/valdrFormItem-directive.spec.js
================================================
describe('valdrFormItem directive', function () {

  // VARIABLES

  var $scope, $compile, element, valdr, valdrEvents, valdrClasses, ngModelController,
    violations = ['violationsArray'],
    validationResults = [{ validator: 'required', valid: true }];

  var inputTemplate =
    '<form name="demoForm">' +
      '<div valdr-type="TestClass">' +
        '<input type="text" name="fieldName" ng-model="myObject.field">' +
      '</div>' +
    '</form>';

  var selectTemplate =
    '<form name="demoForm">' +
      '<div valdr-type="TestClass">' +
        '<select name="fieldName" ng-model="myObject.field">' +
          '<option value></option>' +
          '<option value="1">First</option>' +
          '<option value="2">Second</option>' +
        '</select>' +
      '</div>' +
    '</form>';

  var textareaTemplate =
      '<form name="demoForm">' +
        '<div valdr-type="TestClass">' +
          '<textarea name="fieldName" ng-model="myObject.field">' +
          '</textarea>' +
        '</div>' +
      '</form>';

  var inputWidgetTemplate =
    '<form name="demoForm">' +
      '<div valdr-type="TestClass">' +
        '<section name="fieldName" ng-model="myObject.field" enable-valdr-validation>' +
        '</section>' +
      '</div>' +
    '</form>';

  // TEST UTILITIES

  function compileTemplate(template) {
    element = $compile(angular.element(template))($scope);
    $scope.$digest();
  }

  function compileInputTemplate() {
    compileTemplate(inputTemplate);
    ngModelController = element.find('input').controller('ngModel');
  }

  // COMMON SETUP

  beforeEach(function () {
    module('valdr');

    /**
     * Mock the valdr to always return 'true' when the value equals the string 'valid'.
     */
    module(function ($provide) {
      $provide.value('valdr', {
        validate: function (typeName, fieldName, value) {
          return {
            valid: value === 'valid',
            violations: violations,
            validationResults: validationResults
          };
        }
      });
    });
  });

  beforeEach(inject(function ($rootScope, _$compile_, _valdr_, _valdrEvents_, _valdrClasses_) {
    $compile = _$compile_;
    $scope = $rootScope.$new();
    $scope.myObject = { field: 'fieldValue' };
    valdr = _valdr_;
    valdrEvents = _valdrEvents_;
    valdrClasses = _valdrClasses_;
  }));

  // COMMON TESTS

  function runFormItemCommonTests() {
    it('should set the validity to false on ngModelController if validation fails', function () {
      // when
      $scope.$apply(function () {
        $scope.myObject.field = 'invalid';
      });

      // then
      expect(ngModelController.$valid).toBe(false);
      expect(ngModelController.valdrViolations).toBe(violations);
      expect(ngModelController.$validators.valdr).toBeDefined();
    });

    it('should set the validity to true on ngModelController if validation is ok', function () {
      // when
      $scope.$apply(function () {
        $scope.myObject.field = 'valid';
      });

      // then
      expect(ngModelController.$valid).toBe(true);
      expect(ngModelController.valdrViolations).toBe(violations);
    });

    it('should register in valdrFormGroup', function () {
      // given
      var formGroupTemplate =
        '<form name="demoForm" valdr-type="TestClass" valdr-form-group>' +
            '<input type="text" name="fieldName" ng-model="myObject.field">' +
        '</form>';
      compileTemplate(formGroupTemplate);

      // when
      $scope.$apply(function () {
        $scope.myObject.field = 'invalid';
      });

      // then
      expect(element.hasClass(valdrClasses.invalid)).toBe(true);
    });

    it('should unregister from valdrFormGroup on $destroy', function () {
      // given
      var formGroupTemplate =
        '<form name="demoForm" valdr-type="TestClass" valdr-form-group>' +
          '<input type="text" name="fieldName" ng-model="myObject.field">' +
        '</form>';
      compileTemplate(formGroupTemplate);
      $scope.$apply(function () {
        $scope.myObject.field = 'invalid';
      });

      // when
      $scope.$broadcast('$destroy');
      $scope.$digest();

      // then
      expect(element.hasClass(valdrClasses.invalid)).toBe(false);
    });

    it('should handle constraint changed events', function () {
      // given
      spyOn(valdr, 'validate').andCallThrough();
      ngModelController.$viewValue = 'viewValue';

      // when
      $scope.$broadcast(valdrEvents.revalidate);

      // then
      expect(valdr.validate).toHaveBeenCalledWith(jasmine.any(String), 'fieldName', ngModelController.$modelValue);
    });
  }

  describe('on input fields', function () {

    beforeEach(function () {
      compileInputTemplate();
    });

    runFormItemCommonTests();

    it('should log warning if no field name is provided on the input', function () {
      // given
      spyOn(console, 'warn');
      var invalidInput =
        '<form name="demoForm">' +
          '<div valdr-type="TestClass">' +
            '<input type="text" ng-model="myObject.field">' +
          '</div>' +
        '</form>';

      // when
      $compile(angular.element(invalidInput))($scope);

      // then
      expect(console.warn).toHaveBeenCalledWith('Form element with ID "undefined" is not bound to a field name.');
    });

    it('should NOT log warning if no field name is provided on the input but valdr is disabled', function () {
      // given
      spyOn(console, 'warn');
      var invalidInput =
        '<form name="demoForm" valdr-enabled="false">' +
        '  <div valdr-type="TestClass">' +
        '    <input type="text" ng-model="myObject.field">' +
        '  </div>' +
        '</form>';

      // when
      compileTemplate(invalidInput);

      // then
      expect(console.warn).not.toHaveBeenCalled();
    });

    it('should NOT use valdr validation if valdr-no-validate is set', function () {
      // given
      var noValdrValidationInput =
        '<form name="demoForm">' +
          '<div valdr-type="TestClass">' +
            '<input type="text" name="fieldName" ng-model="myObject.field" valdr-no-validate>' +
          '</div>' +
        '</form>';

      // when
      compileTemplate(noValdrValidationInput);
      ngModelController = element.find('input').controller('ngModel');

      // then
      expect(ngModelController.$validators.valdr).toBeUndefined();
    });
  });

  describe('on select elements', function () {

    beforeEach(function () {
      compileTemplate(selectTemplate);
      ngModelController = element.find('select').controller('ngModel');
    });

    runFormItemCommonTests();

  });


  describe('on textarea elements', function () {

    beforeEach(function () {
      compileTemplate(textareaTemplate);
      ngModelController = element.find('textarea').controller('ngModel');
    });

    runFormItemCommonTests();

  });

  describe('on explicitly enabled elements', function () {

    beforeEach(function () {
      compileTemplate(inputWidgetTemplate);
      ngModelController = element.find('section').controller('ngModel');
    });

    runFormItemCommonTests();

  });

});


================================================
FILE: src/core/valdrType-directive.js
================================================
angular.module('valdr')

/**
 * The valdrType directive defines the type of the model to be validated.
 * The directive exposes the type through the controller to allow access to it by wrapped directives.
 */
  .directive('valdrType', function () {
    return  {
      priority: 1,
      controller: ['$attrs', function ($attrs) {

        this.getType = function () {
          return $attrs.valdrType;
        };

      }]
    };
  });


================================================
FILE: src/core/valdrType-directive.spec.js
================================================
describe('valdrType directive', function () {

  var $scope, $compile, element, FormTypeController;

  var compileTemplate = function () {
    var element = $compile(angular.element('<div valdr-type="TestClass"></div>'))($scope);
    $scope.$digest();
    return element;
  };

  beforeEach(module('valdr'));

  beforeEach(inject(function ($rootScope, $controller, _$compile_) {
    $compile = _$compile_;
    $scope = $rootScope.$new();
    element = compileTemplate();
    FormTypeController = element.controller('valdrType');
  }));

  it('should read the type from the attribute', function () {
    expect(FormTypeController.getType()).toBe('TestClass');
  });

  it('should allow to nest the directive', function () {
    // given
    var element = $compile(angular.element(
      '<div valdr-type="TestClass">' +
        '<span valdr-type="NestedClass"></span>' +
        '</div>'))($scope);

    // when
    var rootController = element.controller('valdrType');
    var nestedController = element.find('span').controller('valdrType');

    // then
    expect(rootController.getType()).toBe('TestClass');
    expect(nestedController.getType()).toBe('NestedClass');
  });

});


================================================
FILE: src/core/valdrUtil-service.js
================================================
angular.module('valdr')

/**
 * Exposes utility functions used in validators and valdr core.
 */
  .factory('valdrUtil', [function () {

    var substringAfterDot = function (string) {
      if (string.lastIndexOf('.') === -1) {
        return string;
      } else {
        return string.substring(string.lastIndexOf('.') + 1, string.length);
      }
    };

    var SLUG_CASE_REGEXP = /[A-Z]/g;
    var slugCase = function (string) {
      return string.replace(SLUG_CASE_REGEXP, function(letter, pos) {
        return (pos ? '-' : '') + letter.toLowerCase();
      });
    };

    /**
     * Converts the given validator name to a validation token. Uses the last part of the validator name after the
     * dot (if present) and converts camel case to slug case (fooBar -> foo-bar).
     * @param validatorName the validator name
     * @returns {string} the validation token
     */
    var validatorNameToToken = function (validatorName) {
      if (angular.isString(validatorName)) {
        var name = substringAfterDot(validatorName);
        name = slugCase(name);
        return 'valdr-' + name;
      } else {
        return validatorName;
      }
    };

    return {
      validatorNameToToken: validatorNameToToken,

      isNaN: function (value) {
        // `NaN` as a primitive is the only value that is not equal to itself
        // (perform the [[Class]] check first to avoid errors with some host objects in IE)
        return this.isNumber(value) && value !== +value;
      },

      isNumber: function (value) {
        var type = typeof value;
        return type === 'number' ||
          value && type === 'object' && Object.prototype.toString.call(value) === '[object Number]' || false;
      },

      has: function (object, key) {
        return object ? Object.prototype.hasOwnProperty.call(object, key) : false;
      },

      /**
       * @param value the value
       * @returns {boolean} true if the given value is not null, not undefined, not an empty string, NaN returns false
       */
      notEmpty: function (value) {
        if (this.isNaN(value)) {
          return false;
        }
        if (angular.isArray(value) && value.length === 0){
          return false;
        }
        return angular.isDefined(value) && value !== '' && value !== null;
      },

      /**
       * @param value the value to validate
       * @returns {boolean} true if the given value is null, undefined, an empty string, NaN returns false
       */
      isEmpty: function (value) {
        if (this.isNaN(value)) {
          return false;
        }
        return !this.notEmpty(value);
      },

      /**
       * Checks if a string value starts with a given prefix.
       *
       * @param value the value
       * @param prefix the prefix
       * @returns {boolean} true if the given value starts with the given prefix.
       */
      startsWith: function (value, prefix) {
        return angular.isString(value)  &&
          angular.isString(prefix) &&
          value.lastIndexOf(prefix, 0) === 0;
      }
    };
  }])
;


================================================
FILE: src/core/valdrUtil-service.spec.js
================================================
describe('valdrUtil', function () {

  var valdrUtil;

  beforeEach(module('valdr'));
  beforeEach(inject(function (_valdrUtil_) {
    valdrUtil = _valdrUtil_;
  }));

  describe('validatorNameToToken()', function () {

    it('should convert camel case to slug case and prepend with valdr', function () {
      expect(valdrUtil.validatorNameToToken(undefined)).toBe(undefined);
      expect(valdrUtil.validatorNameToToken('nocamel')).toBe('valdr-nocamel');
      expect(valdrUtil.validatorNameToToken('camelCase')).toBe('valdr-camel-case');
      expect(valdrUtil.validatorNameToToken('CapitalCamelCase')).toBe('valdr-capital-camel-case');
    });

    it('should convert remove everything before the last dot and convert to slug case', function () {
      expect(valdrUtil.validatorNameToToken('bla.nocamel')).toBe('valdr-nocamel');
      expect(valdrUtil.validatorNameToToken('bla.camelCase')).toBe('valdr-camel-case');
      expect(valdrUtil.validatorNameToToken('bla.CapitalCamelCase')).toBe('valdr-capital-camel-case');
    });

  });

  describe('isNaN()', function () {

    it('should provide isNaN function', inject(function (valdrUtil) {
      expect(valdrUtil.isNaN).toBeDefined();
      expect(typeof valdrUtil.isNaN).toBe('function');
    }));

    it('should check if input is NaN', function () {
      expect(valdrUtil.isNaN(NaN)).toBe(true);
      expect(valdrUtil.isNaN('string')).toBe(false);
      expect(valdrUtil.isNaN(0)).toBe(false);
    });

  });

  describe('isNumber()', function () {

    it('should provide isNumber function', inject(function (valdrUtil) {
      expect(valdrUtil.isNumber).toBeDefined();
      expect(typeof valdrUtil.isNumber).toBe('function');
    }));

    it('should check if input is a number', function () {
      expect(valdrUtil.isNumber(NaN)).toBe(true);
      expect(valdrUtil.isNumber(0)).toBe(true);
      expect(valdrUtil.isNumber('string')).toBe(false);
      expect(valdrUtil.isNumber(undefined)).toBe(false);
      expect(valdrUtil.isNumber(null)).toBe(false);
    });

  });

  describe('has()', function () {

    it('should provide has function', inject(function (valdrUtil) {
      expect(valdrUtil.has).toBeDefined();
      expect(typeof valdrUtil.has).toBe('function');
    }));

    it('should check if object has property', function () {
      expect(valdrUtil.has({foo: 'a'}, 'foo')).toBe(true);
      expect(valdrUtil.has({foo: undefined}, 'foo')).toBe(true);
      expect(valdrUtil.has({}, 'foo')).toBe(false);
      expect(valdrUtil.has(undefined, 'foo')).toBe(false);
    });

  });

  describe('notEmpty()/isEmpty()', function () {

    it('should validate strings', function () {
      expect(valdrUtil.notEmpty('string')).toBe(true);
      expect(valdrUtil.isEmpty('string')).toBe(false);
    });

    it('should validate null value', function () {
      expect(valdrUtil.notEmpty(null)).toBe(false);
      expect(valdrUtil.isEmpty(null)).toBe(true);
      expect(valdrUtil.notEmpty('null')).toBe(true);
      expect(valdrUtil.isEmpty('null')).toBe(false);
    });

    it('should validate undefined value', function () {
      expect(valdrUtil.notEmpty(undefined)).toBe(false);
      expect(valdrUtil.isEmpty(undefined)).toBe(true);
    });

    it('should validate NaN value', function () {
      // NaN obviously is not a number but we don't know what it is, hence we cannot tell whether it's empty or not
      expect(valdrUtil.notEmpty(NaN)).toBe(false);
      expect(valdrUtil.isEmpty(NaN)).toBe(false);
    });

    it('should validate empty string', function () {
      expect(valdrUtil.notEmpty('')).toBe(false);
      expect(valdrUtil.isEmpty('')).toBe(true);
    });

    it('should validate arrays', function () {
      expect(valdrUtil.notEmpty(['Apple', 'Banana'])).toBe(true);
      expect(valdrUtil.isEmpty(['Apple', 'Banana'])).toBe(false);
      expect(valdrUtil.isEmpty([])).toBe(true);
      expect(valdrUtil.notEmpty([])).toBe(false);
    });

  });

  describe('startsWith()', function () {

    it ('should determine if a string starts with the given prefix', function () {
      expect(valdrUtil.startsWith('myString', 'my')).toBe(true);
      expect(valdrUtil.startsWith('string', 'string')).toBe(true);
      expect(valdrUtil.startsWith('', '')).toBe(true);
      expect(valdrUtil.startsWith('value', '')).toBe(true);
    });

    it('should return false if a string does not start with the specified prefix', function () {
      expect(valdrUtil.startsWith('', 'prefix')).toBe(false);
      expect(valdrUtil.startsWith('someThing', 'something')).toBe(false);
      expect(valdrUtil.startsWith(undefined, 'prefix')).toBe(false);
      expect(valdrUtil.startsWith(undefined, undefined)).toBe(false);
      expect(valdrUtil.startsWith('value', undefined)).toBe(false);
    });

  });

});


================================================
FILE: src/core/validators/digitsValidator.js
================================================
angular.module('valdr')

  .factory('valdrDigitsValidator', ['valdrUtil', function (valdrUtil) {

    // matches everything except digits and '.' as decimal separator
    var regexp = new RegExp('[^.\\d]', 'g');

    /**
     * By converting to number and back to string using toString(), we make sure that '.' is used as decimal separator
     * and not the locale specific decimal separator.
     * As we already checked for NaN at this point, we can do this safely.
     */
    var toStringWithoutThousandSeparators = function (value) {
      return Number(value).toString().replace(regexp, '');
    };

    var isNotLongerThan = function (valueAsString, maxLengthConstraint) {
      return !valueAsString ? true : valueAsString.length <= maxLengthConstraint;
    };

    var doValidate = function (value, constraint) {
      var integerConstraint = constraint.integer,
        fractionConstraint = constraint.fraction,
        cleanValueAsString, integerAndFraction;

      cleanValueAsString = toStringWithoutThousandSeparators(value);
      integerAndFraction = cleanValueAsString.split('.');

      return isNotLongerThan(integerAndFraction[0], integerConstraint) &&
        isNotLongerThan(integerAndFraction[1], fractionConstraint);
    };

    return {
      name: 'digits',

      /**
       * Checks if the value is a number within accepted range.
       *
       * @param value the value to validate
       * @param constraint the validation constraint, it is expected to have integer and fraction properties (maximum
       *                   number of integral/fractional digits accepted for this number)
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {

        if (valdrUtil.isEmpty(value)) {
          return true;
        }
        if (valdrUtil.isNaN(Number(value))) {
          return false;
        }

        return doValidate(value, constraint);
      }
    };
  }]);


================================================
FILE: src/core/validators/digitsValidator.spec.js
================================================
describe('valdrDigitsValidator', function () {

  var digitsValidator, constraint;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrDigitsValidator) {
    digitsValidator = valdrDigitsValidator;
    constraint = { integer: '4', fraction: '2' };
  }));

  it('should be named "digits"', function () {
    expect(digitsValidator.name).toBe('digits');
  });

  it('should return true for empty, null, and undefined', function () {
    expect(digitsValidator.validate('', constraint)).toBe(true);
    expect(digitsValidator.validate(null, constraint)).toBe(true);
    expect(digitsValidator.validate(undefined, constraint)).toBe(true);
  });

  it('should return false for NaN', function () {
    expect(digitsValidator.validate(NaN, constraint)).toBe(false);
  });

  it('should return true for valid integers', function () {
    expect(digitsValidator.validate('10', constraint)).toBe(true);
    expect(digitsValidator.validate(10, constraint)).toBe(true);
    expect(digitsValidator.validate(1000, constraint)).toBe(true);
    expect(digitsValidator.validate(-1000, constraint)).toBe(true);
    expect(digitsValidator.validate('10.02', constraint)).toBe(true);
    expect(digitsValidator.validate(10.02, constraint)).toBe(true);
    expect(digitsValidator.validate(-10.02, constraint)).toBe(true);
    expect(digitsValidator.validate(9999.99, constraint)).toBe(true);
    expect(digitsValidator.validate(-9999.99, constraint)).toBe(true);
  });

  it('should return false for invalid numbers and strings', function () {
    expect(digitsValidator.validate(10.001, constraint)).toBe(false);
    expect(digitsValidator.validate('10001', constraint)).toBe(false);
    expect(digitsValidator.validate('10.001', constraint)).toBe(false);
    expect(digitsValidator.validate(1000.001, constraint)).toBe(false);
    expect(digitsValidator.validate('string', constraint)).toBe(false);
    expect(digitsValidator.validate('number', constraint)).toBe(false);
    expect(digitsValidator.validate('47:11', constraint)).toBe(false);
    expect(digitsValidator.validate('47;11', constraint)).toBe(false);
    expect(digitsValidator.validate('47\'11', constraint)).toBe(false);
  });

  it('should validate correctly with fraction > 4', function() {
    constraint.fraction = 4;
    expect(digitsValidator.validate(1000.00001, constraint)).toBe(false);
    expect(digitsValidator.validate(1000.0001, constraint)).toBe(true);
  });

  it('should not choke on integer:1 conditions', function () {
    constraint.integer = 1;
    expect(digitsValidator.validate('10', constraint)).toBe(false);
    expect(digitsValidator.validate(10.0, constraint)).toBe(false);
    expect(digitsValidator.validate(0.1, constraint)).toBe(true);
    expect(digitsValidator.validate(0.11, constraint)).toBe(true);
    expect(digitsValidator.validate(0.111, constraint)).toBe(false);
  });

  it('should not choke on fraction:0 conditions', function () {
    constraint.fraction = 1;
    expect(digitsValidator.validate('10', constraint)).toBe(true);
    expect(digitsValidator.validate(10.0, constraint)).toBe(true);
    expect(digitsValidator.validate(10, constraint)).toBe(true);
    expect(digitsValidator.validate(0, constraint)).toBe(true);
    expect(digitsValidator.validate(0.11, constraint)).toBe(false);
    expect(digitsValidator.validate(0.111, constraint)).toBe(false);
  });
});


================================================
FILE: src/core/validators/emailValidator.js
================================================
angular.module('valdr')

  .factory('valdrEmailValidator', ['valdrUtil', function (valdrUtil) {

    // the e-mail pattern used in angular.js
    var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;

    return {
      name: 'email',

      /**
       * Checks if the value is a valid email address.
       *
       * @param value the value to validate
       * @returns {boolean} true if valid
       */
      validate: function (value) {
        return valdrUtil.isEmpty(value) || EMAIL_REGEXP.test(value);
      }
    };
  }]);


================================================
FILE: src/core/validators/emailValidator.spec.js
================================================
describe('valdrEmailValidator', function () {

  var emailValidator;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrEmailValidator) {
    emailValidator = valdrEmailValidator;
  }));

  it('should provide the correct name', function () {
    expect(emailValidator.name).toBe('email');
  });

  it('should return true for empty values', function () {
    expect(emailValidator.validate('')).toBe(true);
    expect(emailValidator.validate(undefined)).toBe(true);
  });

  it('should return true for valid email addresses', function () {
    expect(emailValidator.validate('hanueli@mountains.ch')).toBe(true);
    expect(emailValidator.validate('valdr.welds@anything.com')).toBe(true);
    expect(emailValidator.validate('hanueli@asdf')).toBe(true);
    expect(emailValidator.validate('hanueli@192.168.1.1')).toBe(true);
    expect(emailValidator.validate('a@3b.c')).toBe(true);
    expect(emailValidator.validate('a@b')).toBe(true);
  });

  it('should return false for invalid email addresses', function () {
    expect(emailValidator.validate('hanueli@')).toBe(false);
    expect(emailValidator.validate('hanueli@@gmail.com')).toBe(false);
    expect(emailValidator.validate('hanueli@gm ail.com')).toBe(false);
    expect(emailValidator.validate('hanueli@gmail..com')).toBe(false);
    expect(emailValidator.validate('hanueli@gmail.com.')).toBe(false);
    expect(emailValidator.validate('@anything.com')).toBe(false);
    expect(emailValidator.validate('...')).toBe(false);
    expect(emailValidator.validate('.@.')).toBe(false);
    expect(emailValidator.validate(' aaa.@. ')).toBe(false);
    expect(emailValidator.validate('a@-b.c')).toBe(false);
    expect(emailValidator.validate('a@b-.c')).toBe(false);
  });

});


================================================
FILE: src/core/validators/futureAndPastSharedValidator.js
================================================
angular.module('valdr')

  .factory('futureAndPastSharedValidator', ['valdrUtil', function (valdrUtil) {

    var someAlternativeDateFormats = ['D-M-YYYY', 'D.M.YYYY', 'D/M/YYYY', 'D. M. YYYY', 'YYYY.M.D'];

    return {
      validate: function (value, comparison) {
        var now = moment(), valueAsMoment;

        if (valdrUtil.isEmpty(value)) {
          return true;
        }

        valueAsMoment = moment(value);

        for (var i = 0; i < someAlternativeDateFormats.length && !valueAsMoment.isValid(); i++) {
          valueAsMoment = moment(value, someAlternativeDateFormats[i], true);
        }

        return valueAsMoment.isValid() && comparison(valueAsMoment, now);
      }
    };
  }]);


================================================
FILE: src/core/validators/futureValidator.js
================================================
angular.module('valdr')

  .factory('valdrFutureValidator', ['futureAndPastSharedValidator', function (futureAndPastSharedValidator) {

    return {
      name: 'future',

      /**
       * Checks if the value is a date in the future.
       *
       * @param value the value to validate
       * @returns {boolean} true if empty, null, undefined or a date in the future, false otherwise
       */
      validate: function (value) {

        return futureAndPastSharedValidator.validate(value, function (valueAsMoment, now) {
          return valueAsMoment.isAfter(now);
        });
      }
    };
  }]);


================================================
FILE: src/core/validators/futureValidator.spec.js
================================================
describe('valdrFutureValidator', function () {

  var futureValidator;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrFutureValidator) {
    futureValidator = valdrFutureValidator;
  }));

  it('should be named "future"', function () {
    expect(futureValidator.name).toBe('future');
  });

  it('should return true for empty values', function () {
    expect(futureValidator.validate('')).toBe(true);
    expect(futureValidator.validate(null)).toBe(true);
    expect(futureValidator.validate(undefined)).toBe(true);
  });

  it('should return false for NaN', function () {
    expect(futureValidator.validate(NaN)).toBe(false);
  });

  it('should return false for non-dates', function () {
    expect(futureValidator.validate(' ')).toBe(false);
    expect(futureValidator.validate('foo')).toBe(false);
    expect(futureValidator.validate('_1.1.2014')).toBe(false);
    expect(futureValidator.validate('31.2.2014')).toBe(false);
    expect(futureValidator.validate('31:1:2014')).toBe(false);
  });

  it('should return false for dates in the past', function () {
    expect(futureValidator.validate('1.1.1900')).toBe(false);
    expect(futureValidator.validate('01.01.1900')).toBe(false);
    expect(futureValidator.validate('1. 1. 1900')).toBe(false);
    expect(futureValidator.validate('01. 01. 1900')).toBe(false);
    expect(futureValidator.validate('1-1-1900')).toBe(false);
    expect(futureValidator.validate('01-01-1900')).toBe(false);
    expect(futureValidator.validate('1/1/1900')).toBe(false);
    expect(futureValidator.validate('01/01/1900')).toBe(false);
    expect(futureValidator.validate('1900.1.1')).toBe(false);
    expect(futureValidator.validate('1900.01.01')).toBe(false);
    expect(futureValidator.validate('2000/12/31')).toBe(false);
    expect(futureValidator.validate('2000-12-31')).toBe(false);
    expect(futureValidator.validate(moment().subtract(1, 'seconds'))).toBe(false);
  });

  it('should return true for dates in the future', function () {
    expect(futureValidator.validate('1.1.2900')).toBe(true);
    expect(futureValidator.validate('2030/12/31')).toBe(true);
    expect(futureValidator.validate('2030-12-31')).toBe(true);
    expect(futureValidator.validate(moment().add(10, 'seconds'))).toBe(true);
  });
});


================================================
FILE: src/core/validators/hibernateEmailValidator.js
================================================
angular.module('valdr')

  .factory('valdrHibernateEmailValidator', ['valdrUtil', function (valdrUtil) {
    var ATOM = '[a-z0-9!#$%&\'*+/=?^_`{|}~-]';
    var DOMAIN = '^' + ATOM + '+(\\.' + ATOM + '+)*$';
    var IP_DOMAIN = '^\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\]$';

    var localPattern = new RegExp('^' + ATOM + '+(\\.' + ATOM + '+)*$', 'i');
    var domainPattern = new RegExp(DOMAIN + '|' + IP_DOMAIN, 'i');

    return {
      name: 'hibernateEmail',

      /**
       * Checks if the value is a valid email address using the same patterns as Hibernate uses in its bean validation
       * implementation.
       *
       * @param value the value to validate
       * @returns {boolean} true if valid
       */
      validate: function (value) {
        if (valdrUtil.isEmpty(value)) {
          return true;
        }

        // split email at '@' and consider local and domain part separately
        var emailParts = value.split('@');
        if (emailParts.length !== 2) {
          return false;
        }

        if (!localPattern.test(emailParts[0])) {
          return false;
        }

        return domainPattern.test(emailParts[1]);
      }
    };
  }]);


================================================
FILE: src/core/validators/hibernateEmailValidator.spec.js
================================================
describe('valdrEmailValidator', function () {

  var hibernateEmailValidator;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrHibernateEmailValidator) {
    hibernateEmailValidator = valdrHibernateEmailValidator;
  }));

  it('should provide the correct name', function () {
    expect(hibernateEmailValidator.name).toBe('hibernateEmail');
  });

  it('should return true for empty values', function () {
    expect(hibernateEmailValidator.validate('')).toBe(true);
    expect(hibernateEmailValidator.validate(undefined)).toBe(true);
  });

  it('should return true for valid email addresses', function () {
    expect(hibernateEmailValidator.validate('hanueli@mountains.ch')).toBe(true);
    expect(hibernateEmailValidator.validate('valdr.welds@anything.com')).toBe(true);
    expect(hibernateEmailValidator.validate('hanueli@asdf')).toBe(true);
    expect(hibernateEmailValidator.validate('hanueli@192.168.1.1')).toBe(true);
  });

  it('should return false for invalid email addresses', function () {
    expect(hibernateEmailValidator.validate('hanueli@')).toBe(false);
    expect(hibernateEmailValidator.validate('hanueli@@gmail.com')).toBe(false);
    expect(hibernateEmailValidator.validate('hanueli@gm ail.com')).toBe(false);
    expect(hibernateEmailValidator.validate('hanueli@gmail..com')).toBe(false);
    expect(hibernateEmailValidator.validate('hanueli@gmail.com.')).toBe(false);
    expect(hibernateEmailValidator.validate('@anything.com')).toBe(false);
    expect(hibernateEmailValidator.validate('...')).toBe(false);
    expect(hibernateEmailValidator.validate('.@.')).toBe(false);
    expect(hibernateEmailValidator.validate(' aaa.@. ')).toBe(false);
    expect(hibernateEmailValidator.validate('hanueli.@gmail.com')).toBe(false);
  });

});


================================================
FILE: src/core/validators/hibernateUrlValidator.js
================================================
angular.module('valdr')

  .factory('valdrHibernateUrlValidator', ['valdrUrlValidator', function (valdrUrlValidator) {

    return {
      name: 'hibernateUrl',

      /**
       * Checks if the value is a valid URL according to the internal URL validator. It'll also ignore the Hibernate
       * properties protocol, host, and port. See https://github.com/netceteragroup/valdr/issues/27 for the rational
       * behind this implementation.
       *
       * @param value the value to validate
       * @returns {boolean} true if valid
       */
      validate: function (value) {
        return valdrUrlValidator.validate(value);
      }
    };
  }]);


================================================
FILE: src/core/validators/hibernateUrlValidator.spec.js
================================================
describe('valdrHibernateUrlValidator', function () {

  var urlValidator;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrHibernateUrlValidator) {
    urlValidator = valdrHibernateUrlValidator;
  }));

  it('should provide the correct name', function () {
    expect(urlValidator.name).toBe('hibernateUrl');
  });

  it('should return true for empty values', function () {
    expect(urlValidator.validate('')).toBe(true);
    expect(urlValidator.validate(undefined)).toBe(true);
  });

  it('should return true for valid url', function () {
    // some trivial tests
    expect(urlValidator.validate('http://www.google.ch')).toBe(true);
    expect(urlValidator.validate('http://server:123/path')).toBe(true);
    // some tests from http://djpowell.net/atomrdf/0.1/files/uritest.xml
    expect(urlValidator.validate('http://a/b/c/d;p?q#s')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/g?y#s')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/g..')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/d;p?y')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/g;x=1/y')).toBe(true);
  });

  it('should return false for invalid url', function () {
    expect(urlValidator.validate('a@B.c')).toBe(false);
    expect(urlValidator.validate('hanueli')).toBe(false);
  });

});


================================================
FILE: src/core/validators/maxLengthValidator.js
================================================
angular.module('valdr')

  .factory('valdrMaxLengthValidator', ['valdrUtil', function (valdrUtil) {
    return {
      name: 'maxLength',

      /**
       * Checks if the value is a string and if it's at most 'constraint.number' of characters long.
       *
       * @param value the value to validate
       * @param constraint with property 'number'
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {
        var maxLength = constraint.number;

        if (valdrUtil.isEmpty(value)) {
          return true;
        }

        if (typeof value === 'string') {
          return value.length <= maxLength;
        } else {
          return false;
        }
      }
    };
  }]);


================================================
FILE: src/core/validators/maxLengthValidator.spec.js
================================================
describe('valdrMaxLengthValidator', function () {

  var maxLengthValidator, constraint = {
    number: 5,
    message: 'message'
  };

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrMaxLengthValidator) {
    maxLengthValidator = valdrMaxLengthValidator;
  }));

  it('should provide the correct name', function () {
    expect(maxLengthValidator.name).toBe('maxLength');
  });

  it('should return true if value is valid', function () {
    // given
    var value = 'a';

    // when
    var valid = maxLengthValidator.validate(value, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should return false if string exceeds maxLength', function () {
    // given
    var value = 'super long string that exceeds maxLength';

    // when
    var valid = maxLengthValidator.validate(value, constraint);

    // then
    expect(valid).toBe(false);
  });

  it('should be valid if value is undefined', function () {
    // given

    // when
    var valid = maxLengthValidator.validate(undefined, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should be invalid if value is number', function () {
    // given
    constraint.number = 2;

    // when
    var valid = maxLengthValidator.validate(123, constraint);

    // then
    expect(valid).toBe(false);
  });

  it('should be valid if value is null', function () {
    // given

    // when
    var valid = maxLengthValidator.validate(null, constraint);

    // then
    expect(valid).toBe(true);
  });

});


================================================
FILE: src/core/validators/maxValidator.js
================================================
angular.module('valdr')

  .factory('valdrMaxValidator', ['valdrUtil', function (valdrUtil) {

    return {
      name: 'max',

      /**
       * Checks if the value is a number and lower or equal as the value specified in the constraint.
       *
       * @param value the value to validate
       * @param constraint the validation constraint
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {
        var maxValue = Number(constraint.value),
          valueAsNumber = Number(value);

        if (valdrUtil.isNaN(value)) {
          return false;
        }

        return valdrUtil.isEmpty(value) || valueAsNumber <= maxValue;
      }
    };
  }]);


================================================
FILE: src/core/validators/maxValidator.spec.js
================================================
describe('valdrMaxValidator', function () {

  var maxValidator, constraint;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrMaxValidator) {
    maxValidator = valdrMaxValidator;
    constraint = { value: '10' };
  }));

  it('should provide the correct name', function () {
    expect(maxValidator.name).toBe('max');
  });

  it('should return true for empty values', function () {
    expect(maxValidator.validate('', constraint)).toBe(true);
    expect(maxValidator.validate(null, constraint)).toBe(true);
    expect(maxValidator.validate(undefined, constraint)).toBe(true);
  });

  it('should return false for NaN', function () {
    expect(maxValidator.validate(NaN, constraint)).toBe(false);
  });

  it('should return true for valid numbers', function () {
    expect(maxValidator.validate('10', constraint)).toBe(true);
    expect(maxValidator.validate(10, constraint)).toBe(true);
    expect(maxValidator.validate('9', constraint)).toBe(true);
    expect(maxValidator.validate(9, constraint)).toBe(true);
    expect(maxValidator.validate('-9', constraint)).toBe(true);
    expect(maxValidator.validate(-9, constraint)).toBe(true);
    expect(maxValidator.validate('9.9999999', constraint)).toBe(true);
  });

  it('should return false for invalid numbers and strings', function () {
    expect(maxValidator.validate(10.001, constraint)).toBe(false);
    expect(maxValidator.validate('10.000001', constraint)).toBe(false);
    expect(maxValidator.validate('11', constraint)).toBe(false);
    expect(maxValidator.validate(11, constraint)).toBe(false);
    expect(maxValidator.validate('string', constraint)).toBe(false);
    expect(maxValidator.validate('number', constraint)).toBe(false);
  });

});


================================================
FILE: src/core/validators/minLengthValidator.js
================================================
angular.module('valdr')

  .factory('valdrMinLengthValidator', ['valdrUtil', function (valdrUtil) {
    return {
      name: 'minLength',

      /**
       * Checks if the value is a string and if it's at least 'constraint.number' of characters long.
       *
       * @param value the value to validate
       * @param constraint with property 'number'
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {
        var minLength = constraint.number;

        if (valdrUtil.isEmpty(value)) {
          return true;
        }

        if (typeof value === 'string') {
          return value.length >= minLength;
        } else {
          return false;
        }
      }
    };
  }]);


================================================
FILE: src/core/validators/minLengthValidator.spec.js
================================================
describe('valdrMinLengthValidator', function () {

  var minLengthValidator, constraint = {
    number: 5,
    message: 'message'
  };

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrMinLengthValidator) {
    minLengthValidator = valdrMinLengthValidator;
  }));

  it('should provide the correct name', function () {
    expect(minLengthValidator.name).toBe('minLength');
  });

  it('should return true if value is valid', function () {
    // given
    var value = 'valid-value';

    // when
    var valid = minLengthValidator.validate(value, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should return the validation result', function () {
    // given
    var value = 'a';

    // when
    var valid = minLengthValidator.validate(value, constraint);

    // then
    expect(valid).toBe(false);
  });

  it('should be valid if minLength is 0 and value undefined', function () {
    // given
    constraint.number = 0;

    // when
    var valid = minLengthValidator.validate(undefined, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should be invalid if value is number', function () {
    // given
    constraint.number = 4;

    // when
    var valid = minLengthValidator.validate(123, constraint);

    // then
    expect(valid).toBe(false);
  });

  it('should be valid if minLength is 0 and value null', function () {
    // given
    constraint.number = 0;

    // when
    var valid = minLengthValidator.validate(null, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should be valid if minLength is 1 and value undefined', function () {
    // given
    constraint.number = 1;

    // when
    var valid = minLengthValidator.validate(undefined, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should be valid if minLength is 1 and value null', function () {
    // given
    constraint.number = 1;

    // when
    var valid = minLengthValidator.validate(null, constraint);

    // then
    expect(valid).toBe(true);
  });

});


================================================
FILE: src/core/validators/minValidator.js
================================================
angular.module('valdr')

  .factory('valdrMinValidator', ['valdrUtil', function (valdrUtil) {

    return {
      name: 'min',

      /**
       * Checks if the value is a number and higher or equal as the value specified in the constraint.
       *
       * @param value the value to validate
       * @param constraint the validation constraint
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {
        var minValue = Number(constraint.value),
          valueAsNumber = Number(value);


        if (valdrUtil.isNaN(value)) {
          return false;
        }

        return valdrUtil.isEmpty(value) || valueAsNumber >= minValue;
      }
    };
  }]);


================================================
FILE: src/core/validators/minValidator.spec.js
================================================
describe('valdrMinValidator', function () {

  var minValidator, constraint;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrMinValidator) {
    minValidator = valdrMinValidator;
    constraint = { value: '10' };
  }));

  it('should provide the correct name', function () {
    expect(minValidator.name).toBe('min');
  });

  it('should return true for empty values', function () {
    expect(minValidator.validate('', constraint)).toBe(true);
    expect(minValidator.validate(null, constraint)).toBe(true);
    expect(minValidator.validate(undefined, constraint)).toBe(true);
  });

  it('should return false for NaN', function () {
    expect(minValidator.validate(NaN, constraint)).toBe(false);
  });

  it('should return true for valid numbers', function () {
    expect(minValidator.validate('10', constraint)).toBe(true);
    expect(minValidator.validate(10, constraint)).toBe(true);
    expect(minValidator.validate(10.001, constraint)).toBe(true);
    expect(minValidator.validate('10.000001', constraint)).toBe(true);
    expect(minValidator.validate('11', constraint)).toBe(true);
    expect(minValidator.validate(11, constraint)).toBe(true);
  });

  it('should return false for invalid numbers and strings', function () {
    expect(minValidator.validate('9', constraint)).toBe(false);
    expect(minValidator.validate(9, constraint)).toBe(false);
    expect(minValidator.validate('-9', constraint)).toBe(false);
    expect(minValidator.validate(-9, constraint)).toBe(false);
    expect(minValidator.validate('9.9999999', constraint)).toBe(false);
    expect(minValidator.validate('string', constraint)).toBe(false);
    expect(minValidator.validate('number', constraint)).toBe(false);
    expect(minValidator.validate(' ', constraint)).toBe(false);
  });

});


================================================
FILE: src/core/validators/pastValidator.js
================================================
angular.module('valdr')

  .factory('valdrPastValidator', ['futureAndPastSharedValidator', function (futureAndPastSharedValidator) {

    return {
      name: 'past',

      /**
       * Checks if the value is a date in the past.
       *
       * @param value the value to validate
       * @returns {boolean} true if empty, null, undefined or a date in the past, false otherwise
       */
      validate: function (value) {
        return futureAndPastSharedValidator.validate(value, function (valueAsMoment, now) {
          return valueAsMoment.isBefore(now);
        });
      }
    };
  }]);


================================================
FILE: src/core/validators/pastValidator.spec.js
================================================
describe('valdrPastValidator', function () {

  var pastValidator;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrPastValidator) {
    pastValidator = valdrPastValidator;
  }));

  it('should be named "past"', function () {
    expect(pastValidator.name).toBe('past');
  });

  it('should return true for empty values', function () {
    expect(pastValidator.validate('')).toBe(true);
    expect(pastValidator.validate(null)).toBe(true);
    expect(pastValidator.validate(undefined)).toBe(true);
  });

  it('should return false for NaN', function () {
    expect(pastValidator.validate(NaN)).toBe(false);
  });

  it('should return false for non-dates', function () {
    expect(pastValidator.validate(' ')).toBe(false);
    expect(pastValidator.validate('foo')).toBe(false);
    expect(pastValidator.validate('_1.1.2014')).toBe(false);
    expect(pastValidator.validate('31.2.2014')).toBe(false);
    expect(pastValidator.validate('31:1:2014')).toBe(false);
  });

  it('should return false for dates in the future', function () {
    expect(pastValidator.validate('1.1.2900')).toBe(false);
    expect(pastValidator.validate('2030/12/31')).toBe(false);
    expect(pastValidator.validate('2030-12-31')).toBe(false);
    expect(pastValidator.validate(moment().add(10, 'seconds'))).toBe(false);
  });

  it('should return true for dates in the past', function () {
    expect(pastValidator.validate('1.1.1900')).toBe(true);
    expect(pastValidator.validate('01.01.1900')).toBe(true);
    expect(pastValidator.validate('1. 1. 1900')).toBe(true);
    expect(pastValidator.validate('01. 01. 1900')).toBe(true);
    expect(pastValidator.validate('1-1-1900')).toBe(true);
    expect(pastValidator.validate('01-01-1900')).toBe(true);
    expect(pastValidator.validate('1/1/1900')).toBe(true);
    expect(pastValidator.validate('01/01/1900')).toBe(true);
    expect(pastValidator.validate('1900.1.1')).toBe(true);
    expect(pastValidator.validate('1900.01.01')).toBe(true);
    expect(pastValidator.validate('2000/12/31')).toBe(true);
    expect(pastValidator.validate('2000-12-31')).toBe(true);
    expect(pastValidator.validate(moment().subtract(10, 'seconds'))).toBe(true);
  });
});


================================================
FILE: src/core/validators/patternValidator.js
================================================
angular.module('valdr')

  .factory('valdrPatternValidator', ['valdrUtil', function (valdrUtil) {

    var REGEXP_PATTERN = /^\/(.*)\/([gim]*)$/;

    /**
     * Converts the given pattern to a RegExp.
     * The pattern can either be a RegExp object or a string containing a regular expression (`/regexp/`).
     * This implementation is based on the AngularJS ngPattern validator.
     * @param pattern the pattern
     * @returns {RegExp} the RegExp
     */
    var asRegExp = function (pattern) {
      var match;

      if (pattern.test) {
        return pattern;
      } else {
        match = pattern.match(REGEXP_PATTERN);
        if (match) {
          return new RegExp(match[1], match[2]);
        } else {
          throw ('Expected ' + pattern + ' to be a RegExp');
        }
      }
    };

    return {
      name: 'pattern',

      /**
       * Checks if the value matches the pattern defined in the constraint.
       *
       * @param value the value to validate
       * @param constraint the constraint with the regexp as value
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {
        var pattern = asRegExp(constraint.value);
        return valdrUtil.isEmpty(value) || pattern.test(value);
      }
    };
  }]);


================================================
FILE: src/core/validators/patternValidator.spec.js
================================================
describe('valdrPatternValidator', function () {

  var patternValidator,
    constraint = { value: '/^[a-z]+$/' };

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrPatternValidator) {
    patternValidator = valdrPatternValidator;
  }));

  it('should provide the correct name', function () {
    expect(patternValidator.name).toBe('pattern');
  });

  it('should return true for empty values', function () {
    expect(patternValidator.validate('', constraint)).toBe(true);
    expect(patternValidator.validate(undefined, constraint)).toBe(true);
  });

  it('should return true for matching values', function () {
    expect(patternValidator.validate('asd', constraint)).toBe(true);
  });

  it('should return false for not matching values', function () {
    expect(patternValidator.validate('123', constraint)).toBe(false);
  });

  it('should work with RegExp object in constraint', function () {
    constraint.value = /^[a-z]+$/;
    expect(patternValidator.validate('asd', constraint)).toBe(true);
    expect(patternValidator.validate('123', constraint)).toBe(false);
  });

  it('should throw when RegExp is invalid', function () {
    constraint.value = 'no RegExp';
    expect(function () {
      patternValidator.validate('asd', constraint);
    }).toThrow();
  });

});


================================================
FILE: src/core/validators/requiredValidator.js
================================================
angular.module('valdr')

  .factory('valdrRequiredValidator', ['valdrUtil', function (valdrUtil) {
    return {
      name: 'required',

      /**
       * Checks if the value is not empty.
       *
       * @param value the value to validate
       * @returns {boolean} true if the value is not empty
       */
      validate: function (value) {
        return valdrUtil.notEmpty(value);
      }
    };
  }]);


================================================
FILE: src/core/validators/requiredValidator.spec.js
================================================
describe('valdrRequiredValidator', function () {

  var requiredValidator, valdrUtil;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrRequiredValidator, _valdrUtil_) {
    requiredValidator = valdrRequiredValidator;
    valdrUtil = _valdrUtil_;
  }));

  it('should provide the correct name', function () {
    expect(requiredValidator.name).toBe('required');
  });

  it('should validate using the valdrUtil', function () {
    // given
    spyOn(valdrUtil, 'notEmpty');
    var value = 'someValue';

    // when
    requiredValidator.validate(value);

    // then
    expect(valdrUtil.notEmpty).toHaveBeenCalledWith(value);
  });

  it('should return the validation result', function () {
    expect(requiredValidator.validate('value')).toBe(true);
  });

});


================================================
FILE: src/core/validators/sizeValidator.js
================================================
angular.module('valdr')

  .factory('valdrSizeValidator', ['valdrUtil', function (valdrUtil) {
    return {
      name: 'size',

      /**
       * Checks if the values length is in the range specified by the constraints min and max properties.
       *
       * @param value the value to validate
       * @param constraint with optional values: min, max
       * @returns {boolean} true if valid
       */
      validate: function (value, constraint) {
        var minLength = constraint.min || 0,
          maxLength = constraint.max;

        value = value || '';

        if (valdrUtil.isEmpty(value)) {
          return true;
        }

        return value.length >= minLength &&
          (maxLength === undefined || value.length <= maxLength);
      }
    };
  }]);


================================================
FILE: src/core/validators/sizeValidator.spec.js
================================================
describe('valdrSizeValidator', function () {

  var sizeValidator, constraint = {
    min: 5,
    max: 20,
    message: 'message'
  };

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrSizeValidator) {
    sizeValidator = valdrSizeValidator;
  }));

  it('should provide the correct name', function () {
    expect(sizeValidator.name).toBe('size');
  });

  it('should return true if value is valid', function () {
    // given
    var value = 'valid-value';

    // when
    var valid = sizeValidator.validate(value, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should return the validation result', function () {
    // given
    var value = 'a';

    // when
    var valid = sizeValidator.validate(value, constraint);

    // then
    expect(valid).toBe(false);
  });

  it('should be valid if min is 0 and value undefined', function () {
    // given
    constraint.min = 0;

    // when
    var valid = sizeValidator.validate(undefined, constraint);

    // then
    expect(valid).toBe(true);
  });

  it('should be valid if min is 1 and value undefined', function () {
    // given
    constraint.min = 1;

    // when
    var valid = sizeValidator.validate(undefined, constraint);

    // then
    expect(valid).toBe(true);
  });

});


================================================
FILE: src/core/validators/urlValidator.js
================================================
angular.module('valdr')

  .factory('valdrUrlValidator', ['valdrUtil', function (valdrUtil) {

    // the url pattern used in angular.js
    var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;

    return {
      name: 'url',

      /**
       * Checks if the value is a valid url.
       *
       * @param value the value to validate
       * @returns {boolean} true if valid
       */
      validate: function (value) {
        return valdrUtil.isEmpty(value) || URL_REGEXP.test(value);
      }
    };
  }]);


================================================
FILE: src/core/validators/urlValidator.spec.js
================================================
describe('valdrUrlValidator', function () {

  var urlValidator;

  beforeEach(module('valdr'));

  beforeEach(inject(function (valdrUrlValidator) {
    urlValidator = valdrUrlValidator;
  }));

  it('should provide the correct name', function () {
    expect(urlValidator.name).toBe('url');
  });

  it('should return true for empty values', function () {
    expect(urlValidator.validate('')).toBe(true);
    expect(urlValidator.validate(undefined)).toBe(true);
  });

  it('should return true for valid url', function () {
    // some trivial tests
    expect(urlValidator.validate('http://www.google.ch')).toBe(true);
    expect(urlValidator.validate('http://server:123/path')).toBe(true);
    // some tests from http://djpowell.net/atomrdf/0.1/files/uritest.xml
    expect(urlValidator.validate('http://a/b/c/d;p?q#s')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/g?y#s')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/g..')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/d;p?y')).toBe(true);
    expect(urlValidator.validate('http://a/b/c/g;x=1/y')).toBe(true);
  });

  it('should return false for invalid url', function () {
    expect(urlValidator.validate('a@B.c')).toBe(false);
    expect(urlValidator.validate('hanueli')).toBe(false);
  });

});


================================================
FILE: src/message/valdrMessage-directive.js
================================================
/**
 * This directive appends a validation message to the parent element of any input, select or textarea element, which
 * is nested in a valdr-type directive and has an ng-model bound to it.
 * If the form element is wrapped in an element marked with the class defined in valdrClasses.formGroup,
 * the messages is appended to this element instead of the direct parent.
 * To prevent adding messages to specific input fields, the attribute 'valdr-no-message' can be added to those input
 * or select fields. The valdr-message directive is used to do the actual rendering of the violation messages.
 */
var valdrMessageDirectiveDefinitionFactory = function (restrict) {
    return ['$compile', function ($compile) {
      return {
        restrict: restrict,
        require: ['?^valdrType', '?^ngModel', '?^valdrFormGroup'],
        link: function (scope, element, attrs, controllers) {

          var valdrTypeController = controllers[0],
            ngModelController = controllers[1],
            valdrFormGroupController = controllers[2],
            valdrNoValidate = attrs.valdrNoValidate,
            valdrNoMessage = attrs.valdrNoMessage,
            fieldName = attrs.name;

          /**
           * Don't do anything if
           * - this is an <input> that's not inside of a valdr-type or valdr-form-group block
           * - there is no ng-model bound to input
           * - there is a 'valdr-no-validate' or 'valdr-no-message' attribute present
           */
          if (!valdrTypeController || !valdrFormGroupController || !ngModelController ||
            angular.isDefined(valdrNoValidate) || angular.isDefined(valdrNoMessage)) {
            return;
          }

          var valdrMessageElement = angular.element('<span valdr-message="' + fieldName + '"></span>');
          $compile(valdrMessageElement)(scope);
          valdrFormGroupController.addMessageElement(ngModelController, valdrMessageElement);

          scope.$on('$destroy', function () {
            valdrFormGroupController.removeMessageElement(ngModelController);
          });

        }
      };
    }];
  },
  valdrMessageElementDirectiveDefinition = valdrMessageDirectiveDefinitionFactory('E'),
  valdrMessageAttributeDirectiveDefinition = valdrMessageDirectiveDefinitionFactory('A');


var nullValdrType = {
  getType: angular.noop
};

angular.module('valdr')
  .directive('input', valdrMessageElementDirectiveDefinition)
  .directive('select', valdrMessageElementDirectiveDefinition)
  .directive('textarea', valdrMessageElementDirectiveDefinition)
  .directive('enableValdrMessage', valdrMessageAttributeDirectiveDefinition)

/**
 * The valdr-message directive is responsible for the rendering of violation messages. The template used for rendering
 * is defined in the valdrMessage service where it can be overridden or a template URL can be configured.
 */
  .directive('valdrMessage',
  ['$rootScope', '$injector', 'valdrMessage', 'valdrUtil', function ($rootScope, $injector, valdrMessage, valdrUtil) {
    return {
      replace: true,
      restrict: 'A',
      scope: {
        formFieldName: '@valdrMessage'
      },
      templateUrl: function () {
        return valdrMessage.templateUrl;
      },
      require: ['^form', '?^valdrType'],
      link: function (scope, element, attrs, controllers) {
        var formController = controllers[0],
          valdrTypeController = controllers[1] || nullValdrType;

        var updateTranslations = function () {
          if (valdrMessage.translateAvailable && angular.isArray(scope.violations)) {
            angular.forEach(scope.violations, function (violation) {
              valdrMessage.$translate(valdrMessage.fieldNameKeyGenerator(violation))
                .then(function (translation) {
                  violation.fieldName = translation;
                })
                .catch(angular.noop);
            });
          }
        };

        var createViolation = function (validatorName) {
          var typeName = valdrTypeController.getType(),
            fieldName = scope.formFieldName;

          return {
            type: typeName,
            field: fieldName,
            validator: validatorName,
            message: valdrMessage.getMessage(typeName, fieldName, validatorName)
          };
        };

        var addViolationsToScope = function () {
          scope.violations = [];

          angular.forEach(scope.formField.valdrViolations, function (violation) {
            scope.violations.push(violation);
          });

          if (valdrMessage.angularMessagesEnabled) {
            angular.forEach(scope.formField.$error, function (isValid, validatorName) {
              if (!valdrUtil.startsWith(validatorName, 'valdr')) {
                scope.violations.push(createViolation(validatorName));
              }
            });
          }

          scope.violation = scope.violations[0];
          updateTranslations();
        };
        var removeViolationsFromScope = function () {
          scope.violations = undefined;
          scope.violation = undefined;
        };

        var watchFormFieldErrors = function () {
          scope.formField = formController[scope.formFieldName];
          if (scope.formField) {
            return {
              valdr: scope.formField.valdrViolations,
              error: scope.formField.$error
            };
          }
        };


        scope.$watch(watchFormFieldErrors, function () {
          if (scope.formField && scope.formField.$invalid) {
            addViolationsToScope();
          } else {
            removeViolationsFromScope();
          }
        }, true);

        var unregisterTranslateChangeHandler = $rootScope.$on('$translateChangeSuccess', function () {
          updateTranslations();
        });

        scope.$on('$destroy', function () {
          unregisterTranslateChangeHandler();
        });
      }
    };
  }]);


================================================
FILE: src/message/valdrMessage-directive.spec.js
================================================
describe('valdrMessage input directive', function () {

  var $scope, $compile;

  beforeEach(module('valdr'));

  var compileTemplate = function (template) {
    var element = $compile(angular.element(template))($scope);
    $scope.$digest();
    return element;
  };

  beforeEach(inject(function ($rootScope, _$compile_) {
    $compile = _$compile_;
    $scope = $rootScope.$new();
    $scope.myObject = { field: 'fieldValue' };
  }));


  it('should add a the valdr-message directive after the input field and bind it to the correct form field',
    function () {
      // given
      var element = compileTemplate(
        '<form name="demoForm">' +
          '<div valdr-type="TestClass" valdr-form-group>' +
            '<input type="text" name="fieldName" ng-model="myObject.field">' +
          '</div>' +
        '</form>'
      );

      // when
      var nextElement = element.find('input').next()[0];

      //then
      expect(nextElement.attributes['valdr-message']).toBeDefined();
      expect(nextElement.attributes['valdr-message'].value).toBe('fieldName');
    });


  it('should add a the valdr-message directive to the form group if there is one', function () {
    // given
    var element = compileTemplate(
            '<form name="demoForm">' +
              '<section valdr-form-group>' +
                '<div valdr-type="TestClass">' +
                  '<input type="text" name="fieldName" ng-model="myObject.field">' +
                '</div>' +
              '</section>' +
            '</form>'
    );

    // when
    var formGroupChildren = element.find('section').children();
    var lastInFormGroup = formGroupChildren[formGroupChildren.length - 1];

    //then
    expect(lastInFormGroup.attributes['valdr-message']).toBeDefined();
    expect(lastInFormGroup.attributes['valdr-message'].value).toBe('fieldName');
  });

  it('should NOT add a the valdr-message after the input if valdr-no-message is set', function () {
    // given
    var element = compileTemplate(
      '<form name="demoForm">' +
        '<div valdr-form-group valdr-type="TestClass">' +
        '<input type="text" name="fieldName" ng-model="myObject.field" valdr-no-message>' +
        '</div>' +
      '</form>'
    );

    // when
    var nextElement = element.find('input').next();

    //then
    expect(nextElement.length).toBe(0);
  });

  it('should NOT add a the valdr-message after the input is not wrapped in a valdr-type', function () {
    // given
    var element = compileTemplate(
      '<form name="demoForm">' +
        '<div>' +
        '<input type="text" name="fieldName" ng-model="myObject.field">' +
        '</div>' +
      '</form>'
    );

    // when
    var nextElement = element.find('input').next();

    //then
    expect(nextElement.length).toBe(0);
  });

  it('should NOT add valdr-message after the input if valdr-no-validate is set', function () {
    // given
    var element = compileTemplate(
        '<form name="demoForm">' +
          '<div valdr-type="TestClass" valdr-form-group>' +
            '<input type="text" name="fieldName" ng-model="myObject.field" valdr-no-validate>' +
          '</div>' +
        '</form>'
    );
    // when
    var nextElement = element.find('input').next();

    //then
    expect(nextElement.length).toBe(0);
  });

});

describe('valdrMessage directive', function () {

  var $scope, $compile, valdrMessage;

  beforeEach(module('valdr'));

  var compileTemplate = function (template) {
    var html = template ||
      '<form name="testForm">' +
        '<input type="text" name="testField" ng-model="model">' +
        '<span valdr-message="testField"></span>' +
      '</form>';
    var element = $compile(angular.element(html))($scope);
    $scope.$digest();
    return element;
  };

  var addViolations = function () {
    // manually add some violations for the tests (these are usually added if the field is invalid)
    $scope.testForm.testField.valdrViolations = [
      { message: 'message-1' },
      { message: 'message-2' }
    ];
    $scope.testForm.testField.$error = { valdr: [] };
    $scope.testForm.testField.$invalid = true;
    $scope.$digest();
  };

  var compileTemplateAndAddViolations = function (template) {
    var element = compileTemplate(template);
    addViolations();
    return element;
  };

  beforeEach(inject(function ($rootScope, _$compile_, _valdrMessage_) {
    $compile = _$compile_;
    $scope = $rootScope.$new();
    valdrMessage = _valdrMessage_;
  }));

  it('should display the first message in the default template', function () {
    // when
    var element = compileTemplateAndAddViolations();

    // then
    expect(element.find('div').html()).toBe('message-1');
  });

  it('should update the messages in the default template', function () {
    // given
    var element = compileTemplateAndAddViolations();

    // when
    $scope.testForm.testField.valdrViolations[0].message = 'updatedMessage';
    $scope.$digest();

    // then
    expect(element.find('div').html()).toBe('updatedMessage');
  });

  it('should support custom templates', function () {
    // given
    valdrMessage.setTemplate('<div>Number of violations: {{ violations.length }}</div>');

    // when
    var element = compileTemplateAndAddViolations();

    // then
    expect(element.find('div').html()).toBe('Number of violations: 2');
  });


  it('should support dynamically added form items', function () {
    // given
    $scope.isVisible = false;
    var element = compileTemplate(
      '<form name="testForm">' +
        '<input type="text" ng-if="isVisible" name="testField" ng-model="model">' +
        '<span valdr-message="testField"></span>' +
      '</form>');

    // when
    $scope.isVisible = true;
    $scope.$digest();
    addViolations();

    // then
    expect(element.find('div').html()).toBe('message-1');
  });


  it('should show messages for angular validators', function () {
    // given
    valdrMessage.angularMessagesEnabled = true;
    valdrMessage.addMessages({
      'required': 'This field is required'
    });
    var template =
      '<form name="testForm">' +
        '<input required type="text" name="testField" ng-model="model">' +
        '<span valdr-message="testField"></span>' +
      '</form>';

    // when
    var element = $compile(angular.element(template))($scope);
    $scope.$digest();

    // then
    expect(element.find('div').html()).toBe('This field is required');
  });

  it('should not show messages for angular validators by default', function () {
    // given
    valdrMessage.addMessages({
      'required': 'This field is required'
    });
    var template =
      '<form name="testForm">' +
        '<input required type="text" name="testField" ng-model="model">' +
        '<span valdr-message="testField"></span>' +
      '</form>';

    // when
    var element = $compile(angular.element(template))($scope);
    $scope.$digest();

    // then
    expect(element.find('div').html()).toBe('');
  });

});

describe('valdrMessage directive with angular-translate', function () {

  var $scope, $compile, $translate, valdrMessage;

  beforeEach(function () {
    module('valdr');
    module('pascalprecht.translate');
    module(function ($translateProvider) {
      $translateProvider.translations('en', {
        'message-1': '{{fieldName}} english.',
        'message-2': 'field: {{fieldName}} param: {{param}} secondParam: {{secondParam}}',
        'Person.testField': 'Field Name'
      });

      $translateProvider.translations('de', {
        'message-1': '{{fieldName}} deutsch.',
        'message-2': 'field: {{fieldName}} param: {{param}} secondParam: {{secondParam}}',
        'Person.testField': 'Feldname'
      });

      $translateProvider.preferredLanguage('en');
    });
  });

  var compileTemplate = function () {
    var html =
      '<form name="testForm">' +
        '<input type="text" name="testField" ng-model="model">' +
        '<span valdr-message="testField"></span>' +
      '</form>';
    var element = $compile(angular.element(html))($scope);

    $scope.testForm.testField.valdrViolations = [
      { message: 'message-1', field: 'testField', type: 'Person', param: '2' },
      { message: 'message-2', field: 'testField', type: 'Person', param: '3' }
    ];
    $scope.testForm.testField.$error = { valdr: [] };
    $scope.testForm.testField.$invalid = true;
    $scope.$digest();
    return element;
  };

  beforeEach(inject(function ($rootScope, _$compile_, _valdrMessage_, _$translate_) {
    $compile = _$compile_;
    $scope = $rootScope.$new();
    $translate = _$translate_;
    valdrMessage = _valdrMessage_;
  }));

  it('should translate the field name', function () {
    // given
    valdrMessage.setTemplate('<div>{{ violations[0].fieldName }}</div>');

    // when
    var element = compileTemplate();

    // then
    expect(element.find('div').html()).toBe('Field Name');
  });

  it('should translate the field name to german', function () {
    // given
    $translate.use('de');
    valdrMessage.setTemplate('<div>{{ violations[0].fieldName }}</div>');

    // when
    var element = compileTemplate();

    // then
    expect(element.find('div').html()).toBe('Feldname');
  });

  it('should update field names on language switch at runtime', function () {
    // given
    valdrMessage.setTemplate('<div>{{ violations[0].fieldName }}</div>');
    var element = compileTemplate();
    expect(element.find('div').html()).toBe('Field Name');

    // when
    $translate.use('de');
    $scope.$digest();

    // then
    expect(element.find('div').html()).toBe('Feldname');
  });


  it('should allow to use parameters in the translated messages', function () {
    // given / when
    var element = compileTemplate();
    $scope.testForm.testField.valdrViolations = [
      // note: message-2 has parameters defined in the translation tables
      { message: 'message-2', field: 'testField', type: 'Person', param: '3', secondParam: '4' }
    ];
    $scope.$digest();

    // then
    expect(element.find('span').html()).toBe('field: Field Name param: 3 secondParam: 4');
  });

  it('should unregister translate change handler on destroy to avoid potential memory leaks', function () {
    // given
    var element = compileTemplate();
    spyOn(valdrMessage, '$translate').andReturn({ then: function () {} });
    $scope.$destroy();
    element.remove();

    // when
    $translate.use('de');
    $scope.$digest();

    // then
    expect(valdrMessage.$translate).not.toHaveBeenCalled();
  });

});

describe('valdrMessage directive with angular-translate and valdrFieldNameKeyGenerator', function () {

  var $scope, $compile, $translate, valdrMessage;

  var fieldNameKeyGenerator = function(violation) {
    return violation.type + '.' + violation.field + '.' + violation.validator + 'Name';
  };

  beforeEach(function () {
    module('valdr');
    module('pascalprecht.translate');
    module(function ($translateProvider, $provide) {
      $provide.factory('valdrFieldNameKeyGenerator', function() { return fieldNameKeyGenerator; });

      $translateProvider.translations('en', {
        'message-1': '{{fieldName}} english.',
        'message-2': 'field: {{fieldName}} param: {{param}} secondParam: {{secondParam}}',
        'Person.testField': 'Field Name',
        'Person.testField.requiredName': 'The Field Name'
      });

      $translateProvider.translations('de', {
        'message-1': '{{fieldName}} deutsch.',
        'message-2': 'field: {{fieldName}} param: {{param}} secondParam: {{secondParam}}',
        'Person.testField': 'Feldname',
        'Person.testField.requiredName': 'Der Feldname'
      });

      $translateProvider.preferredLanguage('en');
    });
  });

  var compileTemplate = function () {
    var html =
      '<form name="testForm">' +
      '<input type="text" name="testField" ng-model="model">' +
      '<span valdr-message="testField"></span>' +
      '</form>';
    var element = $compile(angular.element(html))($scope);

    $scope.testForm.testField.valdrViolations = [
      { message: 'message-1', field: 'testField', type: 'Person', param: '2', validator: 'required' },
      { message: 'message-2', field: 'testField', type: 'Person', param: '3', validator: 'required' }
    ];
    $scope.testForm.testField.$error = { valdr: [] };
    $scope.testForm.testField.$invalid = true;
    $scope.$digest();
    return element;
  };

  beforeEach(inject(function ($rootScope, _$compile_, _valdrMessage_, _$translate_) {
    $compile = _$compile_;
    $scope = $rootScope.$new();
    $translate = _$translate_;
    valdrMessage = _valdrMessage_;
  }));

  it('should translate the field name using the fieldNameKeyGenerator', function () {
    // given
    valdrMessage.setTemplate('<div>{{ violations[0].fieldName }}</div>');

    // when
    var element = compileTemplate();

    // then
    expect(element.find('div').html()).toBe('The Field Name');
  });

  it('should translate the field name to german using the fieldNameKeyGenerator', function () {
    // given
    $translate.use('de');
    valdrMessage.setTemplate('<div>{{ violations[0].fieldName }}</div>');

    // when
    var element = compileTemplate();

    // then
    expect(element.find('div').html()).toBe('Der Feldname');
  });

  it('should update field names on language switch at runtime', function () {
    // given
    valdrMessage.setTemplate('<div>{{ violations[0].fieldName }}</div>');
    var element = compileTemplate();
    expect(element.find('div').html()).toBe('The Field Name');

    // when
    $translate.use('de');
    $scope.$digest();

    // then
    expect(element.find('div').html()).toBe('Der Feldname');
  });


  it('should allow to use parameters in the translated messages', function () {
    // given / when
    var element = compileTemplate();
    $scope.testForm.testField.valdrViolations = [
      // note: message-2 has parameters defined in the translation tables
      { message: 'message-2', field: 'testField', type: 'Person', param: '3', secondParam: '4', validator: 'required' }
    ];
    $scope.$digest();

    // then
    expect(element.find('span').html()).toBe('field: The Field Name param: 3 secondParam: 4');
  });

});


================================================
FILE: src/message/valdrMessage-service.js
================================================
angular.module('valdr')

/**
 * This service provides shared configuration between all valdr-message directive instances like the configured
 * template to render the violation messages and whether or not angular-translate is available.
 */
  .provider('valdrMessage', function () {

    var userDefinedTemplateUrl, userDefinedTemplate,
      messages = {},
      defaultTemplateUrl = 'valdr/default-message.html',
      defaultTemplate =   '<div class="valdr-message">' +
                            '{{ violation.message }}' +
                          '</div>',
      translateTemplate = '<div class="valdr-message" ng-show="violation">' +
                            '<span ' +
                            'translate="{{ violation.message }}" ' +
                            'translate-values="violation"></span>' +
                          '</div>';

    this.setTemplate = function (template) {
      userDefinedTemplate = template;
    };

    this.setTemplateUrl = function (templateUrl) {
      userDefinedTemplateUrl = templateUrl;
    };

    this.addMessages = function (newMessages) {
      angular.extend(messages, newMessages);
    };
    var addMessages = this.addMessages;

    this.getMessage = function (typeName, fieldName, validatorName) {
      var fullMessageKey = typeName + '.' + fieldName + '.' + validatorName;
      return messages[fullMessageKey] || messages[validatorName] || '[' + validatorName + ']';
    };
    var getMessage = this.getMessage;

    this.$get = ['$templateCache', '$injector', function ($templateCache, $injector) {

      var angularMessagesEnabled = false;

      function getTranslateService() {
        try {
          return $injector.get('$translate');
        } catch (error) {
          return undefined;
        }
      }

      function getFieldNameKeyGenerator() {
        try {
          return $injector.get('valdrFieldNameKeyGenerator');
        } catch (error) {
          return function(violation) {
            return violation.type + '.' + violation.field;
          };
        }
      }

      var $translate = getTranslateService(),
        translateAvailable = angular.isDefined($translate),
        fieldNameKeyGenerator = getFieldNameKeyGenerator();

      function determineTemplate() {
        if (angular.isDefined(userDefinedTemplate)) {
          return userDefinedTemplate;
        } else if (translateAvailable) {
          return translateTemplate;
        } else {
          return defaultTemplate;
        }
      }

      function updateTemplateCache() {
        $templateCache.put(defaultTemplateUrl, determineTemplate());
        if (userDefinedTemplateUrl && userDefinedTemplate) {
          $templateCache.put(userDefinedTemplateUrl, userDefinedTemplate);
        }
      }

      updateTemplateCache();

      return {
        templateUrl: userDefinedTemplateUrl || defaultTemplateUrl,
        setTemplate: function (newTemplate) {
          userDefinedTemplate = newTemplate;
          updateTemplateCache();
        },
        translateAvailable: translateAvailable,
        $translate: $translate,
        fieldNameKeyGenerator: fieldNameKeyGenerator,
        addMessages: addMessages,
        getMessage: getMessage,
        angularMessagesEnabled: angularMessagesEnabled
      };
    }];
  });


================================================
FILE: src/message/valdrMessage-service.spec.js
================================================
describe('valdrMessage service', function () {

  var valdrMessage;

  beforeEach(module('valdr'));
  beforeEach(inject(function (_valdrMessage_) {
    valdrMessage = _valdrMessage_;
  }));

  it('should provide the default templateUrl', function () {
    expect(valdrMessage.templateUrl).toBe('valdr/default-message.html');
  });

  it('should detect that $translate is not available', function () {
    expect(valdrMessage.translateAvailable).toBe(false);
  });

  it('should provide undefined for $translate', function () {
    expect(valdrMessage.$translate).toBeUndefined();
  });

  it('should populate the $templateCache with the default template', function () {
    inject(function ($templateCache) {
      // given
      var templateUrl = valdrMessage.templateUrl;

      // when
      var template = $templateCache.get(templateUrl);

      // then
      expect(template).toBeDefined();
      expect(template).toBe('<div class="valdr-message">{{ violation.message }}</div>');
    });
  });

});

describe('valdrMessage service with angular-translate', function () {

  var valdrMessage;

  beforeEach(module('valdr'));
  beforeEach(module('pascalprecht.translate'));
  beforeEach(inject(function (_valdrMessage_) {
    valdrMessage = _valdrMessage_;
  }));

  it('should provide the default templateUrl', function () {
    expect(valdrMessage.templateUrl).toBe('valdr/default-message.html');
  });

  it('should detect that $translate is available', function () {
    expect(valdrMessage.translateAvailable).toBe(true);
  });

  it('should provide $translate service', function () {
    expect(valdrMessage.$translate).toBeDefined();
    expect(typeof valdrMessage.$translate).toBe('function');
  });

  it('should populate the $templateCache with the angular-translate template', function () {
    inject(function ($templateCache) {
      // given
      var templateUrl = valdrMessage.templateUrl;

      // when
      var template = $templateCache.get(templateUrl);

      // then
      expect(template).toBeDefined();
      expect(template).toBe('<div class="valdr-message" ng-show="violation">' +
                              '<span ' +
                              'translate="{{ violation.message }}" ' +
                              'translate-values="violation"></span>' +
                            '</div>');
    });
  });

});

describe('valdrMessageProvider', function () {

  it('should provide the custom template URL and NOT populate $templateCache', function () {
    // given
    var customTemplateUrl = 'custom/template.html';
    module('valdr');

    // when
    module(function (valdrMessageProvider) {
      valdrMessageProvider.setTemplateUrl(customTemplateUrl);
    });

    // then
    inject(function (valdrMessage, $templateCache) {
      expect(valdrMessage.templateUrl).toBe(customTemplateUrl);
      expect($templateCache.get(valdrMessage.templateUrl)).toBeUndefined();
    });
  });

  it('should populate $templateCache with the custom template', function () {
    // given
    var customTemplate = '<div>my template</div>';
    module('valdr');

    // when
    module(function (valdrMessageProvider) {
      valdrMessageProvider.setTemplate(customTemplate);
    });

    // then
    inject(function (valdrMessage, $templateCache) {
      var template = $templateCache.get(valdrMessage.templateUrl);
      expect(template).toBe(customTemplate);
    });
  });


  it('should populate $templateCache with the custom template and custom URL', function () {
    // given
    var customTemplate = '<div>my template</div>';
    var customTemplateUrl = 'custom/my-template.html';
    module('valdr');

    // when
    module(function (valdrMessageProvider) {
      valdrMessageProvider.setTemplateUrl(customTemplateUrl);
      valdrMessageProvider.setTemplate(customTemplate);
    });

    // then
    inject(function (valdrMessage, $templateCache) {
      expect(valdrMessage.templateUrl).toBe(customTemplateUrl);
      var template = $templateCache.get(valdrMessage.templateUrl);
      expect(template).toBe(customTemplate);
    });
  });

  it('should allow to add and get messages', function () {
    // given
    module('valdr');

    // when
    module(function (valdrMessageProvider) {
      valdrMessageProvider.addMessages({
        'validator': 'validator-global-error-message',
        'Type.field.validator': 'fully-qualified-error-message'
      });
    });

    // then
    inject(function (valdrMessage) {
      expect(valdrMessage.getMessage('Type', 'field', 'validator')).toEqual('fully-qualified-error-message');
      expect(valdrMessage.getMessage(undefined, undefined, 'validator')).toEqual('validator-global-error-message');
      expect(valdrMessage.getMessage(undefined, undefined, 'unknown')).toEqual('[unknown]');
    });

  });

});


================================================
FILE: src/valdr.js
================================================
angular.module('valdr', ['ng'])
  .constant('valdrEvents', {
    'revalidate': 'valdr-revalidate'
  })
  .value('valdrConfig', {
    addFormGroupClass: true
  })
  .value('valdrClasses', {
    formGroup: 'form-group',
    valid: 'ng-valid',
    invalid: 'ng-invalid',
    dirty: 'ng-dirty',
    pristine: 'ng-pristine',
    touched: 'ng-touched',
    untouched: 'ng-untouched',
    invalidDirtyTouchedGroup: 'valdr-invalid-dirty-touched-group'
  });

================================================
FILE: src/valdr.spec.js
================================================
describe('valdr', function () {

  beforeEach(module('valdr'));

  it('should provide constant for validation events', inject(function (valdrEvents) {
    expect(valdrEvents).toBeDefined();
  }));

  it('should provide constant for revalidate event', inject(function (valdrEvents) {
    expect(valdrEvents.revalidate).toBe('valdr-revalidate');
  }));

  it('should provide a value for valdrClasses', inject(function (valdrClasses) {
    expect(valdrClasses.valid).toBe('ng-valid');
    expect(valdrClasses.invalid).toBe('ng-invalid');
    expect(valdrClasses.invalidDirtyTouchedGroup).toBe('valdr-invalid-dirty-touched-group');
  }));

});
Download .txt
gitextract_m3qzmqq7/

├── .bowerrc
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── demo/
│   ├── core/
│   │   ├── custom-validator.html
│   │   ├── list.html
│   │   ├── load-constraints.html
│   │   └── simple.html
│   ├── demo-api.js
│   ├── demoConstraints.json
│   ├── index.html
│   └── message/
│       ├── angular-translate.html
│       ├── angular-validation.html
│       ├── message-template.tpl.html
│       └── simple.html
├── js.prefix
├── js.suffix
├── karma.conf.js
├── package.json
├── server.js
└── src/
    ├── core/
    │   ├── valdr-service.js
    │   ├── valdr-service.spec.js
    │   ├── valdrEnabled-directive.js
    │   ├── valdrEnabled-directive.spec.js
    │   ├── valdrFormGroup-directive.js
    │   ├── valdrFormGroup-directive.spec.js
    │   ├── valdrFormItem-directive.js
    │   ├── valdrFormItem-directive.spec.js
    │   ├── valdrType-directive.js
    │   ├── valdrType-directive.spec.js
    │   ├── valdrUtil-service.js
    │   ├── valdrUtil-service.spec.js
    │   └── validators/
    │       ├── digitsValidator.js
    │       ├── digitsValidator.spec.js
    │       ├── emailValidator.js
    │       ├── emailValidator.spec.js
    │       ├── futureAndPastSharedValidator.js
    │       ├── futureValidator.js
    │       ├── futureValidator.spec.js
    │       ├── hibernateEmailValidator.js
    │       ├── hibernateEmailValidator.spec.js
    │       ├── hibernateUrlValidator.js
    │       ├── hibernateUrlValidator.spec.js
    │       ├── maxLengthValidator.js
    │       ├── maxLengthValidator.spec.js
    │       ├── maxValidator.js
    │       ├── maxValidator.spec.js
    │       ├── minLengthValidator.js
    │       ├── minLengthValidator.spec.js
    │       ├── minValidator.js
    │       ├── minValidator.spec.js
    │       ├── pastValidator.js
    │       ├── pastValidator.spec.js
    │       ├── patternValidator.js
    │       ├── patternValidator.spec.js
    │       ├── requiredValidator.js
    │       ├── requiredValidator.spec.js
    │       ├── sizeValidator.js
    │       ├── sizeValidator.spec.js
    │       ├── urlValidator.js
    │       └── urlValidator.spec.js
    ├── message/
    │   ├── valdrMessage-directive.js
    │   ├── valdrMessage-directive.spec.js
    │   ├── valdrMessage-service.js
    │   └── valdrMessage-service.spec.js
    ├── valdr.js
    └── valdr.spec.js
Download .txt
SYMBOL INDEX (11 symbols across 5 files)

FILE: demo/demo-api.js
  function answer (line 7) | function answer(code, data) {

FILE: src/core/valdrEnabled-directive.spec.js
  function compileValdrEnabledTemplate (line 18) | function compileValdrEnabledTemplate() {

FILE: src/core/valdrFormGroup-directive.spec.js
  function compileTemplate (line 33) | function compileTemplate(template) {
  function compileFormGroupTemplate (line 38) | function compileFormGroupTemplate() {

FILE: src/core/valdrFormItem-directive.spec.js
  function compileTemplate (line 45) | function compileTemplate(template) {
  function compileInputTemplate (line 50) | function compileInputTemplate() {
  function runFormItemCommonTests (line 87) | function runFormItemCommonTests() {

FILE: src/message/valdrMessage-service.js
  function getTranslateService (line 44) | function getTranslateService() {
  function getFieldNameKeyGenerator (line 52) | function getFieldNameKeyGenerator() {
  function determineTemplate (line 66) | function determineTemplate() {
  function updateTemplateCache (line 76) | function updateTemplateCache() {
Condensed preview — 74 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (185K chars).
[
  {
    "path": ".bowerrc",
    "chars": 37,
    "preview": "{\n  \"directory\": \"bower_components\"\n}"
  },
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".gitignore",
    "chars": 89,
    "preview": ".DS_Store\nnode_modules\nbower_components\ndist\ncoverage\ndemo/js\nnpm-debug.log\n*.iml\n.idea/\n"
  },
  {
    "path": ".jshintrc",
    "chars": 523,
    "preview": "{\n  \"predef\": [\n    \"describe\",\n    \"it\",\n    \"xit\",\n    \"expect\",\n    \"angular\",\n    \"beforeEach\",\n    \"spyOn\",\n    \"in"
  },
  {
    "path": ".travis.yml",
    "chars": 188,
    "preview": "language: node_js\nnode_js:\n  - \"0.10\"\n\nbefore_install:\n  - npm install -g grunt-cli karma-cli bower\n\nbefore_script:\n  - "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 4962,
    "preview": "# Changelog\n## 1.1.6 - 2016-09-14\n- Fix: min length validator and size validator validate only length [#107](https://git"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 165,
    "preview": "# Contributing Guide\n\n\n## Commit messages\n\nFollow the [AngularJS conventions](https://docs.google.com/document/d/1QrDFcI"
  },
  {
    "path": "Gruntfile.js",
    "chars": 4400,
    "preview": "'use strict';\n\nmodule.exports = function (grunt) {\n\n  require('load-grunt-tasks')(grunt);\n\n  grunt.initConfig({\n\n    pkg"
  },
  {
    "path": "LICENSE",
    "chars": 1079,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Netcetera AG\n\nPermission is hereby granted, free of charge, to any person obta"
  },
  {
    "path": "README.md",
    "chars": 19654,
    "preview": "# valdr\n[![Build Status](https://travis-ci.org/netceteragroup/valdr.svg?branch=master)](https://travis-ci.org/netceterag"
  },
  {
    "path": "bower.json",
    "chars": 438,
    "preview": "{\n  \"author\": \"Netcetera AG\",\n  \"name\": \"valdr\",\n  \"description\": \"A model centric approach to AngularJS form validation"
  },
  {
    "path": "demo/core/custom-validator.html",
    "chars": 2046,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Demo</title>\n</head>\n<body ng-app=\"demoApp\">\n\n<div ng-controller=\"TestController\""
  },
  {
    "path": "demo/core/list.html",
    "chars": 3215,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Demo</title>\n\n  <style>\n    .demo-is-invalid label {\n      color: red;\n    }\n    "
  },
  {
    "path": "demo/core/load-constraints.html",
    "chars": 1838,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Demo</title>\n</head>\n<body ng-app=\"demoApp\">\n\n<div ng-controller=\"TestController\""
  },
  {
    "path": "demo/core/simple.html",
    "chars": 5456,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n  <title>Demo</title>\n</head>\n\n<body ng-app=\"demoApp\">\n\n<div ng-controller=\"TestControlle"
  },
  {
    "path": "demo/demo-api.js",
    "chars": 587,
    "preview": "module.exports = function (app) {\n\n  var url = require('url'),\n      fs = require('fs');\n\n  var getConstraints = functio"
  },
  {
    "path": "demo/demoConstraints.json",
    "chars": 466,
    "preview": "{\n  \"Person\": {\n    \"lastName\": {\n      \"size\": {\n        \"min\": 2,\n        \"max\": 10,\n        \"message\": \"javax.validat"
  },
  {
    "path": "demo/index.html",
    "chars": 617,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>valdr demos</title>\n</head>\n<body>\n\n<h2>Core</h2>\n<ul>\n  <li><a href=\"core/simple"
  },
  {
    "path": "demo/message/angular-translate.html",
    "chars": 2851,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Demo</title>\n</head>\n<body ng-app=\"demoApp\">\n\n<div ng-controller=\"TestController\""
  },
  {
    "path": "demo/message/angular-validation.html",
    "chars": 3169,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Demo</title>\n\n  <style>\n    .valdr-message {\n        display: inline;\n        bac"
  },
  {
    "path": "demo/message/message-template.tpl.html",
    "chars": 160,
    "preview": "<div class=\"my-violations\">\n  <p class=\"some-custom-class\" ng-repeat=\"violation in violations\">\n    {{ violation.message"
  },
  {
    "path": "demo/message/simple.html",
    "chars": 4275,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Demo</title>\n\n  <style>\n    .valdr-message {\n        display: none;\n    }\n    .va"
  },
  {
    "path": "js.prefix",
    "chars": 45,
    "preview": "(function (window, document) {\n'use strict';\n"
  },
  {
    "path": "js.suffix",
    "chars": 21,
    "preview": "})(window, document);"
  },
  {
    "path": "karma.conf.js",
    "chars": 1264,
    "preview": "module.exports = function (config) {\n  config.set({\n\n    basePath: '',\n\n    frameworks: ['jasmine'],\n\n    files: [\n     "
  },
  {
    "path": "package.json",
    "chars": 1048,
    "preview": "{\n  \"name\": \"valdr\",\n  \"version\": \"1.1.6\",\n  \"description\": \"A model centric approach to AngularJS form validation\",\n  \""
  },
  {
    "path": "server.js",
    "chars": 550,
    "preview": "var express = require('express');\n\nvar app = express();\nvar server = require('http').createServer(app);\n\napp.configure(f"
  },
  {
    "path": "src/core/valdr-service.js",
    "chars": 5858,
    "preview": "angular.module('valdr')\n\n  .provider('valdr', function () {\n\n    var constraints = {}, validators = {}, constraintUrl, c"
  },
  {
    "path": "src/core/valdr-service.spec.js",
    "chars": 9589,
    "preview": "describe('valdr', function () {\n\n  var valdr, $rootScope, valdrEvents, valdrClasses, sizeValidator, requiredValidator,\n "
  },
  {
    "path": "src/core/valdrEnabled-directive.js",
    "chars": 990,
    "preview": "angular.module('valdr')\n\n/**\n * This directive allows to dynamically enable and disable the validation with valdr.\n * Al"
  },
  {
    "path": "src/core/valdrEnabled-directive.spec.js",
    "chars": 2448,
    "preview": "describe('valdrEnabled directive', function () {\n\n  // VARIABLES\n\n  var $scope, $compile, element, valdr, ngModelControl"
  },
  {
    "path": "src/core/valdrFormGroup-directive.js",
    "chars": 4282,
    "preview": "/**\n * This directive adds the validity state to a form group element surrounding valdr validated input fields.\n * If va"
  },
  {
    "path": "src/core/valdrFormGroup-directive.spec.js",
    "chars": 3910,
    "preview": "describe('valdrFormGroup directive', function () {\n\n  // VARIABLES\n\n  var $scope, $compile, element, valdr, valdrClasses"
  },
  {
    "path": "src/core/valdrFormItem-directive.js",
    "chars": 4686,
    "preview": "/**\n * This controller is used if no valdrEnabled parent directive is available.\n */\nvar nullValdrEnabledController = {\n"
  },
  {
    "path": "src/core/valdrFormItem-directive.spec.js",
    "chars": 7143,
    "preview": "describe('valdrFormItem directive', function () {\n\n  // VARIABLES\n\n  var $scope, $compile, element, valdr, valdrEvents, "
  },
  {
    "path": "src/core/valdrType-directive.js",
    "chars": 438,
    "preview": "angular.module('valdr')\n\n/**\n * The valdrType directive defines the type of the model to be validated.\n * The directive "
  },
  {
    "path": "src/core/valdrType-directive.spec.js",
    "chars": 1182,
    "preview": "describe('valdrType directive', function () {\n\n  var $scope, $compile, element, FormTypeController;\n\n  var compileTempla"
  },
  {
    "path": "src/core/valdrUtil-service.js",
    "chars": 3057,
    "preview": "angular.module('valdr')\n\n/**\n * Exposes utility functions used in validators and valdr core.\n */\n  .factory('valdrUtil',"
  },
  {
    "path": "src/core/valdrUtil-service.spec.js",
    "chars": 4792,
    "preview": "describe('valdrUtil', function () {\n\n  var valdrUtil;\n\n  beforeEach(module('valdr'));\n  beforeEach(inject(function (_val"
  },
  {
    "path": "src/core/validators/digitsValidator.js",
    "chars": 1943,
    "preview": "angular.module('valdr')\n\n  .factory('valdrDigitsValidator', ['valdrUtil', function (valdrUtil) {\n\n    // matches everyth"
  },
  {
    "path": "src/core/validators/digitsValidator.spec.js",
    "chars": 3374,
    "preview": "describe('valdrDigitsValidator', function () {\n\n  var digitsValidator, constraint;\n\n  beforeEach(module('valdr'));\n\n  be"
  },
  {
    "path": "src/core/validators/emailValidator.js",
    "chars": 597,
    "preview": "angular.module('valdr')\n\n  .factory('valdrEmailValidator', ['valdrUtil', function (valdrUtil) {\n\n    // the e-mail patte"
  },
  {
    "path": "src/core/validators/emailValidator.spec.js",
    "chars": 1742,
    "preview": "describe('valdrEmailValidator', function () {\n\n  var emailValidator;\n\n  beforeEach(module('valdr'));\n\n  beforeEach(injec"
  },
  {
    "path": "src/core/validators/futureAndPastSharedValidator.js",
    "chars": 709,
    "preview": "angular.module('valdr')\n\n  .factory('futureAndPastSharedValidator', ['valdrUtil', function (valdrUtil) {\n\n    var someAl"
  },
  {
    "path": "src/core/validators/futureValidator.js",
    "chars": 606,
    "preview": "angular.module('valdr')\n\n  .factory('valdrFutureValidator', ['futureAndPastSharedValidator', function (futureAndPastShar"
  },
  {
    "path": "src/core/validators/futureValidator.spec.js",
    "chars": 2278,
    "preview": "describe('valdrFutureValidator', function () {\n\n  var futureValidator;\n\n  beforeEach(module('valdr'));\n\n  beforeEach(inj"
  },
  {
    "path": "src/core/validators/hibernateEmailValidator.js",
    "chars": 1196,
    "preview": "angular.module('valdr')\n\n  .factory('valdrHibernateEmailValidator', ['valdrUtil', function (valdrUtil) {\n    var ATOM = "
  },
  {
    "path": "src/core/validators/hibernateEmailValidator.spec.js",
    "chars": 1780,
    "preview": "describe('valdrEmailValidator', function () {\n\n  var hibernateEmailValidator;\n\n  beforeEach(module('valdr'));\n\n  beforeE"
  },
  {
    "path": "src/core/validators/hibernateUrlValidator.js",
    "chars": 655,
    "preview": "angular.module('valdr')\n\n  .factory('valdrHibernateUrlValidator', ['valdrUrlValidator', function (valdrUrlValidator) {\n\n"
  },
  {
    "path": "src/core/validators/hibernateUrlValidator.spec.js",
    "chars": 1335,
    "preview": "describe('valdrHibernateUrlValidator', function () {\n\n  var urlValidator;\n\n  beforeEach(module('valdr'));\n\n  beforeEach("
  },
  {
    "path": "src/core/validators/maxLengthValidator.js",
    "chars": 728,
    "preview": "angular.module('valdr')\n\n  .factory('valdrMaxLengthValidator', ['valdrUtil', function (valdrUtil) {\n    return {\n      n"
  },
  {
    "path": "src/core/validators/maxLengthValidator.spec.js",
    "chars": 1517,
    "preview": "describe('valdrMaxLengthValidator', function () {\n\n  var maxLengthValidator, constraint = {\n    number: 5,\n    message: "
  },
  {
    "path": "src/core/validators/maxValidator.js",
    "chars": 701,
    "preview": "angular.module('valdr')\n\n  .factory('valdrMaxValidator', ['valdrUtil', function (valdrUtil) {\n\n    return {\n      name: "
  },
  {
    "path": "src/core/validators/maxValidator.spec.js",
    "chars": 1729,
    "preview": "describe('valdrMaxValidator', function () {\n\n  var maxValidator, constraint;\n\n  beforeEach(module('valdr'));\n\n  beforeEa"
  },
  {
    "path": "src/core/validators/minLengthValidator.js",
    "chars": 729,
    "preview": "angular.module('valdr')\n\n  .factory('valdrMinLengthValidator', ['valdrUtil', function (valdrUtil) {\n    return {\n      n"
  },
  {
    "path": "src/core/validators/minLengthValidator.spec.js",
    "chars": 2046,
    "preview": "describe('valdrMinLengthValidator', function () {\n\n  var minLengthValidator, constraint = {\n    number: 5,\n    message: "
  },
  {
    "path": "src/core/validators/minValidator.js",
    "chars": 703,
    "preview": "angular.module('valdr')\n\n  .factory('valdrMinValidator', ['valdrUtil', function (valdrUtil) {\n\n    return {\n      name: "
  },
  {
    "path": "src/core/validators/minValidator.spec.js",
    "chars": 1794,
    "preview": "describe('valdrMinValidator', function () {\n\n  var minValidator, constraint;\n\n  beforeEach(module('valdr'));\n\n  beforeEa"
  },
  {
    "path": "src/core/validators/pastValidator.js",
    "chars": 598,
    "preview": "angular.module('valdr')\n\n  .factory('valdrPastValidator', ['futureAndPastSharedValidator', function (futureAndPastShared"
  },
  {
    "path": "src/core/validators/pastValidator.spec.js",
    "chars": 2202,
    "preview": "describe('valdrPastValidator', function () {\n\n  var pastValidator;\n\n  beforeEach(module('valdr'));\n\n  beforeEach(inject("
  },
  {
    "path": "src/core/validators/patternValidator.js",
    "chars": 1283,
    "preview": "angular.module('valdr')\n\n  .factory('valdrPatternValidator', ['valdrUtil', function (valdrUtil) {\n\n    var REGEXP_PATTER"
  },
  {
    "path": "src/core/validators/patternValidator.spec.js",
    "chars": 1301,
    "preview": "describe('valdrPatternValidator', function () {\n\n  var patternValidator,\n    constraint = { value: '/^[a-z]+$/' };\n\n  be"
  },
  {
    "path": "src/core/validators/requiredValidator.js",
    "chars": 411,
    "preview": "angular.module('valdr')\n\n  .factory('valdrRequiredValidator', ['valdrUtil', function (valdrUtil) {\n    return {\n      na"
  },
  {
    "path": "src/core/validators/requiredValidator.spec.js",
    "chars": 782,
    "preview": "describe('valdrRequiredValidator', function () {\n\n  var requiredValidator, valdrUtil;\n\n  beforeEach(module('valdr'));\n\n "
  },
  {
    "path": "src/core/validators/sizeValidator.js",
    "chars": 775,
    "preview": "angular.module('valdr')\n\n  .factory('valdrSizeValidator', ['valdrUtil', function (valdrUtil) {\n    return {\n      name: "
  },
  {
    "path": "src/core/validators/sizeValidator.spec.js",
    "chars": 1284,
    "preview": "describe('valdrSizeValidator', function () {\n\n  var sizeValidator, constraint = {\n    min: 5,\n    max: 20,\n    message: "
  },
  {
    "path": "src/core/validators/urlValidator.js",
    "chars": 563,
    "preview": "angular.module('valdr')\n\n  .factory('valdrUrlValidator', ['valdrUtil', function (valdrUtil) {\n\n    // the url pattern us"
  },
  {
    "path": "src/core/validators/urlValidator.spec.js",
    "chars": 1299,
    "preview": "describe('valdrUrlValidator', function () {\n\n  var urlValidator;\n\n  beforeEach(module('valdr'));\n\n  beforeEach(inject(fu"
  },
  {
    "path": "src/message/valdrMessage-directive.js",
    "chars": 5896,
    "preview": "/**\n * This directive appends a validation message to the parent element of any input, select or textarea element, which"
  },
  {
    "path": "src/message/valdrMessage-directive.spec.js",
    "chars": 14225,
    "preview": "describe('valdrMessage input directive', function () {\n\n  var $scope, $compile;\n\n  beforeEach(module('valdr'));\n\n  var c"
  },
  {
    "path": "src/message/valdrMessage-service.js",
    "chars": 3290,
    "preview": "angular.module('valdr')\n\n/**\n * This service provides shared configuration between all valdr-message directive instances"
  },
  {
    "path": "src/message/valdrMessage-service.spec.js",
    "chars": 4799,
    "preview": "describe('valdrMessage service', function () {\n\n  var valdrMessage;\n\n  beforeEach(module('valdr'));\n  beforeEach(inject("
  },
  {
    "path": "src/valdr.js",
    "chars": 449,
    "preview": "angular.module('valdr', ['ng'])\n  .constant('valdrEvents', {\n    'revalidate': 'valdr-revalidate'\n  })\n  .value('valdrCo"
  },
  {
    "path": "src/valdr.spec.js",
    "chars": 639,
    "preview": "describe('valdr', function () {\n\n  beforeEach(module('valdr'));\n\n  it('should provide constant for validation events', i"
  }
]

About this extraction

This page contains the full source code of the netceteragroup/valdr GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 74 files (169.6 KB), approximately 43.5k tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!