Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 19ecdb54bf | |||
| 30aa3eff4c | |||
| 8d39bd8abf | |||
| 472d076cca | |||
| 1ae0be13c2 | |||
| 159efdd429 | |||
| 4755a35b7d | |||
| b2f8b0b875 | |||
| 24cd70058d | |||
| e46ab43422 | |||
| 8a62a8c7f0 | |||
| b6c2f8b854 | |||
| 00ee090f4f | |||
| 544001f5a3 | |||
| 7175d0d0e3 | |||
| 010d9b6853 | |||
| 122ab074ca | |||
| e22bf9ac78 | |||
| 324cb6b358 | |||
| 80a2176e20 | |||
| 681affef59 | |||
| 0ca8b1df20 | |||
| 20fb626b78 | |||
| 7ea2c7f36e | |||
| 912cbdd468 | |||
| 0202663e93 | |||
| 159bbf11ac | |||
| 9d2cc8341c | |||
| 38520a1a73 | |||
| 470eb37d29 | |||
| 4baf25b3ce | |||
| eb193686a5 | |||
| 4bebe7830b | |||
| 146cbf7eaa | |||
| b8e356191f | |||
| f48244ce5e | |||
| ebba426c0c | |||
| aa11dfc162 | |||
| c6110e8b08 | |||
| 290b5049c2 | |||
| f8a07dd9fe | |||
| 6f39f10827 | |||
| c3a654b7c8 | |||
| e7293daf2a | |||
| c71d414a95 | |||
| 06d4e18cda | |||
| 966e01cf26 | |||
| 67afd9dc63 | |||
| 4175860af1 | |||
| 6fb90bda9a | |||
| 770dd2dcfd | |||
| 0ff7bba2e3 | |||
| 82b0929e4e | |||
| 7d2c6eeef8 | |||
| 6d8c1950a0 | |||
| 1a5ea22079 | |||
| 4f9eb2c6e4 | |||
| 43769fb676 | |||
| 170cd96646 | |||
| 1d18e60ef7 | |||
| ea8016c4c8 | |||
| c3d5e33e18 | |||
| 2f6b6fb7a1 | |||
| ea2518fcea | |||
| 7e67e525a5 | |||
| 0e001084ff | |||
| 85e3203918 | |||
| f95bc42cee | |||
| 9080d2c53c | |||
| 728f7e2a85 | |||
| 5f704065a7 | |||
| 64631bf2e6 | |||
| aa35b243f8 | |||
| 1cc9c9ca9d | |||
| dc48aadd26 | |||
| ebce2f7253 | |||
| d0e50fdcd0 | |||
| d88167318d | |||
| 0a75a3db6e | |||
| b643f0d322 | |||
| 01dd588a28 | |||
| 3d6dc3fe31 | |||
| 0c81e9fd25 | |||
| 5df80e1854 | |||
| ba9fb82f18 |
+167
@@ -1,3 +1,138 @@
|
||||
<a name="1.4.5"></a>
|
||||
# 1.4.5 permanent-internship (2015-08-28)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** `$animate.enabled(false)` should disable animations on $animateCss as well
|
||||
([c3d5e33e](https://github.com/angular/angular.js/commit/c3d5e33e18bd9e423e2d0678e85564fad1dba99f),
|
||||
[#12696](https://github.com/angular/angular.js/issues/12696), [#12685](https://github.com/angular/angular.js/issues/12685))
|
||||
- **$animateCss:**
|
||||
- do not throw errors when a closing timeout is fired on a removed element
|
||||
([2f6b6fb7](https://github.com/angular/angular.js/commit/2f6b6fb7a1dee0ff97c5d2959b927347eeda6e8b),
|
||||
[#12650](https://github.com/angular/angular.js/issues/12650))
|
||||
- fix parse errors on older Android WebViews
|
||||
([1cc9c9ca](https://github.com/angular/angular.js/commit/1cc9c9ca9d9698356ea541517b3d06ce6556c01d),
|
||||
[#12610](https://github.com/angular/angular.js/issues/12610))
|
||||
- properly handle cancellation timeouts for follow-up animations
|
||||
([d8816731](https://github.com/angular/angular.js/commit/d88167318d1c69f0dbd2101c05955eb450c34fd5),
|
||||
[#12490](https://github.com/angular/angular.js/issues/12490), [#12359](https://github.com/angular/angular.js/issues/12359))
|
||||
- ensure failed animations clear the internal cache
|
||||
([0a75a3db](https://github.com/angular/angular.js/commit/0a75a3db6ef265389c8c955981c2fe67bb4f7769),
|
||||
[#12214](https://github.com/angular/angular.js/issues/12214), [#12518](https://github.com/angular/angular.js/issues/12518), [#12381](https://github.com/angular/angular.js/issues/12381))
|
||||
- the transitions options delay value should be applied before class application
|
||||
([0c81e9fd](https://github.com/angular/angular.js/commit/0c81e9fd25285dd757db98d458919776a1fb62fc),
|
||||
[#12584](https://github.com/angular/angular.js/issues/12584))
|
||||
- **ngAnimate:**
|
||||
- use requestAnimationFrame to space out child animations
|
||||
([ea8016c4](https://github.com/angular/angular.js/commit/ea8016c4c8f55bc021549f342618ed869998e335),
|
||||
[#12669](https://github.com/angular/angular.js/issues/12669), [#12594](https://github.com/angular/angular.js/issues/12594), [#12655](https://github.com/angular/angular.js/issues/12655), [#12631](https://github.com/angular/angular.js/issues/12631), [#12612](https://github.com/angular/angular.js/issues/12612), [#12187](https://github.com/angular/angular.js/issues/12187))
|
||||
- only buffer rAF requests within the animation runners
|
||||
([dc48aadd](https://github.com/angular/angular.js/commit/dc48aadd26bbf1797c1c408f63ffde99d67414a9),
|
||||
[#12280](https://github.com/angular/angular.js/issues/12280))
|
||||
- **ngModel:** validate pattern against the viewValue
|
||||
([0e001084](https://github.com/angular/angular.js/commit/0e001084ffff8674efad289d37cb16cc4e46b50a),
|
||||
[#12344](https://github.com/angular/angular.js/issues/12344))
|
||||
- **ngResources:** support IPv6 URLs
|
||||
([b643f0d3](https://github.com/angular/angular.js/commit/b643f0d3223a627ef813f0777524e25d2dd95371),
|
||||
[#12512](https://github.com/angular/angular.js/issues/12512), [#12532](https://github.com/angular/angular.js/issues/12532))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngModel:** due to [0e001084](https://github.com/angular/angular.js/commit/0e001084ffff8674efad289d37cb16cc4e46b50a),
|
||||
|
||||
|
||||
The `ngPattern` and `pattern` directives will validate the regex
|
||||
against the `viewValue` of `ngModel`, i.e. the value of the model
|
||||
before the $parsers are applied. Previously, the modelValue
|
||||
(the result of the $parsers) was validated.
|
||||
|
||||
This fixes issues where `input[date]` and `input[number]` cannot
|
||||
be validated because the viewValue string is parsed into
|
||||
`Date` and `Number` respectively (starting with Angular 1.3).
|
||||
It also brings the directives in line with HTML5 constraint
|
||||
validation, which validates against the input value.
|
||||
|
||||
This change is unlikely to cause applications to fail, because even
|
||||
in Angular 1.2, the value that was validated by pattern could have
|
||||
been manipulated by the $parsers, as all validation was done
|
||||
inside this pipeline.
|
||||
|
||||
If you rely on the pattern being validated against the modelValue,
|
||||
you must create your own validator directive that overwrites
|
||||
the built-in pattern validator:
|
||||
|
||||
```js
|
||||
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
priority: 1,
|
||||
compile: function() {
|
||||
var regexp, patternExp;
|
||||
|
||||
return {
|
||||
pre: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
attr.$observe('pattern', function(regex) {
|
||||
/**
|
||||
* The built-in directive will call our overwritten validator
|
||||
* (see below). We just need to update the regex.
|
||||
* The preLink fn guaranetees our observer is called first.
|
||||
*/
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
//The built-in validator will throw at this point
|
||||
return;
|
||||
}
|
||||
|
||||
regexp = regex || undefined;
|
||||
});
|
||||
|
||||
},
|
||||
post: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
|
||||
//The postLink fn guarantees we overwrite the built-in pattern validator
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) ||
|
||||
isUndefined(regexp) ||
|
||||
regexp.test(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.18"></a>
|
||||
# 1.3.18 collective-penmanship (2015-08-18)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- clear class animations cache if animation is not started
|
||||
([2c03a357](https://github.com/angular/angular.js/commit/2c03a3574336ed814d020cf7ba36cee5b87e65b5),
|
||||
[#12604](https://github.com/angular/angular.js/issues/12604), [#12603](https://github.com/angular/angular.js/issues/12603))
|
||||
- do not throw errors if element is removed before animation starts
|
||||
([6b72598b](https://github.com/angular/angular.js/commit/6b72598b87022e1dd96bddc4451e007ef0601579),
|
||||
[#10205](https://github.com/angular/angular.js/issues/10205))
|
||||
- **ngModel:** correct minErr usage for correct doc creation
|
||||
([64a142b5](https://github.com/angular/angular.js/commit/64a142b58ed0a0e3896d82f3f9ce35373548d0ff),
|
||||
[#12386](https://github.com/angular/angular.js/issues/12386), [#12416](https://github.com/angular/angular.js/issues/12416))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.4"></a>
|
||||
# 1.4.4 pylon-requirement (2015-08-13)
|
||||
|
||||
@@ -122,6 +257,38 @@ and/or `beforeRemoveClass` then the CSS classes will not be applied
|
||||
in time for the children (and the parent class-based animation will not
|
||||
be cancelled by any child animations).
|
||||
|
||||
- **$q** due to [6838c979](https://github.com/angular/angular.js/commit/6838c979451c109d959a15035177ccee715ccf19),
|
||||
When writing tests, there is no need to call `$timeout.flush()` to resolve a call to `$q.when` with a value.
|
||||
|
||||
The previous behavior involved creating an extra promise that needed to be resolved. This is no longer needed when
|
||||
`$q.when` is called with a value. In the case that the test is not aware if `$q.when` is called with a value or
|
||||
another promise, it is possible to replace `$timeout.flush();` with `$timeout.flush(0);`.
|
||||
|
||||
```js
|
||||
describe('$q.when', function() {
|
||||
it('should not need a call to $timeout.flush() to resolve already resolved promises',
|
||||
inject(function($q, $timeout) {
|
||||
$q.when('foo');
|
||||
// In Angular 1.4.3 a call to `$timeout.flush();` was needed
|
||||
$timeout.verifyNoPendingTasks();
|
||||
}));
|
||||
|
||||
it('should accept $timeout.flush(0) when not sure if $q.when was called with a value or a promise',
|
||||
inject(function($q, $timeout) {
|
||||
$q.when('foo');
|
||||
$timeout.flush(0);
|
||||
$timeout.verifyNoPendingTasks();
|
||||
}));
|
||||
|
||||
it('should need a call to $timeout.flush() to resolve $q.when when called with a promise',
|
||||
inject(function($q, $timeout) {
|
||||
$q.when($q.when('foo'));
|
||||
$timeout.flush();
|
||||
$timeout.verifyNoPendingTasks();
|
||||
}));
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.4.3"></a>
|
||||
# 1.4.3 foam-acceleration (2015-07-15)
|
||||
|
||||
+2
-1
@@ -19,7 +19,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
If you have questions about how to use AngularJS, please direct these to the [Google Group][groups]
|
||||
discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC][irc].
|
||||
discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC][irc] and [Gitter][gitter].
|
||||
|
||||
## <a name="issue"></a> Found an Issue?
|
||||
If you find a bug in the source code or a mistake in the documentation, you can help us by
|
||||
@@ -268,6 +268,7 @@ You can find out more detailed information about contributing in the
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[dev-doc]: https://docs.angularjs.org/guide
|
||||
[github]: https://github.com/angular/angular.js
|
||||
[gitter]: https://gitter.im/angular/angular.js
|
||||
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
|
||||
Vendored
+1
@@ -93,6 +93,7 @@ var angularFiles = {
|
||||
'ngAnimate': [
|
||||
'src/ngAnimate/shared.js',
|
||||
'src/ngAnimate/body.js',
|
||||
'src/ngAnimate/rafScheduler.js',
|
||||
'src/ngAnimate/animateChildrenDirective.js',
|
||||
'src/ngAnimate/animateCss.js',
|
||||
'src/ngAnimate/animateCssDriver.js',
|
||||
|
||||
@@ -56,7 +56,7 @@ li.doc-example-live {
|
||||
}
|
||||
|
||||
div.syntaxhighlighter {
|
||||
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
|
||||
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars */
|
||||
}
|
||||
|
||||
/* TABS - tutorial environment navigation */
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<li class="disabled"><a href="http://angularjs.org/">Why AngularJS?</a></li>
|
||||
<li><a href="http://www.youtube.com/user/angularjs">Watch</a></li>
|
||||
<li><a href="tutorial">Tutorial</a></li>
|
||||
<li><a href="http://builtwith.angularjs.org/">Case Studies</a></li>
|
||||
<li><a href="https://www.madewithangular.com/">Case Studies</a></li>
|
||||
<li><a href="https://github.com/angular/angular-seed">Seed App project template</a></li>
|
||||
<li><a href="misc/faq">FAQ</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -301,7 +301,7 @@ it('should show example', inject(
|
||||
### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hasbang
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hashbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ angular.module('myApp', ['ngAria'])...
|
||||
###Using ngAria
|
||||
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
|
||||
added it as a dependency, you can test a few things:
|
||||
* Using your favorite element inspector, look for ngAria attributes in your own code.
|
||||
* Using your favorite element inspector, look for attributes added by ngAria in your own code.
|
||||
* Test using your keyboard to ensure `tabindex` is used correctly.
|
||||
* Fire up a screen reader such as VoiceOver to listen for ARIA support.
|
||||
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
|
||||
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
|
||||
|
||||
##Supported directives
|
||||
@@ -41,8 +41,8 @@ Currently, ngAria interfaces with the following directives:
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
Much of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
|
||||
@@ -134,10 +134,8 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
|
||||
operable** from the keybard. Think of `ng-click` on a `<div>` or `<md-checkbox>`: you still need
|
||||
to bind `ng-keypress` to make it fully operable from the keyboard. As a rule, any time you create
|
||||
a widget involving user interaction, be sure to test it with your keyboard and at least one mobile
|
||||
and desktop screen reader (preferably more).
|
||||
accessible**. As a rule, any time you create a widget involving user interaction, be sure to test
|
||||
it with your keyboard and at least one mobile and desktop screen reader.
|
||||
|
||||
<h2 id="ngdisabled">ngDisabled</h2>
|
||||
|
||||
@@ -160,7 +158,7 @@ Becomes:
|
||||
```
|
||||
|
||||
>You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
[chrome://accessibility](chrome://accessibility).
|
||||
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
|
||||
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
@@ -210,16 +208,25 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
|
||||
already.
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` to any element not in
|
||||
a node blacklist:
|
||||
|
||||
To fix widespread accessibility problems with `ng-click` on div elements, ngAria will dynamically
|
||||
bind keypress by default as long as the element isn't an anchor, button, input or textarea.
|
||||
You can turn this functionality on or off with the `bindKeypress` configuration option. ngAria
|
||||
will also add the `button` role to communicate to users of assistive technologies.
|
||||
* Button
|
||||
* Anchor
|
||||
* Input
|
||||
* Textarea
|
||||
* Select
|
||||
* Details/Summary
|
||||
|
||||
For `ng-dblclick`, you must still manually add `ng-keypress` and role to non-interactive elements such
|
||||
as `div` or `taco-button` to enable keyboard access.
|
||||
To fix widespread accessibility problems with `ng-click` on `div` elements, ngAria will
|
||||
dynamically bind a keypress event by default as long as the element isn't in the node blacklist.
|
||||
You can turn this functionality on or off with the `bindKeypress` configuration option.
|
||||
|
||||
ngAria will also add the `button` role to communicate to users of assistive technologies. This can
|
||||
be disabled with the `bindRoleForClick` configuration option.
|
||||
|
||||
For `ng-dblclick`, you must still manually add `ng-keypress` and a role to non-interactive elements
|
||||
such as `div` or `taco-button` to enable keyboard access.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
@@ -260,62 +267,18 @@ The attribute magic of ngAria may not work for every scenario. To disable indivi
|
||||
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
|
||||
tell ngAria to ignore the attribute globally.
|
||||
|
||||
<example module="ngAria_ngDisabledExample" deps="angular-aria.js">
|
||||
<example module="ngAria_ngClickExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
[role=checkbox] .icon:before {
|
||||
content: '\2610';
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
speak: none;
|
||||
}
|
||||
[role=checkbox].active .icon:before {
|
||||
content: '\2611';
|
||||
}
|
||||
</style>
|
||||
<form ng-controller="formsController">
|
||||
<div ng-model="someModel" show-attrs>
|
||||
Div with ngModel and aria-invalid disabled
|
||||
<div ng-click="someFunction" show-attrs>
|
||||
<div> with ng-click and bindRoleForClick, tabindex set to false
|
||||
</div>
|
||||
<div role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
aria-label="Custom Checkbox" ng-click="toggleCheckbox()" some-checkbox show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox for comparison
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
angular.module('ngAria_ngDisabledExample', ['ngAria'], function config($ariaProvider) {
|
||||
angular.module('ngAria_ngClickExample', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
ariaInvalid: false,
|
||||
tabindex: true
|
||||
bindRoleForClick: false,
|
||||
tabindex: false
|
||||
});
|
||||
})
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function(scope, el, attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
|
||||
@@ -8,22 +8,22 @@
|
||||
This section briefly touches on all of the important parts of AngularJS using a simple example.
|
||||
For a more in-depth explanation, see the {@link tutorial/ tutorial}.
|
||||
|
||||
| Concept | Description |
|
||||
|------------------|------------------------------------------|
|
||||
|{@link concepts#template Template} | HTML with additional markup |
|
||||
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
||||
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
||||
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
||||
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
||||
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|
||||
|{@link concepts#view View} | what the user sees (the DOM) |
|
||||
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|
||||
|{@link concepts#controller Controller} | the business logic behind views |
|
||||
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|
||||
|{@link concepts#injector Injector} | dependency injection container |
|
||||
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|
||||
|{@link concepts#service Service} | reusable business logic independent of views |
|
||||
| Concept | Description |
|
||||
|--------------------------------------------|--------------------------------------------------------------------------|
|
||||
|{@link concepts#template Template} | HTML with additional markup |
|
||||
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
||||
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
||||
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
||||
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
||||
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|
||||
|{@link concepts#view View} | what the user sees (the DOM) |
|
||||
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|
||||
|{@link concepts#controller Controller} | the business logic behind views |
|
||||
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|
||||
|{@link concepts#injector Injector} | dependency injection container |
|
||||
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|
||||
|{@link concepts#service Service} | reusable business logic independent of views |
|
||||
|
||||
|
||||
## A first example: Data binding
|
||||
|
||||
@@ -19,8 +19,9 @@ how to implement them.
|
||||
## What are Directives?
|
||||
|
||||
At a high level, directives are markers on a DOM element (such as an attribute, element
|
||||
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
|
||||
attach a specified behavior to that DOM element or even transform the DOM element and its children.
|
||||
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`})
|
||||
to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform
|
||||
the DOM element and its children.
|
||||
|
||||
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngClass`.
|
||||
Much like you create controllers and services, you can create your own directives for Angular to use.
|
||||
@@ -30,7 +31,7 @@ When Angular {@link guide/bootstrap bootstraps} your application, the
|
||||
<div class="alert alert-info">
|
||||
**What does it mean to "compile" an HTML template?**
|
||||
|
||||
For AngularJS, "compilation" means attaching event listeners to the HTML to make it interactive.
|
||||
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive.
|
||||
The reason we use the term "compile" is that the recursive process of attaching directives
|
||||
mirrors the process of compiling source code in
|
||||
[compiled programming languages](http://en.wikipedia.org/wiki/Compiled_languages).
|
||||
@@ -42,18 +43,28 @@ mirrors the process of compiling source code in
|
||||
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
|
||||
determines when to use a given directive.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive.
|
||||
Similar to the terminology used when an [element **matches** a selector]
|
||||
(https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
directive when the directive is part of its declaration.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
|
||||
|
||||
```html
|
||||
<input ng-model="foo">
|
||||
```
|
||||
|
||||
The following also **matches** `ngModel`:
|
||||
The following `<input>` element also **matches** `ngModel`:
|
||||
|
||||
```html
|
||||
<input data-ng-model="foo">
|
||||
```
|
||||
|
||||
And the following <person> element **matches** the `person` directive:
|
||||
|
||||
```html
|
||||
<person>{{name}}</person>
|
||||
```
|
||||
|
||||
### Normalization
|
||||
|
||||
Angular **normalizes** an element's tag and attribute name to determine which elements match which
|
||||
|
||||
@@ -33,7 +33,7 @@ for other directives to augment its behavior.
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>user = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ and failing to satisfy its validity.
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>user = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
</div>
|
||||
|
||||
@@ -185,7 +185,7 @@ didn't interact with a control
|
||||
<input type="button" ng-click="reset(form)" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>user = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
@@ -156,8 +156,94 @@ The syntax extension is based on a subset of the ICU MessageFormat syntax that c
|
||||
gender selections. Please refer to the links in the “Further Reading” section at the bottom of this
|
||||
section.
|
||||
|
||||
You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview)
|
||||
as you read the examples below.
|
||||
You may find it helpful to play with the following example as you read the explanations below:
|
||||
|
||||
<example name="message-format-example" module="messageFormatExample" deps="angular-message-format.js">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ckCtrl">
|
||||
<b>Set number of recipients</b>
|
||||
<button ng-click="setNumRecipients(0)">None</button>
|
||||
<button ng-click="setNumRecipients(1)">One</button>
|
||||
<button ng-click="setNumRecipients(2)">Two</button>
|
||||
<button ng-click="setNumRecipients(3)">Three</button>
|
||||
|
||||
|
||||
<br><br>
|
||||
<b>Sender's</b> name: <input ng-model="sender.name">
|
||||
|
||||
<br><br><b>Recipients</b><br>
|
||||
<div ng-repeat="recipient in recipients">
|
||||
Name: <input ng-model="recipient.name">
|
||||
Gender: <button ng-click="setGender(recipient, 'male')">male</button>
|
||||
<button ng-click="setGender(recipient, 'female')">female</button>
|
||||
<button ng-click="setGender(recipient, 'other')">other</button>
|
||||
</div>
|
||||
|
||||
<br><br><b>Message</b><br>
|
||||
{{recipients.length, plural, offset:1
|
||||
=0 {You ({{sender.name}}) gave no gifts}
|
||||
=1 { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
||||
}}
|
||||
}
|
||||
one { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
||||
}}
|
||||
}
|
||||
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
||||
}}
|
||||
|
||||
<br><br><b>In an attribute</b><br>
|
||||
<div attrib="{{recipients.length, plural, offset:1
|
||||
=0 {You ({{sender.name}}) gave no gifts}
|
||||
=1 { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
||||
}}
|
||||
}
|
||||
one { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
||||
}}
|
||||
}
|
||||
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
||||
}}">
|
||||
This div has an attribute interpolated with messageformat. Use the DOM inspector to check it out.
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="app.js">
|
||||
function Person(name, gender) {
|
||||
this.name = name;
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
angular.module('messageFormatExample', ['ngMessageFormat'])
|
||||
.controller('ckCtrl', function ($scope, $injector, $parse) {
|
||||
var people = [ new Person("Alice", "female"),
|
||||
new Person("Bob", "male"),
|
||||
new Person("Charlie", "male") ];
|
||||
|
||||
$scope.sender = new Person("Harry Potter", "male");
|
||||
$scope.recipients = people.slice();
|
||||
|
||||
$scope.setNumRecipients = function(n) {
|
||||
n = n > people.length ? people.length : n;
|
||||
$scope.recipients = people.slice(0, n);
|
||||
};
|
||||
|
||||
$scope.setGender = function(person, gender) {
|
||||
person.gender = gender;
|
||||
};
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
### Plural Syntax
|
||||
|
||||
@@ -333,9 +419,9 @@ allows you to nest plural and gender expressions in any order.
|
||||
Please note that if these are intended to reach a translator and be translated, it is recommended
|
||||
that the messages appear as a whole and not be split up.
|
||||
|
||||
### More complex example that demonstrates nesting
|
||||
### Demonstration of nesting
|
||||
|
||||
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
|
||||
This is taken from the above example.
|
||||
|
||||
```text
|
||||
{{recipients.length, plural, offset:1
|
||||
|
||||
@@ -15,14 +15,14 @@ development.
|
||||
production.
|
||||
|
||||
To point your code to an angular script on the Google CDN server, use the following template. This
|
||||
example points to the minified version 1.3.14:
|
||||
example points to the minified version 1.4.5:
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -86,9 +86,15 @@ Yes. See instructions in {@link downloading}.
|
||||
|
||||
### What browsers does Angular work with?
|
||||
|
||||
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera 15,
|
||||
IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari). See {@link guide/ie Internet
|
||||
Explorer Compatibility} for more details on supporting legacy IE browsers.
|
||||
We run our extensive test suite against the following browsers: the latest versions of Chrome,
|
||||
Firefox, Safari, and Safari for iOs, as well as Internet Explorer versions 9-11. See {@link guide/ie
|
||||
Internet Explorer Compatibility} for more details on supporting legacy IE browsers.
|
||||
|
||||
If a browser is untested, it doesn't mean it won't work; for example, older Android (2.3.x)
|
||||
is supported in the sense that we avoid the dot notation for reserved words as property names,
|
||||
but we don't actively test changes against it. You can also expect browsers to work that share
|
||||
a large part of their codebase with a browser we test, such as Opera > version 12
|
||||
(uses the Blink engine), or the various Firefox derivatives.
|
||||
|
||||
|
||||
### What's Angular's performance like?
|
||||
|
||||
@@ -75,7 +75,7 @@ __`test/e2e/scenarios.js`__:
|
||||
query.sendKeys('nexus');
|
||||
element.all(by.css('.phones li a')).first().click();
|
||||
browser.getLocationAbsUrl().then(function(url) {
|
||||
expect(url.split('#')[1]).toBe('/phones/nexus-s');
|
||||
expect(url).toBe('/phones/nexus-s');
|
||||
});
|
||||
});
|
||||
...
|
||||
|
||||
@@ -2284,7 +2284,7 @@
|
||||
"version": "0.6.1",
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "0.0.2"
|
||||
"version": "0.0.3"
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10"
|
||||
@@ -2385,7 +2385,7 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.17",
|
||||
"version": "0.10.19",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.8.7",
|
||||
@@ -2518,7 +2518,7 @@
|
||||
"version": "0.3.0",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.6.4"
|
||||
"version": "2.6.5"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.1"
|
||||
@@ -2552,7 +2552,7 @@
|
||||
"version": "0.2.14",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.6.4"
|
||||
"version": "2.6.5"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.1"
|
||||
@@ -2582,10 +2582,10 @@
|
||||
"version": "0.1.6"
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.8",
|
||||
"dependencies": {
|
||||
"nan": {
|
||||
"version": "1.8.4"
|
||||
"version": "2.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2625,7 +2625,7 @@
|
||||
"version": "4.3.6"
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.5.1"
|
||||
"version": "0.5.3"
|
||||
},
|
||||
"winston": {
|
||||
"version": "0.7.3",
|
||||
|
||||
Generated
+48
-48
@@ -3487,96 +3487,96 @@
|
||||
"dependencies": {
|
||||
"dependency-graph": {
|
||||
"version": "0.1.0",
|
||||
"from": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.1.0.tgz",
|
||||
"from": "dependency-graph@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.1.0.tgz",
|
||||
"dependencies": {
|
||||
"underscore": {
|
||||
"version": "1.4.4",
|
||||
"from": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
|
||||
"from": "underscore@1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"di": {
|
||||
"version": "0.0.1",
|
||||
"from": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
|
||||
"from": "di@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz"
|
||||
},
|
||||
"optimist": {
|
||||
"version": "0.6.1",
|
||||
"from": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"from": "optimist@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "0.0.2",
|
||||
"from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
|
||||
"version": "0.0.3",
|
||||
"from": "wordwrap@>=0.0.2 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"from": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"from": "minimist@>=0.0.1 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"q": {
|
||||
"version": "0.9.7",
|
||||
"from": "https://registry.npmjs.org/q/-/q-0.9.7.tgz",
|
||||
"from": "q@>=0.9.7 <0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-0.9.7.tgz"
|
||||
},
|
||||
"validate.js": {
|
||||
"version": "0.2.0",
|
||||
"from": "https://registry.npmjs.org/validate.js/-/validate.js-0.2.0.tgz",
|
||||
"from": "validate.js@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/validate.js/-/validate.js-0.2.0.tgz"
|
||||
},
|
||||
"winston": {
|
||||
"version": "0.7.3",
|
||||
"from": "https://registry.npmjs.org/winston/-/winston-0.7.3.tgz",
|
||||
"from": "winston@>=0.7.2 <0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/winston/-/winston-0.7.3.tgz",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.2.10",
|
||||
"from": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||
"from": "async@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
|
||||
},
|
||||
"colors": {
|
||||
"version": "0.6.2",
|
||||
"from": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
|
||||
"from": "colors@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz"
|
||||
},
|
||||
"cycle": {
|
||||
"version": "1.0.3",
|
||||
"from": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
|
||||
"from": "cycle@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz"
|
||||
},
|
||||
"eyes": {
|
||||
"version": "0.1.8",
|
||||
"from": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
|
||||
"from": "eyes@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
|
||||
},
|
||||
"pkginfo": {
|
||||
"version": "0.3.0",
|
||||
"from": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.0.tgz",
|
||||
"from": "pkginfo@>=0.3.0 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.0.tgz"
|
||||
},
|
||||
"request": {
|
||||
"version": "2.16.6",
|
||||
"from": "https://registry.npmjs.org/request/-/request-2.16.6.tgz",
|
||||
"from": "request@>=2.16.0 <2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.16.6.tgz",
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "0.0.10",
|
||||
"from": "https://registry.npmjs.org/form-data/-/form-data-0.0.10.tgz",
|
||||
"from": "form-data@>=0.0.3 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.0.10.tgz",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.7",
|
||||
"from": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
|
||||
"from": "combined-stream@>=0.0.4 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz",
|
||||
"from": "delayed-stream@0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
|
||||
}
|
||||
}
|
||||
@@ -3585,81 +3585,81 @@
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"from": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
|
||||
"from": "mime@>=1.2.7 <1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
|
||||
},
|
||||
"hawk": {
|
||||
"version": "0.10.2",
|
||||
"from": "https://registry.npmjs.org/hawk/-/hawk-0.10.2.tgz",
|
||||
"from": "hawk@>=0.10.2 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-0.10.2.tgz",
|
||||
"dependencies": {
|
||||
"hoek": {
|
||||
"version": "0.7.6",
|
||||
"from": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz",
|
||||
"from": "hoek@>=0.7.0 <0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-0.7.6.tgz"
|
||||
},
|
||||
"boom": {
|
||||
"version": "0.3.8",
|
||||
"from": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz",
|
||||
"from": "boom@>=0.3.0 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/boom/-/boom-0.3.8.tgz"
|
||||
},
|
||||
"cryptiles": {
|
||||
"version": "0.1.3",
|
||||
"from": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.1.3.tgz",
|
||||
"from": "cryptiles@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.1.3.tgz"
|
||||
},
|
||||
"sntp": {
|
||||
"version": "0.1.4",
|
||||
"from": "https://registry.npmjs.org/sntp/-/sntp-0.1.4.tgz",
|
||||
"from": "sntp@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sntp/-/sntp-0.1.4.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.3",
|
||||
"from": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz",
|
||||
"from": "node-uuid@>=1.4.0 <1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
|
||||
},
|
||||
"cookie-jar": {
|
||||
"version": "0.2.0",
|
||||
"from": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.2.0.tgz",
|
||||
"from": "cookie-jar@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie-jar/-/cookie-jar-0.2.0.tgz"
|
||||
},
|
||||
"aws-sign": {
|
||||
"version": "0.2.0",
|
||||
"from": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz",
|
||||
"from": "aws-sign@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign/-/aws-sign-0.2.0.tgz"
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.2.0",
|
||||
"from": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.2.0.tgz",
|
||||
"from": "oauth-sign@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.2.0.tgz"
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.2.0",
|
||||
"from": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.2.0.tgz",
|
||||
"from": "forever-agent@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.2.0.tgz"
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.2.0",
|
||||
"from": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.2.0.tgz",
|
||||
"from": "tunnel-agent@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.2.0.tgz"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "3.0.0",
|
||||
"from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz",
|
||||
"from": "json-stringify-safe@>=3.0.0 <3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-3.0.0.tgz"
|
||||
},
|
||||
"qs": {
|
||||
"version": "0.5.6",
|
||||
"from": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz",
|
||||
"from": "qs@>=0.5.4 <0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-0.5.6.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"stack-trace": {
|
||||
"version": "0.0.9",
|
||||
"from": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz",
|
||||
"from": "stack-trace@>=0.0.0 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz"
|
||||
}
|
||||
}
|
||||
@@ -3667,8 +3667,8 @@
|
||||
}
|
||||
},
|
||||
"dgeni-packages": {
|
||||
"version": "0.10.17",
|
||||
"from": "dgeni-packages@0.10.17",
|
||||
"version": "0.10.19",
|
||||
"from": "dgeni-packages@0.10.19",
|
||||
"dependencies": {
|
||||
"catharsis": {
|
||||
"version": "0.8.7",
|
||||
@@ -3877,9 +3877,9 @@
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.6.4",
|
||||
"version": "2.6.5",
|
||||
"from": "lru-cache@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.4.tgz"
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.1",
|
||||
@@ -3931,9 +3931,9 @@
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "2.6.4",
|
||||
"version": "2.6.5",
|
||||
"from": "lru-cache@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.4.tgz"
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.1",
|
||||
@@ -3977,14 +3977,14 @@
|
||||
"resolved": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz"
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.8",
|
||||
"from": "fsevents@>=0.3.1 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-0.3.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-0.3.8.tgz",
|
||||
"dependencies": {
|
||||
"nan": {
|
||||
"version": "1.8.4",
|
||||
"from": "nan@>=1.8.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz"
|
||||
"version": "2.0.5",
|
||||
"from": "nan@>=2.0.2 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.0.5.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4042,9 +4042,9 @@
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.3",
|
||||
"from": "shelljs@>=0.5.0 <0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.1.tgz"
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz"
|
||||
},
|
||||
"winston": {
|
||||
"version": "0.7.3",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.4.0-beta.0",
|
||||
"branchPattern": "1.4.*",
|
||||
"distTag": "latest",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
|
||||
@@ -95,19 +95,10 @@ function publish {
|
||||
|
||||
# don't publish every build to npm
|
||||
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
|
||||
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
|
||||
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
|
||||
# publish 1.2.x releases with the appropriate tag
|
||||
# this ensures that `npm install` by default will not grab `1.2.x` releases
|
||||
npm publish --tag=old
|
||||
else
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
fi
|
||||
else
|
||||
# publish prerelease builds with the beta tag
|
||||
npm publish --tag=beta
|
||||
fi
|
||||
# get the npm dist-tag from a custom property (distTag) in package.json
|
||||
DIST_TAG=$(readJsonProp "package.json" "distTag")
|
||||
echo "-- Publishing to npm as $DIST_TAG"
|
||||
npm publish --tag=$DIST_TAG
|
||||
fi
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
+18
-15
@@ -825,6 +825,8 @@ function copy(source, destination, stackSource, stackDest) {
|
||||
} else if (isRegExp(source)) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isFunction(source.cloneNode)) {
|
||||
destination = source.cloneNode(true);
|
||||
} else {
|
||||
var emptyObject = Object.create(getPrototypeOf(source));
|
||||
return copy(source, emptyObject, stackSource, stackDest);
|
||||
@@ -975,7 +977,7 @@ function equals(o1, o2) {
|
||||
for (key in o2) {
|
||||
if (!(key in keySet) &&
|
||||
key.charAt(0) !== '$' &&
|
||||
o2[key] !== undefined &&
|
||||
isDefined(o2[key]) &&
|
||||
!isFunction(o2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1671,10 +1673,9 @@ function bindJQuery() {
|
||||
|
||||
// bind to jQuery if present;
|
||||
var jqName = jq();
|
||||
jQuery = window.jQuery; // use default jQuery.
|
||||
if (isDefined(jqName)) { // `ngJq` present
|
||||
jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`.
|
||||
}
|
||||
jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
|
||||
!jqName ? undefined : // use jqLite
|
||||
window[jqName]; // use jQuery specified by `ngJq`
|
||||
|
||||
// Use jQuery if it exists with proper functionality, otherwise default to us.
|
||||
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
|
||||
@@ -1779,22 +1780,24 @@ function getter(obj, path, bindFnToScope) {
|
||||
/**
|
||||
* Return the DOM siblings between the first and last node in the given array.
|
||||
* @param {Array} array like object
|
||||
* @returns {jqLite} jqLite collection containing the nodes
|
||||
* @returns {Array} the inputted object or a jqLite collection containing the nodes
|
||||
*/
|
||||
function getBlockNodes(nodes) {
|
||||
// TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
|
||||
// collection, otherwise update the original collection.
|
||||
// TODO(perf): update `nodes` instead of creating a new object?
|
||||
var node = nodes[0];
|
||||
var endNode = nodes[nodes.length - 1];
|
||||
var blockNodes = [node];
|
||||
var blockNodes;
|
||||
|
||||
do {
|
||||
node = node.nextSibling;
|
||||
if (!node) break;
|
||||
blockNodes.push(node);
|
||||
} while (node !== endNode);
|
||||
for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
|
||||
if (blockNodes || nodes[i] !== node) {
|
||||
if (!blockNodes) {
|
||||
blockNodes = jqLite(slice.call(nodes, 0, i));
|
||||
}
|
||||
blockNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return jqLite(blockNodes);
|
||||
return blockNodes || nodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -98,8 +98,9 @@
|
||||
* @name angular.version
|
||||
* @module ng
|
||||
* @description
|
||||
* An object that contains information about the current AngularJS version. This object has the
|
||||
* following properties:
|
||||
* An object that contains information about the current AngularJS version.
|
||||
*
|
||||
* This object has the following properties:
|
||||
*
|
||||
* - `full` – `{string}` – Full version string, such as "0.9.18".
|
||||
* - `major` – `{number}` – Major version number, such as "0".
|
||||
|
||||
+7
-8
@@ -65,7 +65,7 @@
|
||||
* - [`html()`](http://api.jquery.com/html/)
|
||||
* - [`next()`](http://api.jquery.com/next/) - Does not support selectors
|
||||
* - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
|
||||
* - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
|
||||
* - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
|
||||
* - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
|
||||
* - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
|
||||
* - [`prepend()`](http://api.jquery.com/prepend/)
|
||||
@@ -79,7 +79,7 @@
|
||||
* - [`text()`](http://api.jquery.com/text/)
|
||||
* - [`toggleClass()`](http://api.jquery.com/toggleClass/)
|
||||
* - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
|
||||
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
|
||||
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
|
||||
* - [`val()`](http://api.jquery.com/val/)
|
||||
* - [`wrap()`](http://api.jquery.com/wrap/)
|
||||
*
|
||||
@@ -450,7 +450,7 @@ function jqLiteInheritedData(element, name, value) {
|
||||
|
||||
while (element) {
|
||||
for (var i = 0, ii = names.length; i < ii; i++) {
|
||||
if ((value = jqLite.data(element, names[i])) !== undefined) return value;
|
||||
if (isDefined(value = jqLite.data(element, names[i]))) return value;
|
||||
}
|
||||
|
||||
// If dealing with a document fragment node with a host element, and no parent, use the host
|
||||
@@ -556,9 +556,8 @@ function getBooleanAttrName(element, name) {
|
||||
return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
|
||||
}
|
||||
|
||||
function getAliasedAttrName(element, name) {
|
||||
var nodeName = element.nodeName;
|
||||
return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
|
||||
function getAliasedAttrName(name) {
|
||||
return ALIASED_ATTR[name];
|
||||
}
|
||||
|
||||
forEach({
|
||||
@@ -695,7 +694,7 @@ forEach({
|
||||
// in a way that survives minification.
|
||||
// jqLiteEmpty takes no arguments but is a setter.
|
||||
if (fn !== jqLiteEmpty &&
|
||||
(((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
|
||||
(isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
|
||||
if (isObject(arg1)) {
|
||||
|
||||
// we are a write, but the object properties are the key/values
|
||||
@@ -716,7 +715,7 @@ forEach({
|
||||
// TODO: do we still need this?
|
||||
var value = fn.$dv;
|
||||
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
|
||||
var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
|
||||
var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
|
||||
for (var j = 0; j < jj; j++) {
|
||||
var nodeValue = fn(this[j], arg1, arg2);
|
||||
value = value ? value + nodeValue : nodeValue;
|
||||
|
||||
+55
-50
@@ -105,61 +105,66 @@ var $$CoreAnimateQueueProvider = function() {
|
||||
}
|
||||
};
|
||||
|
||||
function addRemoveClassesPostDigest(element, add, remove) {
|
||||
var classVal, data = postDigestQueue.get(element);
|
||||
|
||||
if (!data) {
|
||||
postDigestQueue.put(element, data = {});
|
||||
postDigestElements.push(element);
|
||||
}
|
||||
|
||||
var updateData = function(classes, value) {
|
||||
var changed = false;
|
||||
if (classes) {
|
||||
classes = isString(classes) ? classes.split(' ') :
|
||||
isArray(classes) ? classes : [];
|
||||
forEach(classes, function(className) {
|
||||
if (className) {
|
||||
changed = true;
|
||||
data[className] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
|
||||
var classesAdded = updateData(add, true);
|
||||
var classesRemoved = updateData(remove, false);
|
||||
if ((!classesAdded && !classesRemoved) || postDigestElements.length > 1) return;
|
||||
|
||||
$rootScope.$$postDigest(function() {
|
||||
forEach(postDigestElements, function(element) {
|
||||
var data = postDigestQueue.get(element);
|
||||
if (data) {
|
||||
var existing = splitClasses(element.attr('class'));
|
||||
var toAdd = '';
|
||||
var toRemove = '';
|
||||
forEach(data, function(status, className) {
|
||||
var hasClass = !!existing[className];
|
||||
if (status !== hasClass) {
|
||||
if (status) {
|
||||
toAdd += (toAdd.length ? ' ' : '') + className;
|
||||
} else {
|
||||
toRemove += (toRemove.length ? ' ' : '') + className;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
forEach(element, function(elm) {
|
||||
toAdd && jqLiteAddClass(elm, toAdd);
|
||||
toRemove && jqLiteRemoveClass(elm, toRemove);
|
||||
});
|
||||
postDigestQueue.remove(element);
|
||||
function updateData(data, classes, value) {
|
||||
var changed = false;
|
||||
if (classes) {
|
||||
classes = isString(classes) ? classes.split(' ') :
|
||||
isArray(classes) ? classes : [];
|
||||
forEach(classes, function(className) {
|
||||
if (className) {
|
||||
changed = true;
|
||||
data[className] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
postDigestElements.length = 0;
|
||||
function handleCSSClassChanges() {
|
||||
forEach(postDigestElements, function(element) {
|
||||
var data = postDigestQueue.get(element);
|
||||
if (data) {
|
||||
var existing = splitClasses(element.attr('class'));
|
||||
var toAdd = '';
|
||||
var toRemove = '';
|
||||
forEach(data, function(status, className) {
|
||||
var hasClass = !!existing[className];
|
||||
if (status !== hasClass) {
|
||||
if (status) {
|
||||
toAdd += (toAdd.length ? ' ' : '') + className;
|
||||
} else {
|
||||
toRemove += (toRemove.length ? ' ' : '') + className;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
forEach(element, function(elm) {
|
||||
toAdd && jqLiteAddClass(elm, toAdd);
|
||||
toRemove && jqLiteRemoveClass(elm, toRemove);
|
||||
});
|
||||
postDigestQueue.remove(element);
|
||||
}
|
||||
});
|
||||
postDigestElements.length = 0;
|
||||
}
|
||||
|
||||
|
||||
function addRemoveClassesPostDigest(element, add, remove) {
|
||||
var data = postDigestQueue.get(element) || {};
|
||||
|
||||
var classesAdded = updateData(data, add, true);
|
||||
var classesRemoved = updateData(data, remove, false);
|
||||
|
||||
if (classesAdded || classesRemoved) {
|
||||
|
||||
postDigestQueue.put(element, data);
|
||||
postDigestElements.push(element);
|
||||
|
||||
if (postDigestElements.length === 1) {
|
||||
$rootScope.$$postDigest(handleCSSClassChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
@@ -35,10 +35,10 @@ var $CoreAnimateCssProvider = function() {
|
||||
return this.getPromise().then(f1,f2);
|
||||
},
|
||||
'catch': function(f1) {
|
||||
return this.getPromise().catch(f1);
|
||||
return this.getPromise()['catch'](f1);
|
||||
},
|
||||
'finally': function(f1) {
|
||||
return this.getPromise().finally(f1);
|
||||
return this.getPromise()['finally'](f1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+11
-6
@@ -87,7 +87,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
var cachedState, lastHistoryState,
|
||||
lastBrowserUrl = location.href,
|
||||
baseElement = document.find('base'),
|
||||
reloadLocation = null;
|
||||
pendingLocation = null;
|
||||
|
||||
cacheState();
|
||||
lastHistoryState = cachedState;
|
||||
@@ -147,8 +147,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// Do the assignment again so that those two variables are referentially identical.
|
||||
lastHistoryState = cachedState;
|
||||
} else {
|
||||
if (!sameBase || reloadLocation) {
|
||||
reloadLocation = url;
|
||||
if (!sameBase || pendingLocation) {
|
||||
pendingLocation = url;
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
@@ -157,14 +157,18 @@ function Browser(window, document, $log, $sniffer) {
|
||||
} else {
|
||||
location.hash = getHash(url);
|
||||
}
|
||||
if (location.href !== url) {
|
||||
pendingLocation = url;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
// getter
|
||||
} else {
|
||||
// - reloadLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened.
|
||||
// - pendingLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
|
||||
// https://openradar.appspot.com/22186109).
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return reloadLocation || location.href.replace(/%27/g,"'");
|
||||
return pendingLocation || location.href.replace(/%27/g,"'");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -186,6 +190,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
urlChangeInit = false;
|
||||
|
||||
function cacheStateAndFireUrlChange() {
|
||||
pendingLocation = null;
|
||||
cacheState();
|
||||
fireUrlChange();
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@
|
||||
$scope.keys = [];
|
||||
$scope.cache = $cacheFactory('cacheId');
|
||||
$scope.put = function(key, value) {
|
||||
if ($scope.cache.get(key) === undefined) {
|
||||
if (isUndefined($scope.cache.get(key))) {
|
||||
$scope.keys.push(key);
|
||||
}
|
||||
$scope.cache.put(key, value === undefined ? null : value);
|
||||
$scope.cache.put(key, isUndefined(value) ? null : value);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
+38
-17
@@ -146,18 +146,24 @@
|
||||
* and other directives used in the directive's template will also be excluded from execution.
|
||||
*
|
||||
* #### `scope`
|
||||
* **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
|
||||
* same element request a new scope, only one new scope is created. The new scope rule does not
|
||||
* apply for the root of the template since the root of the template always gets a new scope.
|
||||
* The scope property can be `true`, an object or a falsy value:
|
||||
*
|
||||
* **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from
|
||||
* normal scope in that it does not prototypically inherit from the parent scope. This is useful
|
||||
* when creating reusable components, which should not accidentally read or modify data in the
|
||||
* parent scope.
|
||||
* * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
|
||||
*
|
||||
* The 'isolate' scope takes an object hash which defines a set of local scope properties
|
||||
* derived from the parent scope. These local properties are useful for aliasing values for
|
||||
* templates. Locals definition is a hash of local scope property to its source:
|
||||
* * **`true`:** A new child scope that prototypically inherits from its parent will be created for
|
||||
* the directive's element. If multiple directives on the same element request a new scope,
|
||||
* only one new scope is created. The new scope rule does not apply for the root of the template
|
||||
* since the root of the template always gets a new scope.
|
||||
*
|
||||
* * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
|
||||
* 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
|
||||
* scope. This is useful when creating reusable components, which should not accidentally read or modify
|
||||
* data in the parent scope.
|
||||
*
|
||||
* The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
|
||||
* directive's element. These local properties are useful for aliasing values for templates. The keys in
|
||||
* the object hash map to the name of the property on the isolate scope; the values define how the property
|
||||
* is bound to the parent scope, via matching attributes on the directive's element:
|
||||
*
|
||||
* * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
|
||||
* always a string since DOM attributes are strings. If no `attr` name is specified then the
|
||||
@@ -190,6 +196,20 @@
|
||||
* For example, if the expression is `increment(amount)` then we can specify the amount value
|
||||
* by calling the `localFn` as `localFn({amount: 22})`.
|
||||
*
|
||||
* In general it's possible to apply more than one directive to one element, but there might be limitations
|
||||
* depending on the type of scope required by the directives. The following points will help explain these limitations.
|
||||
* For simplicity only two directives are taken into account, but it is also applicable for several directives:
|
||||
*
|
||||
* * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
|
||||
* * **child scope** + **no scope** => Both directives will share one single child scope
|
||||
* * **child scope** + **child scope** => Both directives will share one single child scope
|
||||
* * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
|
||||
* its parent's scope
|
||||
* * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
|
||||
* be applied to the same element.
|
||||
* * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
|
||||
* cannot be applied to the same element.
|
||||
*
|
||||
*
|
||||
* #### `bindToController`
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
|
||||
@@ -198,7 +218,7 @@
|
||||
*
|
||||
* #### `controller`
|
||||
* Controller constructor function. The controller is instantiated before the
|
||||
* pre-linking phase and it is shared with other directives (see
|
||||
* pre-linking phase and can be accessed by other directives (see
|
||||
* `require` attribute). This allows the directives to communicate with each other and augment
|
||||
* each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
|
||||
*
|
||||
@@ -238,9 +258,10 @@
|
||||
*
|
||||
* #### `controllerAs`
|
||||
* Identifier name for a reference to the controller in the directive's scope.
|
||||
* This allows the controller to be referenced from the directive template. The directive
|
||||
* needs to define a scope for this configuration to be used. Useful in the case when
|
||||
* directive is used as component.
|
||||
* This allows the controller to be referenced from the directive template. This is especially
|
||||
* useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
|
||||
* to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
|
||||
* `controllerAs` reference might overwrite a property that already exists on the parent scope.
|
||||
*
|
||||
*
|
||||
* #### `restrict`
|
||||
@@ -1077,7 +1098,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
var node = this.$$element[0],
|
||||
booleanKey = getBooleanAttrName(node, key),
|
||||
aliasedKey = getAliasedAttrName(node, key),
|
||||
aliasedKey = getAliasedAttrName(key),
|
||||
observer = key,
|
||||
nodeName;
|
||||
|
||||
@@ -1144,7 +1165,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || value === undefined) {
|
||||
if (value === null || isUndefined(value)) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
@@ -2110,7 +2131,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
i = 0, ii = directives.length; i < ii; i++) {
|
||||
try {
|
||||
directive = directives[i];
|
||||
if ((maxPriority === undefined || maxPriority > directive.priority) &&
|
||||
if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
|
||||
directive.restrict.indexOf(location) != -1) {
|
||||
if (startAttrName) {
|
||||
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
|
||||
|
||||
@@ -39,7 +39,7 @@ function $$CookieReader($document) {
|
||||
// the first value that is seen for a cookie is the most
|
||||
// specific one. values for the same cookie name that
|
||||
// follow are for less specific paths.
|
||||
if (lastCookies[name] === undefined) {
|
||||
if (isUndefined(lastCookies[name])) {
|
||||
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
+38
-14
@@ -25,6 +25,7 @@ function nullFormRenameControl(control, name) {
|
||||
* @property {boolean} $dirty True if user has already interacted with the form.
|
||||
* @property {boolean} $valid True if all of the containing forms and controls are valid.
|
||||
* @property {boolean} $invalid True if at least one containing control or form is invalid.
|
||||
* @property {boolean} $pending True if at least one containing control or form is pending.
|
||||
* @property {boolean} $submitted True if user has submitted the form even if its invalid.
|
||||
*
|
||||
* @property {Object} $error Is an object hash, containing references to controls or
|
||||
@@ -64,8 +65,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
var form = this,
|
||||
controls = [];
|
||||
|
||||
var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
|
||||
|
||||
// init state
|
||||
form.$error = {};
|
||||
form.$$success = {};
|
||||
@@ -76,8 +75,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
form.$valid = true;
|
||||
form.$invalid = false;
|
||||
form.$submitted = false;
|
||||
|
||||
parentForm.$addControl(form);
|
||||
form.$$parentForm = nullFormCtrl;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -116,11 +114,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$addControl
|
||||
* @param {object} control control object, either a {@link form.FormController} or an
|
||||
* {@link ngModel.NgModelController}
|
||||
*
|
||||
* @description
|
||||
* Register a control with the form.
|
||||
* Register a control with the form. Input elements using ngModelController do this automatically
|
||||
* when they are linked.
|
||||
*
|
||||
* Input elements using ngModelController do this automatically when they are linked.
|
||||
* Note that the current state of the control will not be reflected on the new parent form. This
|
||||
* is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
|
||||
* state.
|
||||
*
|
||||
* However, if the method is used programmatically, for example by adding dynamically created controls,
|
||||
* or controls that have been previously removed without destroying their corresponding DOM element,
|
||||
* it's the developers responsiblity to make sure the current state propagates to the parent form.
|
||||
*
|
||||
* For example, if an input control is added that is already `$dirty` and has `$error` properties,
|
||||
* calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
|
||||
*/
|
||||
form.$addControl = function(control) {
|
||||
// Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
|
||||
@@ -131,6 +141,8 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
if (control.$name) {
|
||||
form[control.$name] = control;
|
||||
}
|
||||
|
||||
control.$$parentForm = form;
|
||||
};
|
||||
|
||||
// Private API: rename a form control
|
||||
@@ -147,11 +159,18 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$removeControl
|
||||
* @param {object} control control object, either a {@link form.FormController} or an
|
||||
* {@link ngModel.NgModelController}
|
||||
*
|
||||
* @description
|
||||
* Deregister a control from the form.
|
||||
*
|
||||
* Input elements using ngModelController do this automatically when they are destroyed.
|
||||
*
|
||||
* Note that only the removed control's validation state (`$errors`etc.) will be removed from the
|
||||
* form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
|
||||
* different from case to case. For example, removing the only `$dirty` control from a form may or
|
||||
* may not mean that the form is still `$dirty`.
|
||||
*/
|
||||
form.$removeControl = function(control) {
|
||||
if (control.$name && form[control.$name] === control) {
|
||||
@@ -168,6 +187,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
});
|
||||
|
||||
arrayRemove(controls, control);
|
||||
control.$$parentForm = nullFormCtrl;
|
||||
};
|
||||
|
||||
|
||||
@@ -204,7 +224,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
delete object[property];
|
||||
}
|
||||
},
|
||||
parentForm: parentForm,
|
||||
$animate: $animate
|
||||
});
|
||||
|
||||
@@ -223,7 +242,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
$animate.addClass(element, DIRTY_CLASS);
|
||||
form.$dirty = true;
|
||||
form.$pristine = false;
|
||||
parentForm.$setDirty();
|
||||
form.$$parentForm.$setDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -279,7 +298,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
form.$setSubmitted = function() {
|
||||
$animate.addClass(element, SUBMITTED_CLASS);
|
||||
form.$submitted = true;
|
||||
parentForm.$setSubmitted();
|
||||
form.$$parentForm.$setSubmitted();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -329,6 +348,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
* # CSS classes
|
||||
* - `ng-valid` is set if the form is valid.
|
||||
* - `ng-invalid` is set if the form is invalid.
|
||||
* - `ng-pending` is set if the form is pending.
|
||||
* - `ng-pristine` is set if the form is pristine.
|
||||
* - `ng-dirty` is set if the form is dirty.
|
||||
* - `ng-submitted` is set if the form was submitted.
|
||||
@@ -404,7 +424,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
</script>
|
||||
<style>
|
||||
.my-form {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -453,6 +472,7 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
var formDirective = {
|
||||
name: 'form',
|
||||
restrict: isNgForm ? 'EAC' : 'E',
|
||||
require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
|
||||
controller: FormController,
|
||||
compile: function ngFormCompile(formElement, attr) {
|
||||
// Setup initial state of the control
|
||||
@@ -461,7 +481,9 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
|
||||
|
||||
return {
|
||||
pre: function ngFormPreLink(scope, formElement, attr, controller) {
|
||||
pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
|
||||
var controller = ctrls[0];
|
||||
|
||||
// if `action` attr is not present on the form, prevent the default action (submission)
|
||||
if (!('action' in attr)) {
|
||||
// we can't use jq events because if a form is destroyed during submission the default
|
||||
@@ -490,7 +512,9 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
});
|
||||
}
|
||||
|
||||
var parentFormCtrl = controller.$$parentForm;
|
||||
var parentFormCtrl = ctrls[1] || controller.$$parentForm;
|
||||
parentFormCtrl.$addControl(controller);
|
||||
|
||||
var setter = nameAttr ? getSetter(controller.$name) : noop;
|
||||
|
||||
if (nameAttr) {
|
||||
@@ -498,13 +522,13 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
attr.$observe(nameAttr, function(newValue) {
|
||||
if (controller.$name === newValue) return;
|
||||
setter(scope, undefined);
|
||||
parentFormCtrl.$$renameControl(controller, newValue);
|
||||
controller.$$parentForm.$$renameControl(controller, newValue);
|
||||
setter = getSetter(controller.$name);
|
||||
setter(scope, controller);
|
||||
});
|
||||
}
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
controller.$$parentForm.$removeControl(controller);
|
||||
setter(scope, undefined);
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
|
||||
+60
-19
@@ -138,9 +138,17 @@ var inputType = {
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO date string (yyyy-MM-dd).
|
||||
* valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
|
||||
* (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
|
||||
* constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
||||
* a valid ISO date string (yyyy-MM-dd).
|
||||
* a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
|
||||
* (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
|
||||
* constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `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
|
||||
@@ -232,10 +240,18 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO datetime format (yyyy-MM-ddTHH:mm:ss).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
||||
* a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
|
||||
* inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
|
||||
* Note that `min` will also add native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
|
||||
* inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
|
||||
* Note that `max` will also add native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `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
|
||||
@@ -328,10 +344,18 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO time format (HH:mm:ss).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a
|
||||
* valid ISO time format (HH:mm:ss).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
|
||||
* attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
|
||||
* attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
|
||||
* `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
|
||||
* `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `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
|
||||
@@ -423,10 +447,18 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO week format (yyyy-W##).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
||||
* a valid ISO week format (yyyy-W##).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
|
||||
* attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
|
||||
* attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `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
|
||||
@@ -520,10 +552,19 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be
|
||||
* a valid ISO month format (yyyy-MM).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must
|
||||
* be a valid ISO month format (yyyy-MM).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
|
||||
* attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
|
||||
* attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
|
||||
* @param {string=} required Sets `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
|
||||
@@ -1285,7 +1326,7 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
}
|
||||
|
||||
function parseObservedDateValue(val) {
|
||||
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
|
||||
return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ var ngBindDirective = ['$compile', function($compile) {
|
||||
$compile.$$addBindingInfo(element, attr.ngBind);
|
||||
element = element[0];
|
||||
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
|
||||
element.textContent = value === undefined ? '' : value;
|
||||
element.textContent = isUndefined(value) ? '' : value;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -128,7 +128,7 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
|
||||
$compile.$$addBindingInfo(element, interpolateFn.expressions);
|
||||
element = element[0];
|
||||
attr.$observe('ngBindTemplate', function(value) {
|
||||
element.textContent = value === undefined ? '' : value;
|
||||
element.textContent = isUndefined(value) ? '' : value;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,7 +262,6 @@ function classDirective(name, selector) {
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.base-class {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
}
|
||||
|
||||
.animate-if.ng-enter, .animate-if.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
}
|
||||
|
||||
.slide-animate.ng-enter, .slide-animate.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
|
||||
position:absolute;
|
||||
|
||||
@@ -10,16 +10,18 @@
|
||||
* current scope.
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* The only appropriate use of `ngInit` is for aliasing special properties of
|
||||
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you
|
||||
* should use {@link guide/controller controllers} rather than `ngInit`
|
||||
* to initialize values on a scope.
|
||||
* This directive can be abused to add unnecessary amounts of logic into your templates.
|
||||
* There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
|
||||
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
|
||||
* server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
|
||||
* rather than `ngInit` to initialize values on a scope.
|
||||
* </div>
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
|
||||
* sure you have parenthesis for correct precedence:
|
||||
* **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
|
||||
* sure you have parentheses to ensure correct operator precedence:
|
||||
* <pre class="prettyprint">
|
||||
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
|
||||
* `<div ng-init="test1 = ($index | toString)"></div>`
|
||||
* </pre>
|
||||
* </div>
|
||||
*
|
||||
|
||||
+40
-34
@@ -236,7 +236,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$$success = {}; // keep valid keys here
|
||||
this.$pending = undefined; // keep pending keys here
|
||||
this.$name = $interpolate($attr.name || '', false)($scope);
|
||||
|
||||
this.$$parentForm = nullFormCtrl;
|
||||
|
||||
var parsedNgModel = $parse($attr.ngModel),
|
||||
parsedNgModelAssign = parsedNgModel.assign,
|
||||
@@ -316,8 +316,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
return isUndefined(value) || value === '' || value === null || value !== value;
|
||||
};
|
||||
|
||||
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
|
||||
currentValidationRunId = 0;
|
||||
var currentValidationRunId = 0;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -350,7 +349,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
unset: function(object, property) {
|
||||
delete object[property];
|
||||
},
|
||||
parentForm: parentForm,
|
||||
$animate: $animate
|
||||
});
|
||||
|
||||
@@ -388,7 +386,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ctrl.$pristine = false;
|
||||
$animate.removeClass($element, PRISTINE_CLASS);
|
||||
$animate.addClass($element, DIRTY_CLASS);
|
||||
parentForm.$setDirty();
|
||||
ctrl.$$parentForm.$setDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -558,7 +556,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
function processParseErrors() {
|
||||
var errorKey = ctrl.$$parserName || 'parse';
|
||||
if (parserValid === undefined) {
|
||||
if (isUndefined(parserValid)) {
|
||||
setValidity(errorKey, null);
|
||||
} else {
|
||||
if (!parserValid) {
|
||||
@@ -728,37 +726,47 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @description
|
||||
* Update the view value.
|
||||
*
|
||||
* This method should be called when an input directive want to change the view value; typically,
|
||||
* this is done from within a DOM event handler.
|
||||
* This method should be called when a control wants to change the view value; typically,
|
||||
* this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
|
||||
* directive calls it when the value of the input changes and {@link ng.directive:select select}
|
||||
* calls it when an option is selected.
|
||||
*
|
||||
* For example {@link ng.directive:input input} calls it when the value of the input changes and
|
||||
* {@link ng.directive:select select} calls it when an option is selected.
|
||||
*
|
||||
* If the new `value` is an object (rather than a string or a number), we should make a copy of the
|
||||
* object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
|
||||
* watch of objects, it only looks for a change of identity. If you only change the property of
|
||||
* the object then ngModel will not realise that the object has changed and will not invoke the
|
||||
* `$parsers` and `$validators` pipelines.
|
||||
*
|
||||
* For this reason, you should not change properties of the copy once it has been passed to
|
||||
* `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
|
||||
*
|
||||
* When this method is called, the new `value` will be staged for committing through the `$parsers`
|
||||
* When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
|
||||
* and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
|
||||
* value sent directly for processing, finally to be applied to `$modelValue` and then the
|
||||
* **expression** specified in the `ng-model` attribute.
|
||||
*
|
||||
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
|
||||
* **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
|
||||
* in the `$viewChangeListeners` list, are called.
|
||||
*
|
||||
* In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
|
||||
* and the `default` trigger is not listed, all those actions will remain pending until one of the
|
||||
* `updateOn` events is triggered on the DOM element.
|
||||
* All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
|
||||
* directive is used with a custom debounce for this particular event.
|
||||
* Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
|
||||
* is specified, once the timer runs out.
|
||||
*
|
||||
* Note that calling this function does not trigger a `$digest`.
|
||||
* When used with standard inputs, the view value will always be a string (which is in some cases
|
||||
* parsed into another type, such as a `Date` object for `input[date]`.)
|
||||
* However, custom controls might also pass objects to this method. In this case, we should make
|
||||
* a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
|
||||
* perform a deep watch of objects, it only looks for a change of identity. If you only change
|
||||
* the property of the object then ngModel will not realise that the object has changed and
|
||||
* will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
|
||||
* not change properties of the copy once it has been passed to `$setViewValue`.
|
||||
* Otherwise you may cause the model value on the scope to change incorrectly.
|
||||
*
|
||||
* @param {string} value Value from the view.
|
||||
* <div class="alert alert-info">
|
||||
* In any case, the value passed to the method should always reflect the current value
|
||||
* of the control. For example, if you are calling `$setViewValue` for an input element,
|
||||
* you should pass the input DOM value. Otherwise, the control and the scope model become
|
||||
* out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
|
||||
* the control's DOM value in any way. If we want to change the control's DOM value
|
||||
* programmatically, we should update the `ngModel` scope expression. Its new value will be
|
||||
* picked up by the model controller, which will run it through the `$formatters`, `$render` it
|
||||
* to update the DOM, and finally call `$validate` on it.
|
||||
* </div>
|
||||
*
|
||||
* @param {*} value value from the view.
|
||||
* @param {string} trigger Event that triggered the update.
|
||||
*/
|
||||
this.$setViewValue = function(value, trigger) {
|
||||
@@ -935,7 +943,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
</script>
|
||||
<style>
|
||||
.my-input {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -1022,7 +1029,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
|
||||
return {
|
||||
pre: function ngModelPreLink(scope, element, attr, ctrls) {
|
||||
var modelCtrl = ctrls[0],
|
||||
formCtrl = ctrls[1] || nullFormCtrl;
|
||||
formCtrl = ctrls[1] || modelCtrl.$$parentForm;
|
||||
|
||||
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
|
||||
|
||||
@@ -1031,12 +1038,12 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
|
||||
|
||||
attr.$observe('name', function(newValue) {
|
||||
if (modelCtrl.$name !== newValue) {
|
||||
formCtrl.$$renameControl(modelCtrl, newValue);
|
||||
modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
formCtrl.$removeControl(modelCtrl);
|
||||
modelCtrl.$$parentForm.$removeControl(modelCtrl);
|
||||
});
|
||||
},
|
||||
post: function ngModelPostLink(scope, element, attr, ctrls) {
|
||||
@@ -1235,7 +1242,7 @@ var ngModelOptionsDirective = function() {
|
||||
var that = this;
|
||||
this.$options = copy($scope.$eval($attrs.ngModelOptions));
|
||||
// Allow adding/overriding bound events
|
||||
if (this.$options.updateOn !== undefined) {
|
||||
if (isDefined(this.$options.updateOn)) {
|
||||
this.$options.updateOnDefault = false;
|
||||
// extract "default" pseudo-event from list of events that can trigger a model update
|
||||
this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
|
||||
@@ -1258,7 +1265,6 @@ function addSetValidityMethod(context) {
|
||||
classCache = {},
|
||||
set = context.set,
|
||||
unset = context.unset,
|
||||
parentForm = context.parentForm,
|
||||
$animate = context.$animate;
|
||||
|
||||
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
|
||||
@@ -1266,7 +1272,7 @@ function addSetValidityMethod(context) {
|
||||
ctrl.$setValidity = setValidity;
|
||||
|
||||
function setValidity(validationErrorKey, state, controller) {
|
||||
if (state === undefined) {
|
||||
if (isUndefined(state)) {
|
||||
createAndSet('$pending', validationErrorKey, controller);
|
||||
} else {
|
||||
unsetAndCleanup('$pending', validationErrorKey, controller);
|
||||
@@ -1310,7 +1316,7 @@ function addSetValidityMethod(context) {
|
||||
}
|
||||
|
||||
toggleValidationCss(validationErrorKey, combinedState);
|
||||
parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
|
||||
ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
|
||||
}
|
||||
|
||||
function createAndSet(name, value, controller) {
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
* | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
|
||||
* | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
|
||||
*
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
* <div class="alert alert-info">
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
* </div>
|
||||
*
|
||||
*
|
||||
* # Iterating over object properties
|
||||
@@ -256,7 +258,6 @@
|
||||
.animate-repeat.ng-move,
|
||||
.animate-repeat.ng-enter,
|
||||
.animate-repeat.ng-leave {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
}
|
||||
|
||||
@@ -428,7 +429,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
// if object, extract keys, in enumeration order, unsorted
|
||||
collectionKeys = [];
|
||||
for (var itemKey in collection) {
|
||||
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
|
||||
if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
|
||||
collectionKeys.push(itemKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,9 +125,7 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
background: white;
|
||||
}
|
||||
|
||||
.animate-show.ng-hide-add.ng-hide-add-active,
|
||||
.animate-show.ng-hide-remove.ng-hide-remove-active {
|
||||
-webkit-transition: all linear 0.5s;
|
||||
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
|
||||
transition: all linear 0.5s;
|
||||
}
|
||||
|
||||
@@ -284,7 +282,6 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
.animate-hide {
|
||||
-webkit-transition: all linear 0.5s;
|
||||
transition: all linear 0.5s;
|
||||
line-height: 20px;
|
||||
opacity: 1;
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
}
|
||||
|
||||
.animate-switch.ng-animate {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
|
||||
position:absolute;
|
||||
|
||||
+173
-25
@@ -108,31 +108,162 @@ var SelectController =
|
||||
* @description
|
||||
* HTML `SELECT` element with angular data-binding.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
|
||||
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing
|
||||
* memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression.
|
||||
* The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
|
||||
* between the scope and the `<select>` control (including setting default values).
|
||||
* Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
|
||||
* {@link ngOptions `ngOptions`} directives.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
* directive.
|
||||
* When an item in the `<select>` menu is selected, the value of the selected option will be bound
|
||||
* to the model identified by the `ngModel` directive. With static or repeated options, this is
|
||||
* the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
|
||||
* If you want dynamic value attributes, you can use interpolation inside the value attribute.
|
||||
*
|
||||
* If the viewValue contains a value that doesn't match any of the options then the control
|
||||
* will automatically add an "unknown" option, which it then removes when this is resolved.
|
||||
* <div class="alert alert-warning">
|
||||
* Note that the value of a `select` directive used without `ngOptions` is always a string.
|
||||
* When the model needs to be bound to a non-string value, you must either explictly convert it
|
||||
* using a directive (see example below) or use `ngOptions` to specify the set of options.
|
||||
* This is because an option element can only be bound to string values at present.
|
||||
* </div>
|
||||
*
|
||||
* If the viewValue of `ngModel` does not match any of the options, then the control
|
||||
* will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* The value of a `select` directive used without `ngOptions` is always a string.
|
||||
* When the model needs to be bound to a non-string value, you must either explictly convert it
|
||||
* using a directive (see example below) or use `ngOptions` to specify the set of options.
|
||||
* This is because an option element can only be bound to string values at present.
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
|
||||
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression, and additionally in reducing memory and increasing speed by not creating
|
||||
* a new scope for each repeated instance.
|
||||
* </div>
|
||||
*
|
||||
* ### Example (binding `select` to a non-string value)
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} required Sets `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.
|
||||
* @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
|
||||
* interaction with the select element.
|
||||
* @param {string=} ngOptions sets the options that the select is populated with and defines what is
|
||||
* set on the model on selection. See {@link ngOptions `ngOptions`}.
|
||||
*
|
||||
* @example
|
||||
* ### Simple `select` elements with static options
|
||||
*
|
||||
* <example name="static-select" module="staticSelect">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="ExampleController">
|
||||
* <form name="myForm">
|
||||
* <label for="singleSelect"> Single select: </label><br>
|
||||
* <select name="singleSelect" ng-model="data.singleSelect">
|
||||
* <option value="option-1">Option 1</option>
|
||||
* <option value="option-2">Option 2</option>
|
||||
* </select><br>
|
||||
*
|
||||
* <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
|
||||
* <select name="singleSelect" ng-model="data.singleSelect">
|
||||
* <option value="">---Please select---</option> <!-- not selected / blank option -->
|
||||
* <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
|
||||
* <option value="option-2">Option 2</option>
|
||||
* </select><br>
|
||||
* <button ng-click="forceUnknownOption()">Force unknown option</button><br>
|
||||
* <tt>singleSelect = {{data.singleSelect}}</tt>
|
||||
*
|
||||
* <hr>
|
||||
* <label for="multipleSelect"> Multiple select: </label><br>
|
||||
* <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
|
||||
* <option value="option-1">Option 1</option>
|
||||
* <option value="option-2">Option 2</option>
|
||||
* <option value="option-3">Option 3</option>
|
||||
* </select><br>
|
||||
* <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
|
||||
* </form>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('staticSelect', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.data = {
|
||||
* singleSelect: null,
|
||||
* multipleSelect: [],
|
||||
* option1: 'option-1',
|
||||
* };
|
||||
*
|
||||
* $scope.forceUnknownOption = function() {
|
||||
* $scope.data.singleSelect = 'nonsense';
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*</example>
|
||||
*
|
||||
* ### Using `ngRepeat` to generate `select` options
|
||||
* <example name="ngrepeat-select" module="ngrepeatSelect">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="ExampleController">
|
||||
* <form name="myForm">
|
||||
* <label for="repeatSelect"> Repeat select: </label>
|
||||
* <select name="repeatSelect" ng-model="data.repeatSelect">
|
||||
* <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
|
||||
* </select>
|
||||
* </form>
|
||||
* <hr>
|
||||
* <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('ngrepeatSelect', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.data = {
|
||||
* singleSelect: null,
|
||||
* availableOptions: [
|
||||
* {id: '1', name: 'Option A'},
|
||||
* {id: '2', name: 'Option B'},
|
||||
* {id: '3', name: 'Option C'}
|
||||
* ],
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*</example>
|
||||
*
|
||||
*
|
||||
* ### Using `select` with `ngOptions` and setting a default value
|
||||
* See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
|
||||
*
|
||||
* <example name="select-with-default-values" module="defaultValueSelect">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="ExampleController">
|
||||
* <form name="myForm">
|
||||
* <label for="mySelect">Make a choice:</label>
|
||||
* <select name="mySelect" id="mySelect"
|
||||
* ng-options="option.name for option in data.availableOptions track by option.id"
|
||||
* ng-model="data.selectedOption"></select>
|
||||
* </form>
|
||||
* <hr>
|
||||
* <tt>option = {{data.selectedOption}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('defaultValueSelect', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.data = {
|
||||
* availableOptions: [
|
||||
* {id: '1', name: 'Option A'},
|
||||
* {id: '2', name: 'Option B'},
|
||||
* {id: '3', name: 'Option C'}
|
||||
* ],
|
||||
* selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*</example>
|
||||
*
|
||||
*
|
||||
* ### Binding `select` to a non-string value via `ngModel` parsing / formatting
|
||||
*
|
||||
* <example name="select-with-non-string-options" module="nonStringSelect">
|
||||
* <file name="index.html">
|
||||
@@ -270,9 +401,12 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
priority: 100,
|
||||
compile: function(element, attr) {
|
||||
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
if (isUndefined(attr.value)) {
|
||||
if (isDefined(attr.value)) {
|
||||
// If the value attribute is defined, check if it contains an interpolation
|
||||
var valueInterpolated = $interpolate(attr.value, true);
|
||||
} else {
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (!interpolateFn) {
|
||||
attr.$set('value', element.text());
|
||||
@@ -288,24 +422,38 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
function addOption(optionValue) {
|
||||
selectCtrl.addOption(optionValue, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
}
|
||||
|
||||
// Only update trigger option updates if this is an option within a `select`
|
||||
// that also has `ngModel` attached
|
||||
if (selectCtrl && selectCtrl.ngModelCtrl) {
|
||||
|
||||
if (interpolateFn) {
|
||||
if (valueInterpolated) {
|
||||
// The value attribute is interpolated
|
||||
var oldVal;
|
||||
attr.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
if (isDefined(oldVal)) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
addOption(newVal);
|
||||
});
|
||||
} else if (interpolateFn) {
|
||||
// The text content is interpolated
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
selectCtrl.addOption(newVal, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
addOption(newVal);
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
// The value attribute is static
|
||||
addOption(attr.value);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
|
||||
@@ -43,8 +43,9 @@ var patternDirective = function() {
|
||||
ctrl.$validate();
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
|
||||
ctrl.$validators.pattern = function(modelValue, viewValue) {
|
||||
// HTML5 pattern constraint validates the input value, so we validate the viewValue
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
+2
-2
@@ -1289,8 +1289,8 @@ function $HttpProvider() {
|
||||
* Resolves the raw $http promise.
|
||||
*/
|
||||
function resolvePromise(response, status, headers, statusText) {
|
||||
// normalize internal statuses to 0
|
||||
status = Math.max(status, 0);
|
||||
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
|
||||
status = status >= -1 ? status : 0;
|
||||
|
||||
(isSuccess(status) ? deferred.resolve : deferred.reject)({
|
||||
data: response,
|
||||
|
||||
@@ -109,7 +109,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
}
|
||||
}
|
||||
|
||||
xhr.send(post);
|
||||
xhr.send(isUndefined(post) ? null : post);
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
@@ -126,7 +126,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
|
||||
function completeRequest(callback, status, response, headersString, statusText) {
|
||||
// cancel timeout and subsequent timeout promise resolution
|
||||
if (timeoutId !== undefined) {
|
||||
if (isDefined(timeoutId)) {
|
||||
$browserDefer.cancel(timeoutId);
|
||||
}
|
||||
jsonpDone = xhr = null;
|
||||
|
||||
@@ -139,7 +139,7 @@ function $InterpolateProvider() {
|
||||
* ```js
|
||||
* var $interpolate = ...; // injected
|
||||
* var exp = $interpolate('Hello {{name | uppercase}}!');
|
||||
* expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
|
||||
* expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
|
||||
* ```
|
||||
*
|
||||
* `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
|
||||
|
||||
+3
-3
@@ -141,14 +141,14 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
|
||||
var appUrl, prevAppUrl;
|
||||
var rewrittenUrl;
|
||||
|
||||
if ((appUrl = beginsWith(appBase, url)) !== undefined) {
|
||||
if (isDefined(appUrl = beginsWith(appBase, url))) {
|
||||
prevAppUrl = appUrl;
|
||||
if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) {
|
||||
if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
|
||||
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
|
||||
} else {
|
||||
rewrittenUrl = appBase + prevAppUrl;
|
||||
}
|
||||
} else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) {
|
||||
} else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
|
||||
rewrittenUrl = appBaseNoFile + appUrl;
|
||||
} else if (appBaseNoFile == url + '/') {
|
||||
rewrittenUrl = appBaseNoFile;
|
||||
|
||||
@@ -38,6 +38,15 @@ var $parseMinErr = minErr('$parse');
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
// From the JavaScript docs:
|
||||
// Property names must be strings. This means that non-string objects cannot be used
|
||||
// as keys in an object. Any non-string object, including a number, is typecasted
|
||||
// into a string via the toString method.
|
||||
//
|
||||
// So, to ensure that we are checking the same `name` that JavaScript would use,
|
||||
// we cast it to a string, if possible
|
||||
name = (isObject(name) && name.toString) ? name.toString() : name;
|
||||
|
||||
if (name === "__defineGetter__" || name === "__defineSetter__"
|
||||
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|
||||
|| name === "__proto__") {
|
||||
@@ -774,6 +783,7 @@ ASTCompiler.prototype = {
|
||||
this.state.computing = 'assign';
|
||||
var result = this.nextId();
|
||||
this.recurse(assignable, result);
|
||||
this.return_(result);
|
||||
extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
|
||||
}
|
||||
var toWatch = getInputs(ast.body);
|
||||
|
||||
+3
-41
@@ -10,7 +10,7 @@ function $$RAFProvider() { //rAF
|
||||
$window.webkitCancelRequestAnimationFrame;
|
||||
|
||||
var rafSupported = !!requestAnimationFrame;
|
||||
var rafFn = rafSupported
|
||||
var raf = rafSupported
|
||||
? function(fn) {
|
||||
var id = requestAnimationFrame(fn);
|
||||
return function() {
|
||||
@@ -24,46 +24,8 @@ function $$RAFProvider() { //rAF
|
||||
};
|
||||
};
|
||||
|
||||
queueFn.supported = rafSupported;
|
||||
raf.supported = rafSupported;
|
||||
|
||||
var cancelLastRAF;
|
||||
var taskCount = 0;
|
||||
var taskQueue = [];
|
||||
return queueFn;
|
||||
|
||||
function flush() {
|
||||
for (var i = 0; i < taskQueue.length; i++) {
|
||||
var task = taskQueue[i];
|
||||
if (task) {
|
||||
taskQueue[i] = null;
|
||||
task();
|
||||
}
|
||||
}
|
||||
taskCount = taskQueue.length = 0;
|
||||
}
|
||||
|
||||
function queueFn(asyncFn) {
|
||||
var index = taskQueue.length;
|
||||
|
||||
taskCount++;
|
||||
taskQueue.push(asyncFn);
|
||||
|
||||
if (index === 0) {
|
||||
cancelLastRAF = rafFn(flush);
|
||||
}
|
||||
|
||||
return function cancelQueueFn() {
|
||||
if (index >= 0) {
|
||||
taskQueue[index] = null;
|
||||
index = null;
|
||||
|
||||
if (--taskCount === 0 && cancelLastRAF) {
|
||||
cancelLastRAF();
|
||||
cancelLastRAF = null;
|
||||
taskQueue.length = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return raf;
|
||||
}];
|
||||
}
|
||||
|
||||
+6
-6
@@ -253,10 +253,10 @@ function $RootScopeProvider() {
|
||||
* Registers a `listener` callback to be executed whenever the `watchExpression` changes.
|
||||
*
|
||||
* - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
|
||||
* $digest()} and should return the value that will be watched. (Since
|
||||
* {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the
|
||||
* `watchExpression` can execute multiple times per
|
||||
* {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
|
||||
* $digest()} and should return the value that will be watched. (`watchExpression` should not change
|
||||
* its value when executed multiple times with the same input because it may be executed multiple
|
||||
* times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
|
||||
* [idempotent](http://en.wikipedia.org/wiki/Idempotence).
|
||||
* - The `listener` is called only when the value from the current `watchExpression` and the
|
||||
* previous call to `watchExpression` are not equal (with the exception of the initial run,
|
||||
* see below). Inequality is determined according to reference inequality,
|
||||
@@ -605,7 +605,7 @@ function $RootScopeProvider() {
|
||||
// copy the items to oldValue and look for changes.
|
||||
newLength = 0;
|
||||
for (key in newValue) {
|
||||
if (newValue.hasOwnProperty(key)) {
|
||||
if (hasOwnProperty.call(newValue, key)) {
|
||||
newLength++;
|
||||
newItem = newValue[key];
|
||||
oldItem = oldValue[key];
|
||||
@@ -627,7 +627,7 @@ function $RootScopeProvider() {
|
||||
// we used to have more keys, need to find them and destroy them.
|
||||
changeDetected++;
|
||||
for (key in oldValue) {
|
||||
if (!newValue.hasOwnProperty(key)) {
|
||||
if (!hasOwnProperty.call(newValue, key)) {
|
||||
oldLength--;
|
||||
delete oldValue[key];
|
||||
}
|
||||
|
||||
+2
-2
@@ -294,7 +294,7 @@ function $SceDelegateProvider() {
|
||||
'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
|
||||
type, trustedValue);
|
||||
}
|
||||
if (trustedValue === null || trustedValue === undefined || trustedValue === '') {
|
||||
if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
|
||||
return trustedValue;
|
||||
}
|
||||
// All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
|
||||
@@ -349,7 +349,7 @@ function $SceDelegateProvider() {
|
||||
* `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
|
||||
*/
|
||||
function getTrusted(type, maybeTrusted) {
|
||||
if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') {
|
||||
if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
|
||||
return maybeTrusted;
|
||||
}
|
||||
var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
|
||||
|
||||
+67
-35
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var ANIMATE_TIMER_KEY = '$$animateCss';
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $animateCss
|
||||
@@ -326,8 +328,10 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
var gcsLookup = createLocalCacheLookup();
|
||||
var gcsStaggerLookup = createLocalCacheLookup();
|
||||
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$forceReflow', '$sniffer', '$$rAF',
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout, $$forceReflow, $sniffer, $$rAF) {
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
|
||||
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout,
|
||||
$$forceReflow, $sniffer, $$rAFScheduler, $animate) {
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
@@ -387,12 +391,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
var cancelLastRAFRequest;
|
||||
var rafWaitQueue = [];
|
||||
function waitUntilQuiet(callback) {
|
||||
if (cancelLastRAFRequest) {
|
||||
cancelLastRAFRequest(); //cancels the request
|
||||
}
|
||||
rafWaitQueue.push(callback);
|
||||
cancelLastRAFRequest = $$rAF(function() {
|
||||
cancelLastRAFRequest = null;
|
||||
$$rAFScheduler.waitUntilQuiet(function() {
|
||||
gcsLookup.flush();
|
||||
gcsStaggerLookup.flush();
|
||||
|
||||
@@ -409,8 +409,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
});
|
||||
}
|
||||
|
||||
return init;
|
||||
|
||||
function computeTimings(node, className, cacheKey) {
|
||||
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
|
||||
var aD = timings.animationDelay;
|
||||
@@ -425,9 +423,11 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
return timings;
|
||||
}
|
||||
|
||||
function init(element, options) {
|
||||
return function init(element, options) {
|
||||
var node = getDomNode(element);
|
||||
if (!node || !node.parentNode) {
|
||||
if (!node
|
||||
|| !node.parentNode
|
||||
|| !$animate.enabled()) {
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
@@ -483,7 +483,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
// there actually is a detected transition or keyframe animation
|
||||
if (options.applyClassesEarly && addRemoveClassName.length) {
|
||||
applyAnimationClasses(element, options);
|
||||
addRemoveClassName = '';
|
||||
}
|
||||
|
||||
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
|
||||
@@ -598,6 +597,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
if (options.delay != null) {
|
||||
var delayStyle = parseFloat(options.delay);
|
||||
|
||||
if (flags.applyTransitionDelay) {
|
||||
temporaryStyles.push(getCssDelayStyle(delayStyle));
|
||||
}
|
||||
|
||||
if (flags.applyAnimationDelay) {
|
||||
temporaryStyles.push(getCssDelayStyle(delayStyle, true));
|
||||
}
|
||||
}
|
||||
|
||||
// we need to recalculate the delay value since we used a pre-emptive negative
|
||||
// delay value and the delay value is required for the final event checking. This
|
||||
// property will ensure that this will happen after the RAF phase has passed.
|
||||
@@ -712,6 +723,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
cancel: cancelFn
|
||||
});
|
||||
|
||||
// should flush the cache animation
|
||||
waitUntilQuiet(noop);
|
||||
close();
|
||||
|
||||
return {
|
||||
@@ -809,27 +822,16 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
flags.hasAnimations = timings.animationDuration > 0;
|
||||
}
|
||||
|
||||
if (flags.applyTransitionDelay || flags.applyAnimationDelay) {
|
||||
if (flags.applyAnimationDelay) {
|
||||
relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
|
||||
? parseFloat(options.delay)
|
||||
: relativeDelay;
|
||||
|
||||
maxDelay = Math.max(relativeDelay, 0);
|
||||
|
||||
var delayStyle;
|
||||
if (flags.applyTransitionDelay) {
|
||||
timings.transitionDelay = relativeDelay;
|
||||
delayStyle = getCssDelayStyle(relativeDelay);
|
||||
temporaryStyles.push(delayStyle);
|
||||
node.style[delayStyle[0]] = delayStyle[1];
|
||||
}
|
||||
|
||||
if (flags.applyAnimationDelay) {
|
||||
timings.animationDelay = relativeDelay;
|
||||
delayStyle = getCssDelayStyle(relativeDelay, true);
|
||||
temporaryStyles.push(delayStyle);
|
||||
node.style[delayStyle[0]] = delayStyle[1];
|
||||
}
|
||||
timings.animationDelay = relativeDelay;
|
||||
delayStyle = getCssDelayStyle(relativeDelay, true);
|
||||
temporaryStyles.push(delayStyle);
|
||||
node.style[delayStyle[0]] = delayStyle[1];
|
||||
}
|
||||
|
||||
maxDelayTime = maxDelay * ONE_SECOND;
|
||||
@@ -858,17 +860,47 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
startTime = Date.now();
|
||||
element.on(events.join(' '), onAnimationProgress);
|
||||
$timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime, false);
|
||||
var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
|
||||
var endTime = startTime + timerTime;
|
||||
|
||||
var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
|
||||
var setupFallbackTimer = true;
|
||||
if (animationsData.length) {
|
||||
var currentTimerData = animationsData[0];
|
||||
setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
|
||||
if (setupFallbackTimer) {
|
||||
$timeout.cancel(currentTimerData.timer);
|
||||
} else {
|
||||
animationsData.push(close);
|
||||
}
|
||||
}
|
||||
|
||||
if (setupFallbackTimer) {
|
||||
var timer = $timeout(onAnimationExpired, timerTime, false);
|
||||
animationsData[0] = {
|
||||
timer: timer,
|
||||
expectedEndTime: endTime
|
||||
};
|
||||
animationsData.push(close);
|
||||
element.data(ANIMATE_TIMER_KEY, animationsData);
|
||||
}
|
||||
|
||||
element.on(events.join(' '), onAnimationProgress);
|
||||
applyAnimationToStyles(element, options);
|
||||
}
|
||||
|
||||
function onAnimationExpired() {
|
||||
// although an expired animation is a failed animation, getting to
|
||||
// this outcome is very easy if the CSS code screws up. Therefore we
|
||||
// should still continue normally as if the animation completed correctly.
|
||||
close();
|
||||
var animationsData = element.data(ANIMATE_TIMER_KEY);
|
||||
|
||||
// this will be false in the event that the element was
|
||||
// removed from the DOM (via a leave animation or something
|
||||
// similar)
|
||||
if (animationsData) {
|
||||
for (var i = 1; i < animationsData.length; i++) {
|
||||
animationsData[i]();
|
||||
}
|
||||
element.removeData(ANIMATE_TIMER_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
function onAnimationProgress(event) {
|
||||
@@ -895,6 +927,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
}];
|
||||
|
||||
@@ -22,13 +22,13 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
return function initDriverFn(animationDetails, onBeforeClassesAppliedCb) {
|
||||
return function initDriverFn(animationDetails) {
|
||||
return animationDetails.from && animationDetails.to
|
||||
? prepareFromToAnchorAnimation(animationDetails.from,
|
||||
animationDetails.to,
|
||||
animationDetails.classes,
|
||||
animationDetails.anchors)
|
||||
: prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb);
|
||||
: prepareRegularAnimation(animationDetails);
|
||||
};
|
||||
|
||||
function filterCssClasses(classes) {
|
||||
@@ -224,21 +224,14 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
};
|
||||
}
|
||||
|
||||
function prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb) {
|
||||
function prepareRegularAnimation(animationDetails) {
|
||||
var element = animationDetails.element;
|
||||
var options = animationDetails.options || {};
|
||||
|
||||
// since the ng-EVENT, class-ADD and class-REMOVE classes are applied inside
|
||||
// of the animateQueue pre and postDigest stages then there is no need to add
|
||||
// then them here as well.
|
||||
options.$$skipPreparationClasses = true;
|
||||
|
||||
// during the pre/post digest stages inside of animateQueue we also performed
|
||||
// the blocking (transition:-9999s) so there is no point in doing that again.
|
||||
options.skipBlocking = true;
|
||||
|
||||
if (animationDetails.structural) {
|
||||
options.event = animationDetails.event;
|
||||
options.structural = true;
|
||||
options.applyClassesEarly = true;
|
||||
|
||||
// we special case the leave animation since we want to ensure that
|
||||
// the element is removed as soon as the animation is over. Otherwise
|
||||
@@ -248,11 +241,6 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
}
|
||||
}
|
||||
|
||||
// we apply the classes right away since the pre-digest took care of the
|
||||
// preparation classes.
|
||||
onBeforeClassesAppliedCb(element);
|
||||
applyAnimationClasses(element, options);
|
||||
|
||||
// We assign the preparationClasses as the actual animation event since
|
||||
// the internals of $animateCss will just suffix the event token values
|
||||
// with `-active` to trigger the animation.
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// by the time...
|
||||
|
||||
var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
|
||||
this.$get = ['$injector', '$$AnimateRunner', '$$rAFMutex', '$$jqLite',
|
||||
function($injector, $$AnimateRunner, $$rAFMutex, $$jqLite) {
|
||||
this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
|
||||
function($injector, $$AnimateRunner, $$jqLite) {
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
// $animateJs(element, 'enter');
|
||||
|
||||
@@ -381,9 +381,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
return runner;
|
||||
}
|
||||
|
||||
applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
|
||||
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
|
||||
|
||||
// the counter keeps track of cancelled animations
|
||||
var counter = (existingAnimation.counter || 0) + 1;
|
||||
newAnimation.counter = counter;
|
||||
@@ -442,10 +439,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
: animationDetails.event;
|
||||
|
||||
markElementAnimationState(element, RUNNING_STATE);
|
||||
var realRunner = $$animation(element, event, animationDetails.options, function(e) {
|
||||
$$forceReflow();
|
||||
blockTransitions(getDomNode(e), false);
|
||||
});
|
||||
var realRunner = $$animation(element, event, animationDetails.options);
|
||||
|
||||
realRunner.done(function(status) {
|
||||
close(!status);
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
|
||||
var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
|
||||
var waitQueue = [];
|
||||
|
||||
function waitForTick(fn) {
|
||||
waitQueue.push(fn);
|
||||
if (waitQueue.length > 1) return;
|
||||
$$rAF(function() {
|
||||
for (var i = 0; i < waitQueue.length; i++) {
|
||||
waitQueue[i]();
|
||||
}
|
||||
waitQueue = [];
|
||||
});
|
||||
}
|
||||
|
||||
return function() {
|
||||
var passed = false;
|
||||
$$rAF(function() {
|
||||
waitForTick(function() {
|
||||
passed = true;
|
||||
});
|
||||
return function(fn) {
|
||||
passed ? fn() : $$rAF(fn);
|
||||
return function(callback) {
|
||||
passed ? callback() : waitForTick(callback);
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
|
||||
var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
|
||||
function($q, $sniffer, $$animateAsyncRun) {
|
||||
|
||||
var INITIAL_STATE = 0;
|
||||
var DONE_PENDING_STATE = 1;
|
||||
var DONE_COMPLETE_STATE = 2;
|
||||
@@ -57,7 +72,7 @@ var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
|
||||
this.setHost(host);
|
||||
|
||||
this._doneCallbacks = [];
|
||||
this._runInAnimationFrame = $$rAFMutex();
|
||||
this._runInAnimationFrame = $$animateAsyncRun();
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
|
||||
+15
-17
@@ -19,8 +19,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
return element.data(RUNNER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap) {
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
|
||||
|
||||
var animationQueue = [];
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
@@ -88,11 +88,11 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (remainingLevelEntries <= 0) {
|
||||
remainingLevelEntries = nextLevelEntries;
|
||||
nextLevelEntries = 0;
|
||||
result = result.concat(row);
|
||||
result.push(row);
|
||||
row = [];
|
||||
}
|
||||
row.push(entry.fn);
|
||||
forEach(entry.children, function(childEntry) {
|
||||
entry.children.forEach(function(childEntry) {
|
||||
nextLevelEntries++;
|
||||
queue.push(childEntry);
|
||||
});
|
||||
@@ -100,14 +100,15 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
if (row.length) {
|
||||
result = result.concat(row);
|
||||
result.push(row);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(matsko): document the signature in a better way
|
||||
return function(element, event, options, onBeforeClassesAppliedCb) {
|
||||
return function(element, event, options) {
|
||||
options = prepareAnimationOptions(options);
|
||||
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
|
||||
|
||||
@@ -159,8 +160,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
// the element was destroyed early on which removed the runner
|
||||
// form its storage. This means we can't animate this element
|
||||
// at all and it already has been closed due to destruction.
|
||||
var elm = entry.element;
|
||||
if (getRunner(elm) && getDomNode(elm).parentNode) {
|
||||
if (getRunner(entry.element)) {
|
||||
animations.push(entry);
|
||||
} else {
|
||||
entry.close();
|
||||
@@ -191,7 +191,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
: animationEntry.element;
|
||||
|
||||
if (getRunner(targetElement)) {
|
||||
var operation = invokeFirstDriver(animationEntry, onBeforeClassesAppliedCb);
|
||||
var operation = invokeFirstDriver(animationEntry);
|
||||
if (operation) {
|
||||
startAnimationFn = operation.start;
|
||||
}
|
||||
@@ -211,11 +211,9 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
});
|
||||
|
||||
// we need to sort each of the animations in order of parent to child
|
||||
// relationships. This ensures that the parent to child classes are
|
||||
// applied at the right time.
|
||||
forEach(sortAnimations(toBeSortedAnimations), function(triggerAnimation) {
|
||||
triggerAnimation();
|
||||
});
|
||||
// relationships. This ensures that the child classes are applied at the
|
||||
// right time.
|
||||
$$rAFScheduler(sortAnimations(toBeSortedAnimations));
|
||||
});
|
||||
|
||||
return runner;
|
||||
@@ -285,7 +283,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
var lookupKey = from.animationID.toString();
|
||||
if (!anchorGroups[lookupKey]) {
|
||||
var group = anchorGroups[lookupKey] = {
|
||||
// TODO(matsko): double-check this code
|
||||
structural: true,
|
||||
beforeStart: function() {
|
||||
fromAnimation.beforeStart();
|
||||
toAnimation.beforeStart();
|
||||
@@ -339,7 +337,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
return matches.join(' ');
|
||||
}
|
||||
|
||||
function invokeFirstDriver(animationDetails, onBeforeClassesAppliedCb) {
|
||||
function invokeFirstDriver(animationDetails) {
|
||||
// we loop in reverse order since the more general drivers (like CSS and JS)
|
||||
// may attempt more elements, but custom drivers are more particular
|
||||
for (var i = drivers.length - 1; i >= 0; i--) {
|
||||
@@ -347,7 +345,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
|
||||
|
||||
var factory = $injector.get(driverName);
|
||||
var driver = factory(animationDetails, onBeforeClassesAppliedCb);
|
||||
var driver = factory(animationDetails);
|
||||
if (driver) {
|
||||
return driver;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
/* global angularAnimateModule: true,
|
||||
|
||||
$$BodyProvider,
|
||||
$$rAFMutexFactory,
|
||||
$$AnimateAsyncRunFactory,
|
||||
$$rAFSchedulerFactory,
|
||||
$$AnimateChildrenDirective,
|
||||
$$AnimateRunnerFactory,
|
||||
$$AnimateQueueProvider,
|
||||
@@ -20,7 +21,7 @@
|
||||
* @description
|
||||
*
|
||||
* The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
|
||||
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` then the animation hooks are enabled for an Angular app.
|
||||
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
|
||||
*
|
||||
* <div doc-module-components="ngAnimate"></div>
|
||||
*
|
||||
@@ -53,7 +54,7 @@
|
||||
* CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
|
||||
* and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
|
||||
*
|
||||
* The example below shows how an `enter` animation can be made possible on a element using `ng-if`:
|
||||
* The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
|
||||
*
|
||||
* ```html
|
||||
* <div ng-if="bool" class="fade">
|
||||
@@ -188,8 +189,8 @@
|
||||
* /* this will have a 100ms delay between each successive leave animation */
|
||||
* transition-delay: 0.1s;
|
||||
*
|
||||
* /* in case the stagger doesn't work then the duration value
|
||||
* must be set to 0 to avoid an accidental CSS inheritance */
|
||||
* /* As of 1.4.4, this must always be set: it signals ngAnimate
|
||||
* to not accidentally inherit a delay property from another CSS class */
|
||||
* transition-duration: 0s;
|
||||
* }
|
||||
* .my-animation.ng-enter.ng-enter-active {
|
||||
@@ -738,16 +739,16 @@
|
||||
* @description
|
||||
* The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
|
||||
*
|
||||
* Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.
|
||||
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
|
||||
*/
|
||||
angular.module('ngAnimate', [])
|
||||
.provider('$$body', $$BodyProvider)
|
||||
|
||||
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
|
||||
|
||||
.factory('$$rAFMutex', $$rAFMutexFactory)
|
||||
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
|
||||
|
||||
.factory('$$AnimateRunner', $$AnimateRunnerFactory)
|
||||
.factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
|
||||
|
||||
.provider('$$animateQueue', $$AnimateQueueProvider)
|
||||
.provider('$$animation', $$AnimationProvider)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
|
||||
var queue, cancelFn;
|
||||
|
||||
function scheduler(tasks) {
|
||||
// we make a copy since RAFScheduler mutates the state
|
||||
// of the passed in array variable and this would be difficult
|
||||
// to track down on the outside code
|
||||
queue = queue.concat(tasks);
|
||||
nextTick();
|
||||
}
|
||||
|
||||
queue = scheduler.queue = [];
|
||||
|
||||
/* waitUntilQuiet does two things:
|
||||
* 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
|
||||
* 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
|
||||
*
|
||||
* The motivation here is that animation code can request more time from the scheduler
|
||||
* before the next wave runs. This allows for certain DOM properties such as classes to
|
||||
* be resolved in time for the next animation to run.
|
||||
*/
|
||||
scheduler.waitUntilQuiet = function(fn) {
|
||||
if (cancelFn) cancelFn();
|
||||
|
||||
cancelFn = $$rAF(function() {
|
||||
cancelFn = null;
|
||||
fn();
|
||||
nextTick();
|
||||
});
|
||||
};
|
||||
|
||||
return scheduler;
|
||||
|
||||
function nextTick() {
|
||||
if (!queue.length) return;
|
||||
|
||||
var items = queue.shift();
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
items[i]();
|
||||
}
|
||||
|
||||
if (!cancelFn) {
|
||||
$$rAF(function() {
|
||||
if (!cancelFn) nextTick();
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
@@ -36,7 +36,7 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA
|
||||
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
|
||||
// therefore there is no reason to test anymore for other vendor prefixes:
|
||||
// http://caniuse.com/#search=transition
|
||||
if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
|
||||
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
|
||||
CSS_PREFIX = '-webkit-';
|
||||
TRANSITION_PROP = 'WebkitTransition';
|
||||
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
|
||||
@@ -45,7 +45,7 @@ if (window.ontransitionend === undefined && window.onwebkittransitionend !== und
|
||||
TRANSITIONEND_EVENT = 'transitionend';
|
||||
}
|
||||
|
||||
if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
|
||||
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
|
||||
CSS_PREFIX = '-webkit-';
|
||||
ANIMATION_PROP = 'WebkitAnimation';
|
||||
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
|
||||
|
||||
+39
-35
@@ -52,6 +52,16 @@
|
||||
var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
provider('$aria', $AriaProvider);
|
||||
|
||||
/**
|
||||
* Internal Utilities
|
||||
*/
|
||||
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY'];
|
||||
|
||||
var isNodeOneOf = function(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $ariaProvider
|
||||
@@ -113,10 +123,10 @@ function $AriaProvider() {
|
||||
config = angular.extend(config, newConfig);
|
||||
};
|
||||
|
||||
function watchExpr(attrName, ariaAttr, negate) {
|
||||
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
|
||||
return function(scope, elem, attr) {
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !attr[ariaCamelName]) {
|
||||
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
// ensure boolean value
|
||||
boolVal = negate ? !boolVal : !!boolVal;
|
||||
@@ -125,7 +135,6 @@ function $AriaProvider() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $aria
|
||||
@@ -184,10 +193,10 @@ function $AriaProvider() {
|
||||
|
||||
|
||||
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
|
||||
}])
|
||||
.directive('ngHide', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', false);
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
|
||||
}])
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
|
||||
@@ -261,6 +270,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
|
||||
getRadioReaction() : ngAriaCheckboxReaction);
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'range':
|
||||
if (shouldAttachRole(shape, elem)) {
|
||||
@@ -289,6 +301,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'multiline':
|
||||
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
|
||||
@@ -297,10 +312,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
|
||||
scope.$watch(function ngAriaRequiredWatch() {
|
||||
return ngModel.$error.required;
|
||||
@@ -322,7 +333,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
};
|
||||
}])
|
||||
.directive('ngDisabled', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []);
|
||||
}])
|
||||
.directive('ngMessages', function() {
|
||||
return {
|
||||
@@ -342,35 +353,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];
|
||||
if (!isNodeOneOf(elem, nodeBlackList)) {
|
||||
|
||||
function isNodeOneOf(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
if ($aria.config('bindRoleForClick') && !elem.attr('role')) {
|
||||
elem.attr('role', 'button');
|
||||
}
|
||||
}
|
||||
|
||||
if ($aria.config('bindRoleForClick')
|
||||
&& !elem.attr('role')
|
||||
&& !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('role', 'button');
|
||||
}
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
elem.on('keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
if (keyCode === 32 || keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.on('keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
if (keyCode === 32 || keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -378,7 +382,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ function $$CookieWriter($document, $log, $browser) {
|
||||
options = options || {};
|
||||
expires = options.expires;
|
||||
path = angular.isDefined(options.path) ? options.path : cookiePath;
|
||||
if (value === undefined) {
|
||||
if (angular.isUndefined(value)) {
|
||||
expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
value = '';
|
||||
}
|
||||
|
||||
Vendored
+61
-24
@@ -87,7 +87,7 @@ angular.mock.$Browser = function() {
|
||||
if (fn.id === deferId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
self.deferredFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
}
|
||||
@@ -462,7 +462,7 @@ angular.mock.$IntervalProvider = function() {
|
||||
if (fn.id === promise.$$intervalId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
}
|
||||
}
|
||||
@@ -504,7 +504,7 @@ angular.mock.$IntervalProvider = function() {
|
||||
if (fn.id === promise.$$intervalId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
repeatFns[fnIndex].deferred.reject('canceled');
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
@@ -763,25 +763,62 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
||||
return reflowFn;
|
||||
});
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$$forceReflow',
|
||||
function($delegate, $timeout, $browser, $$rAF, $$forceReflow) {
|
||||
$provide.factory('$$animateAsyncRun', function() {
|
||||
var queue = [];
|
||||
var queueFn = function() {
|
||||
return function(fn) {
|
||||
queue.push(fn);
|
||||
};
|
||||
};
|
||||
queueFn.flush = function() {
|
||||
if (queue.length === 0) return false;
|
||||
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
queue[i]();
|
||||
}
|
||||
queue = [];
|
||||
|
||||
return true;
|
||||
};
|
||||
return queueFn;
|
||||
});
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
|
||||
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
|
||||
function($delegate, $timeout, $browser, $$rAF,
|
||||
$$forceReflow, $$animateAsyncRun, $rootScope) {
|
||||
var animate = {
|
||||
queue: [],
|
||||
cancel: $delegate.cancel,
|
||||
on: $delegate.on,
|
||||
off: $delegate.off,
|
||||
pin: $delegate.pin,
|
||||
get reflows() {
|
||||
return $$forceReflow.totalReflows;
|
||||
},
|
||||
enabled: $delegate.enabled,
|
||||
triggerCallbackEvents: function() {
|
||||
$$rAF.flush();
|
||||
},
|
||||
triggerCallbackPromise: function() {
|
||||
$timeout.flush(0);
|
||||
},
|
||||
triggerCallbacks: function() {
|
||||
this.triggerCallbackEvents();
|
||||
this.triggerCallbackPromise();
|
||||
flush: function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
var doNextRun, somethingFlushed = false;
|
||||
do {
|
||||
doNextRun = false;
|
||||
|
||||
if ($$rAF.queue.length) {
|
||||
$$rAF.flush();
|
||||
doNextRun = somethingFlushed = true;
|
||||
}
|
||||
|
||||
if ($$animateAsyncRun.flush()) {
|
||||
doNextRun = somethingFlushed = true;
|
||||
}
|
||||
} while (doNextRun);
|
||||
|
||||
if (!somethingFlushed) {
|
||||
throw new Error('No pending animations ready to be closed or flushed');
|
||||
}
|
||||
|
||||
$rootScope.$digest();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -998,7 +1035,7 @@ angular.mock.dump = function(object) {
|
||||
$http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
|
||||
$scope.status = '';
|
||||
}).error(function() {
|
||||
$scope.status = 'ERROR!';
|
||||
$scope.status = 'Failed...';
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1733,28 +1770,28 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
|
||||
}];
|
||||
|
||||
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
|
||||
var queue = [];
|
||||
var rafFn = function(fn) {
|
||||
var index = queue.length;
|
||||
queue.push(fn);
|
||||
var index = rafFn.queue.length;
|
||||
rafFn.queue.push(fn);
|
||||
return function() {
|
||||
queue.splice(index, 1);
|
||||
rafFn.queue.splice(index, 1);
|
||||
};
|
||||
};
|
||||
|
||||
rafFn.queue = [];
|
||||
rafFn.supported = $delegate.supported;
|
||||
|
||||
rafFn.flush = function() {
|
||||
if (queue.length === 0) {
|
||||
if (rafFn.queue.length === 0) {
|
||||
throw new Error('No rAF callbacks present');
|
||||
}
|
||||
|
||||
var length = queue.length;
|
||||
var length = rafFn.queue.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
queue[i]();
|
||||
rafFn.queue[i]();
|
||||
}
|
||||
|
||||
queue = queue.slice(i);
|
||||
rafFn.queue = rafFn.queue.slice(i);
|
||||
};
|
||||
|
||||
return rafFn;
|
||||
@@ -1802,7 +1839,7 @@ angular.mock.$RootElementProvider = function() {
|
||||
*
|
||||
* describe('myDirectiveController', function() {
|
||||
* it('should write the bound name to the log', inject(function($controller, $log) {
|
||||
* var ctrl = $controller('MyDirective', { /* no locals */ }, { name: 'Clark Kent' });
|
||||
* var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' });
|
||||
* expect(ctrl.name).toEqual('Clark Kent');
|
||||
* expect($log.info.logs).toEqual(['Clark Kent']);
|
||||
* });
|
||||
|
||||
@@ -17,7 +17,7 @@ function lookupDottedPath(obj, path) {
|
||||
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
|
||||
}
|
||||
var keys = path.split('.');
|
||||
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
|
||||
for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
|
||||
var key = keys[i];
|
||||
obj = (obj !== null) ? obj[key] : undefined;
|
||||
}
|
||||
@@ -348,6 +348,7 @@ function shallowClearAndCopy(src, dst) {
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
provider('$resource', function() {
|
||||
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
|
||||
var provider = this;
|
||||
|
||||
this.defaults = {
|
||||
@@ -422,7 +423,8 @@ angular.module('ngResource', ['ng']).
|
||||
var self = this,
|
||||
url = actionUrl || self.template,
|
||||
val,
|
||||
encodedVal;
|
||||
encodedVal,
|
||||
protocolAndDomain = '';
|
||||
|
||||
var urlParams = self.urlParams = {};
|
||||
forEach(url.split(/\W/), function(param) {
|
||||
@@ -435,6 +437,10 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
});
|
||||
url = url.replace(/\\:/g, ':');
|
||||
url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
|
||||
protocolAndDomain = match;
|
||||
return '';
|
||||
});
|
||||
|
||||
params = params || {};
|
||||
forEach(self.urlParams, function(_, urlParam) {
|
||||
@@ -465,7 +471,7 @@ angular.module('ngResource', ['ng']).
|
||||
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
|
||||
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
|
||||
// replace escaped `/\.` with `/.`
|
||||
config.url = url.replace(/\/\\\./, '/.');
|
||||
config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
|
||||
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
|
||||
@@ -90,7 +90,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
||||
}
|
||||
|
||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
|
||||
display:block;
|
||||
|
||||
@@ -293,7 +293,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
}
|
||||
|
||||
function push(value) {
|
||||
if (value === undefined) {
|
||||
if (angular.isUndefined(value)) {
|
||||
value = '';
|
||||
} else if (typeof value !== 'string') {
|
||||
value = angular.toJson(value);
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ function serializeObject(obj) {
|
||||
val = toJsonReplacer(key, val);
|
||||
if (isObject(val)) {
|
||||
|
||||
if (seen.indexOf(val) >= 0) return '<<already seen>>';
|
||||
if (seen.indexOf(val) >= 0) return '...';
|
||||
|
||||
seen.push(val);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ function serializeObject(obj) {
|
||||
function toDebugString(obj) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (typeof obj === 'undefined') {
|
||||
} else if (isUndefined(obj)) {
|
||||
return 'undefined';
|
||||
} else if (typeof obj !== 'string') {
|
||||
return serializeObject(obj);
|
||||
|
||||
@@ -386,6 +386,18 @@ describe('angular', function() {
|
||||
expect(aCopy).toBe(aCopy.self);
|
||||
});
|
||||
|
||||
it('should deeply copy XML nodes', function() {
|
||||
var anElement = document.createElement('foo');
|
||||
anElement.appendChild(document.createElement('bar'));
|
||||
var theCopy = anElement.cloneNode(true);
|
||||
expect(copy(anElement).outerHTML).toEqual(theCopy.outerHTML);
|
||||
expect(copy(anElement)).not.toBe(anElement);
|
||||
});
|
||||
|
||||
it('should not try to call a non-function called `cloneNode`', function() {
|
||||
expect(copy.bind(null, { cloneNode: 100 })).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle objects with multiple references', function() {
|
||||
var b = {};
|
||||
var a = [b, -1, b];
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
{{jqueryVersion}}
|
||||
|
||||
<script src="../../../../bower_components/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Verify that empty ng-jq is not accessing `window['']`.
|
||||
// (See https://github.com/angular/angular.js/issues/12741 for more details)
|
||||
window[''] = window.jQuery;
|
||||
</script>
|
||||
<script src="angular.js"></script>
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -47,6 +47,7 @@ afterEach(function() {
|
||||
if (bod) {
|
||||
bod.$$hashKey = null;
|
||||
}
|
||||
document.$$hashKey = null;
|
||||
|
||||
if (this.$injector) {
|
||||
var $rootScope = this.$injector.get('$rootScope');
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ describe('minErr', function() {
|
||||
a.b.a = a;
|
||||
|
||||
var myError = testError('26', 'a is {0}', a);
|
||||
expect(myError.message).toMatch(/a is {"b":{"a":"<<already seen>>"}}/);
|
||||
expect(myError.message).toMatch(/a is {"b":{"a":"..."}}/);
|
||||
});
|
||||
|
||||
it('should preserve interpolation markers when fewer arguments than needed are provided', function() {
|
||||
|
||||
@@ -341,6 +341,21 @@ describe("$animate", function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not break postDigest for subsequent elements if addClass contains non-valid CSS class names', function() {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
var element1 = jqLite('<div></div>');
|
||||
var element2 = jqLite('<div></div>');
|
||||
|
||||
$animate.enter(element1, $rootElement, null, { addClass: ' ' });
|
||||
$animate.enter(element2, $rootElement, null, { addClass: 'valid-name' });
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element2.hasClass('valid-name')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not issue a call to removeClass if the provided class value is not a string or array', function() {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
var spy = spyOn(window, 'jqLiteRemoveClass').andCallThrough();
|
||||
|
||||
+48
-6
@@ -12,12 +12,20 @@ function MockWindow(options) {
|
||||
var events = {};
|
||||
var timeouts = this.timeouts = [];
|
||||
var locationHref = 'http://server/';
|
||||
var committedHref = 'http://server/';
|
||||
var mockWindow = this;
|
||||
var msie = options.msie;
|
||||
var ieState;
|
||||
|
||||
historyEntriesLength = 1;
|
||||
|
||||
function replaceHash(href, hash) {
|
||||
// replace the hash with the new one (stripping off a leading hash if there is one)
|
||||
// See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members
|
||||
return stripHash(href) + '#' + hash.replace(/^#/,'');
|
||||
}
|
||||
|
||||
|
||||
this.setTimeout = function(fn) {
|
||||
return timeouts.push(fn) - 1;
|
||||
};
|
||||
@@ -46,24 +54,28 @@ function MockWindow(options) {
|
||||
|
||||
this.location = {
|
||||
get href() {
|
||||
return locationHref;
|
||||
return committedHref;
|
||||
},
|
||||
set href(value) {
|
||||
locationHref = value;
|
||||
mockWindow.history.state = null;
|
||||
historyEntriesLength++;
|
||||
if (!options.updateAsync) this.flushHref();
|
||||
},
|
||||
get hash() {
|
||||
return getHash(locationHref);
|
||||
return getHash(committedHref);
|
||||
},
|
||||
set hash(value) {
|
||||
// replace the hash with the new one (stripping off a leading hash if there is one)
|
||||
// See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members
|
||||
locationHref = stripHash(locationHref) + '#' + value.replace(/^#/,'');
|
||||
locationHref = replaceHash(locationHref, value);
|
||||
if (!options.updateAsync) this.flushHref();
|
||||
},
|
||||
replace: function(url) {
|
||||
locationHref = url;
|
||||
mockWindow.history.state = null;
|
||||
if (!options.updateAsync) this.flushHref();
|
||||
},
|
||||
flushHref: function() {
|
||||
committedHref = locationHref;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,7 +144,7 @@ describe('browser', function() {
|
||||
|
||||
logs = {log:[], warn:[], info:[], error:[]};
|
||||
|
||||
var fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
|
||||
fakeLog = {log: function() { logs.log.push(slice.call(arguments)); },
|
||||
warn: function() { logs.warn.push(slice.call(arguments)); },
|
||||
info: function() { logs.info.push(slice.call(arguments)); },
|
||||
error: function() { logs.error.push(slice.call(arguments)); }};
|
||||
@@ -703,7 +715,11 @@ describe('browser', function() {
|
||||
describe('integration tests with $location', function() {
|
||||
|
||||
function setup(options) {
|
||||
fakeWindow = new MockWindow(options);
|
||||
browser = new Browser(fakeWindow, fakeDocument, fakeLog, sniffer);
|
||||
|
||||
module(function($provide, $locationProvider) {
|
||||
|
||||
spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) {
|
||||
fakeWindow.location.href = newUrl;
|
||||
});
|
||||
@@ -827,6 +843,32 @@ describe('browser', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// issue #12241
|
||||
it('should not infinite digest if the browser does not synchronously update the location properties', function() {
|
||||
setup({
|
||||
history: true,
|
||||
html5Mode: true,
|
||||
updateAsync: true // Simulate a browser that doesn't update the href synchronously
|
||||
});
|
||||
|
||||
inject(function($location, $rootScope) {
|
||||
|
||||
// Change the hash within Angular and check that we don't infinitely digest
|
||||
$location.hash('newHash');
|
||||
expect(function() { $rootScope.$digest(); }).not.toThrow();
|
||||
expect($location.absUrl()).toEqual('http://server/#newHash');
|
||||
|
||||
// Now change the hash from outside Angular and check that $location updates correctly
|
||||
fakeWindow.location.hash = '#otherHash';
|
||||
|
||||
// simulate next tick - since this browser doesn't update synchronously
|
||||
fakeWindow.location.flushHref();
|
||||
fakeWindow.fire('hashchange');
|
||||
|
||||
expect($location.absUrl()).toEqual('http://server/#otherHash');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration test with $rootScope', function() {
|
||||
|
||||
+256
-25
@@ -58,6 +58,119 @@ describe('form', function() {
|
||||
expect(form.alias).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should ignore changes in manually removed controls', function() {
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<input name="control" ng-maxlength="1" ng-model="value" store-model-ctrl/>' +
|
||||
'</form>')(scope);
|
||||
|
||||
var form = scope.myForm;
|
||||
|
||||
var input = doc.find('input').eq(0);
|
||||
var inputController = input.controller('ngModel');
|
||||
|
||||
changeInputValue(input, 'ab');
|
||||
scope.$apply();
|
||||
|
||||
expect(form.$error.maxlength).toBeTruthy();
|
||||
expect(form.$dirty).toBe(true);
|
||||
expect(form.$error.maxlength[0].$name).toBe('control');
|
||||
|
||||
// remove control
|
||||
form.$removeControl(form.control);
|
||||
expect(form.control).toBeUndefined();
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
|
||||
inputController.$setPristine();
|
||||
expect(form.$dirty).toBe(true);
|
||||
|
||||
form.$setPristine();
|
||||
|
||||
changeInputValue(input, 'abc');
|
||||
scope.$apply();
|
||||
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
expect(form.$dirty).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should react to validation changes in manually added controls', function() {
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<input name="control" ng-maxlength="1" ng-model="value" store-model-ctrl/>' +
|
||||
'</form>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
var form = scope.myForm;
|
||||
|
||||
var input = doc.find('input').eq(0);
|
||||
|
||||
// remove control and invalidate it
|
||||
form.$removeControl(control);
|
||||
expect(form.control).toBeUndefined();
|
||||
|
||||
changeInputValue(input, 'abc');
|
||||
expect(control.$error.maxlength).toBe(true);
|
||||
expect(control.$dirty).toBe(true);
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
expect(form.$dirty).toBe(false);
|
||||
|
||||
// re-add the control; its current validation state is not propagated
|
||||
form.$addControl(control);
|
||||
expect(form.control).toBe(control);
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
expect(form.$dirty).toBe(false);
|
||||
|
||||
// Only when the input changes again its validation state is propagated
|
||||
changeInputValue(input, 'abcd');
|
||||
expect(form.$error.maxlength[0]).toBe(control);
|
||||
expect(form.$dirty).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should use the correct parent when renaming and removing dynamically added controls', function() {
|
||||
scope.controlName = 'childControl';
|
||||
scope.hasChildControl = true;
|
||||
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<div ng-if="hasChildControl">' +
|
||||
'<input name="{{controlName}}" ng-maxlength="1" ng-model="value"/>' +
|
||||
'</div>' +
|
||||
'</form>' +
|
||||
'<form name="otherForm"></form>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
var form = scope.myForm;
|
||||
var otherForm = scope.otherForm;
|
||||
var childControl = form.childControl;
|
||||
|
||||
// remove child form and add it to another form
|
||||
form.$removeControl(childControl);
|
||||
otherForm.$addControl(childControl);
|
||||
|
||||
expect(form.childControl).toBeUndefined();
|
||||
expect(otherForm.childControl).toBe(childControl);
|
||||
|
||||
// rename the childControl
|
||||
scope.controlName = 'childControlMoved';
|
||||
scope.$digest();
|
||||
|
||||
expect(form.childControlMoved).toBeUndefined();
|
||||
expect(otherForm.childControl).toBeUndefined();
|
||||
expect(otherForm.childControlMoved).toBe(childControl);
|
||||
|
||||
scope.hasChildControl = false;
|
||||
scope.$digest();
|
||||
|
||||
expect(form.childControlMoved).toBeUndefined();
|
||||
expect(otherForm.childControlMoved).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should remove scope reference when form with no parent form is removed from the DOM', function() {
|
||||
var formController;
|
||||
scope.ctrl = {};
|
||||
@@ -547,35 +660,153 @@ describe('form', function() {
|
||||
expect(doc.find('div').hasClass('ng-pending')).toBe(false);
|
||||
});
|
||||
|
||||
it('should leave the parent form invalid when deregister a removed input', function() {
|
||||
doc = jqLite(
|
||||
'<form name="parent">' +
|
||||
'<div class="ng-form" name="child">' +
|
||||
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
|
||||
'<input ng-model="modelB" name="inputB" required>' +
|
||||
'</div>' +
|
||||
'</form>');
|
||||
$compile(doc)(scope);
|
||||
scope.inputPresent = true;
|
||||
scope.$apply();
|
||||
|
||||
var parent = scope.parent,
|
||||
child = scope.child,
|
||||
inputA = child.inputA,
|
||||
inputB = child.inputB;
|
||||
it('should leave the parent form invalid when deregister a removed input', function() {
|
||||
doc = jqLite(
|
||||
'<form name="parent">' +
|
||||
'<div class="ng-form" name="child">' +
|
||||
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
|
||||
'<input ng-model="modelB" name="inputB" required>' +
|
||||
'</div>' +
|
||||
'</form>');
|
||||
$compile(doc)(scope);
|
||||
scope.inputPresent = true;
|
||||
scope.$apply();
|
||||
|
||||
expect(parent).toBeDefined();
|
||||
expect(child).toBeDefined();
|
||||
expect(parent.$error.required).toEqual([child]);
|
||||
expect(child.$error.required).toEqual([inputB, inputA]);
|
||||
var parent = scope.parent,
|
||||
child = scope.child,
|
||||
inputA = child.inputA,
|
||||
inputB = child.inputB;
|
||||
|
||||
//remove child input
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
expect(parent).toBeDefined();
|
||||
expect(child).toBeDefined();
|
||||
expect(parent.$error.required).toEqual([child]);
|
||||
expect(child.$error.required).toEqual([inputB, inputA]);
|
||||
|
||||
//remove child input
|
||||
scope.inputPresent = false;
|
||||
scope.$apply();
|
||||
|
||||
expect(parent.$error.required).toEqual([child]);
|
||||
expect(child.$error.required).toEqual([inputB]);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore changes in manually removed child forms', function() {
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<ng-form name="childform">' +
|
||||
'<input name="childformcontrol" ng-maxlength="1" ng-model="value"/>' +
|
||||
'</ng-form>' +
|
||||
'</form>')(scope);
|
||||
|
||||
var form = scope.myForm;
|
||||
var childformController = doc.find('ng-form').eq(0).controller('form');
|
||||
|
||||
var input = doc.find('input').eq(0);
|
||||
var inputController = input.controller('ngModel');
|
||||
|
||||
changeInputValue(input, 'ab');
|
||||
scope.$apply();
|
||||
|
||||
expect(form.$dirty).toBe(true);
|
||||
expect(form.$error.maxlength).toBeTruthy();
|
||||
expect(form.$error.maxlength[0].$name).toBe('childform');
|
||||
|
||||
inputController.$setPristine();
|
||||
expect(form.$dirty).toBe(true);
|
||||
|
||||
form.$setPristine();
|
||||
|
||||
// remove child form
|
||||
form.$removeControl(childformController);
|
||||
expect(form.childform).toBeUndefined();
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
|
||||
changeInputValue(input, 'abc');
|
||||
scope.$apply();
|
||||
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
expect(form.$dirty).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should react to changes in manually added child forms', function() {
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<ng-form name="childForm">' +
|
||||
'<input name="childformcontrol" ng-maxlength="1" ng-model="value" />' +
|
||||
'</ng-form>' +
|
||||
'</form>')(scope);
|
||||
|
||||
var form = scope.myForm;
|
||||
var childFormController = doc.find('ng-form').eq(0).controller('form');
|
||||
|
||||
var input = doc.find('input').eq(0);
|
||||
|
||||
// remove child form so we can add it manually
|
||||
form.$removeControl(childFormController);
|
||||
changeInputValue(input, 'ab');
|
||||
|
||||
expect(form.childForm).toBeUndefined();
|
||||
expect(form.$dirty).toBe(false);
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
|
||||
// re-add the child form; its current validation state is not propagated
|
||||
form.$addControl(childFormController);
|
||||
expect(form.childForm).toBe(childFormController);
|
||||
expect(form.$error.maxlength).toBeFalsy();
|
||||
expect(form.$dirty).toBe(false);
|
||||
|
||||
// Only when the input inside the child form changes, the validation state is propagated
|
||||
changeInputValue(input, 'abc');
|
||||
expect(form.$error.maxlength[0]).toBe(childFormController);
|
||||
expect(form.$dirty).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should use the correct parent when renaming and removing dynamically added forms', function() {
|
||||
scope.formName = 'childForm';
|
||||
scope.hasChildForm = true;
|
||||
|
||||
doc = $compile(
|
||||
'<form name="myForm">' +
|
||||
'<div ng-if="hasChildForm">' +
|
||||
'<ng-form name="{{formName}}">' +
|
||||
'<input name="childformcontrol" ng-maxlength="1" ng-model="value"/>' +
|
||||
'</ng-form>' +
|
||||
'</div>' +
|
||||
'</form>' +
|
||||
'<form name="otherForm"></form>')(scope);
|
||||
|
||||
scope.$digest();
|
||||
|
||||
var form = scope.myForm;
|
||||
var otherForm = scope.otherForm;
|
||||
var childForm = form.childForm;
|
||||
|
||||
// remove child form and add it to another form
|
||||
form.$removeControl(childForm);
|
||||
otherForm.$addControl(childForm);
|
||||
|
||||
expect(form.childForm).toBeUndefined();
|
||||
expect(otherForm.childForm).toBe(childForm);
|
||||
|
||||
// rename the childForm
|
||||
scope.formName = 'childFormMoved';
|
||||
scope.$digest();
|
||||
|
||||
expect(form.childFormMoved).toBeUndefined();
|
||||
expect(otherForm.childForm).toBeUndefined();
|
||||
expect(otherForm.childFormMoved).toBe(childForm);
|
||||
|
||||
scope.hasChildForm = false;
|
||||
scope.$digest();
|
||||
|
||||
expect(form.childFormMoved).toBeUndefined();
|
||||
expect(otherForm.childFormMoved).toBeUndefined();
|
||||
});
|
||||
|
||||
expect(parent.$error.required).toEqual([child]);
|
||||
expect(child.$error.required).toEqual([inputB]);
|
||||
});
|
||||
|
||||
it('should chain nested forms in repeater', function() {
|
||||
doc = jqLite(
|
||||
|
||||
@@ -688,6 +688,14 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.min).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if min is empty', function() {
|
||||
$rootScope.minVal = undefined;
|
||||
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function() {
|
||||
@@ -722,6 +730,14 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.max).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if max is empty', function() {
|
||||
$rootScope.maxVal = undefined;
|
||||
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -886,6 +902,14 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.min).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if min is empty', function() {
|
||||
$rootScope.minVal = undefined;
|
||||
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function() {
|
||||
@@ -921,6 +945,14 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.max).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if max is empty', function() {
|
||||
$rootScope.maxVal = undefined;
|
||||
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1119,6 +1151,14 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.min).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if min is empty', function() {
|
||||
$rootScope.minVal = undefined;
|
||||
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function() {
|
||||
@@ -1153,6 +1193,14 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.max).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if max is empty', function() {
|
||||
$rootScope.maxVal = undefined;
|
||||
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1428,12 +1476,21 @@ describe('input', function() {
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.alias.$error.min).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if min is empty', function() {
|
||||
$rootScope.minVal = undefined;
|
||||
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function() {
|
||||
var inputElm;
|
||||
beforeEach(function() {
|
||||
inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" max="22:30:00" />');
|
||||
$rootScope.maxVal = '22:30:00';
|
||||
inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" max="{{ maxVal }}" />');
|
||||
});
|
||||
|
||||
it('should invalidate', function() {
|
||||
@@ -1449,11 +1506,19 @@ describe('input', function() {
|
||||
expect(+$rootScope.value).toBe(+new Date(1970, 0, 1, 5, 30, 0));
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should validate if max is empty', function() {
|
||||
$rootScope.maxVal = undefined;
|
||||
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should validate even if max value changes on-the-fly', function() {
|
||||
$rootScope.max = '4:02:00';
|
||||
$rootScope.max = '04:02:00';
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" max="{{max}}" />');
|
||||
|
||||
helper.changeInputValueTo('05:34:00');
|
||||
@@ -1481,7 +1546,7 @@ describe('input', function() {
|
||||
|
||||
|
||||
it('should validate even if ng-max value changes on-the-fly', function() {
|
||||
$rootScope.max = '4:02:00';
|
||||
$rootScope.max = '04:02:00';
|
||||
var inputElm = helper.compileInput('<input type="time" ng-model="value" name="alias" ng-max="max" />');
|
||||
|
||||
helper.changeInputValueTo('05:34:00');
|
||||
@@ -1706,6 +1771,16 @@ describe('input', function() {
|
||||
|
||||
expect($rootScope.form.myControl.$error.min).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if min is empty', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="date" name="alias" ng-model="value" min />');
|
||||
|
||||
$rootScope.value = new Date(-9999, 0, 1, 0, 0, 0);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.min).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('max', function() {
|
||||
@@ -1735,6 +1810,16 @@ describe('input', function() {
|
||||
|
||||
expect($rootScope.form.myControl.$error.max).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should validate if max is empty', function() {
|
||||
var inputElm = helper.compileInput(
|
||||
'<input type="date" name="alias" ng-model="value" max />');
|
||||
|
||||
$rootScope.value = new Date(9999, 11, 31, 23, 59, 59);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.form.alias.$error.max).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2598,14 +2683,17 @@ describe('input', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should set the ngTrueValue when required directive is present', function() {
|
||||
var inputElm = helper.compileInput('<input type="checkbox" ng-model="value" required ng-true-value="\'yes\'" />');
|
||||
it('should pass validation for "required" when trueValue is a string', function() {
|
||||
var inputElm = helper.compileInput('<input type="checkbox" required name="cb"' +
|
||||
'ng-model="value" ng-true-value="\'yes\'" />');
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.cb.$error.required).toBe(true);
|
||||
|
||||
browserTrigger(inputElm, 'click');
|
||||
expect(inputElm[0].checked).toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.cb.$error.required).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -468,7 +468,7 @@ describe('ngClass animations', function() {
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $$body, $$rAF) {
|
||||
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $$body) {
|
||||
$animate.enabled(true);
|
||||
|
||||
$rootScope.val = 'crazy';
|
||||
@@ -488,7 +488,7 @@ describe('ngClass animations', function() {
|
||||
expect(enterComplete).toBe(false);
|
||||
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.hasClass('crazy')).toBe(true);
|
||||
|
||||
@@ -428,9 +428,11 @@ describe('ngInclude', function() {
|
||||
});
|
||||
|
||||
expect(autoScrollSpy).not.toHaveBeenCalled();
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
expect(autoScrollSpy).toHaveBeenCalledOnce();
|
||||
}));
|
||||
|
||||
@@ -446,7 +448,6 @@ describe('ngInclude', function() {
|
||||
});
|
||||
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.tpl = 'another.html';
|
||||
@@ -455,7 +456,6 @@ describe('ngInclude', function() {
|
||||
|
||||
expect($animate.queue.shift().event).toBe('leave');
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.tpl = 'template.html';
|
||||
@@ -464,7 +464,9 @@ describe('ngInclude', function() {
|
||||
|
||||
expect($animate.queue.shift().event).toBe('leave');
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(autoScrollSpy).toHaveBeenCalled();
|
||||
expect(autoScrollSpy.callCount).toBe(3);
|
||||
@@ -480,7 +482,6 @@ describe('ngInclude', function() {
|
||||
});
|
||||
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
expect(autoScrollSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
@@ -496,7 +497,6 @@ describe('ngInclude', function() {
|
||||
});
|
||||
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.tpl = 'template.html';
|
||||
@@ -518,7 +518,9 @@ describe('ngInclude', function() {
|
||||
|
||||
$rootScope.$apply("tpl = 'template.html'");
|
||||
expect($animate.queue.shift().event).toBe('enter');
|
||||
$animate.triggerCallbacks();
|
||||
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(autoScrollSpy).toHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ describe('ngModel', function() {
|
||||
};
|
||||
|
||||
element = jqLite('<form><input></form>');
|
||||
element.data('$formController', parentFormCtrl);
|
||||
|
||||
scope = $rootScope;
|
||||
ngModelAccessor = jasmine.createSpy('ngModel accessor');
|
||||
@@ -28,6 +27,9 @@ describe('ngModel', function() {
|
||||
$element: element.find('input'),
|
||||
$attrs: attrs
|
||||
});
|
||||
|
||||
//Assign the mocked parentFormCtrl to the model controller
|
||||
ctrl.$$parentForm = parentFormCtrl;
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -2013,7 +2013,7 @@ describe('ngOptions', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support biding via ngBind attribute', function() {
|
||||
it('should support binding via ngBind attribute', function() {
|
||||
var option;
|
||||
createSingleSelect('<option value="" ng-bind="blankVal"></option>');
|
||||
|
||||
@@ -2029,6 +2029,20 @@ describe('ngOptions', function() {
|
||||
expect(option.text()).toBe('is blank');
|
||||
});
|
||||
|
||||
it('should support option without a value attribute', function() {
|
||||
createSingleSelect('<option>--select--</option>');
|
||||
scope.$apply(function() {
|
||||
scope.values = [{name: 'A'}, {name: 'B'}, {name: 'C'}];
|
||||
});
|
||||
|
||||
var options = element.find('option');
|
||||
|
||||
expect(options.eq(0)).toEqualUnknownOption();
|
||||
expect(options.eq(1)).toEqualOption(scope.values[0], 'A');
|
||||
expect(options.eq(2)).toEqualOption(scope.values[1], 'B');
|
||||
expect(options.eq(3)).toEqualOption(scope.values[2], 'C');
|
||||
});
|
||||
|
||||
|
||||
it('should be rendered with the attributes preserved', function() {
|
||||
var option;
|
||||
|
||||
@@ -166,6 +166,26 @@ describe('ngRepeat', function() {
|
||||
expect(element.text()).toEqual('age:20|wealth:20|prodname:Bingo|dogname:Bingo|codename:20|');
|
||||
});
|
||||
|
||||
|
||||
it('should iterate over on object created using `Object.create(null)`', function() {
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
'<li ng-repeat="(key, value) in items">{{key}}:{{value}}|</li>' +
|
||||
'</ul>')(scope);
|
||||
|
||||
var items = Object.create(null);
|
||||
items.misko = 'swe';
|
||||
items.shyam = 'set';
|
||||
|
||||
scope.items = items;
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('misko:swe|shyam:set|');
|
||||
|
||||
delete items.shyam;
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('misko:swe|');
|
||||
});
|
||||
|
||||
describe('track by', function() {
|
||||
it('should track using expression function', function() {
|
||||
element = $compile(
|
||||
@@ -1462,7 +1482,7 @@ describe('ngRepeat animations', function() {
|
||||
}));
|
||||
|
||||
it('should not change the position of the block that is being animated away via a leave animation',
|
||||
inject(function($compile, $rootScope, $animate, $document, $window, $sniffer, $timeout, $$rAF) {
|
||||
inject(function($compile, $rootScope, $animate, $document, $window, $sniffer, $timeout) {
|
||||
if (!$sniffer.transitions) return;
|
||||
|
||||
var item;
|
||||
@@ -1487,7 +1507,7 @@ describe('ngRepeat animations', function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(element.text()).toBe('123'); // the original order should be preserved
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$timeout.flush(1500); // 1s * 1.5 closing buffer
|
||||
expect(element.text()).toBe('13');
|
||||
} finally {
|
||||
|
||||
@@ -318,6 +318,26 @@ describe('select', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support option without a value attribute', function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option>--select--</option>' +
|
||||
'<option value="x">robot x</option>' +
|
||||
'<option value="y">robot y</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect(["? undefined:undefined ?"], "--select--", 'x', 'y');
|
||||
});
|
||||
|
||||
|
||||
it('should support option without a value with other HTML attributes', function() {
|
||||
compile('<select ng-model="robot">' +
|
||||
'<option data-foo="bar">--select--</option>' +
|
||||
'<option value="x">robot x</option>' +
|
||||
'<option value="y">robot y</option>' +
|
||||
'</select>');
|
||||
expect(element).toEqualSelect(["? undefined:undefined ?"], "--select--", 'x', 'y');
|
||||
});
|
||||
|
||||
|
||||
describe('interactions with repeated options', function() {
|
||||
|
||||
it('should select empty option when model is undefined', function() {
|
||||
@@ -964,22 +984,123 @@ describe('select', function() {
|
||||
|
||||
describe('option', function() {
|
||||
|
||||
it('should populate value attribute on OPTION', function() {
|
||||
it('should populate a missing value attribute with the option text', function() {
|
||||
compile('<select ng-model="x"><option selected>abc</option></select>');
|
||||
expect(element).toEqualSelect([unknownValue(undefined)], 'abc');
|
||||
});
|
||||
|
||||
it('should ignore value if already exists', function() {
|
||||
|
||||
it('should ignore the option text if the value attribute exists', function() {
|
||||
compile('<select ng-model="x"><option value="abc">xyz</option></select>');
|
||||
expect(element).toEqualSelect([unknownValue(undefined)], 'abc');
|
||||
});
|
||||
|
||||
|
||||
it('should set value even if self closing HTML', function() {
|
||||
scope.x = 'hello';
|
||||
compile('<select ng-model="x"><option>hello</select>');
|
||||
expect(element).toEqualSelect(['hello']);
|
||||
});
|
||||
|
||||
|
||||
it('should add options with interpolated value attributes', function() {
|
||||
scope.option1 = 'option1';
|
||||
scope.option2 = 'option2';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option value="{{option1}}">Option 1</option>' +
|
||||
'<option value="{{option2}}">Option 2</option>' +
|
||||
'</select>');
|
||||
|
||||
scope.$digest();
|
||||
expect(scope.selected).toBeUndefined();
|
||||
|
||||
browserTrigger(element.find('option').eq(0));
|
||||
expect(scope.selected).toBe('option1');
|
||||
|
||||
scope.selected = 'option2';
|
||||
scope.$digest();
|
||||
expect(element.find('option').eq(1).prop('selected')).toBe(true);
|
||||
expect(element.find('option').eq(1).text()).toBe('Option 2');
|
||||
});
|
||||
|
||||
|
||||
it('should update the option when the interpolated value attribute changes', function() {
|
||||
scope.option1 = 'option1';
|
||||
scope.option2 = '';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option value="{{option1}}">Option 1</option>' +
|
||||
'<option value="{{option2}}">Option 2</option>' +
|
||||
'</select>');
|
||||
|
||||
var selectCtrl = element.controller('select');
|
||||
spyOn(selectCtrl, 'removeOption').andCallThrough();
|
||||
|
||||
scope.$digest();
|
||||
expect(scope.selected).toBeUndefined();
|
||||
expect(selectCtrl.removeOption).not.toHaveBeenCalled();
|
||||
|
||||
//Change value of option2
|
||||
scope.option2 = 'option2Changed';
|
||||
scope.selected = 'option2Changed';
|
||||
scope.$digest();
|
||||
|
||||
expect(selectCtrl.removeOption).toHaveBeenCalledWith('');
|
||||
expect(element.find('option').eq(1).prop('selected')).toBe(true);
|
||||
expect(element.find('option').eq(1).text()).toBe('Option 2');
|
||||
});
|
||||
|
||||
|
||||
it('should add options with interpolated text', function() {
|
||||
scope.option1 = 'Option 1';
|
||||
scope.option2 = 'Option 2';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option>{{option1}}</option>' +
|
||||
'<option>{{option2}}</option>' +
|
||||
'</select>');
|
||||
|
||||
scope.$digest();
|
||||
expect(scope.selected).toBeUndefined();
|
||||
|
||||
browserTrigger(element.find('option').eq(0));
|
||||
expect(scope.selected).toBe('Option 1');
|
||||
|
||||
scope.selected = 'Option 2';
|
||||
scope.$digest();
|
||||
expect(element.find('option').eq(1).prop('selected')).toBe(true);
|
||||
expect(element.find('option').eq(1).text()).toBe('Option 2');
|
||||
});
|
||||
|
||||
|
||||
it('should update options when their interpolated text changes', function() {
|
||||
scope.option1 = 'Option 1';
|
||||
scope.option2 = '';
|
||||
|
||||
compile('<select ng-model="selected">' +
|
||||
'<option>{{option1}}</option>' +
|
||||
'<option>{{option2}}</option>' +
|
||||
'</select>');
|
||||
|
||||
var selectCtrl = element.controller('select');
|
||||
spyOn(selectCtrl, 'removeOption').andCallThrough();
|
||||
|
||||
scope.$digest();
|
||||
expect(scope.selected).toBeUndefined();
|
||||
expect(selectCtrl.removeOption).not.toHaveBeenCalled();
|
||||
|
||||
//Change value of option2
|
||||
scope.option2 = 'Option 2 Changed';
|
||||
scope.selected = 'Option 2 Changed';
|
||||
scope.$digest();
|
||||
|
||||
expect(selectCtrl.removeOption).toHaveBeenCalledWith('');
|
||||
expect(element.find('option').eq(1).prop('selected')).toBe(true);
|
||||
expect(element.find('option').eq(1).text()).toBe('Option 2 Changed');
|
||||
});
|
||||
|
||||
|
||||
it('should not blow up when option directive is found inside of a datalist',
|
||||
inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div>' +
|
||||
|
||||
@@ -204,6 +204,39 @@ describe('validators', function() {
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate the viewValue and not the modelValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
|
||||
ctrl.$parsers.push(function(value) {
|
||||
return (value * 10) + '';
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect($rootScope.form.test.$modelValue).toBe('12340');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate on non-input elements', inject(function($compile) {
|
||||
$rootScope.pattern = '\\d{4}';
|
||||
var elm = $compile('<span ng-model="value" pattern="\\d{4}"></span>')($rootScope);
|
||||
var elmNg = $compile('<span ng-model="value" ng-pattern="pattern"></span>')($rootScope);
|
||||
var ctrl = elm.controller('ngModel');
|
||||
var ctrlNg = elmNg.controller('ngModel');
|
||||
|
||||
expect(ctrl.$error.pattern).not.toBe(true);
|
||||
expect(ctrlNg.$error.pattern).not.toBe(true);
|
||||
|
||||
ctrl.$setViewValue('12');
|
||||
ctrlNg.$setViewValue('12');
|
||||
|
||||
expect(ctrl.$error.pattern).toBe(true);
|
||||
expect(ctrlNg.$error.pattern).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -268,6 +301,24 @@ describe('validators', function() {
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should validate on non-input elements', inject(function($compile) {
|
||||
$rootScope.min = 3;
|
||||
var elm = $compile('<span ng-model="value" minlength="{{min}}"></span>')($rootScope);
|
||||
var elmNg = $compile('<span ng-model="value" ng-minlength="min"></span>')($rootScope);
|
||||
var ctrl = elm.controller('ngModel');
|
||||
var ctrlNg = elmNg.controller('ngModel');
|
||||
|
||||
expect(ctrl.$error.minlength).not.toBe(true);
|
||||
expect(ctrlNg.$error.minlength).not.toBe(true);
|
||||
|
||||
ctrl.$setViewValue('12');
|
||||
ctrlNg.$setViewValue('12');
|
||||
|
||||
expect(ctrl.$error.minlength).toBe(true);
|
||||
expect(ctrlNg.$error.minlength).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -438,6 +489,24 @@ describe('validators', function() {
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should validate on non-input elements', inject(function($compile) {
|
||||
$rootScope.max = 3;
|
||||
var elm = $compile('<span ng-model="value" maxlength="{{max}}"></span>')($rootScope);
|
||||
var elmNg = $compile('<span ng-model="value" ng-maxlength="max"></span>')($rootScope);
|
||||
var ctrl = elm.controller('ngModel');
|
||||
var ctrlNg = elmNg.controller('ngModel');
|
||||
|
||||
expect(ctrl.$error.maxlength).not.toBe(true);
|
||||
expect(ctrlNg.$error.maxlength).not.toBe(true);
|
||||
|
||||
ctrl.$setViewValue('1234');
|
||||
ctrlNg.$setViewValue('1234');
|
||||
|
||||
expect(ctrl.$error.maxlength).toBe(true);
|
||||
expect(ctrlNg.$error.maxlength).toBe(true);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -532,6 +601,7 @@ describe('validators', function() {
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate emptiness against the viewValue', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
|
||||
|
||||
@@ -545,5 +615,23 @@ describe('validators', function() {
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should validate on non-input elements', inject(function($compile) {
|
||||
$rootScope.value = '12';
|
||||
var elm = $compile('<span ng-model="value" required></span>')($rootScope);
|
||||
var elmNg = $compile('<span ng-model="value" ng-required="true"></span>')($rootScope);
|
||||
var ctrl = elm.controller('ngModel');
|
||||
var ctrlNg = elmNg.controller('ngModel');
|
||||
|
||||
expect(ctrl.$error.required).not.toBe(true);
|
||||
expect(ctrlNg.$error.required).not.toBe(true);
|
||||
|
||||
ctrl.$setViewValue('');
|
||||
ctrlNg.$setViewValue('');
|
||||
|
||||
expect(ctrl.$error.required).toBe(true);
|
||||
expect(ctrlNg.$error.required).toBe(true);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,21 +44,23 @@ describe('$httpBackend', function() {
|
||||
});
|
||||
|
||||
it('should pass null to send if no body is set', function() {
|
||||
$backend('GET', '/some-url', null, noop);
|
||||
$backend('GET', '/some-url', undefined, noop);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
|
||||
expect(xhr.$$data).toBe(null);
|
||||
});
|
||||
|
||||
it('should pass the correct falsy value to send if falsy body is set (excluding NaN)', function() {
|
||||
var values = [false, 0, "", null, undefined];
|
||||
angular.forEach(values, function(value) {
|
||||
$backend('GET', '/some-url', value, noop);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
it('should pass the correct falsy value to send if falsy body is set (excluding undefined, NaN)',
|
||||
function() {
|
||||
var values = [false, 0, "", null];
|
||||
angular.forEach(values, function(value) {
|
||||
$backend('GET', '/some-url', value, noop);
|
||||
xhr = MockXhr.$$lastInstance;
|
||||
|
||||
expect(xhr.$$data).toBe(value);
|
||||
});
|
||||
});
|
||||
expect(xhr.$$data).toBe(value);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should pass NaN to send if NaN body is set', function() {
|
||||
$backend('GET', '/some-url', NaN, noop);
|
||||
|
||||
+14
-13
@@ -764,8 +764,8 @@ describe('$http', function() {
|
||||
$httpBackend.expect('POST', '/url', 'messageBody', function(headers) {
|
||||
return headers['accept'] == 'Rewritten' &&
|
||||
headers['content-type'] == 'Content-Type Rewritten' &&
|
||||
headers['Accept'] === undefined &&
|
||||
headers['Content-Type'] === undefined;
|
||||
isUndefined(headers['Accept']) &&
|
||||
isUndefined(headers['Content-Type']);
|
||||
}).respond('');
|
||||
|
||||
$http({url: '/url', method: 'POST', data: 'messageBody', headers: {
|
||||
@@ -779,7 +779,7 @@ describe('$http', function() {
|
||||
mockedCookies['XSRF-TOKEN'] = 'secret';
|
||||
$browser.url('http://host.com/base');
|
||||
$httpBackend.expect('GET', 'http://www.test.com/url', undefined, function(headers) {
|
||||
return headers['X-XSRF-TOKEN'] === undefined;
|
||||
return isUndefined(headers['X-XSRF-TOKEN']);
|
||||
}).respond('');
|
||||
|
||||
$http({url: 'http://www.test.com/url', method: 'GET', headers: {}});
|
||||
@@ -933,12 +933,13 @@ describe('$http', function() {
|
||||
it('should handle empty response header', function() {
|
||||
$httpBackend.expect('GET', '/url', undefined)
|
||||
.respond(200, '', { 'Custom-Empty-Response-Header': '', 'Constructor': '' });
|
||||
$http.get('/url').success(callback);
|
||||
$http.get('/url').then(callback);
|
||||
$httpBackend.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
expect(callback.mostRecentCall.args[2]('custom-empty-response-Header')).toBe('');
|
||||
expect(callback.mostRecentCall.args[2]('ToString')).toBe(null);
|
||||
expect(callback.mostRecentCall.args[2]('Constructor')).toBe('');
|
||||
var headers = callback.mostRecentCall.args[0].headers;
|
||||
expect(headers('custom-empty-response-Header')).toEqual('');
|
||||
expect(headers('ToString')).toBe(null);
|
||||
expect(headers('Constructor')).toBe('');
|
||||
});
|
||||
|
||||
it('should have delete()', function() {
|
||||
@@ -1731,12 +1732,12 @@ describe('$http', function() {
|
||||
|
||||
$httpBackend.expect('GET', '/some').respond(200);
|
||||
|
||||
$http({method: 'GET', url: '/some', timeout: canceler.promise}).error(
|
||||
function(data, status, headers, config) {
|
||||
expect(data).toBeUndefined();
|
||||
expect(status).toBe(0);
|
||||
expect(headers()).toEqual({});
|
||||
expect(config.url).toBe('/some');
|
||||
$http({method: 'GET', url: '/some', timeout: canceler.promise}).catch(
|
||||
function(response) {
|
||||
expect(response.data).toBeUndefined();
|
||||
expect(response.status).toBe(-1);
|
||||
expect(response.headers()).toEqual({});
|
||||
expect(response.config.url).toBe('/some');
|
||||
callback();
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('$interval', function() {
|
||||
if (fn.id === id) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (isDefined(fnIndex)) {
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
+28
-8
@@ -1679,11 +1679,10 @@ describe('parser', function() {
|
||||
forEach([true, false], function(cspEnabled) {
|
||||
describe('csp: ' + cspEnabled, function() {
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.decorator('$sniffer', function($delegate) {
|
||||
$delegate.csp = cspEnabled;
|
||||
return $delegate;
|
||||
});
|
||||
beforeEach(module(function() {
|
||||
expect(csp().noUnsafeEval === true ||
|
||||
csp().noUnsafeEval === false).toEqual(true);
|
||||
csp().noUnsafeEval = cspEnabled;
|
||||
}, provideLog));
|
||||
|
||||
beforeEach(inject(function($rootScope) {
|
||||
@@ -2120,9 +2119,8 @@ describe('parser', function() {
|
||||
|
||||
expect(scope.$eval('items[1] = "abc"')).toEqual("abc");
|
||||
expect(scope.$eval('items[1]')).toEqual("abc");
|
||||
// Dont know how to make this work....
|
||||
// expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
|
||||
// expect(scope.$eval('books[1]')).toEqual("moby");
|
||||
expect(scope.$eval('books[1] = "moby"')).toEqual("moby");
|
||||
expect(scope.$eval('books[1]')).toEqual("moby");
|
||||
});
|
||||
|
||||
it('should evaluate grouped filters', function() {
|
||||
@@ -2669,6 +2667,20 @@ describe('parser', function() {
|
||||
scope.$eval('{}["__proto__"].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}[["__proto__"]]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[["__proto__"]].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('0[["__proto__"]]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('0[["__proto__"]].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = "__pro";
|
||||
scope.b = "to__";
|
||||
expect(function() {
|
||||
@@ -2797,6 +2809,14 @@ describe('parser', function() {
|
||||
expect(scope).toEqual({a:123});
|
||||
}));
|
||||
|
||||
it('should return the assigned value', inject(function($parse) {
|
||||
var fn = $parse('a');
|
||||
var scope = {};
|
||||
expect(fn.assign(scope, 123)).toBe(123);
|
||||
var someObject = {};
|
||||
expect(fn.assign(scope, someObject)).toBe(someObject);
|
||||
}));
|
||||
|
||||
it('should expose working assignment function for expressions ending with brackets', inject(function($parse) {
|
||||
var fn = $parse('a.b["c"]');
|
||||
expect(fn.assign).toBeTruthy();
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ describe('q', function() {
|
||||
log.push(logPrefix + '->throw(' + _argToString(returnVal) + ')');
|
||||
throw returnVal;
|
||||
} else {
|
||||
if (returnVal === undefined) {
|
||||
if (isUndefined(returnVal)) {
|
||||
log.push(logPrefix);
|
||||
} else {
|
||||
log.push(logPrefix + '->' + _argToString(returnVal));
|
||||
|
||||
@@ -31,46 +31,6 @@ describe('$$rAF', function() {
|
||||
expect(present).toBe(true);
|
||||
}));
|
||||
|
||||
it('should only consume only one RAF if multiple async functions are registered before the first frame kicks in', inject(function($$rAF) {
|
||||
if (!$$rAF.supported) return;
|
||||
|
||||
//we need to create our own injector to work around the ngMock overrides
|
||||
var rafLog = [];
|
||||
var injector = createInjector(['ng', function($provide) {
|
||||
$provide.value('$window', {
|
||||
location: window.location,
|
||||
history: window.history,
|
||||
webkitRequestAnimationFrame: function(fn) {
|
||||
rafLog.push(fn);
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
$$rAF = injector.get('$$rAF');
|
||||
|
||||
var log = [];
|
||||
function logFn() {
|
||||
log.push(log.length);
|
||||
}
|
||||
|
||||
$$rAF(logFn);
|
||||
$$rAF(logFn);
|
||||
$$rAF(logFn);
|
||||
|
||||
expect(log).toEqual([]);
|
||||
expect(rafLog.length).toBe(1);
|
||||
|
||||
rafLog[0]();
|
||||
|
||||
expect(log).toEqual([0,1,2]);
|
||||
expect(rafLog.length).toBe(1);
|
||||
|
||||
$$rAF(logFn);
|
||||
|
||||
expect(log).toEqual([0,1,2]);
|
||||
expect(rafLog.length).toBe(2);
|
||||
}));
|
||||
|
||||
describe('$timeout fallback', function() {
|
||||
it("it should use a $timeout incase native rAF isn't suppored", function() {
|
||||
var timeoutSpy = jasmine.createSpy('callback');
|
||||
|
||||
@@ -822,6 +822,7 @@ describe('Scope', function() {
|
||||
expect(log.empty()).toEqual([{newVal: {b: {}, c: 'B'}, oldVal: {a: [], b: {}, c: 'B'}}]);
|
||||
});
|
||||
|
||||
|
||||
it('should not infinitely digest when current value is NaN', function() {
|
||||
$rootScope.obj = {a: NaN};
|
||||
expect(function() {
|
||||
@@ -829,6 +830,18 @@ describe('Scope', function() {
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
|
||||
it('should handle objects created using `Object.create(null)`', function() {
|
||||
$rootScope.obj = Object.create(null);
|
||||
$rootScope.obj.a = 'a';
|
||||
$rootScope.obj.b = 'b';
|
||||
$rootScope.$digest();
|
||||
expect(log.empty()[0].newVal).toEqual({a: 'a', b: 'b'});
|
||||
|
||||
delete $rootScope.obj.b;
|
||||
$rootScope.$digest();
|
||||
expect(log.empty()[0].newVal).toEqual({a: 'a'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-2
@@ -282,10 +282,10 @@ describe('SCE', function() {
|
||||
function runTest(cfg, testFn) {
|
||||
return function() {
|
||||
module(function($sceDelegateProvider) {
|
||||
if (cfg.whiteList !== undefined) {
|
||||
if (isDefined(cfg.whiteList)) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist(cfg.whiteList);
|
||||
}
|
||||
if (cfg.blackList !== undefined) {
|
||||
if (isDefined(cfg.blackList)) {
|
||||
$sceDelegateProvider.resourceUrlBlacklist(cfg.blackList);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
function int(x) {
|
||||
return parseInt(x, 10);
|
||||
@@ -104,7 +105,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
expect(capturedAnimation[1].applyClassesEarly).toBeFalsy();
|
||||
|
||||
driver({ element: element, structural: true });
|
||||
expect(capturedAnimation[1].applyClassesEarly).toBeFalsy();
|
||||
expect(capturedAnimation[1].applyClassesEarly).toBeTruthy();
|
||||
}));
|
||||
|
||||
it("should only set the event value if the animation is structural", inject(function() {
|
||||
@@ -414,7 +415,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should then do an addClass('ng-anchor-in') animation on the cloned anchor and remove the old class",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -434,7 +435,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}).start();
|
||||
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var anchorDetails = captureLog.pop().args[1];
|
||||
expect(anchorDetails.removeClass.trim()).toBe('ng-anchor-out');
|
||||
@@ -464,7 +464,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement, $animate) {
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
from.append(fromAnchor);
|
||||
var toAnchor = jqLite('<div></div>');
|
||||
@@ -488,7 +488,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
expect(animationStarted).toBe(expectedClass);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(complete).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -552,7 +552,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
expect(int(fromStyles.left)).toBeGreaterThan(149);
|
||||
}));
|
||||
|
||||
it("should append a `px` value for all seeded animation styles", inject(function($rootElement, $$rAF) {
|
||||
it("should append a `px` value for all seeded animation styles", inject(function($rootElement) {
|
||||
ss.addRule('.starting-element', 'width:10px; height:20px; display:inline-block;');
|
||||
|
||||
var fromAnchor = jqLite('<div class="starting-element"' +
|
||||
@@ -582,7 +582,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
anchorAnimation.runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
anchorAnimation = captureLog.pop();
|
||||
anchorDetails = anchorAnimation.args[1];
|
||||
@@ -593,7 +592,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should then do an removeClass('out') + addClass('in') animation on the cloned anchor",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -614,7 +613,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var anchorDetails = captureLog.pop().args[1];
|
||||
expect(anchorDetails.removeClass).toMatch(/\bout\b/);
|
||||
@@ -623,7 +621,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should add the `ng-anchor` class to the cloned anchor element",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -647,7 +645,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should add and remove the `ng-animate-shim` class on the in anchor element during the animation",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -670,14 +668,13 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
captureLog.pop().runner.end();
|
||||
|
||||
expect(fromAnchor).not.toHaveClass('ng-animate-shim');
|
||||
}));
|
||||
|
||||
it("should add and remove the `ng-animate-shim` class on the out anchor element during the animation",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -700,7 +697,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
expect(toAnchor).toHaveClass('ng-animate-shim');
|
||||
captureLog.pop().runner.end();
|
||||
@@ -709,7 +705,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should create the cloned anchor with all of the classes from the from anchor element",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div class="yes no maybe"></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -733,7 +729,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should remove the classes of the starting anchor from the cloned anchor node during the in animation and also add the classes of the destination anchor within the same animation",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div class="yes no maybe"></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -754,7 +750,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var anchorDetails = captureLog.pop().args[1];
|
||||
var removedClasses = anchorDetails.removeClass.split(' ');
|
||||
@@ -765,7 +760,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should not attempt to add/remove any classes that contain a `ng-` prefix",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div class="ng-yes ng-no sure"></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -786,7 +781,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var inAnimation = captureLog.pop();
|
||||
var details = inAnimation.args[1];
|
||||
@@ -802,7 +796,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should not remove any shared CSS classes between the starting and destination anchor element during the in animation",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div class="blue green red"></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -823,7 +817,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
|
||||
// the out animation goes first
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var inAnimation = captureLog.pop();
|
||||
var clonedAnchor = inAnimation.element;
|
||||
@@ -851,7 +844,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should continue the anchor animation by seeding the to styles based on where the final anchor element will be positioned",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
ss.addRule('.ending-element', 'width:9999px; height:6666px; display:inline-block;');
|
||||
|
||||
var fromAnchor = jqLite('<div></div>');
|
||||
@@ -874,7 +867,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}).start();
|
||||
|
||||
captureLog.pop().runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
var anchorAnimation = captureLog.pop();
|
||||
var anchorElement = anchorAnimation.element;
|
||||
@@ -889,7 +881,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should remove the cloned anchor node from the DOM once the 'in' animation is complete",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
var fromAnchor = jqLite('<div class="blue green red"></div>');
|
||||
from.append(fromAnchor);
|
||||
@@ -913,7 +905,6 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
var clonedAnchor = inAnimation.element;
|
||||
expect(clonedAnchor.parent().length).toBe(1);
|
||||
inAnimation.runner.end();
|
||||
$$rAF.flush();
|
||||
|
||||
// now the in animation completes
|
||||
expect(clonedAnchor.parent().length).toBe(1);
|
||||
@@ -923,7 +914,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should pass the provided domOperation into $animateCss to be run right after the element is animated if a leave animation is present",
|
||||
inject(function($rootElement, $$rAF) {
|
||||
inject(function($rootElement) {
|
||||
|
||||
toAnimation.structural = true;
|
||||
toAnimation.event = 'enter';
|
||||
@@ -949,7 +940,7 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
}));
|
||||
|
||||
it("should fire the returned runner promise when the from, to and anchor animations are all complete",
|
||||
inject(function($rootElement, $rootScope, $$rAF) {
|
||||
inject(function($rootElement, $rootScope, $animate) {
|
||||
|
||||
ss.addRule('.ending-element', 'width:9999px; height:6666px; display:inline-block;');
|
||||
|
||||
@@ -978,7 +969,8 @@ describe("ngAnimate $$animateCssDriver", function() {
|
||||
captureLog.pop().runner.end(); //to
|
||||
captureLog.pop().runner.end(); //anchor(out)
|
||||
captureLog.pop().runner.end(); //anchor(in)
|
||||
$$rAF.flush();
|
||||
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(completed).toBe(true);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
describe("ngAnimate $animateCss", function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
function assertAnimationRunning(element, not) {
|
||||
var className = element.attr('class');
|
||||
@@ -17,9 +18,10 @@ describe("ngAnimate $animateCss", function() {
|
||||
|
||||
var ss, prefix, triggerAnimationStartFrame;
|
||||
beforeEach(module(function() {
|
||||
return function($document, $window, $sniffer, $$rAF) {
|
||||
return function($document, $window, $sniffer, $$rAF, $animate) {
|
||||
prefix = '-' + $sniffer.vendorPrefix.toLowerCase() + '-';
|
||||
ss = createMockStyleSheet($document, $window);
|
||||
$animate.enabled(true);
|
||||
triggerAnimationStartFrame = function() {
|
||||
$$rAF.flush();
|
||||
};
|
||||
@@ -51,8 +53,25 @@ describe("ngAnimate $animateCss", function() {
|
||||
describe('when active', function() {
|
||||
if (!browserSupportsCssAnimations()) return;
|
||||
|
||||
it("should not attempt an animation if animations are globally disabled",
|
||||
inject(function($animateCss, $animate, $rootElement, $$body) {
|
||||
|
||||
$animate.enabled(false);
|
||||
|
||||
var animator, element = jqLite('<div></div>');
|
||||
$rootElement.append(element);
|
||||
$$body.append($rootElement);
|
||||
|
||||
animator = $animateCss(element, {
|
||||
duration: 10,
|
||||
to: { 'height': '100px' }
|
||||
});
|
||||
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should silently quit the animation and not throw when an element has no parent during preparation",
|
||||
inject(function($animateCss, $$rAF, $rootScope, $document, $rootElement) {
|
||||
inject(function($animateCss, $rootScope, $document, $rootElement) {
|
||||
|
||||
var element = jqLite('<div></div>');
|
||||
expect(function() {
|
||||
@@ -385,7 +404,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
they("should close the animation, but still accept $prop callbacks if no animation is detected",
|
||||
['done', 'then'], function(method) {
|
||||
|
||||
inject(function($animateCss, $$rAF, $rootScope) {
|
||||
inject(function($animateCss, $animate, $rootScope) {
|
||||
ss.addRule('.the-third-fake-animation', 'background:green;');
|
||||
|
||||
element.addClass('another-fake-animation');
|
||||
@@ -400,7 +419,8 @@ describe("ngAnimate $animateCss", function() {
|
||||
});
|
||||
|
||||
expect(done).toBe(false);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
if (method === 'then') {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
@@ -411,7 +431,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
they("should close the animation, but still accept recognize runner.$prop if no animation is detected",
|
||||
['done(cancel)', 'catch'], function(method) {
|
||||
|
||||
inject(function($animateCss, $$rAF, $rootScope) {
|
||||
inject(function($animateCss, $rootScope) {
|
||||
ss.addRule('.the-third-fake-animation', 'background:green;');
|
||||
|
||||
element.addClass('another-fake-animation');
|
||||
@@ -1078,7 +1098,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
}));
|
||||
|
||||
it("should still resolve the animation once expired",
|
||||
inject(function($animateCss, $$body, $rootElement, $timeout) {
|
||||
inject(function($animateCss, $$body, $rootElement, $timeout, $animate, $rootScope) {
|
||||
|
||||
ss.addRule('.ng-enter', 'transition:10s linear all;');
|
||||
|
||||
@@ -1097,11 +1117,13 @@ describe("ngAnimate $animateCss", function() {
|
||||
|
||||
triggerAnimationStartFrame();
|
||||
$timeout.flush(15000);
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
expect(passed).toBe(true);
|
||||
}));
|
||||
|
||||
it("should not resolve/reject after passing if the animation completed successfully",
|
||||
inject(function($animateCss, $$body, $rootElement, $timeout, $rootScope) {
|
||||
inject(function($animateCss, $$body, $rootElement, $timeout, $rootScope, $animate) {
|
||||
|
||||
ss.addRule('.ng-enter', 'transition:10s linear all;');
|
||||
|
||||
@@ -1125,6 +1147,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
browserTrigger(element, 'transitionend',
|
||||
{ timeStamp: Date.now() + 1000, elapsedTime: 10 });
|
||||
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(passed).toBe(true);
|
||||
@@ -1135,6 +1158,82 @@ describe("ngAnimate $animateCss", function() {
|
||||
expect(passed).toBe(true);
|
||||
expect(failed).not.toBe(true);
|
||||
}));
|
||||
|
||||
it("should close all stacked animations after the last timeout runs on the same element",
|
||||
inject(function($animateCss, $$body, $rootElement, $timeout, $animate) {
|
||||
|
||||
var now = 0;
|
||||
spyOn(Date, 'now').andCallFake(function() {
|
||||
return now;
|
||||
});
|
||||
|
||||
var cancelSpy = spyOn($timeout, 'cancel').andCallThrough();
|
||||
var doneSpy = jasmine.createSpy();
|
||||
|
||||
ss.addRule('.elm', 'transition:1s linear all;');
|
||||
ss.addRule('.elm.red', 'background:red;');
|
||||
ss.addRule('.elm.blue', 'transition:2s linear all; background:blue;');
|
||||
ss.addRule('.elm.green', 'background:green;');
|
||||
|
||||
var element = jqLite('<div class="elm"></div>');
|
||||
$rootElement.append(element);
|
||||
$$body.append($rootElement);
|
||||
|
||||
// timeout will be at 1500s
|
||||
animate(element, 'red', doneSpy);
|
||||
expect(doneSpy).not.toHaveBeenCalled();
|
||||
|
||||
fastForwardClock(500); //1000s left to go
|
||||
|
||||
// timeout will not be at 500 + 3000s = 3500s
|
||||
animate(element, 'blue', doneSpy);
|
||||
expect(doneSpy).not.toHaveBeenCalled();
|
||||
expect(cancelSpy).toHaveBeenCalled();
|
||||
|
||||
cancelSpy.reset();
|
||||
|
||||
// timeout will not be set again since the former animation is longer
|
||||
animate(element, 'green', doneSpy);
|
||||
expect(doneSpy).not.toHaveBeenCalled();
|
||||
expect(cancelSpy).not.toHaveBeenCalled();
|
||||
|
||||
// this will close the animations fully
|
||||
fastForwardClock(3500);
|
||||
$animate.flush();
|
||||
|
||||
expect(doneSpy).toHaveBeenCalled();
|
||||
expect(doneSpy.callCount).toBe(3);
|
||||
|
||||
function fastForwardClock(time) {
|
||||
now += time;
|
||||
$timeout.flush(time);
|
||||
}
|
||||
|
||||
function animate(element, klass, onDone) {
|
||||
var animator = $animateCss(element, { addClass: klass }).start();
|
||||
animator.done(onDone);
|
||||
triggerAnimationStartFrame();
|
||||
return animator;
|
||||
}
|
||||
}));
|
||||
|
||||
it("should not throw an error any pending timeout requests resolve after the element has already been removed",
|
||||
inject(function($animateCss, $$body, $rootElement, $timeout, $animate) {
|
||||
|
||||
var element = jqLite('<div></div>');
|
||||
$rootElement.append(element);
|
||||
$$body.append($rootElement);
|
||||
|
||||
ss.addRule('.red', 'transition:1s linear all;');
|
||||
|
||||
$animateCss(element, { addClass: 'red' }).start();
|
||||
triggerAnimationStartFrame();
|
||||
element.remove();
|
||||
|
||||
expect(function() {
|
||||
$timeout.flush();
|
||||
}).not.toThrow();
|
||||
}));
|
||||
});
|
||||
|
||||
describe("getComputedStyle", function() {
|
||||
@@ -1162,7 +1261,7 @@ describe("ngAnimate $animateCss", function() {
|
||||
}));
|
||||
|
||||
it("should cache frequent calls to getComputedStyle before the next animation frame kicks in",
|
||||
inject(function($animateCss, $document, $rootElement, $$rAF) {
|
||||
inject(function($animateCss, $document, $rootElement) {
|
||||
|
||||
var i, elm, animator;
|
||||
for (i = 0; i < 5; i++) {
|
||||
@@ -1277,6 +1376,25 @@ describe("ngAnimate $animateCss", function() {
|
||||
expect(element.attr('style')).not.toContain('transition');
|
||||
}));
|
||||
|
||||
it("should clear cache if no animation so follow-up animation on the same element will not be from cache",
|
||||
inject(function($animateCss, $rootElement, $$body, $$rAF) {
|
||||
var element = jqLite('<div class="rclass"></div>');
|
||||
var options = {
|
||||
event: 'enter',
|
||||
structural: true
|
||||
};
|
||||
$rootElement.append(element);
|
||||
$$body.append($rootElement);
|
||||
var animator = $animateCss(element, options);
|
||||
expect(animator.$$willAnimate).toBeFalsy();
|
||||
|
||||
$$rAF.flush();
|
||||
ss.addRule('.ng-enter', '-webkit-animation:3.5s keyframe_animation;' +
|
||||
'animation:3.5s keyframe_animation;');
|
||||
animator = $animateCss(element, options);
|
||||
expect(animator.$$willAnimate).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should apply a custom temporary class when a non-structural animation is used',
|
||||
inject(function($animateCss, $rootElement, $$body) {
|
||||
|
||||
@@ -1671,11 +1789,13 @@ describe("ngAnimate $animateCss", function() {
|
||||
|
||||
describe("options", function() {
|
||||
var element;
|
||||
beforeEach(inject(function($rootElement, $$body) {
|
||||
$$body.append($rootElement);
|
||||
beforeEach(module(function() {
|
||||
return function($rootElement, $$body) {
|
||||
$$body.append($rootElement);
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
$rootElement.append(element);
|
||||
element = jqLite('<div></div>');
|
||||
$rootElement.append(element);
|
||||
};
|
||||
}));
|
||||
|
||||
describe("[$$skipPreparationClasses]", function() {
|
||||
@@ -1862,7 +1982,6 @@ describe("ngAnimate $animateCss", function() {
|
||||
animator.start();
|
||||
triggerAnimationStartFrame();
|
||||
|
||||
|
||||
var prop = element.css('transition-delay');
|
||||
expect(prop).toEqual('500s');
|
||||
}));
|
||||
@@ -1978,6 +2097,53 @@ describe("ngAnimate $animateCss", function() {
|
||||
expect(element.css('transition-delay')).toEqual('10s');
|
||||
}));
|
||||
|
||||
it("should apply the keyframe and transition duration value before the CSS classes are applied", function() {
|
||||
var classSpy = jasmine.createSpy();
|
||||
module(function($provide) {
|
||||
$provide.value('$$jqLite', {
|
||||
addClass: function() {
|
||||
classSpy();
|
||||
},
|
||||
removeClass: function() {
|
||||
classSpy();
|
||||
}
|
||||
});
|
||||
});
|
||||
inject(function($animateCss, $rootElement) {
|
||||
element.addClass('element');
|
||||
ss.addRule('.element', '-webkit-animation:3s keyframe_animation;' +
|
||||
'animation:3s keyframe_animation;' +
|
||||
'transition:5s linear all;');
|
||||
|
||||
var options = {
|
||||
delay: 2,
|
||||
duration: 2,
|
||||
addClass: 'superman',
|
||||
$$skipPreparationClasses: true,
|
||||
structural: true
|
||||
};
|
||||
var animator = $animateCss(element, options);
|
||||
|
||||
expect(element.attr('style') || '').not.toContain('animation-delay');
|
||||
expect(element.attr('style') || '').not.toContain('transition-delay');
|
||||
expect(classSpy).not.toHaveBeenCalled();
|
||||
|
||||
//redefine the classSpy to assert that the delay values have been
|
||||
//applied before the classes are added
|
||||
var assertionsRun = false;
|
||||
classSpy = function() {
|
||||
assertionsRun = true;
|
||||
expect(element.css(prefix + 'animation-delay')).toEqual('2s');
|
||||
expect(element.css('transition-delay')).toEqual('2s');
|
||||
expect(element).not.toHaveClass('superman');
|
||||
};
|
||||
|
||||
animator.start();
|
||||
triggerAnimationStartFrame();
|
||||
expect(assertionsRun).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should apply blocking before the animation starts, but then apply the detected delay when options.delay is true",
|
||||
inject(function($animateCss, $rootElement) {
|
||||
|
||||
@@ -1995,41 +2161,28 @@ describe("ngAnimate $animateCss", function() {
|
||||
animator.start();
|
||||
triggerAnimationStartFrame();
|
||||
|
||||
expect(element.css('transition-delay')).toEqual('1s');
|
||||
expect(element.attr('style') || '').not.toContain('transition-delay');
|
||||
}));
|
||||
|
||||
they("should consider a negative value when delay:true is used with a $prop animation", {
|
||||
'transition': function() {
|
||||
return {
|
||||
prop: 'transition-delay',
|
||||
css: 'transition:2s linear all; transition-delay: -1s'
|
||||
};
|
||||
},
|
||||
'keyframe': function(prefix) {
|
||||
return {
|
||||
prop: prefix + 'animation-delay',
|
||||
css: prefix + 'animation:2s keyframe_animation; ' + prefix + 'animation-delay: -1s;'
|
||||
};
|
||||
}
|
||||
}, function(testDetailsFactory) {
|
||||
it("should consider a negative value when delay:true is used with a keyframe animation",
|
||||
inject(function($animateCss, $rootElement) {
|
||||
var testDetails = testDetailsFactory(prefix);
|
||||
|
||||
ss.addRule('.ng-enter', testDetails.css);
|
||||
var options = {
|
||||
delay: true,
|
||||
event: 'enter',
|
||||
structural: true
|
||||
};
|
||||
ss.addRule('.ng-enter', prefix + 'animation:2s keyframe_animation; ' +
|
||||
prefix + 'animation-delay: -1s;');
|
||||
|
||||
var animator = $animateCss(element, options);
|
||||
var options = {
|
||||
delay: true,
|
||||
event: 'enter',
|
||||
structural: true
|
||||
};
|
||||
|
||||
animator.start();
|
||||
triggerAnimationStartFrame();
|
||||
var animator = $animateCss(element, options);
|
||||
|
||||
expect(element.css(testDetails.prop)).toContain('-1s');
|
||||
});
|
||||
});
|
||||
animator.start();
|
||||
triggerAnimationStartFrame();
|
||||
|
||||
expect(element.css(prefix + 'animation-delay')).toContain('-1s');
|
||||
}));
|
||||
|
||||
they("should consider a negative value when a negative option delay is provided for a $prop animation", {
|
||||
'transition': function() {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
describe("ngAnimate $$animateJsDriver", function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
it('should register the $$animateJsDriver into the list of drivers found in $animateProvider',
|
||||
module(function($animateProvider) {
|
||||
@@ -96,7 +97,7 @@ describe("ngAnimate $$animateJsDriver", function() {
|
||||
}));
|
||||
|
||||
they('should $prop both animations when $prop() is called on the runner', ['end', 'cancel'], function(method) {
|
||||
inject(function($rootScope, $$rAF) {
|
||||
inject(function($rootScope, $animate) {
|
||||
var child1 = jqLite('<div></div>');
|
||||
element.append(child1);
|
||||
var child2 = jqLite('<div></div>');
|
||||
@@ -127,7 +128,7 @@ describe("ngAnimate $$animateJsDriver", function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
runner[method]();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(animationsClosed).toBe(true);
|
||||
expect(status).toBe(method === 'end' ? true : false);
|
||||
@@ -135,7 +136,7 @@ describe("ngAnimate $$animateJsDriver", function() {
|
||||
});
|
||||
|
||||
they('should fully $prop when all inner animations are complete', ['end', 'cancel'], function(method) {
|
||||
inject(function($rootScope, $$rAF) {
|
||||
inject(function($rootScope, $animate) {
|
||||
var child1 = jqLite('<div></div>');
|
||||
element.append(child1);
|
||||
var child2 = jqLite('<div></div>');
|
||||
@@ -163,12 +164,12 @@ describe("ngAnimate $$animateJsDriver", function() {
|
||||
status = s;
|
||||
});
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
captureLog[0].runner[method]();
|
||||
expect(animationsClosed).toBe(false);
|
||||
|
||||
captureLog[1].runner[method]();
|
||||
$animate.flush();
|
||||
|
||||
expect(animationsClosed).toBe(true);
|
||||
|
||||
expect(status).toBe(method === 'end' ? true : false);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
describe("ngAnimate $$animateJs", function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
function getDoneFunction(args) {
|
||||
for (var i = 1; i < args.length; i++) {
|
||||
@@ -86,7 +87,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
$animateProvider.register('.two', makeAnimation('enter'));
|
||||
$animateProvider.register('.three', makeAnimation('enter'));
|
||||
});
|
||||
inject(function($$animateJs, $$rAF) {
|
||||
inject(function($$animateJs, $animate) {
|
||||
var element = jqLite('<div class="one two three"></div>');
|
||||
var animator = $$animateJs(element, 'enter');
|
||||
var complete = false;
|
||||
@@ -97,7 +98,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
forEach(doneCallbacks, function(cb) {
|
||||
cb();
|
||||
});
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(complete).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -206,7 +207,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($$animateJs, $$rAF) {
|
||||
inject(function($$animateJs, $animate) {
|
||||
var element = jqLite('<div class="the-end"></div>');
|
||||
var animator = $$animateJs(element, 'addClass', {
|
||||
addClass: 'red'
|
||||
@@ -221,12 +222,12 @@ describe("ngAnimate $$animateJs", function() {
|
||||
before();
|
||||
|
||||
expect(after).toBeUndefined();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(after).toBeDefined();
|
||||
after();
|
||||
|
||||
expect(endCalled).toBeUndefined();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(endCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -251,7 +252,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($$animateJs, $$rAF) {
|
||||
inject(function($$animateJs, $animate) {
|
||||
var element = jqLite('<div class="the-end"></div>');
|
||||
var animator = $$animateJs(element, 'addClass', {
|
||||
domOperation: function() {
|
||||
@@ -264,7 +265,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
});
|
||||
runner[method]();
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(
|
||||
['before addClass ' + method,
|
||||
'dom addClass',
|
||||
@@ -278,7 +279,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
return { beforeAddClass: noop };
|
||||
});
|
||||
});
|
||||
inject(function($$animateJs, $$rAF, $rootScope) {
|
||||
inject(function($$animateJs, $animate, $rootScope) {
|
||||
var element = jqLite('<div class="the-end"></div>');
|
||||
var animator = $$animateJs(element, 'addClass');
|
||||
var runner = animator.start();
|
||||
@@ -291,7 +292,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
});
|
||||
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
expect(done).toBe(true);
|
||||
expect(cancelled).toBe(false);
|
||||
@@ -304,7 +305,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
return { beforeAddClass: noop };
|
||||
});
|
||||
});
|
||||
inject(function($$animateJs, $$rAF, $rootScope) {
|
||||
inject(function($$animateJs, $animate, $rootScope) {
|
||||
var element = jqLite('<div class="the-end"></div>');
|
||||
var animator = $$animateJs(element, 'addClass');
|
||||
var runner = animator.start();
|
||||
@@ -317,7 +318,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
});
|
||||
|
||||
runner.cancel();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
expect(done).toBe(false);
|
||||
expect(cancelled).toBe(true);
|
||||
@@ -504,7 +505,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
var allEvents = ['leave'].concat(otherEvents).concat(enterMoveEvents);
|
||||
|
||||
they("$prop should asynchronously render the before$prop animation", otherEvents, function(event) {
|
||||
inject(function($$rAF) {
|
||||
inject(function($animate) {
|
||||
var beforeMethod = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
|
||||
animations[beforeMethod] = function(element, a, b, c) {
|
||||
log.push('before ' + event);
|
||||
@@ -514,14 +515,14 @@ describe("ngAnimate $$animateJs", function() {
|
||||
|
||||
runAnimation(event);
|
||||
expect(log).toEqual(['before ' + event]);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(log).toEqual(['before ' + event, 'dom ' + event]);
|
||||
});
|
||||
});
|
||||
|
||||
they("$prop should asynchronously render the $prop animation", allEvents, function(event) {
|
||||
inject(function($$rAF) {
|
||||
inject(function($animate) {
|
||||
animations[event] = function(element, a, b, c) {
|
||||
log.push('after ' + event);
|
||||
var done = getDoneFunction(arguments);
|
||||
@@ -534,11 +535,11 @@ describe("ngAnimate $$animateJs", function() {
|
||||
|
||||
if (event === 'leave') {
|
||||
expect(log).toEqual(['after leave']);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['after leave', 'dom leave', 'complete']);
|
||||
} else {
|
||||
expect(log).toEqual(['dom ' + event, 'after ' + event]);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['dom ' + event, 'after ' + event, 'complete']);
|
||||
}
|
||||
});
|
||||
@@ -547,7 +548,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
they("$prop should asynchronously render the $prop animation when a start/end animator object is returned",
|
||||
allEvents, function(event) {
|
||||
|
||||
inject(function($$rAF, $$AnimateRunner) {
|
||||
inject(function($animate, $$AnimateRunner) {
|
||||
var runner;
|
||||
animations[event] = function(element, a, b, c) {
|
||||
return {
|
||||
@@ -565,12 +566,12 @@ describe("ngAnimate $$animateJs", function() {
|
||||
if (event === 'leave') {
|
||||
expect(log).toEqual(['start leave']);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['start leave', 'dom leave', 'complete']);
|
||||
} else {
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event]);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event, 'complete']);
|
||||
}
|
||||
});
|
||||
@@ -579,7 +580,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
they("$prop should asynchronously render the $prop animation when an instance of $$AnimateRunner is returned",
|
||||
allEvents, function(event) {
|
||||
|
||||
inject(function($$rAF, $$AnimateRunner) {
|
||||
inject(function($animate, $$AnimateRunner) {
|
||||
var runner;
|
||||
animations[event] = function(element, a, b, c) {
|
||||
log.push('start ' + event);
|
||||
@@ -593,19 +594,19 @@ describe("ngAnimate $$animateJs", function() {
|
||||
if (event === 'leave') {
|
||||
expect(log).toEqual(['start leave']);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['start leave', 'dom leave', 'complete']);
|
||||
} else {
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event]);
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['dom ' + event, 'start ' + event, 'complete']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
they("$prop should asynchronously reject the before animation if the callback function is called with false", otherEvents, function(event) {
|
||||
inject(function($$rAF, $rootScope) {
|
||||
inject(function($animate, $rootScope) {
|
||||
var beforeMethod = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
|
||||
animations[beforeMethod] = function(element, a, b, c) {
|
||||
log.push('before ' + event);
|
||||
@@ -624,13 +625,13 @@ describe("ngAnimate $$animateJs", function() {
|
||||
function() { log.push('fail'); });
|
||||
|
||||
expect(log).toEqual(['before ' + event]);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['before ' + event, 'dom ' + event, 'fail']);
|
||||
});
|
||||
});
|
||||
|
||||
they("$prop should asynchronously reject the after animation if the callback function is called with false", allEvents, function(event) {
|
||||
inject(function($$rAF, $rootScope) {
|
||||
inject(function($animate, $rootScope) {
|
||||
animations[event] = function(element, a, b, c) {
|
||||
log.push('after ' + event);
|
||||
var done = getDoneFunction(arguments);
|
||||
@@ -644,17 +645,17 @@ describe("ngAnimate $$animateJs", function() {
|
||||
var expectations = [];
|
||||
if (event === 'leave') {
|
||||
expect(log).toEqual(['after leave']);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['after leave', 'dom leave', 'fail']);
|
||||
} else {
|
||||
expect(log).toEqual(['dom ' + event, 'after ' + event]);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(log).toEqual(['dom ' + event, 'after ' + event, 'fail']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('setClass should delegate down to addClass/removeClass if not defined', inject(function($$rAF) {
|
||||
it('setClass should delegate down to addClass/removeClass if not defined', inject(function($animate) {
|
||||
animations.addClass = function(element, done) {
|
||||
log.push('addClass');
|
||||
};
|
||||
@@ -671,7 +672,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
}));
|
||||
|
||||
it('beforeSetClass should delegate down to beforeAddClass/beforeRemoveClass if not defined',
|
||||
inject(function($$rAF) {
|
||||
inject(function($animate) {
|
||||
|
||||
animations.beforeAddClass = function(element, className, done) {
|
||||
log.push('beforeAddClass');
|
||||
@@ -686,13 +687,13 @@ describe("ngAnimate $$animateJs", function() {
|
||||
expect(animations.setClass).toBeFalsy();
|
||||
|
||||
runAnimation('setClass');
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(log).toEqual(['beforeRemoveClass', 'beforeAddClass', 'dom setClass']);
|
||||
}));
|
||||
|
||||
it('leave should always ignore the `beforeLeave` animation',
|
||||
inject(function($$rAF) {
|
||||
inject(function($animate) {
|
||||
|
||||
animations.beforeLeave = function(element, done) {
|
||||
log.push('beforeLeave');
|
||||
@@ -705,13 +706,13 @@ describe("ngAnimate $$animateJs", function() {
|
||||
};
|
||||
|
||||
runAnimation('leave');
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(log).toEqual(['leave', 'dom leave']);
|
||||
}));
|
||||
|
||||
it('should allow custom events to be triggered',
|
||||
inject(function($$rAF) {
|
||||
inject(function($animate) {
|
||||
|
||||
animations.beforeFlex = function(element, done) {
|
||||
log.push('beforeFlex');
|
||||
@@ -724,7 +725,7 @@ describe("ngAnimate $$animateJs", function() {
|
||||
};
|
||||
|
||||
runAnimation('flex');
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(log).toEqual(['beforeFlex', 'dom flex', 'flex']);
|
||||
}));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
describe('$$rAFMutex', function() {
|
||||
describe('$$animateAsyncRun', function() {
|
||||
beforeEach(module('ngAnimate'));
|
||||
|
||||
it('should fire the callback only when one or more RAFs have passed',
|
||||
inject(function($$rAF, $$rAFMutex) {
|
||||
inject(function($$animateAsyncRun, $$rAF) {
|
||||
|
||||
var trigger = $$rAFMutex();
|
||||
var trigger = $$animateAsyncRun();
|
||||
var called = false;
|
||||
trigger(function() {
|
||||
called = true;
|
||||
@@ -18,9 +18,9 @@ describe('$$rAFMutex', function() {
|
||||
}));
|
||||
|
||||
it('should immediately fire the callback if a RAF has passed since construction',
|
||||
inject(function($$rAF, $$rAFMutex) {
|
||||
inject(function($$animateAsyncRun, $$rAF) {
|
||||
|
||||
var trigger = $$rAFMutex();
|
||||
var trigger = $$animateAsyncRun();
|
||||
$$rAF.flush();
|
||||
|
||||
var called = false;
|
||||
@@ -89,7 +89,7 @@ describe("$$AnimateRunner", function() {
|
||||
they("should immediately resolve each combined runner in a bottom-up order when $prop is called",
|
||||
['end', 'cancel'], function(method) {
|
||||
|
||||
inject(function($$AnimateRunner, $$rAF) {
|
||||
inject(function($$AnimateRunner) {
|
||||
var runner1 = new $$AnimateRunner();
|
||||
var runner2 = new $$AnimateRunner();
|
||||
runner1.setHost(runner2);
|
||||
@@ -206,7 +206,7 @@ describe("$$AnimateRunner", function() {
|
||||
they("should immediately resolve if and when all runners have been $prop",
|
||||
{ ended: 'end', cancelled: 'cancel' }, function(method) {
|
||||
|
||||
inject(function($$rAF, $$AnimateRunner) {
|
||||
inject(function($$AnimateRunner) {
|
||||
var runner1 = new $$AnimateRunner();
|
||||
var runner2 = new $$AnimateRunner();
|
||||
var runner3 = new $$AnimateRunner();
|
||||
@@ -227,7 +227,7 @@ describe("$$AnimateRunner", function() {
|
||||
});
|
||||
|
||||
it("should return a status of `false` if one or more runners was cancelled",
|
||||
inject(function($$rAF, $$AnimateRunner) {
|
||||
inject(function($$AnimateRunner) {
|
||||
|
||||
var runner1 = new $$AnimateRunner();
|
||||
var runner2 = new $$AnimateRunner();
|
||||
|
||||
+48
-116
@@ -3,6 +3,7 @@
|
||||
describe("animations", function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
var element, applyAnimationClasses;
|
||||
afterEach(inject(function($$jqLite) {
|
||||
@@ -410,7 +411,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should remove all element and comment nodes during leave animation',
|
||||
inject(function($compile, $rootScope, $$rAF, $$AnimateRunner) {
|
||||
inject(function($compile, $rootScope, $animate, $$AnimateRunner) {
|
||||
|
||||
element = $compile(
|
||||
'<div>' +
|
||||
@@ -433,7 +434,7 @@ describe("animations", function() {
|
||||
$rootScope.items.length = 0;
|
||||
$rootScope.$digest();
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
// we're left with a text node and a comment node
|
||||
expect(element[0].childNodes.length).toBeLessThan(3);
|
||||
@@ -491,74 +492,6 @@ describe("animations", function() {
|
||||
expect(element).not.toHaveClass('green');
|
||||
}));
|
||||
|
||||
they('should apply the $prop CSS class to the element before digest for the given event and remove when complete',
|
||||
{'ng-enter': 'enter', 'ng-leave': 'leave', 'ng-move': 'move'}, function(event) {
|
||||
|
||||
inject(function($animate, $rootScope, $document, $rootElement) {
|
||||
$animate.enabled(true);
|
||||
|
||||
var element = jqLite('<div></div>');
|
||||
var parent = jqLite('<div></div>');
|
||||
|
||||
$rootElement.append(parent);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
var runner;
|
||||
if (event === 'leave') {
|
||||
parent.append(element);
|
||||
runner = $animate[event](element);
|
||||
} else {
|
||||
runner = $animate[event](element, parent);
|
||||
}
|
||||
|
||||
var expectedClassName = 'ng-' + event;
|
||||
|
||||
expect(element).toHaveClass(expectedClassName);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element).toHaveClass(expectedClassName);
|
||||
|
||||
runner.end();
|
||||
expect(element).not.toHaveClass(expectedClassName);
|
||||
|
||||
dealoc(parent);
|
||||
});
|
||||
});
|
||||
|
||||
they('should add CSS classes with the $prop suffix when depending on the event and remove when complete',
|
||||
{'-add': 'add', '-remove': 'remove'}, function(event) {
|
||||
|
||||
inject(function($animate, $rootScope, $document, $rootElement) {
|
||||
$animate.enabled(true);
|
||||
|
||||
var element = jqLite('<div></div>');
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
var classes = 'one two';
|
||||
var expectedClasses = ['one-',event,' ','two-', event].join('');
|
||||
|
||||
var runner;
|
||||
if (event === 'add') {
|
||||
runner = $animate.addClass(element, classes);
|
||||
} else {
|
||||
element.addClass(classes);
|
||||
runner = $animate.removeClass(element, classes);
|
||||
}
|
||||
|
||||
expect(element).toHaveClass(expectedClasses);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element).toHaveClass(expectedClasses);
|
||||
|
||||
runner.end();
|
||||
expect(element).not.toHaveClass(expectedClasses);
|
||||
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
they('$prop() should operate using a native DOM element',
|
||||
['enter', 'move', 'leave', 'addClass', 'removeClass', 'setClass', 'animate'], function(event) {
|
||||
|
||||
@@ -671,7 +604,7 @@ describe("animations", function() {
|
||||
they('should not cancel a pre-digest parent class-based animation if a child $prop animation is set to run',
|
||||
['structural', 'class-based'], function(animationType) {
|
||||
|
||||
inject(function($rootScope, $animate, $$rAF) {
|
||||
inject(function($rootScope, $animate) {
|
||||
parent.append(element);
|
||||
var child = jqLite('<div></div>');
|
||||
|
||||
@@ -692,7 +625,7 @@ describe("animations", function() {
|
||||
they('should not cancel a post-digest parent class-based animation if a child $prop animation is set to run',
|
||||
['structural', 'class-based'], function(animationType) {
|
||||
|
||||
inject(function($rootScope, $animate, $$rAF) {
|
||||
inject(function($rootScope, $animate) {
|
||||
parent.append(element);
|
||||
var child = jqLite('<div></div>');
|
||||
|
||||
@@ -717,7 +650,7 @@ describe("animations", function() {
|
||||
they('should not cancel a post-digest $prop child animation if a class-based parent animation is set to run',
|
||||
['structural', 'class-based'], function(animationType) {
|
||||
|
||||
inject(function($rootScope, $animate, $$rAF) {
|
||||
inject(function($rootScope, $animate) {
|
||||
parent.append(element);
|
||||
|
||||
var child = jqLite('<div></div>');
|
||||
@@ -815,8 +748,8 @@ describe("animations", function() {
|
||||
expect(capturedAnimation).toBeFalsy();
|
||||
}));
|
||||
|
||||
it("should disable all child animations for atleast one RAF when a structural animation is issued",
|
||||
inject(function($animate, $rootScope, $compile, $$body, $rootElement, $$rAF, $$AnimateRunner) {
|
||||
it("should disable all child animations for atleast one turn when a structural animation is issued",
|
||||
inject(function($animate, $rootScope, $compile, $$body, $rootElement, $$AnimateRunner) {
|
||||
|
||||
element = $compile(
|
||||
'<div><div class="if-animation" ng-if="items.length">' +
|
||||
@@ -847,7 +780,7 @@ describe("animations", function() {
|
||||
expect(element[0].querySelectorAll('.repeat-animation').length).toBe(2);
|
||||
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
$rootScope.items = [1, 2, 3];
|
||||
$rootScope.$digest();
|
||||
@@ -946,7 +879,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should allow follow-up class-based animations to run in parallel on the same element',
|
||||
inject(function($rootScope, $animate, $$rAF) {
|
||||
inject(function($rootScope, $animate) {
|
||||
|
||||
parent.append(element);
|
||||
|
||||
@@ -957,7 +890,7 @@ describe("animations", function() {
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(capturedAnimation).toBeTruthy();
|
||||
expect(runner1done).toBeFalsy();
|
||||
|
||||
@@ -977,7 +910,7 @@ describe("animations", function() {
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(capturedAnimation).toBeTruthy();
|
||||
expect(runner2done).toBeFalsy();
|
||||
|
||||
@@ -990,7 +923,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should remove the animation block on child animations once the parent animation is complete',
|
||||
inject(function($rootScope, $rootElement, $animate, $$AnimateRunner, $$rAF) {
|
||||
inject(function($rootScope, $rootElement, $animate, $$AnimateRunner) {
|
||||
|
||||
var runner = new $$AnimateRunner();
|
||||
overriddenAnimationRunner = runner;
|
||||
@@ -1005,7 +938,7 @@ describe("animations", function() {
|
||||
expect(capturedAnimationHistory.length).toBe(1);
|
||||
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
$animate.addClass(element, 'stark');
|
||||
$rootScope.$digest();
|
||||
@@ -1036,19 +969,19 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should cancel the previous structural animation if a follow-up structural animation takes over before the postDigest',
|
||||
inject(function($animate, $$rAF) {
|
||||
inject(function($animate) {
|
||||
|
||||
var enterDone = jasmine.createSpy('enter animation done');
|
||||
$animate.enter(element, parent).done(enterDone);
|
||||
expect(enterDone).not.toHaveBeenCalled();
|
||||
|
||||
$animate.leave(element);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(enterDone).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should cancel the previously running addClass animation if a follow-up removeClass animation is using the same class value',
|
||||
inject(function($animate, $rootScope, $$rAF) {
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
parent.append(element);
|
||||
var runner = $animate.addClass(element, 'active-class');
|
||||
@@ -1057,7 +990,7 @@ describe("animations", function() {
|
||||
var doneHandler = jasmine.createSpy('addClass done');
|
||||
runner.done(doneHandler);
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(doneHandler).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1068,7 +1001,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should cancel the previously running removeClass animation if a follow-up addClass animation is using the same class value',
|
||||
inject(function($animate, $rootScope, $$rAF) {
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
element.addClass('active-class');
|
||||
parent.append(element);
|
||||
@@ -1078,7 +1011,7 @@ describe("animations", function() {
|
||||
var doneHandler = jasmine.createSpy('addClass done');
|
||||
runner.done(doneHandler);
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(doneHandler).not.toHaveBeenCalled();
|
||||
|
||||
@@ -1294,7 +1227,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should not skip or miss the animations when animations are executed sequential',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement) {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1306,8 +1239,6 @@ describe("animations", function() {
|
||||
$animate.removeClass(element, 'rclass');
|
||||
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
|
||||
expect(element).not.toHaveClass('rclass');
|
||||
}));
|
||||
});
|
||||
@@ -1532,7 +1463,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should trigger a callback for an enter animation',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
|
||||
inject(function($animate, $rootScope, $rootElement, $$body) {
|
||||
|
||||
var callbackTriggered = false;
|
||||
$animate.on('enter', $$body, function() {
|
||||
@@ -1543,13 +1474,13 @@ describe("animations", function() {
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(callbackTriggered).toBe(true);
|
||||
}));
|
||||
|
||||
it('should fire the callback with the signature of (element, phase, data)',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
|
||||
inject(function($animate, $rootScope, $rootElement, $$body) {
|
||||
|
||||
var capturedElement;
|
||||
var capturedPhase;
|
||||
@@ -1565,7 +1496,7 @@ describe("animations", function() {
|
||||
element = jqLite('<div></div>');
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(capturedElement).toBe(element);
|
||||
expect(isString(capturedPhase)).toBe(true);
|
||||
@@ -1573,7 +1504,7 @@ describe("animations", function() {
|
||||
}));
|
||||
|
||||
it('should not fire a callback if the element is outside of the given container',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement) {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
|
||||
var callbackTriggered = false;
|
||||
var innerContainer = jqLite('<div></div>');
|
||||
@@ -1588,13 +1519,13 @@ describe("animations", function() {
|
||||
element = jqLite('<div></div>');
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(callbackTriggered).toBe(false);
|
||||
}));
|
||||
|
||||
it('should fire a callback if the element is the given container',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement) {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1607,13 +1538,13 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(callbackTriggered).toBe(true);
|
||||
}));
|
||||
|
||||
it('should remove all the event-based event listeners when $animate.off(event) is called',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
|
||||
inject(function($animate, $rootScope, $rootElement, $$body) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1627,7 +1558,7 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(2);
|
||||
|
||||
@@ -1635,13 +1566,13 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(2);
|
||||
}));
|
||||
|
||||
it('should remove the container-based event listeners when $animate.off(event, container) is called',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
|
||||
inject(function($animate, $rootScope, $rootElement, $$body) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1657,7 +1588,7 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(2);
|
||||
|
||||
@@ -1665,13 +1596,13 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(3);
|
||||
}));
|
||||
|
||||
it('should remove the callback-based event listener when $animate.off(event, container, callback) is called',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement) {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1693,7 +1624,7 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(2);
|
||||
|
||||
@@ -1701,13 +1632,13 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(3);
|
||||
}));
|
||||
|
||||
it('should fire a `start` callback when the animation starts with the matching element',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
|
||||
inject(function($animate, $rootScope, $rootElement, $$body) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1720,14 +1651,14 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(capturedState).toBe('start');
|
||||
expect(capturedElement).toBe(element);
|
||||
}));
|
||||
|
||||
it('should fire a `close` callback when the animation ends with the matching element',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement, $$body) {
|
||||
inject(function($animate, $rootScope, $rootElement, $$body) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1741,14 +1672,14 @@ describe("animations", function() {
|
||||
var runner = $animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(capturedState).toBe('close');
|
||||
expect(capturedElement).toBe(element);
|
||||
}));
|
||||
|
||||
it('should remove the event listener if the element is removed',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement) {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
|
||||
@@ -1764,19 +1695,20 @@ describe("animations", function() {
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(count).toBe(1);
|
||||
element.remove();
|
||||
|
||||
$animate.addClass(element, 'viljami');
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
|
||||
$animate.flush();
|
||||
expect(count).toBe(1);
|
||||
}));
|
||||
|
||||
it('leave: should remove the element even if another animation is called after',
|
||||
inject(function($animate, $rootScope, $$rAF, $rootElement) {
|
||||
inject(function($animate, $rootScope, $rootElement) {
|
||||
|
||||
var outerContainer = jqLite('<div></div>');
|
||||
element = jqLite('<div></div>');
|
||||
@@ -1787,7 +1719,7 @@ describe("animations", function() {
|
||||
$animate.removeClass(element,'rclass');
|
||||
$rootScope.$digest();
|
||||
runner.end();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
var isElementRemoved = !outerContainer[0].contains(element[0]);
|
||||
expect(isElementRemoved).toBe(true);
|
||||
|
||||
+109
-22
@@ -3,6 +3,7 @@
|
||||
describe('$$animation', function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
var element;
|
||||
afterEach(function() {
|
||||
@@ -14,14 +15,14 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it("should not run an animation if there are no drivers",
|
||||
inject(function($$animation, $$rAF, $rootScope) {
|
||||
inject(function($$animation, $animate, $rootScope) {
|
||||
|
||||
element = jqLite('<div></div>');
|
||||
var done = false;
|
||||
$$animation(element, 'someEvent').then(function() {
|
||||
done = true;
|
||||
});
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
expect(done).toBe(true);
|
||||
}));
|
||||
@@ -33,14 +34,14 @@ describe('$$animation', function() {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
inject(function($$animation, $$rAF, $rootScope) {
|
||||
inject(function($$animation, $animate, $rootScope) {
|
||||
element = jqLite('<div></div>');
|
||||
var done = false;
|
||||
$$animation(element, 'someEvent').then(function() {
|
||||
done = true;
|
||||
});
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
expect(done).toBe(true);
|
||||
});
|
||||
@@ -195,7 +196,7 @@ describe('$$animation', function() {
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
inject(function($$animation, $rootScope, $animate) {
|
||||
var status, element = jqLite('<div></div>');
|
||||
var runner = $$animation(element, 'enter');
|
||||
runner.then(function() {
|
||||
@@ -210,7 +211,7 @@ describe('$$animation', function() {
|
||||
event === 'resolve' ? runner.end() : runner.cancel();
|
||||
|
||||
// the resolve/rejection digest
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(status).toBe(event);
|
||||
@@ -296,6 +297,90 @@ describe('$$animation', function() {
|
||||
};
|
||||
}));
|
||||
|
||||
it('should space out multiple ancestorial class-based animations with a RAF in between',
|
||||
inject(function($rootScope, $$animation, $$rAF) {
|
||||
|
||||
var parent = element;
|
||||
element = jqLite('<div></div>');
|
||||
parent.append(element);
|
||||
|
||||
var child = jqLite('<div></div>');
|
||||
element.append(child);
|
||||
|
||||
$$animation(parent, 'addClass', { addClass: 'blue' });
|
||||
$$animation(element, 'addClass', { addClass: 'red' });
|
||||
$$animation(child, 'addClass', { addClass: 'green' });
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(captureLog.length).toBe(1);
|
||||
expect(capturedAnimation.options.addClass).toBe('blue');
|
||||
|
||||
$$rAF.flush();
|
||||
expect(captureLog.length).toBe(2);
|
||||
expect(capturedAnimation.options.addClass).toBe('red');
|
||||
|
||||
$$rAF.flush();
|
||||
expect(captureLog.length).toBe(3);
|
||||
expect(capturedAnimation.options.addClass).toBe('green');
|
||||
}));
|
||||
|
||||
it('should properly cancel out pending animations that are spaced with a RAF request before the digest completes',
|
||||
inject(function($rootScope, $$animation, $$rAF) {
|
||||
|
||||
var parent = element;
|
||||
element = jqLite('<div></div>');
|
||||
parent.append(element);
|
||||
|
||||
var child = jqLite('<div></div>');
|
||||
element.append(child);
|
||||
|
||||
var r1 = $$animation(parent, 'addClass', { addClass: 'blue' });
|
||||
var r2 = $$animation(element, 'addClass', { addClass: 'red' });
|
||||
var r3 = $$animation(child, 'addClass', { addClass: 'green' });
|
||||
|
||||
r2.end();
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(captureLog.length).toBe(1);
|
||||
expect(capturedAnimation.options.addClass).toBe('blue');
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(captureLog.length).toBe(2);
|
||||
expect(capturedAnimation.options.addClass).toBe('green');
|
||||
}));
|
||||
|
||||
it('should properly cancel out pending animations that are spaced with a RAF request after the digest completes',
|
||||
inject(function($rootScope, $$animation, $$rAF) {
|
||||
|
||||
var parent = element;
|
||||
element = jqLite('<div></div>');
|
||||
parent.append(element);
|
||||
|
||||
var child = jqLite('<div></div>');
|
||||
element.append(child);
|
||||
|
||||
var r1 = $$animation(parent, 'addClass', { addClass: 'blue' });
|
||||
var r2 = $$animation(element, 'addClass', { addClass: 'red' });
|
||||
var r3 = $$animation(child, 'addClass', { addClass: 'green' });
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
r2.end();
|
||||
|
||||
expect(captureLog.length).toBe(1);
|
||||
expect(capturedAnimation.options.addClass).toBe('blue');
|
||||
|
||||
$$rAF.flush();
|
||||
expect(captureLog.length).toBe(1);
|
||||
|
||||
$$rAF.flush();
|
||||
expect(captureLog.length).toBe(2);
|
||||
expect(capturedAnimation.options.addClass).toBe('green');
|
||||
}));
|
||||
|
||||
they('should return a runner that object that contains a $prop() function',
|
||||
['end', 'cancel', 'then'], function(method) {
|
||||
inject(function($$animation) {
|
||||
@@ -306,7 +391,7 @@ describe('$$animation', function() {
|
||||
|
||||
they('should close the animation if runner.$prop() is called before the $postDigest phase kicks in',
|
||||
['end', 'cancel'], function(method) {
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
inject(function($$animation, $rootScope, $animate) {
|
||||
var status;
|
||||
var runner = $$animation(element, 'someEvent');
|
||||
runner.then(function() { status = 'end'; },
|
||||
@@ -316,7 +401,7 @@ describe('$$animation', function() {
|
||||
$rootScope.$digest();
|
||||
expect(runnerLog).toEqual([]);
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(status).toBe(method);
|
||||
});
|
||||
});
|
||||
@@ -363,11 +448,10 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it('should immediately end the animation if the element is removed from the DOM during the animation',
|
||||
inject(function($$animation, $$rAF, $rootScope) {
|
||||
inject(function($$animation, $animate, $rootScope) {
|
||||
|
||||
var runner = $$animation(element, 'someEvent');
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush(); //the animation is "animating"
|
||||
|
||||
expect(capturedAnimation).toBeTruthy();
|
||||
expect(runnerLog).toEqual([]);
|
||||
@@ -376,14 +460,13 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it('should not end the animation when the leave animation removes the element from the DOM',
|
||||
inject(function($$animation, $$rAF, $rootScope) {
|
||||
inject(function($$animation, $animate, $rootScope) {
|
||||
|
||||
var runner = $$animation(element, 'leave', {}, function() {
|
||||
element.remove();
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush(); //the animation is "animating"
|
||||
|
||||
expect(runnerLog).toEqual([]);
|
||||
capturedAnimation.options.domOperation(); //this removes the element
|
||||
@@ -392,7 +475,7 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it('should remove the $destroy event listener when the animation is closed',
|
||||
inject(function($$animation, $$rAF, $rootScope) {
|
||||
inject(function($$animation, $rootScope) {
|
||||
|
||||
var addListen = spyOn(element, 'on').andCallThrough();
|
||||
var removeListen = spyOn(element, 'off').andCallThrough();
|
||||
@@ -408,7 +491,7 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it('should always sort parent-element animations to run in order of parent-to-child DOM structure',
|
||||
inject(function($$animation, $$rAF, $rootScope) {
|
||||
inject(function($$animation, $rootScope, $animate) {
|
||||
|
||||
var child = jqLite('<div></div>');
|
||||
var grandchild = jqLite('<div></div>');
|
||||
@@ -424,6 +507,8 @@ describe('$$animation', function() {
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.flush();
|
||||
|
||||
expect(captureLog[0].element).toBe(element);
|
||||
expect(captureLog[1].element).toBe(child);
|
||||
expect(captureLog[2].element).toBe(grandchild);
|
||||
@@ -543,7 +628,7 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it("should not group animations into an anchored animation if enter/leave events are NOT used",
|
||||
inject(function($$animation, $rootScope) {
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
|
||||
fromElement.addClass('shared-class');
|
||||
fromElement.attr('ng-animate-ref', '1');
|
||||
@@ -558,6 +643,7 @@ describe('$$animation', function() {
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
expect(captureLog.length).toBe(2);
|
||||
}));
|
||||
|
||||
@@ -649,7 +735,7 @@ describe('$$animation', function() {
|
||||
});
|
||||
|
||||
it("should not end the animation when the `from` animation calls its own leave dom operation",
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
inject(function($$animation, $rootScope) {
|
||||
|
||||
fromElement.addClass('group-1');
|
||||
var elementRemoved = false;
|
||||
@@ -679,7 +765,7 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it("should not end the animation if any of the anchor elements are removed from the DOM during the animation",
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
inject(function($$animation, $rootScope) {
|
||||
|
||||
fromElement.addClass('group-1');
|
||||
var elementRemoved = false;
|
||||
@@ -702,7 +788,7 @@ describe('$$animation', function() {
|
||||
}));
|
||||
|
||||
it('should prepare a parent-element animation to run first before the anchored animation',
|
||||
inject(function($$animation, $$rAF, $rootScope, $rootElement) {
|
||||
inject(function($$animation, $rootScope, $rootElement, $animate) {
|
||||
|
||||
fromAnchors[0].attr('ng-animate-ref', 'shared');
|
||||
toAnchors[0].attr('ng-animate-ref', 'shared');
|
||||
@@ -725,6 +811,7 @@ describe('$$animation', function() {
|
||||
expect(captureLog.length).toBe(0);
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(captureLog[0].element).toBe(parent);
|
||||
expect(captureLog[1].from.element).toBe(fromElement);
|
||||
@@ -869,7 +956,7 @@ describe('$$animation', function() {
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
inject(function($$animation, $rootScope, $animate) {
|
||||
element.addClass('four');
|
||||
|
||||
var completed = false;
|
||||
@@ -882,7 +969,7 @@ describe('$$animation', function() {
|
||||
|
||||
$rootScope.$digest(); //runs the animation
|
||||
$rootScope.$digest(); //flushes the step code
|
||||
$$rAF.flush(); //runs the $$animation promise
|
||||
$animate.flush();
|
||||
$rootScope.$digest(); //the runner promise
|
||||
|
||||
expect(completed).toBe(true);
|
||||
@@ -921,7 +1008,7 @@ describe('$$animation', function() {
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($$animation, $rootScope, $$rAF) {
|
||||
inject(function($$animation, $rootScope, $animate) {
|
||||
element.addClass('four');
|
||||
|
||||
var completed = false;
|
||||
@@ -937,7 +1024,7 @@ describe('$$animation', function() {
|
||||
$rootScope.$digest(); //flushes the step code
|
||||
|
||||
runner.end();
|
||||
$$rAF.flush(); //runs the $$animation promise
|
||||
$animate.flush();
|
||||
$rootScope.$digest(); //the runner promise
|
||||
|
||||
expect(completed).toBe(true);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
describe('ngAnimate integration tests', function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
var element, html, ss;
|
||||
beforeEach(module(function() {
|
||||
@@ -30,7 +31,7 @@ describe('ngAnimate integration tests', function() {
|
||||
they('should render an $prop animation',
|
||||
['enter', 'leave', 'move', 'addClass', 'removeClass', 'setClass'], function(event) {
|
||||
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF) {
|
||||
inject(function($animate, $compile, $rootScope, $rootElement) {
|
||||
element = jqLite('<div class="animate-me"></div>');
|
||||
$compile(element)($rootScope);
|
||||
|
||||
@@ -97,10 +98,11 @@ describe('ngAnimate integration tests', function() {
|
||||
});
|
||||
|
||||
expect(element).toHaveClass(setupClass);
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(element).toHaveClass(activeClass);
|
||||
|
||||
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
|
||||
$animate.flush();
|
||||
|
||||
expect(element).not.toHaveClass(setupClass);
|
||||
expect(element).not.toHaveClass(activeClass);
|
||||
@@ -112,7 +114,7 @@ describe('ngAnimate integration tests', function() {
|
||||
});
|
||||
|
||||
it('should not throw an error if the element is orphaned before the CSS animation starts',
|
||||
inject(function($rootScope, $rootElement, $animate, $$rAF) {
|
||||
inject(function($rootScope, $rootElement, $animate) {
|
||||
|
||||
ss.addRule('.animate-me', 'transition:2s linear all;');
|
||||
|
||||
@@ -127,51 +129,19 @@ describe('ngAnimate integration tests', function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
// this will run the first class-based animation
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
element.remove();
|
||||
|
||||
expect(function() {
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
}).not.toThrow();
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
it('should always synchronously add css classes in order for child animations to animate properly',
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
|
||||
|
||||
ss.addRule('.animations-enabled .animate-me.ng-enter', 'transition:2s linear all;');
|
||||
|
||||
element = jqLite('<div ng-class="{\'animations-enabled\':exp}"></div>');
|
||||
var child = jqLite('<div ng-if="exp" class="animate-me"></div>');
|
||||
|
||||
element.append(child);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$compile(element)($rootScope);
|
||||
|
||||
$rootScope.exp = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
child = element.find('div');
|
||||
|
||||
expect(element).toHaveClass('animations-enabled');
|
||||
expect(child).toHaveClass('ng-enter');
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(child).toHaveClass('ng-enter-active');
|
||||
|
||||
browserTrigger(child, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
|
||||
|
||||
expect(child).not.toHaveClass('ng-enter-active');
|
||||
expect(child).not.toHaveClass('ng-enter');
|
||||
}));
|
||||
|
||||
it('should synchronously add/remove ng-class expressions in time for other animations to run on the same element',
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
|
||||
it('should include the added/removed classes in lieu of the enter animation',
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
|
||||
|
||||
ss.addRule('.animate-me.ng-enter.on', 'transition:2s linear all;');
|
||||
|
||||
@@ -184,7 +154,7 @@ describe('ngAnimate integration tests', function() {
|
||||
|
||||
$rootScope.exp = true;
|
||||
$rootScope.$digest();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
var child = element.find('div');
|
||||
|
||||
@@ -203,18 +173,19 @@ describe('ngAnimate integration tests', function() {
|
||||
expect(child).toHaveClass('on');
|
||||
expect(child).toHaveClass('ng-enter');
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(child).toHaveClass('ng-enter-active');
|
||||
|
||||
browserTrigger(child, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
|
||||
$animate.flush();
|
||||
|
||||
expect(child).not.toHaveClass('ng-enter-active');
|
||||
expect(child).not.toHaveClass('ng-enter');
|
||||
}));
|
||||
|
||||
it('should animate ng-class and a structural animation in parallel on the same element',
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
|
||||
|
||||
ss.addRule('.animate-me.ng-enter', 'transition:2s linear all;');
|
||||
ss.addRule('.animate-me.expand', 'transition:5s linear all; font-size:200px;');
|
||||
@@ -236,12 +207,13 @@ describe('ngAnimate integration tests', function() {
|
||||
expect(child).toHaveClass('expand-add');
|
||||
expect(child).toHaveClass('expand');
|
||||
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
|
||||
expect(child).toHaveClass('ng-enter-active');
|
||||
expect(child).toHaveClass('expand-add-active');
|
||||
|
||||
browserTrigger(child, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2 });
|
||||
$animate.flush();
|
||||
|
||||
expect(child).not.toHaveClass('ng-enter-active');
|
||||
expect(child).not.toHaveClass('ng-enter');
|
||||
@@ -249,9 +221,9 @@ describe('ngAnimate integration tests', function() {
|
||||
expect(child).not.toHaveClass('expand-add');
|
||||
}));
|
||||
|
||||
it('should issue a reflow for each element animation on all DOM levels', function() {
|
||||
it('should issue a RAF for each element animation on all DOM levels', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$rAF) {
|
||||
element = jqLite(
|
||||
'<div ng-class="{parent:exp}">' +
|
||||
'<div ng-class="{parent2:exp}">' +
|
||||
@@ -267,70 +239,84 @@ describe('ngAnimate integration tests', function() {
|
||||
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($animate.reflows).toBe(0);
|
||||
|
||||
var outer = element;
|
||||
var inner = element.find('div');
|
||||
|
||||
$rootScope.exp = true;
|
||||
$rootScope.items = [1,2,3,4,5,6,7,8,9,10];
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(outer).not.toHaveClass('parent');
|
||||
expect(inner).not.toHaveClass('parent2');
|
||||
|
||||
// 2 parents + 10 items = 12
|
||||
expect($animate.reflows).toBe(12);
|
||||
assertTotalRepeats(0);
|
||||
|
||||
$$rAF.flush();
|
||||
expect(outer).toHaveClass('parent');
|
||||
|
||||
assertTotalRepeats(0);
|
||||
|
||||
$$rAF.flush();
|
||||
expect(inner).toHaveClass('parent2');
|
||||
|
||||
assertTotalRepeats(10);
|
||||
|
||||
function assertTotalRepeats(total) {
|
||||
expect(inner[0].querySelectorAll('div.ng-enter').length).toBe(total);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should issue a reflow for each element and also its children', function() {
|
||||
it('should pack level elements into their own RAF flush', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
|
||||
ss.addRule('.inner', 'transition:2s linear all;');
|
||||
|
||||
element = jqLite(
|
||||
'<div ng-class="{one:exp}">' +
|
||||
'<div ng-if="exp"></div>' +
|
||||
'</div>' +
|
||||
'<div ng-class="{two:exp}">' +
|
||||
'<div ng-if="exp"></div>' +
|
||||
'</div>' +
|
||||
'<div ng-class="{three:exp}">' +
|
||||
'<div ng-if="false"></div>' +
|
||||
'</div>' +
|
||||
'<div ng-class="{four:exp}"></div>'
|
||||
);
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($animate.reflows).toBe(0);
|
||||
|
||||
$rootScope.exp = true;
|
||||
$rootScope.$digest();
|
||||
|
||||
// there is one element's expression in there that is false
|
||||
expect($animate.reflows).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
it('should always issue atleast one reflow incase there are no parent class-based animations', function() {
|
||||
module('ngAnimateMock');
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF, $document) {
|
||||
element = jqLite(
|
||||
'<div ng-repeat="item in items" ng-class="{someAnimation:exp}">' +
|
||||
'{{ item }}' +
|
||||
'<div>' +
|
||||
'<div class="outer" ng-class="{on:exp}">' +
|
||||
'<div class="inner" ng-if="exp"></div>' +
|
||||
'</div>' +
|
||||
'<div class="outer" ng-class="{on:exp}">' +
|
||||
'<div class="inner" ng-if="exp"></div>' +
|
||||
'</div>' +
|
||||
'<div class="outer" ng-class="{on:exp}">' +
|
||||
'<div class="inner" ng-if="exp"></div>' +
|
||||
'</div>' +
|
||||
'<div class="outer" ng-class="{on:exp}"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
$compile(element)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect($animate.reflows).toBe(0);
|
||||
|
||||
assertGroupHasClass(query('outer'), 'on', true);
|
||||
expect(query('inner').length).toBe(0);
|
||||
|
||||
$rootScope.exp = true;
|
||||
$rootScope.items = [1,2,3,4,5,6,7,8,9,10];
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($animate.reflows).toBe(10);
|
||||
assertGroupHasClass(query('outer'), 'on', true);
|
||||
assertGroupHasClass(query('inner'), 'ng-enter', true);
|
||||
|
||||
$animate.flush();
|
||||
|
||||
assertGroupHasClass(query('outer'), 'on');
|
||||
assertGroupHasClass(query('inner'), 'ng-enter');
|
||||
|
||||
function query(className) {
|
||||
return element[0].querySelectorAll('.' + className);
|
||||
}
|
||||
|
||||
function assertGroupHasClass(elms, className, not) {
|
||||
for (var i = 0; i < elms.length; i++) {
|
||||
var assert = expect(jqLite(elms[i]));
|
||||
(not ? assert.not : assert).toHaveClass(className);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -355,7 +341,7 @@ describe('ngAnimate integration tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($animate, $compile, $rootScope, $rootElement, $$rAF) {
|
||||
inject(function($animate, $compile, $rootScope, $rootElement) {
|
||||
element = jqLite('<div class="animate-me"></div>');
|
||||
$compile(element)($rootScope);
|
||||
|
||||
@@ -407,7 +393,7 @@ describe('ngAnimate integration tests', function() {
|
||||
expect(isFunction(endAnimation)).toBe(true);
|
||||
|
||||
endAnimation();
|
||||
$$rAF.flush();
|
||||
$animate.flush();
|
||||
expect(animateCompleteCallbackFired).toBe(true);
|
||||
|
||||
$rootScope.$digest();
|
||||
@@ -465,6 +451,7 @@ describe('ngAnimate integration tests', function() {
|
||||
}
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(endParentAnimationFn).toBeTruthy();
|
||||
|
||||
@@ -529,6 +516,7 @@ describe('ngAnimate integration tests', function() {
|
||||
}
|
||||
|
||||
$rootScope.$digest();
|
||||
$animate.flush();
|
||||
|
||||
expect(endParentAnimationFn).toBeTruthy();
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
'use strict';
|
||||
|
||||
describe("$$rAFScheduler", function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
|
||||
it('should accept an array of tasks and run the first task immediately',
|
||||
inject(function($$rAFScheduler) {
|
||||
|
||||
var taskSpy = jasmine.createSpy();
|
||||
var tasks = [taskSpy];
|
||||
$$rAFScheduler([tasks]);
|
||||
expect(taskSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should run tasks based on how many RAFs have run in comparison to the task index',
|
||||
inject(function($$rAFScheduler, $$rAF) {
|
||||
|
||||
var i, tasks = [];
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
tasks.push([jasmine.createSpy()]);
|
||||
}
|
||||
|
||||
$$rAFScheduler(tasks);
|
||||
|
||||
for (i = 1; i < 5; i++) {
|
||||
var taskSpy = tasks[i][0];
|
||||
expect(taskSpy).not.toHaveBeenCalled();
|
||||
$$rAF.flush();
|
||||
expect(taskSpy).toHaveBeenCalled();
|
||||
}
|
||||
}));
|
||||
|
||||
it('should space out subarrays by a RAF and run the internals in parallel',
|
||||
inject(function($$rAFScheduler, $$rAF) {
|
||||
|
||||
var spies = {
|
||||
a: jasmine.createSpy(),
|
||||
b: jasmine.createSpy(),
|
||||
c: jasmine.createSpy(),
|
||||
|
||||
x: jasmine.createSpy(),
|
||||
y: jasmine.createSpy(),
|
||||
z: jasmine.createSpy()
|
||||
};
|
||||
|
||||
var items = [[spies.a, spies.x],
|
||||
[spies.b, spies.y],
|
||||
[spies.c, spies.z]];
|
||||
|
||||
expect(spies.a).not.toHaveBeenCalled();
|
||||
expect(spies.x).not.toHaveBeenCalled();
|
||||
|
||||
$$rAFScheduler(items);
|
||||
|
||||
expect(spies.a).toHaveBeenCalled();
|
||||
expect(spies.x).toHaveBeenCalled();
|
||||
|
||||
|
||||
expect(spies.b).not.toHaveBeenCalled();
|
||||
expect(spies.y).not.toHaveBeenCalled();
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(spies.b).toHaveBeenCalled();
|
||||
expect(spies.y).toHaveBeenCalled();
|
||||
|
||||
|
||||
expect(spies.c).not.toHaveBeenCalled();
|
||||
expect(spies.z).not.toHaveBeenCalled();
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(spies.c).toHaveBeenCalled();
|
||||
expect(spies.z).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
describe('.waitUntilQuiet', function() {
|
||||
|
||||
it('should run the `last` provided function when a RAF fully passes',
|
||||
inject(function($$rAFScheduler, $$rAF) {
|
||||
|
||||
var q1 = jasmine.createSpy();
|
||||
$$rAFScheduler.waitUntilQuiet(q1);
|
||||
|
||||
expect(q1).not.toHaveBeenCalled();
|
||||
|
||||
var q2 = jasmine.createSpy();
|
||||
$$rAFScheduler.waitUntilQuiet(q2);
|
||||
|
||||
expect(q1).not.toHaveBeenCalled();
|
||||
expect(q2).not.toHaveBeenCalled();
|
||||
|
||||
var q3 = jasmine.createSpy();
|
||||
$$rAFScheduler.waitUntilQuiet(q3);
|
||||
|
||||
expect(q1).not.toHaveBeenCalled();
|
||||
expect(q2).not.toHaveBeenCalled();
|
||||
expect(q3).not.toHaveBeenCalled();
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(q1).not.toHaveBeenCalled();
|
||||
expect(q2).not.toHaveBeenCalled();
|
||||
expect(q3).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should always execute itself before the next RAF task tick occurs', function() {
|
||||
module(provideLog);
|
||||
inject(function($$rAFScheduler, $$rAF, log) {
|
||||
var quietFn = log.fn('quiet');
|
||||
var tasks = [
|
||||
[log.fn('task1')],
|
||||
[log.fn('task2')],
|
||||
[log.fn('task3')],
|
||||
[log.fn('task4')]
|
||||
];
|
||||
|
||||
$$rAFScheduler(tasks);
|
||||
expect(log).toEqual(['task1']);
|
||||
|
||||
$$rAFScheduler.waitUntilQuiet(quietFn);
|
||||
expect(log).toEqual(['task1']);
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(log).toEqual(['task1', 'quiet', 'task2']);
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(log).toEqual(['task1', 'quiet', 'task2', 'task3']);
|
||||
|
||||
$$rAFScheduler.waitUntilQuiet(quietFn);
|
||||
|
||||
$$rAF.flush();
|
||||
|
||||
expect(log).toEqual(['task1', 'quiet', 'task2', 'task3', 'quiet', 'task4']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+26
-21
@@ -616,10 +616,35 @@ describe('$aria', function() {
|
||||
describe('tabindex', function() {
|
||||
beforeEach(injectScopeAndCompiler);
|
||||
|
||||
it('should attach tabindex to role="checkbox", ng-click, and ng-dblclick', function() {
|
||||
it('should not attach to native controls', function() {
|
||||
var element = [
|
||||
$compile("<button ng-click='something'></button>")(scope),
|
||||
$compile("<a ng-href='#/something'>")(scope),
|
||||
$compile("<input ng-model='val'>")(scope),
|
||||
$compile("<textarea ng-model='val'></textarea>")(scope),
|
||||
$compile("<select ng-model='val'></select>")(scope),
|
||||
$compile("<details ng-model='val'></details>")(scope)
|
||||
];
|
||||
expectAriaAttrOnEachElement(element, 'tabindex', undefined);
|
||||
});
|
||||
|
||||
it('should not attach to random ng-model elements', function() {
|
||||
compileElement('<div ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should attach tabindex to custom inputs', function() {
|
||||
compileElement('<div type="checkbox" ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
|
||||
compileElement('<div role="checkbox" ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
|
||||
compileElement('<div type="range" ng-model="val"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
});
|
||||
|
||||
it('should attach to ng-click and ng-dblclick', function() {
|
||||
compileElement('<div ng-click="someAction()"></div>');
|
||||
expect(element.attr('tabindex')).toBe('0');
|
||||
|
||||
@@ -640,26 +665,6 @@ describe('$aria', function() {
|
||||
compileElement('<div ng-dblclick="someAction()" tabindex="userSetValue"></div>');
|
||||
expect(element.attr('tabindex')).toBe('userSetValue');
|
||||
});
|
||||
|
||||
it('should set proper tabindex values for radiogroup', function() {
|
||||
compileElement('<div role="radiogroup">' +
|
||||
'<div role="radio" ng-model="val" value="one">1</div>' +
|
||||
'<div role="radio" ng-model="val" value="two">2</div>' +
|
||||
'</div>');
|
||||
|
||||
var one = element.contents().eq(0);
|
||||
var two = element.contents().eq(1);
|
||||
|
||||
scope.$apply("val = 'one'");
|
||||
expect(one.attr('tabindex')).toBe('0');
|
||||
expect(two.attr('tabindex')).toBe('-1');
|
||||
|
||||
scope.$apply("val = 'two'");
|
||||
expect(one.attr('tabindex')).toBe('-1');
|
||||
expect(two.attr('tabindex')).toBe('0');
|
||||
|
||||
dealoc(element);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessible actions', function() {
|
||||
|
||||
@@ -138,8 +138,8 @@ describe('cookie options', function() {
|
||||
.reduce(function(prev, value) {
|
||||
var pair = value.split('=', 2);
|
||||
if (pair[0] === key) {
|
||||
if (prev === undefined) {
|
||||
return pair[1] === undefined ? true : pair[1];
|
||||
if (isUndefined(prev)) {
|
||||
return isUndefined(pair[1]) ? true : pair[1];
|
||||
} else {
|
||||
throw 'duplicate key in cookie string';
|
||||
}
|
||||
|
||||
Vendored
+181
@@ -1828,6 +1828,187 @@ describe('ngMockE2E', function() {
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngAnimateMock', function() {
|
||||
|
||||
beforeEach(module('ngAnimate'));
|
||||
beforeEach(module('ngAnimateMock'));
|
||||
|
||||
var ss, element, trackedAnimations;
|
||||
|
||||
afterEach(function() {
|
||||
if (element) {
|
||||
element.remove();
|
||||
}
|
||||
if (ss) {
|
||||
ss.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(module(function($animateProvider) {
|
||||
trackedAnimations = [];
|
||||
$animateProvider.register('.animate', function() {
|
||||
return {
|
||||
leave: logFn('leave'),
|
||||
addClass: logFn('addClass')
|
||||
};
|
||||
|
||||
function logFn(method) {
|
||||
return function(element) {
|
||||
trackedAnimations.push(getDoneCallback(arguments));
|
||||
};
|
||||
}
|
||||
|
||||
function getDoneCallback(args) {
|
||||
for (var i = args.length; i > 0; i--) {
|
||||
if (angular.isFunction(args[i])) return args[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return function($animate, $rootElement, $document, $rootScope, $window) {
|
||||
ss = createMockStyleSheet($document, $window);
|
||||
|
||||
element = angular.element('<div class="animate"></div>');
|
||||
$rootElement.append(element);
|
||||
angular.element($document[0].body).append($rootElement);
|
||||
$animate.enabled(true);
|
||||
$rootScope.$digest();
|
||||
};
|
||||
}));
|
||||
|
||||
describe('$animate.queue', function() {
|
||||
it('should maintain a queue of the executed animations', inject(function($animate) {
|
||||
element.removeClass('animate'); // we don't care to test any actual animations
|
||||
var options = {};
|
||||
|
||||
$animate.addClass(element, 'on', options);
|
||||
var first = $animate.queue[0];
|
||||
expect(first.element).toBe(element);
|
||||
expect(first.event).toBe('addClass');
|
||||
expect(first.options).toBe(options);
|
||||
|
||||
$animate.removeClass(element, 'off', options);
|
||||
var second = $animate.queue[1];
|
||||
expect(second.element).toBe(element);
|
||||
expect(second.event).toBe('removeClass');
|
||||
expect(second.options).toBe(options);
|
||||
|
||||
$animate.leave(element, options);
|
||||
var third = $animate.queue[2];
|
||||
expect(third.element).toBe(element);
|
||||
expect(third.event).toBe('leave');
|
||||
expect(third.options).toBe(options);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('$animate.flush()', function() {
|
||||
it('should throw an error if there is nothing to animate', inject(function($animate) {
|
||||
expect(function() {
|
||||
$animate.flush();
|
||||
}).toThrow('No pending animations ready to be closed or flushed');
|
||||
}));
|
||||
|
||||
it('should trigger the animation to start',
|
||||
inject(function($animate) {
|
||||
|
||||
expect(trackedAnimations.length).toBe(0);
|
||||
$animate.leave(element);
|
||||
$animate.flush();
|
||||
expect(trackedAnimations.length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should trigger the animation to end once run and called',
|
||||
inject(function($animate) {
|
||||
|
||||
$animate.leave(element);
|
||||
$animate.flush();
|
||||
expect(element.parent().length).toBe(1);
|
||||
|
||||
trackedAnimations[0]();
|
||||
$animate.flush();
|
||||
expect(element.parent().length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should trigger the animation promise callback to fire once run and closed',
|
||||
inject(function($animate) {
|
||||
|
||||
var doneSpy = jasmine.createSpy();
|
||||
$animate.leave(element).then(doneSpy);
|
||||
$animate.flush();
|
||||
|
||||
trackedAnimations[0]();
|
||||
expect(doneSpy).not.toHaveBeenCalled();
|
||||
$animate.flush();
|
||||
expect(doneSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should trigger a series of CSS animations to trigger and start once run',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
if (!browserSupportsCssAnimations()) return;
|
||||
|
||||
ss.addRule('.leave-me.ng-leave', 'transition:1s linear all;');
|
||||
|
||||
var i, elm, elms = [];
|
||||
for (i = 0; i < 5; i++) {
|
||||
elm = angular.element('<div class="leave-me"></div>');
|
||||
element.append(elm);
|
||||
elms.push(elm);
|
||||
|
||||
$animate.leave(elm);
|
||||
}
|
||||
|
||||
$rootScope.$digest();
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
elm = elms[i];
|
||||
expect(elm.hasClass('ng-leave')).toBe(true);
|
||||
expect(elm.hasClass('ng-leave-active')).toBe(false);
|
||||
}
|
||||
|
||||
$animate.flush();
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
elm = elms[i];
|
||||
expect(elm.hasClass('ng-leave')).toBe(true);
|
||||
expect(elm.hasClass('ng-leave-active')).toBe(true);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should trigger parent and child animations to run within the same flush',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
var child = angular.element('<div class="animate child"></div>');
|
||||
element.append(child);
|
||||
|
||||
expect(trackedAnimations.length).toBe(0);
|
||||
|
||||
$animate.addClass(element, 'go');
|
||||
$animate.addClass(child, 'start');
|
||||
$animate.flush();
|
||||
|
||||
expect(trackedAnimations.length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should trigger animation callbacks when called',
|
||||
inject(function($animate, $rootScope) {
|
||||
|
||||
var spy = jasmine.createSpy();
|
||||
$animate.on('addClass', element, spy);
|
||||
|
||||
$animate.addClass(element, 'on');
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
|
||||
$animate.flush();
|
||||
expect(spy.callCount).toBe(1);
|
||||
|
||||
trackedAnimations[0]();
|
||||
$animate.flush();
|
||||
expect(spy.callCount).toBe(2);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('make sure that we can create an injector outside of tests', function() {
|
||||
|
||||
@@ -297,6 +297,14 @@ describe("resource", function() {
|
||||
R.get({a: 'foo'});
|
||||
});
|
||||
|
||||
it('should support IPv6 URLs', function() {
|
||||
var R = $resource('http://[2620:0:861:ed1a::1]/:ed1a/', {}, {}, {stripTrailingSlashes: false});
|
||||
$httpBackend.expect('GET', 'http://[2620:0:861:ed1a::1]/foo/').respond({});
|
||||
$httpBackend.expect('GET', 'http://[2620:0:861:ed1a::1]/').respond({});
|
||||
R.get({ed1a: 'foo'});
|
||||
R.get({});
|
||||
});
|
||||
|
||||
it('should support overriding provider default trailing-slash stripping configuration', function() {
|
||||
// Set the new behavior for all new resources created by overriding the
|
||||
// provider configuration
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user