Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat(input) add ng:minlength and ng:maxlength validation support #610

Merged
merged 3 commits into from
Oct 20, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 126 additions & 27 deletions src/widget/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
var INTEGER_REGEXP = /^\s*(\-|\+)?\d+\s*$/;


/**
* @ngdoc inputType
* @name angular.inputType.text
Expand All @@ -16,6 +17,10 @@ var INTEGER_REGEXP = /^\s*(\-|\+)?\d+\s*$/;
* @param {string} ng:model Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the widgets is published.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
Expand Down Expand Up @@ -79,6 +84,10 @@ var INTEGER_REGEXP = /^\s*(\-|\+)?\d+\s*$/;
* @param {string} ng:model Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the widgets is published.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
Expand Down Expand Up @@ -135,6 +144,7 @@ angularInputType('email', function() {
});
});


/**
* @ngdoc inputType
* @name angular.inputType.url
Expand All @@ -146,6 +156,10 @@ angularInputType('email', function() {
* @param {string} ng:model Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the widgets is published.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
Expand Down Expand Up @@ -204,6 +218,7 @@ angularInputType('url', function() {
});
});


/**
* @ngdoc inputType
* @name angular.inputType.list
Expand Down Expand Up @@ -275,6 +290,7 @@ angularInputType('list', function() {
};
});


/**
* @ngdoc inputType
* @name angular.inputType.number
Expand All @@ -288,6 +304,10 @@ angularInputType('list', function() {
* @param {string=} min Sets the `MIN` validation error key if the value entered is less then `min`.
* @param {string=} max Sets the `MAX` validation error key if the value entered is greater then `min`.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
Expand Down Expand Up @@ -340,6 +360,7 @@ angularInputType('list', function() {
*/
angularInputType('number', numericRegexpInputType(NUMBER_REGEXP, 'NUMBER'));


/**
* @ngdoc inputType
* @name angular.inputType.integer
Expand All @@ -353,6 +374,10 @@ angularInputType('number', numericRegexpInputType(NUMBER_REGEXP, 'NUMBER'));
* @param {string=} min Sets the `MIN` validation error key if the value entered is less then `min`.
* @param {string=} max Sets the `MAX` validation error key if the value entered is greater then `min`.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
Expand Down Expand Up @@ -405,6 +430,7 @@ angularInputType('number', numericRegexpInputType(NUMBER_REGEXP, 'NUMBER'));
*/
angularInputType('integer', numericRegexpInputType(INTEGER_REGEXP, 'INTEGER'));


