Compare commits
238 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23c4ae522a | |||
| 3112f8e910 | |||
| 21ab82906e | |||
| 1dd206ef85 | |||
| da5db4b1b3 | |||
| 3694390c90 | |||
| 36a3c81177 | |||
| b0e7d548d0 | |||
| e4bb838795 | |||
| 0ea535035a | |||
| e9aba90f7f | |||
| f50b0cb393 | |||
| 8b3bec7e07 | |||
| 6752337629 | |||
| 48ad7486d9 | |||
| 341b834229 | |||
| 7a668cdd7d | |||
| 5a674f3bb9 | |||
| e94b37e20e | |||
| f5ebcbacf8 | |||
| 0812af49bd | |||
| c3ae6ed78e | |||
| c3a2691115 | |||
| 6976d6d8d8 | |||
| b75c0d8d05 | |||
| df6fade6e6 | |||
| 983b059812 | |||
| 25e8c5927c | |||
| 6628b4f1e5 | |||
| 6e18b50a5b | |||
| 77419cf19f | |||
| 193153c3d3 | |||
| 6a0686d434 | |||
| 08c9a5e9e7 | |||
| a72c12bd70 | |||
| 73e38658c4 | |||
| b641181b93 | |||
| 7ffb2d3c17 | |||
| 023b777a56 | |||
| 0c9480de8c | |||
| 0bc275461c | |||
| 0b5ecc64f0 | |||
| b183eae7ae | |||
| 7ddbc9aa35 | |||
| b2a937d425 | |||
| 4473b81cda | |||
| 98528be311 | |||
| 9190d4c3ad | |||
| d9ec9951e2 | |||
| fedafdc677 | |||
| 26c36bb4d1 | |||
| c7a2028ab3 | |||
| 596af70101 | |||
| 96c73a0672 | |||
| 1537651c8c | |||
| d59aeb4e0b | |||
| 551a33db56 | |||
| cd91640146 | |||
| ab5824ee12 | |||
| 5c50723535 | |||
| 546a277d65 | |||
| a5ff651a59 | |||
| ccd47ec904 | |||
| 2e23a3cdbc | |||
| ca7f4a387c | |||
| 898a3fd3b9 | |||
| 9473371343 | |||
| e5275590db | |||
| 78297d252d | |||
| e5e0884eaf | |||
| 551d1c20cf | |||
| f5aa207960 | |||
| 4412fe238f | |||
| 8088284f66 | |||
| 25f1bbaad1 | |||
| bd7b217729 | |||
| 50557a6cd3 | |||
| 1c13a4f45d | |||
| 1a98c0ee34 | |||
| b837fc3116 | |||
| aff74ec87b | |||
| 077ee37942 | |||
| 0efef2385f | |||
| 7da22e6685 | |||
| 92bdd7627f | |||
| 39ebb06baf | |||
| 9cf6b197ab | |||
| 4971ef12d4 | |||
| 7c792f4cc9 | |||
| 4daafd3dbe | |||
| fe11265fdc | |||
| 75292a6cb5 | |||
| 4ff6c85792 | |||
| e26bc2370b | |||
| 33713deeb8 | |||
| 17715fa366 | |||
| 96288d02d3 | |||
| 8be98e4fdf | |||
| cf83b4f445 | |||
| 15bfea8339 | |||
| 9b90c32f31 | |||
| f4bf744516 | |||
| 0d96995fcc | |||
| 2a85a634f8 | |||
| 00d2b2c4cf | |||
| e688f07023 | |||
| 8e2f7d37e4 | |||
| f5bc3ed9b4 | |||
| 11d60af3dc | |||
| 865f6065e7 | |||
| 91c0b364af | |||
| fce07f55e5 | |||
| c8768d12f2 | |||
| af6342d6fb | |||
| a179757fad | |||
| 35eada68c4 | |||
| bfad2a4f4a | |||
| 54e816552f | |||
| 4fc734665e | |||
| b8736e65b0 | |||
| b9bed7d9da | |||
| b2fc39d2dd | |||
| 7c0731edb2 | |||
| 8fe781fbe7 | |||
| 23932a26ff | |||
| 3ca4ca463c | |||
| 395f3ec638 | |||
| 964a901bd8 | |||
| a995ee17ee | |||
| 794d1c1ebe | |||
| 662fb282c1 | |||
| ffb6b2fb56 | |||
| 941c1c35f1 | |||
| 29a05984fe | |||
| 4038aabffa | |||
| 773efd0812 | |||
| 80881949fc | |||
| 2c8d87e064 | |||
| 33c67ce785 | |||
| ad4296d966 | |||
| 469b14a525 | |||
| 1caf0b6bee | |||
| 9f716dd590 | |||
| 914a934b6f | |||
| a4ada8ba9c | |||
| 40c974ab14 | |||
| 06f002b161 | |||
| 8226ff8b8e | |||
| d67e999dfb | |||
| a8e03b3a90 | |||
| 6f3e26c404 | |||
| 1c75ea613d | |||
| c8e1db2050 | |||
| 74ed28665d | |||
| 4ad0ca130d | |||
| b51dd3010d | |||
| 6e6f31943c | |||
| 66fee7e22a | |||
| beea571660 | |||
| 0c8a9a0e1a | |||
| fd83d3724a | |||
| 2fcfd75a14 | |||
| 2d40507547 | |||
| ecf9304811 | |||
| b9ab88776b | |||
| 00bf218304 | |||
| 95fbf168d1 | |||
| 51a27c0f1a | |||
| f02811f0bb | |||
| bcf78ebb18 | |||
| 3050dd1b47 | |||
| 1efdb4745a | |||
| d73f7dff45 | |||
| f047ad2628 | |||
| 049d3def80 | |||
| 00778aa239 | |||
| 6b123a0419 | |||
| 4079eea6b3 | |||
| d277ac2eb8 | |||
| 9deb123d04 | |||
| 1c2d2e8ba0 | |||
| 144bcc84ab | |||
| 693021c449 | |||
| dc818e1165 | |||
| e51174bf13 | |||
| 7f3f3dd3eb | |||
| d077966ff1 | |||
| 0df4ff800a | |||
| 6208b76afa | |||
| 70dac5ae82 | |||
| aafbd94439 | |||
| e732f8e579 | |||
| 9f67da6252 | |||
| b3a3c6a72e | |||
| e8cdabe129 | |||
| 3af71bee75 | |||
| f2724b2bbc | |||
| 38500669f6 | |||
| 862c9d8bb2 | |||
| 240d5896ec | |||
| 64ef084b91 | |||
| 8b27c3f064 | |||
| 8366622bed | |||
| d3de0066b0 | |||
| b7e5133b2e | |||
| 42c97c5db5 | |||
| e1f4f23f78 | |||
| 630280c7fb | |||
| 496a67c10e | |||
| 7dcfe5e03e | |||
| 9e83b8355e | |||
| 03726f7fbd | |||
| 75893ae9e7 | |||
| d9ca245917 | |||
| 3259aabdf3 | |||
| befeeb3689 | |||
| 79577c5d31 | |||
| b71d7c3f3c | |||
| fa4c7b7f1d | |||
| 652b83eb22 | |||
| 20cf7d5e3a | |||
| ded2518756 | |||
| b366f0352a | |||
| 76c2491a31 | |||
| ef3df93afe | |||
| 106f90aafa | |||
| 86e8088b38 | |||
| a654bdfed9 | |||
| 2c22c57e58 | |||
| a4dfa4d061 | |||
| bc0d8c4eea | |||
| 9b84fcad76 | |||
| f33ce173c9 | |||
| 181fc567d8 | |||
| 94207f8fb6 | |||
| 35a21532b7 | |||
| 1c97a6057b | |||
| 658a865c5b |
+12
-11
@@ -19,10 +19,10 @@ env:
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit BROWSER_PROVIDER=browserstack
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
|
||||
# - JOB=unit BROWSER_PROVIDER=browserstack
|
||||
# - JOB=docs-e2e BROWSER_PROVIDER=browserstack
|
||||
# - JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
|
||||
# - JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
|
||||
global:
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
@@ -31,12 +31,12 @@ env:
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
|
||||
#matrix:
|
||||
# allow_failures:
|
||||
# - env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
# - env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
|
||||
# - env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
|
||||
# - env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
|
||||
|
||||
install:
|
||||
# Check the size of caches
|
||||
@@ -48,7 +48,7 @@ install:
|
||||
- npm config set loglevel http
|
||||
- npm install -g npm@2.5
|
||||
# Instal npm dependecies and ensure that npm cache is not stale
|
||||
- scripts/npm/install-dependencies.sh
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
@@ -61,6 +61,7 @@ script:
|
||||
- ./scripts/travis/build.sh
|
||||
|
||||
after_script:
|
||||
- ./scripts/travis/tear_down_browser_provider.sh
|
||||
- ./scripts/travis/print_logs.sh
|
||||
|
||||
notifications:
|
||||
|
||||
+772
-5
@@ -1,3 +1,717 @@
|
||||
<a name="1.5.0-rc.0"></a>
|
||||
# 1.5.0-rc.0 oblong-panoptikum (2015-12-09)
|
||||
|
||||
This is the first Release Candidate for AngularJS 1.5.0. Please try upgrading your applications and
|
||||
report any regressions or other issues you find as soon as possible.
|
||||
|
||||
## Features
|
||||
|
||||
- **$parse:** provide a mechanism to access the locals object, `$locals`
|
||||
([0ea53503](https://github.com/angular/angular.js/commit/0ea535035a3a1a992948490c3533bffb83235052))
|
||||
|
||||
- **$resource:** add proper support for cancelling requests, `$cancelRequest()`
|
||||
([98528be3](https://github.com/angular/angular.js/commit/98528be311b48269ba0e15ba4e3e2ad9b89693a9),
|
||||
[#9332](https://github.com/angular/angular.js/issues/9332), [#13050](https://github.com/angular/angular.js/issues/13050), [#13058](https://github.com/angular/angular.js/issues/13058), [#13210](https://github.com/angular/angular.js/issues/13210))
|
||||
|
||||
- **ngAnimate:** provide ng-[event]-prepare class for structural animations
|
||||
([6e18b50a](https://github.com/angular/angular.js/commit/6e18b50a5b168848cc526081b0a2a16075ee44bd))
|
||||
|
||||
- **ngLocale:** add support for standalone months
|
||||
([96c73a06](https://github.com/angular/angular.js/commit/96c73a0672f0e46ae9285c482b057bd03ce135ba),
|
||||
[#3744](https://github.com/angular/angular.js/issues/3744), [#10247](https://github.com/angular/angular.js/issues/10247), [#12642](https://github.com/angular/angular.js/issues/12642), [#12844](https://github.com/angular/angular.js/issues/12844))
|
||||
|
||||
- **ngMock:** destroy $rootScope after each test
|
||||
([b75c0d8d](https://github.com/angular/angular.js/commit/b75c0d8d0549261ece551210a11d8be48c3ab3cc),
|
||||
[#13433](https://github.com/angular/angular.js/issues/13433))
|
||||
|
||||
- **ngTransclude:** don't overwrite the contents with an unfilled optional slot
|
||||
([0812af49](https://github.com/angular/angular.js/commit/0812af49bd4f4fad4067603ff64dbe720bd6e3e5),
|
||||
[#13426](https://github.com/angular/angular.js/issues/13426))
|
||||
|
||||
- **ngView:** reference resolved locals in scope, `resolveAs: '$resolve'`
|
||||
([983b0598](https://github.com/angular/angular.js/commit/983b0598121a8c5a3a51a30120e114d7e3085d4d),
|
||||
[#13400](https://github.com/angular/angular.js/issues/13400))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- swap keys and values for transclude definition object
|
||||
([c3a26911](https://github.com/angular/angular.js/commit/c3a2691115b92536fb3d213d0ca16ac68cf32415),
|
||||
[#13439](https://github.com/angular/angular.js/issues/13439))
|
||||
- include non-elements in default transclusion slot
|
||||
([df6fade6](https://github.com/angular/angular.js/commit/df6fade6e67cfbfb5295bab3703ba2054d48daa7))
|
||||
- support merging special attribute names in `replace` directives
|
||||
([a5ff651a](https://github.com/angular/angular.js/commit/a5ff651a59933c2c43b81642454ee458f98e1401),
|
||||
[#13317](https://github.com/angular/angular.js/issues/13317), [#13318](https://github.com/angular/angular.js/issues/13318))
|
||||
|
||||
- **$http:** throw if url passed is not a string
|
||||
([6628b4f1](https://github.com/angular/angular.js/commit/6628b4f1e5835d997290881c6ba394547883a516),
|
||||
[#12925](https://github.com/angular/angular.js/issues/12925), [#13444](https://github.com/angular/angular.js/issues/13444))
|
||||
|
||||
- **$parse:**
|
||||
- prevent assignment on constructor properties
|
||||
([5a674f3b](https://github.com/angular/angular.js/commit/5a674f3bb9d1118d11b333e3b966c01a571c09e6),
|
||||
[#13417](https://github.com/angular/angular.js/issues/13417))
|
||||
- handle interceptors with `undefined` expressions
|
||||
([4473b81c](https://github.com/angular/angular.js/commit/4473b81cdaf16c5509ac53d80b9bdfb0a7ac5f30))
|
||||
|
||||
- **$sanitize:** blacklist SVG `<use>` elements
|
||||
([7a668cdd](https://github.com/angular/angular.js/commit/7a668cdd7d08a7016883eb3c671cbcd586223ae8),
|
||||
[#13453](https://github.com/angular/angular.js/issues/13453))
|
||||
|
||||
- **formatNumber:** cope with large and small number corner cases
|
||||
([6a0686d4](https://github.com/angular/angular.js/commit/6a0686d434c41445c50b2d9669073802ede77b3b),
|
||||
[#13394](https://github.com/angular/angular.js/issues/13394), [#8674](https://github.com/angular/angular.js/issues/8674), [#12709](https://github.com/angular/angular.js/issues/12709), [#8705](https://github.com/angular/angular.js/issues/8705), [#12707](https://github.com/angular/angular.js/issues/12707), [#10246](https://github.com/angular/angular.js/issues/10246), [#10252](https://github.com/angular/angular.js/issues/10252))
|
||||
|
||||
- **input:** add missing chars to URL validation regex
|
||||
([e4bb8387](https://github.com/angular/angular.js/commit/e4bb8387952069cca9da06bbc5c87ae576c2bf6f),
|
||||
[#13379](https://github.com/angular/angular.js/issues/13379), [#13460](https://github.com/angular/angular.js/issues/13460))
|
||||
|
||||
- **ngAnimate:**
|
||||
- consider options.delay value for closing timeout
|
||||
([7ffb2d3c](https://github.com/angular/angular.js/commit/7ffb2d3c17643303a51eb4e324c365af70fe3824),
|
||||
[#13355](https://github.com/angular/angular.js/issues/13355), [#13363](https://github.com/angular/angular.js/issues/13363))
|
||||
- ensure animate runner is the same with and without animations
|
||||
([546a277d](https://github.com/angular/angular.js/commit/546a277d65a3e075178d9b6d7ea6abcebc4bc04b),
|
||||
[#13205](https://github.com/angular/angular.js/issues/13205), [#13347](https://github.com/angular/angular.js/issues/13347))
|
||||
- ignore children without animation data when closing them
|
||||
([77419cf1](https://github.com/angular/angular.js/commit/77419cf19fe625b262e971d5453151c63ff52b34),
|
||||
[#11992](https://github.com/angular/angular.js/issues/11992), [#13424](https://github.com/angular/angular.js/issues/13424))
|
||||
- do not alter the provided options data
|
||||
([193153c3](https://github.com/angular/angular.js/commit/193153c3d391338a859cb7788ef32a8af05fb920),
|
||||
[#13040](https://github.com/angular/angular.js/issues/13040), [#13175](https://github.com/angular/angular.js/issues/13175))
|
||||
|
||||
- **ngMock:** clear out `$providerInjector` after each test
|
||||
([a72c12bd](https://github.com/angular/angular.js/commit/a72c12bd7052da9f60da74625409374342b50b73),
|
||||
[#13397](https://github.com/angular/angular.js/issues/13397), [#13416](https://github.com/angular/angular.js/issues/13416))
|
||||
|
||||
- **ngOptions:** don't $dirty multiple select after compilation
|
||||
([c7a2028a](https://github.com/angular/angular.js/commit/c7a2028ab38cdfc4d956c50b6f41cbccef302165),
|
||||
[#13211](https://github.com/angular/angular.js/issues/13211), [#13326](https://github.com/angular/angular.js/issues/13326))
|
||||
|
||||
- **ngTransclude:**
|
||||
- don't replace existing content if no transcluded content exists
|
||||
([c3ae6ed7](https://github.com/angular/angular.js/commit/c3ae6ed78e145a9b0c13de7ef95852ba3c467551),
|
||||
[#11839](https://github.com/angular/angular.js/issues/11839))
|
||||
- fix case where ngTransclude attribute value equals its key
|
||||
([7ddbc9aa](https://github.com/angular/angular.js/commit/7ddbc9aa35119154acb649e8c5096babc1d43476),
|
||||
[#12934](https://github.com/angular/angular.js/issues/12934), [#13383](https://github.com/angular/angular.js/issues/13383))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [c3a26911](https://github.com/angular/angular.js/commit/c3a2691115b92536fb3d213d0ca16ac68cf32415),
|
||||
|
||||
**This is only a breaking change to a feature that was added in beta 2. If you have not started
|
||||
using multi-slot transclusion then this will not affect you.**
|
||||
|
||||
The keys and values for the `transclude` map of the directive definition have been swapped around
|
||||
to be more consistent with the other maps, such as `scope` and `bindToController`.
|
||||
|
||||
Now the `key` is the slot name and the `value` is a normalized element selector.
|
||||
|
||||
|
||||
- **$resource:** due to [98528be3](https://github.com/angular/angular.js/commit/98528be311b48269ba0e15ba4e3e2ad9b89693a9),
|
||||
|
||||
Using a promise as `timeout` is no longer supported and will log a
|
||||
warning. It never worked the way it was supposed to anyway.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
var deferred = $q.defer();
|
||||
var User = $resource('/api/user/:id', {id: '@id'}, {
|
||||
get: {method: 'GET', timeout: deferred.promise}
|
||||
});
|
||||
|
||||
var user = User.get({id: 1}); // sends a request
|
||||
deferred.resolve(); // aborts the request
|
||||
|
||||
// Now, we need to re-define `User` passing a new promise as `timeout`
|
||||
// or else all subsequent requests from `someAction` will be aborted
|
||||
User = $resource(...);
|
||||
user = User.get({id: 2});
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
var User = $resource('/api/user/:id', {id: '@id'}, {
|
||||
get: {method: 'GET', cancellable: true}
|
||||
});
|
||||
|
||||
var user = User.get({id: 1}); // sends a request
|
||||
instance.$cancelRequest(); // aborts the request
|
||||
|
||||
user = User.get({id: 2});
|
||||
```
|
||||
|
||||
- **$sanitize:** due to [7a668cdd](https://github.com/angular/angular.js/commit/7a668cdd7d08a7016883eb3c671cbcd586223ae8),
|
||||
|
||||
The $sanitize service will now remove instances of the `<use>` tag from the content passed to it.
|
||||
|
||||
This element is used to import external SVG resources, which is a security risk as the `$sanitize`
|
||||
service does not have access to the resource in order to sanitize it.
|
||||
|
||||
- **ngView:** due to [983b0598](https://github.com/angular/angular.js/commit/983b0598121a8c5a3a51a30120e114d7e3085d4d),
|
||||
|
||||
A new property to access route resolves is now available on the scope of the route. The default name
|
||||
for this property is `$resolve`. If your scope already contains a property with this name then it
|
||||
will be hidden or overwritten.
|
||||
|
||||
In this case, you should choose a custom name for this property, that does not collide with other
|
||||
properties on the scope, by specifying the `resolveAs` property on the route.
|
||||
|
||||
|
||||
- **$parse:** due to [0ea53503](https://github.com/angular/angular.js/commit/0ea535035a3a1a992948490c3533bffb83235052),
|
||||
|
||||
A new property to access all the locals for an expression is now available on the scope. This property
|
||||
is `$locals`.
|
||||
|
||||
* If `scope.$locals` already exists, the way to reference this property is now `this.$locals`.
|
||||
* If the locals themselves include a property `$locals` then the way to reference that is now `$locals.$locals`.
|
||||
|
||||
|
||||
<a name="1.4.8"></a>
|
||||
# 1.4.8 ice-manipulation (2015-11-19)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure leave animation calls `close` callback
|
||||
([6bd6dbff](https://github.com/angular/angular.js/commit/6bd6dbff4961a601c03e9465442788781d329ba6),
|
||||
[#12278](https://github.com/angular/angular.js/issues/12278), [#12096](https://github.com/angular/angular.js/issues/12096), [#13054](https://github.com/angular/angular.js/issues/13054))
|
||||
- **$cacheFactory:** check key exists before decreasing cache size count
|
||||
([2a5a52a7](https://github.com/angular/angular.js/commit/2a5a52a76ccf60c6e8c5d881e90e11a2666a6d3c),
|
||||
[#12321](https://github.com/angular/angular.js/issues/12321), [#12329](https://github.com/angular/angular.js/issues/12329))
|
||||
- **$compile:**
|
||||
- bind all directive controllers correctly when using `bindToController`
|
||||
([5d8861fb](https://github.com/angular/angular.js/commit/5d8861fb2f203e8a688b6044cbd1140cd79fd049),
|
||||
[#11343](https://github.com/angular/angular.js/issues/11343), [#11345](https://github.com/angular/angular.js/issues/11345))
|
||||
- evaluate against the correct scope with bindToController on new scope
|
||||
([b9f7c453](https://github.com/angular/angular.js/commit/b9f7c453e00d6938106f414952f74d5e5fdcb993),
|
||||
[#13021](https://github.com/angular/angular.js/issues/13021), [#13025](https://github.com/angular/angular.js/issues/13025))
|
||||
- fix scoping of transclusion directives inside replace directive
|
||||
([74da0340](https://github.com/angular/angular.js/commit/74da03407782d679951cd8f693860cea214f2580),
|
||||
[#12975](https://github.com/angular/angular.js/issues/12975), [#12936](https://github.com/angular/angular.js/issues/12936), [#13244](https://github.com/angular/angular.js/issues/13244))
|
||||
- **$http:** apply `transformResponse` even when `data` is empty
|
||||
([c6909464](https://github.com/angular/angular.js/commit/c690946469e09cfe6b774e63dbe14ace92ce6cb7),
|
||||
[#12976](https://github.com/angular/angular.js/issues/12976), [#12979](https://github.com/angular/angular.js/issues/12979))
|
||||
- **$location:** ensure `$locationChangeSuccess` fires even if URL ends with `#`
|
||||
([6f8ddb6d](https://github.com/angular/angular.js/commit/6f8ddb6d4329441e8d4a856978413aa9b9bd918f),
|
||||
[#12175](https://github.com/angular/angular.js/issues/12175), [#13251](https://github.com/angular/angular.js/issues/13251))
|
||||
- **$parse:** evaluate once simple expressions only once
|
||||
([e4036824](https://github.com/angular/angular.js/commit/e403682444fa08af4f3491badf2f3a10d7595699),
|
||||
[#12983](https://github.com/angular/angular.js/issues/12983), [#13002](https://github.com/angular/angular.js/issues/13002))
|
||||
- **$resource:** allow XHR request to be cancelled via a timeout promise
|
||||
([7170f9d9](https://github.com/angular/angular.js/commit/7170f9d9ca765c578f8d3eb4699860a9330a0a11),
|
||||
[#12657](https://github.com/angular/angular.js/issues/12657), [#12675](https://github.com/angular/angular.js/issues/12675), [#10890](https://github.com/angular/angular.js/issues/10890), [#9332](https://github.com/angular/angular.js/issues/9332))
|
||||
- **$rootScope:** prevent IE9 memory leak when destroying scopes
|
||||
([87b0055c](https://github.com/angular/angular.js/commit/87b0055c80f40589c5bcf3765e59e872bcfae119),
|
||||
[#10706](https://github.com/angular/angular.js/issues/10706), [#11786](https://github.com/angular/angular.js/issues/11786))
|
||||
- **Angular.js:** fix `isArrayLike` for unusual cases
|
||||
([70edec94](https://github.com/angular/angular.js/commit/70edec947c7b189694ae66b129568182e3369cab),
|
||||
[#10186](https://github.com/angular/angular.js/issues/10186), [#8000](https://github.com/angular/angular.js/issues/8000), [#4855](https://github.com/angular/angular.js/issues/4855), [#4751](https://github.com/angular/angular.js/issues/4751), [#10272](https://github.com/angular/angular.js/issues/10272))
|
||||
- **isArrayLike:** handle jQuery objects of length 0
|
||||
([d3da55c4](https://github.com/angular/angular.js/commit/d3da55c40f1e1ddceced5da51e364888ff9d82ff))
|
||||
- **jqLite:**
|
||||
- deregister special `mouseenter` / `mouseleave` events correctly
|
||||
([22f66025](https://github.com/angular/angular.js/commit/22f66025db262417ebb78c1ce1f4d7058dca3fd3),
|
||||
[#12795](https://github.com/angular/angular.js/issues/12795), [#12799](https://github.com/angular/angular.js/issues/12799))
|
||||
- ensure mouseenter works with svg elements on IE
|
||||
([c1f34e8e](https://github.com/angular/angular.js/commit/c1f34e8eeb5105767f6cbf4727b8c5664be2a261),
|
||||
[#10259](https://github.com/angular/angular.js/issues/10259), [#10276](https://github.com/angular/angular.js/issues/10276))
|
||||
- **limitTo:** start at 0 if `begin` is negative and exceeds input length
|
||||
([4fc40bc9](https://github.com/angular/angular.js/commit/4fc40bc9320a1d5902e648b70fa79c7cf7e794c7),
|
||||
[#12775](https://github.com/angular/angular.js/issues/12775), [#12781](https://github.com/angular/angular.js/issues/12781))
|
||||
- **merge:**
|
||||
- ensure that jqlite->jqlite and DOM->DOM
|
||||
([2f8db1bf](https://github.com/angular/angular.js/commit/2f8db1bf01173b546a2868fc7b8b188c2383fbff))
|
||||
- clone elements instead of treating them like simple objects
|
||||
([838cf4be](https://github.com/angular/angular.js/commit/838cf4be3c671903796dbb69d95c0e5ac1516a06),
|
||||
[#12286](https://github.com/angular/angular.js/issues/12286))
|
||||
- **ngAria:** don't add tabindex to radio and checkbox inputs
|
||||
([59f1f4e1](https://github.com/angular/angular.js/commit/59f1f4e19a02e6e6f4c41c15b0e9f3372d85cecc),
|
||||
[#12492](https://github.com/angular/angular.js/issues/12492), [#13095](https://github.com/angular/angular.js/issues/13095))
|
||||
- **ngInput:** change URL_REGEXP to better match RFC3987
|
||||
([cb51116d](https://github.com/angular/angular.js/commit/cb51116dbd225ccfdbc9a565a66a170e65d26331),
|
||||
[#11341](https://github.com/angular/angular.js/issues/11341), [#11381](https://github.com/angular/angular.js/issues/11381))
|
||||
- **ngMock:** reset cache before every test
|
||||
([91b7cd9b](https://github.com/angular/angular.js/commit/91b7cd9b74d72a48d844c5c3e0e9dee03405e0ca),
|
||||
[#13013](https://github.com/angular/angular.js/issues/13013))
|
||||
- **ngOptions:**
|
||||
- skip comments and empty options when looking for options
|
||||
([0f58334b](https://github.com/angular/angular.js/commit/0f58334b7b9a9d3d6ff34e9754961b6f67731fae),
|
||||
[#12190](https://github.com/angular/angular.js/issues/12190), [#13029](https://github.com/angular/angular.js/issues/13029), [#13033](https://github.com/angular/angular.js/issues/13033))
|
||||
- override select option registration to allow compilation of empty option
|
||||
([7b2ecf42](https://github.com/angular/angular.js/commit/7b2ecf42c697eb8d51a0f2d73b324bd900139e05),
|
||||
[#11685](https://github.com/angular/angular.js/issues/11685), [#12972](https://github.com/angular/angular.js/issues/12972), [#12968](https://github.com/angular/angular.js/issues/12968), [#13012](https://github.com/angular/angular.js/issues/13012))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** use static jquery data method to avoid creating new instances
|
||||
([55ad192e](https://github.com/angular/angular.js/commit/55ad192e4ab79295ab15ecaaf8f6b9e7932a0336))
|
||||
- **copy:**
|
||||
- avoid regex in `isTypedArray`
|
||||
([19fab4a1](https://github.com/angular/angular.js/commit/19fab4a1d79d2445795273f1622344353cf4d104))
|
||||
- only validate/clear if the user specifies a destination
|
||||
([d1293540](https://github.com/angular/angular.js/commit/d1293540e13573eb9ea5f90730bb9c9710c345db),
|
||||
[#12068](https://github.com/angular/angular.js/issues/12068))
|
||||
- **merge:** remove unnecessary wrapping of jqLite element
|
||||
([ce6a96b0](https://github.com/angular/angular.js/commit/ce6a96b0d76dd2e5ab2247ca3059d284575bc6f0),
|
||||
[#13236](https://github.com/angular/angular.js/issues/13236))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
|
||||
<a name="1.5.0-beta.2"></a>
|
||||
# 1.5.0-beta.2 effective-delegation (2015-11-17)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure leave animation calls `close` callback
|
||||
([bfad2a4f](https://github.com/angular/angular.js/commit/bfad2a4f4ae71cfead61c112b0d2ab1fcadd39ee),
|
||||
[#12278](https://github.com/angular/angular.js/issues/12278), [#12096](https://github.com/angular/angular.js/issues/12096), [#13054](https://github.com/angular/angular.js/issues/13054))
|
||||
- **$cacheFactory:** check key exists before decreasing cache size count
|
||||
([b9bed7d9](https://github.com/angular/angular.js/commit/b9bed7d9dadb4ba1a4186f2ae562f807b21bcf12),
|
||||
[#12321](https://github.com/angular/angular.js/issues/12321), [#12329](https://github.com/angular/angular.js/issues/12329))
|
||||
- **$compile:**
|
||||
- bind all directive controllers correctly when using `bindToController`
|
||||
([bd7b2177](https://github.com/angular/angular.js/commit/bd7b2177291697a665e4068501b3704200972467),
|
||||
[1c13a4f4](https://github.com/angular/angular.js/commit/1c13a4f45ddc86805a96576b75c969ad577b6274)
|
||||
[#11343](https://github.com/angular/angular.js/issues/11343), [#11345](https://github.com/angular/angular.js/issues/11345))
|
||||
- evaluate against the correct scope with bindToController on new scope
|
||||
([50557a6c](https://github.com/angular/angular.js/commit/50557a6cd329e8438fb5694d11e8a7d018142afe),
|
||||
[#13021](https://github.com/angular/angular.js/issues/13021), [#13025](https://github.com/angular/angular.js/issues/13025))
|
||||
- fix scoping of transclusion directives inside a replace directive
|
||||
([1a98c0ee](https://github.com/angular/angular.js/commit/1a98c0ee346b718b9462da1abf4352a4605cbc7f),
|
||||
[#12975](https://github.com/angular/angular.js/issues/12975), [#12936](https://github.com/angular/angular.js/issues/12936), [#13244](https://github.com/angular/angular.js/issues/13244))
|
||||
- use createMap() for $$observe listeners when initialized from attr interpolation
|
||||
([76c2491a](https://github.com/angular/angular.js/commit/76c2491a316d6b296c721227529fcb09087d369a),
|
||||
[#10446](https://github.com/angular/angular.js/issues/10446))
|
||||
- properly sanitize xlink:href attribute interoplation
|
||||
([f33ce173](https://github.com/angular/angular.js/commit/f33ce173c90736e349cf594df717ae3ee41e0f7a),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- **$http:** apply `transformResponse` even when `data` is empty
|
||||
([7c0731ed](https://github.com/angular/angular.js/commit/7c0731edb2f72bdf0efa186f641dab3b6aecc5d5),
|
||||
[#12976](https://github.com/angular/angular.js/issues/12976), [#12979](https://github.com/angular/angular.js/issues/12979))
|
||||
- **$location:** ensure `$locationChangeSuccess` fires even if URL ends with `#`
|
||||
([4412fe23](https://github.com/angular/angular.js/commit/4412fe238f37f79a2017ee7b20ba089c0acd73e9),
|
||||
[#12175](https://github.com/angular/angular.js/issues/12175), [#13251](https://github.com/angular/angular.js/issues/13251))
|
||||
- **$parse:**
|
||||
- evaluate simple expressions in interpolations only once
|
||||
([1caf0b6b](https://github.com/angular/angular.js/commit/1caf0b6bee5781589e20f7a27a8c60e8b1b784f5),
|
||||
[#12983](https://github.com/angular/angular.js/issues/12983), [#13002](https://github.com/angular/angular.js/issues/13002))
|
||||
- fix typo in error message ("assing" -> "assign")
|
||||
([70dac5ae](https://github.com/angular/angular.js/commit/70dac5ae82ffe9c6250681274905583747523b5d),
|
||||
[#12940](https://github.com/angular/angular.js/issues/12940))
|
||||
- block assigning to fields of a constructor
|
||||
([e1f4f23f](https://github.com/angular/angular.js/commit/e1f4f23f781a79ae8a4046b21130283cec3f2917),
|
||||
[#12860](https://github.com/angular/angular.js/issues/12860))
|
||||
- safer conversion of computed properties to strings
|
||||
([20cf7d5e](https://github.com/angular/angular.js/commit/20cf7d5e3a0af766b1929e24794859c79439351c))
|
||||
- **$resource:** allow XHR request to be cancelled via a timeout promise
|
||||
([4fc73466](https://github.com/angular/angular.js/commit/4fc734665e5dddef26ed30a9d4f75632cd269481),
|
||||
[#12657](https://github.com/angular/angular.js/issues/12657), [#12675](https://github.com/angular/angular.js/issues/12675), [#10890](https://github.com/angular/angular.js/issues/10890), [#9332](https://github.com/angular/angular.js/issues/9332))
|
||||
- **$rootScope:** prevent IE9 memory leak when destroying scopes
|
||||
([8fe781fb](https://github.com/angular/angular.js/commit/8fe781fbe7c42c64eb895c28d9fd5479b037d020),
|
||||
[#10706](https://github.com/angular/angular.js/issues/10706), [#11786](https://github.com/angular/angular.js/issues/11786))
|
||||
- **$sanitize:**
|
||||
- strip urls starting with 'unsafe:' as opposed to 'unsafe'
|
||||
([a4dfa4d0](https://github.com/angular/angular.js/commit/a4dfa4d061fd2f6baf9821f0863dcce7888232ab),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- add mXSS protection
|
||||
([bc0d8c4e](https://github.com/angular/angular.js/commit/bc0d8c4eea9a34bff5e29dd492dcdd668251be40),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- support void elements, fixups, remove dead code, typos
|
||||
([94207f8f](https://github.com/angular/angular.js/commit/94207f8fb6ee8fe26fe18657f6b5aca6def99605),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- **Angular.js:** fix `isArrayLike` for unusual cases
|
||||
([2c8d87e0](https://github.com/angular/angular.js/commit/2c8d87e064dca99a49ed35d1db885b1f2e40dcf4),
|
||||
[#10186](https://github.com/angular/angular.js/issues/10186), [#8000](https://github.com/angular/angular.js/issues/8000), [#4855](https://github.com/angular/angular.js/issues/4855), [#4751](https://github.com/angular/angular.js/issues/4751), [#10272](https://github.com/angular/angular.js/issues/10272))
|
||||
- **filters:** ensure `formatNumber` observes i18n decimal separators
|
||||
([658a865c](https://github.com/angular/angular.js/commit/658a865c5b2580eed53b340e7394945cd76e2260),
|
||||
[#10342](https://github.com/angular/angular.js/issues/10342), [#12850](https://github.com/angular/angular.js/issues/12850))
|
||||
- **injector:** support arrow functions with no parentheses
|
||||
([03726f7f](https://github.com/angular/angular.js/commit/03726f7fbd5d71c0604b8dd40e97cb2fb0fb777f),
|
||||
[#12890](https://github.com/angular/angular.js/issues/12890))
|
||||
- **input:** remove workaround for Firefox bug
|
||||
([b366f035](https://github.com/angular/angular.js/commit/b366f0352abccfe4c4868b5a9e8c0b88659bd1ee))
|
||||
- **isArrayLike:** handle jQuery objects of length 0
|
||||
([773efd08](https://github.com/angular/angular.js/commit/773efd0812097a89944c889c595485a5744326f6))
|
||||
- **jqLite:**
|
||||
- deregister special `mouseenter` / `mouseleave` events correctly
|
||||
([f5aa2079](https://github.com/angular/angular.js/commit/f5aa207960e0df577284a06a4353e2b53b159589),
|
||||
[#12795](https://github.com/angular/angular.js/issues/12795), [#12799](https://github.com/angular/angular.js/issues/12799))
|
||||
- ensure mouseenter works with svg elements on IE
|
||||
([941c1c35](https://github.com/angular/angular.js/commit/941c1c35f175c36171a8855323f086341ea55711),
|
||||
[#10259](https://github.com/angular/angular.js/issues/10259), [#10276](https://github.com/angular/angular.js/issues/10276))
|
||||
- **limitTo:** start at 0 if `begin` is negative and exceeds input length
|
||||
([ecf93048](https://github.com/angular/angular.js/commit/ecf9304811a0fd54289a35b9c3b715a1d4447806),
|
||||
[#12775](https://github.com/angular/angular.js/issues/12775), [#12781](https://github.com/angular/angular.js/issues/12781))
|
||||
- **merge:**
|
||||
- ensure that jqlite->jqlite and DOM->DOM
|
||||
([75292a6c](https://github.com/angular/angular.js/commit/75292a6cb5e17d618902f7996e80eb3118eff7b0))
|
||||
- clone elements instead of treating them like simple objects
|
||||
([17715fa3](https://github.com/angular/angular.js/commit/17715fa3668b1fcabaedcd82e2e57b2a80e0a0c2),
|
||||
[#12286](https://github.com/angular/angular.js/issues/12286))
|
||||
- **ngAnimate:**
|
||||
- ensure anchoring uses body as a container when needed
|
||||
([240d5896](https://github.com/angular/angular.js/commit/240d5896ecdfac2351f9bd6147b52de52c0b7608),
|
||||
[#12872](https://github.com/angular/angular.js/issues/12872))
|
||||
- callback detection should only use RAF when necessary
|
||||
([8b27c3f0](https://github.com/angular/angular.js/commit/8b27c3f064b34532ba99d709cadf09fc4c0cbeab))
|
||||
- **ngAria:** don't add tabindex to radio and checkbox inputs
|
||||
([662fb282](https://github.com/angular/angular.js/commit/662fb282c176ca00a85b6dec7af90446ea90f662),
|
||||
[#12492](https://github.com/angular/angular.js/issues/12492), [#13095](https://github.com/angular/angular.js/issues/13095))
|
||||
- **ngInput:** change URL_REGEXP to better match RFC3987
|
||||
([ffb6b2fb](https://github.com/angular/angular.js/commit/ffb6b2fb56d9ffcb051284965dd538629ea9687a),
|
||||
[#11341](https://github.com/angular/angular.js/issues/11341), [#11381](https://github.com/angular/angular.js/issues/11381))
|
||||
- **ngMessage:** make ngMessage compatible with ngBind
|
||||
([4971ef12](https://github.com/angular/angular.js/commit/4971ef12d4c2c268cb8d26f90385dc96eba19db8),
|
||||
[#8089](https://github.com/angular/angular.js/issues/8089), [#13074](https://github.com/angular/angular.js/issues/13074))
|
||||
- **ngMessages:** prevent race condition with ngAnimate
|
||||
([8366622b](https://github.com/angular/angular.js/commit/8366622bed009d2cad7d0cff28b9c1e48bfbd4e1),
|
||||
[#12856](https://github.com/angular/angular.js/issues/12856), [#12903](https://github.com/angular/angular.js/issues/12903))
|
||||
- **ngMock:** reset cache before every test
|
||||
([fd83d372](https://github.com/angular/angular.js/commit/fd83d3724ad30a93254f08cb82f981eaddb5dbff),
|
||||
[#13013](https://github.com/angular/angular.js/issues/13013))
|
||||
- **ngOptions:**
|
||||
- skip comments and empty options when looking for options
|
||||
([395f3ec6](https://github.com/angular/angular.js/commit/395f3ec638f2ee77d22889823aa80898a6ce812d),
|
||||
[#12190](https://github.com/angular/angular.js/issues/12190), [#13029](https://github.com/angular/angular.js/issues/13029), [#13033](https://github.com/angular/angular.js/issues/13033))
|
||||
- override select option registration to allow compilation of empty option
|
||||
([2fcfd75a](https://github.com/angular/angular.js/commit/2fcfd75a142200e1a4b1b7ed4fb588e3befcbd57),
|
||||
[#11685](https://github.com/angular/angular.js/issues/11685), [#12972](https://github.com/angular/angular.js/issues/12972), [#12968](https://github.com/angular/angular.js/issues/12968), [#13012](https://github.com/angular/angular.js/issues/13012))
|
||||
- prevent frozen select ui in IE
|
||||
([42c97c5d](https://github.com/angular/angular.js/commit/42c97c5db5921e9e5447fb32bdae1f48da42844f),
|
||||
[#11314](https://github.com/angular/angular.js/issues/11314), [#11795](https://github.com/angular/angular.js/issues/11795))
|
||||
- allow falsy values as option group identifiers
|
||||
([b71d7c3f](https://github.com/angular/angular.js/commit/b71d7c3f3c04e65b02d88b33c22dd90ae3cdfc27),
|
||||
[#7015](https://github.com/angular/angular.js/issues/7015), [#7024](https://github.com/angular/angular.js/issues/7024), [#12888](https://github.com/angular/angular.js/issues/12888))
|
||||
- throw if ngModel is not present
|
||||
([ded25187](https://github.com/angular/angular.js/commit/ded2518756d4409fdfda0d4af243f2125bea01b5),
|
||||
[#7047](https://github.com/angular/angular.js/issues/7047), [#12840](https://github.com/angular/angular.js/issues/12840))
|
||||
- **ngResource:** encode `&` in URL query param values
|
||||
([1c97a605](https://github.com/angular/angular.js/commit/1c97a6057bc013262be761bca5e5c22224c4bbf8),
|
||||
[#12201](https://github.com/angular/angular.js/issues/12201))
|
||||
- **orderByFilter:** throw error if input is not array-like
|
||||
([2a85a634](https://github.com/angular/angular.js/commit/2a85a634f86c84f15b411ce009a3515fca7ba580),
|
||||
[#11255](https://github.com/angular/angular.js/issues/11255), [#11719](https://github.com/angular/angular.js/issues/11719))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$animateCss:** add support for temporary styles via `cleanupStyles`
|
||||
([9f67da62](https://github.com/angular/angular.js/commit/9f67da625293441e27559ebde7503cc63408a95c),
|
||||
[#12930](https://github.com/angular/angular.js/issues/12930))
|
||||
- **$compile:** multiple transclusion via named slots
|
||||
([a4ada8ba](https://github.com/angular/angular.js/commit/a4ada8ba9c4358273575e16778e76446ad080054),
|
||||
[#4357](https://github.com/angular/angular.js/issues/4357), [#12742](https://github.com/angular/angular.js/issues/12742), [#11736](https://github.com/angular/angular.js/issues/11736), [#12934](https://github.com/angular/angular.js/issues/12934))
|
||||
- **$http:** add `$xhrFactory` service to enable creation of custom xhr objects
|
||||
([106f90aa](https://github.com/angular/angular.js/commit/106f90aafa0fa5a81ad7af7ffc9d1e00ab97ffef),
|
||||
[#2318](https://github.com/angular/angular.js/issues/2318), [#9319](https://github.com/angular/angular.js/issues/9319), [#12159](https://github.com/angular/angular.js/issues/12159))
|
||||
- **$injector:**
|
||||
- Allow specifying a decorator on $injector
|
||||
([29a05984](https://github.com/angular/angular.js/commit/29a05984fe46c2c18ca51404f07c866dd92d1eec))
|
||||
- add strictDi property to $injector instance
|
||||
([79577c5d](https://github.com/angular/angular.js/commit/79577c5d316c7bf0204d7d1747ddc5b15bfe2955),
|
||||
[#11728](https://github.com/angular/angular.js/issues/11728), [#11734](https://github.com/angular/angular.js/issues/11734))
|
||||
- **$sanitize:** make svg support an opt-in
|
||||
([181fc567](https://github.com/angular/angular.js/commit/181fc567d873df065f1e84af7225deb70a8d2eb9),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- **$templateRequest:** support configuration of $http options
|
||||
([b2fc39d2](https://github.com/angular/angular.js/commit/b2fc39d2ddac64249b4f2961ee18b878a1e98251),
|
||||
[#13188](https://github.com/angular/angular.js/issues/13188), [#11868](https://github.com/angular/angular.js/issues/11868), [#6860](https://github.com/angular/angular.js/issues/6860))
|
||||
- **Module:** add helper method, `component(...)` for creating component directives
|
||||
([54e81655](https://github.com/angular/angular.js/commit/54e816552f20e198e14f849cdb2379fed8570c1a),
|
||||
[#10007](https://github.com/angular/angular.js/issues/10007), [#12933](https://github.com/angular/angular.js/issues/12933))
|
||||
- **linky:** add support for custom attributes
|
||||
([06f002b1](https://github.com/angular/angular.js/commit/06f002b161f61079933d482668440d8649fd84fc),
|
||||
[#12558](https://github.com/angular/angular.js/issues/12558), [#13061](https://github.com/angular/angular.js/issues/13061))
|
||||
- **ngAnimate:** introduce ngAnimateSwap directive
|
||||
([78297d25](https://github.com/angular/angular.js/commit/78297d252de7c80f73ecf9e291ed71bd52578361))
|
||||
- **ngMock:**
|
||||
- add expectRoute and whenRoute shortcuts with colon param matching
|
||||
([d67e999d](https://github.com/angular/angular.js/commit/d67e999dfbdf47b79fdb3830a04f4f4010a98b98),
|
||||
[#12406](https://github.com/angular/angular.js/issues/12406))
|
||||
- invoke nested calls to `module()` immediately
|
||||
([51a27c0f](https://github.com/angular/angular.js/commit/51a27c0f1ad6cd8d3e33ab0d71de22c1627c7ec3),
|
||||
[#12887](https://github.com/angular/angular.js/issues/12887))
|
||||
- **ngModel:** provide ng-empty and ng-not-empty CSS classes
|
||||
([630280c7](https://github.com/angular/angular.js/commit/630280c7fb04a83208d09c97c2efb81be3a3db74),
|
||||
[#10050](https://github.com/angular/angular.js/issues/10050), [#12848](https://github.com/angular/angular.js/issues/12848))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:**
|
||||
- use static jquery data method to avoid creating new instances
|
||||
([9b90c32f](https://github.com/angular/angular.js/commit/9b90c32f31fd56e348539674128acec6536cd846))
|
||||
- lazily compile the `transclude` function
|
||||
([652b83eb](https://github.com/angular/angular.js/commit/652b83eb226131d131a44453520a569202aa4aac))
|
||||
- **$interpolate:** provide a simplified result for constant expressions
|
||||
([cf83b4f4](https://github.com/angular/angular.js/commit/cf83b4f445d3a1fc18fc140e65e670754401d50b))
|
||||
- **copy:**
|
||||
- avoid regex in `isTypedArray`
|
||||
([c8768d12](https://github.com/angular/angular.js/commit/c8768d12f2f0b31f9ac971aeac6d2c17c9ff3db5))
|
||||
- only validate/clear if the user specifies a destination
|
||||
([33c67ce7](https://github.com/angular/angular.js/commit/33c67ce785cf8be7f0c294b3942ca4a337c5759d),
|
||||
[#12068](https://github.com/angular/angular.js/issues/12068))
|
||||
- **merge:** remove unnecessary wrapping of jqLite element
|
||||
([4daafd3d](https://github.com/angular/angular.js/commit/4daafd3dbe6a80d578f5a31df1bb99c77559543e),
|
||||
[#13236](https://github.com/angular/angular.js/issues/13236))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$sanitize:** due to [181fc567](https://github.com/angular/angular.js/commit/181fc567d873df065f1e84af7225deb70a8d2eb9),
|
||||
The svg support in is now an opt-in option
|
||||
|
||||
Applications that depend on this option can use to turn the option back on,
|
||||
but while doing so, please read the warning provided in the documentation for
|
||||
information on preventing click-hijacking attacks when this option is turned on.
|
||||
|
||||
- **ngMessage:** due to [4971ef12](https://github.com/angular/angular.js/commit/4971ef12d4c2c268cb8d26f90385dc96eba19db8),
|
||||
|
||||
ngMessage is now compiled with a priority of 1, which means directives
|
||||
on the same element as ngMessage with a priority lower than 1 will
|
||||
be applied when ngMessage calls the $transclude function.
|
||||
Previously, they were applied during the initial compile phase and were
|
||||
passed the comment element created by the transclusion of ngMessage.
|
||||
To restore this behavior, custom directives need to have
|
||||
their priority increased to at least "1".
|
||||
|
||||
- **ngOptions:** due to [ded25187](https://github.com/angular/angular.js/commit/ded2518756d4409fdfda0d4af243f2125bea01b5),
|
||||
|
||||
`ngOptions` will now throw if `ngModel` is not present on the `select`
|
||||
element. Previously, having no `ngModel` let `ngOptions` silently
|
||||
fail, which could lead to hard to debug errors. The change should
|
||||
therefore not affect any applications, as it simply makes the
|
||||
requirement more strict and alerts the developer explicitly.
|
||||
|
||||
- **orderByFilter:** due to [2a85a634](https://github.com/angular/angular.js/commit/2a85a634f86c84f15b411ce009a3515fca7ba580),
|
||||
|
||||
Previously, an non array-like input would pass through the orderBy filter unchanged.
|
||||
Now, an error is thrown. This can be worked around by converting an object
|
||||
to an array, either manually or using a filter such as
|
||||
https://github.com/petebacondarwin/angular-toArrayFilter.
|
||||
(`null` and `undefined` still pass through without an error, in order to
|
||||
support asynchronous loading of resources.)
|
||||
|
||||
Closes #11255
|
||||
Closes #11719
|
||||
|
||||
|
||||
|
||||
<a name="1.5.0-beta.1"></a>
|
||||
# 1.5.0-beta.1 dense-dispersion (2015-09-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- use createMap() for $$observe listeners when initialized from attr interpolation
|
||||
([76c2491a](https://github.com/angular/angular.js/commit/76c2491a316d6b296c721227529fcb09087d369a),
|
||||
[#10446](https://github.com/angular/angular.js/issues/10446))
|
||||
- properly sanitize xlink:href attribute interoplation
|
||||
([f33ce173](https://github.com/angular/angular.js/commit/f33ce173c90736e349cf594df717ae3ee41e0f7a),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- **$parse:**
|
||||
- fix typo in error message ("assing" -> "assign")
|
||||
([70dac5ae](https://github.com/angular/angular.js/commit/70dac5ae82ffe9c6250681274905583747523b5d),
|
||||
[#12940](https://github.com/angular/angular.js/issues/12940))
|
||||
- block assigning to fields of a constructor
|
||||
([e1f4f23f](https://github.com/angular/angular.js/commit/e1f4f23f781a79ae8a4046b21130283cec3f2917),
|
||||
[#12860](https://github.com/angular/angular.js/issues/12860))
|
||||
- do not convert to string computed properties multiple times
|
||||
([20cf7d5e](https://github.com/angular/angular.js/commit/20cf7d5e3a0af766b1929e24794859c79439351c))
|
||||
- **$sanitize:**
|
||||
- strip urls starting with 'unsafe:' as opposed to 'unsafe'
|
||||
([a4dfa4d0](https://github.com/angular/angular.js/commit/a4dfa4d061fd2f6baf9821f0863dcce7888232ab),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- add mXSS protection
|
||||
([bc0d8c4e](https://github.com/angular/angular.js/commit/bc0d8c4eea9a34bff5e29dd492dcdd668251be40),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- support void elements, fixups, remove dead code, typos
|
||||
([94207f8f](https://github.com/angular/angular.js/commit/94207f8fb6ee8fe26fe18657f6b5aca6def99605),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- **filters:** ensure `formatNumber` observes i18n decimal separators
|
||||
([658a865c](https://github.com/angular/angular.js/commit/658a865c5b2580eed53b340e7394945cd76e2260),
|
||||
[#10342](https://github.com/angular/angular.js/issues/10342), [#12850](https://github.com/angular/angular.js/issues/12850))
|
||||
- **injector:** support arrow functions with no parenthesis
|
||||
([03726f7f](https://github.com/angular/angular.js/commit/03726f7fbd5d71c0604b8dd40e97cb2fb0fb777f),
|
||||
[#12890](https://github.com/angular/angular.js/issues/12890))
|
||||
- **input:** remove workaround for Firefox bug
|
||||
([b366f035](https://github.com/angular/angular.js/commit/b366f0352abccfe4c4868b5a9e8c0b88659bd1ee))
|
||||
- **ngAnimate:**
|
||||
- ensure anchoring uses body as a container when needed
|
||||
([240d5896](https://github.com/angular/angular.js/commit/240d5896ecdfac2351f9bd6147b52de52c0b7608),
|
||||
[#12872](https://github.com/angular/angular.js/issues/12872))
|
||||
- callback detection should only use RAF when necessary
|
||||
([8b27c3f0](https://github.com/angular/angular.js/commit/8b27c3f064b34532ba99d709cadf09fc4c0cbeab))
|
||||
- **ngMessages:** prevent race condition with ngAnimate
|
||||
([8366622b](https://github.com/angular/angular.js/commit/8366622bed009d2cad7d0cff28b9c1e48bfbd4e1),
|
||||
[#12856](https://github.com/angular/angular.js/issues/12856), [#12903](https://github.com/angular/angular.js/issues/12903))
|
||||
- **ngOptions:**
|
||||
- skip comments when looking for option elements
|
||||
([7f3f3dd3](https://github.com/angular/angular.js/commit/7f3f3dd3ebcc44711600ac292af54c411c3c705f),
|
||||
[#12190](https://github.com/angular/angular.js/issues/12190))
|
||||
- prevent frozen select ui in IE
|
||||
([42c97c5d](https://github.com/angular/angular.js/commit/42c97c5db5921e9e5447fb32bdae1f48da42844f),
|
||||
[#11314](https://github.com/angular/angular.js/issues/11314), [#11795](https://github.com/angular/angular.js/issues/11795))
|
||||
- allow falsy values as option group identifiers
|
||||
([b71d7c3f](https://github.com/angular/angular.js/commit/b71d7c3f3c04e65b02d88b33c22dd90ae3cdfc27),
|
||||
[#7015](https://github.com/angular/angular.js/issues/7015), [#7024](https://github.com/angular/angular.js/issues/7024), [#12888](https://github.com/angular/angular.js/issues/12888))
|
||||
- throw if ngModel is not present
|
||||
([ded25187](https://github.com/angular/angular.js/commit/ded2518756d4409fdfda0d4af243f2125bea01b5),
|
||||
[#7047](https://github.com/angular/angular.js/issues/7047), [#12840](https://github.com/angular/angular.js/issues/12840))
|
||||
- **ngResource:** encode `&` in URL query param values
|
||||
([1c97a605](https://github.com/angular/angular.js/commit/1c97a6057bc013262be761bca5e5c22224c4bbf8),
|
||||
[#12201](https://github.com/angular/angular.js/issues/12201))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$animateCss:** add support for temporary styles via `cleanupStyles`
|
||||
([9f67da62](https://github.com/angular/angular.js/commit/9f67da625293441e27559ebde7503cc63408a95c),
|
||||
[#12930](https://github.com/angular/angular.js/issues/12930))
|
||||
- **$http:** add `$xhrFactory` service to enable creation of custom xhr objects
|
||||
([106f90aa](https://github.com/angular/angular.js/commit/106f90aafa0fa5a81ad7af7ffc9d1e00ab97ffef),
|
||||
[#2318](https://github.com/angular/angular.js/issues/2318), [#9319](https://github.com/angular/angular.js/issues/9319), [#12159](https://github.com/angular/angular.js/issues/12159))
|
||||
- **$injector:** add strictDi property to $injector instance
|
||||
([79577c5d](https://github.com/angular/angular.js/commit/79577c5d316c7bf0204d7d1747ddc5b15bfe2955),
|
||||
[#11728](https://github.com/angular/angular.js/issues/11728), [#11734](https://github.com/angular/angular.js/issues/11734))
|
||||
- **$sanitize:** make svg support an opt-in
|
||||
([181fc567](https://github.com/angular/angular.js/commit/181fc567d873df065f1e84af7225deb70a8d2eb9),
|
||||
[#12524](https://github.com/angular/angular.js/issues/12524))
|
||||
- **ngModel:** provide ng-empty and ng-not-empty CSS classes
|
||||
([630280c7](https://github.com/angular/angular.js/commit/630280c7fb04a83208d09c97c2efb81be3a3db74),
|
||||
[#10050](https://github.com/angular/angular.js/issues/10050), [#12848](https://github.com/angular/angular.js/issues/12848))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** Lazily compile the `transclude` function
|
||||
([652b83eb](https://github.com/angular/angular.js/commit/652b83eb226131d131a44453520a569202aa4aac))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$sanitize:** due to [181fc567](https://github.com/angular/angular.js/commit/181fc567d873df065f1e84af7225deb70a8d2eb9),
|
||||
The svg support in is now an opt-in option
|
||||
|
||||
Applications that depend on this option can use to turn the option back on,
|
||||
but while doing so, please read the warning provided in the documentation for
|
||||
information on preventing click-hijacking attacks when this option is turned on.
|
||||
|
||||
- **ngOptions:** due to [ded25187](https://github.com/angular/angular.js/commit/ded2518756d4409fdfda0d4af243f2125bea01b5),
|
||||
|
||||
`ngOptions` will now throw if `ngModel` is not present on the `select`
|
||||
element. Previously, having no `ngModel` let `ngOptions` silently
|
||||
fail, which could lead to hard to debug errors. The change should
|
||||
therefore not affect any applications, as it simply makes the
|
||||
requirement more strict and alerts the developer explicitly.
|
||||
|
||||
|
||||
<a name="1.4.7"></a>
|
||||
# 1.4.7 dark-luminescence (2015-09-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** use createMap() for $$observe listeners when initialized from attr interpolation
|
||||
([5a98e806](https://github.com/angular/angular.js/commit/5a98e806ef3c59916bb4668268125610b11effe8),
|
||||
[#10446](https://github.com/angular/angular.js/issues/10446))
|
||||
- **$parse:**
|
||||
- block assigning to fields of a constructor
|
||||
([a7f3761e](https://github.com/angular/angular.js/commit/a7f3761eda5309f76b73c6fb1d3173a270112899),
|
||||
[#12860](https://github.com/angular/angular.js/issues/12860))
|
||||
- do not convert to string computed properties multiple times
|
||||
([698af191](https://github.com/angular/angular.js/commit/698af191ded2465ca4e0f97959b75fede5a531ab))
|
||||
- **filters:** ensure `formatNumber` observes i18n decimal separators
|
||||
([4994acd2](https://github.com/angular/angular.js/commit/4994acd26e582eec8a92b139bfc09ca79a9b8835),
|
||||
[#10342](https://github.com/angular/angular.js/issues/10342), [#12850](https://github.com/angular/angular.js/issues/12850))
|
||||
- **jqLite:** properly handle dash-delimited node names in `jqLiteBuildFragment`
|
||||
([cdd1227a](https://github.com/angular/angular.js/commit/cdd1227a308edd34d31b67f338083b6e0c4c0db9),
|
||||
[#10617](https://github.com/angular/angular.js/issues/10617), [#12759](https://github.com/angular/angular.js/issues/12759))
|
||||
- **ngAnimate:**
|
||||
- ensure anchoring uses body as a container when needed
|
||||
([9d3704ca](https://github.com/angular/angular.js/commit/9d3704ca467081f16b71b011eb50c53d5cdb2f34),
|
||||
[#12872](https://github.com/angular/angular.js/issues/12872))
|
||||
- callback detection should only use RAF when necessary
|
||||
([fa8c399f](https://github.com/angular/angular.js/commit/fa8c399fadc30b78710868fe59d2930fdc17c7a5))
|
||||
- **ngMessages:** prevent race condition with ngAnimate
|
||||
([7295c60f](https://github.com/angular/angular.js/commit/7295c60ffb9f2e4f32043c538ace740b187f565a),
|
||||
[#12856](https://github.com/angular/angular.js/issues/12856), [#12903](https://github.com/angular/angular.js/issues/12903))
|
||||
- **ngOptions:**
|
||||
- skip comments when looking for option elements
|
||||
([68d4dc5b](https://github.com/angular/angular.js/commit/68d4dc5b71b23e4c7c2650e6da3d7200de99f1ae),
|
||||
[#12190](https://github.com/angular/angular.js/issues/12190))
|
||||
- prevent frozen select ui in IE
|
||||
([dbc69851](https://github.com/angular/angular.js/commit/dbc698517ff620b3a6279f65d4a9b6e3c15087b9),
|
||||
[#11314](https://github.com/angular/angular.js/issues/11314), [#11795](https://github.com/angular/angular.js/issues/11795))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$animateCss:** add support for temporary styles via `cleanupStyles`
|
||||
([e52d731b](https://github.com/angular/angular.js/commit/e52d731bfd1fbb6c616125fbde2fb365722254b7),
|
||||
[#12930](https://github.com/angular/angular.js/issues/12930))
|
||||
- **$http:** add `$xhrFactory` service to enable creation of custom xhr objects
|
||||
([7a413df5](https://github.com/angular/angular.js/commit/7a413df5e47e04e20a1c93d35922050bbcbfb492),
|
||||
[#2318](https://github.com/angular/angular.js/issues/2318), [#9319](https://github.com/angular/angular.js/issues/9319), [#12159](https://github.com/angular/angular.js/issues/12159))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
|
||||
<a name="1.3.20"></a>
|
||||
# 1.3.20 shallow-translucence (2015-09-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$parse:** do not convert to string computed properties multiple times
|
||||
([d434f3db](https://github.com/angular/angular.js/commit/d434f3db53d6209eb140b904e83bbde401686c16))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
|
||||
<a name="1.2.29"></a>
|
||||
# 1.2.29 ultimate-deprecation (2015-09-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:** prevent infinite digests when clearing the hash of a url
|
||||
([9845cee6](https://github.com/angular/angular.js/commit/9845cee63eda3ad5024622c792c5e745b59ec5cb),
|
||||
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
|
||||
- **$compile:** workaround for IE11 MutationObserver
|
||||
([fccce96d](https://github.com/angular/angular.js/commit/fccce96d444f442ba5ecfb67201505a445d0c209),
|
||||
[#11781](https://github.com/angular/angular.js/issues/11781), [#12613](https://github.com/angular/angular.js/issues/12613))
|
||||
- **$location:** strip off empty hash segments when comparing
|
||||
([e81b2f72](https://github.com/angular/angular.js/commit/e81b2f726cacd08bcf91500183a7ea3f71961718),
|
||||
[#9635](https://github.com/angular/angular.js/issues/9635), [#10748](https://github.com/angular/angular.js/issues/10748))
|
||||
- **$parse:**
|
||||
- do not convert to string computed properties multiple times
|
||||
([afb65c11](https://github.com/angular/angular.js/commit/afb65c11e5e3425f8431ad5b82308ff6b8ecbc64))
|
||||
- throw error when accessing a restricted property indirectly
|
||||
([e6cbd4fa](https://github.com/angular/angular.js/commit/e6cbd4faa211b2c0c8879c255e3194fb0717dcec),
|
||||
[#12833](https://github.com/angular/angular.js/issues/12833))
|
||||
- **ngAnimate:** ensure that minified repaint code isn't removed
|
||||
([b041b664](https://github.com/angular/angular.js/commit/b041b664752e34a42bbc65e02bf0009f0836c50c),
|
||||
[#9936](https://github.com/angular/angular.js/issues/9936))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
|
||||
<a name="1.4.6"></a>
|
||||
# 1.4.6 multiplicative-elevation (2015-09-17)
|
||||
|
||||
@@ -170,8 +884,6 @@ the built-in pattern validator:
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.5"></a>
|
||||
# 1.4.5 permanent-internship (2015-08-28)
|
||||
|
||||
@@ -1340,7 +2052,6 @@ mechanism.
|
||||
|
||||
- **ngMessages:** due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
|
||||
|
||||
The `ngMessagesInclude` attribute is now its own directive and that must
|
||||
be placed as a **child** element within the element with the ngMessages
|
||||
directive. (Keep in mind that the former behaviour of the
|
||||
@@ -1363,6 +2074,26 @@ end of the container containing the ngMessages directive).
|
||||
</div>
|
||||
```
|
||||
|
||||
- **ngMessages:** due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
|
||||
it is no longer possible to use interpolation inside the `ngMessages` attribute expression. This technique
|
||||
is generally not recommended, and can easily break when a directive implementation changes. In cases
|
||||
where a simple expression is not possible, you can delegate accessing the object to a function:
|
||||
|
||||
```html
|
||||
<div ng-messages="ctrl.form['field_{{$index}}'].$error">...</div>
|
||||
```
|
||||
would become
|
||||
```html
|
||||
<div ng-messages="ctrl.getMessages($index)">...</div>
|
||||
```
|
||||
where `ctrl.getMessages()`
|
||||
```javascript
|
||||
ctrl.getMessages = function($index) {
|
||||
return ctrl.form['field_' + $index].$error;
|
||||
}
|
||||
```
|
||||
|
||||
- **$http:** due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
|
||||
|
||||
`transformRequest` functions can no longer modify request headers.
|
||||
@@ -1818,6 +2549,12 @@ But in practice this is not what people want and so this change iterates over pr
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
setting the ngOptions attribute expression after the element is compiled, will no longer trigger the ngOptions behavior.
|
||||
This worked previously because the ngOptions logic was part of the select directive, while
|
||||
it is now implemented in the ngOptions directive itself.
|
||||
|
||||
- **select:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
the `select` directive will now use strict comparison of the `ngModel` scope value against `option`
|
||||
@@ -2420,7 +3157,36 @@ We also added a documentation page focused on security, which contains some of t
|
||||
[#9578](https://github.com/angular/angular.js/issues/9578), [#9751](https://github.com/angular/angular.js/issues/9751))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$observe:** Due to [531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
observers no longer register on undefined attributes. For example, if you were using `$observe` on
|
||||
an absent optional attribute to set a default value, the following would not work anymore:
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
```
|
||||
|
||||
```js
|
||||
// link function for directive myDir
|
||||
link: function(scope, element, attr) {
|
||||
attr.$observe('myAttr', function(newVal) {
|
||||
scope.myValue = newVal ? newVal : 'myDefaultValue';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Instead, check if the attribute is set before registering the observer:
|
||||
|
||||
```js
|
||||
link: function(scope, element, attr) {
|
||||
if (attr.myAttr) {
|
||||
// register the observer
|
||||
} else {
|
||||
// set the default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
# 1.3.0 superluminal-nudge (2014-10-13)
|
||||
@@ -4917,8 +5683,9 @@ See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
`toJson()` will no longer strip properties starting with a single `$`. If you relied on
|
||||
`toJson()`'s stripping these types of properties before, you will have to do it manually now.
|
||||
It will still strip properties starting with `$$` though.
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -71,7 +71,7 @@ chances of your issue being dealt with quickly:
|
||||
* **Angular Version(s)** - is it a regression?
|
||||
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
|
||||
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
|
||||
[JSFiddle][jsfiddle]) or an unambiguous set of steps.
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
@@ -187,6 +187,8 @@ We have very precise rules over how our git commit messages can be formatted. T
|
||||
readable messages** that are easy to follow when looking through the **project history**. But also,
|
||||
we use the git commit messages to **generate the AngularJS change log**.
|
||||
|
||||
The commit message formatting can be added using a typical git workflow or through the use of a CLI wizard ([Commitizen](https://github.com/commitizen/cz-cli)). To use the wizard, run `npm run commit` in your terminal after staging your changes in git.
|
||||
|
||||
### Commit Message Format
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||
format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
+1
-1
@@ -305,7 +305,7 @@ module.exports = function(grunt) {
|
||||
|
||||
shell: {
|
||||
"npm-install": {
|
||||
command: path.normalize('scripts/npm/install-dependencies.sh')
|
||||
command: 'node scripts/npm/check-node-modules.js'
|
||||
},
|
||||
|
||||
"promises-aplus-tests": {
|
||||
|
||||
@@ -21,7 +21,7 @@ piece of cake. Best of all?? It makes development fun!
|
||||
|
||||
Building AngularJS
|
||||
---------
|
||||
[Once you have your environment setup](http://docs.angularjs.org/misc/contribute) just run:
|
||||
[Once you have your environment set up](http://docs.angularjs.org/misc/contribute) just run:
|
||||
|
||||
grunt package
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -14,6 +14,7 @@ var angularFiles = {
|
||||
|
||||
'src/ng/anchorScroll.js',
|
||||
'src/ng/animate.js',
|
||||
'src/ng/animateRunner.js',
|
||||
'src/ng/animateCss.js',
|
||||
'src/ng/browser.js',
|
||||
'src/ng/cacheFactory.js',
|
||||
@@ -92,7 +93,6 @@ var angularFiles = {
|
||||
'angularModules': {
|
||||
'ngAnimate': [
|
||||
'src/ngAnimate/shared.js',
|
||||
'src/ngAnimate/body.js',
|
||||
'src/ngAnimate/rafScheduler.js',
|
||||
'src/ngAnimate/animateChildrenDirective.js',
|
||||
'src/ngAnimate/animateCss.js',
|
||||
@@ -100,8 +100,8 @@ var angularFiles = {
|
||||
'src/ngAnimate/animateJs.js',
|
||||
'src/ngAnimate/animateJsDriver.js',
|
||||
'src/ngAnimate/animateQueue.js',
|
||||
'src/ngAnimate/animateRunner.js',
|
||||
'src/ngAnimate/animation.js',
|
||||
'src/ngAnimate/ngAnimateSwap.js',
|
||||
'src/ngAnimate/module.js'
|
||||
],
|
||||
'ngCookies': [
|
||||
|
||||
@@ -315,8 +315,13 @@ iframe.example {
|
||||
color:white;
|
||||
}
|
||||
|
||||
.search-results-group .search-results {
|
||||
padding: 0 5px 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.search-results-frame > .search-results-group:first-child > .search-results {
|
||||
border-right:1px solid #050505;
|
||||
border-right:1px solid #222;
|
||||
}
|
||||
|
||||
.search-results-group.col-group-api { width:30%; }
|
||||
@@ -325,10 +330,57 @@ iframe.example {
|
||||
.search-results-group.col-group-misc,
|
||||
.search-results-group.col-group-error { width:15%; float: right; }
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group.col-group-api .search-results {
|
||||
-moz-column-count: 2;
|
||||
-ms-column-count: 2;
|
||||
-webkit-column-count: 2;
|
||||
column-count: 2;
|
||||
/* Prevent bullets in the second column from being hidden in Chrome and IE */
|
||||
-webkit-column-gap: 2em;
|
||||
-ms-column-gap: 2em;
|
||||
column-gap: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results-group .search-result {
|
||||
word-wrap: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
hyphens: auto;
|
||||
-ms-column-break-inside: avoid;
|
||||
-webkit-column-break-inside: avoid;
|
||||
-moz-column-break-inside: avoid; /* Unsupported */
|
||||
column-break-inside: avoid;
|
||||
text-indent: -0.65em; /* Make sure line wrapped words are aligned vertically */
|
||||
}
|
||||
|
||||
@supports (-moz-column-count: 2) {
|
||||
.search-results-group .search-result {
|
||||
/* Prevents column breaks inside words in FF, but has adverse effects in IE11 and Chrome */
|
||||
overflow: hidden;
|
||||
padding-left: 1em; /* In FF the list item bullet is otherwise hidden */
|
||||
margin-left: -1em; /* offset the padding left */
|
||||
}
|
||||
}
|
||||
|
||||
.search-result:before {
|
||||
content: "\002D\00A0"; /* Dash and non-breaking space as List item type */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-results-group.col-group-api .search-result {
|
||||
width:48%;
|
||||
display:inline-block;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group.col-group-api .search-result {
|
||||
width:auto;
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
|
||||
.search-close {
|
||||
@@ -589,6 +641,12 @@ ul.events > li {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.table > tbody > tr.head > td,
|
||||
.table > tbody > tr.head > th {
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 769px) and (max-width: 991px) {
|
||||
.main-body-grid {
|
||||
margin-top: 160px;
|
||||
@@ -682,6 +740,11 @@ ul.events > li {
|
||||
padding-bottom:60px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.search-results-frame > .search-results-group:first-child > .search-results {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.search-results-group {
|
||||
float:none!important;
|
||||
display:block!important;
|
||||
@@ -689,14 +752,42 @@ ul.events > li {
|
||||
border:0!important;
|
||||
padding:0!important;
|
||||
}
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group .search-results {
|
||||
-moz-column-count: 2;
|
||||
-ms-column-count: 2;
|
||||
-webkit-column-count: 2;
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results-group .search-result {
|
||||
display:inline-block!important;
|
||||
padding:0 5px;
|
||||
width:auto!important;
|
||||
text-indent: initial;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.search-results-group .search-result:after {
|
||||
content:", ";
|
||||
}
|
||||
|
||||
.search-results-group .search-result:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
@supports ((column-count: 2) or (-moz-column-count: 2) or (-ms-column-count: 2) or (-webkit-column-count: 2)) {
|
||||
.search-results-group .search-result {
|
||||
display: list-item !important;
|
||||
}
|
||||
|
||||
.search-results-group .search-result:after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
padding-bottom:0px;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* global importScripts, onmessage: true, postMessage, lunr */
|
||||
|
||||
// Load up the lunr library
|
||||
importScripts('../components/lunr.js-0.4.2/lunr.min.js');
|
||||
importScripts('../components/lunr.js-0.5.12/lunr.min.js');
|
||||
|
||||
// Create the lunr index - the docs should be an array of object, each object containing
|
||||
// the path and search terms for a page
|
||||
|
||||
@@ -11,7 +11,15 @@ angular.module('search', [])
|
||||
var MIN_SEARCH_LENGTH = 2;
|
||||
if(q.length >= MIN_SEARCH_LENGTH) {
|
||||
docsSearch(q).then(function(hits) {
|
||||
var results = {};
|
||||
// Make sure the areas are always in the same order
|
||||
var results = {
|
||||
api: [],
|
||||
guide: [],
|
||||
tutorial: [],
|
||||
error: [],
|
||||
misc: []
|
||||
};
|
||||
|
||||
angular.forEach(hits, function(hit) {
|
||||
var area = hit.area;
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "AngularJS-docs-app",
|
||||
"dependencies": {
|
||||
"jquery": "2.1.1",
|
||||
"lunr.js": "0.4.3",
|
||||
"lunr.js": "0.5.12",
|
||||
"open-sans-fontface": "1.0.4",
|
||||
"google-code-prettify": "1.0.1",
|
||||
"bootstrap": "3.1.1"
|
||||
|
||||
@@ -147,13 +147,13 @@
|
||||
<div class="search-results-container" ng-show="hasResults">
|
||||
<div class="container">
|
||||
<div class="search-results-frame">
|
||||
<div ng-repeat="(key, value) in results" class="search-results-group" ng-class="colClassName + ' col-group-' + key">
|
||||
<div ng-repeat="(key, value) in results track by key" class="search-results-group" ng-class="colClassName + ' col-group-' + key" ng-show="value.length > 0">
|
||||
<h4 class="search-results-group-heading">{{ key }}</h4>
|
||||
<div class="search-results">
|
||||
<div ng-repeat="item in value" class="search-result">
|
||||
- <a ng-click="hideResults()" ng-href="{{ item.path }}">{{ item.name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="search-results">
|
||||
<!-- Do not insert a line break between li and a. Chrome will insert an actual line-break, which breaks the list item view.
|
||||
TODO: use a html minifier instead -->
|
||||
<li ng-repeat="item in value" class="search-result"><a ng-click="hideResults()" ng-href="{{ item.path }}">{{ item.name }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="hideResults()" class="search-close">
|
||||
|
||||
@@ -8,7 +8,7 @@ but the required directive controller is not present on the current DOM element
|
||||
|
||||
To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element.
|
||||
|
||||
If the required controller is expected to be on a ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
If the required controller is expected to be on an ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
|
||||
If the required controller is optionally requested, use `?` or `^?` to specify that.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $location:nobase
|
||||
@fullName $location in HTML5 mode requires a <base> tag to be present!
|
||||
@fullName $location in HTML5 mode requires a `<base>` tag to be present!
|
||||
@description
|
||||
|
||||
If you configure {@link ng.$location `$location`} to use
|
||||
@@ -15,7 +15,7 @@ $locationProvider.html5Mode({
|
||||
});
|
||||
```
|
||||
|
||||
Note that removing the requirement for a <base> tag will have adverse side effects when resolving
|
||||
Note that removing the requirement for a `<base>` tag will have adverse side effects when resolving
|
||||
relative paths with `$location` in IE9.
|
||||
|
||||
The base URL is then used to resolve all relative URLs throughout the application regardless of the
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $sanitize:badparse
|
||||
@fullName Parsing Error while Sanitizing
|
||||
@description
|
||||
|
||||
This error occurs when the HTML string passed to '$sanitize' can't be parsed by the sanitizer.
|
||||
The error contains part of the html string that can't be parsed.
|
||||
|
||||
The parser is more strict than a typical browser parser, so it's possible that some obscure input would produce this error despite the string being recognized as valid HTML by a browser.
|
||||
|
||||
If a valid html code results in this error, please file a bug.
|
||||
@@ -0,0 +1,10 @@
|
||||
@ngdoc error
|
||||
@name $sanitize:noinert
|
||||
@fullName Can't create an inert html document
|
||||
@description
|
||||
|
||||
This error occurs when `$sanitize` sanitizer determines that `document.implementation.createHTMLDocument ` api is not supported by the current browser.
|
||||
|
||||
This api is necessary for safe parsing of HTML strings into DOM trees and without it the sanitizer can't sanitize the input.
|
||||
|
||||
The api is present in all supported browsers including IE 9.0, so the presence of this error usually indicates that Angular's `$sanitize` is being used on an unsupported platform.
|
||||
@@ -0,0 +1,13 @@
|
||||
@ngdoc error
|
||||
@name $sanitize:uinput
|
||||
@fullName Failed to sanitize html because the input is unstable
|
||||
@description
|
||||
|
||||
This error occurs when `$sanitize` sanitizer tries to check the input for possible mXSS payload and the verification
|
||||
errors due to the input mutating indefinitely. This could be a sign that the payload contains code exploiting an mXSS
|
||||
vulnerability in the browser.
|
||||
|
||||
mXSS attack exploit browser bugs that cause some browsers parse a certain html strings into DOM, which once serialized
|
||||
doesn't match the original input. These browser bugs can be exploited by attackers to create payload which looks
|
||||
harmless to sanitizers, but due to mutations caused by the browser are turned into dangerous code once processed after
|
||||
sanitization.
|
||||
@@ -0,0 +1,52 @@
|
||||
@ngdoc error
|
||||
@name orderBy:notarray
|
||||
@fullName Value is not array-like
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.orderBy orderBy} is not passed an array-like value:
|
||||
```html
|
||||
<div ng-repeat="(key, value) in myObj | orderBy:someProp">
|
||||
{{ key }} : {{ value }}
|
||||
</div>
|
||||
```
|
||||
|
||||
`orderBy` must be used with an array-like value so a subset of items can be returned.
|
||||
The array can be initialized asynchronously and therefore `null` or `undefined` won't throw this error.
|
||||
|
||||
To use `orderBy` to order the properties of an object, you can create your own array based on that object:
|
||||
```js
|
||||
angular.module('aModule', [])
|
||||
.controller('aController', function($scope) {
|
||||
var myObj = {
|
||||
one: {id: 1, name: 'Some thing'},
|
||||
two: {id: 2, name: 'Another thing'},
|
||||
three: {id: 3, name: 'A third thing'}
|
||||
};
|
||||
|
||||
$scope.arrFromMyObj = Object.keys(myObj).map(function(key) {
|
||||
return myObj[key];
|
||||
});
|
||||
});
|
||||
```
|
||||
That can be used as:
|
||||
```html
|
||||
<label>
|
||||
Order by:
|
||||
<select ng-model="orderProp" ng-options="prop for prop in ['id', 'name']"></select>
|
||||
</label>
|
||||
<div ng-repeat="item in arrFromMyObj | orderBy:orderProp">
|
||||
[{{ item.id }}] {{ item.name }}
|
||||
</div>
|
||||
```
|
||||
|
||||
You could as well convert the object to an array using a filter such as
|
||||
[toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter):
|
||||
```html
|
||||
<label>
|
||||
Order by:
|
||||
<select ng-model="orderProp" ng-options="prop for prop in ['id', 'name']"></select>
|
||||
</label>
|
||||
<div ng-repeat="item in myObj | toArray:false | orderBy:orderProp">
|
||||
[{{ item.id }}] {{ item.name }}
|
||||
</div>
|
||||
```
|
||||
@@ -356,15 +356,15 @@ legacy browsers and hashbang links in modern browser:
|
||||
|
||||
### Example
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents the address bar of the browser.
|
||||
Here you can see two `$location` instances that show the difference between **Html5 mode** and **Html5 Fallback mode**.
|
||||
Note that to simulate different levels of browser support, the `$location` instances are connected to
|
||||
a fakeBrowser service, which you don't have to set up in actual projects.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
Note that when you type hashbang url into the first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
= on page reload.
|
||||
|
||||
In these examples we use `<base href="/base/index.html" />`
|
||||
In these examples we use `<base href="/base/index.html" />`. The inputs represent the address bar of the browser.
|
||||
|
||||
#### Browser in HTML5 mode
|
||||
<example module="html5-mode" name="location-html5-mode">
|
||||
@@ -389,6 +389,7 @@ In these examples we use `<base href="/base/index.html" />`
|
||||
<file name="app.js">
|
||||
angular.module('html5-mode', ['fake-browser', 'address-bar'])
|
||||
|
||||
// Configure the fakeBrowser. Do not set these values in actual projects.
|
||||
.constant('initUrl', 'http://www.example.com/base/path?a=b#h')
|
||||
.constant('baseHref', '/base/index.html')
|
||||
.value('$sniffer', { history: true })
|
||||
@@ -538,6 +539,7 @@ In these examples we use `<base href="/base/index.html" />`
|
||||
<file name="app.js">
|
||||
angular.module('hashbang-mode', ['fake-browser', 'address-bar'])
|
||||
|
||||
// Configure the fakeBrowser. Do not set these values in actual projects.
|
||||
.constant('initUrl', 'http://www.example.com/base/index.html#!/path?a=b#h')
|
||||
.constant('baseHref', '/base/index.html')
|
||||
.value('$sniffer', { history: false })
|
||||
@@ -769,8 +771,8 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Navigation outside the app</td>
|
||||
<td>Use lower level API</td>
|
||||
<th>Navigation outside the app</td>
|
||||
<th>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -784,8 +786,8 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Read access</td>
|
||||
<td>Change to</td>
|
||||
<th>Read access</td>
|
||||
<th>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
@@ -274,6 +274,37 @@ myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
}]);
|
||||
```
|
||||
|
||||
## Preventing flicker before an animation starts
|
||||
|
||||
When nesting elements with structural animations such as `ngIf` into elements that have class-based
|
||||
animations such as `ngClass`, it sometimes happens that before the actual animation starts, there is a brief flicker or flash of content
|
||||
where the animated element is briefly visible.
|
||||
|
||||
To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as an animation is initialized,
|
||||
but removed before the actual animation starts (after waiting for a $digest). This class is only added for *structural*
|
||||
animations (`enter`, `move`, and `leave`).
|
||||
|
||||
Here's an example where you might see flickering:
|
||||
|
||||
```html
|
||||
<div ng-class="{red: myProp}">
|
||||
<div ng-class="{blue: myProp}">
|
||||
<div class="message" ng-if="myProp"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
It is possible that during the `enter` event, the `.message` div will be briefly visible before it starts animating.
|
||||
In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
|
||||
|
||||
```css
|
||||
.message.ng-enter-prepare {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Other animation styles ... */
|
||||
```
|
||||
|
||||
## More about animations
|
||||
|
||||
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
|
||||
|
||||
@@ -76,7 +76,7 @@ stores/updates the value of the input field into/from a variable.
|
||||
The second kind of new markup are the double curly braces `{{ expression | filter }}`:
|
||||
When the compiler encounters this markup, it will replace it with the evaluated value of the markup.
|
||||
An <a name="expression">{@link expression expression}</a> in a template is a JavaScript-like code snippet that allows
|
||||
to read and write variables. Note that those variables are not global variables.
|
||||
Angular to read and write variables. Note that those variables are not global variables.
|
||||
Just like variables in a JavaScript function live in a scope,
|
||||
Angular provides a <a name="scope">{@link scope scope}</a> for the variables accessible to expressions.
|
||||
The values that are stored in variables on the scope are referred to as the <a name="model"></a>*model*
|
||||
@@ -334,9 +334,9 @@ The following example shows how this is done with Angular:
|
||||
var refresh = function() {
|
||||
var url = YAHOO_FINANCE_URL_PATTERN.
|
||||
replace('PAIRS', 'USD' + currencies.join('","USD'));
|
||||
return $http.jsonp(url).success(function(data) {
|
||||
return $http.jsonp(url).then(function(response) {
|
||||
var newUsdToForeignRates = {};
|
||||
angular.forEach(data.query.results.rate, function(rate) {
|
||||
angular.forEach(response.data.query.results.rate, function(rate) {
|
||||
var currency = rate.id.substring(3,6);
|
||||
newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
|
||||
});
|
||||
@@ -348,8 +348,7 @@ The following example shows how this is done with Angular:
|
||||
|
||||
return {
|
||||
currencies: currencies,
|
||||
convert: convert,
|
||||
refresh: refresh
|
||||
convert: convert
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
@@ -43,8 +43,7 @@ mirrors the process of compiling source code in
|
||||
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
|
||||
determines when to use a given directive.
|
||||
|
||||
Similar to the terminology used when an [element **matches** a selector]
|
||||
(https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
Similar to the terminology used when an [element **matches** a selector](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
directive when the directive is part of its declaration.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
|
||||
@@ -588,14 +587,24 @@ want to reuse throughout your app.
|
||||
In this example we will build a directive that displays the current time.
|
||||
Once a second, it updates the DOM to reflect the current time.
|
||||
|
||||
Directives that want to modify the DOM typically use the `link` option.
|
||||
`link` takes a function with the following signature, `function link(scope, element, attrs) { ... }`
|
||||
where:
|
||||
Directives that want to modify the DOM typically use the `link` option to register DOM listeners
|
||||
as well as update the DOM. It is executed after the template has been cloned and is where
|
||||
directive logic will be put.
|
||||
|
||||
`link` takes a function with the following signature,
|
||||
`function link(scope, element, attrs, controller, transcludeFn) { ... }`, where:
|
||||
|
||||
* `scope` is an Angular scope object.
|
||||
* `element` is the jqLite-wrapped element that this directive matches.
|
||||
* `attrs` is a hash object with key-value pairs of normalized attribute names and their
|
||||
corresponding attribute values.
|
||||
* `controller` is the directive's required controller instance(s) or its own controller (if any).
|
||||
The exact value depends on the directive's require property.
|
||||
* `transcludeFn` is a transclude linking function pre-bound to the correct transclusion scope.
|
||||
|
||||
<div class="alert alert-info">
|
||||
For more details on the `link` option refer to the {@link ng.$compile#-link- `$compile` API} page.
|
||||
</div>
|
||||
|
||||
In our `link` function, we want to update the displayed time once a second, or whenever a user
|
||||
changes the time formatting string that our directive binds to. We will use the `$interval` service
|
||||
@@ -903,7 +912,7 @@ to which tab is active.
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {},
|
||||
controller: function($scope) {
|
||||
controller: ['$scope', function($scope) {
|
||||
var panes = $scope.panes = [];
|
||||
|
||||
$scope.select = function(pane) {
|
||||
@@ -919,7 +928,7 @@ to which tab is active.
|
||||
}
|
||||
panes.push(pane);
|
||||
};
|
||||
},
|
||||
}],
|
||||
templateUrl: 'my-tabs.html'
|
||||
};
|
||||
})
|
||||
|
||||
@@ -35,7 +35,9 @@ Angular expressions are like JavaScript expressions with the following differenc
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
|
||||
* **No Object Creation With New Operator:** You cannot use `new` operator in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` operators in an Angular expression.
|
||||
|
||||
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
|
||||
displaying it.
|
||||
@@ -113,6 +115,9 @@ This restriction is intentional. It prevents accidental access to the global sta
|
||||
Instead use services like `$window` and `$location` in functions called from expressions. Such services
|
||||
provide mockable access to globals.
|
||||
|
||||
It is possible to access the context object using the identifier `this` and the locals object using the
|
||||
identifier `$locals`.
|
||||
|
||||
<example module="expressionExample">
|
||||
<file name="index.html">
|
||||
<div class="example2" ng-controller="ExampleController">
|
||||
|
||||
@@ -383,7 +383,7 @@ In the following example we create two directives:
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, elm, attrs, ctrl) {
|
||||
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
||||
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
||||
|
||||
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
|
||||
|
||||
|
||||
@@ -170,6 +170,25 @@ other inline messages situated as children within the `ngMessages` container dir
|
||||
Depending on where the `ngMessagesInclude` directive is placed it will be prioritized inline with the other messages
|
||||
before and after it.
|
||||
|
||||
Also due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
it is no longer possible to use interpolation inside the `ngMessages` attribute expression. This technique
|
||||
is generally not recommended, and can easily break when a directive implementation changes. In cases
|
||||
where a simple expression is not possible, you can delegate accessing the object to a function:
|
||||
|
||||
```html
|
||||
<div ng-messages="ctrl.form['field_{{$index}}'].$error">...</div>
|
||||
```
|
||||
would become
|
||||
```html
|
||||
<div ng-messages="ctrl.getMessages($index)">...</div>
|
||||
```
|
||||
where `ctrl.getMessages()`
|
||||
```javascript
|
||||
ctrl.getMessages = function($index) {
|
||||
return ctrl.form['field_' + $index].$error;
|
||||
}
|
||||
```
|
||||
|
||||
### ngOptions
|
||||
|
||||
The `ngOptions` directive has also been refactored and as a result some long-standing bugs
|
||||
@@ -189,6 +208,10 @@ But in practice this is not what people want and so this change iterates over pr
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
Also due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
setting the ngOptions attribute expression after the element is compiled, will no longer trigger the ngOptions behavior.
|
||||
This worked previously because the ngOptions logic was part of the select directive, while
|
||||
it is now implemented in the ngOptions directive itself.
|
||||
|
||||
### select
|
||||
|
||||
@@ -473,10 +496,9 @@ This change also makes our forEach behave more like Array#forEach.
|
||||
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before, you will have to
|
||||
manually do this yourself now.
|
||||
|
||||
`toJson()` will no longer strip properties starting with a single `$`. If you relied on
|
||||
`toJson()`'s stripping these types of properties before, you will have to do it manually now.
|
||||
It will still strip properties starting with `$$` though.
|
||||
|
||||
|
||||
|
||||
@@ -561,6 +583,36 @@ After:
|
||||
};
|
||||
});
|
||||
|
||||
- due to [531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
`$observe` no longer registers on undefined attributes. For example, if you were using `$observe` on
|
||||
an absent optional attribute to set a default value, the following would not work anymore:
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
```
|
||||
|
||||
```js
|
||||
// Link function for directive myDir
|
||||
link: function(scope, element, attr) {
|
||||
attr.$observe('myAttr', function(newVal) {
|
||||
scope.myValue = newVal ? newVal : 'myDefaultValue';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Instead, check if the attribute is set before registering the observer:
|
||||
|
||||
```js
|
||||
link: function(scope, element, attr) {
|
||||
if (attr.myAttr) {
|
||||
// register the observer
|
||||
} else {
|
||||
// set the default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -633,8 +685,15 @@ $scope.resetWithCancel = function (e) {
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
- {@link input[checkbox] `input[checkbox]`} now supports constant expressions in `ngTrueValue` and
|
||||
`ngFalseValue`, making it now possible to e.g. use boolean and integer values. Previously, these attributes would
|
||||
always be treated as strings, whereas they are now parsed as expressions, and will throw if an expression
|
||||
is non-constant. To convert non-constant strings into constant expressions, simply wrap them in an
|
||||
extra pair of quotes, like so:
|
||||
|
||||
`<input type="checkbox" ng-model="..." ng-true-value="'truthyValue'">`
|
||||
|
||||
See [c90cefe1614](https://github.com/angular/angular.js/commit/c90cefe16142d973a123e945fc9058e8a874c357)
|
||||
|
||||
|
||||
## Scopes and Digests (`$scope`)
|
||||
|
||||
@@ -44,7 +44,7 @@ and {@link angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
|
||||
|
||||
## Strict DI Mode
|
||||
|
||||
Using strict di mode in your production application will throw errors when a injectable
|
||||
Using strict di mode in your production application will throw errors when an injectable
|
||||
function is not
|
||||
{@link di#dependency-annotation annotated explicitly}. Strict di mode is intended to help
|
||||
you make sure that your code will work when minified. However, it also will force you to
|
||||
|
||||
@@ -391,7 +391,7 @@ implementing custom event callbacks, or when working with third-party library ca
|
||||
5. The {@link ng.$rootScope.Scope#$watch $watch} list is a set of expressions
|
||||
which may have changed since last iteration. If a change is detected then the `$watch`
|
||||
function is called which typically updates the DOM with the new value.
|
||||
6. Once the Angular {@link ng.$rootScope.Scope#$digest $digest} loop finishes
|
||||
6. Once the Angular {@link ng.$rootScope.Scope#$digest $digest} loop finishes,
|
||||
the execution leaves the Angular and JavaScript context. This is followed by the browser
|
||||
re-rendering the DOM to reflect any changes.
|
||||
|
||||
@@ -419,4 +419,4 @@ user enters text into the text field.
|
||||
which in turn updates the DOM.
|
||||
6. Angular exits the execution context, which in turn exits the `keydown` event and with it
|
||||
the JavaScript execution context.
|
||||
7. The browser re-renders the view with update text.
|
||||
7. The browser re-renders the view with the updated text.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
JavaScript is a dynamically typed language which comes with great power of expression, but it also
|
||||
comes with almost no help from the compiler. For this reason we feel very strongly that any code
|
||||
written in JavaScript needs to come with a strong set of tests. We have built many features into
|
||||
Angular which makes testing your Angular applications easy. So there is no excuse for not testing.
|
||||
Angular which make testing your Angular applications easy. With Angular, there is no excuse for not testing.
|
||||
|
||||
## Separation of Concerns
|
||||
|
||||
@@ -20,13 +20,13 @@ related pieces such as the DOM elements, or making any XHR calls to fetch the da
|
||||
|
||||
While this may seem obvious it can be very difficult to call an individual function on a
|
||||
typical project. The reason is that the developers often mix concerns resulting in a
|
||||
piece of code which does everything. It makes an XHR request, it sorts the response data and then it
|
||||
piece of code which does everything. It makes an XHR request, it sorts the response data, and then it
|
||||
manipulates the DOM.
|
||||
|
||||
With Angular we try to make it easy for you to do the right thing, and so we
|
||||
provide dependency injection for your XHR requests, which can be mocked, and we provide abstractions which
|
||||
allow you to test your model without having to resort to manipulating the DOM. The test can then
|
||||
assert that the data has been sorted without having to create or look at the state of the DOM or
|
||||
With Angular, we try to make it easy for you to do the right thing. For your XHR requests, we
|
||||
provide dependency injection, so your requests can be simulated. For the DOM, we abstract it, so you can
|
||||
test your model without having to manipulate the DOM directly. Your tests can then
|
||||
assert that the data has been sorted without having to create or look at the state of the DOM or to
|
||||
wait for any XHR requests to return data. The individual sort function can be tested in isolation.
|
||||
|
||||
## With great power comes great responsibility
|
||||
@@ -359,7 +359,7 @@ element, to which it can then insert the transcluded content into its template.
|
||||
|
||||
Before compilation:
|
||||
```html
|
||||
<div translude-directive>
|
||||
<div transclude-directive>
|
||||
Some transcluded content
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
<ul doc-tutorial-nav="0"></ul>
|
||||
|
||||
|
||||
You are now ready to build the AngularJS phonecat app. In this step, you will become familiar
|
||||
with the most important source code files, learn how to start the development servers bundled with
|
||||
In this step of the tutorial, you will become familiar with the most important source code files of
|
||||
the AngularJS phonecat app. You will also learn how to start the development servers bundled with
|
||||
angular-seed, and run the application in the browser.
|
||||
|
||||
Before you continue, make sure you have set up your development environment and installed all necessary
|
||||
dependencies, as described in {@link tutorial/index#get-started Get Started}.
|
||||
|
||||
In `angular-phonecat` directory, run this command:
|
||||
In the `angular-phonecat` directory, run this command:
|
||||
|
||||
```
|
||||
git checkout -f step-0
|
||||
|
||||
@@ -61,7 +61,7 @@ by the value of the expressions.
|
||||
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
|
||||
__controller__ to the <body> tag. At this point:
|
||||
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}` denote
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) denote
|
||||
bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
|
||||
controller.
|
||||
|
||||
@@ -195,8 +195,19 @@ to ensure that Karma and its necessary plugins are installed. You can do this by
|
||||
|
||||
To run the tests, and then watch the files for changes: `npm test`.
|
||||
|
||||
* Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
|
||||
the background. Karma will use this browser for test execution.
|
||||
* Karma will start new instances of Chrome and Firefox browsers automatically. Just ignore them and
|
||||
let them run in the background. Karma will use these browsers for test execution.
|
||||
* If you only have one of the browsers installed on your machine (either Chrome or Firefox), make
|
||||
sure to update the karma configuration file before running the test. Locate the configuration file
|
||||
in `test/karma.conf.js`, then update the `browsers` property.
|
||||
|
||||
E.g. if you only have Chrome installed:
|
||||
<pre>
|
||||
...
|
||||
browsers: ['Chrome'],
|
||||
...
|
||||
</pre>
|
||||
|
||||
* You should see the following or similar output in the terminal:
|
||||
|
||||
<pre>
|
||||
@@ -250,7 +261,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
|
||||
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
@@ -37,12 +37,12 @@ We are using [Bower][bower] to install client-side dependencies. This step upda
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.4.0"
|
||||
"angular-route": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "~1.4.0"` tells bower to install a version of the
|
||||
The new dependency `"angular-route": "1.4.x"` tells bower to install a version of the
|
||||
angular-route component that is compatible with version 1.4.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ phone in the phone list.
|
||||
* When you click on a phone on the list, the phone details page with phone-specific information
|
||||
is displayed.
|
||||
|
||||
To implement the phone details view we used {@link ng.$http $http} to fetch our data, and we
|
||||
fleshed out the `phone-detail.html` view template.
|
||||
To implement the phone details view we are going to use {@link ng.$http $http} to fetch our data,
|
||||
and then flesh out the `phone-detail.html` view template.
|
||||
|
||||
<div doc-tutorial-reset="8"></div>
|
||||
|
||||
|
||||
@@ -32,17 +32,18 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"angular": "1.4.x",
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0"
|
||||
"angular-route": "1.4.x",
|
||||
"angular-resource": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "~1.3.0"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.3.x. We must ask bower to download
|
||||
The new dependency `"angular-resource": "1.4.x"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.4.x. We must ask bower to download
|
||||
and install this dependency. We can do this by running:
|
||||
|
||||
```
|
||||
|
||||
@@ -36,20 +36,20 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0",
|
||||
"angular": "1.4.x",
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"angular-animate": "~1.3.0"
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "1.4.x",
|
||||
"angular-resource": "1.4.x",
|
||||
"angular-animate": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "~1.3.0"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.3.x.
|
||||
* `"jquery": "2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
* `"angular-animate": "1.4.x"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.4.x.
|
||||
* `"jquery": "~2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd
|
||||
party libraries.
|
||||
|
||||
@@ -111,7 +111,7 @@ __`app/index.html`.__
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.3; jQuery 1.x is
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.4; jQuery 1.x is
|
||||
not officially supported.
|
||||
Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and
|
||||
animations will not work as expected.
|
||||
@@ -239,9 +239,9 @@ The name of the starting class is the name of the event that is fired (like `ent
|
||||
The active class name is the same as the starting class's but with an `-active` suffix.
|
||||
This two-class CSS naming convention allows the developer to craft an animation, beginning to end.
|
||||
|
||||
In our example above, elements expand from a height of **0** to **120 pixels** when items are added or moved,
|
||||
around and collapsing the items before removing them from the list.
|
||||
There's also a nice fade-in and fade-out effect that also occurs at the same time. All of this is handled
|
||||
In our example above, elements are expanded from a height of **0** to **120 pixels** when they're added to the
|
||||
list and are collapsed back down to **0 pixels** before being removed from the list.
|
||||
There's also a nice fade-in and fade-out effect that occurs at the same time. All of this is handled
|
||||
by the CSS transition declarations at the top of the example code above.
|
||||
|
||||
Although most modern browsers have good support for [CSS transitions](http://caniuse.com/#feat=css-transitions)
|
||||
|
||||
@@ -11,6 +11,8 @@ function newTestLocaleInfo() {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
STANDALONEMONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
@@ -180,6 +182,8 @@ describe("extractDateTimeSymbols", function() {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
STANDALONEMONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
|
||||
'août', 'septembre', 'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
|
||||
@@ -27,6 +27,7 @@ describe("convertNumberData", function() {
|
||||
describe("convertDatetimeData", function() {
|
||||
var convert = converter.convertDatetimeData,
|
||||
dataObj = { MONTHS: ['Enero', 'Pebrero'],
|
||||
STANDALONEMONTHS: ['Enero', 'Pebrero'],
|
||||
SHORTMONTHS: ['Ene', 'Peb'],
|
||||
WEEKDAYS: ['Linggo', 'Lunes'],
|
||||
SHORTWEEKDAYS: ['Lin', 'Lun'],
|
||||
@@ -37,6 +38,7 @@ describe("convertDatetimeData", function() {
|
||||
it('should convert empty datetime obj', function() {
|
||||
var processedData = convert(dataObj);
|
||||
expect(processedData.MONTH).toEqual(['Enero', 'Pebrero']);
|
||||
expect(processedData.STANDALONEMONTH).toEqual(['Enero', 'Pebrero']);
|
||||
expect(processedData.SHORTMONTH).toEqual(['Ene', 'Peb']);
|
||||
expect(processedData.DAY).toEqual(['Linggo', 'Lunes']);
|
||||
expect(processedData.SHORTDAY).toEqual(['Lin', 'Lun']);
|
||||
|
||||
@@ -39,6 +39,7 @@ function convertDatetimeData(dataObj) {
|
||||
|
||||
datetimeFormats.MONTH = dataObj.MONTHS;
|
||||
datetimeFormats.SHORTMONTH = dataObj.SHORTMONTHS;
|
||||
datetimeFormats.STANDALONEMONTH = dataObj.STANDALONEMONTHS;
|
||||
datetimeFormats.DAY = dataObj.WEEKDAYS;
|
||||
datetimeFormats.SHORTDAY = dataObj.SHORTWEEKDAYS;
|
||||
datetimeFormats.AMPMS = dataObj.AMPMS;
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = function(config, specificOptions) {
|
||||
'SL_Chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
version: '43'
|
||||
version: '45'
|
||||
},
|
||||
'SL_Firefox': {
|
||||
base: 'SauceLabs',
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Browserstack tunnel"
|
||||
echo "TODO: implement me"
|
||||
exit 1
|
||||
@@ -1,309 +0,0 @@
|
||||
/*
|
||||
* HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
|
||||
endTag = /^<\/(\w+)[^>]*>/,
|
||||
attr = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var special = makeMap("script,style");
|
||||
|
||||
var htmlParser = this.htmlParser = function( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){
|
||||
return this[ this.length - 1 ];
|
||||
};
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !special[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") == 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( html.indexOf("</") == 0 ) {
|
||||
match = html.match( endTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( endTag, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( html.indexOf("<") == 0 ) {
|
||||
match = html.match( startTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( startTag, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
|
||||
text = text.replace(/<!--(.*?)-->/g, "$1")
|
||||
.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last )
|
||||
throw "Parse Error: " + html;
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
if ( block[ tagName ] ) {
|
||||
while ( stack.last() && inline[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( closeSelf[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = empty[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = [];
|
||||
|
||||
rest.replace(attr, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
// If no tag name is provided, clean shop
|
||||
if ( !tagName )
|
||||
var pos = 0;
|
||||
|
||||
// Find the closest opened tag of the same type
|
||||
else
|
||||
for ( var pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( var i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.HTMLtoXML = function( html ) {
|
||||
var results = "";
|
||||
|
||||
htmlParser(html, {
|
||||
start: function( tag, attrs, unary ) {
|
||||
results += "<" + tag;
|
||||
|
||||
for ( var i = 0; i < attrs.length; i++ )
|
||||
results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';
|
||||
|
||||
results += (unary ? "/" : "") + ">";
|
||||
},
|
||||
end: function( tag ) {
|
||||
results += "</" + tag + ">";
|
||||
},
|
||||
chars: function( text ) {
|
||||
results += text;
|
||||
},
|
||||
comment: function( text ) {
|
||||
results += "<!--" + text + "-->";
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
this.HTMLtoDOM = function( html, doc ) {
|
||||
// There can be only one of these elements
|
||||
var one = makeMap("html,head,body,title");
|
||||
|
||||
// Enforce a structure for the document
|
||||
var structure = {
|
||||
link: "head",
|
||||
base: "head"
|
||||
};
|
||||
|
||||
if ( !doc ) {
|
||||
if ( typeof DOMDocument != "undefined" )
|
||||
doc = new DOMDocument();
|
||||
else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
|
||||
doc = document.implementation.createDocument("", "", null);
|
||||
else if ( typeof ActiveX != "undefined" )
|
||||
doc = new ActiveXObject("Msxml.DOMDocument");
|
||||
|
||||
} else
|
||||
doc = doc.ownerDocument ||
|
||||
doc.getOwnerDocument && doc.getOwnerDocument() ||
|
||||
doc;
|
||||
|
||||
var elems = [],
|
||||
documentElement = doc.documentElement ||
|
||||
doc.getDocumentElement && doc.getDocumentElement();
|
||||
|
||||
// If we're dealing with an empty document then we
|
||||
// need to pre-populate it with the HTML document structure
|
||||
if ( !documentElement && doc.createElement ) (function(){
|
||||
var html = doc.createElement("html");
|
||||
var head = doc.createElement("head");
|
||||
head.appendChild( doc.createElement("title") );
|
||||
html.appendChild( head );
|
||||
html.appendChild( doc.createElement("body") );
|
||||
doc.appendChild( html );
|
||||
})();
|
||||
|
||||
// Find all the unique elements
|
||||
if ( doc.getElementsByTagName )
|
||||
for ( var i in one )
|
||||
one[ i ] = doc.getElementsByTagName( i )[0];
|
||||
|
||||
// If we're working with a document, inject contents into
|
||||
// the body element
|
||||
var curParentNode = one.body;
|
||||
|
||||
htmlParser( html, {
|
||||
start: function( tagName, attrs, unary ) {
|
||||
// If it's a pre-built element, then we can ignore
|
||||
// its construction
|
||||
if ( one[ tagName ] ) {
|
||||
curParentNode = one[ tagName ];
|
||||
return;
|
||||
}
|
||||
|
||||
var elem = doc.createElement( tagName );
|
||||
|
||||
for ( var attr in attrs )
|
||||
elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
|
||||
|
||||
if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
|
||||
one[ structure[ tagName ] ].appendChild( elem );
|
||||
|
||||
else if ( curParentNode && curParentNode.appendChild )
|
||||
curParentNode.appendChild( elem );
|
||||
|
||||
if ( !unary ) {
|
||||
elems.push( elem );
|
||||
curParentNode = elem;
|
||||
}
|
||||
},
|
||||
end: function( tag ) {
|
||||
elems.length -= 1;
|
||||
|
||||
// Init the new parentNode
|
||||
curParentNode = elems[ elems.length - 1 ];
|
||||
},
|
||||
chars: function( text ) {
|
||||
curParentNode.appendChild( doc.createTextNode( text ) );
|
||||
},
|
||||
comment: function( text ) {
|
||||
// create comment node
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
function makeMap(str){
|
||||
var obj = {}, items = str.split(",");
|
||||
for ( var i = 0; i < items.length; i++ )
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
})();
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Sauce Connect tunnel"
|
||||
|
||||
killall sc
|
||||
|
||||
while [[ -n `ps -ef | grep "sauce-connect-" | grep -v "grep"` ]]; do
|
||||
printf "."
|
||||
sleep .5
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Sauce Connect tunnel has been shut down"
|
||||
+3797
-48
File diff suppressed because it is too large
Load Diff
Generated
+5920
-140
File diff suppressed because it is too large
Load Diff
+17
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.5.0-beta.0",
|
||||
"branchVersion": "^1.5.0-rc.0",
|
||||
"branchPattern": "1.5.*",
|
||||
"distTag": "beta",
|
||||
"repository": {
|
||||
@@ -9,10 +9,15 @@
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~0.10",
|
||||
"node": "<5",
|
||||
"npm": "~2.5"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/npm/check-node-modules.js --purge",
|
||||
"postinstall": "node scripts/npm/copy-npm-shrinkwrap.js",
|
||||
"commit": "git-cz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
@@ -20,8 +25,10 @@
|
||||
"browserstacktunnel-wrapper": "~1.3.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"cheerio": "^0.17.0",
|
||||
"commitizen": "^2.3.0",
|
||||
"cz-conventional-changelog": "1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"dgeni-packages": "^0.11.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-bump": "~0.0.13",
|
||||
@@ -32,7 +39,7 @@
|
||||
"grunt-contrib-jshint": "~0.10.0",
|
||||
"grunt-ddescribe-iit": "~0.0.1",
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~1.2.0",
|
||||
"grunt-jscs": "^2.1.0",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
@@ -76,5 +83,10 @@
|
||||
"url": "https://github.com/angular/angular.js/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"dependencies": {}
|
||||
"dependencies": {},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ config.specs = [
|
||||
];
|
||||
|
||||
config.capabilities = {
|
||||
browserName: 'chrome',
|
||||
browserName: 'chrome'
|
||||
};
|
||||
|
||||
exports.config = config;
|
||||
|
||||
@@ -14,8 +14,9 @@ function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
BUILD_DIR=$(resolveDir ../../build)
|
||||
NEW_VERSION=$(cat $BUILD_DIR/version.txt)
|
||||
PROJECT_DIR=$(resolveDir ../..)
|
||||
# get the npm dist-tag from a custom property (distTag) in package.json
|
||||
DIST_TAG=$(readJsonProp "package.json" "distTag")
|
||||
DIST_TAG=$(readJsonProp "$PROJECT_DIR/package.json" "distTag")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Implementation based on:
|
||||
// https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Constants
|
||||
var PROJECT_ROOT = path.join(__dirname, '../../');
|
||||
var NODE_MODULES_DIR = 'node_modules';
|
||||
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
|
||||
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
var purgeIfStale = process.argv.indexOf('--purge') !== -1;
|
||||
|
||||
process.chdir(PROJECT_ROOT);
|
||||
checkNodeModules(purgeIfStale);
|
||||
}
|
||||
|
||||
function checkNodeModules(purgeIfStale) {
|
||||
var nodeModulesOk = compareMarkerFiles(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE);
|
||||
|
||||
if (nodeModulesOk) {
|
||||
console.log(':-) npm dependencies are looking good!');
|
||||
} else if (purgeIfStale) {
|
||||
console.log(':-( npm dependencies are stale or in an unknown state!');
|
||||
console.log(' Purging \'' + NODE_MODULES_DIR + '\'...');
|
||||
deleteDirSync(NODE_MODULES_DIR);
|
||||
} else {
|
||||
var separator = new Array(81).join('!');
|
||||
|
||||
console.warn(separator);
|
||||
console.warn(':-( npm dependencies are stale or in an unknown state!');
|
||||
console.warn('You can rebuild the dependencies by running `npm install`.');
|
||||
console.warn(separator);
|
||||
}
|
||||
|
||||
return nodeModulesOk;
|
||||
}
|
||||
|
||||
function compareMarkerFiles(markerFilePath, cachedMarkerFilePath) {
|
||||
if (!fs.existsSync(cachedMarkerFilePath)) return false;
|
||||
|
||||
var opts = {encoding: 'utf-8'};
|
||||
var markerContent = fs.readFileSync(markerFilePath, opts);
|
||||
var cachedMarkerContent = fs.readFileSync(cachedMarkerFilePath, opts);
|
||||
|
||||
return markerContent === cachedMarkerContent;
|
||||
}
|
||||
|
||||
// Custom implementation of `rm -rf` that works consistently across OSes
|
||||
function deleteDirSync(path) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.readdirSync(path).forEach(deleteDirOrFileSync);
|
||||
fs.rmdirSync(path);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function deleteDirOrFileSync(subpath) {
|
||||
var curPath = path + '/' + subpath;
|
||||
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteDirSync(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Constants
|
||||
var PROJECT_ROOT = path.join(__dirname, '../../');
|
||||
var NODE_MODULES_DIR = 'node_modules';
|
||||
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
|
||||
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
process.chdir(PROJECT_ROOT);
|
||||
copyFile(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE, onCopied);
|
||||
}
|
||||
|
||||
// Implementation based on:
|
||||
// https://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js#answer-21995878
|
||||
function copyFile(srcPath, dstPath, callback) {
|
||||
var callbackCalled = false;
|
||||
|
||||
if (!fs.existsSync(srcPath)) {
|
||||
done(new Error('Missing source file: ' + srcPath));
|
||||
return;
|
||||
}
|
||||
|
||||
var rs = fs.createReadStream(srcPath);
|
||||
rs.on('error', done);
|
||||
|
||||
var ws = fs.createWriteStream(dstPath);
|
||||
ws.on('error', done);
|
||||
ws.on('finish', done);
|
||||
|
||||
rs.pipe(ws);
|
||||
|
||||
// Helpers
|
||||
function done(err) {
|
||||
if (callback && !callbackCalled) {
|
||||
callbackCalled = true;
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCopied(err) {
|
||||
if (err) {
|
||||
var separator = new Array(81).join('!');
|
||||
|
||||
console.error(separator);
|
||||
console.error(
|
||||
'Failed to copy `' + NPM_SHRINKWRAP_FILE + '` to `' + NPM_SHRINKWRAP_CACHED_FILE + '`:');
|
||||
console.error(err);
|
||||
console.error(separator);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SHRINKWRAP_FILE=npm-shrinkwrap.json
|
||||
SHRINKWRAP_CACHED_FILE=node_modules/npm-shrinkwrap.cached.json
|
||||
|
||||
if diff -q $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE; then
|
||||
echo 'No shrinkwrap changes detected. npm install will be skipped...';
|
||||
else
|
||||
echo 'Blowing away node_modules and reinstalling npm dependencies...'
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
cp $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE
|
||||
echo 'npm install successful!'
|
||||
fi
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Has to be run from project root directory.
|
||||
|
||||
./lib/${BROWSER_PROVIDER}/teardown_tunnel.sh
|
||||
@@ -94,8 +94,6 @@
|
||||
"VALIDITY_STATE_PROPERTY": false,
|
||||
"reloadWithDebugInfo": false,
|
||||
|
||||
"skipDestroyOnNextJQueryCleanData": true,
|
||||
|
||||
"NODE_TYPE_ELEMENT": false,
|
||||
"NODE_TYPE_ATTRIBUTE": false,
|
||||
"NODE_TYPE_TEXT": false,
|
||||
|
||||
+117
-103
@@ -198,20 +198,24 @@ msie = document.documentMode;
|
||||
* String ...)
|
||||
*/
|
||||
function isArrayLike(obj) {
|
||||
if (obj == null || isWindow(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `null`, `undefined` and `window` are not array-like
|
||||
if (obj == null || isWindow(obj)) return false;
|
||||
|
||||
// arrays, strings and jQuery/jqLite objects are array like
|
||||
// * jqLite is either the jQuery or jqLite constructor function
|
||||
// * we have to check the existance of jqLite first as this method is called
|
||||
// via the forEach method when constructing the jqLite object in the first place
|
||||
if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
|
||||
|
||||
// Support: iOS 8.2 (not reproducible in simulator)
|
||||
// "length" in obj used to prevent JIT error (gh-11508)
|
||||
var length = "length" in Object(obj) && obj.length;
|
||||
|
||||
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isString(obj) || isArray(obj) || length === 0 ||
|
||||
typeof length === 'number' && length > 0 && (length - 1) in obj;
|
||||
// NodeList objects (with `item` method) and
|
||||
// other objects with suitable length characteristics are array-like
|
||||
return isNumber(length) &&
|
||||
(length >= 0 && (length - 1) in obj || typeof obj.item == 'function');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +360,10 @@ function baseExtend(dst, objs, deep) {
|
||||
dst[key] = new Date(src.valueOf());
|
||||
} else if (isRegExp(src)) {
|
||||
dst[key] = new RegExp(src);
|
||||
} else if (src.nodeName) {
|
||||
dst[key] = src.cloneNode(true);
|
||||
} else if (isElement(src)) {
|
||||
dst[key] = src.clone();
|
||||
} else {
|
||||
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
|
||||
baseExtend(dst[key], [src], true);
|
||||
@@ -471,7 +479,7 @@ identity.$inject = [];
|
||||
function valueFn(value) {return function() {return value;};}
|
||||
|
||||
function hasCustomToString(obj) {
|
||||
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
|
||||
return isFunction(obj.toString) && obj.toString !== toString;
|
||||
}
|
||||
|
||||
|
||||
@@ -670,9 +678,9 @@ function isPromiseLike(obj) {
|
||||
}
|
||||
|
||||
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
|
||||
function isTypedArray(value) {
|
||||
return TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
}
|
||||
|
||||
|
||||
@@ -794,100 +802,111 @@ function arrayRemove(array, value) {
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
function copy(source, destination, stackSource, stackDest) {
|
||||
if (isWindow(source) || isScope(source)) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
if (isTypedArray(destination)) {
|
||||
throw ngMinErr('cpta',
|
||||
"Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
function copy(source, destination) {
|
||||
var stackSource = [];
|
||||
var stackDest = [];
|
||||
|
||||
if (!destination) {
|
||||
destination = source;
|
||||
if (isObject(source)) {
|
||||
var index;
|
||||
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
|
||||
return stackDest[index];
|
||||
}
|
||||
|
||||
// TypedArray, Date and RegExp have specific copy functionality and must be
|
||||
// pushed onto the stack before returning.
|
||||
// Array and other objects create the base object and recurse to copy child
|
||||
// objects. The array/object will be pushed onto the stack when recursed.
|
||||
if (isArray(source)) {
|
||||
return copy(source, [], stackSource, stackDest);
|
||||
} else if (isTypedArray(source)) {
|
||||
destination = new source.constructor(source);
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isRegExp(source)) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isFunction(source.cloneNode)) {
|
||||
destination = source.cloneNode(true);
|
||||
} else {
|
||||
var emptyObject = Object.create(getPrototypeOf(source));
|
||||
return copy(source, emptyObject, stackSource, stackDest);
|
||||
}
|
||||
|
||||
if (stackDest) {
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
}
|
||||
if (destination) {
|
||||
if (isTypedArray(destination)) {
|
||||
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
} else {
|
||||
if (source === destination) throw ngMinErr('cpi',
|
||||
"Can't copy! Source and destination are identical.");
|
||||
|
||||
stackSource = stackSource || [];
|
||||
stackDest = stackDest || [];
|
||||
|
||||
if (isObject(source)) {
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
if (source === destination) {
|
||||
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
|
||||
}
|
||||
|
||||
// Empty the destination object
|
||||
if (isArray(destination)) {
|
||||
destination.length = 0;
|
||||
} else {
|
||||
forEach(destination, function(value, key) {
|
||||
if (key !== '$$hashKey') {
|
||||
delete destination[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
return copyRecurse(source, destination);
|
||||
}
|
||||
|
||||
return copyElement(source);
|
||||
|
||||
function copyRecurse(source, destination) {
|
||||
var h = destination.$$hashKey;
|
||||
var result, key;
|
||||
if (isArray(source)) {
|
||||
destination.length = 0;
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
destination.push(copy(source[i], null, stackSource, stackDest));
|
||||
for (var i = 0, ii = source.length; i < ii; i++) {
|
||||
destination.push(copyElement(source[i]));
|
||||
}
|
||||
} else if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var h = destination.$$hashKey;
|
||||
if (isArray(destination)) {
|
||||
destination.length = 0;
|
||||
} else {
|
||||
forEach(destination, function(value, key) {
|
||||
delete destination[key];
|
||||
});
|
||||
}
|
||||
if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
}
|
||||
setHashKey(destination,h);
|
||||
}
|
||||
setHashKey(destination, h);
|
||||
return destination;
|
||||
}
|
||||
|
||||
function copyElement(source) {
|
||||
// Simple values
|
||||
if (!isObject(source)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Already copied values
|
||||
var index = stackSource.indexOf(source);
|
||||
if (index !== -1) {
|
||||
return stackDest[index];
|
||||
}
|
||||
|
||||
if (isWindow(source) || isScope(source)) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
|
||||
var needsRecurse = false;
|
||||
var destination;
|
||||
|
||||
if (isArray(source)) {
|
||||
destination = [];
|
||||
needsRecurse = true;
|
||||
} else if (isTypedArray(source)) {
|
||||
destination = new source.constructor(source);
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isRegExp(source)) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isFunction(source.cloneNode)) {
|
||||
destination = source.cloneNode(true);
|
||||
} else {
|
||||
destination = Object.create(getPrototypeOf(source));
|
||||
needsRecurse = true;
|
||||
}
|
||||
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
|
||||
return needsRecurse
|
||||
? copyRecurse(source, destination)
|
||||
: destination;
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1663,7 +1682,6 @@ function snake_case(name, separator) {
|
||||
}
|
||||
|
||||
var bindJQueryFired = false;
|
||||
var skipDestroyOnNextJQueryCleanData;
|
||||
function bindJQuery() {
|
||||
var originalCleanData;
|
||||
|
||||
@@ -1697,15 +1715,11 @@ function bindJQuery() {
|
||||
originalCleanData = jQuery.cleanData;
|
||||
jQuery.cleanData = function(elems) {
|
||||
var events;
|
||||
if (!skipDestroyOnNextJQueryCleanData) {
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jQuery._data(elem, "events");
|
||||
if (events && events.$destroy) {
|
||||
jQuery(elem).triggerHandler('$destroy');
|
||||
}
|
||||
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
|
||||
events = jQuery._data(elem, "events");
|
||||
if (events && events.$destroy) {
|
||||
jQuery(elem).triggerHandler('$destroy');
|
||||
}
|
||||
} else {
|
||||
skipDestroyOnNextJQueryCleanData = false;
|
||||
}
|
||||
originalCleanData(elems);
|
||||
};
|
||||
|
||||
@@ -57,10 +57,12 @@
|
||||
$AnimateProvider,
|
||||
$CoreAnimateCssProvider,
|
||||
$$CoreAnimateQueueProvider,
|
||||
$$CoreAnimateRunnerProvider,
|
||||
$$AnimateRunnerFactoryProvider,
|
||||
$$AnimateAsyncRunFactoryProvider,
|
||||
$BrowserProvider,
|
||||
$CacheFactoryProvider,
|
||||
$ControllerProvider,
|
||||
$DateProvider,
|
||||
$DocumentProvider,
|
||||
$ExceptionHandlerProvider,
|
||||
$FilterProvider,
|
||||
@@ -72,6 +74,7 @@
|
||||
$HttpParamSerializerProvider,
|
||||
$HttpParamSerializerJQLikeProvider,
|
||||
$HttpBackendProvider,
|
||||
$xhrFactoryProvider,
|
||||
$LocationProvider,
|
||||
$LogProvider,
|
||||
$ParseProvider,
|
||||
@@ -216,7 +219,8 @@ function publishExternalAPI(angular) {
|
||||
$animate: $AnimateProvider,
|
||||
$animateCss: $CoreAnimateCssProvider,
|
||||
$$animateQueue: $$CoreAnimateQueueProvider,
|
||||
$$AnimateRunner: $$CoreAnimateRunnerProvider,
|
||||
$$AnimateRunner: $$AnimateRunnerFactoryProvider,
|
||||
$$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
|
||||
$browser: $BrowserProvider,
|
||||
$cacheFactory: $CacheFactoryProvider,
|
||||
$controller: $ControllerProvider,
|
||||
@@ -230,6 +234,7 @@ function publishExternalAPI(angular) {
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
$httpBackend: $HttpBackendProvider,
|
||||
$xhrFactory: $xhrFactoryProvider,
|
||||
$location: $LocationProvider,
|
||||
$log: $LogProvider,
|
||||
$parse: $ParseProvider,
|
||||
|
||||
+19
-10
@@ -62,17 +62,23 @@
|
||||
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
|
||||
*/
|
||||
|
||||
var ARROW_ARG = /^([^\(]+?)=>/;
|
||||
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
|
||||
var FN_ARG_SPLIT = /,/;
|
||||
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
var $injectorMinErr = minErr('$injector');
|
||||
|
||||
function extractArgs(fn) {
|
||||
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
|
||||
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
|
||||
return args;
|
||||
}
|
||||
|
||||
function anonFn(fn) {
|
||||
// For anonymous functions, showing at the very least the function signature can help in
|
||||
// debugging.
|
||||
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
|
||||
args = fnText.match(FN_ARGS);
|
||||
var args = extractArgs(fn);
|
||||
if (args) {
|
||||
return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
|
||||
}
|
||||
@@ -81,7 +87,6 @@ function anonFn(fn) {
|
||||
|
||||
function annotate(fn, strictDi, name) {
|
||||
var $inject,
|
||||
fnText,
|
||||
argDecl,
|
||||
last;
|
||||
|
||||
@@ -96,8 +101,7 @@ function annotate(fn, strictDi, name) {
|
||||
throw $injectorMinErr('strictdi',
|
||||
'{0} is not using explicit annotation and cannot be invoked in strict mode', name);
|
||||
}
|
||||
fnText = fn.toString().replace(STRIP_COMMENTS, '');
|
||||
argDecl = fnText.match(FN_ARGS);
|
||||
argDecl = extractArgs(fn);
|
||||
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
|
||||
arg.replace(FN_ARG, function(all, underscore, name) {
|
||||
$inject.push(name);
|
||||
@@ -638,14 +642,19 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
|
||||
})),
|
||||
instanceCache = {},
|
||||
instanceInjector = (instanceCache.$injector =
|
||||
protoInstanceInjector =
|
||||
createInternalInjector(instanceCache, function(serviceName, caller) {
|
||||
var provider = providerInjector.get(serviceName + providerSuffix, caller);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
|
||||
}));
|
||||
return instanceInjector.invoke(
|
||||
provider.$get, provider, undefined, serviceName);
|
||||
}),
|
||||
instanceInjector = protoInstanceInjector;
|
||||
|
||||
|
||||
forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
|
||||
providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
|
||||
var runBlocks = loadModules(modulesToLoad);
|
||||
instanceInjector = protoInstanceInjector.get('$injector');
|
||||
instanceInjector.strictDi = strictDi;
|
||||
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });
|
||||
|
||||
return instanceInjector;
|
||||
|
||||
|
||||
+83
-43
@@ -33,16 +33,22 @@
|
||||
*
|
||||
* If jQuery is available, `angular.element` is an alias for the
|
||||
* [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
|
||||
* delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
|
||||
* delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
|
||||
*
|
||||
* <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.</div>
|
||||
* jqLite is a tiny, API-compatible subset of jQuery that allows
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.
|
||||
*
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
|
||||
* {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
|
||||
* specific version of jQuery if multiple versions exist on the page.
|
||||
*
|
||||
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
|
||||
* jqLite; they are never raw DOM references.</div>
|
||||
* <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or
|
||||
* jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
|
||||
*
|
||||
* <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
|
||||
* by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
|
||||
* or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
|
||||
*
|
||||
* ## Angular's jqLite
|
||||
* jqLite provides only the following jQuery methods:
|
||||
@@ -55,7 +61,8 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
|
||||
* As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`detach()`](http://api.jquery.com/detach/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
@@ -189,6 +196,12 @@ function jqLiteHasData(node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function jqLiteCleanData(nodes) {
|
||||
for (var i = 0, ii = nodes.length; i < ii; i++) {
|
||||
jqLiteRemoveData(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function jqLiteBuildFragment(html, context) {
|
||||
var tmp, tag, wrap,
|
||||
fragment = context.createDocumentFragment(),
|
||||
@@ -241,6 +254,14 @@ function jqLiteParseHTML(html, context) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
|
||||
var jqLiteContains = Node.prototype.contains || function(arg) {
|
||||
// jshint bitwise: false
|
||||
return !!(this.compareDocumentPosition(arg) & 16);
|
||||
// jshint bitwise: true
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////
|
||||
function JQLite(element) {
|
||||
if (element instanceof JQLite) {
|
||||
@@ -299,17 +320,23 @@ function jqLiteOff(element, type, fn, unsupported) {
|
||||
delete events[type];
|
||||
}
|
||||
} else {
|
||||
forEach(type.split(' '), function(type) {
|
||||
if (isDefined(fn)) {
|
||||
var listenerFns = events[type];
|
||||
arrayRemove(listenerFns || [], fn);
|
||||
if (listenerFns && listenerFns.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListenerFn(element, type, handle);
|
||||
delete events[type];
|
||||
var removeHandler = function(type) {
|
||||
var listenerFns = events[type];
|
||||
if (isDefined(fn)) {
|
||||
arrayRemove(listenerFns || [], fn);
|
||||
}
|
||||
if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
|
||||
removeEventListenerFn(element, type, handle);
|
||||
delete events[type];
|
||||
}
|
||||
};
|
||||
|
||||
forEach(type.split(' '), function(type) {
|
||||
removeHandler(type);
|
||||
if (MOUSE_EVENT_MAP[type]) {
|
||||
removeHandler(MOUSE_EVENT_MAP[type]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -563,7 +590,8 @@ function getAliasedAttrName(name) {
|
||||
forEach({
|
||||
data: jqLiteData,
|
||||
removeData: jqLiteRemoveData,
|
||||
hasData: jqLiteHasData
|
||||
hasData: jqLiteHasData,
|
||||
cleanData: jqLiteCleanData
|
||||
}, function(fn, name) {
|
||||
JQLite[name] = fn;
|
||||
});
|
||||
@@ -764,6 +792,9 @@ function createEventHandler(element, events) {
|
||||
return event.immediatePropagationStopped === true;
|
||||
};
|
||||
|
||||
// Some events have special handlers that wrap the real handler
|
||||
var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
|
||||
|
||||
// Copy event handlers in case event handlers array is modified during execution.
|
||||
if ((eventFnsLength > 1)) {
|
||||
eventFns = shallowCopy(eventFns);
|
||||
@@ -771,7 +802,7 @@ function createEventHandler(element, events) {
|
||||
|
||||
for (var i = 0; i < eventFnsLength; i++) {
|
||||
if (!event.isImmediatePropagationStopped()) {
|
||||
eventFns[i].call(element, event);
|
||||
handlerWrapper(element, event, eventFns[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -782,6 +813,22 @@ function createEventHandler(element, events) {
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
function defaultHandlerWrapper(element, event, handler) {
|
||||
handler.call(element, event);
|
||||
}
|
||||
|
||||
function specialMouseHandlerWrapper(target, event, handler) {
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
var related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if (!related || (related !== target && !jqLiteContains.call(target, related))) {
|
||||
handler.call(target, event);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Functions iterating traversal.
|
||||
// These functions chain results into a single
|
||||
@@ -810,35 +857,28 @@ forEach({
|
||||
var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
|
||||
var i = types.length;
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
var addHandler = function(type, specialHandlerWrapper, noEventListener) {
|
||||
var eventFns = events[type];
|
||||
|
||||
if (!eventFns) {
|
||||
events[type] = [];
|
||||
|
||||
if (type === 'mouseenter' || type === 'mouseleave') {
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
|
||||
jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
|
||||
var target = this, related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if (!related || (related !== target && !target.contains(related))) {
|
||||
handle(event, type);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (type !== '$destroy') {
|
||||
addEventListenerFn(element, type, handle);
|
||||
}
|
||||
eventFns = events[type] = [];
|
||||
eventFns.specialHandlerWrapper = specialHandlerWrapper;
|
||||
if (type !== '$destroy' && !noEventListener) {
|
||||
addEventListenerFn(element, type, handle);
|
||||
}
|
||||
eventFns = events[type];
|
||||
}
|
||||
|
||||
eventFns.push(fn);
|
||||
};
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
if (MOUSE_EVENT_MAP[type]) {
|
||||
addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
|
||||
addHandler(type, undefined, true);
|
||||
} else {
|
||||
addHandler(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+114
-1
@@ -188,7 +188,7 @@ function setupModuleLoader(window) {
|
||||
* @param {string} name constant name
|
||||
* @param {*} object Constant value.
|
||||
* @description
|
||||
* Because the constant are fixed, they get applied before other provide methods.
|
||||
* Because the constants are fixed, they get applied before other provide methods.
|
||||
* See {@link auto.$provide#constant $provide.constant()}.
|
||||
*/
|
||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||
@@ -282,6 +282,119 @@ function setupModuleLoader(window) {
|
||||
*/
|
||||
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#component
|
||||
* @module ng
|
||||
* @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
|
||||
* @param {Object} options Component definition object (a simplified
|
||||
* {@link ng.$compile#directive-definition-object directive definition object}),
|
||||
* has the following properties (all optional):
|
||||
*
|
||||
* - `controller` – `{(string|function()=}` – Controller constructor function that should be
|
||||
* associated with newly created scope or the name of a {@link ng.$compile#-controller-
|
||||
* registered controller} if passed as a string. Empty function by default.
|
||||
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
|
||||
* If present, the controller will be published to scope under the `controllerAs` name.
|
||||
* If not present, this will default to be the same as the component name.
|
||||
* - `template` – `{string=|function()=}` – html template as a string or a function that
|
||||
* returns an html template as a string which should be used as the contents of this component.
|
||||
* Empty string by default.
|
||||
*
|
||||
* If `template` is a function, then it is {@link auto.$injector#invoke injected} with
|
||||
* the following locals:
|
||||
*
|
||||
* - `$element` - Current element
|
||||
* - `$attrs` - Current attributes object for the element
|
||||
*
|
||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
|
||||
* template that should be used as the contents of this component.
|
||||
*
|
||||
* If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with
|
||||
* the following locals:
|
||||
*
|
||||
* - `$element` - Current element
|
||||
* - `$attrs` - Current attributes object for the element
|
||||
* - `bindings` – `{object=}` – Define DOM attribute binding to component properties.
|
||||
* Component properties are always bound to the component controller and not to the scope.
|
||||
* - `transclude` – `{boolean=}` – Whether {@link $compile#transclusion transclusion} is enabled.
|
||||
* Enabled by default.
|
||||
* - `isolate` – `{boolean=}` – Whether the new scope is isolated. Isolated by default.
|
||||
* - `restrict` - `{string=}` - String of subset of {@link ng.$compile#-restrict- EACM} which
|
||||
* restricts the component to specific directive declaration style. If omitted, this defaults to 'E'.
|
||||
* - `$canActivate` – `{function()=}` – TBD.
|
||||
* - `$routeConfig` – `{object=}` – TBD.
|
||||
*
|
||||
* @description
|
||||
* Register a component definition with the compiler. This is short for registering a specific
|
||||
* subset of directives which represents actual UI components in your application. Component
|
||||
* definitions are very simple and do not require the complexity behind defining directives.
|
||||
* Component definitions usually consist only of the template and the controller backing it.
|
||||
* In order to make the definition easier, components enforce best practices like controllerAs
|
||||
* and default behaviors like scope isolation, restrict to elements and allow transclusion.
|
||||
*
|
||||
* Here are a few examples of how you would usually define components:
|
||||
*
|
||||
* ```js
|
||||
* var myMod = angular.module(...);
|
||||
* myMod.component('myComp', {
|
||||
* template: '<div>My name is {{myComp.name}}</div>',
|
||||
* controller: function() {
|
||||
* this.name = 'shahar';
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* myMod.component('myComp', {
|
||||
* template: '<div>My name is {{myComp.name}}</div>',
|
||||
* bindings: {name: '@'}
|
||||
* });
|
||||
*
|
||||
* myMod.component('myComp', {
|
||||
* templateUrl: 'views/my-comp.html',
|
||||
* controller: 'MyCtrl as ctrl',
|
||||
* bindings: {name: '@'}
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* See {@link ng.$compileProvider#directive $compileProvider.directive()}.
|
||||
*/
|
||||
component: function(name, options) {
|
||||
function factory($injector) {
|
||||
function makeInjectable(fn) {
|
||||
if (angular.isFunction(fn)) {
|
||||
return function(tElement, tAttrs) {
|
||||
return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs});
|
||||
};
|
||||
} else {
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
var template = (!options.template && !options.templateUrl ? '' : options.template);
|
||||
return {
|
||||
controller: options.controller || function() {},
|
||||
controllerAs: identifierForController(options.controller) || options.controllerAs || name,
|
||||
template: makeInjectable(template),
|
||||
templateUrl: makeInjectable(options.templateUrl),
|
||||
transclude: options.transclude === undefined ? true : options.transclude,
|
||||
scope: options.isolate === false ? true : {},
|
||||
bindToController: options.bindings || {},
|
||||
restrict: options.restrict || 'E'
|
||||
};
|
||||
}
|
||||
|
||||
if (options.$canActivate) {
|
||||
factory.$canActivate = options.$canActivate;
|
||||
}
|
||||
if (options.$routeConfig) {
|
||||
factory.$routeConfig = options.$routeConfig;
|
||||
}
|
||||
factory.$inject = ['$injector'];
|
||||
|
||||
return moduleInstance.directive(name, factory);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#config
|
||||
|
||||
@@ -41,7 +41,7 @@ function $AnchorScrollProvider() {
|
||||
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
|
||||
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
|
||||
* in the
|
||||
* [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
|
||||
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document).
|
||||
*
|
||||
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
|
||||
* match any anchor whenever it changes. This can be disabled by calling
|
||||
|
||||
+7
-25
@@ -53,29 +53,6 @@ function prepareAnimateOptions(options) {
|
||||
: {};
|
||||
}
|
||||
|
||||
var $$CoreAnimateRunnerProvider = function() {
|
||||
this.$get = ['$q', '$$rAF', function($q, $$rAF) {
|
||||
function AnimateRunner() {}
|
||||
AnimateRunner.all = noop;
|
||||
AnimateRunner.chain = noop;
|
||||
AnimateRunner.prototype = {
|
||||
end: noop,
|
||||
cancel: noop,
|
||||
resume: noop,
|
||||
pause: noop,
|
||||
complete: noop,
|
||||
then: function(pass, fail) {
|
||||
return $q(function(resolve) {
|
||||
$$rAF(function() {
|
||||
resolve();
|
||||
});
|
||||
}).then(pass, fail);
|
||||
}
|
||||
};
|
||||
return AnimateRunner;
|
||||
}];
|
||||
};
|
||||
|
||||
// this is prefixed with Core since it conflicts with
|
||||
// the animateQueueProvider defined in ngAnimate/animateQueue.js
|
||||
var $$CoreAnimateQueueProvider = function() {
|
||||
@@ -101,7 +78,12 @@ var $$CoreAnimateQueueProvider = function() {
|
||||
addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
|
||||
}
|
||||
|
||||
return new $$AnimateRunner(); // jshint ignore:line
|
||||
var runner = new $$AnimateRunner(); // jshint ignore:line
|
||||
|
||||
// since there are no animations to run the runner needs to be
|
||||
// notified that the animation call is complete.
|
||||
runner.complete();
|
||||
return runner;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,7 +267,7 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* when an animation is detected (and animations are enabled), $animate will do the heavy lifting
|
||||
* to ensure that animation runs with the triggered DOM operation.
|
||||
*
|
||||
* By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
|
||||
* By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
|
||||
* included and only when it is active then the animation hooks that `$animate` triggers will be
|
||||
* functional. Once active then all structural `ng-` directives will trigger animations as they perform
|
||||
* their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
|
||||
|
||||
+17
-32
@@ -12,43 +12,28 @@
|
||||
* Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
|
||||
*/
|
||||
var $CoreAnimateCssProvider = function() {
|
||||
this.$get = ['$$rAF', '$q', function($$rAF, $q) {
|
||||
this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
|
||||
|
||||
var RAFPromise = function() {};
|
||||
RAFPromise.prototype = {
|
||||
done: function(cancel) {
|
||||
this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
|
||||
},
|
||||
end: function() {
|
||||
this.done();
|
||||
},
|
||||
cancel: function() {
|
||||
this.done(true);
|
||||
},
|
||||
getPromise: function() {
|
||||
if (!this.defer) {
|
||||
this.defer = $q.defer();
|
||||
}
|
||||
return this.defer.promise;
|
||||
},
|
||||
then: function(f1,f2) {
|
||||
return this.getPromise().then(f1,f2);
|
||||
},
|
||||
'catch': function(f1) {
|
||||
return this.getPromise()['catch'](f1);
|
||||
},
|
||||
'finally': function(f1) {
|
||||
return this.getPromise()['finally'](f1);
|
||||
return function(element, initialOptions) {
|
||||
// we always make a copy of the options since
|
||||
// there should never be any side effects on
|
||||
// the input data when running `$animateCss`.
|
||||
var options = copy(initialOptions);
|
||||
|
||||
// there is no point in applying the styles since
|
||||
// there is no animation that goes on at all in
|
||||
// this version of $animateCss.
|
||||
if (options.cleanupStyles) {
|
||||
options.from = options.to = null;
|
||||
}
|
||||
};
|
||||
|
||||
return function(element, options) {
|
||||
if (options.from) {
|
||||
element.css(options.from);
|
||||
options.from = null;
|
||||
}
|
||||
|
||||
var closed, runner = new RAFPromise();
|
||||
/* jshint newcap: false */
|
||||
var closed, runner = new $$AnimateRunner();
|
||||
return {
|
||||
start: run,
|
||||
end: run
|
||||
@@ -56,16 +41,16 @@ var $CoreAnimateCssProvider = function() {
|
||||
|
||||
function run() {
|
||||
$$rAF(function() {
|
||||
close();
|
||||
applyAnimationContents();
|
||||
if (!closed) {
|
||||
runner.done();
|
||||
runner.complete();
|
||||
}
|
||||
closed = true;
|
||||
});
|
||||
return runner;
|
||||
}
|
||||
|
||||
function close() {
|
||||
function applyAnimationContents() {
|
||||
if (options.addClass) {
|
||||
element.addClass(options.addClass);
|
||||
options.addClass = null;
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
'use strict';
|
||||
|
||||
var $$AnimateAsyncRunFactoryProvider = function() {
|
||||
this.$get = ['$$rAF', function($$rAF) {
|
||||
var waitQueue = [];
|
||||
|
||||
function waitForTick(fn) {
|
||||
waitQueue.push(fn);
|
||||
if (waitQueue.length > 1) return;
|
||||
$$rAF(function() {
|
||||
for (var i = 0; i < waitQueue.length; i++) {
|
||||
waitQueue[i]();
|
||||
}
|
||||
waitQueue = [];
|
||||
});
|
||||
}
|
||||
|
||||
return function() {
|
||||
var passed = false;
|
||||
waitForTick(function() {
|
||||
passed = true;
|
||||
});
|
||||
return function(callback) {
|
||||
passed ? callback() : waitForTick(callback);
|
||||
};
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
var $$AnimateRunnerFactoryProvider = function() {
|
||||
this.$get = ['$q', '$sniffer', '$$animateAsyncRun',
|
||||
function($q, $sniffer, $$animateAsyncRun) {
|
||||
|
||||
var INITIAL_STATE = 0;
|
||||
var DONE_PENDING_STATE = 1;
|
||||
var DONE_COMPLETE_STATE = 2;
|
||||
|
||||
AnimateRunner.chain = function(chain, callback) {
|
||||
var index = 0;
|
||||
|
||||
next();
|
||||
function next() {
|
||||
if (index === chain.length) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
chain[index](function(response) {
|
||||
if (response === false) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AnimateRunner.all = function(runners, callback) {
|
||||
var count = 0;
|
||||
var status = true;
|
||||
forEach(runners, function(runner) {
|
||||
runner.done(onProgress);
|
||||
});
|
||||
|
||||
function onProgress(response) {
|
||||
status = status && response;
|
||||
if (++count === runners.length) {
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function AnimateRunner(host) {
|
||||
this.setHost(host);
|
||||
|
||||
this._doneCallbacks = [];
|
||||
this._runInAnimationFrame = $$animateAsyncRun();
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
AnimateRunner.prototype = {
|
||||
setHost: function(host) {
|
||||
this.host = host || {};
|
||||
},
|
||||
|
||||
done: function(fn) {
|
||||
if (this._state === DONE_COMPLETE_STATE) {
|
||||
fn();
|
||||
} else {
|
||||
this._doneCallbacks.push(fn);
|
||||
}
|
||||
},
|
||||
|
||||
progress: noop,
|
||||
|
||||
getPromise: function() {
|
||||
if (!this.promise) {
|
||||
var self = this;
|
||||
this.promise = $q(function(resolve, reject) {
|
||||
self.done(function(status) {
|
||||
status === false ? reject() : resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this.promise;
|
||||
},
|
||||
|
||||
then: function(resolveHandler, rejectHandler) {
|
||||
return this.getPromise().then(resolveHandler, rejectHandler);
|
||||
},
|
||||
|
||||
'catch': function(handler) {
|
||||
return this.getPromise()['catch'](handler);
|
||||
},
|
||||
|
||||
'finally': function(handler) {
|
||||
return this.getPromise()['finally'](handler);
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
if (this.host.pause) {
|
||||
this.host.pause();
|
||||
}
|
||||
},
|
||||
|
||||
resume: function() {
|
||||
if (this.host.resume) {
|
||||
this.host.resume();
|
||||
}
|
||||
},
|
||||
|
||||
end: function() {
|
||||
if (this.host.end) {
|
||||
this.host.end();
|
||||
}
|
||||
this._resolve(true);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
if (this.host.cancel) {
|
||||
this.host.cancel();
|
||||
}
|
||||
this._resolve(false);
|
||||
},
|
||||
|
||||
complete: function(response) {
|
||||
var self = this;
|
||||
if (self._state === INITIAL_STATE) {
|
||||
self._state = DONE_PENDING_STATE;
|
||||
self._runInAnimationFrame(function() {
|
||||
self._resolve(response);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_resolve: function(response) {
|
||||
if (this._state !== DONE_COMPLETE_STATE) {
|
||||
forEach(this._doneCallbacks, function(fn) {
|
||||
fn(response);
|
||||
});
|
||||
this._doneCallbacks.length = 0;
|
||||
this._state = DONE_COMPLETE_STATE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return AnimateRunner;
|
||||
}];
|
||||
};
|
||||
@@ -67,10 +67,10 @@
|
||||
$scope.keys = [];
|
||||
$scope.cache = $cacheFactory('cacheId');
|
||||
$scope.put = function(key, value) {
|
||||
if (isUndefined($scope.cache.get(key))) {
|
||||
if (angular.isUndefined($scope.cache.get(key))) {
|
||||
$scope.keys.push(key);
|
||||
}
|
||||
$scope.cache.put(key, isUndefined(value) ? null : value);
|
||||
$scope.cache.put(key, angular.isUndefined(value) ? null : value);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
@@ -93,9 +93,9 @@ function $CacheFactoryProvider() {
|
||||
|
||||
var size = 0,
|
||||
stats = extend({}, options, {id: cacheId}),
|
||||
data = {},
|
||||
data = createMap(),
|
||||
capacity = (options && options.capacity) || Number.MAX_VALUE,
|
||||
lruHash = {},
|
||||
lruHash = createMap(),
|
||||
freshEnd = null,
|
||||
staleEnd = null;
|
||||
|
||||
@@ -223,6 +223,8 @@ function $CacheFactoryProvider() {
|
||||
delete lruHash[key];
|
||||
}
|
||||
|
||||
if (!(key in data)) return;
|
||||
|
||||
delete data[key];
|
||||
size--;
|
||||
},
|
||||
@@ -237,9 +239,9 @@ function $CacheFactoryProvider() {
|
||||
* Clears the cache object of any entries.
|
||||
*/
|
||||
removeAll: function() {
|
||||
data = {};
|
||||
data = createMap();
|
||||
size = 0;
|
||||
lruHash = {};
|
||||
lruHash = createMap();
|
||||
freshEnd = staleEnd = null;
|
||||
},
|
||||
|
||||
@@ -399,4 +401,3 @@ function $TemplateCacheProvider() {
|
||||
return $cacheFactory('templates');
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
+301
-125
@@ -226,10 +226,10 @@
|
||||
* * `$element` - Current element
|
||||
* * `$attrs` - Current attributes object for the element
|
||||
* * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
|
||||
* `function([scope], cloneLinkingFn, futureParentElement)`.
|
||||
* * `scope`: optional argument to override the scope.
|
||||
* * `cloneLinkingFn`: optional argument to create clones of the original transcluded content.
|
||||
* * `futureParentElement`:
|
||||
* `function([scope], cloneLinkingFn, futureParentElement, slotName)`:
|
||||
* * `scope`: (optional) override the scope.
|
||||
* * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content.
|
||||
* * `futureParentElement` (optional):
|
||||
* * defines the parent to which the `cloneLinkingFn` will add the cloned elements.
|
||||
* * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`.
|
||||
* * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements)
|
||||
@@ -237,7 +237,10 @@
|
||||
* as those elements need to created and cloned in a special way when they are defined outside their
|
||||
* usual containers (e.g. like `<svg>`).
|
||||
* * See also the `directive.templateNamespace` property.
|
||||
*
|
||||
* * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`)
|
||||
* then the default translusion is provided.
|
||||
* The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns
|
||||
* `true` if the specified slot contains content (i.e. one or more DOM nodes).
|
||||
*
|
||||
* #### `require`
|
||||
* Require another directive and inject its controller as the fourth argument to the linking function. The
|
||||
@@ -337,14 +340,6 @@
|
||||
* The contents are compiled and provided to the directive as a **transclusion function**. See the
|
||||
* {@link $compile#transclusion Transclusion} section below.
|
||||
*
|
||||
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element or the entire element:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
*
|
||||
*
|
||||
* #### `compile`
|
||||
*
|
||||
@@ -474,6 +469,34 @@
|
||||
* Testing Transclusion Directives}.
|
||||
* </div>
|
||||
*
|
||||
* There are three kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element, the entire element or multiple parts of the element contents:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
* * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template.
|
||||
*
|
||||
* **Mult-slot transclusion** is declared by providing an object for the `transclude` property.
|
||||
*
|
||||
* This object is a map where the keys are the name of the slot to fill and the value is an element selector
|
||||
* used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`)
|
||||
* and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc).
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* If the element selector is prefixed with a `?` then that slot is optional.
|
||||
*
|
||||
* For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to
|
||||
* the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive.
|
||||
*
|
||||
* Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements
|
||||
* in the transclude content. If you wish to know if an optional slot was filled with content, then you can call
|
||||
* `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and
|
||||
* injectable into the directive's controller.
|
||||
*
|
||||
*
|
||||
* #### Transclusion Functions
|
||||
*
|
||||
* When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
|
||||
@@ -989,6 +1012,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
|
||||
$controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
|
||||
|
||||
var SIMPLE_ATTR_NAME = /^\w/;
|
||||
var specialAttrHolder = document.createElement('div');
|
||||
var Attributes = function(element, attributesToCopy) {
|
||||
if (attributesToCopy) {
|
||||
var keys = Object.keys(attributesToCopy);
|
||||
@@ -1124,7 +1149,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
if ((nodeName === 'a' && key === 'href') ||
|
||||
if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
|
||||
(nodeName === 'img' && key === 'src')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = $$sanitizeUri(value, key === 'src');
|
||||
@@ -1168,7 +1193,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (value === null || isUndefined(value)) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
if (SIMPLE_ATTR_NAME.test(attrName)) {
|
||||
this.$$element.attr(attrName, value);
|
||||
} else {
|
||||
setSpecialAttr(this.$$element[0], attrName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1221,6 +1250,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
};
|
||||
|
||||
function setSpecialAttr(element, attrName, value) {
|
||||
// Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute`
|
||||
// so we have to jump through some hoops to get such an attribute
|
||||
// https://github.com/angular/angular.js/pull/13318
|
||||
specialAttrHolder.innerHTML = "<span " + attrName + ">";
|
||||
var attributes = specialAttrHolder.firstChild.attributes;
|
||||
var attribute = attributes[0];
|
||||
// We have to remove the attribute from its container element before we can add it to the destination element
|
||||
attributes.removeNamedItem(attribute.name);
|
||||
attribute.value = value;
|
||||
element.attributes.setNamedItem(attribute);
|
||||
}
|
||||
|
||||
function safeAddClass($element, className) {
|
||||
try {
|
||||
@@ -1240,6 +1281,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
||||
},
|
||||
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
||||
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
|
||||
|
||||
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
|
||||
var bindings = $element.data('$binding') || [];
|
||||
@@ -1280,7 +1322,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// We can not compile top level text elements since text nodes can be merged and we will
|
||||
// not be able to attach scope data to them, so we will wrap them in <span>
|
||||
forEach($compileNodes, function(node, index) {
|
||||
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
||||
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */) {
|
||||
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
|
||||
}
|
||||
});
|
||||
@@ -1292,6 +1334,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return function publicLinkFn(scope, cloneConnectFn, options) {
|
||||
assertArg(scope, 'scope');
|
||||
|
||||
if (previousCompileContext && previousCompileContext.needsNewScope) {
|
||||
// A parent directive did a replace and a directive on this element asked
|
||||
// for transclusion, which caused us to lose a layer of element on which
|
||||
// we could hold the new transclusion scope, so we will create it manually
|
||||
// here.
|
||||
scope = scope.$parent.$new();
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
|
||||
transcludeControllers = options.transcludeControllers,
|
||||
@@ -1437,11 +1487,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (nodeLinkFn.scope) {
|
||||
childScope = scope.$new();
|
||||
compile.$$addScopeInfo(jqLite(node), childScope);
|
||||
var destroyBindings = nodeLinkFn.$$destroyBindings;
|
||||
if (destroyBindings) {
|
||||
nodeLinkFn.$$destroyBindings = null;
|
||||
childScope.$on('$destroyed', destroyBindings);
|
||||
}
|
||||
} else {
|
||||
childScope = scope;
|
||||
}
|
||||
@@ -1460,8 +1505,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = null;
|
||||
}
|
||||
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
|
||||
nodeLinkFn);
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
|
||||
|
||||
} else if (childLinkFn) {
|
||||
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
|
||||
@@ -1486,6 +1530,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
};
|
||||
|
||||
// We need to attach the transclusion slots onto the `boundTranscludeFn`
|
||||
// so that they are available inside the `controllersBoundTransclude` function
|
||||
var boundSlots = boundTranscludeFn.$$slots = createMap();
|
||||
for (var slotName in transcludeFn.$$slots) {
|
||||
if (transcludeFn.$$slots[slotName]) {
|
||||
boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn);
|
||||
} else {
|
||||
boundSlots[slotName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return boundTranscludeFn;
|
||||
}
|
||||
|
||||
@@ -1530,13 +1585,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
if (directiveIsMultiElement(directiveNName)) {
|
||||
if (ngAttrName === directiveNName + 'Start') {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
|
||||
if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
@@ -1646,6 +1699,37 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A function generator that is used to support both eager and lazy compilation
|
||||
* linking function.
|
||||
* @param eager
|
||||
* @param $compileNodes
|
||||
* @param transcludeFn
|
||||
* @param maxPriority
|
||||
* @param ignoreDirective
|
||||
* @param previousCompileContext
|
||||
* @returns {Function}
|
||||
*/
|
||||
function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
|
||||
if (eager) {
|
||||
return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
}
|
||||
|
||||
var compiled;
|
||||
|
||||
return function() {
|
||||
if (!compiled) {
|
||||
compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext);
|
||||
|
||||
// Null out all of these references in order to make them eligible for garbage collection
|
||||
// since this is a potentially long lived closure
|
||||
$compileNodes = transcludeFn = previousCompileContext = null;
|
||||
}
|
||||
|
||||
return compiled.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Once the directives have been collected, their compile functions are executed. This method
|
||||
* is responsible for inlining directive templates as well as terminating the application
|
||||
@@ -1690,6 +1774,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
replaceDirective = originalReplaceDirective,
|
||||
childTranscludeFn = transcludeFn,
|
||||
linkFn,
|
||||
didScanForMultipleTransclusion = false,
|
||||
mightHaveMultipleTransclusionError = false,
|
||||
directiveValue;
|
||||
|
||||
// executes all directives on the current element
|
||||
@@ -1732,6 +1818,27 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
directiveName = directive.name;
|
||||
|
||||
// If we encounter a condition that can result in transclusion on the directive,
|
||||
// then scan ahead in the remaining directives for others that may cause a multiple
|
||||
// transclusion error to be thrown during the compilation process. If a matching directive
|
||||
// is found, then we know that when we encounter a transcluded directive, we need to eagerly
|
||||
// compile the `transclude` function rather than doing it lazily in order to throw
|
||||
// exceptions at the correct time
|
||||
if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template))
|
||||
|| (directive.transclude && !directive.$$tlb))) {
|
||||
var candidateDirective;
|
||||
|
||||
for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) {
|
||||
if ((candidateDirective.transclude && !candidateDirective.$$tlb)
|
||||
|| (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) {
|
||||
mightHaveMultipleTransclusionError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
didScanForMultipleTransclusion = true;
|
||||
}
|
||||
|
||||
if (!directive.templateUrl && directive.controller) {
|
||||
directiveValue = directive.controller;
|
||||
controllerDirectives = controllerDirectives || createMap();
|
||||
@@ -1761,7 +1868,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compileNode = $compileNode[0];
|
||||
replaceWith(jqCollection, sliceArgs($template), compileNode);
|
||||
|
||||
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
|
||||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority,
|
||||
replaceDirective && replaceDirective.name, {
|
||||
// Don't pass in:
|
||||
// - controllerDirectives - otherwise we'll create duplicates controllers
|
||||
@@ -1773,9 +1880,69 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
nonTlbTranscludeDirective: nonTlbTranscludeDirective
|
||||
});
|
||||
} else {
|
||||
|
||||
var slots = createMap();
|
||||
|
||||
$template = jqLite(jqLiteClone(compileNode)).contents();
|
||||
|
||||
if (isObject(directiveValue)) {
|
||||
|
||||
// We have transclusion slots,
|
||||
// collect them up, compile them and store their transclusion functions
|
||||
$template = [];
|
||||
|
||||
var slotMap = createMap();
|
||||
var filledSlots = createMap();
|
||||
|
||||
// Parse the element selectors
|
||||
forEach(directiveValue, function(elementSelector, slotName) {
|
||||
// If an element selector starts with a ? then it is optional
|
||||
var optional = (elementSelector.charAt(0) === '?');
|
||||
elementSelector = optional ? elementSelector.substring(1) : elementSelector;
|
||||
|
||||
slotMap[elementSelector] = slotName;
|
||||
|
||||
// We explicitly assign `null` since this implies that a slot was defined but not filled.
|
||||
// Later when calling boundTransclusion functions with a slot name we only error if the
|
||||
// slot is `undefined`
|
||||
slots[slotName] = null;
|
||||
|
||||
// filledSlots contains `true` for all slots that are either optional or have been
|
||||
// filled. This is used to check that we have not missed any required slots
|
||||
filledSlots[slotName] = optional;
|
||||
});
|
||||
|
||||
// Add the matching elements into their slot
|
||||
forEach($compileNode.contents(), function(node) {
|
||||
var slotName = slotMap[directiveNormalize(nodeName_(node))];
|
||||
if (slotName) {
|
||||
filledSlots[slotName] = true;
|
||||
slots[slotName] = slots[slotName] || [];
|
||||
slots[slotName].push(node);
|
||||
} else {
|
||||
$template.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for required slots that were not filled
|
||||
forEach(filledSlots, function(filled, slotName) {
|
||||
if (!filled) {
|
||||
throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName);
|
||||
}
|
||||
});
|
||||
|
||||
for (var slotName in slots) {
|
||||
if (slots[slotName]) {
|
||||
// Only define a transclusion function if the slot was filled
|
||||
slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compileNode.empty(); // clear contents
|
||||
childTranscludeFn = compile($template, transcludeFn);
|
||||
childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined,
|
||||
undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
|
||||
childTranscludeFn.$$slots = slots;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1817,8 +1984,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
|
||||
var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
markDirectivesAsIsolate(templateDirectives);
|
||||
if (newIsolateScopeDirective || newScopeDirective) {
|
||||
// The original directive caused the current element to be replaced but this element
|
||||
// also needs to have a new scope, so we need to tell the template directives
|
||||
// that they would need to get their scope from further up, if they require transclusion
|
||||
markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
|
||||
}
|
||||
directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
|
||||
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
|
||||
@@ -1971,10 +2141,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
|
||||
thisLinkFn) {
|
||||
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
|
||||
attrs;
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
||||
var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
|
||||
attrs, removeScopeBindingWatches, removeControllerBindingWatches;
|
||||
|
||||
if (compileNode === linkNode) {
|
||||
attrs = templateAttrs;
|
||||
@@ -1984,8 +2153,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
attrs = new Attributes($element, templateAttrs);
|
||||
}
|
||||
|
||||
controllerScope = scope;
|
||||
if (newIsolateScopeDirective) {
|
||||
isolateScope = scope.$new(true);
|
||||
} else if (newScopeDirective) {
|
||||
controllerScope = scope.$parent;
|
||||
}
|
||||
|
||||
if (boundTranscludeFn) {
|
||||
@@ -1993,6 +2165,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// is later passed as `parentBoundTranscludeFn` to `publicLinkFn`
|
||||
transcludeFn = controllersBoundTransclude;
|
||||
transcludeFn.$$boundTransclude = boundTranscludeFn;
|
||||
// expose the slots on the `$transclude` function
|
||||
transcludeFn.isSlotFilled = function(slotName) {
|
||||
return !!boundTranscludeFn.$$slots[slotName];
|
||||
};
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
@@ -2006,42 +2182,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile.$$addScopeClass($element, true);
|
||||
isolateScope.$$isolateBindings =
|
||||
newIsolateScopeDirective.$$isolateBindings;
|
||||
initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective, isolateScope);
|
||||
}
|
||||
if (elementControllers) {
|
||||
// Initialize bindToController bindings for new/isolate scopes
|
||||
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
|
||||
var bindings;
|
||||
var controllerForBindings;
|
||||
if (scopeDirective && elementControllers[scopeDirective.name]) {
|
||||
bindings = scopeDirective.$$bindings.bindToController;
|
||||
controller = elementControllers[scopeDirective.name];
|
||||
|
||||
if (controller && controller.identifier && bindings) {
|
||||
controllerForBindings = controller;
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controller.instance,
|
||||
bindings, scopeDirective);
|
||||
}
|
||||
removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective);
|
||||
if (removeScopeBindingWatches) {
|
||||
isolateScope.$on('$destroy', removeScopeBindingWatches);
|
||||
}
|
||||
for (i in elementControllers) {
|
||||
controller = elementControllers[i];
|
||||
var controllerResult = controller();
|
||||
}
|
||||
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers and update the element data
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + i + 'Controller', controllerResult);
|
||||
if (controller === controllerForBindings) {
|
||||
// Remove and re-install bindToController bindings
|
||||
thisLinkFn.$$destroyBindings();
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
|
||||
}
|
||||
}
|
||||
// Initialize bindToController bindings
|
||||
for (var name in elementControllers) {
|
||||
var controllerDirective = controllerDirectives[name];
|
||||
var controller = elementControllers[name];
|
||||
var bindings = controllerDirective.$$bindings.bindToController;
|
||||
|
||||
if (controller.identifier && bindings) {
|
||||
removeControllerBindingWatches =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
|
||||
var controllerResult = controller();
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
|
||||
removeControllerBindingWatches && removeControllerBindingWatches();
|
||||
removeControllerBindingWatches =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2080,11 +2248,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// This is the function that is injected as `$transclude`.
|
||||
// Note: all arguments are optional!
|
||||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) {
|
||||
function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) {
|
||||
var transcludeControllers;
|
||||
|
||||
// No scope passed in:
|
||||
if (!isScope(scope)) {
|
||||
slotName = futureParentElement;
|
||||
futureParentElement = cloneAttachFn;
|
||||
cloneAttachFn = scope;
|
||||
scope = undefined;
|
||||
@@ -2096,15 +2264,36 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!futureParentElement) {
|
||||
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
|
||||
}
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
if (slotName) {
|
||||
// slotTranscludeFn can be one of three things:
|
||||
// * a transclude function - a filled slot
|
||||
// * `null` - an optional slot that was not filled
|
||||
// * `undefined` - a slot that was not declared (i.e. invalid)
|
||||
var slotTranscludeFn = boundTranscludeFn.$$slots[slotName];
|
||||
if (slotTranscludeFn) {
|
||||
return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
} else if (isUndefined(slotTranscludeFn)) {
|
||||
throw $compileMinErr('noslot',
|
||||
'No parent directive that requires a transclusion with slot name "{0}". ' +
|
||||
'Element: {1}',
|
||||
slotName, startingTag($element));
|
||||
}
|
||||
} else {
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function markDirectivesAsIsolate(directives) {
|
||||
// mark all directives as needing isolate scope.
|
||||
// Depending upon the context in which a directive finds itself it might need to have a new isolated
|
||||
// or child scope created. For instance:
|
||||
// * if the directive has been pulled into a template because another directive with a higher priority
|
||||
// asked for element transclusion
|
||||
// * if the directive itself asks for transclusion but it is at the root of a template and the original
|
||||
// element was replaced. See https://github.com/angular/angular.js/issues/12936
|
||||
function markDirectiveScope(directives, isolateScope, newScope) {
|
||||
for (var j = 0, jj = directives.length; j < jj; j++) {
|
||||
directives[j] = inherit(directives[j], {$$isolateScope: true});
|
||||
directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2251,7 +2440,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
|
||||
|
||||
if (isObject(origAsyncDirective.scope)) {
|
||||
markDirectivesAsIsolate(templateDirectives);
|
||||
// the original directive that caused the template to be loaded async required
|
||||
// an isolate scope
|
||||
markDirectiveScope(templateDirectives, true);
|
||||
}
|
||||
directives = templateDirectives.concat(directives);
|
||||
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
|
||||
@@ -2300,7 +2491,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
|
||||
childBoundTranscludeFn, afterTemplateNodeLinkFn);
|
||||
childBoundTranscludeFn);
|
||||
}
|
||||
linkQueue = null;
|
||||
});
|
||||
@@ -2317,8 +2508,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
|
||||
afterTemplateNodeLinkFn);
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2427,7 +2617,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile: function() {
|
||||
return {
|
||||
pre: function attrInterpolatePreLinkFn(scope, element, attr) {
|
||||
var $$observers = (attr.$$observers || (attr.$$observers = {}));
|
||||
var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
|
||||
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
||||
throw $compileMinErr('nodomevents',
|
||||
@@ -2522,41 +2712,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
parent.replaceChild(newNode, firstElementToRemove);
|
||||
}
|
||||
|
||||
// TODO(perf): what's this document fragment for? is it needed? can we at least reuse it?
|
||||
// Append all the `elementsToRemove` to a fragment. This will...
|
||||
// - remove them from the DOM
|
||||
// - allow them to still be traversed with .nextSibling
|
||||
// - allow a single fragment.qSA to fetch all elements being removed
|
||||
var fragment = document.createDocumentFragment();
|
||||
fragment.appendChild(firstElementToRemove);
|
||||
for (i = 0; i < removeCount; i++) {
|
||||
fragment.appendChild(elementsToRemove[i]);
|
||||
}
|
||||
|
||||
if (jqLite.hasData(firstElementToRemove)) {
|
||||
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
|
||||
// data here because there's no public interface in jQuery to do that and copying over
|
||||
// event listeners (which is the main use of private data) wouldn't work anyway.
|
||||
jqLite(newNode).data(jqLite(firstElementToRemove).data());
|
||||
jqLite.data(newNode, jqLite.data(firstElementToRemove));
|
||||
|
||||
// Remove data of the replaced element. We cannot just call .remove()
|
||||
// on the element it since that would deallocate scope that is needed
|
||||
// for the new node. Instead, remove the data "manually".
|
||||
if (!jQuery) {
|
||||
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
|
||||
} else {
|
||||
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
|
||||
// the replaced element. The cleanData version monkey-patched by Angular would cause
|
||||
// the scope to be trashed and we do need the very same scope to work with the new
|
||||
// element. However, we cannot just cache the non-patched version and use it here as
|
||||
// that would break if another library patches the method after Angular does (one
|
||||
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
|
||||
// skipped this one time.
|
||||
skipDestroyOnNextJQueryCleanData = true;
|
||||
jQuery.cleanData([firstElementToRemove]);
|
||||
}
|
||||
// Remove $destroy event listeners from `firstElementToRemove`
|
||||
jqLite(firstElementToRemove).off('$destroy');
|
||||
}
|
||||
|
||||
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
|
||||
var element = elementsToRemove[k];
|
||||
jqLite(element).remove(); // must do this way to clean up expando
|
||||
fragment.appendChild(element);
|
||||
delete elementsToRemove[k];
|
||||
}
|
||||
// Cleanup any data/listeners on the elements and children.
|
||||
// This includes invoking the $destroy event on any elements with listeners.
|
||||
jqLite.cleanData(fragment.querySelectorAll('*'));
|
||||
|
||||
// Update the jqLite collection to only contain the `newNode`
|
||||
for (i = 1; i < removeCount; i++) {
|
||||
delete elementsToRemove[i];
|
||||
}
|
||||
elementsToRemove[0] = newNode;
|
||||
elementsToRemove.length = 1;
|
||||
}
|
||||
@@ -2578,9 +2760,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// Set up $watches for isolate scope and controller bindings. This process
|
||||
// only occurs for isolate scopes and new scopes with controllerAs.
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings,
|
||||
directive, newScope) {
|
||||
var onNewScopeDestroyed;
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
|
||||
var removeWatchCollection = [];
|
||||
forEach(bindings, function(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
@@ -2642,14 +2823,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return lastValue = parentValue;
|
||||
};
|
||||
parentValueWatch.$stateful = true;
|
||||
var unwatch;
|
||||
var removeWatch;
|
||||
if (definition.collection) {
|
||||
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
} else {
|
||||
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
}
|
||||
onNewScopeDestroyed = (onNewScopeDestroyed || []);
|
||||
onNewScopeDestroyed.push(unwatch);
|
||||
removeWatchCollection.push(removeWatch);
|
||||
break;
|
||||
|
||||
case '&':
|
||||
@@ -2665,16 +2845,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
|
||||
for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
|
||||
onNewScopeDestroyed[i]();
|
||||
|
||||
return removeWatchCollection.length && function removeWatches() {
|
||||
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
|
||||
removeWatchCollection[i]();
|
||||
}
|
||||
} : noop;
|
||||
if (newScope && destroyBindings !== noop) {
|
||||
newScope.$on('$destroy', destroyBindings);
|
||||
return noop;
|
||||
}
|
||||
return destroyBindings;
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+16
-19
@@ -11,7 +11,8 @@
|
||||
|
||||
// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
|
||||
var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
|
||||
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
|
||||
// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987)
|
||||
var URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/[\]$'()*,;~]*)?$/;
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
||||
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
|
||||
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
|
||||
@@ -44,8 +45,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -332,7 +333,7 @@ var inputType = {
|
||||
*
|
||||
* @description
|
||||
* Input with time validation and transformation. In browsers that do not yet support
|
||||
* the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
||||
* the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601
|
||||
* local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a
|
||||
* Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`.
|
||||
*
|
||||
@@ -679,8 +680,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -777,8 +778,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -876,8 +877,8 @@ var inputType = {
|
||||
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
|
||||
* that contains the regular expression body that will be converted to a regular expression
|
||||
* as in the ngPattern directive.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -1337,11 +1338,7 @@ function badInputChecker(scope, element, attr, ctrl) {
|
||||
if (nativeValidation) {
|
||||
ctrl.$parsers.push(function(value) {
|
||||
var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
|
||||
// Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
|
||||
// - also sets validity.badInput (should only be validity.typeMismatch).
|
||||
// - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
|
||||
// - can ignore this case as we can still read out the erroneous email...
|
||||
return validity.badInput && !validity.typeMismatch ? undefined : value;
|
||||
return validity.badInput || validity.typeMismatch ? undefined : value;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1513,8 +1510,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
||||
* length.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
@@ -1552,8 +1549,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
|
||||
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
|
||||
* maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
|
||||
* length.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
|
||||
* a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
|
||||
* value does not match a RegExp found by evaluating the Angular expression given in the attribute value.
|
||||
* If the expression evaluates to a RegExp object, then this is used directly.
|
||||
* If the expression evaluates to a string, then it will be converted to a RegExp
|
||||
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
|
||||
|
||||
@@ -34,7 +34,13 @@
|
||||
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
|
||||
* make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
|
||||
* @param {string=} onload Expression to evaluate when a new partial is loaded.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** When using onload on SVG elements in IE11, the browser will try to call
|
||||
* a function with the name on the window element, which will usually throw a
|
||||
* "function is undefined" error. To fix this, you can instead use `data-onload` or a
|
||||
* different form that {@link guide/directive#normalization matches} `onload`.
|
||||
* </div>
|
||||
*
|
||||
* @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
|
||||
* $anchorScroll} to scroll the viewport after the content is loaded.
|
||||
*
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* ### Example - splitting on whitespace
|
||||
* ### Example - splitting on newline
|
||||
* <example name="ngList-directive-newlines">
|
||||
* <file name="index.html">
|
||||
* <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
|
||||
|
||||
+86
-37
@@ -14,7 +14,9 @@ var VALID_CLASS = 'ng-valid',
|
||||
DIRTY_CLASS = 'ng-dirty',
|
||||
UNTOUCHED_CLASS = 'ng-untouched',
|
||||
TOUCHED_CLASS = 'ng-touched',
|
||||
PENDING_CLASS = 'ng-pending';
|
||||
PENDING_CLASS = 'ng-pending',
|
||||
EMPTY_CLASS = 'ng-empty',
|
||||
NOT_EMPTY_CLASS = 'ng-not-empty';
|
||||
|
||||
var ngModelMinErr = minErr('ngModel');
|
||||
|
||||
@@ -22,7 +24,9 @@ var ngModelMinErr = minErr('ngModel');
|
||||
* @ngdoc type
|
||||
* @name ngModel.NgModelController
|
||||
*
|
||||
* @property {string} $viewValue Actual string value in the view.
|
||||
* @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
|
||||
* String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
|
||||
* is set.
|
||||
* @property {*} $modelValue The value in the model that the control is bound to.
|
||||
* @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
|
||||
the control reads value from the DOM. The functions are called in array order, each passing
|
||||
@@ -316,6 +320,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
return isUndefined(value) || value === '' || value === null || value !== value;
|
||||
};
|
||||
|
||||
this.$$updateEmptyClasses = function(value) {
|
||||
if (ctrl.$isEmpty(value)) {
|
||||
$animate.removeClass($element, NOT_EMPTY_CLASS);
|
||||
$animate.addClass($element, EMPTY_CLASS);
|
||||
} else {
|
||||
$animate.removeClass($element, EMPTY_CLASS);
|
||||
$animate.addClass($element, NOT_EMPTY_CLASS);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var currentValidationRunId = 0;
|
||||
|
||||
/**
|
||||
@@ -433,11 +448,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* which may be caused by a pending debounced event or because the input is waiting for a some
|
||||
* future event.
|
||||
*
|
||||
* If you have an input that uses `ng-model-options` to set up debounced events or events such
|
||||
* as blur you can have a situation where there is a period when the `$viewValue`
|
||||
* is out of synch with the ngModel's `$modelValue`.
|
||||
* If you have an input that uses `ng-model-options` to set up debounced updates or updates that
|
||||
* depend on special events such as blur, you can have a situation where there is a period when
|
||||
* the `$viewValue` is out of sync with the ngModel's `$modelValue`.
|
||||
*
|
||||
* In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
|
||||
* In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update
|
||||
* and reset the input to the last committed view value.
|
||||
*
|
||||
* It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue`
|
||||
* programmatically before these debounced/future events have resolved/occurred, because Angular's
|
||||
* dirty checking mechanism is not able to tell whether the model has actually changed or not.
|
||||
*
|
||||
@@ -450,39 +468,63 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* angular.module('cancel-update-example', [])
|
||||
*
|
||||
* .controller('CancelUpdateController', ['$scope', function($scope) {
|
||||
* $scope.resetWithCancel = function(e) {
|
||||
* $scope.model = {};
|
||||
*
|
||||
* $scope.setEmpty = function(e, value, rollback) {
|
||||
* if (e.keyCode == 27) {
|
||||
* $scope.myForm.myInput1.$rollbackViewValue();
|
||||
* $scope.myValue = '';
|
||||
* }
|
||||
* };
|
||||
* $scope.resetWithoutCancel = function(e) {
|
||||
* if (e.keyCode == 27) {
|
||||
* $scope.myValue = '';
|
||||
* e.preventDefault();
|
||||
* if (rollback) {
|
||||
* $scope.myForm[value].$rollbackViewValue();
|
||||
* }
|
||||
* $scope.model[value] = '';
|
||||
* }
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="CancelUpdateController">
|
||||
* <p>Try typing something in each input. See that the model only updates when you
|
||||
* blur off the input.
|
||||
* </p>
|
||||
* <p>Now see what happens if you start typing then press the Escape key</p>
|
||||
* <p>Both of these inputs are only updated if they are blurred. Hitting escape should
|
||||
* empty them. Follow these steps and observe the difference:</p>
|
||||
* <ol>
|
||||
* <li>Type something in the input. You will see that the model is not yet updated</li>
|
||||
* <li>Press the Escape key.
|
||||
* <ol>
|
||||
* <li> In the first example, nothing happens, because the model is already '', and no
|
||||
* update is detected. If you blur the input, the model will be set to the current view.
|
||||
* </li>
|
||||
* <li> In the second example, the pending update is cancelled, and the input is set back
|
||||
* to the last committed view value (''). Blurring the input does nothing.
|
||||
* </li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
|
||||
* <p id="inputDescription1">With $rollbackViewValue()</p>
|
||||
* <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue"
|
||||
* ng-keydown="resetWithCancel($event)"><br/>
|
||||
* myValue: "{{ myValue }}"
|
||||
* <div>
|
||||
* <p id="inputDescription1">Without $rollbackViewValue():</p>
|
||||
* <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1"
|
||||
* ng-keydown="setEmpty($event, 'value1')">
|
||||
* value1: "{{ model.value1 }}"
|
||||
* </div>
|
||||
*
|
||||
* <p id="inputDescription2">Without $rollbackViewValue()</p>
|
||||
* <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue"
|
||||
* ng-keydown="resetWithoutCancel($event)"><br/>
|
||||
* myValue: "{{ myValue }}"
|
||||
* <div>
|
||||
* <p id="inputDescription2">With $rollbackViewValue():</p>
|
||||
* <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2"
|
||||
* ng-keydown="setEmpty($event, 'value2', true)">
|
||||
* value2: "{{ model.value2 }}"
|
||||
* </div>
|
||||
* </form>
|
||||
* </div>
|
||||
* </file>
|
||||
<file name="style.css">
|
||||
div {
|
||||
display: table-cell;
|
||||
}
|
||||
div:nth-child(1) {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
</file>
|
||||
* </example>
|
||||
*/
|
||||
this.$rollbackViewValue = function() {
|
||||
@@ -652,6 +694,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
|
||||
return;
|
||||
}
|
||||
ctrl.$$updateEmptyClasses(viewValue);
|
||||
ctrl.$$lastCommittedViewValue = viewValue;
|
||||
|
||||
// change to dirty
|
||||
@@ -834,6 +877,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
viewValue = formatters[idx](viewValue);
|
||||
}
|
||||
if (ctrl.$viewValue !== viewValue) {
|
||||
ctrl.$$updateEmptyClasses(viewValue);
|
||||
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
|
||||
ctrl.$render();
|
||||
|
||||
@@ -864,7 +908,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* require.
|
||||
* - Providing validation behavior (i.e. required, number, email, url).
|
||||
* - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
|
||||
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
|
||||
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
|
||||
* `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
|
||||
* - Registering the control with its parent {@link ng.directive:form form}.
|
||||
*
|
||||
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
|
||||
@@ -905,13 +950,16 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* - `ng-touched`: the control has been blurred
|
||||
* - `ng-untouched`: the control hasn't been blurred
|
||||
* - `ng-pending`: any `$asyncValidators` are unfulfilled
|
||||
* - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
|
||||
* by the {@link ngModel.NgModelController#$isEmpty} method
|
||||
* - `ng-not-empty`: the view contains a non-empty value
|
||||
*
|
||||
* Keep in mind that ngAnimate can detect each of these classes when added and removed.
|
||||
*
|
||||
* ## Animation Hooks
|
||||
*
|
||||
* Animations within models are triggered when any of the associated CSS classes are added and removed
|
||||
* on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
|
||||
* on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
|
||||
* `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
|
||||
* The animations that are triggered within ngModel are similar to how they work in ngClass and
|
||||
* animations can be hooked into using CSS transitions, keyframes as well as JS animations.
|
||||
@@ -1139,12 +1187,13 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
</label><br />
|
||||
</form>
|
||||
<pre>user.name = <span ng-bind="user.name"></span></pre>
|
||||
<pre>user.data = <span ng-bind="user.data"></span></pre>
|
||||
</div>
|
||||
</file>
|
||||
<file name="app.js">
|
||||
angular.module('optionsExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.user = { name: 'say', data: '' };
|
||||
$scope.user = { name: 'John', data: '' };
|
||||
|
||||
$scope.cancel = function(e) {
|
||||
if (e.keyCode == 27) {
|
||||
@@ -1159,20 +1208,20 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
var other = element(by.model('user.data'));
|
||||
|
||||
it('should allow custom events', function() {
|
||||
input.sendKeys(' hello');
|
||||
input.sendKeys(' Doe');
|
||||
input.click();
|
||||
expect(model.getText()).toEqual('say');
|
||||
expect(model.getText()).toEqual('John');
|
||||
other.click();
|
||||
expect(model.getText()).toEqual('say hello');
|
||||
expect(model.getText()).toEqual('John Doe');
|
||||
});
|
||||
|
||||
it('should $rollbackViewValue when model changes', function() {
|
||||
input.sendKeys(' hello');
|
||||
expect(input.getAttribute('value')).toEqual('say hello');
|
||||
input.sendKeys(' Doe');
|
||||
expect(input.getAttribute('value')).toEqual('John Doe');
|
||||
input.sendKeys(protractor.Key.ESCAPE);
|
||||
expect(input.getAttribute('value')).toEqual('say');
|
||||
expect(input.getAttribute('value')).toEqual('John');
|
||||
other.click();
|
||||
expect(model.getText()).toEqual('say');
|
||||
expect(model.getText()).toEqual('John');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -1198,7 +1247,7 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
<file name="app.js">
|
||||
angular.module('optionsExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.user = { name: 'say' };
|
||||
$scope.user = { name: 'Igor' };
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -33,19 +33,27 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
*
|
||||
* ## Complex Models (objects or collections)
|
||||
*
|
||||
* **Note:** By default, `ngModel` watches the model by reference, not value. This is important when
|
||||
* binding any input directive to a model that is an object or a collection.
|
||||
* By default, `ngModel` watches the model by reference, not value. This is important to know when
|
||||
* binding the select to a model that is an object or a collection.
|
||||
*
|
||||
* Since this is a common situation for `ngOptions` the directive additionally watches the model using
|
||||
* `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in
|
||||
* the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual
|
||||
* object/collection has not changed identity but only a property on the object or an item in the collection
|
||||
* changes.
|
||||
* One issue occurs if you want to preselect an option. For example, if you set
|
||||
* the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection,
|
||||
* because the objects are not identical. So by default, you should always reference the item in your collection
|
||||
* for preselections, e.g.: `$scope.selected = $scope.collection[3]`.
|
||||
*
|
||||
* Another solution is to use a `track by` clause, because then `ngOptions` will track the identity
|
||||
* of the item not by reference, but by the result of the `track by` expression. For example, if your
|
||||
* collection items have an id property, you would `track by item.id`.
|
||||
*
|
||||
* A different issue with objects or collections is that ngModel won't detect if an object property or
|
||||
* a collection item changes. For that reason, `ngOptions` additionally watches the model using
|
||||
* `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute.
|
||||
* This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection
|
||||
* has not changed identity, but only a property on the object or an item in the collection changes.
|
||||
*
|
||||
* Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
|
||||
* if the model is an array). This means that changing a property deeper inside the object/collection that the
|
||||
* first level will not trigger a re-rendering.
|
||||
*
|
||||
* if the model is an array). This means that changing a property deeper than the first level inside the
|
||||
* object/collection will not trigger a re-rendering.
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
@@ -58,17 +66,13 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* ### `select` **`as`** and **`track by`**
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
|
||||
* Be careful when using `select` **`as`** and **`track by`** in the same expression.
|
||||
* </div>
|
||||
*
|
||||
* Consider the following example:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select>
|
||||
* ```
|
||||
* Given this array of items on the $scope:
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* $scope.items = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
@@ -77,20 +81,33 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* label: 'bLabel',
|
||||
* subItem: { name: 'bSubItem' }
|
||||
* }];
|
||||
*
|
||||
* $scope.selected = { name: 'aSubItem' };
|
||||
* ```
|
||||
*
|
||||
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
|
||||
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
|
||||
* following:
|
||||
* This will work:
|
||||
*
|
||||
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
|
||||
* 2. Apply **`track by`** to the already selected value in `ngModel`.
|
||||
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
|
||||
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
|
||||
* selected" option.
|
||||
* ```html
|
||||
* <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select>
|
||||
* ```
|
||||
* ```js
|
||||
* $scope.selected = $scope.items[0];
|
||||
* ```
|
||||
*
|
||||
* but this will not work:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select>
|
||||
* ```
|
||||
* ```js
|
||||
* $scope.selected = $scope.items[0].subItem;
|
||||
* ```
|
||||
*
|
||||
* In both examples, the **`track by`** expression is applied successfully to each `item` in the
|
||||
* `items` array. Because the selected option has been set programmatically in the controller, the
|
||||
* **`track by`** expression is also applied to the `ngModel` value. In the first example, the
|
||||
* `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with
|
||||
* no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`**
|
||||
* expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value
|
||||
* is not matched against any `<option>` and the `<select>` appears as having no selected value.
|
||||
*
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
@@ -392,17 +409,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var optionTemplate = document.createElement('option'),
|
||||
optGroupTemplate = document.createElement('optgroup');
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
terminal: true,
|
||||
require: ['select', '?ngModel'],
|
||||
link: function(scope, selectElement, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
if (!ngModelCtrl) return;
|
||||
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
||||
|
||||
var selectCtrl = ctrls[0];
|
||||
var ngModelCtrl = ctrls[1];
|
||||
var multiple = attr.multiple;
|
||||
|
||||
// The emptyOption allows the application developer to provide their own custom "empty"
|
||||
@@ -451,7 +461,6 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
unknownOption.remove();
|
||||
};
|
||||
|
||||
|
||||
// Update the controller methods for multiple selectable options
|
||||
if (!multiple) {
|
||||
|
||||
@@ -579,11 +588,16 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
function updateOptionElement(option, element) {
|
||||
option.element = element;
|
||||
element.disabled = option.disabled;
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
// NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive
|
||||
// selects in certain circumstances when multiple selects are next to each other and display
|
||||
// the option list in listbox style, i.e. the select is [multiple], or specifies a [size].
|
||||
// See https://github.com/angular/angular.js/issues/11314 for more info.
|
||||
// This is unfortunately untestable with unit / e2e tests
|
||||
if (option.label !== element.label) {
|
||||
element.label = option.label;
|
||||
element.textContent = option.label;
|
||||
}
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
}
|
||||
|
||||
function addOrReuseElement(parent, current, type, templateElement) {
|
||||
@@ -621,10 +635,15 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var emptyOption_ = emptyOption && emptyOption[0];
|
||||
var unknownOption_ = unknownOption && unknownOption[0];
|
||||
|
||||
// We cannot rely on the extracted empty option being the same as the compiled empty option,
|
||||
// because the compiled empty option might have been replaced by a comment because
|
||||
// it had an "element" transclusion directive on it (such as ngIf)
|
||||
if (emptyOption_ || unknownOption_) {
|
||||
while (current &&
|
||||
(current === emptyOption_ ||
|
||||
current === unknownOption_)) {
|
||||
current === unknownOption_ ||
|
||||
current.nodeType === NODE_TYPE_COMMENT ||
|
||||
current.value === '')) {
|
||||
current = current.nextSibling;
|
||||
}
|
||||
}
|
||||
@@ -653,7 +672,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var groupElement;
|
||||
var optionElement;
|
||||
|
||||
if (option.group) {
|
||||
if (isDefined(option.group)) {
|
||||
|
||||
// This option is to live in a group
|
||||
// See if we have already created this group
|
||||
@@ -714,14 +733,28 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// Check to see if the value has changed due to the update to the options
|
||||
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
||||
var nextValue = selectCtrl.readValue();
|
||||
if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
|
||||
var isNotPrimitive = ngOptions.trackBy || multiple;
|
||||
if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
|
||||
ngModelCtrl.$setViewValue(nextValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
terminal: true,
|
||||
require: ['select', 'ngModel'],
|
||||
link: {
|
||||
pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) {
|
||||
// Deactivate the SelectController.register method to prevent
|
||||
// option directives from accidentally registering themselves
|
||||
// (and unwanted $destroy handlers etc.)
|
||||
ctrls[0].registerOption = noop;
|
||||
},
|
||||
post: ngOptionsPostLink
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
|
||||
* when running `for key in myObj`. It seems that browsers generally follow the strategy of providing
|
||||
* keys in the order in which they were defined, although there are exceptions when keys are deleted
|
||||
* and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
|
||||
* and reinstated. See the [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes).
|
||||
*
|
||||
* If this is not desired, the recommended workaround is to convert your object into an array
|
||||
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
|
||||
@@ -53,15 +53,21 @@
|
||||
*
|
||||
* # Tracking and Duplicates
|
||||
*
|
||||
* When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
|
||||
* `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in
|
||||
* the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM:
|
||||
*
|
||||
* * When an item is added, a new instance of the template is added to the DOM.
|
||||
* * When an item is removed, its template instance is removed from the DOM.
|
||||
* * When items are reordered, their respective templates are reordered in the DOM.
|
||||
*
|
||||
* By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
|
||||
* there are duplicates, it is not possible to maintain a one-to-one mapping between collection
|
||||
* items and DOM elements.
|
||||
* To minimize creation of DOM elements, `ngRepeat` uses a function
|
||||
* to "keep track" of all items in the collection and their corresponding DOM elements.
|
||||
* For example, if an item is added to the collection, ngRepeat will know that all other items
|
||||
* already have DOM elements, and will not re-render them.
|
||||
*
|
||||
* The default tracking function (which tracks items by their identity) does not allow
|
||||
* duplicate items in arrays. This is because when there are duplicates, it is not possible
|
||||
* to maintain a one-to-one mapping between collection items and DOM elements.
|
||||
*
|
||||
* If you do need to repeat duplicate items, you can substitute the default tracking behavior
|
||||
* with your own using the `track by` expression.
|
||||
@@ -74,7 +80,7 @@
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* You may use arbitrary expressions in `track by`, including references to custom functions
|
||||
* You may also use arbitrary expressions in `track by`, including references to custom functions
|
||||
* on the scope:
|
||||
* ```html
|
||||
* <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
|
||||
@@ -82,10 +88,14 @@
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If you are working with objects that have an identifier property, you can track
|
||||
* <div class="alert alert-success">
|
||||
* If you are working with objects that have an identifier property, you should track
|
||||
* by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
|
||||
* will not have to rebuild the DOM elements for items it has already rendered, even if the
|
||||
* JavaScript objects in the collection have been substituted for new ones:
|
||||
* JavaScript objects in the collection have been substituted for new ones. For large collections,
|
||||
* this signifincantly improves rendering performance. If you don't have a unique identifier,
|
||||
* `track by $index` can also provide a performance boost.
|
||||
* </div>
|
||||
* ```html
|
||||
* <div ng-repeat="model in collection track by model.id">
|
||||
* {{model.name}}
|
||||
|
||||
@@ -8,66 +8,186 @@
|
||||
* @description
|
||||
* Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
|
||||
*
|
||||
* Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
|
||||
* You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name
|
||||
* as the value of the `ng-transclude` or `ng-transclude-slot` attribute.
|
||||
*
|
||||
* If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing
|
||||
* content of this element will be removed before the transcluded content is inserted.
|
||||
* If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case
|
||||
* that no transcluded content is provided.
|
||||
*
|
||||
* @element ANY
|
||||
*
|
||||
* @example
|
||||
<example module="transcludeExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('transcludeExample', [])
|
||||
.directive('pane', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: { title:'@' },
|
||||
template: '<div style="border: 1px solid black;">' +
|
||||
'<div style="background-color: gray">{{title}}</div>' +
|
||||
'<ng-transclude></ng-transclude>' +
|
||||
'</div>'
|
||||
};
|
||||
})
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.title = 'Lorem Ipsum';
|
||||
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<input ng-model="title" aria-label="title"> <br/>
|
||||
<textarea ng-model="text" aria-label="text"></textarea> <br/>
|
||||
<pane title="{{title}}">{{text}}</pane>
|
||||
</div>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should have transcluded', function() {
|
||||
var titleElement = element(by.model('title'));
|
||||
titleElement.clear();
|
||||
titleElement.sendKeys('TITLE');
|
||||
var textElement = element(by.model('text'));
|
||||
textElement.clear();
|
||||
textElement.sendKeys('TEXT');
|
||||
expect(element(by.binding('title')).getText()).toEqual('TITLE');
|
||||
expect(element(by.binding('text')).getText()).toEqual('TEXT');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
* @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty
|
||||
* or its value is the same as the name of the attribute then the default slot is used.
|
||||
*
|
||||
* @example
|
||||
* ### Basic transclusion
|
||||
* This example demonstrates basic transclusion of content into a component directive.
|
||||
* <example name="simpleTranscludeExample" module="transcludeExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('transcludeExample', [])
|
||||
* .directive('pane', function(){
|
||||
* return {
|
||||
* restrict: 'E',
|
||||
* transclude: true,
|
||||
* scope: { title:'@' },
|
||||
* template: '<div style="border: 1px solid black;">' +
|
||||
* '<div style="background-color: gray">{{title}}</div>' +
|
||||
* '<ng-transclude></ng-transclude>' +
|
||||
* '</div>'
|
||||
* };
|
||||
* })
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.title = 'Lorem Ipsum';
|
||||
* $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
* }]);
|
||||
* </script>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input ng-model="title" aria-label="title"> <br/>
|
||||
* <textarea ng-model="text" aria-label="text"></textarea> <br/>
|
||||
* <pane title="{{title}}">{{text}}</pane>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should have transcluded', function() {
|
||||
* var titleElement = element(by.model('title'));
|
||||
* titleElement.clear();
|
||||
* titleElement.sendKeys('TITLE');
|
||||
* var textElement = element(by.model('text'));
|
||||
* textElement.clear();
|
||||
* textElement.sendKeys('TEXT');
|
||||
* expect(element(by.binding('title')).getText()).toEqual('TITLE');
|
||||
* expect(element(by.binding('text')).getText()).toEqual('TEXT');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### Transclude fallback content
|
||||
* This example shows how to use `NgTransclude` with fallback content, that
|
||||
* is displayed if no transcluded content is provided.
|
||||
*
|
||||
* <example module="transcludeFallbackContentExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('transcludeFallbackContentExample', [])
|
||||
* .directive('myButton', function(){
|
||||
* return {
|
||||
* restrict: 'E',
|
||||
* transclude: true,
|
||||
* scope: true,
|
||||
* template: '<button style="cursor: pointer;">' +
|
||||
* '<ng-transclude>' +
|
||||
* '<b style="color: red;">Button1</b>' +
|
||||
* '</ng-transclude>' +
|
||||
* '</button>'
|
||||
* };
|
||||
* });
|
||||
* </script>
|
||||
* <!-- fallback button content -->
|
||||
* <my-button id="fallback"></my-button>
|
||||
* <!-- modified button content -->
|
||||
* <my-button id="modified">
|
||||
* <i style="color: green;">Button2</i>
|
||||
* </my-button>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should have different transclude element content', function() {
|
||||
* expect(element(by.id('fallback')).getText()).toBe('Button1');
|
||||
* expect(element(by.id('modified')).getText()).toBe('Button2');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @example
|
||||
* ### Multi-slot transclusion
|
||||
* This example demonstrates using multi-slot transclusion in a component directive.
|
||||
* <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample">
|
||||
* <file name="index.html">
|
||||
* <style>
|
||||
* .title, .footer {
|
||||
* background-color: gray
|
||||
* }
|
||||
* </style>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input ng-model="title" aria-label="title"> <br/>
|
||||
* <textarea ng-model="text" aria-label="text"></textarea> <br/>
|
||||
* <pane>
|
||||
* <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title>
|
||||
* <pane-body><p>{{text}}</p></pane-body>
|
||||
* </pane>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('multiSlotTranscludeExample', [])
|
||||
* .directive('pane', function(){
|
||||
* return {
|
||||
* restrict: 'E',
|
||||
* transclude: {
|
||||
* 'title': '?paneTitle',
|
||||
* 'body': 'paneBody',
|
||||
* 'footer': '?paneFooter'
|
||||
* },
|
||||
* template: '<div style="border: 1px solid black;">' +
|
||||
* '<div class="title" ng-transclude="title">Fallback Title</div>' +
|
||||
* '<div ng-transclude="body"></div>' +
|
||||
* '<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
|
||||
* '</div>'
|
||||
* };
|
||||
* })
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.title = 'Lorem Ipsum';
|
||||
* $scope.link = "https://google.com";
|
||||
* $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it('should have transcluded the title and the body', function() {
|
||||
* var titleElement = element(by.model('title'));
|
||||
* titleElement.clear();
|
||||
* titleElement.sendKeys('TITLE');
|
||||
* var textElement = element(by.model('text'));
|
||||
* textElement.clear();
|
||||
* textElement.sendKeys('TEXT');
|
||||
* expect(element(by.css('.title')).getText()).toEqual('TITLE');
|
||||
* expect(element(by.binding('text')).getText()).toEqual('TEXT');
|
||||
* expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngTranscludeMinErr = minErr('ngTransclude');
|
||||
var ngTranscludeDirective = ngDirective({
|
||||
restrict: 'EAC',
|
||||
link: function($scope, $element, $attrs, controller, $transclude) {
|
||||
|
||||
if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) {
|
||||
// If the attribute is of the form: `ng-transclude="ng-transclude"`
|
||||
// then treat it like the default
|
||||
$attrs.ngTransclude = '';
|
||||
}
|
||||
|
||||
function ngTranscludeCloneAttachFn(clone) {
|
||||
if (clone.length) {
|
||||
$element.empty();
|
||||
$element.append(clone);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$transclude) {
|
||||
throw minErr('ngTransclude')('orphan',
|
||||
throw ngTranscludeMinErr('orphan',
|
||||
'Illegal use of ngTransclude directive in the template! ' +
|
||||
'No parent directive that requires a transclusion found. ' +
|
||||
'Element: {0}',
|
||||
startingTag($element));
|
||||
}
|
||||
|
||||
$transclude(function(clone) {
|
||||
$element.empty();
|
||||
$element.append(clone);
|
||||
});
|
||||
// If there is no slot name defined or the slot name is not optional
|
||||
// then transclude the slot
|
||||
var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot;
|
||||
$transclude(ngTranscludeCloneAttachFn, null, slotName);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+58
-53
@@ -2,6 +2,15 @@
|
||||
|
||||
var noopNgModelController = { $setViewValue: noop, $render: noop };
|
||||
|
||||
function chromeHack(optionElement) {
|
||||
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
|
||||
// Adding an <option selected="selected"> element to a <select required="required"> should
|
||||
// automatically select the new element
|
||||
if (optionElement[0].hasAttribute('selected')) {
|
||||
optionElement[0].selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name select.SelectController
|
||||
@@ -77,6 +86,8 @@ var SelectController =
|
||||
}
|
||||
var count = optionsMap.get(value) || 0;
|
||||
optionsMap.put(value, count + 1);
|
||||
self.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
};
|
||||
|
||||
// Tell the select control that an option, with the given value, has been removed
|
||||
@@ -98,6 +109,39 @@ var SelectController =
|
||||
self.hasOption = function(value) {
|
||||
return !!optionsMap.get(value);
|
||||
};
|
||||
|
||||
|
||||
self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
|
||||
|
||||
if (interpolateValueFn) {
|
||||
// The value attribute is interpolated
|
||||
var oldVal;
|
||||
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
if (isDefined(oldVal)) {
|
||||
self.removeOption(oldVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
self.addOption(newVal, optionElement);
|
||||
});
|
||||
} else if (interpolateTextFn) {
|
||||
// The text content is interpolated
|
||||
optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
optionAttrs.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
self.removeOption(oldVal);
|
||||
}
|
||||
self.addOption(newVal, optionElement);
|
||||
});
|
||||
} else {
|
||||
// The value attribute is static
|
||||
self.addOption(optionAttrs.value, optionElement);
|
||||
}
|
||||
|
||||
optionElement.on('$destroy', function() {
|
||||
self.removeOption(optionAttrs.value);
|
||||
self.ngModelCtrl.$render();
|
||||
});
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
@@ -143,6 +187,8 @@ var SelectController =
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} multiple Allows multiple options to be selected. The selected values will be
|
||||
* bound to the model as an array.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds required attribute and required validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
|
||||
@@ -308,7 +354,13 @@ var selectDirective = function() {
|
||||
restrict: 'E',
|
||||
require: ['select', '?ngModel'],
|
||||
controller: SelectController,
|
||||
link: function(scope, element, attr, ctrls) {
|
||||
priority: 1,
|
||||
link: {
|
||||
pre: selectPreLink
|
||||
}
|
||||
};
|
||||
|
||||
function selectPreLink(scope, element, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
@@ -378,7 +430,6 @@ var selectDirective = function() {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -386,16 +437,6 @@ var selectDirective = function() {
|
||||
// of dynamically created (and destroyed) option elements to their containing select
|
||||
// directive via its controller.
|
||||
var optionDirective = ['$interpolate', function($interpolate) {
|
||||
|
||||
function chromeHack(optionElement) {
|
||||
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
|
||||
// Adding an <option selected="selected"> element to a <select required="required"> should
|
||||
// automatically select the new element
|
||||
if (optionElement[0].hasAttribute('selected')) {
|
||||
optionElement[0].selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 100,
|
||||
@@ -403,12 +444,12 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
|
||||
if (isDefined(attr.value)) {
|
||||
// If the value attribute is defined, check if it contains an interpolation
|
||||
var valueInterpolated = $interpolate(attr.value, true);
|
||||
var interpolateValueFn = $interpolate(attr.value, true);
|
||||
} else {
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (!interpolateFn) {
|
||||
var interpolateTextFn = $interpolate(element.text(), true);
|
||||
if (!interpolateTextFn) {
|
||||
attr.$set('value', element.text());
|
||||
}
|
||||
}
|
||||
@@ -422,44 +463,8 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
function addOption(optionValue) {
|
||||
selectCtrl.addOption(optionValue, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
}
|
||||
|
||||
// Only update trigger option updates if this is an option within a `select`
|
||||
// that also has `ngModel` attached
|
||||
if (selectCtrl && selectCtrl.ngModelCtrl) {
|
||||
|
||||
if (valueInterpolated) {
|
||||
// The value attribute is interpolated
|
||||
var oldVal;
|
||||
attr.$observe('value', function valueAttributeObserveAction(newVal) {
|
||||
if (isDefined(oldVal)) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
oldVal = newVal;
|
||||
addOption(newVal);
|
||||
});
|
||||
} else if (interpolateFn) {
|
||||
// The text content is interpolated
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
addOption(newVal);
|
||||
});
|
||||
} else {
|
||||
// The value attribute is static
|
||||
addOption(attr.value);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
});
|
||||
if (selectCtrl) {
|
||||
selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+183
-79
@@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var MAX_DIGITS = 22;
|
||||
var DECIMAL_SEP = '.';
|
||||
var ZERO_CHAR = '0';
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name currency
|
||||
@@ -89,7 +93,7 @@ function currencyFilter($locale) {
|
||||
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
|
||||
* If this is not provided then the fraction size is computed from the current locale's number
|
||||
* formatting pattern. In the case of the default locale, it will be 3.
|
||||
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
|
||||
* @returns {string} Number rounded to fractionSize and places a “,” after each third digit.
|
||||
*
|
||||
* @example
|
||||
<example module="numberFilterExample">
|
||||
@@ -124,8 +128,6 @@ function currencyFilter($locale) {
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
|
||||
numberFilter.$inject = ['$locale'];
|
||||
function numberFilter($locale) {
|
||||
var formats = $locale.NUMBER_FORMATS;
|
||||
@@ -139,92 +141,194 @@ function numberFilter($locale) {
|
||||
};
|
||||
}
|
||||
|
||||
var DECIMAL_SEP = '.';
|
||||
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (isObject(number)) return '';
|
||||
/**
|
||||
* Parse a number (as a string) into three components that can be used
|
||||
* for formatting the number.
|
||||
*
|
||||
* (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/)
|
||||
*
|
||||
* @param {string} numStr The number to parse
|
||||
* @return {object} An object describing this number, containing the following keys:
|
||||
* - d : an array of digits containing leading zeros as necessary
|
||||
* - i : the number of the digits in `d` that are to the left of the decimal point
|
||||
* - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d`
|
||||
*
|
||||
*/
|
||||
function parse(numStr) {
|
||||
var exponent = 0, digits, numberOfIntegerDigits;
|
||||
var i, j, zeros;
|
||||
|
||||
var isNegative = number < 0;
|
||||
number = Math.abs(number);
|
||||
|
||||
var isInfinity = number === Infinity;
|
||||
if (!isInfinity && !isFinite(number)) return '';
|
||||
|
||||
var numStr = number + '',
|
||||
formatedText = '',
|
||||
hasExponent = false,
|
||||
parts = [];
|
||||
|
||||
if (isInfinity) formatedText = '\u221e';
|
||||
|
||||
if (!isInfinity && numStr.indexOf('e') !== -1) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
number = 0;
|
||||
} else {
|
||||
formatedText = numStr;
|
||||
hasExponent = true;
|
||||
}
|
||||
// Decimal point?
|
||||
if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) {
|
||||
numStr = numStr.replace(DECIMAL_SEP, '');
|
||||
}
|
||||
|
||||
if (!isInfinity && !hasExponent) {
|
||||
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
|
||||
// Exponential form?
|
||||
if ((i = numStr.search(/e/i)) > 0) {
|
||||
// Work out the exponent.
|
||||
if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i;
|
||||
numberOfIntegerDigits += +numStr.slice(i + 1);
|
||||
numStr = numStr.substring(0, i);
|
||||
} else if (numberOfIntegerDigits < 0) {
|
||||
// There was no decimal point or exponent so it is an integer.
|
||||
numberOfIntegerDigits = numStr.length;
|
||||
}
|
||||
|
||||
// determine fractionSize if it is not specified
|
||||
if (isUndefined(fractionSize)) {
|
||||
fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
|
||||
}
|
||||
// Count the number of leading zeros.
|
||||
for (i = 0; numStr.charAt(i) == ZERO_CHAR; i++);
|
||||
|
||||
// safely round numbers in JS without hitting imprecisions of floating-point arithmetics
|
||||
// inspired by:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
|
||||
var i, pos = 0,
|
||||
lgroup = pattern.lgSize,
|
||||
group = pattern.gSize;
|
||||
|
||||
if (whole.length >= (lgroup + group)) {
|
||||
pos = whole.length - lgroup;
|
||||
for (i = 0; i < pos; i++) {
|
||||
if ((pos - i) % group === 0 && i !== 0) {
|
||||
formatedText += groupSep;
|
||||
}
|
||||
formatedText += whole.charAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = pos; i < whole.length; i++) {
|
||||
if ((whole.length - i) % lgroup === 0 && i !== 0) {
|
||||
formatedText += groupSep;
|
||||
}
|
||||
formatedText += whole.charAt(i);
|
||||
}
|
||||
|
||||
// format fraction part.
|
||||
while (fraction.length < fractionSize) {
|
||||
fraction += '0';
|
||||
}
|
||||
|
||||
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
if (i == (zeros = numStr.length)) {
|
||||
// The digits are all zero.
|
||||
digits = [0];
|
||||
numberOfIntegerDigits = 1;
|
||||
} else {
|
||||
if (fractionSize > 0 && number < 1) {
|
||||
formatedText = number.toFixed(fractionSize);
|
||||
number = parseFloat(formatedText);
|
||||
// Count the number of trailing zeros
|
||||
zeros--;
|
||||
while (numStr.charAt(zeros) == ZERO_CHAR) zeros--;
|
||||
|
||||
// Trailing zeros are insignificant so ignore them
|
||||
numberOfIntegerDigits -= i;
|
||||
digits = [];
|
||||
// Convert string to array of digits without leading/trailing zeros.
|
||||
for (j = 0; i <= zeros; i++, j++) {
|
||||
digits[j] = +numStr.charAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
// If the number overflows the maximum allowed digits then use an exponent.
|
||||
if (numberOfIntegerDigits > MAX_DIGITS) {
|
||||
digits = digits.splice(0, MAX_DIGITS - 1);
|
||||
exponent = numberOfIntegerDigits - 1;
|
||||
numberOfIntegerDigits = 1;
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre,
|
||||
formatedText,
|
||||
isNegative ? pattern.negSuf : pattern.posSuf);
|
||||
return parts.join('');
|
||||
return { d: digits, e: exponent, i: numberOfIntegerDigits };
|
||||
}
|
||||
|
||||
/**
|
||||
* Round the parsed number to the specified number of decimal places
|
||||
* This function changed the parsedNumber in-place
|
||||
*/
|
||||
function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) {
|
||||
var digits = parsedNumber.d;
|
||||
var fractionLen = digits.length - parsedNumber.i;
|
||||
|
||||
// determine fractionSize if it is not specified; `+fractionSize` converts it to a number
|
||||
fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize;
|
||||
|
||||
// The index of the digit to where rounding is to occur
|
||||
var roundAt = fractionSize + parsedNumber.i;
|
||||
var digit = digits[roundAt];
|
||||
|
||||
if (roundAt > 0) {
|
||||
digits.splice(roundAt);
|
||||
} else {
|
||||
// We rounded to zero so reset the parsedNumber
|
||||
parsedNumber.i = 1;
|
||||
digits.length = roundAt = fractionSize + 1;
|
||||
for (var i=0; i < roundAt; i++) digits[i] = 0;
|
||||
}
|
||||
|
||||
if (digit >= 5) digits[roundAt - 1]++;
|
||||
|
||||
// Pad out with zeros to get the required fraction length
|
||||
for (; fractionLen < fractionSize; fractionLen++) digits.push(0);
|
||||
|
||||
|
||||
// Do any carrying, e.g. a digit was rounded up to 10
|
||||
var carry = digits.reduceRight(function(carry, d, i, digits) {
|
||||
d = d + carry;
|
||||
digits[i] = d % 10;
|
||||
return Math.floor(d / 10);
|
||||
}, 0);
|
||||
if (carry) {
|
||||
digits.unshift(carry);
|
||||
parsedNumber.i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number into a string
|
||||
* @param {number} number The number to format
|
||||
* @param {{
|
||||
* minFrac, // the minimum number of digits required in the fraction part of the number
|
||||
* maxFrac, // the maximum number of digits required in the fraction part of the number
|
||||
* gSize, // number of digits in each group of separated digits
|
||||
* lgSize, // number of digits in the last group of digits before the decimal separator
|
||||
* negPre, // the string to go in front of a negative number (e.g. `-` or `(`))
|
||||
* posPre, // the string to go in front of a positive number
|
||||
* negSuf, // the string to go after a negative number (e.g. `)`)
|
||||
* posSuf // the string to go after a positive number
|
||||
* }} pattern
|
||||
* @param {string} groupSep The string to separate groups of number (e.g. `,`)
|
||||
* @param {string} decimalSep The string to act as the decimal separator (e.g. `.`)
|
||||
* @param {[type]} fractionSize The size of the fractional part of the number
|
||||
* @return {string} The number formatted as a string
|
||||
*/
|
||||
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
|
||||
if (!(isString(number) || isNumber(number)) || isNaN(number)) return '';
|
||||
|
||||
var isInfinity = !isFinite(number);
|
||||
var isZero = false;
|
||||
var numStr = Math.abs(number) + '',
|
||||
formattedText = '',
|
||||
parsedNumber;
|
||||
|
||||
if (isInfinity) {
|
||||
formattedText = '\u221e';
|
||||
} else {
|
||||
parsedNumber = parse(numStr);
|
||||
|
||||
roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac);
|
||||
|
||||
var digits = parsedNumber.d;
|
||||
var integerLen = parsedNumber.i;
|
||||
var exponent = parsedNumber.e;
|
||||
var decimals = [];
|
||||
isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true);
|
||||
|
||||
// pad zeros for small numbers
|
||||
while (integerLen < 0) {
|
||||
digits.unshift(0);
|
||||
integerLen++;
|
||||
}
|
||||
|
||||
// extract decimals digits
|
||||
if (integerLen > 0) {
|
||||
decimals = digits.splice(integerLen);
|
||||
} else {
|
||||
decimals = digits;
|
||||
digits = [0];
|
||||
}
|
||||
|
||||
// format the integer digits with grouping separators
|
||||
var groups = [];
|
||||
if (digits.length > pattern.lgSize) {
|
||||
groups.unshift(digits.splice(-pattern.lgSize).join(''));
|
||||
}
|
||||
while (digits.length > pattern.gSize) {
|
||||
groups.unshift(digits.splice(-pattern.gSize).join(''));
|
||||
}
|
||||
if (digits.length) {
|
||||
groups.unshift(digits.join(''));
|
||||
}
|
||||
formattedText = groups.join(groupSep);
|
||||
|
||||
// append the decimal digits
|
||||
if (decimals.length) {
|
||||
formattedText += decimalSep + decimals.join('');
|
||||
}
|
||||
|
||||
if (exponent) {
|
||||
formattedText += 'e+' + exponent;
|
||||
}
|
||||
}
|
||||
if (number < 0 && !isZero) {
|
||||
return pattern.negPre + formattedText + pattern.negSuf;
|
||||
} else {
|
||||
return pattern.posPre + formattedText + pattern.posSuf;
|
||||
}
|
||||
}
|
||||
|
||||
function padNumber(num, digits, trim) {
|
||||
@@ -234,7 +338,7 @@ function padNumber(num, digits, trim) {
|
||||
num = -num;
|
||||
}
|
||||
num = '' + num;
|
||||
while (num.length < digits) num = '0' + num;
|
||||
while (num.length < digits) num = ZERO_CHAR + num;
|
||||
if (trim) {
|
||||
num = num.substr(num.length - digits);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ function limitToFilter() {
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
|
||||
begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
|
||||
begin = (begin < 0) ? Math.max(0, input.length + begin) : begin;
|
||||
|
||||
if (limit >= 0) {
|
||||
return input.slice(begin, begin + limit);
|
||||
|
||||
+93
-69
@@ -9,8 +9,9 @@
|
||||
* Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
|
||||
* for strings and numerically for numbers. Note: if you notice numbers are not being sorted
|
||||
* as expected, make sure they are actually being saved as numbers and not strings.
|
||||
* Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported.
|
||||
*
|
||||
* @param {Array} array The array to sort.
|
||||
* @param {Array} array The array (or array-like object) to sort.
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
|
||||
* used by the comparator to determine the order of elements.
|
||||
*
|
||||
@@ -41,17 +42,6 @@
|
||||
* `reverse` is not set, which means it defaults to `false`.
|
||||
<example module="orderByExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('orderByExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.friends =
|
||||
[{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}];
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<table class="friend">
|
||||
<tr>
|
||||
@@ -67,6 +57,17 @@
|
||||
</table>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('orderByExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.friends =
|
||||
[{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}];
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* The predicate and reverse parameters can be controlled dynamically through scope properties,
|
||||
@@ -74,49 +75,24 @@
|
||||
* @example
|
||||
<example module="orderByExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('orderByExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.friends =
|
||||
[{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}];
|
||||
$scope.predicate = 'age';
|
||||
$scope.reverse = true;
|
||||
$scope.order = function(predicate) {
|
||||
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
|
||||
$scope.predicate = predicate;
|
||||
};
|
||||
}]);
|
||||
</script>
|
||||
<style type="text/css">
|
||||
.sortorder:after {
|
||||
content: '\25b2';
|
||||
}
|
||||
.sortorder.reverse:after {
|
||||
content: '\25bc';
|
||||
}
|
||||
</style>
|
||||
<div ng-controller="ExampleController">
|
||||
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
|
||||
<hr/>
|
||||
[ <a href="" ng-click="predicate=''">unsorted</a> ]
|
||||
<button ng-click="predicate=''">Set to unsorted</button>
|
||||
<table class="friend">
|
||||
<tr>
|
||||
<th>
|
||||
<a href="" ng-click="order('name')">Name</a>
|
||||
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<a href="" ng-click="order('phone')">Phone Number</a>
|
||||
<span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<a href="" ng-click="order('age')">Age</a>
|
||||
<span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<button ng-click="order('name')">Name</button>
|
||||
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<button ng-click="order('phone')">Phone Number</button>
|
||||
<span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<button ng-click="order('age')">Age</button>
|
||||
<span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr ng-repeat="friend in friends | orderBy:predicate:reverse">
|
||||
<td>{{friend.name}}</td>
|
||||
@@ -126,6 +102,31 @@
|
||||
</table>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('orderByExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.friends =
|
||||
[{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}];
|
||||
$scope.predicate = 'age';
|
||||
$scope.reverse = true;
|
||||
$scope.order = function(predicate) {
|
||||
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
|
||||
$scope.predicate = predicate;
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.sortorder:after {
|
||||
content: '\25b2';
|
||||
}
|
||||
.sortorder.reverse:after {
|
||||
content: '\25bc';
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
|
||||
@@ -137,21 +138,30 @@
|
||||
* @example
|
||||
<example module="orderByExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<table class="friend">
|
||||
<tr>
|
||||
<th><a href="" ng-click="reverse=false;order('name', false)">Name</a>
|
||||
(<a href="" ng-click="order('-name',false)">^</a>)</th>
|
||||
<th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th>
|
||||
<th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th>
|
||||
</tr>
|
||||
<tr ng-repeat="friend in friends">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<td>{{friend.age}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div ng-controller="ExampleController">
|
||||
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
|
||||
<table class="friend">
|
||||
<tr>
|
||||
<th>
|
||||
<button ng-click="order('name')">Name</button>
|
||||
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<button ng-click="order('phone')">Phone Number</button>
|
||||
<span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
<th>
|
||||
<button ng-click="order('age')">Age</button>
|
||||
<span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr ng-repeat="friend in friends">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<td>{{friend.age}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="script.js">
|
||||
@@ -165,19 +175,33 @@
|
||||
{ name: 'Adam', phone: '555-5678', age: 35 },
|
||||
{ name: 'Julie', phone: '555-8765', age: 29 }
|
||||
];
|
||||
$scope.order = function(predicate, reverse) {
|
||||
$scope.friends = orderBy($scope.friends, predicate, reverse);
|
||||
$scope.order = function(predicate) {
|
||||
$scope.predicate = predicate;
|
||||
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
|
||||
$scope.friends = orderBy($scope.friends, predicate, $scope.reverse);
|
||||
};
|
||||
$scope.order('-age',false);
|
||||
$scope.order('age', true);
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="style.css">
|
||||
.sortorder:after {
|
||||
content: '\25b2';
|
||||
}
|
||||
.sortorder.reverse:after {
|
||||
content: '\25bc';
|
||||
}
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse) {
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
|
||||
if (!(isArrayLike(array))) return array;
|
||||
if (array == null) return array;
|
||||
if (!isArrayLike(array)) {
|
||||
throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array);
|
||||
}
|
||||
|
||||
if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
|
||||
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
||||
|
||||
+33
-40
@@ -345,9 +345,9 @@ function $HttpProvider() {
|
||||
* Configure `$http` service to return promises without the shorthand methods `success` and `error`.
|
||||
* This should be used to make sure that applications work without these methods.
|
||||
*
|
||||
* Defaults to false. If no value is specified, returns the current configured value.
|
||||
* Defaults to true. If no value is specified, returns the current configured value.
|
||||
*
|
||||
* @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods.
|
||||
* @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods.
|
||||
*
|
||||
* @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
|
||||
* otherwise, returns the current configured value.
|
||||
@@ -425,28 +425,18 @@ function $HttpProvider() {
|
||||
*
|
||||
*
|
||||
* ## General usage
|
||||
* The `$http` service is a function which takes a single argument — a configuration object —
|
||||
* The `$http` service is a function which takes a single argument — a {@link $http#usage configuration object} —
|
||||
* that is used to generate an HTTP request and returns a {@link ng.$q promise}.
|
||||
*
|
||||
* ```js
|
||||
* // Simple GET request example :
|
||||
* $http.get('/someUrl').
|
||||
* then(function(response) {
|
||||
* // Simple GET request example:
|
||||
* $http({
|
||||
* method: 'GET',
|
||||
* url: '/someUrl'
|
||||
* }).then(function successCallback(response) {
|
||||
* // this callback will be called asynchronously
|
||||
* // when the response is available
|
||||
* }, function(response) {
|
||||
* // called asynchronously if an error occurs
|
||||
* // or server returns response with an error status.
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* // Simple POST request example (passing data) :
|
||||
* $http.post('/someUrl', {msg:'hello word!'}).
|
||||
* then(function(response) {
|
||||
* // this callback will be called asynchronously
|
||||
* // when the response is available
|
||||
* }, function(response) {
|
||||
* }, function errorCallback(response) {
|
||||
* // called asynchronously if an error occurs
|
||||
* // or server returns response with an error status.
|
||||
* });
|
||||
@@ -466,25 +456,16 @@ function $HttpProvider() {
|
||||
* XMLHttpRequest will transparently follow it, meaning that the error callback will not be
|
||||
* called for such responses.
|
||||
*
|
||||
* ## Writing Unit Tests that use $http
|
||||
* When unit testing (using {@link ngMock ngMock}), it is necessary to call
|
||||
* {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
|
||||
* request using trained responses.
|
||||
*
|
||||
* ```
|
||||
* $httpBackend.expectGET(...);
|
||||
* $http.get(...);
|
||||
* $httpBackend.flush();
|
||||
* ```
|
||||
*
|
||||
* ## Shortcut methods
|
||||
*
|
||||
* Shortcut methods are also available. All shortcut methods require passing in the URL, and
|
||||
* request data must be passed in for POST/PUT requests.
|
||||
* request data must be passed in for POST/PUT requests. An optional config can be passed as the
|
||||
* last argument.
|
||||
*
|
||||
* ```js
|
||||
* $http.get('/someUrl').then(successCallback);
|
||||
* $http.post('/someUrl', data).then(successCallback);
|
||||
* $http.get('/someUrl', config).then(successCallback, errorCallback);
|
||||
* $http.post('/someUrl', data, config).then(successCallback, errorCallback);
|
||||
* ```
|
||||
*
|
||||
* Complete list of shortcut methods:
|
||||
@@ -498,6 +479,17 @@ function $HttpProvider() {
|
||||
* - {@link ng.$http#patch $http.patch}
|
||||
*
|
||||
*
|
||||
* ## Writing Unit Tests that use $http
|
||||
* When unit testing (using {@link ngMock ngMock}), it is necessary to call
|
||||
* {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
|
||||
* request using trained responses.
|
||||
*
|
||||
* ```
|
||||
* $httpBackend.expectGET(...);
|
||||
* $http.get(...);
|
||||
* $httpBackend.flush();
|
||||
* ```
|
||||
*
|
||||
* ## Deprecation Notice
|
||||
* <div class="alert alert-danger">
|
||||
* The `$http` legacy promise methods `success` and `error` have been deprecated.
|
||||
@@ -529,7 +521,7 @@ function $HttpProvider() {
|
||||
*
|
||||
* ```
|
||||
* module.run(function($http) {
|
||||
* $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'
|
||||
* $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w';
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
@@ -655,7 +647,7 @@ function $HttpProvider() {
|
||||
*
|
||||
* There are two kinds of interceptors (and two kinds of rejection interceptors):
|
||||
*
|
||||
* * `request`: interceptors get called with a http `config` object. The function is free to
|
||||
* * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to
|
||||
* modify the `config` object or create a new one. The function needs to return the `config`
|
||||
* object directly, or a promise containing the `config` or a new `config` object.
|
||||
* * `requestError`: interceptor gets called when a previous interceptor threw an error or
|
||||
@@ -922,10 +914,14 @@ function $HttpProvider() {
|
||||
*/
|
||||
function $http(requestConfig) {
|
||||
|
||||
if (!angular.isObject(requestConfig)) {
|
||||
if (!isObject(requestConfig)) {
|
||||
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
|
||||
}
|
||||
|
||||
if (!isString(requestConfig.url)) {
|
||||
throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url);
|
||||
}
|
||||
|
||||
var config = extend({
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
@@ -1007,11 +1003,8 @@ function $HttpProvider() {
|
||||
function transformResponse(response) {
|
||||
// make a copy since the response must be cacheable
|
||||
var resp = extend({}, response);
|
||||
if (!response.data) {
|
||||
resp.data = response.data;
|
||||
} else {
|
||||
resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
|
||||
}
|
||||
resp.data = transformData(response.data, response.headers, response.status,
|
||||
config.transformResponse);
|
||||
return (isSuccess(response.status))
|
||||
? resp
|
||||
: $q.reject(resp);
|
||||
|
||||
+31
-5
@@ -1,7 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
function createXhr() {
|
||||
return new window.XMLHttpRequest();
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $xhrFactory
|
||||
*
|
||||
* @description
|
||||
* Factory function used to create XMLHttpRequest objects.
|
||||
*
|
||||
* Replace or decorate this service to create your own custom XMLHttpRequest objects.
|
||||
*
|
||||
* ```
|
||||
* angular.module('myApp', [])
|
||||
* .factory('$xhrFactory', function() {
|
||||
* return function createXhr(method, url) {
|
||||
* return new window.XMLHttpRequest({mozSystem: true});
|
||||
* };
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {string} method HTTP method of the request (GET, POST, PUT, ..)
|
||||
* @param {string} url URL of the request.
|
||||
*/
|
||||
function $xhrFactoryProvider() {
|
||||
this.$get = function() {
|
||||
return function createXhr() {
|
||||
return new window.XMLHttpRequest();
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -9,6 +34,7 @@ function createXhr() {
|
||||
* @name $httpBackend
|
||||
* @requires $window
|
||||
* @requires $document
|
||||
* @requires $xhrFactory
|
||||
*
|
||||
* @description
|
||||
* HTTP backend used by the {@link ng.$http service} that delegates to
|
||||
@@ -21,8 +47,8 @@ function createXhr() {
|
||||
* $httpBackend} which can be trained with responses.
|
||||
*/
|
||||
function $HttpBackendProvider() {
|
||||
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
|
||||
return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]);
|
||||
this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
|
||||
return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -46,7 +72,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
});
|
||||
} else {
|
||||
|
||||
var xhr = createXhr();
|
||||
var xhr = createXhr(method, url);
|
||||
|
||||
xhr.open(method, url, true);
|
||||
forEach(headers, function(value, key) {
|
||||
|
||||
@@ -20,6 +20,14 @@ $interpolateMinErr.interr = function(text, err) {
|
||||
*
|
||||
* Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular
|
||||
* template within a Python Jinja template (or any other template language). Mixing templating
|
||||
* languages is **very dangerous**. The embedding template language will not safely escape Angular
|
||||
* expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS)
|
||||
* security bugs!
|
||||
* </div>
|
||||
*
|
||||
* @example
|
||||
<example module="customInterpolationApp">
|
||||
<file name="index.html">
|
||||
@@ -120,6 +128,15 @@ function $InterpolateProvider() {
|
||||
return value;
|
||||
}
|
||||
|
||||
//TODO: this is the same as the constantWatchDelegate in parse.js
|
||||
function constantWatchDelegate(scope, listener, objectEquality, constantInterp) {
|
||||
var unwatch;
|
||||
return unwatch = scope.$watch(function constantInterpolateWatch(scope) {
|
||||
unwatch();
|
||||
return constantInterp(scope);
|
||||
}, listener, objectEquality);
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $interpolate
|
||||
@@ -215,6 +232,19 @@ function $InterpolateProvider() {
|
||||
* - `context`: evaluation context for all expressions embedded in the interpolated text
|
||||
*/
|
||||
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
|
||||
// Provide a quick exit and simplified result function for text with no interpolation
|
||||
if (!text.length || text.indexOf(startSymbol) === -1) {
|
||||
var constantInterp;
|
||||
if (!mustHaveExpression) {
|
||||
var unescapedText = unescapeText(text);
|
||||
constantInterp = valueFn(unescapedText);
|
||||
constantInterp.exp = text;
|
||||
constantInterp.expressions = [];
|
||||
constantInterp.$$watchDelegate = constantWatchDelegate;
|
||||
}
|
||||
return constantInterp;
|
||||
}
|
||||
|
||||
allOrNothing = !!allOrNothing;
|
||||
var startIndex,
|
||||
endIndex,
|
||||
|
||||
+15
-6
@@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
function $IntervalProvider() {
|
||||
this.$get = ['$rootScope', '$window', '$q', '$$q',
|
||||
function($rootScope, $window, $q, $$q) {
|
||||
this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser',
|
||||
function($rootScope, $window, $q, $$q, $browser) {
|
||||
var intervals = {};
|
||||
|
||||
|
||||
@@ -144,11 +144,12 @@ function $IntervalProvider() {
|
||||
|
||||
count = isDefined(count) ? count : 0;
|
||||
|
||||
promise.then(null, null, (!hasParams) ? fn : function() {
|
||||
fn.apply(null, args);
|
||||
});
|
||||
|
||||
promise.$$intervalId = setInterval(function tick() {
|
||||
if (skipApply) {
|
||||
$browser.defer(callback);
|
||||
} else {
|
||||
$rootScope.$evalAsync(callback);
|
||||
}
|
||||
deferred.notify(iteration++);
|
||||
|
||||
if (count > 0 && iteration >= count) {
|
||||
@@ -164,6 +165,14 @@ function $IntervalProvider() {
|
||||
intervals[promise.$$intervalId] = deferred;
|
||||
|
||||
return promise;
|
||||
|
||||
function callback() {
|
||||
if (!hasParams) {
|
||||
fn(iteration);
|
||||
} else {
|
||||
fn.apply(null, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -574,9 +574,9 @@ var locationPrototype = {
|
||||
* @description
|
||||
* This method is getter / setter.
|
||||
*
|
||||
* Return hash fragment when called without any parameter.
|
||||
* Returns the hash fragment when called without any parameters.
|
||||
*
|
||||
* Change hash fragment when called with parameter and return `$location`.
|
||||
* Changes the hash fragment when called with a parameter and returns `$location`.
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
@@ -597,8 +597,8 @@ var locationPrototype = {
|
||||
* @name $location#replace
|
||||
*
|
||||
* @description
|
||||
* If called, all changes to $location during current `$digest` will be replacing current history
|
||||
* record, instead of adding new one.
|
||||
* If called, all changes to $location during the current `$digest` will replace the current history
|
||||
* record, instead of adding a new one.
|
||||
*/
|
||||
replace: function() {
|
||||
this.$$replace = true;
|
||||
@@ -918,7 +918,7 @@ function $LocationProvider() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
var defaultPrevented;
|
||||
|
||||
newUrl = trimEmptyHash(newUrl);
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
|
||||
|
||||
+83
-22
@@ -38,20 +38,30 @@ var $parseMinErr = minErr('$parse');
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
if (name === "__defineGetter__" || name === "__defineSetter__"
|
||||
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|
||||
|| name === "__proto__") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
+ 'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function getStringValue(name, fullExpression) {
|
||||
// From the JavaScript docs:
|
||||
// Property names must be strings. This means that non-string objects cannot be used
|
||||
// as keys in an object. Any non-string object, including a number, is typecasted
|
||||
// into a string via the toString method.
|
||||
//
|
||||
// So, to ensure that we are checking the same `name` that JavaScript would use,
|
||||
// we cast it to a string, if possible
|
||||
name = (isObject(name) && name.toString) ? name.toString() : name;
|
||||
|
||||
if (name === "__defineGetter__" || name === "__defineSetter__"
|
||||
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|
||||
|| name === "__proto__") {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
// we cast it to a string, if possible.
|
||||
// Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
|
||||
// this is, this will handle objects that misbehave.
|
||||
name = name + '';
|
||||
if (!isString(name)) {
|
||||
throw $parseMinErr('iseccst',
|
||||
'Cannot convert object to primitive value! '
|
||||
+ 'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
@@ -102,6 +112,16 @@ function ensureSafeFunction(obj, fullExpression) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSafeAssignContext(obj, fullExpression) {
|
||||
if (obj) {
|
||||
if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
|
||||
obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
|
||||
throw $parseMinErr('isecaf',
|
||||
'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var OPERATORS = createMap();
|
||||
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
|
||||
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
||||
@@ -305,6 +325,7 @@ AST.ArrayExpression = 'ArrayExpression';
|
||||
AST.Property = 'Property';
|
||||
AST.ObjectExpression = 'ObjectExpression';
|
||||
AST.ThisExpression = 'ThisExpression';
|
||||
AST.LocalsExpression = 'LocalsExpression';
|
||||
|
||||
// Internal use only
|
||||
AST.NGValueParameter = 'NGValueParameter';
|
||||
@@ -605,7 +626,8 @@ AST.prototype = {
|
||||
'false': { type: AST.Literal, value: false },
|
||||
'null': { type: AST.Literal, value: null },
|
||||
'undefined': {type: AST.Literal, value: undefined },
|
||||
'this': {type: AST.ThisExpression }
|
||||
'this': {type: AST.ThisExpression },
|
||||
'$locals': {type: AST.LocalsExpression }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -725,6 +747,10 @@ function findConstantAndWatchExpressions(ast, $filter) {
|
||||
ast.constant = false;
|
||||
ast.toWatch = [];
|
||||
break;
|
||||
case AST.LocalsExpression:
|
||||
ast.constant = false;
|
||||
ast.toWatch = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +842,8 @@ ASTCompiler.prototype = {
|
||||
'ensureSafeMemberName',
|
||||
'ensureSafeObject',
|
||||
'ensureSafeFunction',
|
||||
'getStringValue',
|
||||
'ensureSafeAssignContext',
|
||||
'ifDefined',
|
||||
'plus',
|
||||
'text',
|
||||
@@ -824,6 +852,8 @@ ASTCompiler.prototype = {
|
||||
ensureSafeMemberName,
|
||||
ensureSafeObject,
|
||||
ensureSafeFunction,
|
||||
getStringValue,
|
||||
ensureSafeAssignContext,
|
||||
ifDefined,
|
||||
plusFn,
|
||||
expression);
|
||||
@@ -964,9 +994,13 @@ ASTCompiler.prototype = {
|
||||
intoId = intoId || this.nextId();
|
||||
self.recurse(ast.object, left, undefined, function() {
|
||||
self.if_(self.notNull(left), function() {
|
||||
if (create && create !== 1) {
|
||||
self.addEnsureSafeAssignContext(left);
|
||||
}
|
||||
if (ast.computed) {
|
||||
right = self.nextId();
|
||||
self.recurse(ast.property, right);
|
||||
self.getStringValue(right);
|
||||
self.addEnsureSafeMemberName(right);
|
||||
if (create && create !== 1) {
|
||||
self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
|
||||
@@ -1044,12 +1078,13 @@ ASTCompiler.prototype = {
|
||||
right = this.nextId();
|
||||
left = {};
|
||||
if (!isAssignable(ast.left)) {
|
||||
throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
|
||||
throw $parseMinErr('lval', 'Trying to assign a value to a non l-value');
|
||||
}
|
||||
this.recurse(ast.left, undefined, left, function() {
|
||||
self.if_(self.notNull(left.context), function() {
|
||||
self.recurse(ast.right, right);
|
||||
self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
|
||||
self.addEnsureSafeAssignContext(left.context);
|
||||
expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
|
||||
self.assign(intoId, expression);
|
||||
recursionFn(intoId || expression);
|
||||
@@ -1085,6 +1120,10 @@ ASTCompiler.prototype = {
|
||||
this.assign(intoId, 's');
|
||||
recursionFn('s');
|
||||
break;
|
||||
case AST.LocalsExpression:
|
||||
this.assign(intoId, 'l');
|
||||
recursionFn('l');
|
||||
break;
|
||||
case AST.NGValueParameter:
|
||||
this.assign(intoId, 'v');
|
||||
recursionFn('v');
|
||||
@@ -1175,6 +1214,10 @@ ASTCompiler.prototype = {
|
||||
this.current().body.push(this.ensureSafeFunction(item), ';');
|
||||
},
|
||||
|
||||
addEnsureSafeAssignContext: function(item) {
|
||||
this.current().body.push(this.ensureSafeAssignContext(item), ';');
|
||||
},
|
||||
|
||||
ensureSafeObject: function(item) {
|
||||
return 'ensureSafeObject(' + item + ',text)';
|
||||
},
|
||||
@@ -1187,6 +1230,14 @@ ASTCompiler.prototype = {
|
||||
return 'ensureSafeFunction(' + item + ',text)';
|
||||
},
|
||||
|
||||
getStringValue: function(item) {
|
||||
this.assign(item, 'getStringValue(' + item + ',text)');
|
||||
},
|
||||
|
||||
ensureSafeAssignContext: function(item) {
|
||||
return 'ensureSafeAssignContext(' + item + ',text)';
|
||||
},
|
||||
|
||||
lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
|
||||
var self = this;
|
||||
return function() {
|
||||
@@ -1364,6 +1415,7 @@ ASTInterpreter.prototype = {
|
||||
var lhs = left(scope, locals, assign, inputs);
|
||||
var rhs = right(scope, locals, assign, inputs);
|
||||
ensureSafeObject(lhs.value, self.expression);
|
||||
ensureSafeAssignContext(lhs.context);
|
||||
lhs.context[lhs.name] = rhs;
|
||||
return context ? {value: rhs} : rhs;
|
||||
};
|
||||
@@ -1399,6 +1451,10 @@ ASTInterpreter.prototype = {
|
||||
return function(scope) {
|
||||
return context ? {value: scope} : scope;
|
||||
};
|
||||
case AST.LocalsExpression:
|
||||
return function(scope, locals) {
|
||||
return context ? {value: locals} : locals;
|
||||
};
|
||||
case AST.NGValueParameter:
|
||||
return function(scope, locals, assign, inputs) {
|
||||
return context ? {value: assign} : assign;
|
||||
@@ -1561,9 +1617,13 @@ ASTInterpreter.prototype = {
|
||||
var value;
|
||||
if (lhs != null) {
|
||||
rhs = right(scope, locals, assign, inputs);
|
||||
rhs = getStringValue(rhs);
|
||||
ensureSafeMemberName(rhs, expression);
|
||||
if (create && create !== 1 && lhs && !(lhs[rhs])) {
|
||||
lhs[rhs] = {};
|
||||
if (create && create !== 1) {
|
||||
ensureSafeAssignContext(lhs);
|
||||
if (lhs && !(lhs[rhs])) {
|
||||
lhs[rhs] = {};
|
||||
}
|
||||
}
|
||||
value = lhs[rhs];
|
||||
ensureSafeObject(value, expression);
|
||||
@@ -1578,8 +1638,11 @@ ASTInterpreter.prototype = {
|
||||
nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
|
||||
return function(scope, locals, assign, inputs) {
|
||||
var lhs = left(scope, locals, assign, inputs);
|
||||
if (create && create !== 1 && lhs && !(lhs[right])) {
|
||||
lhs[right] = {};
|
||||
if (create && create !== 1) {
|
||||
ensureSafeAssignContext(lhs);
|
||||
if (lhs && !(lhs[right])) {
|
||||
lhs[right] = {};
|
||||
}
|
||||
}
|
||||
var value = lhs != null ? lhs[right] : undefined;
|
||||
if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
|
||||
@@ -1735,7 +1798,7 @@ function $ParseProvider() {
|
||||
return addInterceptor(exp, interceptorFn);
|
||||
|
||||
default:
|
||||
return noop;
|
||||
return addInterceptor(noop, interceptorFn);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1854,25 +1917,22 @@ function $ParseProvider() {
|
||||
function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) {
|
||||
var unwatch;
|
||||
return unwatch = scope.$watch(function constantWatch(scope) {
|
||||
return parsedExpression(scope);
|
||||
}, function constantListener(value, old, scope) {
|
||||
if (isFunction(listener)) {
|
||||
listener.apply(this, arguments);
|
||||
}
|
||||
unwatch();
|
||||
}, objectEquality);
|
||||
return parsedExpression(scope);
|
||||
}, listener, objectEquality);
|
||||
}
|
||||
|
||||
function addInterceptor(parsedExpression, interceptorFn) {
|
||||
if (!interceptorFn) return parsedExpression;
|
||||
var watchDelegate = parsedExpression.$$watchDelegate;
|
||||
var useInputs = false;
|
||||
|
||||
var regularWatch =
|
||||
watchDelegate !== oneTimeLiteralWatchDelegate &&
|
||||
watchDelegate !== oneTimeWatchDelegate;
|
||||
|
||||
var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) {
|
||||
var value = parsedExpression(scope, locals, assign, inputs);
|
||||
var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs);
|
||||
return interceptorFn(value, scope, locals);
|
||||
} : function oneTimeInterceptedExpression(scope, locals, assign, inputs) {
|
||||
var value = parsedExpression(scope, locals, assign, inputs);
|
||||
@@ -1890,6 +1950,7 @@ function $ParseProvider() {
|
||||
// If there is an interceptor, but no watchDelegate then treat the interceptor like
|
||||
// we treat filters - it is assumed to be a pure function unless flagged with $stateful
|
||||
fn.$$watchDelegate = inputsWatchDelegate;
|
||||
useInputs = !parsedExpression.inputs;
|
||||
fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression];
|
||||
}
|
||||
|
||||
|
||||
+24
-22
@@ -53,6 +53,8 @@
|
||||
*
|
||||
* Note: progress/notify callbacks are not currently supported via the ES6-style interface.
|
||||
*
|
||||
* Note: unlike ES6 behaviour, an exception thrown in the constructor function will NOT implicitly reject the promise.
|
||||
*
|
||||
* However, the more traditional CommonJS-style usage is still available, and documented below.
|
||||
*
|
||||
* [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
|
||||
@@ -241,18 +243,6 @@ function $$QProvider() {
|
||||
*/
|
||||
function qFactory(nextTick, exceptionHandler) {
|
||||
var $qMinErr = minErr('$q', TypeError);
|
||||
function callOnce(self, resolveFn, rejectFn) {
|
||||
var called = false;
|
||||
function wrap(fn) {
|
||||
return function(value) {
|
||||
if (called) return;
|
||||
called = true;
|
||||
fn.call(self, value);
|
||||
};
|
||||
}
|
||||
|
||||
return [wrap(resolveFn), wrap(rejectFn)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -265,7 +255,12 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
* @returns {Deferred} Returns a new instance of deferred.
|
||||
*/
|
||||
var defer = function() {
|
||||
return new Deferred();
|
||||
var d = new Deferred();
|
||||
//Necessary to support unbound execution :/
|
||||
d.resolve = simpleBind(d, d.resolve);
|
||||
d.reject = simpleBind(d, d.reject);
|
||||
d.notify = simpleBind(d, d.notify);
|
||||
return d;
|
||||
};
|
||||
|
||||
function Promise() {
|
||||
@@ -338,10 +333,6 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
|
||||
function Deferred() {
|
||||
this.promise = new Promise();
|
||||
//Necessary to support unbound execution :/
|
||||
this.resolve = simpleBind(this, this.resolve);
|
||||
this.reject = simpleBind(this, this.reject);
|
||||
this.notify = simpleBind(this, this.notify);
|
||||
}
|
||||
|
||||
extend(Deferred.prototype, {
|
||||
@@ -359,23 +350,34 @@ function qFactory(nextTick, exceptionHandler) {
|
||||
},
|
||||
|
||||
$$resolve: function(val) {
|
||||
var then, fns;
|
||||
|
||||
fns = callOnce(this, this.$$resolve, this.$$reject);
|
||||
var then;
|
||||
var that = this;
|
||||
var done = false;
|
||||
try {
|
||||
if ((isObject(val) || isFunction(val))) then = val && val.then;
|
||||
if (isFunction(then)) {
|
||||
this.promise.$$state.status = -1;
|
||||
then.call(val, fns[0], fns[1], this.notify);
|
||||
then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify));
|
||||
} else {
|
||||
this.promise.$$state.value = val;
|
||||
this.promise.$$state.status = 1;
|
||||
scheduleProcessQueue(this.promise.$$state);
|
||||
}
|
||||
} catch (e) {
|
||||
fns[1](e);
|
||||
rejectPromise(e);
|
||||
exceptionHandler(e);
|
||||
}
|
||||
|
||||
function resolvePromise(val) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
that.$$resolve(val);
|
||||
}
|
||||
function rejectPromise(val) {
|
||||
if (done) return;
|
||||
done = true;
|
||||
that.$$reject(val);
|
||||
}
|
||||
},
|
||||
|
||||
reject: function(reason) {
|
||||
|
||||
+35
-19
@@ -14,15 +14,15 @@
|
||||
* exposed as $$____ properties
|
||||
*
|
||||
* Loop operations are optimized by using while(count--) { ... }
|
||||
* - this means that in order to keep the same order of execution as addition we have to add
|
||||
* - This means that in order to keep the same order of execution as addition we have to add
|
||||
* items to the array at the beginning (unshift) instead of at the end (push)
|
||||
*
|
||||
* Child scopes are created and removed often
|
||||
* - Using an array would be slow since inserts in middle are expensive so we use linked list
|
||||
* - Using an array would be slow since inserts in the middle are expensive; so we use linked lists
|
||||
*
|
||||
* There are few watches then a lot of observers. This is why you don't want the observer to be
|
||||
* implemented in the same way as watch. Watch requires return of initialization function which
|
||||
* are expensive to construct.
|
||||
* There are fewer watches than observers. This is why you don't want the observer to be implemented
|
||||
* in the same way as watch. Watch requires return of the initialization function which is expensive
|
||||
* to construct.
|
||||
*/
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
* Every application has a single root {@link ng.$rootScope.Scope scope}.
|
||||
* All other scopes are descendant scopes of the root scope. Scopes provide separation
|
||||
* between the model and the view, via a mechanism for watching the model for changes.
|
||||
* They also provide an event emission/broadcast and subscription facility. See the
|
||||
* They also provide event emission/broadcast and subscription facility. See the
|
||||
* {@link guide/scope developer guide on scopes}.
|
||||
*/
|
||||
function $RootScopeProvider() {
|
||||
@@ -94,13 +94,36 @@ function $RootScopeProvider() {
|
||||
return ChildScope;
|
||||
}
|
||||
|
||||
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
|
||||
function($injector, $exceptionHandler, $parse, $browser) {
|
||||
this.$get = ['$exceptionHandler', '$parse', '$browser',
|
||||
function($exceptionHandler, $parse, $browser) {
|
||||
|
||||
function destroyChildScope($event) {
|
||||
$event.currentScope.$$destroyed = true;
|
||||
}
|
||||
|
||||
function cleanUpScope($scope) {
|
||||
|
||||
if (msie === 9) {
|
||||
// There is a memory leak in IE9 if all child scopes are not disconnected
|
||||
// completely when a scope is destroyed. So this code will recurse up through
|
||||
// all this scopes children
|
||||
//
|
||||
// See issue https://github.com/angular/angular.js/issues/10706
|
||||
$scope.$$childHead && cleanUpScope($scope.$$childHead);
|
||||
$scope.$$nextSibling && cleanUpScope($scope.$$nextSibling);
|
||||
}
|
||||
|
||||
// The code below works around IE9 and V8's memory leaks
|
||||
//
|
||||
// See:
|
||||
// - https://code.google.com/p/v8/issues/detail?id=2073#c26
|
||||
// - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
|
||||
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
|
||||
|
||||
$scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead =
|
||||
$scope.$$childTail = $scope.$root = $scope.$$watchers = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name $rootScope.Scope
|
||||
@@ -356,7 +379,7 @@ function $RootScopeProvider() {
|
||||
* - `newVal` contains the current value of the `watchExpression`
|
||||
* - `oldVal` contains the previous value of the `watchExpression`
|
||||
* - `scope` refers to the current scope
|
||||
* @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of
|
||||
* @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of
|
||||
* comparing for reference equality.
|
||||
* @returns {function()} Returns a deregistration function for this listener.
|
||||
*/
|
||||
@@ -897,16 +920,9 @@ function $RootScopeProvider() {
|
||||
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
|
||||
this.$$listeners = {};
|
||||
|
||||
// All of the code below is bogus code that works around V8's memory leak via optimized code
|
||||
// and inline caches.
|
||||
//
|
||||
// see:
|
||||
// - https://code.google.com/p/v8/issues/detail?id=2073#c26
|
||||
// - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
|
||||
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
|
||||
|
||||
this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
|
||||
this.$$childTail = this.$root = this.$$watchers = null;
|
||||
// Disconnect the next sibling to prevent `cleanUpScope` destroying those too
|
||||
this.$$nextSibling = null;
|
||||
cleanUpScope(this);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
+6
-1
@@ -342,6 +342,11 @@ function $SceDelegateProvider() {
|
||||
* returns the originally supplied value if the queried context type is a supertype of the
|
||||
* created type. If this condition isn't satisfied, throws an exception.
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting
|
||||
* (XSS) vulnerability in your application.
|
||||
* </div>
|
||||
*
|
||||
* @param {string} type The kind of context in which this value is to be used.
|
||||
* @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
|
||||
* `$sceDelegate.trustAs`} call.
|
||||
@@ -484,7 +489,7 @@ function $SceDelegateProvider() {
|
||||
* By default, Angular only loads templates from the same domain and protocol as the application
|
||||
* document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
|
||||
* $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
|
||||
* protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
|
||||
* protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
|
||||
* them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
|
||||
*
|
||||
* *Please note*:
|
||||
|
||||
+56
-21
@@ -3,26 +3,63 @@
|
||||
var $compileMinErr = minErr('$compile');
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $templateRequest
|
||||
*
|
||||
* @ngdoc provider
|
||||
* @name $templateRequestProvider
|
||||
* @description
|
||||
* The `$templateRequest` service runs security checks then downloads the provided template using
|
||||
* `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
|
||||
* fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
|
||||
* exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
|
||||
* contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
|
||||
* when `tpl` is of type string and `$templateCache` has the matching entry.
|
||||
* Used to configure the options passed to the {@link $http} service when making a template request.
|
||||
*
|
||||
* @param {string|TrustedResourceUrl} tpl The HTTP request template URL
|
||||
* @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
|
||||
*
|
||||
* @return {Promise} a promise for the HTTP response data of the given URL.
|
||||
*
|
||||
* @property {number} totalPendingRequests total amount of pending template requests being downloaded.
|
||||
* For example, it can be used for specifying the "Accept" header that is sent to the server, when
|
||||
* requesting a template.
|
||||
*/
|
||||
function $TemplateRequestProvider() {
|
||||
|
||||
var httpOptions;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $templateRequestProvider#httpOptions
|
||||
* @description
|
||||
* The options to be passed to the {@link $http} service when making the request.
|
||||
* You can use this to override options such as the "Accept" header for template requests.
|
||||
*
|
||||
* The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the
|
||||
* options if not overridden here.
|
||||
*
|
||||
* @param {string=} value new value for the {@link $http} options.
|
||||
* @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter.
|
||||
*/
|
||||
this.httpOptions = function(val) {
|
||||
if (val) {
|
||||
httpOptions = val;
|
||||
return this;
|
||||
}
|
||||
return httpOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $templateRequest
|
||||
*
|
||||
* @description
|
||||
* The `$templateRequest` service runs security checks then downloads the provided template using
|
||||
* `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
|
||||
* fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
|
||||
* exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
|
||||
* contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
|
||||
* when `tpl` is of type string and `$templateCache` has the matching entry.
|
||||
*
|
||||
* If you want to pass custom options to the `$http` service, such as setting the Accept header you
|
||||
* can configure this via {@link $templateRequestProvider#httpOptions}.
|
||||
*
|
||||
* @param {string|TrustedResourceUrl} tpl The HTTP request template URL
|
||||
* @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
|
||||
*
|
||||
* @return {Promise} a promise for the HTTP response data of the given URL.
|
||||
*
|
||||
* @property {number} totalPendingRequests total amount of pending template requests being downloaded.
|
||||
*/
|
||||
this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
|
||||
|
||||
function handleRequestFn(tpl, ignoreRequestError) {
|
||||
handleRequestFn.totalPendingRequests++;
|
||||
|
||||
@@ -45,12 +82,10 @@ function $TemplateRequestProvider() {
|
||||
transformResponse = null;
|
||||
}
|
||||
|
||||
var httpOptions = {
|
||||
cache: $templateCache,
|
||||
transformResponse: transformResponse
|
||||
};
|
||||
|
||||
return $http.get(tpl, httpOptions)
|
||||
return $http.get(tpl, extend({
|
||||
cache: $templateCache,
|
||||
transformResponse: transformResponse
|
||||
}, httpOptions))
|
||||
['finally'](function() {
|
||||
handleRequestFn.totalPendingRequests--;
|
||||
})
|
||||
|
||||
+2
-2
@@ -33,8 +33,8 @@ function $TimeoutProvider() {
|
||||
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
|
||||
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
|
||||
* @param {...*=} Pass additional parameters to the executed function.
|
||||
* @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
|
||||
* promise will be resolved with is the return value of the `fn` function.
|
||||
* @returns {Promise} Promise that will be resolved when the timeout is reached. The promise
|
||||
* will be resolved with the return value of the `fn` function.
|
||||
*
|
||||
*/
|
||||
function timeout(fn, delay, invokeApply) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"angular": false,
|
||||
"noop": false,
|
||||
|
||||
"copy": false,
|
||||
"forEach": false,
|
||||
"extend": false,
|
||||
"jqLite": false,
|
||||
@@ -28,6 +29,7 @@
|
||||
"REMOVE_CLASS_SUFFIX": false,
|
||||
"EVENT_CLASS_PREFIX": false,
|
||||
"ACTIVE_CLASS_SUFFIX": false,
|
||||
"PREPARE_CLASS_SUFFIX": false,
|
||||
|
||||
"TRANSITION_DURATION_PROP": false,
|
||||
"TRANSITION_DELAY_PROP": false,
|
||||
|
||||
@@ -186,8 +186,10 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
|
||||
*
|
||||
* * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
|
||||
* to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
|
||||
* * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
|
||||
* `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
|
||||
* * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
|
||||
* * `transition` - The raw CSS transition style that will be used (e.g. `1s linear all`).
|
||||
* * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
|
||||
* * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
|
||||
* * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
|
||||
* * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
|
||||
@@ -202,8 +204,12 @@ var ANIMATE_TIMER_KEY = '$$animateCss';
|
||||
* * `stagger` - A numeric time value representing the delay between successively animated elements
|
||||
* ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
|
||||
* * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
|
||||
* * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
|
||||
* `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
|
||||
* * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occuring on the classes being added and removed.)
|
||||
* * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
|
||||
* the animation is closed. This is useful for when the styles are used purely for the sake of
|
||||
* the animation and do not have a lasting visual effect on the element (e.g. a colapse and open animation).
|
||||
* By default this value is set to `false`.
|
||||
*
|
||||
* @return {object} an object with start and end methods and details about the animation.
|
||||
*
|
||||
@@ -324,6 +330,23 @@ function createLocalCacheLookup() {
|
||||
};
|
||||
}
|
||||
|
||||
// we do not reassign an already present style value since
|
||||
// if we detect the style property value again we may be
|
||||
// detecting styles that were added via the `from` styles.
|
||||
// We make use of `isDefined` here since an empty string
|
||||
// or null value (which is what getPropertyValue will return
|
||||
// for a non-existing style) will still be marked as a valid
|
||||
// value for the style (a falsy value implies that the style
|
||||
// is to be removed at the end of the animation). If we had a simple
|
||||
// "OR" statement then it would not be enough to catch that.
|
||||
function registerRestorableStyles(backup, node, properties) {
|
||||
forEach(properties, function(prop) {
|
||||
backup[prop] = isDefined(backup[prop])
|
||||
? backup[prop]
|
||||
: node.style.getPropertyValue(prop);
|
||||
});
|
||||
}
|
||||
|
||||
var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
var gcsLookup = createLocalCacheLookup();
|
||||
var gcsStaggerLookup = createLocalCacheLookup();
|
||||
@@ -423,7 +446,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
return timings;
|
||||
}
|
||||
|
||||
return function init(element, options) {
|
||||
return function init(element, initialOptions) {
|
||||
// we always make a copy of the options since
|
||||
// there should never be any side effects on
|
||||
// the input data when running `$animateCss`.
|
||||
var options = copy(initialOptions);
|
||||
|
||||
var restoreStyles = {};
|
||||
var node = getDomNode(element);
|
||||
if (!node
|
||||
|| !node.parentNode
|
||||
@@ -598,7 +627,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
if (options.delay != null) {
|
||||
var delayStyle = parseFloat(options.delay);
|
||||
var delayStyle;
|
||||
if (typeof options.delay !== "boolean") {
|
||||
delayStyle = parseFloat(options.delay);
|
||||
// number in options.delay means we have to recalculate the delay for the closing timeout
|
||||
maxDelay = Math.max(delayStyle, 0);
|
||||
}
|
||||
|
||||
if (flags.applyTransitionDelay) {
|
||||
temporaryStyles.push(getCssDelayStyle(delayStyle));
|
||||
@@ -625,7 +659,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
stagger.animationDuration === 0;
|
||||
}
|
||||
|
||||
applyAnimationFromStyles(element, options);
|
||||
if (options.from) {
|
||||
if (options.cleanupStyles) {
|
||||
registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
|
||||
}
|
||||
applyAnimationFromStyles(element, options);
|
||||
}
|
||||
|
||||
if (flags.blockTransition || flags.blockKeyframeAnimation) {
|
||||
applyBlocking(maxDuration);
|
||||
@@ -692,6 +731,13 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
applyAnimationClasses(element, options);
|
||||
applyAnimationStyles(element, options);
|
||||
|
||||
if (Object.keys(restoreStyles).length) {
|
||||
forEach(restoreStyles, function(value, prop) {
|
||||
value ? node.style.setProperty(prop, value)
|
||||
: node.style.removeProperty(prop);
|
||||
});
|
||||
}
|
||||
|
||||
// the reason why we have this option is to allow a synchronous closing callback
|
||||
// that is fired as SOON as the animation ends (when the CSS is removed) or if
|
||||
// the animation never takes off at all. A good example is a leave animation since
|
||||
@@ -886,7 +932,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
element.on(events.join(' '), onAnimationProgress);
|
||||
applyAnimationToStyles(element, options);
|
||||
if (options.to) {
|
||||
if (options.cleanupStyles) {
|
||||
registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
|
||||
}
|
||||
applyAnimationToStyles(element, options);
|
||||
}
|
||||
}
|
||||
|
||||
function onAnimationExpired() {
|
||||
|
||||
@@ -9,16 +9,25 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
|
||||
var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
|
||||
|
||||
this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$$body', '$sniffer', '$$jqLite',
|
||||
function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $$body, $sniffer, $$jqLite) {
|
||||
function isDocumentFragment(node) {
|
||||
return node.parentNode && node.parentNode.nodeType === 11;
|
||||
}
|
||||
|
||||
this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
|
||||
function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) {
|
||||
|
||||
// only browsers that support these properties can render animations
|
||||
if (!$sniffer.animations && !$sniffer.transitions) return noop;
|
||||
|
||||
var bodyNode = getDomNode($$body);
|
||||
var bodyNode = $document[0].body;
|
||||
var rootNode = getDomNode($rootElement);
|
||||
|
||||
var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
|
||||
var rootBodyElement = jqLite(
|
||||
// this is to avoid using something that exists outside of the body
|
||||
// we also special case the doc fragement case because our unit test code
|
||||
// appends the $rootElement to the body after the app has been bootstrapped
|
||||
isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
|
||||
);
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
|
||||
@@ -66,15 +66,33 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
return (nO.addClass && nO.addClass === cO.removeClass) || (nO.removeClass && nO.removeClass === cO.addClass);
|
||||
});
|
||||
|
||||
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$body', '$$HashMap',
|
||||
this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
|
||||
'$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
|
||||
function($$rAF, $rootScope, $rootElement, $document, $$body, $$HashMap,
|
||||
function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
|
||||
$$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
|
||||
|
||||
var activeAnimationsLookup = new $$HashMap();
|
||||
var disabledElementsLookup = new $$HashMap();
|
||||
var animationsEnabled = null;
|
||||
|
||||
function postDigestTaskFactory() {
|
||||
var postDigestCalled = false;
|
||||
return function(fn) {
|
||||
// we only issue a call to postDigest before
|
||||
// it has first passed. This prevents any callbacks
|
||||
// from not firing once the animation has completed
|
||||
// since it will be out of the digest cycle.
|
||||
if (postDigestCalled) {
|
||||
fn();
|
||||
} else {
|
||||
$rootScope.$$postDigest(function() {
|
||||
postDigestCalled = true;
|
||||
fn();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Wait until all directive and route-related templates are downloaded and
|
||||
// compiled. The $templateRequest.totalPendingRequests variable keeps track of
|
||||
// all of the remote templates being currently downloaded. If there are no
|
||||
@@ -121,8 +139,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
return mergeAnimationOptions(element, options, {});
|
||||
}
|
||||
|
||||
function findCallbacks(element, event) {
|
||||
function findCallbacks(parent, element, event) {
|
||||
var targetNode = getDomNode(element);
|
||||
var targetParentNode = getDomNode(parent);
|
||||
|
||||
var matches = [];
|
||||
var entries = callbackRegistry[event];
|
||||
@@ -130,6 +149,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
forEach(entries, function(entry) {
|
||||
if (entry.node.contains(targetNode)) {
|
||||
matches.push(entry.callback);
|
||||
} else if (event === 'leave' && entry.node.contains(targetParentNode)) {
|
||||
matches.push(entry.callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -137,14 +158,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
return matches;
|
||||
}
|
||||
|
||||
function triggerCallback(event, element, phase, data) {
|
||||
$$rAF(function() {
|
||||
forEach(findCallbacks(element, event), function(callback) {
|
||||
callback(element, phase, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
on: function(event, container, callback) {
|
||||
var node = extractElementNode(container);
|
||||
@@ -225,7 +238,12 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
};
|
||||
|
||||
function queueAnimation(element, event, options) {
|
||||
function queueAnimation(element, event, initialOptions) {
|
||||
// we always make a copy of the options since
|
||||
// there should never be any side effects on
|
||||
// the input data when running `$animateCss`.
|
||||
var options = copy(initialOptions);
|
||||
|
||||
var node, parent;
|
||||
element = stripCommentsFromElement(element);
|
||||
if (element) {
|
||||
@@ -239,6 +257,9 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
// These methods will become available after the digest has passed
|
||||
var runner = new $$AnimateRunner();
|
||||
|
||||
// this is used to trigger callbacks in postDigest mode
|
||||
var runInNextPostDigestOrNow = postDigestTaskFactory();
|
||||
|
||||
if (isArray(options.addClass)) {
|
||||
options.addClass = options.addClass.join(' ');
|
||||
}
|
||||
@@ -459,7 +480,20 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
return runner;
|
||||
|
||||
function notifyProgress(runner, event, phase, data) {
|
||||
triggerCallback(event, element, phase, data);
|
||||
runInNextPostDigestOrNow(function() {
|
||||
var callbacks = findCallbacks(parent, element, event);
|
||||
if (callbacks.length) {
|
||||
// do not optimize this call here to RAF because
|
||||
// we don't know how heavy the callback code here will
|
||||
// be and if this code is buffered then this can
|
||||
// lead to a performance regression.
|
||||
$$rAF(function() {
|
||||
forEach(callbacks, function(callback) {
|
||||
callback(element, phase, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
runner.progress(event, phase, data);
|
||||
}
|
||||
|
||||
@@ -478,15 +512,15 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
forEach(children, function(child) {
|
||||
var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
|
||||
var animationDetails = activeAnimationsLookup.get(child);
|
||||
switch (state) {
|
||||
case RUNNING_STATE:
|
||||
animationDetails.runner.end();
|
||||
/* falls through */
|
||||
case PRE_DIGEST_STATE:
|
||||
if (animationDetails) {
|
||||
if (animationDetails) {
|
||||
switch (state) {
|
||||
case RUNNING_STATE:
|
||||
animationDetails.runner.end();
|
||||
/* falls through */
|
||||
case PRE_DIGEST_STATE:
|
||||
activeAnimationsLookup.remove(child);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -502,7 +536,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
function areAnimationsAllowed(element, parentElement, event) {
|
||||
var bodyElementDetected = isMatchingElement(element, $$body) || element[0].nodeName === 'HTML';
|
||||
var bodyElement = jqLite($document[0].body);
|
||||
var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
|
||||
var rootElementDetected = isMatchingElement(element, $rootElement);
|
||||
var parentAnimationDetected = false;
|
||||
var animateChildren;
|
||||
@@ -558,7 +593,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (!bodyElementDetected) {
|
||||
// we also need to ensure that the element is or will be apart of the body element
|
||||
// otherwise it is pointless to even issue an animation to be rendered
|
||||
bodyElementDetected = isMatchingElement(parentElement, $$body);
|
||||
bodyElementDetected = isMatchingElement(parentElement, bodyElement);
|
||||
}
|
||||
|
||||
parentElement = parentElement.parent();
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
|
||||
var waitQueue = [];
|
||||
|
||||
function waitForTick(fn) {
|
||||
waitQueue.push(fn);
|
||||
if (waitQueue.length > 1) return;
|
||||
$$rAF(function() {
|
||||
for (var i = 0; i < waitQueue.length; i++) {
|
||||
waitQueue[i]();
|
||||
}
|
||||
waitQueue = [];
|
||||
});
|
||||
}
|
||||
|
||||
return function() {
|
||||
var passed = false;
|
||||
waitForTick(function() {
|
||||
passed = true;
|
||||
});
|
||||
return function(callback) {
|
||||
passed ? callback() : waitForTick(callback);
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
|
||||
function($q, $sniffer, $$animateAsyncRun) {
|
||||
|
||||
var INITIAL_STATE = 0;
|
||||
var DONE_PENDING_STATE = 1;
|
||||
var DONE_COMPLETE_STATE = 2;
|
||||
|
||||
AnimateRunner.chain = function(chain, callback) {
|
||||
var index = 0;
|
||||
|
||||
next();
|
||||
function next() {
|
||||
if (index === chain.length) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
chain[index](function(response) {
|
||||
if (response === false) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AnimateRunner.all = function(runners, callback) {
|
||||
var count = 0;
|
||||
var status = true;
|
||||
forEach(runners, function(runner) {
|
||||
runner.done(onProgress);
|
||||
});
|
||||
|
||||
function onProgress(response) {
|
||||
status = status && response;
|
||||
if (++count === runners.length) {
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function AnimateRunner(host) {
|
||||
this.setHost(host);
|
||||
|
||||
this._doneCallbacks = [];
|
||||
this._runInAnimationFrame = $$animateAsyncRun();
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
AnimateRunner.prototype = {
|
||||
setHost: function(host) {
|
||||
this.host = host || {};
|
||||
},
|
||||
|
||||
done: function(fn) {
|
||||
if (this._state === DONE_COMPLETE_STATE) {
|
||||
fn();
|
||||
} else {
|
||||
this._doneCallbacks.push(fn);
|
||||
}
|
||||
},
|
||||
|
||||
progress: noop,
|
||||
|
||||
getPromise: function() {
|
||||
if (!this.promise) {
|
||||
var self = this;
|
||||
this.promise = $q(function(resolve, reject) {
|
||||
self.done(function(status) {
|
||||
status === false ? reject() : resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this.promise;
|
||||
},
|
||||
|
||||
then: function(resolveHandler, rejectHandler) {
|
||||
return this.getPromise().then(resolveHandler, rejectHandler);
|
||||
},
|
||||
|
||||
'catch': function(handler) {
|
||||
return this.getPromise()['catch'](handler);
|
||||
},
|
||||
|
||||
'finally': function(handler) {
|
||||
return this.getPromise()['finally'](handler);
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
if (this.host.pause) {
|
||||
this.host.pause();
|
||||
}
|
||||
},
|
||||
|
||||
resume: function() {
|
||||
if (this.host.resume) {
|
||||
this.host.resume();
|
||||
}
|
||||
},
|
||||
|
||||
end: function() {
|
||||
if (this.host.end) {
|
||||
this.host.end();
|
||||
}
|
||||
this._resolve(true);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
if (this.host.cancel) {
|
||||
this.host.cancel();
|
||||
}
|
||||
this._resolve(false);
|
||||
},
|
||||
|
||||
complete: function(response) {
|
||||
var self = this;
|
||||
if (self._state === INITIAL_STATE) {
|
||||
self._state = DONE_PENDING_STATE;
|
||||
self._runInAnimationFrame(function() {
|
||||
self._resolve(response);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_resolve: function(response) {
|
||||
if (this._state !== DONE_COMPLETE_STATE) {
|
||||
forEach(this._doneCallbacks, function(fn) {
|
||||
fn(response);
|
||||
});
|
||||
this._doneCallbacks.length = 0;
|
||||
this._state = DONE_COMPLETE_STATE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return AnimateRunner;
|
||||
}];
|
||||
@@ -135,6 +135,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
options.tempClasses = null;
|
||||
}
|
||||
|
||||
var prepareClassName;
|
||||
if (isStructural) {
|
||||
prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
|
||||
$$jqLite.addClass(element, prepareClassName);
|
||||
}
|
||||
|
||||
animationQueue.push({
|
||||
// this data is used by the postDigest code and passed into
|
||||
// the driver step function
|
||||
@@ -357,6 +363,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (tempClasses) {
|
||||
$$jqLite.addClass(element, tempClasses);
|
||||
}
|
||||
if (prepareClassName) {
|
||||
$$jqLite.removeClass(element, prepareClassName);
|
||||
prepareClassName = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateAnimationRunners(animation, newRunner) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
function $$BodyProvider() {
|
||||
this.$get = ['$document', function($document) {
|
||||
return jqLite($document[0].body);
|
||||
}];
|
||||
}
|
||||
+41
-20
@@ -2,11 +2,10 @@
|
||||
|
||||
/* global angularAnimateModule: true,
|
||||
|
||||
$$BodyProvider,
|
||||
ngAnimateSwapDirective,
|
||||
$$AnimateAsyncRunFactory,
|
||||
$$rAFSchedulerFactory,
|
||||
$$AnimateChildrenDirective,
|
||||
$$AnimateRunnerFactory,
|
||||
$$AnimateQueueProvider,
|
||||
$$AnimationProvider,
|
||||
$AnimateCssProvider,
|
||||
@@ -255,6 +254,34 @@
|
||||
* the CSS class once an animation has completed.)
|
||||
*
|
||||
*
|
||||
* ### The `ng-[event]-prepare` class
|
||||
*
|
||||
* This is a special class that can be used to prevent unwanted flickering / flash of content before
|
||||
* the actual animation starts. The class is added as soon as an animation is initialized, but removed
|
||||
* before the actual animation starts (after waiting for a $digest).
|
||||
* It is also only added for *structural* animations (`enter`, `move`, and `leave`).
|
||||
*
|
||||
* In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
|
||||
* into elements that have class-based animations such as `ngClass`.
|
||||
*
|
||||
* ```html
|
||||
* <div ng-class="{red: myProp}">
|
||||
* <div ng-class="{blue: myProp}">
|
||||
* <div class="message" ng-if="myProp"></div>
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
|
||||
* In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
|
||||
*
|
||||
* ```css
|
||||
* .message.ng-enter-prepare {
|
||||
* opacity: 0;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ## JavaScript-based Animations
|
||||
*
|
||||
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
|
||||
@@ -291,7 +318,7 @@
|
||||
* jQuery(element).fadeOut(1000, doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
|
||||
@@ -322,7 +349,7 @@
|
||||
* // do some cool animation and call the doneFn
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* ## CSS + JS Animations Together
|
||||
@@ -344,7 +371,7 @@
|
||||
* jQuery(element).slideIn(1000, doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* ```css
|
||||
@@ -364,16 +391,15 @@
|
||||
* ```js
|
||||
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
|
||||
* return {
|
||||
* enter: function(element, doneFn) {
|
||||
* enter: function(element) {
|
||||
* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
|
||||
* var runner = $animateCss(element, {
|
||||
* return $animateCss(element, {
|
||||
* event: 'enter',
|
||||
* structural: true
|
||||
* }).start();
|
||||
* runner.done(doneFn);
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
|
||||
@@ -385,19 +411,17 @@
|
||||
* ```js
|
||||
* myModule.animation('.slide', ['$animateCss', function($animateCss) {
|
||||
* return {
|
||||
* enter: function(element, doneFn) {
|
||||
* var runner = $animateCss(element, {
|
||||
* enter: function(element) {
|
||||
* return $animateCss(element, {
|
||||
* event: 'enter',
|
||||
* structural: true,
|
||||
* addClass: 'maroon-setting',
|
||||
* from: { height:0 },
|
||||
* to: { height: 200 }
|
||||
* }).start();
|
||||
*
|
||||
* runner.done(doneFn);
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* Now we can fill in the rest via our transition CSS code:
|
||||
@@ -742,14 +766,11 @@
|
||||
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
|
||||
*/
|
||||
angular.module('ngAnimate', [])
|
||||
.provider('$$body', $$BodyProvider)
|
||||
.directive('ngAnimateSwap', ngAnimateSwapDirective)
|
||||
|
||||
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
|
||||
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
|
||||
|
||||
.factory('$$AnimateRunner', $$AnimateRunnerFactory)
|
||||
.factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
|
||||
|
||||
.provider('$$animateQueue', $$AnimateQueueProvider)
|
||||
.provider('$$animation', $$AnimationProvider)
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngAnimateSwap
|
||||
* @restrict A
|
||||
* @scope
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* ngAnimateSwap is a animation-oriented directive that allows for the container to
|
||||
* be removed and entered in whenever the associated expression changes. A
|
||||
* common usecase for this directive is a rotating banner component which
|
||||
* contains one image being present at a time. When the active image changes
|
||||
* then the old image will perform a `leave` animation and the new element
|
||||
* will be inserted via an `enter` animation.
|
||||
*
|
||||
* @example
|
||||
* <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
|
||||
* deps="angular-animate.js"
|
||||
* animations="true" fixBase="true">
|
||||
* <file name="index.html">
|
||||
* <div class="container" ng-controller="AppCtrl">
|
||||
* <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
|
||||
* {{ number }}
|
||||
* </div>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="script.js">
|
||||
* angular.module('ngAnimateSwapExample', ['ngAnimate'])
|
||||
* .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
|
||||
* $scope.number = 0;
|
||||
* $interval(function() {
|
||||
* $scope.number++;
|
||||
* }, 1000);
|
||||
*
|
||||
* var colors = ['red','blue','green','yellow','orange'];
|
||||
* $scope.colorClass = function(number) {
|
||||
* return colors[number % colors.length];
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="animations.css">
|
||||
* .container {
|
||||
* height:250px;
|
||||
* width:250px;
|
||||
* position:relative;
|
||||
* overflow:hidden;
|
||||
* border:2px solid black;
|
||||
* }
|
||||
* .container .cell {
|
||||
* font-size:150px;
|
||||
* text-align:center;
|
||||
* line-height:250px;
|
||||
* position:absolute;
|
||||
* top:0;
|
||||
* left:0;
|
||||
* right:0;
|
||||
* border-bottom:2px solid black;
|
||||
* }
|
||||
* .swap-animation.ng-enter, .swap-animation.ng-leave {
|
||||
* transition:0.5s linear all;
|
||||
* }
|
||||
* .swap-animation.ng-enter {
|
||||
* top:-250px;
|
||||
* }
|
||||
* .swap-animation.ng-enter-active {
|
||||
* top:0px;
|
||||
* }
|
||||
* .swap-animation.ng-leave {
|
||||
* top:0px;
|
||||
* }
|
||||
* .swap-animation.ng-leave-active {
|
||||
* top:250px;
|
||||
* }
|
||||
* .red { background:red; }
|
||||
* .green { background:green; }
|
||||
* .blue { background:blue; }
|
||||
* .yellow { background:yellow; }
|
||||
* .orange { background:orange; }
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
transclude: 'element',
|
||||
terminal: true,
|
||||
priority: 600, // we use 600 here to ensure that the directive is caught before others
|
||||
link: function(scope, $element, attrs, ctrl, $transclude) {
|
||||
var previousElement, previousScope;
|
||||
scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
|
||||
if (previousElement) {
|
||||
$animate.leave(previousElement);
|
||||
}
|
||||
if (previousScope) {
|
||||
previousScope.$destroy();
|
||||
previousScope = null;
|
||||
}
|
||||
if (value || value === 0) {
|
||||
previousScope = scope.$new();
|
||||
$transclude(previousScope, function(element) {
|
||||
previousElement = element;
|
||||
$animate.enter(element, null, $element);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
/* jshint ignore:start */
|
||||
var noop = angular.noop;
|
||||
var copy = angular.copy;
|
||||
var extend = angular.extend;
|
||||
var jqLite = angular.element;
|
||||
var forEach = angular.forEach;
|
||||
@@ -20,6 +21,7 @@ var ADD_CLASS_SUFFIX = '-add';
|
||||
var REMOVE_CLASS_SUFFIX = '-remove';
|
||||
var EVENT_CLASS_PREFIX = 'ng-';
|
||||
var ACTIVE_CLASS_SUFFIX = '-active';
|
||||
var PREPARE_CLASS_SUFFIX = '-prepare';
|
||||
|
||||
var NG_ANIMATE_CLASSNAME = 'ng-animate';
|
||||
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
|
||||
|
||||
+2
-1
@@ -235,7 +235,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
},
|
||||
post: function(scope, elem, attr, ngModel) {
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem)
|
||||
&& !isNodeOneOf(elem, nodeBlackList);
|
||||
|
||||
function ngAriaWatchModelValue() {
|
||||
return ngModel.$modelValue;
|
||||
|
||||
Vendored
+14
@@ -62,6 +62,20 @@ $provide.value("$locale", {
|
||||
"Nov",
|
||||
"Des"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"Januarie",
|
||||
"Februarie",
|
||||
"Maart",
|
||||
"April",
|
||||
"Mei",
|
||||
"Junie",
|
||||
"Julie",
|
||||
"Augustus",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Desember"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
|
||||
Vendored
+14
@@ -62,6 +62,20 @@ $provide.value("$locale", {
|
||||
"Nov",
|
||||
"Des"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"Januarie",
|
||||
"Februarie",
|
||||
"Maart",
|
||||
"April",
|
||||
"Mei",
|
||||
"Junie",
|
||||
"Julie",
|
||||
"Augustus",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Desember"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
|
||||
Vendored
+14
@@ -62,6 +62,20 @@ $provide.value("$locale", {
|
||||
"Nov",
|
||||
"Des"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"Januarie",
|
||||
"Februarie",
|
||||
"Maart",
|
||||
"April",
|
||||
"Mei",
|
||||
"Junie",
|
||||
"Julie",
|
||||
"Augustus",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Desember"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
|
||||
+14
@@ -80,6 +80,20 @@ $provide.value("$locale", {
|
||||
"kaa",
|
||||
"fwo"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300n\u00f9m",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300k\u0197\u0300z\u00f9\u0294",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300t\u0197\u0300d\u0289\u0300gh\u00e0",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300t\u01ceaf\u0289\u0304gh\u0101",
|
||||
"ndz\u0254\u0300\u014b\u00e8s\u00e8e",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300nz\u00f9gh\u00f2",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300d\u00f9mlo",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300kw\u00eef\u0254\u0300e",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300t\u0197\u0300f\u0289\u0300gh\u00e0dzugh\u00f9",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300gh\u01d4uwel\u0254\u0300m",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300chwa\u0294\u00e0kaa wo",
|
||||
"ndz\u0254\u0300\u014b\u00e8fw\u00f2o"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
|
||||
Vendored
+14
@@ -80,6 +80,20 @@ $provide.value("$locale", {
|
||||
"kaa",
|
||||
"fwo"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300n\u00f9m",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300k\u0197\u0300z\u00f9\u0294",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300t\u0197\u0300d\u0289\u0300gh\u00e0",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300t\u01ceaf\u0289\u0304gh\u0101",
|
||||
"ndz\u0254\u0300\u014b\u00e8s\u00e8e",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300nz\u00f9gh\u00f2",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300d\u00f9mlo",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300kw\u00eef\u0254\u0300e",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300t\u0197\u0300f\u0289\u0300gh\u00e0dzugh\u00f9",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300gh\u01d4uwel\u0254\u0300m",
|
||||
"ndz\u0254\u0300\u014b\u0254\u0300chwa\u0294\u00e0kaa wo",
|
||||
"ndz\u0254\u0300\u014b\u00e8fw\u00f2o"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
|
||||
Vendored
+14
@@ -80,6 +80,20 @@ $provide.value("$locale", {
|
||||
"\u0186-O",
|
||||
"M-\u0186"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"Sanda-\u0186p\u025bp\u0254n",
|
||||
"Kwakwar-\u0186gyefuo",
|
||||
"Eb\u0254w-\u0186benem",
|
||||
"Eb\u0254bira-Oforisuo",
|
||||
"Esusow Aketseaba-K\u0254t\u0254nimba",
|
||||
"Obirade-Ay\u025bwohomumu",
|
||||
"Ay\u025bwoho-Kitawonsa",
|
||||
"Difuu-\u0186sandaa",
|
||||
"Fankwa-\u0190b\u0254",
|
||||
"\u0186b\u025bs\u025b-Ahinime",
|
||||
"\u0186ber\u025bf\u025bw-Obubuo",
|
||||
"Mumu-\u0186p\u025bnimba"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
5,
|
||||
6
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user