2011-09-08 20:56:29 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
2013-08-02 13:57:58 +00:00
|
|
|
var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/;
|
2011-09-08 20:56:29 +00:00
|
|
|
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
var inputType = {
|
2011-10-19 23:42:32 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
2012-03-09 23:19:24 +00:00
|
|
|
* @ngdoc inputType
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input.text
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Standard HTML text input with angular data binding.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-11-09 00:15:50 +00:00
|
|
|
* @param {string=} required Adds `required` validation error key if the value is not entered.
|
|
|
|
|
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
|
|
|
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
|
|
|
* `required` when you want to data-bind to the `required` attribute.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
2012-02-24 03:47:58 +00:00
|
|
|
* minlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
2012-02-24 03:47:58 +00:00
|
|
|
* maxlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
2012-02-24 03:47:58 +00:00
|
|
|
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
|
|
|
|
* patterns defined as scope expressions.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
2012-08-31 23:20:01 +00:00
|
|
|
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trimming the
|
|
|
|
|
* input.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.text = 'guest';
|
2012-08-31 23:20:01 +00:00
|
|
|
$scope.word = /^\s*\w*\s*$/;
|
2012-02-24 03:47:58 +00:00
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
|
|
|
|
Single word: <input type="text" name="input" ng-model="text"
|
2012-08-31 23:20:01 +00:00
|
|
|
ng-pattern="word" required ng-trim="false">
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.required">
|
2011-09-08 20:56:29 +00:00
|
|
|
Required!</span>
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.pattern">
|
2012-02-24 03:47:58 +00:00
|
|
|
Single word only!</span>
|
|
|
|
|
|
2012-02-16 01:16:02 +00:00
|
|
|
<tt>text = {{text}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
|
|
|
|
|
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
|
|
|
|
|
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
2012-03-13 07:29:10 +00:00
|
|
|
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
|
|
|
|
expect(binding('text')).toEqual('guest');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('true');
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
it('should be invalid if empty', function() {
|
|
|
|
|
input('text').enter('');
|
|
|
|
|
expect(binding('text')).toEqual('');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
2011-09-08 20:56:29 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
it('should be invalid if multi word', function() {
|
|
|
|
|
input('text').enter('hello world');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
2012-08-31 23:20:01 +00:00
|
|
|
|
|
|
|
|
it('should not be trimmed', function() {
|
|
|
|
|
input('text').enter('untrimmed ');
|
|
|
|
|
expect(binding('text')).toEqual('untrimmed ');
|
|
|
|
|
expect(binding('myForm.input.$valid')).toEqual('true');
|
|
|
|
|
});
|
2012-02-24 03:47:58 +00:00
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
|
|
|
|
'text': textInputType,
|
2011-09-08 20:56:29 +00:00
|
|
|
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
2012-03-09 23:19:24 +00:00
|
|
|
* @ngdoc inputType
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input.number
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-13 07:29:10 +00:00
|
|
|
* Text input with number validation and transformation. Sets the `number` validation
|
2012-02-24 03:47:58 +00:00
|
|
|
* error if not a valid number.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2013-04-18 16:20:26 +00:00
|
|
|
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
|
|
|
|
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
2012-03-13 07:29:10 +00:00
|
|
|
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
2012-11-09 00:15:50 +00:00
|
|
|
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
|
|
|
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
|
|
|
* `required` when you want to data-bind to the `required` attribute.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
2012-02-24 03:47:58 +00:00
|
|
|
* minlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
2012-02-24 03:47:58 +00:00
|
|
|
* maxlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
2012-02-24 03:47:58 +00:00
|
|
|
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
|
|
|
|
* patterns defined as scope expressions.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.value = 12;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
|
|
|
|
Number: <input type="number" name="input" ng-model="value"
|
2012-02-24 03:47:58 +00:00
|
|
|
min="0" max="99" required>
|
2013-07-19 17:13:36 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.required">
|
2012-02-24 03:47:58 +00:00
|
|
|
Required!</span>
|
2013-07-19 17:13:36 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.number">
|
2012-02-24 03:47:58 +00:00
|
|
|
Not valid number!</span>
|
|
|
|
|
<tt>value = {{value}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
|
|
|
|
|
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
|
|
|
|
|
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
2012-03-13 07:29:10 +00:00
|
|
|
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
|
|
|
|
expect(binding('value')).toEqual('12');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('true');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
2011-09-08 20:56:29 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
it('should be invalid if empty', function() {
|
|
|
|
|
input('value').enter('');
|
|
|
|
|
expect(binding('value')).toEqual('');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
2011-10-17 23:18:00 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
it('should be invalid if over max', function() {
|
|
|
|
|
input('value').enter('123');
|
2012-03-12 22:54:48 +00:00
|
|
|
expect(binding('value')).toEqual('');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
|
|
|
|
'number': numberInputType,
|
2011-10-17 23:18:00 +00:00
|
|
|
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
2012-03-09 23:19:24 +00:00
|
|
|
* @ngdoc inputType
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input.url
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-13 07:29:10 +00:00
|
|
|
* Text input with URL validation. Sets the `url` validation error key if the content is not a
|
2012-02-24 03:47:58 +00:00
|
|
|
* valid URL.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-03-13 07:29:10 +00:00
|
|
|
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
2012-11-09 00:15:50 +00:00
|
|
|
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
|
|
|
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
|
|
|
* `required` when you want to data-bind to the `required` attribute.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
2012-02-24 03:47:58 +00:00
|
|
|
* minlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
2012-02-24 03:47:58 +00:00
|
|
|
* maxlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
2012-02-24 03:47:58 +00:00
|
|
|
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
|
|
|
|
* patterns defined as scope expressions.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.text = 'http://google.com';
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
|
|
|
|
URL: <input type="url" name="input" ng-model="text" required>
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.required">
|
2012-02-24 03:47:58 +00:00
|
|
|
Required!</span>
|
2012-03-13 04:12:15 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.url">
|
2012-02-24 03:47:58 +00:00
|
|
|
Not valid url!</span>
|
|
|
|
|
<tt>text = {{text}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
|
|
|
|
|
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
|
|
|
|
|
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
2012-03-13 07:29:10 +00:00
|
|
|
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
|
|
|
|
expect(binding('text')).toEqual('http://google.com');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('true');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
2011-09-08 20:56:29 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
it('should be invalid if empty', function() {
|
|
|
|
|
input('text').enter('');
|
|
|
|
|
expect(binding('text')).toEqual('');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
2011-10-19 23:43:16 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
it('should be invalid if not url', function() {
|
|
|
|
|
input('text').enter('xxx');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2012-02-16 01:16:02 +00:00
|
|
|
'url': urlInputType,
|
2012-02-24 03:47:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-03-09 23:19:24 +00:00
|
|
|
* @ngdoc inputType
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input.email
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-13 07:29:10 +00:00
|
|
|
* Text input with email validation. Sets the `email` validation error key if not a valid email
|
2012-02-24 03:47:58 +00:00
|
|
|
* address.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-03-13 07:29:10 +00:00
|
|
|
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
2012-11-09 00:15:50 +00:00
|
|
|
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
|
|
|
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
|
|
|
* `required` when you want to data-bind to the `required` attribute.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
2012-02-24 03:47:58 +00:00
|
|
|
* minlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
2012-02-24 03:47:58 +00:00
|
|
|
* maxlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
2012-02-24 03:47:58 +00:00
|
|
|
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
|
|
|
|
* patterns defined as scope expressions.
|
2013-05-23 04:27:41 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
|
|
|
|
* interaction with the input element.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.text = 'me@example.com';
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
|
|
|
|
Email: <input type="email" name="input" ng-model="text" required>
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.required">
|
2012-02-24 03:47:58 +00:00
|
|
|
Required!</span>
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.input.$error.email">
|
2012-02-24 03:47:58 +00:00
|
|
|
Not valid email!</span>
|
|
|
|
|
<tt>text = {{text}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
|
|
|
|
|
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
|
|
|
|
|
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
2012-03-13 07:29:10 +00:00
|
|
|
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
|
|
|
|
<tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
|
|
|
|
expect(binding('text')).toEqual('me@example.com');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('true');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be invalid if empty', function() {
|
|
|
|
|
input('text').enter('');
|
|
|
|
|
expect(binding('text')).toEqual('');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be invalid if not email', function() {
|
|
|
|
|
input('text').enter('xxx');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.input.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2012-02-16 01:16:02 +00:00
|
|
|
'email': emailInputType,
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
|
|
|
|
|
/**
|
2012-03-09 23:19:24 +00:00
|
|
|
* @ngdoc inputType
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input.radio
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* HTML radio button.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-02-24 03:47:58 +00:00
|
|
|
* @param {string} value The value to which the expression should be set when selected.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.color = 'blue';
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
|
|
|
|
<input type="radio" ng-model="color" value="red"> Red <br/>
|
|
|
|
|
<input type="radio" ng-model="color" value="green"> Green <br/>
|
|
|
|
|
<input type="radio" ng-model="color" value="blue"> Blue <br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
<tt>color = {{color}}</tt><br/>
|
|
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should change state', function() {
|
|
|
|
|
expect(binding('color')).toEqual('blue');
|
|
|
|
|
|
|
|
|
|
input('color').select('red');
|
|
|
|
|
expect(binding('color')).toEqual('red');
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2012-02-16 01:16:02 +00:00
|
|
|
'radio': radioInputType,
|
2012-02-24 03:47:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-03-09 23:19:24 +00:00
|
|
|
* @ngdoc inputType
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input.checkbox
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* HTML checkbox.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngTrueValue The value to which the expression should be set when selected.
|
|
|
|
|
* @param {string=} ngFalseValue The value to which the expression should be set when not selected.
|
|
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.value1 = true;
|
|
|
|
|
$scope.value2 = 'YES'
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
|
|
|
|
Value1: <input type="checkbox" ng-model="value1"> <br/>
|
|
|
|
|
Value2: <input type="checkbox" ng-model="value2"
|
|
|
|
|
ng-true-value="YES" ng-false-value="NO"> <br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
<tt>value1 = {{value1}}</tt><br/>
|
|
|
|
|
<tt>value2 = {{value2}}</tt><br/>
|
|
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should change state', function() {
|
|
|
|
|
expect(binding('value1')).toEqual('true');
|
|
|
|
|
expect(binding('value2')).toEqual('YES');
|
|
|
|
|
|
|
|
|
|
input('value1').check();
|
|
|
|
|
input('value2').check();
|
|
|
|
|
expect(binding('value1')).toEqual('false');
|
|
|
|
|
expect(binding('value2')).toEqual('NO');
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2012-02-16 01:16:02 +00:00
|
|
|
'checkbox': checkboxInputType,
|
|
|
|
|
|
|
|
|
|
'hidden': noop,
|
|
|
|
|
'button': noop,
|
|
|
|
|
'submit': noop,
|
|
|
|
|
'reset': noop
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isEmpty(value) {
|
|
|
|
|
return isUndefined(value) || value === '' || value === null || value !== value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-03-30 19:07:19 +00:00
|
|
|
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
|
|
|
|
|
|
|
|
|
var listener = function() {
|
2012-08-31 23:20:01 +00:00
|
|
|
var value = element.val();
|
|
|
|
|
|
|
|
|
|
// By default we will trim the value
|
|
|
|
|
// If the attribute ng-trim exists we will avoid trimming
|
|
|
|
|
// e.g. <input ng-model="foo" ng-trim="false">
|
|
|
|
|
if (toBoolean(attr.ngTrim || 'T')) {
|
|
|
|
|
value = trim(value);
|
|
|
|
|
}
|
2012-03-30 19:07:19 +00:00
|
|
|
|
|
|
|
|
if (ctrl.$viewValue !== value) {
|
|
|
|
|
scope.$apply(function() {
|
|
|
|
|
ctrl.$setViewValue(value);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-05-06 16:27:10 +00:00
|
|
|
// if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
|
|
|
|
|
// input event on backspace, delete or cut
|
2012-03-30 19:07:19 +00:00
|
|
|
if ($sniffer.hasEvent('input')) {
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('input', listener);
|
2012-03-30 19:07:19 +00:00
|
|
|
} else {
|
|
|
|
|
var timeout;
|
|
|
|
|
|
2013-03-21 07:07:49 +00:00
|
|
|
var deferListener = function() {
|
|
|
|
|
if (!timeout) {
|
|
|
|
|
timeout = $browser.defer(function() {
|
|
|
|
|
listener();
|
|
|
|
|
timeout = null;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('keydown', function(event) {
|
2012-03-30 19:07:19 +00:00
|
|
|
var key = event.keyCode;
|
|
|
|
|
|
|
|
|
|
// ignore
|
|
|
|
|
// command modifiers arrows
|
|
|
|
|
if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
|
|
|
|
|
|
2013-03-21 07:07:49 +00:00
|
|
|
deferListener();
|
2012-03-10 01:19:48 +00:00
|
|
|
});
|
2012-03-30 19:07:19 +00:00
|
|
|
|
|
|
|
|
// if user paste into input using mouse, we need "change" event to catch it
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('change', listener);
|
2013-03-21 07:07:49 +00:00
|
|
|
|
|
|
|
|
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
|
|
|
|
|
if ($sniffer.hasEvent('paste')) {
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('paste cut', deferListener);
|
2013-03-21 07:07:49 +00:00
|
|
|
}
|
2012-03-30 19:07:19 +00:00
|
|
|
}
|
|
|
|
|
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$render = function() {
|
|
|
|
|
element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
|
2012-02-16 01:16:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// pattern validator
|
|
|
|
|
var pattern = attr.ngPattern,
|
2013-03-30 04:45:53 +00:00
|
|
|
patternValidator,
|
|
|
|
|
match;
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-03-12 22:54:48 +00:00
|
|
|
var validate = function(regexp, value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
if (isEmpty(value) || regexp.test(value)) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('pattern', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('pattern', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (pattern) {
|
2013-03-30 04:45:53 +00:00
|
|
|
match = pattern.match(/^\/(.*)\/([gim]*)$/);
|
|
|
|
|
if (match) {
|
|
|
|
|
pattern = new RegExp(match[1], match[2]);
|
2012-02-16 01:16:02 +00:00
|
|
|
patternValidator = function(value) {
|
2012-03-12 22:54:48 +00:00
|
|
|
return validate(pattern, value)
|
2012-02-16 01:16:02 +00:00
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
patternValidator = function(value) {
|
|
|
|
|
var patternObj = scope.$eval(pattern);
|
|
|
|
|
|
|
|
|
|
if (!patternObj || !patternObj.test) {
|
2013-07-10 19:42:50 +00:00
|
|
|
throw minErr('ngPattern')('noregexp',
|
|
|
|
|
'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern,
|
2013-06-08 01:24:30 +00:00
|
|
|
patternObj, startingTag(element));
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
2012-03-12 22:54:48 +00:00
|
|
|
return validate(patternObj, value);
|
2012-02-16 01:16:02 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(patternValidator);
|
|
|
|
|
ctrl.$parsers.push(patternValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// min length validator
|
|
|
|
|
if (attr.ngMinlength) {
|
2012-03-16 19:55:54 +00:00
|
|
|
var minlength = int(attr.ngMinlength);
|
2012-02-16 01:16:02 +00:00
|
|
|
var minLengthValidator = function(value) {
|
|
|
|
|
if (!isEmpty(value) && value.length < minlength) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('minlength', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('minlength', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(minLengthValidator);
|
|
|
|
|
ctrl.$formatters.push(minLengthValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// max length validator
|
|
|
|
|
if (attr.ngMaxlength) {
|
2012-03-16 19:55:54 +00:00
|
|
|
var maxlength = int(attr.ngMaxlength);
|
2012-02-16 01:16:02 +00:00
|
|
|
var maxLengthValidator = function(value) {
|
|
|
|
|
if (!isEmpty(value) && value.length > maxlength) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('maxlength', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('maxlength', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(maxLengthValidator);
|
|
|
|
|
ctrl.$formatters.push(maxLengthValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
2012-04-10 21:29:49 +00:00
|
|
|
}
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-03-30 19:07:19 +00:00
|
|
|
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
|
|
|
|
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(function(value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
var empty = isEmpty(value);
|
|
|
|
|
if (empty || NUMBER_REGEXP.test(value)) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('number', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value === '' ? null : (empty ? value : parseFloat(value));
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('number', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(function(value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
return isEmpty(value) ? '' : '' + value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (attr.min) {
|
|
|
|
|
var min = parseFloat(attr.min);
|
|
|
|
|
var minValidator = function(value) {
|
|
|
|
|
if (!isEmpty(value) && value < min) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('min', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('min', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(minValidator);
|
|
|
|
|
ctrl.$formatters.push(minValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attr.max) {
|
|
|
|
|
var max = parseFloat(attr.max);
|
|
|
|
|
var maxValidator = function(value) {
|
|
|
|
|
if (!isEmpty(value) && value > max) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('max', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('max', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(maxValidator);
|
|
|
|
|
ctrl.$formatters.push(maxValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(function(value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
if (isEmpty(value) || isNumber(value)) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('number', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('number', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-30 19:07:19 +00:00
|
|
|
function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
|
|
|
|
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
var urlValidator = function(value) {
|
|
|
|
|
if (isEmpty(value) || URL_REGEXP.test(value)) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('url', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('url', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(urlValidator);
|
|
|
|
|
ctrl.$parsers.push(urlValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-30 19:07:19 +00:00
|
|
|
function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
|
|
|
|
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
var emailValidator = function(value) {
|
|
|
|
|
if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('email', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('email', false);
|
2012-02-16 01:16:02 +00:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(emailValidator);
|
|
|
|
|
ctrl.$parsers.push(emailValidator);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function radioInputType(scope, element, attr, ctrl) {
|
2012-04-10 20:41:51 +00:00
|
|
|
// make the name unique, if not defined
|
|
|
|
|
if (isUndefined(attr.name)) {
|
|
|
|
|
element.attr('name', nextUid());
|
|
|
|
|
}
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('click', function() {
|
2012-02-16 01:16:02 +00:00
|
|
|
if (element[0].checked) {
|
|
|
|
|
scope.$apply(function() {
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$setViewValue(attr.value);
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
2012-04-10 21:29:49 +00:00
|
|
|
}
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$render = function() {
|
2012-02-16 01:16:02 +00:00
|
|
|
var value = attr.value;
|
2012-03-23 20:04:52 +00:00
|
|
|
element[0].checked = (value == ctrl.$viewValue);
|
2012-02-16 01:16:02 +00:00
|
|
|
};
|
2012-03-20 20:05:42 +00:00
|
|
|
|
|
|
|
|
attr.$observe('value', ctrl.$render);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function checkboxInputType(scope, element, attr, ctrl) {
|
|
|
|
|
var trueValue = attr.ngTrueValue,
|
|
|
|
|
falseValue = attr.ngFalseValue;
|
|
|
|
|
|
|
|
|
|
if (!isString(trueValue)) trueValue = true;
|
|
|
|
|
if (!isString(falseValue)) falseValue = false;
|
|
|
|
|
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('click', function() {
|
2012-02-16 01:16:02 +00:00
|
|
|
scope.$apply(function() {
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$setViewValue(element[0].checked);
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$render = function() {
|
|
|
|
|
element[0].checked = ctrl.$viewValue;
|
2012-02-16 01:16:02 +00:00
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(function(value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
return value === trueValue;
|
|
|
|
|
});
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(function(value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
return value ? trueValue : falseValue;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
2012-03-08 06:47:01 +00:00
|
|
|
* @ngdoc directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:textarea
|
2012-05-25 17:29:54 +00:00
|
|
|
* @restrict E
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-13 04:30:03 +00:00
|
|
|
* HTML textarea element control with angular data-binding. The data-binding and validation
|
2012-02-24 03:47:58 +00:00
|
|
|
* properties of this element are exactly the same as those of the
|
2012-06-12 06:49:24 +00:00
|
|
|
* {@link ng.directive:input input element}.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-03-13 07:29:10 +00:00
|
|
|
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
2012-11-09 00:15:50 +00:00
|
|
|
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
|
|
|
|
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
|
|
|
|
* `required` when you want to data-bind to the `required` attribute.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
2012-02-24 03:47:58 +00:00
|
|
|
* minlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
2012-02-24 03:47:58 +00:00
|
|
|
* maxlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
2012-02-24 03:47:58 +00:00
|
|
|
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
|
|
|
|
* patterns defined as scope expressions.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2012-03-08 06:47:01 +00:00
|
|
|
* @ngdoc directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:input
|
2012-03-09 23:12:48 +00:00
|
|
|
* @restrict E
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-13 04:30:03 +00:00
|
|
|
* HTML input element control with angular data-binding. Input control follows HTML5 input types
|
2012-02-24 03:47:58 +00:00
|
|
|
* and polyfills the HTML5 validation behavior for older browsers.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string} ngModel Assignable angular expression to data-bind to.
|
2012-03-13 04:30:03 +00:00
|
|
|
* @param {string=} name Property name of the form under which the control is published.
|
2012-03-13 07:29:10 +00:00
|
|
|
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
2012-11-09 00:15:50 +00:00
|
|
|
* @param {boolean=} ngRequired Sets `required` attribute if set to true
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
|
2012-02-24 03:47:58 +00:00
|
|
|
* minlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
2012-02-24 03:47:58 +00:00
|
|
|
* maxlength.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
|
2012-02-24 03:47:58 +00:00
|
|
|
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
|
|
|
|
* patterns defined as scope expressions.
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngChange Angular expression to be executed when input changes due to user
|
2012-02-24 03:47:58 +00:00
|
|
|
* interaction with the input element.
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.user = {name: 'guest', last: 'visitor'};
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<div ng-controller="Ctrl">
|
2012-02-24 03:47:58 +00:00
|
|
|
<form name="myForm">
|
2012-03-09 08:00:05 +00:00
|
|
|
User name: <input type="text" name="userName" ng-model="user.name" required>
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.userName.$error.required">
|
2012-02-24 03:47:58 +00:00
|
|
|
Required!</span><br>
|
2012-03-09 08:00:05 +00:00
|
|
|
Last name: <input type="text" name="lastName" ng-model="user.last"
|
|
|
|
|
ng-minlength="3" ng-maxlength="10">
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.lastName.$error.minlength">
|
2012-02-24 03:47:58 +00:00
|
|
|
Too short!</span>
|
2012-03-13 07:29:10 +00:00
|
|
|
<span class="error" ng-show="myForm.lastName.$error.maxlength">
|
2012-02-24 03:47:58 +00:00
|
|
|
Too long!</span><br>
|
|
|
|
|
</form>
|
|
|
|
|
<hr>
|
|
|
|
|
<tt>user = {{user}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br>
|
|
|
|
|
<tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br>
|
|
|
|
|
<tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br>
|
2013-05-07 00:57:00 +00:00
|
|
|
<tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.$valid = {{myForm.$valid}}</tt><br>
|
2012-03-13 07:29:10 +00:00
|
|
|
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
|
|
|
|
|
<tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br>
|
|
|
|
|
<tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
|
2012-02-24 03:47:58 +00:00
|
|
|
</div>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
2012-03-27 19:44:37 +00:00
|
|
|
expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.userName.$valid')).toEqual('true');
|
|
|
|
|
expect(binding('myForm.$valid')).toEqual('true');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be invalid if empty when required', function() {
|
|
|
|
|
input('user.name').enter('');
|
2012-03-12 22:54:48 +00:00
|
|
|
expect(binding('user')).toEqual('{"last":"visitor"}');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.userName.$valid')).toEqual('false');
|
|
|
|
|
expect(binding('myForm.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be valid if empty when min length is set', function() {
|
|
|
|
|
input('user.last').enter('');
|
2012-03-27 19:44:37 +00:00
|
|
|
expect(binding('user')).toEqual('{"name":"guest","last":""}');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.lastName.$valid')).toEqual('true');
|
|
|
|
|
expect(binding('myForm.$valid')).toEqual('true');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be invalid if less than required min length', function() {
|
|
|
|
|
input('user.last').enter('xx');
|
2012-03-12 22:54:48 +00:00
|
|
|
expect(binding('user')).toEqual('{"name":"guest"}');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.lastName.$valid')).toEqual('false');
|
2012-03-13 07:29:10 +00:00
|
|
|
expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
2012-03-12 22:54:48 +00:00
|
|
|
it('should be invalid if longer than max length', function() {
|
2012-02-24 03:47:58 +00:00
|
|
|
input('user.last').enter('some ridiculously long name');
|
|
|
|
|
expect(binding('user'))
|
2012-03-12 22:54:48 +00:00
|
|
|
.toEqual('{"name":"guest"}');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.lastName.$valid')).toEqual('false');
|
2012-03-13 07:29:10 +00:00
|
|
|
expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.$valid')).toEqual('false');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2012-03-30 19:07:19 +00:00
|
|
|
var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
|
2012-02-16 01:16:02 +00:00
|
|
|
return {
|
|
|
|
|
restrict: 'E',
|
|
|
|
|
require: '?ngModel',
|
|
|
|
|
link: function(scope, element, attr, ctrl) {
|
|
|
|
|
if (ctrl) {
|
2012-03-30 19:07:19 +00:00
|
|
|
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
|
|
|
|
|
$browser);
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}];
|
|
|
|
|
|
2012-03-13 20:17:39 +00:00
|
|
|
var VALID_CLASS = 'ng-valid',
|
|
|
|
|
INVALID_CLASS = 'ng-invalid',
|
|
|
|
|
PRISTINE_CLASS = 'ng-pristine',
|
|
|
|
|
DIRTY_CLASS = 'ng-dirty';
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc object
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngModel.NgModelController
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-03-13 20:52:57 +00:00
|
|
|
* @property {string} $viewValue Actual string value in the view.
|
|
|
|
|
* @property {*} $modelValue The value in the model, that the control is bound to.
|
2013-06-12 20:17:29 +00:00
|
|
|
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
|
|
|
|
|
the control reads value from the DOM. Each function is called, in turn, passing the value
|
|
|
|
|
through to the next. Used to sanitize / convert the value as well as validation.
|
2013-08-09 16:52:27 +00:00
|
|
|
For validation, the parsers should update the validity state using
|
|
|
|
|
{@link ng.directive:ngModel.NgModelController#$setValidity $setValidity()},
|
|
|
|
|
and return `undefined` for invalid values.
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2013-06-12 20:17:29 +00:00
|
|
|
* @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
|
|
|
|
|
the model value changes. Each function is called, in turn, passing the value through to the
|
|
|
|
|
next. Used to format / convert values for display in the control and validation.
|
|
|
|
|
* <pre>
|
|
|
|
|
* function formatter(value) {
|
|
|
|
|
* if (value) {
|
|
|
|
|
* return value.toUpperCase();
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* ngModel.$formatters.push(formatter);
|
|
|
|
|
* </pre>
|
2013-03-21 19:09:47 +00:00
|
|
|
* @property {Object} $error An object hash with all errors as keys.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-03-13 20:52:57 +00:00
|
|
|
* @property {boolean} $pristine True if user has not interacted with the control yet.
|
|
|
|
|
* @property {boolean} $dirty True if user has already interacted with the control.
|
|
|
|
|
* @property {boolean} $valid True if there is no error.
|
|
|
|
|
* @property {boolean} $invalid True if at least one error on the control.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
*
|
2012-05-25 17:29:54 +00:00
|
|
|
* `NgModelController` provides API for the `ng-model` directive. The controller contains
|
2013-09-19 14:01:33 +00:00
|
|
|
* services for data-binding, validation, CSS updates, and value formatting and parsing. It
|
|
|
|
|
* purposefully does not contain any logic which deals with DOM rendering or listening to
|
|
|
|
|
* DOM events. Such DOM related logic should be provided by other directives which make use of
|
|
|
|
|
* `NgModelController` for data-binding.
|
2012-05-25 17:29:54 +00:00
|
|
|
*
|
2013-09-19 20:08:37 +00:00
|
|
|
* ## Custom Control Example
|
2012-05-25 17:29:54 +00:00
|
|
|
* This example shows how to use `NgModelController` with a custom control to achieve
|
|
|
|
|
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
|
|
|
|
|
* collaborate together to achieve the desired result.
|
|
|
|
|
*
|
2013-09-02 16:57:13 +00:00
|
|
|
* Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element
|
|
|
|
|
* contents be edited in place by the user. This will not work on older browsers.
|
|
|
|
|
*
|
2012-05-25 17:29:54 +00:00
|
|
|
* <example module="customControl">
|
|
|
|
|
<file name="style.css">
|
|
|
|
|
[contenteditable] {
|
|
|
|
|
border: 1px solid black;
|
|
|
|
|
background-color: white;
|
|
|
|
|
min-height: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ng-invalid {
|
|
|
|
|
border: 1px solid red;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</file>
|
|
|
|
|
<file name="script.js">
|
|
|
|
|
angular.module('customControl', []).
|
|
|
|
|
directive('contenteditable', function() {
|
|
|
|
|
return {
|
|
|
|
|
restrict: 'A', // only activate on element attribute
|
|
|
|
|
require: '?ngModel', // get a hold of NgModelController
|
|
|
|
|
link: function(scope, element, attrs, ngModel) {
|
|
|
|
|
if(!ngModel) return; // do nothing if no ng-model
|
|
|
|
|
|
|
|
|
|
// Specify how UI should be updated
|
|
|
|
|
ngModel.$render = function() {
|
|
|
|
|
element.html(ngModel.$viewValue || '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Listen for change events to enable binding
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('blur keyup change', function() {
|
2012-05-25 17:29:54 +00:00
|
|
|
scope.$apply(read);
|
|
|
|
|
});
|
|
|
|
|
read(); // initialize
|
|
|
|
|
|
|
|
|
|
// Write data to the model
|
|
|
|
|
function read() {
|
2013-07-08 13:52:31 +00:00
|
|
|
var html = element.html();
|
|
|
|
|
// When we clear the content editable the browser leaves a <br> behind
|
|
|
|
|
// If strip-br attribute is provided then we strip this out
|
|
|
|
|
if( attrs.stripBr && html == '<br>' ) {
|
|
|
|
|
html = '';
|
|
|
|
|
}
|
|
|
|
|
ngModel.$setViewValue(html);
|
2012-05-25 17:29:54 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
</file>
|
|
|
|
|
<file name="index.html">
|
|
|
|
|
<form name="myForm">
|
|
|
|
|
<div contenteditable
|
|
|
|
|
name="myWidget" ng-model="userContent"
|
2013-07-08 13:52:31 +00:00
|
|
|
strip-br="true"
|
2012-05-25 17:29:54 +00:00
|
|
|
required>Change me!</div>
|
|
|
|
|
<span ng-show="myForm.myWidget.$error.required">Required!</span>
|
|
|
|
|
<hr>
|
|
|
|
|
<textarea ng-model="userContent"></textarea>
|
|
|
|
|
</form>
|
|
|
|
|
</file>
|
|
|
|
|
<file name="scenario.js">
|
|
|
|
|
it('should data-bind and become invalid', function() {
|
|
|
|
|
var contentEditable = element('[contenteditable]');
|
|
|
|
|
|
|
|
|
|
expect(contentEditable.text()).toEqual('Change me!');
|
|
|
|
|
input('userContent').enter('');
|
|
|
|
|
expect(contentEditable.text()).toEqual('');
|
|
|
|
|
expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
|
|
|
|
|
});
|
|
|
|
|
</file>
|
|
|
|
|
* </example>
|
|
|
|
|
*
|
2013-09-19 20:08:37 +00:00
|
|
|
* ## Isolated Scope Pitfall
|
|
|
|
|
*
|
|
|
|
|
* Note that if you have a directive with an isolated scope, you cannot require `ngModel`
|
|
|
|
|
* since the model value will be looked up on the isolated scope rather than the outer scope.
|
|
|
|
|
* When the directive updates the model value, calling `ngModel.$setViewValue()` the property
|
|
|
|
|
* on the outer scope will not be updated.
|
|
|
|
|
*
|
|
|
|
|
* Here is an example of this situation. You'll notice that even though both 'input' and 'div'
|
|
|
|
|
* seem to be attached to the same model, they are not kept in synch.
|
|
|
|
|
*
|
|
|
|
|
* <example module="badIsolatedDirective">
|
|
|
|
|
<file name="script.js">
|
|
|
|
|
angular.module('badIsolatedDirective', []).directive('bad', function() {
|
|
|
|
|
return {
|
|
|
|
|
require: 'ngModel',
|
|
|
|
|
scope: { },
|
|
|
|
|
template: '<input ng-model="innerModel">',
|
|
|
|
|
link: function(scope, element, attrs, ngModel) {
|
|
|
|
|
scope.$watch('innerModel', function(value) {
|
|
|
|
|
console.log(value);
|
|
|
|
|
ngModel.$setViewValue(value);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
</file>
|
|
|
|
|
<file name="index.html">
|
|
|
|
|
<input ng-model="someModel">
|
|
|
|
|
<div bad ng-model="someModel"></div>
|
|
|
|
|
</file>
|
|
|
|
|
* </example>
|
|
|
|
|
*
|
fix($compile): fix (reverse) directive postLink fn execution order
previously the compile/link fns executed in this order controlled via priority:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityHigh, PostLinkPriorityMedium, PostLinkPriorityLow
This was changed to:
- CompilePriorityHigh, CompilePriorityMedium, CompilePriorityLow
- PreLinkPriorityHigh, PreLinkPriorityMedium, PreLinkPriorityLow
- link children
- PostLinkPriorityLow, PostLinkPriorityMedium , PostLinkPriorityHigh
Using this order the child transclusion directive that gets replaced
onto the current element get executed correctly (see issue #3558),
and more generally, the order of execution of post linking function
makes more sense. The incorrect order was an oversight that has
gone unnoticed for many suns and moons.
(FYI: postLink functions are the default linking functions)
BREAKING CHANGE: the order of postLink fn is now mirror opposite of
the order in which corresponding preLinking and compile functions
execute.
Very few directives in practice rely on order of postLinking function
(unlike on the order of compile functions), so in the rare case
of this change affecting an existing directive, it might be necessary
to convert it to a preLinking function or give it negative priority
(look at the diff of this commit to see how an internal attribute
interpolation directive was adjusted).
Closes #3558
2013-10-01 14:55:47 +00:00
|
|
|
*
|
2012-02-24 03:47:58 +00:00
|
|
|
*/
|
2012-06-06 20:58:10 +00:00
|
|
|
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
|
|
|
|
|
function($scope, $exceptionHandler, $attr, $element, $parse) {
|
2012-03-13 04:12:15 +00:00
|
|
|
this.$viewValue = Number.NaN;
|
|
|
|
|
this.$modelValue = Number.NaN;
|
|
|
|
|
this.$parsers = [];
|
|
|
|
|
this.$formatters = [];
|
|
|
|
|
this.$viewChangeListeners = [];
|
|
|
|
|
this.$pristine = true;
|
|
|
|
|
this.$dirty = false;
|
|
|
|
|
this.$valid = true;
|
|
|
|
|
this.$invalid = false;
|
|
|
|
|
this.$name = $attr.name;
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-06-06 20:58:10 +00:00
|
|
|
var ngModelGet = $parse($attr.ngModel),
|
|
|
|
|
ngModelSet = ngModelGet.assign;
|
|
|
|
|
|
|
|
|
|
if (!ngModelSet) {
|
2013-08-01 22:11:10 +00:00
|
|
|
throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
|
2013-06-08 01:24:30 +00:00
|
|
|
$attr.ngModel, startingTag($element));
|
2012-06-06 20:58:10 +00:00
|
|
|
}
|
|
|
|
|
|
2012-05-25 17:29:54 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngModel.NgModelController#$render
|
|
|
|
|
* @methodOf ng.directive:ngModel.NgModelController
|
2012-05-25 17:29:54 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Called when the view needs to be updated. It is expected that the user of the ng-model
|
|
|
|
|
* directive will implement this method.
|
|
|
|
|
*/
|
|
|
|
|
this.$render = noop;
|
|
|
|
|
|
2012-03-13 20:17:39 +00:00
|
|
|
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
|
|
|
|
|
invalidCount = 0, // used to easily determine if we are valid
|
|
|
|
|
$error = this.$error = {}; // keep invalid keys here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Setup initial state of the control
|
|
|
|
|
$element.addClass(PRISTINE_CLASS);
|
|
|
|
|
toggleValidCss(true);
|
|
|
|
|
|
|
|
|
|
// convenience method for easy toggling of classes
|
|
|
|
|
function toggleValidCss(isValid, validationErrorKey) {
|
|
|
|
|
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
|
|
|
|
|
$element.
|
|
|
|
|
removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
|
|
|
|
|
addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
|
|
|
|
|
}
|
2012-02-24 03:47:58 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngModel.NgModelController#$setValidity
|
|
|
|
|
* @methodOf ng.directive:ngModel.NgModelController
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2012-03-13 04:30:03 +00:00
|
|
|
* Change the validity state, and notifies the form when the control changes validity. (i.e. it
|
2012-03-12 22:54:48 +00:00
|
|
|
* does not notify form if given validator is already marked as invalid).
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-03-12 22:54:48 +00:00
|
|
|
* This method should be called by validators - i.e. the parser or formatter functions.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-03-13 20:17:39 +00:00
|
|
|
* @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
|
|
|
|
|
* to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
|
|
|
|
|
* The `validationErrorKey` should be in camelCase and will get converted into dash-case
|
|
|
|
|
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
|
|
|
|
|
* class and can be bound to as `{{someForm.someControl.$error.myError}}` .
|
2012-03-12 22:54:48 +00:00
|
|
|
* @param {boolean} isValid Whether the current state is valid (true) or invalid (false).
|
2012-02-24 03:47:58 +00:00
|
|
|
*/
|
2012-03-13 07:29:10 +00:00
|
|
|
this.$setValidity = function(validationErrorKey, isValid) {
|
2012-03-13 20:17:39 +00:00
|
|
|
if ($error[validationErrorKey] === !isValid) return;
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
if (isValid) {
|
2012-03-13 20:17:39 +00:00
|
|
|
if ($error[validationErrorKey]) invalidCount--;
|
|
|
|
|
if (!invalidCount) {
|
2012-03-16 00:17:23 +00:00
|
|
|
toggleValidCss(true);
|
2012-03-13 04:12:15 +00:00
|
|
|
this.$valid = true;
|
|
|
|
|
this.$invalid = false;
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
2012-02-24 03:47:58 +00:00
|
|
|
} else {
|
2012-04-10 21:29:49 +00:00
|
|
|
toggleValidCss(false);
|
2012-03-13 04:12:15 +00:00
|
|
|
this.$invalid = true;
|
|
|
|
|
this.$valid = false;
|
2012-03-16 00:17:23 +00:00
|
|
|
invalidCount++;
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-16 00:17:23 +00:00
|
|
|
$error[validationErrorKey] = !isValid;
|
|
|
|
|
toggleValidCss(isValid, validationErrorKey);
|
|
|
|
|
|
2012-03-13 21:56:52 +00:00
|
|
|
parentForm.$setValidity(validationErrorKey, isValid, this);
|
2012-02-16 01:16:02 +00:00
|
|
|
};
|
|
|
|
|
|
2012-07-07 17:02:04 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
|
|
|
|
* @name ng.directive:ngModel.NgModelController#$setPristine
|
|
|
|
|
* @methodOf ng.directive:ngModel.NgModelController
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Sets the control to its pristine state.
|
|
|
|
|
*
|
|
|
|
|
* This method can be called to remove the 'ng-dirty' class and set the control to its pristine
|
|
|
|
|
* state (ng-pristine class).
|
|
|
|
|
*/
|
|
|
|
|
this.$setPristine = function () {
|
|
|
|
|
this.$dirty = false;
|
|
|
|
|
this.$pristine = true;
|
|
|
|
|
$element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
|
|
|
|
|
};
|
2012-02-24 03:47:58 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngModel.NgModelController#$setViewValue
|
|
|
|
|
* @methodOf ng.directive:ngModel.NgModelController
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Read a value from view.
|
|
|
|
|
*
|
|
|
|
|
* This method should be called from within a DOM event handler.
|
2012-06-12 06:49:24 +00:00
|
|
|
* For example {@link ng.directive:input input} or
|
|
|
|
|
* {@link ng.directive:select select} directives call it.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2013-08-09 16:52:27 +00:00
|
|
|
* It internally calls all `$parsers` (including validators) and updates the `$modelValue` and the actual model path.
|
2013-08-09 06:07:46 +00:00
|
|
|
* Lastly it calls all registered change listeners.
|
|
|
|
|
*
|
2012-03-12 22:54:48 +00:00
|
|
|
* @param {string} value Value from the view.
|
2012-02-24 03:47:58 +00:00
|
|
|
*/
|
2012-03-13 04:12:15 +00:00
|
|
|
this.$setViewValue = function(value) {
|
|
|
|
|
this.$viewValue = value;
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-03-10 01:19:48 +00:00
|
|
|
// change to dirty
|
2012-03-13 04:12:15 +00:00
|
|
|
if (this.$pristine) {
|
|
|
|
|
this.$dirty = true;
|
|
|
|
|
this.$pristine = false;
|
2012-03-13 20:17:39 +00:00
|
|
|
$element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
|
2012-03-13 21:56:52 +00:00
|
|
|
parentForm.$setDirty();
|
2012-03-10 01:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
forEach(this.$parsers, function(fn) {
|
2012-02-16 01:16:02 +00:00
|
|
|
value = fn(value);
|
|
|
|
|
});
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
if (this.$modelValue !== value) {
|
|
|
|
|
this.$modelValue = value;
|
2012-06-06 20:58:10 +00:00
|
|
|
ngModelSet($scope, value);
|
2012-03-13 04:12:15 +00:00
|
|
|
forEach(this.$viewChangeListeners, function(listener) {
|
2012-03-12 22:54:48 +00:00
|
|
|
try {
|
|
|
|
|
listener();
|
|
|
|
|
} catch(e) {
|
|
|
|
|
$exceptionHandler(e);
|
|
|
|
|
}
|
|
|
|
|
})
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// model -> value
|
|
|
|
|
var ctrl = this;
|
|
|
|
|
|
2012-11-23 21:40:01 +00:00
|
|
|
$scope.$watch(function ngModelWatch() {
|
|
|
|
|
var value = ngModelGet($scope);
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-11-23 21:40:01 +00:00
|
|
|
// if scope model value and ngModel value are out of sync
|
|
|
|
|
if (ctrl.$modelValue !== value) {
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-11-23 21:40:01 +00:00
|
|
|
var formatters = ctrl.$formatters,
|
|
|
|
|
idx = formatters.length;
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2012-11-23 21:40:01 +00:00
|
|
|
ctrl.$modelValue = value;
|
|
|
|
|
while(idx--) {
|
|
|
|
|
value = formatters[idx](value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctrl.$viewValue !== value) {
|
|
|
|
|
ctrl.$viewValue = value;
|
|
|
|
|
ctrl.$render();
|
|
|
|
|
}
|
2012-02-16 01:16:02 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngModel
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @element input
|
|
|
|
|
*
|
|
|
|
|
* @description
|
2013-10-05 22:00:50 +00:00
|
|
|
* The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
|
|
|
|
|
* property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController},
|
|
|
|
|
* which is created and exposed by this directive.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* `ngModel` is responsible for:
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2013-10-05 22:00:50 +00:00
|
|
|
* - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
|
|
|
|
|
* require.
|
|
|
|
|
* - Providing validation behavior (i.e. required, number, email, url).
|
|
|
|
|
* - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors).
|
|
|
|
|
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`).
|
|
|
|
|
* - Registering the control with its parent {@link ng.directive:form form}.
|
2013-07-10 19:42:50 +00:00
|
|
|
*
|
2013-06-04 19:12:31 +00:00
|
|
|
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
|
|
|
|
|
* current scope. If the property doesn't already exist on this scope, it will be created
|
|
|
|
|
* implicitly and added to the scope.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2013-09-11 17:51:20 +00:00
|
|
|
* For best practices on using `ngModel`, see:
|
|
|
|
|
*
|
|
|
|
|
* - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes}
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* For basic examples, how to use `ngModel`, see:
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
2012-06-12 06:49:24 +00:00
|
|
|
* - {@link ng.directive:input input}
|
|
|
|
|
* - {@link ng.directive:input.text text}
|
|
|
|
|
* - {@link ng.directive:input.checkbox checkbox}
|
|
|
|
|
* - {@link ng.directive:input.radio radio}
|
|
|
|
|
* - {@link ng.directive:input.number number}
|
|
|
|
|
* - {@link ng.directive:input.email email}
|
|
|
|
|
* - {@link ng.directive:input.url url}
|
|
|
|
|
* - {@link ng.directive:select select}
|
|
|
|
|
* - {@link ng.directive:textarea textarea}
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
*/
|
2012-05-25 17:29:54 +00:00
|
|
|
var ngModelDirective = function() {
|
2012-02-16 01:16:02 +00:00
|
|
|
return {
|
2012-03-12 22:54:48 +00:00
|
|
|
require: ['ngModel', '^?form'],
|
2012-02-16 01:16:02 +00:00
|
|
|
controller: NgModelController,
|
2012-03-12 22:54:48 +00:00
|
|
|
link: function(scope, element, attr, ctrls) {
|
2012-03-08 23:03:24 +00:00
|
|
|
// notify others, especially parent forms
|
2012-03-12 22:54:48 +00:00
|
|
|
|
|
|
|
|
var modelCtrl = ctrls[0],
|
2012-03-13 21:56:52 +00:00
|
|
|
formCtrl = ctrls[1] || nullFormCtrl;
|
2012-03-12 22:54:48 +00:00
|
|
|
|
2012-03-13 21:56:52 +00:00
|
|
|
formCtrl.$addControl(modelCtrl);
|
2012-02-16 01:16:02 +00:00
|
|
|
|
2013-06-19 19:52:50 +00:00
|
|
|
element.on('$destroy', function() {
|
2012-03-13 21:56:52 +00:00
|
|
|
formCtrl.$removeControl(modelCtrl);
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2012-05-25 17:29:54 +00:00
|
|
|
};
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngChange
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Evaluate given expression when user changes the input.
|
|
|
|
|
* The expression is not evaluated when the value change is coming from the model.
|
|
|
|
|
*
|
2012-04-06 23:35:17 +00:00
|
|
|
* Note, this directive requires `ngModel` to be present.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @element input
|
2013-09-08 21:24:13 +00:00
|
|
|
* @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
|
|
|
|
|
* in input value.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* <doc:example>
|
|
|
|
|
* <doc:source>
|
|
|
|
|
* <script>
|
|
|
|
|
* function Controller($scope) {
|
|
|
|
|
* $scope.counter = 0;
|
|
|
|
|
* $scope.change = function() {
|
|
|
|
|
* $scope.counter++;
|
|
|
|
|
* };
|
|
|
|
|
* }
|
|
|
|
|
* </script>
|
2012-03-09 08:00:05 +00:00
|
|
|
* <div ng-controller="Controller">
|
|
|
|
|
* <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
|
|
|
|
|
* <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
|
2012-02-24 03:47:58 +00:00
|
|
|
* <label for="ng-change-example2">Confirmed</label><br />
|
|
|
|
|
* debug = {{confirmed}}<br />
|
|
|
|
|
* counter = {{counter}}
|
|
|
|
|
* </div>
|
|
|
|
|
* </doc:source>
|
|
|
|
|
* <doc:scenario>
|
|
|
|
|
* it('should evaluate the expression if changing from view', function() {
|
|
|
|
|
* expect(binding('counter')).toEqual('0');
|
|
|
|
|
* element('#ng-change-example1').click();
|
|
|
|
|
* expect(binding('counter')).toEqual('1');
|
|
|
|
|
* expect(binding('confirmed')).toEqual('true');
|
|
|
|
|
* });
|
|
|
|
|
*
|
|
|
|
|
* it('should not evaluate the expression if changing from model', function() {
|
|
|
|
|
* element('#ng-change-example2').click();
|
|
|
|
|
* expect(binding('counter')).toEqual('0');
|
|
|
|
|
* expect(binding('confirmed')).toEqual('true');
|
|
|
|
|
* });
|
|
|
|
|
* </doc:scenario>
|
|
|
|
|
* </doc:example>
|
|
|
|
|
*/
|
2012-02-16 01:16:02 +00:00
|
|
|
var ngChangeDirective = valueFn({
|
|
|
|
|
require: 'ngModel',
|
|
|
|
|
link: function(scope, element, attr, ctrl) {
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$viewChangeListeners.push(function() {
|
2012-03-12 22:54:48 +00:00
|
|
|
scope.$eval(attr.ngChange);
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2012-05-25 17:29:54 +00:00
|
|
|
var requiredDirective = function() {
|
2012-02-16 01:16:02 +00:00
|
|
|
return {
|
|
|
|
|
require: '?ngModel',
|
|
|
|
|
link: function(scope, elm, attr, ctrl) {
|
|
|
|
|
if (!ctrl) return;
|
2012-05-25 17:29:54 +00:00
|
|
|
attr.required = true; // force truthy in case we are on non input element
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
var validator = function(value) {
|
2012-03-12 08:24:43 +00:00
|
|
|
if (attr.required && (isEmpty(value) || value === false)) {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('required', false);
|
2012-03-12 22:54:48 +00:00
|
|
|
return;
|
2012-02-16 01:16:02 +00:00
|
|
|
} else {
|
2012-03-13 07:29:10 +00:00
|
|
|
ctrl.$setValidity('required', true);
|
2012-02-16 01:16:02 +00:00
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$formatters.push(validator);
|
|
|
|
|
ctrl.$parsers.unshift(validator);
|
2012-02-16 01:16:02 +00:00
|
|
|
|
|
|
|
|
attr.$observe('required', function() {
|
2012-03-13 04:12:15 +00:00
|
|
|
validator(ctrl.$viewValue);
|
2012-02-16 01:16:02 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2012-05-25 17:29:54 +00:00
|
|
|
};
|
2012-02-24 03:47:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @ngdoc directive
|
2012-06-12 06:49:24 +00:00
|
|
|
* @name ng.directive:ngList
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @description
|
2013-10-03 03:42:01 +00:00
|
|
|
* Text input that converts between a delimited string and an array of strings. The delimiter
|
|
|
|
|
* can be a fixed string (by default a comma) or a regular expression.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @element input
|
2012-04-06 23:35:17 +00:00
|
|
|
* @param {string=} ngList optional delimiter that should be used to split the value. If
|
2012-03-13 05:56:36 +00:00
|
|
|
* specified in form `/something/` then the value will be converted into a regular expression.
|
2012-02-24 03:47:58 +00:00
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.names = ['igor', 'misko', 'vojta'];
|
|
|
|
|
}
|
|
|
|
|
</script>
|
2012-03-09 08:00:05 +00:00
|
|
|
<form name="myForm" ng-controller="Ctrl">
|
2012-03-13 04:12:15 +00:00
|
|
|
List: <input name="namesInput" ng-model="names" ng-list required>
|
2013-07-10 19:52:28 +00:00
|
|
|
<span class="error" ng-show="myForm.namesInput.$error.required">
|
2012-02-24 03:47:58 +00:00
|
|
|
Required!</span>
|
2013-07-10 19:52:28 +00:00
|
|
|
<br>
|
2012-02-24 03:47:58 +00:00
|
|
|
<tt>names = {{names}}</tt><br/>
|
2012-03-13 04:12:15 +00:00
|
|
|
<tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
|
|
|
|
|
<tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
|
|
|
|
|
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
2012-03-13 07:29:10 +00:00
|
|
|
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
2012-02-24 03:47:58 +00:00
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
|
|
|
|
expect(binding('names')).toEqual('["igor","misko","vojta"]');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.namesInput.$valid')).toEqual('true');
|
2013-07-10 19:52:28 +00:00
|
|
|
expect(element('span.error').css('display')).toBe('none');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be invalid if empty', function() {
|
|
|
|
|
input('names').enter('');
|
2012-03-08 00:54:12 +00:00
|
|
|
expect(binding('names')).toEqual('[]');
|
2012-03-13 04:12:15 +00:00
|
|
|
expect(binding('myForm.namesInput.$valid')).toEqual('false');
|
2013-07-10 19:52:28 +00:00
|
|
|
expect(element('span.error').css('display')).not().toBe('none');
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
|
|
|
|
var ngListDirective = function() {
|
|
|
|
|
return {
|
|
|
|
|
require: 'ngModel',
|
|
|
|
|
link: function(scope, element, attr, ctrl) {
|
2012-03-13 05:56:36 +00:00
|
|
|
var match = /\/(.*)\//.exec(attr.ngList),
|
|
|
|
|
separator = match && new RegExp(match[1]) || attr.ngList || ',';
|
|
|
|
|
|
2012-02-24 03:47:58 +00:00
|
|
|
var parse = function(viewValue) {
|
|
|
|
|
var list = [];
|
|
|
|
|
|
|
|
|
|
if (viewValue) {
|
2012-03-13 05:56:36 +00:00
|
|
|
forEach(viewValue.split(separator), function(value) {
|
|
|
|
|
if (value) list.push(trim(value));
|
2012-02-24 03:47:58 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
};
|
|
|
|
|
|
2012-03-13 04:12:15 +00:00
|
|
|
ctrl.$parsers.push(parse);
|
|
|
|
|
ctrl.$formatters.push(function(value) {
|
2012-06-01 21:58:51 +00:00
|
|
|
if (isArray(value)) {
|
2012-02-24 03:47:58 +00:00
|
|
|
return value.join(', ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
2012-03-23 20:04:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
|
2013-10-03 20:28:44 +00:00
|
|
|
/**
|
|
|
|
|
* @ngdoc directive
|
|
|
|
|
* @name ng.directive:ngValue
|
|
|
|
|
*
|
|
|
|
|
* @description
|
|
|
|
|
* Binds the given expression to the value of `input[select]` or `input[radio]`, so
|
|
|
|
|
* that when the element is selected, the `ngModel` of that element is set to the
|
|
|
|
|
* bound value.
|
|
|
|
|
*
|
|
|
|
|
* `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as
|
|
|
|
|
* shown below.
|
|
|
|
|
*
|
|
|
|
|
* @element input
|
|
|
|
|
* @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute
|
|
|
|
|
* of the `input` element
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
<doc:example>
|
|
|
|
|
<doc:source>
|
|
|
|
|
<script>
|
|
|
|
|
function Ctrl($scope) {
|
|
|
|
|
$scope.names = ['pizza', 'unicorns', 'robots'];
|
|
|
|
|
$scope.my = { favorite: 'unicorns' };
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
<form ng-controller="Ctrl">
|
|
|
|
|
<h2>Which is your favorite?</h2>
|
|
|
|
|
<label ng-repeat="name in names" for="{{name}}">
|
|
|
|
|
{{name}}
|
|
|
|
|
<input type="radio"
|
|
|
|
|
ng-model="my.favorite"
|
|
|
|
|
ng-value="name"
|
|
|
|
|
id="{{name}}"
|
|
|
|
|
name="favorite">
|
|
|
|
|
</label>
|
|
|
|
|
</span>
|
|
|
|
|
<div>You chose {{my.favorite}}</div>
|
|
|
|
|
</form>
|
|
|
|
|
</doc:source>
|
|
|
|
|
<doc:scenario>
|
|
|
|
|
it('should initialize to model', function() {
|
|
|
|
|
expect(binding('my.favorite')).toEqual('unicorns');
|
|
|
|
|
});
|
|
|
|
|
it('should bind the values to the inputs', function() {
|
|
|
|
|
input('my.favorite').select('pizza');
|
|
|
|
|
expect(binding('my.favorite')).toEqual('pizza');
|
|
|
|
|
});
|
|
|
|
|
</doc:scenario>
|
|
|
|
|
</doc:example>
|
|
|
|
|
*/
|
2012-05-25 17:29:54 +00:00
|
|
|
var ngValueDirective = function() {
|
2012-03-23 20:04:52 +00:00
|
|
|
return {
|
|
|
|
|
priority: 100,
|
2012-04-10 20:41:51 +00:00
|
|
|
compile: function(tpl, tplAttr) {
|
|
|
|
|
if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
|
2013-10-03 20:28:44 +00:00
|
|
|
return function ngValueConstantLink(scope, elm, attr) {
|
2012-03-23 20:04:52 +00:00
|
|
|
attr.$set('value', scope.$eval(attr.ngValue));
|
|
|
|
|
};
|
|
|
|
|
} else {
|
2013-10-03 20:28:44 +00:00
|
|
|
return function ngValueLink(scope, elm, attr) {
|
2012-07-06 09:53:40 +00:00
|
|
|
scope.$watch(attr.ngValue, function valueWatchAction(value) {
|
2013-05-14 17:14:06 +00:00
|
|
|
attr.$set('value', value);
|
2012-03-23 20:04:52 +00:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2012-05-25 17:29:54 +00:00
|
|
|
};
|