Permalink
Comparing changes
Open a pull request
- 2 commits
- 3 files changed
- 0 commit comments
- 2 contributors
Unified
Split
Showing
with
153 additions
and 59 deletions.
- +78 −55 src/ng/directive/validators.js
- +36 −4 src/ngAnimate/animateQueue.js
- +39 −0 test/ngAnimate/integrationSpec.js
| @@ -6,7 +6,7 @@ | ||
| * @description | ||
| * | ||
| * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. | ||
| * It is most often used for [@link input `input`} and {@link select `select`} controls, but can also be | ||
| * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be | ||
| * applied to custom controls. | ||
| * | ||
| * The directive sets the `required` attribute on the element if the Angular expression inside | ||
| @@ -43,16 +43,17 @@ | ||
| * </div> | ||
| * </file> | ||
| * <file name="protractor.js" type="protractor"> | ||
| * var required = element(by.binding('form.input.$error.required')); | ||
| * var model = element(by.binding('model')); | ||
| * | ||
| * it('should set the required error', function() { | ||
| * expect(required.getText()).toContain('true'); | ||
| * | ||
| * element(by.id('input')).sendKeys('123'); | ||
| * expect(required.getText()).not.toContain('true'); | ||
| * expect(model.getText()).toContain('123'); | ||
| * }); | ||
| var required = element(by.binding('form.input.$error.required')); | ||
| var model = element(by.binding('model')); | ||
| var input = element(by.id('input')); | ||
| it('should set the required error', function() { | ||
| expect(required.getText()).toContain('true'); | ||
| input.sendKeys('123'); | ||
| expect(required.getText()).not.toContain('true'); | ||
| expect(model.getText()).toContain('123'); | ||
| }); | ||
| * </file> | ||
| * </example> | ||
| */ | ||
| @@ -82,14 +83,14 @@ var requiredDirective = function() { | ||
| * @description | ||
| * | ||
| * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. | ||
| * It is most often used for text-based [@link input `input`} controls, but can also be applied to custom text-based controls. | ||
| * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. | ||
| * | ||
| * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} | ||
| * does not match a RegExp which is obtained by evaluating the Angular expression given in the | ||
| * `ngPattern` attribute value: | ||
| * * If the expression evaluates to a RegExp object, then this is used directly. | ||
| * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it | ||
| * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. | ||
| * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} | ||
| * does not match a RegExp which is obtained by evaluating the Angular expression given in the | ||
| * `ngPattern` attribute value: | ||
| * * If the expression evaluates to a RegExp object, then this is used directly. | ||
| * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it | ||
| * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. | ||
| * | ||
| * <div class="alert alert-info"> | ||
| * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to | ||
| @@ -100,9 +101,16 @@ var requiredDirective = function() { | ||
| * <div class="alert alert-info"> | ||
| * **Note:** This directive is also added when the plain `pattern` attribute is used, with two | ||
| * differences: | ||
| * 1. `ngPattern` does not set the `pattern` attribute and therefore not HTML5 constraint validation | ||
| * is available. | ||
| * 2. The `ngPattern` attribute must be an expression, while the `pattern` value must be interpolated | ||
| * <ol> | ||
| * <li> | ||
| * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is | ||
| * not available. | ||
| * </li> | ||
| * <li> | ||
| * The `ngPattern` attribute must be an expression, while the `pattern` value must be | ||
| * interpolated. | ||
| * </li> | ||
| * </ol> | ||
| * </div> | ||
| * | ||
| * @example | ||
| @@ -128,18 +136,18 @@ var requiredDirective = function() { | ||
| * </div> | ||
| * </file> | ||
| * <file name="protractor.js" type="protractor"> | ||
| var model = element(by.binding('model')); | ||
| var input = element(by.id('input')); | ||
| var model = element(by.binding('model')); | ||
| var input = element(by.id('input')); | ||
| it('should validate the input with the default pattern', function() { | ||
| input.sendKeys('aaa'); | ||
| expect(model.getText()).not.toContain('aaa'); | ||
| it('should validate the input with the default pattern', function() { | ||
| input.sendKeys('aaa'); | ||
| expect(model.getText()).not.toContain('aaa'); | ||
| input.clear().then(function() { | ||
| input.sendKeys('123'); | ||
| expect(model.getText()).toContain('123'); | ||
| }); | ||
| }); | ||
| input.clear().then(function() { | ||
| input.sendKeys('123'); | ||
| expect(model.getText()).toContain('123'); | ||
| }); | ||
| }); | ||
| * </file> | ||
| * </example> | ||
| */ | ||
| @@ -181,18 +189,25 @@ var patternDirective = function() { | ||
| * @description | ||
| * | ||
| * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. | ||
| * It is most often used for text-based [@link input `input`} controls, but can also be applied to custom text-based controls. | ||
| * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. | ||
| * | ||
| * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} | ||
| * is longer than the integer obtained by evaluating the Angular expression given in the | ||
| * `ngMaxlength` attribute value. | ||
| * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} | ||
| * is longer than the integer obtained by evaluating the Angular expression given in the | ||
| * `ngMaxlength` attribute value. | ||
| * | ||
| * <div class="alert alert-info"> | ||
| * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two | ||
| * differences: | ||
| * 1. `ngMaxlength` does not set the `maxlength` attribute and therefore not HTML5 constraint validation | ||
| * is available. | ||
| * 2. The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be interpolated | ||
| * <ol> | ||
| * <li> | ||
| * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint | ||
| * validation is not available. | ||
| * </li> | ||
| * <li> | ||
| * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be | ||
| * interpolated. | ||
| * </li> | ||
| * </ol> | ||
| * </div> | ||
| * | ||
| * @example | ||
| @@ -218,7 +233,7 @@ var patternDirective = function() { | ||
| * </div> | ||
| * </file> | ||
| * <file name="protractor.js" type="protractor"> | ||
| * var model = element(by.binding('model')); | ||
| var model = element(by.binding('model')); | ||
| var input = element(by.id('input')); | ||
| it('should validate the input with the default maxlength', function() { | ||
| @@ -260,18 +275,25 @@ var maxlengthDirective = function() { | ||
| * @description | ||
| * | ||
| * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. | ||
| * It is most often used for text-based [@link input `input`} controls, but can also be applied to custom text-based controls. | ||
| * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. | ||
| * | ||
| * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} | ||
| * is shorter than the integer obtained by evaluating the Angular expression given in the | ||
| * `ngMinlength` attribute value. | ||
| * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} | ||
| * is shorter than the integer obtained by evaluating the Angular expression given in the | ||
| * `ngMinlength` attribute value. | ||
| * | ||
| * <div class="alert alert-info"> | ||
| * **Note:** This directive is also added when the plain `minlength` attribute is used, with two | ||
| * differences: | ||
| * 1. `ngMinlength` does not set the `minlength` attribute and therefore not HTML5 constraint validation | ||
| * is available. | ||
| * 2. The `ngMinlength` value must be an expression, while the `minlength` value must be interpolated | ||
| * <ol> | ||
| * <li> | ||
| * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint | ||
| * validation is not available. | ||
| * </li> | ||
| * <li> | ||
| * The `ngMinlength` value must be an expression, while the `minlength` value must be | ||
| * interpolated. | ||
| * </li> | ||
| * </ol> | ||
| * </div> | ||
| * | ||
| * @example | ||
| @@ -297,15 +319,16 @@ var maxlengthDirective = function() { | ||
| * </div> | ||
| * </file> | ||
| * <file name="protractor.js" type="protractor"> | ||
| * var model = element(by.binding('model')); | ||
| * | ||
| * it('should validate the input with the default minlength', function() { | ||
| * element(by.id('input')).sendKeys('ab'); | ||
| * expect(model.getText()).not.toContain('ab'); | ||
| * | ||
| * element(by.id('input')).sendKeys('abc'); | ||
| * expect(model.getText()).toContain('abc'); | ||
| * }); | ||
| var model = element(by.binding('model')); | ||
| var input = element(by.id('input')); | ||
| it('should validate the input with the default minlength', function() { | ||
| input.sendKeys('ab'); | ||
| expect(model.getText()).not.toContain('ab'); | ||
| input.sendKeys('abc'); | ||
| expect(model.getText()).toContain('abc'); | ||
| }); | ||
| * </file> | ||
| * </example> | ||
| */ | ||
| @@ -5,13 +5,37 @@ var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; | ||
| var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { | ||
| var PRE_DIGEST_STATE = 1; | ||
| var RUNNING_STATE = 2; | ||
| var ONE_SPACE = ' '; | ||
|
|
||
| var rules = this.rules = { | ||
| skip: [], | ||
| cancel: [], | ||
| join: [] | ||
| }; | ||
|
|
||
| function makeTruthyCssClassMap(classString) { | ||
| if (!classString) { | ||
| return null; | ||
| } | ||
|
|
||
| var keys = classString.split(ONE_SPACE); | ||
| var map = Object.create(null); | ||
|
|
||
| forEach(keys, function(key) { | ||
| map[key] = true; | ||
| }); | ||
| return map; | ||
| } | ||
|
|
||
| function hasMatchingClasses(newClassString, currentClassString) { | ||
| if (newClassString && currentClassString) { | ||
| var currentClassMap = makeTruthyCssClassMap(currentClassString); | ||
| return newClassString.split(ONE_SPACE).some(function(className) { | ||
| return currentClassMap[className]; | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function isAllowed(ruleType, element, currentAnimation, previousAnimation) { | ||
| return rules[ruleType].some(function(fn) { | ||
| return fn(element, currentAnimation, previousAnimation); | ||
| @@ -59,11 +83,19 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { | ||
| }); | ||
|
|
||
| rules.cancel.push(function(element, newAnimation, currentAnimation) { | ||
| var nO = newAnimation.options; | ||
| var cO = currentAnimation.options; | ||
|
|
||
| // if the exact same CSS class is added/removed then it's safe to cancel it | ||
| return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass); | ||
|
|
||
| var nA = newAnimation.options.addClass; | ||
| var nR = newAnimation.options.removeClass; | ||
| var cA = currentAnimation.options.addClass; | ||
| var cR = currentAnimation.options.removeClass; | ||
|
|
||
| // early detection to save the global CPU shortage :) | ||
| if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { | ||
| return false; | ||
| } | ||
|
|
||
| return (hasMatchingClasses(nA, cR)) || (hasMatchingClasses(nR, cA)); | ||
| }); | ||
|
|
||
| this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', | ||
| @@ -351,6 +351,45 @@ describe('ngAnimate integration tests', function() { | ||
|
|
||
| dealoc(element); | ||
| })); | ||
|
|
||
|
|
||
| it("should remove a class when the same class is currently being added by a joined class-based animation", | ||
| inject(function($animate, $animateCss, $rootScope, $document, $rootElement, $$rAF) { | ||
|
|
||
| ss.addRule('.hide', 'opacity: 0'); | ||
| ss.addRule('.hide-add, .hide-remove', 'transition: 1s linear all'); | ||
|
|
||
| jqLite($document[0].body).append($rootElement); | ||
| element = jqLite('<div></div>'); | ||
| $rootElement.append(element); | ||
|
|
||
| // These animations will be joined together | ||
| $animate.addClass(element, 'red'); | ||
| $animate.addClass(element, 'hide'); | ||
| $rootScope.$digest(); | ||
|
|
||
| expect(element).toHaveClass('red-add'); | ||
| expect(element).toHaveClass('hide-add'); | ||
|
|
||
| // When a digest has passed, but no $rAF has been issued yet, .hide hasn't been added to | ||
| // the element yet | ||
| $animate.removeClass(element, 'hide'); | ||
| $rootScope.$digest(); | ||
| $$rAF.flush(); | ||
|
|
||
| expect(element).not.toHaveClass('hide-add hide-add-active'); | ||
| expect(element).toHaveClass('hide-remove hide-remove-active'); | ||
|
|
||
| //End the animation process | ||
| browserTrigger(element, 'transitionend', | ||
| { timeStamp: Date.now() + 1000, elapsedTime: 2 }); | ||
| $animate.flush(); | ||
|
|
||
| expect(element).not.toHaveClass('hide-add-active red-add-active'); | ||
| expect(element).toHaveClass('red'); | ||
| expect(element).not.toHaveClass('hide'); | ||
| })); | ||
|
|
||
| }); | ||
|
|
||
| describe('JS animations', function() { | ||