/**
* @ngdoc inputType
* @name angular.inputType.checkbox
Expand Down Expand Up @@ -476,9 +502,9 @@ angularInputType('checkbox', function(inputElement) {
widget.$parseView = function() {
widget.$modelValue = widget.$viewValue ? trueValue : falseValue;
};

});


/**
* @ngdoc inputType
* @name angular.inputType.radio
Expand Down Expand Up @@ -601,6 +627,10 @@ var HTML5_INPUTS_TYPES = makeMap(
* @param {string} ng:model Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the widgets is published.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
Expand All @@ -612,32 +642,69 @@ var HTML5_INPUTS_TYPES = makeMap(
<doc:source>
<script>
function Ctrl() {
this.text = 'guest';
this.user = {name: 'guest', last: 'visitor'};
}
</script>
<div ng:controller="Ctrl">
<form name="myForm">
text: <input type="text" name="input" ng:model="text" required>
<span class="error" ng:show="myForm.input.$error.REQUIRED">
Required!</span>
User name: <input type="text" name="userName" ng:model="user.name" required>
<span class="error" ng:show="myForm.userName.$error.REQUIRED">
Required!</span><br>
Last name: <input type="text" name="lastName" ng:model="user.last"
ng:minlength="3" ng:maxlength="10">
<span class="error" ng:show="myForm.lastName.$error.MINLENGTH">
Too short!</span>
<span class="error" ng:show="myForm.lastName.$error.MAXLENGTH">
Too long!</span><br>
</form>
<tt>text = {{text}}</tt><br/>
<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/>
<tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt><br/>
<hr>
<tt>user = {{user}}</tt><br/>
<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>
<tt>myForm.userName.$error = {{myForm.lastName.$error}}</tt><br>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br>
<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>
</div>
</doc:source>
<doc:scenario>
it('should initialize to model', function() {
expect(binding('text')).toEqual('guest');
expect(binding('myForm.input.$valid')).toEqual('true');
expect(binding('user')).toEqual('{\n \"last\":\"visitor",\n \"name\":\"guest\"}');
expect(binding('myForm.userName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});

it('should be invalid if empty', function() {
input('text').enter('');
expect(binding('text')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
it('should be invalid if empty when required', function() {
input('user.name').enter('');
expect(binding('user')).toEqual('{\n \"last\":\"visitor",\n \"name\":\"\"}');
expect(binding('myForm.userName.$valid')).toEqual('false');
expect(binding('myForm.$valid')).toEqual('false');
});

it('should be valid if empty when min length is set', function() {
input('user.last').enter('');
expect(binding('user')).toEqual('{\n \"last\":\"",\n \"name\":\"guest\"}');
expect(binding('myForm.lastName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});

it('should be invalid if less than required min length', function() {
input('user.last').enter('xx');
expect(binding('user')).toEqual('{\n \"last\":\"xx",\n \"name\":\"guest\"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/MINLENGTH/);
expect(binding('myForm.$valid')).toEqual('false');
});

it('should be valid if longer than max length', function() {
input('user.last').enter('some ridiculously long name');
expect(binding('user'))
.toEqual('{\n \"last\":\"some ridiculously long name",\n \"name\":\"guest\"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/MAXLENGTH/);
expect(binding('myForm.$valid')).toEqual('false');
});
</doc:scenario>
</doc:example>
Expand All @@ -656,6 +723,8 @@ angularWidget('input', function(inputElement){
modelScope = this,
patternMatch, widget,
pattern = trim(inputElement.attr('ng:pattern')),
minlength = parseInt(inputElement.attr('ng:minlength'), 10),
maxlength = parseInt(inputElement.attr('ng:maxlength'), 10),
loadFromScope = type.match(/^\s*\@\s*(.*)/);


Expand Down Expand Up @@ -702,31 +771,37 @@ angularWidget('input', function(inputElement){
controller: TypeController,
controllerArgs: [inputElement]});

widget.$pattern =
watchElementProperty(this, widget, 'required', inputElement);
watchElementProperty(this, widget, 'readonly', inputElement);
watchElementProperty(this, widget, 'disabled', inputElement);


widget.$pristine = !(widget.$dirty = false);

widget.$on('$validate', function(event) {
var $viewValue = trim(widget.$viewValue);
var inValid = widget.$required && !$viewValue;
var missMatch = $viewValue && !patternMatch($viewValue);
widget.$on('$validate', function() {
var $viewValue = trim(widget.$viewValue),
inValid = widget.$required && !$viewValue,
tooLong = maxlength && $viewValue && $viewValue.length > maxlength,
tooShort = minlength && $viewValue && $viewValue.length < minlength,
missMatch = $viewValue && !patternMatch($viewValue);

if (widget.$error.REQUIRED != inValid){
widget.$emit(inValid ? '$invalid' : '$valid', 'REQUIRED');
}
if (widget.$error.PATTERN != missMatch){
widget.$emit(missMatch ? '$invalid' : '$valid', 'PATTERN');
}
if (widget.$error.MINLENGTH != tooShort){
widget.$emit(tooShort ? '$invalid' : '$valid', 'MINLENGTH');
}
if (widget.$error.MAXLENGTH != tooLong){
widget.$emit(tooLong ? '$invalid' : '$valid', 'MAXLENGTH');
}
});

forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) {
widget.$watch('$' + name, function(scope, value) {
inputElement[value ? 'addClass' : 'removeClass']('ng-' + name);
}
);
inputElement[value ? 'addClass' : 'removeClass']('ng-' + name);
});
});

inputElement.bind('$destroy', function() {
Expand Down Expand Up @@ -756,9 +831,34 @@ angularWidget('input', function(inputElement){
});
}
});

});


/**
* @ngdoc widget
* @name angular.widget.textarea
*
* @description
* HTML textarea element widget with angular data-binding. The data-binding and validation
* properties of this element are exactly the same as those of the
* {@link angular.widget.input input element}.
*
* @param {string} type Widget types as defined by {@link angular.inputType}. If the
* type is in the format of `@ScopeType` then `ScopeType` is loaded from the
* current scope, allowing quick definition of type.
* @param {string} ng:model Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the widgets is published.
* @param {string=} required Sets `REQUIRED` validation error key if the value is not entered.
* @param {number=} ng:minlength Sets `MINLENGTH` validation error key if the value is shorter than
* minlength.
* @param {number=} ng:maxlength Sets `MAXLENGTH` validation error key if the value is longer than
* maxlength.
* @param {string=} ng:pattern Sets `PATTERN` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
* @param {string=} ng:change Angular expression to be executed when input changes due to user
* interaction with the input element.
*/
angularWidget('textarea', angularWidget('input'));


Expand All @@ -777,4 +877,3 @@ function watchElementProperty(modelScope, widget, name, element) {
});
}
}

12 changes: 12 additions & 0 deletions test/widget/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,18 @@ describe('widget: input', function() {
});


itShouldVerify('text with ng:minlength limit',
['', 'aaa', 'aaaaa', 'aaaaaaaaa'],
['a', 'aa'],
{'ng:minlength': 3});


itShouldVerify('text with ng:maxlength limit',
['', 'a', 'aa', 'aaa'],
['aaaa', 'aaaaa', 'aaaaaaaaa'],
{'ng:maxlength': 3});


it('should throw an error when scope pattern can\'t be found', function() {
var el = jqLite('<input ng:model="foo" ng:pattern="fooRegexp">'),
scope = angular.compile(el)();
Expand Down