Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75e876424d | |||
| 19fab4a1d7 | |||
| d1293540e1 | |||
| 22f66025db | |||
| 6f8ddb6d43 | |||
| 34590e15d4 | |||
| 83098b9add | |||
| 5d8861fb2f | |||
| b9f7c453e0 | |||
| 750344129e | |||
| 74da034077 | |||
| 91ef94d284 | |||
| ab9b021572 | |||
| b268c0b7b4 | |||
| b0c19f8b06 | |||
| bbc2a0ae48 | |||
| ca53dfcc18 | |||
| ce6a96b0d7 | |||
| d4b359f4b2 | |||
| 8d841c3405 | |||
| 2b285c75f4 | |||
| 6e4464331d | |||
| 2f8db1bf01 | |||
| 838cf4be3c | |||
| de2a56bbc8 | |||
| 55ad192e4a | |||
| 5b4713e43e | |||
| 3fa9aba0cc | |||
| 1bba358a75 | |||
| 7a4124c298 | |||
| 2512a81e09 | |||
| 44c9d1616a | |||
| 5758d73964 | |||
| 6bd6dbff49 | |||
| 7170f9d9ca | |||
| 1c0f721368 | |||
| 2a5a52a76c | |||
| c690946469 | |||
| 87b0055c80 | |||
| 2116857a2a | |||
| 0f58334b7b | |||
| bcc257b459 | |||
| 980fb395e4 | |||
| 62ed26a84f | |||
| 59f1f4e19a | |||
| cb51116dbd | |||
| c1f34e8eeb | |||
| 7bf5429e3b | |||
| d3da55c40f | |||
| 70edec947c | |||
| fe17c0e066 | |||
| e403682444 | |||
| 27d441b0d6 | |||
| 8a944b0872 | |||
| 786a1a4429 | |||
| 8e5c4e92f7 | |||
| 46d24ae4c8 | |||
| 9dd33c09b1 | |||
| fea8240c81 | |||
| 3d2b1be211 | |||
| f08a0c5ad1 | |||
| 6f1e0ba563 | |||
| 540338f9a5 | |||
| 0e6a700807 | |||
| 4fc40bc932 | |||
| 216724b4cb | |||
| 9bd1645970 | |||
| 3397a031a1 | |||
| 5ec5aa7751 | |||
| bf5ac5261d | |||
| 91b7cd9b74 | |||
| 7b2ecf42c6 | |||
| 256d9a948c | |||
| 99fc6cda98 | |||
| 690b69b9cd | |||
| 4262f15e16 | |||
| 3a8d1354ce | |||
| f9387c6890 | |||
| 48d0ffcbc4 | |||
| 3485ba1e2b | |||
| 2f61145475 | |||
| 8c618d896b | |||
| 68d4dc5b71 | |||
| 03a4a96cf9 | |||
| 655c52a621 | |||
| fa3ddba5f2 | |||
| c4a1b6124e | |||
| e52d731bfd | |||
| 9b72843018 | |||
| 9c1f8ea70b | |||
| 9fde5648e4 | |||
| ea829620b2 | |||
| 1731d091f8 | |||
| 9d3704ca46 | |||
| 215dff34dd | |||
| fa8c399fad | |||
| 7295c60ffb | |||
| fa01571036 | |||
| dbc698517f | |||
| a7f3761eda | |||
| 5a98e806ef | |||
| 808f984ec0 | |||
| 698af191de | |||
| 7a413df5e4 | |||
| 4994acd26e | |||
| 4dd10fd964 | |||
| 26119c09d1 | |||
| 927ebd9986 | |||
| f5536ab43c | |||
| 9e6a9b9922 | |||
| bfa66a90c2 | |||
| cdd1227a30 | |||
| 19ecdb54bf | |||
| 30aa3eff4c | |||
| 8d39bd8abf | |||
| 472d076cca | |||
| 1ae0be13c2 | |||
| 159efdd429 | |||
| 4755a35b7d | |||
| b2f8b0b875 | |||
| 24cd70058d | |||
| e46ab43422 | |||
| 8a62a8c7f0 | |||
| b6c2f8b854 | |||
| 00ee090f4f | |||
| 544001f5a3 | |||
| 7175d0d0e3 | |||
| 010d9b6853 | |||
| 122ab074ca | |||
| e22bf9ac78 | |||
| 324cb6b358 | |||
| 80a2176e20 | |||
| 681affef59 | |||
| 0ca8b1df20 | |||
| 20fb626b78 | |||
| 7ea2c7f36e | |||
| 912cbdd468 | |||
| 0202663e93 | |||
| 159bbf11ac | |||
| 9d2cc8341c | |||
| 38520a1a73 | |||
| 470eb37d29 | |||
| 4baf25b3ce | |||
| eb193686a5 | |||
| 4bebe7830b | |||
| 146cbf7eaa | |||
| b8e356191f | |||
| f48244ce5e | |||
| ebba426c0c | |||
| aa11dfc162 | |||
| c6110e8b08 | |||
| 290b5049c2 | |||
| f8a07dd9fe | |||
| 6f39f10827 | |||
| c3a654b7c8 | |||
| e7293daf2a | |||
| c71d414a95 | |||
| 06d4e18cda | |||
| 966e01cf26 | |||
| 67afd9dc63 | |||
| 4175860af1 | |||
| 6fb90bda9a | |||
| 770dd2dcfd | |||
| 0ff7bba2e3 | |||
| 82b0929e4e | |||
| 7d2c6eeef8 | |||
| 6d8c1950a0 | |||
| 1a5ea22079 | |||
| 4f9eb2c6e4 | |||
| 43769fb676 | |||
| 170cd96646 | |||
| 1d18e60ef7 | |||
| ea8016c4c8 | |||
| c3d5e33e18 | |||
| 2f6b6fb7a1 | |||
| ea2518fcea | |||
| 7e67e525a5 | |||
| 0e001084ff | |||
| 85e3203918 | |||
| f95bc42cee | |||
| 9080d2c53c | |||
| 728f7e2a85 | |||
| 5f704065a7 | |||
| 64631bf2e6 | |||
| aa35b243f8 | |||
| 1cc9c9ca9d | |||
| dc48aadd26 | |||
| ebce2f7253 | |||
| d0e50fdcd0 | |||
| d88167318d | |||
| 0a75a3db6e | |||
| b643f0d322 | |||
| 01dd588a28 | |||
| 3d6dc3fe31 | |||
| 0c81e9fd25 | |||
| 5df80e1854 | |||
| ba9fb82f18 |
+2
-1
@@ -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:
|
||||
|
||||
+510
-1
@@ -1,3 +1,455 @@
|
||||
<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.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:**
|
||||
- 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.4.6"></a>
|
||||
# 1.4.6 multiplicative-elevation (2015-09-17)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** invalid CSS class names should not break subsequent elements
|
||||
([c3a654b7](https://github.com/angular/angular.js/commit/c3a654b7c8e585b8fb9f90ece10ef54d19fd74c8),
|
||||
[#12674](https://github.com/angular/angular.js/issues/12674), [#12725](https://github.com/angular/angular.js/issues/12725))
|
||||
- **$browser:** handle async updates to location
|
||||
([8d39bd8a](https://github.com/angular/angular.js/commit/8d39bd8abf423517b5bff70137c2a29e32bff76d),
|
||||
[#12241](https://github.com/angular/angular.js/issues/12241), [#12819](https://github.com/angular/angular.js/issues/12819))
|
||||
- **$http:** propagate status -1 for timed out requests
|
||||
([38520a1a](https://github.com/angular/angular.js/commit/38520a1a73fffb6cfeffc7edfcab5be33e1619eb),
|
||||
[#4491](https://github.com/angular/angular.js/issues/4491), [#8756](https://github.com/angular/angular.js/issues/8756))
|
||||
- **$httpBackend:** send `null` when post-data is undefined
|
||||
([6f39f108](https://github.com/angular/angular.js/commit/6f39f1082773921e79b48a78aa6cd8a7d1921da7),
|
||||
[#12141](https://github.com/angular/angular.js/issues/12141), [#12739](https://github.com/angular/angular.js/issues/12739))
|
||||
- **$parse:**
|
||||
- throw error when accessing a restricted property indirectly
|
||||
([b2f8b0b8](https://github.com/angular/angular.js/commit/b2f8b0b875dbabf7bba0ba6e9bd553c7a8b910d0),
|
||||
[#12833](https://github.com/angular/angular.js/issues/12833))
|
||||
- `assign` returns the new value
|
||||
([7d2c6eee](https://github.com/angular/angular.js/commit/7d2c6eeef8ad61690737b6298c94f066082eff58),
|
||||
[#12675](https://github.com/angular/angular.js/issues/12675), [#12708](https://github.com/angular/angular.js/issues/12708))
|
||||
- **angular.copy:** support copying XML nodes
|
||||
([122ab074](https://github.com/angular/angular.js/commit/122ab074cac6401ecded51fa031af139360f40aa),
|
||||
[#5429](https://github.com/angular/angular.js/issues/5429), [#12786](https://github.com/angular/angular.js/issues/12786))
|
||||
- **form, ngModel:** correctly notify parent form when children are added
|
||||
([c6110e8b](https://github.com/angular/angular.js/commit/c6110e8b08c7e9bb2b7da5ecc5c42d1a834ea92d))
|
||||
- **input:** ignore min/max if they are empty on all input types
|
||||
([544001f5](https://github.com/angular/angular.js/commit/544001f5a331de06961c0201d69ecc92893abd0b),
|
||||
[#12363](https://github.com/angular/angular.js/issues/12363), [#12785](https://github.com/angular/angular.js/issues/12785))
|
||||
- **ngAnimateMock:** $animate.flush should work for looping animations
|
||||
([472d076c](https://github.com/angular/angular.js/commit/472d076cca2ffb99bd87d3c026ef69afc713268d))
|
||||
- **ngAria:** clean up tabindex usage
|
||||
([f48244ce](https://github.com/angular/angular.js/commit/f48244ce5e6d11637aab97af1aff3430bda12429),
|
||||
[#11500](https://github.com/angular/angular.js/issues/11500))
|
||||
- **ngJq:** properly detect when `ng-jq` is empty
|
||||
([19ecdb54](https://github.com/angular/angular.js/commit/19ecdb54bf85fc4e7bd3cde453aa6843f869a1ab),
|
||||
[#12741](https://github.com/angular/angular.js/issues/12741))
|
||||
- **ngModel:**
|
||||
- remove reference to parentForm from removed control
|
||||
([290b5049](https://github.com/angular/angular.js/commit/290b5049c2de4aa0d6ba8eea624bc6dce027b197),
|
||||
[#12263](https://github.com/angular/angular.js/issues/12263))
|
||||
- let aliased validator directives work on any element
|
||||
([43769fb6](https://github.com/angular/angular.js/commit/43769fb676ae904852582a2c88a5523f0b9f58fc),
|
||||
[#12158](https://github.com/angular/angular.js/issues/12158), [#12658](https://github.com/angular/angular.js/issues/12658))
|
||||
- **ngRepeat:** add support to iterate an object's properties even if it does not inherit from Object
|
||||
([7ea2c7f3](https://github.com/angular/angular.js/commit/7ea2c7f36ef854391df3f6b127ad42a2d5cbf1a3),
|
||||
[#9964](https://github.com/angular/angular.js/issues/9964))
|
||||
- **rootScope:** add support for watchCollection to watch an object which does not inherit from Object
|
||||
([20fb626b](https://github.com/angular/angular.js/commit/20fb626b78ed8fbd02f59f5b26df9387a2a6ea0e),
|
||||
[#9964](https://github.com/angular/angular.js/issues/9964))
|
||||
- **select:** update option if interpolated value attribute changes
|
||||
([82b0929e](https://github.com/angular/angular.js/commit/82b0929e4ea0ae087f766f2ee26f9570c8a3c8ac),
|
||||
[#12005](https://github.com/angular/angular.js/issues/12005), [#12582](https://github.com/angular/angular.js/issues/12582))
|
||||
- **toDebugString:** change replacement string
|
||||
([0ca8b1df](https://github.com/angular/angular.js/commit/0ca8b1df201044019596db7173d784aeebdea0a7),
|
||||
[#10103](https://github.com/angular/angular.js/issues/10103))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **Angular:** only create new collection in getBlockNodes if the block has changed
|
||||
([0202663e](https://github.com/angular/angular.js/commit/0202663e938a477cd86145bb158bf7a02efd8fb5),
|
||||
[#9899](https://github.com/angular/angular.js/issues/9899))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.19"></a>
|
||||
# 1.3.19 glutinous-shriek (2015-09-15)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:** propagate status -1 for timed out requests
|
||||
([f13055a0](https://github.com/angular/angular.js/commit/f13055a0a53a39b160448713a5617edee6042801),
|
||||
[#4491](https://github.com/angular/angular.js/issues/4491), [#8756](https://github.com/angular/angular.js/issues/8756))
|
||||
- **$location:** don't crash if navigating outside the app base
|
||||
([623ce1ad](https://github.com/angular/angular.js/commit/623ce1ad2cf68024719c5cae5d682d00195df30c),
|
||||
[#11667](https://github.com/angular/angular.js/issues/11667))
|
||||
- **$parse:** throw error when accessing a restricted property indirectly
|
||||
([ec98c94c](https://github.com/angular/angular.js/commit/ec98c94ccbfc97b655447956738d5f6ff98b2f33),
|
||||
[#12833](https://github.com/angular/angular.js/issues/12833))
|
||||
- **ngModel:** validate pattern against the viewValue
|
||||
([274e9353](https://github.com/angular/angular.js/commit/274e93537ed4e95aefeacea48909eb334894f0ac),
|
||||
[#12344](https://github.com/angular/angular.js/issues/12344))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngAnimate:** introduce `$animate.flush` for unit testing
|
||||
([f98e0384](https://github.com/angular/angular.js/commit/f98e038418f7367b2373adcf4887f64a8e8bdcb0))
|
||||
|
||||
|
||||
## Possible Breaking Changes
|
||||
|
||||
- **ngModel:** due to [274e9353](https://github.com/angular/angular.js/commit/274e93537ed4e95aefeacea48909eb334894f0ac),
|
||||
|
||||
|
||||
The `ngPattern` and `pattern` directives will validate the regex
|
||||
against the `viewValue` of `ngModel`, i.e. the value of the model
|
||||
before the $parsers are applied. Previously, the modelValue
|
||||
(the result of the $parsers) was validated.
|
||||
|
||||
This fixes issues where `input[date]` and `input[number]` cannot
|
||||
be validated because the viewValue string is parsed into
|
||||
`Date` and `Number` respectively (starting with Angular 1.3).
|
||||
It also brings the directives in line with HTML5 constraint
|
||||
validation, which validates against the input value.
|
||||
|
||||
This change is unlikely to cause applications to fail, because even
|
||||
in Angular 1.2, the value that was validated by pattern could have
|
||||
been manipulated by the $parsers, as all validation was done
|
||||
inside this pipeline.
|
||||
|
||||
If you rely on the pattern being validated against the modelValue,
|
||||
you must create your own validator directive that overwrites
|
||||
the built-in pattern validator:
|
||||
|
||||
```
|
||||
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
priority: 1,
|
||||
compile: function() {
|
||||
var regexp, patternExp;
|
||||
|
||||
return {
|
||||
pre: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
attr.$observe('pattern', function(regex) {
|
||||
/**
|
||||
* The built-in directive will call our overwritten validator
|
||||
* (see below). We just need to update the regex.
|
||||
* The preLink fn guaranetees our observer is called first.
|
||||
*/
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
//The built-in validator will throw at this point
|
||||
return;
|
||||
}
|
||||
|
||||
regexp = regex || undefined;
|
||||
});
|
||||
|
||||
},
|
||||
post: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
|
||||
//The postLink fn guarantees we overwrite the built-in pattern validator
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) ||
|
||||
isUndefined(regexp) ||
|
||||
regexp.test(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.5"></a>
|
||||
# 1.4.5 permanent-internship (2015-08-28)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** `$animate.enabled(false)` should disable animations on $animateCss as well
|
||||
([c3d5e33e](https://github.com/angular/angular.js/commit/c3d5e33e18bd9e423e2d0678e85564fad1dba99f),
|
||||
[#12696](https://github.com/angular/angular.js/issues/12696), [#12685](https://github.com/angular/angular.js/issues/12685))
|
||||
- **$animateCss:**
|
||||
- do not throw errors when a closing timeout is fired on a removed element
|
||||
([2f6b6fb7](https://github.com/angular/angular.js/commit/2f6b6fb7a1dee0ff97c5d2959b927347eeda6e8b),
|
||||
[#12650](https://github.com/angular/angular.js/issues/12650))
|
||||
- fix parse errors on older Android WebViews
|
||||
([1cc9c9ca](https://github.com/angular/angular.js/commit/1cc9c9ca9d9698356ea541517b3d06ce6556c01d),
|
||||
[#12610](https://github.com/angular/angular.js/issues/12610))
|
||||
- properly handle cancellation timeouts for follow-up animations
|
||||
([d8816731](https://github.com/angular/angular.js/commit/d88167318d1c69f0dbd2101c05955eb450c34fd5),
|
||||
[#12490](https://github.com/angular/angular.js/issues/12490), [#12359](https://github.com/angular/angular.js/issues/12359))
|
||||
- ensure failed animations clear the internal cache
|
||||
([0a75a3db](https://github.com/angular/angular.js/commit/0a75a3db6ef265389c8c955981c2fe67bb4f7769),
|
||||
[#12214](https://github.com/angular/angular.js/issues/12214), [#12518](https://github.com/angular/angular.js/issues/12518), [#12381](https://github.com/angular/angular.js/issues/12381))
|
||||
- the transitions options delay value should be applied before class application
|
||||
([0c81e9fd](https://github.com/angular/angular.js/commit/0c81e9fd25285dd757db98d458919776a1fb62fc),
|
||||
[#12584](https://github.com/angular/angular.js/issues/12584))
|
||||
- **ngAnimate:**
|
||||
- use requestAnimationFrame to space out child animations
|
||||
([ea8016c4](https://github.com/angular/angular.js/commit/ea8016c4c8f55bc021549f342618ed869998e335),
|
||||
[#12669](https://github.com/angular/angular.js/issues/12669), [#12594](https://github.com/angular/angular.js/issues/12594), [#12655](https://github.com/angular/angular.js/issues/12655), [#12631](https://github.com/angular/angular.js/issues/12631), [#12612](https://github.com/angular/angular.js/issues/12612), [#12187](https://github.com/angular/angular.js/issues/12187))
|
||||
- only buffer rAF requests within the animation runners
|
||||
([dc48aadd](https://github.com/angular/angular.js/commit/dc48aadd26bbf1797c1c408f63ffde99d67414a9),
|
||||
[#12280](https://github.com/angular/angular.js/issues/12280))
|
||||
- **ngModel:** validate pattern against the viewValue
|
||||
([0e001084](https://github.com/angular/angular.js/commit/0e001084ffff8674efad289d37cb16cc4e46b50a),
|
||||
[#12344](https://github.com/angular/angular.js/issues/12344))
|
||||
- **ngResources:** support IPv6 URLs
|
||||
([b643f0d3](https://github.com/angular/angular.js/commit/b643f0d3223a627ef813f0777524e25d2dd95371),
|
||||
[#12512](https://github.com/angular/angular.js/issues/12512), [#12532](https://github.com/angular/angular.js/issues/12532))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngModel:** due to [0e001084](https://github.com/angular/angular.js/commit/0e001084ffff8674efad289d37cb16cc4e46b50a),
|
||||
|
||||
|
||||
The `ngPattern` and `pattern` directives will validate the regex
|
||||
against the `viewValue` of `ngModel`, i.e. the value of the model
|
||||
before the $parsers are applied. Previously, the modelValue
|
||||
(the result of the $parsers) was validated.
|
||||
|
||||
This fixes issues where `input[date]` and `input[number]` cannot
|
||||
be validated because the viewValue string is parsed into
|
||||
`Date` and `Number` respectively (starting with Angular 1.3).
|
||||
It also brings the directives in line with HTML5 constraint
|
||||
validation, which validates against the input value.
|
||||
|
||||
This change is unlikely to cause applications to fail, because even
|
||||
in Angular 1.2, the value that was validated by pattern could have
|
||||
been manipulated by the $parsers, as all validation was done
|
||||
inside this pipeline.
|
||||
|
||||
If you rely on the pattern being validated against the modelValue,
|
||||
you must create your own validator directive that overwrites
|
||||
the built-in pattern validator:
|
||||
|
||||
```js
|
||||
.directive('patternModelOverwrite', function patternModelOverwriteDirective() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
priority: 1,
|
||||
compile: function() {
|
||||
var regexp, patternExp;
|
||||
|
||||
return {
|
||||
pre: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
attr.$observe('pattern', function(regex) {
|
||||
/**
|
||||
* The built-in directive will call our overwritten validator
|
||||
* (see below). We just need to update the regex.
|
||||
* The preLink fn guaranetees our observer is called first.
|
||||
*/
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
//The built-in validator will throw at this point
|
||||
return;
|
||||
}
|
||||
|
||||
regexp = regex || undefined;
|
||||
});
|
||||
|
||||
},
|
||||
post: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
|
||||
//The postLink fn guarantees we overwrite the built-in pattern validator
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) ||
|
||||
isUndefined(regexp) ||
|
||||
regexp.test(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.18"></a>
|
||||
# 1.3.18 collective-penmanship (2015-08-18)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:**
|
||||
- clear class animations cache if animation is not started
|
||||
([2c03a357](https://github.com/angular/angular.js/commit/2c03a3574336ed814d020cf7ba36cee5b87e65b5),
|
||||
[#12604](https://github.com/angular/angular.js/issues/12604), [#12603](https://github.com/angular/angular.js/issues/12603))
|
||||
- do not throw errors if element is removed before animation starts
|
||||
([6b72598b](https://github.com/angular/angular.js/commit/6b72598b87022e1dd96bddc4451e007ef0601579),
|
||||
[#10205](https://github.com/angular/angular.js/issues/10205))
|
||||
- **ngModel:** correct minErr usage for correct doc creation
|
||||
([64a142b5](https://github.com/angular/angular.js/commit/64a142b58ed0a0e3896d82f3f9ce35373548d0ff),
|
||||
[#12386](https://github.com/angular/angular.js/issues/12386), [#12416](https://github.com/angular/angular.js/issues/12416))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.4"></a>
|
||||
# 1.4.4 pylon-requirement (2015-08-13)
|
||||
|
||||
@@ -122,6 +574,38 @@ and/or `beforeRemoveClass` then the CSS classes will not be applied
|
||||
in time for the children (and the parent class-based animation will not
|
||||
be cancelled by any child animations).
|
||||
|
||||
- **$q** due to [6838c979](https://github.com/angular/angular.js/commit/6838c979451c109d959a15035177ccee715ccf19),
|
||||
When writing tests, there is no need to call `$timeout.flush()` to resolve a call to `$q.when` with a value.
|
||||
|
||||
The previous behavior involved creating an extra promise that needed to be resolved. This is no longer needed when
|
||||
`$q.when` is called with a value. In the case that the test is not aware if `$q.when` is called with a value or
|
||||
another promise, it is possible to replace `$timeout.flush();` with `$timeout.flush(0);`.
|
||||
|
||||
```js
|
||||
describe('$q.when', function() {
|
||||
it('should not need a call to $timeout.flush() to resolve already resolved promises',
|
||||
inject(function($q, $timeout) {
|
||||
$q.when('foo');
|
||||
// In Angular 1.4.3 a call to `$timeout.flush();` was needed
|
||||
$timeout.verifyNoPendingTasks();
|
||||
}));
|
||||
|
||||
it('should accept $timeout.flush(0) when not sure if $q.when was called with a value or a promise',
|
||||
inject(function($q, $timeout) {
|
||||
$q.when('foo');
|
||||
$timeout.flush(0);
|
||||
$timeout.verifyNoPendingTasks();
|
||||
}));
|
||||
|
||||
it('should need a call to $timeout.flush() to resolve $q.when when called with a promise',
|
||||
inject(function($q, $timeout) {
|
||||
$q.when($q.when('foo'));
|
||||
$timeout.flush();
|
||||
$timeout.verifyNoPendingTasks();
|
||||
}));
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.4.3"></a>
|
||||
# 1.4.3 foam-acceleration (2015-07-15)
|
||||
@@ -999,7 +1483,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
|
||||
@@ -1022,6 +1505,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.
|
||||
@@ -1477,6 +1980,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`
|
||||
|
||||
+3
-2
@@ -19,7 +19,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
If you have questions about how to use AngularJS, please direct these to the [Google Group][groups]
|
||||
discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC][irc].
|
||||
discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC][irc] and [Gitter][gitter].
|
||||
|
||||
## <a name="issue"></a> Found an Issue?
|
||||
If you find a bug in the source code or a mistake in the documentation, you can help us by
|
||||
@@ -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)
|
||||
@@ -268,6 +268,7 @@ You can find out more detailed information about contributing in the
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[dev-doc]: https://docs.angularjs.org/guide
|
||||
[github]: https://github.com/angular/angular.js
|
||||
[gitter]: https://gitter.im/angular/angular.js
|
||||
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
|
||||
|
||||
+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
+1
-1
@@ -92,7 +92,7 @@ var angularFiles = {
|
||||
'angularModules': {
|
||||
'ngAnimate': [
|
||||
'src/ngAnimate/shared.js',
|
||||
'src/ngAnimate/body.js',
|
||||
'src/ngAnimate/rafScheduler.js',
|
||||
'src/ngAnimate/animateChildrenDirective.js',
|
||||
'src/ngAnimate/animateCss.js',
|
||||
'src/ngAnimate/animateCssDriver.js',
|
||||
|
||||
@@ -44,7 +44,7 @@ var noArgs = function (fn) {
|
||||
|
||||
var identity = function (i) { return i; };
|
||||
|
||||
// like Q.all, but runs the comands in series
|
||||
// like Q.all, but runs the commands in series
|
||||
// useful for ensuring env state (like which branch is checked out)
|
||||
var allInSeries = function (fn) {
|
||||
return function (args) {
|
||||
|
||||
@@ -56,7 +56,7 @@ li.doc-example-live {
|
||||
}
|
||||
|
||||
div.syntaxhighlighter {
|
||||
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
|
||||
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars */
|
||||
}
|
||||
|
||||
/* TABS - tutorial environment navigation */
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<li class="disabled"><a href="http://angularjs.org/">Why AngularJS?</a></li>
|
||||
<li><a href="http://www.youtube.com/user/angularjs">Watch</a></li>
|
||||
<li><a href="tutorial">Tutorial</a></li>
|
||||
<li><a href="http://builtwith.angularjs.org/">Case Studies</a></li>
|
||||
<li><a href="https://www.madewithangular.com/">Case Studies</a></li>
|
||||
<li><a href="https://github.com/angular/angular-seed">Seed App project template</a></li>
|
||||
<li><a href="misc/faq">FAQ</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ it('should show example', inject(
|
||||
### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hasbang
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hashbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -23,9 +23,9 @@ angular.module('myApp', ['ngAria'])...
|
||||
###Using ngAria
|
||||
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
|
||||
added it as a dependency, you can test a few things:
|
||||
* Using your favorite element inspector, look for ngAria attributes in your own code.
|
||||
* Using your favorite element inspector, look for attributes added by ngAria in your own code.
|
||||
* Test using your keyboard to ensure `tabindex` is used correctly.
|
||||
* Fire up a screen reader such as VoiceOver to listen for ARIA support.
|
||||
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
|
||||
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
|
||||
|
||||
##Supported directives
|
||||
@@ -41,8 +41,8 @@ Currently, ngAria interfaces with the following directives:
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Most of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
Much of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
For those elements using ngModel, ngAria will dynamically bind and update the following ARIA
|
||||
@@ -134,10 +134,8 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
|
||||
operable** from the keybard. Think of `ng-click` on a `<div>` or `<md-checkbox>`: you still need
|
||||
to bind `ng-keypress` to make it fully operable from the keyboard. As a rule, any time you create
|
||||
a widget involving user interaction, be sure to test it with your keyboard and at least one mobile
|
||||
and desktop screen reader (preferably more).
|
||||
accessible**. As a rule, any time you create a widget involving user interaction, be sure to test
|
||||
it with your keyboard and at least one mobile and desktop screen reader.
|
||||
|
||||
<h2 id="ngdisabled">ngDisabled</h2>
|
||||
|
||||
@@ -160,7 +158,7 @@ Becomes:
|
||||
```
|
||||
|
||||
>You can check whether a control is legitimately disabled for a screen reader by visiting
|
||||
[chrome://accessibility](chrome://accessibility).
|
||||
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
|
||||
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
@@ -210,16 +208,25 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
|
||||
already.
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` to any element not in
|
||||
a node blacklist:
|
||||
|
||||
To fix widespread accessibility problems with `ng-click` on div elements, ngAria will dynamically
|
||||
bind keypress by default as long as the element isn't an anchor, button, input or textarea.
|
||||
You can turn this functionality on or off with the `bindKeypress` configuration option. ngAria
|
||||
will also add the `button` role to communicate to users of assistive technologies.
|
||||
* Button
|
||||
* Anchor
|
||||
* Input
|
||||
* Textarea
|
||||
* Select
|
||||
* Details/Summary
|
||||
|
||||
For `ng-dblclick`, you must still manually add `ng-keypress` and role to non-interactive elements such
|
||||
as `div` or `taco-button` to enable keyboard access.
|
||||
To fix widespread accessibility problems with `ng-click` on `div` elements, ngAria will
|
||||
dynamically bind a keypress event by default as long as the element isn't in the node blacklist.
|
||||
You can turn this functionality on or off with the `bindKeypress` configuration option.
|
||||
|
||||
ngAria will also add the `button` role to communicate to users of assistive technologies. This can
|
||||
be disabled with the `bindRoleForClick` configuration option.
|
||||
|
||||
For `ng-dblclick`, you must still manually add `ng-keypress` and a role to non-interactive elements
|
||||
such as `div` or `taco-button` to enable keyboard access.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
@@ -260,62 +267,18 @@ The attribute magic of ngAria may not work for every scenario. To disable indivi
|
||||
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
|
||||
tell ngAria to ignore the attribute globally.
|
||||
|
||||
<example module="ngAria_ngDisabledExample" deps="angular-aria.js">
|
||||
<example module="ngAria_ngClickExample" deps="angular-aria.js">
|
||||
<file name="index.html">
|
||||
<style>
|
||||
[role=checkbox] {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
[role=checkbox] .icon:before {
|
||||
content: '\2610';
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
speak: none;
|
||||
}
|
||||
[role=checkbox].active .icon:before {
|
||||
content: '\2611';
|
||||
}
|
||||
</style>
|
||||
<form ng-controller="formsController">
|
||||
<div ng-model="someModel" show-attrs>
|
||||
Div with ngModel and aria-invalid disabled
|
||||
<div ng-click="someFunction" show-attrs>
|
||||
<div> with ng-click and bindRoleForClick, tabindex set to false
|
||||
</div>
|
||||
<div role="checkbox" ng-model="checked" ng-class="{active: checked}"
|
||||
aria-label="Custom Checkbox" ng-click="toggleCheckbox()" some-checkbox show-attrs>
|
||||
<span class="icon" aria-hidden="true"></span>
|
||||
Custom Checkbox for comparison
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
angular.module('ngAria_ngDisabledExample', ['ngAria'], function config($ariaProvider) {
|
||||
angular.module('ngAria_ngClickExample', ['ngAria'], function config($ariaProvider) {
|
||||
$ariaProvider.config({
|
||||
ariaInvalid: false,
|
||||
tabindex: true
|
||||
bindRoleForClick: false,
|
||||
tabindex: false
|
||||
});
|
||||
})
|
||||
.controller('formsController', function($scope){
|
||||
$scope.checked = false;
|
||||
$scope.toggleCheckbox = function(){
|
||||
$scope.checked = !$scope.checked;
|
||||
}
|
||||
})
|
||||
.directive('someCheckbox', function(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $el, $attrs) {
|
||||
$el.on('keypress', function(event){
|
||||
event.preventDefault();
|
||||
if(event.keyCode === 32 || event.keyCode === 13){
|
||||
$scope.toggleCheckbox();
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.directive('showAttrs', function() {
|
||||
return function(scope, el, attrs) {
|
||||
var pre = document.createElement('pre');
|
||||
|
||||
@@ -8,22 +8,22 @@
|
||||
This section briefly touches on all of the important parts of AngularJS using a simple example.
|
||||
For a more in-depth explanation, see the {@link tutorial/ tutorial}.
|
||||
|
||||
| Concept | Description |
|
||||
|------------------|------------------------------------------|
|
||||
|{@link concepts#template Template} | HTML with additional markup |
|
||||
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
||||
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
||||
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
||||
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
||||
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|
||||
|{@link concepts#view View} | what the user sees (the DOM) |
|
||||
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|
||||
|{@link concepts#controller Controller} | the business logic behind views |
|
||||
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|
||||
|{@link concepts#injector Injector} | dependency injection container |
|
||||
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|
||||
|{@link concepts#service Service} | reusable business logic independent of views |
|
||||
| Concept | Description |
|
||||
|--------------------------------------------|--------------------------------------------------------------------------|
|
||||
|{@link concepts#template Template} | HTML with additional markup |
|
||||
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|
||||
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|
||||
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|
||||
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|
||||
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
|
||||
|{@link concepts#filter Filter} | formats the value of an expression for display to the user |
|
||||
|{@link concepts#view View} | what the user sees (the DOM) |
|
||||
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|
||||
|{@link concepts#controller Controller} | the business logic behind views |
|
||||
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|
||||
|{@link concepts#injector Injector} | dependency injection container |
|
||||
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|
||||
|{@link concepts#service Service} | reusable business logic independent of views |
|
||||
|
||||
|
||||
## A first example: Data binding
|
||||
@@ -348,8 +348,7 @@ The following example shows how this is done with Angular:
|
||||
|
||||
return {
|
||||
currencies: currencies,
|
||||
convert: convert,
|
||||
refresh: refresh
|
||||
convert: convert
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
@@ -19,8 +19,9 @@ how to implement them.
|
||||
## What are Directives?
|
||||
|
||||
At a high level, directives are markers on a DOM element (such as an attribute, element
|
||||
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
|
||||
attach a specified behavior to that DOM element or even transform the DOM element and its children.
|
||||
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`})
|
||||
to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform
|
||||
the DOM element and its children.
|
||||
|
||||
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngClass`.
|
||||
Much like you create controllers and services, you can create your own directives for Angular to use.
|
||||
@@ -30,7 +31,7 @@ When Angular {@link guide/bootstrap bootstraps} your application, the
|
||||
<div class="alert alert-info">
|
||||
**What does it mean to "compile" an HTML template?**
|
||||
|
||||
For AngularJS, "compilation" means attaching event listeners to the HTML to make it interactive.
|
||||
For AngularJS, "compilation" means attaching directives to the HTML to make it interactive.
|
||||
The reason we use the term "compile" is that the recursive process of attaching directives
|
||||
mirrors the process of compiling source code in
|
||||
[compiled programming languages](http://en.wikipedia.org/wiki/Compiled_languages).
|
||||
@@ -42,18 +43,27 @@ mirrors the process of compiling source code in
|
||||
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
|
||||
determines when to use a given directive.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive.
|
||||
Similar to the terminology used when an [element **matches** a selector](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
directive when the directive is part of its declaration.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
|
||||
|
||||
```html
|
||||
<input ng-model="foo">
|
||||
```
|
||||
|
||||
The following also **matches** `ngModel`:
|
||||
The following `<input>` element also **matches** `ngModel`:
|
||||
|
||||
```html
|
||||
<input data-ng-model="foo">
|
||||
```
|
||||
|
||||
And the following <person> element **matches** the `person` directive:
|
||||
|
||||
```html
|
||||
<person>{{name}}</person>
|
||||
```
|
||||
|
||||
### Normalization
|
||||
|
||||
Angular **normalizes** an element's tag and attribute name to determine which elements match which
|
||||
@@ -577,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
|
||||
@@ -892,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) {
|
||||
@@ -908,7 +928,7 @@ to which tab is active.
|
||||
}
|
||||
panes.push(pane);
|
||||
};
|
||||
},
|
||||
}],
|
||||
templateUrl: 'my-tabs.html'
|
||||
};
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ for other directives to augment its behavior.
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>user = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ and failing to satisfy its validity.
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>user = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
</div>
|
||||
|
||||
@@ -185,7 +185,7 @@ didn't interact with a control
|
||||
<input type="button" ng-click="reset(form)" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>user = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
</div>
|
||||
</file>
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -156,8 +156,94 @@ The syntax extension is based on a subset of the ICU MessageFormat syntax that c
|
||||
gender selections. Please refer to the links in the “Further Reading” section at the bottom of this
|
||||
section.
|
||||
|
||||
You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview)
|
||||
as you read the examples below.
|
||||
You may find it helpful to play with the following example as you read the explanations below:
|
||||
|
||||
<example name="message-format-example" module="messageFormatExample" deps="angular-message-format.js">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ckCtrl">
|
||||
<b>Set number of recipients</b>
|
||||
<button ng-click="setNumRecipients(0)">None</button>
|
||||
<button ng-click="setNumRecipients(1)">One</button>
|
||||
<button ng-click="setNumRecipients(2)">Two</button>
|
||||
<button ng-click="setNumRecipients(3)">Three</button>
|
||||
|
||||
|
||||
<br><br>
|
||||
<b>Sender's</b> name: <input ng-model="sender.name">
|
||||
|
||||
<br><br><b>Recipients</b><br>
|
||||
<div ng-repeat="recipient in recipients">
|
||||
Name: <input ng-model="recipient.name">
|
||||
Gender: <button ng-click="setGender(recipient, 'male')">male</button>
|
||||
<button ng-click="setGender(recipient, 'female')">female</button>
|
||||
<button ng-click="setGender(recipient, 'other')">other</button>
|
||||
</div>
|
||||
|
||||
<br><br><b>Message</b><br>
|
||||
{{recipients.length, plural, offset:1
|
||||
=0 {You ({{sender.name}}) gave no gifts}
|
||||
=1 { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
||||
}}
|
||||
}
|
||||
one { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
||||
}}
|
||||
}
|
||||
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
||||
}}
|
||||
|
||||
<br><br><b>In an attribute</b><br>
|
||||
<div attrib="{{recipients.length, plural, offset:1
|
||||
=0 {You ({{sender.name}}) gave no gifts}
|
||||
=1 { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
|
||||
}}
|
||||
}
|
||||
one { {{ recipients[0].gender, select,
|
||||
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
|
||||
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
|
||||
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
|
||||
}}
|
||||
}
|
||||
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
|
||||
}}">
|
||||
This div has an attribute interpolated with messageformat. Use the DOM inspector to check it out.
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="app.js">
|
||||
function Person(name, gender) {
|
||||
this.name = name;
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
angular.module('messageFormatExample', ['ngMessageFormat'])
|
||||
.controller('ckCtrl', function ($scope, $injector, $parse) {
|
||||
var people = [ new Person("Alice", "female"),
|
||||
new Person("Bob", "male"),
|
||||
new Person("Charlie", "male") ];
|
||||
|
||||
$scope.sender = new Person("Harry Potter", "male");
|
||||
$scope.recipients = people.slice();
|
||||
|
||||
$scope.setNumRecipients = function(n) {
|
||||
n = n > people.length ? people.length : n;
|
||||
$scope.recipients = people.slice(0, n);
|
||||
};
|
||||
|
||||
$scope.setGender = function(person, gender) {
|
||||
person.gender = gender;
|
||||
};
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
### Plural Syntax
|
||||
|
||||
@@ -333,9 +419,9 @@ allows you to nest plural and gender expressions in any order.
|
||||
Please note that if these are intended to reach a translator and be translated, it is recommended
|
||||
that the messages appear as a whole and not be split up.
|
||||
|
||||
### More complex example that demonstrates nesting
|
||||
### Demonstration of nesting
|
||||
|
||||
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
|
||||
This is taken from the above example.
|
||||
|
||||
```text
|
||||
{{recipients.length, plural, offset:1
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ development.
|
||||
production.
|
||||
|
||||
To point your code to an angular script on the Google CDN server, use the following template. This
|
||||
example points to the minified version 1.3.14:
|
||||
example points to the minified version 1.4.5:
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -86,9 +86,15 @@ Yes. See instructions in {@link downloading}.
|
||||
|
||||
### What browsers does Angular work with?
|
||||
|
||||
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera 15,
|
||||
IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari). See {@link guide/ie Internet
|
||||
Explorer Compatibility} for more details on supporting legacy IE browsers.
|
||||
We run our extensive test suite against the following browsers: the latest versions of Chrome,
|
||||
Firefox, Safari, and Safari for iOs, as well as Internet Explorer versions 9-11. See {@link guide/ie
|
||||
Internet Explorer Compatibility} for more details on supporting legacy IE browsers.
|
||||
|
||||
If a browser is untested, it doesn't mean it won't work; for example, older Android (2.3.x)
|
||||
is supported in the sense that we avoid the dot notation for reserved words as property names,
|
||||
but we don't actively test changes against it. You can also expect browsers to work that share
|
||||
a large part of their codebase with a browser we test, such as Opera > version 12
|
||||
(uses the Blink engine), or the various Firefox derivatives.
|
||||
|
||||
|
||||
### What's Angular's performance like?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)`.
|
||||
|
||||
@@ -75,7 +75,7 @@ __`test/e2e/scenarios.js`__:
|
||||
query.sendKeys('nexus');
|
||||
element.all(by.css('.phones li a')).first().click();
|
||||
browser.getLocationAbsUrl().then(function(url) {
|
||||
expect(url.split('#')[1]).toBe('/phones/nexus-s');
|
||||
expect(url).toBe('/phones/nexus-s');
|
||||
});
|
||||
});
|
||||
...
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -173,7 +173,7 @@ function request(method, url, options, response) {
|
||||
res.on('error', function (e) { console.log(e); });
|
||||
break;
|
||||
case 401:
|
||||
console.log('Eror: Login credentials expired! Please login.');
|
||||
console.log('Error: Login credentials expired! Please login.');
|
||||
break;
|
||||
default:
|
||||
data = [];
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Browserstack tunnel"
|
||||
echo "TODO: implement me"
|
||||
exit 1
|
||||
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"
|
||||
+3137
-12
File diff suppressed because it is too large
Load Diff
Generated
+4843
-31
File diff suppressed because it is too large
Load Diff
+15
-2
@@ -3,6 +3,7 @@
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.4.0-beta.0",
|
||||
"branchPattern": "1.4.*",
|
||||
"distTag": "latest",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -12,6 +13,11 @@
|
||||
"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",
|
||||
@@ -19,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",
|
||||
@@ -75,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,6 +14,8 @@ function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
BUILD_DIR=$(resolveDir ../../build)
|
||||
NEW_VERSION=$(cat $BUILD_DIR/version.txt)
|
||||
# get the npm dist-tag from a custom property (distTag) in package.json
|
||||
DIST_TAG=$(readJsonProp "package.json" "distTag")
|
||||
}
|
||||
|
||||
|
||||
@@ -95,19 +97,8 @@ function publish {
|
||||
|
||||
# don't publish every build to npm
|
||||
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
|
||||
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
|
||||
if [[ $NEW_VERSION =~ ^1\.2\.[0-9]+$ ]] ; then
|
||||
# publish 1.2.x releases with the appropriate tag
|
||||
# this ensures that `npm install` by default will not grab `1.2.x` releases
|
||||
npm publish --tag=old
|
||||
else
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
fi
|
||||
else
|
||||
# publish prerelease builds with the beta tag
|
||||
npm publish --tag=beta
|
||||
fi
|
||||
echo "-- Publishing to npm as $DIST_TAG"
|
||||
npm publish --tag=$DIST_TAG
|
||||
fi
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
|
||||
@@ -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
|
||||
+129
-107
@@ -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,98 +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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -975,7 +996,7 @@ function equals(o1, o2) {
|
||||
for (key in o2) {
|
||||
if (!(key in keySet) &&
|
||||
key.charAt(0) !== '$' &&
|
||||
o2[key] !== undefined &&
|
||||
isDefined(o2[key]) &&
|
||||
!isFunction(o2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1671,10 +1692,9 @@ function bindJQuery() {
|
||||
|
||||
// bind to jQuery if present;
|
||||
var jqName = jq();
|
||||
jQuery = window.jQuery; // use default jQuery.
|
||||
if (isDefined(jqName)) { // `ngJq` present
|
||||
jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`.
|
||||
}
|
||||
jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
|
||||
!jqName ? undefined : // use jqLite
|
||||
window[jqName]; // use jQuery specified by `ngJq`
|
||||
|
||||
// Use jQuery if it exists with proper functionality, otherwise default to us.
|
||||
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
|
||||
@@ -1779,22 +1799,24 @@ function getter(obj, path, bindFnToScope) {
|
||||
/**
|
||||
* Return the DOM siblings between the first and last node in the given array.
|
||||
* @param {Array} array like object
|
||||
* @returns {jqLite} jqLite collection containing the nodes
|
||||
* @returns {Array} the inputted object or a jqLite collection containing the nodes
|
||||
*/
|
||||
function getBlockNodes(nodes) {
|
||||
// TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
|
||||
// collection, otherwise update the original collection.
|
||||
// TODO(perf): update `nodes` instead of creating a new object?
|
||||
var node = nodes[0];
|
||||
var endNode = nodes[nodes.length - 1];
|
||||
var blockNodes = [node];
|
||||
var blockNodes;
|
||||
|
||||
do {
|
||||
node = node.nextSibling;
|
||||
if (!node) break;
|
||||
blockNodes.push(node);
|
||||
} while (node !== endNode);
|
||||
for (var i = 1; node !== endNode && (node = node.nextSibling); i++) {
|
||||
if (blockNodes || nodes[i] !== node) {
|
||||
if (!blockNodes) {
|
||||
blockNodes = jqLite(slice.call(nodes, 0, i));
|
||||
}
|
||||
blockNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return jqLite(blockNodes);
|
||||
return blockNodes || nodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
$HttpParamSerializerProvider,
|
||||
$HttpParamSerializerJQLikeProvider,
|
||||
$HttpBackendProvider,
|
||||
$xhrFactoryProvider,
|
||||
$LocationProvider,
|
||||
$LogProvider,
|
||||
$ParseProvider,
|
||||
@@ -98,8 +99,9 @@
|
||||
* @name angular.version
|
||||
* @module ng
|
||||
* @description
|
||||
* An object that contains information about the current AngularJS version. This object has the
|
||||
* following properties:
|
||||
* An object that contains information about the current AngularJS version.
|
||||
*
|
||||
* This object has the following properties:
|
||||
*
|
||||
* - `full` – `{string}` – Full version string, such as "0.9.18".
|
||||
* - `major` – `{number}` – Major version number, such as "0".
|
||||
@@ -229,6 +231,7 @@ function publishExternalAPI(angular) {
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
$httpBackend: $HttpBackendProvider,
|
||||
$xhrFactory: $xhrFactoryProvider,
|
||||
$location: $LocationProvider,
|
||||
$log: $LogProvider,
|
||||
$parse: $ParseProvider,
|
||||
|
||||
+70
-45
@@ -65,7 +65,7 @@
|
||||
* - [`html()`](http://api.jquery.com/html/)
|
||||
* - [`next()`](http://api.jquery.com/next/) - Does not support selectors
|
||||
* - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
|
||||
* - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
|
||||
* - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter
|
||||
* - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
|
||||
* - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
|
||||
* - [`prepend()`](http://api.jquery.com/prepend/)
|
||||
@@ -79,7 +79,7 @@
|
||||
* - [`text()`](http://api.jquery.com/text/)
|
||||
* - [`toggleClass()`](http://api.jquery.com/toggleClass/)
|
||||
* - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
|
||||
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
|
||||
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter
|
||||
* - [`val()`](http://api.jquery.com/val/)
|
||||
* - [`wrap()`](http://api.jquery.com/wrap/)
|
||||
*
|
||||
@@ -151,10 +151,10 @@ function camelCase(name) {
|
||||
replace(MOZ_HACK_REGEXP, 'Moz$1');
|
||||
}
|
||||
|
||||
var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
|
||||
var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
|
||||
var HTML_REGEXP = /<|&#?\w+;/;
|
||||
var TAG_NAME_REGEXP = /<([\w:]+)/;
|
||||
var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;
|
||||
var TAG_NAME_REGEXP = /<([\w:-]+)/;
|
||||
var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;
|
||||
|
||||
var wrapMap = {
|
||||
'option': [1, '<select multiple="multiple">', '</select>'],
|
||||
@@ -241,6 +241,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 +307,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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -450,7 +464,7 @@ function jqLiteInheritedData(element, name, value) {
|
||||
|
||||
while (element) {
|
||||
for (var i = 0, ii = names.length; i < ii; i++) {
|
||||
if ((value = jqLite.data(element, names[i])) !== undefined) return value;
|
||||
if (isDefined(value = jqLite.data(element, names[i]))) return value;
|
||||
}
|
||||
|
||||
// If dealing with a document fragment node with a host element, and no parent, use the host
|
||||
@@ -556,9 +570,8 @@ function getBooleanAttrName(element, name) {
|
||||
return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
|
||||
}
|
||||
|
||||
function getAliasedAttrName(element, name) {
|
||||
var nodeName = element.nodeName;
|
||||
return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
|
||||
function getAliasedAttrName(name) {
|
||||
return ALIASED_ATTR[name];
|
||||
}
|
||||
|
||||
forEach({
|
||||
@@ -695,7 +708,7 @@ forEach({
|
||||
// in a way that survives minification.
|
||||
// jqLiteEmpty takes no arguments but is a setter.
|
||||
if (fn !== jqLiteEmpty &&
|
||||
(((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
|
||||
(isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
|
||||
if (isObject(arg1)) {
|
||||
|
||||
// we are a write, but the object properties are the key/values
|
||||
@@ -716,7 +729,7 @@ forEach({
|
||||
// TODO: do we still need this?
|
||||
var value = fn.$dv;
|
||||
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
|
||||
var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
|
||||
var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
|
||||
for (var j = 0; j < jj; j++) {
|
||||
var nodeValue = fn(this[j], arg1, arg2);
|
||||
value = value ? value + nodeValue : nodeValue;
|
||||
@@ -765,6 +778,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);
|
||||
@@ -772,7 +788,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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -783,6 +799,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
|
||||
@@ -811,35 +843,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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+1
-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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
+56
-51
@@ -105,61 +105,66 @@ var $$CoreAnimateQueueProvider = function() {
|
||||
}
|
||||
};
|
||||
|
||||
function addRemoveClassesPostDigest(element, add, remove) {
|
||||
var classVal, data = postDigestQueue.get(element);
|
||||
|
||||
if (!data) {
|
||||
postDigestQueue.put(element, data = {});
|
||||
postDigestElements.push(element);
|
||||
}
|
||||
|
||||
var updateData = function(classes, value) {
|
||||
var changed = false;
|
||||
if (classes) {
|
||||
classes = isString(classes) ? classes.split(' ') :
|
||||
isArray(classes) ? classes : [];
|
||||
forEach(classes, function(className) {
|
||||
if (className) {
|
||||
changed = true;
|
||||
data[className] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return changed;
|
||||
};
|
||||
|
||||
var classesAdded = updateData(add, true);
|
||||
var classesRemoved = updateData(remove, false);
|
||||
if ((!classesAdded && !classesRemoved) || postDigestElements.length > 1) return;
|
||||
|
||||
$rootScope.$$postDigest(function() {
|
||||
forEach(postDigestElements, function(element) {
|
||||
var data = postDigestQueue.get(element);
|
||||
if (data) {
|
||||
var existing = splitClasses(element.attr('class'));
|
||||
var toAdd = '';
|
||||
var toRemove = '';
|
||||
forEach(data, function(status, className) {
|
||||
var hasClass = !!existing[className];
|
||||
if (status !== hasClass) {
|
||||
if (status) {
|
||||
toAdd += (toAdd.length ? ' ' : '') + className;
|
||||
} else {
|
||||
toRemove += (toRemove.length ? ' ' : '') + className;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
forEach(element, function(elm) {
|
||||
toAdd && jqLiteAddClass(elm, toAdd);
|
||||
toRemove && jqLiteRemoveClass(elm, toRemove);
|
||||
});
|
||||
postDigestQueue.remove(element);
|
||||
function updateData(data, classes, value) {
|
||||
var changed = false;
|
||||
if (classes) {
|
||||
classes = isString(classes) ? classes.split(' ') :
|
||||
isArray(classes) ? classes : [];
|
||||
forEach(classes, function(className) {
|
||||
if (className) {
|
||||
changed = true;
|
||||
data[className] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
postDigestElements.length = 0;
|
||||
function handleCSSClassChanges() {
|
||||
forEach(postDigestElements, function(element) {
|
||||
var data = postDigestQueue.get(element);
|
||||
if (data) {
|
||||
var existing = splitClasses(element.attr('class'));
|
||||
var toAdd = '';
|
||||
var toRemove = '';
|
||||
forEach(data, function(status, className) {
|
||||
var hasClass = !!existing[className];
|
||||
if (status !== hasClass) {
|
||||
if (status) {
|
||||
toAdd += (toAdd.length ? ' ' : '') + className;
|
||||
} else {
|
||||
toRemove += (toRemove.length ? ' ' : '') + className;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
forEach(element, function(elm) {
|
||||
toAdd && jqLiteAddClass(elm, toAdd);
|
||||
toRemove && jqLiteRemoveClass(elm, toRemove);
|
||||
});
|
||||
postDigestQueue.remove(element);
|
||||
}
|
||||
});
|
||||
postDigestElements.length = 0;
|
||||
}
|
||||
|
||||
|
||||
function addRemoveClassesPostDigest(element, add, remove) {
|
||||
var data = postDigestQueue.get(element) || {};
|
||||
|
||||
var classesAdded = updateData(data, add, true);
|
||||
var classesRemoved = updateData(data, remove, false);
|
||||
|
||||
if (classesAdded || classesRemoved) {
|
||||
|
||||
postDigestQueue.put(element, data);
|
||||
postDigestElements.push(element);
|
||||
|
||||
if (postDigestElements.length === 1) {
|
||||
$rootScope.$$postDigest(handleCSSClassChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
};
|
||||
@@ -280,7 +285,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`,
|
||||
|
||||
@@ -35,14 +35,21 @@ var $CoreAnimateCssProvider = function() {
|
||||
return this.getPromise().then(f1,f2);
|
||||
},
|
||||
'catch': function(f1) {
|
||||
return this.getPromise().catch(f1);
|
||||
return this.getPromise()['catch'](f1);
|
||||
},
|
||||
'finally': function(f1) {
|
||||
return this.getPromise().finally(f1);
|
||||
return this.getPromise()['finally'](f1);
|
||||
}
|
||||
};
|
||||
|
||||
return function(element, options) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (options.from) {
|
||||
element.css(options.from);
|
||||
options.from = null;
|
||||
|
||||
+11
-6
@@ -87,7 +87,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
var cachedState, lastHistoryState,
|
||||
lastBrowserUrl = location.href,
|
||||
baseElement = document.find('base'),
|
||||
reloadLocation = null;
|
||||
pendingLocation = null;
|
||||
|
||||
cacheState();
|
||||
lastHistoryState = cachedState;
|
||||
@@ -147,8 +147,8 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// Do the assignment again so that those two variables are referentially identical.
|
||||
lastHistoryState = cachedState;
|
||||
} else {
|
||||
if (!sameBase || reloadLocation) {
|
||||
reloadLocation = url;
|
||||
if (!sameBase || pendingLocation) {
|
||||
pendingLocation = url;
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
@@ -157,14 +157,18 @@ function Browser(window, document, $log, $sniffer) {
|
||||
} else {
|
||||
location.hash = getHash(url);
|
||||
}
|
||||
if (location.href !== url) {
|
||||
pendingLocation = url;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
// getter
|
||||
} else {
|
||||
// - reloadLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened.
|
||||
// - pendingLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
|
||||
// https://openradar.appspot.com/22186109).
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return reloadLocation || location.href.replace(/%27/g,"'");
|
||||
return pendingLocation || location.href.replace(/%27/g,"'");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -186,6 +190,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
urlChangeInit = false;
|
||||
|
||||
function cacheStateAndFireUrlChange() {
|
||||
pendingLocation = null;
|
||||
cacheState();
|
||||
fireUrlChange();
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@
|
||||
$scope.keys = [];
|
||||
$scope.cache = $cacheFactory('cacheId');
|
||||
$scope.put = function(key, value) {
|
||||
if ($scope.cache.get(key) === undefined) {
|
||||
if (angular.isUndefined($scope.cache.get(key))) {
|
||||
$scope.keys.push(key);
|
||||
}
|
||||
$scope.cache.put(key, value === undefined ? 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');
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
+118
-98
@@ -146,18 +146,24 @@
|
||||
* and other directives used in the directive's template will also be excluded from execution.
|
||||
*
|
||||
* #### `scope`
|
||||
* **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the
|
||||
* same element request a new scope, only one new scope is created. The new scope rule does not
|
||||
* apply for the root of the template since the root of the template always gets a new scope.
|
||||
* The scope property can be `true`, an object or a falsy value:
|
||||
*
|
||||
* **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from
|
||||
* normal scope in that it does not prototypically inherit from the parent scope. This is useful
|
||||
* when creating reusable components, which should not accidentally read or modify data in the
|
||||
* parent scope.
|
||||
* * **falsy:** No scope will be created for the directive. The directive will use its parent's scope.
|
||||
*
|
||||
* The 'isolate' scope takes an object hash which defines a set of local scope properties
|
||||
* derived from the parent scope. These local properties are useful for aliasing values for
|
||||
* templates. Locals definition is a hash of local scope property to its source:
|
||||
* * **`true`:** A new child scope that prototypically inherits from its parent will be created for
|
||||
* the directive's element. If multiple directives on the same element request a new scope,
|
||||
* only one new scope is created. The new scope rule does not apply for the root of the template
|
||||
* since the root of the template always gets a new scope.
|
||||
*
|
||||
* * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The
|
||||
* 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent
|
||||
* scope. This is useful when creating reusable components, which should not accidentally read or modify
|
||||
* data in the parent scope.
|
||||
*
|
||||
* The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the
|
||||
* directive's element. These local properties are useful for aliasing values for templates. The keys in
|
||||
* the object hash map to the name of the property on the isolate scope; the values define how the property
|
||||
* is bound to the parent scope, via matching attributes on the directive's element:
|
||||
*
|
||||
* * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
|
||||
* always a string since DOM attributes are strings. If no `attr` name is specified then the
|
||||
@@ -190,6 +196,20 @@
|
||||
* For example, if the expression is `increment(amount)` then we can specify the amount value
|
||||
* by calling the `localFn` as `localFn({amount: 22})`.
|
||||
*
|
||||
* In general it's possible to apply more than one directive to one element, but there might be limitations
|
||||
* depending on the type of scope required by the directives. The following points will help explain these limitations.
|
||||
* For simplicity only two directives are taken into account, but it is also applicable for several directives:
|
||||
*
|
||||
* * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope
|
||||
* * **child scope** + **no scope** => Both directives will share one single child scope
|
||||
* * **child scope** + **child scope** => Both directives will share one single child scope
|
||||
* * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
|
||||
* its parent's scope
|
||||
* * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot
|
||||
* be applied to the same element.
|
||||
* * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives
|
||||
* cannot be applied to the same element.
|
||||
*
|
||||
*
|
||||
* #### `bindToController`
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
|
||||
@@ -198,7 +218,7 @@
|
||||
*
|
||||
* #### `controller`
|
||||
* Controller constructor function. The controller is instantiated before the
|
||||
* pre-linking phase and it is shared with other directives (see
|
||||
* pre-linking phase and can be accessed by other directives (see
|
||||
* `require` attribute). This allows the directives to communicate with each other and augment
|
||||
* each other's behavior. The controller is injectable (and supports bracket notation) with the following locals:
|
||||
*
|
||||
@@ -238,9 +258,10 @@
|
||||
*
|
||||
* #### `controllerAs`
|
||||
* Identifier name for a reference to the controller in the directive's scope.
|
||||
* This allows the controller to be referenced from the directive template. The directive
|
||||
* needs to define a scope for this configuration to be used. Useful in the case when
|
||||
* directive is used as component.
|
||||
* This allows the controller to be referenced from the directive template. This is especially
|
||||
* useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible
|
||||
* to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the
|
||||
* `controllerAs` reference might overwrite a property that already exists on the parent scope.
|
||||
*
|
||||
*
|
||||
* #### `restrict`
|
||||
@@ -1077,7 +1098,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
var node = this.$$element[0],
|
||||
booleanKey = getBooleanAttrName(node, key),
|
||||
aliasedKey = getAliasedAttrName(node, key),
|
||||
aliasedKey = getAliasedAttrName(key),
|
||||
observer = key,
|
||||
nodeName;
|
||||
|
||||
@@ -1144,7 +1165,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
if (value === null || value === undefined) {
|
||||
if (value === null || isUndefined(value)) {
|
||||
this.$$element.removeAttr(attrName);
|
||||
} else {
|
||||
this.$$element.attr(attrName, value);
|
||||
@@ -1219,6 +1240,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') || [];
|
||||
@@ -1271,6 +1293,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,
|
||||
@@ -1416,11 +1446,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;
|
||||
}
|
||||
@@ -1439,8 +1464,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);
|
||||
@@ -1509,13 +1533,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());
|
||||
@@ -1754,7 +1776,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
} else {
|
||||
$template = jqLite(jqLiteClone(compileNode)).contents();
|
||||
$compileNode.empty(); // clear contents
|
||||
childTranscludeFn = compile($template, transcludeFn);
|
||||
childTranscludeFn = compile($template, transcludeFn, undefined,
|
||||
undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1796,8 +1819,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);
|
||||
@@ -1950,10 +1976,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;
|
||||
@@ -1963,8 +1988,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) {
|
||||
@@ -1985,42 +2013,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,10 +2100,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2110,7 +2135,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
i = 0, ii = directives.length; i < ii; i++) {
|
||||
try {
|
||||
directive = directives[i];
|
||||
if ((maxPriority === undefined || maxPriority > directive.priority) &&
|
||||
if ((isUndefined(maxPriority) || maxPriority > directive.priority) &&
|
||||
directive.restrict.indexOf(location) != -1) {
|
||||
if (startAttrName) {
|
||||
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
|
||||
@@ -2230,7 +2255,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);
|
||||
@@ -2279,7 +2306,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
|
||||
childBoundTranscludeFn, afterTemplateNodeLinkFn);
|
||||
childBoundTranscludeFn);
|
||||
}
|
||||
linkQueue = null;
|
||||
});
|
||||
@@ -2296,8 +2323,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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2406,7 +2432,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',
|
||||
@@ -2509,7 +2535,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// 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
|
||||
@@ -2557,9 +2583,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,
|
||||
@@ -2621,14 +2646,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 '&':
|
||||
@@ -2644,16 +2668,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;
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ function $$CookieReader($document) {
|
||||
// the first value that is seen for a cookie is the most
|
||||
// specific one. values for the same cookie name that
|
||||
// follow are for less specific paths.
|
||||
if (lastCookies[name] === undefined) {
|
||||
if (isUndefined(lastCookies[name])) {
|
||||
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
|
||||
}
|
||||
}
|
||||
|
||||
+38
-14
@@ -25,6 +25,7 @@ function nullFormRenameControl(control, name) {
|
||||
* @property {boolean} $dirty True if user has already interacted with the form.
|
||||
* @property {boolean} $valid True if all of the containing forms and controls are valid.
|
||||
* @property {boolean} $invalid True if at least one containing control or form is invalid.
|
||||
* @property {boolean} $pending True if at least one containing control or form is pending.
|
||||
* @property {boolean} $submitted True if user has submitted the form even if its invalid.
|
||||
*
|
||||
* @property {Object} $error Is an object hash, containing references to controls or
|
||||
@@ -64,8 +65,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
var form = this,
|
||||
controls = [];
|
||||
|
||||
var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
|
||||
|
||||
// init state
|
||||
form.$error = {};
|
||||
form.$$success = {};
|
||||
@@ -76,8 +75,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
form.$valid = true;
|
||||
form.$invalid = false;
|
||||
form.$submitted = false;
|
||||
|
||||
parentForm.$addControl(form);
|
||||
form.$$parentForm = nullFormCtrl;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -116,11 +114,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$addControl
|
||||
* @param {object} control control object, either a {@link form.FormController} or an
|
||||
* {@link ngModel.NgModelController}
|
||||
*
|
||||
* @description
|
||||
* Register a control with the form.
|
||||
* Register a control with the form. Input elements using ngModelController do this automatically
|
||||
* when they are linked.
|
||||
*
|
||||
* Input elements using ngModelController do this automatically when they are linked.
|
||||
* Note that the current state of the control will not be reflected on the new parent form. This
|
||||
* is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine`
|
||||
* state.
|
||||
*
|
||||
* However, if the method is used programmatically, for example by adding dynamically created controls,
|
||||
* or controls that have been previously removed without destroying their corresponding DOM element,
|
||||
* it's the developers responsiblity to make sure the current state propagates to the parent form.
|
||||
*
|
||||
* For example, if an input control is added that is already `$dirty` and has `$error` properties,
|
||||
* calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form.
|
||||
*/
|
||||
form.$addControl = function(control) {
|
||||
// Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
|
||||
@@ -131,6 +141,8 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
if (control.$name) {
|
||||
form[control.$name] = control;
|
||||
}
|
||||
|
||||
control.$$parentForm = form;
|
||||
};
|
||||
|
||||
// Private API: rename a form control
|
||||
@@ -147,11 +159,18 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$removeControl
|
||||
* @param {object} control control object, either a {@link form.FormController} or an
|
||||
* {@link ngModel.NgModelController}
|
||||
*
|
||||
* @description
|
||||
* Deregister a control from the form.
|
||||
*
|
||||
* Input elements using ngModelController do this automatically when they are destroyed.
|
||||
*
|
||||
* Note that only the removed control's validation state (`$errors`etc.) will be removed from the
|
||||
* form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be
|
||||
* different from case to case. For example, removing the only `$dirty` control from a form may or
|
||||
* may not mean that the form is still `$dirty`.
|
||||
*/
|
||||
form.$removeControl = function(control) {
|
||||
if (control.$name && form[control.$name] === control) {
|
||||
@@ -168,6 +187,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
});
|
||||
|
||||
arrayRemove(controls, control);
|
||||
control.$$parentForm = nullFormCtrl;
|
||||
};
|
||||
|
||||
|
||||
@@ -204,7 +224,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
delete object[property];
|
||||
}
|
||||
},
|
||||
parentForm: parentForm,
|
||||
$animate: $animate
|
||||
});
|
||||
|
||||
@@ -223,7 +242,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
$animate.addClass(element, DIRTY_CLASS);
|
||||
form.$dirty = true;
|
||||
form.$pristine = false;
|
||||
parentForm.$setDirty();
|
||||
form.$$parentForm.$setDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -279,7 +298,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
form.$setSubmitted = function() {
|
||||
$animate.addClass(element, SUBMITTED_CLASS);
|
||||
form.$submitted = true;
|
||||
parentForm.$setSubmitted();
|
||||
form.$$parentForm.$setSubmitted();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -329,6 +348,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
* # CSS classes
|
||||
* - `ng-valid` is set if the form is valid.
|
||||
* - `ng-invalid` is set if the form is invalid.
|
||||
* - `ng-pending` is set if the form is pending.
|
||||
* - `ng-pristine` is set if the form is pristine.
|
||||
* - `ng-dirty` is set if the form is dirty.
|
||||
* - `ng-submitted` is set if the form was submitted.
|
||||
@@ -404,7 +424,6 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
</script>
|
||||
<style>
|
||||
.my-form {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -453,6 +472,7 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
var formDirective = {
|
||||
name: 'form',
|
||||
restrict: isNgForm ? 'EAC' : 'E',
|
||||
require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form
|
||||
controller: FormController,
|
||||
compile: function ngFormCompile(formElement, attr) {
|
||||
// Setup initial state of the control
|
||||
@@ -461,7 +481,9 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
|
||||
|
||||
return {
|
||||
pre: function ngFormPreLink(scope, formElement, attr, controller) {
|
||||
pre: function ngFormPreLink(scope, formElement, attr, ctrls) {
|
||||
var controller = ctrls[0];
|
||||
|
||||
// if `action` attr is not present on the form, prevent the default action (submission)
|
||||
if (!('action' in attr)) {
|
||||
// we can't use jq events because if a form is destroyed during submission the default
|
||||
@@ -490,7 +512,9 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
});
|
||||
}
|
||||
|
||||
var parentFormCtrl = controller.$$parentForm;
|
||||
var parentFormCtrl = ctrls[1] || controller.$$parentForm;
|
||||
parentFormCtrl.$addControl(controller);
|
||||
|
||||
var setter = nameAttr ? getSetter(controller.$name) : noop;
|
||||
|
||||
if (nameAttr) {
|
||||
@@ -498,13 +522,13 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
attr.$observe(nameAttr, function(newValue) {
|
||||
if (controller.$name === newValue) return;
|
||||
setter(scope, undefined);
|
||||
parentFormCtrl.$$renameControl(controller, newValue);
|
||||
controller.$$parentForm.$$renameControl(controller, newValue);
|
||||
setter = getSetter(controller.$name);
|
||||
setter(scope, controller);
|
||||
});
|
||||
}
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
controller.$$parentForm.$removeControl(controller);
|
||||
setter(scope, undefined);
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
|
||||
+62
-20
@@ -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})$/;
|
||||
@@ -138,9 +139,17 @@ var inputType = {
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO date string (yyyy-MM-dd).
|
||||
* valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
|
||||
* (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5
|
||||
* constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
||||
* a valid ISO date string (yyyy-MM-dd).
|
||||
* a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute
|
||||
* (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5
|
||||
* constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
@@ -232,10 +241,18 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO datetime format (yyyy-MM-ddTHH:mm:ss).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
||||
* a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
|
||||
* inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
|
||||
* Note that `min` will also add native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation
|
||||
* inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`).
|
||||
* Note that `max` will also add native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
@@ -328,10 +345,18 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO time format (HH:mm:ss).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a
|
||||
* valid ISO time format (HH:mm:ss).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
|
||||
* attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this
|
||||
* attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the
|
||||
* `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the
|
||||
* `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
@@ -423,10 +448,18 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a
|
||||
* valid ISO week format (yyyy-W##).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be
|
||||
* a valid ISO week format (yyyy-W##).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
|
||||
* attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this
|
||||
* attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
@@ -520,10 +553,19 @@ var inputType = {
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be
|
||||
* a valid ISO month format (yyyy-MM).
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must
|
||||
* be a valid ISO month format (yyyy-MM).
|
||||
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
|
||||
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
|
||||
* attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`.
|
||||
* This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this
|
||||
* attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add
|
||||
* native HTML5 constraint validation.
|
||||
* @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string
|
||||
* the `ngMin` expression evaluates to. Note that it does not set the `min` attribute.
|
||||
* @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string
|
||||
* the `ngMax` expression evaluates to. Note that it does not set the `max` attribute.
|
||||
|
||||
* @param {string=} required Sets `required` validation error key if the value is not entered.
|
||||
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
|
||||
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
|
||||
@@ -1285,7 +1327,7 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
}
|
||||
|
||||
function parseObservedDateValue(val) {
|
||||
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
|
||||
return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ var ngBindDirective = ['$compile', function($compile) {
|
||||
$compile.$$addBindingInfo(element, attr.ngBind);
|
||||
element = element[0];
|
||||
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
|
||||
element.textContent = value === undefined ? '' : value;
|
||||
element.textContent = isUndefined(value) ? '' : value;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -128,7 +128,7 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
|
||||
$compile.$$addBindingInfo(element, interpolateFn.expressions);
|
||||
element = element[0];
|
||||
attr.$observe('ngBindTemplate', function(value) {
|
||||
element.textContent = value === undefined ? '' : value;
|
||||
element.textContent = isUndefined(value) ? '' : value;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,7 +262,6 @@ function classDirective(name, selector) {
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.base-class {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
}
|
||||
|
||||
.animate-if.ng-enter, .animate-if.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -85,7 +91,6 @@
|
||||
}
|
||||
|
||||
.slide-animate.ng-enter, .slide-animate.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
|
||||
position:absolute;
|
||||
|
||||
@@ -10,16 +10,18 @@
|
||||
* current scope.
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* The only appropriate use of `ngInit` is for aliasing special properties of
|
||||
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you
|
||||
* should use {@link guide/controller controllers} rather than `ngInit`
|
||||
* to initialize values on a scope.
|
||||
* This directive can be abused to add unnecessary amounts of logic into your templates.
|
||||
* There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of
|
||||
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via
|
||||
* server side scripting. Besides these few cases, you should use {@link guide/controller controllers}
|
||||
* rather than `ngInit` to initialize values on a scope.
|
||||
* </div>
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
|
||||
* sure you have parenthesis for correct precedence:
|
||||
* **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make
|
||||
* sure you have parentheses to ensure correct operator precedence:
|
||||
* <pre class="prettyprint">
|
||||
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
|
||||
* `<div ng-init="test1 = ($index | toString)"></div>`
|
||||
* </pre>
|
||||
* </div>
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
+53
-44
@@ -22,7 +22,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
|
||||
@@ -236,7 +238,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$$success = {}; // keep valid keys here
|
||||
this.$pending = undefined; // keep pending keys here
|
||||
this.$name = $interpolate($attr.name || '', false)($scope);
|
||||
|
||||
this.$$parentForm = nullFormCtrl;
|
||||
|
||||
var parsedNgModel = $parse($attr.ngModel),
|
||||
parsedNgModelAssign = parsedNgModel.assign,
|
||||
@@ -316,8 +318,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
return isUndefined(value) || value === '' || value === null || value !== value;
|
||||
};
|
||||
|
||||
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
|
||||
currentValidationRunId = 0;
|
||||
var currentValidationRunId = 0;
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -350,7 +351,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
unset: function(object, property) {
|
||||
delete object[property];
|
||||
},
|
||||
parentForm: parentForm,
|
||||
$animate: $animate
|
||||
});
|
||||
|
||||
@@ -388,7 +388,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ctrl.$pristine = false;
|
||||
$animate.removeClass($element, PRISTINE_CLASS);
|
||||
$animate.addClass($element, DIRTY_CLASS);
|
||||
parentForm.$setDirty();
|
||||
ctrl.$$parentForm.$setDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -558,7 +558,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
function processParseErrors() {
|
||||
var errorKey = ctrl.$$parserName || 'parse';
|
||||
if (parserValid === undefined) {
|
||||
if (isUndefined(parserValid)) {
|
||||
setValidity(errorKey, null);
|
||||
} else {
|
||||
if (!parserValid) {
|
||||
@@ -728,37 +728,47 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
* @description
|
||||
* Update the view value.
|
||||
*
|
||||
* This method should be called when an input directive want to change the view value; typically,
|
||||
* this is done from within a DOM event handler.
|
||||
* This method should be called when a control wants to change the view value; typically,
|
||||
* this is done from within a DOM event handler. For example, the {@link ng.directive:input input}
|
||||
* directive calls it when the value of the input changes and {@link ng.directive:select select}
|
||||
* calls it when an option is selected.
|
||||
*
|
||||
* For example {@link ng.directive:input input} calls it when the value of the input changes and
|
||||
* {@link ng.directive:select select} calls it when an option is selected.
|
||||
*
|
||||
* If the new `value` is an object (rather than a string or a number), we should make a copy of the
|
||||
* object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
|
||||
* watch of objects, it only looks for a change of identity. If you only change the property of
|
||||
* the object then ngModel will not realise that the object has changed and will not invoke the
|
||||
* `$parsers` and `$validators` pipelines.
|
||||
*
|
||||
* For this reason, you should not change properties of the copy once it has been passed to
|
||||
* `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
|
||||
*
|
||||
* When this method is called, the new `value` will be staged for committing through the `$parsers`
|
||||
* When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers`
|
||||
* and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
|
||||
* value sent directly for processing, finally to be applied to `$modelValue` and then the
|
||||
* **expression** specified in the `ng-model` attribute.
|
||||
*
|
||||
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
|
||||
* **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners,
|
||||
* in the `$viewChangeListeners` list, are called.
|
||||
*
|
||||
* In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
|
||||
* and the `default` trigger is not listed, all those actions will remain pending until one of the
|
||||
* `updateOn` events is triggered on the DOM element.
|
||||
* All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
|
||||
* directive is used with a custom debounce for this particular event.
|
||||
* Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce`
|
||||
* is specified, once the timer runs out.
|
||||
*
|
||||
* Note that calling this function does not trigger a `$digest`.
|
||||
* When used with standard inputs, the view value will always be a string (which is in some cases
|
||||
* parsed into another type, such as a `Date` object for `input[date]`.)
|
||||
* However, custom controls might also pass objects to this method. In this case, we should make
|
||||
* a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
|
||||
* perform a deep watch of objects, it only looks for a change of identity. If you only change
|
||||
* the property of the object then ngModel will not realise that the object has changed and
|
||||
* will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
|
||||
* not change properties of the copy once it has been passed to `$setViewValue`.
|
||||
* Otherwise you may cause the model value on the scope to change incorrectly.
|
||||
*
|
||||
* @param {string} value Value from the view.
|
||||
* <div class="alert alert-info">
|
||||
* In any case, the value passed to the method should always reflect the current value
|
||||
* of the control. For example, if you are calling `$setViewValue` for an input element,
|
||||
* you should pass the input DOM value. Otherwise, the control and the scope model become
|
||||
* out of sync. It's also important to note that `$setViewValue` does not call `$render` or change
|
||||
* the control's DOM value in any way. If we want to change the control's DOM value
|
||||
* programmatically, we should update the `ngModel` scope expression. Its new value will be
|
||||
* picked up by the model controller, which will run it through the `$formatters`, `$render` it
|
||||
* to update the DOM, and finally call `$validate` on it.
|
||||
* </div>
|
||||
*
|
||||
* @param {*} value value from the view.
|
||||
* @param {string} trigger Event that triggered the update.
|
||||
*/
|
||||
this.$setViewValue = function(value, trigger) {
|
||||
@@ -935,7 +945,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
</script>
|
||||
<style>
|
||||
.my-input {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -1022,7 +1031,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
|
||||
return {
|
||||
pre: function ngModelPreLink(scope, element, attr, ctrls) {
|
||||
var modelCtrl = ctrls[0],
|
||||
formCtrl = ctrls[1] || nullFormCtrl;
|
||||
formCtrl = ctrls[1] || modelCtrl.$$parentForm;
|
||||
|
||||
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
|
||||
|
||||
@@ -1031,12 +1040,12 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
|
||||
|
||||
attr.$observe('name', function(newValue) {
|
||||
if (modelCtrl.$name !== newValue) {
|
||||
formCtrl.$$renameControl(modelCtrl, newValue);
|
||||
modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
formCtrl.$removeControl(modelCtrl);
|
||||
modelCtrl.$$parentForm.$removeControl(modelCtrl);
|
||||
});
|
||||
},
|
||||
post: function ngModelPostLink(scope, element, attr, ctrls) {
|
||||
@@ -1132,12 +1141,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) {
|
||||
@@ -1152,20 +1162,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>
|
||||
@@ -1191,7 +1201,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>
|
||||
@@ -1235,7 +1245,7 @@ var ngModelOptionsDirective = function() {
|
||||
var that = this;
|
||||
this.$options = copy($scope.$eval($attrs.ngModelOptions));
|
||||
// Allow adding/overriding bound events
|
||||
if (this.$options.updateOn !== undefined) {
|
||||
if (isDefined(this.$options.updateOn)) {
|
||||
this.$options.updateOnDefault = false;
|
||||
// extract "default" pseudo-event from list of events that can trigger a model update
|
||||
this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
|
||||
@@ -1258,7 +1268,6 @@ function addSetValidityMethod(context) {
|
||||
classCache = {},
|
||||
set = context.set,
|
||||
unset = context.unset,
|
||||
parentForm = context.parentForm,
|
||||
$animate = context.$animate;
|
||||
|
||||
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
|
||||
@@ -1266,7 +1275,7 @@ function addSetValidityMethod(context) {
|
||||
ctrl.$setValidity = setValidity;
|
||||
|
||||
function setValidity(validationErrorKey, state, controller) {
|
||||
if (state === undefined) {
|
||||
if (isUndefined(state)) {
|
||||
createAndSet('$pending', validationErrorKey, controller);
|
||||
} else {
|
||||
unsetAndCleanup('$pending', validationErrorKey, controller);
|
||||
@@ -1310,7 +1319,7 @@ function addSetValidityMethod(context) {
|
||||
}
|
||||
|
||||
toggleValidationCss(validationErrorKey, combinedState);
|
||||
parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
|
||||
ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
|
||||
}
|
||||
|
||||
function createAndSet(name, value, controller) {
|
||||
|
||||
@@ -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,11 +409,8 @@ 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) {
|
||||
|
||||
function ngOptionsPostLink(scope, selectElement, attr, ctrls) {
|
||||
|
||||
// if ngModel is not defined, we don't need to do anything
|
||||
var ngModelCtrl = ctrls[1];
|
||||
@@ -451,7 +465,6 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
unknownOption.remove();
|
||||
};
|
||||
|
||||
|
||||
// Update the controller methods for multiple selectable options
|
||||
if (!multiple) {
|
||||
|
||||
@@ -579,11 +592,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 +639,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;
|
||||
}
|
||||
}
|
||||
@@ -721,7 +744,20 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
* | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). |
|
||||
* | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). |
|
||||
*
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
* <div class="alert alert-info">
|
||||
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
|
||||
* This may be useful when, for instance, nesting ngRepeats.
|
||||
* </div>
|
||||
*
|
||||
*
|
||||
* # Iterating over object properties
|
||||
@@ -41,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
|
||||
@@ -51,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.
|
||||
@@ -72,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)">
|
||||
@@ -80,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}}
|
||||
@@ -256,7 +268,6 @@
|
||||
.animate-repeat.ng-move,
|
||||
.animate-repeat.ng-enter,
|
||||
.animate-repeat.ng-leave {
|
||||
-webkit-transition:all linear 0.5s;
|
||||
transition:all linear 0.5s;
|
||||
}
|
||||
|
||||
@@ -428,7 +439,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
// if object, extract keys, in enumeration order, unsorted
|
||||
collectionKeys = [];
|
||||
for (var itemKey in collection) {
|
||||
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
|
||||
if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') {
|
||||
collectionKeys.push(itemKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,9 +125,7 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
background: white;
|
||||
}
|
||||
|
||||
.animate-show.ng-hide-add.ng-hide-add-active,
|
||||
.animate-show.ng-hide-remove.ng-hide-remove-active {
|
||||
-webkit-transition: all linear 0.5s;
|
||||
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
|
||||
transition: all linear 0.5s;
|
||||
}
|
||||
|
||||
@@ -284,7 +282,6 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
</file>
|
||||
<file name="animations.css">
|
||||
.animate-hide {
|
||||
-webkit-transition: all linear 0.5s;
|
||||
transition: all linear 0.5s;
|
||||
line-height: 20px;
|
||||
opacity: 1;
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
}
|
||||
|
||||
.animate-switch.ng-animate {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
|
||||
|
||||
position:absolute;
|
||||
|
||||
+209
-56
@@ -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();
|
||||
});
|
||||
};
|
||||
}];
|
||||
|
||||
/**
|
||||
@@ -108,31 +152,164 @@ var SelectController =
|
||||
* @description
|
||||
* HTML `SELECT` element with angular data-binding.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
|
||||
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing
|
||||
* memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression.
|
||||
* The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
|
||||
* between the scope and the `<select>` control (including setting default values).
|
||||
* Ìt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
|
||||
* {@link ngOptions `ngOptions`} directives.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
* directive.
|
||||
* When an item in the `<select>` menu is selected, the value of the selected option will be bound
|
||||
* to the model identified by the `ngModel` directive. With static or repeated options, this is
|
||||
* the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
|
||||
* If you want dynamic value attributes, you can use interpolation inside the value attribute.
|
||||
*
|
||||
* If the viewValue contains a value that doesn't match any of the options then the control
|
||||
* will automatically add an "unknown" option, which it then removes when this is resolved.
|
||||
* <div class="alert alert-warning">
|
||||
* Note that the value of a `select` directive used without `ngOptions` is always a string.
|
||||
* When the model needs to be bound to a non-string value, you must either explictly convert it
|
||||
* using a directive (see example below) or use `ngOptions` to specify the set of options.
|
||||
* This is because an option element can only be bound to string values at present.
|
||||
* </div>
|
||||
*
|
||||
* If the viewValue of `ngModel` does not match any of the options, then the control
|
||||
* will automatically add an "unknown" option, which it then removes when the mismatch is resolved.
|
||||
*
|
||||
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
|
||||
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* The value of a `select` directive used without `ngOptions` is always a string.
|
||||
* When the model needs to be bound to a non-string value, you must either explictly convert it
|
||||
* using a directive (see example below) or use `ngOptions` to specify the set of options.
|
||||
* This is because an option element can only be bound to string values at present.
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
|
||||
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression, and additionally in reducing memory and increasing speed by not creating
|
||||
* a new scope for each repeated instance.
|
||||
* </div>
|
||||
*
|
||||
* ### Example (binding `select` to a non-string value)
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
* @param {string=} 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
|
||||
* when you want to data-bind to the required attribute.
|
||||
* @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user
|
||||
* interaction with the select element.
|
||||
* @param {string=} ngOptions sets the options that the select is populated with and defines what is
|
||||
* set on the model on selection. See {@link ngOptions `ngOptions`}.
|
||||
*
|
||||
* @example
|
||||
* ### Simple `select` elements with static options
|
||||
*
|
||||
* <example name="static-select" module="staticSelect">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="ExampleController">
|
||||
* <form name="myForm">
|
||||
* <label for="singleSelect"> Single select: </label><br>
|
||||
* <select name="singleSelect" ng-model="data.singleSelect">
|
||||
* <option value="option-1">Option 1</option>
|
||||
* <option value="option-2">Option 2</option>
|
||||
* </select><br>
|
||||
*
|
||||
* <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
|
||||
* <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
|
||||
* <option value="">---Please select---</option> <!-- not selected / blank option -->
|
||||
* <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
|
||||
* <option value="option-2">Option 2</option>
|
||||
* </select><br>
|
||||
* <button ng-click="forceUnknownOption()">Force unknown option</button><br>
|
||||
* <tt>singleSelect = {{data.singleSelect}}</tt>
|
||||
*
|
||||
* <hr>
|
||||
* <label for="multipleSelect"> Multiple select: </label><br>
|
||||
* <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
|
||||
* <option value="option-1">Option 1</option>
|
||||
* <option value="option-2">Option 2</option>
|
||||
* <option value="option-3">Option 3</option>
|
||||
* </select><br>
|
||||
* <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
|
||||
* </form>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('staticSelect', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.data = {
|
||||
* singleSelect: null,
|
||||
* multipleSelect: [],
|
||||
* option1: 'option-1',
|
||||
* };
|
||||
*
|
||||
* $scope.forceUnknownOption = function() {
|
||||
* $scope.data.singleSelect = 'nonsense';
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*</example>
|
||||
*
|
||||
* ### Using `ngRepeat` to generate `select` options
|
||||
* <example name="ngrepeat-select" module="ngrepeatSelect">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="ExampleController">
|
||||
* <form name="myForm">
|
||||
* <label for="repeatSelect"> Repeat select: </label>
|
||||
* <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect">
|
||||
* <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
|
||||
* </select>
|
||||
* </form>
|
||||
* <hr>
|
||||
* <tt>repeatSelect = {{data.repeatSelect}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('ngrepeatSelect', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.data = {
|
||||
* repeatSelect: null,
|
||||
* availableOptions: [
|
||||
* {id: '1', name: 'Option A'},
|
||||
* {id: '2', name: 'Option B'},
|
||||
* {id: '3', name: 'Option C'}
|
||||
* ],
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*</example>
|
||||
*
|
||||
*
|
||||
* ### Using `select` with `ngOptions` and setting a default value
|
||||
* See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.
|
||||
*
|
||||
* <example name="select-with-default-values" module="defaultValueSelect">
|
||||
* <file name="index.html">
|
||||
* <div ng-controller="ExampleController">
|
||||
* <form name="myForm">
|
||||
* <label for="mySelect">Make a choice:</label>
|
||||
* <select name="mySelect" id="mySelect"
|
||||
* ng-options="option.name for option in data.availableOptions track by option.id"
|
||||
* ng-model="data.selectedOption"></select>
|
||||
* </form>
|
||||
* <hr>
|
||||
* <tt>option = {{data.selectedOption}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="app.js">
|
||||
* angular.module('defaultValueSelect', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.data = {
|
||||
* availableOptions: [
|
||||
* {id: '1', name: 'Option A'},
|
||||
* {id: '2', name: 'Option B'},
|
||||
* {id: '3', name: 'Option C'}
|
||||
* ],
|
||||
* selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
|
||||
* };
|
||||
* }]);
|
||||
* </file>
|
||||
*</example>
|
||||
*
|
||||
*
|
||||
* ### Binding `select` to a non-string value via `ngModel` parsing / formatting
|
||||
*
|
||||
* <example name="select-with-non-string-options" module="nonStringSelect">
|
||||
* <file name="index.html">
|
||||
@@ -177,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];
|
||||
@@ -247,7 +430,6 @@ var selectDirective = function() {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -255,26 +437,19 @@ 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,
|
||||
compile: function(element, attr) {
|
||||
|
||||
// If the value attribute is not defined then we fall back to the
|
||||
// text content of the option element, which may be interpolated
|
||||
if (isUndefined(attr.value)) {
|
||||
var interpolateFn = $interpolate(element.text(), true);
|
||||
if (!interpolateFn) {
|
||||
if (isDefined(attr.value)) {
|
||||
// If the value attribute is defined, check if it contains an interpolation
|
||||
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 interpolateTextFn = $interpolate(element.text(), true);
|
||||
if (!interpolateTextFn) {
|
||||
attr.$set('value', element.text());
|
||||
}
|
||||
}
|
||||
@@ -288,30 +463,8 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
// Only update trigger option updates if this is an option within a `select`
|
||||
// that also has `ngModel` attached
|
||||
if (selectCtrl && selectCtrl.ngModelCtrl) {
|
||||
|
||||
if (interpolateFn) {
|
||||
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
|
||||
attr.$set('value', newVal);
|
||||
if (oldVal !== newVal) {
|
||||
selectCtrl.removeOption(oldVal);
|
||||
}
|
||||
selectCtrl.addOption(newVal, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
});
|
||||
} else {
|
||||
selectCtrl.addOption(attr.value, element);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
chromeHack(element);
|
||||
}
|
||||
|
||||
element.on('$destroy', function() {
|
||||
selectCtrl.removeOption(attr.value);
|
||||
selectCtrl.ngModelCtrl.$render();
|
||||
});
|
||||
if (selectCtrl) {
|
||||
selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ var patternDirective = function() {
|
||||
ctrl.$validate();
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
|
||||
ctrl.$validators.pattern = function(modelValue, viewValue) {
|
||||
// HTML5 pattern constraint validates the input value, so we validate the viewValue
|
||||
return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -214,6 +214,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (fractionSize > 0 && number < 1) {
|
||||
formatedText = number.toFixed(fractionSize);
|
||||
number = parseFloat(formatedText);
|
||||
formatedText = formatedText.replace(DECIMAL_SEP, decimalSep);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+29
-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.
|
||||
@@ -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
|
||||
@@ -1007,11 +999,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);
|
||||
@@ -1289,8 +1278,8 @@ function $HttpProvider() {
|
||||
* Resolves the raw $http promise.
|
||||
*/
|
||||
function resolvePromise(response, status, headers, statusText) {
|
||||
// normalize internal statuses to 0
|
||||
status = Math.max(status, 0);
|
||||
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
|
||||
status = status >= -1 ? status : 0;
|
||||
|
||||
(isSuccess(status) ? deferred.resolve : deferred.reject)({
|
||||
data: response,
|
||||
|
||||
+33
-7
@@ -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) {
|
||||
@@ -109,7 +135,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
}
|
||||
}
|
||||
|
||||
xhr.send(post);
|
||||
xhr.send(isUndefined(post) ? null : post);
|
||||
}
|
||||
|
||||
if (timeout > 0) {
|
||||
@@ -126,7 +152,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
|
||||
function completeRequest(callback, status, response, headersString, statusText) {
|
||||
// cancel timeout and subsequent timeout promise resolution
|
||||
if (timeoutId !== undefined) {
|
||||
if (isDefined(timeoutId)) {
|
||||
$browserDefer.cancel(timeoutId);
|
||||
}
|
||||
jsonpDone = xhr = null;
|
||||
|
||||
@@ -139,7 +139,7 @@ function $InterpolateProvider() {
|
||||
* ```js
|
||||
* var $interpolate = ...; // injected
|
||||
* var exp = $interpolate('Hello {{name | uppercase}}!');
|
||||
* expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
|
||||
* expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!');
|
||||
* ```
|
||||
*
|
||||
* `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
|
||||
|
||||
+8
-8
@@ -141,14 +141,14 @@ function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) {
|
||||
var appUrl, prevAppUrl;
|
||||
var rewrittenUrl;
|
||||
|
||||
if ((appUrl = beginsWith(appBase, url)) !== undefined) {
|
||||
if (isDefined(appUrl = beginsWith(appBase, url))) {
|
||||
prevAppUrl = appUrl;
|
||||
if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) {
|
||||
if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
|
||||
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
|
||||
} else {
|
||||
rewrittenUrl = appBase + prevAppUrl;
|
||||
}
|
||||
} else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) {
|
||||
} else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
|
||||
rewrittenUrl = appBaseNoFile + appUrl;
|
||||
} else if (appBaseNoFile == url + '/') {
|
||||
rewrittenUrl = appBaseNoFile;
|
||||
@@ -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;
|
||||
|
||||
|
||||
+53
-1
@@ -48,6 +48,25 @@ function ensureSafeMemberName(name, 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.
|
||||
// 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;
|
||||
}
|
||||
|
||||
function ensureSafeObject(obj, fullExpression) {
|
||||
// nifty check if obj is Function that is fast and works across iframes and other contexts
|
||||
if (obj) {
|
||||
@@ -93,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", "'":"'", '"':'"'};
|
||||
@@ -774,6 +803,7 @@ ASTCompiler.prototype = {
|
||||
this.state.computing = 'assign';
|
||||
var result = this.nextId();
|
||||
this.recurse(assignable, result);
|
||||
this.return_(result);
|
||||
extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l');
|
||||
}
|
||||
var toWatch = getInputs(ast.body);
|
||||
@@ -806,6 +836,8 @@ ASTCompiler.prototype = {
|
||||
'ensureSafeMemberName',
|
||||
'ensureSafeObject',
|
||||
'ensureSafeFunction',
|
||||
'getStringValue',
|
||||
'ensureSafeAssignContext',
|
||||
'ifDefined',
|
||||
'plus',
|
||||
'text',
|
||||
@@ -814,6 +846,8 @@ ASTCompiler.prototype = {
|
||||
ensureSafeMemberName,
|
||||
ensureSafeObject,
|
||||
ensureSafeFunction,
|
||||
getStringValue,
|
||||
ensureSafeAssignContext,
|
||||
ifDefined,
|
||||
plusFn,
|
||||
expression);
|
||||
@@ -957,6 +991,7 @@ ASTCompiler.prototype = {
|
||||
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), '{}'));
|
||||
@@ -1040,6 +1075,7 @@ ASTCompiler.prototype = {
|
||||
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);
|
||||
@@ -1165,6 +1201,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)';
|
||||
},
|
||||
@@ -1177,6 +1217,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() {
|
||||
@@ -1354,6 +1402,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;
|
||||
};
|
||||
@@ -1551,6 +1600,7 @@ 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] = {};
|
||||
@@ -1856,13 +1906,14 @@ function $ParseProvider() {
|
||||
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);
|
||||
@@ -1880,6 +1931,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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-41
@@ -10,7 +10,7 @@ function $$RAFProvider() { //rAF
|
||||
$window.webkitCancelRequestAnimationFrame;
|
||||
|
||||
var rafSupported = !!requestAnimationFrame;
|
||||
var rafFn = rafSupported
|
||||
var raf = rafSupported
|
||||
? function(fn) {
|
||||
var id = requestAnimationFrame(fn);
|
||||
return function() {
|
||||
@@ -24,46 +24,8 @@ function $$RAFProvider() { //rAF
|
||||
};
|
||||
};
|
||||
|
||||
queueFn.supported = rafSupported;
|
||||
raf.supported = rafSupported;
|
||||
|
||||
var cancelLastRAF;
|
||||
var taskCount = 0;
|
||||
var taskQueue = [];
|
||||
return queueFn;
|
||||
|
||||
function flush() {
|
||||
for (var i = 0; i < taskQueue.length; i++) {
|
||||
var task = taskQueue[i];
|
||||
if (task) {
|
||||
taskQueue[i] = null;
|
||||
task();
|
||||
}
|
||||
}
|
||||
taskCount = taskQueue.length = 0;
|
||||
}
|
||||
|
||||
function queueFn(asyncFn) {
|
||||
var index = taskQueue.length;
|
||||
|
||||
taskCount++;
|
||||
taskQueue.push(asyncFn);
|
||||
|
||||
if (index === 0) {
|
||||
cancelLastRAF = rafFn(flush);
|
||||
}
|
||||
|
||||
return function cancelQueueFn() {
|
||||
if (index >= 0) {
|
||||
taskQueue[index] = null;
|
||||
index = null;
|
||||
|
||||
if (--taskCount === 0 && cancelLastRAF) {
|
||||
cancelLastRAF();
|
||||
cancelLastRAF = null;
|
||||
taskQueue.length = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return raf;
|
||||
}];
|
||||
}
|
||||
|
||||
+38
-22
@@ -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() {
|
||||
@@ -101,6 +101,29 @@ function $RootScopeProvider() {
|
||||
$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
|
||||
@@ -253,10 +276,10 @@ function $RootScopeProvider() {
|
||||
* Registers a `listener` callback to be executed whenever the `watchExpression` changes.
|
||||
*
|
||||
* - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
|
||||
* $digest()} and should return the value that will be watched. (Since
|
||||
* {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the
|
||||
* `watchExpression` can execute multiple times per
|
||||
* {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
|
||||
* $digest()} and should return the value that will be watched. (`watchExpression` should not change
|
||||
* its value when executed multiple times with the same input because it may be executed multiple
|
||||
* times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be
|
||||
* [idempotent](http://en.wikipedia.org/wiki/Idempotence).
|
||||
* - The `listener` is called only when the value from the current `watchExpression` and the
|
||||
* previous call to `watchExpression` are not equal (with the exception of the initial run,
|
||||
* see below). Inequality is determined according to reference inequality,
|
||||
@@ -605,7 +628,7 @@ function $RootScopeProvider() {
|
||||
// copy the items to oldValue and look for changes.
|
||||
newLength = 0;
|
||||
for (key in newValue) {
|
||||
if (newValue.hasOwnProperty(key)) {
|
||||
if (hasOwnProperty.call(newValue, key)) {
|
||||
newLength++;
|
||||
newItem = newValue[key];
|
||||
oldItem = oldValue[key];
|
||||
@@ -627,7 +650,7 @@ function $RootScopeProvider() {
|
||||
// we used to have more keys, need to find them and destroy them.
|
||||
changeDetected++;
|
||||
for (key in oldValue) {
|
||||
if (!newValue.hasOwnProperty(key)) {
|
||||
if (!hasOwnProperty.call(newValue, key)) {
|
||||
oldLength--;
|
||||
delete oldValue[key];
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -294,7 +294,7 @@ function $SceDelegateProvider() {
|
||||
'Attempted to trust a value in invalid context. Context: {0}; Value: {1}',
|
||||
type, trustedValue);
|
||||
}
|
||||
if (trustedValue === null || trustedValue === undefined || trustedValue === '') {
|
||||
if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') {
|
||||
return trustedValue;
|
||||
}
|
||||
// All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting
|
||||
@@ -349,7 +349,7 @@ function $SceDelegateProvider() {
|
||||
* `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception.
|
||||
*/
|
||||
function getTrusted(type, maybeTrusted) {
|
||||
if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') {
|
||||
if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
|
||||
return maybeTrusted;
|
||||
}
|
||||
var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
|
||||
@@ -484,7 +484,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*:
|
||||
|
||||
+111
-38
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var ANIMATE_TIMER_KEY = '$$animateCss';
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $animateCss
|
||||
@@ -184,8 +186,10 @@
|
||||
*
|
||||
* * `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,6 +206,10 @@
|
||||
* * `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`)
|
||||
* * `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.
|
||||
*
|
||||
@@ -322,12 +330,31 @@ 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();
|
||||
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', '$$forceReflow', '$sniffer', '$$rAF',
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout, $$forceReflow, $sniffer, $$rAF) {
|
||||
this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
|
||||
'$$forceReflow', '$sniffer', '$$rAFScheduler', '$animate',
|
||||
function($window, $$jqLite, $$AnimateRunner, $timeout,
|
||||
$$forceReflow, $sniffer, $$rAFScheduler, $animate) {
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
|
||||
@@ -387,12 +414,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
var cancelLastRAFRequest;
|
||||
var rafWaitQueue = [];
|
||||
function waitUntilQuiet(callback) {
|
||||
if (cancelLastRAFRequest) {
|
||||
cancelLastRAFRequest(); //cancels the request
|
||||
}
|
||||
rafWaitQueue.push(callback);
|
||||
cancelLastRAFRequest = $$rAF(function() {
|
||||
cancelLastRAFRequest = null;
|
||||
$$rAFScheduler.waitUntilQuiet(function() {
|
||||
gcsLookup.flush();
|
||||
gcsStaggerLookup.flush();
|
||||
|
||||
@@ -409,8 +432,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
});
|
||||
}
|
||||
|
||||
return init;
|
||||
|
||||
function computeTimings(node, className, cacheKey) {
|
||||
var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
|
||||
var aD = timings.animationDelay;
|
||||
@@ -425,9 +446,12 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
return timings;
|
||||
}
|
||||
|
||||
function init(element, options) {
|
||||
return function init(element, options) {
|
||||
var restoreStyles = {};
|
||||
var node = getDomNode(element);
|
||||
if (!node || !node.parentNode) {
|
||||
if (!node
|
||||
|| !node.parentNode
|
||||
|| !$animate.enabled()) {
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
@@ -483,7 +507,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
// there actually is a detected transition or keyframe animation
|
||||
if (options.applyClassesEarly && addRemoveClassName.length) {
|
||||
applyAnimationClasses(element, options);
|
||||
addRemoveClassName = '';
|
||||
}
|
||||
|
||||
var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
|
||||
@@ -598,6 +621,18 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
return closeAndReturnNoopAnimator();
|
||||
}
|
||||
|
||||
if (options.delay != null) {
|
||||
var delayStyle = parseFloat(options.delay);
|
||||
|
||||
if (flags.applyTransitionDelay) {
|
||||
temporaryStyles.push(getCssDelayStyle(delayStyle));
|
||||
}
|
||||
|
||||
if (flags.applyAnimationDelay) {
|
||||
temporaryStyles.push(getCssDelayStyle(delayStyle, true));
|
||||
}
|
||||
}
|
||||
|
||||
// we need to recalculate the delay value since we used a pre-emptive negative
|
||||
// delay value and the delay value is required for the final event checking. This
|
||||
// property will ensure that this will happen after the RAF phase has passed.
|
||||
@@ -614,7 +649,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);
|
||||
@@ -681,6 +721,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
|
||||
@@ -712,6 +759,8 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
cancel: cancelFn
|
||||
});
|
||||
|
||||
// should flush the cache animation
|
||||
waitUntilQuiet(noop);
|
||||
close();
|
||||
|
||||
return {
|
||||
@@ -809,27 +858,16 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
flags.hasAnimations = timings.animationDuration > 0;
|
||||
}
|
||||
|
||||
if (flags.applyTransitionDelay || flags.applyAnimationDelay) {
|
||||
if (flags.applyAnimationDelay) {
|
||||
relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
|
||||
? parseFloat(options.delay)
|
||||
: relativeDelay;
|
||||
|
||||
maxDelay = Math.max(relativeDelay, 0);
|
||||
|
||||
var delayStyle;
|
||||
if (flags.applyTransitionDelay) {
|
||||
timings.transitionDelay = relativeDelay;
|
||||
delayStyle = getCssDelayStyle(relativeDelay);
|
||||
temporaryStyles.push(delayStyle);
|
||||
node.style[delayStyle[0]] = delayStyle[1];
|
||||
}
|
||||
|
||||
if (flags.applyAnimationDelay) {
|
||||
timings.animationDelay = relativeDelay;
|
||||
delayStyle = getCssDelayStyle(relativeDelay, true);
|
||||
temporaryStyles.push(delayStyle);
|
||||
node.style[delayStyle[0]] = delayStyle[1];
|
||||
}
|
||||
timings.animationDelay = relativeDelay;
|
||||
delayStyle = getCssDelayStyle(relativeDelay, true);
|
||||
temporaryStyles.push(delayStyle);
|
||||
node.style[delayStyle[0]] = delayStyle[1];
|
||||
}
|
||||
|
||||
maxDelayTime = maxDelay * ONE_SECOND;
|
||||
@@ -858,17 +896,52 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
startTime = Date.now();
|
||||
element.on(events.join(' '), onAnimationProgress);
|
||||
$timeout(onAnimationExpired, maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime, false);
|
||||
var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
|
||||
var endTime = startTime + timerTime;
|
||||
|
||||
applyAnimationToStyles(element, options);
|
||||
var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
|
||||
var setupFallbackTimer = true;
|
||||
if (animationsData.length) {
|
||||
var currentTimerData = animationsData[0];
|
||||
setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
|
||||
if (setupFallbackTimer) {
|
||||
$timeout.cancel(currentTimerData.timer);
|
||||
} else {
|
||||
animationsData.push(close);
|
||||
}
|
||||
}
|
||||
|
||||
if (setupFallbackTimer) {
|
||||
var timer = $timeout(onAnimationExpired, timerTime, false);
|
||||
animationsData[0] = {
|
||||
timer: timer,
|
||||
expectedEndTime: endTime
|
||||
};
|
||||
animationsData.push(close);
|
||||
element.data(ANIMATE_TIMER_KEY, animationsData);
|
||||
}
|
||||
|
||||
element.on(events.join(' '), onAnimationProgress);
|
||||
if (options.to) {
|
||||
if (options.cleanupStyles) {
|
||||
registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
|
||||
}
|
||||
applyAnimationToStyles(element, options);
|
||||
}
|
||||
}
|
||||
|
||||
function onAnimationExpired() {
|
||||
// although an expired animation is a failed animation, getting to
|
||||
// this outcome is very easy if the CSS code screws up. Therefore we
|
||||
// should still continue normally as if the animation completed correctly.
|
||||
close();
|
||||
var animationsData = element.data(ANIMATE_TIMER_KEY);
|
||||
|
||||
// this will be false in the event that the element was
|
||||
// removed from the DOM (via a leave animation or something
|
||||
// similar)
|
||||
if (animationsData) {
|
||||
for (var i = 1; i < animationsData.length; i++) {
|
||||
animationsData[i]();
|
||||
}
|
||||
element.removeData(ANIMATE_TIMER_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
function onAnimationProgress(event) {
|
||||
@@ -895,6 +968,6 @@ var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
}];
|
||||
|
||||
@@ -9,26 +9,35 @@ 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);
|
||||
|
||||
return function initDriverFn(animationDetails, onBeforeClassesAppliedCb) {
|
||||
return function initDriverFn(animationDetails) {
|
||||
return animationDetails.from && animationDetails.to
|
||||
? prepareFromToAnchorAnimation(animationDetails.from,
|
||||
animationDetails.to,
|
||||
animationDetails.classes,
|
||||
animationDetails.anchors)
|
||||
: prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb);
|
||||
: prepareRegularAnimation(animationDetails);
|
||||
};
|
||||
|
||||
function filterCssClasses(classes) {
|
||||
@@ -224,21 +233,14 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
};
|
||||
}
|
||||
|
||||
function prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb) {
|
||||
function prepareRegularAnimation(animationDetails) {
|
||||
var element = animationDetails.element;
|
||||
var options = animationDetails.options || {};
|
||||
|
||||
// since the ng-EVENT, class-ADD and class-REMOVE classes are applied inside
|
||||
// of the animateQueue pre and postDigest stages then there is no need to add
|
||||
// then them here as well.
|
||||
options.$$skipPreparationClasses = true;
|
||||
|
||||
// during the pre/post digest stages inside of animateQueue we also performed
|
||||
// the blocking (transition:-9999s) so there is no point in doing that again.
|
||||
options.skipBlocking = true;
|
||||
|
||||
if (animationDetails.structural) {
|
||||
options.event = animationDetails.event;
|
||||
options.structural = true;
|
||||
options.applyClassesEarly = true;
|
||||
|
||||
// we special case the leave animation since we want to ensure that
|
||||
// the element is removed as soon as the animation is over. Otherwise
|
||||
@@ -248,11 +250,6 @@ var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationPro
|
||||
}
|
||||
}
|
||||
|
||||
// we apply the classes right away since the pre-digest took care of the
|
||||
// preparation classes.
|
||||
onBeforeClassesAppliedCb(element);
|
||||
applyAnimationClasses(element, options);
|
||||
|
||||
// We assign the preparationClasses as the actual animation event since
|
||||
// the internals of $animateCss will just suffix the event token values
|
||||
// with `-active` to trigger the animation.
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
// by the time...
|
||||
|
||||
var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
|
||||
this.$get = ['$injector', '$$AnimateRunner', '$$rAFMutex', '$$jqLite',
|
||||
function($injector, $$AnimateRunner, $$rAFMutex, $$jqLite) {
|
||||
this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
|
||||
function($injector, $$AnimateRunner, $$jqLite) {
|
||||
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
// $animateJs(element, 'enter');
|
||||
|
||||
@@ -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);
|
||||
@@ -239,6 +252,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(' ');
|
||||
}
|
||||
@@ -381,9 +397,6 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
return runner;
|
||||
}
|
||||
|
||||
applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
|
||||
blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
|
||||
|
||||
// the counter keeps track of cancelled animations
|
||||
var counter = (existingAnimation.counter || 0) + 1;
|
||||
newAnimation.counter = counter;
|
||||
@@ -442,10 +455,7 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
|
||||
: animationDetails.event;
|
||||
|
||||
markElementAnimationState(element, RUNNING_STATE);
|
||||
var realRunner = $$animation(element, event, animationDetails.options, function(e) {
|
||||
$$forceReflow();
|
||||
blockTransitions(getDomNode(e), false);
|
||||
});
|
||||
var realRunner = $$animation(element, event, animationDetails.options);
|
||||
|
||||
realRunner.done(function(status) {
|
||||
close(!status);
|
||||
@@ -465,7 +475,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);
|
||||
}
|
||||
|
||||
@@ -508,7 +531,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;
|
||||
@@ -564,7 +588,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,18 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var $$rAFMutexFactory = ['$$rAF', function($$rAF) {
|
||||
var $$AnimateAsyncRunFactory = ['$$rAF', function($$rAF) {
|
||||
var waitQueue = [];
|
||||
|
||||
function waitForTick(fn) {
|
||||
waitQueue.push(fn);
|
||||
if (waitQueue.length > 1) return;
|
||||
$$rAF(function() {
|
||||
for (var i = 0; i < waitQueue.length; i++) {
|
||||
waitQueue[i]();
|
||||
}
|
||||
waitQueue = [];
|
||||
});
|
||||
}
|
||||
|
||||
return function() {
|
||||
var passed = false;
|
||||
$$rAF(function() {
|
||||
waitForTick(function() {
|
||||
passed = true;
|
||||
});
|
||||
return function(fn) {
|
||||
passed ? fn() : $$rAF(fn);
|
||||
return function(callback) {
|
||||
passed ? callback() : waitForTick(callback);
|
||||
};
|
||||
};
|
||||
}];
|
||||
|
||||
var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
|
||||
var $$AnimateRunnerFactory = ['$q', '$sniffer', '$$animateAsyncRun',
|
||||
function($q, $sniffer, $$animateAsyncRun) {
|
||||
|
||||
var INITIAL_STATE = 0;
|
||||
var DONE_PENDING_STATE = 1;
|
||||
var DONE_COMPLETE_STATE = 2;
|
||||
@@ -57,7 +72,7 @@ var $$AnimateRunnerFactory = ['$q', '$$rAFMutex', function($q, $$rAFMutex) {
|
||||
this.setHost(host);
|
||||
|
||||
this._doneCallbacks = [];
|
||||
this._runInAnimationFrame = $$rAFMutex();
|
||||
this._runInAnimationFrame = $$animateAsyncRun();
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
|
||||
+15
-17
@@ -19,8 +19,8 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
return element.data(RUNNER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap) {
|
||||
this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
|
||||
function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
|
||||
|
||||
var animationQueue = [];
|
||||
var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
|
||||
@@ -88,11 +88,11 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (remainingLevelEntries <= 0) {
|
||||
remainingLevelEntries = nextLevelEntries;
|
||||
nextLevelEntries = 0;
|
||||
result = result.concat(row);
|
||||
result.push(row);
|
||||
row = [];
|
||||
}
|
||||
row.push(entry.fn);
|
||||
forEach(entry.children, function(childEntry) {
|
||||
entry.children.forEach(function(childEntry) {
|
||||
nextLevelEntries++;
|
||||
queue.push(childEntry);
|
||||
});
|
||||
@@ -100,14 +100,15 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
}
|
||||
|
||||
if (row.length) {
|
||||
result = result.concat(row);
|
||||
result.push(row);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(matsko): document the signature in a better way
|
||||
return function(element, event, options, onBeforeClassesAppliedCb) {
|
||||
return function(element, event, options) {
|
||||
options = prepareAnimationOptions(options);
|
||||
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
|
||||
|
||||
@@ -159,8 +160,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
// the element was destroyed early on which removed the runner
|
||||
// form its storage. This means we can't animate this element
|
||||
// at all and it already has been closed due to destruction.
|
||||
var elm = entry.element;
|
||||
if (getRunner(elm) && getDomNode(elm).parentNode) {
|
||||
if (getRunner(entry.element)) {
|
||||
animations.push(entry);
|
||||
} else {
|
||||
entry.close();
|
||||
@@ -191,7 +191,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
: animationEntry.element;
|
||||
|
||||
if (getRunner(targetElement)) {
|
||||
var operation = invokeFirstDriver(animationEntry, onBeforeClassesAppliedCb);
|
||||
var operation = invokeFirstDriver(animationEntry);
|
||||
if (operation) {
|
||||
startAnimationFn = operation.start;
|
||||
}
|
||||
@@ -211,11 +211,9 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
});
|
||||
|
||||
// we need to sort each of the animations in order of parent to child
|
||||
// relationships. This ensures that the parent to child classes are
|
||||
// applied at the right time.
|
||||
forEach(sortAnimations(toBeSortedAnimations), function(triggerAnimation) {
|
||||
triggerAnimation();
|
||||
});
|
||||
// relationships. This ensures that the child classes are applied at the
|
||||
// right time.
|
||||
$$rAFScheduler(sortAnimations(toBeSortedAnimations));
|
||||
});
|
||||
|
||||
return runner;
|
||||
@@ -285,7 +283,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
var lookupKey = from.animationID.toString();
|
||||
if (!anchorGroups[lookupKey]) {
|
||||
var group = anchorGroups[lookupKey] = {
|
||||
// TODO(matsko): double-check this code
|
||||
structural: true,
|
||||
beforeStart: function() {
|
||||
fromAnimation.beforeStart();
|
||||
toAnimation.beforeStart();
|
||||
@@ -339,7 +337,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
return matches.join(' ');
|
||||
}
|
||||
|
||||
function invokeFirstDriver(animationDetails, onBeforeClassesAppliedCb) {
|
||||
function invokeFirstDriver(animationDetails) {
|
||||
// we loop in reverse order since the more general drivers (like CSS and JS)
|
||||
// may attempt more elements, but custom drivers are more particular
|
||||
for (var i = drivers.length - 1; i >= 0; i--) {
|
||||
@@ -347,7 +345,7 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
|
||||
if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
|
||||
|
||||
var factory = $injector.get(driverName);
|
||||
var driver = factory(animationDetails, onBeforeClassesAppliedCb);
|
||||
var driver = factory(animationDetails);
|
||||
if (driver) {
|
||||
return driver;
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
function $$BodyProvider() {
|
||||
this.$get = ['$document', function($document) {
|
||||
return jqLite($document[0].body);
|
||||
}];
|
||||
}
|
||||
+20
-25
@@ -2,8 +2,8 @@
|
||||
|
||||
/* global angularAnimateModule: true,
|
||||
|
||||
$$BodyProvider,
|
||||
$$rAFMutexFactory,
|
||||
$$AnimateAsyncRunFactory,
|
||||
$$rAFSchedulerFactory,
|
||||
$$AnimateChildrenDirective,
|
||||
$$AnimateRunnerFactory,
|
||||
$$AnimateQueueProvider,
|
||||
@@ -20,7 +20,7 @@
|
||||
* @description
|
||||
*
|
||||
* The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
|
||||
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` then the animation hooks are enabled for an Angular app.
|
||||
* callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
|
||||
*
|
||||
* <div doc-module-components="ngAnimate"></div>
|
||||
*
|
||||
@@ -53,7 +53,7 @@
|
||||
* CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
|
||||
* and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
|
||||
*
|
||||
* The example below shows how an `enter` animation can be made possible on a element using `ng-if`:
|
||||
* The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
|
||||
*
|
||||
* ```html
|
||||
* <div ng-if="bool" class="fade">
|
||||
@@ -188,8 +188,8 @@
|
||||
* /* this will have a 100ms delay between each successive leave animation */
|
||||
* transition-delay: 0.1s;
|
||||
*
|
||||
* /* in case the stagger doesn't work then the duration value
|
||||
* must be set to 0 to avoid an accidental CSS inheritance */
|
||||
* /* As of 1.4.4, this must always be set: it signals ngAnimate
|
||||
* to not accidentally inherit a delay property from another CSS class */
|
||||
* transition-duration: 0s;
|
||||
* }
|
||||
* .my-animation.ng-enter.ng-enter-active {
|
||||
@@ -290,7 +290,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
|
||||
@@ -321,7 +321,7 @@
|
||||
* // do some cool animation and call the doneFn
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* ## CSS + JS Animations Together
|
||||
@@ -343,7 +343,7 @@
|
||||
* jQuery(element).slideIn(1000, doneFn);
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* }]);
|
||||
* ```
|
||||
*
|
||||
* ```css
|
||||
@@ -363,16 +363,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.
|
||||
@@ -384,19 +383,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:
|
||||
@@ -738,16 +735,14 @@
|
||||
* @description
|
||||
* The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
|
||||
*
|
||||
* Click here {@link ng.$animate $animate to learn more about animations with `$animate`}.
|
||||
* Click here {@link ng.$animate to learn more about animations with `$animate`}.
|
||||
*/
|
||||
angular.module('ngAnimate', [])
|
||||
.provider('$$body', $$BodyProvider)
|
||||
|
||||
.directive('ngAnimateChildren', $$AnimateChildrenDirective)
|
||||
|
||||
.factory('$$rAFMutex', $$rAFMutexFactory)
|
||||
.factory('$$rAFScheduler', $$rAFSchedulerFactory)
|
||||
|
||||
.factory('$$AnimateRunner', $$AnimateRunnerFactory)
|
||||
.factory('$$animateAsyncRun', $$AnimateAsyncRunFactory)
|
||||
|
||||
.provider('$$animateQueue', $$AnimateQueueProvider)
|
||||
.provider('$$animation', $$AnimationProvider)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
|
||||
var queue, cancelFn;
|
||||
|
||||
function scheduler(tasks) {
|
||||
// we make a copy since RAFScheduler mutates the state
|
||||
// of the passed in array variable and this would be difficult
|
||||
// to track down on the outside code
|
||||
queue = queue.concat(tasks);
|
||||
nextTick();
|
||||
}
|
||||
|
||||
queue = scheduler.queue = [];
|
||||
|
||||
/* waitUntilQuiet does two things:
|
||||
* 1. It will run the FINAL `fn` value only when an uncancelled RAF has passed through
|
||||
* 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
|
||||
*
|
||||
* The motivation here is that animation code can request more time from the scheduler
|
||||
* before the next wave runs. This allows for certain DOM properties such as classes to
|
||||
* be resolved in time for the next animation to run.
|
||||
*/
|
||||
scheduler.waitUntilQuiet = function(fn) {
|
||||
if (cancelFn) cancelFn();
|
||||
|
||||
cancelFn = $$rAF(function() {
|
||||
cancelFn = null;
|
||||
fn();
|
||||
nextTick();
|
||||
});
|
||||
};
|
||||
|
||||
return scheduler;
|
||||
|
||||
function nextTick() {
|
||||
if (!queue.length) return;
|
||||
|
||||
var items = queue.shift();
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
items[i]();
|
||||
}
|
||||
|
||||
if (!cancelFn) {
|
||||
$$rAF(function() {
|
||||
if (!cancelFn) nextTick();
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
@@ -36,7 +36,7 @@ var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMA
|
||||
// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
|
||||
// therefore there is no reason to test anymore for other vendor prefixes:
|
||||
// http://caniuse.com/#search=transition
|
||||
if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
|
||||
if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
|
||||
CSS_PREFIX = '-webkit-';
|
||||
TRANSITION_PROP = 'WebkitTransition';
|
||||
TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
|
||||
@@ -45,7 +45,7 @@ if (window.ontransitionend === undefined && window.onwebkittransitionend !== und
|
||||
TRANSITIONEND_EVENT = 'transitionend';
|
||||
}
|
||||
|
||||
if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
|
||||
if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
|
||||
CSS_PREFIX = '-webkit-';
|
||||
ANIMATION_PROP = 'WebkitAnimation';
|
||||
ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
|
||||
|
||||
+41
-36
@@ -52,6 +52,16 @@
|
||||
var ngAriaModule = angular.module('ngAria', ['ng']).
|
||||
provider('$aria', $AriaProvider);
|
||||
|
||||
/**
|
||||
* Internal Utilities
|
||||
*/
|
||||
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY'];
|
||||
|
||||
var isNodeOneOf = function(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $ariaProvider
|
||||
@@ -113,10 +123,10 @@ function $AriaProvider() {
|
||||
config = angular.extend(config, newConfig);
|
||||
};
|
||||
|
||||
function watchExpr(attrName, ariaAttr, negate) {
|
||||
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) {
|
||||
return function(scope, elem, attr) {
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !attr[ariaCamelName]) {
|
||||
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
// ensure boolean value
|
||||
boolVal = negate ? !boolVal : !!boolVal;
|
||||
@@ -125,7 +135,6 @@ function $AriaProvider() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $aria
|
||||
@@ -184,10 +193,10 @@ function $AriaProvider() {
|
||||
|
||||
|
||||
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true);
|
||||
}])
|
||||
.directive('ngHide', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', false);
|
||||
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
|
||||
}])
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
|
||||
@@ -226,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;
|
||||
@@ -261,6 +271,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
|
||||
getRadioReaction() : ngAriaCheckboxReaction);
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'range':
|
||||
if (shouldAttachRole(shape, elem)) {
|
||||
@@ -289,6 +302,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
break;
|
||||
case 'multiline':
|
||||
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
|
||||
@@ -297,10 +313,6 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (needsTabIndex) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
|
||||
scope.$watch(function ngAriaRequiredWatch() {
|
||||
return ngModel.$error.required;
|
||||
@@ -322,7 +334,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
};
|
||||
}])
|
||||
.directive('ngDisabled', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled');
|
||||
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', []);
|
||||
}])
|
||||
.directive('ngMessages', function() {
|
||||
return {
|
||||
@@ -342,35 +354,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
|
||||
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];
|
||||
if (!isNodeOneOf(elem, nodeBlackList)) {
|
||||
|
||||
function isNodeOneOf(elem, nodeTypeArray) {
|
||||
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
|
||||
return true;
|
||||
if ($aria.config('bindRoleForClick') && !elem.attr('role')) {
|
||||
elem.attr('role', 'button');
|
||||
}
|
||||
}
|
||||
|
||||
if ($aria.config('bindRoleForClick')
|
||||
&& !elem.attr('role')
|
||||
&& !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('role', 'button');
|
||||
}
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
elem.on('keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
if (keyCode === 32 || keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.on('keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
if (keyCode === 32 || keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -378,7 +383,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ function $$CookieWriter($document, $log, $browser) {
|
||||
options = options || {};
|
||||
expires = options.expires;
|
||||
path = angular.isDefined(options.path) ? options.path : cookiePath;
|
||||
if (value === undefined) {
|
||||
if (angular.isUndefined(value)) {
|
||||
expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
value = '';
|
||||
}
|
||||
|
||||
@@ -325,6 +325,9 @@ angular.module('ngMessages', [])
|
||||
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
|
||||
var ctrl = this;
|
||||
var latestKey = 0;
|
||||
var nextAttachId = 0;
|
||||
|
||||
this.getAttachId = function getAttachId() { return nextAttachId++; };
|
||||
|
||||
var messages = this.messages = {};
|
||||
var renderLater, cachedCollection;
|
||||
@@ -636,11 +639,15 @@ function ngMessageDirectiveFactory(restrict) {
|
||||
$animate.enter(elm, null, element);
|
||||
currentElement = elm;
|
||||
|
||||
// Each time we attach this node to a message we get a new id that we can match
|
||||
// when we are destroying the node later.
|
||||
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();
|
||||
|
||||
// in the event that the parent element is destroyed
|
||||
// by any other structural directive then it's time
|
||||
// to deregister the message from the controller
|
||||
currentElement.on('$destroy', function() {
|
||||
if (currentElement) {
|
||||
if (currentElement && currentElement.$$attachId === $$attachId) {
|
||||
ngMessagesCtrl.deregister(commentNode);
|
||||
messageCtrl.detach();
|
||||
}
|
||||
|
||||
Vendored
+64
-26
@@ -87,7 +87,7 @@ angular.mock.$Browser = function() {
|
||||
if (fn.id === deferId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
self.deferredFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
}
|
||||
@@ -462,7 +462,7 @@ angular.mock.$IntervalProvider = function() {
|
||||
if (fn.id === promise.$$intervalId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
}
|
||||
}
|
||||
@@ -504,7 +504,7 @@ angular.mock.$IntervalProvider = function() {
|
||||
if (fn.id === promise.$$intervalId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex !== undefined) {
|
||||
if (angular.isDefined(fnIndex)) {
|
||||
repeatFns[fnIndex].deferred.reject('canceled');
|
||||
repeatFns.splice(fnIndex, 1);
|
||||
return true;
|
||||
@@ -763,25 +763,62 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
|
||||
return reflowFn;
|
||||
});
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$$forceReflow',
|
||||
function($delegate, $timeout, $browser, $$rAF, $$forceReflow) {
|
||||
$provide.factory('$$animateAsyncRun', function() {
|
||||
var queue = [];
|
||||
var queueFn = function() {
|
||||
return function(fn) {
|
||||
queue.push(fn);
|
||||
};
|
||||
};
|
||||
queueFn.flush = function() {
|
||||
if (queue.length === 0) return false;
|
||||
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
queue[i]();
|
||||
}
|
||||
queue = [];
|
||||
|
||||
return true;
|
||||
};
|
||||
return queueFn;
|
||||
});
|
||||
|
||||
$provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
|
||||
'$$forceReflow', '$$animateAsyncRun', '$rootScope',
|
||||
function($delegate, $timeout, $browser, $$rAF,
|
||||
$$forceReflow, $$animateAsyncRun, $rootScope) {
|
||||
var animate = {
|
||||
queue: [],
|
||||
cancel: $delegate.cancel,
|
||||
on: $delegate.on,
|
||||
off: $delegate.off,
|
||||
pin: $delegate.pin,
|
||||
get reflows() {
|
||||
return $$forceReflow.totalReflows;
|
||||
},
|
||||
enabled: $delegate.enabled,
|
||||
triggerCallbackEvents: function() {
|
||||
$$rAF.flush();
|
||||
},
|
||||
triggerCallbackPromise: function() {
|
||||
$timeout.flush(0);
|
||||
},
|
||||
triggerCallbacks: function() {
|
||||
this.triggerCallbackEvents();
|
||||
this.triggerCallbackPromise();
|
||||
flush: function() {
|
||||
$rootScope.$digest();
|
||||
|
||||
var doNextRun, somethingFlushed = false;
|
||||
do {
|
||||
doNextRun = false;
|
||||
|
||||
if ($$rAF.queue.length) {
|
||||
$$rAF.flush();
|
||||
doNextRun = somethingFlushed = true;
|
||||
}
|
||||
|
||||
if ($$animateAsyncRun.flush()) {
|
||||
doNextRun = somethingFlushed = true;
|
||||
}
|
||||
} while (doNextRun);
|
||||
|
||||
if (!somethingFlushed) {
|
||||
throw new Error('No pending animations ready to be closed or flushed');
|
||||
}
|
||||
|
||||
$rootScope.$digest();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -998,7 +1035,7 @@ angular.mock.dump = function(object) {
|
||||
$http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
|
||||
$scope.status = '';
|
||||
}).error(function() {
|
||||
$scope.status = 'ERROR!';
|
||||
$scope.status = 'Failed...';
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1733,28 +1770,28 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $
|
||||
}];
|
||||
|
||||
angular.mock.$RAFDecorator = ['$delegate', function($delegate) {
|
||||
var queue = [];
|
||||
var rafFn = function(fn) {
|
||||
var index = queue.length;
|
||||
queue.push(fn);
|
||||
var index = rafFn.queue.length;
|
||||
rafFn.queue.push(fn);
|
||||
return function() {
|
||||
queue.splice(index, 1);
|
||||
rafFn.queue.splice(index, 1);
|
||||
};
|
||||
};
|
||||
|
||||
rafFn.queue = [];
|
||||
rafFn.supported = $delegate.supported;
|
||||
|
||||
rafFn.flush = function() {
|
||||
if (queue.length === 0) {
|
||||
if (rafFn.queue.length === 0) {
|
||||
throw new Error('No rAF callbacks present');
|
||||
}
|
||||
|
||||
var length = queue.length;
|
||||
var length = rafFn.queue.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
queue[i]();
|
||||
rafFn.queue[i]();
|
||||
}
|
||||
|
||||
queue = queue.slice(i);
|
||||
rafFn.queue = rafFn.queue.slice(i);
|
||||
};
|
||||
|
||||
return rafFn;
|
||||
@@ -1802,7 +1839,7 @@ angular.mock.$RootElementProvider = function() {
|
||||
*
|
||||
* describe('myDirectiveController', function() {
|
||||
* it('should write the bound name to the log', inject(function($controller, $log) {
|
||||
* var ctrl = $controller('MyDirective', { /* no locals */ }, { name: 'Clark Kent' });
|
||||
* var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' });
|
||||
* expect(ctrl.name).toEqual('Clark Kent');
|
||||
* expect($log.info.logs).toEqual(['Clark Kent']);
|
||||
* });
|
||||
@@ -2234,8 +2271,9 @@ if (window.jasmine || window.mocha) {
|
||||
* @param {...(string|Function|Object)} fns any number of modules which are represented as string
|
||||
* aliases or as anonymous module initialization functions. The modules are used to
|
||||
* configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an
|
||||
* object literal is passed they will be registered as values in the module, the key being
|
||||
* the module name and the value being what is returned.
|
||||
* object literal is passed each key-value pair will be registered on the module via
|
||||
* {@link auto.$provide $provide}.value, the key being the string name (or token) to associate
|
||||
* with the value on the injector.
|
||||
*/
|
||||
window.module = angular.mock.module = function() {
|
||||
var moduleFns = Array.prototype.slice.call(arguments, 0);
|
||||
|
||||
@@ -17,7 +17,7 @@ function lookupDottedPath(obj, path) {
|
||||
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
|
||||
}
|
||||
var keys = path.split('.');
|
||||
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
|
||||
for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
|
||||
var key = keys[i];
|
||||
obj = (obj !== null) ? obj[key] : undefined;
|
||||
}
|
||||
@@ -348,6 +348,7 @@ function shallowClearAndCopy(src, dst) {
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
provider('$resource', function() {
|
||||
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
|
||||
var provider = this;
|
||||
|
||||
this.defaults = {
|
||||
@@ -422,7 +423,8 @@ angular.module('ngResource', ['ng']).
|
||||
var self = this,
|
||||
url = actionUrl || self.template,
|
||||
val,
|
||||
encodedVal;
|
||||
encodedVal,
|
||||
protocolAndDomain = '';
|
||||
|
||||
var urlParams = self.urlParams = {};
|
||||
forEach(url.split(/\W/), function(param) {
|
||||
@@ -435,6 +437,10 @@ angular.module('ngResource', ['ng']).
|
||||
}
|
||||
});
|
||||
url = url.replace(/\\:/g, ':');
|
||||
url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) {
|
||||
protocolAndDomain = match;
|
||||
return '';
|
||||
});
|
||||
|
||||
params = params || {};
|
||||
forEach(self.urlParams, function(_, urlParam) {
|
||||
@@ -465,7 +471,7 @@ angular.module('ngResource', ['ng']).
|
||||
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
|
||||
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
|
||||
// replace escaped `/\.` with `/.`
|
||||
config.url = url.replace(/\/\\\./, '/.');
|
||||
config.url = protocolAndDomain + url.replace(/\/\\\./, '/.');
|
||||
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
@@ -562,8 +568,17 @@ angular.module('ngResource', ['ng']).
|
||||
undefined;
|
||||
|
||||
forEach(action, function(value, key) {
|
||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') {
|
||||
httpConfig[key] = copy(value);
|
||||
switch (key) {
|
||||
default:
|
||||
httpConfig[key] = copy(value);
|
||||
break;
|
||||
case 'params':
|
||||
case 'isArray':
|
||||
case 'interceptor':
|
||||
break;
|
||||
case 'timeout':
|
||||
httpConfig[key] = value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
||||
}
|
||||
|
||||
.view-animate.ng-enter, .view-animate.ng-leave {
|
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
||||
|
||||
display:block;
|
||||
|
||||
@@ -293,7 +293,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
|
||||
}
|
||||
|
||||
function push(value) {
|
||||
if (value === undefined) {
|
||||
if (angular.isUndefined(value)) {
|
||||
value = '';
|
||||
} else if (typeof value !== 'string') {
|
||||
value = angular.toJson(value);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
if (!element) return;
|
||||
|
||||
eventData = eventData || {};
|
||||
var relatedTarget = eventData.relatedTarget || element;
|
||||
var keys = eventData.keys;
|
||||
var x = eventData.x;
|
||||
var y = eventData.y;
|
||||
@@ -84,7 +85,7 @@
|
||||
x = x || 0;
|
||||
y = y || 0;
|
||||
evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'),
|
||||
pressed('alt'), pressed('shift'), pressed('meta'), 0, element);
|
||||
pressed('alt'), pressed('shift'), pressed('meta'), 0, relatedTarget);
|
||||
}
|
||||
|
||||
/* we're unable to change the timeStamp value directly so this
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ function serializeObject(obj) {
|
||||
val = toJsonReplacer(key, val);
|
||||
if (isObject(val)) {
|
||||
|
||||
if (seen.indexOf(val) >= 0) return '<<already seen>>';
|
||||
if (seen.indexOf(val) >= 0) return '...';
|
||||
|
||||
seen.push(val);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ function serializeObject(obj) {
|
||||
function toDebugString(obj) {
|
||||
if (typeof obj === 'function') {
|
||||
return obj.toString().replace(/ \{[\s\S]*$/, '');
|
||||
} else if (typeof obj === 'undefined') {
|
||||
} else if (isUndefined(obj)) {
|
||||
return 'undefined';
|
||||
} else if (typeof obj !== 'string') {
|
||||
return serializeObject(obj);
|
||||
|
||||
@@ -313,11 +313,19 @@ describe('angular', function() {
|
||||
it('should throw an exception if a Scope is being copied', inject(function($rootScope) {
|
||||
expect(function() { copy($rootScope.$new()); }).
|
||||
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
expect(function() { copy({child: $rootScope.$new()}, {}); }).
|
||||
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
expect(function() { copy([$rootScope.$new()]); }).
|
||||
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}));
|
||||
|
||||
it('should throw an exception if a Window is being copied', function() {
|
||||
expect(function() { copy(window); }).
|
||||
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
expect(function() { copy({child: window}); }).
|
||||
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
expect(function() { copy([window], []); }).
|
||||
toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
});
|
||||
|
||||
it('should throw an exception when source and destination are equivalent', function() {
|
||||
@@ -334,6 +342,11 @@ describe('angular', function() {
|
||||
hashKey(src);
|
||||
dst = copy(src);
|
||||
expect(hashKey(dst)).not.toEqual(hashKey(src));
|
||||
|
||||
src = {foo: {}};
|
||||
hashKey(src.foo);
|
||||
dst = copy(src);
|
||||
expect(hashKey(src.foo)).not.toEqual(hashKey(dst.foo));
|
||||
});
|
||||
|
||||
it('should retain the previous $$hashKey when copying object with hashKey', function() {
|
||||
@@ -386,6 +399,18 @@ describe('angular', function() {
|
||||
expect(aCopy).toBe(aCopy.self);
|
||||
});
|
||||
|
||||
it('should deeply copy XML nodes', function() {
|
||||
var anElement = document.createElement('foo');
|
||||
anElement.appendChild(document.createElement('bar'));
|
||||
var theCopy = anElement.cloneNode(true);
|
||||
expect(copy(anElement).outerHTML).toEqual(theCopy.outerHTML);
|
||||
expect(copy(anElement)).not.toBe(anElement);
|
||||
});
|
||||
|
||||
it('should not try to call a non-function called `cloneNode`', function() {
|
||||
expect(copy.bind(null, { cloneNode: 100 })).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle objects with multiple references', function() {
|
||||
var b = {};
|
||||
var a = [b, -1, b];
|
||||
@@ -449,6 +474,7 @@ describe('angular', function() {
|
||||
});
|
||||
|
||||
describe("extend", function() {
|
||||
|
||||
it('should not copy the private $$hashKey', function() {
|
||||
var src,dst;
|
||||
src = {};
|
||||
@@ -459,6 +485,24 @@ describe('angular', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should copy the properties of the source object onto the destination object', function() {
|
||||
var destination, source;
|
||||
destination = {};
|
||||
source = {foo: true};
|
||||
destination = extend(destination, source);
|
||||
expect(isDefined(destination.foo)).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('ISSUE #4751 - should copy the length property of an object source to the destination object', function() {
|
||||
var destination, source;
|
||||
destination = {};
|
||||
source = {radius: 30, length: 0};
|
||||
destination = extend(destination, source);
|
||||
expect(isDefined(destination.length)).toBe(true);
|
||||
expect(isDefined(destination.radius)).toBe(true);
|
||||
});
|
||||
|
||||
it('should retain the previous $$hashKey', function() {
|
||||
var src,dst,h;
|
||||
src = {};
|
||||
@@ -491,6 +535,17 @@ describe('angular', function() {
|
||||
|
||||
expect(dst.date).toBe(src.date);
|
||||
});
|
||||
|
||||
it('should copy elements by reference', function() {
|
||||
var src = { element: document.createElement('div'),
|
||||
jqObject: jqLite("<p><span>s1</span><span>s2</span></p>").find("span") };
|
||||
var dst = {};
|
||||
|
||||
extend(dst, src);
|
||||
|
||||
expect(dst.element).toBe(src.element);
|
||||
expect(dst.jqObject).toBe(src.jqObject);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -581,6 +636,25 @@ describe('angular', function() {
|
||||
expect(isRegExp(dst.regexp)).toBe(true);
|
||||
expect(dst.regexp.toString()).toBe(src.regexp.toString());
|
||||
});
|
||||
|
||||
|
||||
it('should copy(clone) elements', function() {
|
||||
var src = {
|
||||
element: document.createElement('div'),
|
||||
jqObject: jqLite('<p><span>s1</span><span>s2</span></p>').find('span')
|
||||
};
|
||||
var dst = {};
|
||||
|
||||
merge(dst, src);
|
||||
|
||||
expect(dst.element).not.toBe(src.element);
|
||||
expect(dst.jqObject).not.toBe(src.jqObject);
|
||||
|
||||
expect(isElement(dst.element)).toBeTruthy();
|
||||
expect(dst.element.nodeName).toBeDefined(); // i.e it is a DOM element
|
||||
expect(isElement(dst.jqObject)).toBeTruthy();
|
||||
expect(dst.jqObject.nodeName).toBeUndefined(); // i.e it is a jqLite/jQuery object
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1023,6 +1097,42 @@ describe('angular', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isArrayLike', function() {
|
||||
|
||||
it('should return false if passed a number', function() {
|
||||
expect(isArrayLike(10)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true if passed an array', function() {
|
||||
expect(isArrayLike([1,2,3,4])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if passed an object', function() {
|
||||
expect(isArrayLike({0:"test", 1:"bob", 2:"tree", length:3})).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if passed arguments object', function() {
|
||||
function test(a,b,c) {
|
||||
expect(isArrayLike(arguments)).toBe(true);
|
||||
}
|
||||
test(1,2,3);
|
||||
});
|
||||
|
||||
it('should return true if passed a nodelist', function() {
|
||||
var nodes = document.body.childNodes;
|
||||
expect(isArrayLike(nodes)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for objects with `length` but no matching indexable items', function() {
|
||||
var obj = {
|
||||
a: 'a',
|
||||
b:'b',
|
||||
length: 10
|
||||
};
|
||||
expect(isArrayLike(obj)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('forEach', function() {
|
||||
it('should iterate over *own* object properties', function() {
|
||||
@@ -1062,6 +1172,11 @@ describe('angular', function() {
|
||||
|
||||
forEach(jqObject, function(value, key) { log.push(key + ':' + value.innerHTML); });
|
||||
expect(log).toEqual(['0:s1', '1:s2']);
|
||||
|
||||
log = [];
|
||||
jqObject = jqLite("<pane></pane>");
|
||||
forEach(jqObject.children(), function(value, key) { log.push(key + ':' + value.innerHTML); });
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
{{jqueryVersion}}
|
||||
|
||||
<script src="../../../../bower_components/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript">
|
||||
// Verify that empty ng-jq is not accessing `window['']`.
|
||||
// (See https://github.com/angular/angular.js/issues/12741 for more details)
|
||||
window[''] = window.jQuery;
|
||||
</script>
|
||||
<script src="angular.js"></script>
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global jQuery: true, uid: true */
|
||||
/* global jQuery: true, uid: true, jqCache: true */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
@@ -12,6 +12,7 @@ if (window._jQuery) _jQuery.event.special.change = undefined;
|
||||
if (window.bindJQuery) bindJQuery();
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
// all this stuff is not needed for module tests, where jqlite and publishExternalAPI and jqLite are not global vars
|
||||
if (window.publishExternalAPI) {
|
||||
publishExternalAPI(angular);
|
||||
@@ -28,7 +29,10 @@ beforeEach(function() {
|
||||
|
||||
// reset to jQuery or default to us.
|
||||
bindJQuery();
|
||||
jqLiteCacheSizeInit();
|
||||
|
||||
// Clear the cache to prevent memory leak failures from previous tests
|
||||
// breaking subsequent tests unnecessarily
|
||||
jqCache = jqLite.cache = {};
|
||||
}
|
||||
|
||||
angular.element(document.body).empty().removeData();
|
||||
@@ -47,6 +51,7 @@ afterEach(function() {
|
||||
if (bod) {
|
||||
bod.$$hashKey = null;
|
||||
}
|
||||
document.$$hashKey = null;
|
||||
|
||||
if (this.$injector) {
|
||||
var $rootScope = this.$injector.get('$rootScope');
|
||||
@@ -83,7 +88,6 @@ afterEach(function() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// copied from Angular.js
|
||||
// we need this method here so that we can run module tests with wrapped angular.js
|
||||
function forEachSorted(obj, iterator, context) {
|
||||
@@ -132,14 +136,7 @@ function dealoc(obj) {
|
||||
|
||||
|
||||
function jqLiteCacheSize() {
|
||||
var size = 0;
|
||||
forEach(jqLite.cache, function() { size++; });
|
||||
return size - jqLiteCacheSize.initSize;
|
||||
}
|
||||
jqLiteCacheSize.initSize = 0;
|
||||
|
||||
function jqLiteCacheSizeInit() {
|
||||
jqLiteCacheSize.initSize = jqLiteCacheSize.initSize + jqLiteCacheSize();
|
||||
return Object.keys(jqLite.cache).length;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+135
-30
@@ -78,6 +78,33 @@ describe('jqLite', function() {
|
||||
});
|
||||
|
||||
|
||||
// This is not working correctly in jQuery prior to v3.0.
|
||||
// See https://github.com/jquery/jquery/issues/1987 for details.
|
||||
it('should properly handle dash-delimited node names', function() {
|
||||
var jQueryVersion = window.jQuery && window.jQuery.fn.jquery.split('.')[0];
|
||||
var jQuery3xOrNewer = jQueryVersion && (Number(jQueryVersion) >= 3);
|
||||
|
||||
if (_jqLiteMode || jQuery3xOrNewer) {
|
||||
var nodeNames = 'thead tbody tfoot colgroup caption tr th td div kung'.split(' ');
|
||||
var nodeNamesTested = 0;
|
||||
var nodes, customNodeName;
|
||||
|
||||
forEach(nodeNames, function(nodeName) {
|
||||
var customNodeName = nodeName + '-foo';
|
||||
var nodes = jqLite('<' + customNodeName + '>Hello, world !</' + customNodeName + '>');
|
||||
|
||||
expect(nodes.length).toBe(1);
|
||||
expect(nodeName_(nodes)).toBe(customNodeName);
|
||||
expect(nodes.html()).toBe('Hello, world !');
|
||||
|
||||
nodeNamesTested++;
|
||||
});
|
||||
|
||||
expect(nodeNamesTested).toBe(10);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should allow creation of comment tags', function() {
|
||||
var nodes = jqLite('<!-- foo -->');
|
||||
expect(nodes.length).toBe(1);
|
||||
@@ -1166,21 +1193,45 @@ describe('jqLite', function() {
|
||||
});
|
||||
|
||||
describe('mouseenter-mouseleave', function() {
|
||||
var root, parent, sibling, child, log;
|
||||
var root, parent, child, log;
|
||||
|
||||
beforeEach(function() {
|
||||
function setup(html, parentNode, childNode) {
|
||||
log = '';
|
||||
root = jqLite('<div>root<p>parent<span>child</span></p><ul></ul></div>');
|
||||
parent = root.find('p');
|
||||
sibling = root.find('ul');
|
||||
child = parent.find('span');
|
||||
root = jqLite(html);
|
||||
parent = root.find(parentNode);
|
||||
child = parent.find(childNode);
|
||||
|
||||
parent.on('mouseenter', function() { log += 'parentEnter;'; });
|
||||
parent.on('mouseleave', function() { log += 'parentLeave;'; });
|
||||
|
||||
child.on('mouseenter', function() { log += 'childEnter;'; });
|
||||
child.on('mouseleave', function() { log += 'childLeave;'; });
|
||||
});
|
||||
}
|
||||
|
||||
function browserMoveTrigger(from, to) {
|
||||
var fireEvent = function(type, element, relatedTarget) {
|
||||
var evnt;
|
||||
evnt = document.createEvent('MouseEvents');
|
||||
|
||||
var originalPreventDefault = evnt.preventDefault,
|
||||
appWindow = window,
|
||||
fakeProcessDefault = true,
|
||||
finalProcessDefault;
|
||||
|
||||
evnt.preventDefault = function() {
|
||||
fakeProcessDefault = false;
|
||||
return originalPreventDefault.apply(evnt, arguments);
|
||||
};
|
||||
|
||||
var x = 0, y = 0;
|
||||
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
|
||||
false, false, 0, relatedTarget);
|
||||
|
||||
element.dispatchEvent(evnt);
|
||||
};
|
||||
fireEvent('mouseout', from[0], to[0]);
|
||||
fireEvent('mouseover', to[0], from[0]);
|
||||
}
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(root);
|
||||
@@ -1188,30 +1239,8 @@ describe('jqLite', function() {
|
||||
|
||||
it('should fire mouseenter when coming from outside the browser window', function() {
|
||||
if (window.jQuery) return;
|
||||
var browserMoveTrigger = function(from, to) {
|
||||
var fireEvent = function(type, element, relatedTarget) {
|
||||
var evnt;
|
||||
evnt = document.createEvent('MouseEvents');
|
||||
|
||||
var originalPreventDefault = evnt.preventDefault,
|
||||
appWindow = window,
|
||||
fakeProcessDefault = true,
|
||||
finalProcessDefault;
|
||||
|
||||
evnt.preventDefault = function() {
|
||||
fakeProcessDefault = false;
|
||||
return originalPreventDefault.apply(evnt, arguments);
|
||||
};
|
||||
|
||||
var x = 0, y = 0;
|
||||
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
|
||||
false, false, 0, relatedTarget);
|
||||
|
||||
element.dispatchEvent(evnt);
|
||||
};
|
||||
fireEvent('mouseout', from[0], to[0]);
|
||||
fireEvent('mouseover', to[0], from[0]);
|
||||
};
|
||||
setup('<div>root<p>parent<span>child</span></p><ul></ul></div>', 'p', 'span');
|
||||
|
||||
browserMoveTrigger(root, parent);
|
||||
expect(log).toEqual('parentEnter;');
|
||||
@@ -1226,6 +1255,28 @@ describe('jqLite', function() {
|
||||
expect(log).toEqual('parentEnter;childEnter;childLeave;parentLeave;');
|
||||
|
||||
});
|
||||
|
||||
it('should fire the mousenter on SVG elements', function() {
|
||||
if (window.jQuery) return;
|
||||
|
||||
setup(
|
||||
'<div>' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg"' +
|
||||
' viewBox="0 0 18.75 18.75"' +
|
||||
' width="18.75"' +
|
||||
' height="18.75"' +
|
||||
' version="1.1">' +
|
||||
' <path d="M0,0c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5-3.358-7.5-7.5-7.5-7.5,3.358-7.5,7.5"' +
|
||||
' fill-rule="nonzero"' +
|
||||
' fill="#CCC"' +
|
||||
' ng-attr-fill="{{data.color || \'#CCC\'}}"/>' +
|
||||
'</svg>' +
|
||||
'</div>',
|
||||
'svg', 'path');
|
||||
|
||||
browserMoveTrigger(parent, child);
|
||||
expect(log).toEqual('childEnter;');
|
||||
});
|
||||
});
|
||||
|
||||
// Only run this test for jqLite and not normal jQuery
|
||||
@@ -1380,6 +1431,60 @@ describe('jqLite', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should correctly deregister the mouseenter/mouseleave listeners', function() {
|
||||
var aElem = jqLite(a);
|
||||
var onMouseenter = jasmine.createSpy('onMouseenter');
|
||||
var onMouseleave = jasmine.createSpy('onMouseleave');
|
||||
|
||||
aElem.on('mouseenter', onMouseenter);
|
||||
aElem.on('mouseleave', onMouseleave);
|
||||
aElem.off('mouseenter', onMouseenter);
|
||||
aElem.off('mouseleave', onMouseleave);
|
||||
aElem.on('mouseenter', onMouseenter);
|
||||
aElem.on('mouseleave', onMouseleave);
|
||||
|
||||
browserTrigger(a, 'mouseover', {relatedTarget: b});
|
||||
expect(onMouseenter).toHaveBeenCalledOnce();
|
||||
|
||||
browserTrigger(a, 'mouseout', {relatedTarget: b});
|
||||
expect(onMouseleave).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should call a `mouseenter/leave` listener only once when `mouseenter/leave` and `mouseover/out` '
|
||||
+ 'are triggered simultaneously', function() {
|
||||
var aElem = jqLite(a);
|
||||
var onMouseenter = jasmine.createSpy('mouseenter');
|
||||
var onMouseleave = jasmine.createSpy('mouseleave');
|
||||
|
||||
aElem.on('mouseenter', onMouseenter);
|
||||
aElem.on('mouseleave', onMouseleave);
|
||||
|
||||
browserTrigger(a, 'mouseenter', {relatedTarget: b});
|
||||
browserTrigger(a, 'mouseover', {relatedTarget: b});
|
||||
expect(onMouseenter).toHaveBeenCalledOnce();
|
||||
|
||||
browserTrigger(a, 'mouseleave', {relatedTarget: b});
|
||||
browserTrigger(a, 'mouseout', {relatedTarget: b});
|
||||
expect(onMouseleave).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should call a `mouseenter/leave` listener when manually triggering the event', function() {
|
||||
var aElem = jqLite(a);
|
||||
var onMouseenter = jasmine.createSpy('mouseenter');
|
||||
var onMouseleave = jasmine.createSpy('mouseleave');
|
||||
|
||||
aElem.on('mouseenter', onMouseenter);
|
||||
aElem.on('mouseleave', onMouseleave);
|
||||
|
||||
aElem.triggerHandler('mouseenter');
|
||||
expect(onMouseenter).toHaveBeenCalledOnce();
|
||||
|
||||
aElem.triggerHandler('mouseleave');
|
||||
expect(onMouseleave).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should deregister specific listener within the listener and call subsequent listeners', function() {
|
||||
var aElem = jqLite(a),
|
||||
clickSpy = jasmine.createSpy('click'),
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ describe('minErr', function() {
|
||||
a.b.a = a;
|
||||
|
||||
var myError = testError('26', 'a is {0}', a);
|
||||
expect(myError.message).toMatch(/a is {"b":{"a":"<<already seen>>"}}/);
|
||||
expect(myError.message).toMatch(/a is {"b":{"a":"..."}}/);
|
||||
});
|
||||
|
||||
it('should preserve interpolation markers when fewer arguments than needed are provided', function() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user