From 78f394fd17be581c84ecd526bb786ed1681d35cb Mon Sep 17 00:00:00 2001 From: Konstantin Stepanov Date: Tue, 18 Oct 2011 02:18:00 +0300 Subject: [PATCH 1/3] feat(input): add ng:minlength and ng:maxlength validation notes(igor): I also e2e tests and refactorred the e2e test example to be more clear about what is a variable and what is an html/framework api. --- src/widget/input.js | 108 ++++++++++++++++++++++++++++++++------- test/widget/inputSpec.js | 12 +++++ 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/widget/input.js b/src/widget/input.js index e920733be8ae..e9c7208eb823 100644 --- a/src/widget/input.js +++ b/src/widget/input.js @@ -16,6 +16,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. @@ -79,6 +83,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. @@ -146,6 +154,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. @@ -288,6 +300,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. @@ -353,6 +369,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. @@ -601,6 +621,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. @@ -612,32 +636,69 @@ var HTML5_INPUTS_TYPES = makeMap(
- text: - - Required! + User name: + + Required!
+ Last name: + + Too short! + + Too long!
- text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
+
+ user = {{user}}
+ myForm.userName.$valid = {{myForm.userName.$valid}}
+ myForm.userName.$error = {{myForm.userName.$error}}
+ myForm.lastName.$valid = {{myForm.lastName.$valid}}
+ myForm.userName.$error = {{myForm.lastName.$error}}
+ myForm.$valid = {{myForm.$valid}}
+ myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}
+ myForm.$error.MINLENGTH = {{!!myForm.$error.MINLENGTH}}
+ myForm.$error.MAXLENGTH = {{!!myForm.$error.MAXLENGTH}}
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'); }); @@ -656,6 +717,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*(.*)/); @@ -711,15 +774,24 @@ angularWidget('input', function(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); + 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) { diff --git a/test/widget/inputSpec.js b/test/widget/inputSpec.js index 837d8c83190c..6524f1e3503c 100644 --- a/test/widget/inputSpec.js +++ b/test/widget/inputSpec.js @@ -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(''), scope = angular.compile(el)(); From 3217a249e18c12fb1ade59073773e2ab67a67792 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Wed, 19 Oct 2011 16:42:32 -0700 Subject: [PATCH 2/3] style(input): fix style violations in the input.js file --- src/widget/input.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/widget/input.js b/src/widget/input.js index e9c7208eb823..627aee2793c1 100644 --- a/src/widget/input.js +++ b/src/widget/input.js @@ -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 @@ -143,6 +144,7 @@ angularInputType('email', function() { }); }); + /** * @ngdoc inputType * @name angular.inputType.url @@ -216,6 +218,7 @@ angularInputType('url', function() { }); }); + /** * @ngdoc inputType * @name angular.inputType.list @@ -287,6 +290,7 @@ angularInputType('list', function() { }; }); + /** * @ngdoc inputType * @name angular.inputType.number @@ -356,6 +360,7 @@ angularInputType('list', function() { */ angularInputType('number', numericRegexpInputType(NUMBER_REGEXP, 'NUMBER')); + /** * @ngdoc inputType * @name angular.inputType.integer @@ -425,6 +430,7 @@ angularInputType('number', numericRegexpInputType(NUMBER_REGEXP, 'NUMBER')); */ angularInputType('integer', numericRegexpInputType(INTEGER_REGEXP, 'INTEGER')); + /** * @ngdoc inputType * @name angular.inputType.checkbox @@ -496,9 +502,9 @@ angularInputType('checkbox', function(inputElement) { widget.$parseView = function() { widget.$modelValue = widget.$viewValue ? trueValue : falseValue; }; - }); + /** * @ngdoc inputType * @name angular.inputType.radio @@ -765,15 +771,13 @@ 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) { + widget.$on('$validate', function() { var $viewValue = trim(widget.$viewValue), inValid = widget.$required && !$viewValue, tooLong = maxlength && $viewValue && $viewValue.length > maxlength, @@ -796,9 +800,8 @@ angularWidget('input', function(inputElement){ 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() { @@ -849,4 +852,3 @@ function watchElementProperty(modelScope, widget, name, element) { }); } } - From a46f2a0db37c88465072d0eec22b0439bf003ff7 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Wed, 19 Oct 2011 16:43:16 -0700 Subject: [PATCH 3/3] docs(textarea): add docs for angular.widget.textarea --- src/widget/input.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/widget/input.js b/src/widget/input.js index 627aee2793c1..a530b6a89d85 100644 --- a/src/widget/input.js +++ b/src/widget/input.js @@ -831,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'));