Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6afe1b208 | |||
| c8700f04fb | |||
| 73ab107a1e | |||
| 9dfa949dad | |||
| a057e0896a | |||
| 948120ecdb | |||
| 7757f0a9e3 | |||
| 560566f396 | |||
| d8d30ce676 | |||
| d996305b44 | |||
| 79fa7ddf8d | |||
| 3621dbc100 | |||
| 65bed615df | |||
| 6c8464ad14 | |||
| 731c8b5e2d | |||
| 06a9f0a95f | |||
| 2cdb2016b9 | |||
| 10ae33b2d8 | |||
| c55a494433 | |||
| ffbeb32172 | |||
| b5002ab62a | |||
| 393f50324c | |||
| 0a9c4681c2 | |||
| 50bd7059e1 | |||
| 36fd167e1d | |||
| da75d138b1 | |||
| 171b9f7f23 | |||
| 73f3515ba7 | |||
| 7e5c447fa9 | |||
| 4ba43d2e6f | |||
| f353db9d86 | |||
| b2b33608a3 | |||
| 448e789142 | |||
| b8dbdb0c5e | |||
| 67af519764 | |||
| 4f12ed0e4e | |||
| afd0807520 | |||
| 6a6f403f24 | |||
| 7811eadac7 | |||
| e5d1d6587d | |||
| 8c52f1daf4 | |||
| ee6a3ccd24 | |||
| e0e40e8c0a | |||
| f8c8cf698a | |||
| 9f7a80c53d | |||
| bea99e34a4 | |||
| c7ebce6fb8 | |||
| 41428477ed | |||
| cc4213f03e | |||
| 7c9ad277ad | |||
| 4588e627bb | |||
| ea9fd82ce1 | |||
| 44337f63fa | |||
| 4b7a46adad | |||
| 1af563d43e | |||
| 1e58488ad6 | |||
| 170ff9a37d | |||
| e57138d7ef | |||
| 9093efe062 | |||
| 8028315640 | |||
| 9900610eea | |||
| db866f1f86 | |||
| d8492f4331 | |||
| 67688d5ca0 | |||
| 9e8a687c37 | |||
| 3613a6007c | |||
| 2979bf38dd | |||
| 4c613a2603 | |||
| e6688ec280 | |||
| 4fdac4d22f | |||
| 2f4be3e2b9 | |||
| 3086fcd644 | |||
| 6a03ca2743 | |||
| 210c184184 | |||
| 1846572d6f | |||
| 1c3bbada27 | |||
| 891b364c1b | |||
| 67297de109 | |||
| 68146cc092 | |||
| c01b1f47c0 | |||
| 1fc87e2b6a | |||
| 9b2a4c6851 | |||
| 2b4dfa9e2b | |||
| 68dbbfbf32 | |||
| 4acb0af24c | |||
| bb5bf7f816 | |||
| 1924cf2216 | |||
| fb7db4a07b | |||
| 4f1f9cfdb7 | |||
| 3a4b6b83ef | |||
| 41fdb3d536 | |||
| 770a4ddcc6 | |||
| f6b51fc0a2 | |||
| f227f7a5af | |||
| 212975af96 | |||
| abf87673e5 | |||
| 248b036888 | |||
| 3fd48742b0 | |||
| 17f02d5bb2 | |||
| 342e5f3ce3 | |||
| 44e9d2ca6c | |||
| 7705edc0da | |||
| d02d0585a0 | |||
| 2d0eda10e4 | |||
| 4374f892c6 | |||
| caa0b9dab3 | |||
| 0c541cfb2a | |||
| 2404b77e48 | |||
| 8783453784 | |||
| e650c45894 | |||
| 82000111dc | |||
| ddc612056e | |||
| 0413bee8cc | |||
| c0498d45fe | |||
| f591776313 | |||
| f8c4216170 | |||
| 4501da327d | |||
| 9b35dfb658 | |||
| 53c6636991 | |||
| 92c366d205 | |||
| 38fbe3ee83 | |||
| 997fdea1ee | |||
| 76b1b2bec2 | |||
| c62fa6bd89 | |||
| 3b8163b7b6 | |||
| 2907a0288b | |||
| bb365070a3 | |||
| 0f50b01cc7 | |||
| 29cdaee2b6 | |||
| 410f7c6826 | |||
| 9d071b2fc0 | |||
| 7cfa79e98e | |||
| f22e1fc961 | |||
| 80f139b860 | |||
| 3d149c7f20 | |||
| 2c4ffd6af4 | |||
| d7ec5f392e | |||
| bfd7b227db | |||
| 581ee9d0b6 | |||
| 57aa00e5dc | |||
| 958bc1ab77 | |||
| 3bc429ad9f | |||
| d8832d5527 | |||
| f6d0ac5bc8 | |||
| 0356d72cd9 | |||
| a773f89bc9 | |||
| f497358df1 | |||
| 500d352901 | |||
| 05ae2815dc | |||
| f88178323a | |||
| 76edec7f9b | |||
| 367c7d90d4 | |||
| 750d06bc25 | |||
| efb74642a1 | |||
| ec27ce7198 | |||
| 46b80654ca | |||
| c67f88b26c | |||
| cbedff0619 | |||
| 5cdefba1b4 | |||
| b299e73130 | |||
| 16f12c86f6 | |||
| a9e02de5e2 | |||
| 0a8e113542 | |||
| 3f09847b73 | |||
| 7a52da6c63 | |||
| 5da1256fc2 | |||
| 910de49399 | |||
| 7fe139af5a | |||
| 507ee2d9ba | |||
| ef894c87ea | |||
| 30b48132e0 | |||
| da9eac8660 | |||
| 5b5228675f | |||
| aaae3cc416 | |||
| 49b54b0d77 | |||
| f627233312 | |||
| 75725b44f8 | |||
| fa0aa83937 | |||
| bd6c04a112 | |||
| 056a317008 | |||
| 27fcca9a27 | |||
| 2015ed2341 | |||
| 51faaffdbc | |||
| 3c6a0e568d | |||
| cc744412b3 | |||
| c9a4421fc3 | |||
| 732776f5f3 | |||
| c211e7a5ad | |||
| d6eba21733 | |||
| c1199fb6b0 | |||
| 06c39a033e | |||
| 9d53e5a38d | |||
| 030a42e79d | |||
| 898714df9e | |||
| 966f6d831f | |||
| 28114faff4 | |||
| cc8755cda6 | |||
| 7b7b56d36d | |||
| 9278ae67c6 | |||
| a509e9aa14 | |||
| 09ee82d84d | |||
| c1cf053f37 |
+14
-2
@@ -2,6 +2,12 @@ language: node_js
|
||||
node_js:
|
||||
- '0.10'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- bower_components
|
||||
- docs/bower_components
|
||||
|
||||
branches:
|
||||
except:
|
||||
- /^g3_.*$/
|
||||
@@ -27,15 +33,21 @@ env:
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
|
||||
|
||||
install:
|
||||
# Check the size of caches
|
||||
- du -sh ./node_modules ./bower_components/ ./docs/bower_components/ || true
|
||||
# - npm config set registry http://23.251.144.68
|
||||
# Disable the spinner, it looks bad on Travis
|
||||
- npm config set spin false
|
||||
# Log HTTP requests
|
||||
- npm config set loglevel http
|
||||
- time ./scripts/travis/npm-bundle-deps.sh
|
||||
- time npm install
|
||||
- npm install -g npm@2.5
|
||||
# Instal npm dependecies and ensure that npm cache is not stale
|
||||
- scripts/npm/install-dependencies.sh
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
|
||||
+524
-2
@@ -1,3 +1,517 @@
|
||||
<a name="v1.4.0-rc.0"></a>
|
||||
# v1.4.0-rc.0 smooth-unwinding (2015-04-10)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- throw error on invalid directive name
|
||||
([170ff9a3](https://github.com/angular/angular.js/commit/170ff9a37dea8772dda7c89e84176ac1a8992878),
|
||||
[#11281](https://github.com/angular/angular.js/issues/11281), [#11109](https://github.com/angular/angular.js/issues/11109))
|
||||
- update data() when controller returns custom value
|
||||
([9900610e](https://github.com/angular/angular.js/commit/9900610eea4ece87b063f2aa9d82c75c369927df),
|
||||
[#11147](https://github.com/angular/angular.js/issues/11147), [#11326](https://github.com/angular/angular.js/issues/11326))
|
||||
- **$http:** throw error if `success` and `error` methods do not receive a function
|
||||
([1af563d4](https://github.com/angular/angular.js/commit/1af563d43e74cb7be53e815b66fd91dd93986ed6),
|
||||
[#11330](https://github.com/angular/angular.js/issues/11330), [#11333](https://github.com/angular/angular.js/issues/11333))
|
||||
- **$parse:** fix parse errors on older Android WebViews which choke with reserved keywords
|
||||
([10ae33b2](https://github.com/angular/angular.js/commit/10ae33b2d88b04df76f519edc50a47fa30f83e96),
|
||||
[#11455](https://github.com/angular/angular.js/issues/11455))
|
||||
- **$rootScope:** allow destroying a root scope
|
||||
([f8c8cf69](https://github.com/angular/angular.js/commit/f8c8cf698aa23640249d79fd405605694478e4f7),
|
||||
[#11241](https://github.com/angular/angular.js/issues/11241), [#10895](https://github.com/angular/angular.js/issues/10895))
|
||||
- **cookieReader:** safely access $document so it can be mocked
|
||||
([a057e089](https://github.com/angular/angular.js/commit/a057e0896a7fe2fdaba50b2515555b86e4f4be27),
|
||||
[#11373](https://github.com/angular/angular.js/issues/11373), [#11388](https://github.com/angular/angular.js/issues/11388))
|
||||
- **filterFilter:** fix matching against `null`/`undefined`
|
||||
([b5002ab6](https://github.com/angular/angular.js/commit/b5002ab62ad6e13f4339e20106e1fdece14912a2),
|
||||
[#11432](https://github.com/angular/angular.js/issues/11432), [#11445](https://github.com/angular/angular.js/issues/11445))
|
||||
- **ngAnimate:** ensure that minified repaint code isn't removed
|
||||
([c55a4944](https://github.com/angular/angular.js/commit/c55a494433e619aad0c7ef9fddadc0b3fdf53915),
|
||||
[#9936](https://github.com/angular/angular.js/issues/9936))
|
||||
- **ngAria:** handle elements with role="checkbox/menuitemcheckbox"
|
||||
([44337f63](https://github.com/angular/angular.js/commit/44337f63fa94116795e83e3a764a6ba6782809c7),
|
||||
[#11317](https://github.com/angular/angular.js/issues/11317), [#11321](https://github.com/angular/angular.js/issues/11321))
|
||||
- **ngModel:** allow setting model to NaN when asyncValidator is present
|
||||
([948120ec](https://github.com/angular/angular.js/commit/948120ecdbc4dd07880c0107564c50c7675b8a93),
|
||||
[#11315](https://github.com/angular/angular.js/issues/11315), [#11411](https://github.com/angular/angular.js/issues/11411))
|
||||
- **ngTouch:** register touches properly when jQuery is used
|
||||
([06a9f0a9](https://github.com/angular/angular.js/commit/06a9f0a95f0e72fa2e9879fe8a49e9bf69986a5f),
|
||||
[#4001](https://github.com/angular/angular.js/issues/4001), [#8584](https://github.com/angular/angular.js/issues/8584), [#10797](https://github.com/angular/angular.js/issues/10797), [#11488](https://github.com/angular/angular.js/issues/11488))
|
||||
- **select:** don't call $render twice if $viewValue ref changes
|
||||
([7e5c447f](https://github.com/angular/angular.js/commit/7e5c447fa9ad7d81cc818d6e79392c3e4a6b23a0),
|
||||
[#11329](https://github.com/angular/angular.js/issues/11329), [#11412](https://github.com/angular/angular.js/issues/11412))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$anchorScroll:** allow scrolling to a specified element
|
||||
([731c8b5e](https://github.com/angular/angular.js/commit/731c8b5e2d01a44aa91f967f1a6acbadb8005a8b),
|
||||
[#4568](https://github.com/angular/angular.js/issues/4568), [#9596](https://github.com/angular/angular.js/issues/9596))
|
||||
- **$animate:** complete refactor of internal animation code
|
||||
([c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef))
|
||||
- **$http:** support custom params serializers
|
||||
([6c8464ad](https://github.com/angular/angular.js/commit/6c8464ad14dd308349f632245c1a064c9aae242a),
|
||||
[#3740](https://github.com/angular/angular.js/issues/3740), [#7429](https://github.com/angular/angular.js/issues/7429), [#9224](https://github.com/angular/angular.js/issues/9224), [#11461](https://github.com/angular/angular.js/issues/11461))
|
||||
- **$interpolate:** extend interpolation with MessageFormat like syntax
|
||||
([1e58488a](https://github.com/angular/angular.js/commit/1e58488ad65abf7031bab5813523bb9d86dbd28c),
|
||||
[#11152](https://github.com/angular/angular.js/issues/11152))
|
||||
- **angular.Module:** add `decorator` method
|
||||
([e57138d7](https://github.com/angular/angular.js/commit/e57138d7eff1210f99238c475fff57530bf0ab19),
|
||||
[#11305](https://github.com/angular/angular.js/issues/11305), [#11300](https://github.com/angular/angular.js/issues/11300))
|
||||
- **ngClass:** add support for conditional map within an array.
|
||||
([4588e627](https://github.com/angular/angular.js/commit/4588e627bb7238b2113241919b948d0e5166c76d),
|
||||
[#4807](https://github.com/angular/angular.js/issues/4807))
|
||||
- **travis:** run unit tests on iOS 8
|
||||
([2cdb2016](https://github.com/angular/angular.js/commit/2cdb2016b9d89abfb5ab988b67d5f26f3bf21908),
|
||||
[#11479](https://github.com/angular/angular.js/issues/11479))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$rootScope:** remove history event handler when app is torn down
|
||||
([d996305b](https://github.com/angular/angular.js/commit/d996305b4470f80fbb1cbddf54b7d10ffbb6ab47),
|
||||
[#9897](https://github.com/angular/angular.js/issues/9897), [#9905](https://github.com/angular/angular.js/issues/9905))
|
||||
- **benchmark:** add ngmodel benchmarks to largetable-bp
|
||||
([b8dbdb0c](https://github.com/angular/angular.js/commit/b8dbdb0c5e2cd176c6d94d60f781cfc02e646592),
|
||||
[#11082](https://github.com/angular/angular.js/issues/11082))
|
||||
- **ngOptions:** only perform deep equality check on ngModel if using track by
|
||||
([171b9f7f](https://github.com/angular/angular.js/commit/171b9f7f2339ef9047b8526b2c3f36bb58d14feb),
|
||||
[#11448](https://github.com/angular/angular.js/issues/11448), [#11447](https://github.com/angular/angular.js/issues/11447))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
|
||||
JavaSript and CSS animations can no longer be run in
|
||||
parallel. With earlier versions of ngAnimate, both CSS and JS animations
|
||||
would be run together when multiple animations were detected. This
|
||||
feature has now been removed, however, the same effect, with even more
|
||||
possibilities, can be achieved by injecting `$animateCss` into a
|
||||
JavaScript-defined animation and creating custom CSS-based animations
|
||||
from there. Read the ngAnimate docs for more info.
|
||||
|
||||
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
|
||||
The function params for `$animate.enabled()` when an
|
||||
element is used are now flipped. This fix allows the function to act as
|
||||
a getter when a single element param is provided.
|
||||
|
||||
```js
|
||||
// < 1.4
|
||||
$animate.enabled(false, element);
|
||||
|
||||
// 1.4+
|
||||
$animate.enabled(element, false);
|
||||
```
|
||||
|
||||
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
|
||||
In addition to disabling the children of the element,
|
||||
`$animate.enabled(element, false)` will now also disable animations on
|
||||
the element itself.
|
||||
|
||||
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
|
||||
Animation-related callbacks are now fired on
|
||||
`$animate.on` instead of directly being on the element.
|
||||
|
||||
```js
|
||||
// < 1.4
|
||||
element.on('$animate:before', function(e, data) {
|
||||
if (data.event === 'enter') { ... }
|
||||
});
|
||||
element.off('$animate:before', fn);
|
||||
|
||||
// 1.4+
|
||||
$animate.on(element, 'enter', function(data) {
|
||||
//...
|
||||
});
|
||||
$animate.off(element, 'enter', fn);
|
||||
```
|
||||
|
||||
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
|
||||
There is no need to call `$scope.$apply` or
|
||||
`$scope.$digest` inside of a animation promise callback anymore
|
||||
since the promise is resolved within a digest automatically (but a
|
||||
digest is not run unless the promise is chained).
|
||||
|
||||
```js
|
||||
// < 1.4
|
||||
$animate.enter(element).then(function() {
|
||||
$scope.$apply(function() {
|
||||
$scope.explode = true;
|
||||
});
|
||||
});
|
||||
|
||||
// 1.4+
|
||||
$animate.enter(element).then(function() {
|
||||
$scope.explode = true;
|
||||
});
|
||||
```
|
||||
|
||||
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
|
||||
When an enter, leave or move animation is triggered then it
|
||||
will always end any pending or active parent class based animations
|
||||
(animations triggered via ngClass) in order to ensure that any CSS
|
||||
styles are resolved in time.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.6"></a>
|
||||
# 1.4.0-beta.6 cookie-liberation (2015-03-17)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** call `applyStyles` from options on `leave`
|
||||
([4374f892](https://github.com/angular/angular.js/commit/4374f892c6fa4af6ba1f2ed47c5f888fdb5fadc5),
|
||||
[#10068](https://github.com/angular/angular.js/issues/10068))
|
||||
- **$browser:** don't crash if `history.state` access causes error in IE
|
||||
([3b8163b7](https://github.com/angular/angular.js/commit/3b8163b7b664f24499e75460ab50c066eaec0f78),
|
||||
[#10367](https://github.com/angular/angular.js/issues/10367), [#10369](https://github.com/angular/angular.js/issues/10369))
|
||||
- **$sanitize:** disallow unsafe svg animation tags
|
||||
([67688d5c](https://github.com/angular/angular.js/commit/67688d5ca00f6de4c7fe6084e2fa762a00d25610),
|
||||
[#11290](https://github.com/angular/angular.js/issues/11290))
|
||||
- **Angular:** properly compare RegExp with other objects for equality
|
||||
([f22e1fc9](https://github.com/angular/angular.js/commit/f22e1fc9610ae111a3ea8746a3a57169c99ce142),
|
||||
[#11204](https://github.com/angular/angular.js/issues/11204), [#11205](https://github.com/angular/angular.js/issues/11205))
|
||||
- **date filter:** display localised era for `G` format codes
|
||||
([2b4dfa9e](https://github.com/angular/angular.js/commit/2b4dfa9e2b63d7ebb78f3b0fd3439d18f932e1cd),
|
||||
[#10503](https://github.com/angular/angular.js/issues/10503), [#11266](https://github.com/angular/angular.js/issues/11266))
|
||||
- **filterFilter:**
|
||||
- fix filtering using an object expression when the filter value is undefined
|
||||
([c62fa6bd](https://github.com/angular/angular.js/commit/c62fa6bd898e1048d4690d41034489dc60ba6ac2),
|
||||
[#10419](https://github.com/angular/angular.js/issues/10419), [#10424](https://github.com/angular/angular.js/issues/10424))
|
||||
- do not throw an error if property is null when comparing objects
|
||||
([2c4ffd6a](https://github.com/angular/angular.js/commit/2c4ffd6af4eb012c4054fe7c096267bbc5510af0),
|
||||
[#10991](https://github.com/angular/angular.js/issues/10991), [#10992](https://github.com/angular/angular.js/issues/10992), [#11116](https://github.com/angular/angular.js/issues/11116))
|
||||
- **form:** allow dynamic form names which initially evaluate to blank
|
||||
([410f7c68](https://github.com/angular/angular.js/commit/410f7c682633c681be641cd2a321f9e51671d474))
|
||||
- **jqLite:** attr should ignore comment, text and attribute nodes
|
||||
([bb5bf7f8](https://github.com/angular/angular.js/commit/bb5bf7f8162d11610a53428e630b47030bdc38e5))
|
||||
- **ng/$locale:** add ERA info in generic locale
|
||||
([4acb0af2](https://github.com/angular/angular.js/commit/4acb0af24c7fb3705a197ca96adc532de4766a7a))
|
||||
- **ngJq:** don't rely on existence of jqlite
|
||||
([342e5f3c](https://github.com/angular/angular.js/commit/342e5f3ce38d2fd10c5d5a98ca66f864286a7922),
|
||||
[#11044](https://github.com/angular/angular.js/issues/11044))
|
||||
- **ngMessages:** ensure that multi-level transclusion works with `ngMessagesInclude`
|
||||
([d7ec5f39](https://github.com/angular/angular.js/commit/d7ec5f392e1550658ddf271a30627b1749eccb69),
|
||||
[#11196](https://github.com/angular/angular.js/issues/11196))
|
||||
- **ngOptions:** fix model<->option interaction when using `track by`
|
||||
([6a03ca27](https://github.com/angular/angular.js/commit/6a03ca274314352052c3082163367a146bb11c2d),
|
||||
[#10869](https://github.com/angular/angular.js/issues/10869), [#10893](https://github.com/angular/angular.js/issues/10893))
|
||||
- **rootScope:** prevent memory leak when destroying scopes
|
||||
([fb7db4a0](https://github.com/angular/angular.js/commit/fb7db4a07bd1b0b67824d3808fe315419b272689),
|
||||
[#11173](https://github.com/angular/angular.js/issues/11173), [#11169](https://github.com/angular/angular.js/issues/11169))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$cookies:**
|
||||
- allow passing cookie options
|
||||
([92c366d2](https://github.com/angular/angular.js/commit/92c366d205da36ec26502aded23db71a6473dad7),
|
||||
[#8324](https://github.com/angular/angular.js/issues/8324), [#3988](https://github.com/angular/angular.js/issues/3988), [#1786](https://github.com/angular/angular.js/issues/1786), [#950](https://github.com/angular/angular.js/issues/950))
|
||||
- move logic into $cookies and deprecate $cookieStore
|
||||
([38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
|
||||
[#6411](https://github.com/angular/angular.js/issues/6411), [#7631](https://github.com/angular/angular.js/issues/7631))
|
||||
- **$cookiesProvider:** provide path, domain, expires and secure options
|
||||
([53c66369](https://github.com/angular/angular.js/commit/53c663699126815eabc2a3bc1e3bafc8b3874268))
|
||||
- **$interval:** pass additional arguments to the callback
|
||||
([4f1f9cfd](https://github.com/angular/angular.js/commit/4f1f9cfdb721cf308ca1162b2227836dc1d28388),
|
||||
[#10632](https://github.com/angular/angular.js/issues/10632))
|
||||
- **$timeout:** pass additional arguments to the callback
|
||||
([3a4b6b83](https://github.com/angular/angular.js/commit/3a4b6b83efdb8051e5c4803c0892c19ceb2cba50),
|
||||
[#10631](https://github.com/angular/angular.js/issues/10631))
|
||||
- **angular.merge:** provide an alternative to `angular.extend` that merges 'deeply'
|
||||
([c0498d45](https://github.com/angular/angular.js/commit/c0498d45feb913c318224ea70b5adf7112df6bac),
|
||||
[#10507](https://github.com/angular/angular.js/issues/10507), [#10519](https://github.com/angular/angular.js/issues/10519))
|
||||
- **filterFilter:** compare object with custom `toString()` to primitive
|
||||
([f8c42161](https://github.com/angular/angular.js/commit/f8c421617096a8d613f4eb6d0f5b098ee149c029),
|
||||
[#10464](https://github.com/angular/angular.js/issues/10464), [#10548](https://github.com/angular/angular.js/issues/10548))
|
||||
- **ngAria:**
|
||||
- add `button` role to `ngClick`
|
||||
([bb365070](https://github.com/angular/angular.js/commit/bb365070a3ed7c2d26056d378ab6a8ef493b23cc),
|
||||
[#9254](https://github.com/angular/angular.js/issues/9254), [#10318](https://github.com/angular/angular.js/issues/10318))
|
||||
- add roles to custom inputs
|
||||
([29cdaee2](https://github.com/angular/angular.js/commit/29cdaee2b6e853bc3f8882a00661698d146ecd18),
|
||||
[#10012](https://github.com/angular/angular.js/issues/10012), [#10318](https://github.com/angular/angular.js/issues/10318))
|
||||
- **ngLocale:** Add FIRSTDAYOFWEEK and WEEKENDRANGE from google data
|
||||
([3d149c7f](https://github.com/angular/angular.js/commit/3d149c7f20ffabab5a635af9ddcfc7105112ab4a))
|
||||
- **ngMock:**
|
||||
- allow mock $controller service to set up controller bindings
|
||||
([d02d0585](https://github.com/angular/angular.js/commit/d02d0585a086ecd2e1de628218b5a6d85c8fc7bd),
|
||||
[#9425](https://github.com/angular/angular.js/issues/9425), [#11239](https://github.com/angular/angular.js/issues/11239))
|
||||
- add `they` helpers for testing multiple specs
|
||||
([e650c458](https://github.com/angular/angular.js/commit/e650c45894abe6314a806e6b3e32c908df5c00fd),
|
||||
[#10864](https://github.com/angular/angular.js/issues/10864))
|
||||
- **ngModel:** support conversion to timezone other than UTC
|
||||
([0413bee8](https://github.com/angular/angular.js/commit/0413bee8cc563a6555f8d42d5f183f6fbefc7350),
|
||||
[#11005](https://github.com/angular/angular.js/issues/11005))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$cookies:** due to [38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
|
||||
|
||||
|
||||
`$cookies` no longer exposes properties that represent the current browser cookie
|
||||
values. Now you must explicitly the methods described above to access the cookie
|
||||
values. This also means that you can no longer watch the `$cookies` properties for
|
||||
changes to the browser's cookies.
|
||||
|
||||
This feature is generally only needed if a 3rd party library was programmatically
|
||||
changing the cookies at runtime. If you rely on this then you must either write code that
|
||||
can react to the 3rd party library making the changes to cookies or implement your own polling
|
||||
mechanism.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.15"></a>
|
||||
# 1.3.15 locality-filtration (2015-03-17)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** call `applyStyles` with options on `leave`
|
||||
([ebd84e80](https://github.com/angular/angular.js/commit/ebd84e8008f45ccaa84290f6da8c2a114fcfa8cd),
|
||||
[#10068](https://github.com/angular/angular.js/issues/10068))
|
||||
- **$browser:** don't crash if history.state access causes error in IE
|
||||
([92767c09](https://github.com/angular/angular.js/commit/92767c098feaf8c58faf2d67f882305019d8160e),
|
||||
[#10367](https://github.com/angular/angular.js/issues/10367), [#10369](https://github.com/angular/angular.js/issues/10369))
|
||||
- **Angular:** properly compare RegExp with other objects for equality
|
||||
([b8e8f9af](https://github.com/angular/angular.js/commit/b8e8f9af78f4ef3e556dd3cef6bfee35ad4cb82a),
|
||||
[#11204](https://github.com/angular/angular.js/issues/11204), [#11205](https://github.com/angular/angular.js/issues/11205))
|
||||
- **date filter:** display localised era for `G` format codes
|
||||
([f2683f95](https://github.com/angular/angular.js/commit/f2683f956fcd3216eaa263db20b31e0d46338800),
|
||||
[#10503](https://github.com/angular/angular.js/issues/10503), [#11266](https://github.com/angular/angular.js/issues/11266))
|
||||
- **filterFilter:**
|
||||
- fix filtering using an object expression when the filter value is `undefined`
|
||||
([63b9956f](https://github.com/angular/angular.js/commit/63b9956faf4c3679c88a9401b8ccbb111c0294ee),
|
||||
[#10419](https://github.com/angular/angular.js/issues/10419), [#10424](https://github.com/angular/angular.js/issues/10424))
|
||||
- do not throw an error if property is null when comparing objects
|
||||
([01161a0e](https://github.com/angular/angular.js/commit/01161a0e9fb1af93e9f06535aed8392ed7f116a4),
|
||||
[#10991](https://github.com/angular/angular.js/issues/10991), [#10992](https://github.com/angular/angular.js/issues/10992), [#11116](https://github.com/angular/angular.js/issues/11116))
|
||||
- **form:** allow dynamic form names which initially evaluate to blank
|
||||
([190ea883](https://github.com/angular/angular.js/commit/190ea883c588d63f8b900a8de1d45c6c9ebb01ec),
|
||||
[#11096](https://github.com/angular/angular.js/issues/11096))
|
||||
- **ng/$locale:** add ERA info in generic locale
|
||||
([57842530](https://github.com/angular/angular.js/commit/578425303f2480959da80f31920d08f277d42010))
|
||||
- **rootScope:** prevent memory leak when destroying scopes
|
||||
([528cf09e](https://github.com/angular/angular.js/commit/528cf09e3f78ad4e3bb6a329ebe315c4f29b4cdb),
|
||||
[#11173](https://github.com/angular/angular.js/issues/11173), [#11169](https://github.com/angular/angular.js/issues/11169))
|
||||
- **templateRequest:** avoid throwing syntax error in Android 2.3
|
||||
([75abbd52](https://github.com/angular/angular.js/commit/75abbd525f07866fdcc6fb311802b8fe700af174),
|
||||
[#11089](https://github.com/angular/angular.js/issues/11089), [#11051](https://github.com/angular/angular.js/issues/11051), [#11088](https://github.com/angular/angular.js/issues/11088))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngAria:**
|
||||
- add `button` role to `ngClick`
|
||||
([b9ad91cf](https://github.com/angular/angular.js/commit/b9ad91cf1e86310a2d2bf13b29fa13a9b835e1ce),
|
||||
[#9254](https://github.com/angular/angular.js/issues/9254), [#10318](https://github.com/angular/angular.js/issues/10318))
|
||||
- add roles to custom inputs
|
||||
([21369943](https://github.com/angular/angular.js/commit/21369943fafd577b36827a641b021b1c14cefb57),
|
||||
[#10012](https://github.com/angular/angular.js/issues/10012), [#10318](https://github.com/angular/angular.js/issues/10318))
|
||||
- **ngMock:**
|
||||
- allow mock $controller service to set up controller bindings
|
||||
([b3878a36](https://github.com/angular/angular.js/commit/b3878a36d9f8e56ad7be1eedb9691c9bd12568cb),
|
||||
[#9425](https://github.com/angular/angular.js/issues/9425), [#11239](https://github.com/angular/angular.js/issues/11239))
|
||||
- add `they` helpers for testing multiple specs
|
||||
([7288be25](https://github.com/angular/angular.js/commit/7288be25a75d6ca6ac7eca05a7d6b12ccb3a22f8),
|
||||
[#10864](https://github.com/angular/angular.js/issues/10864))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.5"></a>
|
||||
# 1.4.0-beta.5 karmic-stabilization (2015-02-24)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:** properly access request headers with mixed case
|
||||
([5da1256f](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
|
||||
[#10881](https://github.com/angular/angular.js/issues/10881), [#10883](https://github.com/angular/angular.js/issues/10883))
|
||||
- **input:** create max and/or min validator regardless of initial value
|
||||
([c211e7a5](https://github.com/angular/angular.js/commit/c211e7a5ad5f1fb8748125f14912aa8715081925),
|
||||
[#10307](https://github.com/angular/angular.js/issues/10307), [#10327](https://github.com/angular/angular.js/issues/10327))
|
||||
- **ngAria:** correctly set "checked" attr for checkboxes and radios
|
||||
([d6eba217](https://github.com/angular/angular.js/commit/d6eba21733c6e67e90e3a4763d8d41ad89a73a0c),
|
||||
[#10389](https://github.com/angular/angular.js/issues/10389), [#10212](https://github.com/angular/angular.js/issues/10212))
|
||||
- **ngModel:** fix issues when parserName is same as validator key
|
||||
([056a3170](https://github.com/angular/angular.js/commit/056a31700803c0a6014b43cfcc36c5c500cc596e),
|
||||
[#10698](https://github.com/angular/angular.js/issues/10698), [#10850](https://github.com/angular/angular.js/issues/10850), [#11046](https://github.com/angular/angular.js/issues/11046))
|
||||
- **ngOptions:** ngModel is optional
|
||||
([ef894c87](https://github.com/angular/angular.js/commit/ef894c87eaead76d90169113ab6acc9287654ea3))
|
||||
- **ngSanitize:** Do not ignore white-listed svg camelCased attributes
|
||||
([46b80654](https://github.com/angular/angular.js/commit/46b80654cae9105642909cd55f73f7c26d2fbd80),
|
||||
[#10779](https://github.com/angular/angular.js/issues/10779), [#10990](https://github.com/angular/angular.js/issues/10990), [#11124](https://github.com/angular/angular.js/issues/11124))
|
||||
- **select:** remove unknown option when model is undefined and empty option is available
|
||||
([30b48132](https://github.com/angular/angular.js/commit/30b48132e0fb92ea8dd25a9794b4c41a3a81a951),
|
||||
[#11078](https://github.com/angular/angular.js/issues/11078), [#11092](https://github.com/angular/angular.js/issues/11092))
|
||||
- **templateRequest:** avoid throwing syntax error in Android 2.3
|
||||
([f6272333](https://github.com/angular/angular.js/commit/f6272333127d908b19da23f9cd8a74052711795b),
|
||||
[#11089](https://github.com/angular/angular.js/issues/11089), [#11051](https://github.com/angular/angular.js/issues/11051), [#11088](https://github.com/angular/angular.js/issues/11088))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **CommonJS:** - angular modules are now packaged for npm with helpful exports
|
||||
|
||||
- **limitTo:** extend the filter to take a beginning index argument
|
||||
([aaae3cc4](https://github.com/angular/angular.js/commit/aaae3cc4160417e6dad802ed9d9f6d5471821a87),
|
||||
[#5355](https://github.com/angular/angular.js/issues/5355), [#10899](https://github.com/angular/angular.js/issues/10899))
|
||||
- **ngMessages:** provide support for dynamic message resolution
|
||||
([c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
[#10036](https://github.com/angular/angular.js/issues/10036), [#9338](https://github.com/angular/angular.js/issues/9338))
|
||||
- **ngOptions:** add support for disabling an option
|
||||
([da9eac86](https://github.com/angular/angular.js/commit/da9eac8660343b1cd9fdcf9d2d1bda06067142d7),
|
||||
[#638](https://github.com/angular/angular.js/issues/638), [#11017](https://github.com/angular/angular.js/issues/11017))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:**
|
||||
- replace forEach(controller) with plain loops
|
||||
([5b522867](https://github.com/angular/angular.js/commit/5b5228675f67c8f5e04c7183c3ef5e71cb2bf08b),
|
||||
[#11084](https://github.com/angular/angular.js/issues/11084))
|
||||
- avoid .data when fetching required controllers
|
||||
([fa0aa839](https://github.com/angular/angular.js/commit/fa0aa83937378cf8fc720c38bcc5c78fc923624e))
|
||||
- **ngOptions:** only watch labels if a display expression is specified
|
||||
([51faaffd](https://github.com/angular/angular.js/commit/51faaffdbcc734c55d52ff6c42b386d5c90207ea))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **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
|
||||
ngMessageInclude attribute was that all **included** ngMessage template
|
||||
code was placed at the **bottom** of the element containing the
|
||||
ngMessages directive; therefore to make this behave in the same way,
|
||||
place the element containing the ngMessagesInclude directive at the
|
||||
end of the container containing the ngMessages directive).
|
||||
|
||||
```html
|
||||
<!-- AngularJS 1.3.x -->
|
||||
<div ng-messages="model.$error" ng-messages-include="remote.html">
|
||||
<div ng-message="required">Your message is required</div>
|
||||
</div>
|
||||
|
||||
<!-- AngularJS 1.4.x -->
|
||||
<div ng-messages="model.$error">
|
||||
<div ng-message="required">Your message is required</div>
|
||||
<div ng-messages-include="remote.html"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- **$http:** due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
|
||||
|
||||
`transformRequest` functions can no longer modify request headers.
|
||||
|
||||
Before this commit `transformRequest` could modify request headers, ex.:
|
||||
|
||||
```javascript
|
||||
function requestTransform(data, headers) {
|
||||
headers = angular.extend(headers(), {
|
||||
'X-MY_HEADER': 'abcd'
|
||||
});
|
||||
}
|
||||
return angular.toJson(data);
|
||||
}
|
||||
```
|
||||
|
||||
This behavior was unintended and undocumented, so the change should affect very few applications. If one
|
||||
needs to dynamically add / remove headers it should be done in a header function, for example:
|
||||
|
||||
```javascript
|
||||
$http.get(url, {
|
||||
headers: {
|
||||
'X-MY_HEADER': function(config) {
|
||||
return 'abcd'; //you've got access to a request config object to specify header value dynamically
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<a name="1.3.14"></a>
|
||||
# 1.3.14 instantaneous-browserification (2015-02-24)
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **CommonJS:** - angular modules are now packaged for npm with helpful exports
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **input:** create max and/or min validator regardless of initial value
|
||||
([abfce532](https://github.com/angular/angular.js/commit/abfce5327ce6fd29c33c62d2edf3600674a6b4c0),
|
||||
[#10307](https://github.com/angular/angular.js/issues/10307), [#10327](https://github.com/angular/angular.js/issues/10327))
|
||||
- **ngAria:** correctly set "checked" attr for checkboxes and radios
|
||||
([944c150e](https://github.com/angular/angular.js/commit/944c150e6c3001e51d4bf5e2d8149ae4c565d1e3),
|
||||
[#10389](https://github.com/angular/angular.js/issues/10389), [#10212](https://github.com/angular/angular.js/issues/10212))
|
||||
- **ngModel:** fix issues when parserName is same as validator key
|
||||
([6b7625a0](https://github.com/angular/angular.js/commit/6b7625a09508c4b5355121a9d4206a734b07b2e1),
|
||||
[#10698](https://github.com/angular/angular.js/issues/10698), [#10850](https://github.com/angular/angular.js/issues/10850), [#11046](https://github.com/angular/angular.js/issues/11046))
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.4"></a>
|
||||
# 1.4.0-beta.4 overlyexplosive-poprocks (2015-02-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** prevent page reload if initial url has empty hash at the end
|
||||
([a509e9aa](https://github.com/angular/angular.js/commit/a509e9aa149d0f88cc39f703d539f7ffd4cd6103),
|
||||
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
|
||||
- **$parse:** Initialize elements in an array from left to right
|
||||
([966f6d83](https://github.com/angular/angular.js/commit/966f6d831f9469a917601f9a10604612cd7bd792))
|
||||
- **ngAria:** ensure native controls fire a single click
|
||||
([9d53e5a3](https://github.com/angular/angular.js/commit/9d53e5a38dd369dec82d82e13e078df3d6054c8a),
|
||||
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
|
||||
- **ngMock:** handle cases where injector is created before tests
|
||||
([898714df](https://github.com/angular/angular.js/commit/898714df9ea38f9ef700015ced5ddea52f096b77),
|
||||
[#10967](https://github.com/angular/angular.js/issues/10967))
|
||||
- **sanitize:** handle newline characters inside special tags
|
||||
([cc8755cd](https://github.com/angular/angular.js/commit/cc8755cda6efda0b52954388e8a8d5306e4bfbca),
|
||||
[030a42e7](https://github.com/angular/angular.js/commit/030a42e79dec8a4bb73053762f7a54d797a058f6)
|
||||
[#10943](https://github.com/angular/angular.js/issues/10943))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ng-jq:** adds the ability to force jqLite or a specific jQuery version
|
||||
([09ee82d8](https://github.com/angular/angular.js/commit/09ee82d84dcbea4a6e8d85903af82dcd087a78a7))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.13"></a>
|
||||
# 1.3.13 meticulous-riffleshuffle (2015-02-09)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** prevent page reload if initial url has empty hash at the end
|
||||
([4b3a590b](https://github.com/angular/angular.js/commit/4b3a590b009d7fdceda7f52e7ba0352a271b3256),
|
||||
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
|
||||
- **ngAria:** ensure native controls fire a single click
|
||||
([69ee593f](https://github.com/angular/angular.js/commit/69ee593fd2cb5f1d7757efbe6b256e4458752fd7),
|
||||
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
|
||||
- **ngMock:** handle cases where injector is created before tests
|
||||
([39ddef68](https://github.com/angular/angular.js/commit/39ddef682971d3b7282bf9d08f6eaf97b7f4bca4),
|
||||
[#10967](https://github.com/angular/angular.js/issues/10967))
|
||||
- **sanitize:** handle newline characters inside special tags
|
||||
([11aedbd7](https://github.com/angular/angular.js/commit/11aedbd741ccddba060a9805adba1779391731da),
|
||||
[ce49d4d6](https://github.com/angular/angular.js/commit/ce49d4d61bd02464b6c6376af8048f6eb09330a8)
|
||||
[#10943](https://github.com/angular/angular.js/issues/10943))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.3"></a>
|
||||
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
|
||||
|
||||
@@ -13,7 +527,10 @@
|
||||
- **$controller:** throw better error when controller expression is bad
|
||||
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
|
||||
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
|
||||
- **$parse:** remove references to last arguments to a fn call
|
||||
- **$parse:**
|
||||
- handle null targets at assign
|
||||
([2e5a7e52](https://github.com/angular/angular.js/commit/2e5a7e52a0385575bbb55a801471b009afafeca3))
|
||||
- remove references to last arguments to a fn call
|
||||
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
|
||||
[#10894](https://github.com/angular/angular.js/issues/10894))
|
||||
- **a:** don't reload if there is only a name attribute
|
||||
@@ -41,6 +558,9 @@
|
||||
- **$compile:** allow using bindToController as object, support both new/isolate scopes
|
||||
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
|
||||
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
|
||||
- **filter:** support conversion to timezone other than UTC
|
||||
([c6d8512a](https://github.com/angular/angular.js/commit/c6d8512a1d7345516d1bd9a039d81821b9518bff),
|
||||
[#10858](https://github.com/angular/angular.js/issues/10858))
|
||||
- **ngMocks:** cleanup $inject annotations after each test
|
||||
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
|
||||
|
||||
@@ -49,6 +569,8 @@
|
||||
|
||||
- **$scope:** Add a property $$watchersCount to scope
|
||||
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
|
||||
- **$parse** new and more performant parser
|
||||
([0d42426](https://github.com/angular/angular.js/commit/0d424263ead16635afb582affab2b147f8e71626))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
@@ -88,7 +610,7 @@ is marked as optional and the attribute is not specified, no function will be ad
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.0-beta.2"></a>
|
||||
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
|
||||
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
#Contributing to AngularJS
|
||||
# Contributing to AngularJS
|
||||
|
||||
We'd love for you to contribute to our source code and to make AngularJS even better than it is
|
||||
today! Here are the guidelines we'd like you to follow:
|
||||
@@ -54,7 +54,7 @@ For large fixes, please build and test the documentation before submitting the P
|
||||
accidentally introduced any layout or formatting issues. You should also make sure that your commit message
|
||||
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
|
||||
|
||||
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly. When naming the commit, it is advised to still label it according to the commit guidelines below, by starting the commit message with **docs** and referencing the filename. Since this is not obvious and some changes are made on the fly, this is not strictly necessary and we will understand if this isn't done the first few times.
|
||||
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly. When naming the commit, it is advised to still label it according to the commit guidelines below, by starting the commit message with **docs** and referencing the filename. Since this is not obvious and some changes are made on the fly, this is not strictly necessary and we will understand if this isn't done the first few times.
|
||||
|
||||
## <a name="submit"></a> Submission Guidelines
|
||||
|
||||
@@ -227,11 +227,11 @@ The subject contains succinct description of the change:
|
||||
* don't capitalize first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
###Body
|
||||
### Body
|
||||
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
###Footer
|
||||
### Footer
|
||||
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||
reference GitHub issues that this commit **Closes**.
|
||||
|
||||
|
||||
+29
-6
@@ -17,10 +17,6 @@ module.exports = function(grunt) {
|
||||
NG_VERSION.cdn = versionInfo.cdnVersion;
|
||||
var dist = 'angular-'+ NG_VERSION.full;
|
||||
|
||||
//global beforeEach
|
||||
util.init();
|
||||
|
||||
|
||||
//config
|
||||
grunt.initConfig({
|
||||
NG_VERSION: NG_VERSION,
|
||||
@@ -130,6 +126,9 @@ module.exports = function(grunt) {
|
||||
ngLocale: {
|
||||
files: { src: 'src/ngLocale/**/*.js' },
|
||||
},
|
||||
ngMessageFormat: {
|
||||
files: { src: 'src/ngMessageFormat/**/*.js' },
|
||||
},
|
||||
ngMessages: {
|
||||
files: { src: 'src/ngMessages/**/*.js' },
|
||||
},
|
||||
@@ -204,6 +203,10 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-resource.js',
|
||||
src: util.wrap(files['angularModules']['ngResource'], 'module')
|
||||
},
|
||||
messageformat: {
|
||||
dest: 'build/angular-messageFormat.js',
|
||||
src: util.wrap(files['angularModules']['ngMessageFormat'], 'module')
|
||||
},
|
||||
messages: {
|
||||
dest: 'build/angular-messages.js',
|
||||
src: util.wrap(files['angularModules']['ngMessages'], 'module')
|
||||
@@ -236,6 +239,7 @@ module.exports = function(grunt) {
|
||||
animate: 'build/angular-animate.js',
|
||||
cookies: 'build/angular-cookies.js',
|
||||
loader: 'build/angular-loader.js',
|
||||
messageformat: 'build/angular-messageFormat.js',
|
||||
messages: 'build/angular-messages.js',
|
||||
touch: 'build/angular-touch.js',
|
||||
resource: 'build/angular-resource.js',
|
||||
@@ -251,8 +255,19 @@ module.exports = function(grunt) {
|
||||
'test/**/*.js',
|
||||
'!test/ngScenario/DescribeSpec.js',
|
||||
'!src/ng/directive/attrs.js', // legitimate xit here
|
||||
'!src/ngScenario/**/*.js'
|
||||
]
|
||||
'!src/ngScenario/**/*.js',
|
||||
'!test/helpers/privateMocks*.js'
|
||||
],
|
||||
options: {
|
||||
disallowed: [
|
||||
'iit',
|
||||
'xit',
|
||||
'tthey',
|
||||
'xthey',
|
||||
'ddescribe',
|
||||
'xdescribe'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"merge-conflict": {
|
||||
@@ -285,6 +300,10 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
shell: {
|
||||
"npm-install": {
|
||||
command: path.normalize('scripts/npm/install-dependencies.sh')
|
||||
},
|
||||
|
||||
"promises-aplus-tests": {
|
||||
options: {
|
||||
stdout: false,
|
||||
@@ -311,6 +330,10 @@ module.exports = function(grunt) {
|
||||
}
|
||||
});
|
||||
|
||||
// global beforeEach task
|
||||
if (!process.env.TRAVIS) {
|
||||
grunt.task.run('shell:npm-install');
|
||||
}
|
||||
|
||||
//alias tasks
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
|
||||
+5
-1
@@ -55,7 +55,11 @@ This process based on the idea of minimizing user pain
|
||||
* inconvenience - causes ugly/boilerplate code in apps
|
||||
1. Label `component: *`
|
||||
* In rare cases, it's ok to have multiple components.
|
||||
1. Label `PRs plz!` - These issues are good targets for PRs from the open source community. Apply to issues where the problem and solution are well defined in the comments, and it's not too complex.
|
||||
1. Label `PRs plz!` - These issues are good targets for PRs from the open source community. In addition to applying this label, you must:
|
||||
* Leave a comment explaining the problem and solution so someone can easily finish it.
|
||||
* Assign the issue to yourself.
|
||||
* Give feedback on PRs addressing this issue.
|
||||
* You are responsible for mentoring contributors helping with this issue.
|
||||
1. Label `origin: google` for issues from Google
|
||||
1. Assign a milestone:
|
||||
* Backlog - triaged fixes and features, should be the default choice
|
||||
|
||||
Vendored
+25
-4
@@ -40,6 +40,7 @@ var angularFiles = {
|
||||
'src/ng/timeout.js',
|
||||
'src/ng/urlUtils.js',
|
||||
'src/ng/window.js',
|
||||
'src/ng/cookieReader.js',
|
||||
|
||||
'src/ng/filter.js',
|
||||
'src/ng/filter/filter.js',
|
||||
@@ -86,10 +87,28 @@ var angularFiles = {
|
||||
|
||||
'angularModules': {
|
||||
'ngAnimate': [
|
||||
'src/ngAnimate/animate.js'
|
||||
'src/ngAnimate/shared.js',
|
||||
'src/ngAnimate/animateChildrenDirective.js',
|
||||
'src/ngAnimate/animateCss.js',
|
||||
'src/ngAnimate/animateCssDriver.js',
|
||||
'src/ngAnimate/animateJs.js',
|
||||
'src/ngAnimate/animateJsDriver.js',
|
||||
'src/ngAnimate/animateQueue.js',
|
||||
'src/ngAnimate/animateRunner.js',
|
||||
'src/ngAnimate/animation.js',
|
||||
'src/ngAnimate/module.js'
|
||||
],
|
||||
'ngCookies': [
|
||||
'src/ngCookies/cookies.js'
|
||||
'src/ngCookies/cookies.js',
|
||||
'src/ngCookies/cookieStore.js',
|
||||
'src/ngCookies/cookieWriter.js'
|
||||
],
|
||||
'ngMessageFormat': [
|
||||
'src/ngMessageFormat/messageFormatCommon.js',
|
||||
'src/ngMessageFormat/messageFormatSelector.js',
|
||||
'src/ngMessageFormat/messageFormatInterpolationParts.js',
|
||||
'src/ngMessageFormat/messageFormatParser.js',
|
||||
'src/ngMessageFormat/messageFormatService.js'
|
||||
],
|
||||
'ngMessages': [
|
||||
'src/ngMessages/messages.js'
|
||||
@@ -162,7 +181,7 @@ var angularFiles = {
|
||||
'src/publishExternalApis.js',
|
||||
'@angularSrcModules',
|
||||
'@angularScenario',
|
||||
'@angularTest',
|
||||
'@angularTest'
|
||||
],
|
||||
|
||||
'karmaExclude': [
|
||||
@@ -181,6 +200,7 @@ var angularFiles = {
|
||||
'@angularSrcModules',
|
||||
'src/ngScenario/browserTrigger.js',
|
||||
'test/helpers/*.js',
|
||||
'test/ngMessageFormat/*.js',
|
||||
'test/ngMock/*.js',
|
||||
'test/ngCookies/*.js',
|
||||
'test/ngRoute/**/*.js',
|
||||
@@ -197,7 +217,7 @@ var angularFiles = {
|
||||
'src/publishExternalApis.js',
|
||||
'@angularSrcModules',
|
||||
'@angularScenario',
|
||||
'@angularTest',
|
||||
'@angularTest'
|
||||
],
|
||||
|
||||
'karmaJqueryExclude': [
|
||||
@@ -209,6 +229,7 @@ var angularFiles = {
|
||||
|
||||
angularFiles['angularSrcModules'] = [].concat(
|
||||
angularFiles['angularModules']['ngAnimate'],
|
||||
angularFiles['angularModules']['ngMessageFormat'],
|
||||
angularFiles['angularModules']['ngMessages'],
|
||||
angularFiles['angularModules']['ngCookies'],
|
||||
angularFiles['angularModules']['ngResource'],
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
|
||||
<div>ngBind + filter: <input type="radio" ng-model="benchmarkType" value="ngBindFilter"></div>
|
||||
<div>interpolation + filter: <input type="radio" ng-model="benchmarkType" value="interpolationFilter"></div>
|
||||
<div>ngModel (const name): <input type="radio" ng-model="benchmarkType" value="ngModelConstName"></div>
|
||||
<div>ngModel (interp name): <input type="radio" ng-model="benchmarkType" value="ngModelInterpName"></div>
|
||||
|
||||
<ng-switch on="benchmarkType">
|
||||
<baseline-binding-table ng-switch-when="baselineBinding">
|
||||
@@ -84,7 +86,21 @@
|
||||
<span ng-repeat="column in row">{{column.i | noop}}:{{column.j | noop}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngModelConstName">
|
||||
<h2>ngModel (const name)</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<input type="text" ng-model="row.i" name="constName" />
|
||||
<input type="text" ng-model="row.j" />
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngModelInterpName">
|
||||
<h2>ngModel (interp name)</h2>
|
||||
<div ng-repeat="(rowIdx, row) in data">
|
||||
<input type="text" ng-model="row.i" name="input-{{rowIdx}}" />
|
||||
<input type="text" ng-model="row.j" name="input2-{{rowIdx}}" />
|
||||
</div>
|
||||
</div>
|
||||
</ng-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -9,3 +9,11 @@
|
||||
ng\:form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ng-animate-shim {
|
||||
visibility:hidden;
|
||||
}
|
||||
|
||||
.ng-animate-anchor {
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ iframe.example {
|
||||
|
||||
.main-body-grid .side-navigation {
|
||||
position:relative;
|
||||
padding-bottom:50px;
|
||||
padding-bottom:120px;
|
||||
}
|
||||
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
@@ -631,6 +631,7 @@ ul.events > li {
|
||||
}
|
||||
.main-body-grid .side-navigation {
|
||||
display:block!important;
|
||||
padding-bottom:50px;
|
||||
}
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
display:none!important;
|
||||
|
||||
@@ -21,11 +21,13 @@ angular.module('versions', [])
|
||||
};
|
||||
|
||||
$scope.jumpToDocsVersion = function(version) {
|
||||
var currentPagePath = $location.path().replace(/\/$/, '');
|
||||
|
||||
// TODO: We need to do some munging of the path for different versions of the API...
|
||||
|
||||
|
||||
$window.location = version.docsUrl + currentPagePath;
|
||||
var currentPagePath = $location.path().replace(/\/$/, ''),
|
||||
url = '';
|
||||
if (version.isOldDocsUrl) {
|
||||
url = version.docsUrl;
|
||||
}else{
|
||||
url = version.docsUrl + currentPagePath;
|
||||
}
|
||||
$window.location = url;
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -125,10 +125,16 @@ module.exports = new Package('angularjs', [
|
||||
});
|
||||
|
||||
computeIdsProcessor.idTemplates.push({
|
||||
docTypes: ['error', 'errorNamespace'],
|
||||
docTypes: ['error'],
|
||||
getId: function(doc) { return 'error:' + doc.namespace + ':' + doc.name; },
|
||||
getAliases: function(doc) { return [doc.name, doc.namespace + ':' + doc.name, doc.id]; }
|
||||
},
|
||||
{
|
||||
docTypes: ['errorNamespace'],
|
||||
getId: function(doc) { return 'error:' + doc.name; },
|
||||
getAliases: function(doc) { return [doc.id]; }
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
.config(function(checkAnchorLinksProcessor) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.template.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Error: {$ doc.id $}
|
||||
<h1>Error: {$ doc.namespace $}:{$ doc.name $}
|
||||
<div><span class='hint'>{$ doc.fullName $}</span></div>
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate#service Services / Factories}
|
||||
</td>
|
||||
<td>
|
||||
Use {@link ngAnimate.$animate $animate} to trigger animation operations within your directive code.
|
||||
Use {@link ng.$animate $animate} to trigger animation operations within your directive code.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@ngdoc error
|
||||
@name $compile:baddir
|
||||
@fullName Invalid Directive Name
|
||||
@description
|
||||
|
||||
This error occurs when the name of a directive is not valid.
|
||||
|
||||
Directives must start with a lowercase character.
|
||||
@@ -69,3 +69,15 @@ angular.module('myModule', [])
|
||||
```
|
||||
|
||||
Use the `$controller` service if you want to instantiate controllers yourself.
|
||||
|
||||
Attempting to inject a scope object into anything that's not a controller or a directive,
|
||||
for example a service, will also throw an `Unknown provider: $scopeProvider <- $scope` error.
|
||||
This might happen if one mistakenly registers a controller as a service, ex.:
|
||||
|
||||
```
|
||||
angular.module('myModule', [])
|
||||
.service('MyController', ['$scope', function($scope) {
|
||||
// This controller throws an unknown provider error because
|
||||
// a scope object cannot be injected into a service.
|
||||
}]);
|
||||
```
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:badexpr
|
||||
@fullName Expecting end operator
|
||||
@description
|
||||
|
||||
The Angular expression is missing the corresponding closing operator.
|
||||
@@ -0,0 +1,11 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:dupvalue
|
||||
@fullName Duplicate choice in plural/select
|
||||
@description
|
||||
|
||||
You have repeated a match selection for your plural or select MessageFormat
|
||||
extension in your interpolation expression. The different choices have to be unique.
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:logicbug
|
||||
@fullName Bug in ngMessageFormat module
|
||||
@description
|
||||
|
||||
You've just hit a bug in the ngMessageFormat module provided by angular-messageFormat.min.js.
|
||||
Please file a github issue for this and provide the interpolation text that caused you to hit this
|
||||
bug mentioning the exact version of AngularJS used and we will fix it!
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,17 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:nochgmustache
|
||||
@fullName Redefinition of start/endSymbol incompatible with MessageFormat extensions
|
||||
@description
|
||||
|
||||
You have redefined `$interpolate.startSymbol`/`$interpolate.endSymbol` and also
|
||||
loaded the `ngMessageFormat` module (provided by angular-messageFormat.min.js)
|
||||
while creating your injector.
|
||||
|
||||
`ngMessageFormat` currently does not support redefinition of the
|
||||
startSymbol/endSymbol used by `$interpolate`. If this is affecting you, please
|
||||
file an issue and mention @chirayuk on it. This is intended to be fixed in a
|
||||
future commit and the github issue will help gauge urgency.
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:reqarg
|
||||
@fullName Missing required argument for MessageFormat
|
||||
@description
|
||||
|
||||
You must specify the MessageFormat function that you're using right after the
|
||||
comma following the Angular expression. Currently, the supported functions are
|
||||
"plural" and "select" (for gender selections.)
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,11 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:reqcomma
|
||||
@fullName Missing comma following MessageFormat plural/select keyword
|
||||
@description
|
||||
|
||||
The MessageFormat syntax requires a comma following the "plural" or "select"
|
||||
extension keyword in the extended interpolation syntax.
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,11 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:reqendbrace
|
||||
@fullName Unterminated message for plural/select value
|
||||
@description
|
||||
|
||||
The plural or select message for a value or keyword choice has no matching end
|
||||
brace to mark the end of the message.
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:reqendinterp
|
||||
@fullName Unterminated interpolation
|
||||
@description
|
||||
|
||||
The interpolation text does not have an ending `endSymbol` ("}}" by default) and is unterminated.
|
||||
@@ -0,0 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:reqopenbrace
|
||||
@fullName An opening brace was expected but not found
|
||||
@description
|
||||
|
||||
The plural or select extension keyword or values (such as "other", "male",
|
||||
"female", "=0", "one", "many", etc.) MUST be followed by a message enclosed in
|
||||
braces.
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,13 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:reqother
|
||||
@fullName Required choice "other" for select/plural in MessageFormat
|
||||
@description
|
||||
|
||||
Your interpolation expression with a MessageFormat extension for either
|
||||
"plural" or "select" (typically used for gender selection) does not contain a
|
||||
message for the choice "other". Using either select or plural MessageFormat
|
||||
extensions require that you provide a message for the selection "other".
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:unknarg
|
||||
@fullName Unrecognized MessageFormat extension
|
||||
@description
|
||||
|
||||
The MessageFormat extensions provided by `ngMessageFormat` are currently
|
||||
limited to "plural" and "select". The extension that you have used is either
|
||||
unsupported or invalid.
|
||||
|
||||
For more information about the MessageFormat syntax in interpolation
|
||||
expressions, please refer to MessageFormat extensions section at
|
||||
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
|
||||
@@ -0,0 +1,10 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:unsafe
|
||||
@fullName MessageFormat extensions not allowed in secure context
|
||||
@description
|
||||
|
||||
You have attempted to use a MessageFormat extension in your interpolation expression that is marked as a secure context. For security purposes, this is not supported.
|
||||
|
||||
Read more about secure contexts at {@link ng.$sce Strict Contextual Escaping
|
||||
(SCE)} and about the MessageFormat extensions at {@link
|
||||
guide/i18n#MessageFormat Angular i18n MessageFormat}.
|
||||
@@ -0,0 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:untermstr
|
||||
@fullName Unterminated string literal
|
||||
@description
|
||||
|
||||
The string literal was not terminated in your Angular expression.
|
||||
@@ -0,0 +1,8 @@
|
||||
@ngdoc error
|
||||
@name $interpolate:wantstring
|
||||
@fullName Expected the beginning of a string
|
||||
@description
|
||||
|
||||
We expected to see the beginning of a string (either a single quote or a double
|
||||
quote character) in the expression but it was not found. The expression is
|
||||
invalid. If this is incorrect, please file an issue on github.
|
||||
@@ -0,0 +1,56 @@
|
||||
@ngdoc error
|
||||
@name ngModel:numfmt
|
||||
@fullName Model is not of type `number`
|
||||
@description
|
||||
|
||||
The number input directive `<input type="number">` requires the model to be a `number`.
|
||||
|
||||
If the model is something else, this error will be thrown.
|
||||
|
||||
Angular does not set validation errors on the `<input>` in this case
|
||||
as this error is caused by incorrect application logic and not by bad input from the user.
|
||||
|
||||
If your model does not contain actual numbers then it is up to the application developer
|
||||
to use a directive that will do the conversion in the `ngModel` `$formatters` and `$parsers`
|
||||
pipeline.
|
||||
|
||||
## Example
|
||||
|
||||
In this example, our model stores the number as a string, so we provide the `stringToNumber`
|
||||
directive to convert it into the format the `input[number]` directive expects.
|
||||
|
||||
|
||||
<example module="numfmt-error-module">
|
||||
<file name="index.html">
|
||||
<table>
|
||||
<tr ng-repeat="x in ['0', '1']">
|
||||
<td>
|
||||
<input type="number" string-to-number ng-model="x" /> {{ x }} : {{ typeOf(x) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</file>
|
||||
<file name="app.js">
|
||||
angular.module('numfmt-error-module', [])
|
||||
|
||||
.run(function($rootScope) {
|
||||
$rootScope.typeOf = function(value) {
|
||||
return typeof value;
|
||||
};
|
||||
})
|
||||
|
||||
.directive('stringToNumber', function() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
ngModel.$parsers.push(function(value) {
|
||||
return '' + value;
|
||||
});
|
||||
ngModel.$formatters.push(function(value) {
|
||||
return parseFloat(value, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -37,6 +37,7 @@ Currently, ngAria interfaces with the following directives:
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
* {@link guide/accessibility#ngmessages ngMessages}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
@@ -209,10 +210,16 @@ 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` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
should also bind `ng-keypress`.
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
|
||||
already.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
<h3>Example</h3>
|
||||
```html
|
||||
@@ -223,13 +230,12 @@ Becomes:
|
||||
```html
|
||||
<div ng-click="toggleMenu()" tabindex="0"></div>
|
||||
```
|
||||
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
|
||||
|
||||
<h2 id="ngmessages">ngMessages</h2>
|
||||
|
||||
The new ngMessages module makes it easy to display form validation or other messages with priority
|
||||
sequencing and animation. To expose these visual messages to screen readers,
|
||||
ngAria injects `aria-live="polite"`, causing them to be read aloud any time a message is shown,
|
||||
ngAria injects `aria-live="assertive"`, causing them to be read aloud any time a message is shown,
|
||||
regardless of the user's focus location.
|
||||
###Example
|
||||
|
||||
@@ -243,7 +249,7 @@ regardless of the user's focus location.
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<div ng-messages="myForm.myName.$error" aria-live="polite">
|
||||
<div ng-messages="myForm.myName.$error" aria-live="assertive">
|
||||
<div ng-message="required">You did not enter a field</div>
|
||||
<div ng-message="maxlength">Your field is too long</div>
|
||||
</div>
|
||||
|
||||
@@ -236,7 +236,7 @@ The example below shows how to perform animations during class changes:
|
||||
</file>
|
||||
</example>
|
||||
|
||||
Although the CSS is a little different then what we saw before, the idea is the same.
|
||||
Although the CSS is a little different than what we saw before, the idea is the same.
|
||||
|
||||
## Which directives support animations?
|
||||
|
||||
@@ -253,7 +253,7 @@ The table below explains in detail which animation events are triggered
|
||||
| {@link ng.directive:ngClass#animations ngClass or {{class}}} | add and remove |
|
||||
| {@link ng.directive:ngShow#animations ngShow & ngHide} | add and remove (the ng-hide class value) |
|
||||
|
||||
For a full breakdown of the steps involved during each animation event, refer to the {@link ngAnimate.$animate API docs}.
|
||||
For a full breakdown of the steps involved during each animation event, refer to the {@link ng.$animate API docs}.
|
||||
|
||||
## How do I use animations in my own directives?
|
||||
|
||||
@@ -276,6 +276,6 @@ myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
|
||||
## More about animations
|
||||
|
||||
For a full breakdown of each method available on `$animate`, see the {@link ngAnimate.$animate API documentation}.
|
||||
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
|
||||
|
||||
To see a complete demo, see the {@link tutorial/step_12 animation step within the AngularJS phonecat tutorial}.
|
||||
|
||||
@@ -55,11 +55,11 @@ Try out the Live Preview above, and then let's walk through the example and desc
|
||||
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding1.png">
|
||||
|
||||
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
|
||||
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
|
||||
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
|
||||
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
|
||||
<a name="template">{@link templates template}</a>. When Angular starts your application, it parses and
|
||||
processes this new markup from the template using the <a name="compiler">{@link compiler compiler}</a>.
|
||||
The loaded, transformed and rendered DOM is then called the <a name="view">view</a>.
|
||||
|
||||
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
|
||||
The first kind of new markup are the <a name="directive">{@link directive directives}</a>.
|
||||
They apply special behavior to attributes or elements in the HTML. In the example above we use the
|
||||
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
|
||||
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
|
||||
@@ -75,16 +75,16 @@ stores/updates the value of the input field into/from a variable.
|
||||
|
||||
The second kind of new markup are the double curly braces `{{ expression | filter }}`:
|
||||
When the compiler encounters this markup, it will replace it with the evaluated value of the markup.
|
||||
An <a name="expression">"{@link expression expression}"</a> in a template is a JavaScript-like code snippet that allows
|
||||
An <a name="expression">{@link expression expression}</a> in a template is a JavaScript-like code snippet that allows
|
||||
to read and write variables. Note that those variables are not global variables.
|
||||
Just like variables in a JavaScript function live in a scope,
|
||||
Angular provides a <a name="scope">"{@link scope scope}"</a> for the variables accessible to expressions.
|
||||
The values that are stored in variables on the scope are referred to as the <a name="model">"model"</a>
|
||||
Angular provides a <a name="scope">{@link scope scope}</a> for the variables accessible to expressions.
|
||||
The values that are stored in variables on the scope are referred to as the <a name="model">model</a>
|
||||
in the rest of the documentation.
|
||||
Applied to the example above, the markup directs Angular to "take the data we got from the input widgets
|
||||
and multiply them together".
|
||||
|
||||
The example above also contains a <a name="filter">"{@link guide/filter filter}"</a>.
|
||||
The example above also contains a <a name="filter">{@link guide/filter filter}</a>.
|
||||
A filter formats the value of an expression for display to the user.
|
||||
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
|
||||
into an output that looks like money.
|
||||
@@ -92,7 +92,7 @@ into an output that looks like money.
|
||||
The important thing in the example is that Angular provides _live_ bindings:
|
||||
Whenever the input values change, the value of the expressions are automatically
|
||||
recalculated and the DOM is updated with their values.
|
||||
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
|
||||
The concept behind this is <a name="databinding">{@link databinding two-way data binding}</a>.
|
||||
|
||||
|
||||
## Adding UI logic: Controllers
|
||||
@@ -150,7 +150,7 @@ different currencies and also pay the invoice.
|
||||
|
||||
What changed?
|
||||
|
||||
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
|
||||
First, there is a new JavaScript file that contains a <a name="controller">{@link controller controller}</a>.
|
||||
More exactly, the file contains a constructor function that creates the actual controller instance.
|
||||
The purpose of controllers is to expose variables and functionality to expressions and directives.
|
||||
|
||||
@@ -179,11 +179,11 @@ The following graphic shows how everything works together after we introduced th
|
||||
|
||||
<img style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding2.png">
|
||||
|
||||
## View independent business logic: Services
|
||||
## View-independent business logic: Services
|
||||
|
||||
Right now, the `InvoiceController` contains all logic of our example. When the application grows it
|
||||
is a good practice to move view independent logic from the controller into a so called
|
||||
<a name="service">"{@link services service}"</a>, so it can be reused by other parts
|
||||
is a good practice to move view-independent logic from the controller into a
|
||||
<a name="service">{@link services service}</a>, so it can be reused by other parts
|
||||
of the application as well. Later on, we could also change that service to load the exchange rates
|
||||
from the web, e.g. by calling the Yahoo Finance API, without changing the controller.
|
||||
|
||||
@@ -255,15 +255,15 @@ We moved the `convertCurrency` function and the definition of the existing curre
|
||||
into the new file `finance2.js`. But how does the controller
|
||||
get a hold of the now separated function?
|
||||
|
||||
This is where <a name="di">"{@link di Dependency Injection}"</a> comes into play.
|
||||
This is where <a name="di">{@link di Dependency Injection}</a> comes into play.
|
||||
Dependency Injection (DI) is a software design pattern that
|
||||
deals with how objects and functions get created and how they get a hold of their dependencies.
|
||||
Everything within Angular (directives, filters, controllers,
|
||||
services, ...) is created and wired using dependency injection. Within Angular,
|
||||
the DI container is called the <a name="injector">"{@link di injector}"</a>.
|
||||
the DI container is called the <a name="injector">{@link di injector}</a>.
|
||||
|
||||
To use DI, there needs to be a place where all the things that should work together are registered.
|
||||
In Angular, this is the purpose of the so-called <a name="module">"{@link module modules}"</a>.
|
||||
In Angular, this is the purpose of the <a name="module">{@link module modules}</a>.
|
||||
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
|
||||
including the configuration of all modules that this module depends on.
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@ Do not use controllers to:
|
||||
services} instead.
|
||||
- Manage the life-cycle of other components (for example, to create service instances).
|
||||
|
||||
# Setting up the initial state of a `$scope` object
|
||||
## Setting up the initial state of a `$scope` object
|
||||
|
||||
Typically, when you create an application you need to set up the initial state for the Angular
|
||||
`$scope`. You set up the initial state of a scope by attaching properties to the `$scope` object.
|
||||
The properties contain the **view model** (the model that will be presented by the view). All the
|
||||
`$scope` properties will be available to the template at the point in the DOM where the Controller
|
||||
`$scope` properties will be available to the {@link templates template} at the point in the DOM where the Controller
|
||||
is registered.
|
||||
|
||||
The following example demonstrates creating a `GreetingController`, which attaches a `greeting`
|
||||
@@ -69,13 +69,13 @@ now be data-bound to the template:
|
||||
```
|
||||
|
||||
|
||||
# Adding Behavior to a Scope Object
|
||||
## Adding Behavior to a Scope Object
|
||||
|
||||
In order to react to events or execute computation in the view we must provide behavior to the
|
||||
scope. We add behavior to the scope by attaching methods to the `$scope` object. These methods are
|
||||
then available to be called from the template/view.
|
||||
|
||||
The following example uses a Controller to add a method to the scope, which doubles a number:
|
||||
The following example uses a Controller to add a method, which doubles a number, to the scope:
|
||||
|
||||
```js
|
||||
var myApp = angular.module('myApp',[]);
|
||||
@@ -99,7 +99,7 @@ objects (or primitives) assigned to the scope become model properties. Any metho
|
||||
the scope are available in the template/view, and can be invoked via angular expressions
|
||||
and `ng` event handler directives (e.g. {@link ng.directive:ngClick ngClick}).
|
||||
|
||||
# Using Controllers Correctly
|
||||
## Using Controllers Correctly
|
||||
|
||||
In general, a Controller shouldn't try to do too much. It should contain only the business logic
|
||||
needed for a single view.
|
||||
@@ -125,7 +125,7 @@ following components:
|
||||
- A model consisting of a string named `spice`
|
||||
- A Controller with two functions that set the value of `spice`
|
||||
|
||||
The message in our template contains a binding to the `spice` model, which by default is set to the
|
||||
The message in our template contains a binding to the `spice` model which, by default, is set to the
|
||||
string "very". Depending on which button is clicked, the `spice` model is set to `chili` or
|
||||
`jalapeño`, and the message is automatically updated by data-binding.
|
||||
|
||||
@@ -259,7 +259,7 @@ Inheritance works with methods in the same way as it does with properties. So in
|
||||
examples, all of the properties could be replaced with methods that return string values.
|
||||
|
||||
|
||||
## Testing Controllers
|
||||
# Testing Controllers
|
||||
|
||||
Although there are many ways to test a Controller, one of the best conventions, shown below,
|
||||
involves injecting the {@link ng.$rootScope $rootScope} and {@link ng.$controller $controller}:
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 210
|
||||
@description
|
||||
|
||||
# Data Binding
|
||||
|
||||
Data-binding in Angular apps is the automatic synchronization of data between the model and view
|
||||
components. The way that Angular implements data-binding lets you treat the model as the
|
||||
single-source-of-truth in your application. The view is a projection of the model at all times.
|
||||
|
||||
@@ -54,6 +54,8 @@ The following also **matches** `ngModel`:
|
||||
<input data-ng:model="foo">
|
||||
```
|
||||
|
||||
### Normalization
|
||||
|
||||
Angular **normalizes** an element's tag and attribute name to determine which elements match which
|
||||
directives. We typically refer to directives by their case-sensitive
|
||||
[camelCase](http://en.wikipedia.org/wiki/CamelCase) **normalized** name (e.g. `ngModel`).
|
||||
@@ -100,6 +102,8 @@ If you want to use an HTML validating tool, you can instead use the `data`-prefi
|
||||
The other forms shown above are accepted for legacy reasons but we advise you to avoid them.
|
||||
</div>
|
||||
|
||||
### Directive types
|
||||
|
||||
`$compile` can match directives based on element names, attributes, class names, as well as comments.
|
||||
|
||||
All of the Angular-provided directives match attribute name, tag name, comments, or class name.
|
||||
@@ -827,37 +831,39 @@ element?
|
||||
<file name="script.js">
|
||||
angular.module('dragModule', [])
|
||||
.directive('myDraggable', ['$document', function($document) {
|
||||
return function(scope, element, attr) {
|
||||
var startX = 0, startY = 0, x = 0, y = 0;
|
||||
return {
|
||||
link: function(scope, element, attr) {
|
||||
var startX = 0, startY = 0, x = 0, y = 0;
|
||||
|
||||
element.css({
|
||||
position: 'relative',
|
||||
border: '1px solid red',
|
||||
backgroundColor: 'lightgrey',
|
||||
cursor: 'pointer'
|
||||
});
|
||||
|
||||
element.on('mousedown', function(event) {
|
||||
// Prevent default dragging of selected content
|
||||
event.preventDefault();
|
||||
startX = event.pageX - x;
|
||||
startY = event.pageY - y;
|
||||
$document.on('mousemove', mousemove);
|
||||
$document.on('mouseup', mouseup);
|
||||
});
|
||||
|
||||
function mousemove(event) {
|
||||
y = event.pageY - startY;
|
||||
x = event.pageX - startX;
|
||||
element.css({
|
||||
top: y + 'px',
|
||||
left: x + 'px'
|
||||
position: 'relative',
|
||||
border: '1px solid red',
|
||||
backgroundColor: 'lightgrey',
|
||||
cursor: 'pointer'
|
||||
});
|
||||
}
|
||||
|
||||
function mouseup() {
|
||||
$document.off('mousemove', mousemove);
|
||||
$document.off('mouseup', mouseup);
|
||||
element.on('mousedown', function(event) {
|
||||
// Prevent default dragging of selected content
|
||||
event.preventDefault();
|
||||
startX = event.pageX - x;
|
||||
startY = event.pageY - y;
|
||||
$document.on('mousemove', mousemove);
|
||||
$document.on('mouseup', mouseup);
|
||||
});
|
||||
|
||||
function mousemove(event) {
|
||||
y = event.pageY - startY;
|
||||
x = event.pageX - startX;
|
||||
element.css({
|
||||
top: y + 'px',
|
||||
left: x + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function mouseup() {
|
||||
$document.off('mousemove', mousemove);
|
||||
$document.off('mouseup', mouseup);
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -297,9 +297,9 @@ then the expression is not fulfilled and will remain watched.
|
||||
2. If V is not undefined, mark the result of the expression as stable and schedule a task
|
||||
to deregister the watch for this expression when we exit the digest loop
|
||||
3. Process the digest loop as normal
|
||||
4. When digest loop is done and all the values have settled process the queue of watch
|
||||
deregistration tasks. For each watch to be deregistered check if it still evaluates
|
||||
to value that is not `undefined`. If that's the case, deregister the watch. Otherwise
|
||||
4. When digest loop is done and all the values have settled, process the queue of watch
|
||||
deregistration tasks. For each watch to be deregistered, check if it still evaluates
|
||||
to a value that is not `undefined`. If that's the case, deregister the watch. Otherwise,
|
||||
keep dirty-checking the watch in the future digest loops by following the same
|
||||
algorithm starting from step 1
|
||||
|
||||
|
||||
@@ -92,8 +92,10 @@ means that it should be stateless and idempotent. Angular relies on these proper
|
||||
the filter only when the inputs to the function change.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** filter names must be valid angular expression identifiers, such as `uppercase` or `orderBy`.
|
||||
Names with special characters, such as hyphens and dots, are not allowed.
|
||||
**Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
|
||||
Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
|
||||
your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
|
||||
(`myapp_subsection_filterx`).
|
||||
</div>
|
||||
|
||||
The following sample filter reverses a text string. In addition, it conditionally makes the
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
@sortOrder 290
|
||||
@description
|
||||
|
||||
# Forms
|
||||
|
||||
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
|
||||
A Form is a collection of controls for the purpose of grouping related controls together.
|
||||
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input.
|
||||
This provides a better user experience, because the user gets instant feedback on how to
|
||||
correct the error. Keep in mind that while client-side validation plays an important role
|
||||
in providing good user experience, it can easily be circumvented and thus can not be trusted.
|
||||
Server-side validation is still necessary for a secure application.
|
||||
Form and controls provide validation services, so that the user can be notified of invalid input
|
||||
before submitting a form. This provides a better user experience than server-side validation alone
|
||||
because the user gets instant feedback on how to correct the error. Keep in mind that while
|
||||
client-side validation plays an important role in providing good user experience, it can easily
|
||||
be circumvented and thus can not be trusted. Server-side validation is still necessary for a
|
||||
secure application.
|
||||
|
||||
|
||||
# Simple form
|
||||
## Simple form
|
||||
The key directive in understanding two-way data-binding is {@link ng.directive:ngModel ngModel}.
|
||||
The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view,
|
||||
as well as view to the model. In addition it provides an {@link ngModel.NgModelController API}
|
||||
@@ -61,7 +64,7 @@ For example: inputs of type `email` must have a value in the form of `user@domai
|
||||
|
||||
|
||||
|
||||
# Using CSS classes
|
||||
## Using CSS classes
|
||||
|
||||
To allow styling of form as well as controls, `ngModel` adds these CSS classes:
|
||||
|
||||
@@ -125,7 +128,7 @@ and failing to satisfy its validity.
|
||||
|
||||
|
||||
|
||||
# Binding to form and control state
|
||||
## Binding to form and control state
|
||||
|
||||
A form is an instance of {@link form.FormController FormController}.
|
||||
The form instance can optionally be published into the scope using the `name` attribute.
|
||||
@@ -207,7 +210,7 @@ didn't interact with a control
|
||||
|
||||
|
||||
|
||||
# Custom model update triggers
|
||||
## Custom model update triggers
|
||||
|
||||
By default, any change to the content will trigger a model update and form validation. You can
|
||||
override this behavior using the {@link ng.directive:ngModelOptions ngModelOptions} directive to
|
||||
@@ -248,7 +251,7 @@ will update the model only when the control loses focus (blur event).
|
||||
|
||||
|
||||
|
||||
# Non-immediate (debounced) model updates
|
||||
## Non-immediate (debounced) model updates
|
||||
|
||||
You can delay the model update/validation by using the `debounce` key with the
|
||||
{@link ng.directive:ngModelOptions ngModelOptions} directive. This delay will also apply to
|
||||
@@ -289,7 +292,7 @@ after last change.
|
||||
</file>
|
||||
</example>
|
||||
|
||||
# Custom Validation
|
||||
## Custom Validation
|
||||
|
||||
Angular provides basic implementation for most common HTML5 {@link ng.directive:input input}
|
||||
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
|
||||
@@ -406,7 +409,7 @@ In the following example we create two directives:
|
||||
</file>
|
||||
</example>
|
||||
|
||||
# Modifying built-in validators
|
||||
## Modifying built-in validators
|
||||
|
||||
Since Angular itself uses `$validators`, you can easily replace or remove built-in validators,
|
||||
should you find it necessary. The following example shows you how to overwrite the email validator
|
||||
@@ -451,7 +454,7 @@ Note that you can alternatively use `ng-pattern` to further restrict the validat
|
||||
</example>
|
||||
|
||||
|
||||
# Implementing custom form controls (using `ngModel`)
|
||||
## Implementing custom form controls (using `ngModel`)
|
||||
Angular implements all of the basic HTML form controls ({@link ng.directive:input input},
|
||||
{@link ng.directive:select select}, {@link ng.directive:textarea textarea}),
|
||||
which should be sufficient for most cases. However, if you need more flexibility,
|
||||
|
||||
@@ -137,3 +137,115 @@ The Angular datetime filter uses the time zone settings of the browser. The same
|
||||
application will show different time information depending on the time zone settings of the
|
||||
computer that the application is running on. Neither JavaScript nor Angular currently supports
|
||||
displaying the date with a timezone specified by the developer.
|
||||
|
||||
|
||||
<a name="MessageFormat"></a>
|
||||
## MessageFormat extensions
|
||||
|
||||
AngularJS interpolations via `$interpolate` and in templates
|
||||
support an extended syntax based on a subset of the ICU
|
||||
MessageFormat that covers plurals and gender selections.
|
||||
|
||||
Please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit)
|
||||
for a lot more details. You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview).
|
||||
|
||||
You can read more about the ICU MessageFormat syntax at
|
||||
[Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat).
|
||||
|
||||
This extended syntax is provided by way of the
|
||||
`ngMessageFormat` module that your application can depend
|
||||
upon (shipped separately as `angular-messageFormat.min.js` and
|
||||
`angular-messageFormat.js`.) A current limitation of the
|
||||
`ngMessageFormat` module, is that it does not support
|
||||
redefining the `$interpolate` start and end symbols. Only the
|
||||
default `{{` and `}}` are allowed.
|
||||
|
||||
This syntax extension, while based on MessageFormat, has
|
||||
been designed to be backwards compatible with existing
|
||||
AngularJS interpolation expressions. The key rule is simply
|
||||
this: **All interpolations are done inside double curlies.**
|
||||
The top level comma operator after an expression inside the
|
||||
double curlies causes MessageFormat extensions to be
|
||||
recognized. Such a top level comma is otherwise illegal in
|
||||
an Angular expression and is used by MessageFormat to
|
||||
specify the function (such as plural/select) and it's
|
||||
related syntax.
|
||||
|
||||
To understand the extension, take a look at the ICU
|
||||
MessageFormat syntax as specified by the ICU documentation.
|
||||
Anywhere in that MessageFormat that you have regular message
|
||||
text and you want to substitute an expression, just put it
|
||||
in double curlies instead of single curlies that
|
||||
MessageFormat dictates. This has a huge advantage. **You
|
||||
are no longer limited to simple identifiers for
|
||||
substitutions**. Because you are using double curlies, you
|
||||
can stick in any arbitrary interpolation syntax there,
|
||||
including nesting more MessageFormat expressions! Some
|
||||
examples will make this clear. In the following example, I
|
||||
will only be showing you the AngularJS syntax.
|
||||
|
||||
|
||||
### Simple plural example
|
||||
|
||||
```
|
||||
{{numMessages, plural,
|
||||
=0 { You have no new messages }
|
||||
=1 { You have one new message }
|
||||
other { You have # new messages }
|
||||
}}
|
||||
```
|
||||
|
||||
While I won't be teaching you MessageFormat here, you will
|
||||
note that the `#` symbol works as expected. You could have
|
||||
also written it as:
|
||||
|
||||
```
|
||||
{{numMessages, plural,
|
||||
=0 { You have no new messages }
|
||||
=1 { You have one new message }
|
||||
other { You have {{numMessages}} new messages }
|
||||
}}
|
||||
```
|
||||
|
||||
where you explicitly typed in `numMessages` for "other"
|
||||
instead of using `#`. They are nearly the same except if
|
||||
you're using "offset". Refer to the ICU MessageFormat
|
||||
documentation to learn about offset.
|
||||
|
||||
Please note that **other** is a **required** category (for
|
||||
both the plural syntax and the select syntax that is shown
|
||||
later.)
|
||||
|
||||
|
||||
### Simple select (for gender) example
|
||||
|
||||
```
|
||||
{{friendGender, select,
|
||||
male { Invite him }
|
||||
female { Invite her }
|
||||
other { Invite them }
|
||||
}}
|
||||
```
|
||||
|
||||
### More complex example that combines some of these
|
||||
|
||||
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
|
||||
|
||||
```
|
||||
{{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. }
|
||||
}}
|
||||
```
|
||||
|
||||
@@ -245,7 +245,7 @@ of the `$watch` expressions and compares them with the previous value. This dirt
|
||||
asynchronously. This means that assignment such as `$scope.username="angular"` will not
|
||||
immediately cause a `$watch` to be notified, instead the `$watch` notification is delayed until
|
||||
the `$digest` phase. This delay is desirable, since it coalesces multiple model updates into one
|
||||
`$watch` notification as well as it guarantees that during the `$watch` notification no other
|
||||
`$watch` notification as well as guarantees that during the `$watch` notification no other
|
||||
`$watch`es are running. If a `$watch` changes the value of the model, it will force additional
|
||||
`$digest` cycle.
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ on the service.
|
||||
### Registering Services
|
||||
|
||||
Services are registered to modules via the {@link angular.Module Module API}.
|
||||
Typically you use the {@link angular.module Module#factory} API to register a service:
|
||||
Typically you use the {@link angular.Module#factory Module factory} API to register a service:
|
||||
|
||||
```js
|
||||
var myModule = angular.module('myModule', []);
|
||||
@@ -181,7 +181,7 @@ of a real browser alert.
|
||||
|
||||
```js
|
||||
var mock, notify;
|
||||
|
||||
beforeEach(module('myServiceModule'));
|
||||
beforeEach(function() {
|
||||
mock = {alert: jasmine.createSpy()};
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ are available on [the Karma website](http://karma-runner.github.io/0.12/intro/in
|
||||
|
||||
### Jasmine
|
||||
|
||||
[Jasmine](http://jasmine.github.io/1.3/introduction.html) is a test driven development framework for
|
||||
[Jasmine](http://jasmine.github.io/1.3/introduction.html) is a behavior driven development framework for
|
||||
JavaScript that has become the most popular choice for testing Angular applications. Jasmine
|
||||
provides functions to help with structuring your tests and also making assertions. As your tests
|
||||
grow, keeping them well structured and documented is vital, and Jasmine helps achieve this.
|
||||
|
||||
@@ -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.2.0:
|
||||
example points to the minified version 1.3.14:
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -20,6 +20,42 @@ AngularJS is 100% JavaScript, 100% client-side and compatible with both desktop
|
||||
So it's definitely not a plugin or some other native browser extension.
|
||||
|
||||
|
||||
### What is the AngularJS versioning strategy?
|
||||
|
||||
In Angular 1 we do not allow intentional breaking changes to appear in versions where only the "patch"
|
||||
number changes. For example between 1.3.12 and 1.3.13 there can be no breaking changes. We do allow
|
||||
breaking changes happen between "minor" number changes. For example between 1.3.15 and 1.4.0 there
|
||||
will be a number of breaking changes. We also allow breaking changes between beta releases of Angular.
|
||||
For example between 1.4.0-beta.4 and 1.4.0-beta.5 there may be breaking changes. We try hard to minimize
|
||||
these kinds of change only to those where there is a strong use case such as a strongly requested feature
|
||||
improvement, a considerable simplification of the code or a measurable performance improvement.
|
||||
|
||||
When adding new code to branches of Angular, have a very stringent commit policy:
|
||||
|
||||
- Every commit must contain tests and documentation updates alongside the code changes and that all the
|
||||
tests must pass;
|
||||
- Commit messages must be written in a specific manner that allows us to parse them and extract the changes
|
||||
for release notes.
|
||||
|
||||
The Angular code base has a very large set of unit tests (over 4000) and end to end tests, which are pretty
|
||||
comprehensive. This means that a breaking change will require one or more tests to be changed to allow the
|
||||
tests to pass. So when a commit includes tests that are being removed or modified, this is a flag that the
|
||||
code might include a breaking change. When reviewing the commit we can then decide whether there really is
|
||||
a breaking change and if it is appropriate for the branch to which it is being merged. If so, then we
|
||||
require that the commit message contains an appropriate breaking change message.
|
||||
|
||||
Additionally, when a commit lands in our master repository it is synced to Google where we test it against
|
||||
over 2000 applications using the test suites of these applications. This allows us to catch regressions
|
||||
quickly before a release. We've had a pretty good experience with this setup. Only bugs that affect features
|
||||
not used at Google or without sufficient test coverage, have a chance of making it through.
|
||||
|
||||
Lastly, when we are making a release we generate updates to the changelog directly from the commits. This
|
||||
generated update contains a highlighted section that contains all the breaking changes that have been
|
||||
extracted from the commits. We can quickly see in the new changelog exactly what commits contain breaking
|
||||
changes and so can application developers when they are deciding whether to update to a new version of
|
||||
Angular.
|
||||
|
||||
|
||||
### Is AngularJS a templating system?
|
||||
|
||||
At the highest level, Angular does look like just another templating system. But there is one
|
||||
@@ -67,7 +103,7 @@ illustration, we typically build snappy apps with hundreds or thousands of activ
|
||||
|
||||
### How big is the angular.js file that I need to include?
|
||||
|
||||
The size of the file is < 36KB compressed and minified.
|
||||
The size of the file is ~50KB compressed and minified.
|
||||
|
||||
|
||||
### Can I use the open-source Closure Library with Angular?
|
||||
|
||||
@@ -65,7 +65,7 @@ __`app/index.html`:__
|
||||
|
||||
## What is the code doing?
|
||||
|
||||
* `ng-app` directive:
|
||||
**`ng-app` directive:**
|
||||
|
||||
<html ng-app>
|
||||
|
||||
@@ -77,17 +77,17 @@ __`app/index.html`:__
|
||||
This gives application developers the freedom to tell Angular if the entire html page or only a
|
||||
portion of it should be treated as the Angular application.
|
||||
|
||||
* AngularJS script tag:
|
||||
**AngularJS script tag:**
|
||||
|
||||
<script src="bower_components/angular/angular.js">
|
||||
|
||||
This code downloads the `angular.js` script and registers a callback that will be executed by the
|
||||
This code downloads the `angular.js` script which registers a callback that will be executed by the
|
||||
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
|
||||
looks for the {@link ng.directive:ngApp ngApp} directive. If
|
||||
Angular finds the directive, it will bootstrap the application with the root of the application DOM
|
||||
being the element on which the `ngApp` directive was defined.
|
||||
|
||||
* Double-curly binding with an expression:
|
||||
**Double-curly binding with an expression:**
|
||||
|
||||
Nothing here {{'yet' + '!'}}
|
||||
|
||||
@@ -111,7 +111,7 @@ being the element on which the `ngApp` directive was defined.
|
||||
## Bootstrapping AngularJS apps
|
||||
|
||||
Bootstrapping AngularJS apps automatically using the `ngApp` directive is very easy and suitable
|
||||
for most cases. In advanced cases, such as when using script loaders, you can use
|
||||
for most cases. In advanced cases, such as when using script loaders, you can use the
|
||||
{@link guide/bootstrap imperative / manual way} to bootstrap the app.
|
||||
|
||||
There are 3 important things that happen during the app bootstrap:
|
||||
|
||||
@@ -225,7 +225,7 @@ inserted into and removed from the list:
|
||||
* The `ng-leave` class is applied when they're removed from the list.
|
||||
|
||||
The phone listing items are added and removed depending on the data passed to the `ng-repeat` attribute.
|
||||
For example, if the filter data changes the items will be animated in and out of the repeat list.
|
||||
For example, if the filter data changes, the items will be animated in and out of the repeat list.
|
||||
|
||||
Something important to note is that when an animation occurs, two sets of CSS classes
|
||||
are added to the element:
|
||||
@@ -233,7 +233,7 @@ are added to the element:
|
||||
1. a "starting" class that represents the style at the beginning of the animation
|
||||
2. an "active" class that represents the style at the end of the animation
|
||||
|
||||
The name of the starting class is the name of event that is fired (like `enter`, `move` or `leave`) prefixed with
|
||||
The name of the starting class is the name of the event that is fired (like `enter`, `move` or `leave`) prefixed with
|
||||
`ng-`. So an `enter` event will result in a class called `ng-enter`.
|
||||
|
||||
The active class name is the same as the starting class's but with an `-active` suffix.
|
||||
|
||||
@@ -184,7 +184,11 @@ describe("extractDateTimeSymbols", function() {
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
SHORTDAY: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
WEEKENDRANGE: [5, 6],
|
||||
AMPMS: ['AM', 'PM'],
|
||||
ERAS: ['av. J.-C.', 'ap. J.-C.'],
|
||||
ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],
|
||||
medium: 'yyyy-MM-dd HH:mm:ss',
|
||||
short: 'yy-MM-dd HH:mm',
|
||||
fullDate: 'EEEE d MMMM y',
|
||||
|
||||
@@ -42,6 +42,10 @@ function convertDatetimeData(dataObj) {
|
||||
datetimeFormats.DAY = dataObj.WEEKDAYS;
|
||||
datetimeFormats.SHORTDAY = dataObj.SHORTWEEKDAYS;
|
||||
datetimeFormats.AMPMS = dataObj.AMPMS;
|
||||
datetimeFormats.FIRSTDAYOFWEEK = dataObj.FIRSTDAYOFWEEK;
|
||||
datetimeFormats.WEEKENDRANGE = dataObj.WEEKENDRANGE;
|
||||
datetimeFormats.ERAS = dataObj.ERAS;
|
||||
datetimeFormats.ERANAMES = dataObj.ERANAMES;
|
||||
|
||||
|
||||
datetimeFormats.medium = dataObj.DATEFORMATS[2] + ' ' + dataObj.TIMEFORMATS[2];
|
||||
|
||||
@@ -66,6 +66,12 @@ module.exports = function(config, specificOptions) {
|
||||
platform: 'Windows 8.1',
|
||||
version: '11'
|
||||
},
|
||||
'SL_iOS': {
|
||||
base: "SauceLabs",
|
||||
browserName: "iphone",
|
||||
platform: "OS X 10.10",
|
||||
version: "8.1"
|
||||
},
|
||||
|
||||
'BS_Chrome': {
|
||||
base: 'BrowserStack',
|
||||
@@ -105,6 +111,12 @@ module.exports = function(config, specificOptions) {
|
||||
browser_version: '11.0',
|
||||
os: 'Windows',
|
||||
os_version: '8.1'
|
||||
},
|
||||
'BS_iOS': {
|
||||
base: 'BrowserStack',
|
||||
device: 'iPhone 6',
|
||||
os: 'ios',
|
||||
os_version: '8.0'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+3
-8
@@ -14,13 +14,6 @@ var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP
|
||||
|
||||
module.exports = {
|
||||
|
||||
init: function() {
|
||||
if (!process.env.TRAVIS) {
|
||||
shell.exec('npm install');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
startKarma: function(config, singleRun, done){
|
||||
var browsers = grunt.option('browsers');
|
||||
var reporters = grunt.option('reporters');
|
||||
@@ -192,6 +185,8 @@ module.exports = {
|
||||
var mapFileName = mapFile.match(/[^\/]+$/)[0];
|
||||
var errorFileName = file.replace(/\.js$/, '-errors.json');
|
||||
var versionNumber = grunt.config('NG_VERSION').full;
|
||||
var compilationLevel = (file === 'build/angular-messageFormat.js') ?
|
||||
'ADVANCED_OPTIMIZATIONS' : 'SIMPLE_OPTIMIZATIONS';
|
||||
shell.exec(
|
||||
'java ' +
|
||||
this.java32flags() + ' ' +
|
||||
@@ -199,7 +194,7 @@ module.exports = {
|
||||
'-cp bower_components/closure-compiler/compiler.jar' + classPathSep +
|
||||
'bower_components/ng-closure-runner/ngcompiler.jar ' +
|
||||
'org.angularjs.closurerunner.NgClosureRunner ' +
|
||||
'--compilation_level SIMPLE_OPTIMIZATIONS ' +
|
||||
'--compilation_level ' + compilationLevel + ' ' +
|
||||
'--language_in ECMASCRIPT5_STRICT ' +
|
||||
'--minerr_pass ' +
|
||||
'--minerr_errors ' + errorFileName + ' ' +
|
||||
|
||||
@@ -12,9 +12,9 @@ set -e
|
||||
# before_script:
|
||||
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
|
||||
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
|
||||
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.7-linux.tar.gz"
|
||||
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
|
||||
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
|
||||
CONNECT_DOWNLOAD="sc-4.3.7-linux.tar.gz"
|
||||
|
||||
CONNECT_LOG="$LOGS_DIR/sauce-connect"
|
||||
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
|
||||
@@ -46,5 +46,5 @@ echo "Starting Sauce Connect in the background, logging into:"
|
||||
echo " $CONNECT_LOG"
|
||||
echo " $CONNECT_STDOUT"
|
||||
echo " $CONNECT_STDERR"
|
||||
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS -v \
|
||||
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \
|
||||
--logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &
|
||||
|
||||
@@ -110,10 +110,16 @@ var getPreviousVersions = function() {
|
||||
})
|
||||
.filter()
|
||||
.map(function(version) {
|
||||
// angular.js didn't follow semantic version until 1.20rc1
|
||||
if ((version.major === 1 && version.minor === 0 && version.prerelease.length > 0) || (version.major === 1 && version.minor === 2 && version.prerelease[0] === 'rc1')) {
|
||||
version.version = [version.major, version.minor, version.patch].join('.') + version.prerelease.join('');
|
||||
version.raw = 'v' + version.version;
|
||||
}
|
||||
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
|
||||
// Versions before 1.0.2 had a different docs folder name
|
||||
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2)) {
|
||||
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.patch < 2)) {
|
||||
version.docsUrl += '-' + version.version;
|
||||
version.isOldDocsUrl = true;
|
||||
}
|
||||
return version;
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+6954
-1523
File diff suppressed because it is too large
Load Diff
+7
-2
@@ -6,6 +6,11 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "~0.10",
|
||||
"npm": "~2.5"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
@@ -39,7 +44,7 @@
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "vojtajina/karma#socketio_10",
|
||||
"karma": "0.12.32",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
@@ -53,7 +58,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "^1.6.0",
|
||||
"protractor": "^2.0.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
|
||||
@@ -63,6 +63,21 @@ function prepare {
|
||||
cp $BUILD_DIR/angular-csp.css $TMP_DIR/bower-angular
|
||||
|
||||
|
||||
#
|
||||
# Run local precommit script if there is one
|
||||
#
|
||||
for repo in "${REPOS[@]}"
|
||||
do
|
||||
if [ -f $TMP_DIR/bower-$repo/precommit.sh ]
|
||||
then
|
||||
echo "-- Running precommit.sh script for bower-$repo"
|
||||
cd $TMP_DIR/bower-$repo
|
||||
$TMP_DIR/bower-$repo/precommit.sh
|
||||
cd $SCRIPT_DIR
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
#
|
||||
# update bower.json
|
||||
# tag each repo
|
||||
|
||||
@@ -23,22 +23,26 @@ function init {
|
||||
}
|
||||
|
||||
function prepare {
|
||||
if [[ $IS_SNAPSHOT_BUILD ]]; then
|
||||
# nothing to prepare for snapshot builds as
|
||||
# code.angularjs.org will fetch the current snapshot from
|
||||
# the build server during publish
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "-- Cloning code.angularjs.org"
|
||||
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR
|
||||
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR --depth=1
|
||||
|
||||
#
|
||||
# copy the files from the build
|
||||
#
|
||||
echo "-- Updating code.angularjs.org"
|
||||
mkdir $REPO_DIR/$NEW_VERSION
|
||||
cp -r $BUILD_DIR/* $REPO_DIR/$NEW_VERSION/
|
||||
|
||||
if [[ $IS_SNAPSHOT_BUILD ]]; then
|
||||
#
|
||||
# update the snapshot folder
|
||||
#
|
||||
rm -rf $REPO_DIR/snapshot
|
||||
mkdir $REPO_DIR/snapshot
|
||||
cp -r $BUILD_DIR/* $REPO_DIR/snapshot/
|
||||
else
|
||||
#
|
||||
# copy the files from the build
|
||||
#
|
||||
mkdir $REPO_DIR/$NEW_VERSION
|
||||
cp -r $BUILD_DIR/* $REPO_DIR/$NEW_VERSION/
|
||||
fi
|
||||
|
||||
#
|
||||
# commit
|
||||
@@ -50,13 +54,6 @@ function prepare {
|
||||
}
|
||||
|
||||
|
||||
function _update_snapshot() {
|
||||
for backend in "$@" ; do
|
||||
echo "-- Updating snapshot version: backend=$backend"
|
||||
curl -G --data-urlencode "ver=$NEW_VERSION" http://$backend:8003/fetchLatestSnapshot.php
|
||||
done
|
||||
}
|
||||
|
||||
function _update_code() {
|
||||
cd $REPO_DIR
|
||||
|
||||
@@ -74,12 +71,7 @@ function publish {
|
||||
# the currently serving Compute Engine backends.
|
||||
# code.angularjs.org is served out of port 8003 on these backends.
|
||||
backends=("$(dig backends.angularjs.org +short TXT | python -c 'print raw_input()[1:-1].replace(",", "\n")')")
|
||||
|
||||
if [[ $IS_SNAPSHOT_BUILD ]]; then
|
||||
_update_snapshot ${backends[@]}
|
||||
else
|
||||
_update_code ${backends[@]}
|
||||
fi
|
||||
_update_code ${backends[@]}
|
||||
}
|
||||
|
||||
source $(dirname $0)/../utils.inc
|
||||
|
||||
@@ -10,22 +10,17 @@
|
||||
var _ = require('lodash');
|
||||
var sorted = require('sorted-object');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
|
||||
function cleanModule(module, name) {
|
||||
|
||||
// keep `from` and `resolve` properties for git dependencies, delete otherwise
|
||||
if (!(module.resolved && module.resolved.match(/^git:\/\//))) {
|
||||
delete module.from;
|
||||
// keep `resolve` properties for git dependencies, delete otherwise
|
||||
delete module.from;
|
||||
if (!(module.resolved && module.resolved.match(/^git(\+[a-z]+)?:\/\//))) {
|
||||
delete module.resolved;
|
||||
}
|
||||
|
||||
if (name === 'chokidar') {
|
||||
if (module.version === '0.8.1') {
|
||||
delete module.dependencies;
|
||||
}
|
||||
}
|
||||
|
||||
_.forEach(module.dependencies, function(mod, name) {
|
||||
cleanModule(mod, name);
|
||||
});
|
||||
@@ -33,10 +28,11 @@ function cleanModule(module, name) {
|
||||
|
||||
|
||||
console.log('Reading npm-shrinkwrap.json');
|
||||
var shrinkwrap = require('./../npm-shrinkwrap.json');
|
||||
var shrinkwrap = require('../../npm-shrinkwrap.json');
|
||||
|
||||
console.log('Cleaning shrinkwrap object');
|
||||
cleanModule(shrinkwrap, shrinkwrap.name);
|
||||
|
||||
console.log('Writing cleaned npm-shrinkwrap.json');
|
||||
fs.writeFileSync('./npm-shrinkwrap.json', JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
|
||||
var cleanShrinkwrapPath = path.join(__dirname, '..', '..', 'npm-shrinkwrap.clean.json');
|
||||
console.log('Writing cleaned to', cleanShrinkwrapPath);
|
||||
fs.writeFileSync(cleanShrinkwrapPath, JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/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
|
||||
@@ -7,9 +7,9 @@ export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
|
||||
|
||||
if [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11"
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS"
|
||||
else
|
||||
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11"
|
||||
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS"
|
||||
fi
|
||||
|
||||
grunt test:promises-aplus
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
|
||||
# Wait for Connect to be ready before exiting
|
||||
# Time out if we wait for more than 2 minutes, so that we can print logs.
|
||||
let "counter=0"
|
||||
|
||||
while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do
|
||||
let "counter++"
|
||||
if [ $counter -gt 240 ]; then
|
||||
echo "Timed out after 2 minutes waiting for browser provider ready file"
|
||||
# We must manually print logs here because travis will not run
|
||||
# after_script commands if the failure occurs before the script
|
||||
# phase.
|
||||
./scripts/travis/print_logs.sh
|
||||
exit 5
|
||||
fi
|
||||
sleep .5
|
||||
done
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"extend": false,
|
||||
"toInt": false,
|
||||
"inherit": false,
|
||||
"merge": false,
|
||||
"noop": false,
|
||||
"identity": false,
|
||||
"valueFn": false,
|
||||
@@ -70,6 +71,8 @@
|
||||
"toJsonReplacer": false,
|
||||
"toJson": false,
|
||||
"fromJson": false,
|
||||
"convertTimezoneToLocal": false,
|
||||
"timezoneToOffset": false,
|
||||
"startingTag": false,
|
||||
"tryDecodeURIComponent": false,
|
||||
"parseKeyValue": false,
|
||||
@@ -93,6 +96,7 @@
|
||||
"skipDestroyOnNextJQueryCleanData": true,
|
||||
|
||||
"NODE_TYPE_ELEMENT": false,
|
||||
"NODE_TYPE_ATTRIBUTE": false,
|
||||
"NODE_TYPE_TEXT": false,
|
||||
"NODE_TYPE_COMMENT": false,
|
||||
"NODE_TYPE_COMMENT": false,
|
||||
|
||||
+150
-24
@@ -29,6 +29,7 @@
|
||||
extend: true,
|
||||
toInt: true,
|
||||
inherit: true,
|
||||
merge: true,
|
||||
noop: true,
|
||||
identity: true,
|
||||
valueFn: true,
|
||||
@@ -58,12 +59,15 @@
|
||||
shallowCopy: true,
|
||||
equals: true,
|
||||
csp: true,
|
||||
jq: true,
|
||||
concat: true,
|
||||
sliceArgs: true,
|
||||
bind: true,
|
||||
toJsonReplacer: true,
|
||||
toJson: true,
|
||||
fromJson: true,
|
||||
convertTimezoneToLocal: true,
|
||||
timezoneToOffset: true,
|
||||
startingTag: true,
|
||||
tryDecodeURIComponent: true,
|
||||
parseKeyValue: true,
|
||||
@@ -84,6 +88,7 @@
|
||||
createMap: true,
|
||||
|
||||
NODE_TYPE_ELEMENT: true,
|
||||
NODE_TYPE_ATTRIBUTE: true,
|
||||
NODE_TYPE_TEXT: true,
|
||||
NODE_TYPE_COMMENT: true,
|
||||
NODE_TYPE_DOCUMENT: true,
|
||||
@@ -317,6 +322,31 @@ function setHashKey(obj, h) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function baseExtend(dst, objs, deep) {
|
||||
var h = dst.$$hashKey;
|
||||
|
||||
for (var i = 0, ii = objs.length; i < ii; ++i) {
|
||||
var obj = objs[i];
|
||||
if (!isObject(obj) && !isFunction(obj)) continue;
|
||||
var keys = Object.keys(obj);
|
||||
for (var j = 0, jj = keys.length; j < jj; j++) {
|
||||
var key = keys[j];
|
||||
var src = obj[key];
|
||||
|
||||
if (deep && isObject(src)) {
|
||||
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
|
||||
baseExtend(dst[key], [src], true);
|
||||
} else {
|
||||
dst[key] = src;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setHashKey(dst, h);
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.extend
|
||||
@@ -327,30 +357,43 @@ function setHashKey(obj, h) {
|
||||
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
|
||||
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
|
||||
* by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
|
||||
* Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
|
||||
*
|
||||
* **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
|
||||
* {@link angular.merge} for this.
|
||||
*
|
||||
* @param {Object} dst Destination object.
|
||||
* @param {...Object} src Source object(s).
|
||||
* @returns {Object} Reference to `dst`.
|
||||
*/
|
||||
function extend(dst) {
|
||||
var h = dst.$$hashKey;
|
||||
|
||||
for (var i = 1, ii = arguments.length; i < ii; i++) {
|
||||
var obj = arguments[i];
|
||||
if (obj) {
|
||||
var keys = Object.keys(obj);
|
||||
for (var j = 0, jj = keys.length; j < jj; j++) {
|
||||
var key = keys[j];
|
||||
dst[key] = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setHashKey(dst, h);
|
||||
return dst;
|
||||
return baseExtend(dst, slice.call(arguments, 1), false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.merge
|
||||
* @module ng
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
|
||||
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
|
||||
* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
|
||||
*
|
||||
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
|
||||
* objects, performing a deep copy.
|
||||
*
|
||||
* @param {Object} dst Destination object.
|
||||
* @param {...Object} src Source object(s).
|
||||
* @returns {Object} Reference to `dst`.
|
||||
*/
|
||||
function merge(dst) {
|
||||
return baseExtend(dst, slice.call(arguments, 1), true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function toInt(str) {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
@@ -477,6 +520,12 @@ function isString(value) {return typeof value === 'string';}
|
||||
* @description
|
||||
* Determines if a reference is a `Number`.
|
||||
*
|
||||
* This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
|
||||
*
|
||||
* If you wish to exclude these then you can use the native
|
||||
* [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
|
||||
* method.
|
||||
*
|
||||
* @param {*} value Reference to check.
|
||||
* @returns {boolean} True if `value` is a `Number`.
|
||||
*/
|
||||
@@ -859,10 +908,11 @@ function equals(o1, o2) {
|
||||
} else if (isDate(o1)) {
|
||||
if (!isDate(o2)) return false;
|
||||
return equals(o1.getTime(), o2.getTime());
|
||||
} else if (isRegExp(o1) && isRegExp(o2)) {
|
||||
return o1.toString() == o2.toString();
|
||||
} else if (isRegExp(o1)) {
|
||||
return isRegExp(o2) ? o1.toString() == o2.toString() : false;
|
||||
} else {
|
||||
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
|
||||
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
|
||||
isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
|
||||
keySet = {};
|
||||
for (key in o1) {
|
||||
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
|
||||
@@ -901,7 +951,58 @@ var csp = function() {
|
||||
return (csp.isActive_ = active);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @module ng
|
||||
* @name ngJq
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string=} the name of the library available under `window`
|
||||
* to be used for angular.element
|
||||
* @description
|
||||
* Use this directive to force the angular.element library. This should be
|
||||
* used to force either jqLite by leaving ng-jq blank or setting the name of
|
||||
* the jquery variable under window (eg. jQuery).
|
||||
*
|
||||
* Since this directive is global for the angular library, it is recommended
|
||||
* that it's added to the same element as ng-app or the HTML element, but it is not mandatory.
|
||||
* It needs to be noted that only the first instance of `ng-jq` will be used and all others
|
||||
* ignored.
|
||||
*
|
||||
* @example
|
||||
* This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
|
||||
```html
|
||||
<!doctype html>
|
||||
<html ng-app ng-jq>
|
||||
...
|
||||
...
|
||||
</html>
|
||||
```
|
||||
* @example
|
||||
* This example shows how to use a jQuery based library of a different name.
|
||||
* The library name must be available at the top most 'window'.
|
||||
```html
|
||||
<!doctype html>
|
||||
<html ng-app ng-jq="jQueryLib">
|
||||
...
|
||||
...
|
||||
</html>
|
||||
```
|
||||
*/
|
||||
var jq = function() {
|
||||
if (isDefined(jq.name_)) return jq.name_;
|
||||
var el;
|
||||
var i, ii = ngAttrPrefixes.length, prefix, name;
|
||||
for (i = 0; i < ii; ++i) {
|
||||
prefix = ngAttrPrefixes[i];
|
||||
if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
|
||||
name = el.getAttribute(prefix + 'jq');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (jq.name_ = name);
|
||||
};
|
||||
|
||||
function concat(array1, array2, index) {
|
||||
return array1.concat(slice.call(array2, index));
|
||||
@@ -980,8 +1081,8 @@ function toJsonReplacer(key, value) {
|
||||
* stripped since angular uses this notation internally.
|
||||
*
|
||||
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
|
||||
* @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
|
||||
* @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
|
||||
* If set to an integer, the JSON output will contain that many spaces per indentation.
|
||||
* @returns {string|undefined} JSON-ified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
@@ -1012,6 +1113,26 @@ function fromJson(json) {
|
||||
}
|
||||
|
||||
|
||||
function timezoneToOffset(timezone, fallback) {
|
||||
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
|
||||
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
|
||||
}
|
||||
|
||||
|
||||
function addDateMinutes(date, minutes) {
|
||||
date = new Date(date.getTime());
|
||||
date.setMinutes(date.getMinutes() + minutes);
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
function convertTimezoneToLocal(date, timezone, reverse) {
|
||||
reverse = reverse ? -1 : 1;
|
||||
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
|
||||
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @returns {string} Returns the string representation of the element.
|
||||
*/
|
||||
@@ -1140,10 +1261,9 @@ var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
|
||||
|
||||
function getNgAttribute(element, ngAttr) {
|
||||
var attr, i, ii = ngAttrPrefixes.length;
|
||||
element = jqLite(element);
|
||||
for (i = 0; i < ii; ++i) {
|
||||
attr = ngAttrPrefixes[i] + ngAttr;
|
||||
if (isString(attr = element.attr(attr))) {
|
||||
if (isString(attr = element.getAttribute(attr))) {
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@@ -1474,7 +1594,12 @@ function bindJQuery() {
|
||||
}
|
||||
|
||||
// bind to jQuery if present;
|
||||
jQuery = window.jQuery;
|
||||
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`.
|
||||
}
|
||||
|
||||
// Use jQuery if it exists with proper functionality, otherwise default to us.
|
||||
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
|
||||
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
|
||||
@@ -1613,6 +1738,7 @@ function createMap() {
|
||||
}
|
||||
|
||||
var NODE_TYPE_ELEMENT = 1;
|
||||
var NODE_TYPE_ATTRIBUTE = 2;
|
||||
var NODE_TYPE_TEXT = 3;
|
||||
var NODE_TYPE_COMMENT = 8;
|
||||
var NODE_TYPE_DOCUMENT = 9;
|
||||
|
||||
+15
-2
@@ -57,6 +57,8 @@
|
||||
|
||||
$AnchorScrollProvider,
|
||||
$AnimateProvider,
|
||||
$$CoreAnimateQueueProvider,
|
||||
$$CoreAnimateRunnerProvider,
|
||||
$BrowserProvider,
|
||||
$CacheFactoryProvider,
|
||||
$ControllerProvider,
|
||||
@@ -65,7 +67,10 @@
|
||||
$FilterProvider,
|
||||
$InterpolateProvider,
|
||||
$IntervalProvider,
|
||||
$$HashMapProvider,
|
||||
$HttpProvider,
|
||||
$HttpParamSerializerProvider,
|
||||
$HttpParamSerializerJQLikeProvider,
|
||||
$HttpBackendProvider,
|
||||
$LocationProvider,
|
||||
$LogProvider,
|
||||
@@ -84,7 +89,8 @@
|
||||
$$RAFProvider,
|
||||
$$AsyncCallbackProvider,
|
||||
$WindowProvider,
|
||||
$$jqLiteProvider
|
||||
$$jqLiteProvider,
|
||||
$$CookieReaderProvider
|
||||
*/
|
||||
|
||||
|
||||
@@ -116,6 +122,7 @@ function publishExternalAPI(angular) {
|
||||
'bootstrap': bootstrap,
|
||||
'copy': copy,
|
||||
'extend': extend,
|
||||
'merge': merge,
|
||||
'equals': equals,
|
||||
'element': jqLite,
|
||||
'forEach': forEach,
|
||||
@@ -212,6 +219,8 @@ function publishExternalAPI(angular) {
|
||||
$provide.provider({
|
||||
$anchorScroll: $AnchorScrollProvider,
|
||||
$animate: $AnimateProvider,
|
||||
$$animateQueue: $$CoreAnimateQueueProvider,
|
||||
$$AnimateRunner: $$CoreAnimateRunnerProvider,
|
||||
$browser: $BrowserProvider,
|
||||
$cacheFactory: $CacheFactoryProvider,
|
||||
$controller: $ControllerProvider,
|
||||
@@ -221,6 +230,8 @@ function publishExternalAPI(angular) {
|
||||
$interpolate: $InterpolateProvider,
|
||||
$interval: $IntervalProvider,
|
||||
$http: $HttpProvider,
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
$httpBackend: $HttpBackendProvider,
|
||||
$location: $LocationProvider,
|
||||
$log: $LogProvider,
|
||||
@@ -238,7 +249,9 @@ function publishExternalAPI(angular) {
|
||||
$window: $WindowProvider,
|
||||
$$rAF: $$RAFProvider,
|
||||
$$asyncCallback: $$AsyncCallbackProvider,
|
||||
$$jqLite: $$jqLiteProvider
|
||||
$$jqLite: $$jqLiteProvider,
|
||||
$$HashMap: $$HashMapProvider,
|
||||
$$cookieReader: $$CookieReaderProvider
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -73,3 +73,9 @@ HashMap.prototype = {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
var $$HashMapProvider = [function() {
|
||||
this.$get = [function() {
|
||||
return HashMap;
|
||||
}];
|
||||
}];
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* Any commits to this file should be reviewed with security in mind. *
|
||||
* Changes to this file can potentially create security vulnerabilities. *
|
||||
* An approval from 2 Core members with history of modifying *
|
||||
* this file is required. *
|
||||
* *
|
||||
* Does the change somehow allow for arbitrary javascript to be executed? *
|
||||
* Or allows for someone to change the prototype of built-in objects? *
|
||||
* Or gives undesired access to variables likes document or window? *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* global JQLitePrototype: true,
|
||||
addEventListenerFn: true,
|
||||
removeEventListenerFn: true,
|
||||
@@ -587,6 +598,10 @@ forEach({
|
||||
},
|
||||
|
||||
attr: function(element, name, value) {
|
||||
var nodeType = element.nodeType;
|
||||
if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
|
||||
return;
|
||||
}
|
||||
var lowercasedName = lowercase(name);
|
||||
if (BOOLEAN_ATTR[lowercasedName]) {
|
||||
if (isDefined(value)) {
|
||||
|
||||
+21
-2
@@ -193,6 +193,18 @@ function setupModuleLoader(window) {
|
||||
*/
|
||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#decorator
|
||||
* @module ng
|
||||
* @param {string} The name of the service to decorate.
|
||||
* @param {Function} This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @description
|
||||
* See {@link auto.$provide#decorator $provide.decorator()}.
|
||||
*/
|
||||
decorator: invokeLater('$provide', 'decorator'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.Module#animation
|
||||
@@ -206,7 +218,7 @@ function setupModuleLoader(window) {
|
||||
*
|
||||
*
|
||||
* Defines an animation hook that can be later used with
|
||||
* {@link ngAnimate.$animate $animate} service and directives that use this service.
|
||||
* {@link $animate $animate} service and directives that use this service.
|
||||
*
|
||||
* ```js
|
||||
* module.animation('.animation-name', function($inject1, $inject2) {
|
||||
@@ -231,10 +243,17 @@ function setupModuleLoader(window) {
|
||||
* @ngdoc method
|
||||
* @name angular.Module#filter
|
||||
* @module ng
|
||||
* @param {string} name Filter name.
|
||||
* @param {string} name Filter name - this must be a valid angular expression identifier
|
||||
* @param {Function} filterFactory Factory function for creating new instance of filter.
|
||||
* @description
|
||||
* See {@link ng.$filterProvider#register $filterProvider.register()}.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
|
||||
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
|
||||
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
|
||||
* (`myapp_subsection_filterx`).
|
||||
* </div>
|
||||
*/
|
||||
filter: invokeLater('$filterProvider', 'register'),
|
||||
|
||||
|
||||
+10
-5
@@ -38,9 +38,10 @@ function $AnchorScrollProvider() {
|
||||
* @requires $rootScope
|
||||
*
|
||||
* @description
|
||||
* When called, it checks the current value of {@link ng.$location#hash $location.hash()} and
|
||||
* scrolls to the related element, according to the rules specified in the
|
||||
* [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
|
||||
* 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).
|
||||
*
|
||||
* 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
|
||||
@@ -49,6 +50,9 @@ function $AnchorScrollProvider() {
|
||||
* Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
|
||||
* vertical scroll-offset (either fixed or dynamic).
|
||||
*
|
||||
* @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
|
||||
* {@link ng.$location#hash $location.hash()} will be used.
|
||||
*
|
||||
* @property {(number|function|jqLite)} yOffset
|
||||
* If set, specifies a vertical scroll-offset. This is often useful when there are fixed
|
||||
* positioned elements at the top of the page, such as navbars, headers etc.
|
||||
@@ -232,8 +236,9 @@ function $AnchorScrollProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
function scroll() {
|
||||
var hash = $location.hash(), elm;
|
||||
function scroll(hash) {
|
||||
hash = isString(hash) ? hash : $location.hash();
|
||||
var elm;
|
||||
|
||||
// empty hash, scroll to the top of the page
|
||||
if (!hash) scrollTo(null);
|
||||
|
||||
+329
-246
@@ -1,6 +1,151 @@
|
||||
'use strict';
|
||||
|
||||
var $animateMinErr = minErr('$animate');
|
||||
var ELEMENT_NODE = 1;
|
||||
|
||||
function mergeClasses(a,b) {
|
||||
if (!a && !b) return '';
|
||||
if (!a) return b;
|
||||
if (!b) return a;
|
||||
if (isArray(a)) a = a.join(' ');
|
||||
if (isArray(b)) b = b.join(' ');
|
||||
return a + ' ' + b;
|
||||
}
|
||||
|
||||
function extractElementNode(element) {
|
||||
for (var i = 0; i < element.length; i++) {
|
||||
var elm = element[i];
|
||||
if (elm.nodeType === ELEMENT_NODE) {
|
||||
return elm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitClasses(classes) {
|
||||
if (isString(classes)) {
|
||||
classes = classes.split(' ');
|
||||
}
|
||||
|
||||
var obj = {};
|
||||
forEach(classes, function(klass) {
|
||||
// sometimes the split leaves empty string values
|
||||
// incase extra spaces were applied to the options
|
||||
if (klass.length) {
|
||||
obj[klass] = true;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
var $$CoreAnimateRunnerProvider = function() {
|
||||
this.$get = ['$q', '$$rAF', function($q, $$rAF) {
|
||||
function AnimateRunner() {}
|
||||
AnimateRunner.all = noop;
|
||||
AnimateRunner.chain = noop;
|
||||
AnimateRunner.prototype = {
|
||||
end: noop,
|
||||
cancel: noop,
|
||||
resume: noop,
|
||||
pause: noop,
|
||||
complete: noop,
|
||||
then: function(pass, fail) {
|
||||
return $q(function(resolve) {
|
||||
$$rAF(function() {
|
||||
resolve();
|
||||
});
|
||||
}).then(pass, fail);
|
||||
}
|
||||
};
|
||||
return AnimateRunner;
|
||||
}];
|
||||
};
|
||||
|
||||
// this is prefixed with Core since it conflicts with
|
||||
// the animateQueueProvider defined in ngAnimate/animateQueue.js
|
||||
var $$CoreAnimateQueueProvider = function() {
|
||||
var postDigestQueue = new HashMap();
|
||||
var postDigestElements = [];
|
||||
|
||||
this.$get = ['$$AnimateRunner', '$rootScope',
|
||||
function($$AnimateRunner, $rootScope) {
|
||||
return {
|
||||
enabled: noop,
|
||||
on: noop,
|
||||
off: noop,
|
||||
|
||||
push: function(element, event, options, domOperation) {
|
||||
domOperation && domOperation();
|
||||
|
||||
options = options || {};
|
||||
options.from && element.css(options.from);
|
||||
options.to && element.css(options.to);
|
||||
|
||||
if (options.addClass || options.removeClass) {
|
||||
addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
|
||||
}
|
||||
|
||||
return new $$AnimateRunner(); // jshint ignore:line
|
||||
}
|
||||
};
|
||||
|
||||
function addRemoveClassesPostDigest(element, add, remove) {
|
||||
var data = postDigestQueue.get(element);
|
||||
var classVal;
|
||||
|
||||
if (!data) {
|
||||
postDigestQueue.put(element, data = {});
|
||||
postDigestElements.push(element);
|
||||
}
|
||||
|
||||
if (add) {
|
||||
forEach(add.split(' '), function(className) {
|
||||
if (className) {
|
||||
data[className] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
forEach(remove.split(' '), function(className) {
|
||||
if (className) {
|
||||
data[className] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (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);
|
||||
}
|
||||
});
|
||||
|
||||
postDigestElements.length = 0;
|
||||
});
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
@@ -8,20 +153,18 @@ var $animateMinErr = minErr('$animate');
|
||||
*
|
||||
* @description
|
||||
* Default implementation of $animate that doesn't perform any animations, instead just
|
||||
* synchronously performs DOM
|
||||
* updates and calls done() callbacks.
|
||||
* synchronously performs DOM updates and resolves the returned runner promise.
|
||||
*
|
||||
* In order to enable animations the ngAnimate module has to be loaded.
|
||||
* In order to enable animations the `ngAnimate` module has to be loaded.
|
||||
*
|
||||
* To see the functional implementation check out src/ngAnimate/animate.js
|
||||
* To see the functional implementation check out `src/ngAnimate/animate.js`.
|
||||
*/
|
||||
var $AnimateProvider = ['$provide', function($provide) {
|
||||
var provider = this;
|
||||
|
||||
this.$$registeredAnimations = [];
|
||||
|
||||
this.$$selectors = {};
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $animateProvider#register
|
||||
*
|
||||
@@ -30,33 +173,43 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* animation object which contains callback functions for each event that is expected to be
|
||||
* animated.
|
||||
*
|
||||
* * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction`
|
||||
* must be called once the element animation is complete. If a function is returned then the
|
||||
* animation service will use this function to cancel the animation whenever a cancel event is
|
||||
* triggered.
|
||||
* * `eventFn`: `function(element, ... , doneFunction, options)`
|
||||
* The element to animate, the `doneFunction` and the options fed into the animation. Depending
|
||||
* on the type of animation additional arguments will be injected into the animation function. The
|
||||
* list below explains the function signatures for the different animation methods:
|
||||
*
|
||||
* - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
|
||||
* - addClass: function(element, addedClasses, doneFunction, options)
|
||||
* - removeClass: function(element, removedClasses, doneFunction, options)
|
||||
* - enter, leave, move: function(element, doneFunction, options)
|
||||
* - animate: function(element, fromStyles, toStyles, doneFunction, options)
|
||||
*
|
||||
* Make sure to trigger the `doneFunction` once the animation is fully complete.
|
||||
*
|
||||
* ```js
|
||||
* return {
|
||||
* eventFn : function(element, done) {
|
||||
* //code to run the animation
|
||||
* //once complete, then run done()
|
||||
* return function cancellationFunction() {
|
||||
* //code to cancel the animation
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* //enter, leave, move signature
|
||||
* eventFn : function(element, done, options) {
|
||||
* //code to run the animation
|
||||
* //once complete, then run done()
|
||||
* return function endFunction(wasCancelled) {
|
||||
* //code to cancel the animation
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {string} name The name of the animation.
|
||||
* @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
|
||||
* @param {Function} factory The factory function that will be executed to return the animation
|
||||
* object.
|
||||
*/
|
||||
this.register = function(name, factory) {
|
||||
if (name && name.charAt(0) !== '.') {
|
||||
throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
|
||||
}
|
||||
|
||||
var key = name + '-animation';
|
||||
if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel',
|
||||
"Expecting class selector starting with '.' got '{0}'.", name);
|
||||
this.$$selectors[name.substr(1)] = key;
|
||||
provider.$$registeredAnimations[name.substr(1)] = key;
|
||||
$provide.factory(key, factory);
|
||||
};
|
||||
|
||||
@@ -67,8 +220,8 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @description
|
||||
* Sets and/or returns the CSS class regular expression that is checked when performing
|
||||
* an animation. Upon bootstrap the classNameFilter value is not set at all and will
|
||||
* therefore enable $animate to attempt to perform an animation on any element.
|
||||
* When setting the classNameFilter value, animations will only be performed on elements
|
||||
* therefore enable $animate to attempt to perform an animation on any element that is triggered.
|
||||
* When setting the `classNameFilter` value, animations will only be performed on elements
|
||||
* that successfully match the filter expression. This in turn can boost performance
|
||||
* for low-powered devices as well as applications containing a lot of structural operations.
|
||||
* @param {RegExp=} expression The className expression which will be checked against all animations
|
||||
@@ -81,98 +234,48 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
return this.$$classNameFilter;
|
||||
};
|
||||
|
||||
this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
|
||||
|
||||
var currentDefer;
|
||||
|
||||
function runAnimationPostDigest(fn) {
|
||||
var cancelFn, defer = $$q.defer();
|
||||
defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
|
||||
cancelFn && cancelFn();
|
||||
};
|
||||
|
||||
$rootScope.$$postDigest(function ngAnimatePostDigest() {
|
||||
cancelFn = fn(function ngAnimateNotifyComplete() {
|
||||
defer.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function resolveElementClasses(element, classes) {
|
||||
var toAdd = [], toRemove = [];
|
||||
|
||||
var hasClasses = createMap();
|
||||
forEach((element.attr('class') || '').split(/\s+/), function(className) {
|
||||
hasClasses[className] = true;
|
||||
});
|
||||
|
||||
forEach(classes, function(status, className) {
|
||||
var hasClass = hasClasses[className];
|
||||
|
||||
// If the most recent class manipulation (via $animate) was to remove the class, and the
|
||||
// element currently has the class, the class is scheduled for removal. Otherwise, if
|
||||
// the most recent class manipulation (via $animate) was to add the class, and the
|
||||
// element does not currently have the class, the class is scheduled to be added.
|
||||
if (status === false && hasClass) {
|
||||
toRemove.push(className);
|
||||
} else if (status === true && !hasClass) {
|
||||
toAdd.push(className);
|
||||
this.$get = ['$$animateQueue', function($$animateQueue) {
|
||||
function domInsert(element, parentElement, afterElement) {
|
||||
// if for some reason the previous element was removed
|
||||
// from the dom sometime before this code runs then let's
|
||||
// just stick to using the parent element as the anchor
|
||||
if (afterElement) {
|
||||
var afterNode = extractElementNode(afterElement);
|
||||
if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
|
||||
afterElement = null;
|
||||
}
|
||||
});
|
||||
|
||||
return (toAdd.length + toRemove.length) > 0 &&
|
||||
[toAdd.length ? toAdd : null, toRemove.length ? toRemove : null];
|
||||
}
|
||||
|
||||
function cachedClassManipulation(cache, classes, op) {
|
||||
for (var i=0, ii = classes.length; i < ii; ++i) {
|
||||
var className = classes[i];
|
||||
cache[className] = op;
|
||||
}
|
||||
}
|
||||
|
||||
function asyncPromise() {
|
||||
// only serve one instance of a promise in order to save CPU cycles
|
||||
if (!currentDefer) {
|
||||
currentDefer = $$q.defer();
|
||||
$$asyncCallback(function() {
|
||||
currentDefer.resolve();
|
||||
currentDefer = null;
|
||||
});
|
||||
}
|
||||
return currentDefer.promise;
|
||||
}
|
||||
|
||||
function applyStyles(element, options) {
|
||||
if (angular.isObject(options)) {
|
||||
var styles = extend(options.from || {}, options.to || {});
|
||||
element.css(styles);
|
||||
}
|
||||
afterElement ? afterElement.after(element) : parentElement.prepend(element);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @ngdoc service
|
||||
* @name $animate
|
||||
* @description The $animate service provides rudimentary DOM manipulation functions to
|
||||
* insert, remove and move elements within the DOM, as well as adding and removing classes.
|
||||
* This service is the core service used by the ngAnimate $animator service which provides
|
||||
* high-level animation hooks for CSS and JavaScript.
|
||||
* @description The $animate service exposes a series of DOM utility methods that provide support
|
||||
* for animation hooks. The default behavior is the application of DOM operations, however,
|
||||
* 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.
|
||||
*
|
||||
* $animate is available in the AngularJS core, however, the ngAnimate module must be included
|
||||
* to enable full out animation support. Otherwise, $animate will only perform simple DOM
|
||||
* manipulation operations.
|
||||
* By default $animate doesn't trigger an 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`,
|
||||
* `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
|
||||
*
|
||||
* To learn more about enabling animation support, click here to visit the {@link ngAnimate
|
||||
* ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service
|
||||
* page}.
|
||||
* It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
|
||||
*
|
||||
* To learn more about enabling animation support, click here to visit the
|
||||
* {@link ngAnimate ngAnimate module page}.
|
||||
*/
|
||||
return {
|
||||
animate: function(element, from, to) {
|
||||
applyStyles(element, { from: from, to: to });
|
||||
return asyncPromise();
|
||||
// we don't call it directly since non-existant arguments may
|
||||
// be interpreted as null within the sub enabled function
|
||||
on: $$animateQueue.on,
|
||||
off: $$animateQueue.off,
|
||||
enabled: $$animateQueue.enabled,
|
||||
|
||||
cancel: function(runner) {
|
||||
runner.cancel && runner.end();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -180,38 +283,23 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @ngdoc method
|
||||
* @name $animate#enter
|
||||
* @kind function
|
||||
* @description Inserts the element into the DOM either after the `after` element or
|
||||
* as the first child within the `parent` element. When the function is called a promise
|
||||
* is returned that will be resolved at a later time.
|
||||
* @description Inserts the element into the DOM either after the `after` element (if provided) or
|
||||
* as the first child within the `parent` element and then triggers an animation.
|
||||
* A promise is returned that will be resolved during the next digest once the animation
|
||||
* has completed.
|
||||
*
|
||||
* @param {DOMElement} element the element which will be inserted into the DOM
|
||||
* @param {DOMElement} parent the parent element which will append the element as
|
||||
* a child (if the after element is not present)
|
||||
* @param {DOMElement} after the sibling element which will append the element
|
||||
* after itself
|
||||
* @param {object=} options an optional collection of styles that will be applied to the element.
|
||||
* a child (so long as the after element is not present)
|
||||
* @param {DOMElement=} after the sibling element after which the element will be appended
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
enter: function(element, parent, after, options) {
|
||||
applyStyles(element, options);
|
||||
after ? after.after(element)
|
||||
: parent.prepend(element);
|
||||
return asyncPromise();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @ngdoc method
|
||||
* @name $animate#leave
|
||||
* @kind function
|
||||
* @description Removes the element from the DOM. When the function is called a promise
|
||||
* is returned that will be resolved at a later time.
|
||||
* @param {DOMElement} element the element which will be removed from the DOM
|
||||
* @param {object=} options an optional collection of options that will be applied to the element.
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
leave: function(element, options) {
|
||||
element.remove();
|
||||
return asyncPromise();
|
||||
parent = parent || after.parent();
|
||||
domInsert(element, parent, after);
|
||||
return $$animateQueue.push(element, 'enter', options);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -219,153 +307,148 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @ngdoc method
|
||||
* @name $animate#move
|
||||
* @kind function
|
||||
* @description Moves the position of the provided element within the DOM to be placed
|
||||
* either after the `after` element or inside of the `parent` element. When the function
|
||||
* is called a promise is returned that will be resolved at a later time.
|
||||
* @description Inserts (moves) the element into its new position in the DOM either after
|
||||
* the `after` element (if provided) or as the first child within the `parent` element
|
||||
* and then triggers an animation. A promise is returned that will be resolved
|
||||
* during the next digest once the animation has completed.
|
||||
*
|
||||
* @param {DOMElement} element the element which will be moved into the new DOM position
|
||||
* @param {DOMElement} parent the parent element which will append the element as
|
||||
* a child (so long as the after element is not present)
|
||||
* @param {DOMElement=} after the sibling element after which the element will be appended
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @param {DOMElement} element the element which will be moved around within the
|
||||
* DOM
|
||||
* @param {DOMElement} parent the parent element where the element will be
|
||||
* inserted into (if the after element is not present)
|
||||
* @param {DOMElement} after the sibling element where the element will be
|
||||
* positioned next to
|
||||
* @param {object=} options an optional collection of options that will be applied to the element.
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
move: function(element, parent, after, options) {
|
||||
// Do not remove element before insert. Removing will cause data associated with the
|
||||
// element to be dropped. Insert will implicitly do the remove.
|
||||
return this.enter(element, parent, after, options);
|
||||
parent = parent || after.parent();
|
||||
domInsert(element, parent, after);
|
||||
return $$animateQueue.push(element, 'move', options);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $animate#leave
|
||||
* @kind function
|
||||
* @description Triggers an animation and then removes the element from the DOM.
|
||||
* When the function is called a promise is returned that will be resolved during the next
|
||||
* digest once the animation has completed.
|
||||
*
|
||||
* @param {DOMElement} element the element which will be removed from the DOM
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
leave: function(element, options) {
|
||||
return $$animateQueue.push(element, 'leave', options, function() {
|
||||
element.remove();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $animate#addClass
|
||||
* @kind function
|
||||
* @description Adds the provided className CSS class value to the provided element.
|
||||
* When the function is called a promise is returned that will be resolved at a later time.
|
||||
* @param {DOMElement} element the element which will have the className value
|
||||
* added to it
|
||||
* @param {string} className the CSS class which will be added to the element
|
||||
* @param {object=} options an optional collection of options that will be applied to the element.
|
||||
*
|
||||
* @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
|
||||
* execution, the addClass operation will only be handled after the next digest and it will not trigger an
|
||||
* animation if element already contains the CSS class or if the class is removed at a later step.
|
||||
* Note that class-based animations are treated differently compared to structural animations
|
||||
* (like enter, move and leave) since the CSS classes may be added/removed at different points
|
||||
* depending if CSS or JavaScript animations are used.
|
||||
*
|
||||
* @param {DOMElement} element the element which the CSS classes will be applied to
|
||||
* @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
addClass: function(element, className, options) {
|
||||
return this.setClass(element, className, [], options);
|
||||
},
|
||||
|
||||
$$addClassImmediately: function(element, className, options) {
|
||||
element = jqLite(element);
|
||||
className = !isString(className)
|
||||
? (isArray(className) ? className.join(' ') : '')
|
||||
: className;
|
||||
forEach(element, function(element) {
|
||||
jqLiteAddClass(element, className);
|
||||
});
|
||||
applyStyles(element, options);
|
||||
return asyncPromise();
|
||||
options = options || {};
|
||||
options.addClass = mergeClasses(options.addclass, className);
|
||||
return $$animateQueue.push(element, 'addClass', options);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @ngdoc method
|
||||
* @name $animate#removeClass
|
||||
* @kind function
|
||||
* @description Removes the provided className CSS class value from the provided element.
|
||||
* When the function is called a promise is returned that will be resolved at a later time.
|
||||
* @param {DOMElement} element the element which will have the className value
|
||||
* removed from it
|
||||
* @param {string} className the CSS class which will be removed from the element
|
||||
* @param {object=} options an optional collection of options that will be applied to the element.
|
||||
*
|
||||
* @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
|
||||
* execution, the removeClass operation will only be handled after the next digest and it will not trigger an
|
||||
* animation if element does not contain the CSS class or if the class is added at a later step.
|
||||
* Note that class-based animations are treated differently compared to structural animations
|
||||
* (like enter, move and leave) since the CSS classes may be added/removed at different points
|
||||
* depending if CSS or JavaScript animations are used.
|
||||
*
|
||||
* @param {DOMElement} element the element which the CSS classes will be applied to
|
||||
* @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
removeClass: function(element, className, options) {
|
||||
return this.setClass(element, [], className, options);
|
||||
},
|
||||
|
||||
$$removeClassImmediately: function(element, className, options) {
|
||||
element = jqLite(element);
|
||||
className = !isString(className)
|
||||
? (isArray(className) ? className.join(' ') : '')
|
||||
: className;
|
||||
forEach(element, function(element) {
|
||||
jqLiteRemoveClass(element, className);
|
||||
});
|
||||
applyStyles(element, options);
|
||||
return asyncPromise();
|
||||
options = options || {};
|
||||
options.removeClass = mergeClasses(options.removeClass, className);
|
||||
return $$animateQueue.push(element, 'removeClass', options);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @ngdoc method
|
||||
* @name $animate#setClass
|
||||
* @kind function
|
||||
* @description Adds and/or removes the given CSS classes to and from the element.
|
||||
* When the function is called a promise is returned that will be resolved at a later time.
|
||||
* @param {DOMElement} element the element which will have its CSS classes changed
|
||||
* removed from it
|
||||
* @param {string} add the CSS classes which will be added to the element
|
||||
* @param {string} remove the CSS class which will be removed from the element
|
||||
* @param {object=} options an optional collection of options that will be applied to the element.
|
||||
*
|
||||
* @description Performs both the addition and removal of a CSS classes on an element and (during the process)
|
||||
* triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
|
||||
* `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
|
||||
* passed. Note that class-based animations are treated differently compared to structural animations
|
||||
* (like enter, move and leave) since the CSS classes may be added/removed at different points
|
||||
* depending if CSS or JavaScript animations are used.
|
||||
*
|
||||
* @param {DOMElement} element the element which the CSS classes will be applied to
|
||||
* @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
|
||||
* @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
setClass: function(element, add, remove, options) {
|
||||
var self = this;
|
||||
var STORAGE_KEY = '$$animateClasses';
|
||||
var createdCache = false;
|
||||
element = jqLite(element);
|
||||
|
||||
var cache = element.data(STORAGE_KEY);
|
||||
if (!cache) {
|
||||
cache = {
|
||||
classes: {},
|
||||
options: options
|
||||
};
|
||||
createdCache = true;
|
||||
} else if (options && cache.options) {
|
||||
cache.options = angular.extend(cache.options || {}, options);
|
||||
}
|
||||
|
||||
var classes = cache.classes;
|
||||
|
||||
add = isArray(add) ? add : add.split(' ');
|
||||
remove = isArray(remove) ? remove : remove.split(' ');
|
||||
cachedClassManipulation(classes, add, true);
|
||||
cachedClassManipulation(classes, remove, false);
|
||||
|
||||
if (createdCache) {
|
||||
cache.promise = runAnimationPostDigest(function(done) {
|
||||
var cache = element.data(STORAGE_KEY);
|
||||
element.removeData(STORAGE_KEY);
|
||||
|
||||
// in the event that the element is removed before postDigest
|
||||
// is run then the cache will be undefined and there will be
|
||||
// no need anymore to add or remove and of the element classes
|
||||
if (cache) {
|
||||
var classes = resolveElementClasses(element, cache.classes);
|
||||
if (classes) {
|
||||
self.$$setClassImmediately(element, classes[0], classes[1], cache.options);
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
element.data(STORAGE_KEY, cache);
|
||||
}
|
||||
|
||||
return cache.promise;
|
||||
options = options || {};
|
||||
options.addClass = mergeClasses(options.addClass, add);
|
||||
options.removeClass = mergeClasses(options.removeClass, remove);
|
||||
return $$animateQueue.push(element, 'setClass', options);
|
||||
},
|
||||
|
||||
$$setClassImmediately: function(element, add, remove, options) {
|
||||
add && this.$$addClassImmediately(element, add);
|
||||
remove && this.$$removeClassImmediately(element, remove);
|
||||
applyStyles(element, options);
|
||||
return asyncPromise();
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $animate#animate
|
||||
* @kind function
|
||||
*
|
||||
* @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
|
||||
* If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
|
||||
* on the provided styles. For example, if a transition animation is set for the given className then the provided from and
|
||||
* to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
|
||||
* will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
|
||||
*
|
||||
* @param {DOMElement} element the element which the CSS styles will be applied to
|
||||
* @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
|
||||
* @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
|
||||
* @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
|
||||
* this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
|
||||
* (Note that if no animation is detected then this value will not be appplied to the element.)
|
||||
* @param {object=} options an optional collection of options/styles that will be applied to the element
|
||||
*
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
animate: function(element, from, to, className, options) {
|
||||
options = options || {};
|
||||
options.from = options.from ? extend(options.from, from) : from;
|
||||
options.to = options.to ? extend(options.to, to) : to;
|
||||
|
||||
enabled: noop,
|
||||
cancel: noop
|
||||
className = className || 'ng-inline-animate';
|
||||
options.tempClasses = mergeClasses(options.tempClasses, className);
|
||||
return $$animateQueue.push(element, 'animate', options);
|
||||
}
|
||||
};
|
||||
}];
|
||||
}];
|
||||
|
||||
+19
-127
@@ -73,11 +73,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* @param {function()} callback Function that will be called when no outstanding request
|
||||
*/
|
||||
self.notifyWhenNoOutstandingRequests = function(callback) {
|
||||
// force browser to execute all pollFns - this is needed so that cookies and other pollers fire
|
||||
// at some deterministic time in respect to the test runner's actions. Leaving things up to the
|
||||
// regular poller would result in flaky tests.
|
||||
forEach(pollFns, function(pollFn) { pollFn(); });
|
||||
|
||||
if (outstandingRequestCount === 0) {
|
||||
callback();
|
||||
} else {
|
||||
@@ -85,44 +80,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Poll Watcher API
|
||||
//////////////////////////////////////////////////////////////
|
||||
var pollFns = [],
|
||||
pollTimeout;
|
||||
|
||||
/**
|
||||
* @name $browser#addPollFn
|
||||
*
|
||||
* @param {function()} fn Poll function to add
|
||||
*
|
||||
* @description
|
||||
* Adds a function to the list of functions that poller periodically executes,
|
||||
* and starts polling if not started yet.
|
||||
*
|
||||
* @returns {function()} the added function
|
||||
*/
|
||||
self.addPollFn = function(fn) {
|
||||
if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
|
||||
pollFns.push(fn);
|
||||
return fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} interval How often should browser call poll functions (ms)
|
||||
* @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
|
||||
*
|
||||
* @description
|
||||
* Configures the poller to run in the specified intervals, using the specified
|
||||
* setTimeout fn and kicks it off.
|
||||
*/
|
||||
function startPoller(interval, setTimeout) {
|
||||
(function check() {
|
||||
forEach(pollFns, function(pollFn) { pollFn(); });
|
||||
pollTimeout = setTimeout(check, interval);
|
||||
})();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// URL API
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -233,11 +190,19 @@ function Browser(window, document, $log, $sniffer) {
|
||||
fireUrlChange();
|
||||
}
|
||||
|
||||
function getCurrentState() {
|
||||
try {
|
||||
return history.state;
|
||||
} catch (e) {
|
||||
// MSIE can reportedly throw when there is no state (UNCONFIRMED).
|
||||
}
|
||||
}
|
||||
|
||||
// This variable should be used *only* inside the cacheState function.
|
||||
var lastCachedState = null;
|
||||
function cacheState() {
|
||||
// This should be the only place in $browser where `history.state` is read.
|
||||
cachedState = window.history.state;
|
||||
cachedState = getCurrentState();
|
||||
cachedState = isUndefined(cachedState) ? null : cachedState;
|
||||
|
||||
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
|
||||
@@ -299,6 +264,16 @@ function Browser(window, document, $log, $sniffer) {
|
||||
return callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Remove popstate and hashchange handler from window.
|
||||
*
|
||||
* NOTE: this api is intended for use only by $rootScope.
|
||||
*/
|
||||
self.$$applicationDestroyed = function() {
|
||||
jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the url has changed outside of Angular.
|
||||
* Needs to be exported to be able to check for changes that have been done in sync,
|
||||
@@ -324,89 +299,6 @@ function Browser(window, document, $log, $sniffer) {
|
||||
return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Cookies API
|
||||
//////////////////////////////////////////////////////////////
|
||||
var lastCookies = {};
|
||||
var lastCookieString = '';
|
||||
var cookiePath = self.baseHref();
|
||||
|
||||
function safeDecodeURIComponent(str) {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name $browser#cookies
|
||||
*
|
||||
* @param {string=} name Cookie name
|
||||
* @param {string=} value Cookie value
|
||||
*
|
||||
* @description
|
||||
* The cookies method provides a 'private' low level access to browser cookies.
|
||||
* It is not meant to be used directly, use the $cookie service instead.
|
||||
*
|
||||
* The return values vary depending on the arguments that the method was called with as follows:
|
||||
*
|
||||
* - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
|
||||
* it
|
||||
* - cookies(name, value) -> set name to value, if value is undefined delete the cookie
|
||||
* - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
|
||||
* way)
|
||||
*
|
||||
* @returns {Object} Hash of all cookies (if called without any parameter)
|
||||
*/
|
||||
self.cookies = function(name, value) {
|
||||
var cookieLength, cookieArray, cookie, i, index;
|
||||
|
||||
if (name) {
|
||||
if (value === undefined) {
|
||||
rawDocument.cookie = encodeURIComponent(name) + "=;path=" + cookiePath +
|
||||
";expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
||||
} else {
|
||||
if (isString(value)) {
|
||||
cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) +
|
||||
';path=' + cookiePath).length + 1;
|
||||
|
||||
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
|
||||
// - 300 cookies
|
||||
// - 20 cookies per unique domain
|
||||
// - 4096 bytes per cookie
|
||||
if (cookieLength > 4096) {
|
||||
$log.warn("Cookie '" + name +
|
||||
"' possibly not set or overflowed because it was too large (" +
|
||||
cookieLength + " > 4096 bytes)!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (rawDocument.cookie !== lastCookieString) {
|
||||
lastCookieString = rawDocument.cookie;
|
||||
cookieArray = lastCookieString.split("; ");
|
||||
lastCookies = {};
|
||||
|
||||
for (i = 0; i < cookieArray.length; i++) {
|
||||
cookie = cookieArray[i];
|
||||
index = cookie.indexOf('=');
|
||||
if (index > 0) { //ignore nameless cookies
|
||||
name = safeDecodeURIComponent(cookie.substring(0, index));
|
||||
// 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) {
|
||||
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastCookies;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @name $browser#defer
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
|
||||
@@ -372,7 +372,7 @@ function $CacheFactoryProvider() {
|
||||
* the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
|
||||
* element with ng-app attribute), otherwise the template will be ignored.
|
||||
*
|
||||
* Adding via the $templateCache service:
|
||||
* Adding via the `$templateCache` service:
|
||||
*
|
||||
* ```js
|
||||
* var myApp = angular.module('myApp', []);
|
||||
|
||||
+104
-82
@@ -1,5 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* Any commits to this file should be reviewed with security in mind. *
|
||||
* Changes to this file can potentially create security vulnerabilities. *
|
||||
* An approval from 2 Core members with history of modifying *
|
||||
* this file is required. *
|
||||
* *
|
||||
* Does the change somehow allow for arbitrary javascript to be executed? *
|
||||
* Or allows for someone to change the prototype of built-in objects? *
|
||||
* Or gives undesired access to variables likes document or window? *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
|
||||
*
|
||||
* DOM-related variables:
|
||||
@@ -65,6 +76,7 @@
|
||||
* scope: false,
|
||||
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
|
||||
* controllerAs: 'stringIdentifier',
|
||||
* bindToController: false,
|
||||
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
|
||||
* compile: function compile(tElement, tAttrs, transclude) {
|
||||
* return {
|
||||
@@ -211,7 +223,8 @@
|
||||
* Require another directive and inject its controller as the fourth argument to the linking function. The
|
||||
* `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
|
||||
* injected argument will be an array in corresponding order. If no such directive can be
|
||||
* found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
|
||||
* found, or if the directive does not have a controller, then an error is raised (unless no link function
|
||||
* is specified, in which case error checking is skipped). The name can be prefixed with:
|
||||
*
|
||||
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
|
||||
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
|
||||
@@ -346,7 +359,7 @@
|
||||
* `templateUrl` declaration or manual compilation inside the compile function.
|
||||
* </div>
|
||||
*
|
||||
* <div class="alert alert-error">
|
||||
* <div class="alert alert-danger">
|
||||
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
|
||||
* e.g. does not know about the right outer scope. Please use the transclude function that is passed
|
||||
* to the link function instead.
|
||||
@@ -383,9 +396,15 @@
|
||||
* * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
|
||||
* between all directive linking functions.
|
||||
*
|
||||
* * `controller` - a controller instance - A controller instance if at least one directive on the
|
||||
* element defines a controller. The controller is shared among all the directives, which allows
|
||||
* the directives to use the controllers as a communication channel.
|
||||
* * `controller` - the directive's required controller instance(s) - Instances are shared
|
||||
* among all directives, which allows the directives to use the controllers as a communication
|
||||
* channel. The exact value depends on the directive's `require` property:
|
||||
* * `string`: the controller instance
|
||||
* * `array`: array of controller instances
|
||||
* * no controller(s) required: `undefined`
|
||||
*
|
||||
* If a required controller cannot be found, and it is optional, the instance is `null`,
|
||||
* otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
|
||||
*
|
||||
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
|
||||
* This is the same as the `$transclude`
|
||||
@@ -625,7 +644,7 @@
|
||||
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
|
||||
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
|
||||
*
|
||||
* <div class="alert alert-error">
|
||||
* <div class="alert alert-danger">
|
||||
* **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
|
||||
* e.g. will not use the right outer scope. Please pass the transclude function as a
|
||||
* `parentBoundTranscludeFn` to the link function instead.
|
||||
@@ -778,6 +797,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
function assertValidDirectiveName(name) {
|
||||
var letter = name.charAt(0);
|
||||
if (!letter || letter !== lowercase(letter)) {
|
||||
throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#directive
|
||||
@@ -796,6 +822,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
this.directive = function registerDirective(name, directiveFactory) {
|
||||
assertNotHasOwnProperty(name, 'directive');
|
||||
if (isString(name)) {
|
||||
assertValidDirectiveName(name);
|
||||
assertArg(directiveFactory, 'directiveFactory');
|
||||
if (!hasDirectives.hasOwnProperty(name)) {
|
||||
hasDirectives[name] = [];
|
||||
@@ -1614,7 +1641,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var terminalPriority = -Number.MAX_VALUE,
|
||||
newScopeDirective,
|
||||
controllerDirectives = previousCompileContext.controllerDirectives,
|
||||
controllers,
|
||||
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
|
||||
templateDirective = previousCompileContext.templateDirective,
|
||||
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
|
||||
@@ -1672,7 +1698,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
if (!directive.templateUrl && directive.controller) {
|
||||
directiveValue = directive.controller;
|
||||
controllerDirectives = controllerDirectives || {};
|
||||
controllerDirectives = controllerDirectives || createMap();
|
||||
assertNoDuplicate("'" + directiveName + "' controller",
|
||||
controllerDirectives[directiveName], directive, $compileNode);
|
||||
controllerDirectives[directiveName] = directive;
|
||||
@@ -1840,51 +1866,74 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
|
||||
function getControllers(directiveName, require, $element, elementControllers) {
|
||||
var value, retrievalMethod = 'data', optional = false;
|
||||
var $searchElement = $element;
|
||||
var match;
|
||||
var value;
|
||||
|
||||
if (isString(require)) {
|
||||
match = require.match(REQUIRE_PREFIX_REGEXP);
|
||||
require = require.substring(match[0].length);
|
||||
var match = require.match(REQUIRE_PREFIX_REGEXP);
|
||||
var name = require.substring(match[0].length);
|
||||
var inheritType = match[1] || match[3];
|
||||
var optional = match[2] === '?';
|
||||
|
||||
if (match[3]) {
|
||||
if (match[1]) match[3] = null;
|
||||
else match[1] = match[3];
|
||||
}
|
||||
if (match[1] === '^') {
|
||||
retrievalMethod = 'inheritedData';
|
||||
} else if (match[1] === '^^') {
|
||||
retrievalMethod = 'inheritedData';
|
||||
$searchElement = $element.parent();
|
||||
}
|
||||
if (match[2] === '?') {
|
||||
optional = true;
|
||||
//If only parents then start at the parent element
|
||||
if (inheritType === '^^') {
|
||||
$element = $element.parent();
|
||||
//Otherwise attempt getting the controller from elementControllers in case
|
||||
//the element is transcluded (and has no data) and to avoid .data if possible
|
||||
} else {
|
||||
value = elementControllers && elementControllers[name];
|
||||
value = value && value.instance;
|
||||
}
|
||||
|
||||
value = null;
|
||||
|
||||
if (elementControllers && retrievalMethod === 'data') {
|
||||
if (value = elementControllers[require]) {
|
||||
value = value.instance;
|
||||
}
|
||||
if (!value) {
|
||||
var dataName = '$' + name + 'Controller';
|
||||
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
|
||||
}
|
||||
value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
|
||||
|
||||
if (!value && !optional) {
|
||||
throw $compileMinErr('ctreq',
|
||||
"Controller '{0}', required by directive '{1}', can't be found!",
|
||||
require, directiveName);
|
||||
name, directiveName);
|
||||
}
|
||||
return value || null;
|
||||
} else if (isArray(require)) {
|
||||
value = [];
|
||||
forEach(require, function(require) {
|
||||
value.push(getControllers(directiveName, require, $element, elementControllers));
|
||||
});
|
||||
for (var i = 0, ii = require.length; i < ii; i++) {
|
||||
value[i] = getControllers(directiveName, require[i], $element, elementControllers);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
|
||||
var elementControllers = createMap();
|
||||
for (var controllerKey in controllerDirectives) {
|
||||
var directive = controllerDirectives[controllerKey];
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: transcludeFn
|
||||
};
|
||||
|
||||
var controller = directive.controller;
|
||||
if (controller == '@') {
|
||||
controller = attrs[directive.name];
|
||||
}
|
||||
|
||||
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
|
||||
|
||||
// For directives with element transclusion the element is a comment,
|
||||
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
|
||||
// clean up (http://bugs.jquery.com/ticket/8335).
|
||||
// Instead, we save the controllers for the element in a local hash and attach to .data
|
||||
// later, once we have the actual element.
|
||||
elementControllers[directive.name] = controllerInstance;
|
||||
if (!hasElementTranscludeDirective) {
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
|
||||
}
|
||||
}
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
|
||||
thisLinkFn) {
|
||||
@@ -1911,36 +1960,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
|
||||
if (controllerDirectives) {
|
||||
// TODO: merge `controllers` and `elementControllers` into single object.
|
||||
controllers = {};
|
||||
elementControllers = {};
|
||||
forEach(controllerDirectives, function(directive) {
|
||||
var locals = {
|
||||
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
|
||||
$element: $element,
|
||||
$attrs: attrs,
|
||||
$transclude: transcludeFn
|
||||
}, controllerInstance;
|
||||
|
||||
controller = directive.controller;
|
||||
if (controller == '@') {
|
||||
controller = attrs[directive.name];
|
||||
}
|
||||
|
||||
controllerInstance = $controller(controller, locals, true, directive.controllerAs);
|
||||
|
||||
// For directives with element transclusion the element is a comment,
|
||||
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
|
||||
// clean up (http://bugs.jquery.com/ticket/8335).
|
||||
// Instead, we save the controllers for the element in a local hash and attach to .data
|
||||
// later, once we have the actual element.
|
||||
elementControllers[directive.name] = controllerInstance;
|
||||
if (!hasElementTranscludeDirective) {
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
|
||||
}
|
||||
|
||||
controllers[directive.name] = controllerInstance;
|
||||
});
|
||||
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
|
||||
}
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
@@ -1954,14 +1974,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective, isolateScope);
|
||||
}
|
||||
if (controllers) {
|
||||
if (elementControllers) {
|
||||
// Initialize bindToController bindings for new/isolate scopes
|
||||
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
|
||||
var bindings;
|
||||
var controllerForBindings;
|
||||
if (scopeDirective && controllers[scopeDirective.name]) {
|
||||
if (scopeDirective && elementControllers[scopeDirective.name]) {
|
||||
bindings = scopeDirective.$$bindings.bindToController;
|
||||
controller = controllers[scopeDirective.name];
|
||||
controller = elementControllers[scopeDirective.name];
|
||||
|
||||
if (controller && controller.identifier && bindings) {
|
||||
controllerForBindings = controller;
|
||||
@@ -1970,18 +1990,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
bindings, scopeDirective);
|
||||
}
|
||||
}
|
||||
forEach(controllers, function(controller) {
|
||||
var result = controller();
|
||||
if (result !== controller.instance &&
|
||||
controller === controllerForBindings) {
|
||||
// Remove and re-install bindToController bindings
|
||||
thisLinkFn.$$destroyBindings();
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, result,
|
||||
bindings, scopeDirective);
|
||||
for (i in elementControllers) {
|
||||
controller = elementControllers[i];
|
||||
var controllerResult = controller();
|
||||
if (controllerResult !== controller.instance) {
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + directive.name + 'Controller', controllerResult);
|
||||
if (controller === controllerForBindings) {
|
||||
// Remove and re-install bindToController bindings
|
||||
thisLinkFn.$$destroyBindings();
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
|
||||
}
|
||||
}
|
||||
});
|
||||
controllers = null;
|
||||
}
|
||||
}
|
||||
|
||||
// PRELINKING
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @name $$cookieReader
|
||||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
* This is a private service for reading cookies used by $http and ngCookies
|
||||
*
|
||||
* @return {Object} a key/value map of the current cookies
|
||||
*/
|
||||
function $$CookieReader($document) {
|
||||
var rawDocument = $document[0] || {};
|
||||
var lastCookies = {};
|
||||
var lastCookieString = '';
|
||||
|
||||
function safeDecodeURIComponent(str) {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch (e) {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
return function() {
|
||||
var cookieArray, cookie, i, index, name;
|
||||
var currentCookieString = rawDocument.cookie || '';
|
||||
|
||||
if (currentCookieString !== lastCookieString) {
|
||||
lastCookieString = currentCookieString;
|
||||
cookieArray = lastCookieString.split('; ');
|
||||
lastCookies = {};
|
||||
|
||||
for (i = 0; i < cookieArray.length; i++) {
|
||||
cookie = cookieArray[i];
|
||||
index = cookie.indexOf('=');
|
||||
if (index > 0) { //ignore nameless cookies
|
||||
name = safeDecodeURIComponent(cookie.substring(0, index));
|
||||
// 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) {
|
||||
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastCookies;
|
||||
};
|
||||
}
|
||||
|
||||
$$CookieReader.$inject = ['$document'];
|
||||
|
||||
function $$CookieReaderProvider() {
|
||||
this.$get = $$CookieReader;
|
||||
}
|
||||
@@ -159,20 +159,24 @@
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
|
||||
* This directive sets the `disabled` attribute on the element if the
|
||||
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
|
||||
*
|
||||
* A special directive is necessary because we cannot use interpolation inside the `disabled`
|
||||
* attribute. The following example would make the button enabled on Chrome/Firefox
|
||||
* but not on older IEs:
|
||||
*
|
||||
* ```html
|
||||
* <div ng-init="scope = { isDisabled: false }">
|
||||
* <button disabled="{{scope.isDisabled}}">Disabled</button>
|
||||
* <!-- See below for an example of ng-disabled being used correctly -->
|
||||
* <div ng-init="isDisabled = false">
|
||||
* <button disabled="{{isDisabled}}">Disabled</button>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* The HTML specification does not require browsers to preserve the values of boolean attributes
|
||||
* such as disabled. (Their presence means true and their absence means false.)
|
||||
* This is because the HTML specification does not require browsers to preserve the values of
|
||||
* boolean attributes such as `disabled` (Their presence means true and their absence means false.)
|
||||
* If we put an Angular interpolation expression into such an attribute then the
|
||||
* binding information would be lost when the browser removes the attribute.
|
||||
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
@@ -191,7 +195,7 @@
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "disabled" will be set on the element
|
||||
* then the `disabled` attribute will be set on the element
|
||||
*/
|
||||
|
||||
|
||||
|
||||
+14
-14
@@ -316,7 +316,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
*
|
||||
* # Alias: {@link ng.directive:ngForm `ngForm`}
|
||||
*
|
||||
* In Angular forms can be nested. This means that the outer form is valid when all of the child
|
||||
* In Angular, forms can be nested. This means that the outer form is valid when all of the child
|
||||
* forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
|
||||
* Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
|
||||
* `<form>` but can be nested. This allows you to have nested forms, which is very useful when
|
||||
@@ -454,10 +454,12 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
name: 'form',
|
||||
restrict: isNgForm ? 'EAC' : 'E',
|
||||
controller: FormController,
|
||||
compile: function ngFormCompile(formElement) {
|
||||
compile: function ngFormCompile(formElement, attr) {
|
||||
// Setup initial state of the control
|
||||
formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
|
||||
|
||||
var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
|
||||
|
||||
return {
|
||||
pre: function ngFormPreLink(scope, formElement, attr, controller) {
|
||||
// if `action` attr is not present on the form, prevent the default action (submission)
|
||||
@@ -488,23 +490,21 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
});
|
||||
}
|
||||
|
||||
var parentFormCtrl = controller.$$parentForm,
|
||||
alias = controller.$name;
|
||||
var parentFormCtrl = controller.$$parentForm;
|
||||
|
||||
if (alias) {
|
||||
setter(scope, alias, controller, alias);
|
||||
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
|
||||
if (alias === newValue) return;
|
||||
setter(scope, alias, undefined, alias);
|
||||
alias = newValue;
|
||||
setter(scope, alias, controller, alias);
|
||||
parentFormCtrl.$$renameControl(controller, alias);
|
||||
if (nameAttr) {
|
||||
setter(scope, controller.$name, controller, controller.$name);
|
||||
attr.$observe(nameAttr, function(newValue) {
|
||||
if (controller.$name === newValue) return;
|
||||
setter(scope, controller.$name, undefined, controller.$name);
|
||||
parentFormCtrl.$$renameControl(controller, newValue);
|
||||
setter(scope, controller.$name, controller, controller.$name);
|
||||
});
|
||||
}
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
if (alias) {
|
||||
setter(scope, alias, undefined, alias);
|
||||
if (nameAttr) {
|
||||
setter(scope, attr[nameAttr], undefined, controller.$name);
|
||||
}
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
|
||||
@@ -587,7 +587,11 @@ var inputType = {
|
||||
* Text input with number validation and transformation. Sets the `number` validation
|
||||
* error if not a valid number.
|
||||
*
|
||||
* The model must always be a number, otherwise Angular will throw an error.
|
||||
* <div class="alert alert-warning">
|
||||
* The model must always be of type `number` otherwise Angular will throw an error.
|
||||
* Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
|
||||
* error docs for more information and an example of how to convert your model if necessary.
|
||||
* </div>
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -1156,8 +1160,8 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
// parser/formatter in the processing chain so that the model
|
||||
// contains some different data format!
|
||||
var parsedDate = parseDate(value, previousDate);
|
||||
if (timezone === 'UTC') {
|
||||
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
|
||||
if (timezone) {
|
||||
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
|
||||
}
|
||||
return parsedDate;
|
||||
}
|
||||
@@ -1170,9 +1174,8 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
}
|
||||
if (isValidDate(value)) {
|
||||
previousDate = value;
|
||||
if (previousDate && timezone === 'UTC') {
|
||||
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
|
||||
previousDate = new Date(previousDate.getTime() + timezoneOffset);
|
||||
if (previousDate && timezone) {
|
||||
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
|
||||
}
|
||||
return $filter('date')(value, format, timezone);
|
||||
} else {
|
||||
@@ -1250,7 +1253,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
return value;
|
||||
});
|
||||
|
||||
if (attr.min || attr.ngMin) {
|
||||
if (isDefined(attr.min) || attr.ngMin) {
|
||||
var minVal;
|
||||
ctrl.$validators.min = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
|
||||
@@ -1266,7 +1269,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
|
||||
});
|
||||
}
|
||||
|
||||
if (attr.max || attr.ngMax) {
|
||||
if (isDefined(attr.max) || attr.ngMax) {
|
||||
var maxVal;
|
||||
ctrl.$validators.max = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
|
||||
|
||||
+32
-13
@@ -96,12 +96,15 @@ function classDirective(name, selector) {
|
||||
}
|
||||
|
||||
function arrayClasses(classVal) {
|
||||
var classes = [];
|
||||
if (isArray(classVal)) {
|
||||
return classVal.join(' ').split(' ');
|
||||
forEach(classVal, function(v) {
|
||||
classes = classes.concat(arrayClasses(v));
|
||||
});
|
||||
return classes;
|
||||
} else if (isString(classVal)) {
|
||||
return classVal.split(' ');
|
||||
} else if (isObject(classVal)) {
|
||||
var classes = [];
|
||||
forEach(classVal, function(v, k) {
|
||||
if (v) {
|
||||
classes = classes.concat(k.split(' '));
|
||||
@@ -129,16 +132,18 @@ function classDirective(name, selector) {
|
||||
* 1. If the expression evaluates to a string, the string should be one or more space-delimited class
|
||||
* names.
|
||||
*
|
||||
* 2. If the expression evaluates to an array, each element of the array should be a string that is
|
||||
* one or more space-delimited class names.
|
||||
*
|
||||
* 3. If the expression evaluates to an object, then for each key-value pair of the
|
||||
* 2. If the expression evaluates to an object, then for each key-value pair of the
|
||||
* object with a truthy value the corresponding key is used as a class name.
|
||||
*
|
||||
* 3. If the expression evaluates to an array, each element of the array should either be a string as in
|
||||
* type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
|
||||
* to give you more control over what CSS classes appear. See the code below for an example of this.
|
||||
*
|
||||
*
|
||||
* The directive won't add duplicate classes if a particular class was already set.
|
||||
*
|
||||
* When the expression changes, the previously added classes are removed and only then the
|
||||
* new classes are added.
|
||||
* When the expression changes, the previously added classes are removed and only then are the
|
||||
* new classes added.
|
||||
*
|
||||
* @animations
|
||||
* **add** - happens just before the class is applied to the elements
|
||||
@@ -167,10 +172,14 @@ function classDirective(name, selector) {
|
||||
<input ng-model="style1" placeholder="Type: bold, strike or red"><br>
|
||||
<input ng-model="style2" placeholder="Type: bold, strike or red"><br>
|
||||
<input ng-model="style3" placeholder="Type: bold, strike or red"><br>
|
||||
<hr>
|
||||
<p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
|
||||
<input ng-model="style4" placeholder="Type: bold, strike"><br>
|
||||
<input type="checkbox" ng-model="warning"> warning (apply "orange" class)
|
||||
</file>
|
||||
<file name="style.css">
|
||||
.strike {
|
||||
text-decoration: line-through;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
@@ -178,6 +187,9 @@ function classDirective(name, selector) {
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
.orange {
|
||||
color: orange;
|
||||
}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
var ps = element.all(by.css('p'));
|
||||
@@ -202,11 +214,18 @@ function classDirective(name, selector) {
|
||||
});
|
||||
|
||||
it('array example should have 3 classes', function() {
|
||||
expect(ps.last().getAttribute('class')).toBe('');
|
||||
expect(ps.get(2).getAttribute('class')).toBe('');
|
||||
element(by.model('style1')).sendKeys('bold');
|
||||
element(by.model('style2')).sendKeys('strike');
|
||||
element(by.model('style3')).sendKeys('red');
|
||||
expect(ps.last().getAttribute('class')).toBe('bold strike red');
|
||||
expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
|
||||
});
|
||||
|
||||
it('array with map example should have 2 classes', function() {
|
||||
expect(ps.last().getAttribute('class')).toBe('');
|
||||
element(by.model('style4')).sendKeys('bold');
|
||||
element(by.model('warning')).click();
|
||||
expect(ps.last().getAttribute('class')).toBe('bold orange');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -256,8 +275,8 @@ function classDirective(name, selector) {
|
||||
The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
|
||||
Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
|
||||
any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
|
||||
to view the step by step details of {@link ng.$animate#addClass $animate.addClass} and
|
||||
{@link ng.$animate#removeClass $animate.removeClass}.
|
||||
to view the step by step details of {@link $animate#addClass $animate.addClass} and
|
||||
{@link $animate#removeClass $animate.removeClass}.
|
||||
*/
|
||||
var ngClassDirective = classDirective('', true);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* The `ngInit` directive allows you to evaluate an expression in the
|
||||
* current scope.
|
||||
*
|
||||
* <div class="alert alert-error">
|
||||
* <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`
|
||||
|
||||
+24
-20
@@ -129,8 +129,8 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`.
|
||||
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
|
||||
* collaborate together to achieve the desired result.
|
||||
*
|
||||
* Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element
|
||||
* contents be edited in place by the user. This will not work on older browsers.
|
||||
* `contenteditable` is an HTML5 attribute, which tells the browser to let the element
|
||||
* contents be edited in place by the user.
|
||||
*
|
||||
* We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
|
||||
* module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
|
||||
@@ -244,6 +244,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ngModelGet = parsedNgModel,
|
||||
ngModelSet = parsedNgModelAssign,
|
||||
pendingDebounce = null,
|
||||
parserValid,
|
||||
ctrl = this;
|
||||
|
||||
this.$$setOptions = function(options) {
|
||||
@@ -516,16 +517,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
// the model although neither viewValue nor the model on the scope changed
|
||||
var modelValue = ctrl.$$rawModelValue;
|
||||
|
||||
// Check if the there's a parse error, so we don't unset it accidentially
|
||||
var parserName = ctrl.$$parserName || 'parse';
|
||||
var parserValid = ctrl.$error[parserName] ? false : undefined;
|
||||
|
||||
var prevValid = ctrl.$valid;
|
||||
var prevModelValue = ctrl.$modelValue;
|
||||
|
||||
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
|
||||
|
||||
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
|
||||
ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
|
||||
// If there was no change in validity, don't update the model
|
||||
// This prevents changing an invalid modelValue to undefined
|
||||
if (!allowInvalid && prevValid !== allValid) {
|
||||
@@ -543,12 +540,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
};
|
||||
|
||||
this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
|
||||
this.$$runValidators = function(modelValue, viewValue, doneCallback) {
|
||||
currentValidationRunId++;
|
||||
var localValidationRunId = currentValidationRunId;
|
||||
|
||||
// check parser error
|
||||
if (!processParseErrors(parseValid)) {
|
||||
if (!processParseErrors()) {
|
||||
validationDone(false);
|
||||
return;
|
||||
}
|
||||
@@ -558,21 +555,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
}
|
||||
processAsyncValidators();
|
||||
|
||||
function processParseErrors(parseValid) {
|
||||
function processParseErrors() {
|
||||
var errorKey = ctrl.$$parserName || 'parse';
|
||||
if (parseValid === undefined) {
|
||||
if (parserValid === undefined) {
|
||||
setValidity(errorKey, null);
|
||||
} else {
|
||||
setValidity(errorKey, parseValid);
|
||||
if (!parseValid) {
|
||||
if (!parserValid) {
|
||||
forEach(ctrl.$validators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
forEach(ctrl.$asyncValidators, function(v, name) {
|
||||
setValidity(name, null);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// Set the parse error last, to prevent unsetting it, should a $validators key == parserName
|
||||
setValidity(errorKey, parserValid);
|
||||
return parserValid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -667,7 +665,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$$parseAndValidate = function() {
|
||||
var viewValue = ctrl.$$lastCommittedViewValue;
|
||||
var modelValue = viewValue;
|
||||
var parserValid = isUndefined(modelValue) ? undefined : true;
|
||||
parserValid = isUndefined(modelValue) ? undefined : true;
|
||||
|
||||
if (parserValid) {
|
||||
for (var i = 0; i < ctrl.$parsers.length; i++) {
|
||||
@@ -693,7 +691,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
|
||||
// This can happen if e.g. $setViewValue is called from inside a parser
|
||||
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
|
||||
ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
|
||||
if (!allowInvalid) {
|
||||
// Note: Don't check ctrl.$valid here, as we could have
|
||||
// external validators (e.g. calculated on the server),
|
||||
@@ -812,8 +810,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
|
||||
// if scope model value and ngModel value are out of sync
|
||||
// TODO(perf): why not move this to the action fn?
|
||||
if (modelValue !== ctrl.$modelValue) {
|
||||
if (modelValue !== ctrl.$modelValue &&
|
||||
// checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
|
||||
(ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
|
||||
) {
|
||||
ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
|
||||
parserValid = undefined;
|
||||
|
||||
var formatters = ctrl.$formatters,
|
||||
idx = formatters.length;
|
||||
@@ -826,7 +828,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
|
||||
ctrl.$render();
|
||||
|
||||
ctrl.$$runValidators(undefined, modelValue, viewValue, noop);
|
||||
ctrl.$$runValidators(modelValue, viewValue, noop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1098,8 +1100,10 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
|
||||
`ngModel` as getters/setters.
|
||||
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
|
||||
* `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
|
||||
* otherwise the default timezone of the browser will be used.
|
||||
* `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the
|
||||
* continental US time zone abbreviations, but for general use, use a time zone offset, for
|
||||
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
|
||||
* If not specified, the timezone of the browser will be used.
|
||||
*
|
||||
* @example
|
||||
|
||||
|
||||
@@ -32,8 +32,9 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by`
|
||||
* in an `ngOptions` expression, however, deep equality checks will be performed.
|
||||
* </div>
|
||||
*
|
||||
* ## `select` **`as`**
|
||||
@@ -94,15 +95,20 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
||||
* (for including a filter with `track by`)
|
||||
* * for object data sources:
|
||||
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`group by`** `group`
|
||||
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`disable when`** `disable`
|
||||
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
@@ -116,6 +122,8 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* element. If not specified, `select` expression will default to `value`.
|
||||
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
|
||||
* DOM element.
|
||||
* * `disable`: The result of this expression will be used to disable the rendered `<option>`
|
||||
* element. Return `true` to disable.
|
||||
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
|
||||
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
@@ -129,10 +137,10 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.colors = [
|
||||
{name:'black', shade:'dark'},
|
||||
{name:'white', shade:'light'},
|
||||
{name:'white', shade:'light', notAnOption: true},
|
||||
{name:'red', shade:'dark'},
|
||||
{name:'blue', shade:'dark'},
|
||||
{name:'yellow', shade:'light'}
|
||||
{name:'blue', shade:'dark', notAnOption: true},
|
||||
{name:'yellow', shade:'light', notAnOption: false}
|
||||
];
|
||||
$scope.myColor = $scope.colors[2]; // red
|
||||
}]);
|
||||
@@ -141,6 +149,7 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
<ul>
|
||||
<li ng-repeat="color in colors">
|
||||
Name: <input ng-model="color.name">
|
||||
<input type="checkbox" ng-model="color.notAnOption"> Disabled?
|
||||
[<a href ng-click="colors.splice($index, 1)">X</a>]
|
||||
</li>
|
||||
<li>
|
||||
@@ -162,6 +171,12 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
|
||||
</select><br/>
|
||||
|
||||
Color grouped by shade, with some disabled:
|
||||
<select ng-model="myColor"
|
||||
ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
|
||||
</select><br/>
|
||||
|
||||
|
||||
|
||||
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
|
||||
<hr/>
|
||||
@@ -186,16 +201,17 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
*/
|
||||
|
||||
// jshint maxlen: false
|
||||
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
|
||||
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
|
||||
// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
|
||||
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
|
||||
// 1: value expression (valueFn)
|
||||
// 2: label expression (displayFn)
|
||||
// 3: group by expression (groupByFn)
|
||||
// 4: array item variable name
|
||||
// 5: object item key variable name
|
||||
// 6: object item value variable name
|
||||
// 7: collection expression
|
||||
// 8: track by expression
|
||||
// 4: disable when expression (disableWhenFn)
|
||||
// 5: array item variable name
|
||||
// 6: object item key variable name
|
||||
// 7: object item value variable name
|
||||
// 8: collection expression
|
||||
// 9: track by expression
|
||||
// jshint maxlen: 100
|
||||
|
||||
|
||||
@@ -215,14 +231,14 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// Extract the parts from the ngOptions expression
|
||||
|
||||
// The variable name for the value of the item in the collection
|
||||
var valueName = match[4] || match[6];
|
||||
var valueName = match[5] || match[7];
|
||||
// The variable name for the key of the item in the collection
|
||||
var keyName = match[5];
|
||||
var keyName = match[6];
|
||||
|
||||
// An expression that generates the viewValue for an option if there is a label expression
|
||||
var selectAs = / as /.test(match[0]) && match[1];
|
||||
// An expression that is used to track the id of each object in the options collection
|
||||
var trackBy = match[8];
|
||||
var trackBy = match[9];
|
||||
// An expression that generates the viewValue for an option if there is no label expression
|
||||
var valueFn = $parse(match[2] ? match[1] : valueName);
|
||||
var selectAsFn = selectAs && $parse(selectAs);
|
||||
@@ -237,7 +253,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
function getHashOfValue(viewValue) { return hashKey(viewValue); };
|
||||
var displayFn = $parse(match[2] || match[1]);
|
||||
var groupByFn = $parse(match[3] || '');
|
||||
var valuesFn = $parse(match[7]);
|
||||
var disableWhenFn = $parse(match[4] || '');
|
||||
var valuesFn = $parse(match[8]);
|
||||
|
||||
var locals = {};
|
||||
var getLocals = keyName ? function(value, key) {
|
||||
@@ -250,14 +267,16 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
};
|
||||
|
||||
|
||||
function Option(selectValue, viewValue, label, group) {
|
||||
function Option(selectValue, viewValue, label, group, disabled) {
|
||||
this.selectValue = selectValue;
|
||||
this.viewValue = viewValue;
|
||||
this.label = label;
|
||||
this.group = group;
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
return {
|
||||
trackBy: trackBy,
|
||||
getWatchables: $parse(valuesFn, function(values) {
|
||||
// Create a collection of things that we would like to watch (watchedArray)
|
||||
// so that they can all be watched using a single $watchCollection
|
||||
@@ -267,10 +286,20 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
Object.keys(values).forEach(function getWatchable(key) {
|
||||
var locals = getLocals(values[key], key);
|
||||
var label = displayFn(scope, locals);
|
||||
var selectValue = getTrackByValue(values[key], locals);
|
||||
watchedArray.push(selectValue);
|
||||
watchedArray.push(label);
|
||||
|
||||
// Only need to watch the displayFn if there is a specific label expression
|
||||
if (match[2]) {
|
||||
var label = displayFn(scope, locals);
|
||||
watchedArray.push(label);
|
||||
}
|
||||
|
||||
// Only need to watch the disableWhenFn if there is a specific disable expression
|
||||
if (match[4]) {
|
||||
var disableWhen = disableWhenFn(scope, locals);
|
||||
watchedArray.push(disableWhen);
|
||||
}
|
||||
});
|
||||
return watchedArray;
|
||||
}),
|
||||
@@ -296,7 +325,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
var selectValue = getTrackByValue(viewValue, locals);
|
||||
var label = displayFn(scope, locals);
|
||||
var group = groupByFn(scope, locals);
|
||||
var optionItem = new Option(selectValue, viewValue, label, group);
|
||||
var disabled = disableWhenFn(scope, locals);
|
||||
var optionItem = new Option(selectValue, viewValue, label, group, disabled);
|
||||
|
||||
optionItems.push(optionItem);
|
||||
selectValueMap[selectValue] = optionItem;
|
||||
@@ -307,6 +337,11 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
selectValueMap: selectValueMap,
|
||||
getOptionFromViewValue: function(value) {
|
||||
return selectValueMap[getTrackByValue(value, getLocals(value))];
|
||||
},
|
||||
getViewValueFromOption: function(option) {
|
||||
// If the viewValue could be an object that may be mutated by the application,
|
||||
// we need to make a copy and not return the reference to the value on the option.
|
||||
return trackBy ? angular.copy(option.viewValue) : option.viewValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -373,7 +408,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
selectCtrl.writeValue = function writeNgOptionsValue(value) {
|
||||
var option = options.getOptionFromViewValue(value);
|
||||
|
||||
if (option) {
|
||||
if (option && !option.disabled) {
|
||||
if (selectElement[0].value !== option.selectValue) {
|
||||
removeUnknownOption();
|
||||
removeEmptyOption();
|
||||
@@ -397,10 +432,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
var selectedOption = options.selectValueMap[selectElement.val()];
|
||||
|
||||
if (selectedOption) {
|
||||
if (selectedOption && !selectedOption.disabled) {
|
||||
removeEmptyOption();
|
||||
removeUnknownOption();
|
||||
return selectedOption.viewValue;
|
||||
return options.getViewValueFromOption(selectedOption);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -422,18 +457,22 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
if (value) {
|
||||
value.forEach(function(item) {
|
||||
var option = options.getOptionFromViewValue(item);
|
||||
if (option) option.element.selected = true;
|
||||
if (option && !option.disabled) option.element.selected = true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
selectCtrl.readValue = function readNgOptionsMultiple() {
|
||||
var selectedValues = selectElement.val() || [];
|
||||
return selectedValues.map(function(selectedKey) {
|
||||
var option = options.selectValueMap[selectedKey];
|
||||
return option.viewValue;
|
||||
var selectedValues = selectElement.val() || [],
|
||||
selections = [];
|
||||
|
||||
forEach(selectedValues, function(value) {
|
||||
var option = options.selectValueMap[value];
|
||||
if (!option.disabled) selections.push(options.getViewValueFromOption(option));
|
||||
});
|
||||
|
||||
return selections;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -461,11 +500,17 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// We will re-render the option elements if the option values or labels change
|
||||
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
|
||||
|
||||
// We also need to watch to see if the internals of the model changes, since
|
||||
// ngModel only watches for object identity change
|
||||
if (ngOptions.trackBy) {
|
||||
scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true);
|
||||
}
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
|
||||
function updateOptionElement(option, element) {
|
||||
option.element = element;
|
||||
element.disabled = option.disabled;
|
||||
if (option.value !== element.value) element.value = option.selectValue;
|
||||
if (option.label !== element.label) {
|
||||
element.label = option.label;
|
||||
@@ -601,10 +646,13 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// Check to see if the value has changed due to the update to the options
|
||||
if (!ngModelCtrl.$isEmpty(previousValue)) {
|
||||
var nextValue = selectCtrl.readValue();
|
||||
if (!equals(previousValue, nextValue)) {
|
||||
if (ngOptions.trackBy && !equals(previousValue, nextValue) ||
|
||||
previousValue !== nextValue) {
|
||||
ngModelCtrl.$setViewValue(nextValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -180,7 +180,6 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale,
|
||||
IS_WHEN = /^when(Minus)?(.+)$/;
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
link: function(scope, element, attr) {
|
||||
var numberExp = attr.count,
|
||||
whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
* <div ng-repeat="(key, value) in myObj"> ... </div>
|
||||
* ```
|
||||
*
|
||||
* You need to be aware that the JavaScript specification does not define what order
|
||||
* it will return the keys for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
|
||||
* You need to be aware that the JavaScript specification does not define the order of keys
|
||||
* returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
|
||||
* used to sort the keys alphabetically.)
|
||||
*
|
||||
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
|
||||
@@ -48,6 +48,55 @@
|
||||
* or implement a `$watch` on the object yourself.
|
||||
*
|
||||
*
|
||||
* # Tracking and Duplicates
|
||||
*
|
||||
* When the contents of the collection change, `ngRepeat` 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.
|
||||
*
|
||||
* If you do need to repeat duplicate items, you can substitute the default tracking behavior
|
||||
* with your own using the `track by` expression.
|
||||
*
|
||||
* For example, you may track items by the index of each item in the collection, using the
|
||||
* special scope property `$index`:
|
||||
* ```html
|
||||
* <div ng-repeat="n in [42, 42, 43, 43] track by $index">
|
||||
* {{n}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* You may 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)">
|
||||
* {{n}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* If you are working with objects that have an identifier property, you can 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:
|
||||
* ```html
|
||||
* <div ng-repeat="model in collection track by model.id">
|
||||
* {{model.name}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* When no `track by` expression is provided, it is equivalent to tracking by the built-in
|
||||
* `$id` function, which tracks items by their identity:
|
||||
* ```html
|
||||
* <div ng-repeat="obj in collection track by $id(obj)">
|
||||
* {{obj.prop}}
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* # Special repeat start and end points
|
||||
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
|
||||
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
|
||||
@@ -115,12 +164,12 @@
|
||||
*
|
||||
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
|
||||
*
|
||||
* * `variable in expression track by tracking_expression` – You can also provide an optional tracking function
|
||||
* which can be used to associate the objects in the collection with the DOM elements. If no tracking function
|
||||
* is specified the ng-repeat associates elements by identity in the collection. It is an error to have
|
||||
* more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
|
||||
* mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
|
||||
* before specifying a tracking expression.
|
||||
* * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
|
||||
* which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
|
||||
* is specified, ng-repeat associates elements by identity. It is an error to have
|
||||
* more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
|
||||
* mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be
|
||||
* applied before the tracking expression.
|
||||
*
|
||||
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
|
||||
* will be associated by item identity in the array.
|
||||
@@ -144,6 +193,11 @@
|
||||
* For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
|
||||
* the items have been processed through the filter.
|
||||
*
|
||||
* Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end
|
||||
* (and not as operator, inside an expression).
|
||||
*
|
||||
* For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
|
||||
*
|
||||
* @example
|
||||
* This example initializes the scope to a list of names and
|
||||
* then uses `ngRepeat` to display every person:
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
</example>
|
||||
*/
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
});
|
||||
}, true);
|
||||
});
|
||||
|
||||
@@ -130,7 +130,6 @@
|
||||
*/
|
||||
var ngSwitchDirective = ['$animate', function($animate) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
require: 'ngSwitch',
|
||||
|
||||
// asks for $scope to fool the BC controller module
|
||||
|
||||
@@ -73,6 +73,7 @@ var SelectController =
|
||||
if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
|
||||
} else {
|
||||
if (isUndefined(value) && self.emptyOption) {
|
||||
self.removeUnknownOption();
|
||||
$element.val('');
|
||||
} else {
|
||||
self.renderUnknownOption(value);
|
||||
@@ -134,13 +135,13 @@ var SelectController =
|
||||
* option. See example below for demonstration.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* **Note:** By default, `ngModel` compares by reference, not value. This is important when binding to an
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). When using `track by`
|
||||
* in an `ngOptions` expression, however, deep equality checks will be performed.
|
||||
* </div>
|
||||
*
|
||||
*/
|
||||
var selectDirective = function() {
|
||||
var lastView;
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
@@ -199,11 +200,13 @@ var selectDirective = function() {
|
||||
|
||||
// we have to do it on each watch since ngModel watches reference, but
|
||||
// we need to work of an array, so we need to see if anything was inserted/removed
|
||||
var lastView, lastViewRef = NaN;
|
||||
scope.$watch(function selectMultipleWatch() {
|
||||
if (!equals(lastView, ngModelCtrl.$viewValue)) {
|
||||
if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
|
||||
lastView = shallowCopy(ngModelCtrl.$viewValue);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
lastViewRef = ngModelCtrl.$viewValue;
|
||||
});
|
||||
|
||||
// If we are a multiple select then value is now a collection
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
* Dependency Injected. To achieve this a filter definition consists of a factory function which is
|
||||
* annotated with dependencies and is responsible for creating a filter function.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
|
||||
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
|
||||
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
|
||||
* (`myapp_subsection_filterx`).
|
||||
* </div>
|
||||
*
|
||||
* ```js
|
||||
* // Filter registration
|
||||
* function MyModule($provide, $filterProvider) {
|
||||
@@ -101,6 +108,13 @@ function $FilterProvider($provide) {
|
||||
* @name $filterProvider#register
|
||||
* @param {string|Object} name Name of the filter function, or an object map of filters where
|
||||
* the keys are the filter names and the values are the filter factories.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
|
||||
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
|
||||
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
|
||||
* (`myapp_subsection_filterx`).
|
||||
* </div>
|
||||
* @returns {Object} Registered filter instance, or if a map of filters was provided then a map
|
||||
* of the registered filter instances.
|
||||
*/
|
||||
|
||||
+28
-6
@@ -54,6 +54,9 @@
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
* insensitive way.
|
||||
*
|
||||
* Primitive values are converted to strings. Objects are not compared against primitives,
|
||||
* unless they have a custom `toString` method (e.g. `Date` objects).
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
@@ -132,14 +135,16 @@ function filterFilter() {
|
||||
}
|
||||
}
|
||||
|
||||
var expressionType = getTypeForFilter(expression);
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
|
||||
switch (typeof expression) {
|
||||
switch (expressionType) {
|
||||
case 'function':
|
||||
predicateFn = expression;
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
case 'number':
|
||||
case 'string':
|
||||
matchAgainstAnyProp = true;
|
||||
@@ -156,6 +161,10 @@ function filterFilter() {
|
||||
};
|
||||
}
|
||||
|
||||
function hasCustomToString(obj) {
|
||||
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
|
||||
}
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
|
||||
@@ -165,8 +174,16 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
comparator = equals;
|
||||
} else if (!isFunction(comparator)) {
|
||||
comparator = function(actual, expected) {
|
||||
if (isObject(actual) || isObject(expected)) {
|
||||
// Prevent an object to be considered equal to a string like `'[object'`
|
||||
if (isUndefined(actual)) {
|
||||
// No substring matching against `undefined`
|
||||
return false;
|
||||
}
|
||||
if ((actual === null) || (expected === null)) {
|
||||
// No substring matching against `null`; only match against `null`
|
||||
return actual === expected;
|
||||
}
|
||||
if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
|
||||
// Should not compare primitives against objects, unless they have custom `toString` method
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -187,8 +204,8 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
}
|
||||
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
|
||||
var actualType = typeof actual;
|
||||
var expectedType = typeof expected;
|
||||
var actualType = getTypeForFilter(actual);
|
||||
var expectedType = getTypeForFilter(expected);
|
||||
|
||||
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
|
||||
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
|
||||
@@ -213,7 +230,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
if (isFunction(expectedVal)) {
|
||||
if (isFunction(expectedVal) || isUndefined(expectedVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -234,3 +251,8 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
// Used for easily differentiating between `null` and actual `object`
|
||||
function getTypeForFilter(val) {
|
||||
return (val === null) ? 'null' : typeof val;
|
||||
}
|
||||
|
||||
@@ -302,6 +302,14 @@ function ampmGetter(date, formats) {
|
||||
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
|
||||
}
|
||||
|
||||
function eraGetter(date, formats) {
|
||||
return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
|
||||
}
|
||||
|
||||
function longEraGetter(date, formats) {
|
||||
return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
|
||||
}
|
||||
|
||||
var DATE_FORMATS = {
|
||||
yyyy: dateGetter('FullYear', 4),
|
||||
yy: dateGetter('FullYear', 2, 0, true),
|
||||
@@ -328,10 +336,14 @@ var DATE_FORMATS = {
|
||||
a: ampmGetter,
|
||||
Z: timeZoneGetter,
|
||||
ww: weekGetter(2),
|
||||
w: weekGetter(1)
|
||||
w: weekGetter(1),
|
||||
G: eraGetter,
|
||||
GG: eraGetter,
|
||||
GGG: eraGetter,
|
||||
GGGG: longEraGetter
|
||||
};
|
||||
|
||||
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,
|
||||
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
|
||||
NUMBER_STRING = /^\-?\d+$/;
|
||||
|
||||
/**
|
||||
@@ -368,6 +380,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
|
||||
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
|
||||
* * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
|
||||
* * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
|
||||
* * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
|
||||
*
|
||||
* `format` string can also be one of the following predefined
|
||||
* {@link guide/i18n localizable formats}:
|
||||
@@ -393,7 +407,9 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
|
||||
* specified in the string input, the time is considered to be in the local timezone.
|
||||
* @param {string=} format Formatting rules (see Description). If not specified,
|
||||
* `mediumDate` is used.
|
||||
* @param {string=} timezone Timezone to be used for formatting. Right now, only `'UTC'` is supported.
|
||||
* @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
|
||||
* continental US time zone abbreviations, but for general use, use a time zone offset, for
|
||||
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
|
||||
* If not specified, the timezone of the browser will be used.
|
||||
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
|
||||
*
|
||||
@@ -486,12 +502,8 @@ function dateFilter($locale) {
|
||||
|
||||
var dateTimezoneOffset = date.getTimezoneOffset();
|
||||
if (timezone) {
|
||||
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
|
||||
if (!isNaN(requestedTimezoneOffset)) {
|
||||
date = new Date(date.getTime());
|
||||
date.setMinutes(date.getMinutes() + dateTimezoneOffset - requestedTimezoneOffset);
|
||||
dateTimezoneOffset = requestedTimezoneOffset;
|
||||
}
|
||||
dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
|
||||
date = convertTimezoneToLocal(date, timezone, true);
|
||||
}
|
||||
forEach(parts, function(value) {
|
||||
fn = DATE_FORMATS[value];
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* If the number is negative, `limit` number of items from the end of the source array/string
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
|
||||
* the input will be returned unchanged.
|
||||
* @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
|
||||
* indicates an offset from the end of `input`. Defaults to `0`.
|
||||
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
|
||||
* had less than `limit` elements.
|
||||
*
|
||||
@@ -88,7 +90,7 @@
|
||||
</example>
|
||||
*/
|
||||
function limitToFilter() {
|
||||
return function(input, limit) {
|
||||
return function(input, limit, begin) {
|
||||
if (Math.abs(Number(limit)) === Infinity) {
|
||||
limit = Number(limit);
|
||||
} else {
|
||||
@@ -99,6 +101,17 @@ function limitToFilter() {
|
||||
if (isNumber(input)) input = input.toString();
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit);
|
||||
begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
|
||||
begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
|
||||
|
||||
if (limit >= 0) {
|
||||
return input.slice(begin, begin + limit);
|
||||
} else {
|
||||
if (begin === 0) {
|
||||
return input.slice(limit, input.length);
|
||||
} else {
|
||||
return input.slice(Math.max(0, begin + limit), begin);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* Can be one of:
|
||||
*
|
||||
* - `function`: Getter function. The result of this function will be sorted using the
|
||||
* `<`, `=`, `>` operator.
|
||||
* `<`, `===`, `>` operator.
|
||||
* - `string`: An Angular expression. The result of this expression is used to compare elements
|
||||
* (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
|
||||
* 3 first characters of a property called `name`). The result of a constant expression
|
||||
@@ -34,6 +34,43 @@
|
||||
* @param {boolean=} reverse Reverse the order of the array.
|
||||
* @returns {Array} Sorted copy of the source array.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* The example below demonstrates a simple ngRepeat, where the data is sorted
|
||||
* by age in descending order (predicate is set to `'-age'`).
|
||||
* `reverse` is not set, which means it defaults to `false`.
|
||||
<example module="orderByExample">
|
||||
<file name="index.html">
|
||||
<script>
|
||||
angular.module('orderByExample', [])
|
||||
.controller('ExampleController', ['$scope', function($scope) {
|
||||
$scope.friends =
|
||||
[{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}];
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<table class="friend">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Phone Number</th>
|
||||
<th>Age</th>
|
||||
</tr>
|
||||
<tr ng-repeat="friend in friends | orderBy:'-age'">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<td>{{friend.age}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
* The predicate and reverse parameters can be controlled dynamically through scope properties,
|
||||
* as shown in the next example.
|
||||
* @example
|
||||
<example module="orderByExample">
|
||||
<file name="index.html">
|
||||
|
||||
+108
-39
@@ -9,6 +9,65 @@ var JSON_ENDS = {
|
||||
};
|
||||
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
||||
|
||||
function paramSerializerFactory(jQueryMode) {
|
||||
|
||||
function serializeValue(v) {
|
||||
if (isObject(v)) {
|
||||
return isDate(v) ? v.toISOString() : toJson(v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
return function paramSerializer(params) {
|
||||
if (!params) return '';
|
||||
var parts = [];
|
||||
forEachSorted(params, function(value, key) {
|
||||
if (value === null || isUndefined(value)) return;
|
||||
if (isArray(value) || isObject(value) && jQueryMode) {
|
||||
forEach(value, function(v, k) {
|
||||
var keySuffix = jQueryMode ? '[' + (!isArray(value) ? k : '') + ']' : '';
|
||||
parts.push(encodeUriQuery(key + keySuffix) + '=' + encodeUriQuery(serializeValue(v)));
|
||||
});
|
||||
} else {
|
||||
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
|
||||
}
|
||||
});
|
||||
|
||||
return parts.length > 0 ? parts.join('&') : '';
|
||||
};
|
||||
}
|
||||
|
||||
function $HttpParamSerializerProvider() {
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $httpParamSerializer
|
||||
* @description
|
||||
*
|
||||
* Default $http params serializer that converts objects to a part of a request URL
|
||||
* according to the following rules:
|
||||
* * `{'foo': 'bar'}` results in `foo=bar`
|
||||
* * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
|
||||
* * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
|
||||
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
|
||||
* */
|
||||
this.$get = function() {
|
||||
return paramSerializerFactory(false);
|
||||
};
|
||||
}
|
||||
|
||||
function $HttpParamSerializerJQLikeProvider() {
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $httpParamSerializerJQLike
|
||||
* @description
|
||||
*
|
||||
* Alternative $http params serializer that follows jQuerys `param()` method {http://api.jquery.com/jquery.param/} logic.
|
||||
* */
|
||||
this.$get = function() {
|
||||
return paramSerializerFactory(true);
|
||||
};
|
||||
}
|
||||
|
||||
function defaultHttpResponseTransform(data, headers) {
|
||||
if (isString(data)) {
|
||||
// Strip json vulnerability protection prefix and trim whitespace
|
||||
@@ -37,19 +96,24 @@ function isJsonLike(str) {
|
||||
* @returns {Object} Parsed headers as key value object
|
||||
*/
|
||||
function parseHeaders(headers) {
|
||||
var parsed = createMap(), key, val, i;
|
||||
|
||||
if (!headers) return parsed;
|
||||
|
||||
forEach(headers.split('\n'), function(line) {
|
||||
i = line.indexOf(':');
|
||||
key = lowercase(trim(line.substr(0, i)));
|
||||
val = trim(line.substr(i + 1));
|
||||
var parsed = createMap(), i;
|
||||
|
||||
function fillInParsed(key, val) {
|
||||
if (key) {
|
||||
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isString(headers)) {
|
||||
forEach(headers.split('\n'), function(line) {
|
||||
i = line.indexOf(':');
|
||||
fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
|
||||
});
|
||||
} else if (isObject(headers)) {
|
||||
forEach(headers, function(headerVal, headerKey) {
|
||||
fillInParsed(lowercase(headerKey), trim(headerVal));
|
||||
});
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
@@ -68,7 +132,7 @@ function parseHeaders(headers) {
|
||||
* - if called with no arguments returns an object containing all headers.
|
||||
*/
|
||||
function headersGetter(headers) {
|
||||
var headersObj = isObject(headers) ? headers : undefined;
|
||||
var headersObj;
|
||||
|
||||
return function(name) {
|
||||
if (!headersObj) headersObj = parseHeaders(headers);
|
||||
@@ -148,6 +212,11 @@ function $HttpProvider() {
|
||||
* - **`defaults.headers.put`**
|
||||
* - **`defaults.headers.patch`**
|
||||
*
|
||||
* - **`defaults.paramSerializer`** - {string|function(Object<string,string>):string} - A function used to prepare string representation
|
||||
* of request parameters (specified as an object).
|
||||
* Is specified as string, it is interpreted as function registered in with the {$injector}.
|
||||
* Defaults to {$httpParamSerializer}.
|
||||
*
|
||||
**/
|
||||
var defaults = this.defaults = {
|
||||
// transform incoming response data
|
||||
@@ -169,7 +238,9 @@ function $HttpProvider() {
|
||||
},
|
||||
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN'
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN',
|
||||
|
||||
paramSerializer: '$httpParamSerializer'
|
||||
};
|
||||
|
||||
var useApplyAsync = false;
|
||||
@@ -183,7 +254,7 @@ function $HttpProvider() {
|
||||
* significant performance improvement for bigger applications that make many HTTP requests
|
||||
* concurrently (common during application bootstrap).
|
||||
*
|
||||
* Defaults to false. If no value is specifed, returns the current configured value.
|
||||
* Defaults to false. If no value is specified, returns the current configured value.
|
||||
*
|
||||
* @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
|
||||
* "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
|
||||
@@ -215,11 +286,17 @@ function $HttpProvider() {
|
||||
**/
|
||||
var interceptorFactories = this.interceptors = [];
|
||||
|
||||
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
|
||||
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
|
||||
this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
|
||||
function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
|
||||
|
||||
var defaultCache = $cacheFactory('$http');
|
||||
|
||||
/**
|
||||
* Make sure that default param serializer is exposed as a function
|
||||
*/
|
||||
defaults.paramSerializer = isString(defaults.paramSerializer) ?
|
||||
$injector.get(defaults.paramSerializer) : defaults.paramSerializer;
|
||||
|
||||
/**
|
||||
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
|
||||
* The reversal is needed so that we can build up the interception chain around the
|
||||
@@ -372,7 +449,7 @@ function $HttpProvider() {
|
||||
* headers: {
|
||||
* 'Content-Type': undefined
|
||||
* },
|
||||
* data: { test: 'test' },
|
||||
* data: { test: 'test' }
|
||||
* }
|
||||
*
|
||||
* $http(req).success(function(){...}).error(function(){...});
|
||||
@@ -631,6 +708,9 @@ function $HttpProvider() {
|
||||
* response body, headers and status and returns its transformed (typically deserialized) version.
|
||||
* See {@link ng.$http#overriding-the-default-transformations-per-request
|
||||
* Overriding the Default Transformations}
|
||||
* - **paramSerializer** - {string|function(Object<string,string>):string} - A function used to prepare string representation
|
||||
* of request parameters (specified as an object).
|
||||
* Is specified as string, it is interpreted as function registered in with the {$injector}.
|
||||
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with
|
||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
||||
@@ -759,11 +839,14 @@ function $HttpProvider() {
|
||||
var config = extend({
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
transformResponse: defaults.transformResponse
|
||||
transformResponse: defaults.transformResponse,
|
||||
paramSerializer: defaults.paramSerializer
|
||||
}, requestConfig);
|
||||
|
||||
config.headers = mergeHeaders(requestConfig);
|
||||
config.method = uppercase(config.method);
|
||||
config.paramSerializer = isString(config.paramSerializer) ?
|
||||
$injector.get(config.paramSerializer) : config.paramSerializer;
|
||||
|
||||
var serverRequest = function(config) {
|
||||
var headers = config.headers;
|
||||
@@ -807,6 +890,8 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
promise.success = function(fn) {
|
||||
assertArgFn(fn, 'fn');
|
||||
|
||||
promise.then(function(response) {
|
||||
fn(response.data, response.status, response.headers, config);
|
||||
});
|
||||
@@ -814,6 +899,8 @@ function $HttpProvider() {
|
||||
};
|
||||
|
||||
promise.error = function(fn) {
|
||||
assertArgFn(fn, 'fn');
|
||||
|
||||
promise.then(null, function(response) {
|
||||
fn(response.data, response.status, response.headers, config);
|
||||
});
|
||||
@@ -1023,7 +1110,7 @@ function $HttpProvider() {
|
||||
cache,
|
||||
cachedResp,
|
||||
reqHeaders = config.headers,
|
||||
url = buildUrl(config.url, config.params);
|
||||
url = buildUrl(config.url, config.paramSerializer(config.params));
|
||||
|
||||
$http.pendingRequests.push(config);
|
||||
promise.then(removePendingReq, removePendingReq);
|
||||
@@ -1061,7 +1148,7 @@ function $HttpProvider() {
|
||||
// send the request to the backend
|
||||
if (isUndefined(cachedResp)) {
|
||||
var xsrfValue = urlIsSameOrigin(config.url)
|
||||
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
|
||||
? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
|
||||
: undefined;
|
||||
if (xsrfValue) {
|
||||
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
|
||||
@@ -1130,27 +1217,9 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
|
||||
function buildUrl(url, params) {
|
||||
if (!params) return url;
|
||||
var parts = [];
|
||||
forEachSorted(params, function(value, key) {
|
||||
if (value === null || isUndefined(value)) return;
|
||||
if (!isArray(value)) value = [value];
|
||||
|
||||
forEach(value, function(v) {
|
||||
if (isObject(v)) {
|
||||
if (isDate(v)) {
|
||||
v = v.toISOString();
|
||||
} else {
|
||||
v = toJson(v);
|
||||
}
|
||||
}
|
||||
parts.push(encodeUriQuery(key) + '=' +
|
||||
encodeUriQuery(v));
|
||||
});
|
||||
});
|
||||
if (parts.length > 0) {
|
||||
url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
|
||||
function buildUrl(url, serializedParams) {
|
||||
if (serializedParams.length > 0) {
|
||||
url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
};
|
||||
|
||||
function jsonpReq(url, callbackId, done) {
|
||||
// we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
|
||||
// we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
|
||||
// - fetches local scripts via XHR and evals them
|
||||
// - adds and immediately removes script elements from the document
|
||||
var script = rawDocument.createElement('script'), callback = null;
|
||||
|
||||
+17
-13
@@ -1,6 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
var $interpolateMinErr = minErr('$interpolate');
|
||||
var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
|
||||
$interpolateMinErr.throwNoconcat = function(text) {
|
||||
throw $interpolateMinErr('noconcat',
|
||||
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
|
||||
"interpolations that concatenate multiple expressions when a trusted value is " +
|
||||
"required. See http://docs.angularjs.org/api/ng.$sce", text);
|
||||
};
|
||||
|
||||
$interpolateMinErr.interr = function(text, err) {
|
||||
return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
@@ -244,10 +254,7 @@ function $InterpolateProvider() {
|
||||
// make it obvious that you bound the value to some user controlled value. This helps reduce
|
||||
// the load when auditing for XSS issues.
|
||||
if (trustedContext && concat.length > 1) {
|
||||
throw $interpolateMinErr('noconcat',
|
||||
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
|
||||
"interpolations that concatenate multiple expressions when a trusted value is " +
|
||||
"required. See http://docs.angularjs.org/api/ng.$sce", text);
|
||||
$interpolateMinErr.throwNoconcat(text);
|
||||
}
|
||||
|
||||
if (!mustHaveExpression || expressions.length) {
|
||||
@@ -277,16 +284,14 @@ function $InterpolateProvider() {
|
||||
|
||||
return compute(values);
|
||||
} catch (err) {
|
||||
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
|
||||
err.toString());
|
||||
$exceptionHandler(newErr);
|
||||
$exceptionHandler($interpolateMinErr.interr(text, err));
|
||||
}
|
||||
|
||||
}, {
|
||||
// all of these properties are undocumented for now
|
||||
exp: text, //just for compatibility with regular watchers created via $watch
|
||||
expressions: expressions,
|
||||
$$watchDelegate: function(scope, listener, objectEquality) {
|
||||
$$watchDelegate: function(scope, listener) {
|
||||
var lastValue;
|
||||
return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
|
||||
var currValue = compute(values);
|
||||
@@ -294,7 +299,7 @@ function $InterpolateProvider() {
|
||||
listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
|
||||
}
|
||||
lastValue = currValue;
|
||||
}, objectEquality);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -304,9 +309,7 @@ function $InterpolateProvider() {
|
||||
value = getValue(value);
|
||||
return allOrNothing && !isDefined(value) ? value : stringify(value);
|
||||
} catch (err) {
|
||||
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
|
||||
err.toString());
|
||||
$exceptionHandler(newErr);
|
||||
$exceptionHandler($interpolateMinErr.interr(text, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,3 +350,4 @@ function $InterpolateProvider() {
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-2
@@ -39,6 +39,7 @@ function $IntervalProvider() {
|
||||
* indefinitely.
|
||||
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
|
||||
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
|
||||
* @param {...*=} Pass additional parameters to the executed function.
|
||||
* @returns {promise} A promise which will be notified on each iteration.
|
||||
*
|
||||
* @example
|
||||
@@ -132,7 +133,9 @@ function $IntervalProvider() {
|
||||
* </example>
|
||||
*/
|
||||
function interval(fn, delay, count, invokeApply) {
|
||||
var setInterval = $window.setInterval,
|
||||
var hasParams = arguments.length > 4,
|
||||
args = hasParams ? sliceArgs(arguments, 4) : [],
|
||||
setInterval = $window.setInterval,
|
||||
clearInterval = $window.clearInterval,
|
||||
iteration = 0,
|
||||
skipApply = (isDefined(invokeApply) && !invokeApply),
|
||||
@@ -141,7 +144,9 @@ function $IntervalProvider() {
|
||||
|
||||
count = isDefined(count) ? count : 0;
|
||||
|
||||
promise.then(null, null, fn);
|
||||
promise.then(null, null, (!hasParams) ? fn : function() {
|
||||
fn.apply(null, args);
|
||||
});
|
||||
|
||||
promise.$$intervalId = setInterval(function tick() {
|
||||
deferred.notify(iteration++);
|
||||
|
||||
+9
-1
@@ -59,7 +59,15 @@ function $LocaleProvider() {
|
||||
mediumDate: 'MMM d, y',
|
||||
shortDate: 'M/d/yy',
|
||||
mediumTime: 'h:mm:ss a',
|
||||
shortTime: 'h:mm a'
|
||||
shortTime: 'h:mm a',
|
||||
ERANAMES: [
|
||||
"Before Christ",
|
||||
"Anno Domini"
|
||||
],
|
||||
ERAS: [
|
||||
"BC",
|
||||
"AD"
|
||||
]
|
||||
},
|
||||
|
||||
pluralCat: function(num) {
|
||||
|
||||
+1
-1
@@ -882,7 +882,7 @@ function $LocationProvider() {
|
||||
|
||||
|
||||
// rewrite hashbang url <> html5 url
|
||||
if ($location.absUrl() != initialUrl) {
|
||||
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<button ng-click="$log.warn(message)">warn</button>
|
||||
<button ng-click="$log.info(message)">info</button>
|
||||
<button ng-click="$log.error(message)">error</button>
|
||||
<button ng-click="$log.debug(message)">debug</button>
|
||||
</div>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
+30
-19
@@ -1,5 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* Any commits to this file should be reviewed with security in mind. *
|
||||
* Changes to this file can potentially create security vulnerabilities. *
|
||||
* An approval from 2 Core members with history of modifying *
|
||||
* this file is required. *
|
||||
* *
|
||||
* Does the change somehow allow for arbitrary javascript to be executed? *
|
||||
* Or allows for someone to change the prototype of built-in objects? *
|
||||
* Or gives undesired access to variables likes document or window? *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
var $parseMinErr = minErr('$parse');
|
||||
|
||||
// Sandboxing Angular Expressions
|
||||
@@ -773,7 +784,7 @@ ASTCompiler.prototype = {
|
||||
self.state.computing = fnKey;
|
||||
var intoId = self.nextId();
|
||||
self.recurse(watch, intoId);
|
||||
self.return(intoId);
|
||||
self.return_(intoId);
|
||||
self.state.inputs.push(fnKey);
|
||||
watch.watchId = key;
|
||||
});
|
||||
@@ -860,7 +871,7 @@ ASTCompiler.prototype = {
|
||||
recursionFn = recursionFn || noop;
|
||||
if (!skipWatchIdCheck && isDefined(ast.watchId)) {
|
||||
intoId = intoId || this.nextId();
|
||||
this.if('i',
|
||||
this.if_('i',
|
||||
this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
|
||||
this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
|
||||
);
|
||||
@@ -873,7 +884,7 @@ ASTCompiler.prototype = {
|
||||
if (pos !== ast.body.length - 1) {
|
||||
self.current().body.push(right, ';');
|
||||
} else {
|
||||
self.return(right);
|
||||
self.return_(right);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@@ -904,13 +915,13 @@ ASTCompiler.prototype = {
|
||||
case AST.LogicalExpression:
|
||||
intoId = intoId || this.nextId();
|
||||
self.recurse(ast.left, intoId);
|
||||
self.if(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
|
||||
self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
|
||||
recursionFn(intoId);
|
||||
break;
|
||||
case AST.ConditionalExpression:
|
||||
intoId = intoId || this.nextId();
|
||||
self.recurse(ast.test, intoId);
|
||||
self.if(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
|
||||
self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
|
||||
recursionFn(intoId);
|
||||
break;
|
||||
case AST.Identifier:
|
||||
@@ -921,11 +932,11 @@ ASTCompiler.prototype = {
|
||||
nameId.name = ast.name;
|
||||
}
|
||||
ensureSafeMemberName(ast.name);
|
||||
self.if(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
|
||||
self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
|
||||
function() {
|
||||
self.if(self.stage === 'inputs' || 's', function() {
|
||||
self.if_(self.stage === 'inputs' || 's', function() {
|
||||
if (create && create !== 1) {
|
||||
self.if(
|
||||
self.if_(
|
||||
self.not(self.nonComputedMember('s', ast.name)),
|
||||
self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
|
||||
}
|
||||
@@ -942,13 +953,13 @@ ASTCompiler.prototype = {
|
||||
left = nameId && (nameId.context = this.nextId()) || this.nextId();
|
||||
intoId = intoId || this.nextId();
|
||||
self.recurse(ast.object, left, undefined, function() {
|
||||
self.if(self.notNull(left), function() {
|
||||
self.if_(self.notNull(left), function() {
|
||||
if (ast.computed) {
|
||||
right = self.nextId();
|
||||
self.recurse(ast.property, right);
|
||||
self.addEnsureSafeMemberName(right);
|
||||
if (create && create !== 1) {
|
||||
self.if(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
|
||||
self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
|
||||
}
|
||||
expression = self.ensureSafeObject(self.computedMember(left, right));
|
||||
self.assign(intoId, expression);
|
||||
@@ -959,7 +970,7 @@ ASTCompiler.prototype = {
|
||||
} else {
|
||||
ensureSafeMemberName(ast.property.name);
|
||||
if (create && create !== 1) {
|
||||
self.if(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
|
||||
self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
|
||||
}
|
||||
expression = self.nonComputedMember(left, ast.property.name);
|
||||
if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
|
||||
@@ -993,10 +1004,10 @@ ASTCompiler.prototype = {
|
||||
left = {};
|
||||
args = [];
|
||||
self.recurse(ast.callee, right, left, function() {
|
||||
self.if(self.notNull(right), function() {
|
||||
self.if_(self.notNull(right), function() {
|
||||
self.addEnsureSafeFunction(right);
|
||||
forEach(ast.arguments, function(expr) {
|
||||
self.recurse(expr, undefined, undefined, function(argument) {
|
||||
self.recurse(expr, self.nextId(), undefined, function(argument) {
|
||||
args.push(self.ensureSafeObject(argument));
|
||||
});
|
||||
});
|
||||
@@ -1022,19 +1033,19 @@ ASTCompiler.prototype = {
|
||||
throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
|
||||
}
|
||||
this.recurse(ast.left, undefined, left, function() {
|
||||
self.if(self.notNull(left.context), function() {
|
||||
self.if_(self.notNull(left.context), function() {
|
||||
self.recurse(ast.right, right);
|
||||
self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
|
||||
expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
|
||||
self.assign(intoId, expression);
|
||||
recursionFn(expression);
|
||||
recursionFn(intoId || expression);
|
||||
});
|
||||
}, 1);
|
||||
break;
|
||||
case AST.ArrayExpression:
|
||||
args = [];
|
||||
forEach(ast.elements, function(expr) {
|
||||
self.recurse(expr, undefined, undefined, function(argument) {
|
||||
self.recurse(expr, self.nextId(), undefined, function(argument) {
|
||||
args.push(argument);
|
||||
});
|
||||
});
|
||||
@@ -1045,7 +1056,7 @@ ASTCompiler.prototype = {
|
||||
case AST.ObjectExpression:
|
||||
args = [];
|
||||
forEach(ast.properties, function(property) {
|
||||
self.recurse(property.value, undefined, undefined, function(expr) {
|
||||
self.recurse(property.value, self.nextId(), undefined, function(expr) {
|
||||
args.push(self.escape(
|
||||
property.key.type === AST.Identifier ? property.key.name :
|
||||
('' + property.key.value)) +
|
||||
@@ -1097,11 +1108,11 @@ ASTCompiler.prototype = {
|
||||
return 'plus(' + left + ',' + right + ')';
|
||||
},
|
||||
|
||||
'return': function(id) {
|
||||
return_: function(id) {
|
||||
this.current().body.push('return ', id, ';');
|
||||
},
|
||||
|
||||
'if': function(test, alternate, consequent) {
|
||||
if_: function(test, alternate, consequent) {
|
||||
if (test === true) {
|
||||
alternate();
|
||||
} else {
|
||||
|
||||
+28
-19
@@ -80,9 +80,27 @@ function $RootScopeProvider() {
|
||||
return TTL;
|
||||
};
|
||||
|
||||
function createChildScopeClass(parent) {
|
||||
function ChildScope() {
|
||||
this.$$watchers = this.$$nextSibling =
|
||||
this.$$childHead = this.$$childTail = null;
|
||||
this.$$listeners = {};
|
||||
this.$$listenerCount = {};
|
||||
this.$$watchersCount = 0;
|
||||
this.$id = nextUid();
|
||||
this.$$ChildScope = null;
|
||||
}
|
||||
ChildScope.prototype = parent;
|
||||
return ChildScope;
|
||||
}
|
||||
|
||||
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
|
||||
function($injector, $exceptionHandler, $parse, $browser) {
|
||||
|
||||
function destroyChildScope($event) {
|
||||
$event.currentScope.$$destroyed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name $rootScope.Scope
|
||||
@@ -206,16 +224,7 @@ function $RootScopeProvider() {
|
||||
// Only create a child scope class if somebody asks for one,
|
||||
// but cache it to allow the VM to optimize lookups.
|
||||
if (!this.$$ChildScope) {
|
||||
this.$$ChildScope = function ChildScope() {
|
||||
this.$$watchers = this.$$nextSibling =
|
||||
this.$$childHead = this.$$childTail = null;
|
||||
this.$$listeners = {};
|
||||
this.$$listenerCount = {};
|
||||
this.$$watchersCount = 0;
|
||||
this.$id = nextUid();
|
||||
this.$$ChildScope = null;
|
||||
};
|
||||
this.$$ChildScope.prototype = this;
|
||||
this.$$ChildScope = createChildScopeClass(this);
|
||||
}
|
||||
child = new this.$$ChildScope();
|
||||
}
|
||||
@@ -233,13 +242,9 @@ function $RootScopeProvider() {
|
||||
// prototypically. In all other cases, this property needs to be set
|
||||
// when the parent scope is destroyed.
|
||||
// The listener needs to be added after the parent is set
|
||||
if (isolate || parent != this) child.$on('$destroy', destroyChild);
|
||||
if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
|
||||
|
||||
return child;
|
||||
|
||||
function destroyChild() {
|
||||
child.$$destroyed = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -866,13 +871,17 @@ function $RootScopeProvider() {
|
||||
* clean up DOM bindings before an element is removed from the DOM.
|
||||
*/
|
||||
$destroy: function() {
|
||||
// we can't destroy the root scope or a scope that has been already destroyed
|
||||
// We can't destroy a scope that has been already destroyed.
|
||||
if (this.$$destroyed) return;
|
||||
var parent = this.$parent;
|
||||
|
||||
this.$broadcast('$destroy');
|
||||
this.$$destroyed = true;
|
||||
if (this === $rootScope) return;
|
||||
|
||||
if (this === $rootScope) {
|
||||
//Remove handlers attached to window when $rootScope is removed
|
||||
$browser.$$applicationDestroyed();
|
||||
}
|
||||
|
||||
incrementWatchersCount(this, -this.$$watchersCount);
|
||||
for (var eventName in this.$$listenerCount) {
|
||||
@@ -881,8 +890,8 @@ function $RootScopeProvider() {
|
||||
|
||||
// sever all the references to parent scopes (after this cleanup, the current scope should
|
||||
// not be retained by any of our references and should be eligible for garbage collection)
|
||||
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
||||
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
||||
if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
|
||||
if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
|
||||
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
|
||||
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user