Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6ece7d993 | |||
| 0563a0a636 | |||
| f4ff11b01e | |||
| 0d3b69a5f2 | |||
| 39d0b36826 | |||
| 035ffb82c4 | |||
| d5445c601f | |||
| df1a00b11a | |||
| d9ff4e42ce | |||
| b3e09be589 | |||
| 0c2378de79 | |||
| 30996f82af | |||
| aad60953ce | |||
| ab354cf04e | |||
| 208114c2b2 | |||
| 22358cf9c7 | |||
| 003c44ecee | |||
| 9e4701a5ab | |||
| cae01f4941 | |||
| 667183a8c7 | |||
| 35049be9d9 | |||
| 323862ff63 | |||
| e63f670ff2 | |||
| 6b786dcb57 | |||
| 1beebee968 | |||
| 74a214c043 | |||
| c93924ed27 | |||
| a84480affb | |||
| 6fd36deed9 | |||
| 8ee1ba4b94 | |||
| ab240196bf | |||
| 613d0a3212 | |||
| cb85cbcec1 | |||
| a75546afdf | |||
| e843ae7a4c | |||
| 40d8da80ce | |||
| f52203ce71 | |||
| d9457aa288 | |||
| 46db47bb37 | |||
| 0e4390c923 | |||
| c6d76d1e2e | |||
| da366f7e92 | |||
| 3ad774b01c | |||
| 7911c3cfe7 | |||
| 2cb0b309cb | |||
| 8ef69d2d7a | |||
| 687981913c | |||
| 806ef998be | |||
| a21d49c900 | |||
| 43d4fffdbe | |||
| 409ad62042 | |||
| 7cb01a80be | |||
| feba0174db | |||
| 7b6c1d08ac | |||
| 858360b680 | |||
| f7174169f4 | |||
| 0656484d3e | |||
| f3539f3cb5 | |||
| 404b95fe30 | |||
| d277641eec | |||
| e7cf04bad3 | |||
| 607f016a0b | |||
| f2942447c1 | |||
| b8c5b87119 | |||
| 074a146d8b | |||
| 86c7d1221c | |||
| 8da08a1ebd | |||
| f7b2d85a2c | |||
| 391d8c04da | |||
| 11c76369aa | |||
| dc5130c611 | |||
| 84912134d5 | |||
| 59cb9e8a77 | |||
| 10644432ca | |||
| a0bfdd0d60 | |||
| 3624e3800f | |||
| b1ee5386d5 | |||
| ab80cd9066 | |||
| 8199f4dbde | |||
| 313d7956e4 | |||
| b9479ee73b | |||
| 769a00dc86 | |||
| 6593c2371e | |||
| 8b54524c07 | |||
| 66bb5aa41c | |||
| b186709003 | |||
| a27d827c22 | |||
| a1648a76c0 | |||
| 2bcd02dc1a | |||
| b0033a44bd | |||
| 76b755f3cb | |||
| 6303c3dcf6 | |||
| cd2cfafcab | |||
| 86d33c5f9d | |||
| eb935e6be0 | |||
| b119251827 | |||
| 5572b40b15 | |||
| 4a6c7cf8ce | |||
| 27d12340d9 | |||
| fb0c77f0b6 | |||
| 6417a3e9eb | |||
| 07e3abc7dd | |||
| b9df121655 | |||
| b5bb4a986a | |||
| 8202c4dcea | |||
| 2c8b464852 | |||
| a192c41ddc | |||
| a8fe2cc345 | |||
| e522c25fd4 | |||
| 5dbc2d65f3 | |||
| 27300072d1 | |||
| 7ffc247d0f | |||
| 8ab673d430 | |||
| b9e899c8b2 | |||
| 92f05e5a59 | |||
| e81ae1464d | |||
| de38899f74 | |||
| 729c238e19 | |||
| dc3de7fb7a | |||
| ace40d5526 | |||
| fd8997551f | |||
| d8c8b2ebb7 | |||
| f5bb34ab4a | |||
| 4b83f6ca2c | |||
| a591e8b8d3 | |||
| 6b05105c08 | |||
| 728832ec85 | |||
| f1a75a445c | |||
| c59bee5d21 | |||
| 17ecf84b90 | |||
| df8d9507aa | |||
| 6e7fbe77c9 | |||
| 3686f45398 | |||
| ad28baaa6c | |||
| 8f9c4daca5 | |||
| f0c94ea292 | |||
| 729129b461 | |||
| bf2c55ea29 | |||
| deafb5e545 | |||
| 0702aef7ee | |||
| f0ee335311 | |||
| 02169d4957 | |||
| 25d0eff3e6 | |||
| bd41bd594c | |||
| 373d7c95d9 | |||
| 4c5c762378 | |||
| d1434c999a | |||
| 8b8f6f5124 | |||
| 25082b3439 | |||
| 27b3ea4d32 | |||
| ffc32b4e42 | |||
| 80b0909927 | |||
| efbb365533 | |||
| 38e0ab9bd8 | |||
| 3c53b28cc2 | |||
| 0ba864184b | |||
| 4f9dc44f88 | |||
| f3884df0a9 | |||
| f7a4a70c28 | |||
| 8428a0adef |
+335
@@ -1,3 +1,338 @@
|
||||
<a name="1.3.0-rc.5"></a>
|
||||
# 1.3.0-rc.5 impossible-choreography (2014-10-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$anchorScroll:** don't scroll to top when initializing and location hash is empty
|
||||
([d5445c60](https://github.com/angular/angular.js/commit/d5445c601fafd6ecd38befeaa4c9ec7bb044127c),
|
||||
[#8848](https://github.com/angular/angular.js/issues/8848), [#9393](https://github.com/angular/angular.js/issues/9393))
|
||||
- **$animate:**
|
||||
- ensure hidden elements with ngShow/ngHide stay hidden during animations
|
||||
([39d0b368](https://github.com/angular/angular.js/commit/39d0b36826a077f7549a70d0cf3edebe90a10aaa),
|
||||
[#9103](https://github.com/angular/angular.js/issues/9103), [#9493](https://github.com/angular/angular.js/issues/9493))
|
||||
- permit class-based animations for leave operations if ngAnimateChildren is enabled
|
||||
([df1a00b1](https://github.com/angular/angular.js/commit/df1a00b11ac2722f4da441837795985f12682030),
|
||||
[#8092](https://github.com/angular/angular.js/issues/8092), [#9491](https://github.com/angular/angular.js/issues/9491))
|
||||
- ensure that class-based animations only consider the most recent DOM operations
|
||||
([c93924ed](https://github.com/angular/angular.js/commit/c93924ed275a62683b85c82f1c6c2e19d5662c9a),
|
||||
[#8946](https://github.com/angular/angular.js/issues/8946), [#9458](https://github.com/angular/angular.js/issues/9458))
|
||||
- abort class-based animations if the element is removed during digest
|
||||
([613d0a32](https://github.com/angular/angular.js/commit/613d0a3212de8dc01c817ca8526e09c57978a621),
|
||||
[#8796](https://github.com/angular/angular.js/issues/8796))
|
||||
- clear the GCS cache even when no animation is detected
|
||||
([cb85cbce](https://github.com/angular/angular.js/commit/cb85cbcec1c876db6062a0dc0bad80f842782194),
|
||||
[#8813](https://github.com/angular/angular.js/issues/8813))
|
||||
- **$browser:**
|
||||
- Cache `location.href` only during page reload phase
|
||||
([8ee1ba4b](https://github.com/angular/angular.js/commit/8ee1ba4b94d6fccff06d8781f7ed256c6ce664ff),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235), [#9455](https://github.com/angular/angular.js/issues/9455))
|
||||
- don’t use the history API when only the hash changes
|
||||
([7cb01a80](https://github.com/angular/angular.js/commit/7cb01a80beec669d8f6aae1dc211d2f0b7d4eac4),
|
||||
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424),
|
||||
[858360b6](https://github.com/angular/angular.js/commit/858360b680a2bb5c19429c1be1c9506700cda476),
|
||||
[0656484d](https://github.com/angular/angular.js/commit/0656484d3e709c5162570b0dd6473b0b6140e5b2),
|
||||
[#9143](https://github.com/angular/angular.js/issues/9143), [#9406](https://github.com/angular/angular.js/issues/9406))
|
||||
- handle async href on url change in <=IE9
|
||||
([404b95fe](https://github.com/angular/angular.js/commit/404b95fe30a1bcd1313adafbd0018578d5b21d3d),
|
||||
[#9235](https://github.com/angular/angular.js/issues/9235))
|
||||
- **$compile:**
|
||||
- handle the removal of an interpolated attribute
|
||||
([a75546af](https://github.com/angular/angular.js/commit/a75546afdf41adab786eda30c258190cd4c5f1ae),
|
||||
[#9236](https://github.com/angular/angular.js/issues/9236), [#9240](https://github.com/angular/angular.js/issues/9240))
|
||||
- remove comment nodes from templates before asserting single root node
|
||||
([feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
|
||||
[#9212](https://github.com/angular/angular.js/issues/9212), [#9215](https://github.com/angular/angular.js/issues/9215))
|
||||
- use the correct namespace for transcluded svg elements
|
||||
([f3539f3c](https://github.com/angular/angular.js/commit/f3539f3cb5d9477f50f065c6a0ac7d6ca0a31092),
|
||||
[#9344](https://github.com/angular/angular.js/issues/9344), [#9415](https://github.com/angular/angular.js/issues/9415))
|
||||
- **$http:** honor application/json response header and parse json primitives
|
||||
([7b6c1d08](https://github.com/angular/angular.js/commit/7b6c1d08aceba6704a40302f373400aed9ed0e0b),
|
||||
[#2973](https://github.com/angular/angular.js/issues/2973))
|
||||
- **$injector:** throw when factory $get method does not return a value
|
||||
([0d3b69a5](https://github.com/angular/angular.js/commit/0d3b69a5f27b41745b504c7ffd8d72653bac1f85),
|
||||
[#4575](https://github.com/angular/angular.js/issues/4575), [#9210](https://github.com/angular/angular.js/issues/9210))
|
||||
- **$location:** allow `0` in `path()` and `hash()`
|
||||
([b8c5b871](https://github.com/angular/angular.js/commit/b8c5b87119a06edb8e8d1cefad81ee8d1f64f070))
|
||||
- **form:** fix submit prevention
|
||||
([86c7d122](https://github.com/angular/angular.js/commit/86c7d1221c706993044583d51a0c61423fee5bcf),
|
||||
[#3370](https://github.com/angular/angular.js/issues/3370), [#3776](https://github.com/angular/angular.js/issues/3776))
|
||||
- **ngAnimate:** defer DOM operations for changing classes to postDigest
|
||||
([667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
[#8234](https://github.com/angular/angular.js/issues/8234), [#9263](https://github.com/angular/angular.js/issues/9263))
|
||||
- **orderBy:** sort by identity if no predicate is given
|
||||
([607f016a](https://github.com/angular/angular.js/commit/607f016a0ba705ce40df0164360fb96a9d7f5912),
|
||||
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
|
||||
- **select:**
|
||||
- throw for `selectAs` and `trackBy`
|
||||
([30996f82](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d))
|
||||
- use `$viewValue` instead of `$modelValue`
|
||||
([f7174169](https://github.com/angular/angular.js/commit/f7174169f4f710d605f6a67f39f90a67a07d4cab),
|
||||
[#8929](https://github.com/angular/angular.js/issues/8929))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:**
|
||||
- add support for History API state handling ([6fd36dee](https://github.com/angular/angular.js/commit/6fd36deed954b338e48390862971d465148dc1f2),
|
||||
[#9027](https://github.com/angular/angular.js/issues/9027))
|
||||
- allow automatic rewriting of links to be disabled
|
||||
([b3e09be5](https://github.com/angular/angular.js/commit/b3e09be58960b913fee3869bf36e7de3305bbe00),
|
||||
[#5487](https://github.com/angular/angular.js/issues/5487))
|
||||
- **$route:** ability to cancel $routeChangeStart event
|
||||
([f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
|
||||
[#5581](https://github.com/angular/angular.js/issues/5581), [#5714](https://github.com/angular/angular.js/issues/5714), [#9502](https://github.com/angular/angular.js/issues/9502))
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$animate:**
|
||||
- access DOM less in resolveElementClasses
|
||||
([22358cf9](https://github.com/angular/angular.js/commit/22358cf9c703d67f3cf9eb4899404b09578a5fad))
|
||||
- don't join classes before it's necessary in resolveElementClasses
|
||||
([003c44ec](https://github.com/angular/angular.js/commit/003c44eceee54c3398b0d2971fd97a512d7f7cec))
|
||||
- **ngBind:** set textContent rather than using element.text()
|
||||
([074a146d](https://github.com/angular/angular.js/commit/074a146d8b1ee7c93bf6d5892448a5c2a0143a28),
|
||||
[#9369](https://github.com/angular/angular.js/issues/9369), [#9396](https://github.com/angular/angular.js/issues/9396))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
|
||||
|
||||
|
||||
If a template contains directives within comment nodes, and there is more than a single node in the
|
||||
template, those comment nodes are removed. The impact of this breaking change is expected to be
|
||||
quite low.
|
||||
|
||||
Closes #9212
|
||||
Closes #9215
|
||||
|
||||
- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
|
||||
|
||||
The `$animate` CSS class API will always defer changes until the end of the next digest. This allows ngAnimate
|
||||
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
|
||||
many. This prevents jank in browsers such as IE, and is generally a good thing.
|
||||
|
||||
If you find that your classes are not being immediately applied, be sure to invoke `$digest()`.
|
||||
|
||||
Closes #8234
|
||||
Closes #9263
|
||||
|
||||
- **$select:** due to [30996f8](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d)
|
||||
|
||||
`ngOptions` will now throw an error when the comprehension expressions contains both a `select as`
|
||||
and `track by` expression.
|
||||
|
||||
These expressions are fundamentally incompatible because it is not possible to reliably and
|
||||
consistently determine the parent object of a model, since `select as` can assign any child of a
|
||||
`value` as the model value.
|
||||
|
||||
Prior to refactorings in this release, neither of these expressions worked correctly independently,
|
||||
and did not work at all when combined.
|
||||
|
||||
See #6564
|
||||
|
||||
- **$route:** due to [f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
|
||||
|
||||
Order of events has changed.
|
||||
Previously: `$locationChangeStart` -> `$locationChangeSuccess`
|
||||
-> `$routeChangeStart` -> `$routeChangeSuccess`
|
||||
|
||||
Now: `$locationChangeStart` -> `$routeChangeStart`
|
||||
-> `$locationChangeSuccess` -> -> `$routeChangeSuccess`
|
||||
|
||||
Fixes #5581
|
||||
Closes #5714
|
||||
Closes #9502- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
|
||||
|
||||
|
||||
The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
|
||||
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
|
||||
many. This prevents jank in browsers such as IE, and is generally a good thing.
|
||||
|
||||
If you're finding that your classes are not being immediately applied, be sure to invoke $digest().
|
||||
|
||||
Closes #8234
|
||||
Closes #9263
|
||||
|
||||
|
||||
<a name="1.3.0-rc.4"></a>
|
||||
# 1.3.0-rc.4 unicorn-hydrafication (2014-10-01)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- get $$observe listeners array as own property
|
||||
([a27d827c](https://github.com/angular/angular.js/commit/a27d827c22b0b6b3ba6b7495cf4fc338c6934b37),
|
||||
[#9343](https://github.com/angular/angular.js/issues/9343), [#9345](https://github.com/angular/angular.js/issues/9345))
|
||||
- Resolve leak with asynchronous compilation
|
||||
([6303c3dc](https://github.com/angular/angular.js/commit/6303c3dcf64685458fc84aa12289f5c9d57f4e47),
|
||||
[#9199](https://github.com/angular/angular.js/issues/9199), [#9079](https://github.com/angular/angular.js/issues/9079), [#8504](https://github.com/angular/angular.js/issues/8504), [#9197](https://github.com/angular/angular.js/issues/9197))
|
||||
- connect transclude scopes to their containing scope to prevent memory leaks
|
||||
([fb0c77f0](https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f),
|
||||
[#9095](https://github.com/angular/angular.js/issues/9095), [#9281](https://github.com/angular/angular.js/issues/9281))
|
||||
- sanitize srcset attribute
|
||||
([ab80cd90](https://github.com/angular/angular.js/commit/ab80cd90661396dbb1c94c5f4dd2d11ee8f6b6af))
|
||||
- **input:**
|
||||
- register builtin parsers/formatters before anyone else
|
||||
([10644432](https://github.com/angular/angular.js/commit/10644432ca9d5da69ce790a8d9e691640f333711),
|
||||
[#9218](https://github.com/angular/angular.js/issues/9218), [#9358](https://github.com/angular/angular.js/issues/9358))
|
||||
- correctly handle invalid model values for `input[date/time/…]`
|
||||
([a0bfdd0d](https://github.com/angular/angular.js/commit/a0bfdd0d60882125f614a91c321f12f730735e7b),
|
||||
[#8949](https://github.com/angular/angular.js/issues/8949), [#9375](https://github.com/angular/angular.js/issues/9375))
|
||||
- **ngModel:** do not parse undefined viewValue when validating
|
||||
([92f05e5a](https://github.com/angular/angular.js/commit/92f05e5a5900713301e64373d7b7daa45a88278b),
|
||||
[#9106](https://github.com/angular/angular.js/issues/9106), [#9260](https://github.com/angular/angular.js/issues/9260))
|
||||
- **ngView:** use animation promises ensure that only one leave animation occurs at a time
|
||||
([3624e380](https://github.com/angular/angular.js/commit/3624e3800fb3ccd2e9ea361a763e20131fd42c29),
|
||||
[#9355](https://github.com/angular/angular.js/issues/9355), [#7606](https://github.com/angular/angular.js/issues/7606), [#9374](https://github.com/angular/angular.js/issues/9374))
|
||||
- **select:** make ctrl.hasOption method consistent
|
||||
([2bcd02dc](https://github.com/angular/angular.js/commit/2bcd02dc1a6b28b357d47c83be3bed5c9a38417c),
|
||||
[#8761](https://github.com/angular/angular.js/issues/8761))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** optionally get controllers from ancestors only
|
||||
([07e3abc7](https://github.com/angular/angular.js/commit/07e3abc7dda872adc3fb25cb3e133f86f494b35d),
|
||||
[#4518](https://github.com/angular/angular.js/issues/4518), [#4540](https://github.com/angular/angular.js/issues/4540), [#8240](https://github.com/angular/angular.js/issues/8240), [#8511](https://github.com/angular/angular.js/issues/8511))
|
||||
- **Scope:** allow the parent of a new scope to be specified on creation
|
||||
([6417a3e9](https://github.com/angular/angular.js/commit/6417a3e9eb7ab0011cefada8db855aa929a64ff8))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$rootScope:** moving internal queues out of the Scope instances
|
||||
([b1192518](https://github.com/angular/angular.js/commit/b119251827cea670051198e1b48af7ee0c9f2a1b),
|
||||
[#9071](https://github.com/angular/angular.js/issues/9071))
|
||||
- **benchmark:** add ngBindOnce benchmarks to largetable-bp
|
||||
([2c8b4648](https://github.com/angular/angular.js/commit/2c8b4648526acf5c2645de8408a6d9ace2144b5f))
|
||||
- **ngForm,ngModel:** move initial addClass to the compile phase
|
||||
([b1ee5386](https://github.com/angular/angular.js/commit/b1ee5386d584f208bce6d3b613afdb3bae9df76a),
|
||||
[#8268](https://github.com/angular/angular.js/issues/8268))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [fb0c77f0](https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f),
|
||||
|
||||
|
||||
`$transclude` functions no longer attach `$destroy` event handlers to the
|
||||
transcluded content, and so the associated transclude scope will not automatically
|
||||
be destroyed if you remove a transcluded element from the DOM using direct DOM
|
||||
manipulation such as the jquery `remove()` method.
|
||||
|
||||
If you want to explicitly remove DOM elements inside your directive that have
|
||||
been compiled, and so potentially contain child (and transcluded) scopes, then
|
||||
it is your responsibility to get hold of the scope and destroy it at the same time.
|
||||
|
||||
The suggested approach is to create a new child scope of your own around any DOM
|
||||
elements that you wish to manipulate in this way and destroy those scopes if you
|
||||
remove their contents - any child scopes will then be destroyed and cleaned up
|
||||
automatically.
|
||||
|
||||
Note that all the built-in directives that manipulate the DOM (ngIf, ngRepeat,
|
||||
ngSwitch, etc) already follow this best practice, so if you only use these for
|
||||
manipulating the DOM then you do not have to worry about this change.
|
||||
|
||||
Closes #9095
|
||||
Closes #9281
|
||||
|
||||
- **$parse:** due to [5572b40b](https://github.com/angular/angular.js/commit/5572b40b15ed06969c8e0e92866c5afd088484b4),
|
||||
|
||||
- $scope['this'] no longer exits on the $scope object
|
||||
- $parse-ed expressions no longer allow chaining 'this' such as this['this'] or $parent['this']
|
||||
- 'this' in $parse-ed expressions can no longer be overriden, if a variable named 'this' is put on the scope it must be accessed using this['this']
|
||||
|
||||
Closes #9105
|
||||
|
||||
- **input:** due to [1eda1836](https://github.com/angular/angular.js/commit/1eda18365a348c9597aafba9d195d345e4f13d1e),
|
||||
|
||||
(Note: this change landed in 1.3.0-rc.3, but was not considered a breaking change at the time).
|
||||
|
||||
For text based inputs (text, email, url), the `$viewValue` will now always be converted to a string,
|
||||
regardless of what type the value is on the model.
|
||||
|
||||
To migrate, any code or expressions that expect the `$viewValue` to be anything other than string
|
||||
should be updated to expect a string.
|
||||
|
||||
|
||||
- **input:** due to a0bfdd0d60882125f614a91c321f12f730735e7b (see #8949),
|
||||
|
||||
Similar to `input[number]` Angular will now throw if the model value
|
||||
for a `input[date]` is not a `Date` object. Previously, Angular only
|
||||
showed an empty string instead.
|
||||
Angular does not set validation errors on the `<input>` in this case
|
||||
as those errors are shown to the user, but the erroneous state was
|
||||
caused by incorrect application logic and not by the user.
|
||||
|
||||
<a name="1.2.26"></a>
|
||||
# 1.2.26 captivating-disinterest (2014-10-01)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
|
||||
|
||||
- **select:** make ctrl.hasOption method consistent
|
||||
([11d2242d](https://github.com/angular/angular.js/commit/11d2242df65b2ade0dabe366a0c42963b6d37df5),
|
||||
[#8761](https://github.com/angular/angular.js/issues/8761))
|
||||
|
||||
|
||||
<a name="1.3.0-rc.3"></a>
|
||||
# 1.3.0-rc.3 aggressive-pacification (2014-09-23)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **ngModel:** support milliseconds in time and datetime
|
||||
([4b83f6ca](https://github.com/angular/angular.js/commit/4b83f6ca2c15bd65fe2b3894a02c04f9967fbff4),
|
||||
[#8874](https://github.com/angular/angular.js/issues/8874))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$location:** add ability to opt-out of `<base>` tag requirement in html5Mode
|
||||
([dc3de7fb](https://github.com/angular/angular.js/commit/dc3de7fb7a14c38b5c3dc7decfafb0b51d422dd1),
|
||||
[#8934](https://github.com/angular/angular.js/issues/8934))
|
||||
- **formController:** add $setUntouched to propagate untouched state
|
||||
([fd899755](https://github.com/angular/angular.js/commit/fd8997551f9ed4431f5e99d61f637139485076b9),
|
||||
[#9050](https://github.com/angular/angular.js/issues/9050))
|
||||
- **input:** support dynamic element validation
|
||||
([729c238e](https://github.com/angular/angular.js/commit/729c238e19ab27deff01448d79342ea53721bfed),
|
||||
[#4791](https://github.com/angular/angular.js/issues/4791), [#1404](https://github.com/angular/angular.js/issues/1404))
|
||||
- **ngAria:** add an ngAria module to make a11y easier
|
||||
([d1434c99](https://github.com/angular/angular.js/commit/d1434c999a66c6bb915ee1a8b091e497d288d940),
|
||||
[#5486](https://github.com/angular/angular.js/issues/5486))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **map:** use Array.prototype.map
|
||||
([a591e8b8](https://github.com/angular/angular.js/commit/a591e8b8d302efefd67bf0d5c4bad300a5f3aded))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$location:** due to [dc3de7fb](https://github.com/angular/angular.js/commit/dc3de7fb7a14c38b5c3dc7decfafb0b51d422dd1),
|
||||
The $location.html5Mode API has changed to allow enabling html5Mode by
|
||||
passing an object (as well as still supporting passing a boolean). Symmetrically, the
|
||||
method now returns an object instead of a boolean value.
|
||||
|
||||
To migrate, follow the code example below:
|
||||
|
||||
Before:
|
||||
|
||||
var mode = $locationProvider.html5Mode();
|
||||
|
||||
After:
|
||||
|
||||
var mode = $locationProvider.html5Mode().enabled;
|
||||
|
||||
Fixes #8934
|
||||
|
||||
|
||||
<a name="1.2.25"></a>
|
||||
# 1.2.25 hypnotic-gesticulation (2014-09-16)
|
||||
|
||||
|
||||
+4
-4
@@ -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.
|
||||
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
|
||||
|
||||
@@ -66,13 +66,13 @@ Help us to maximize the effort we can spend fixing issues and adding new
|
||||
features, by not reporting duplicate issues. Providing the following information will increase the
|
||||
chances of your issue being dealt with quickly:
|
||||
|
||||
* **Overview of the issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Motivation for or Use Case** - explain why this is a bug for you
|
||||
* **Angular Version(s)** - is it a regression?
|
||||
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
|
||||
* **Reproduce the error** - provide a live example (using [Plunker][plunker] or
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
|
||||
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
|
||||
* **Related issues** - has a similar issue been reported before?
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
|
||||
|
||||
+11
-3
@@ -47,8 +47,7 @@ module.exports = function(grunt) {
|
||||
keepalive: true,
|
||||
middleware: function(connect, options){
|
||||
return [
|
||||
//uncomment to enable CSP
|
||||
// util.csp(),
|
||||
util.conditionalCsp(),
|
||||
util.rewrite(),
|
||||
connect.favicon('images/favicon.ico'),
|
||||
connect.static(options.base),
|
||||
@@ -74,6 +73,7 @@ module.exports = function(grunt) {
|
||||
|
||||
next();
|
||||
},
|
||||
util.conditionalCsp(),
|
||||
connect.favicon('images/favicon.ico'),
|
||||
connect.static(options.base)
|
||||
];
|
||||
@@ -153,6 +153,9 @@ module.exports = function(grunt) {
|
||||
},
|
||||
ngTouch: {
|
||||
files: { src: 'src/ngTouch/**/*.js' },
|
||||
},
|
||||
ngAria: {
|
||||
files: {src: 'src/ngAria/**/*.js'},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -220,6 +223,10 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-cookies.js',
|
||||
src: util.wrap(files['angularModules']['ngCookies'], 'module')
|
||||
},
|
||||
aria: {
|
||||
dest: 'build/angular-aria.js',
|
||||
src: util.wrap(files['angularModules']['ngAria'], 'module')
|
||||
},
|
||||
"promises-aplus-adapter": {
|
||||
dest:'tmp/promises-aplus-adapter++.js',
|
||||
src:['src/ng/q.js','lib/promises-aplus/promises-aplus-test-adapter.js']
|
||||
@@ -236,7 +243,8 @@ module.exports = function(grunt) {
|
||||
touch: 'build/angular-touch.js',
|
||||
resource: 'build/angular-resource.js',
|
||||
route: 'build/angular-route.js',
|
||||
sanitize: 'build/angular-sanitize.js'
|
||||
sanitize: 'build/angular-sanitize.js',
|
||||
aria: 'build/angular-aria.js'
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,11 @@ use good old HTML (or HAML, Jade and friends!) as your template language and let
|
||||
syntax to express your application’s components clearly and succinctly. It automatically
|
||||
synchronizes data from your UI (view) with your JavaScript objects (model) through 2-way data
|
||||
binding. To help you structure your application better and make it easy to test, AngularJS teaches
|
||||
the browser how to do dependency injection and inversion of control. Oh yeah and it also helps with
|
||||
server-side communication, taming async callbacks with promises and deferreds; and makes client-side
|
||||
navigation and deeplinking with hashbang urls or HTML5 pushState a piece of cake. The best of all:
|
||||
it makes development fun!
|
||||
the browser how to do dependency injection and inversion of control.
|
||||
|
||||
Oh yeah and it helps with server-side communication, taming async callbacks with promises and
|
||||
deferreds. It also makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
|
||||
piece of cake. The best of all: it makes development fun!
|
||||
|
||||
* Web site: http://angularjs.org
|
||||
* Tutorial: http://docs.angularjs.org/tutorial
|
||||
|
||||
@@ -26,7 +26,6 @@ This process based on the idea of minimizing user pain
|
||||
* You can triage older issues as well
|
||||
* Triage to your heart's content
|
||||
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
|
||||
|
||||
1. Understandable? - verify if the description of the request is clear.
|
||||
* If not, [close it][] according to the instructions below and go to the last step.
|
||||
1. Duplicate?
|
||||
@@ -36,7 +35,6 @@ This process based on the idea of minimizing user pain
|
||||
* Label `Type: Bug`
|
||||
* Reproducible? - Steps to reproduce the bug are clear. If they are not, ask for a clarification. If there's no reply after a week, [close it][].
|
||||
* Reproducible on master? - <http://code.angularjs.org/snapshot/>
|
||||
|
||||
1. Non bugs:
|
||||
* Label `Type: Feature`, `Type: Chore`, or `Type: Perf`
|
||||
* Belongs in core? – Often new features should be implemented as a third-party module rather than an addition to the core.
|
||||
@@ -59,7 +57,6 @@ This process based on the idea of minimizing user pain
|
||||
* 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 `origin: google` for issues from Google
|
||||
|
||||
1. Assign a milestone:
|
||||
* Backlog - triaged fixes and features, should be the default choice
|
||||
* Current 1.x.y milestone (e.g. 1.3.0-beta-2) - regressions and urgent bugs only
|
||||
|
||||
Vendored
+9
-3
@@ -108,6 +108,9 @@ var angularFiles = {
|
||||
'src/ngTouch/directive/ngClick.js',
|
||||
'src/ngTouch/directive/ngSwipe.js'
|
||||
],
|
||||
'ngAria': [
|
||||
'src/ngAria/aria.js'
|
||||
]
|
||||
},
|
||||
|
||||
'angularScenario': [
|
||||
@@ -141,7 +144,8 @@ var angularFiles = {
|
||||
'test/ngRoute/**/*.js',
|
||||
'test/ngSanitize/**/*.js',
|
||||
'test/ngMock/*.js',
|
||||
'test/ngTouch/**/*.js'
|
||||
'test/ngTouch/**/*.js',
|
||||
'test/ngAria/*.js'
|
||||
],
|
||||
|
||||
'karma': [
|
||||
@@ -175,7 +179,8 @@ var angularFiles = {
|
||||
'test/ngRoute/**/*.js',
|
||||
'test/ngResource/*.js',
|
||||
'test/ngSanitize/**/*.js',
|
||||
'test/ngTouch/**/*.js'
|
||||
'test/ngTouch/**/*.js',
|
||||
'test/ngAria/*.js'
|
||||
],
|
||||
|
||||
'karmaJquery': [
|
||||
@@ -203,7 +208,8 @@ angularFiles['angularSrcModules'] = [].concat(
|
||||
angularFiles['angularModules']['ngRoute'],
|
||||
angularFiles['angularModules']['ngSanitize'],
|
||||
angularFiles['angularModules']['ngMock'],
|
||||
angularFiles['angularModules']['ngTouch']
|
||||
angularFiles['angularModules']['ngTouch'],
|
||||
angularFiles['angularModules']['ngAria']
|
||||
);
|
||||
|
||||
if (exports) {
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
Large table rendered with AngularJS
|
||||
</p>
|
||||
|
||||
<div>none: <input type=radio ng-model="benchmarkType" value="none"></div>
|
||||
<div>baseline binding: <input type=radio ng-model="benchmarkType" value="baselineBinding"></div>
|
||||
<div>baseline interpolation: <input type=radio ng-model="benchmarkType" value="baselineInterpolation"></div>
|
||||
<div>ngBind: <input type=radio ng-model="benchmarkType" value="ngBind"></div>
|
||||
<div>interpolation: <input type=radio ng-model="benchmarkType" value="interpolation"></div>
|
||||
<div>ngBind + fnInvocation: <input type=radio ng-model="benchmarkType" value="ngBindFn"></div>
|
||||
<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>none: <input type="radio" ng-model="benchmarkType" value="none"></div>
|
||||
<div>baseline binding: <input type="radio" ng-model="benchmarkType" value="baselineBinding"></div>
|
||||
<div>baseline interpolation: <input type="radio" ng-model="benchmarkType" value="baselineInterpolation"></div>
|
||||
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
|
||||
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
|
||||
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
|
||||
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
|
||||
<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>
|
||||
|
||||
<ng-switch on="benchmarkType">
|
||||
<baseline-binding-table ng-switch-when="baselineBinding">
|
||||
@@ -26,7 +27,17 @@
|
||||
<div ng-switch-when="ngBind">
|
||||
<h2>baseline binding</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row"><span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|</span>
|
||||
<span ng-repeat="column in row">
|
||||
<span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngBindOnce">
|
||||
<h2>baseline binding once</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in ::row">
|
||||
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolation">
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
|
||||
.ng-cloak, .x-ng-cloak,
|
||||
.ng-hide:not(.ng-animate) {
|
||||
.ng-hide:not(.ng-hide-animate) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -316,10 +316,10 @@ iframe.example {
|
||||
}
|
||||
|
||||
.search-results-group.col-group-api { width:30%; }
|
||||
.search-results-group.col-group-guide { width:30%; }
|
||||
.search-results-group.col-group-tutorial { width:25%; }
|
||||
.search-results-group.col-group-guide,
|
||||
.search-results-group.col-group-tutorial { width:20%; }
|
||||
.search-results-group.col-group-misc,
|
||||
.search-results-group.col-group-error { float:right; clear:both; width:15% }
|
||||
.search-results-group.col-group-error { width:15%; float: right; }
|
||||
|
||||
|
||||
.search-results-group.col-group-api .search-result {
|
||||
@@ -391,7 +391,6 @@ iframe.example {
|
||||
position:fixed;
|
||||
top:120px;
|
||||
bottom:0;
|
||||
padding-bottom:120px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
@@ -412,6 +411,7 @@ iframe.example {
|
||||
|
||||
.main-body-grid .side-navigation {
|
||||
position:relative;
|
||||
padding-bottom:120px;
|
||||
}
|
||||
|
||||
.main-body-grid .side-navigation.ng-hide {
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var directive = {};
|
||||
var service = { value: {} };
|
||||
|
||||
var DEPENDENCIES = {
|
||||
'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
|
||||
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
|
||||
'angular-route.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-route.min.js',
|
||||
'angular-animate.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-animate.min.js',
|
||||
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
|
||||
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
|
||||
};
|
||||
|
||||
|
||||
function escape(text) {
|
||||
return text.
|
||||
replace(/\&/g, '&').
|
||||
replace(/\</g, '<').
|
||||
replace(/\>/g, '>').
|
||||
replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
|
||||
* http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
|
||||
*/
|
||||
function setHtmlIe8SafeWay(element, html) {
|
||||
var newElement = angular.element('<pre>' + html + '</pre>');
|
||||
|
||||
element.empty();
|
||||
element.append(newElement.contents());
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
directive.jsFiddle = function(getEmbeddedTemplate, escape, script) {
|
||||
return {
|
||||
terminal: true,
|
||||
link: function(scope, element, attr) {
|
||||
var name = '',
|
||||
stylesheet = '<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">\n',
|
||||
fields = {
|
||||
html: '',
|
||||
css: '',
|
||||
js: ''
|
||||
};
|
||||
|
||||
angular.forEach(attr.jsFiddle.split(' '), function(file, index) {
|
||||
var fileType = file.split('.')[1];
|
||||
|
||||
if (fileType == 'html') {
|
||||
if (index == 0) {
|
||||
fields[fileType] +=
|
||||
'<div ng-app' + (attr.module ? '="' + attr.module + '"' : '') + '>\n' +
|
||||
getEmbeddedTemplate(file, 2);
|
||||
} else {
|
||||
fields[fileType] += '\n\n\n <!-- CACHE FILE: ' + file + ' -->\n' +
|
||||
' <script type="text/ng-template" id="' + file + '">\n' +
|
||||
getEmbeddedTemplate(file, 4) +
|
||||
' </script>\n';
|
||||
}
|
||||
} else {
|
||||
fields[fileType] += getEmbeddedTemplate(file) + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
fields.html += '</div>\n';
|
||||
|
||||
setHtmlIe8SafeWay(element,
|
||||
'<form class="jsfiddle" method="post" action="http://jsfiddle.net/api/post/library/pure/" target="_blank">' +
|
||||
hiddenField('title', 'AngularJS Example: ' + name) +
|
||||
hiddenField('css', '</style> <!-- Ugly Hack due to jsFiddle issue: http://goo.gl/BUfGZ --> \n' +
|
||||
stylesheet +
|
||||
script.angular +
|
||||
(attr.resource ? script.resource : '') +
|
||||
'<style>\n' +
|
||||
fields.css) +
|
||||
hiddenField('html', fields.html) +
|
||||
hiddenField('js', fields.js) +
|
||||
'<button class="btn btn-primary"><i class="icon-white icon-pencil"></i> Edit Me</button>' +
|
||||
'</form>');
|
||||
|
||||
function hiddenField(name, value) {
|
||||
return '<input type="hidden" name="' + name + '" value="' + escape(value) + '">';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
directive.ngSetText = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
restrict: 'CA',
|
||||
priority: 10,
|
||||
compile: function(element, attr) {
|
||||
setHtmlIe8SafeWay(element, escape(getEmbeddedTemplate(attr.ngSetText)));
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
directive.ngHtmlWrap = ['reindentCode', 'templateMerge', function(reindentCode, templateMerge) {
|
||||
return {
|
||||
compile: function(element, attr) {
|
||||
var properties = {
|
||||
head: '',
|
||||
module: '',
|
||||
body: element.text()
|
||||
},
|
||||
html = "<!doctype html>\n<html ng-app{{module}}>\n <head>\n{{head:4}} </head>\n <body>\n{{body:4}} </body>\n</html>";
|
||||
|
||||
angular.forEach((attr.ngHtmlWrap || '').split(' '), function(dep) {
|
||||
if (!dep) return;
|
||||
dep = DEPENDENCIES[dep] || dep;
|
||||
|
||||
var ext = dep.split(/\./).pop();
|
||||
|
||||
if (ext == 'css') {
|
||||
properties.head += '<link rel="stylesheet" href="' + dep + '" type="text/css">\n';
|
||||
} else if(ext == 'js') {
|
||||
properties.head += '<script src="' + dep + '"></script>\n';
|
||||
} else {
|
||||
properties.module = '="' + dep + '"';
|
||||
}
|
||||
});
|
||||
|
||||
setHtmlIe8SafeWay(element, escape(templateMerge(html, properties)));
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
directive.ngSetHtml = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
restrict: 'CA',
|
||||
priority: 10,
|
||||
compile: function(element, attr) {
|
||||
setHtmlIe8SafeWay(element, getEmbeddedTemplate(attr.ngSetHtml));
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
directive.ngEvalJavascript = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
|
||||
return {
|
||||
compile: function (element, attr) {
|
||||
var fileNames = attr.ngEvalJavascript.split(' ');
|
||||
angular.forEach(fileNames, function(fileName) {
|
||||
var script = getEmbeddedTemplate(fileName);
|
||||
try {
|
||||
if (window.execScript) { // IE
|
||||
window.execScript(script || '""'); // IE complains when evaling empty string
|
||||
} else {
|
||||
window.eval(script + '//@ sourceURL=' + fileName);
|
||||
}
|
||||
} catch (e) {
|
||||
if (window.console) {
|
||||
window.console.log(script, '\n', e);
|
||||
} else {
|
||||
window.alert(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', '$animate',
|
||||
function($templateCache, $browser, docsRootScope, $location, $sniffer, $animate) {
|
||||
return {
|
||||
terminal: true,
|
||||
link: function(scope, element, attrs) {
|
||||
var modules = ['ngAnimate'],
|
||||
embedRootScope,
|
||||
deregisterEmbedRootScope;
|
||||
|
||||
modules.push(['$provide', function($provide) {
|
||||
$provide.value('$templateCache', $templateCache);
|
||||
$provide.value('$anchorScroll', angular.noop);
|
||||
$provide.value('$browser', $browser);
|
||||
$provide.value('$sniffer', $sniffer);
|
||||
$provide.value('$animate', $animate);
|
||||
$provide.provider('$location', function() {
|
||||
this.$get = ['$rootScope', function($rootScope) {
|
||||
docsRootScope.$on('$locationChangeSuccess', function(event, oldUrl, newUrl) {
|
||||
$rootScope.$broadcast('$locationChangeSuccess', oldUrl, newUrl);
|
||||
});
|
||||
return $location;
|
||||
}];
|
||||
this.html5Mode = angular.noop;
|
||||
});
|
||||
|
||||
$provide.decorator('$rootScope', ['$delegate', function($delegate) {
|
||||
embedRootScope = $delegate;
|
||||
|
||||
// Since we are teleporting the $animate service, which relies on the $$postDigestQueue
|
||||
// we need the embedded scope to use the same $$postDigestQueue as the outer scope
|
||||
embedRootScope.$$postDigestQueue = docsRootScope.$$postDigestQueue;
|
||||
|
||||
deregisterEmbedRootScope = docsRootScope.$watch(function embedRootScopeDigestWatch() {
|
||||
embedRootScope.$digest();
|
||||
});
|
||||
|
||||
return embedRootScope;
|
||||
}]);
|
||||
}]);
|
||||
if (attrs.ngEmbedApp) modules.push(attrs.ngEmbedApp);
|
||||
|
||||
element.on('click', function(event) {
|
||||
if (event.target.attributes.getNamedItem('ng-click')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
element.on('$destroy', function() {
|
||||
deregisterEmbedRootScope();
|
||||
embedRootScope.$destroy();
|
||||
});
|
||||
|
||||
element.data('$injector', null);
|
||||
angular.bootstrap(element, modules);
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
service.reindentCode = function() {
|
||||
return function (text, spaces) {
|
||||
if (!text) return text;
|
||||
var lines = text.split(/\r?\n/);
|
||||
var prefix = ' '.substr(0, spaces || 0);
|
||||
var i;
|
||||
|
||||
// remove any leading blank lines
|
||||
while (lines.length && lines[0].match(/^\s*$/)) lines.shift();
|
||||
// remove any trailing blank lines
|
||||
while (lines.length && lines[lines.length - 1].match(/^\s*$/)) lines.pop();
|
||||
var minIndent = 999;
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
var line = lines[0];
|
||||
var reindentCode = line.match(/^\s*/)[0];
|
||||
if (reindentCode !== line && reindentCode.length < minIndent) {
|
||||
minIndent = reindentCode.length;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < lines.length; i++) {
|
||||
lines[i] = prefix + lines[i].substring(minIndent);
|
||||
}
|
||||
lines.push('');
|
||||
return lines.join('\n');
|
||||
}
|
||||
};
|
||||
|
||||
service.templateMerge = ['reindentCode', function(indentCode) {
|
||||
return function(template, properties) {
|
||||
return template.replace(/\{\{(\w+)(?:\:(\d+))?\}\}/g, function(_, key, indent) {
|
||||
var value = properties[key];
|
||||
|
||||
if (indent) {
|
||||
value = indentCode(value, indent);
|
||||
}
|
||||
|
||||
return value == undefined ? '' : value;
|
||||
});
|
||||
};
|
||||
}];
|
||||
|
||||
service.getEmbeddedTemplate = ['reindentCode', function(reindentCode) {
|
||||
return function (id) {
|
||||
var element = document.getElementById(id);
|
||||
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return reindentCode(angular.element(element).html(), 0);
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
angular.module('bootstrapPrettify', []).directive(directive).factory(service);
|
||||
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
/* jshint browser: true */
|
||||
/* global importScripts, onmessage: true, postMessage, lunr */
|
||||
|
||||
// Load up the lunr library
|
||||
importScripts('../components/lunr.js-0.4.2/lunr.min.js');
|
||||
|
||||
// Create the lunr index - the docs should be an array of object, each object containing
|
||||
// the path and search terms for a page
|
||||
var index = lunr(function() {
|
||||
this.ref('path');
|
||||
this.field('titleWords', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
|
||||
// Retrieve the searchData which contains the information about each page to be indexed
|
||||
var searchData = {};
|
||||
var searchDataRequest = new XMLHttpRequest();
|
||||
searchDataRequest.onload = function() {
|
||||
|
||||
// Store the pages data to be used in mapping query results back to pages
|
||||
searchData = JSON.parse(this.responseText);
|
||||
// Add search terms from each page to the search index
|
||||
searchData.forEach(function(page) {
|
||||
index.add(page);
|
||||
});
|
||||
postMessage({ e: 'index-ready' });
|
||||
};
|
||||
searchDataRequest.open('GET', 'search-data.json');
|
||||
searchDataRequest.send();
|
||||
|
||||
// The worker receives a message everytime the web app wants to query the index
|
||||
onmessage = function(oEvent) {
|
||||
var q = oEvent.data.q;
|
||||
var hits = index.search(q);
|
||||
var results = [];
|
||||
// Only return the array of paths to pages
|
||||
hits.forEach(function(hit) {
|
||||
results.push(hit.ref);
|
||||
});
|
||||
// The results of the query are sent back to the web app via a new message
|
||||
postMessage({ e: 'query-ready', q: q, d: results });
|
||||
};
|
||||
@@ -76,11 +76,11 @@ describe('docs.angularjs.org', function () {
|
||||
expect(element(by.css('.minerr-errmsg')).getText()).toEqual("Argument 'Missing' is not a function, got undefined");
|
||||
});
|
||||
|
||||
|
||||
it("should display an error if the page does not exist", function() {
|
||||
browser.get('index-debug.html#!/api/does/not/exist');
|
||||
expect(element(by.css('h1')).getText()).toBe('Oops!');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
+3
-3
@@ -6,6 +6,7 @@ angular.module('docsApp', [
|
||||
'DocsController',
|
||||
'versionsData',
|
||||
'pagesData',
|
||||
'navData',
|
||||
'directives',
|
||||
'errors',
|
||||
'examples',
|
||||
@@ -13,11 +14,10 @@ angular.module('docsApp', [
|
||||
'tutorials',
|
||||
'versions',
|
||||
'bootstrap',
|
||||
'bootstrapPrettify',
|
||||
'ui.bootstrap.dropdown'
|
||||
])
|
||||
|
||||
|
||||
.config(function($locationProvider) {
|
||||
.config(['$locationProvider', function($locationProvider) {
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
});
|
||||
}]);
|
||||
|
||||
+9
-74
@@ -6,31 +6,10 @@ angular.module('DocsController', [])
|
||||
function($scope, $rootScope, $location, $window, $cookies, openPlunkr,
|
||||
NG_PAGES, NG_NAVIGATION, NG_VERSION) {
|
||||
|
||||
|
||||
$scope.openPlunkr = openPlunkr;
|
||||
|
||||
$scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version;
|
||||
|
||||
$scope.fold = function(url) {
|
||||
if(url) {
|
||||
$scope.docs_fold = '/notes/' + url;
|
||||
if(/\/build/.test($window.location.href)) {
|
||||
$scope.docs_fold = '/build/docs' + $scope.docs_fold;
|
||||
}
|
||||
window.scrollTo(0,0);
|
||||
}
|
||||
else {
|
||||
$scope.docs_fold = null;
|
||||
}
|
||||
};
|
||||
var OFFLINE_COOKIE_NAME = 'ng-offline',
|
||||
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
|
||||
|
||||
|
||||
/**********************************
|
||||
Publish methods
|
||||
***********************************/
|
||||
|
||||
$scope.navClass = function(navItem) {
|
||||
return {
|
||||
active: navItem.href && this.currentPage && this.currentPage.path,
|
||||
@@ -38,55 +17,22 @@ angular.module('DocsController', [])
|
||||
};
|
||||
};
|
||||
|
||||
$scope.afterPartialLoaded = function() {
|
||||
|
||||
|
||||
$scope.$on('$includeContentLoaded', function() {
|
||||
var pagePath = $scope.currentPage ? $scope.currentPage.path : $location.path();
|
||||
$window._gaq.push(['_trackPageview', pagePath]);
|
||||
};
|
||||
|
||||
/** stores a cookie that is used by apache to decide which manifest ot send */
|
||||
$scope.enableOffline = function() {
|
||||
//The cookie will be good for one year!
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime()+(365*24*60*60*1000));
|
||||
var expires = "; expires="+date.toGMTString();
|
||||
var value = angular.version.full;
|
||||
document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path;
|
||||
|
||||
//force the page to reload so server can serve new manifest file
|
||||
window.location.reload(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**********************************
|
||||
Watches
|
||||
***********************************/
|
||||
|
||||
});
|
||||
|
||||
$scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) {
|
||||
|
||||
var currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
if ( !currentPage && path.charAt(0)==='/' ) {
|
||||
// Strip off leading slash
|
||||
path = path.substr(1);
|
||||
}
|
||||
|
||||
currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
if ( !currentPage && path.charAt(path.length-1) === '/' && path.length > 1 ) {
|
||||
// Strip off trailing slash
|
||||
path = path.substr(0, path.length-1);
|
||||
}
|
||||
|
||||
currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
if ( !currentPage && /\/index$/.test(path) ) {
|
||||
// Strip off index from the end
|
||||
path = path.substr(0, path.length - 6);
|
||||
}
|
||||
path = path.replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
|
||||
|
||||
currentPage = $scope.currentPage = NG_PAGES[path];
|
||||
|
||||
if ( currentPage ) {
|
||||
$scope.currentArea = currentPage && NG_NAVIGATION[currentPage.area];
|
||||
$scope.partialPath = 'partials/' + path + '.html';
|
||||
$scope.currentArea = NG_NAVIGATION[currentPage.area];
|
||||
var pathParts = currentPage.path.split('/');
|
||||
var breadcrumb = $scope.breadcrumb = [];
|
||||
var breadcrumbPath = '';
|
||||
@@ -98,6 +44,7 @@ angular.module('DocsController', [])
|
||||
} else {
|
||||
$scope.currentArea = NG_NAVIGATION['api'];
|
||||
$scope.breadcrumb = [];
|
||||
$scope.partialPath = 'Error404.html';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,24 +54,12 @@ angular.module('DocsController', [])
|
||||
|
||||
$scope.versionNumber = angular.version.full;
|
||||
$scope.version = angular.version.full + " " + angular.version.codeName;
|
||||
$scope.subpage = false;
|
||||
$scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
|
||||
$scope.futurePartialTitle = null;
|
||||
$scope.loading = 0;
|
||||
$scope.$cookies = $cookies;
|
||||
|
||||
$cookies.platformPreference = $cookies.platformPreference || 'gitUnix';
|
||||
|
||||
var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
|
||||
if (!$location.path() || INDEX_PATH.test($location.path())) {
|
||||
$location.path('/api').replace();
|
||||
}
|
||||
|
||||
// bind escape to hash reset callback
|
||||
angular.element(window).on('keydown', function(e) {
|
||||
if (e.keyCode === 27) {
|
||||
$scope.$apply(function() {
|
||||
$scope.subpage = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
angular.module('docsApp.navigationService', [])
|
||||
|
||||
.factory('navigationService', function($window) {
|
||||
var service = {
|
||||
currentPage: null,
|
||||
currentVersion: null,
|
||||
changePage: function(newPage) {
|
||||
|
||||
},
|
||||
changeVersion: function(newVersion) {
|
||||
|
||||
//TODO =========
|
||||
// var currentPagePath = '';
|
||||
|
||||
// // preserve URL path when switching between doc versions
|
||||
// if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) {
|
||||
// currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id;
|
||||
// }
|
||||
|
||||
// $window.location = version.url + currentPagePath;
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
+123
-64
@@ -10,22 +10,35 @@ angular.module('search', [])
|
||||
$scope.search = function(q) {
|
||||
var MIN_SEARCH_LENGTH = 2;
|
||||
if(q.length >= MIN_SEARCH_LENGTH) {
|
||||
var results = docsSearch(q);
|
||||
var totalAreas = 0;
|
||||
for(var i in results) {
|
||||
++totalAreas;
|
||||
}
|
||||
if(totalAreas > 0) {
|
||||
$scope.colClassName = 'cols-' + totalAreas;
|
||||
}
|
||||
$scope.hasResults = totalAreas > 0;
|
||||
$scope.results = results;
|
||||
docsSearch(q).then(function(hits) {
|
||||
var results = {};
|
||||
angular.forEach(hits, function(hit) {
|
||||
var area = hit.area;
|
||||
|
||||
var limit = (area == 'api') ? 40 : 14;
|
||||
results[area] = results[area] || [];
|
||||
if(results[area].length < limit) {
|
||||
results[area].push(hit);
|
||||
}
|
||||
});
|
||||
|
||||
var totalAreas = 0;
|
||||
for(var i in results) {
|
||||
++totalAreas;
|
||||
}
|
||||
if(totalAreas > 0) {
|
||||
$scope.colClassName = 'cols-' + totalAreas;
|
||||
}
|
||||
$scope.hasResults = totalAreas > 0;
|
||||
$scope.results = results;
|
||||
});
|
||||
}
|
||||
else {
|
||||
clearResults();
|
||||
}
|
||||
if(!$scope.$$phase) $scope.$apply();
|
||||
};
|
||||
|
||||
$scope.submit = function() {
|
||||
var result;
|
||||
for(var i in $scope.results) {
|
||||
@@ -39,78 +52,124 @@ angular.module('search', [])
|
||||
$scope.hideResults();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.hideResults = function() {
|
||||
clearResults();
|
||||
$scope.q = '';
|
||||
};
|
||||
}])
|
||||
|
||||
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) {
|
||||
$scope.results = docsSearch($location.path().split(/[\/\.:]/).pop());
|
||||
|
||||
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch',
|
||||
function($scope, $location, docsSearch) {
|
||||
docsSearch($location.path().split(/[\/\.:]/).pop()).then(function(results) {
|
||||
$scope.results = {};
|
||||
angular.forEach(results, function(result) {
|
||||
var area = $scope.results[result.area] || [];
|
||||
area.push(result);
|
||||
$scope.results[result.area] = area;
|
||||
});
|
||||
});
|
||||
}])
|
||||
|
||||
.factory('lunrSearch', function() {
|
||||
return function(properties) {
|
||||
if (window.RUNNING_IN_NG_TEST_RUNNER) return null;
|
||||
|
||||
var engine = lunr(properties);
|
||||
return {
|
||||
store : function(values) {
|
||||
engine.add(values);
|
||||
},
|
||||
search : function(q) {
|
||||
return engine.search(q);
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.provider('docsSearch', function() {
|
||||
|
||||
.factory('docsSearch', ['$rootScope','lunrSearch', 'NG_PAGES',
|
||||
function($rootScope, lunrSearch, NG_PAGES) {
|
||||
if (window.RUNNING_IN_NG_TEST_RUNNER) {
|
||||
return null;
|
||||
}
|
||||
// This version of the service builds the index in the current thread,
|
||||
// which blocks rendering and other browser activities.
|
||||
// It should only be used where the browser does not support WebWorkers
|
||||
function localSearchFactory($http, $timeout, NG_PAGES) {
|
||||
|
||||
var index = lunrSearch(function() {
|
||||
this.ref('id');
|
||||
this.field('title', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
console.log('Using Local Search Index');
|
||||
|
||||
angular.forEach(NG_PAGES, function(page, key) {
|
||||
if(page.searchTerms) {
|
||||
index.store({
|
||||
id : key,
|
||||
title : page.searchTerms.titleWords,
|
||||
keywords : page.searchTerms.keywords,
|
||||
members : page.searchTerms.members
|
||||
// Create the lunr index
|
||||
var index = lunr(function() {
|
||||
this.ref('path');
|
||||
this.field('titleWords', {boost: 50});
|
||||
this.field('members', { boost: 40});
|
||||
this.field('keywords', { boost : 20 });
|
||||
});
|
||||
|
||||
// Delay building the index by loading the data asynchronously
|
||||
var indexReadyPromise = $http.get('js/search-data.json').then(function(response) {
|
||||
var searchData = response.data;
|
||||
// Delay building the index for 500ms to allow the page to render
|
||||
return $timeout(function() {
|
||||
// load the page data into the index
|
||||
angular.forEach(searchData, function(page) {
|
||||
index.add(page);
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// The actual service is a function that takes a query string and
|
||||
// returns a promise to the search results
|
||||
// (In this case we just resolve the promise immediately as it is not
|
||||
// inherently an async process)
|
||||
return function(q) {
|
||||
return indexReadyPromise.then(function() {
|
||||
var hits = index.search(q);
|
||||
var results = [];
|
||||
angular.forEach(hits, function(hit) {
|
||||
results.push(NG_PAGES[hit.ref]);
|
||||
});
|
||||
return results;
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
localSearchFactory.$inject = ['$http', '$timeout', 'NG_PAGES'];
|
||||
|
||||
return function(q) {
|
||||
var results = {
|
||||
api : [],
|
||||
tutorial : [],
|
||||
guide : [],
|
||||
error : [],
|
||||
misc : []
|
||||
// This version of the service builds the index in a WebWorker,
|
||||
// which does not block rendering and other browser activities.
|
||||
// It should only be used where the browser does support WebWorkers
|
||||
function webWorkerSearchFactory($q, $rootScope, NG_PAGES) {
|
||||
|
||||
console.log('Using WebWorker Search Index')
|
||||
|
||||
var searchIndex = $q.defer();
|
||||
var results;
|
||||
|
||||
var worker = new Worker('js/search-worker.js');
|
||||
|
||||
// The worker will send us a message in two situations:
|
||||
// - when the index has been built, ready to run a query
|
||||
// - when it has completed a search query and the results are available
|
||||
worker.onmessage = function(oEvent) {
|
||||
$rootScope.$apply(function() {
|
||||
|
||||
switch(oEvent.data.e) {
|
||||
case 'index-ready':
|
||||
searchIndex.resolve();
|
||||
break;
|
||||
case 'query-ready':
|
||||
var pages = oEvent.data.d.map(function(path) {
|
||||
return NG_PAGES[path];
|
||||
});
|
||||
results.resolve(pages);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
angular.forEach(index.search(q), function(result) {
|
||||
var key = result.ref;
|
||||
var item = NG_PAGES[key];
|
||||
var area = item.area;
|
||||
item.path = key;
|
||||
|
||||
var limit = area == 'api' ? 40 : 14;
|
||||
if(results[area].length < limit) {
|
||||
results[area].push(item);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
// The actual service is a function that takes a query string and
|
||||
// returns a promise to the search results
|
||||
return function(q) {
|
||||
|
||||
// We only run the query once the index is ready
|
||||
return searchIndex.promise.then(function() {
|
||||
|
||||
results = $q.defer();
|
||||
worker.postMessage({ q: q });
|
||||
return results.promise;
|
||||
});
|
||||
};
|
||||
}
|
||||
webWorkerSearchFactory.$inject = ['$q', '$rootScope', 'NG_PAGES'];
|
||||
|
||||
return {
|
||||
$get: window.Worker ? webWorkerSearchFactory : localSearchFactory
|
||||
};
|
||||
}])
|
||||
})
|
||||
|
||||
.directive('focused', function($timeout) {
|
||||
return function(scope, element, attrs) {
|
||||
|
||||
+15
-16
@@ -1,6 +1,6 @@
|
||||
angular.module('tutorials', [])
|
||||
|
||||
.directive('docTutorialNav', function(templateMerge) {
|
||||
.directive('docTutorialNav', function() {
|
||||
var pages = [
|
||||
'',
|
||||
'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
|
||||
@@ -8,23 +8,22 @@ angular.module('tutorials', [])
|
||||
'step_10', 'step_11', 'step_12', 'the_end'
|
||||
];
|
||||
return {
|
||||
compile: function(element, attrs) {
|
||||
var seq = 1 * attrs.docTutorialNav,
|
||||
props = {
|
||||
seq: seq,
|
||||
prev: pages[seq],
|
||||
next: pages[2 + seq],
|
||||
diffLo: seq ? (seq - 1): '0~1',
|
||||
diffHi: seq
|
||||
};
|
||||
scope: {},
|
||||
template:
|
||||
'<a ng-href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a ng-href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a ng-href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>',
|
||||
link: function(scope, element, attrs) {
|
||||
var seq = 1 * attrs.docTutorialNav;
|
||||
scope.seq = seq;
|
||||
scope.prev = pages[seq];
|
||||
scope.next = pages[2 + seq];
|
||||
scope.diffLo = seq ? (seq - 1): '0~1';
|
||||
scope.diffHi = seq;
|
||||
|
||||
element.addClass('btn-group');
|
||||
element.addClass('tutorial-nav');
|
||||
element.append(templateMerge(
|
||||
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>', props));
|
||||
}
|
||||
};
|
||||
})
|
||||
@@ -47,4 +46,4 @@ angular.module('tutorials', [])
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}">GitHub</a>\n' +
|
||||
'</p>'
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
angular.module('versions', [])
|
||||
|
||||
.controller('DocsVersionsCtrl', ['$scope', '$location', '$window', 'NG_VERSIONS', function($scope, $location, $window, NG_VERSIONS) {
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("DocsController", function() {
|
||||
it("should update the Google Analytics with currentPage path if currentPage exists", inject(function($window) {
|
||||
$window._gaq = [];
|
||||
$scope.currentPage = { path: 'a/b/c' };
|
||||
$scope.afterPartialLoaded();
|
||||
$scope.$broadcast('$includeContentLoaded');
|
||||
expect($window._gaq.pop()).toEqual(['_trackPageview', 'a/b/c']);
|
||||
}));
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("DocsController", function() {
|
||||
it("should update the Google Analytics with $location.path if currentPage is missing", inject(function($window, $location) {
|
||||
$window._gaq = [];
|
||||
spyOn($location, 'path').andReturn('x/y/z');
|
||||
$scope.afterPartialLoaded();
|
||||
$scope.$broadcast('$includeContentLoaded');
|
||||
expect($window._gaq.pop()).toEqual(['_trackPageview', 'x/y/z']);
|
||||
}));
|
||||
});
|
||||
|
||||
+18
-5
@@ -92,10 +92,7 @@ module.exports = new Package('angularjs', [
|
||||
}
|
||||
return docPath;
|
||||
},
|
||||
getOutputPath: function(doc) {
|
||||
return 'partials/' + doc.path +
|
||||
( doc.fileInfo.baseName === 'index' ? '/index.html' : '.html');
|
||||
}
|
||||
outputPathTemplate: 'partials/${path}.html'
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
@@ -106,10 +103,20 @@ module.exports = new Package('angularjs', [
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['indexPage'],
|
||||
getPath: function() {},
|
||||
pathTemplate: '.',
|
||||
outputPathTemplate: '${id}.html'
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['module' ],
|
||||
pathTemplate: '${area}/${name}',
|
||||
outputPathTemplate: 'partials/${area}/${name}.html'
|
||||
});
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['componentGroup' ],
|
||||
pathTemplate: '${area}/${moduleName}/${groupType}',
|
||||
outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html'
|
||||
});
|
||||
|
||||
computeIdsProcessor.idTemplates.push({
|
||||
docTypes: ['overview', 'tutorial', 'e2e-test', 'indexPage'],
|
||||
@@ -124,6 +131,12 @@ module.exports = new Package('angularjs', [
|
||||
});
|
||||
})
|
||||
|
||||
.config(function(checkAnchorLinksProcessor) {
|
||||
checkAnchorLinksProcessor.base = '/';
|
||||
// We are only interested in docs that have an area (i.e. they are pages)
|
||||
checkAnchorLinksProcessor.checkDoc = function(doc) { return doc.area; };
|
||||
})
|
||||
|
||||
|
||||
.config(function(
|
||||
generateIndexPagesProcessor,
|
||||
|
||||
@@ -147,24 +147,18 @@ module.exports = function generatePagesDataProcessor(log) {
|
||||
};
|
||||
|
||||
return {
|
||||
$runAfter: ['paths-computed'],
|
||||
$runAfter: ['paths-computed', 'generateKeywordsProcessor'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process: function(docs) {
|
||||
|
||||
_(docs)
|
||||
.filter(function(doc) { return doc.area === 'api' && doc.docType === 'module'; })
|
||||
.forEach(function(doc) { if ( !doc.path ) {
|
||||
log.warn('Missing path property for ', doc.id);
|
||||
}})
|
||||
.map(function(doc) { return _.pick(doc, ['id', 'module', 'docType', 'area']); })
|
||||
.tap(function(docs) {
|
||||
log.debug(docs);
|
||||
// We are only interested in docs that are in an area
|
||||
var pages = _.filter(docs, function(doc) {
|
||||
return doc.area;
|
||||
});
|
||||
|
||||
|
||||
// We are only interested in docs that are in an area and are not landing pages
|
||||
var navPages = _.filter(docs, function(page) {
|
||||
return page.area && page.docType != 'componentGroup';
|
||||
// We are only interested in pages that are not landing pages
|
||||
var navPages = _.filter(pages, function(page) {
|
||||
return page.docType != 'componentGroup';
|
||||
});
|
||||
|
||||
// Generate an object collection of pages that is grouped by area e.g.
|
||||
@@ -198,28 +192,48 @@ module.exports = function generatePagesDataProcessor(log) {
|
||||
area.navGroups = navGroupMapper(pages, area);
|
||||
});
|
||||
|
||||
docs.push({
|
||||
docType: 'nav-data',
|
||||
id: 'nav-data',
|
||||
template: 'nav-data.template.js',
|
||||
outputPath: 'js/nav-data.js',
|
||||
areas: areas
|
||||
});
|
||||
|
||||
|
||||
|
||||
var searchData = _(pages)
|
||||
.filter(function(page) {
|
||||
return page.searchTerms;
|
||||
})
|
||||
.map(function(page) {
|
||||
return _.extend({ path: page.path }, page.searchTerms);
|
||||
})
|
||||
.value();
|
||||
|
||||
docs.push({
|
||||
docType: 'json-doc',
|
||||
id: 'search-data-json',
|
||||
template: 'json-doc.template.json',
|
||||
outputPath: 'js/search-data.json',
|
||||
data: searchData
|
||||
});
|
||||
|
||||
// Extract a list of basic page information for mapping paths to partials and for client side searching
|
||||
var pages = _(docs)
|
||||
var pageData = _(docs)
|
||||
.map(function(doc) {
|
||||
var page = _.pick(doc, [
|
||||
'docType', 'id', 'name', 'area', 'outputPath', 'path', 'searchTerms'
|
||||
]);
|
||||
return page;
|
||||
return _.pick(doc, ['name', 'area', 'path']);
|
||||
})
|
||||
.indexBy('path')
|
||||
.value();
|
||||
|
||||
var docData = {
|
||||
docs.push({
|
||||
docType: 'pages-data',
|
||||
id: 'pages-data',
|
||||
template: 'pages-data.template.js',
|
||||
outputPath: 'js/pages-data.js',
|
||||
|
||||
areas: areas,
|
||||
pages: pages
|
||||
};
|
||||
|
||||
docs.push(docData);
|
||||
pages: pageData
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,13 +19,13 @@ module.exports = function debugDeployment(getVersion) {
|
||||
'../angular-animate.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/nav-data.js',
|
||||
'js/docs.js'
|
||||
],
|
||||
stylesheets: [
|
||||
|
||||
@@ -18,15 +18,15 @@ module.exports = function defaultDeployment(getVersion) {
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/docs.js'
|
||||
'js/nav-data.js',
|
||||
'js/docs.min.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
|
||||
+4
-4
@@ -22,15 +22,15 @@ module.exports = function jqueryDeployment(getVersion) {
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/docs.js'
|
||||
'js/nav-data.js',
|
||||
'js/docs.min.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
|
||||
@@ -21,15 +21,15 @@ module.exports = function productionDeployment(getVersion) {
|
||||
cdnUrl + '/angular-touch.min.js',
|
||||
cdnUrl + '/angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/bootstrap-prettify.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
|
||||
'js/versions-data.js',
|
||||
'js/pages-data.js',
|
||||
'js/docs.js'
|
||||
'js/nav-data.js',
|
||||
'js/docs.min.js'
|
||||
],
|
||||
stylesheets: [
|
||||
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
var gruntUtils = require('../../../lib/grunt/utils');
|
||||
var versionInfo = require('../../../lib/versions/version-info');
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app="docsApp" ng-controller="DocsController">
|
||||
<html lang="en" ng-app="docsApp" ng-strict-di ng-controller="DocsController">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="utf-8">
|
||||
@@ -56,15 +56,6 @@
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// force page reload when new update is available
|
||||
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
window.applicationCache.swapCache();
|
||||
window.location.reload();
|
||||
}
|
||||
}, false);
|
||||
|
||||
// GA asynchronous tracker
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-8594346-3']);
|
||||
@@ -85,7 +76,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9 header-branding">
|
||||
<a class="brand navbar-brand" href="http://angularjs.org">
|
||||
<img class="logo" src="img/angularjs-for-header-only.svg">
|
||||
<img width="117" height="30" class="logo" ng-src="img/angularjs-for-header-only.svg">
|
||||
</a>
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="divider-vertical"></li>
|
||||
@@ -155,9 +146,6 @@
|
||||
</div>
|
||||
<div class="search-results-container" ng-show="hasResults">
|
||||
<div class="container">
|
||||
<a href="" ng-click="hideResults()" class="search-close">
|
||||
<span class="glyphicon glyphicon-remove search-close-icon"></span> Close
|
||||
</a>
|
||||
<div class="search-results-frame">
|
||||
<div ng-repeat="(key, value) in results" class="search-results-group" ng-class="colClassName + ' col-group-' + key">
|
||||
<h4 class="search-results-group-heading">{{ key }}</h4>
|
||||
@@ -168,6 +156,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="" ng-click="hideResults()" class="search-close">
|
||||
<span class="glyphicon glyphicon-remove search-close-icon"></span> Close
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -219,7 +210,7 @@
|
||||
</div>
|
||||
<div class="grid-right">
|
||||
<div id="loading" ng-show="loading">Loading...</div>
|
||||
<div ng-hide="loading" ng-include="currentPage.outputPath || 'Error404.html'" onload="afterPartialLoaded()" autoscroll></div>
|
||||
<div ng-hide="loading" ng-include="partialPath" autoscroll></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{$ doc.data | json $}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Meta data used by the AngularJS docs app
|
||||
angular.module('navData', [])
|
||||
.value('NG_NAVIGATION', {$ doc.areas | json $});
|
||||
@@ -1,4 +1,3 @@
|
||||
// Meta data used by the AngularJS docs app
|
||||
angular.module('pagesData', [])
|
||||
.value('NG_PAGES', {$ doc.pages | json $})
|
||||
.value('NG_NAVIGATION', {$ doc.areas | json $});
|
||||
.value('NG_PAGES', {$ doc.pages | json $});
|
||||
|
||||
@@ -12,10 +12,10 @@ $controller(MyController);
|
||||
$controller(MyController, {scope: newScope});
|
||||
```
|
||||
|
||||
To fix the example above please provide a scope to the $controller call:
|
||||
To fix the example above please provide a scope (using the `$scope` property in the locals object) to the $controller call:
|
||||
|
||||
```
|
||||
$controller(MyController, {$scope, newScope});
|
||||
$controller(MyController, {$scope: newScope});
|
||||
```
|
||||
|
||||
Please consult the {@link ng.$controller $controller} service api docs to learn more.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $httpBackend:noxhr
|
||||
@fullName Unsupported XHR
|
||||
@description
|
||||
|
||||
This error occurs in browsers that do not support XmlHttpRequest. AngularJS
|
||||
supports Safari, Chrome, Firefox, Opera, IE8 and higher, and mobile browsers
|
||||
(Android, Chrome Mobile, iOS Safari). To avoid this error, use an officially
|
||||
supported browser.
|
||||
|
||||
@@ -7,5 +7,5 @@ This error occurs when a module fails to load due to some exception. The error
|
||||
message above should provide additional context.
|
||||
|
||||
In AngularJS `1.2.0` and later, `ngRoute` has been moved to its own module.
|
||||
If you are getting this error after upgrading to `1.2.x`, be sure that you've
|
||||
If you are getting this error after upgrading to `1.2.x` or later, be sure that you've
|
||||
installed {@link ngRoute `ngRoute`}.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
@ngdoc error
|
||||
@name $injector:undef
|
||||
@fullName Undefined Value
|
||||
@description
|
||||
|
||||
This error results from registering a factory which does not return a value (or whose return value is undefined).
|
||||
|
||||
The following is an example of a factory which will throw this error upon injection:
|
||||
|
||||
```js
|
||||
angular.module("badModule", []).
|
||||
factory("badFactory", function() {
|
||||
doLotsOfThings();
|
||||
butDontReturnAValue();
|
||||
});
|
||||
```
|
||||
|
||||
In order to prevent the error, return a value of some sort, such as an object which exposes an API for working
|
||||
with the injected object.
|
||||
|
||||
```js
|
||||
angular.module("goodModule", []).
|
||||
factory("goodFactory", function() {
|
||||
doLotsOfThings();
|
||||
butDontReturnAValue();
|
||||
|
||||
return {
|
||||
doTheThing: function methodThatDoesAThing() {
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
@@ -54,4 +54,18 @@ angular.module('myModule')
|
||||
.directive('myDirective', ['myCoolService', function (myCoolService) {
|
||||
// This directive definition does not throw unknown provider.
|
||||
}]);
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
Attempting to inject one controller into another will also throw an `Unknown provider` error:
|
||||
|
||||
```
|
||||
angular.module('myModule', [])
|
||||
.controller('MyFirstController', function() { /* ... */ });
|
||||
.controller('MySecondController', ['MyFirstController', function(MyFirstController) {
|
||||
// This controller throws an unknown provider error because
|
||||
// MyFirstController cannot be injected.
|
||||
}]);
|
||||
```
|
||||
|
||||
Use the `$controller` service if you want to instantiate controllers yourself.
|
||||
|
||||
@@ -4,7 +4,19 @@
|
||||
@description
|
||||
|
||||
If you configure {@link ng.$location `$location`} to use
|
||||
{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [`<base href="">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag.
|
||||
{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [`<base href="">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag or configure
|
||||
`$locationProvider` to not require a base tag by passing a definition object with
|
||||
`requireBase:false` to `$locationProvider.html5Mode()`:
|
||||
|
||||
```javascript
|
||||
$locationProvider.html5Mode({
|
||||
enabled: true,
|
||||
requireBase: false
|
||||
});
|
||||
```
|
||||
|
||||
Note that removing the requirement for a <base> tag will have adverse side effects when resolving
|
||||
relative paths with `$location` in IE9.
|
||||
|
||||
The base URL is then used to resolve all relative URLs throughout the application regardless of the
|
||||
entry point into the app.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@ngdoc error
|
||||
@name $location:nostate
|
||||
@fullName History API state support is available only in HTML5 mode and only in browsers supporting HTML5 History API
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ng.$location#state $location.state} method is used when {@link ng.$locationProvider#html5Mode $locationProvider.html5Mode} is not turned on or the browser used doesn't support the HTML5 History API (for example, IE9 or Android 2.3).
|
||||
|
||||
To avoid this error, either drop support for those older browsers or avoid using this method.
|
||||
@@ -0,0 +1,11 @@
|
||||
@ngdoc error
|
||||
@name ngModel:datefmt
|
||||
@fullName Model is not a date object
|
||||
@description
|
||||
|
||||
All date-related inputs like `<input type="date">` require the model to be a `Date` object.
|
||||
If the model is something else, this error will be thrown.
|
||||
Angular does not set validation errors on the `<input>` in this case
|
||||
as those errors are shown to the user, but the erroneous state was
|
||||
caused by incorrect application logic and not by the user.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
@ngdoc error
|
||||
@name ngOptions:trkslct
|
||||
@fullName Comprehension expression cannot contain both `select as` and `track by` expressions.
|
||||
@description
|
||||
|
||||
This error occurs when 'ngOptions' is passed a comprehension expression that contains both a
|
||||
`select as` expression and a `track by` expression. These two expressions are fundamentally
|
||||
incompatible.
|
||||
|
||||
* Example of bad expression: `<select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">`
|
||||
`values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}]`,
|
||||
`$scope.selected = {name: 'aSubItem'};`
|
||||
* track by is always applied to `value`, with purpose to preserve the selection,
|
||||
(to `item` in this case)
|
||||
* To calculate whether an item is selected, `ngOptions` does the following:
|
||||
1. apply `track by` to the values in the array:
|
||||
In the example: [1,2]
|
||||
2. apply `track by` to the already selected value in `ngModel`:
|
||||
In the example: this is not possible, as `track by` refers to `item.id`, but the selected
|
||||
value from `ngModel` is `{name: aSubItem}`.
|
||||
|
||||
Here's an example of how to make this example work by using `track by` without `select as`:
|
||||
|
||||
```
|
||||
<select ng-model="selected" ng-options="item.label for item in values track by item.id">
|
||||
```
|
||||
|
||||
Note: This would store the whole `item` as the model to `scope.selected` instead of `item.subItem`.
|
||||
|
||||
For more information on valid expression syntax, see 'ngOptions' in {@link ng.directive:select select} directive docs.
|
||||
@@ -91,10 +91,11 @@ To configure the `$location` service, retrieve the
|
||||
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
|
||||
|
||||
|
||||
- **html5Mode(mode)**: {boolean}<br />
|
||||
`true` - see HTML5 mode<br />
|
||||
`false` - see Hashbang mode<br />
|
||||
default: `false`
|
||||
- **html5Mode(mode)**: {boolean|Object}<br />
|
||||
`true` or `enabled:true` - see HTML5 mode<br />
|
||||
`false` or `enabled:false` - see Hashbang mode<br />
|
||||
`requireBase:true` - see Relative links<br />
|
||||
default: `enabled:false`
|
||||
|
||||
- **hashPrefix(prefix)**: {string}<br />
|
||||
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
|
||||
@@ -164,7 +165,7 @@ encoded.
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
HTML5 [History API](http://www.w3.org/TR/html5/history.html). Applications use the same API in
|
||||
HTML5 [History API](http://www.w3.org/TR/html5/introduction.html#history-0). Applications use the same API in
|
||||
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
|
||||
facilitate the browser URL change and history management.
|
||||
|
||||
@@ -245,7 +246,7 @@ it('should show example', inject(
|
||||
## HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API, which allows for use of regular URL path and search segments,
|
||||
through the HTML5 history API. This allows for use of regular URL path and search segments,
|
||||
instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
|
||||
`$location` service will fall back to using the hashbang URLs automatically. This frees you from
|
||||
having to worry about whether the browser displaying your app supports the history API or not; the
|
||||
@@ -328,9 +329,11 @@ reload to the original link.
|
||||
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url base in
|
||||
the head of your main html file (`<base href="/my-base">`). With that, relative urls will
|
||||
always be resolved to this base url, event if the initial url of the document was different.
|
||||
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url
|
||||
base in the head of your main html file (`<base href="/my-base">`) unless `html5Mode.requireBase` is
|
||||
set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
|
||||
that, relative urls will always be resolved to this base url, event if the initial url of the
|
||||
document was different.
|
||||
|
||||
There is one exception: Links that only contain a hash fragment (e.g. `<a href="#target">`)
|
||||
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@ngdoc overview
|
||||
@name Accessibility
|
||||
@sortOrder 530
|
||||
@description
|
||||
|
||||
|
||||
# Accessibility with ngAria
|
||||
|
||||
You can use the `ngAria` module to have common ARIA attributes automatically applied when you
|
||||
use certain directives. To enable `ngAria`, just require the module into your application and
|
||||
the code will hook into your ng-show/ng-hide, input, textarea, button, select and
|
||||
ng-required directives and add the appropriate ARIA states and properties.
|
||||
|
||||
Currently, the following attributes are implemented:
|
||||
* aria-hidden
|
||||
* aria-checked
|
||||
* aria-disabled
|
||||
* aria-required
|
||||
* aria-invalid
|
||||
* aria-multiline
|
||||
* aria-valuenow
|
||||
* aria-valuemin
|
||||
* aria-valuemax
|
||||
* tabindex
|
||||
|
||||
You can disable individual attributes by using the `{@link ngAria.$ariaProvider#config config}` method.
|
||||
|
||||
###Example
|
||||
|
||||
```js
|
||||
angular.module('myApp', ['ngAria'])...
|
||||
```
|
||||
|
||||
Elements using `ng-model` with `required` or `ngRequired` directives will automatically have
|
||||
`aria-required` attributes with the proper corresponding values.
|
||||
|
||||
```html
|
||||
<material-input ng-model="val" required>
|
||||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```html
|
||||
<material-input ng-model="val" required aria-required="true">
|
||||
```
|
||||
|
||||
ngAria is just a starting point. You'll have to manually choose how to implement some
|
||||
accessibility features.
|
||||
|
||||
For instance, you may want to add `ng-keypress` bindings alongside `ng-click` to make keyboard
|
||||
navigation easier.
|
||||
|
||||
|
||||
## Additional Resources
|
||||
|
||||
Accessibility best practices that apply to web apps in general also apply to Angular.
|
||||
|
||||
* [WebAim](http://webaim.org/)
|
||||
* [Using WAI-ARIA in HTML](http://www.w3.org/TR/2014/WD-aria-in-html-20140626/)
|
||||
* [Apps For All: Coding Accessible Web Applications](https://shop.smashingmagazine.com/apps-for-all-coding-accessible-web-applications.html)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Animations
|
||||
|
||||
AngularJS 1.2 provides animation hooks for common directives such as `ngRepeat`, `ngSwitch`, and `ngView`, as well as custom directives
|
||||
AngularJS 1.3 provides animation hooks for common directives such as `ngRepeat`, `ngSwitch`, and `ngView`, as well as custom directives
|
||||
via the `$animate` service. These animation hooks are set in place to trigger animations during the life cycle of various directives and when
|
||||
triggered, will attempt to perform a CSS Transition, CSS Keyframe Animation or a JavaScript callback Animation (depending on if an animation is
|
||||
placed on the given directive). Animations can be placed using vanilla CSS by following the naming conventions set in place by AngularJS
|
||||
|
||||
@@ -87,7 +87,9 @@ Here is an example of manually initializing Angular:
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
Hello {{greetMe}}!
|
||||
<div ng-controller="MyController">
|
||||
Hello {{greetMe}}!
|
||||
</div>
|
||||
<script src="http://code.angularjs.org/snapshot/angular.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -25,8 +25,8 @@ browser how the window size needs to be divided in half so that the center is fo
|
||||
center needs to be aligned with the text's center. Simply add an `align="center"` attribute to any
|
||||
element to achieve the desired behavior. Such is the power of declarative language.
|
||||
|
||||
But the declarative language is also limited, since it does not allow you to teach the browser new
|
||||
syntax. For example there is no easy way to get the browser to align the text at 1/3 the position
|
||||
However, the declarative language is also limited, as it does not allow you to teach the browser new
|
||||
syntax. For example, there is no easy way to get the browser to align the text at 1/3 the position
|
||||
instead of 1/2. What is needed is a way to teach the browser new HTML syntax.
|
||||
|
||||
Angular comes pre-bundled with common directives which are useful for building any app. We also
|
||||
@@ -85,7 +85,9 @@ Here is a directive which makes any element draggable. Notice the `draggable` at
|
||||
position: 'relative',
|
||||
border: '1px solid red',
|
||||
backgroundColor: 'lightgrey',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
display: 'block',
|
||||
width: '65px'
|
||||
});
|
||||
element.on('mousedown', function(event) {
|
||||
// Prevent default dragging of selected content
|
||||
|
||||
@@ -67,8 +67,8 @@ element that adds extra behavior to the element. The {@link ng.directive:ngModel
|
||||
stores/updates the value of the input field into/from a variable.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Custom directives to access the DOM**: In Angular, the only place where an application touches the DOM is
|
||||
within directives. This is good as artifacts that access the DOM are hard to test.
|
||||
**Custom directives to access the DOM**: In Angular, the only place where an application should access the DOM is
|
||||
within directives. This is important because artifacts that access the DOM are hard to test.
|
||||
If you need to access the DOM directly you should write a custom directive for this. The
|
||||
{@link directive directives guide} explains how to do this.
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ At a high level, directives are markers on a DOM element (such as an attribute,
|
||||
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
|
||||
attach a specified behavior to that DOM element or even transform the DOM element and its children.
|
||||
|
||||
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngView`.
|
||||
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngClass`.
|
||||
Much like you create controllers and services, you can create your own directives for Angular to use.
|
||||
When Angular {@link guide/bootstrap bootstraps} your application, the
|
||||
{@link guide/compiler HTML compiler} traverses the DOM matching directives against the DOM elements.
|
||||
@@ -160,7 +160,7 @@ restrictions, you cannot simply write `cx="{{cx}}"`.
|
||||
With `ng-attr-cx` you can work around this problem.
|
||||
|
||||
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
|
||||
then during the binding will be applied to the corresponding unprefixed attribute. This allows
|
||||
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
|
||||
you to bind to attributes that would otherwise be eagerly processed by browsers
|
||||
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
|
||||
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
|
||||
@@ -282,9 +282,6 @@ using `templateUrl` instead:
|
||||
</file>
|
||||
</example>
|
||||
|
||||
Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead?
|
||||
If we simply put a `<my-customer>` element into the HTML, it doesn't work.
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** When you create a directive, it is restricted to attribute and elements only by default. In order to
|
||||
create directives that are triggered by class name, you need to use the `restrict` option.
|
||||
|
||||
@@ -143,7 +143,7 @@ means that it will be executed one or more times during the each `$digest` cycle
|
||||
Input: <input ng-model="greeting" type="text"><br>
|
||||
Decoration: <input ng-model="decoration.symbol" type="text"><br>
|
||||
No filter: {{greeting}}<br>
|
||||
Reverse: {{greeting | decorate}}<br>
|
||||
Decorated: {{greeting | decorate}}<br>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ only when the control loses focus (blur event).
|
||||
<input type="text" ng-model="user.data" /><br />
|
||||
</form>
|
||||
<pre>username = "{{user.name}}"</pre>
|
||||
<pre>userdata = "{{user.data}}"</pre>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
|
||||
@@ -70,7 +70,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/).
|
||||
|
||||
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
|
||||
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/), [angular-localization](http://doshprompt.github.io/angular-localization/)
|
||||
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
|
||||
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
|
||||
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/), [ngTagsInput](https://github.com/mbenford/ngTagsInput)
|
||||
@@ -90,7 +90,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
|
||||
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
|
||||
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
|
||||
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
|
||||
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
|
||||
@@ -105,6 +105,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
|
||||
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
|
||||
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
|
||||
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template
|
||||
language and lets you extend HTML's syntax to express your application's components clearly and
|
||||
succinctly. Angular's data binding and dependency injection eliminate much of the code you
|
||||
currently have to write. And it all happens within the browser, making it
|
||||
would otherwise have to write. And it all happens within the browser, making it
|
||||
an ideal partner with any server technology.
|
||||
|
||||
Angular is what HTML would have been had it been designed for applications. HTML is a great
|
||||
@@ -103,7 +103,7 @@ Angular frees you from the following pains:
|
||||
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
|
||||
of plumbing just to get a basic "Hello World" AJAX app working. With Angular you can bootstrap
|
||||
your app easily using services, which are auto-injected into your application in a
|
||||
[Guice](http://code.google.com/p/google-guice/)-like dependency-injection style. This allows you
|
||||
[Guice](https://github.com/google/guice)-like dependency-injection style. This allows you
|
||||
to get started developing features quickly. As a bonus, you get full control over the
|
||||
initialization process in automated tests.
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ this limitation, use a regular expression object as the value for the expression
|
||||
//after
|
||||
$scope.exp = /abc/i;
|
||||
- **Scope:** due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
Scope#$id is now of time number rather than string. Since the
|
||||
Scope#$id is now of type number rather than string. Since the
|
||||
id is primarily being used for debugging purposes this change should not affect
|
||||
anyone.
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
|
||||
@@ -338,6 +338,16 @@ We inject the $compile service and $rootScope before each jasmine test. The $com
|
||||
to render the aGreatEye directive. After rendering the directive we ensure that the directive has
|
||||
replaced the content and "lidless, wreathed in flame, 2 times" is present.
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Underscore notation**:
|
||||
|
||||
The use of the underscore notation (e.g.: `_$rootScope_`) is a convention wide spread in AngularJS
|
||||
community to keep the variable names clean in your tests. That's why the
|
||||
{@link $injector} strips out the leading and the trailing underscores when
|
||||
matching the parameters. The underscore rule applies ***only*** if the name starts **and** ends with
|
||||
exactly one underscore, otherwise no replacing happens.
|
||||
</div>
|
||||
|
||||
### Testing Transclusion Directives
|
||||
|
||||
Directives that use transclusion are treated specially by the compiler. Before their compile
|
||||
|
||||
@@ -22,7 +22,7 @@ So it's definitely not a plugin or some other native browser extension.
|
||||
|
||||
### Is AngularJS a templating system?
|
||||
|
||||
At the highest level, Angular does look like a just another templating system. But there is one
|
||||
At the highest level, Angular does look like just another templating system. But there is one
|
||||
important reason why the Angular templating system is different, that makes it very good fit for
|
||||
application development: bidirectional data binding. The template is compiled in the browser and
|
||||
the compilation step produces a live view. This means you, the developers, don't need to write
|
||||
@@ -39,7 +39,7 @@ for server-side communication.
|
||||
|
||||
AngularJS was designed to be compatible with other security measures like Content Security Policy
|
||||
(CSP), HTTPS (SSL/TLS) and server-side authentication and authorization that greatly reduce the
|
||||
possible attack vectors and we highly recommended their use.
|
||||
possible attack vectors and we highly recommend their use.
|
||||
|
||||
|
||||
### Can I download the source, build, and host the AngularJS environment locally?
|
||||
@@ -205,7 +205,7 @@ If you want to apply a directive to each inner piece of the repeat, put it on a
|
||||
|
||||
### `$rootScope` exists, but it can be used for evil
|
||||
|
||||
Scopes in Angular form a hierarchy, prototypically inheriting from a root scope at the top of the tree.
|
||||
Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree.
|
||||
Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
|
||||
|
||||
Occasionally there are pieces of data that you want to make global to the whole app.
|
||||
|
||||
@@ -233,7 +233,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
|
||||
Refresh your browser and verify that it says "Hello, World!".
|
||||
|
||||
* Update the unit test for the controller in ./test/unit/controllersSpec.js to reflect the previous change. For example by adding:
|
||||
* Update the unit test for the controller in `./test/unit/controllersSpec.js` to reflect the previous change. For example by adding:
|
||||
|
||||
expect(scope.name).toBe('World');
|
||||
|
||||
@@ -251,7 +251,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
Extra points: try and make an 8x8 table using an additional ng-repeat.
|
||||
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
|
||||
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('PhoneCat App', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should filter the phone list as user types into the search box', function() {
|
||||
it('should filter the phone list as a user types into the search box', function() {
|
||||
|
||||
var phoneList = element.all(by.repeater('phone in phones'));
|
||||
var query = element(by.model('query'));
|
||||
@@ -159,7 +159,7 @@ Let's see how we can get the current value of the `query` model to appear in the
|
||||
var phoneList = element.all(by.repeater('phone in phones'));
|
||||
var query = element(by.model('query'));
|
||||
|
||||
it('should filter the phone list as user types into the search box', function() {
|
||||
it('should filter the phone list as a user types into the search box', function() {
|
||||
expect(phoneList.count()).toBe(3);
|
||||
|
||||
query.sendKeys('nexus');
|
||||
|
||||
@@ -11,7 +11,7 @@ from our server using one of Angular's built-in {@link guide/services services}
|
||||
ng.$http $http}. We will use Angular's {@link guide/di dependency
|
||||
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
|
||||
|
||||
* There are now a list of 20 phones, loaded from the server.
|
||||
* There is now a list of 20 phones, loaded from the server.
|
||||
|
||||
<div doc-tutorial-reset="5"></div>
|
||||
|
||||
|
||||
@@ -33,17 +33,17 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "1.2.x",
|
||||
"angular-mocks": "~1.2.x",
|
||||
"jquery": "1.10.2",
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"jquery": "2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.2.x"
|
||||
"angular-route": "~1.3.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "~1.2.x"` tells bower to install a version of the
|
||||
angular-route component that is compatible with version 1.2.x. We must tell bower to download
|
||||
The new dependency `"angular-route": "~1.3.0"` tells bower to install a version of the
|
||||
angular-route component that is compatible with version 1.3.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
|
||||
@@ -32,17 +32,17 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "1.2.x",
|
||||
"angular-mocks": "~1.2.x",
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.2.x",
|
||||
"angular-resource": "~1.2.x"
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "~1.2.x"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.2.x. We must ask bower to download
|
||||
The new dependency `"angular-resource": "~1.3.0"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.3.x. We must ask bower to download
|
||||
and install this dependency. We can do this by running:
|
||||
|
||||
```
|
||||
|
||||
@@ -21,7 +21,7 @@ animations on top of the template code we created before.
|
||||
## Dependencies
|
||||
|
||||
The animation functionality is provided by Angular in the `ngAnimate` module, which is distributed
|
||||
separately from the core Angular framework. In addition we will use `JQuery` in this project to do
|
||||
separately from the core Angular framework. In addition we will use `jQuery` in this project to do
|
||||
extra JavaScript animations.
|
||||
|
||||
We are using [Bower][bower] to install client side dependencies. This step updates the
|
||||
@@ -36,21 +36,21 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "1.2.x",
|
||||
"angular-mocks": "~1.2.x",
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.2.x",
|
||||
"angular-resource": "~1.2.x",
|
||||
"jquery": "1.10.2",
|
||||
"angular-animate": "~1.2.x"
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0",
|
||||
"jquery": "~2.1.1",
|
||||
"angular-animate": "~1.3.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "~1.2.x"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.2.x.
|
||||
* `"jquery": "1.10.2"` tells bower to install the 1.10.2 version of JQuery. Note that this is not an
|
||||
Angular library, it is the standard JQuery library. We can use bower to install a wide range of 3rd
|
||||
* `"angular-animate": "~1.3.0"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.3.x.
|
||||
* `"jquery": "2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd
|
||||
party libraries.
|
||||
|
||||
We must ask bower to download and install this dependency. We can do this by running:
|
||||
@@ -255,7 +255,7 @@ which are described in detail below.
|
||||
Next let's add an animation for transitions between route changes in {@link api/ngRoute.directive:ngView `ngView`}.
|
||||
|
||||
To start, let's add a new CSS class to our HTML like we did in the example above.
|
||||
This time, instead of the `ng-repeat` element, let's add it to the element containing the ng-view directive.
|
||||
This time, instead of the `ng-repeat` element, let's add it to the element containing the `ng-view` directive.
|
||||
In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our
|
||||
animations between view changes.
|
||||
|
||||
@@ -340,13 +340,13 @@ a cross fade animation in between. So as the previous page is just about to be r
|
||||
while the new page fades in right on top of it.
|
||||
|
||||
Once the leave animation is over then element is removed and once the enter animation is complete
|
||||
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to
|
||||
be position itself with its default CSS code (so no more absolute positioning once the animation is
|
||||
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element, causing it to rerender and
|
||||
reposition itself with its default CSS code (so no more absolute positioning once the animation is
|
||||
over). This works fluidly so that pages flow naturally between route changes without anything
|
||||
jumping around.
|
||||
|
||||
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time
|
||||
a new page is loaded the ng-view directive will create a copy of itself, download the template and
|
||||
a new page is loaded the `ng-view` directive will create a copy of itself, download the template and
|
||||
append the contents. This ensures that all views are contained within a single HTML element which
|
||||
allows for easy animation control.
|
||||
|
||||
|
||||
+37
-5
@@ -8,7 +8,10 @@ var bower = require('bower');
|
||||
var Dgeni = require('dgeni');
|
||||
var merge = require('event-stream').merge;
|
||||
var path = require('canonical-path');
|
||||
|
||||
var foreach = require('gulp-foreach');
|
||||
var uglify = require('gulp-uglify');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var rename = require('gulp-rename');
|
||||
|
||||
// We indicate to gulp that tasks are async by returning the stream.
|
||||
// Gulp can then wait for the stream to close before starting dependent tasks.
|
||||
@@ -17,6 +20,9 @@ var path = require('canonical-path');
|
||||
var outputFolder = '../build/docs';
|
||||
var bowerFolder = 'bower_components';
|
||||
|
||||
var src = 'app/src/**/*.js';
|
||||
var assets = 'app/assets/**/*';
|
||||
|
||||
|
||||
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
|
||||
pattern = pattern || '/**/*';
|
||||
@@ -40,14 +46,37 @@ gulp.task('bower', function() {
|
||||
});
|
||||
|
||||
gulp.task('build-app', function() {
|
||||
gulp.src('app/src/**/*.js')
|
||||
.pipe(concat('docs.js'))
|
||||
.pipe(gulp.dest(outputFolder + '/js/'));
|
||||
var file = 'docs.js';
|
||||
var minFile = 'docs.min.js';
|
||||
var folder = outputFolder + '/js/';
|
||||
|
||||
return gulp.src(src)
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat(file))
|
||||
.pipe(gulp.dest(folder))
|
||||
.pipe(rename(minFile))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(folder));
|
||||
});
|
||||
|
||||
gulp.task('assets', ['bower'], function() {
|
||||
var JS_EXT = /\.js$/;
|
||||
return merge(
|
||||
gulp.src(['app/assets/**/*']).pipe(gulp.dest(outputFolder)),
|
||||
gulp.src([assets])
|
||||
.pipe(gulp.dest(outputFolder)),
|
||||
gulp.src([assets])
|
||||
.pipe(foreach(function(stream, file) {
|
||||
if (JS_EXT.test(file.relative)) {
|
||||
var minFile = file.relative.replace(JS_EXT, '.min.js');
|
||||
return stream
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(concat(minFile))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest(outputFolder));
|
||||
}
|
||||
})),
|
||||
copyComponent('bootstrap', '/dist/**/*'),
|
||||
copyComponent('open-sans-fontface'),
|
||||
copyComponent('lunr.js','/*.js'),
|
||||
@@ -77,3 +106,6 @@ gulp.task('jshint', ['doc-gen'], function() {
|
||||
// The default task that will be run if no task is supplied
|
||||
gulp.task('default', ['assets', 'doc-gen', 'build-app', 'jshint']);
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch([src, assets], ['assets', 'build-app']);
|
||||
});
|
||||
|
||||
+8
-4
@@ -285,11 +285,15 @@ module.exports = {
|
||||
|
||||
|
||||
//csp connect middleware
|
||||
csp: function(){
|
||||
conditionalCsp: function(){
|
||||
return function(req, res, next){
|
||||
res.setHeader("X-WebKit-CSP", "default-src 'self';");
|
||||
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
|
||||
res.setHeader("Content-Security-Policy", "default-src 'self'");
|
||||
var CSP = /\.csp\W/;
|
||||
|
||||
if (CSP.test(req.url)) {
|
||||
res.setHeader("X-WebKit-CSP", "default-src 'self';");
|
||||
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
|
||||
res.setHeader("Content-Security-Policy", "default-src 'self'");
|
||||
}
|
||||
next();
|
||||
};
|
||||
},
|
||||
|
||||
@@ -83,6 +83,7 @@ var getTaggedVersion = function() {
|
||||
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
|
||||
version.codeName = getCodeName(tag);
|
||||
version.full = version.version;
|
||||
version.branch = 'v' + currentPackage.branchVersion.replace('*', 'x');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -197,6 +198,7 @@ var getSnapshotVersion = function() {
|
||||
version.isSnapshot = true;
|
||||
version.format();
|
||||
version.full = version.version + '+' + version.build;
|
||||
version.branch = 'master';
|
||||
|
||||
return version;
|
||||
};
|
||||
|
||||
Generated
+829
-341
File diff suppressed because it is too large
Load Diff
+7
-6
@@ -13,7 +13,6 @@
|
||||
"canonical-path": "0.0.2",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"es6-shim": "^0.14.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-bump": "~0.0.13",
|
||||
@@ -29,8 +28,12 @@
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-shell": "~0.4.0",
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "~2.1.7",
|
||||
"gulp-concat": "^2.4.1",
|
||||
"gulp-foreach": "0.0.1",
|
||||
"gulp-jshint": "~1.4.2",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"gulp-sourcemaps": "^1.2.2",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"jasmine-node": "~1.11.0",
|
||||
"jasmine-reporters": "~0.2.1",
|
||||
@@ -49,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.0.4",
|
||||
"protractor": "1.2.0",
|
||||
"protractor": "1.3.1",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
@@ -57,9 +60,7 @@
|
||||
"semver": "~2.1.0",
|
||||
"shelljs": "~0.2.6",
|
||||
"sorted-object": "^1.0.0",
|
||||
"stringmap": "^0.2.2",
|
||||
"winston": "~0.7.2",
|
||||
"yaml-js": "~0.0.8"
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
@@ -21,9 +21,9 @@ exports.config = {
|
||||
|
||||
// Disable animations so e2e tests run more quickly
|
||||
var disableNgAnimate = function() {
|
||||
angular.module('disableNgAnimate', []).run(function($animate) {
|
||||
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
});
|
||||
}]);
|
||||
};
|
||||
|
||||
browser.addMockModule('disableNgAnimate', disableNgAnimate);
|
||||
|
||||
@@ -12,9 +12,9 @@ exports.config = {
|
||||
|
||||
// Disable animations so e2e tests run more quickly
|
||||
var disableNgAnimate = function() {
|
||||
angular.module('disableNgAnimate', []).run(function($animate) {
|
||||
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
|
||||
$animate.enabled(false);
|
||||
});
|
||||
}]);
|
||||
};
|
||||
|
||||
browser.addMockModule('disableNgAnimate', disableNgAnimate);
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Untags a release.
|
||||
|
||||
echo "###################################"
|
||||
echo "## Untag angular.js for a release #"
|
||||
echo "###################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
"--action=(prepare|publish)"
|
||||
# the version number of the release.
|
||||
# e.g. 1.2.12 or 1.2.12-rc.1
|
||||
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
|
||||
)
|
||||
|
||||
function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
TAG_NAME="v$VERSION_NUMBER"
|
||||
}
|
||||
|
||||
function prepare() {
|
||||
:
|
||||
}
|
||||
|
||||
function publish() {
|
||||
# push the tag deletion to github
|
||||
tags=`git ls-remote --tags git@github.com:angular/angular.js`
|
||||
if [[ $tags =~ "refs/tags/v$VERSION_NUMBER^" ]]; then
|
||||
echo "-- Creating dummy git repo for angular.js with origin remote"
|
||||
mkdir $TMP_DIR/empty-angular.js
|
||||
cd $TMP_DIR/empty-angular.js
|
||||
git init
|
||||
git remote add origin git@github.com:angular/angular.js.git
|
||||
git push origin ":$TAG_NAME"
|
||||
else
|
||||
echo "-- Tag v$VERSION_NUMBER does not exist on remote. Moving on"
|
||||
fi
|
||||
}
|
||||
|
||||
source $(dirname $0)/../utils.inc
|
||||
@@ -17,6 +17,7 @@ function init {
|
||||
REPOS=(
|
||||
angular
|
||||
angular-animate
|
||||
angular-aria
|
||||
angular-cookies
|
||||
angular-i18n
|
||||
angular-loader
|
||||
@@ -72,6 +73,8 @@ function prepare {
|
||||
cd $TMP_DIR/bower-$repo
|
||||
replaceJsonProp "bower.json" "version" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "bower.json" "angular.*" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "package.json" "version" ".*" "$NEW_VERSION"
|
||||
replaceJsonProp "package.json" "angular.*" ".*" "$NEW_VERSION"
|
||||
|
||||
git add -A
|
||||
|
||||
@@ -89,6 +92,18 @@ function publish {
|
||||
cd $TMP_DIR/bower-$repo
|
||||
git push origin master
|
||||
git push origin v$NEW_VERSION
|
||||
|
||||
# don't publish every build to npm
|
||||
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
|
||||
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
|
||||
# publish releases as "latest"
|
||||
npm publish
|
||||
else
|
||||
# publish prerelease builds with the beta tag
|
||||
npm publish --tag=beta
|
||||
fi
|
||||
fi
|
||||
|
||||
cd $SCRIPT_DIR
|
||||
done
|
||||
}
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for removing tags from the Angular bower repos
|
||||
|
||||
echo "#################################"
|
||||
echo "#### Untag bower ################"
|
||||
echo "#################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
"--action=(prepare|publish)"
|
||||
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
|
||||
)
|
||||
|
||||
function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
REPOS=(
|
||||
angular
|
||||
angular-animate
|
||||
angular-cookies
|
||||
angular-i18n
|
||||
angular-loader
|
||||
angular-mocks
|
||||
angular-route
|
||||
angular-resource
|
||||
angular-sanitize
|
||||
angular-scenario
|
||||
angular-touch
|
||||
)
|
||||
}
|
||||
|
||||
function prepare {
|
||||
:
|
||||
}
|
||||
|
||||
function publish {
|
||||
for repo in "${REPOS[@]}"
|
||||
do
|
||||
tags=`git ls-remote --tags git@github.com:angular/bower-$repo`
|
||||
if [[ $tags =~ "refs/tags/v$VERSION_NUMBER" ]]; then
|
||||
echo "-- Creating dummy git repo for bower-$repo with origin remote"
|
||||
mkdir $TMP_DIR/bower-$repo
|
||||
cd $TMP_DIR/bower-$repo
|
||||
git init
|
||||
git remote add origin git@github.com:angular/bower-$repo.git
|
||||
git push origin :v$VERSION_NUMBER
|
||||
echo "-- Deleting v$VERSION_NUMBER tag from bower-$repo"
|
||||
cd $SCRIPT_DIR
|
||||
else
|
||||
echo "-- No remote tag matching v$VERSION_NUMBER exists on bower-$repo"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
source $(dirname $0)/../utils.inc
|
||||
@@ -3,7 +3,7 @@
|
||||
# Script for updating code.angularjs.org repo from current local build.
|
||||
|
||||
echo "#################################"
|
||||
echo "## Update code.angular.js.org ###"
|
||||
echo "## Update code.angularjs.org ###"
|
||||
echo "#################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for removing specified release dir from code.angularjs.org.
|
||||
|
||||
echo "################################################"
|
||||
echo "## Remove a version from code.angular.js.org ###"
|
||||
echo "################################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
"--action=(prepare|publish)"
|
||||
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
|
||||
)
|
||||
|
||||
function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
REPO_DIR=$TMP_DIR/code.angularjs.org
|
||||
echo "code tmp $TMP_DIR"
|
||||
}
|
||||
|
||||
function prepare {
|
||||
echo "-- Cloning code.angularjs.org"
|
||||
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR
|
||||
|
||||
#
|
||||
# Remove the files from the repo
|
||||
#
|
||||
echo "-- Removing $VERSION_NUMBER from code.angularjs.org"
|
||||
cd $REPO_DIR
|
||||
if [ -d "$VERSION_NUMBER" ]; then
|
||||
git rm -r $VERSION_NUMBER
|
||||
echo "-- Committing removal to code.angularjs.org"
|
||||
git commit -m "removing v$VERSION_NUMBER"
|
||||
else
|
||||
echo "-- Version: $VERSION_NUMBER does not exist in code.angularjs.org!"
|
||||
fi
|
||||
}
|
||||
|
||||
function publish {
|
||||
cd $REPO_DIR
|
||||
|
||||
echo "-- Pushing code.angularjs.org to github"
|
||||
git push origin master
|
||||
}
|
||||
|
||||
source $(dirname $0)/../utils.inc
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "#################################"
|
||||
echo "#### undo a release ############"
|
||||
echo "#################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
# require the git dryrun flag so the script can't be run without
|
||||
# thinking about this!
|
||||
"--git-push-dryrun=(true|false)"
|
||||
# the version number of the release.
|
||||
# e.g. 1.2.12 or 1.2.12-rc.1
|
||||
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
|
||||
)
|
||||
|
||||
function init {
|
||||
if [[ ! $VERBOSE ]]; then
|
||||
VERBOSE=false
|
||||
fi
|
||||
VERBOSE_ARG="--verbose=$VERBOSE"
|
||||
}
|
||||
|
||||
function phase {
|
||||
ACTION_ARG="--action=$1"
|
||||
VERSION_NUMBER_ARG="--version-number=$VERSION_NUMBER"
|
||||
../angular.js/untag-release.sh $ACTION_ARG $VERBOSE_ARG\
|
||||
--version-number=$VERSION_NUMBER
|
||||
|
||||
../code.angularjs.org/unpublish.sh $ACTION_ARG $VERSION_NUMBER_ARG $VERBOSE_ARG
|
||||
../bower/unpublish.sh $ACTION_ARG $VERSION_NUMBER_ARG $VERBOSE_ARG
|
||||
}
|
||||
|
||||
function run {
|
||||
# First prepare all scripts (build, commit, tag, ...),
|
||||
# so we are sure everything is all right
|
||||
phase prepare
|
||||
# only then publish to github
|
||||
phase publish
|
||||
}
|
||||
|
||||
source $(dirname $0)/../utils.inc
|
||||
+8
-1
@@ -11,6 +11,7 @@
|
||||
"jqLite": false,
|
||||
"jQuery": false,
|
||||
"slice": false,
|
||||
"splice": false,
|
||||
"push": false,
|
||||
"toString": false,
|
||||
"ngMinErr": false,
|
||||
@@ -55,7 +56,6 @@
|
||||
"trim": false,
|
||||
"isElement": false,
|
||||
"makeMap": false,
|
||||
"map": false,
|
||||
"size": false,
|
||||
"includes": false,
|
||||
"arrayRemove": false,
|
||||
@@ -92,6 +92,13 @@
|
||||
|
||||
"skipDestroyOnNextJQueryCleanData": true,
|
||||
|
||||
"NODE_TYPE_ELEMENT": false,
|
||||
"NODE_TYPE_TEXT": false,
|
||||
"NODE_TYPE_COMMENT": false,
|
||||
"NODE_TYPE_COMMENT": false,
|
||||
"NODE_TYPE_DOCUMENT": false,
|
||||
"NODE_TYPE_DOCUMENT_FRAGMENT": false,
|
||||
|
||||
/* filters.js */
|
||||
"getFirstThursdayOfYear": false,
|
||||
|
||||
|
||||
+20
-21
@@ -6,6 +6,7 @@
|
||||
jqLite: true,
|
||||
jQuery: true,
|
||||
slice: true,
|
||||
splice: true,
|
||||
push: true,
|
||||
toString: true,
|
||||
ngMinErr: true,
|
||||
@@ -50,7 +51,6 @@
|
||||
trim: true,
|
||||
isElement: true,
|
||||
makeMap: true,
|
||||
map: true,
|
||||
size: true,
|
||||
includes: true,
|
||||
arrayRemove: true,
|
||||
@@ -83,6 +83,12 @@
|
||||
getBlockNodes: true,
|
||||
hasOwnProperty: true,
|
||||
createMap: true,
|
||||
|
||||
NODE_TYPE_ELEMENT: true,
|
||||
NODE_TYPE_TEXT: true,
|
||||
NODE_TYPE_COMMENT: true,
|
||||
NODE_TYPE_DOCUMENT: true,
|
||||
NODE_TYPE_DOCUMENT_FRAGMENT: true,
|
||||
*/
|
||||
|
||||
////////////////////////////////////
|
||||
@@ -162,6 +168,7 @@ var /** holds major version number for IE or NaN for real browsers */
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
slice = [].slice,
|
||||
splice = [].splice,
|
||||
push = [].push,
|
||||
toString = Object.prototype.toString,
|
||||
ngMinErr = minErr('ng'),
|
||||
@@ -172,13 +179,10 @@ var /** holds major version number for IE or NaN for real browsers */
|
||||
uid = 0;
|
||||
|
||||
/**
|
||||
* IE 11 changed the format of the UserAgent string.
|
||||
* See http://msdn.microsoft.com/en-us/library/ms537503.aspx
|
||||
* documentMode is an IE-only property
|
||||
* http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
|
||||
*/
|
||||
msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
|
||||
if (isNaN(msie)) {
|
||||
msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
|
||||
}
|
||||
msie = document.documentMode;
|
||||
|
||||
|
||||
/**
|
||||
@@ -194,7 +198,7 @@ function isArrayLike(obj) {
|
||||
|
||||
var length = obj.length;
|
||||
|
||||
if (obj.nodeType === 1 && length) {
|
||||
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -327,7 +331,7 @@ function setHashKey(obj, h) {
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Extends the destination object `dst` by copying all of the properties from the `src` object(s)
|
||||
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
|
||||
* to `dst`. You can specify multiple `src` objects.
|
||||
*
|
||||
* @param {Object} dst Destination object.
|
||||
@@ -617,15 +621,6 @@ function nodeName_(element) {
|
||||
}
|
||||
|
||||
|
||||
function map(obj, iterator, context) {
|
||||
var results = [];
|
||||
forEach(obj, function(value, index, list) {
|
||||
results.push(iterator.call(context, value, index, list));
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Determines the number of elements in an array, the number of properties an object has, or
|
||||
@@ -1039,11 +1034,9 @@ function startingTag(element) {
|
||||
// are not allowed to have children. So we just ignore it.
|
||||
element.empty();
|
||||
} catch(e) {}
|
||||
// As Per DOM Standards
|
||||
var TEXT_NODE = 3;
|
||||
var elemHtml = jqLite('<div>').append(element).html();
|
||||
try {
|
||||
return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
|
||||
return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
|
||||
elemHtml.
|
||||
match(/^(<[^>]+>)/)[1].
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
|
||||
@@ -1621,3 +1614,9 @@ function getBlockNodes(nodes) {
|
||||
function createMap() {
|
||||
return Object.create(null);
|
||||
}
|
||||
|
||||
var NODE_TYPE_ELEMENT = 1;
|
||||
var NODE_TYPE_TEXT = 3;
|
||||
var NODE_TYPE_COMMENT = 8;
|
||||
var NODE_TYPE_DOCUMENT = 9;
|
||||
var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
|
||||
|
||||
@@ -140,8 +140,7 @@ function publishExternalAPI(angular){
|
||||
'getTestability': getTestability,
|
||||
'$$minErr': minErr,
|
||||
'$$csp': csp,
|
||||
'reloadWithDebugInfo': reloadWithDebugInfo,
|
||||
'$$hasClass': jqLiteHasClass
|
||||
'reloadWithDebugInfo': reloadWithDebugInfo
|
||||
});
|
||||
|
||||
angularModule = setupModuleLoader(window);
|
||||
|
||||
+21
-8
@@ -7,13 +7,13 @@
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Creates an injector function that can be used for retrieving services as well as for
|
||||
* Creates an injector object that can be used for retrieving services as well as for
|
||||
* dependency injection (see {@link guide/di dependency injection}).
|
||||
*
|
||||
|
||||
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
|
||||
* {@link angular.module}. The `ng` module must be explicitly added.
|
||||
* @returns {function()} Injector function. See {@link auto.$injector $injector}.
|
||||
* @returns {function()} Injector object. See {@link auto.$injector $injector}.
|
||||
*
|
||||
* @example
|
||||
* Typical usage
|
||||
@@ -120,7 +120,6 @@ function annotate(fn, strictDi, name) {
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $injector
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
*
|
||||
@@ -135,7 +134,7 @@ function annotate(fn, strictDi, name) {
|
||||
* expect($injector.get('$injector')).toBe($injector);
|
||||
* expect($injector.invoke(function($injector) {
|
||||
* return $injector;
|
||||
* }).toBe($injector);
|
||||
* })).toBe($injector);
|
||||
* ```
|
||||
*
|
||||
* # Injection Function Annotation
|
||||
@@ -202,8 +201,8 @@ function annotate(fn, strictDi, name) {
|
||||
* @description
|
||||
* Allows the user to query if the particular service exists.
|
||||
*
|
||||
* @param {string} Name of the service to query.
|
||||
* @returns {boolean} returns true if injector has given service.
|
||||
* @param {string} name Name of the service to query.
|
||||
* @returns {boolean} `true` if injector has given service.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -663,7 +662,21 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
return providerCache[name + providerSuffix] = provider_;
|
||||
}
|
||||
|
||||
function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
|
||||
function enforceReturnValue(name, factory) {
|
||||
return function enforcedReturnValue() {
|
||||
var result = instanceInjector.invoke(factory);
|
||||
if (isUndefined(result)) {
|
||||
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function factory(name, factoryFn, enforce) {
|
||||
return provider(name, {
|
||||
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
|
||||
});
|
||||
}
|
||||
|
||||
function service(name, constructor) {
|
||||
return factory(name, ['$injector', function($injector) {
|
||||
@@ -671,7 +684,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}]);
|
||||
}
|
||||
|
||||
function value(name, val) { return factory(name, valueFn(val)); }
|
||||
function value(name, val) { return factory(name, valueFn(val), false); }
|
||||
|
||||
function constant(name, value) {
|
||||
assertNotHasOwnProperty(name, 'constant');
|
||||
|
||||
+9
-9
@@ -44,7 +44,7 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`detach()`](http://api.jquery.com/detach/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
@@ -166,7 +166,7 @@ function jqLiteAcceptsData(node) {
|
||||
// The window object can accept data but has no nodeType
|
||||
// Otherwise we are only interested in elements (1) and documents (9)
|
||||
var nodeType = node.nodeType;
|
||||
return nodeType === 1 || !nodeType || nodeType === 9;
|
||||
return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
|
||||
}
|
||||
|
||||
function jqLiteBuildFragment(html, context) {
|
||||
@@ -419,7 +419,7 @@ function jqLiteController(element, name) {
|
||||
function jqLiteInheritedData(element, name, value) {
|
||||
// if element is the document object work with the html element instead
|
||||
// this makes $(document).scope() possible
|
||||
if(element.nodeType == 9) {
|
||||
if(element.nodeType == NODE_TYPE_DOCUMENT) {
|
||||
element = element.documentElement;
|
||||
}
|
||||
var names = isArray(name) ? name : [name];
|
||||
@@ -432,7 +432,7 @@ function jqLiteInheritedData(element, name, value) {
|
||||
// If dealing with a document fragment node with a host element, and no parent, use the host
|
||||
// element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
|
||||
// to lookup parent controllers.
|
||||
element = element.parentNode || (element.nodeType === 11 && element.host);
|
||||
element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +610,7 @@ forEach({
|
||||
function getText(element, value) {
|
||||
if (isUndefined(value)) {
|
||||
var nodeType = element.nodeType;
|
||||
return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
|
||||
return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
|
||||
}
|
||||
element.textContent = value;
|
||||
}
|
||||
@@ -832,7 +832,7 @@ forEach({
|
||||
children: function(element) {
|
||||
var children = [];
|
||||
forEach(element.childNodes, function(element){
|
||||
if (element.nodeType === 1)
|
||||
if (element.nodeType === NODE_TYPE_ELEMENT)
|
||||
children.push(element);
|
||||
});
|
||||
return children;
|
||||
@@ -844,7 +844,7 @@ forEach({
|
||||
|
||||
append: function(element, node) {
|
||||
var nodeType = element.nodeType;
|
||||
if (nodeType !== 1 && nodeType !== 11) return;
|
||||
if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
|
||||
|
||||
node = new JQLite(node);
|
||||
|
||||
@@ -855,7 +855,7 @@ forEach({
|
||||
},
|
||||
|
||||
prepend: function(element, node) {
|
||||
if (element.nodeType === 1) {
|
||||
if (element.nodeType === NODE_TYPE_ELEMENT) {
|
||||
var index = element.firstChild;
|
||||
forEach(new JQLite(node), function(child){
|
||||
element.insertBefore(child, index);
|
||||
@@ -906,7 +906,7 @@ forEach({
|
||||
|
||||
parent: function(element) {
|
||||
var parent = element.parentNode;
|
||||
return parent && parent.nodeType !== 11 ? parent : null;
|
||||
return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
|
||||
},
|
||||
|
||||
next: function(element) {
|
||||
|
||||
@@ -94,7 +94,10 @@ function $AnchorScrollProvider() {
|
||||
// (no url change, no $location.hash() change), browser native does scroll
|
||||
if (autoScrollingEnabled) {
|
||||
$rootScope.$watch(function autoScrollWatch() {return $location.hash();},
|
||||
function autoScrollWatchAction() {
|
||||
function autoScrollWatchAction(newVal, oldVal) {
|
||||
// skip the initial scroll if $location.hash is empty
|
||||
if (newVal === oldVal && newVal === '') return;
|
||||
|
||||
$rootScope.$evalAsync(scroll);
|
||||
});
|
||||
}
|
||||
|
||||
+106
-6
@@ -81,9 +81,57 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
return this.$$classNameFilter;
|
||||
};
|
||||
|
||||
this.$get = ['$$q', '$$asyncCallback', function($$q, $$asyncCallback) {
|
||||
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, cache) {
|
||||
var toAdd = [], toRemove = [];
|
||||
|
||||
var hasClasses = createMap();
|
||||
forEach((element.attr('class') || '').split(/\s+/), function(className) {
|
||||
hasClasses[className] = true;
|
||||
});
|
||||
|
||||
forEach(cache.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);
|
||||
}
|
||||
});
|
||||
|
||||
return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove];
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -187,13 +235,17 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
addClass : function(element, className) {
|
||||
return this.setClass(element, className, []);
|
||||
},
|
||||
|
||||
$$addClassImmediately : function addClassImmediately(element, className) {
|
||||
element = jqLite(element);
|
||||
className = !isString(className)
|
||||
? (isArray(className) ? className.join(' ') : '')
|
||||
: className;
|
||||
forEach(element, function (element) {
|
||||
jqLiteAddClass(element, className);
|
||||
});
|
||||
return asyncPromise();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -209,6 +261,11 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
removeClass : function(element, className) {
|
||||
return this.setClass(element, [], className);
|
||||
},
|
||||
|
||||
$$removeClassImmediately : function removeClassImmediately(element, className) {
|
||||
element = jqLite(element);
|
||||
className = !isString(className)
|
||||
? (isArray(className) ? className.join(' ') : '')
|
||||
: className;
|
||||
@@ -231,10 +288,53 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @param {string} remove the CSS class which will be removed from the element
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
setClass : function(element, add, remove) {
|
||||
this.addClass(element, add);
|
||||
this.removeClass(element, remove);
|
||||
return asyncPromise();
|
||||
setClass : function(element, add, remove, runSynchronously) {
|
||||
var self = this;
|
||||
var STORAGE_KEY = '$$animateClasses';
|
||||
var createdCache = false;
|
||||
element = jqLite(element);
|
||||
|
||||
if (runSynchronously) {
|
||||
// TODO(@caitp/@matsko): Remove undocumented `runSynchronously` parameter, and always
|
||||
// perform DOM manipulation asynchronously or in postDigest.
|
||||
self.$$addClassImmediately(element, add);
|
||||
self.$$removeClassImmediately(element, remove);
|
||||
return asyncPromise();
|
||||
}
|
||||
|
||||
var cache = element.data(STORAGE_KEY);
|
||||
if (!cache) {
|
||||
cache = {
|
||||
classes: {}
|
||||
};
|
||||
createdCache = true;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var classes = cache && resolveElementClasses(element, cache);
|
||||
|
||||
if (classes) {
|
||||
if (classes[0]) self.$$addClassImmediately(element, classes[0]);
|
||||
if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
element.data(STORAGE_KEY, cache);
|
||||
}
|
||||
|
||||
return cache.promise;
|
||||
},
|
||||
|
||||
enabled : noop,
|
||||
|
||||
+52
-21
@@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
/* global stripHash: true */
|
||||
|
||||
/**
|
||||
* ! This is a private undocumented service !
|
||||
@@ -123,8 +124,9 @@ function Browser(window, document, $log, $sniffer) {
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
var lastBrowserUrl = location.href,
|
||||
lastHistoryState = history.state,
|
||||
baseElement = document.find('base'),
|
||||
newLocation = null;
|
||||
reloadLocation = null;
|
||||
|
||||
/**
|
||||
* @name $browser#url
|
||||
@@ -143,26 +145,42 @@ function Browser(window, document, $log, $sniffer) {
|
||||
* {@link ng.$location $location service} to change url.
|
||||
*
|
||||
* @param {string} url New url (when used as setter)
|
||||
* @param {boolean=} replace Should new url replace current history record ?
|
||||
* @param {boolean=} replace Should new url replace current history record?
|
||||
* @param {object=} state object to use with pushState/replaceState
|
||||
*/
|
||||
self.url = function(url, replace) {
|
||||
self.url = function(url, replace, state) {
|
||||
// In modern browsers `history.state` is `null` by default; treating it separately
|
||||
// from `undefined` would cause `$browser.url('/foo')` to change `history.state`
|
||||
// to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
|
||||
if (isUndefined(state)) {
|
||||
state = null;
|
||||
}
|
||||
|
||||
// Android Browser BFCache causes location, history reference to become stale.
|
||||
if (location !== window.location) location = window.location;
|
||||
if (history !== window.history) history = window.history;
|
||||
|
||||
// setter
|
||||
if (url) {
|
||||
if (lastBrowserUrl == url) return;
|
||||
// Don't change anything if previous and current URLs and states match. This also prevents
|
||||
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
|
||||
// See https://github.com/angular/angular.js/commit/ffb2701
|
||||
if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
|
||||
return;
|
||||
}
|
||||
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
|
||||
lastBrowserUrl = url;
|
||||
if ($sniffer.history) {
|
||||
if (replace) history.replaceState(null, '', url);
|
||||
else {
|
||||
history.pushState(null, '', url);
|
||||
// Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
|
||||
baseElement.attr('href', baseElement.attr('href'));
|
||||
}
|
||||
// Don't use history API if only the hash changed
|
||||
// due to a bug in IE10/IE11 which leads
|
||||
// to not firing a `hashchange` nor `popstate` event
|
||||
// in some cases (see #9143).
|
||||
if ($sniffer.history && (!sameBase || history.state !== state)) {
|
||||
history[replace ? 'replaceState' : 'pushState'](state, '', url);
|
||||
lastHistoryState = history.state;
|
||||
} else {
|
||||
newLocation = url;
|
||||
if (!sameBase) {
|
||||
reloadLocation = url;
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
} else {
|
||||
@@ -172,23 +190,38 @@ function Browser(window, document, $log, $sniffer) {
|
||||
return self;
|
||||
// getter
|
||||
} else {
|
||||
// - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
|
||||
// methods not updating location.href synchronously.
|
||||
// - reloadLocation is needed as browsers don't allow to read out
|
||||
// the new location.href if a reload happened.
|
||||
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
|
||||
return newLocation || location.href.replace(/%27/g,"'");
|
||||
return reloadLocation || location.href.replace(/%27/g,"'");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @name $browser#state
|
||||
*
|
||||
* @description
|
||||
* This method is a getter.
|
||||
*
|
||||
* Return history.state or null if history.state is undefined.
|
||||
*
|
||||
* @returns {object} state
|
||||
*/
|
||||
self.state = function() {
|
||||
return isUndefined(history.state) ? null : history.state;
|
||||
};
|
||||
|
||||
var urlChangeListeners = [],
|
||||
urlChangeInit = false;
|
||||
|
||||
function fireUrlChange() {
|
||||
newLocation = null;
|
||||
if (lastBrowserUrl == self.url()) return;
|
||||
if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastBrowserUrl = self.url();
|
||||
forEach(urlChangeListeners, function(listener) {
|
||||
listener(self.url());
|
||||
listener(self.url(), history.state);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -223,9 +256,7 @@ function Browser(window, document, $log, $sniffer) {
|
||||
// html5 history api - popstate event
|
||||
if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
|
||||
// hashchange event
|
||||
if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
|
||||
// polling
|
||||
else self.addPollFn(fireUrlChange);
|
||||
jqLite(window).on('hashchange', fireUrlChange);
|
||||
|
||||
urlChangeInit = true;
|
||||
}
|
||||
|
||||
+266
-88
@@ -212,8 +212,11 @@
|
||||
* * (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.
|
||||
* * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
|
||||
* * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
|
||||
* * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
|
||||
* `null` to the `link` fn if not found.
|
||||
* * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
|
||||
* `null` to the `link` fn if not found.
|
||||
*
|
||||
*
|
||||
* #### `controllerAs`
|
||||
@@ -291,22 +294,18 @@
|
||||
* (because SVG doesn't work with custom elements in the DOM tree).
|
||||
*
|
||||
* #### `transclude`
|
||||
* compile the content of the element and make it available to the directive.
|
||||
* Typically used with {@link ng.directive:ngTransclude
|
||||
* ngTransclude}. The advantage of transclusion is that the linking function receives a
|
||||
* transclusion function which is pre-bound to the correct scope. In a typical setup the widget
|
||||
* creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
|
||||
* scope. This makes it possible for the widget to have private state, and the transclusion to
|
||||
* be bound to the parent (pre-`isolate`) scope.
|
||||
* Extract the contents of the element where the directive appears and make it available to the directive.
|
||||
* The contents are compiled and provided to the directive as a **transclusion function**. See the
|
||||
* {@link $compile#transclusion Transclusion} section below.
|
||||
*
|
||||
* * `true` - transclude the content of the directive.
|
||||
* * `'element'` - transclude the whole element including any directives defined at lower priority.
|
||||
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
|
||||
* directive's element or the entire element:
|
||||
*
|
||||
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
|
||||
* * `'element'` - transclude the whole of the directive's element including any directives on this
|
||||
* element that defined at a lower priority than this directive. When used, the `template`
|
||||
* property is ignored.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
|
||||
* DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
|
||||
* Testing Transclusion Directives}.
|
||||
* </div>
|
||||
*
|
||||
* #### `compile`
|
||||
*
|
||||
@@ -404,7 +403,121 @@
|
||||
* It is safe to do DOM transformation in the post-linking function on elements that are not waiting
|
||||
* for their async templates to be resolved.
|
||||
*
|
||||
* <a name="Attributes"></a>
|
||||
*
|
||||
* ### Transclusion
|
||||
*
|
||||
* Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
|
||||
* copying them to another part of the DOM, while maintaining their connection to the original AngularJS
|
||||
* scope from where they were taken.
|
||||
*
|
||||
* Transclusion is used (often with {@link ngTransclude}) to insert the
|
||||
* original contents of a directive's element into a specified place in the template of the directive.
|
||||
* The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
|
||||
* content has access to the properties on the scope from which it was taken, even if the directive
|
||||
* has isolated scope.
|
||||
* See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
|
||||
*
|
||||
* This makes it possible for the widget to have private state for its template, while the transcluded
|
||||
* content has access to its originating scope.
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
|
||||
* DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
|
||||
* Testing Transclusion Directives}.
|
||||
* </div>
|
||||
*
|
||||
* #### Transclusion Functions
|
||||
*
|
||||
* When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
|
||||
* function** to the directive's `link` function and `controller`. This transclusion function is a special
|
||||
* **linking function** that will return the compiled contents linked to a new transclusion scope.
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* If you are just using {@link ngTransclude} then you don't need to worry about this function, since
|
||||
* ngTransclude will deal with it for us.
|
||||
* </div>
|
||||
*
|
||||
* If you want to manually control the insertion and removal of the transcluded content in your directive
|
||||
* then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
|
||||
* object that contains the compiled DOM, which is linked to the correct transclusion scope.
|
||||
*
|
||||
* When you call a transclusion function you can pass in a **clone attach function**. This function is accepts
|
||||
* two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
|
||||
* content and the `scope` is the newly created transclusion scope, to which the clone is bound.
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
|
||||
* since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
|
||||
* </div>
|
||||
*
|
||||
* It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
|
||||
* attach function**:
|
||||
*
|
||||
* ```js
|
||||
* var transcludedContent, transclusionScope;
|
||||
*
|
||||
* $transclude(function(clone, scope) {
|
||||
* element.append(clone);
|
||||
* transcludedContent = clone;
|
||||
* transclusionScope = scope;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Later, if you want to remove the transcluded content from your DOM then you should also destroy the
|
||||
* associated transclusion scope:
|
||||
*
|
||||
* ```js
|
||||
* transcludedContent.remove();
|
||||
* transclusionScope.$destroy();
|
||||
* ```
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* **Best Practice**: if you intend to add and remove transcluded content manually in your directive
|
||||
* (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
|
||||
* then you are also responsible for calling `$destroy` on the transclusion scope.
|
||||
* </div>
|
||||
*
|
||||
* The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
|
||||
* automatically destroy their transluded clones as necessary so you do not need to worry about this if
|
||||
* you are simply using {@link ngTransclude} to inject the transclusion into your directive.
|
||||
*
|
||||
*
|
||||
* #### Transclusion Scopes
|
||||
*
|
||||
* When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
|
||||
* scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
|
||||
* when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
|
||||
* was taken.
|
||||
*
|
||||
* For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
|
||||
* like this:
|
||||
*
|
||||
* ```html
|
||||
* <div ng-app>
|
||||
* <div isolate>
|
||||
* <div transclusion>
|
||||
* </div>
|
||||
* </div>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* The `$parent` scope hierarchy will look like this:
|
||||
*
|
||||
* ```
|
||||
* - $rootScope
|
||||
* - isolate
|
||||
* - transclusion
|
||||
* ```
|
||||
*
|
||||
* but the scopes will inherit prototypically from different scopes to their `$parent`.
|
||||
*
|
||||
* ```
|
||||
* - $rootScope
|
||||
* - transclusion
|
||||
* - isolate
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* ### Attributes
|
||||
*
|
||||
* The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
|
||||
@@ -442,7 +555,7 @@
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Below is an example using `$compileProvider`.
|
||||
* ## Example
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: Typically directives are registered with `module.directive`. The example below is
|
||||
@@ -567,7 +680,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
Suffix = 'Directive',
|
||||
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
|
||||
CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
|
||||
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
|
||||
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
|
||||
REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
|
||||
|
||||
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
|
||||
// The assumption is that future DOM event attribute names will begin with
|
||||
@@ -872,10 +986,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
// sanitize a[href] and img[src] values
|
||||
if ((nodeName === 'a' && key === 'href') ||
|
||||
(nodeName === 'img' && key === 'src')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = $$sanitizeUri(value, key === 'src');
|
||||
} else if (nodeName === 'img' && key === 'srcset') {
|
||||
// sanitize img[srcset] values
|
||||
var result = "";
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
var trimmedSrcset = trim(value);
|
||||
// ( 999x ,| 999w ,| ,|, )
|
||||
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
|
||||
var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
|
||||
|
||||
// split srcset into tuple of uri and descriptor except for the last item
|
||||
var rawUris = trimmedSrcset.split(pattern);
|
||||
|
||||
// for each tuples
|
||||
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
|
||||
for (var i=0; i<nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i*2;
|
||||
// sanitize the uri
|
||||
result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
|
||||
// add the descriptor
|
||||
result += ( " " + trim(rawUris[innerIdx+1]));
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i*2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $$sanitizeUri(trim(lastTuple[0]), true);
|
||||
|
||||
// and add the last descriptor if any
|
||||
if( lastTuple.length === 2) {
|
||||
result += (" " + trim(lastTuple[1]));
|
||||
}
|
||||
this[key] = value = result;
|
||||
}
|
||||
|
||||
if (writeAttr !== false) {
|
||||
@@ -913,12 +1061,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* @param {string} key Normalized key. (ie ngAttribute) .
|
||||
* @param {function(interpolatedValue)} fn Function that will be called whenever
|
||||
the interpolated value of the attribute changes.
|
||||
* See the {@link guide/directive#Attributes Directives} guide for more info.
|
||||
* See {@link ng.$compile#attributes $compile} for more info.
|
||||
* @returns {function()} Returns a deregistration function for this observer.
|
||||
*/
|
||||
$observe: function(key, fn) {
|
||||
var attrs = this,
|
||||
$$observers = (attrs.$$observers || (attrs.$$observers = {})),
|
||||
$$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
|
||||
listeners = ($$observers[key] || ($$observers[key] = []));
|
||||
|
||||
listeners.push(fn);
|
||||
@@ -994,7 +1142,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// We can not compile top level text elements since text nodes can be merged and we will
|
||||
// not be able to attach scope data to them, so we will wrap them in <span>
|
||||
forEach($compileNodes, function(node, index){
|
||||
if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
||||
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
||||
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
|
||||
}
|
||||
});
|
||||
@@ -1003,27 +1151,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
maxPriority, ignoreDirective, previousCompileContext);
|
||||
compile.$$addScopeClass($compileNodes);
|
||||
var namespace = null;
|
||||
var namespaceAdaptedCompileNodes = $compileNodes;
|
||||
var lastCompileNode;
|
||||
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
|
||||
assertArg(scope, 'scope');
|
||||
if (!namespace) {
|
||||
namespace = detectNamespaceForChildElements(futureParentElement);
|
||||
}
|
||||
if (namespace !== 'html' && $compileNodes[0] !== lastCompileNode) {
|
||||
namespaceAdaptedCompileNodes = jqLite(
|
||||
var $linkNode;
|
||||
if (namespace !== 'html') {
|
||||
// When using a directive with replace:true and templateUrl the $compileNodes
|
||||
// (or a child element inside of them)
|
||||
// might change, so we need to recreate the namespace adapted compileNodes
|
||||
// for call to the link function.
|
||||
// Note: This will already clone the nodes...
|
||||
$linkNode = jqLite(
|
||||
wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
|
||||
);
|
||||
} else if (cloneConnectFn) {
|
||||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
||||
// and sometimes changes the structure of the DOM.
|
||||
$linkNode = JQLitePrototype.clone.call($compileNodes);
|
||||
} else {
|
||||
$linkNode = $compileNodes;
|
||||
}
|
||||
// When using a directive with replace:true and templateUrl the $compileNodes
|
||||
// might change, so we need to recreate the namespace adapted compileNodes.
|
||||
lastCompileNode = $compileNodes[0];
|
||||
|
||||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
||||
// and sometimes changes the structure of the DOM.
|
||||
var $linkNode = cloneConnectFn
|
||||
? JQLitePrototype.clone.call(namespaceAdaptedCompileNodes) // IMPORTANT!!!
|
||||
: namespaceAdaptedCompileNodes;
|
||||
|
||||
if (transcludeControllers) {
|
||||
for (var controllerName in transcludeControllers) {
|
||||
@@ -1166,20 +1315,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) {
|
||||
|
||||
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement) {
|
||||
var scopeCreated = false;
|
||||
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
|
||||
|
||||
if (!transcludedScope) {
|
||||
transcludedScope = scope.$new();
|
||||
transcludedScope = scope.$new(false, containingScope);
|
||||
transcludedScope.$$transcluded = true;
|
||||
scopeCreated = true;
|
||||
}
|
||||
|
||||
var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
|
||||
if (scopeCreated && !elementTransclusion) {
|
||||
clone.on('$destroy', function() { transcludedScope.$destroy(); });
|
||||
}
|
||||
return clone;
|
||||
return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
|
||||
};
|
||||
|
||||
return boundTranscludeFn;
|
||||
@@ -1202,7 +1345,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
className;
|
||||
|
||||
switch(nodeType) {
|
||||
case 1: /* Element */
|
||||
case NODE_TYPE_ELEMENT: /* Element */
|
||||
// use the node name: <directive>
|
||||
addDirective(directives,
|
||||
directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
|
||||
@@ -1214,37 +1357,35 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var attrEndName = false;
|
||||
|
||||
attr = nAttrs[j];
|
||||
if (!msie || msie >= 8 || attr.specified) {
|
||||
name = attr.name;
|
||||
value = trim(attr.value);
|
||||
name = attr.name;
|
||||
value = trim(attr.value);
|
||||
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
name = snake_case(ngAttrName.substr(6), '-');
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
if (directiveIsMultiElement(directiveNName)) {
|
||||
if (ngAttrName === directiveNName + 'Start') {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
attrs[nName] = value;
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
}
|
||||
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
attrEndName);
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
name = snake_case(ngAttrName.substr(6), '-');
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
if (directiveIsMultiElement(directiveNName)) {
|
||||
if (ngAttrName === directiveNName + 'Start') {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
attrsMap[nName] = name;
|
||||
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
|
||||
attrs[nName] = value;
|
||||
if (getBooleanAttrName(node, nName)) {
|
||||
attrs[nName] = true; // presence means true
|
||||
}
|
||||
}
|
||||
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
|
||||
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
|
||||
attrEndName);
|
||||
}
|
||||
|
||||
// use class as directive
|
||||
@@ -1259,10 +1400,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3: /* Text Node */
|
||||
case NODE_TYPE_TEXT: /* Text Node */
|
||||
addTextInterpolateDirective(directives, node.nodeValue);
|
||||
break;
|
||||
case 8: /* Comment */
|
||||
case NODE_TYPE_COMMENT: /* Comment */
|
||||
try {
|
||||
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
|
||||
if (match) {
|
||||
@@ -1302,7 +1443,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
"Unterminated attribute, found '{0}' but no matching '{1}' found.",
|
||||
attrStart, attrEnd);
|
||||
}
|
||||
if (node.nodeType == 1 /** Element **/) {
|
||||
if (node.nodeType == NODE_TYPE_ELEMENT) {
|
||||
if (node.hasAttribute(attrStart)) depth++;
|
||||
if (node.hasAttribute(attrEnd)) depth--;
|
||||
}
|
||||
@@ -1481,11 +1622,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (jqLiteIsTextNode(directiveValue)) {
|
||||
$template = [];
|
||||
} else {
|
||||
$template = jqLite(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
|
||||
$template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
|
||||
}
|
||||
compileNode = $template[0];
|
||||
|
||||
if ($template.length != 1 || compileNode.nodeType !== 1) {
|
||||
if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
|
||||
throw $compileMinErr('tplrt',
|
||||
"Template for directive '{0}' must have exactly one root element. {1}",
|
||||
directiveName, '');
|
||||
@@ -1589,14 +1730,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
function getControllers(directiveName, require, $element, elementControllers) {
|
||||
var value, retrievalMethod = 'data', optional = false;
|
||||
var $searchElement = $element;
|
||||
var match;
|
||||
if (isString(require)) {
|
||||
while((value = require.charAt(0)) == '^' || value == '?') {
|
||||
require = require.substr(1);
|
||||
if (value == '^') {
|
||||
retrievalMethod = 'inheritedData';
|
||||
}
|
||||
optional = optional || value == '?';
|
||||
match = require.match(REQUIRE_PREFIX_REGEXP);
|
||||
require = require.substring(match[0].length);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
value = null;
|
||||
|
||||
if (elementControllers && retrievalMethod === 'data') {
|
||||
@@ -1604,7 +1757,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
value = value.instance;
|
||||
}
|
||||
}
|
||||
value = value || $element[retrievalMethod]('$' + require + 'Controller');
|
||||
value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
|
||||
|
||||
if (!value && !optional) {
|
||||
throw $compileMinErr('ctreq',
|
||||
@@ -1810,7 +1963,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (!futureParentElement) {
|
||||
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
|
||||
}
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement);
|
||||
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1951,11 +2104,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (jqLiteIsTextNode(content)) {
|
||||
$template = [];
|
||||
} else {
|
||||
$template = jqLite(wrapTemplate(templateNamespace, trim(content)));
|
||||
$template = removeComments(wrapTemplate(templateNamespace, trim(content)));
|
||||
}
|
||||
compileNode = $template[0];
|
||||
|
||||
if ($template.length != 1 || compileNode.nodeType !== 1) {
|
||||
if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
|
||||
throw $compileMinErr('tplrt',
|
||||
"Template for directive '{0}' must have exactly one root element. {1}",
|
||||
origAsyncDirective.name, templateUrl);
|
||||
@@ -1994,6 +2147,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
boundTranscludeFn = linkQueue.shift(),
|
||||
linkNode = $compileNode[0];
|
||||
|
||||
if (scope.$$destroyed) continue;
|
||||
|
||||
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
|
||||
var oldClasses = beforeTemplateLinkNode.className;
|
||||
|
||||
@@ -2020,6 +2175,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
|
||||
var childBoundTranscludeFn = boundTranscludeFn;
|
||||
if (scope.$$destroyed) return;
|
||||
if (linkQueue) {
|
||||
linkQueue.push(scope);
|
||||
linkQueue.push(node);
|
||||
@@ -2136,6 +2292,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
"ng- versions (such as ng-click instead of onclick) instead.");
|
||||
}
|
||||
|
||||
// If the attribute was removed, then we are done
|
||||
if (!attr[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we need to interpolate again, in case the attribute value has been updated
|
||||
// (e.g. by another directive's compile function)
|
||||
interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name),
|
||||
@@ -2362,3 +2523,20 @@ function tokenDifference(str1, str2) {
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function removeComments(jqNodes) {
|
||||
jqNodes = jqLite(jqNodes);
|
||||
var i = jqNodes.length;
|
||||
|
||||
if (i <= 1) {
|
||||
return jqNodes;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
var node = jqNodes[i];
|
||||
if (node.nodeType === NODE_TYPE_COMMENT) {
|
||||
splice.call(jqNodes, i, 1);
|
||||
}
|
||||
}
|
||||
return jqNodes;
|
||||
}
|
||||
|
||||
@@ -16,22 +16,6 @@
|
||||
var htmlAnchorDirective = valueFn({
|
||||
restrict: 'E',
|
||||
compile: function(element, attr) {
|
||||
|
||||
if (msie <= 8) {
|
||||
|
||||
// turn <a href ng-click="..">link</a> into a stylable link in IE
|
||||
// but only if it doesn't have name attribute, in which case it's an anchor
|
||||
if (!attr.href && !attr.name) {
|
||||
attr.$set('href', '');
|
||||
}
|
||||
|
||||
// add a comment node to anchors to workaround IE bug that causes element content to be reset
|
||||
// to new attribute content if attribute is updated with value containing @ and element also
|
||||
// contains value with @
|
||||
// see issue #1949
|
||||
element.append(document.createComment('IE fix'));
|
||||
}
|
||||
|
||||
if (!attr.href && !attr.xlinkHref && !attr.name) {
|
||||
return function(scope, element) {
|
||||
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
|
||||
|
||||
+58
-16
@@ -4,16 +4,19 @@
|
||||
*/
|
||||
var nullFormCtrl = {
|
||||
$addControl: noop,
|
||||
$$renameControl: nullFormRenameControl,
|
||||
$removeControl: noop,
|
||||
$setValidity: noop,
|
||||
$$setPending: noop,
|
||||
$setDirty: noop,
|
||||
$setPristine: noop,
|
||||
$setSubmitted: noop,
|
||||
$$clearControlValidity: noop
|
||||
$setSubmitted: noop
|
||||
},
|
||||
SUBMITTED_CLASS = 'ng-submitted';
|
||||
|
||||
function nullFormRenameControl(control, name) {
|
||||
control.$name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc type
|
||||
* @name form.FormController
|
||||
@@ -51,17 +54,18 @@ SUBMITTED_CLASS = 'ng-submitted';
|
||||
*
|
||||
*/
|
||||
//asks for $scope to fool the BC controller module
|
||||
FormController.$inject = ['$element', '$attrs', '$scope', '$animate'];
|
||||
function FormController(element, attrs, $scope, $animate) {
|
||||
FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
|
||||
function FormController(element, attrs, $scope, $animate, $interpolate) {
|
||||
var form = this,
|
||||
parentForm = element.parent().controller('form') || nullFormCtrl,
|
||||
controls = [];
|
||||
|
||||
var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
|
||||
|
||||
// init state
|
||||
form.$error = {};
|
||||
form.$$success = {};
|
||||
form.$pending = undefined;
|
||||
form.$name = attrs.name || attrs.ngForm;
|
||||
form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
|
||||
form.$dirty = false;
|
||||
form.$pristine = true;
|
||||
form.$valid = true;
|
||||
@@ -70,9 +74,6 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
|
||||
parentForm.$addControl(form);
|
||||
|
||||
// Setup initial state of the control
|
||||
element.addClass(PRISTINE_CLASS);
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$rollbackViewValue
|
||||
@@ -127,6 +128,17 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
}
|
||||
};
|
||||
|
||||
// Private API: rename a form control
|
||||
form.$$renameControl = function(control, newName) {
|
||||
var oldName = control.$name;
|
||||
|
||||
if (form[oldName] === control) {
|
||||
delete form[oldName];
|
||||
}
|
||||
form[newName] = control;
|
||||
control.$name = newName;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$removeControl
|
||||
@@ -230,6 +242,25 @@ function FormController(element, attrs, $scope, $animate) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$setUntouched
|
||||
*
|
||||
* @description
|
||||
* Sets the form to its untouched state.
|
||||
*
|
||||
* This method can be called to remove the 'ng-touched' class and set the form controls to their
|
||||
* untouched state (ng-untouched class).
|
||||
*
|
||||
* Setting a form controls back to their untouched state is often useful when setting the form
|
||||
* back to its pristine state.
|
||||
*/
|
||||
form.$setUntouched = function () {
|
||||
forEach(controls, function(control) {
|
||||
control.$setUntouched();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name form.FormController#$setSubmitted
|
||||
@@ -415,10 +446,14 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
name: 'form',
|
||||
restrict: isNgForm ? 'EAC' : 'E',
|
||||
controller: FormController,
|
||||
compile: function() {
|
||||
compile: function ngFormCompile(formElement) {
|
||||
// Setup initial state of the control
|
||||
formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
|
||||
|
||||
return {
|
||||
pre: function(scope, formElement, attr, controller) {
|
||||
if (!attr.action) {
|
||||
pre: function ngFormPreLink(scope, formElement, attr, controller) {
|
||||
// if `action` attr is not present on the form, prevent the default action (submission)
|
||||
if (!('action' in attr)) {
|
||||
// we can't use jq events because if a form is destroyed during submission the default
|
||||
// action is not prevented. see #1238
|
||||
//
|
||||
@@ -447,13 +482,20 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
});
|
||||
}
|
||||
|
||||
var parentFormCtrl = formElement.parent().controller('form'),
|
||||
alias = attr.name || attr.ngForm;
|
||||
var parentFormCtrl = controller.$$parentForm,
|
||||
alias = controller.$name;
|
||||
|
||||
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 (parentFormCtrl) {
|
||||
if (parentFormCtrl !== nullFormCtrl) {
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
if (alias) {
|
||||
|
||||
+90
-60
@@ -14,10 +14,10 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
||||
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
|
||||
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
|
||||
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d))?$/;
|
||||
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
||||
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
|
||||
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
|
||||
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d))?$/;
|
||||
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
|
||||
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
|
||||
|
||||
var $ngModelMinErr = new minErr('ngModel');
|
||||
@@ -281,8 +281,8 @@ var inputType = {
|
||||
</example>
|
||||
*/
|
||||
'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
|
||||
createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss']),
|
||||
'yyyy-MM-ddTHH:mm:ss'),
|
||||
createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
|
||||
'yyyy-MM-ddTHH:mm:ss.sss'),
|
||||
|
||||
/**
|
||||
* @ngdoc input
|
||||
@@ -370,8 +370,8 @@ var inputType = {
|
||||
</example>
|
||||
*/
|
||||
'time': createDateInputType('time', TIME_REGEXP,
|
||||
createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss']),
|
||||
'HH:mm:ss'),
|
||||
createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
|
||||
'HH:mm:ss.sss'),
|
||||
|
||||
/**
|
||||
* @ngdoc input
|
||||
@@ -1067,7 +1067,7 @@ function createDateParser(regexp, mapping) {
|
||||
HH: date.getHours(),
|
||||
mm: date.getMinutes(),
|
||||
ss: date.getSeconds(),
|
||||
sss: date.getMilliseconds()
|
||||
sss: date.getMilliseconds() / 1000
|
||||
};
|
||||
} else {
|
||||
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
|
||||
@@ -1078,7 +1078,7 @@ function createDateParser(regexp, mapping) {
|
||||
map[mapping[index]] = +part;
|
||||
}
|
||||
});
|
||||
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss || 0);
|
||||
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1091,16 +1091,15 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
badInputChecker(scope, element, attr, ctrl);
|
||||
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
|
||||
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
|
||||
var previousDate;
|
||||
|
||||
ctrl.$$parserName = type;
|
||||
ctrl.$parsers.push(function(value) {
|
||||
if (ctrl.$isEmpty(value)) return null;
|
||||
if (regexp.test(value)) {
|
||||
var previousDate = ctrl.$modelValue;
|
||||
if (previousDate && timezone === 'UTC') {
|
||||
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
|
||||
previousDate = new Date(previousDate.getTime() + timezoneOffset);
|
||||
}
|
||||
// Note: We cannot read ctrl.$modelValue, as there might be a different
|
||||
// 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());
|
||||
@@ -1111,8 +1110,18 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
});
|
||||
|
||||
ctrl.$formatters.push(function(value) {
|
||||
if (isDate(value)) {
|
||||
if (!ctrl.$isEmpty(value)) {
|
||||
if (!isDate(value)) {
|
||||
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
|
||||
}
|
||||
previousDate = value;
|
||||
if (previousDate && timezone === 'UTC') {
|
||||
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
|
||||
previousDate = new Date(previousDate.getTime() + timezoneOffset);
|
||||
}
|
||||
return $filter('date')(value, format, timezone);
|
||||
} else {
|
||||
previousDate = null;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
@@ -1138,6 +1147,11 @@ function createDateInputType(type, regexp, parseDate, format) {
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
// Override the standard $isEmpty to detect invalid dates as well
|
||||
ctrl.$isEmpty = function(value) {
|
||||
// Invalid Date: getTime() returns NaN
|
||||
return !value || (value.getTime && value.getTime() !== value.getTime());
|
||||
};
|
||||
|
||||
function parseObservedDateValue(val) {
|
||||
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
|
||||
@@ -1452,10 +1466,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: ['?ngModel'],
|
||||
link: function(scope, element, attr, ctrls) {
|
||||
if (ctrls[0]) {
|
||||
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
|
||||
$browser, $filter, $parse);
|
||||
link: {
|
||||
pre: function(scope, element, attr, ctrls) {
|
||||
if (ctrls[0]) {
|
||||
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
|
||||
$browser, $filter, $parse);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1657,8 +1673,8 @@ var VALID_CLASS = 'ng-valid',
|
||||
*
|
||||
*
|
||||
*/
|
||||
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q',
|
||||
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q) {
|
||||
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
|
||||
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
|
||||
this.$viewValue = Number.NaN;
|
||||
this.$modelValue = Number.NaN;
|
||||
this.$validators = {};
|
||||
@@ -1675,7 +1691,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
this.$error = {}; // keep invalid keys here
|
||||
this.$$success = {}; // keep valid keys here
|
||||
this.$pending = undefined; // keep pending keys here
|
||||
this.$name = $attr.name;
|
||||
this.$name = $interpolate($attr.name || '', false)($scope);
|
||||
|
||||
|
||||
var parsedNgModel = $parse($attr.ngModel),
|
||||
@@ -1756,11 +1772,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
|
||||
currentValidationRunId = 0;
|
||||
|
||||
// Setup initial state of the control
|
||||
$element
|
||||
.addClass(PRISTINE_CLASS)
|
||||
.addClass(UNTOUCHED_CLASS);
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ngModel.NgModelController#$setValidity
|
||||
@@ -2053,14 +2064,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
|
||||
};
|
||||
|
||||
this.$$parseAndValidate = function() {
|
||||
var parserValid = true,
|
||||
viewValue = ctrl.$$lastCommittedViewValue,
|
||||
modelValue = viewValue;
|
||||
for(var i = 0; i < ctrl.$parsers.length; i++) {
|
||||
modelValue = ctrl.$parsers[i](modelValue);
|
||||
if (isUndefined(modelValue)) {
|
||||
parserValid = false;
|
||||
break;
|
||||
var viewValue = ctrl.$$lastCommittedViewValue;
|
||||
var modelValue = viewValue;
|
||||
var parserValid = isUndefined(modelValue) ? undefined : true;
|
||||
|
||||
if (parserValid) {
|
||||
for(var i = 0; i < ctrl.$parsers.length; i++) {
|
||||
modelValue = ctrl.$parsers[i](modelValue);
|
||||
if (isUndefined(modelValue)) {
|
||||
parserValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
|
||||
@@ -2377,36 +2391,51 @@ var ngModelDirective = function() {
|
||||
restrict: 'A',
|
||||
require: ['ngModel', '^?form', '^?ngModelOptions'],
|
||||
controller: NgModelController,
|
||||
link: {
|
||||
pre: function(scope, element, attr, ctrls) {
|
||||
var modelCtrl = ctrls[0],
|
||||
formCtrl = ctrls[1] || nullFormCtrl;
|
||||
// Prelink needs to run before any input directive
|
||||
// so that we can set the NgModelOptions in NgModelController
|
||||
// before anyone else uses it.
|
||||
priority: 1,
|
||||
compile: function ngModelCompile(element) {
|
||||
// Setup initial state of the control
|
||||
element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
|
||||
|
||||
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
|
||||
return {
|
||||
pre: function ngModelPreLink(scope, element, attr, ctrls) {
|
||||
var modelCtrl = ctrls[0],
|
||||
formCtrl = ctrls[1] || nullFormCtrl;
|
||||
|
||||
// notify others, especially parent forms
|
||||
formCtrl.$addControl(modelCtrl);
|
||||
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
formCtrl.$removeControl(modelCtrl);
|
||||
});
|
||||
},
|
||||
post: function(scope, element, attr, ctrls) {
|
||||
var modelCtrl = ctrls[0];
|
||||
if (modelCtrl.$options && modelCtrl.$options.updateOn) {
|
||||
element.on(modelCtrl.$options.updateOn, function(ev) {
|
||||
modelCtrl.$$debounceViewValueCommit(ev && ev.type);
|
||||
// notify others, especially parent forms
|
||||
formCtrl.$addControl(modelCtrl);
|
||||
|
||||
attr.$observe('name', function(newValue) {
|
||||
if (modelCtrl.$name !== newValue) {
|
||||
formCtrl.$$renameControl(modelCtrl, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
formCtrl.$removeControl(modelCtrl);
|
||||
});
|
||||
},
|
||||
post: function ngModelPostLink(scope, element, attr, ctrls) {
|
||||
var modelCtrl = ctrls[0];
|
||||
if (modelCtrl.$options && modelCtrl.$options.updateOn) {
|
||||
element.on(modelCtrl.$options.updateOn, function(ev) {
|
||||
modelCtrl.$$debounceViewValueCommit(ev && ev.type);
|
||||
});
|
||||
}
|
||||
|
||||
element.on('blur', function(ev) {
|
||||
if (modelCtrl.$touched) return;
|
||||
|
||||
scope.$apply(function() {
|
||||
modelCtrl.$setTouched();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
element.on('blur', function(ev) {
|
||||
if (modelCtrl.$touched) return;
|
||||
|
||||
scope.$apply(function() {
|
||||
modelCtrl.$setTouched();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -2961,8 +2990,9 @@ function addSetValidityMethod(context) {
|
||||
parentForm = context.parentForm,
|
||||
$animate = context.$animate;
|
||||
|
||||
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
|
||||
|
||||
ctrl.$setValidity = setValidity;
|
||||
toggleValidationCss('', true);
|
||||
|
||||
function setValidity(validationErrorKey, state, options) {
|
||||
if (state === undefined) {
|
||||
|
||||
@@ -58,11 +58,9 @@ var ngBindDirective = ['$compile', function($compile) {
|
||||
$compile.$$addBindingClass(templateElement);
|
||||
return function ngBindLink(scope, element, attr) {
|
||||
$compile.$$addBindingInfo(element, attr.ngBind);
|
||||
element = element[0];
|
||||
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
|
||||
// We are purposefully using == here rather than === because we want to
|
||||
// catch when value is "null or undefined"
|
||||
// jshint -W041
|
||||
element.text(value == undefined ? '' : value);
|
||||
element.textContent = value === undefined ? '' : value;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -128,8 +126,9 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
|
||||
return function ngBindTemplateLink(scope, element, attr) {
|
||||
var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
|
||||
$compile.$$addBindingInfo(element, interpolateFn.expressions);
|
||||
element = element[0];
|
||||
attr.$observe('ngBindTemplate', function(value) {
|
||||
element.text(value);
|
||||
element.textContent = value === undefined ? '' : value;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -146,7 +145,10 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
|
||||
* element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
|
||||
* ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
|
||||
* is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
|
||||
* core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
|
||||
* include "angular-sanitize.js" in your application.
|
||||
*
|
||||
* You may also bypass sanitization for values you know are safe. To do so, bind to
|
||||
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
|
||||
* under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
|
||||
*
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*
|
||||
* @element ANY
|
||||
* @scope
|
||||
* @priority 500
|
||||
* @param {expression} ngController Name of a constructor function registered with the current
|
||||
* {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
|
||||
* that on the current scope evaluates to a constructor function.
|
||||
|
||||
+119
-1
@@ -49,7 +49,125 @@
|
||||
...
|
||||
</html>
|
||||
```
|
||||
*/
|
||||
* @example
|
||||
// Note: the suffix `.csp` in the example name triggers
|
||||
// csp mode in our http server!
|
||||
<example name="example.csp" module="cspExample" ng-csp="true">
|
||||
<file name="index.html">
|
||||
<div ng-controller="MainController as ctrl">
|
||||
<div>
|
||||
<button ng-click="ctrl.inc()" id="inc">Increment</button>
|
||||
<span id="counter">
|
||||
{{ctrl.counter}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button ng-click="ctrl.evil()" id="evil">Evil</button>
|
||||
<span id="evilError">
|
||||
{{ctrl.evilError}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</file>
|
||||
<file name="script.js">
|
||||
angular.module('cspExample', [])
|
||||
.controller('MainController', function() {
|
||||
this.counter = 0;
|
||||
this.inc = function() {
|
||||
this.counter++;
|
||||
};
|
||||
this.evil = function() {
|
||||
// jshint evil:true
|
||||
try {
|
||||
eval('1+2');
|
||||
} catch (e) {
|
||||
this.evilError = e.message;
|
||||
}
|
||||
};
|
||||
});
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
var util, webdriver;
|
||||
|
||||
var incBtn = element(by.id('inc'));
|
||||
var counter = element(by.id('counter'));
|
||||
var evilBtn = element(by.id('evil'));
|
||||
var evilError = element(by.id('evilError'));
|
||||
|
||||
function getAndClearSevereErrors() {
|
||||
return browser.manage().logs().get('browser').then(function(browserLog) {
|
||||
return browserLog.filter(function(logEntry) {
|
||||
return logEntry.level.value > webdriver.logging.Level.WARNING.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearErrors() {
|
||||
getAndClearSevereErrors();
|
||||
}
|
||||
|
||||
function expectNoErrors() {
|
||||
getAndClearSevereErrors().then(function(filteredLog) {
|
||||
expect(filteredLog.length).toEqual(0);
|
||||
if (filteredLog.length) {
|
||||
console.log('browser console errors: ' + util.inspect(filteredLog));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function expectError(regex) {
|
||||
getAndClearSevereErrors().then(function(filteredLog) {
|
||||
var found = false;
|
||||
filteredLog.forEach(function(log) {
|
||||
if (log.message.match(regex)) {
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
throw new Error('expected an error that matches ' + regex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
util = require('util');
|
||||
webdriver = require('protractor/node_modules/selenium-webdriver');
|
||||
});
|
||||
|
||||
// For now, we only test on Chrome,
|
||||
// as Safari does not load the page with Protractor's injected scripts,
|
||||
// and Firefox webdriver always disables content security policy (#6358)
|
||||
if (browser.params.browser !== 'chrome') {
|
||||
return;
|
||||
}
|
||||
|
||||
it('should not report errors when the page is loaded', function() {
|
||||
// clear errors so we are not dependent on previous tests
|
||||
clearErrors();
|
||||
// Need to reload the page as the page is already loaded when
|
||||
// we come here
|
||||
browser.driver.getCurrentUrl().then(function(url) {
|
||||
browser.get(url);
|
||||
});
|
||||
expectNoErrors();
|
||||
});
|
||||
|
||||
it('should evaluate expressions', function() {
|
||||
expect(counter.getText()).toEqual('0');
|
||||
incBtn.click();
|
||||
expect(counter.getText()).toEqual('1');
|
||||
expectNoErrors();
|
||||
});
|
||||
|
||||
it('should throw and report an error when using "eval"', function() {
|
||||
evilBtn.click();
|
||||
expect(evilError.getText()).toMatch(/Content Security Policy/);
|
||||
expectError(/Content Security Policy/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
|
||||
// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
|
||||
// bootstrap the system (before $parse is instantiated), for this reason we just have
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
|
||||
* is created when the element is restored. The scope created within `ngIf` inherits from
|
||||
* its parent scope using
|
||||
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance).
|
||||
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
|
||||
* An important implication of this is if `ngModel` is used within `ngIf` to bind to
|
||||
* a javascript primitive defined in the parent scope. In this case any modifications made to the
|
||||
* variable within the child scope will override (hide) the value in the parent scope.
|
||||
@@ -33,8 +33,8 @@
|
||||
* and `leave` effects.
|
||||
*
|
||||
* @animations
|
||||
* enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container
|
||||
* leave - happens just before the ngIf contents are removed from the DOM
|
||||
* enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
|
||||
* leave - happens just before the `ngIf` contents are removed from the DOM
|
||||
*
|
||||
* @element ANY
|
||||
* @scope
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var NG_HIDE_CLASS = 'ng-hide';
|
||||
var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngShow
|
||||
@@ -161,7 +163,11 @@ var ngShowDirective = ['$animate', function($animate) {
|
||||
multiElement: true,
|
||||
link: function(scope, element, attr) {
|
||||
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
|
||||
$animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide');
|
||||
// we're adding a temporary, animation-specific class for ng-hide since this way
|
||||
// we can control when the element is actually displayed on screen without having
|
||||
// to have a global/greedy CSS selector that breaks when other animations are run.
|
||||
// Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
|
||||
$animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -316,7 +322,9 @@ var ngHideDirective = ['$animate', function($animate) {
|
||||
multiElement: true,
|
||||
link: function(scope, element, attr) {
|
||||
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
|
||||
$animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide');
|
||||
// The comment inside of the ngShowDirective explains why we add and
|
||||
// remove a temporary class for the show/hide animation
|
||||
$animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+150
-123
@@ -35,6 +35,12 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* be bound to string values at present.
|
||||
* </div>
|
||||
*
|
||||
* <div class="alert alert-info">
|
||||
* **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
|
||||
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
|
||||
* or property name (for object data sources) of the value within the collection.
|
||||
* </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.
|
||||
* @param {string=} required The control is considered valid only if value is entered.
|
||||
@@ -69,7 +75,25 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* DOM element.
|
||||
* * `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`).
|
||||
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
|
||||
* even when the options are recreated (e.g. reloaded from the server).
|
||||
|
||||
* <div class="alert alert-info">
|
||||
* **Note:** Using `select as` together with `trackexpr` is not possible (and will throw).
|
||||
* Reasoning:
|
||||
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItemß'}}],
|
||||
* $scope.selected = {name: 'aSubItem'};
|
||||
* - track by is always applied to `value`, with purpose to preserve the selection,
|
||||
* (to `item` in this case)
|
||||
* - to calculate whether an item is selected we do the following:
|
||||
* 1. apply `track by` to the values in the array, e.g.
|
||||
* In the example: [1,2]
|
||||
* 2. apply `track by` to the already selected value in `ngModel`:
|
||||
* In the example: this is not possible, as `track by` refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: aSubItem}`.
|
||||
*
|
||||
* </div>
|
||||
*
|
||||
* @example
|
||||
<example module="selectExample">
|
||||
@@ -326,6 +350,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
var displayFn = $parse(match[2] || match[1]),
|
||||
valueName = match[4] || match[6],
|
||||
selectAs = / as /.test(match[0]) && match[1],
|
||||
selectAsFn = selectAs ? $parse(selectAs) : null,
|
||||
keyName = match[5],
|
||||
groupByFn = $parse(match[3] || ''),
|
||||
valueFn = $parse(match[2] ? match[1] : valueName),
|
||||
@@ -336,7 +362,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// We try to reuse these if possible
|
||||
// - optionGroupsCache[0] is the options with no option group
|
||||
// - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
|
||||
optionGroupsCache = [[{element: selectElement, label:''}]];
|
||||
optionGroupsCache = [[{element: selectElement, label:''}]],
|
||||
//re-usable object to represent option's locals
|
||||
locals = {};
|
||||
|
||||
if (trackFn && selectAsFn) {
|
||||
throw ngOptionsMinErr('trkslct',
|
||||
"Comprehension expression cannot contain both selectAs '{0}' " +
|
||||
"and trackBy '{1}' expressions.",
|
||||
selectAs, track);
|
||||
}
|
||||
|
||||
if (nullOption) {
|
||||
// compile the element since there might be bindings in it
|
||||
@@ -354,103 +389,110 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// clear contents, we'll add what's needed based on the model
|
||||
selectElement.empty();
|
||||
|
||||
selectElement.on('change', function() {
|
||||
scope.$apply(function() {
|
||||
var optionGroup,
|
||||
collection = valuesFn(scope) || [],
|
||||
locals = {},
|
||||
key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
|
||||
|
||||
if (multiple) {
|
||||
value = [];
|
||||
for (groupIndex = 0, groupLength = optionGroupsCache.length;
|
||||
groupIndex < groupLength;
|
||||
groupIndex++) {
|
||||
// list of options for that group. (first item has the parent)
|
||||
optionGroup = optionGroupsCache[groupIndex];
|
||||
|
||||
for(index = 1, length = optionGroup.length; index < length; index++) {
|
||||
if ((optionElement = optionGroup[index].element)[0].selected) {
|
||||
key = optionElement.val();
|
||||
if (keyName) locals[keyName] = key;
|
||||
if (trackFn) {
|
||||
for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
|
||||
locals[valueName] = collection[trackIndex];
|
||||
if (trackFn(scope, locals) == key) break;
|
||||
}
|
||||
} else {
|
||||
locals[valueName] = collection[key];
|
||||
}
|
||||
value.push(valueFn(scope, locals));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
key = selectElement.val();
|
||||
if (key == '?') {
|
||||
value = undefined;
|
||||
} else if (key === ''){
|
||||
value = null;
|
||||
} else {
|
||||
if (trackFn) {
|
||||
for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
|
||||
locals[valueName] = collection[trackIndex];
|
||||
if (trackFn(scope, locals) == key) {
|
||||
value = valueFn(scope, locals);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locals[valueName] = collection[key];
|
||||
if (keyName) locals[keyName] = key;
|
||||
value = valueFn(scope, locals);
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl.$setViewValue(value);
|
||||
render();
|
||||
});
|
||||
});
|
||||
selectElement.on('change', selectionChanged);
|
||||
|
||||
ctrl.$render = render;
|
||||
|
||||
scope.$watchCollection(valuesFn, scheduleRendering);
|
||||
scope.$watchCollection(function () {
|
||||
var locals = {},
|
||||
values = valuesFn(scope);
|
||||
if (values) {
|
||||
var toDisplay = new Array(values.length);
|
||||
for (var i = 0, ii = values.length; i < ii; i++) {
|
||||
locals[valueName] = values[i];
|
||||
toDisplay[i] = displayFn(scope, locals);
|
||||
}
|
||||
return toDisplay;
|
||||
}
|
||||
}, scheduleRendering);
|
||||
scope.$watchCollection(getLabels, scheduleRendering);
|
||||
|
||||
if (multiple) {
|
||||
scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
function getSelectedSet() {
|
||||
var selectedSet = false;
|
||||
if (multiple) {
|
||||
var modelValue = ctrl.$modelValue;
|
||||
if (trackFn && isArray(modelValue)) {
|
||||
selectedSet = new HashMap([]);
|
||||
var locals = {};
|
||||
for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) {
|
||||
locals[valueName] = modelValue[trackIndex];
|
||||
selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]);
|
||||
}
|
||||
} else {
|
||||
selectedSet = new HashMap(modelValue);
|
||||
}
|
||||
}
|
||||
return selectedSet;
|
||||
function callExpression(exprFn, key, value) {
|
||||
locals[valueName] = value;
|
||||
if (keyName) locals[keyName] = key;
|
||||
return exprFn(scope, locals);
|
||||
}
|
||||
|
||||
function selectionChanged() {
|
||||
scope.$apply(function() {
|
||||
var optionGroup,
|
||||
collection = valuesFn(scope) || [],
|
||||
key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
|
||||
var viewValue;
|
||||
if (multiple) {
|
||||
viewValue = [];
|
||||
forEach(selectElement.val(), function(selectedKey) {
|
||||
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
|
||||
});
|
||||
} else {
|
||||
var selectedKey = selectElement.val();
|
||||
viewValue = getViewValue(selectedKey, collection[selectedKey]);
|
||||
}
|
||||
ctrl.$setViewValue(viewValue);
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function getViewValue(key, value) {
|
||||
if (key === '?') {
|
||||
return undefined;
|
||||
} else if (key === '') {
|
||||
return null;
|
||||
} else {
|
||||
var viewValueFn = selectAsFn ? selectAsFn : valueFn;
|
||||
return callExpression(viewValueFn, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
var values = valuesFn(scope);
|
||||
var toDisplay;
|
||||
if (values && isArray(values)) {
|
||||
toDisplay = new Array(values.length);
|
||||
for (var i = 0, ii = values.length; i < ii; i++) {
|
||||
toDisplay[i] = callExpression(displayFn, i, values[i]);
|
||||
}
|
||||
return toDisplay;
|
||||
} else if (values) {
|
||||
// TODO: Add a test for this case
|
||||
toDisplay = {};
|
||||
for (var prop in values) {
|
||||
if (values.hasOwnProperty(prop)) {
|
||||
toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toDisplay;
|
||||
}
|
||||
|
||||
function createIsSelectedFn(viewValue) {
|
||||
var selectedSet;
|
||||
if (multiple) {
|
||||
if (!selectAs && trackFn && isArray(viewValue)) {
|
||||
|
||||
selectedSet = new HashMap([]);
|
||||
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
|
||||
// tracking by key
|
||||
selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
|
||||
}
|
||||
} else {
|
||||
selectedSet = new HashMap(viewValue);
|
||||
}
|
||||
} else if (!selectAsFn && trackFn) {
|
||||
viewValue = callExpression(trackFn, null, viewValue);
|
||||
}
|
||||
return function isSelected(key, value) {
|
||||
var compareValueFn;
|
||||
if (selectAsFn) {
|
||||
compareValueFn = selectAsFn;
|
||||
} else if (trackFn) {
|
||||
compareValueFn = trackFn;
|
||||
} else {
|
||||
compareValueFn = valueFn;
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
|
||||
} else {
|
||||
return viewValue == callExpression(compareValueFn, key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRendering() {
|
||||
if (!renderScheduled) {
|
||||
@@ -459,78 +501,64 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function render() {
|
||||
renderScheduled = false;
|
||||
|
||||
// Temporary location for the option groups before we render them
|
||||
// Temporary location for the option groups before we render them
|
||||
var optionGroups = {'':[]},
|
||||
optionGroupNames = [''],
|
||||
optionGroupName,
|
||||
optionGroup,
|
||||
option,
|
||||
existingParent, existingOptions, existingOption,
|
||||
modelValue = ctrl.$modelValue,
|
||||
viewValue = ctrl.$viewValue,
|
||||
values = valuesFn(scope) || [],
|
||||
keys = keyName ? sortedKeys(values) : values,
|
||||
key,
|
||||
value,
|
||||
groupLength, length,
|
||||
groupIndex, index,
|
||||
locals = {},
|
||||
selected,
|
||||
selectedSet = getSelectedSet(),
|
||||
isSelected = createIsSelectedFn(viewValue),
|
||||
anySelected = false,
|
||||
lastElement,
|
||||
element,
|
||||
label;
|
||||
|
||||
|
||||
// We now build up the list of options we need (we merge later)
|
||||
for (index = 0; length = keys.length, index < length; index++) {
|
||||
|
||||
key = index;
|
||||
if (keyName) {
|
||||
key = keys[index];
|
||||
if ( key.charAt(0) === '$' ) continue;
|
||||
locals[keyName] = key;
|
||||
}
|
||||
value = values[key];
|
||||
|
||||
locals[valueName] = values[key];
|
||||
|
||||
optionGroupName = groupByFn(scope, locals) || '';
|
||||
optionGroupName = callExpression(groupByFn, key, value) || '';
|
||||
if (!(optionGroup = optionGroups[optionGroupName])) {
|
||||
optionGroup = optionGroups[optionGroupName] = [];
|
||||
optionGroupNames.push(optionGroupName);
|
||||
}
|
||||
if (multiple) {
|
||||
selected = isDefined(
|
||||
selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals))
|
||||
);
|
||||
} else {
|
||||
if (trackFn) {
|
||||
var modelCast = {};
|
||||
modelCast[valueName] = modelValue;
|
||||
selected = trackFn(scope, modelCast) === trackFn(scope, locals);
|
||||
} else {
|
||||
selected = modelValue === valueFn(scope, locals);
|
||||
}
|
||||
selectedSet = selectedSet || selected; // see if at least one item is selected
|
||||
}
|
||||
label = displayFn(scope, locals); // what will be seen by the user
|
||||
|
||||
selected = isSelected(key, value);
|
||||
anySelected = anySelected || selected;
|
||||
|
||||
label = callExpression(displayFn, key, value); // what will be seen by the user
|
||||
|
||||
// doing displayFn(scope, locals) || '' overwrites zero values
|
||||
label = isDefined(label) ? label : '';
|
||||
optionGroup.push({
|
||||
// either the index into array or key from object
|
||||
id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index),
|
||||
id: (keyName ? keys[index] : index),
|
||||
label: label,
|
||||
selected: selected // determine if we should be selected
|
||||
});
|
||||
}
|
||||
if (!multiple) {
|
||||
if (nullOption || modelValue === null) {
|
||||
if (nullOption || viewValue === null) {
|
||||
// insert null option if we have a placeholder, or the model is null
|
||||
optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
|
||||
} else if (!selectedSet) {
|
||||
optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
|
||||
} else if (!anySelected) {
|
||||
// option could not be found, we have to insert the undefined item
|
||||
optionGroups[''].unshift({id:'?', label:'', selected:true});
|
||||
}
|
||||
@@ -611,6 +639,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
id: option.id,
|
||||
selected: option.selected
|
||||
});
|
||||
selectCtrl.addOption(option.label, element);
|
||||
if (lastElement) {
|
||||
lastElement.after(element);
|
||||
} else {
|
||||
@@ -622,7 +651,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
// remove any excessive OPTIONs in a group
|
||||
index++; // increment since the existingOptions[0] is parent element not OPTION
|
||||
while(existingOptions.length > index) {
|
||||
existingOptions.pop().element.remove();
|
||||
option = existingOptions.pop();
|
||||
selectCtrl.removeOption(option.label);
|
||||
option.element.remove();
|
||||
}
|
||||
}
|
||||
// remove any excessive OPTGROUPs from select
|
||||
@@ -658,11 +689,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
|
||||
selectCtrl = parent.data(selectCtrlName) ||
|
||||
parent.parent().data(selectCtrlName); // in case we are in optgroup
|
||||
|
||||
if (selectCtrl && selectCtrl.databound) {
|
||||
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
|
||||
// We don't want the view to drive the initialization of the model anyway.
|
||||
element.prop('selected', false);
|
||||
} else {
|
||||
if (!selectCtrl || !selectCtrl.databound) {
|
||||
selectCtrl = nullSelectCtrl;
|
||||
}
|
||||
|
||||
|
||||
+15
-14
@@ -34,9 +34,9 @@
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
Limit {{numbers}} to: <input type="integer" ng-model="numLimit">
|
||||
Limit {{numbers}} to: <input type="number" step="1" ng-model="numLimit">
|
||||
<p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
|
||||
Limit {{letters}} to: <input type="integer" ng-model="letterLimit">
|
||||
Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit">
|
||||
<p>Output letters: {{ letters | limitTo:letterLimit }}</p>
|
||||
Limit {{longNumber}} to: <input type="integer" ng-model="longNumberLimit">
|
||||
<p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
|
||||
@@ -59,17 +59,18 @@
|
||||
expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
|
||||
});
|
||||
|
||||
it('should update the output when -3 is entered', function() {
|
||||
numLimitInput.clear();
|
||||
numLimitInput.sendKeys('-3');
|
||||
letterLimitInput.clear();
|
||||
letterLimitInput.sendKeys('-3');
|
||||
longNumberLimitInput.clear();
|
||||
longNumberLimitInput.sendKeys('-3');
|
||||
expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
|
||||
expect(limitedLetters.getText()).toEqual('Output letters: ghi');
|
||||
expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
|
||||
});
|
||||
// There is a bug in safari and protractor that doesn't like the minus key
|
||||
// it('should update the output when -3 is entered', function() {
|
||||
// numLimitInput.clear();
|
||||
// numLimitInput.sendKeys('-3');
|
||||
// letterLimitInput.clear();
|
||||
// letterLimitInput.sendKeys('-3');
|
||||
// longNumberLimitInput.clear();
|
||||
// longNumberLimitInput.sendKeys('-3');
|
||||
// expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
|
||||
// expect(limitedLetters.getText()).toEqual('Output letters: ghi');
|
||||
// expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
|
||||
// });
|
||||
|
||||
it('should not exceed the maximum size of input array', function() {
|
||||
numLimitInput.clear();
|
||||
@@ -84,7 +85,7 @@
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
*/
|
||||
function limitToFilter(){
|
||||
return function(input, limit) {
|
||||
if (isNumber(input)) input = input.toString();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* correctly, make sure they are actually being saved as numbers and not strings.
|
||||
*
|
||||
* @param {Array} array The array to sort.
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
|
||||
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
|
||||
* used by the comparator to determine the order of elements.
|
||||
*
|
||||
* Can be one of:
|
||||
@@ -24,10 +24,13 @@
|
||||
* is interpreted as a property name to be used in comparisons (for example `"special name"`
|
||||
* to sort object by the value of their `special name` property). An expression can be
|
||||
* optionally prefixed with `+` or `-` to control ascending or descending sort order
|
||||
* (for example, `+name` or `-name`).
|
||||
* (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
|
||||
* element itself is used to compare where sorting.
|
||||
* - `Array`: An array of function or string predicates. The first predicate in the array
|
||||
* is used for sorting, but when two items are equivalent, the next predicate is used.
|
||||
*
|
||||
* If the predicate is missing or empty then it defaults to `'+'`.
|
||||
*
|
||||
* @param {boolean=} reverse Reverse the order of the array.
|
||||
* @returns {Array} Sorted copy of the source array.
|
||||
*
|
||||
@@ -116,15 +119,21 @@ orderByFilter.$inject = ['$parse'];
|
||||
function orderByFilter($parse){
|
||||
return function(array, sortPredicate, reverseOrder) {
|
||||
if (!(isArrayLike(array))) return array;
|
||||
if (!sortPredicate) return array;
|
||||
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
|
||||
sortPredicate = map(sortPredicate, function(predicate){
|
||||
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
|
||||
sortPredicate = sortPredicate.map(function(predicate){
|
||||
var descending = false, get = predicate || identity;
|
||||
if (isString(predicate)) {
|
||||
if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
|
||||
descending = predicate.charAt(0) == '-';
|
||||
predicate = predicate.substring(1);
|
||||
}
|
||||
if ( predicate === '' ) {
|
||||
// Effectively no predicate was passed so we compare identity
|
||||
return reverseComparator(function(a,b) {
|
||||
return compare(a, b);
|
||||
}, descending);
|
||||
}
|
||||
get = $parse(predicate);
|
||||
if (get.constant) {
|
||||
var key = get();
|
||||
|
||||
+14
-9
@@ -89,7 +89,8 @@ function $HttpProvider() {
|
||||
var JSON_START = /^\s*(\[|\{[^\{])/,
|
||||
JSON_END = /[\}\]]\s*$/,
|
||||
PROTECTION_PREFIX = /^\)\]\}',?\n/,
|
||||
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
|
||||
APPLICATION_JSON = 'application/json',
|
||||
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
@@ -114,12 +115,15 @@ function $HttpProvider() {
|
||||
**/
|
||||
var defaults = this.defaults = {
|
||||
// transform incoming response data
|
||||
transformResponse: [function(data) {
|
||||
transformResponse: [function defaultHttpResponseTransform(data, headers) {
|
||||
if (isString(data)) {
|
||||
// strip json vulnerability protection prefix
|
||||
data = data.replace(PROTECTION_PREFIX, '');
|
||||
if (JSON_START.test(data) && JSON_END.test(data))
|
||||
var contentType = headers('Content-Type');
|
||||
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
|
||||
(JSON_START.test(data) && JSON_END.test(data))) {
|
||||
data = fromJson(data);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}],
|
||||
@@ -662,12 +666,13 @@ function $HttpProvider() {
|
||||
expect(data.getText()).toMatch(/Hello, \$http!/);
|
||||
});
|
||||
|
||||
it('should make a JSONP request to angularjs.org', function() {
|
||||
sampleJsonpBtn.click();
|
||||
fetchBtn.click();
|
||||
expect(status.getText()).toMatch('200');
|
||||
expect(data.getText()).toMatch(/Super Hero!/);
|
||||
});
|
||||
// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
|
||||
// it('should make a JSONP request to angularjs.org', function() {
|
||||
// sampleJsonpBtn.click();
|
||||
// fetchBtn.click();
|
||||
// expect(status.getText()).toMatch('200');
|
||||
// expect(data.getText()).toMatch(/Super Hero!/);
|
||||
// });
|
||||
|
||||
it('should make JSONP request to invalid URL and invoke the error handler',
|
||||
function() {
|
||||
|
||||
+30
-60
@@ -1,17 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
function createXhr(method) {
|
||||
//if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
|
||||
//is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
|
||||
//if it is available
|
||||
if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
|
||||
!window.XMLHttpRequest)) {
|
||||
return new window.ActiveXObject("Microsoft.XMLHTTP");
|
||||
} else if (window.XMLHttpRequest) {
|
||||
return new window.XMLHttpRequest();
|
||||
}
|
||||
|
||||
throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
|
||||
function createXhr() {
|
||||
return new window.XMLHttpRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,11 +27,8 @@ function $HttpBackendProvider() {
|
||||
}
|
||||
|
||||
function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
|
||||
var ABORTED = -1;
|
||||
|
||||
// TODO(vojta): fix the signature
|
||||
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
|
||||
var status;
|
||||
$browser.$$incOutstandingRequestCount();
|
||||
url = url || $browser.url();
|
||||
|
||||
@@ -59,7 +46,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
});
|
||||
} else {
|
||||
|
||||
var xhr = createXhr(method);
|
||||
var xhr = createXhr();
|
||||
|
||||
xhr.open(method, url, true);
|
||||
forEach(headers, function(value, key) {
|
||||
@@ -68,44 +55,39 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
}
|
||||
});
|
||||
|
||||
// In IE6 and 7, this might be called synchronously when xhr.send below is called and the
|
||||
// response is in the cache. the promise api will ensure that to the app code the api is
|
||||
// always async
|
||||
xhr.onreadystatechange = function() {
|
||||
// onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
|
||||
// xhrs that are resolved while the app is in the background (see #5426).
|
||||
// since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
|
||||
// continuing
|
||||
//
|
||||
// we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
|
||||
// Safari respectively.
|
||||
if (xhr && xhr.readyState == 4) {
|
||||
var responseHeaders = null,
|
||||
response = null,
|
||||
statusText = '';
|
||||
xhr.onload = function requestLoaded() {
|
||||
var statusText = xhr.statusText || '';
|
||||
|
||||
if(status !== ABORTED) {
|
||||
responseHeaders = xhr.getAllResponseHeaders();
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
|
||||
var response = ('response' in xhr) ? xhr.response : xhr.responseText;
|
||||
|
||||
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
|
||||
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
|
||||
response = ('response' in xhr) ? xhr.response : xhr.responseText;
|
||||
}
|
||||
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
|
||||
var status = xhr.status === 1223 ? 204 : xhr.status;
|
||||
|
||||
// Accessing statusText on an aborted xhr object will
|
||||
// throw an 'c00c023f error' in IE9 and lower, don't touch it.
|
||||
if (!(status === ABORTED && msie < 10)) {
|
||||
statusText = xhr.statusText;
|
||||
}
|
||||
|
||||
completeRequest(callback,
|
||||
status || xhr.status,
|
||||
response,
|
||||
responseHeaders,
|
||||
statusText);
|
||||
// fix status code when it is 0 (0 status is undocumented).
|
||||
// Occurs when accessing file resources or on Android 4.1 stock browser
|
||||
// while retrieving files from application cache.
|
||||
if (status === 0) {
|
||||
status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
|
||||
}
|
||||
|
||||
completeRequest(callback,
|
||||
status,
|
||||
response,
|
||||
xhr.getAllResponseHeaders(),
|
||||
statusText);
|
||||
};
|
||||
|
||||
var requestError = function () {
|
||||
// The response is always empty
|
||||
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
|
||||
completeRequest(callback, -1, null, null, '');
|
||||
};
|
||||
|
||||
xhr.onerror = requestError;
|
||||
xhr.onabort = requestError;
|
||||
|
||||
if (withCredentials) {
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
@@ -138,7 +120,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
|
||||
|
||||
function timeoutRequest() {
|
||||
status = ABORTED;
|
||||
jsonpDone && jsonpDone();
|
||||
xhr && xhr.abort();
|
||||
}
|
||||
@@ -148,17 +129,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
timeoutId && $browserDefer.cancel(timeoutId);
|
||||
jsonpDone = xhr = null;
|
||||
|
||||
// fix status code when it is 0 (0 status is undocumented).
|
||||
// Occurs when accessing file resources or on Android 4.1 stock browser
|
||||
// while retrieving files from application cache.
|
||||
if (status === 0) {
|
||||
status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
|
||||
}
|
||||
|
||||
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
|
||||
status = status === 1223 ? 204 : status;
|
||||
statusText = statusText || '';
|
||||
|
||||
callback(status, response, headersString, statusText);
|
||||
$browser.$$completeOutstandingRequest(noop);
|
||||
}
|
||||
|
||||
+157
-41
@@ -303,9 +303,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
|
||||
}
|
||||
|
||||
|
||||
LocationHashbangInHtml5Url.prototype =
|
||||
LocationHashbangUrl.prototype =
|
||||
LocationHtml5Url.prototype = {
|
||||
var locationPrototype = {
|
||||
|
||||
/**
|
||||
* Are we in html5 mode?
|
||||
@@ -314,7 +312,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
$$html5: false,
|
||||
|
||||
/**
|
||||
* Has any change been replacing ?
|
||||
* Has any change been replacing?
|
||||
* @private
|
||||
*/
|
||||
$$replace: false,
|
||||
@@ -416,7 +414,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* @return {string} path
|
||||
*/
|
||||
path: locationGetterSetter('$$path', function(path) {
|
||||
path = path ? path.toString() : '';
|
||||
path = path !== null ? path.toString() : '';
|
||||
return path.charAt(0) == '/' ? path : '/' + path;
|
||||
}),
|
||||
|
||||
@@ -513,7 +511,7 @@ LocationHashbangInHtml5Url.prototype =
|
||||
* @return {string} hash
|
||||
*/
|
||||
hash: locationGetterSetter('$$hash', function(hash) {
|
||||
return hash ? hash.toString() : '';
|
||||
return hash !== null ? hash.toString() : '';
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -530,6 +528,46 @@ LocationHashbangInHtml5Url.prototype =
|
||||
}
|
||||
};
|
||||
|
||||
forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) {
|
||||
Location.prototype = Object.create(locationPrototype);
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $location#state
|
||||
*
|
||||
* @description
|
||||
* This method is getter / setter.
|
||||
*
|
||||
* Return the history state object when called without any parameter.
|
||||
*
|
||||
* Change the history state object when called with one parameter and return `$location`.
|
||||
* The state object is later passed to `pushState` or `replaceState`.
|
||||
*
|
||||
* NOTE: This method is supported only in HTML5 mode and only in browsers supporting
|
||||
* the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
|
||||
* older browsers (like IE9 or Android < 4.0), don't use this method.
|
||||
*
|
||||
* @param {object=} state State object for pushState or replaceState
|
||||
* @return {object} state
|
||||
*/
|
||||
Location.prototype.state = function(state) {
|
||||
if (!arguments.length)
|
||||
return this.$$state;
|
||||
|
||||
if (Location !== LocationHtml5Url || !this.$$html5) {
|
||||
throw $locationMinErr('nostate', 'History API state support is available only ' +
|
||||
'in HTML5 mode and only in browsers supporting HTML5 History API');
|
||||
}
|
||||
// The user might modify `stateObject` after invoking `$location.state(stateObject)`
|
||||
// but we're changing the $$state reference to $browser.state() during the $digest
|
||||
// so the modification window is narrow.
|
||||
this.$$state = isUndefined(state) ? null : state;
|
||||
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
function locationGetter(property) {
|
||||
return function() {
|
||||
return this[property];
|
||||
@@ -584,7 +622,11 @@ function locationGetterSetter(property, preprocess) {
|
||||
*/
|
||||
function $LocationProvider(){
|
||||
var hashPrefix = '',
|
||||
html5Mode = false;
|
||||
html5Mode = {
|
||||
enabled: false,
|
||||
requireBase: true,
|
||||
rewriteLinks: true
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -606,12 +648,39 @@ function $LocationProvider(){
|
||||
* @ngdoc method
|
||||
* @name $locationProvider#html5Mode
|
||||
* @description
|
||||
* @param {boolean=} mode Use HTML5 strategy if available.
|
||||
* @returns {*} current value if used as getter or itself (chaining) if used as setter
|
||||
* @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
|
||||
* If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
|
||||
* properties:
|
||||
* - **enabled** – `{boolean}` – (default: false) If true, will rely on `history.pushState` to
|
||||
* change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
|
||||
* support `pushState`.
|
||||
* - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
|
||||
* whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
|
||||
* true, and a base tag is not present, an error will be thrown when `$location` is injected.
|
||||
* See the {@link guide/$location $location guide for more information}
|
||||
* - **rewriteLinks** - `{boolean}` - (default: `false`) When html5Mode is enabled, disables
|
||||
* url rewriting for relative linksTurns off url rewriting for relative links.
|
||||
*
|
||||
* @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
|
||||
*/
|
||||
this.html5Mode = function(mode) {
|
||||
if (isDefined(mode)) {
|
||||
html5Mode = mode;
|
||||
if (isBoolean(mode)) {
|
||||
html5Mode.enabled = mode;
|
||||
return this;
|
||||
} else if (isObject(mode)) {
|
||||
|
||||
if (isBoolean(mode.enabled)) {
|
||||
html5Mode.enabled = mode.enabled;
|
||||
}
|
||||
|
||||
if (isBoolean(mode.requireBase)) {
|
||||
html5Mode.requireBase = mode.requireBase;
|
||||
}
|
||||
|
||||
if (isBoolean(mode.rewriteLinks)) {
|
||||
html5Mode.rewriteLinks = mode.rewriteLinks;
|
||||
}
|
||||
|
||||
return this;
|
||||
} else {
|
||||
return html5Mode;
|
||||
@@ -623,14 +692,21 @@ function $LocationProvider(){
|
||||
* @name $location#$locationChangeStart
|
||||
* @eventType broadcast on root scope
|
||||
* @description
|
||||
* Broadcasted before a URL will change. This change can be prevented by calling
|
||||
* Broadcasted before a URL will change.
|
||||
*
|
||||
* This change can be prevented by calling
|
||||
* `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
|
||||
* details about event object. Upon successful change
|
||||
* {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
|
||||
*
|
||||
* The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
|
||||
* the browser supports the HTML5 History API.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object.
|
||||
* @param {string} newUrl New URL
|
||||
* @param {string=} oldUrl URL that was before it was changed.
|
||||
* @param {string=} newState New history state object
|
||||
* @param {string=} oldState History state object that was before it was changed.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -640,9 +716,14 @@ function $LocationProvider(){
|
||||
* @description
|
||||
* Broadcasted after a URL was changed.
|
||||
*
|
||||
* The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
|
||||
* the browser supports the HTML5 History API.
|
||||
*
|
||||
* @param {Object} angularEvent Synthetic event object.
|
||||
* @param {string} newUrl New URL
|
||||
* @param {string=} oldUrl URL that was before it was changed.
|
||||
* @param {string=} newState New history state object
|
||||
* @param {string=} oldState History state object that was before it was changed.
|
||||
*/
|
||||
|
||||
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
|
||||
@@ -653,8 +734,8 @@ function $LocationProvider(){
|
||||
initialUrl = $browser.url(),
|
||||
appBase;
|
||||
|
||||
if (html5Mode) {
|
||||
if (!baseHref) {
|
||||
if (html5Mode.enabled) {
|
||||
if (!baseHref && html5Mode.requireBase) {
|
||||
throw $locationMinErr('nobase',
|
||||
"$location in HTML5 mode requires a <base> tag to be present!");
|
||||
}
|
||||
@@ -667,13 +748,34 @@ function $LocationProvider(){
|
||||
$location = new LocationMode(appBase, '#' + hashPrefix);
|
||||
$location.$$parseLinkUrl(initialUrl, initialUrl);
|
||||
|
||||
$location.$$state = $browser.state();
|
||||
|
||||
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
|
||||
|
||||
function setBrowserUrlWithFallback(url, replace, state) {
|
||||
var oldUrl = $location.url();
|
||||
var oldState = $location.$$state;
|
||||
try {
|
||||
$browser.url(url, replace, state);
|
||||
|
||||
// Make sure $location.state() returns referentially identical (not just deeply equal)
|
||||
// state object; this makes possible quick checking if the state changed in the digest
|
||||
// loop. Checking deep equality would be too expensive.
|
||||
$location.$$state = $browser.state();
|
||||
} catch (e) {
|
||||
// Restore old values if pushState fails
|
||||
$location.url(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
$rootElement.on('click', function(event) {
|
||||
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
||||
// currently we open nice url link and redirect then
|
||||
|
||||
if (event.ctrlKey || event.metaKey || event.which == 2) return;
|
||||
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
|
||||
|
||||
var elm = jqLite(event.target);
|
||||
|
||||
@@ -699,6 +801,9 @@ function $LocationProvider(){
|
||||
|
||||
if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
|
||||
if ($location.$$parseLinkUrl(absHref, relHref)) {
|
||||
// We do a preventDefault for all urls that are part of the angular application,
|
||||
// in html5mode and also without, so that we are able to abort navigation without
|
||||
// getting double entries in the location history.
|
||||
event.preventDefault();
|
||||
// update location manually
|
||||
if ($location.absUrl() != $browser.url()) {
|
||||
@@ -716,52 +821,63 @@ function $LocationProvider(){
|
||||
$browser.url($location.absUrl(), true);
|
||||
}
|
||||
|
||||
// update $location when $browser url changes
|
||||
$browser.onUrlChange(function(newUrl) {
|
||||
if ($location.absUrl() != newUrl) {
|
||||
$rootScope.$evalAsync(function() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var initializing = true;
|
||||
|
||||
$location.$$parse(newUrl);
|
||||
if ($rootScope.$broadcast('$locationChangeStart', newUrl,
|
||||
oldUrl).defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$browser.url(oldUrl);
|
||||
} else {
|
||||
afterLocationChange(oldUrl);
|
||||
}
|
||||
});
|
||||
if (!$rootScope.$$phase) $rootScope.$digest();
|
||||
}
|
||||
// update $location when $browser url changes
|
||||
$browser.onUrlChange(function(newUrl, newState) {
|
||||
$rootScope.$evalAsync(function() {
|
||||
var oldUrl = $location.absUrl();
|
||||
var oldState = $location.$$state;
|
||||
|
||||
$location.$$parse(newUrl);
|
||||
$location.$$state = newState;
|
||||
if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
|
||||
newState, oldState).defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
setBrowserUrlWithFallback(oldUrl, false, oldState);
|
||||
} else {
|
||||
initializing = false;
|
||||
afterLocationChange(oldUrl, oldState);
|
||||
}
|
||||
});
|
||||
if (!$rootScope.$$phase) $rootScope.$digest();
|
||||
});
|
||||
|
||||
// update browser
|
||||
var changeCounter = 0;
|
||||
$rootScope.$watch(function $locationWatch() {
|
||||
var oldUrl = $browser.url();
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
|
||||
if (!changeCounter || oldUrl != $location.absUrl()) {
|
||||
changeCounter++;
|
||||
if (initializing || oldUrl !== $location.absUrl() ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
|
||||
initializing = false;
|
||||
|
||||
$rootScope.$evalAsync(function() {
|
||||
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
|
||||
defaultPrevented) {
|
||||
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
|
||||
$location.$$state, oldState).defaultPrevented) {
|
||||
$location.$$parse(oldUrl);
|
||||
$location.$$state = oldState;
|
||||
} else {
|
||||
$browser.url($location.absUrl(), currentReplace);
|
||||
afterLocationChange(oldUrl);
|
||||
setBrowserUrlWithFallback($location.absUrl(), currentReplace,
|
||||
oldState === $location.$$state ? null : $location.$$state);
|
||||
afterLocationChange(oldUrl, oldState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$location.$$replace = false;
|
||||
|
||||
return changeCounter;
|
||||
// we don't need to return anything because $evalAsync will make the digest loop dirty when
|
||||
// there is a change
|
||||
});
|
||||
|
||||
return $location;
|
||||
|
||||
function afterLocationChange(oldUrl) {
|
||||
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
|
||||
function afterLocationChange(oldUrl, oldState) {
|
||||
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
|
||||
$location.$$state, oldState);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ forEach({
|
||||
CONSTANTS[name] = constantGetter;
|
||||
});
|
||||
|
||||
//Not quite a constant, but can be lex/parsed the same
|
||||
CONSTANTS['this'] = function(self) { return self; };
|
||||
CONSTANTS['this'].sharedGetter = true;
|
||||
|
||||
|
||||
//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
|
||||
var OPERATORS = extend(createMap(), {
|
||||
/* jshint bitwise : false */
|
||||
|
||||
+45
-34
@@ -128,14 +128,11 @@ function $RootScopeProvider(){
|
||||
this.$$phase = this.$parent = this.$$watchers =
|
||||
this.$$nextSibling = this.$$prevSibling =
|
||||
this.$$childHead = this.$$childTail = null;
|
||||
this['this'] = this.$root = this;
|
||||
this.$root = this;
|
||||
this.$$destroyed = false;
|
||||
this.$$asyncQueue = [];
|
||||
this.$$postDigestQueue = [];
|
||||
this.$$listeners = {};
|
||||
this.$$listenerCount = {};
|
||||
this.$$isolateBindings = null;
|
||||
this.$$applyAsyncQueue = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,18 +181,23 @@ function $RootScopeProvider(){
|
||||
* When creating widgets, it is useful for the widget to not accidentally read parent
|
||||
* state.
|
||||
*
|
||||
* @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
|
||||
* of the newly created scope. Defaults to `this` scope if not provided.
|
||||
* This is used when creating a transclude scope to correctly place it
|
||||
* in the scope hierarchy while maintaining the correct prototypical
|
||||
* inheritance.
|
||||
*
|
||||
* @returns {Object} The newly created child scope.
|
||||
*
|
||||
*/
|
||||
$new: function(isolate) {
|
||||
$new: function(isolate, parent) {
|
||||
var child;
|
||||
|
||||
parent = parent || this;
|
||||
|
||||
if (isolate) {
|
||||
child = new Scope();
|
||||
child.$root = this.$root;
|
||||
// ensure that there is just one async queue per $rootScope and its children
|
||||
child.$$asyncQueue = this.$$asyncQueue;
|
||||
child.$$postDigestQueue = this.$$postDigestQueue;
|
||||
} else {
|
||||
// Only create a child scope class if somebody asks for one,
|
||||
// but cache it to allow the VM to optimize lookups.
|
||||
@@ -212,16 +214,27 @@ function $RootScopeProvider(){
|
||||
}
|
||||
child = new this.$$ChildScope();
|
||||
}
|
||||
child['this'] = child;
|
||||
child.$parent = this;
|
||||
child.$$prevSibling = this.$$childTail;
|
||||
if (this.$$childHead) {
|
||||
this.$$childTail.$$nextSibling = child;
|
||||
this.$$childTail = child;
|
||||
child.$parent = parent;
|
||||
child.$$prevSibling = parent.$$childTail;
|
||||
if (parent.$$childHead) {
|
||||
parent.$$childTail.$$nextSibling = child;
|
||||
parent.$$childTail = child;
|
||||
} else {
|
||||
this.$$childHead = this.$$childTail = child;
|
||||
parent.$$childHead = parent.$$childTail = child;
|
||||
}
|
||||
|
||||
// When the new scope is not isolated or we inherit from `this`, and
|
||||
// the parent scope is destroyed, the property `$$destroyed` is inherited
|
||||
// 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);
|
||||
|
||||
return child;
|
||||
|
||||
function destroyChild() {
|
||||
child.$$destroyed = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -697,8 +710,6 @@ function $RootScopeProvider(){
|
||||
$digest: function() {
|
||||
var watch, value, last,
|
||||
watchers,
|
||||
asyncQueue = this.$$asyncQueue,
|
||||
postDigestQueue = this.$$postDigestQueue,
|
||||
length,
|
||||
dirty, ttl = TTL,
|
||||
next, current, target = this,
|
||||
@@ -863,6 +874,10 @@ function $RootScopeProvider(){
|
||||
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
|
||||
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
|
||||
|
||||
// Disable listeners, watchers and apply/digest methods
|
||||
this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
|
||||
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
|
||||
this.$$listeners = {};
|
||||
|
||||
// All of the code below is bogus code that works around V8's memory leak via optimized code
|
||||
// and inline caches.
|
||||
@@ -873,15 +888,7 @@ function $RootScopeProvider(){
|
||||
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
|
||||
|
||||
this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
|
||||
this.$$childTail = this.$root = null;
|
||||
|
||||
// don't reset these to null in case some async task tries to register a listener/watch/task
|
||||
this.$$listeners = {};
|
||||
this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
|
||||
|
||||
// prevent NPEs since these methods have references to properties we nulled out
|
||||
this.$destroy = this.$digest = this.$apply = noop;
|
||||
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
|
||||
this.$$childTail = this.$root = this.$$watchers = null;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -948,19 +955,19 @@ function $RootScopeProvider(){
|
||||
$evalAsync: function(expr) {
|
||||
// if we are outside of an $digest loop and this is the first time we are scheduling async
|
||||
// task also schedule async auto-flush
|
||||
if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
|
||||
if (!$rootScope.$$phase && !asyncQueue.length) {
|
||||
$browser.defer(function() {
|
||||
if ($rootScope.$$asyncQueue.length) {
|
||||
if (asyncQueue.length) {
|
||||
$rootScope.$digest();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.$$asyncQueue.push({scope: this, expression: expr});
|
||||
asyncQueue.push({scope: this, expression: expr});
|
||||
},
|
||||
|
||||
$$postDigest : function(fn) {
|
||||
this.$$postDigestQueue.push(fn);
|
||||
postDigestQueue.push(fn);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1044,7 +1051,7 @@ function $RootScopeProvider(){
|
||||
*/
|
||||
$applyAsync: function(expr) {
|
||||
var scope = this;
|
||||
expr && $rootScope.$$applyAsyncQueue.push($applyAsyncExpression);
|
||||
expr && applyAsyncQueue.push($applyAsyncExpression);
|
||||
scheduleApplyAsync();
|
||||
|
||||
function $applyAsyncExpression() {
|
||||
@@ -1253,6 +1260,11 @@ function $RootScopeProvider(){
|
||||
|
||||
var $rootScope = new Scope();
|
||||
|
||||
//The internal queues. Expose them on the $rootScope for debugging/testing purposes.
|
||||
var asyncQueue = $rootScope.$$asyncQueue = [];
|
||||
var postDigestQueue = $rootScope.$$postDigestQueue = [];
|
||||
var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
|
||||
|
||||
return $rootScope;
|
||||
|
||||
|
||||
@@ -1286,10 +1298,9 @@ function $RootScopeProvider(){
|
||||
function initWatchVal() {}
|
||||
|
||||
function flushApplyAsync() {
|
||||
var queue = $rootScope.$$applyAsyncQueue;
|
||||
while (queue.length) {
|
||||
while (applyAsyncQueue.length) {
|
||||
try {
|
||||
queue.shift()();
|
||||
applyAsyncQueue.shift()();
|
||||
} catch(e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
|
||||
@@ -61,12 +61,9 @@ function $$SanitizeUriProvider() {
|
||||
return function sanitizeUri(uri, isImage) {
|
||||
var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
|
||||
var normalizedVal;
|
||||
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
|
||||
if (!msie || msie >= 8 ) {
|
||||
normalizedVal = urlResolve(uri).href;
|
||||
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
|
||||
return 'unsafe:'+normalizedVal;
|
||||
}
|
||||
normalizedVal = urlResolve(uri).href;
|
||||
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
|
||||
return 'unsafe:'+normalizedVal;
|
||||
}
|
||||
return uri;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* @requires $document
|
||||
*
|
||||
* @property {boolean} history Does the browser support html5 history api ?
|
||||
* @property {boolean} hashchange Does the browser support hashchange event ?
|
||||
* @property {boolean} transitions Does the browser support CSS transition events ?
|
||||
* @property {boolean} animations Does the browser support CSS animation events ?
|
||||
*
|
||||
@@ -65,9 +64,6 @@ function $SnifferProvider() {
|
||||
// jshint -W018
|
||||
history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
|
||||
// jshint +W018
|
||||
hashchange: 'onhashchange' in $window &&
|
||||
// IE8 compatible mode lies
|
||||
(!documentMode || documentMode > 7),
|
||||
hasEvent: function(event) {
|
||||
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
|
||||
// it. In particular the event is not fired when backspace or delete key are pressed or
|
||||
|
||||
+110
-47
@@ -221,7 +221,7 @@
|
||||
*
|
||||
* ### CSS Staggering Animations
|
||||
* A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
|
||||
* curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
|
||||
* curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
|
||||
* performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
|
||||
* the animation. The style property expected within the stagger class can either be a **transition-delay** or an
|
||||
* **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
|
||||
@@ -377,6 +377,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var forEach = angular.forEach;
|
||||
var selectors = $animateProvider.$$selectors;
|
||||
var isArray = angular.isArray;
|
||||
var isString = angular.isString;
|
||||
|
||||
var ELEMENT_NODE = 1;
|
||||
var NG_ANIMATE_STATE = '$$ngAnimateState';
|
||||
@@ -467,34 +468,32 @@ angular.module('ngAnimate', ['ng'])
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
function parseAnimateOptions(options) {
|
||||
// some plugin code may still be passing in the callback
|
||||
// function as the last param for the $animate methods so
|
||||
// it's best to only allow string or array values for now
|
||||
if (isArray(options)) return options;
|
||||
if (isString(options)) return [options];
|
||||
}
|
||||
|
||||
function resolveElementClasses(element, cache, runningAnimations) {
|
||||
runningAnimations = runningAnimations || {};
|
||||
var map = {};
|
||||
|
||||
forEach(cache.add, function(className) {
|
||||
if (className && className.length) {
|
||||
map[className] = map[className] || 0;
|
||||
map[className]++;
|
||||
}
|
||||
});
|
||||
|
||||
forEach(cache.remove, function(className) {
|
||||
if (className && className.length) {
|
||||
map[className] = map[className] || 0;
|
||||
map[className]--;
|
||||
}
|
||||
});
|
||||
|
||||
var lookup = [];
|
||||
var lookup = {};
|
||||
forEach(runningAnimations, function(data, selector) {
|
||||
forEach(selector.split(' '), function(s) {
|
||||
lookup[s]=data;
|
||||
});
|
||||
});
|
||||
|
||||
var hasClasses = Object.create(null);
|
||||
forEach((element.attr('class') || '').split(/\s+/), function(className) {
|
||||
hasClasses[className] = true;
|
||||
});
|
||||
|
||||
var toAdd = [], toRemove = [];
|
||||
forEach(map, function(status, className) {
|
||||
var hasClass = angular.$$hasClass(element[0], className);
|
||||
forEach(cache.classes, function(status, className) {
|
||||
var hasClass = hasClasses[className];
|
||||
var matchingAnimation = lookup[className] || {};
|
||||
|
||||
// When addClass and removeClass is called then $animate will check to
|
||||
@@ -505,12 +504,12 @@ angular.module('ngAnimate', ['ng'])
|
||||
// Once an animation is allowed then the code will also check to see if
|
||||
// there exists any on-going animation that is already adding or remvoing
|
||||
// the matching CSS class.
|
||||
if (status < 0) {
|
||||
if (status === false) {
|
||||
//does it have the class or will it have the class
|
||||
if (hasClass || matchingAnimation.event == 'addClass') {
|
||||
toRemove.push(className);
|
||||
}
|
||||
} else if (status > 0) {
|
||||
} else if (status === true) {
|
||||
//is the class missing or will it be removed?
|
||||
if (!hasClass || matchingAnimation.event == 'removeClass') {
|
||||
toAdd.push(className);
|
||||
@@ -794,7 +793,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
enter : function(element, parentElement, afterElement) {
|
||||
enter : function(element, parentElement, afterElement, options) {
|
||||
options = parseAnimateOptions(options);
|
||||
element = angular.element(element);
|
||||
parentElement = prepareElement(parentElement);
|
||||
afterElement = prepareElement(afterElement);
|
||||
@@ -802,7 +802,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
classBasedAnimationsBlocked(element, true);
|
||||
$delegate.enter(element, parentElement, afterElement);
|
||||
return runAnimationPostDigest(function(done) {
|
||||
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
|
||||
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -836,16 +836,16 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @param {DOMElement} element the element that will be the focus of the leave animation
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
leave : function(element) {
|
||||
leave : function(element, options) {
|
||||
options = parseAnimateOptions(options);
|
||||
element = angular.element(element);
|
||||
|
||||
cancelChildAnimations(element);
|
||||
classBasedAnimationsBlocked(element, true);
|
||||
this.enabled(false, element);
|
||||
return runAnimationPostDigest(function(done) {
|
||||
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
|
||||
$delegate.leave(element);
|
||||
}, done);
|
||||
}, options, done);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -882,7 +882,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
move : function(element, parentElement, afterElement) {
|
||||
move : function(element, parentElement, afterElement, options) {
|
||||
options = parseAnimateOptions(options);
|
||||
element = angular.element(element);
|
||||
parentElement = prepareElement(parentElement);
|
||||
afterElement = prepareElement(afterElement);
|
||||
@@ -891,7 +892,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
classBasedAnimationsBlocked(element, true);
|
||||
$delegate.move(element, parentElement, afterElement);
|
||||
return runAnimationPostDigest(function(done) {
|
||||
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
|
||||
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -924,8 +925,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @param {string} className the CSS class that will be added to the element and then animated
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
addClass : function(element, className) {
|
||||
return this.setClass(element, className, []);
|
||||
addClass : function(element, className, options) {
|
||||
return this.setClass(element, className, [], options);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -957,8 +958,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @param {string} className the CSS class that will be animated and then removed from the element
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
removeClass : function(element, className) {
|
||||
return this.setClass(element, [], className);
|
||||
removeClass : function(element, className, options) {
|
||||
return this.setClass(element, [], className, options);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -988,33 +989,68 @@ angular.module('ngAnimate', ['ng'])
|
||||
* CSS classes have been set on the element
|
||||
* @return {Promise} the animation callback promise
|
||||
*/
|
||||
setClass : function(element, add, remove) {
|
||||
setClass : function(element, add, remove, options) {
|
||||
options = parseAnimateOptions(options);
|
||||
|
||||
var STORAGE_KEY = '$$animateClasses';
|
||||
element = angular.element(element);
|
||||
element = stripCommentsFromElement(element);
|
||||
|
||||
if (classBasedAnimationsBlocked(element)) {
|
||||
return $delegate.setClass(element, add, remove);
|
||||
// TODO(@caitp/@matsko): Don't use private/undocumented API here --- we should not be
|
||||
// changing the DOM synchronously in this case. The `true` parameter must eventually be
|
||||
// removed.
|
||||
return $delegate.setClass(element, add, remove, true);
|
||||
}
|
||||
|
||||
add = isArray(add) ? add : add.split(' ');
|
||||
remove = isArray(remove) ? remove : remove.split(' ');
|
||||
// we're using a combined array for both the add and remove
|
||||
// operations since the ORDER OF addClass and removeClass matters
|
||||
var classes, cache = element.data(STORAGE_KEY);
|
||||
var hasCache = !!cache;
|
||||
if (!cache) {
|
||||
cache = {};
|
||||
cache.classes = {};
|
||||
}
|
||||
classes = cache.classes;
|
||||
|
||||
var cache = element.data(STORAGE_KEY);
|
||||
if (cache) {
|
||||
cache.add = cache.add.concat(add);
|
||||
cache.remove = cache.remove.concat(remove);
|
||||
add = isArray(add) ? add : add.split(' ');
|
||||
forEach(add, function(c) {
|
||||
if (c && c.length) {
|
||||
classes[c] = true;
|
||||
}
|
||||
});
|
||||
|
||||
remove = isArray(remove) ? remove : remove.split(' ');
|
||||
forEach(remove, function(c) {
|
||||
if (c && c.length) {
|
||||
classes[c] = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasCache) {
|
||||
if (options && cache.options) {
|
||||
cache.options = cache.options.concat(options);
|
||||
}
|
||||
|
||||
//the digest cycle will combine all the animations into one function
|
||||
return cache.promise;
|
||||
} else {
|
||||
element.data(STORAGE_KEY, cache = {
|
||||
add : add,
|
||||
remove : remove
|
||||
classes : classes,
|
||||
options : options
|
||||
});
|
||||
}
|
||||
|
||||
return cache.promise = runAnimationPostDigest(function(done) {
|
||||
var parentElement = element.parent();
|
||||
var elementNode = extractElementNode(element);
|
||||
var parentNode = elementNode.parentNode;
|
||||
// TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed
|
||||
if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
var cache = element.data(STORAGE_KEY);
|
||||
element.removeData(STORAGE_KEY);
|
||||
|
||||
@@ -1022,9 +1058,10 @@ angular.module('ngAnimate', ['ng'])
|
||||
var classes = resolveElementClasses(element, cache, state.active);
|
||||
return !classes
|
||||
? done()
|
||||
: performAnimation('setClass', classes, element, null, null, function() {
|
||||
$delegate.setClass(element, classes[0], classes[1]);
|
||||
}, done);
|
||||
: performAnimation('setClass', classes, element, parentElement, null, function() {
|
||||
if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
|
||||
if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
|
||||
}, cache.options, done);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1086,7 +1123,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
|
||||
and the onComplete callback will be fired once the animation is fully complete.
|
||||
*/
|
||||
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
|
||||
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
|
||||
|
||||
var noopCancel = noop;
|
||||
var runner = animationRunner(element, animationEvent, className);
|
||||
@@ -1194,6 +1231,11 @@ angular.module('ngAnimate', ['ng'])
|
||||
//the ng-animate class does nothing, but it's here to allow for
|
||||
//parent animations to find and cancel child animations when needed
|
||||
element.addClass(NG_ANIMATE_CLASS_NAME);
|
||||
if (isArray(options)) {
|
||||
forEach(options, function(className) {
|
||||
element.addClass(className);
|
||||
});
|
||||
}
|
||||
|
||||
var localAnimationCount = globalAnimationCounter++;
|
||||
totalActiveAnimations++;
|
||||
@@ -1263,8 +1305,15 @@ angular.module('ngAnimate', ['ng'])
|
||||
function closeAnimation() {
|
||||
if (!closeAnimation.hasBeenRun) {
|
||||
closeAnimation.hasBeenRun = true;
|
||||
if (isArray(options)) {
|
||||
forEach(options, function(className) {
|
||||
element.removeClass(className);
|
||||
});
|
||||
}
|
||||
|
||||
var data = element.data(NG_ANIMATE_STATE);
|
||||
if (data) {
|
||||
|
||||
/* only structural animations wait for reflow before removing an
|
||||
animation, but class-based animations don't. An example of this
|
||||
failing would be when a parent HTML tag has a ng-class attribute
|
||||
@@ -1419,6 +1468,16 @@ angular.module('ngAnimate', ['ng'])
|
||||
var parentCounter = 0;
|
||||
var animationReflowQueue = [];
|
||||
var cancelAnimationReflow;
|
||||
function clearCacheAfterReflow() {
|
||||
if (!cancelAnimationReflow) {
|
||||
cancelAnimationReflow = $$animateReflow(function() {
|
||||
animationReflowQueue = [];
|
||||
cancelAnimationReflow = null;
|
||||
lookupCache = {};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function afterReflow(element, callback) {
|
||||
if (cancelAnimationReflow) {
|
||||
cancelAnimationReflow();
|
||||
@@ -1519,7 +1578,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
|
||||
function parseMaxTime(str) {
|
||||
var maxValue = 0;
|
||||
var values = angular.isString(str) ?
|
||||
var values = isString(str) ?
|
||||
str.split(/\s*,\s*/) :
|
||||
[];
|
||||
forEach(values, function(value) {
|
||||
@@ -1764,6 +1823,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
//to perform at all
|
||||
var preReflowCancellation = animateBefore(animationEvent, element, className);
|
||||
if (!preReflowCancellation) {
|
||||
clearCacheAfterReflow();
|
||||
animationComplete();
|
||||
return;
|
||||
}
|
||||
@@ -1820,6 +1880,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
afterReflow(element, animationCompleted);
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
@@ -1829,6 +1890,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
afterReflow(element, animationCompleted);
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
@@ -1838,6 +1900,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
afterReflow(element, animationCompleted);
|
||||
return cancellationMethod;
|
||||
}
|
||||
clearCacheAfterReflow();
|
||||
animationCompleted();
|
||||
},
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user