Compare commits

...

36 Commits

Author SHA1 Message Date
Brian Ford c086f831fb docs(changelog): release notes for 1.2.13 2014-02-14 16:41:02 -08:00
Igor Minar fab2d3c92f style(animate): remove ws 2014-02-14 16:31:57 -08:00
Matias Niemelä 4f84f6b3e4 fix($animate): ensure $animate doesn't break natural CSS transitions
BREAKING CHANGE: ngClass and {{ class }} will now call the `setClass`
animation callback instead of addClass / removeClass when both a
addClass/removeClass operation is being executed on the element during the animation.

Please include the setClass animation callback as well as addClass and removeClass within
your JS animations to work with ngClass and {{ class }} directives.

Closes #6019
2014-02-14 16:30:48 -08:00
Matias Niemelä cf5e463abd pref($animate): only trigger DOM callbacks if registered on the element being animated
BREAKING CHANGE: Both the `$animate:before` and `$animate:after` DOM events must be now
registered prior to the $animate operation taking place. The `$animate:close` event
can be registered anytime afterwards.

DOM callbacks used to fired for each and every animation operation that occurs within the
$animate service provided in the ngAnimate module. This may end up slowing down an
application if 100s of elements are being inserted into the page. Therefore after this
change callbacks are only fired if registered on the element being animated.
2014-02-14 16:28:56 -08:00
Matias Niemelä f288b8f010 pref($animate): group all asynchronous requests into one shared buffer 2014-02-14 16:28:34 -08:00
Matias Niemelä b7e4e92014 chore(jqLite): expose the _data lookup function to angular.element 2014-02-14 15:49:08 -08:00
Caitlin Potter 31c450bcee fix($compile) support templates with table content root nodes
If the first element in a template is a <tr>, <th>, <td>, or <tbody> tag,
the HTML compiler will ensure that the template is wrapped in a <table>
element so that the table content is not discarded.

Closes #2848
Closes #1459
Closes #3647
Closes #3241
2014-02-14 14:42:55 -05:00
Tim Whitbeck a9fcb0d0fc fix(input): don't apply textInput to <input type="file">
textInput shouldn't be applied to file inputs to ease writing of custom file input directives.

This change prevents file inputs from instantiating the text input parser/formatter pipelines.

Closes #6247
Closes #6231
2014-02-13 16:43:18 -05:00
Naomi Black a3846ab837 Merge pull request #6245 from brianhall/master
Google logo in AngularJS.exports to vector format.
2014-02-13 10:35:05 -08:00
Brian Hall 1953b0bed9 docs(logo): change logo to vector format in .eps file
Browser: Other
Component: docs
Regression: no

Closes issue #6092
2014-02-13 09:51:11 -08:00
Caitlin Potter 2b73027136 fix(input): setViewValue on compositionend
Because of a4e6d962, model is not updated on input/change between the
compositionstart and compositionend events. Unfortunately, the compositionend
event does not always happen prior to an input/change event.

This changeset calls the listener function to update the model after a
compositionend event is received.

Closes #6058
Closes #5433
2014-02-12 20:28:13 -05:00
Caitlin Potter 1079105443 docs($interpolate): fix link to $interpolateProvider#endSymbol
The markup here was missing the methods_ prefix and behaved incorrectly.

Closes #5802
2014-02-12 14:41:37 -05:00
Caitlin Potter d119e36302 docs($location): fix link to $rootScope.Scope.$on
Previously missing the methods_ prefix.

Closes #5798
2014-02-12 14:39:41 -05:00
James Kyle 98b2f8ef18 docs(currencyFilter): added missing line break in currency doc ptor test
Closes #6229
2014-02-12 10:05:35 -05:00
Stéphane Reynaud e7ab857ddb docs(guide/$location): correct link to HTML5 draft section 5.5 (history api)
Previous link url is no longer served, responds with bad link (error 404). This change corrects the
URL to point to section 5.5 of the draft. The old URL appears to have been removed from service in
2012.

Corrects the link to "History API"

Closes #6225
2014-02-12 08:26:21 -05:00
Mathieu Tricoire 46cba2e05d docs(input): document NgModelController.$isEmpty parameters / return value
Closes #6224
2014-02-12 08:19:24 -05:00
Jason Schapiro 72894f0dd2 docs(tutorial): inject phonecapApp module into unit test
When I was reading this doc I was thinking "but what about phonecatApp?" and when I looked in the
file from the step-11 branch there it is. Should be reflected in the docs as well

Closes #6209
2014-02-11 21:29:47 -05:00
Caitlin Potter 760f49de10 chore(dependencies): upgrade kriskowal/q to version ~1.0.0
CI builds on travis occasionally freak out because of the recursive use of process.nextTick, which
has been deprecated in Node relatively recently, to be replaced with setImmediate. Unfortunately,
this change does not resolve the issue. However, it does not hurt, either.

Closes #6161
2014-02-11 18:54:38 -05:00
Jesse Palmer 686b13bf60 docs(core): add closing tag to ngApp directive example
added missing closing tag to ngApp example.

Closes #6066
2014-02-11 18:42:16 -05:00
Evgeniy Tkachenko 56cc7bcc98 docs(jqLite): link to jQuery.fn.bind/unbind docs rather than jQuery.fn.on/off docs
Сorrect link.

Closes #6171
2014-02-11 18:23:25 -05:00
Caitlin Potter b4eed8ad94 feat(filterFilter): support deeply nested predicate objects
Due to 339a165, it became impossible to filter nested properties of an object using the filterFilter.
A proposed solution to this was to enable the use of nested predicate objects. This change enables the
use of these nested predicate objects.

Example:

```html
<div ng-repeat="it in items | filter:{ address: { country: 'Canuckistan'}}"></div>
```

Or

```js
$filter('filter')(items, { address: { country: 'Canuckistan' } });
```

Closes #6215
Related to #6009
2014-02-11 17:08:41 -05:00
Daniel Tabuenca 08793a690a refactor(ngTransclude): use transclusion function passed in to link
Since we now pass in the transclusion function directly to the link function, we no longer need
the old scheme whereby we saved the transclude function injected into the controller for later
use in during linking.

Additionally, this change may aid in correcting a memory leak of detached DOM nodes (see #6181
for details).

This commit removes the controller and simplifies ngTransclude.

Closes #5375
Closes #6181
2014-02-11 14:57:56 -05:00
Stéphane Reynaud ef4bf8c77c docs(guide/index): replace "shold" to "should"
Replace "shold" to "should"

Closes #6216
2014-02-11 11:46:36 -05:00
Igor Minar b6ab826c4b style(guide): remove ws 2014-02-10 17:09:35 -08:00
Jeremy Likness 71f974b459 docs(guide): add new resource links
Added a link to 10 reasons to use and online courses for Angular

Closes #6194
2014-02-10 17:09:34 -08:00
James Wagoner a68624444a docs(ngSubmit): ngSubmit also works with the data-action/x-action attributes
The documentation states only the "action" attribute triggers this, which is incorrect. When using
the attribute "data-action" (as for AJAX control, attempting to bypass the "action" attribute but
still make it obvious what its for), Angular thinks this is also classified as "action" and
continues with the page submission.

Closes #6196
2014-02-10 19:35:22 -05:00
Igor Minar 945fc1a4bc style(guide/concepts): remove ws 2014-02-10 16:19:10 -08:00
Sequoia McDowell ec900cabfc docs(guide/concepts): removing confusing use of hoisting
Closes #6207
2014-02-10 16:19:10 -08:00
Mark Miyashita f99fe799e2 docs(faq): add link to MIT license
Closes #6197
2014-02-10 15:58:34 -08:00
Caitlin Potter e7338d3f27 fix($compile): ensure element transclusion directives are linked with comment element
This corrects a complicated compiler issue, described in detail below:

Previously, if an element transclusion directive contained an asynchronous directive whose template
contained another element transclusion directive, the inner element transclusion directive would be
linked with the element, rather than the expected comment node.

An example manifestation of this bug would look like so:

```html
<div ng-repeat="i in [1,2,3,4,5]">
  <div my-directive>
  </div>
</div>
```

`my-directive` would be a replace directive, and its template would contain another element
transclusion directive, like so:

```html
<div ng-if="true">{{i}}</div>
```

ngIf would be linked with this template content, rather than the comment node, and the template element
would be attached to the DOM, rather than the comment. As a result, this caused ng-if to duplicate the
template when its expression evaluated to true.

Closes #6006
Closes #6101
2014-02-10 18:41:28 -05:00
Sequoia McDowell 2dfbc083c5 docs(concepts): Remove pointless * 1s
Closes #6206
2014-02-10 15:15:30 -08:00
Victor Berchet 27613fd500 docs(guide/scope): fix a typo
Signed-off-by: Caitlin Potter <caitpotter88@gmail.com>

Closes #6202
2014-02-10 18:09:03 -05:00
Julie e645f7cae1 refactor(testing): split travis end to end tests into separate jobs for jquery and jqlite
Closes #6159
2014-02-07 20:41:39 -08:00
Julie ad275b2265 refactor(doc): separate end to end tests into jquery and jqlite files 2014-02-07 20:41:11 -08:00
Julie 600e6218fe chore(testing): switch Jenkins to test e2e only on chrome
End to end tests will continue to be run on Safari and Firefox on Travis.

Closes #6187
2014-02-07 20:23:26 -08:00
jenkins 5218c7bbdc chore(release): update cdn version 2014-02-07 14:38:15 -08:00
38 changed files with 870 additions and 424 deletions
+6 -3
View File
@@ -5,9 +5,12 @@ node_js:
env:
matrix:
- JOB=unit
- JOB=e2e-chrome
- JOB=e2e-firefox
- JOB=e2e-safari
- JOB=e2e BROWSER=chrome JQVERSION=jqlite
- JOB=e2e BROWSER=firefox JQVERSION=jqlite
- JOB=e2e BROWSER=safari JQVERSION=jqlite
- JOB=e2e BROWSER=chrome JQVERSION=jquery
- JOB=e2e BROWSER=firefox JQVERSION=jquery
- JOB=e2e BROWSER=safari JQVERSION=jquery
global:
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
+55
View File
@@ -1,3 +1,58 @@
<a name="1.2.13"></a>
# 1.2.13 romantic-transclusion (2014-02-14)
## Bug Fixes
- **$animate:** ensure $animate doesn't break natural CSS transitions
([4f84f6b3](https://github.com/angular/angular.js/commit/4f84f6b3e4210ae1eb14728a46d43dd961700a0c),
[#6019](https://github.com/angular/angular.js/issues/6019))
- **$compile:**
- ensure element transclusion directives are linked with comment element
([e7338d3f](https://github.com/angular/angular.js/commit/e7338d3f27e8824196136a18e1c3e0fcf51a0e28),
[#6006](https://github.com/angular/angular.js/issues/6006), [#6101](https://github.com/angular/angular.js/issues/6101))
- support templates with table content root nodes
([e7338d3f](https://github.com/angular/angular.js/commit/31c450bcee53d0a3827b7e0a611e9013b2496506),
[#2848](https://github.com/angular/angular.js/issues/2848), [#1459](https://github.com/angular/angular.js/issues/1459), [#3647](https://github.com/angular/angular.js/issues/3647), [#3241](https://github.com/angular/angular.js/issues/3241))
- **input:**
- don't apply textInput to `<input type="file">`
([a9fcb0d0](https://github.com/angular/angular.js/commit/a9fcb0d0fc6456f80501b8820d02b04d7c15b6d6),
[#6247](https://github.com/angular/angular.js/issues/6247), [#6231](https://github.com/angular/angular.js/issues/6231))
- setViewValue on compositionend
([2b730271](https://github.com/angular/angular.js/commit/2b7302713674506fdbcdc396c38f18dcb90dee8c),
[#6058](https://github.com/angular/angular.js/issues/6058), [#5433](https://github.com/angular/angular.js/issues/5433))
## Features
- **filterFilter:** support deeply nested predicate objects
([b4eed8ad](https://github.com/angular/angular.js/commit/b4eed8ad94ce9719540462c1ee969dfd3c6b2355),
[#6215](https://github.com/angular/angular.js/issues/6215))
## Breaking Changes
- **$animate:**
- due to [4f84f6b3](https://github.com/angular/angular.js/commit/4f84f6b3e4210ae1eb14728a46d43dd961700a0c),
ngClass and {{ class }} will now call the `setClass`
animation callback instead of addClass / removeClass when both a
addClass/removeClass operation is being executed on the element during the animation.
Please include the setClass animation callback as well as addClass and removeClass within
your JS animations to work with ngClass and {{ class }} directives.
- due to [cf5e463a](https://github.com/angular/angular.js/commit/cf5e463abd2c23f62e9c2e6361e6c53048c8910e),
Both the `$animate:before` and `$animate:after` DOM events must be now
registered prior to the $animate operation taking place. The `$animate:close` event
can be registered anytime afterwards.
DOM callbacks used to fired for each and every animation operation that occurs within the
$animate service provided in the ngAnimate module. This may end up slowing down an
application if 100s of elements are being inserted into the page. Therefore after this
change callbacks are only fired if registered on the element being animated.
<a name="1.2.12"></a>
# 1.2.12 cauliflower-eradication (2014-02-07)
+2
View File
@@ -92,6 +92,7 @@ module.exports = function(grunt) {
protractor: {
normal: 'protractor-conf.js',
jquery: 'protractor-jquery-conf.js',
jenkins: 'protractor-jenkins-conf.js'
},
@@ -292,6 +293,7 @@ module.exports = function(grunt) {
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
grunt.registerTask('test:jq-protractor', 'Run the end to end tests against jquery with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:jquery']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
grunt.registerTask('test:e2e', 'Alias for test:protractor', ['test:protractor']);
grunt.registerTask('test:docgen', ['jasmine_node']);
+5
View File
@@ -9,3 +9,8 @@
ng\:form {
display: block;
}
.ng-animate-block-transitions {
transition:0s all!important;
-webkit-transition:0s all!important;
}
+26 -24
View File
@@ -54,18 +54,18 @@ Try out the Live Preview above, and then let's walk through the example and desc
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding1.png">
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
They apply special behavior to attributes or elements in the HTML. In the example above we use the
They apply special behavior to attributes or elements in the HTML. In the example above we use the
{@link api/ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
initializes our application. Angular also defines a directive for the {@link api/ng.directive:input `input`}
element that adds extra behavior to the element. E.g. it is able to automatically validate that the entered
text is non empty by evaluating the `required` attribute.
element that adds extra behavior to the element. E.g. it is able to automatically validate that the entered
text is non empty by evaluating the `required` attribute.
The {@link api/ng.directive:ngModel `ng-model`} directive stores/updates
the value of the input field into/from a variable and shows the validation state of the input field by
the value of the input field into/from a variable and shows the validation state of the input field by
adding css classes. In the example we use these css classes to mark an empty input field with a red border.
<div class="alert alert-info">
@@ -120,7 +120,7 @@ different currencies and also pay the invoice.
return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
};
this.convertCurrency = function convertCurrency(amount, inCurr, outCurr) {
return amount * this.usdToForeignRates[outCurr] * 1 / this.usdToForeignRates[inCurr];
return amount * this.usdToForeignRates[outCurr] / this.usdToForeignRates[inCurr];
};
this.pay = function pay() {
window.alert("Thanks!");
@@ -195,20 +195,20 @@ Let's refactor our example and move the currency conversion into a service in an
<file name="finance2.js">
angular.module('finance2', [])
.factory('currencyConverter', function() {
var currencies = ['USD', 'EUR', 'CNY'],
usdToForeignRates = {
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {
USD: 1,
EUR: 0.74,
CNY: 6.09
};
var convert = function (amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
}
return {
currencies: currencies,
convert: convert
};
function convert(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
}
});
</file>
<file name="invoice2.js">
@@ -325,21 +325,15 @@ The following example shows how this is done with Angular:
var YAHOO_FINANCE_URL_PATTERN =
'http://query.yahooapis.com/v1/public/yql?q=select * from '+
'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK',
currencies = ['USD', 'EUR', 'CNY'],
usdToForeignRates = {};
refresh();
return {
currencies: currencies,
convert: convert,
refresh: refresh
};
'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK';
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {};
function convert(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
var convert = function (amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
}
function refresh() {
var refresh = function() {
var url = YAHOO_FINANCE_URL_PATTERN.
replace('PAIRS', 'USD' + currencies.join('","USD'));
return $http.jsonp(url).success(function(data) {
@@ -351,6 +345,14 @@ The following example shows how this is done with Angular:
usdToForeignRates = newUsdToForeignRates;
});
}
refresh();
return {
currencies: currencies,
convert: convert,
refresh: refresh
};
}]);
</file>
<file name="index.html">
@@ -160,7 +160,7 @@ encoded.
`$location` service has two configuration modes which control the format of the URL in the browser
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
HTML5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
HTML5 {@link http://www.w3.org/TR/html5/browsers.html#history History API}. Applications use the same API in
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
facilitate the browser URL change and history management.
+3 -1
View File
@@ -13,6 +13,7 @@ Everything you need to know about AngularJS
* {@link tutorial/index Official AngularJS Tutorial}
* [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/)
* [10 Reasons Why Developers Should Learn AngularJS](http://wintellect.com/blogs/jlikness/10-reasons-web-developers-should-learn-angularjs)
* [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c)
* [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM)
* [For folks with jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background)
@@ -87,7 +88,7 @@ This is a short list of libraries with specific support and documentation for wo
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
@@ -113,6 +114,7 @@ This is a short list of libraries with specific support and documentation for wo
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
* **Paid onsite:**
[angularbootcamp.com](http://angularbootcamp.com/)
+1 -1
View File
@@ -333,7 +333,7 @@ information.
Dirty checking the scope for property changes is a common operation in Angular and for this reason
the dirty checking function must be efficient. Care should be taken that the dirty checking
function does not do any DOM access, as DOM access is orders of magnitude slower then property
function does not do any DOM access, as DOM access is orders of magnitude slower than property
access on JavaScript object.
## Integration with the browser event loop
+1 -1
View File
@@ -100,7 +100,7 @@ Watch the July 17, 2012 talk
### How is Angular licensed?
The MIT License.
The {@link https://github.com/angular/angular.js/blob/master/LICENSE MIT License}.
### Can I download and use the Angular logo artwork?
+1 -1
View File
@@ -148,7 +148,7 @@ describe('PhoneCat controllers', function() {
});
});
beforeEach(module('phonecatApp'));
beforeEach(module('phonecatServices'));
+4 -1
View File
@@ -57,7 +57,10 @@ writer.makeDir('build/docs/', true).then(function() {
fileFutures.push(writer.output('partials/' + doc.section + '/' + id + '.html', doc.html()));
// If it has a sample Protractor test, output that as well.
if (doc.protractorTests.length) {
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '_test.js', ngdoc.writeProtractorTest(doc)));
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '.jquery_test.js',
ngdoc.writeProtractorTest(doc, 'index-jq-nocache.html#!/')));
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '.jqlite_test.js',
ngdoc.writeProtractorTest(doc, 'index-nocache.html#!/')));
}
});
+5 -16
View File
@@ -1110,28 +1110,17 @@ function scenarios(docs){
}
}
function writeProtractorTest(doc){
function writeProtractorTest(doc, pathPrefix){
var lines = [];
lines.push('describe("' + doc.section + '/' + doc.id + '", function() {');
lines.push(' describe("angular+jqLite", function() {')
lines.push(' beforeEach(function() {');
lines.push(' browser.get("index-nocache.html#!/' + doc.section + '/' + doc.id + '");');
lines.push(' });');
lines.push(' beforeEach(function() {');
lines.push(' browser.get("' + pathPrefix + doc.section + '/' + doc.id + '");');
lines.push(' });');
lines.push('');
doc.protractorTests.forEach(function(test){
lines.push(indentCode(trim(test), 4));
lines.push(indentCode(trim(test), 0));
lines.push('');
});
lines.push(' });');
lines.push(' describe("angular+jQuery", function() {')
lines.push(' beforeEach(function() {');
lines.push(' browser.get("index-jq-nocache.html#!/' + doc.section + '/' + doc.id + '");');
lines.push(' });');
doc.protractorTests.forEach(function(test){
lines.push(indentCode(trim(test), 4));
lines.push('');
});
lines.push(' });');
lines.push('});');
lines.push('');
return lines.join('\n');
Binary file not shown.
-2
View File
@@ -32,8 +32,6 @@ grunt test:unit --browsers $BROWSERS --reporters=dots,junit --no-colors --no-col
# END TO END TESTS #
grunt test:ci-protractor
grunt test:ci-protractor --browser safari
grunt test:ci-protractor --browser firefox
# Promises/A+ TESTS #
grunt test:promises-aplus --no-color
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "angularjs",
"branchVersion": "1.2.*",
"cdnVersion": "1.2.11",
"cdnVersion": "1.2.12",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -23,7 +23,7 @@
"load-grunt-tasks": "~0.3.0",
"bower": "~1.2.2",
"jasmine-node": "~1.11.0",
"q": "~0.9.2",
"q": "~1.0.0",
"q-io": "~1.10.6",
"qq": "~0.3.5",
"shelljs": "~0.2.6",
+3 -2
View File
@@ -2,12 +2,13 @@ exports.config = {
allScriptsTimeout: 11000,
specs: [
'build/docs/ptore2e/**/*.js',
'build/docs/ptore2e/**/*jqlite_test.js',
'test/e2e/docsAppE2E.js'
],
capabilities: {
'browserName': 'chrome'
'browserName': 'chrome',
'name': 'Angular E2E: jqlite'
},
baseUrl: 'http://localhost:8000/build/docs/',
+32
View File
@@ -0,0 +1,32 @@
exports.config = {
allScriptsTimeout: 11000,
specs: [
'build/docs/ptore2e/**/*jquery_test.js',
'test/e2e/docsAppE2E.js'
],
capabilities: {
'browserName': 'chrome',
'name': 'Angular E2E: jquery'
},
baseUrl: 'http://localhost:8000/build/docs/',
framework: 'jasmine',
onPrepare: function() {
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(function($animate) {
$animate.enabled(false);
});
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
},
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};
+6 -3
View File
@@ -3,15 +3,18 @@
set -e
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
export BROWSER=${JOB#*-}
if [ $JOB = "unit" ]; then
grunt ci-checks
grunt test:docgen
grunt test:promises-aplus
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
elif [[ $JOB == e2e* ]]; then
grunt test:protractor --sauceUser $SAUCE_USERNAME \
elif [ $JOB = "e2e" ]; then
export GRUNT_TARGET="test:protractor"
if [ $JQVERSION = "jquery" ]; then
GRUNT_TARGET="test:jq-protractor"
fi
grunt $GRUNT_TARGET --sauceUser $SAUCE_USERNAME \
--sauceKey $SAUCE_ACCESS_KEY \
--capabilities.tunnel-identifier=$TRAVIS_JOB_NUMBER \
--capabilities.build=$TRAVIS_BUILD_NUMBER \
+3 -1
View File
@@ -81,6 +81,7 @@
-assertNotHasOwnProperty,
-getter,
-getBlockElements,
-hasOwnProperty,
*/
@@ -96,7 +97,7 @@
* @returns {string} Lowercased string.
*/
var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* @ngdoc function
@@ -1131,6 +1132,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
<file name="index.html">
<div ng-controller="ngAppDemoController">
I can add: {{a}} + {{b}} = {{ a+b }}
</div>
</file>
<file name="script.js">
angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
+10 -2
View File
@@ -40,7 +40,7 @@
* - [`after()`](http://api.jquery.com/after/)
* - [`append()`](http://api.jquery.com/append/)
* - [`attr()`](http://api.jquery.com/attr/)
* - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
* - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
@@ -67,7 +67,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/off/) - Does not support namespaces
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
* - [`val()`](http://api.jquery.com/val/)
* - [`wrap()`](http://api.jquery.com/wrap/)
*
@@ -107,6 +107,14 @@ var jqCache = JQLite.cache = {},
? function(element, type, fn) {element.removeEventListener(type, fn, false); }
: function(element, type, fn) {element.detachEvent('on' + type, fn); });
/*
* !!! This is an undocumented "private" function !!!
*/
var jqData = JQLite._data = function(node) {
//jQuery always returns an object on cache miss
return this.cache[node[this.expando]] || {};
};
function jqNextId() { return ++jqId; }
+23
View File
@@ -222,6 +222,29 @@ var $AnimateProvider = ['$provide', function($provide) {
done && $timeout(done, 0, false);
},
/**
*
* @ngdoc function
* @name ng.$animate#setClass
* @methodOf ng.$animate
* @function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* @param {jQuery/jqLite element} element the element which will it's CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
*/
setClass : function(element, add, remove, done) {
forEach(element, function (element) {
jqLiteAddClass(element, add);
jqLiteRemoveClass(element, remove);
});
done && $timeout(done, 0, false);
},
enabled : noop
};
}];
+45 -10
View File
@@ -502,7 +502,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|tbody)(\s+[^>]*)?>/i;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -689,8 +690,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} oldClasses The former CSS className value
*/
$updateClass : function(newClasses, oldClasses) {
this.$removeClass(tokenDifference(oldClasses, newClasses));
this.$addClass(tokenDifference(newClasses, oldClasses));
var toAdd = tokenDifference(newClasses, oldClasses);
var toRemove = tokenDifference(oldClasses, newClasses);
if(toAdd.length === 0) {
$animate.removeClass(this.$$element, toRemove);
} else if(toRemove.length === 0) {
$animate.addClass(this.$$element, toAdd);
} else {
$animate.setClass(this.$$element, toAdd, toRemove);
}
},
/**
@@ -1142,7 +1151,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
templateDirective = previousCompileContext.templateDirective,
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
hasTranscludeDirective = false,
hasElementTranscludeDirective = false,
hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
directiveName,
@@ -1243,9 +1252,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (directive.replace) {
replaceDirective = directive;
$template = jqLite('<div>' +
trim(directiveValue) +
'</div>').contents();
$template = directiveTemplateContents(directiveValue);
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -1316,6 +1323,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
return nodeLinkFn;
@@ -1643,6 +1651,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function directiveTemplateContents(template) {
var type;
template = trim(template);
if ((type = TABLE_CONTENT_REGEXP.exec(template))) {
type = type[1].toLowerCase();
var table = jqLite('<table>' + template + '</table>'),
tbody = table.children('tbody'),
leaf = /(td|th)/.test(type) && table.find('tr');
if (tbody.length && type !== 'tbody') {
table = tbody;
}
if (leaf && leaf.length) {
table = leaf;
}
return table.contents();
}
return jqLite('<div>' +
template +
'</div>').contents();
}
function compileTemplateUrl(directives, $compileNode, tAttrs,
$rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
var linkQueue = [],
@@ -1667,7 +1697,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
content = denormalizeTemplate(content);
if (origAsyncDirective.replace) {
$template = jqLite('<div>' + trim(content) + '</div>').contents();
$template = directiveTemplateContents(content);
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -1712,8 +1742,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
var oldClasses = beforeTemplateLinkNode.className;
// it was cloned therefore we have to clone as well.
linkNode = jqLiteClone(compileNode);
if (!(previousCompileContext.hasElementTranscludeDirective &&
origAsyncDirective.replace)) {
// it was cloned therefore we have to clone as well.
linkNode = jqLiteClone(compileNode);
}
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
// Copy in CSS classes from original node
+6 -1
View File
@@ -424,7 +424,8 @@ var inputType = {
'hidden': noop,
'button': noop,
'submit': noop,
'reset': noop
'reset': noop,
'file': noop
};
// A helper function to call $setValidity and return the value / undefined,
@@ -447,6 +448,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
element.on('compositionend', function() {
composing = false;
listener();
});
}
@@ -1022,6 +1024,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* You can override this for input directives whose concept of being empty is different to the
* default. The `checkboxInputType` directive does this because in its case a value of `false`
* implies empty.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is empty.
*/
this.$isEmpty = function(value) {
return isUndefined(value) || value === '' || value === null || value !== value;
+2 -2
View File
@@ -298,8 +298,8 @@ forEach(
* Enables binding angular expressions to onsubmit events.
*
* Additionally it prevents the default action (which for form means sending the request to the
* server and reloading the current page) **but only if the form does not contain an `action`
* attribute**.
* server and reloading the current page), but only if the form does not contain `action`,
* `data-action`, or `x-action` attributes.
*
* @element form
* @priority 0
+7 -14
View File
@@ -56,23 +56,16 @@
*
*/
var ngTranscludeDirective = ngDirective({
controller: ['$element', '$transclude', function($element, $transclude) {
link: function($scope, $element, $attrs, controller, $transclude) {
if (!$transclude) {
throw minErr('ngTransclude')('orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
}
// remember the transclusion fn but call it during linking so that we don't process transclusion before directives on
// the parent element even when the transclusion replaces the current element. (we can't use priority here because
// that applies only to compile fns and not controllers
this.$transclude = $transclude;
}],
link: function($scope, $element, $attrs, controller) {
controller.$transclude(function(clone) {
$transclude(function(clone) {
$element.empty();
$element.append(clone);
});
+9
View File
@@ -136,6 +136,15 @@ function filterFilter() {
};
} else {
comparator = function(obj, text) {
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
for (var objKey in obj) {
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
comparator(obj[objKey], text[objKey])) {
return true;
}
}
return false;
}
text = (''+text).toLowerCase();
return (''+obj).toLowerCase().indexOf(text) > -1;
};
+2 -1
View File
@@ -40,7 +40,8 @@
return;
}
element(by.model('amount')).clear();
element(by.model('amount')).sendKeys('-1234'); expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
element(by.model('amount')).sendKeys('-1234');
expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
});
</doc:protractor>
+1 -1
View File
@@ -227,7 +227,7 @@ function $InterpolateProvider() {
* @description
* Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
*
* Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
* Use {@link ng.$interpolateProvider#methods_endSymbol $interpolateProvider#endSymbol} to change
* the symbol.
*
* @returns {string} start symbol.
+1 -1
View File
@@ -574,7 +574,7 @@ function $LocationProvider(){
* @eventType broadcast on root scope
* @description
* Broadcasted before a URL will change. This change can be prevented by calling
* `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
* `preventDefault` method of the event. See {@link ng.$rootScope.Scope#methods_$on} for more
* details about event object. Upon successful change
* {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
*
+329 -226
View File
@@ -248,7 +248,9 @@ angular.module('ngAnimate', ['ng'])
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
*
*/
.factory('$$animateReflow', ['$window', '$timeout', function($window, $timeout) {
.factory('$$animateReflow', ['$window', '$timeout', '$document',
function($window, $timeout, $document) {
var bod = $document[0].body;
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
function(fn) {
@@ -261,13 +263,30 @@ angular.module('ngAnimate', ['ng'])
return $timeout.cancel(timer);
};
return function(fn) {
var id = requestAnimationFrame(fn);
var id = requestAnimationFrame(function() {
var a = bod.offsetWidth + 1;
fn();
});
return function() {
cancelAnimationFrame(id);
};
};
}])
.factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
var timer, queue = [];
return function(fn) {
$timeout.cancel(timer);
queue.push(fn);
timer = $timeout(function() {
for(var i = 0; i < queue.length; i++) {
queue[i]();
}
queue = [];
}, 0, false);
};
}])
.config(['$provide', '$animateProvider', function($provide, $animateProvider) {
var noop = angular.noop;
var forEach = angular.forEach;
@@ -287,13 +306,18 @@ angular.module('ngAnimate', ['ng'])
}
}
function stripCommentsFromElement(element) {
return angular.element(extractElementNode(element));
}
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
var globalAnimationCounter = 0;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// disable animations during bootstrap, but once we bootstrapped, wait again
@@ -315,10 +339,6 @@ angular.module('ngAnimate', ['ng'])
return classNameFilter.test(className);
};
function async(fn) {
return $timeout(fn, 0, false);
}
function lookup(name) {
if (name) {
var matches = [],
@@ -400,6 +420,7 @@ angular.module('ngAnimate', ['ng'])
this.enabled(false, element);
$delegate.enter(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
});
},
@@ -436,6 +457,7 @@ angular.module('ngAnimate', ['ng'])
cancelChildAnimations(element);
this.enabled(false, element);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('leave', 'ng-leave', element, null, null, function() {
$delegate.leave(element);
}, doneCallback);
@@ -478,6 +500,7 @@ angular.module('ngAnimate', ['ng'])
this.enabled(false, element);
$delegate.move(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
});
},
@@ -513,6 +536,7 @@ angular.module('ngAnimate', ['ng'])
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
addClass : function(element, className, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('addClass', className, element, null, null, function() {
$delegate.addClass(element, className);
}, doneCallback);
@@ -549,11 +573,34 @@ angular.module('ngAnimate', ['ng'])
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
removeClass : function(element, className, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('removeClass', className, element, null, null, function() {
$delegate.removeClass(element, className);
}, doneCallback);
},
/**
*
* @ngdoc function
* @name ng.$animate#setClass
* @methodOf ng.$animate
* @function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* @param {jQuery/jqLite element} element the element which will it's CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
*/
setClass : function(element, add, remove, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('setClass', [add, remove], element, null, null, function() {
$delegate.setClass(element, add, remove);
}, doneCallback);
},
/**
* @ngdoc function
* @name ngAnimate.$animate#enabled
@@ -600,7 +647,15 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var currentClassName, classes, node = extractElementNode(element);
var classNameAdd, classNameRemove, setClassOperation = animationEvent == 'setClass';
if(setClassOperation) {
classNameAdd = className[0];
classNameRemove = className[1];
className = classNameAdd + ' ' + classNameRemove;
}
var currentClassName, classes, node = element[0];
if(node) {
currentClassName = node.className;
classes = currentClassName + ' ' + className;
@@ -612,18 +667,27 @@ angular.module('ngAnimate', ['ng'])
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
fireDoneCallbackAsync();
return;
}
var elementEvents = angular.element._data(node);
elementEvents = elementEvents && elementEvents.events;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
parentElement = afterElement ? afterElement.parent() : element.parent();
}
var matches = lookup(animationLookup);
var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
var matches = lookup(animationLookup);
var isClassBased = animationEvent == 'addClass' ||
animationEvent == 'removeClass' ||
setClassOperation;
var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
var runningAnimations = ngAnimateState.active || {};
var totalActiveAnimations = ngAnimateState.totalActive || 0;
var lastAnimation = ngAnimateState.last;
//skip the animation if animations are disabled, a parent is already being animated,
//the element is not currently attached to the document body or then completely close
@@ -642,7 +706,7 @@ angular.module('ngAnimate', ['ng'])
//only add animations if the currently running animation is not structural
//or if there is no animation running at all
var allowAnimations = isClassBased ?
!ngAnimateState.disabled && (!ngAnimateState.running || !ngAnimateState.structural) :
!ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) :
true;
if(allowAnimations) {
@@ -677,55 +741,48 @@ angular.module('ngAnimate', ['ng'])
return;
}
var ONE_SPACE = ' ';
//this value will be searched for class-based CSS className lookup. Therefore,
//we prefix and suffix the current className value with spaces to avoid substring
//lookups of className tokens
var futureClassName = ONE_SPACE + currentClassName + ONE_SPACE;
if(ngAnimateState.running) {
//if an animation is currently running on the element then lets take the steps
//to cancel that animation and fire any required callbacks
$timeout.cancel(ngAnimateState.closeAnimationTimeout);
cleanup(element);
cancelAnimations(ngAnimateState.animations);
var skipAnimation = false;
if(totalActiveAnimations > 0) {
var animationsToCancel = [];
if(!isClassBased) {
if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
skipAnimation = true;
} else {
//cancel all animations when a structural animation takes place
for(var klass in runningAnimations) {
animationsToCancel.push(runningAnimations[klass]);
cleanup(element, klass);
}
runningAnimations = {};
totalActiveAnimations = 0;
}
} else if(lastAnimation.event == 'setClass') {
animationsToCancel.push(lastAnimation);
cleanup(element, className);
}
else if(runningAnimations[className]) {
var current = runningAnimations[className];
if(current.event == animationEvent) {
skipAnimation = true;
} else {
animationsToCancel.push(current);
cleanup(element, className);
}
}
//in the event that the CSS is class is quickly added and removed back
//then we don't want to wait until after the reflow to add/remove the CSS
//class since both class animations may run into a race condition.
//The code below will check to see if that is occurring and will
//immediately remove the former class before the reflow so that the
//animation can snap back to the original animation smoothly
var isFullyClassBasedAnimation = isClassBased && !ngAnimateState.structural;
var isRevertingClassAnimation = isFullyClassBasedAnimation &&
ngAnimateState.className == className &&
animationEvent != ngAnimateState.event;
//if the class is removed during the reflow then it will revert the styles temporarily
//back to the base class CSS styling causing a jump-like effect to occur. This check
//here ensures that the domOperation is only performed after the reflow has commenced
if(ngAnimateState.beforeComplete || isRevertingClassAnimation) {
(ngAnimateState.done || noop)(true);
} else if(isFullyClassBasedAnimation) {
//class-based animations will compare element className values after cancelling the
//previous animation to see if the element properties already contain the final CSS
//class and if so then the animation will be skipped. Since the domOperation will
//be performed only after the reflow is complete then our element's className value
//will be invalid. Therefore the same string manipulation that would occur within the
//DOM operation will be performed below so that the class comparison is valid...
futureClassName = ngAnimateState.event == 'removeClass' ?
futureClassName.replace(ONE_SPACE + ngAnimateState.className + ONE_SPACE, ONE_SPACE) :
futureClassName + ngAnimateState.className + ONE_SPACE;
if(animationsToCancel.length > 0) {
angular.forEach(animationsToCancel, function(operation) {
(operation.done || noop)(true);
cancelAnimations(operation.animations);
});
}
}
//There is no point in perform a class-based animation if the element already contains
//(on addClass) or doesn't contain (on removeClass) the className being animated.
//The reason why this is being called after the previous animations are cancelled
//is so that the CSS classes present on the element can be properly examined.
var classNameToken = ONE_SPACE + className + ONE_SPACE;
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
fireDOMOperation();
if(isClassBased && !setClassOperation && !skipAnimation) {
skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
}
if(skipAnimation) {
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
@@ -736,13 +793,22 @@ angular.module('ngAnimate', ['ng'])
//parent animations to find and cancel child animations when needed
element.addClass(NG_ANIMATE_CLASS_NAME);
element.data(NG_ANIMATE_STATE, {
running:true,
event:animationEvent,
className:className,
structural:!isClassBased,
animations:animations,
var localAnimationCount = globalAnimationCounter++;
lastAnimation = {
classBased : isClassBased,
event : animationEvent,
animations : animations,
done:onBeforeAnimationsComplete
};
totalActiveAnimations++;
runningAnimations[className] = lastAnimation;
element.data(NG_ANIMATE_STATE, {
last : lastAnimation,
active : runningAnimations,
index : localAnimationCount,
totalActive : totalActiveAnimations
});
//first we run the before animations and when all of those are complete
@@ -750,6 +816,11 @@ angular.module('ngAnimate', ['ng'])
invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
function onBeforeAnimationsComplete(cancelled) {
var data = element.data(NG_ANIMATE_STATE);
cancelled = cancelled ||
!data || !data.active[className] ||
(isClassBased && data.active[className].event != animationEvent);
fireDOMOperation();
if(cancelled === true) {
closeAnimation();
@@ -759,11 +830,8 @@ angular.module('ngAnimate', ['ng'])
//set the done function to the final done function
//so that the DOM event won't be executed twice by accident
//if the after animation is cancelled as well
var data = element.data(NG_ANIMATE_STATE);
if(data) {
data.done = closeAnimation;
element.data(NG_ANIMATE_STATE, data);
}
var currentAnimation = data.active[className];
currentAnimation.done = closeAnimation;
invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
}
@@ -786,9 +854,13 @@ angular.module('ngAnimate', ['ng'])
}
if(animation[phase]) {
animation[endFnName] = isClassBased ?
animation[phase](element, className, animationPhaseCompleted) :
animation[phase](element, animationPhaseCompleted);
if(setClassOperation) {
animation[endFnName] = animation[phase](element, classNameAdd, classNameRemove, animationPhaseCompleted);
} else {
animation[endFnName] = isClassBased ?
animation[phase](element, className, animationPhaseCompleted) :
animation[phase](element, animationPhaseCompleted);
}
} else {
animationPhaseCompleted();
}
@@ -809,29 +881,32 @@ angular.module('ngAnimate', ['ng'])
}
function fireDOMCallback(animationPhase) {
element.triggerHandler('$animate:' + animationPhase, {
event : animationEvent,
className : className
});
var eventName = '$animate:' + animationPhase;
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
$$asyncQueueBuffer(function() {
element.triggerHandler(eventName, {
event : animationEvent,
className : className
});
});
}
}
function fireBeforeCallbackAsync() {
async(function() {
fireDOMCallback('before');
});
fireDOMCallback('before');
}
function fireAfterCallbackAsync() {
async(function() {
fireDOMCallback('after');
});
fireDOMCallback('after');
}
function fireDoneCallbackAsync() {
async(function() {
fireDOMCallback('close');
doneCallback && doneCallback();
});
fireDOMCallback('close');
if(doneCallback) {
$$asyncQueueBuffer(function() {
doneCallback();
});
}
}
//it is less complicated to use a flag than managing and cancelling
@@ -853,10 +928,13 @@ angular.module('ngAnimate', ['ng'])
failing would be when a parent HTML tag has a ng-class attribute
causing ALL directives below to skip animations during the digest */
if(isClassBased) {
cleanup(element);
cleanup(element, className);
} else {
data.closeAnimationTimeout = async(function() {
cleanup(element);
$$asyncQueueBuffer(function() {
var data = element.data(NG_ANIMATE_STATE) || {};
if(localAnimationCount == data.index) {
cleanup(element, className, animationEvent);
}
});
element.data(NG_ANIMATE_STATE, data);
}
@@ -871,9 +949,11 @@ angular.module('ngAnimate', ['ng'])
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
element = angular.element(element);
var data = element.data(NG_ANIMATE_STATE);
if(data) {
cancelAnimations(data.animations);
cleanup(element);
if(data && data.active) {
angular.forEach(data.active, function(operation) {
(operation.done || noop)(true);
cancelAnimations(operation.animations);
});
}
});
}
@@ -890,15 +970,27 @@ angular.module('ngAnimate', ['ng'])
});
}
function cleanup(element) {
function cleanup(element, className) {
if(isMatchingElement(element, $rootElement)) {
if(!rootAnimateState.disabled) {
rootAnimateState.running = false;
rootAnimateState.structural = false;
}
} else {
element.removeClass(NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
} else if(className) {
var data = element.data(NG_ANIMATE_STATE) || {};
var removeAnimations = className === true;
if(!removeAnimations) {
if(data.active && data.active[className]) {
data.totalActive--;
delete data.active[className];
}
}
if(removeAnimations || !data.totalActive) {
element.removeClass(NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
}
}
}
@@ -917,7 +1009,7 @@ angular.module('ngAnimate', ['ng'])
var isRoot = isMatchingElement(parentElement, $rootElement);
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
var result = state && (!!state.disabled || !!state.running);
var result = state && (!!state.disabled || state.running || state.totalActive > 0);
if(isRoot || result) {
return result;
}
@@ -967,74 +1059,57 @@ angular.module('ngAnimate', ['ng'])
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5;
var ONE_SECOND = 1000;
var animationCounter = 0;
var lookupCache = {};
var parentCounter = 0;
var animationReflowQueue = [];
var animationElementQueue = [];
var cancelAnimationReflow;
var closingAnimationTime = 0;
var timeOut = false;
function afterReflow(element, callback) {
if(cancelAnimationReflow) {
cancelAnimationReflow();
}
animationReflowQueue.push(callback);
var node = extractElementNode(element);
element = angular.element(node);
animationElementQueue.push(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
var stagger = elementData.stagger;
var staggerTime = elementData.itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
var animationTime = (elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER;
closingAnimationTime = Math.max(closingAnimationTime, (staggerTime + animationTime) * ONE_SECOND);
//by placing a counter we can avoid an accidental
//race condition which may close an animation when
//a follow-up animation is midway in its animation
elementData.animationCount = animationCounter;
cancelAnimationReflow = $$animateReflow(function() {
forEach(animationReflowQueue, function(fn) {
fn();
});
//copy the list of elements so that successive
//animations won't conflict if they're added before
//the closing animation timeout has run
var elementQueueSnapshot = [];
var animationCounterSnapshot = animationCounter;
forEach(animationElementQueue, function(elm) {
elementQueueSnapshot.push(elm);
});
$timeout(function() {
closeAllAnimations(elementQueueSnapshot, animationCounterSnapshot);
elementQueueSnapshot = null;
}, closingAnimationTime, false);
animationReflowQueue = [];
animationElementQueue = [];
cancelAnimationReflow = null;
lookupCache = {};
closingAnimationTime = 0;
animationCounter++;
});
}
function closeAllAnimations(elements, count) {
var closingTimer = null;
var closingTimestamp = 0;
var animationElementQueue = [];
function animationCloseHandler(element, totalTime) {
var futureTimestamp = Date.now() + (totalTime * 1000);
if(futureTimestamp <= closingTimestamp) {
return;
}
$timeout.cancel(closingTimer);
var node = extractElementNode(element);
element = angular.element(node);
animationElementQueue.push(element);
closingTimestamp = futureTimestamp;
closingTimer = $timeout(function() {
closeAllAnimations(animationElementQueue);
animationElementQueue = [];
}, totalTime, false);
}
function closeAllAnimations(elements) {
forEach(elements, function(element) {
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(elementData && elementData.animationCount == count) {
if(elementData) {
(elementData.closeAnimationFn || noop)();
}
});
@@ -1119,12 +1194,12 @@ angular.module('ngAnimate', ['ng'])
return parentID + '-' + extractElementNode(element).className;
}
function animateSetup(element, className, calculationDecorator) {
function animateSetup(animationEvent, element, className, calculationDecorator) {
var cacheKey = getCacheKey(element);
var eventCacheKey = cacheKey + ' ' + className;
var stagger = {};
var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
var stagger = {};
if(itemIndex > 0) {
var staggerClassName = className + '-stagger';
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
@@ -1144,60 +1219,63 @@ angular.module('ngAnimate', ['ng'])
element.addClass(className);
var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
var timings = calculationDecorator(function() {
return getElementAnimationDetails(element, eventCacheKey);
});
/* there is no point in performing a reflow if the animation
timeout is empty (this would cause a flicker bug normally
in the page. There is also no point in performing an animation
that only has a delay and no duration */
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
if(maxDuration === 0) {
var transitionDuration = timings.transitionDuration;
var animationDuration = timings.animationDuration;
if(transitionDuration === 0 && animationDuration === 0) {
element.removeClass(className);
return false;
}
element.data(NG_ANIMATE_CSS_DATA_KEY, {
running : formerData.running || 0,
itemIndex : itemIndex,
stagger : stagger,
timings : timings,
closeAnimationFn : angular.noop
});
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
var activeClassName = '';
timings.transitionDuration > 0 ?
blockTransitions(element) :
var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
if(transitionDuration > 0) {
blockTransitions(element, className, isCurrentlyAnimating);
}
if(animationDuration > 0) {
blockKeyframeAnimations(element);
forEach(className.split(' '), function(klass, i) {
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
});
element.data(NG_ANIMATE_CSS_DATA_KEY, {
className : className,
activeClassName : activeClassName,
maxDuration : maxDuration,
maxDelay : maxDelay,
classes : className + ' ' + activeClassName,
timings : timings,
stagger : stagger,
itemIndex : itemIndex
});
}
return true;
}
function blockTransitions(element) {
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
function isStructuralAnimation(className) {
return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
}
function blockTransitions(element, className, isAnimating) {
if(isStructuralAnimation(className) || !isAnimating) {
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
} else {
element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
}
}
function blockKeyframeAnimations(element) {
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
}
function unblockTransitions(element) {
function unblockTransitions(element, className) {
var prop = TRANSITION_PROP + PROPERTY_KEY;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
}
function unblockKeyframeAnimations(element) {
@@ -1208,22 +1286,28 @@ angular.module('ngAnimate', ['ng'])
}
}
function animateRun(element, className, activeAnimationComplete) {
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
function animateRun(animationEvent, element, className, activeAnimationComplete) {
var node = extractElementNode(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(node.className.indexOf(className) == -1 || !elementData) {
activeAnimationComplete();
return;
}
var timings = elementData.timings;
var activeClassName = '';
forEach(className.split(' '), function(klass, i) {
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
});
var stagger = elementData.stagger;
var maxDuration = elementData.maxDuration;
var activeClassName = elementData.activeClassName;
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND;
var timings = elementData.timings;
var itemIndex = elementData.itemIndex;
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
var maxDelayTime = maxDelay * ONE_SECOND;
var startTime = Date.now();
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
var itemIndex = elementData.itemIndex;
var style = '', appliedStyles = [];
if(timings.transitionDuration > 0) {
@@ -1265,6 +1349,13 @@ angular.module('ngAnimate', ['ng'])
onEnd();
activeAnimationComplete();
};
var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
elementData.running++;
animationCloseHandler(element, totalTime);
return onEnd;
// This will automatically be called by $animate so
@@ -1284,7 +1375,7 @@ angular.module('ngAnimate', ['ng'])
event.stopPropagation();
var ev = event.originalEvent || event;
var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
/* Firefox (or possibly just Gecko) likes to not round values up
* when a ms measurement is used for the animation */
var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
@@ -1311,28 +1402,28 @@ angular.module('ngAnimate', ['ng'])
return style;
}
function animateBefore(element, className, calculationDecorator) {
if(animateSetup(element, className, calculationDecorator)) {
function animateBefore(animationEvent, element, className, calculationDecorator) {
if(animateSetup(animationEvent, element, className, calculationDecorator)) {
return function(cancelled) {
cancelled && animateClose(element, className);
};
}
}
function animateAfter(element, className, afterAnimationComplete) {
function animateAfter(animationEvent, element, className, afterAnimationComplete) {
if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
return animateRun(element, className, afterAnimationComplete);
return animateRun(animationEvent, element, className, afterAnimationComplete);
} else {
animateClose(element, className);
afterAnimationComplete();
}
}
function animate(element, className, animationComplete) {
function animate(animationEvent, element, className, animationComplete) {
//If the animateSetup function doesn't bother returning a
//cancellation function then it means that there is no animation
//to perform at all
var preReflowCancellation = animateBefore(element, className);
var preReflowCancellation = animateBefore(animationEvent, element, className);
if(!preReflowCancellation) {
animationComplete();
return;
@@ -1345,12 +1436,12 @@ angular.module('ngAnimate', ['ng'])
//happen in the first place
var cancel = preReflowCancellation;
afterReflow(element, function() {
unblockTransitions(element);
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
//once the reflow is complete then we point cancel to
//the new cancellation function which will remove all of the
//animation properties from the active animation
cancel = animateAfter(element, className, animationComplete);
cancel = animateAfter(animationEvent, element, className, animationComplete);
});
return function(cancelled) {
@@ -1360,54 +1451,59 @@ angular.module('ngAnimate', ['ng'])
function animateClose(element, className) {
element.removeClass(className);
element.removeData(NG_ANIMATE_CSS_DATA_KEY);
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(data) {
if(data.running) {
data.running--;
}
if(!data.running || data.running === 0) {
element.removeData(NG_ANIMATE_CSS_DATA_KEY);
}
}
}
return {
allowCancel : function(element, animationEvent, className) {
//always cancel the current animation if it is a
//structural animation
var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
return true;
}
var parentElement = element.parent();
var clone = angular.element(extractElementNode(element).cloneNode());
//make the element super hidden and override any CSS style values
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
clone.removeAttr('id');
clone.empty();
forEach(oldClasses.split(' '), function(klass) {
clone.removeClass(klass);
});
var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
clone.addClass(suffixClasses(className, suffix));
parentElement.append(clone);
var timings = getElementAnimationDetails(clone);
clone.remove();
return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
},
enter : function(element, animationCompleted) {
return animate(element, 'ng-enter', animationCompleted);
return animate('enter', element, 'ng-enter', animationCompleted);
},
leave : function(element, animationCompleted) {
return animate(element, 'ng-leave', animationCompleted);
return animate('leave', element, 'ng-leave', animationCompleted);
},
move : function(element, animationCompleted) {
return animate(element, 'ng-move', animationCompleted);
return animate('move', element, 'ng-move', animationCompleted);
},
beforeSetClass : function(element, add, remove, animationCompleted) {
var className = suffixClasses(remove, '-remove') + ' ' +
suffixClasses(add, '-add');
var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
/* when classes are removed from an element then the transition style
* that is applied is the transition defined on the element without the
* CSS class being there. This is how CSS3 functions outside of ngAnimate.
* http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
var klass = element.attr('class');
element.removeClass(remove);
element.addClass(add);
var timings = fn();
element.attr('class', klass);
return timings;
});
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
animationCompleted();
});
return cancellationMethod;
}
animationCompleted();
},
beforeAddClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'), function(fn) {
var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
/* when a CSS class is added to an element then the transition style that
* is applied is the transition defined on the element when the CSS class
@@ -1421,7 +1517,7 @@ angular.module('ngAnimate', ['ng'])
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
animationCompleted();
});
@@ -1430,12 +1526,19 @@ angular.module('ngAnimate', ['ng'])
animationCompleted();
},
setClass : function(element, add, remove, animationCompleted) {
remove = suffixClasses(remove, '-remove');
add = suffixClasses(add, '-add');
var className = remove + ' ' + add;
return animateAfter('setClass', element, className, animationCompleted);
},
addClass : function(element, className, animationCompleted) {
return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
},
beforeRemoveClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'), function(fn) {
var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
/* when classes are removed from an element then the transition style
* that is applied is the transition defined on the element without the
* CSS class being there. This is how CSS3 functions outside of ngAnimate.
@@ -1449,7 +1552,7 @@ angular.module('ngAnimate', ['ng'])
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
animationCompleted();
});
@@ -1459,7 +1562,7 @@ angular.module('ngAnimate', ['ng'])
},
removeClass : function(element, className, animationCompleted) {
return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
}
};
+2 -1
View File
@@ -782,7 +782,8 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
}
};
angular.forEach(['enter','leave','move','addClass','removeClass'], function(method) {
angular.forEach(
['enter','leave','move','addClass','removeClass','setClass'], function(method) {
animate[method] = function() {
animate.queue.push({
event : method,
+17
View File
@@ -97,6 +97,23 @@ describe('jqLite', function() {
});
});
describe('_data', function() {
it('should provide access to the data present on the element', function() {
var element = jqLite('<i>foo</i>');
var data = ['value'];
element.data('val', data);
expect(angular.element._data(element[0]).data.val).toBe(data);
dealoc(element);
});
it('should provide access to the events present on the element', function() {
var element = jqLite('<i>foo</i>');
expect(angular.element._data(element[0]).events).toBeUndefined();
element.on('click', function() { });
expect(angular.element._data(element[0]).events.click).toBeDefined();
});
});
describe('inheritedData', function() {
+150 -4
View File
@@ -517,6 +517,22 @@ describe('$compile', function() {
expect(element).toBe(attr.$$element);
}
}));
directive('replaceWithTr', valueFn({
replace: true,
template: '<tr><td>TR</td></tr>'
}));
directive('replaceWithTd', valueFn({
replace: true,
template: '<td>TD</td>'
}));
directive('replaceWithTh', valueFn({
replace: true,
template: '<th>TH</th>'
}));
directive('replaceWithTbody', valueFn({
replace: true,
template: '<tbody><tr><td>TD</td></tr></tbody>'
}));
}));
@@ -680,6 +696,34 @@ describe('$compile', function() {
}).not.toThrow();
});
});
it('should support templates with root <tr> tags', inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div replace-with-tr></div>')($rootScope);
}).not.toThrow();
expect(nodeName_(element)).toMatch(/tr/i);
}));
it('should support templates with root <td> tags', inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div replace-with-td></div>')($rootScope);
}).not.toThrow();
expect(nodeName_(element)).toMatch(/td/i);
}));
it('should support templates with root <th> tags', inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div replace-with-th></div>')($rootScope);
}).not.toThrow();
expect(nodeName_(element)).toMatch(/th/i);
}));
it('should support templates with root <tbody> tags', inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div replace-with-tbody></div>')($rootScope);
}).not.toThrow();
expect(nodeName_(element)).toMatch(/tbody/i);
}));
});
@@ -776,6 +820,23 @@ describe('$compile', function() {
replace: true,
template: '<span>Hello, {{name}}!</span>'
}));
directive('replaceWithTr', valueFn({
replace: true,
templateUrl: 'tr.html'
}));
directive('replaceWithTd', valueFn({
replace: true,
templateUrl: 'td.html'
}));
directive('replaceWithTh', valueFn({
replace: true,
templateUrl: 'th.html'
}));
directive('replaceWithTbody', valueFn({
replace: true,
templateUrl: 'tbody.html'
}));
}
));
@@ -1411,6 +1472,42 @@ describe('$compile', function() {
expect(element.html()).toContain('i = 1');
});
});
it('should support templates with root <tr> tags', inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('tr.html', '<tr><td>TR</td></tr>');
expect(function() {
element = $compile('<div replace-with-tr></div>')($rootScope);
}).not.toThrow();
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/tr/i);
}));
it('should support templates with root <td> tags', inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('td.html', '<td>TD</td>');
expect(function() {
element = $compile('<div replace-with-td></div>')($rootScope);
}).not.toThrow();
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/td/i);
}));
it('should support templates with root <th> tags', inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('th.html', '<th>TH</th>');
expect(function() {
element = $compile('<div replace-with-th></div>')($rootScope);
}).not.toThrow();
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/th/i);
}));
it('should support templates with root <tbody> tags', inject(function($compile, $rootScope, $templateCache) {
$templateCache.put('tbody.html', '<tbody><tr><td>TD</td></tr></tbody>');
expect(function() {
element = $compile('<div replace-with-tbody></div>')($rootScope);
}).not.toThrow();
$rootScope.$digest();
expect(nodeName_(element)).toMatch(/tbody/i);
}));
});
@@ -3972,6 +4069,57 @@ describe('$compile', function() {
});
});
// issue #6006
it('should link directive with $element as a comment node', function() {
module(function($provide) {
directive('innerAgain', function(log) {
return {
transclude: 'element',
link: function(scope, element, attr, controllers, transclude) {
log('innerAgain:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
transclude(scope, function(clone) {
element.parent().append(clone);
});
}
};
});
directive('inner', function(log) {
return {
replace: true,
templateUrl: 'inner.html',
link: function(scope, element) {
log('inner:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
}
};
});
directive('outer', function(log) {
return {
transclude: 'element',
link: function(scope, element, attrs, controllers, transclude) {
log('outer:'+lowercase(nodeName_(element))+':'+trim(element[0].data));
transclude(scope, function(clone) {
element.parent().append(clone);
});
}
};
});
});
inject(function(log, $compile, $rootScope, $templateCache) {
$templateCache.put('inner.html', '<div inner-again><p>Content</p></div>');
element = $compile('<div><div outer><div inner></div></div></div>')($rootScope);
$rootScope.$digest();
var child = element.children();
expect(log.toArray()).toEqual([
"outer:#comment:outer:",
"innerAgain:#comment:innerAgain:",
"inner:#comment:innerAgain:"]);
expect(child.length).toBe(1);
expect(child.contents().length).toBe(2);
expect(lowercase(nodeName_(child.contents().eq(0)))).toBe('#comment');
expect(lowercase(nodeName_(child.contents().eq(1)))).toBe('div');
});
});
});
it('should safely create transclude comment node and not break with "-->"',
@@ -4518,11 +4666,9 @@ describe('$compile', function() {
$rootScope.$digest();
data = $animate.queue.shift();
expect(data.event).toBe('removeClass');
expect(data.args[1]).toBe('rice');
data = $animate.queue.shift();
expect(data.event).toBe('addClass');
expect(data.event).toBe('setClass');
expect(data.args[1]).toBe('dice');
expect(data.args[2]).toBe('rice');
expect(element.hasClass('ice')).toBe(true);
expect(element.hasClass('dice')).toBe(true);
+11
View File
@@ -509,6 +509,17 @@ describe('input', function() {
});
}
it('should update the model on "compositionend"', function() {
compileInput('<input type="text" ng-model="name" name="alias" />');
if (!(msie < 9)) {
browserTrigger(inputElm, 'compositionstart');
changeInputValueTo('caitp');
expect(scope.name).toBeUndefined();
browserTrigger(inputElm, 'compositionend');
expect(scope.name).toEqual('caitp');
}
});
describe('"change" event', function() {
function assertBrowserSupportsChangeEvent(inputEventSupported) {
// Force browser to report a lack of an 'input' event
+3 -7
View File
@@ -335,8 +335,7 @@ describe('ngClass animations', function() {
$rootScope.val = 'two';
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('removeClass');
expect($animate.queue.shift().event).toBe('addClass');
expect($animate.queue.shift().event).toBe('setClass');
expect($animate.queue.length).toBe(0);
});
});
@@ -450,12 +449,9 @@ describe('ngClass animations', function() {
$rootScope.$digest();
item = $animate.queue.shift();
expect(item.event).toBe('removeClass');
expect(item.args[1]).toBe('two');
item = $animate.queue.shift();
expect(item.event).toBe('addClass');
expect(item.event).toBe('setClass');
expect(item.args[1]).toBe('three');
expect(item.args[2]).toBe('two');
expect($animate.queue.length).toBe(0);
});
+11
View File
@@ -70,6 +70,17 @@ describe('Filter: filter', function() {
});
it('should support deep predicate objects', function() {
var items = [{person: {name: 'John'}},
{person: {name: 'Rita'}},
{person: {name: 'Billy'}},
{person: {name: 'Joan'}}];
expect(filter(items, {person: {name: 'Jo'}}).length).toBe(2);
expect(filter(items, {person: {name: 'Jo'}})).toEqual([
{person: {name: 'John'}}, {person: {name: 'Joan'}}]);
});
it('should match any properties for given "$" property', function() {
var items = [{first: 'tom', last: 'hevery'},
{first: 'adam', last: 'hevery', alias: 'tom', done: false},
+84 -93
View File
@@ -482,7 +482,7 @@ describe("ngAnimate", function() {
element.append(child);
child.attr('style', 'width: 20px');
$animate.addClass(child, 'ng-hide');
$animate.leave(child);
$rootScope.$digest();
@@ -490,11 +490,11 @@ describe("ngAnimate", function() {
if($sniffer.transitions) {
$animate.triggerReflow();
//this is to verify that the existing style is appended with a semicolon automatically
expect(child.attr('style')).toMatch(/width: 20px;.+?/i);
//this is to verify that the existing style is appended with a semicolon automatically
expect(child.attr('style')).toMatch(/width: 20px;.*?/i);
browserTrigger(child,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
expect(child.attr('style')).toMatch(/width: 20px/i);
}));
@@ -523,7 +523,7 @@ describe("ngAnimate", function() {
var completed = false;
$animate.enter(child, element, null, function() {
completed = true;
completed = true;
});
$rootScope.$digest();
@@ -564,7 +564,7 @@ describe("ngAnimate", function() {
});
});
it("should fire the cancel/end function with the correct flag in the parameters",
it("should not apply a cancellation when addClass is done multiple times",
inject(function($animate, $rootScope, $sniffer, $timeout) {
element.append(child);
@@ -572,7 +572,7 @@ describe("ngAnimate", function() {
$animate.addClass(child, 'custom-delay');
$animate.addClass(child, 'custom-long-delay');
expect(child.hasClass('animation-cancelled')).toBe(true);
expect(child.hasClass('animation-cancelled')).toBe(false);
expect(child.hasClass('animation-ended')).toBe(false);
$timeout.flush();
@@ -764,7 +764,6 @@ describe("ngAnimate", function() {
$animate.addClass(element, 'ng-hide');
expect(element.hasClass('ng-hide-remove')).toBe(false); //added right away
if($sniffer.animations) { //cleanup some pending animations
$animate.triggerReflow();
expect(element.hasClass('ng-hide-add')).toBe(true);
@@ -784,19 +783,19 @@ describe("ngAnimate", function() {
$animate.enabled(true);
ss.addRule('.real-animation.ng-enter, .real-animation.ng-leave, .real-animation-fake.ng-enter, .real-animation-fake.ng-leave',
'-webkit-animation:1s my_animation;' +
'-webkit-animation:1s my_animation;' +
'animation:1s my_animation;');
ss.addRule('.real-animation.ng-enter-stagger, .real-animation.ng-leave-stagger',
'-webkit-animation-delay:0.1s;' +
'-webkit-animation-duration:0s;' +
'animation-delay:0.1s;' +
'animation-delay:0.1s;' +
'animation-duration:0s;');
ss.addRule('.fake-animation.ng-enter-stagger, .fake-animation.ng-leave-stagger',
'-webkit-animation-delay:0.1s;' +
'-webkit-animation-duration:1s;' +
'animation-delay:0.1s;' +
'animation-delay:0.1s;' +
'animation-duration:1s;');
var container = $compile(html('<div></div>'))($rootScope);
@@ -851,7 +850,7 @@ describe("ngAnimate", function() {
$animate.enabled(true);
ss.addRule('.stagger-animation.ng-enter, .stagger-animation.ng-leave',
'-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' +
'-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' +
'animation:my_animation 1s 1s, your_animation 1s 2s;');
ss.addRule('.stagger-animation.ng-enter-stagger, .stagger-animation.ng-leave-stagger',
@@ -1075,19 +1074,19 @@ describe("ngAnimate", function() {
$animate.enabled(true);
ss.addRule('.real-animation.ng-enter, .real-animation.ng-leave, .real-animation-fake.ng-enter, .real-animation-fake.ng-leave',
'-webkit-transition:1s linear all;' +
'-webkit-transition:1s linear all;' +
'transition:1s linear all;');
ss.addRule('.real-animation.ng-enter-stagger, .real-animation.ng-leave-stagger',
'-webkit-transition-delay:0.1s;' +
'-webkit-transition-duration:0s;' +
'transition-delay:0.1s;' +
'transition-delay:0.1s;' +
'transition-duration:0s;');
ss.addRule('.fake-animation.ng-enter-stagger, .fake-animation.ng-leave-stagger',
'-webkit-transition-delay:0.1s;' +
'-webkit-transition-duration:1s;' +
'transition-delay:0.1s;' +
'transition-delay:0.1s;' +
'transition-duration:1s;');
var container = $compile(html('<div></div>'))($rootScope);
@@ -1143,7 +1142,7 @@ describe("ngAnimate", function() {
$animate.enabled(true);
ss.addRule('.stagger-animation.ng-enter, .ani.ng-leave',
'-webkit-transition:1s linear color 2s, 3s linear font-size 4s;' +
'-webkit-transition:1s linear color 2s, 3s linear font-size 4s;' +
'transition:1s linear color 2s, 3s linear font-size 4s;');
ss.addRule('.stagger-animation.ng-enter-stagger, .ani.ng-leave-stagger',
@@ -1235,7 +1234,7 @@ describe("ngAnimate", function() {
it("should not allow the closing animation to close off a successive animation midway",
inject(function($animate, $rootScope, $compile, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.some-class-add', '-webkit-transition:5s linear all;' +
@@ -1273,9 +1272,9 @@ describe("ngAnimate", function() {
$animate.enabled(true);
ss.addRule('.stagger-animation.ng-enter, .stagger-animation.ng-leave',
'-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' +
'-webkit-animation:my_animation 1s 1s, your_animation 1s 2s;' +
'animation:my_animation 1s 1s, your_animation 1s 2s;' +
'-webkit-transition:1s linear all 1s;' +
'-webkit-transition:1s linear all 1s;' +
'transition:1s linear all 1s;');
ss.addRule('.stagger-animation.ng-enter-stagger, .stagger-animation.ng-leave-stagger',
@@ -1472,6 +1471,8 @@ describe("ngAnimate", function() {
expect(flag).toBe(true);
expect(element.parent().id).toBe(parent2.id);
dealoc(element);
}));
@@ -1543,7 +1544,7 @@ describe("ngAnimate", function() {
expect(steps.shift()).toEqual(['close', 'klass', 'addClass']);
expect(steps.shift()).toEqual(['done', 'klass', 'addClass']);
}));
}));
it('should fire the DOM callbacks even if no animation is rendered',
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
@@ -1571,7 +1572,22 @@ describe("ngAnimate", function() {
expect(steps.shift()).toEqual(['before', 'ng-enter', 'enter']);
expect(steps.shift()).toEqual(['after', 'ng-enter', 'enter']);
}));
}));
it('should not fire DOM callbacks on the element being animated unless registered',
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
$animate.enabled(true);
var element = jqLite('<div></div>');
$rootElement.append(element);
body.append($rootElement);
$animate.addClass(element, 'class');
$rootScope.$digest();
$timeout.verifyNoPendingTasks();
}));
it("should fire a done callback when provided with no animation",
inject(function($animate, $rootScope, $compile, $sniffer, $rootElement, $timeout) {
@@ -1605,11 +1621,12 @@ describe("ngAnimate", function() {
var element = parent.find('span');
var flag = false;
$animate.removeClass(element, 'ng-hide', function() {
$animate.addClass(element, 'ng-hide', function() {
flag = true;
});
if($sniffer.transitions) {
$animate.triggerReflow();
browserTrigger(element,'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
}
$timeout.flush();
@@ -2534,7 +2551,7 @@ describe("ngAnimate", function() {
}
});
});
inject(function($compile, $rootScope, $animate, $timeout, $rootElement) {
$animate.enabled(true);
@@ -2563,8 +2580,9 @@ describe("ngAnimate", function() {
});
it("should disable all child animations on structural animations until the post animation timeout has passed", function() {
var intercepted;
it("should disable all child animations on structural animations until the post animation" +
"timeout has passed as well as all structural animations", function() {
var intercepted, continueAnimation;
module(function($animateProvider) {
$animateProvider.register('.animated', function() {
return {
@@ -2578,7 +2596,10 @@ describe("ngAnimate", function() {
function ani(type) {
return function(element, className, done) {
intercepted = type;
(done || className)();
continueAnimation = function() {
continueAnimation = angular.noop;
(done || className)();
}
}
}
});
@@ -2595,26 +2616,45 @@ describe("ngAnimate", function() {
var child2 = $compile('<div class="child2 animated">...</div>')($rootScope);
var container = $compile('<div class="container">...</div>')($rootScope);
jqLite($document[0].body).append($rootElement);
var body = angular.element($document[0].body);
body.append($rootElement);
$rootElement.append(container);
element.append(child1);
element.append(child2);
$animate.enter(element, container);
$rootScope.$digest();
expect(intercepted).toBe('enter');
continueAnimation();
$animate.addClass(child1, 'test');
expect(child1.hasClass('test')).toBe(true);
expect(element.children().length).toBe(2);
expect(intercepted).toBe('enter');
$animate.leave(child1);
$rootScope.$digest();
expect(element.children().length).toBe(1);
expect(intercepted).toBe('enter');
$animate.move(element, null, container);
$rootScope.$digest();
expect(intercepted).toBe('move');
$animate.addClass(child1, 'test');
expect(child1.hasClass('test')).toBe(true);
expect(intercepted).toBe('move');
$animate.leave(child1);
$rootScope.$digest();
//flush the enter reflow
$timeout.flush();
$animate.addClass(child2, 'testing');
expect(intercepted).toBe('move');
//reflow has passed
continueAnimation();
//flush the move reflow
$timeout.flush();
$animate.leave(child2);
@@ -2696,42 +2736,6 @@ describe("ngAnimate", function() {
});
it("should cancel an ongoing class-based animation only if the new class contains transition/animation CSS code",
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
if (!$sniffer.transitions) return;
ss.addRule('.green-add', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
ss.addRule('.blue-add', 'background:blue;');
ss.addRule('.red-add', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
ss.addRule('.yellow-add', '-webkit-animation: some_animation 4s linear 1s 2 alternate;' +
'animation: some_animation 4s linear 1s 2 alternate;');
var element = $compile('<div></div>')($rootScope);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$animate.addClass(element, 'green');
expect(element.hasClass('green-add')).toBe(true);
$animate.addClass(element, 'blue');
expect(element.hasClass('blue')).toBe(true);
expect(element.hasClass('green-add')).toBe(true); //not cancelled
$animate.addClass(element, 'red');
expect(element.hasClass('green-add')).toBe(false);
expect(element.hasClass('red-add')).toBe(true);
$animate.addClass(element, 'yellow');
expect(element.hasClass('red-add')).toBe(false);
expect(element.hasClass('yellow-add')).toBe(true);
}));
it("should cancel and perform the dom operation only after the reflow has run",
inject(function($compile, $rootScope, $animate, $sniffer, $timeout) {
@@ -2799,7 +2803,7 @@ describe("ngAnimate", function() {
$animate.removeClass(element, 'on');
$animate.addClass(element, 'on');
expect(currentAnimation).toBe(null);
expect(currentAnimation).toBe('addClass');
});
});
@@ -2828,7 +2832,7 @@ describe("ngAnimate", function() {
it('should perform pre and post animations', function() {
var steps = [];
var steps = [];
module(function($animateProvider) {
$animateProvider.register('.class-animate', function() {
return {
@@ -2857,7 +2861,7 @@ describe("ngAnimate", function() {
it('should treat the leave event always as a before event and discard the beforeLeave function', function() {
var parentID, steps = [];
var parentID, steps = [];
module(function($animateProvider) {
$animateProvider.register('.animate', function() {
return {
@@ -3076,7 +3080,7 @@ describe("ngAnimate", function() {
var element = $compile('<div>' +
' <div ng-repeat="item in items"' +
' ng-include="tpl"' +
' class="special"></div>' +
' class="special"></div>' +
'</div>')($rootScope);
ss.addRule('.special', '-webkit-transition:1s linear all;' +
@@ -3221,7 +3225,7 @@ describe("ngAnimate", function() {
expect(ready).toBe(true);
}));
it('should avoid skip animations if the same CSS class is added / removed synchronously before the reflow kicks in',
it('should immediately close the former animation if the same CSS class is added/removed',
inject(function($sniffer, $compile, $rootScope, $rootElement, $animate, $timeout) {
if (!$sniffer.transitions) return;
@@ -3243,28 +3247,15 @@ describe("ngAnimate", function() {
signature += 'B';
});
$timeout.flush(1);
expect(signature).toBe('AB');
signature = '';
$animate.removeClass(element, 'on', function() {
signature += 'A';
});
$animate.addClass(element, 'on', function() {
signature += 'B';
});
$animate.removeClass(element, 'on', function() {
signature += 'C';
});
$timeout.flush(1);
expect(signature).toBe('AB');
$animate.triggerReflow();
$timeout.flush(1);
expect(signature).toBe('A');
browserTrigger(element, 'transitionend', { timeStamp: Date.now(), elapsedTime: 2000 });
$timeout.flush(1);
expect(signature).toBe('ABC');
expect(signature).toBe('AB');
}));
});
});
+1 -2
View File
@@ -767,8 +767,7 @@ describe('ngView animations', function() {
$rootScope.klass = 'boring';
$rootScope.$digest();
expect($animate.queue.shift().event).toBe('removeClass');
expect($animate.queue.shift().event).toBe('addClass');
expect($animate.queue.shift().event).toBe('setClass');
expect(item.hasClass('classy')).toBe(false);
expect(item.hasClass('boring')).toBe(true);