Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4adebd07a | |||
| ac94f6125f | |||
| c5686c5271 | |||
| 7fdb54d12b | |||
| 869008140a | |||
| 26ee32ec79 | |||
| a06193f97b | |||
| ba9dee170c | |||
| d4b60ada1e | |||
| b839f73ad0 | |||
| aee32931fd | |||
| 7ee5f46bbc | |||
| 2b97854bf4 | |||
| 316ee8f7ca | |||
| 2e18f44fcd | |||
| c85d064ecf | |||
| 7b9b82281a | |||
| aab632b330 | |||
| 4c8d8ad508 | |||
| 3a8f3dc9ea | |||
| c139e68d99 | |||
| 2caec44632 | |||
| eae848a712 | |||
| 47a55ca767 | |||
| 3d78bf3fa8 | |||
| 6e80d0ad54 | |||
| ae637acdfb | |||
| 661f6d9ecf | |||
| 56a7abd38f | |||
| 7d70dcdab1 | |||
| 9e6161e579 | |||
| bd28c74c1d | |||
| cd77c089ba | |||
| 83f88c1818 | |||
| fb2c585897 | |||
| bdbe4fd34a | |||
| 0b4e150aa5 | |||
| e091bb7dbb | |||
| b62c858499 | |||
| 4a18274670 | |||
| e3bf1ed217 | |||
| a5f037d033 | |||
| 25152bb218 | |||
| 24eb528b05 | |||
| d161cc6b25 | |||
| d604f941e8 | |||
| 85758ce3af | |||
| 924e68c7d5 | |||
| f297aa5d0a | |||
| 337ce67612 | |||
| 8b56c08327 | |||
| 32eec67023 | |||
| fd1528a6c8 | |||
| 6cb5fbf5ef | |||
| f6644c720e | |||
| 0d9aafba3b | |||
| 0524e92d2e | |||
| 9b96cea462 | |||
| c90ad96808 | |||
| 69f69db1e0 | |||
| 8e28bb4c2f | |||
| ab41e48493 | |||
| ef640cbc2a | |||
| 081fef60b2 | |||
| 99d1a438b6 | |||
| 6bd4292c24 | |||
| e0663312fb | |||
| 9ae0c01c2b | |||
| 79b3b8b686 | |||
| 1b740974f5 | |||
| b9bdbe615c | |||
| 6617b42bc7 | |||
| 8a907eb3ff | |||
| aac3c4aa63 | |||
| d162f152b8 | |||
| 4025883803 | |||
| c437d0a470 | |||
| 5d28d19623 | |||
| 7fd2dc11ca | |||
| 63db09753e | |||
| a097aa95b7 | |||
| b3dfb38359 | |||
| 3b5ba87797 | |||
| e50002baed | |||
| a8089166f5 | |||
| d8e3707860 | |||
| ca4df475e1 | |||
| 02c9dc6e16 | |||
| 6ad109e745 | |||
| c5cba6e9c1 | |||
| 924d3c6bfe | |||
| ee29819dba | |||
| 41f03e4b02 | |||
| facfec9841 | |||
| e2d1969d68 | |||
| f380cd220c | |||
| 2db0aabee3 | |||
| ee42cfea04 | |||
| fbbf6ac161 | |||
| a1c5f2b4f0 | |||
| 2d6a0a1dc1 | |||
| 0c4f9fa574 | |||
| 7ad66527ba | |||
| f2e7f875e2 | |||
| 40a537c25f | |||
| cb192293f4 | |||
| 8c3a42cd68 | |||
| 96e7897fef | |||
| 2dc34a9699 | |||
| 10ac594809 | |||
| d21dff21ed | |||
| 55d9db56a6 | |||
| 579aa59324 | |||
| b9e5eaf669 | |||
| 228281eecc | |||
| acb066e84a | |||
| ed1243ffc7 | |||
| 3aa5752894 | |||
| 8bfeddb5d6 | |||
| 015111fd79 | |||
| cc0fbe37d4 | |||
| 1b640f9665 | |||
| 32806caf13 | |||
| 9c113aa4af | |||
| 9a616eade4 | |||
| 7daf4e0125 | |||
| 1191edba4e | |||
| c8c9bbc412 | |||
| 429938da1f | |||
| a75537d461 | |||
| 5ced914cc8 | |||
| a631a759d2 | |||
| f7cf846045 | |||
| 96c61fe756 | |||
| 915a891ad4 | |||
| 8df47db72f | |||
| 013b522c9e | |||
| 7c6be43e83 | |||
| e93710fe0e | |||
| 5481e2cfcd | |||
| c6b57f1ec6 | |||
| 240e0d5c8e | |||
| 9fa73cb4e7 | |||
| f6458826ac | |||
| ab2531143e | |||
| 9a83f9d2fa | |||
| 3f07eb227d | |||
| 446e5669a1 | |||
| b264be40bc | |||
| 814c9847e8 | |||
| dde613f18e | |||
| e6a2527cdf | |||
| d0351c4803 | |||
| b2b6d74ae5 | |||
| 30694c8027 | |||
| 655ac6474b | |||
| e5a9b265ba | |||
| e2b9eccde0 | |||
| 9b3d9656a6 | |||
| 1e6a5b29a6 | |||
| 8f05ca5552 | |||
| 2ec8d1ffc0 | |||
| 08cd5c19c7 | |||
| 41dc7d5ebd | |||
| 0caa5ad83f | |||
| 5d36353bc9 | |||
| 719d5c5fa5 | |||
| 266da34098 |
+16
-5
@@ -8,14 +8,25 @@ branches:
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- JOB=unit
|
||||
- JOB=e2e TEST_TARGET=jqlite
|
||||
- JOB=e2e TEST_TARGET=jquery
|
||||
- JOB=unit BROWSER_PROVIDER=saucelabs
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
|
||||
- JOB=unit BROWSER_PROVIDER=browserstack
|
||||
- JOB=docs-e2e BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
|
||||
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
|
||||
global:
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
- BROWSER_STACK_USERNAME=VojtaJina
|
||||
- BROWSER_STACK_ACCESS_KEY=QCQJ1ZpWXpBkSwEdD8ev
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
|
||||
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
|
||||
install:
|
||||
# - npm config set registry http://23.251.144.68
|
||||
@@ -28,7 +39,7 @@ install:
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./lib/sauce/sauce_connect_setup.sh
|
||||
- ./scripts/travis/start_browser_provider.sh
|
||||
- npm install -g grunt-cli
|
||||
- grunt package
|
||||
- ./scripts/travis/wait_for_browser_provider.sh
|
||||
|
||||
+417
-1
@@ -1,3 +1,409 @@
|
||||
<a name="1.4.0-beta.0"></a>
|
||||
# 1.4.0-beta.0 photonic-umbrakinesis (2015-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** support right button click on anchors in firefox
|
||||
([aa798f12](https://github.com/angular/angular.js/commit/aa798f123658cb78b5581513d26577016195cafe),
|
||||
[#7984](https://github.com/angular/angular.js/issues/7984))
|
||||
- **$templateRequest:** propagate HTTP status on failed requests
|
||||
([e24f22bd](https://github.com/angular/angular.js/commit/e24f22bdb1740388938d58778aa24d307a79a796),
|
||||
[#10514](https://github.com/angular/angular.js/issues/10514), [#10628](https://github.com/angular/angular.js/issues/10628))
|
||||
- **dateFilter:** ignore invalid dates
|
||||
([1334b8c8](https://github.com/angular/angular.js/commit/1334b8c8326b93e0ca016c85516627900c7a9fd3),
|
||||
[#10640](https://github.com/angular/angular.js/issues/10640))
|
||||
- **filterFilter:** use isArray() to determine array type
|
||||
([a01ce6b8](https://github.com/angular/angular.js/commit/a01ce6b81c197b0a4a1057981e8e9c1b74f37587),
|
||||
[#10621](https://github.com/angular/angular.js/issues/10621))
|
||||
- **ngChecked:** ensure that ngChecked doesn't interfere with ngModel
|
||||
([e079111b](https://github.com/angular/angular.js/commit/e079111b33bf36be21c0941718b41cc9ca67bea0),
|
||||
[#10662](https://github.com/angular/angular.js/issues/10662), [#10664](https://github.com/angular/angular.js/issues/10664))
|
||||
- **ngClass:** handle multi-class definitions as an element of an array
|
||||
([e1132f53](https://github.com/angular/angular.js/commit/e1132f53b03a5a71aa9b6eded24d64e3bc83929b),
|
||||
[#8578](https://github.com/angular/angular.js/issues/8578), [#10651](https://github.com/angular/angular.js/issues/10651))
|
||||
- **ngModelOptions:** allow sharing options between multiple inputs
|
||||
([9c9c6b3f](https://github.com/angular/angular.js/commit/9c9c6b3fe4edfe78ae275c413ee3eefb81f1ebf6),
|
||||
[#10667](https://github.com/angular/angular.js/issues/10667))
|
||||
- **ngOptions:**
|
||||
- support one-time binding on the option values
|
||||
([ba90261b](https://github.com/angular/angular.js/commit/ba90261b7586b519483883800ea876510faf5c21),
|
||||
[#10687](https://github.com/angular/angular.js/issues/10687), [#10694](https://github.com/angular/angular.js/issues/10694))
|
||||
- prevent infinite digest if track by expression is stable
|
||||
([fc21db8a](https://github.com/angular/angular.js/commit/fc21db8a15545fad53124fc941b3c911a8d57067),
|
||||
[#9464](https://github.com/angular/angular.js/issues/9464))
|
||||
- update model if selected option is removed
|
||||
([933591d6](https://github.com/angular/angular.js/commit/933591d69cee2c5580da1d8522ba90a7d924da0e),
|
||||
[#7736](https://github.com/angular/angular.js/issues/7736))
|
||||
- ensure that the correct option is selected when options are loaded async
|
||||
([7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
[#8019](https://github.com/angular/angular.js/issues/8019), [#9714](https://github.com/angular/angular.js/issues/9714), [#10639](https://github.com/angular/angular.js/issues/10639))
|
||||
- **ngPluralize:** generate a warning when using a not defined rule
|
||||
([c66b4b6a](https://github.com/angular/angular.js/commit/c66b4b6a133f7215d50c23db516986cfc1f0a985))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$filter:** display Infinity symbol when number is Infinity
|
||||
([51d67742](https://github.com/angular/angular.js/commit/51d6774286202b55ade402ca097e417e70fd546b),
|
||||
[#10421](https://github.com/angular/angular.js/issues/10421))
|
||||
- **$timeout:** allow `fn` to be an optional parameter
|
||||
([5a603023](https://github.com/angular/angular.js/commit/5a60302389162c6ef45f311c1aaa65a00d538c66),
|
||||
[#9176](https://github.com/angular/angular.js/issues/9176))
|
||||
- **limitTo:** ignore limit when invalid
|
||||
([a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
|
||||
[#10510](https://github.com/angular/angular.js/issues/10510))
|
||||
- **ngMock/$exceptionHandler:** log errors when rethrowing
|
||||
([deb3cb4d](https://github.com/angular/angular.js/commit/deb3cb4daef0054457bd9fb8995829fff0e8f1e4),
|
||||
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngStyleDirective:** use $watchCollection
|
||||
([8928d023](https://github.com/angular/angular.js/commit/8928d0234551a272992d0eccef73b3ad6cb8bfd1),
|
||||
[#10535](https://github.com/angular/angular.js/issues/10535))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **limitTo:** due to [a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
|
||||
limitTo changed behavior when limit value is invalid.
|
||||
Instead of returning empty object/array it returns unchanged input.
|
||||
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
|
||||
When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
|
||||
This commit changes the actual string used as the surrogate key. We now store a string that is computed
|
||||
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
|
||||
item in the collection.
|
||||
|
||||
(This is in keeping with the way that the unknown option value is represented in the select directive.)
|
||||
|
||||
Before you might have seen:
|
||||
|
||||
```
|
||||
<select ng-model="x" ng-option="i in items">
|
||||
<option value="1">a</option>
|
||||
<option value="2">b</option>
|
||||
<option value="3">c</option>
|
||||
<option value="4">d</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
Now it will be something like:
|
||||
|
||||
```
|
||||
<select ng-model="x" ng-option="i in items">
|
||||
<option value="string:a">a</option>
|
||||
<option value="string:b">b</option>
|
||||
<option value="string:c">c</option>
|
||||
<option value="string:d">d</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
If your application code relied on this value, which it shouldn't, then you will need to modify your
|
||||
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
|
||||
as this provides the ability to specify the key that is stored.
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
When iterating over an object's properties using the `(key, value) in obj` syntax
|
||||
the order of the elements used to be sorted alphabetically. This was an artificial
|
||||
attempt to create a deterministic ordering since browsers don't guarantee the order.
|
||||
But in practice this is not what people want and so this change iterates over properties
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
|
||||
|
||||
<a name="1.3.9"></a>
|
||||
# 1.3.9 multidimensional-awareness (2015-01-13)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$parse:** allow use of locals in assignments
|
||||
([86900814](https://github.com/angular/angular.js/commit/869008140a96e0e9e0d9774cc2e5fdd66ada7ba9))
|
||||
- **filterFilter:** use isArray() to determine array type
|
||||
([d4b60ada](https://github.com/angular/angular.js/commit/d4b60ada1ecff5afdb3210caa44e149e9f3d4c1b),
|
||||
[#10621](https://github.com/angular/angular.js/issues/10621))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **ngMock/$exceptionHandler:** log errors when rethrowing
|
||||
([2b97854b](https://github.com/angular/angular.js/commit/2b97854bf4786fe8579974e2b9d6b4adee8a3dc3),
|
||||
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngStyleDirective:** use $watchCollection
|
||||
([4c8d8ad5](https://github.com/angular/angular.js/commit/4c8d8ad5083d9dd17c0b8480339d5f95943f1b71),
|
||||
[#10535](https://github.com/angular/angular.js/issues/10535))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.8"></a>
|
||||
# 1.3.8 prophetic-narwhal (2014-12-19)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
- **filterFilter:**
|
||||
- make `$` match properties on deeper levels as well
|
||||
([bd28c74c](https://github.com/angular/angular.js/commit/bd28c74c1d91c477a86f10fe36576cba0249e6ef),
|
||||
[#10401](https://github.com/angular/angular.js/issues/10401))
|
||||
- let expression object `{$: '...'}` also match primitive items
|
||||
([fb2c5858](https://github.com/angular/angular.js/commit/fb2c58589758744c0eef8c2ead3dbcf27a5cf200),
|
||||
[#10428](https://github.com/angular/angular.js/issues/10428))
|
||||
- **ngAria:** trigger digest on `ng-click` via keypress, pass `$event` to expression
|
||||
([924e68c7](https://github.com/angular/angular.js/commit/924e68c7d522a1086969f3583d0ce87e59110bc5),
|
||||
[#10442](https://github.com/angular/angular.js/issues/10442), [#10443](https://github.com/angular/angular.js/issues/10443), [#10447](https://github.com/angular/angular.js/issues/10447))
|
||||
- **orderBy:** compare timestamps when sorting date objects
|
||||
([661f6d9e](https://github.com/angular/angular.js/commit/661f6d9ecf1459ce3b2794c3cde373e17ae83972),
|
||||
[#10512](https://github.com/angular/angular.js/issues/10512), [#10516](https://github.com/angular/angular.js/issues/10516))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **limitTo:** replace for loop with slice
|
||||
([cd77c089](https://github.com/angular/angular.js/commit/cd77c089ba2f4b94ccc74f32f0ffa9fb70851c02))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.3.7"></a>
|
||||
# 1.3.7 leaky-obstruction (2014-12-15)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** use `createMap()` for `$$observe` listeners when initialized from attr interpolation
|
||||
([8e28bb4c](https://github.com/angular/angular.js/commit/8e28bb4c2f6d015dfe1cec7755f1ca9b0ecef1f8))
|
||||
- **$http:**
|
||||
- only parse as JSON when opening/closing brackets match
|
||||
([b9bdbe61](https://github.com/angular/angular.js/commit/b9bdbe615cc4070d2233ff06830a4c6fb1217cda),
|
||||
[#10349](https://github.com/angular/angular.js/issues/10349), [#10357](https://github.com/angular/angular.js/issues/10357))
|
||||
- don't convert FormData objects to JSON
|
||||
([40258838](https://github.com/angular/angular.js/commit/40258838031604feecb862afdc6f1f503d80ce4a),
|
||||
[#10373](https://github.com/angular/angular.js/issues/10373))
|
||||
- **$parse:** a chain of field accessors should use a single `getterFn`
|
||||
([c90ad968](https://github.com/angular/angular.js/commit/c90ad96808be350526516626205c3a7d1da79024))
|
||||
- **ngRepeat:** allow extra whitespaces in `(key,value)` part of micro-syntax
|
||||
([ef640cbc](https://github.com/angular/angular.js/commit/ef640cbc2af5794c987e75472c12e63a59590044),
|
||||
[#6827](https://github.com/angular/angular.js/issues/6827), [#6833](https://github.com/angular/angular.js/issues/6833))
|
||||
- **orderBy:** do not try to call `valueOf`/`toString` on `null`
|
||||
([a097aa95](https://github.com/angular/angular.js/commit/a097aa95b7c78beab6d1b7d521c25f7d9d7843d9),
|
||||
[#10385](https://github.com/angular/angular.js/issues/10385), [#10386](https://github.com/angular/angular.js/issues/10386))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** add support for `ng-attr` with camelCased attributes
|
||||
([d8e37078](https://github.com/angular/angular.js/commit/d8e37078600089839f82f0e84022f1087e1fd3f2),
|
||||
[#9845](https://github.com/angular/angular.js/issues/9845), [#10194](https://github.com/angular/angular.js/issues/10194))
|
||||
- **$http:** pass response status code to data transform functions
|
||||
([1b740974](https://github.com/angular/angular.js/commit/1b740974f5eb373bed04071d51f908ced7c5a8e5),
|
||||
[#10324](https://github.com/angular/angular.js/issues/10324), [#6734](https://github.com/angular/angular.js/issues/6734), [#10440](https://github.com/angular/angular.js/issues/10440))
|
||||
- **$rootScope:** allow passing `locals` argument to `$evalAsync`
|
||||
([9b96cea4](https://github.com/angular/angular.js/commit/9b96cea462676d123e1b2dd852aedbe3da8fa4a0),
|
||||
[#10390](https://github.com/angular/angular.js/issues/10390))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** only re-`$interpolate` attribute values at link time if changed since compile
|
||||
([9ae0c01c](https://github.com/angular/angular.js/commit/9ae0c01c2bcaff2f3906eec574f9c6ed8abde14a))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **orderBy:** due to [a097aa95](https://github.com/angular/angular.js/commit/a097aa95b7c78beab6d1b7d521c25f7d9d7843d9),
|
||||
|
||||
Previously, if either value being compared in the orderBy comparator was null or undefined, the
|
||||
order would, incorrectly, not change. Now, this order behaves more like Array.prototype.sort, which
|
||||
by default pushes `null` behind objects, due to `n` occurring after `[` (the first characters of their
|
||||
stringified forms) in ASCII / Unicode. If `toString` is customized, or does not exist, the
|
||||
behaviour is undefined.
|
||||
|
||||
|
||||
|
||||
<a name="1.2.28"></a>
|
||||
# 1.2.28 finnish-disembarkation (2014-12-15)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$route:** fix redirection with optional/eager params
|
||||
([1b9e408d](https://github.com/angular/angular.js/commit/1b9e408ddbe48a6d3db27f501515d6efad01f42d),
|
||||
[#9742](https://github.com/angular/angular.js/issues/9742), [#10202](https://github.com/angular/angular.js/issues/10202))
|
||||
- **linky:** encode double quotes when serializing email addresses
|
||||
([929dd15b](https://github.com/angular/angular.js/commit/929dd15b9b65034350f18abe6c56a8d956f4b978),
|
||||
[#8945](https://github.com/angular/angular.js/issues/8945), [#8964](https://github.com/angular/angular.js/issues/8964), [#5946](https://github.com/angular/angular.js/issues/5946), [#10090](https://github.com/angular/angular.js/issues/10090), [#9256](https://github.com/angular/angular.js/issues/9256))
|
||||
|
||||
|
||||
|
||||
<a name="1.3.6"></a>
|
||||
# 1.3.6 robofunky-danceblaster (2014-12-08)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$browser:** prevent infinite digests when clearing the hash of a url
|
||||
([10ac5948](https://github.com/angular/angular.js/commit/10ac5948097e2c8eaead238603d29ee580dc8273),
|
||||
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
|
||||
- **$http:** preserve config object when resolving from cache
|
||||
([facfec98](https://github.com/angular/angular.js/commit/facfec98412c0bb8678d578bade05ffef06a9e84),
|
||||
[#9004](https://github.com/angular/angular.js/issues/9004), [#9030](https://github.com/angular/angular.js/issues/9030))
|
||||
- **$location:**
|
||||
- allow hash fragments with hashPrefix in hash-bang location urls
|
||||
([2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
|
||||
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
|
||||
- strip off empty hash segments when comparing
|
||||
([e93710fe](https://github.com/angular/angular.js/commit/e93710fe0e4fb05ceee59a04f290692a5bec5d20),
|
||||
[#9635](https://github.com/angular/angular.js/issues/9635))
|
||||
- **$parse:**
|
||||
- fix operators associativity
|
||||
([ed1243ff](https://github.com/angular/angular.js/commit/ed1243ffc7c2cb4bd5b4dece597597db8eb08e34))
|
||||
- follow JavaScript context for unbound functions
|
||||
([429938da](https://github.com/angular/angular.js/commit/429938da1f45b8a649b8c77762fb0ae59b6d0cea))
|
||||
- **filterFilter:**
|
||||
- don't match primitive sub-expressions against any prop
|
||||
([a75537d4](https://github.com/angular/angular.js/commit/a75537d461c92e3455e372ff5005bf0cad2d2e95))
|
||||
- ignore function properties and account for inherited properties
|
||||
([5ced914c](https://github.com/angular/angular.js/commit/5ced914cc8625008e6249d5ac5942d5822287cc0),
|
||||
[#9984](https://github.com/angular/angular.js/issues/9984))
|
||||
- correctly handle deep expression objects
|
||||
([f7cf8460](https://github.com/angular/angular.js/commit/f7cf846045b1e2fb39c62e304c61b44d5c805e31),
|
||||
[#7323](https://github.com/angular/angular.js/issues/7323), [#9698](https://github.com/angular/angular.js/issues/9698), [#9757](https://github.com/angular/angular.js/issues/9757))
|
||||
- **inputs:** ignoring input events in IE caused by placeholder changes or focus/blur on inputs with placeholders
|
||||
([55d9db56](https://github.com/angular/angular.js/commit/55d9db56a6f7d29b16f8393612648080c6d535d6),
|
||||
[#9265](https://github.com/angular/angular.js/issues/9265))
|
||||
- **linky:** make urls starting with www. links, like markdown
|
||||
([915a891a](https://github.com/angular/angular.js/commit/915a891ad4cdcaa5e47e976db8f4d402d230be77),
|
||||
[#10290](https://github.com/angular/angular.js/issues/10290))
|
||||
- **ngAnimate:** do not use jQuery class API
|
||||
([40a537c2](https://github.com/angular/angular.js/commit/40a537c25f70ad556a41bb2d00ea3e257410e9af),
|
||||
[#10024](https://github.com/angular/angular.js/issues/10024), [#10329](https://github.com/angular/angular.js/issues/10329))
|
||||
- **ngMock:** allow numeric timeouts in $httpBackend mock
|
||||
([acb066e8](https://github.com/angular/angular.js/commit/acb066e84a10483e1025eed295352b66747dbb8a),
|
||||
[#4891](https://github.com/angular/angular.js/issues/4891))
|
||||
- **ngModel:**
|
||||
- always use the most recent viewValue for validation
|
||||
([2d6a0a1d](https://github.com/angular/angular.js/commit/2d6a0a1dc1e7125cab2e30244e35e97e11802843),
|
||||
[#10126](https://github.com/angular/angular.js/issues/10126), [#10299](https://github.com/angular/angular.js/issues/10299))
|
||||
- fixing many keys incorrectly marking inputs as dirty
|
||||
([d21dff21](https://github.com/angular/angular.js/commit/d21dff21ed8beb015ad911f11d57cceb56fc439f))
|
||||
- **ngSanitize:** exclude smart quotes at the end of the link
|
||||
([7c6be43e](https://github.com/angular/angular.js/commit/7c6be43e83590798cffef63d076fb79d5296fba2),
|
||||
[#7307](https://github.com/angular/angular.js/issues/7307))
|
||||
- **numberFilter:** numbers rounding to zero shouldn't be negative
|
||||
([96c61fe7](https://github.com/angular/angular.js/commit/96c61fe756d7d3db011818bf0925e3d86ffff8ce),
|
||||
[#10278](https://github.com/angular/angular.js/issues/10278))
|
||||
- **orderBy:**
|
||||
- make object-to-primtiive behaviour work for objects with null prototype
|
||||
([3aa57528](https://github.com/angular/angular.js/commit/3aa5752894419b4638d5c934879258fa6a1c0d07))
|
||||
- maintain order in array of objects when predicate is not provided
|
||||
([8bfeddb5](https://github.com/angular/angular.js/commit/8bfeddb5d671017f4a21b8b46334ac816710b143),
|
||||
[#9566](https://github.com/angular/angular.js/issues/9566), [#9747](https://github.com/angular/angular.js/issues/9747), [#10311](https://github.com/angular/angular.js/issues/10311))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$$jqLite:** export jqLite as a private service
|
||||
([f2e7f875](https://github.com/angular/angular.js/commit/f2e7f875e2ad4b271c4e72ebd3860f905132eed9))
|
||||
- **$injector:** print caller name in "unknown provider" errors (when available)
|
||||
([013b522c](https://github.com/angular/angular.js/commit/013b522c9e690665aecb0e0f656e4557a673ec09),
|
||||
[#8135](https://github.com/angular/angular.js/issues/8135), [#9721](https://github.com/angular/angular.js/issues/9721))
|
||||
- **jsonFilter:** add optional arg to define custom indentation
|
||||
([1191edba](https://github.com/angular/angular.js/commit/1191edba4eaa15f675fa4ed047949a150843971b),
|
||||
[#9771](https://github.com/angular/angular.js/issues/9771))
|
||||
- **ngAria:** bind keypress on ng-click w/ option
|
||||
([5481e2cf](https://github.com/angular/angular.js/commit/5481e2cfcd4d136a1c7f45cd4ce0fa1a8a15074d),
|
||||
[#10288](https://github.com/angular/angular.js/issues/10288))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$location:** due to [2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
|
||||
|
||||
|
||||
We no longer throw an `ihshprfx` error if the URL after the base path
|
||||
contains only a hash fragment. Previously, if the base URL was `http://abc.com/base/`
|
||||
and the hashPrefix is `!` then trying to parse `http://abc.com/base/#some-fragment`
|
||||
would have thrown an error. Now we simply assume it is a normal fragment and
|
||||
that the path is empty, resulting `$location.absUrl() === "http://abc.com/base/#!/#some-fragment"`.
|
||||
|
||||
This should not break any applications, but you can no longer rely on receiving the
|
||||
`ihshprfx` error for paths that have the syntax above. It is actually more similar
|
||||
to what currently happens for invalid extra paths anyway: If the base URL
|
||||
and hashPrfix are set up as above, then `http://abc.com/base/other/path` does not
|
||||
throw an error but just ignores the extra path: `http://abc.com/base`.
|
||||
|
||||
|
||||
- **filterFilter:** due to [a75537d4](https://github.com/angular/angular.js/commit/a75537d461c92e3455e372ff5005bf0cad2d2e95),
|
||||
|
||||
Named properties in the expression object will only match against properties on the **same level**.
|
||||
Previously, named string properties would match against properties on the same level **or deeper**.
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: 'test'}); // arr.length -> 1
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: 'test'}); // arr.length -> 0
|
||||
```
|
||||
|
||||
In order to match deeper nested properties, you have to either match the depth level of the
|
||||
property or use the special `$` key (which still matches properties on the same level
|
||||
**or deeper**). E.g.:
|
||||
|
||||
```js
|
||||
// Both examples below have `arr.length === 1`
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {level1: {level2: 'test'}});
|
||||
arr = filterFilter([{level1: {level2: 'test'}}], {$: 'test'});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.5"></a>
|
||||
# 1.3.5 cybernetic-mercantilism (2014-12-01)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$templateRequest:** propagate rejection reason when ignoreRequestError flag is set
|
||||
([f6458826](https://github.com/angular/angular.js/commit/f6458826ac974914597a10b0ffdeee3c5d2c62ef),
|
||||
[#10266](https://github.com/angular/angular.js/issues/10266))
|
||||
- **$httpBackend:** allow canceling request with falsy timeoutId
|
||||
([719d5c5f](https://github.com/angular/angular.js/commit/719d5c5fa59ae1617691a0dca02da861fcf5f933),
|
||||
[#10177](https://github.com/angular/angular.js/issues/10177))
|
||||
- **linky:** encode all double quotes when serializing email addresses
|
||||
([2ec8d1ff](https://github.com/angular/angular.js/commit/2ec8d1ffc04e06a39cb1b74a8d675da38e0a1c6b),
|
||||
[#10090](https://github.com/angular/angular.js/issues/10090))
|
||||
- **ngMock:**
|
||||
- annotate $RootScopeDecorator
|
||||
([9a83f9d2](https://github.com/angular/angular.js/commit/9a83f9d2fabe0a259c283b7f7cd935e4b36e2b5d),
|
||||
[#10273](https://github.com/angular/angular.js/issues/10273), [#10275](https://github.com/angular/angular.js/issues/10275), [#10277](https://github.com/angular/angular.js/issues/10277))
|
||||
- respond did not always take a statusText argument
|
||||
([08cd5c19](https://github.com/angular/angular.js/commit/08cd5c19c7a5116e7e74691391fc5e28bfae4521),
|
||||
[#8270](https://github.com/angular/angular.js/issues/8270))
|
||||
- **select:**
|
||||
- use strict compare when removing option from ctrl
|
||||
([9fa73cb4](https://github.com/angular/angular.js/commit/9fa73cb4e7190b4d00b65f2f8f9f7d37607308ba),
|
||||
[#9714](https://github.com/angular/angular.js/issues/9714), [#10115](https://github.com/angular/angular.js/issues/10115), [#10203](https://github.com/angular/angular.js/issues/10203))
|
||||
- fix several issues when moving options between groups
|
||||
([30694c80](https://github.com/angular/angular.js/commit/30694c802763d46d6787f7298f47dfef53ed4229),
|
||||
[#10166](https://github.com/angular/angular.js/issues/10166))
|
||||
|
||||
|
||||
<a name="1.3.4"></a>
|
||||
# 1.3.4 highfalutin-petroglyph (2014-11-24)
|
||||
|
||||
@@ -60,7 +466,7 @@
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- ***:** use Object.create instead of creating temporary constructors
|
||||
- use Object.create instead of creating temporary constructors
|
||||
([bf6a79c3](https://github.com/angular/angular.js/commit/bf6a79c3484f474c300b5442ae73483030ef5782),
|
||||
[#10058](https://github.com/angular/angular.js/issues/10058))
|
||||
|
||||
@@ -2767,6 +3173,16 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$compile:** due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
|
||||
|
||||
The isolated scope of a component directive no longer leaks into the template
|
||||
that contains the instance of the directive. This means that you can no longer
|
||||
access the isolated scope from attributes on the element where the isolated
|
||||
directive is defined.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ Before you submit your pull request consider the following guidelines:
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push -f
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
@@ -30,14 +30,6 @@ module.exports = function(grunt) {
|
||||
benchmarksPath: 'benchmarks'
|
||||
}
|
||||
},
|
||||
parallel: {
|
||||
travis: {
|
||||
tasks: [
|
||||
util.parallelTask(['test:unit', 'test:promises-aplus', 'tests:docs'], {stream: true}),
|
||||
util.parallelTask(['test:e2e'])
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
connect: {
|
||||
devserver: {
|
||||
|
||||
Vendored
+5
-1
@@ -53,6 +53,7 @@ var angularFiles = {
|
||||
'src/ng/directive/form.js',
|
||||
'src/ng/directive/input.js',
|
||||
'src/ng/directive/ngBind.js',
|
||||
'src/ng/directive/ngChange.js',
|
||||
'src/ng/directive/ngClass.js',
|
||||
'src/ng/directive/ngCloak.js',
|
||||
'src/ng/directive/ngController.js',
|
||||
@@ -61,6 +62,8 @@ var angularFiles = {
|
||||
'src/ng/directive/ngIf.js',
|
||||
'src/ng/directive/ngInclude.js',
|
||||
'src/ng/directive/ngInit.js',
|
||||
'src/ng/directive/ngList.js',
|
||||
'src/ng/directive/ngModel.js',
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
@@ -70,7 +73,8 @@ var angularFiles = {
|
||||
'src/ng/directive/ngTransclude.js',
|
||||
'src/ng/directive/script.js',
|
||||
'src/ng/directive/select.js',
|
||||
'src/ng/directive/style.js'
|
||||
'src/ng/directive/style.js',
|
||||
'src/ng/directive/validators.js'
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<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>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></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>
|
||||
@@ -46,6 +47,12 @@
|
||||
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="interpolationAttr">
|
||||
<h2>attribute interpolation</h2>
|
||||
<div ng-repeat="row in data">
|
||||
<span ng-repeat="column in row" i="{{column.i}}" j="{{column.j}}">i,j attrs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngBindFn">
|
||||
<h2>bindings with functions</h2>
|
||||
<div ng-repeat="row in data">
|
||||
|
||||
@@ -202,6 +202,7 @@ var generate = function(version, file) {
|
||||
|
||||
// publish for testing
|
||||
exports.parseRawCommit = parseRawCommit;
|
||||
exports.printSection = printSection;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
|
||||
+62
-1
@@ -1,4 +1,4 @@
|
||||
/* global describe: false, it: false, expect: false */
|
||||
/* global describe: false, beforeEach: false, afterEach: false, it: false, expect: false */
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -44,4 +44,65 @@ describe('changelog.js', function() {
|
||||
expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('printSection', function() {
|
||||
var output;
|
||||
var streamMock = {
|
||||
write: function(str) {
|
||||
output += str;
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
output = '';
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there is 1 item per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [{subject: 'breaking change 1'}],
|
||||
module2: [{subject: 'breaking change 2'}]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:** breaking change 1\n' +
|
||||
'- **module2:** breaking change 2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should add a new line at the end of each breaking change list item ' +
|
||||
'when there are multiple items per component', function() {
|
||||
var title = 'test';
|
||||
var printCommitLinks = false;
|
||||
|
||||
var section = {
|
||||
module1: [
|
||||
{subject: 'breaking change 1.1'},
|
||||
{subject: 'breaking change 1.2'}
|
||||
],
|
||||
module2: [
|
||||
{subject: 'breaking change 2.1'},
|
||||
{subject: 'breaking change 2.2'}
|
||||
]
|
||||
};
|
||||
var expectedOutput =
|
||||
'\n' + '## test\n\n' +
|
||||
'- **module1:**\n' +
|
||||
' - breaking change 1.1\n' +
|
||||
' - breaking change 1.2\n' +
|
||||
'- **module2:**\n' +
|
||||
' - breaking change 2.1\n' +
|
||||
' - breaking change 2.2\n' +
|
||||
'\n';
|
||||
|
||||
ch.printSection(streamMock, title, section, printCommitLinks);
|
||||
expect(output).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ Use ngRoute to enable URL routing to your application. The ngRoute module suppor
|
||||
|
||||
## {@link ngAnimate ngAnimate}
|
||||
|
||||
Use ngAnimate to enable animation features into your application. Various core ng directives will provide
|
||||
Use ngAnimate to enable animation features within your application. Various core ng directives will provide
|
||||
animation hooks into your application when ngAnimate is included. Animations are defined by using CSS transitions/animations
|
||||
or JavaScript callbacks.
|
||||
|
||||
@@ -148,7 +148,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate CSS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Follow ngAnimate’s CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -156,7 +156,7 @@ or JavaScript callbacks.
|
||||
{@link ngAnimate JS-based animations}
|
||||
</td>
|
||||
<td>
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered, the animation can be triggered by referencing the CSS class within the HTML template code.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -273,7 +273,7 @@ Use ngSanitize to securely parse and manipulate HTML data in your application.
|
||||
|
||||
## {@link ngMock ngMock}
|
||||
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
|
||||
Use ngMock to inject and mock modules, factories, services and providers within your unit tests.
|
||||
|
||||
<div class="alert alert-info">Include the **angular-mocks.js** file into your test runner for this to work.</div>
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $location:ihshprfx
|
||||
@fullName Missing Hash Prefix
|
||||
@description
|
||||
|
||||
This error occurs when {@link ng.$location $location} service is configured to use a hash prefix but this prefix was not present in a url that the `$location` service was asked to parse.
|
||||
|
||||
For example if you configure `$location` service with prefix `'!'`:
|
||||
```
|
||||
myApp.config(function($locationProvider) {
|
||||
$locationProvider.hashPrefix('!');
|
||||
});
|
||||
```
|
||||
|
||||
If you enter the app at url `http://myapp.com/#/myView` this error will be thrown.
|
||||
|
||||
The correct url for this configuration is `http://myapp.com/#!/myView` (note the `'!'` after `'#'` symbol).
|
||||
@@ -3,7 +3,7 @@
|
||||
@fullName Response does not match configured parameter
|
||||
@description
|
||||
|
||||
This error occurs when the {@link ngResource.$resource `$resource`} service expects a response that can be deserialized as an array, receives an object, or vice versa.
|
||||
This error occurs when the {@link ngResource.$resource `$resource`} service expects a response that can be deserialized as an array but receives an object, or vice versa.
|
||||
By default, all resource actions expect objects, except `query` which expects arrays.
|
||||
|
||||
To resolve this error, make sure your `$resource` configuration matches the actual format of the data returned from the server.
|
||||
|
||||
@@ -35,7 +35,7 @@ var users = [ { name: 'Hank' }, { name: 'Francisco' } ];
|
||||
|
||||
$scope.getUsers = function() {
|
||||
return users;
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The maximum number of allowed iterations of the `$digest` cycle is controlled via TTL setting which can be configured via {@link ng.$rootScopeProvider $rootScopeProvider}.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
@fullName Bad `hasOwnProperty` Name
|
||||
@description
|
||||
|
||||
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allow.
|
||||
Occurs when you try to use the name `hasOwnProperty` in a context where it is not allowed.
|
||||
Generally, a name cannot be `hasOwnProperty` because it is used, internally, on a object
|
||||
and allowing such a name would break lookups on this object.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@ngdoc error
|
||||
@name ng:test
|
||||
@fullName Testability Not Found
|
||||
@description
|
||||
|
||||
Angular's testability helper, getTestability, requires a root element to be
|
||||
passed in. This helps differentiate between different Angular apps on the same
|
||||
page. This error is thrown when no injector is found for root element. It is
|
||||
often because the root element is outside of the ng-app.
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
# What does it do?
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on the [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into $location service and
|
||||
changes to $location are reflected into the browser address bar.
|
||||
The `$location` service parses the URL in the browser address bar (based on [`window.location`](https://developer.mozilla.org/en/window.location)) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into the `$location` service and
|
||||
changes to `$location` are reflected into the browser address bar.
|
||||
|
||||
**The $location service:**
|
||||
|
||||
@@ -21,7 +21,7 @@ changes to $location are reflected into the browser address bar.
|
||||
- Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
|
||||
|
||||
## Comparing $location to window.location
|
||||
## Comparing `$location` to `window.location`
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
@@ -68,7 +68,7 @@ changes to $location are reflected into the browser address bar.
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## When should I use $location?
|
||||
## When should I use `$location`?
|
||||
Any time your application needs to react to a change in the current URL or if you want to change
|
||||
the current URL in the browser.
|
||||
|
||||
@@ -85,7 +85,7 @@ others customizing the configuration can enable new features.
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## $location service configuration
|
||||
## `$location` service configuration
|
||||
|
||||
To configure the `$location` service, retrieve the
|
||||
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
|
||||
@@ -344,7 +344,7 @@ to anchors on the same page without needing to know on which page the user curre
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html). Requiring a `<base>` tag is also important for
|
||||
this case, as it allows Angular to differentiate between the part of the url that is the application
|
||||
base and the path that should be handeled by the application.
|
||||
base and the path that should be handled by the application.
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
@@ -358,7 +358,7 @@ legacy browsers and hashbang links in modern browser:
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents address bar of the browser.
|
||||
input represents the address bar of the browser.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
@@ -835,7 +835,7 @@ angular.module('locationExample', [])
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link ng.$location $location API}
|
||||
* {@link ng.$location `$location` API}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ added it as a dependency, you can test a few things:
|
||||
##Supported directives
|
||||
Currently, ngAria interfaces with the following directives:
|
||||
|
||||
* <a href="#ngmodel">ngModel</a>
|
||||
* <a href="#ngdisabled">ngDisabled</a>
|
||||
* <a href="#ngshow">ngShow</a>
|
||||
* <a href="#nghide">ngHide</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngClick</a>
|
||||
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
|
||||
* {@link guide/accessibility#ngmodel ngModel}
|
||||
* {@link guide/accessibility#ngdisabled ngDisabled}
|
||||
* {@link guide/accessibility#ngshow ngShow}
|
||||
* {@link guide/accessibility#nghide ngHide}
|
||||
* {@link guide/accessibility#ngclick ngClick}
|
||||
* {@link guide/accessibility#ngdblclick ngDblClick}
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
@@ -206,9 +206,9 @@ shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
|
||||
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
|
||||
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2 id="ngclick-and-ngdblclick">ngClick and ngDblclick</h2>
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
|
||||
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
|
||||
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
|
||||
|
||||
@@ -200,7 +200,7 @@ code is present, and match the CSS class name on the element, then AngularJS wil
|
||||
## Class and ngClass animation hooks
|
||||
|
||||
AngularJS also pays attention to CSS class changes on elements by triggering the **add** and **remove** hooks.
|
||||
This means that if a CSS class is added to or removed from an element then an animation can be executed in between
|
||||
This means that if a CSS class is added to or removed from an element then an animation can be executed in between,
|
||||
before the CSS class addition or removal is finalized. (Keep in mind that AngularJS will only be
|
||||
able to capture class changes if an **expression** or the **ng-class** directive is used on the element.)
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ As a best practice, consider adding an `ng-strict-di` directive on the same elem
|
||||
```
|
||||
|
||||
This will ensure that all services in your application are properly annotated.
|
||||
See the {@link guide/di#using-strict-dependency-injection dependancy injection strict mode} docs
|
||||
See the {@link guide/di#using-strict-dependency-injection dependency injection strict mode} docs
|
||||
for more.
|
||||
|
||||
|
||||
@@ -156,4 +156,4 @@ until `angular.resumeBootstrap()` is called.
|
||||
|
||||
`angular.resumeBootstrap()` takes an optional array of modules that
|
||||
should be added to the original list of modules that the app was
|
||||
about to be bootstrapped with.
|
||||
about to be bootstrapped with.
|
||||
@@ -326,7 +326,7 @@ describe('state', function() {
|
||||
expect(childScope.timeOfDay).toBe('morning');
|
||||
expect(childScope.name).toBe('Mattie');
|
||||
expect(grandChildScope.timeOfDay).toBe('evening');
|
||||
expect(grandChildScope.name).toBe('Gingerbreak Baby');
|
||||
expect(grandChildScope.name).toBe('Gingerbread Baby');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
@@ -13,21 +13,26 @@ Angular sets these CSS classes. It is up to your application to provide useful s
|
||||
is defined. (see {@link guide/scope scope} guide for more information about scopes)
|
||||
|
||||
* `ng-isolate-scope`
|
||||
- **Usage:** angular applies this class to any element for which a new
|
||||
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
|
||||
- **Usage:** angular applies this class to any element for which a new
|
||||
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
|
||||
|
||||
* `ng-binding`
|
||||
- **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or
|
||||
`{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide)
|
||||
|
||||
* `ng-invalid`, `ng-valid`
|
||||
- **Usage:** angular applies this class to an input widget element if that element's input does
|
||||
- **Usage:** angular applies this class to a form control widget element if that element's input does
|
||||
not pass validation. (see {@link ng.directive:input input} directive)
|
||||
|
||||
* `ng-pristine`, `ng-dirty`
|
||||
- **Usage:** angular {@link ng.directive:input input} directive applies `ng-pristine` class
|
||||
to a new input widget element which did not have user interaction. Once the user interacts with
|
||||
the input widget the class is changed to `ng-dirty`.
|
||||
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-pristine` class
|
||||
to a new form control widget which did not have user interaction. Once the user interacts with
|
||||
the form control, the class is changed to `ng-dirty`.
|
||||
|
||||
* `ng-touched`, `ng-untouched`
|
||||
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-untouched` class
|
||||
to a new form control widget which has not been blurred. Once the user blurs the form control,
|
||||
the class is changed to `ng-touched`.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
@@ -174,6 +174,15 @@ For example, we could fix the example above by instead writing:
|
||||
</svg>
|
||||
```
|
||||
|
||||
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes), such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind to is naturally camelcased.
|
||||
|
||||
For example, to bind to `viewBox`, we can write:
|
||||
|
||||
```html
|
||||
<svg ng-attr-view_box="{{viewBox}}">
|
||||
</svg>
|
||||
```
|
||||
|
||||
|
||||
## Creating Directives
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# E2E Testing
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Note:** In the past, end to end testing could be done with a deprecated tool called
|
||||
**Note:** In the past, end-to-end testing could be done with a deprecated tool called
|
||||
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
|
||||
is now in maintenance mode.
|
||||
</div>
|
||||
@@ -14,7 +14,7 @@ is now in maintenance mode.
|
||||
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
|
||||
verify the correctness of new features, catch bugs and notice regressions. Unit tests
|
||||
are the first line of defense for catching bugs, but sometimes issues come up with integration
|
||||
between components which can't be captured in a unit test. End to end tests are made to find
|
||||
between components which can't be captured in a unit test. End-to-end tests are made to find
|
||||
these problems.
|
||||
|
||||
We have built [Protractor](https://github.com/angular/protractor), an end
|
||||
@@ -23,7 +23,7 @@ Angular application.
|
||||
|
||||
## Using Protractor
|
||||
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests that are also
|
||||
Protractor is a [Node.js](http://nodejs.org) program, and runs end-to-end tests that are also
|
||||
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
|
||||
to control browsers and simulate user actions.
|
||||
|
||||
@@ -77,7 +77,7 @@ filter the list of items.
|
||||
## Example
|
||||
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look
|
||||
at the embedded examples in the Angular documentation (For example, {@link $http $http}
|
||||
has an end to end test in the example under the `protractor.js` tag).
|
||||
has an end-to-end test in the example under the `protractor.js` tag).
|
||||
|
||||
## Caveats
|
||||
|
||||
|
||||
@@ -26,8 +26,16 @@ Angular expressions are like JavaScript expressions with the following differenc
|
||||
* **Forgiving:** In JavaScript, trying to evaluate undefined properties generates `ReferenceError`
|
||||
or `TypeError`. In Angular, expression evaluation is forgiving to `undefined` and `null`.
|
||||
|
||||
* **No Control Flow Statements:** you cannot use the following in an Angular expression:
|
||||
* **No Control Flow Statements:** You cannot use the following in an Angular expression:
|
||||
conditionals, loops, or exceptions.
|
||||
|
||||
* **No Function Declarations:** You cannot declare functions in an Angular expression,
|
||||
even inside `ng-init` directive.
|
||||
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
|
||||
|
||||
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
|
||||
displaying it.
|
||||
@@ -164,11 +172,11 @@ expression. The reason behind this is core to the Angular philosophy that applic
|
||||
be in controllers, not the views. If you need a real conditional, loop, or to throw from a view
|
||||
expression, delegate to a JavaScript method instead.
|
||||
|
||||
## No RegExp creation with literal notation
|
||||
## No function declarations or RegExp creation with literal notation
|
||||
|
||||
You can't create regular expressions from within AngularJS expressions. This is to avoid complex
|
||||
model transformation logic inside templates. Such logic is better placed in a controller or in a dedicated
|
||||
filter where it can be tested properly.
|
||||
You can't declare functions or create regular expressions from within AngularJS expressions. This is
|
||||
to avoid complex model transformation logic inside templates. Such logic is better placed in a
|
||||
controller or in a dedicated filter where it can be tested properly.
|
||||
|
||||
## `$event`
|
||||
|
||||
@@ -298,14 +306,16 @@ then the expression is not fulfilled and will remain watched.
|
||||
|
||||
### How to benefit from one-time binding
|
||||
|
||||
When interpolating text or attributes. If the expression, once set, will not change
|
||||
then it is a candidate for one-time expression.
|
||||
If the expression will not change once set, it is a candidate for one-time binding.
|
||||
Here are three example cases.
|
||||
|
||||
When interpolating text or attributes:
|
||||
|
||||
```html
|
||||
<div name="attr: {{::color}}">text: {{::name}}</div>
|
||||
```
|
||||
|
||||
When using a directive with bidirectional binding and the parameters will not change
|
||||
When using a directive with bidirectional binding and the parameters will not change:
|
||||
|
||||
```js
|
||||
someModule.directive('someDirective', function() {
|
||||
@@ -324,7 +334,7 @@ someModule.directive('someDirective', function() {
|
||||
```
|
||||
|
||||
|
||||
When using a directive that takes an expression
|
||||
When using a directive that takes an expression:
|
||||
|
||||
```html
|
||||
<ul>
|
||||
|
||||
@@ -27,8 +27,8 @@ for other directives to augment its behavior.
|
||||
E-mail: <input type="email" ng-model="user.email" /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
<button ng-click="reset()">RESET</button>
|
||||
<button ng-click="update(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
<pre>form = {{user | json}}</pre>
|
||||
<pre>master = {{master | json}}</pre>
|
||||
@@ -77,29 +77,29 @@ To allow styling of form as well as controls, `ngModel` adds these CSS classes:
|
||||
|
||||
The following example uses the CSS to display validity of each form control.
|
||||
In the example both `user.name` and `user.email` are required, but are rendered
|
||||
with red background only when they are dirty. This ensures that the user is not distracted
|
||||
with an error until after interacting with the control, and failing to satisfy its validity.
|
||||
with red background only after the input is blurred (loses focus).
|
||||
This ensures that the user is not distracted with an error until after interacting with the control,
|
||||
and failing to satisfy its validity.
|
||||
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form novalidate class="css-form">
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" required /><br />
|
||||
Name: <input type="text" ng-model="user.name" required /><br />
|
||||
E-mail: <input type="email" ng-model="user.email" required /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
<button ng-click="reset()">RESET</button>
|
||||
<button ng-click="update(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset()" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style type="text/css">
|
||||
.css-form input.ng-invalid.ng-dirty {
|
||||
.css-form input.ng-invalid.ng-touched {
|
||||
background-color: #FA787E;
|
||||
}
|
||||
|
||||
.css-form input.ng-valid.ng-dirty {
|
||||
.css-form input.ng-valid.ng-touched {
|
||||
background-color: #78FA89;
|
||||
}
|
||||
</style>
|
||||
@@ -131,7 +131,7 @@ A form is an instance of {@link form.FormController FormController}.
|
||||
The form instance can optionally be published into the scope using the `name` attribute.
|
||||
|
||||
Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an
|
||||
instance of {@link ngModel.NgModelController NgModelController}.Such a control instance
|
||||
instance of {@link ngModel.NgModelController NgModelController}. Such a control instance
|
||||
can be published as a property of the form instance using the `name` attribute on the input control.
|
||||
The name attribute specifies the name of the property on the form instance.
|
||||
|
||||
@@ -140,34 +140,45 @@ the view using the standard binding primitives.
|
||||
|
||||
This allows us to extend the above example with these features:
|
||||
|
||||
- RESET button is enabled only if form has some changes
|
||||
- SAVE button is enabled only if form has some changes and is valid
|
||||
- custom error messages for `user.email` and `user.agree`
|
||||
- Custom error message displayed after the user interacted with a control (i.e. when `$touched` is set)
|
||||
- Custom error message displayed upon submitting the form (`$submitted` is set), even if the user
|
||||
didn't interact with a control
|
||||
|
||||
|
||||
<example module="formExample">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ExampleController">
|
||||
<form name="form" class="css-form" novalidate>
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" name="uName" required /><br />
|
||||
<input type="text" ng-model="user.name" name="uName" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.uName.$touched">
|
||||
<div ng-show="form.uName.$error.required">Tell us your name.</div>
|
||||
</div>
|
||||
|
||||
E-mail:
|
||||
<input type="email" ng-model="user.email" name="uEmail" required/><br />
|
||||
<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
|
||||
<input type="email" ng-model="user.email" name="uEmail" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.uEmail.$touched">
|
||||
<span ng-show="form.uEmail.$error.required">Tell us your email.</span>
|
||||
<span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
|
||||
</div>
|
||||
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
Gender:
|
||||
<input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female
|
||||
<br />
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required="" />
|
||||
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required />
|
||||
I agree: <input ng-show="user.agree" type="text" ng-model="user.agreeSign"
|
||||
required /><br />
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
I agree:
|
||||
<input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />
|
||||
<br />
|
||||
<div ng-show="form.$submitted || form.userAgree.$touched">
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
</div>
|
||||
|
||||
<button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button>
|
||||
<button ng-click="update(user)"
|
||||
ng-disabled="form.$invalid || isUnchanged(user)">SAVE</button>
|
||||
<input type="button" ng-click="reset(form)" value="Reset" />
|
||||
<input type="submit" ng-click="update(user)" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
</file>
|
||||
@@ -181,14 +192,14 @@ This allows us to extend the above example with these features:
|
||||
$scope.master = angular.copy(user);
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.reset = function(form) {
|
||||
if (form) {
|
||||
form.$setPristine();
|
||||
form.$setUntouched();
|
||||
}
|
||||
$scope.user = angular.copy($scope.master);
|
||||
};
|
||||
|
||||
$scope.isUnchanged = function(user) {
|
||||
return angular.equals(user, $scope.master);
|
||||
};
|
||||
|
||||
$scope.reset();
|
||||
}]);
|
||||
</file>
|
||||
@@ -282,7 +293,7 @@ after last change.
|
||||
|
||||
Angular provides basic implementation for most common HTML5 {@link ng.directive:input input}
|
||||
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
|
||||
{@link input[email] email}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
|
||||
{@link input[email] email}, {@link input[date] date}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
|
||||
as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`,
|
||||
`min`, `max`).
|
||||
|
||||
@@ -328,7 +339,7 @@ In the following example we create two directives:
|
||||
<div>
|
||||
Username:
|
||||
<input type="text" ng-model="name" name="name" username />{{name}}<br />
|
||||
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
|
||||
<span ng-show="form.name.$pending.username">Checking if this name is available...</span>
|
||||
<span ng-show="form.name.$error.username">This username is already taken!</span>
|
||||
</div>
|
||||
|
||||
|
||||
+5
-184
@@ -6,7 +6,7 @@
|
||||
# Internet Explorer Compatibility
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Note:** AngularJS 1.3 is dropping support for IE8. Read more about it on
|
||||
**Note:** AngularJS 1.3 has dropped support for IE8. Read more about it on
|
||||
[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html).
|
||||
AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time
|
||||
addressing issues specific to IE8 or earlier.
|
||||
@@ -14,7 +14,7 @@ addressing issues specific to IE8 or earlier.
|
||||
|
||||
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
|
||||
attributes and tags. Read this document if you are planning on deploying your Angular application
|
||||
on IE8 or earlier.
|
||||
on IE.
|
||||
|
||||
The project currently supports and will attempt to fix bugs for IE9 and above. The continuous
|
||||
integration server runs all the tests against IE9, IE10, and IE11. See
|
||||
@@ -25,186 +25,7 @@ We do not run tests on IE8 and below. A subset of the AngularJS functionality ma
|
||||
browsers, but it is up to you to test and decide whether it works for your particular app.
|
||||
|
||||
|
||||
## Short Version
|
||||
|
||||
To make your Angular application work on IE please make sure that:
|
||||
|
||||
1. You polyfill JSON.stringify for IE7 and below. You can use
|
||||
[JSON2](https://github.com/douglascrockford/JSON-js) or
|
||||
[JSON3](http://bestiejs.github.com/json3/) polyfills for this.
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<!--[if lte IE 7]>
|
||||
<script src="/path/to/json2.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
2. add `id="ng-app"` to the root element in conjunction with `ng-app` attribute
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
...
|
||||
</html>
|
||||
```
|
||||
|
||||
3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version
|
||||
`<div ng-view>` instead), or
|
||||
|
||||
4. if you **do use** custom element tags, then you must take these steps to make IE 8 and below happy:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
<script>
|
||||
document.createElement('ng-include');
|
||||
document.createElement('ng-pluralize');
|
||||
document.createElement('ng-view');
|
||||
|
||||
// Optionally these for CSS
|
||||
document.createElement('ng:include');
|
||||
document.createElement('ng:pluralize');
|
||||
document.createElement('ng:view');
|
||||
</script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
5. Use `ng-style` tags instead of `style="{{ someCss }}"`. The later works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
|
||||
|
||||
The **important** parts are:
|
||||
|
||||
* `xmlns:ng` - *namespace* - you need one namespace for each custom tag you are planning on
|
||||
using.
|
||||
|
||||
* `document.createElement(yourTagName)` - *creation of custom tag names* - Since this is an
|
||||
issue only for older version of IE you need to load it conditionally. For each tag which does
|
||||
not have namespace and which is not defined in HTML you need to pre-declare it to make IE
|
||||
happy.
|
||||
|
||||
|
||||
## Long Version
|
||||
|
||||
IE has issues with element tag names which are not standard HTML tag names. These fall into two
|
||||
categories, and each category has its own fix.
|
||||
|
||||
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
|
||||
have corresponding namespace declaration on `<html xmlns:my="ignored">`
|
||||
|
||||
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
|
||||
`document.createElement('my-tag')`
|
||||
|
||||
* If you are planning on styling the custom tag with CSS selectors, then it must be
|
||||
pre-created using `document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
|
||||
## The Good News
|
||||
|
||||
The good news is that these restrictions only apply to element tag names, and not to element
|
||||
attribute names. So this requires no special handling in IE: `<div my-tag your:tag></div>`.
|
||||
|
||||
|
||||
## What happens if I fail to do this?
|
||||
|
||||
Suppose you have HTML with unknown tag `mytag` (this could also be `my:tag` or `my-tag` with same
|
||||
result):
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<mytag>some text</mytag>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
It should parse into the following DOM:
|
||||
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
```
|
||||
|
||||
The expected behavior is that the `BODY` element has a child element `mytag`, which in turn has
|
||||
the text `some text`.
|
||||
|
||||
But this is not what IE does (if the above fixes are not included):
|
||||
|
||||
```
|
||||
#document
|
||||
+- HTML
|
||||
+- BODY
|
||||
+- mytag
|
||||
+- #text: some text
|
||||
+- /mytag
|
||||
```
|
||||
|
||||
In IE, the behavior is that the `BODY` element has three children:
|
||||
|
||||
1. A self closing `mytag`. Example of self closing tag is `<br/>`. The trailing `/` is optional,
|
||||
but the `<br>` tag is not allowed to have any children, and browsers consider `<br>some
|
||||
text</br>` as three siblings not a `<br>` with `some text` as child.
|
||||
|
||||
2. A text node with `some text`. This should have been a child of `mytag` above, not a sibling.
|
||||
|
||||
3. A corrupt self closing `/mytag`. This is corrupt since element names are not allowed to have
|
||||
the `/` character. Furthermore this closing element should not be part of the DOM since it is
|
||||
only used to delineate the structure of the DOM.
|
||||
|
||||
|
||||
## CSS Styling of Custom Tag Names
|
||||
|
||||
To make CSS selectors work with custom elements, the custom element name must be pre-created with
|
||||
`document.createElement('my-tag')` regardless of XML namespace.
|
||||
|
||||
```html
|
||||
<html xmlns:ng="needed for ng: namespace">
|
||||
<head>
|
||||
<!--[if lte IE 8]>
|
||||
<script>
|
||||
// needed to make ng-include parse properly
|
||||
document.createElement('ng-include');
|
||||
|
||||
// needed to enable CSS reference
|
||||
document.createElement('ng:view');
|
||||
</script>
|
||||
<![endif]-->
|
||||
<style>
|
||||
ng\:view {
|
||||
display: block;
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
ng-include {
|
||||
display: block;
|
||||
border: 1px solid blue;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<ng:view></ng:view>
|
||||
<ng-include></ng-include>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
To ensure your Angular application works on IE please consider:
|
||||
|
||||
1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
|
||||
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
|
||||
@@ -53,7 +53,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
## Specific Topics
|
||||
|
||||
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
|
||||
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Faceb0ok library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
|
||||
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
|
||||
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
|
||||
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
|
||||
@@ -62,6 +62,7 @@ In Angular applications, you move the job of filling page templates with data fr
|
||||
|
||||
## Tools
|
||||
|
||||
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
|
||||
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
|
||||
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
|
||||
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
|
||||
@@ -109,6 +110,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [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
|
||||
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
|
||||
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
|
||||
|
||||
###Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
@@ -117,12 +119,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
### Courses
|
||||
* **Free online:**
|
||||
[thinkster.io](http://thinkster.io),
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
|
||||
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1),
|
||||
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
|
||||
* **Paid online:**
|
||||
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
|
||||
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
|
||||
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
|
||||
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
|
||||
* **Paid onsite:**
|
||||
[angularbootcamp.com](http://angularbootcamp.com/)
|
||||
|
||||
+259
-145
@@ -15,28 +15,68 @@ which drives many of these changes.
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
- **$parse:**
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
## Angular Expression Parsing (`$parse` + `$interpolate`)
|
||||
|
||||
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
|
||||
|
||||
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
|
||||
This is to disallow changing the behaviour of existing functions
|
||||
in an unforseen fashion.
|
||||
in an unforeseen fashion.
|
||||
|
||||
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
|
||||
|
||||
The (deprecated) __proto__ property does not work inside angular expressions
|
||||
anymore.
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
|
||||
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
|
||||
|
||||
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
|
||||
expressions. If you really need them for some reason, please wrap/bind them to make them
|
||||
less dangerous, then make them available through the scope object.
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
|
||||
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
|
||||
|
||||
This prevents the use of `Object` inside angular expressions.
|
||||
If you need Object.keys, make it accessible in the scope.
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
|
||||
- due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
|
||||
- due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
|
||||
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
|
||||
It can no longer be turned on.
|
||||
Two methods have been removed:
|
||||
* `$parseProvider.unwrapPromises`
|
||||
* `$parseProvider.logPromiseWarnings`
|
||||
|
||||
|
||||
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
|
||||
the function returned by `$interpolate`
|
||||
no longer has a `.parts` array set on it.
|
||||
|
||||
Instead it has two arrays:
|
||||
* `.expressions`, an array of the expressions in the
|
||||
interpolated text. The expressions are parsed with
|
||||
`$parse`, with an extra layer converting them to strings
|
||||
when computed
|
||||
* `.separators`, an array of strings representing the
|
||||
separations between interpolations in the text.
|
||||
This array is **always** 1 item longer than the
|
||||
`.expressions` array for easy merging with it
|
||||
|
||||
|
||||
|
||||
|
||||
## Miscellaneous Angular helpers
|
||||
|
||||
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
|
||||
|
||||
This changes `angular.copy` so that it applies the prototype of the original
|
||||
object to the copied object. Previously, `angular.copy` would copy properties
|
||||
of the original object's prototype chain directly onto the copied object.
|
||||
@@ -53,17 +93,54 @@ not filter them with `hasOwnProperty`.
|
||||
**Be aware that this change also uses a feature that is not compatible with
|
||||
IE8.** If you need this to work on IE8 then you would need to provide a polyfill
|
||||
for `Object.create` and `Object.getPrototypeOf`.
|
||||
- **core:** due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
|
||||
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
|
||||
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
|
||||
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
|
||||
|
||||
Closes #3969
|
||||
Closes #4277
|
||||
Closes #7960
|
||||
|
||||
|
||||
- **$compile:** due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before, you will have to
|
||||
manually do this yourself now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## jqLite / JQuery
|
||||
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
|
||||
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
|
||||
the jQuery `detach()` method does not trigger the `$destroy` event.
|
||||
If you want to destroy Angular data attached to the element, use `remove()`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Angular HTML Compiler (`$compile`)
|
||||
|
||||
|
||||
- due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
|
||||
|
||||
The isolated scope of a component directive no longer leaks into the template
|
||||
that contains the instance of the directive. This means that you can no longer
|
||||
access the isolated scope from attributes on the element where the isolated
|
||||
directive is defined.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/10236 for an example.
|
||||
|
||||
- due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
|
||||
|
||||
|
||||
Requesting isolate scope and any other scope on a single element is an error.
|
||||
@@ -77,9 +154,50 @@ If you find that your code is now throwing a `$compile:multidir` error,
|
||||
check that you do not have directives on the same element that are trying
|
||||
to request both an isolate and a non-isolate scope and fix your code.
|
||||
|
||||
Closes #4402
|
||||
Closes #4421
|
||||
- **NgModel:** due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
- due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
|
||||
replace the element that they are on will be removed in the next major angular version.
|
||||
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
|
||||
problems compared to what it solves. Also, with Web Components it is normal to have
|
||||
custom elements in the DOM.
|
||||
|
||||
|
||||
- due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
|
||||
calling `attr.$observe` no longer returns the observer function, but a
|
||||
deregistration function instead. To migrate the code follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = attr.$observe('someAttr', function(value) {
|
||||
console.log(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
After:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = function(value) {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
attr.$observe('someAttr', observer);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
## Forms, Inputs and ngModel
|
||||
|
||||
- due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
|
||||
|
||||
|
||||
If an expression is used on ng-pattern (such as `ng-pattern="exp"`) or on the
|
||||
@@ -95,45 +213,70 @@ 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),
|
||||
|
||||
|
||||
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
|
||||
|
||||
|
||||
This commit changes the API on `NgModelController`, both semantically and
|
||||
in terms of adding and renaming methods.
|
||||
|
||||
* `$setViewValue(value)` -
|
||||
This method still changes the `$viewValue` but does not immediately commit this
|
||||
change through to the `$modelValue` as it did previously.
|
||||
Now the value is committed only when a trigger specified in an associated
|
||||
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
|
||||
specified for the trigger then the change will also be debounced before being
|
||||
committed.
|
||||
In most cases this should not have a significant impact on how `NgModelController`
|
||||
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
|
||||
a (potentially debounced) commit immediately.
|
||||
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
|
||||
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
|
||||
to cancel any pending debounced updates and to re-render the input.
|
||||
|
||||
To migrate code that used `$cancelUpdate()` follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$cancelUpdate();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$rollbackViewValue();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- types date, time, datetime-local, month, week now always
|
||||
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Scopes and Digests (`$scope`)
|
||||
|
||||
- due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
|
||||
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),
|
||||
forEach will iterate only over the initial number of items in
|
||||
the array. So if items are added to the array during the iteration, these won't
|
||||
be iterated over during the initial forEach call.
|
||||
|
||||
This change also makes our forEach behave more like Array#forEach.
|
||||
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
|
||||
previously it was possible to set jqLite data on Text/Comment
|
||||
nodes, but now that is allowed only on Element and Document nodes just like in
|
||||
jQuery. We don't expect that app code actually depends on this accidental feature.
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
|
||||
|
||||
If you expected `toJson` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
- **$compile:** due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
|
||||
replace the element that they are on will be removed in the next major angular version.
|
||||
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
|
||||
problems compared to what it solves. Also, with Web Components it is normal to have
|
||||
custom elements in the DOM.
|
||||
|
||||
- **$parse:** due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
|
||||
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
|
||||
It can no longer be turned on.
|
||||
Two methods have been removed:
|
||||
* `$parseProvider.unwrapPromises`
|
||||
* `$parseProvider.logPromiseWarnings`
|
||||
|
||||
- **Scope:** due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
|
||||
- due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
|
||||
[#7445](https://github.com/angular/angular.js/issues/7445),
|
||||
[#7523](https://github.com/angular/angular.js/issues/7523)
|
||||
`$broadcast` and `$emit` will now reset the `currentScope` property of the event to
|
||||
@@ -141,11 +284,11 @@ jQuery. We don't expect that app code actually depends on this accidental featur
|
||||
`currentScope` property, it should be migrated to use `targetScope` instead. All of these cases
|
||||
should be considered programming bugs.
|
||||
|
||||
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
|
||||
the jQuery `detach()` method does not trigger the `$destroy` event.
|
||||
If you want to destroy Angular data attached to the element, use `remove()`.
|
||||
|
||||
|
||||
|
||||
|
||||
## Server Requests (`$http`, `$resource`)
|
||||
- **$http:** due to [ad4336f9](https://github.com/angular/angular.js/commit/ad4336f9359a073e272930f8f9bcd36587a8648f),
|
||||
|
||||
|
||||
@@ -197,7 +340,24 @@ More details on the new interceptors API (which has been around as of v1.1.4) ca
|
||||
{@link $http#interceptors interceptors}
|
||||
|
||||
|
||||
- **injector:** due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
|
||||
|
||||
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
|
||||
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
|
||||
correct events to detect errors, i.e. even empty responses can be successful.
|
||||
|
||||
|
||||
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
|
||||
|
||||
If you expected `$resource` to strip these types of properties before,
|
||||
you will have to manually do this yourself now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Modules and Injector (`$inject`)
|
||||
|
||||
- due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
|
||||
|
||||
Previously, config blocks would be able to control behaviour of provider registration, due to being
|
||||
invoked prior to provider registration. Now, provider registration always occurs prior to configuration
|
||||
@@ -234,66 +394,13 @@ and "$dependentProvider" would have actually accomplished something and changed
|
||||
app. This is no longer possible within a single module.
|
||||
|
||||
|
||||
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
|
||||
|
||||
|
||||
This commit changes the API on `NgModelController`, both semantically and
|
||||
in terms of adding and renaming methods.
|
||||
|
||||
* `$setViewValue(value)` -
|
||||
This method still changes the `$viewValue` but does not immediately commit this
|
||||
change through to the `$modelValue` as it did previously.
|
||||
Now the value is committed only when a trigger specified in an associated
|
||||
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
|
||||
specified for the trigger then the change will also be debounced before being
|
||||
committed.
|
||||
In most cases this should not have a significant impact on how `NgModelController`
|
||||
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
|
||||
a (potentially debounced) commit immediately.
|
||||
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
|
||||
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
|
||||
to cancel any pending debounced updates and to re-render the input.
|
||||
|
||||
To migrate code that used `$cancelUpdate()` follow the example below:
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$cancelUpdate();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
$scope.resetWithCancel = function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
$scope.myForm.myInput1.$rollbackViewValue();
|
||||
$scope.myValue = '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
|
||||
the function returned by `$interpolate`
|
||||
no longer has a `.parts` array set on it.
|
||||
|
||||
Instead it has two arrays:
|
||||
* `.expressions`, an array of the expressions in the
|
||||
interpolated text. The expressions are parsed with
|
||||
`$parse`, with an extra layer converting them to strings
|
||||
when computed
|
||||
* `.separators`, an array of strings representing the
|
||||
separations between interpolations in the text.
|
||||
This array is **always** 1 item longer than the
|
||||
`.expressions` array for easy merging with it
|
||||
## Animation (`ngAnimate`)
|
||||
|
||||
|
||||
- **$animate:** due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
|
||||
- due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
|
||||
`$animate` will no longer default the after parameter to the last element of the parent
|
||||
container. Instead, when after is not specified, the new element will be inserted as the
|
||||
first child of the parent container.
|
||||
@@ -308,7 +415,7 @@ to:
|
||||
|
||||
|
||||
|
||||
- **$animate:** due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
|
||||
- due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
|
||||
|
||||
Any class-based animation code that makes use of transitions
|
||||
and uses the setup CSS classes (such as class-add and class-remove) must now
|
||||
@@ -343,45 +450,52 @@ After:
|
||||
Please view the documentation for ngAnimate for more info.
|
||||
|
||||
|
||||
- **$compile:** due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
|
||||
calling `attr.$observe` no longer returns the observer function, but a
|
||||
deregistration function instead. To migrate the code follow the example below:
|
||||
## Testing
|
||||
|
||||
- due to [85880a64](https://github.com/angular/angular.js/commit/85880a64900fa22a61feb926bf52de0965332ca5), some deprecated features of
|
||||
Protractor tests no longer work.
|
||||
|
||||
`by.binding(descriptor)` no longer allows using the surrounding interpolation
|
||||
markers in the descriptor (the default interpolation markers are `{{}}`).
|
||||
Previously, these were optional.
|
||||
|
||||
Before:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = attr.$observe('someAttr', function(value) {
|
||||
console.log(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
var el = element(by.binding('{{foo}}'));
|
||||
|
||||
After:
|
||||
|
||||
directive('directiveName', function() {
|
||||
return {
|
||||
link: function(scope, elm, attr) {
|
||||
var observer = function(value) {
|
||||
console.log(value);
|
||||
};
|
||||
var el = element(by.binding('foo'));
|
||||
|
||||
attr.$observe('someAttr', observer);
|
||||
}
|
||||
};
|
||||
});
|
||||
Prefixes `ng_` and `x-ng-` are no longer allowed for models. Use `ng-model`.
|
||||
|
||||
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
|
||||
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
|
||||
correct events to detect errors, i.e. even empty responses can be successful.
|
||||
`by.repeater` cannot find elements by row and column which are not children of
|
||||
the row. For example, if your template is
|
||||
|
||||
- **build:** due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
|
||||
<div ng-repeat="foo in foos">{{foo.name}}</div>
|
||||
|
||||
Before:
|
||||
|
||||
var el = element(by.repeater('foo in foos').row(2).column('foo.name'))
|
||||
|
||||
After:
|
||||
|
||||
You may either enclose `{{foo.name}}` in a child element
|
||||
|
||||
<div ng-repeat="foo in foos"><span>{{foo.name}}</span></div>
|
||||
|
||||
or simply use:
|
||||
|
||||
var el = element(by.repeater('foo in foos').row(2))
|
||||
|
||||
|
||||
## Internet Explorer 8
|
||||
|
||||
- due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
|
||||
As communicated before, IE8 is no longer supported.
|
||||
- **input:** types date, time, datetime-local, month, week now always
|
||||
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -763,7 +877,7 @@ of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanit
|
||||
module is not loaded) and the bound expression evaluates to a value that is not trusted an
|
||||
exception is thrown.
|
||||
|
||||
When using this directive you can either include `ngSanitize` in your module's dependencis (See the
|
||||
When using this directive you can either include `ngSanitize` in your module's dependencies (See the
|
||||
example at the {@link ngBindHtml} reference) or use the {@link $sce} service to set the value as
|
||||
trusted.
|
||||
|
||||
@@ -1020,10 +1134,10 @@ freely available to JavaScript code (as before).
|
||||
|
||||
Angular expressions execute in a limited context. They do not have
|
||||
direct access to the global scope, `window`, `document` or the Function
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
constructor. However, they have direct access to names/properties on
|
||||
the scope chain. It has been a long standing best practice to keep
|
||||
sensitive APIs outside of the scope chain (in a closure or your
|
||||
controller.) That's easier said that done for two reasons:
|
||||
controller.) That's easier said than done for two reasons:
|
||||
|
||||
1. JavaScript does not have a notion of private properties so if you need
|
||||
someone on the scope chain for JavaScript use, you also expose it to
|
||||
|
||||
@@ -177,7 +177,7 @@ for example, only a portion of the view needs to be controlled by Angular.
|
||||
|
||||
To examine the scope in the debugger:
|
||||
|
||||
1. right click on the element of interest in your browser and select 'inspect element'. You
|
||||
1. Right click on the element of interest in your browser and select 'inspect element'. You
|
||||
should see the browser debugger with the element you clicked on highlighted.
|
||||
|
||||
2. The debugger allows you to access the currently selected element in the console as `$0`
|
||||
@@ -345,7 +345,7 @@ access on JavaScript object.
|
||||
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
|
||||
|
||||
- Watching *by reference* ({@link
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
|
||||
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient strategy.
|
||||
- Watching *collection contents* ({@link
|
||||
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
|
||||
- Watching *by value* ({@link
|
||||
|
||||
@@ -116,7 +116,7 @@ npm --version
|
||||
```
|
||||
|
||||
|
||||
<div class="alert alert-info">If you need to run a different versions of node.js
|
||||
<div class="alert alert-info">If you need to run different versions of node.js
|
||||
in your local environment, consider installing
|
||||
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
|
||||
Node Version Manager (nvm)
|
||||
@@ -160,7 +160,7 @@ globally and run directly from a terminal/command prompt. You don't need to do t
|
||||
tutorial, but if you decide you do want to run them directly, you can install these modules globally
|
||||
using, `sudo npm install -g ...`.
|
||||
|
||||
For instance to install the Bower command line executable you would do:
|
||||
For instance, to install the Bower command line executable you would do:
|
||||
|
||||
```
|
||||
sudo npm install -g bower
|
||||
|
||||
@@ -16,8 +16,8 @@ about the phones in the catalog.
|
||||
|
||||
## Data
|
||||
|
||||
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
|
||||
urls point to the `app/img/phones/` directory.
|
||||
Note that the `phones.json` file contains unique IDs and image URLs for each of the phones. The
|
||||
URLs point to the `app/img/phones/` directory.
|
||||
|
||||
__`app/phones/phones.json`__ (sample snippet):
|
||||
|
||||
@@ -59,7 +59,7 @@ the element attribute.
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
ng.directive:ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the Angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
invalid URL `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ fleshed out the `phone-detail.html` view template.
|
||||
|
||||
## Data
|
||||
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one JSON file for each
|
||||
phone:
|
||||
|
||||
__`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
@@ -53,7 +53,7 @@ show this data in the phone detail view.
|
||||
|
||||
## Controller
|
||||
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the json files. This works
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the JSON files. This works
|
||||
the same way as the phone list controller.
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
|
||||
@@ -97,7 +97,7 @@ __`app/index.html`.__
|
||||
...
|
||||
|
||||
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
|
||||
<script src="bower_components/jquery/jquery.js"></script>
|
||||
<script src="bower_components/jquery/dist/jquery.js"></script>
|
||||
|
||||
...
|
||||
|
||||
|
||||
+10
-6
@@ -114,26 +114,30 @@ module.exports = function(config, specificOptions) {
|
||||
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
|
||||
|
||||
config.logLevel = config.LOG_DEBUG;
|
||||
config.transports = ['websocket', 'xhr-polling'];
|
||||
config.captureTimeout = 0; // rely on SL timeout
|
||||
// Karma (with socket.io 1.x) buffers by 50 and 50 tests can take a long time on IEs;-)
|
||||
config.browserNoActivityTimeout = 120000;
|
||||
|
||||
config.browserStack.build = buildLabel;
|
||||
config.browserStack.startTunnel = false;
|
||||
config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
config.sauceLabs.build = buildLabel;
|
||||
config.sauceLabs.startConnect = false;
|
||||
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
|
||||
config.sauceLabs.recordScreenshots = true;
|
||||
|
||||
// TODO(vojta): remove once SauceLabs supports websockets.
|
||||
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
|
||||
config.transports = ['xhr-polling'];
|
||||
|
||||
// Debug logging into a file, that we print out at the end of the build.
|
||||
config.loggers.push({
|
||||
type: 'file',
|
||||
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log')
|
||||
});
|
||||
|
||||
if (process.env.BROWSER_PROVIDER === 'saucelabs' || !process.env.BROWSER_PROVIDER) {
|
||||
// Allocating a browser can take pretty long (eg. if we are out of capacity and need to wait
|
||||
// for another build to finish) and so the `captureTimeout` typically kills
|
||||
// an in-queue-pending request, which makes no sense.
|
||||
config.captureTimeout = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
node ./lib/browser-stack/start-tunnel.js &
|
||||
@@ -5,9 +5,10 @@ var http = require('http');
|
||||
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
|
||||
|
||||
var HOSTNAME = 'localhost';
|
||||
var PORTS = require('../grunt/utils').availablePorts;
|
||||
var PORTS = [9876, 8000];
|
||||
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
|
||||
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
|
||||
var READY_FILE = process.env.BROWSER_PROVIDER_READY_FILE;
|
||||
var TUNNEL_IDENTIFIER = process.env.TRAVIS_JOB_NUMBER;
|
||||
|
||||
// We need to start fake servers, otherwise the tunnel does not start.
|
||||
var fakeServers = [];
|
||||
@@ -24,6 +25,7 @@ PORTS.forEach(function(port) {
|
||||
|
||||
var tunnel = new BrowserStackTunnel({
|
||||
key: ACCESS_KEY,
|
||||
tunnelIdentifier: TUNNEL_IDENTIFIER,
|
||||
hosts: hosts
|
||||
});
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
|
||||
node ./lib/browserstack/start_tunnel.js &
|
||||
+1
-43
@@ -11,21 +11,6 @@ var _ = require('lodash');
|
||||
|
||||
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
|
||||
|
||||
var PORT_MIN = 8000;
|
||||
var PORT_MAX = 9999;
|
||||
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
|
||||
var getRandomPorts = function() {
|
||||
if (!process.env.TRAVIS) {
|
||||
return [9876, 9877];
|
||||
}
|
||||
|
||||
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
|
||||
return [
|
||||
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
|
||||
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -312,32 +297,5 @@ module.exports = {
|
||||
}
|
||||
next();
|
||||
};
|
||||
},
|
||||
|
||||
parallelTask: function(args, options) {
|
||||
var task = {
|
||||
grunt: true,
|
||||
args: args,
|
||||
stream: options && options.stream
|
||||
};
|
||||
|
||||
args.push('--port=' + this.sauceLabsAvailablePorts.pop());
|
||||
|
||||
if (args.indexOf('test:e2e') !== -1 && grunt.option('e2e-browsers')) {
|
||||
args.push('--browsers=' + grunt.option('e2e-browsers'));
|
||||
} else if (grunt.option('browsers')) {
|
||||
args.push('--browsers=' + grunt.option('browsers'));
|
||||
}
|
||||
|
||||
if (grunt.option('reporters')) {
|
||||
args.push('--reporters=' + grunt.option('reporters'));
|
||||
}
|
||||
|
||||
return task;
|
||||
},
|
||||
|
||||
// see http://saucelabs.com/docs/connect#localhost
|
||||
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
|
||||
// pseudo-random port numbers for BrowserStack
|
||||
availablePorts: getRandomPorts()
|
||||
}
|
||||
};
|
||||
|
||||
Generated
+18
-18
@@ -2453,17 +2453,6 @@
|
||||
"grunt-merge-conflict": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"grunt-parallel": {
|
||||
"version": "0.3.1",
|
||||
"dependencies": {
|
||||
"q": {
|
||||
"version": "0.8.12"
|
||||
},
|
||||
"lpad": {
|
||||
"version": "0.1.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"grunt-shell": {
|
||||
"version": "1.1.1",
|
||||
"dependencies": {
|
||||
@@ -4810,7 +4799,7 @@
|
||||
}
|
||||
},
|
||||
"protractor": {
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.0",
|
||||
"dependencies": {
|
||||
"request": {
|
||||
"version": "2.36.0",
|
||||
@@ -4828,7 +4817,7 @@
|
||||
"version": "0.5.2"
|
||||
},
|
||||
"node-uuid": {
|
||||
"version": "1.4.1"
|
||||
"version": "1.4.2"
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "0.12.1",
|
||||
@@ -4858,16 +4847,16 @@
|
||||
"version": "0.4.0"
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"dependencies": {
|
||||
"assert-plus": {
|
||||
"version": "0.1.2"
|
||||
"version": "0.1.5"
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.1.11"
|
||||
},
|
||||
"ctype": {
|
||||
"version": "0.5.2"
|
||||
"version": "0.5.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4909,7 +4898,7 @@
|
||||
"version": "0.6.1"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.5",
|
||||
"dependencies": {
|
||||
"lodash-node": {
|
||||
"version": "2.4.1"
|
||||
@@ -4926,6 +4915,17 @@
|
||||
"jasminewd": {
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"jasminewd2": {
|
||||
"version": "0.0.2"
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "2.1.1",
|
||||
"dependencies": {
|
||||
"jasmine-core": {
|
||||
"version": "2.1.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saucelabs": {
|
||||
"version": "0.1.1"
|
||||
},
|
||||
@@ -4966,7 +4966,7 @@
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.2.8",
|
||||
"version": "0.2.9",
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.1.32",
|
||||
|
||||
+5
-6
@@ -11,6 +11,7 @@
|
||||
"bower": "~1.3.9",
|
||||
"browserstacktunnel-wrapper": "~1.3.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"cheerio": "^0.17.0",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"event-stream": "~3.1.0",
|
||||
@@ -25,7 +26,6 @@
|
||||
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
|
||||
"grunt-jscs": "~0.7.1",
|
||||
"grunt-merge-conflict": "~0.0.1",
|
||||
"grunt-parallel": "~0.3.1",
|
||||
"grunt-shell": "~1.1.1",
|
||||
"gulp": "~3.8.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
@@ -38,8 +38,8 @@
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "^0.12.0",
|
||||
"karma-browserstack-launcher": "0.1.1",
|
||||
"karma": "vojtajina/karma#socketio_10",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-jasmine": "0.1.5",
|
||||
@@ -52,7 +52,7 @@
|
||||
"marked": "~0.3.0",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"promises-aplus-tests": "~2.1.0",
|
||||
"protractor": "1.4.0",
|
||||
"protractor": "^1.6.0",
|
||||
"q": "~1.0.0",
|
||||
"q-io": "^1.10.9",
|
||||
"qq": "^0.3.5",
|
||||
@@ -60,8 +60,7 @@
|
||||
"semver": "~4.0.3",
|
||||
"shelljs": "~0.3.0",
|
||||
"sorted-object": "^1.0.0",
|
||||
"stringmap": "^0.2.2",
|
||||
"cheerio": "^0.17.0"
|
||||
"stringmap": "^0.2.2"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
+72
-23
@@ -2,32 +2,81 @@
|
||||
|
||||
var config = require('./protractor-shared-conf').config;
|
||||
|
||||
config.sauceUser = process.env.SAUCE_USERNAME;
|
||||
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
|
||||
if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
// Using BrowserStack.
|
||||
config.seleniumAddress = 'http://hub.browserstack.com/wd/hub';
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'chrome',
|
||||
platform: 'MAC',
|
||||
version: '34'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'safari',
|
||||
platform: 'MAC',
|
||||
version: '7'
|
||||
})
|
||||
];
|
||||
} else {
|
||||
// Using SauceLabs.
|
||||
config.sauceUser = process.env.SAUCE_USERNAME;
|
||||
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'chrome',
|
||||
platform: 'OS X 10.9',
|
||||
version: '34'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'firefox',
|
||||
version: '28'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
config.multiCapabilities = [{
|
||||
'browserName': 'chrome',
|
||||
'platform': 'OS X 10.9',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
'version': '34'
|
||||
}, {
|
||||
'browserName': 'firefox',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
'version': '28'
|
||||
}, {
|
||||
browserName: 'safari',
|
||||
'platform': 'OS X 10.9',
|
||||
'version': '7',
|
||||
'name': 'Angular E2E',
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER
|
||||
}];
|
||||
|
||||
config.allScriptsTimeout = 30000;
|
||||
config.getPageTimeout = 30000;
|
||||
|
||||
exports.config = config;
|
||||
|
||||
|
||||
function capabilitiesForBrowserStack(capabilities) {
|
||||
return {
|
||||
'browserstack.user': process.env.BROWSER_STACK_USERNAME,
|
||||
'browserstack.key': process.env.BROWSER_STACK_ACCESS_KEY,
|
||||
'browserstack.local' : 'true',
|
||||
'browserstack.debug': 'true',
|
||||
'browserstack.tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
|
||||
'name': 'Angular E2E',
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
};
|
||||
}
|
||||
|
||||
function capabilitiesForSauceLabs(capabilities) {
|
||||
return {
|
||||
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
|
||||
|
||||
'name': 'Angular E2E',
|
||||
'build': process.env.TRAVIS_BUILD_NUMBER,
|
||||
|
||||
'browserName': capabilities.browserName,
|
||||
'platform': capabilities.platform,
|
||||
'version': capabilities.version
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ function init {
|
||||
REPOS=(
|
||||
angular
|
||||
angular-animate
|
||||
angular-aria
|
||||
angular-cookies
|
||||
angular-i18n
|
||||
angular-loader
|
||||
angular-messages
|
||||
angular-mocks
|
||||
angular-route
|
||||
angular-resource
|
||||
|
||||
+10
-2
@@ -2,13 +2,21 @@
|
||||
|
||||
set -e
|
||||
|
||||
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
|
||||
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
|
||||
|
||||
if [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11"
|
||||
else
|
||||
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11"
|
||||
fi
|
||||
|
||||
grunt test:promises-aplus
|
||||
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
elif [ $JOB = "docs-e2e" ]; then
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
elif [ $JOB = "e2e" ]; then
|
||||
if [ $TEST_TARGET = "jquery" ]; then
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Has to be run from project root directory.
|
||||
|
||||
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
echo "Using BrowserStack"
|
||||
elif [ "$BROWSER_PROVIDER" == "saucelabs" ]; then
|
||||
echo "Using SauceLabs"
|
||||
else
|
||||
echo "Invalid BROWSER_PROVIDER. Please set env var BROWSER_PROVIDER to 'saucelabs' or 'browserstack'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./lib/${BROWSER_PROVIDER}/start_tunnel.sh
|
||||
@@ -51,6 +51,7 @@
|
||||
"isWindow": false,
|
||||
"isScope": false,
|
||||
"isFile": false,
|
||||
"isFormData": false,
|
||||
"isBlob": false,
|
||||
"isBoolean": false,
|
||||
"isPromiseLike": false,
|
||||
|
||||
+26
-9
@@ -45,6 +45,7 @@
|
||||
isWindow: true,
|
||||
isScope: true,
|
||||
isFile: true,
|
||||
isFormData: true,
|
||||
isBlob: true,
|
||||
isBoolean: true,
|
||||
isPromiseLike: true,
|
||||
@@ -162,8 +163,8 @@ if ('i' !== 'I'.toLowerCase()) {
|
||||
}
|
||||
|
||||
|
||||
var /** holds major version number for IE or NaN for real browsers */
|
||||
msie,
|
||||
var
|
||||
msie, // holds major version number for IE, or NaN if UA is not IE.
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
slice = [].slice,
|
||||
@@ -400,6 +401,8 @@ noop.$inject = [];
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
```
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*/
|
||||
function identity($) {return $;}
|
||||
identity.$inject = [];
|
||||
@@ -566,6 +569,11 @@ function isFile(obj) {
|
||||
}
|
||||
|
||||
|
||||
function isFormData(obj) {
|
||||
return toString.call(obj) === '[object FormData]';
|
||||
}
|
||||
|
||||
|
||||
function isBlob(obj) {
|
||||
return toString.call(obj) === '[object Blob]';
|
||||
}
|
||||
@@ -649,7 +657,7 @@ function arrayRemove(array, value) {
|
||||
* Creates a deep copy of `source`, which should be an object or an array.
|
||||
*
|
||||
* * If no destination is supplied, a copy of the object or array is created.
|
||||
* * If a destination is provided, all of its elements (for array) or properties (for objects)
|
||||
* * If a destination is provided, all of its elements (for arrays) or properties (for objects)
|
||||
* are deleted and then all elements/properties from the source are copied to it.
|
||||
* * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
|
||||
* * If `source` is identical to 'destination' an exception will be thrown.
|
||||
@@ -964,12 +972,16 @@ function toJsonReplacer(key, value) {
|
||||
* stripped since angular uses this notation internally.
|
||||
*
|
||||
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
|
||||
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
|
||||
* If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
|
||||
* @returns {string|undefined} JSON-ified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
if (typeof obj === 'undefined') return undefined;
|
||||
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
|
||||
if (!isNumber(pretty)) {
|
||||
pretty = pretty ? 2 : null;
|
||||
}
|
||||
return JSON.stringify(obj, toJsonReplacer, pretty);
|
||||
}
|
||||
|
||||
|
||||
@@ -983,7 +995,7 @@ function toJson(obj, pretty) {
|
||||
* Deserializes a JSON string.
|
||||
*
|
||||
* @param {string} json JSON string to deserialize.
|
||||
* @returns {Object|Array|string|number} Deserialized thingy.
|
||||
* @returns {Object|Array|string|number} Deserialized JSON string.
|
||||
*/
|
||||
function fromJson(json) {
|
||||
return isString(json)
|
||||
@@ -1156,7 +1168,7 @@ function getNgAttribute(element, ngAttr) {
|
||||
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
|
||||
*
|
||||
* You can specify an **AngularJS module** to be used as the root module for the application. This
|
||||
* module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
|
||||
* module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
|
||||
* should contain the application code needed or have dependencies on other modules that will
|
||||
* contain the code. See {@link angular.module} for more information.
|
||||
*
|
||||
@@ -1164,7 +1176,7 @@ function getNgAttribute(element, ngAttr) {
|
||||
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
|
||||
* would not be resolved to `3`.
|
||||
*
|
||||
* `ngApp` is the easiest, and most common, way to bootstrap an application.
|
||||
* `ngApp` is the easiest, and most common way to bootstrap an application.
|
||||
*
|
||||
<example module="ngAppDemo">
|
||||
<file name="index.html">
|
||||
@@ -1424,7 +1436,12 @@ function reloadWithDebugInfo() {
|
||||
* @param {DOMElement} element DOM element which is the root of angular application.
|
||||
*/
|
||||
function getTestability(rootElement) {
|
||||
return angular.element(rootElement).injector().get('$$testability');
|
||||
var injector = angular.element(rootElement).injector();
|
||||
if (!injector) {
|
||||
throw ngMinErr('test',
|
||||
'no injector found for element argument to getTestability');
|
||||
}
|
||||
return injector.get('$$testability');
|
||||
}
|
||||
|
||||
var SNAKE_CASE_REGEXP = /[A-Z]/g;
|
||||
|
||||
@@ -83,7 +83,8 @@
|
||||
$TimeoutProvider,
|
||||
$$RAFProvider,
|
||||
$$AsyncCallbackProvider,
|
||||
$WindowProvider
|
||||
$WindowProvider,
|
||||
$$jqLiteProvider
|
||||
*/
|
||||
|
||||
|
||||
@@ -236,7 +237,8 @@ function publishExternalAPI(angular) {
|
||||
$timeout: $TimeoutProvider,
|
||||
$window: $WindowProvider,
|
||||
$$rAF: $$RAFProvider,
|
||||
$$asyncCallback: $$AsyncCallbackProvider
|
||||
$$asyncCallback: $$AsyncCallbackProvider,
|
||||
$$jqLite: $$jqLiteProvider
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
+12
-8
@@ -179,6 +179,7 @@ function annotate(fn, strictDi, name) {
|
||||
* Return an instance of the service.
|
||||
*
|
||||
* @param {string} name The name of the instance to retrieve.
|
||||
* @param {string} caller An optional string to provide the origin of the function call for error messages.
|
||||
* @return {*} The instance.
|
||||
*/
|
||||
|
||||
@@ -629,14 +630,17 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
}
|
||||
},
|
||||
providerInjector = (providerCache.$injector =
|
||||
createInternalInjector(providerCache, function() {
|
||||
createInternalInjector(providerCache, function(serviceName, caller) {
|
||||
if (angular.isString(caller)) {
|
||||
path.push(caller);
|
||||
}
|
||||
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
|
||||
})),
|
||||
instanceCache = {},
|
||||
instanceInjector = (instanceCache.$injector =
|
||||
createInternalInjector(instanceCache, function(servicename) {
|
||||
var provider = providerInjector.get(servicename + providerSuffix);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
|
||||
createInternalInjector(instanceCache, function(serviceName, caller) {
|
||||
var provider = providerInjector.get(serviceName + providerSuffix, caller);
|
||||
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
|
||||
}));
|
||||
|
||||
|
||||
@@ -671,7 +675,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
|
||||
function enforceReturnValue(name, factory) {
|
||||
return function enforcedReturnValue() {
|
||||
var result = instanceInjector.invoke(factory, this, undefined, name);
|
||||
var result = instanceInjector.invoke(factory, this);
|
||||
if (isUndefined(result)) {
|
||||
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
|
||||
}
|
||||
@@ -766,7 +770,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
|
||||
function createInternalInjector(cache, factory) {
|
||||
|
||||
function getService(serviceName) {
|
||||
function getService(serviceName, caller) {
|
||||
if (cache.hasOwnProperty(serviceName)) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
|
||||
@@ -777,7 +781,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
try {
|
||||
path.unshift(serviceName);
|
||||
cache[serviceName] = INSTANTIATING;
|
||||
return cache[serviceName] = factory(serviceName);
|
||||
return cache[serviceName] = factory(serviceName, caller);
|
||||
} catch (err) {
|
||||
if (cache[serviceName] === INSTANTIATING) {
|
||||
delete cache[serviceName];
|
||||
@@ -809,7 +813,7 @@ function createInjector(modulesToLoad, strictDi) {
|
||||
args.push(
|
||||
locals && locals.hasOwnProperty(key)
|
||||
? locals[key]
|
||||
: getService(key)
|
||||
: getService(key, serviceName)
|
||||
);
|
||||
}
|
||||
if (isArray(fn)) {
|
||||
|
||||
@@ -1003,3 +1003,24 @@ forEach({
|
||||
JQLite.prototype.bind = JQLite.prototype.on;
|
||||
JQLite.prototype.unbind = JQLite.prototype.off;
|
||||
});
|
||||
|
||||
|
||||
// Provider for private $$jqLite service
|
||||
function $$jqLiteProvider() {
|
||||
this.$get = function $$jqLite() {
|
||||
return extend(JQLite, {
|
||||
hasClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteHasClass(node, classes);
|
||||
},
|
||||
addClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteAddClass(node, classes);
|
||||
},
|
||||
removeClass: function(node, classes) {
|
||||
if (node.attr) node = node[0];
|
||||
return jqLiteRemoveClass(node, classes);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
+8
-1
@@ -61,6 +61,11 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
}
|
||||
|
||||
function getHash(url) {
|
||||
var index = url.indexOf('#');
|
||||
return index === -1 ? '' : url.substr(index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Note: this method is used only by scenario runner
|
||||
@@ -190,8 +195,10 @@ function Browser(window, document, $log, $sniffer) {
|
||||
}
|
||||
if (replace) {
|
||||
location.replace(url);
|
||||
} else {
|
||||
} else if (!sameBase) {
|
||||
location.href = url;
|
||||
} else {
|
||||
location.hash = getHash(url);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
|
||||
+34
-20
@@ -115,7 +115,7 @@
|
||||
* #### `multiElement`
|
||||
* When this property is set to true, the HTML compiler will collect DOM nodes between
|
||||
* nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
|
||||
* together as the directive elements. It is recomended that this feature be used on directives
|
||||
* together as the directive elements. It is recommended that this feature be used on directives
|
||||
* which are not strictly behavioural (such as {@link ngClick}), and which
|
||||
* do not manipulate or replace child nodes (such as {@link ngInclude}).
|
||||
*
|
||||
@@ -803,7 +803,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during a[href] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
* The sanitization is a security measure aimed at preventing XSS attacks via html links.
|
||||
*
|
||||
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
|
||||
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
|
||||
@@ -870,7 +870,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* * `ng-binding` CSS class
|
||||
* * `$binding` data property containing an array of the binding expressions
|
||||
*
|
||||
* You may want to use this in production for a significant performance boost. See
|
||||
* You may want to disable this in production for a significant performance boost. See
|
||||
* {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
|
||||
*
|
||||
* The default value is true.
|
||||
@@ -907,6 +907,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
};
|
||||
|
||||
Attributes.prototype = {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compile.directive.Attributes#$normalize
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
|
||||
* `data-`) to its normalized, camelCase form.
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
*
|
||||
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
|
||||
*
|
||||
* @param {string} name Name to normalize
|
||||
*/
|
||||
$normalize: directiveNormalize,
|
||||
|
||||
|
||||
@@ -1405,7 +1420,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// support ngAttr attribute binding
|
||||
ngAttrName = directiveNormalize(name);
|
||||
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
|
||||
name = snake_case(ngAttrName.substr(6), '-');
|
||||
name = name.replace(PREFIX_REGEXP, '')
|
||||
.substr(8).replace(/_(.)/g, function(match, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
@@ -2317,7 +2335,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
|
||||
function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
|
||||
var interpolateFn = $interpolate(value, true);
|
||||
var trustedContext = getTrustedContext(node, name);
|
||||
allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
|
||||
|
||||
var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
|
||||
|
||||
// no interpolation found -> ignore
|
||||
if (!interpolateFn) return;
|
||||
@@ -2342,16 +2363,16 @@ 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;
|
||||
// If the attribute has changed since last $interpolate()ed
|
||||
var newValue = attr[name];
|
||||
if (newValue !== value) {
|
||||
// we need to interpolate again since the attribute value has been updated
|
||||
// (e.g. by another directive's compile function)
|
||||
// ensure unset/empty values make interpolateFn falsy
|
||||
interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
// 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),
|
||||
ALL_OR_NOTHING_ATTRS[name] || allOrNothing);
|
||||
|
||||
// if attribute was updated so that there is no interpolation going on we don't want to
|
||||
// register any observers
|
||||
if (!interpolateFn) return;
|
||||
@@ -2485,13 +2506,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
* All of these will become 'myDirective':
|
||||
* my:Directive
|
||||
* my-directive
|
||||
* x-my-directive
|
||||
* data-my:directive
|
||||
*
|
||||
* Also there is special case for Moz prefix starting with upper case letter.
|
||||
* @param name Name to normalize
|
||||
*/
|
||||
function directiveNormalize(name) {
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
* make the link go to the wrong URL if the user clicks it before
|
||||
* Angular has a chance to replace the `{{hash}}` markup with its
|
||||
* value. Until Angular replaces the markup the link will be broken
|
||||
* and will most likely return a 404 error.
|
||||
*
|
||||
* The `ngHref` directive solves this problem.
|
||||
* and will most likely return a 404 error. The `ngHref` directive
|
||||
* solves this problem.
|
||||
*
|
||||
* The wrong way to write it:
|
||||
* ```html
|
||||
|
||||
@@ -489,19 +489,19 @@ var formDirectiveFactory = function(isNgForm) {
|
||||
alias = controller.$name;
|
||||
|
||||
if (alias) {
|
||||
setter(scope, alias, controller, alias);
|
||||
setter(scope, null, alias, controller, alias);
|
||||
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
|
||||
if (alias === newValue) return;
|
||||
setter(scope, alias, undefined, alias);
|
||||
setter(scope, null, alias, undefined, alias);
|
||||
alias = newValue;
|
||||
setter(scope, alias, controller, alias);
|
||||
setter(scope, null, alias, controller, alias);
|
||||
parentFormCtrl.$$renameControl(controller, alias);
|
||||
});
|
||||
}
|
||||
formElement.on('$destroy', function() {
|
||||
parentFormCtrl.$removeControl(controller);
|
||||
if (alias) {
|
||||
setter(scope, alias, undefined, alias);
|
||||
setter(scope, null, alias, undefined, alias);
|
||||
}
|
||||
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
|
||||
});
|
||||
|
||||
+16
-1628
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngChange
|
||||
*
|
||||
* @description
|
||||
* Evaluate the given expression when the user changes the input.
|
||||
* The expression is evaluated immediately, unlike the JavaScript onchange event
|
||||
* which only triggers at the end of a change (usually, when the user leaves the
|
||||
* form element or presses the return key).
|
||||
*
|
||||
* The `ngChange` expression is only evaluated when a change in the input value causes
|
||||
* a new value to be committed to the model.
|
||||
*
|
||||
* It will not be evaluated:
|
||||
* * if the value returned from the `$parsers` transformation pipeline has not changed
|
||||
* * if the input has continued to be invalid since the model will stay `null`
|
||||
* * if the model is changed programmatically and not by a change to the input value
|
||||
*
|
||||
*
|
||||
* Note, this directive requires `ngModel` to be present.
|
||||
*
|
||||
* @element input
|
||||
* @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
|
||||
* in input value.
|
||||
*
|
||||
* @example
|
||||
* <example name="ngChange-directive" module="changeExample">
|
||||
* <file name="index.html">
|
||||
* <script>
|
||||
* angular.module('changeExample', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.counter = 0;
|
||||
* $scope.change = function() {
|
||||
* $scope.counter++;
|
||||
* };
|
||||
* }]);
|
||||
* </script>
|
||||
* <div ng-controller="ExampleController">
|
||||
* <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
|
||||
* <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
|
||||
* <label for="ng-change-example2">Confirmed</label><br />
|
||||
* <tt>debug = {{confirmed}}</tt><br/>
|
||||
* <tt>counter = {{counter}}</tt><br/>
|
||||
* </div>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var counter = element(by.binding('counter'));
|
||||
* var debug = element(by.binding('confirmed'));
|
||||
*
|
||||
* it('should evaluate the expression if changing from view', function() {
|
||||
* expect(counter.getText()).toContain('0');
|
||||
*
|
||||
* element(by.id('ng-change-example1')).click();
|
||||
*
|
||||
* expect(counter.getText()).toContain('1');
|
||||
* expect(debug.getText()).toContain('true');
|
||||
* });
|
||||
*
|
||||
* it('should not evaluate the expression if changing from model', function() {
|
||||
* element(by.id('ng-change-example2')).click();
|
||||
|
||||
* expect(counter.getText()).toContain('0');
|
||||
* expect(debug.getText()).toContain('true');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*/
|
||||
var ngChangeDirective = valueFn({
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
ctrl.$viewChangeListeners.push(function() {
|
||||
scope.$eval(attr.ngChange);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -49,7 +49,7 @@
|
||||
<select ng-model="template" ng-options="t.name for t in templates">
|
||||
<option value="">(blank)</option>
|
||||
</select>
|
||||
url of the template: <tt>{{template.url}}</tt>
|
||||
url of the template: <code>{{template.url}}</code>
|
||||
<hr/>
|
||||
<div class="slide-animate-container">
|
||||
<div class="slide-animate" ng-include="template.url"></div>
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngList
|
||||
*
|
||||
* @description
|
||||
* Text input that converts between a delimited string and an array of strings. The default
|
||||
* delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
|
||||
* delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
|
||||
*
|
||||
* The behaviour of the directive is affected by the use of the `ngTrim` attribute.
|
||||
* * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
|
||||
* list item is respected. This implies that the user of the directive is responsible for
|
||||
* dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
|
||||
* tab or newline character.
|
||||
* * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
|
||||
* when joining the list items back together) and whitespace around each list item is stripped
|
||||
* before it is added to the model.
|
||||
*
|
||||
* ### Example with Validation
|
||||
*
|
||||
* <example name="ngList-directive" module="listExample">
|
||||
* <file name="app.js">
|
||||
* angular.module('listExample', [])
|
||||
* .controller('ExampleController', ['$scope', function($scope) {
|
||||
* $scope.names = ['morpheus', 'neo', 'trinity'];
|
||||
* }]);
|
||||
* </file>
|
||||
* <file name="index.html">
|
||||
* <form name="myForm" ng-controller="ExampleController">
|
||||
* List: <input name="namesInput" ng-model="names" ng-list required>
|
||||
* <span class="error" ng-show="myForm.namesInput.$error.required">
|
||||
* Required!</span>
|
||||
* <br>
|
||||
* <tt>names = {{names}}</tt><br/>
|
||||
* <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
|
||||
* <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
|
||||
* <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
|
||||
* <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
|
||||
* </form>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* var listInput = element(by.model('names'));
|
||||
* var names = element(by.exactBinding('names'));
|
||||
* var valid = element(by.binding('myForm.namesInput.$valid'));
|
||||
* var error = element(by.css('span.error'));
|
||||
*
|
||||
* it('should initialize to model', function() {
|
||||
* expect(names.getText()).toContain('["morpheus","neo","trinity"]');
|
||||
* expect(valid.getText()).toContain('true');
|
||||
* expect(error.getCssValue('display')).toBe('none');
|
||||
* });
|
||||
*
|
||||
* it('should be invalid if empty', function() {
|
||||
* listInput.clear();
|
||||
* listInput.sendKeys('');
|
||||
*
|
||||
* expect(names.getText()).toContain('');
|
||||
* expect(valid.getText()).toContain('false');
|
||||
* expect(error.getCssValue('display')).not.toBe('none');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* ### Example - splitting on whitespace
|
||||
* <example name="ngList-directive-newlines">
|
||||
* <file name="index.html">
|
||||
* <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea>
|
||||
* <pre>{{ list | json }}</pre>
|
||||
* </file>
|
||||
* <file name="protractor.js" type="protractor">
|
||||
* it("should split the text by newlines", function() {
|
||||
* var listInput = element(by.model('list'));
|
||||
* var output = element(by.binding('list | json'));
|
||||
* listInput.sendKeys('abc\ndef\nghi');
|
||||
* expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
|
||||
* });
|
||||
* </file>
|
||||
* </example>
|
||||
*
|
||||
* @element input
|
||||
* @param {string=} ngList optional delimiter that should be used to split the value.
|
||||
*/
|
||||
var ngListDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 100,
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
// We want to control whitespace trimming so we use this convoluted approach
|
||||
// to access the ngList attribute, which doesn't pre-trim the attribute
|
||||
var ngList = element.attr(attr.$attr.ngList) || ', ';
|
||||
var trimValues = attr.ngTrim !== 'false';
|
||||
var separator = trimValues ? trim(ngList) : ngList;
|
||||
|
||||
var parse = function(viewValue) {
|
||||
// If the viewValue is invalid (say required but empty) it will be `undefined`
|
||||
if (isUndefined(viewValue)) return;
|
||||
|
||||
var list = [];
|
||||
|
||||
if (viewValue) {
|
||||
forEach(viewValue.split(separator), function(value) {
|
||||
if (value) list.push(trimValues ? trim(value) : value);
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
ctrl.$parsers.push(parse);
|
||||
ctrl.$formatters.push(function(value) {
|
||||
if (isArray(value)) {
|
||||
return value.join(ngList);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Override the standard $isEmpty because an empty array means the input is empty.
|
||||
ctrl.$isEmpty = function(value) {
|
||||
return !value || !value.length;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -257,7 +257,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
|
||||
var aliasAs = match[3];
|
||||
var trackByExp = match[4];
|
||||
|
||||
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
|
||||
match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
|
||||
|
||||
if (!match) {
|
||||
throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
</example>
|
||||
*/
|
||||
var ngStyleDirective = ngDirective(function(scope, element, attr) {
|
||||
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
|
||||
if (oldStyles && (newStyles !== oldStyles)) {
|
||||
forEach(oldStyles, function(val, style) { element.css(style, '');});
|
||||
}
|
||||
if (newStyles) element.css(newStyles);
|
||||
}, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}]);
|
||||
</script>
|
||||
<div ng-controller="ExampleController">
|
||||
<input ng-model="title"><br>
|
||||
<input ng-model="title"> <br/>
|
||||
<textarea ng-model="text"></textarea> <br/>
|
||||
<pane title="{{title}}">{{text}}</pane>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,6 @@ var scriptDirective = ['$templateCache', function($templateCache) {
|
||||
compile: function(element, attr) {
|
||||
if (attr.type == 'text/ng-template') {
|
||||
var templateUrl = attr.id,
|
||||
// IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
|
||||
text = element[0].text;
|
||||
|
||||
$templateCache.put(templateUrl, text);
|
||||
|
||||
+63
-32
@@ -13,14 +13,15 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
*
|
||||
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
|
||||
* elements for the `<select>` element using the array or object obtained by evaluating the
|
||||
* `ngOptions` comprehension_expression.
|
||||
* `ngOptions` comprehension expression.
|
||||
*
|
||||
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
|
||||
* similar result. However, the `ngOptions` provides some benefits such as reducing memory and
|
||||
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
|
||||
* increasing speed by not creating a new scope for each repeated instance, as well as providing
|
||||
* more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
|
||||
* used when the `select` model needs to be bound to a non-string value. This is because an option
|
||||
* element can only be bound to string values at present.
|
||||
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
|
||||
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
|
||||
* to a non-string value. This is because an option element can only be bound to string values at
|
||||
* present.
|
||||
*
|
||||
* When an item in the `<select>` menu is selected, the array element or object property
|
||||
* represented by the selected option will be bound to the model identified by the `ngModel`
|
||||
@@ -35,28 +36,51 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
|
||||
* </div>
|
||||
*
|
||||
* ## `select as`
|
||||
* ## `select` **`as`**
|
||||
*
|
||||
* Using `select as` will bind the result of the `select as` expression to the model, but
|
||||
* Using `select` **`as`** will bind the result of the `select` 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. If a `track by` expression
|
||||
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
|
||||
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
|
||||
*
|
||||
* ### `select as` with `trackexpr`
|
||||
*
|
||||
* Using `select as` together with `trackexpr` is not recommended. Reasoning:
|
||||
* ### `select` **`as`** and **`track by`**
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
|
||||
* </div>
|
||||
*
|
||||
* Consider the following example:
|
||||
*
|
||||
* ```html
|
||||
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
|
||||
* ```
|
||||
*
|
||||
* ```js
|
||||
* $scope.values = [{
|
||||
* id: 1,
|
||||
* label: 'aLabel',
|
||||
* subItem: { name: 'aSubItem' }
|
||||
* }, {
|
||||
* id: 2,
|
||||
* label: 'bLabel',
|
||||
* subItem: { name: 'bSubItem' }
|
||||
* }];
|
||||
*
|
||||
* $scope.selected = { name: 'aSubItem' };
|
||||
* ```
|
||||
*
|
||||
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
|
||||
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
|
||||
* following:
|
||||
*
|
||||
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
|
||||
* 2. Apply **`track by`** to the already selected value in `ngModel`.
|
||||
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
|
||||
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
|
||||
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
|
||||
* selected" option.
|
||||
*
|
||||
* - 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 the purpose of preserving 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}`.
|
||||
*
|
||||
* @param {string} ngModel Assignable angular expression to data-bind to.
|
||||
* @param {string=} name Property name of the form under which the control is published.
|
||||
@@ -69,8 +93,10 @@ var ngOptionsMinErr = minErr('ngOptions');
|
||||
* * for array data sources:
|
||||
* * `label` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
|
||||
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
|
||||
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
|
||||
* (for including a filter with `track by`)
|
||||
* * for object data sources:
|
||||
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
|
||||
@@ -212,7 +238,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
self.removeOption = function(value) {
|
||||
if (this.hasOption(value)) {
|
||||
delete optionsMap[value];
|
||||
if (ngModelCtrl.$viewValue == value) {
|
||||
if (ngModelCtrl.$viewValue === value) {
|
||||
this.renderUnknownOption(value);
|
||||
}
|
||||
}
|
||||
@@ -679,18 +705,23 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
updateLabelMap(labelMap, option.label, false);
|
||||
option.element.remove();
|
||||
}
|
||||
forEach(labelMap, function(count, label) {
|
||||
if (count > 0) {
|
||||
selectCtrl.addOption(label);
|
||||
} else if (count < 0) {
|
||||
selectCtrl.removeOption(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
// remove any excessive OPTGROUPs from select
|
||||
while (optionGroupsCache.length > groupIndex) {
|
||||
optionGroupsCache.pop()[0].element.remove();
|
||||
// remove all the labels in the option group
|
||||
optionGroup = optionGroupsCache.pop();
|
||||
for (index = 1; index < optionGroup.length; ++index) {
|
||||
updateLabelMap(labelMap, optionGroup[index].label, false);
|
||||
}
|
||||
optionGroup[0].element.remove();
|
||||
}
|
||||
forEach(labelMap, function(count, label) {
|
||||
if (count > 0) {
|
||||
selectCtrl.addOption(label);
|
||||
} else if (count < 0) {
|
||||
selectCtrl.removeOption(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
var requiredDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
attr.required = true; // force truthy in case we are on non input element
|
||||
|
||||
ctrl.$validators.required = function(modelValue, viewValue) {
|
||||
return !attr.required || !ctrl.$isEmpty(viewValue);
|
||||
};
|
||||
|
||||
attr.$observe('required', function() {
|
||||
ctrl.$validate();
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var patternDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var regexp, patternExp = attr.ngPattern || attr.pattern;
|
||||
attr.$observe('pattern', function(regex) {
|
||||
if (isString(regex) && regex.length > 0) {
|
||||
regex = new RegExp('^' + regex + '$');
|
||||
}
|
||||
|
||||
if (regex && !regex.test) {
|
||||
throw minErr('ngPattern')('noregexp',
|
||||
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
|
||||
regex, startingTag(elm));
|
||||
}
|
||||
|
||||
regexp = regex || undefined;
|
||||
ctrl.$validate();
|
||||
});
|
||||
|
||||
ctrl.$validators.pattern = function(value) {
|
||||
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
var maxlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var maxlength = -1;
|
||||
attr.$observe('maxlength', function(value) {
|
||||
var intVal = int(value);
|
||||
maxlength = isNaN(intVal) ? -1 : intVal;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.maxlength = function(modelValue, viewValue) {
|
||||
return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var minlengthDirective = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attr, ctrl) {
|
||||
if (!ctrl) return;
|
||||
|
||||
var minlength = 0;
|
||||
attr.$observe('minlength', function(value) {
|
||||
minlength = int(value) || 0;
|
||||
ctrl.$validate();
|
||||
});
|
||||
ctrl.$validators.minlength = function(modelValue, viewValue) {
|
||||
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
+106
-98
@@ -14,19 +14,26 @@
|
||||
*
|
||||
* Can be one of:
|
||||
*
|
||||
* - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
|
||||
* the contents of the `array`. All strings or objects with string properties in `array` that contain this string
|
||||
* will be returned. The predicate can be negated by prefixing the string with `!`.
|
||||
* - `string`: The string is used for matching against the contents of the `array`. All strings or
|
||||
* objects with string properties in `array` that match this string will be returned. This also
|
||||
* applies to nested object properties.
|
||||
* The predicate can be negated by prefixing the string with `!`.
|
||||
*
|
||||
* - `Object`: A pattern object can be used to filter specific properties on objects contained
|
||||
* by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
|
||||
* which have property `name` containing "M" and property `phone` containing "1". A special
|
||||
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
|
||||
* property of the object. That's equivalent to the simple substring match with a `string`
|
||||
* as described above. The predicate can be negated by prefixing the string with `!`.
|
||||
* For Example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* property of the object or its nested object properties. That's equivalent to the simple
|
||||
* substring match with a `string` as described above. The predicate can be negated by prefixing
|
||||
* the string with `!`.
|
||||
* For example `{name: "!M"}` predicate will return an array of items which have property `name`
|
||||
* not containing "M".
|
||||
*
|
||||
* Note that a named property will match properties on the same level only, while the special
|
||||
* `$` property will match properties on the same level or deeper. E.g. an array item like
|
||||
* `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
|
||||
* **will** be matched by `{$: 'John'}`.
|
||||
*
|
||||
* - `function(value, index)`: A predicate function can be used to write arbitrary filters. The
|
||||
* function is called for each element of `array`. The final result is an array of those
|
||||
* elements that the predicate returned true for.
|
||||
@@ -39,10 +46,10 @@
|
||||
*
|
||||
* - `function(actual, expected)`:
|
||||
* The function will be given the object value and the predicate value to compare and
|
||||
* should return true if the item should be included in filtered result.
|
||||
* should return true if both values should be considered equal.
|
||||
*
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
|
||||
* this is essentially strict comparison of expected and actual.
|
||||
* - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
|
||||
* This is essentially strict comparison of expected and actual.
|
||||
*
|
||||
* - `false|undefined`: A short hand for a function which will look for a substring match in case
|
||||
* insensitive way.
|
||||
@@ -119,104 +126,105 @@ function filterFilter() {
|
||||
return function(array, expression, comparator) {
|
||||
if (!isArray(array)) return array;
|
||||
|
||||
var comparatorType = typeof(comparator),
|
||||
predicates = [];
|
||||
var predicateFn;
|
||||
var matchAgainstAnyProp;
|
||||
|
||||
predicates.check = function(value, index) {
|
||||
for (var j = 0; j < predicates.length; j++) {
|
||||
if (!predicates[j](value, index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (comparatorType !== 'function') {
|
||||
if (comparatorType === 'boolean' && comparator) {
|
||||
comparator = function(obj, text) {
|
||||
return angular.equals(obj, text);
|
||||
};
|
||||
} else {
|
||||
comparator = function(obj, text) {
|
||||
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
|
||||
for (var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
|
||||
comparator(obj[objKey], text[objKey])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
text = ('' + text).toLowerCase();
|
||||
return ('' + obj).toLowerCase().indexOf(text) > -1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var search = function(obj, text) {
|
||||
if (typeof text === 'string' && text.charAt(0) === '!') {
|
||||
return !search(obj, text.substr(1));
|
||||
}
|
||||
switch (typeof obj) {
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
return comparator(obj, text);
|
||||
case 'object':
|
||||
switch (typeof text) {
|
||||
case 'object':
|
||||
return comparator(obj, text);
|
||||
default:
|
||||
for (var objKey in obj) {
|
||||
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
case 'array':
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
if (search(obj[i], text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
switch (typeof expression) {
|
||||
case 'function':
|
||||
predicateFn = expression;
|
||||
break;
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
case 'string':
|
||||
// Set up expression object and fall through
|
||||
expression = {$:expression};
|
||||
// jshint -W086
|
||||
matchAgainstAnyProp = true;
|
||||
//jshint -W086
|
||||
case 'object':
|
||||
// jshint +W086
|
||||
for (var key in expression) {
|
||||
(function(path) {
|
||||
if (typeof expression[path] === 'undefined') return;
|
||||
predicates.push(function(value) {
|
||||
return search(path == '$' ? value : (value && value[path]), expression[path]);
|
||||
});
|
||||
})(key);
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
predicates.push(expression);
|
||||
//jshint +W086
|
||||
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
|
||||
break;
|
||||
default:
|
||||
return array;
|
||||
}
|
||||
var filtered = [];
|
||||
for (var j = 0; j < array.length; j++) {
|
||||
var value = array[j];
|
||||
if (predicates.check(value, j)) {
|
||||
filtered.push(value);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
|
||||
return array.filter(predicateFn);
|
||||
};
|
||||
}
|
||||
|
||||
// Helper functions for `filterFilter`
|
||||
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
|
||||
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
|
||||
var predicateFn;
|
||||
|
||||
if (comparator === true) {
|
||||
comparator = equals;
|
||||
} else if (!isFunction(comparator)) {
|
||||
comparator = function(actual, expected) {
|
||||
if (isObject(actual) || isObject(expected)) {
|
||||
// Prevent an object to be considered equal to a string like `'[object'`
|
||||
return false;
|
||||
}
|
||||
|
||||
actual = lowercase('' + actual);
|
||||
expected = lowercase('' + expected);
|
||||
return actual.indexOf(expected) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
predicateFn = function(item) {
|
||||
if (shouldMatchPrimitives && !isObject(item)) {
|
||||
return deepCompare(item, expression.$, comparator, false);
|
||||
}
|
||||
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
|
||||
};
|
||||
|
||||
return predicateFn;
|
||||
}
|
||||
|
||||
function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
|
||||
var actualType = typeof actual;
|
||||
var expectedType = typeof expected;
|
||||
|
||||
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
|
||||
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
|
||||
} else if (isArray(actual)) {
|
||||
// In case `actual` is an array, consider it a match
|
||||
// if ANY of it's items matches `expected`
|
||||
return actual.some(function(item) {
|
||||
return deepCompare(item, expected, comparator, matchAgainstAnyProp);
|
||||
});
|
||||
}
|
||||
|
||||
switch (actualType) {
|
||||
case 'object':
|
||||
var key;
|
||||
if (matchAgainstAnyProp) {
|
||||
for (key in actual) {
|
||||
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
|
||||
} else if (expectedType === 'object') {
|
||||
for (key in expected) {
|
||||
var expectedVal = expected[key];
|
||||
if (isFunction(expectedVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchAnyProperty = key === '$';
|
||||
var actualVal = matchAnyProperty ? actual : actual[key];
|
||||
if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
return false;
|
||||
default:
|
||||
return comparator(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-13
@@ -150,7 +150,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (numStr.indexOf('e') !== -1) {
|
||||
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
|
||||
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
|
||||
numStr = '0';
|
||||
number = 0;
|
||||
} else {
|
||||
formatedText = numStr;
|
||||
@@ -171,10 +170,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
||||
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
var fraction = ('' + number).split(DECIMAL_SEP);
|
||||
var whole = fraction[0];
|
||||
fraction = fraction[1] || '';
|
||||
@@ -207,12 +202,16 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
|
||||
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
|
||||
} else {
|
||||
|
||||
if (fractionSize > 0 && number > -1 && number < 1) {
|
||||
if (fractionSize > 0 && number < 1) {
|
||||
formatedText = number.toFixed(fractionSize);
|
||||
number = parseFloat(formatedText);
|
||||
}
|
||||
}
|
||||
|
||||
if (number === 0) {
|
||||
isNegative = false;
|
||||
}
|
||||
|
||||
parts.push(isNegative ? pattern.negPre : pattern.posPre,
|
||||
formatedText,
|
||||
isNegative ? pattern.negSuf : pattern.posSuf);
|
||||
@@ -357,8 +356,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
|
||||
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
|
||||
* * `'a'`: AM/PM marker
|
||||
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
|
||||
* * `'ww'`: ISO-8601 week of year (00-53)
|
||||
* * `'w'`: ISO-8601 week of year (0-53)
|
||||
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
|
||||
* * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
|
||||
*
|
||||
* `format` string can also be one of the following predefined
|
||||
* {@link guide/i18n localizable formats}:
|
||||
@@ -502,25 +501,31 @@ function dateFilter($locale) {
|
||||
* the binding is automatically converted to JSON.
|
||||
*
|
||||
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
|
||||
* @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
|
||||
* @returns {string} JSON string.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
<pre>{{ {'name':'value'} | json }}</pre>
|
||||
<pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
|
||||
<pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
*
|
||||
*/
|
||||
function jsonFilter() {
|
||||
return function(object) {
|
||||
return toJson(object, true);
|
||||
return function(object, spacing) {
|
||||
if (isUndefined(spacing)) {
|
||||
spacing = 2;
|
||||
}
|
||||
return toJson(object, spacing);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -97,36 +97,11 @@ function limitToFilter() {
|
||||
limit = int(limit);
|
||||
}
|
||||
|
||||
if (isString(input)) {
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
var out = [],
|
||||
i, n;
|
||||
|
||||
// if abs(limit) exceeds maximum length, trim it
|
||||
if (limit > input.length)
|
||||
limit = input.length;
|
||||
else if (limit < -input.length)
|
||||
limit = -input.length;
|
||||
|
||||
if (limit > 0) {
|
||||
i = 0;
|
||||
n = limit;
|
||||
//NaN check on limit
|
||||
if (limit) {
|
||||
return limit > 0 ? input.slice(0, limit) : input.slice(limit);
|
||||
} else {
|
||||
i = input.length + limit;
|
||||
n = input.length;
|
||||
return isString(input) ? "" : [];
|
||||
}
|
||||
|
||||
for (; i < n; i++) {
|
||||
out.push(input[i]);
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,9 +130,7 @@ function orderByFilter($parse) {
|
||||
}
|
||||
if (predicate === '') {
|
||||
// Effectively no predicate was passed so we compare identity
|
||||
return reverseComparator(function(a, b) {
|
||||
return compare(a, b);
|
||||
}, descending);
|
||||
return reverseComparator(compare, descending);
|
||||
}
|
||||
get = $parse(predicate);
|
||||
if (get.constant) {
|
||||
@@ -160,15 +158,40 @@ function orderByFilter($parse) {
|
||||
? function(a, b) {return comp(b,a);}
|
||||
: comp;
|
||||
}
|
||||
|
||||
function isPrimitive(value) {
|
||||
switch (typeof value) {
|
||||
case 'number': /* falls through */
|
||||
case 'boolean': /* falls through */
|
||||
case 'string':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function objectToString(value) {
|
||||
if (value === null) return 'null';
|
||||
if (typeof value.valueOf === 'function') {
|
||||
value = value.valueOf();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
if (typeof value.toString === 'function') {
|
||||
value = value.toString();
|
||||
if (isPrimitive(value)) return value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function compare(v1, v2) {
|
||||
var t1 = typeof v1;
|
||||
var t2 = typeof v2;
|
||||
if (t1 == t2) {
|
||||
if (isDate(v1) && isDate(v2)) {
|
||||
v1 = v1.valueOf();
|
||||
v2 = v2.valueOf();
|
||||
}
|
||||
if (t1 == "string") {
|
||||
if (t1 === t2 && t1 === "object") {
|
||||
v1 = objectToString(v1);
|
||||
v2 = objectToString(v2);
|
||||
}
|
||||
if (t1 === t2) {
|
||||
if (t1 === "string") {
|
||||
v1 = v1.toLowerCase();
|
||||
v2 = v2.toLowerCase();
|
||||
}
|
||||
|
||||
+67
-50
@@ -2,23 +2,34 @@
|
||||
|
||||
var APPLICATION_JSON = 'application/json';
|
||||
var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
|
||||
var JSON_START = /^\s*(\[|\{[^\{])/;
|
||||
var JSON_END = /[\}\]]\s*$/;
|
||||
var JSON_START = /^\[|^\{(?!\{)/;
|
||||
var JSON_ENDS = {
|
||||
'[': /]$/,
|
||||
'{': /}$/
|
||||
};
|
||||
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
|
||||
|
||||
function defaultHttpResponseTransform(data, headers) {
|
||||
if (isString(data)) {
|
||||
// strip json vulnerability protection prefix
|
||||
data = data.replace(JSON_PROTECTION_PREFIX, '');
|
||||
var contentType = headers('Content-Type');
|
||||
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0 && data.trim()) ||
|
||||
(JSON_START.test(data) && JSON_END.test(data))) {
|
||||
data = fromJson(data);
|
||||
// Strip json vulnerability protection prefix and trim whitespace
|
||||
var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
|
||||
|
||||
if (tempData) {
|
||||
var contentType = headers('Content-Type');
|
||||
if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
|
||||
data = fromJson(tempData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function isJsonLike(str) {
|
||||
var jsonStart = str.match(JSON_START);
|
||||
return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse headers into key value object
|
||||
*
|
||||
@@ -81,16 +92,17 @@ function headersGetter(headers) {
|
||||
* This function is used for both request and response transforming
|
||||
*
|
||||
* @param {*} data Data to transform.
|
||||
* @param {function(string=)} headers Http headers getter fn.
|
||||
* @param {function(string=)} headers HTTP headers getter fn.
|
||||
* @param {number} status HTTP status code of the response.
|
||||
* @param {(Function|Array.<Function>)} fns Function or an array of functions.
|
||||
* @returns {*} Transformed data.
|
||||
*/
|
||||
function transformData(data, headers, fns) {
|
||||
function transformData(data, headers, status, fns) {
|
||||
if (isFunction(fns))
|
||||
return fns(data, headers);
|
||||
return fns(data, headers, status);
|
||||
|
||||
forEach(fns, function(fn) {
|
||||
data = fn(data, headers);
|
||||
data = fn(data, headers, status);
|
||||
});
|
||||
|
||||
return data;
|
||||
@@ -142,7 +154,7 @@ function $HttpProvider() {
|
||||
|
||||
// transform outgoing request data
|
||||
transformRequest: [function(d) {
|
||||
return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d;
|
||||
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
|
||||
}],
|
||||
|
||||
// default headers
|
||||
@@ -369,7 +381,7 @@ function $HttpProvider() {
|
||||
*
|
||||
* Both requests and responses can be transformed using transformation functions: `transformRequest`
|
||||
* and `transformResponse`. These properties can be a single function that returns
|
||||
* the transformed value (`{function(data, headersGetter)`) or an array of such transformation functions,
|
||||
* the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
|
||||
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
|
||||
*
|
||||
* ### Default Transformations
|
||||
@@ -610,12 +622,14 @@ function $HttpProvider() {
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* request body and headers and returns its transformed (typically serialized) version.
|
||||
* See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
|
||||
* See {@link ng.$http#overriding-the-default-transformations-per-request
|
||||
* Overriding the Default Transformations}
|
||||
* - **transformResponse** –
|
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||||
* `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
|
||||
* transform function or an array of such functions. The transform function takes the http
|
||||
* response body and headers and returns its transformed (typically deserialized) version.
|
||||
* See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
|
||||
* response body, headers and status and returns its transformed (typically deserialized) version.
|
||||
* See {@link ng.$http#overriding-the-default-transformations-per-request
|
||||
* Overriding the Default Transformations}
|
||||
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||||
* GET request, otherwise if a cache instance built with
|
||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
||||
@@ -736,24 +750,23 @@ function $HttpProvider() {
|
||||
</example>
|
||||
*/
|
||||
function $http(requestConfig) {
|
||||
var config = {
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
transformResponse: defaults.transformResponse
|
||||
};
|
||||
var headers = mergeHeaders(requestConfig);
|
||||
|
||||
if (!angular.isObject(requestConfig)) {
|
||||
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
|
||||
}
|
||||
|
||||
extend(config, requestConfig);
|
||||
config.headers = headers;
|
||||
var config = extend({
|
||||
method: 'get',
|
||||
transformRequest: defaults.transformRequest,
|
||||
transformResponse: defaults.transformResponse
|
||||
}, requestConfig);
|
||||
|
||||
config.headers = mergeHeaders(requestConfig);
|
||||
config.method = uppercase(config.method);
|
||||
|
||||
var serverRequest = function(config) {
|
||||
headers = config.headers;
|
||||
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
|
||||
var headers = config.headers;
|
||||
var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
|
||||
|
||||
// strip content-type if data is undefined
|
||||
if (isUndefined(reqData)) {
|
||||
@@ -769,7 +782,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
// send request
|
||||
return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
|
||||
return sendReq(config, reqData).then(transformResponse, transformResponse);
|
||||
};
|
||||
|
||||
var chain = [serverRequest, undefined];
|
||||
@@ -814,13 +827,30 @@ function $HttpProvider() {
|
||||
if (!response.data) {
|
||||
resp.data = response.data;
|
||||
} else {
|
||||
resp.data = transformData(response.data, response.headers, config.transformResponse);
|
||||
resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
|
||||
}
|
||||
return (isSuccess(response.status))
|
||||
? resp
|
||||
: $q.reject(resp);
|
||||
}
|
||||
|
||||
function executeHeaderFns(headers) {
|
||||
var headerContent, processedHeaders = {};
|
||||
|
||||
forEach(headers, function(headerFn, header) {
|
||||
if (isFunction(headerFn)) {
|
||||
headerContent = headerFn();
|
||||
if (headerContent != null) {
|
||||
processedHeaders[header] = headerContent;
|
||||
}
|
||||
} else {
|
||||
processedHeaders[header] = headerFn;
|
||||
}
|
||||
});
|
||||
|
||||
return processedHeaders;
|
||||
}
|
||||
|
||||
function mergeHeaders(config) {
|
||||
var defHeaders = defaults.headers,
|
||||
reqHeaders = extend({}, config.headers),
|
||||
@@ -843,23 +873,7 @@ function $HttpProvider() {
|
||||
}
|
||||
|
||||
// execute if header value is a function for merged headers
|
||||
execHeaders(reqHeaders);
|
||||
return reqHeaders;
|
||||
|
||||
function execHeaders(headers) {
|
||||
var headerContent;
|
||||
|
||||
forEach(headers, function(headerFn, header) {
|
||||
if (isFunction(headerFn)) {
|
||||
headerContent = headerFn();
|
||||
if (headerContent != null) {
|
||||
headers[header] = headerContent;
|
||||
} else {
|
||||
delete headers[header];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return executeHeaderFns(reqHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1002,11 +1016,12 @@ function $HttpProvider() {
|
||||
* !!! ACCESSES CLOSURE VARS:
|
||||
* $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
|
||||
*/
|
||||
function sendReq(config, reqData, reqHeaders) {
|
||||
function sendReq(config, reqData) {
|
||||
var deferred = $q.defer(),
|
||||
promise = deferred.promise,
|
||||
cache,
|
||||
cachedResp,
|
||||
reqHeaders = config.headers,
|
||||
url = buildUrl(config.url, config.params);
|
||||
|
||||
$http.pendingRequests.push(config);
|
||||
@@ -1025,8 +1040,7 @@ function $HttpProvider() {
|
||||
if (isDefined(cachedResp)) {
|
||||
if (isPromiseLike(cachedResp)) {
|
||||
// cached request has already been sent, but there is no response yet
|
||||
cachedResp.then(removePendingReq, removePendingReq);
|
||||
return cachedResp;
|
||||
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
|
||||
} else {
|
||||
// serving from cache
|
||||
if (isArray(cachedResp)) {
|
||||
@@ -1104,6 +1118,9 @@ function $HttpProvider() {
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePromiseWithResult(result) {
|
||||
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
|
||||
}
|
||||
|
||||
function removePendingReq() {
|
||||
var idx = $http.pendingRequests.indexOf(config);
|
||||
|
||||
@@ -126,7 +126,9 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
|
||||
|
||||
function completeRequest(callback, status, response, headersString, statusText) {
|
||||
// cancel timeout and subsequent timeout promise resolution
|
||||
timeoutId && $browserDefer.cancel(timeoutId);
|
||||
if (timeoutId !== undefined) {
|
||||
$browserDefer.cancel(timeoutId);
|
||||
}
|
||||
jsonpDone = xhr = null;
|
||||
|
||||
callback(status, response, headersString, statusText);
|
||||
|
||||
+24
-24
@@ -57,33 +57,33 @@ function $IntervalProvider() {
|
||||
* // Don't start a new fight if we are already fighting
|
||||
* if ( angular.isDefined(stop) ) return;
|
||||
*
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* stop = $interval(function() {
|
||||
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
|
||||
* $scope.blood_1 = $scope.blood_1 - 3;
|
||||
* $scope.blood_2 = $scope.blood_2 - 4;
|
||||
* } else {
|
||||
* $scope.stopFight();
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* }, 100);
|
||||
* };
|
||||
* };
|
||||
*
|
||||
* $scope.stopFight = function() {
|
||||
* if (angular.isDefined(stop)) {
|
||||
* $interval.cancel(stop);
|
||||
* stop = undefined;
|
||||
* }
|
||||
* };
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.resetFight = function() {
|
||||
* $scope.blood_1 = 100;
|
||||
* $scope.blood_2 = 120;
|
||||
* };
|
||||
*
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* $scope.$on('$destroy', function() {
|
||||
* // Make sure that the interval is destroyed too
|
||||
* $scope.stopFight();
|
||||
* });
|
||||
* }])
|
||||
* // Register the 'myCurrentTime' directive factory method.
|
||||
* // We inject $interval and dateFilter service since the factory method is DI.
|
||||
* .directive('myCurrentTime', ['$interval', 'dateFilter',
|
||||
|
||||
+28
-14
@@ -68,6 +68,10 @@ function stripHash(url) {
|
||||
return index == -1 ? url : url.substr(0, index);
|
||||
}
|
||||
|
||||
function trimEmptyHash(url) {
|
||||
return url.replace(/(#.+)|#$/, '$1');
|
||||
}
|
||||
|
||||
|
||||
function stripFile(url) {
|
||||
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
|
||||
@@ -179,16 +183,25 @@ function LocationHashbangUrl(appBase, hashPrefix) {
|
||||
*/
|
||||
this.$$parse = function(url) {
|
||||
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
|
||||
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
|
||||
? beginsWith(hashPrefix, withoutBaseUrl)
|
||||
: (this.$$html5)
|
||||
? withoutBaseUrl
|
||||
: '';
|
||||
var withoutHashUrl;
|
||||
|
||||
if (!isString(withoutHashUrl)) {
|
||||
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
|
||||
hashPrefix);
|
||||
if (withoutBaseUrl.charAt(0) === '#') {
|
||||
|
||||
// The rest of the url starts with a hash so we have
|
||||
// got either a hashbang path or a plain hash fragment
|
||||
withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
|
||||
if (isUndefined(withoutHashUrl)) {
|
||||
// There was no hashbang prefix so we just have a hash fragment
|
||||
withoutHashUrl = withoutBaseUrl;
|
||||
}
|
||||
|
||||
} else {
|
||||
// There was no hashbang path nor hash fragment:
|
||||
// If we are in HTML5 mode we use what is left as the path;
|
||||
// Otherwise we ignore what is left
|
||||
withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
|
||||
}
|
||||
|
||||
parseAppUrl(withoutHashUrl, this);
|
||||
|
||||
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
|
||||
@@ -551,7 +564,7 @@ var locationPrototype = {
|
||||
*
|
||||
*
|
||||
* ```js
|
||||
* // given url http://example.com/some/path?foo=bar&baz=xoxo#hashValue
|
||||
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
|
||||
* var hash = $location.hash();
|
||||
* // => "hashValue"
|
||||
* ```
|
||||
@@ -775,8 +788,8 @@ function $LocationProvider() {
|
||||
* @param {string=} oldState History state object that was before it was changed.
|
||||
*/
|
||||
|
||||
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
|
||||
function($rootScope, $browser, $sniffer, $rootElement) {
|
||||
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
|
||||
function($rootScope, $browser, $sniffer, $rootElement, $window) {
|
||||
var $location,
|
||||
LocationMode,
|
||||
baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
|
||||
@@ -858,7 +871,7 @@ function $LocationProvider() {
|
||||
if ($location.absUrl() != $browser.url()) {
|
||||
$rootScope.$apply();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
window.angular['ff-684208-preventDefault'] = true;
|
||||
$window.angular['ff-684208-preventDefault'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -903,10 +916,11 @@ function $LocationProvider() {
|
||||
|
||||
// update browser
|
||||
$rootScope.$watch(function $locationWatch() {
|
||||
var oldUrl = $browser.url();
|
||||
var oldUrl = trimEmptyHash($browser.url());
|
||||
var newUrl = trimEmptyHash($location.absUrl());
|
||||
var oldState = $browser.state();
|
||||
var currentReplace = $location.$$replace;
|
||||
var urlOrStateChanged = oldUrl !== $location.absUrl() ||
|
||||
var urlOrStateChanged = oldUrl !== newUrl ||
|
||||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
|
||||
|
||||
if (initializing || urlOrStateChanged) {
|
||||
|
||||
+24
-22
@@ -362,6 +362,8 @@ Parser.prototype = {
|
||||
primary = this.arrayDeclaration();
|
||||
} else if (this.expect('{')) {
|
||||
primary = this.object();
|
||||
} else if (this.peek().identifier && this.peek().text in CONSTANTS) {
|
||||
primary = CONSTANTS[this.consume().text];
|
||||
} else if (this.peek().identifier) {
|
||||
primary = this.identifier();
|
||||
} else if (this.peek().constant) {
|
||||
@@ -464,7 +466,7 @@ Parser.prototype = {
|
||||
id += this.consume().text + this.consume().text;
|
||||
}
|
||||
|
||||
return CONSTANTS[id] || getterFn(id, this.options, this.text);
|
||||
return getterFn(id, this.options, this.text);
|
||||
},
|
||||
|
||||
constant: function() {
|
||||
@@ -598,8 +600,8 @@ Parser.prototype = {
|
||||
logicalAND: function() {
|
||||
var left = this.equality();
|
||||
var token;
|
||||
if ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.text, this.logicalAND(), true);
|
||||
while ((token = this.expect('&&'))) {
|
||||
left = this.binaryFn(left, token.text, this.equality(), true);
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -607,8 +609,8 @@ Parser.prototype = {
|
||||
equality: function() {
|
||||
var left = this.relational();
|
||||
var token;
|
||||
if ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.text, this.equality());
|
||||
while ((token = this.expect('==','!=','===','!=='))) {
|
||||
left = this.binaryFn(left, token.text, this.relational());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -616,8 +618,8 @@ Parser.prototype = {
|
||||
relational: function() {
|
||||
var left = this.additive();
|
||||
var token;
|
||||
if ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.text, this.relational());
|
||||
while ((token = this.expect('<', '>', '<=', '>='))) {
|
||||
left = this.binaryFn(left, token.text, this.additive());
|
||||
}
|
||||
return left;
|
||||
},
|
||||
@@ -654,17 +656,16 @@ Parser.prototype = {
|
||||
},
|
||||
|
||||
fieldAccess: function(object) {
|
||||
var expression = this.text;
|
||||
var field = this.consume().text;
|
||||
var getter = getterFn(field, this.options, expression);
|
||||
var getter = this.identifier();
|
||||
|
||||
return extend(function $parseFieldAccess(scope, locals, self) {
|
||||
return getter(self || object(scope, locals));
|
||||
var o = self || object(scope, locals);
|
||||
return (o == null) ? undefined : getter(o);
|
||||
}, {
|
||||
assign: function(scope, value, locals) {
|
||||
var o = object(scope, locals);
|
||||
if (!o) object.assign(scope, o = {});
|
||||
return setter(o, field, value, expression);
|
||||
if (!o) object.assign(scope, o = {}, locals);
|
||||
return getter.assign(o, value);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -689,7 +690,7 @@ Parser.prototype = {
|
||||
var key = ensureSafeMemberName(indexFn(self, locals), expression);
|
||||
// prevent overwriting of Function.constructor which would break ensureSafeObject check
|
||||
var o = ensureSafeObject(obj(self, locals), expression);
|
||||
if (!o) obj.assign(self, o = {});
|
||||
if (!o) obj.assign(self, o = {}, locals);
|
||||
return o[key] = value;
|
||||
}
|
||||
});
|
||||
@@ -709,7 +710,7 @@ Parser.prototype = {
|
||||
var args = argsFn.length ? [] : null;
|
||||
|
||||
return function $parseFunctionCall(scope, locals) {
|
||||
var context = contextGetter ? contextGetter(scope, locals) : scope;
|
||||
var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
|
||||
var fn = fnGetter(scope, locals, context) || noop;
|
||||
|
||||
if (args) {
|
||||
@@ -722,13 +723,13 @@ Parser.prototype = {
|
||||
ensureSafeObject(context, expressionText);
|
||||
ensureSafeFunction(fn, expressionText);
|
||||
|
||||
// IE stupidity! (IE doesn't have apply for some native functions)
|
||||
// IE doesn't have apply for some native functions
|
||||
var v = fn.apply
|
||||
? fn.apply(context, args)
|
||||
: fn(args[0], args[1], args[2], args[3], args[4]);
|
||||
|
||||
return ensureSafeObject(v, expressionText);
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
// This is used with json array declaration
|
||||
@@ -799,18 +800,19 @@ Parser.prototype = {
|
||||
// Parser helper functions
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
function setter(obj, path, setValue, fullExp) {
|
||||
function setter(obj, locals, path, setValue, fullExp) {
|
||||
ensureSafeObject(obj, fullExp);
|
||||
ensureSafeObject(locals, fullExp);
|
||||
|
||||
var element = path.split('.'), key;
|
||||
for (var i = 0; element.length > 1; i++) {
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
var propertyObj = ensureSafeObject(obj[key], fullExp);
|
||||
var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
|
||||
if (!propertyObj) {
|
||||
propertyObj = {};
|
||||
obj[key] = propertyObj;
|
||||
}
|
||||
obj = propertyObj;
|
||||
obj = ensureSafeObject(propertyObj, fullExp);
|
||||
}
|
||||
key = ensureSafeMemberName(element.shift(), fullExp);
|
||||
ensureSafeObject(obj[key], fullExp);
|
||||
@@ -937,8 +939,8 @@ function getterFn(path, options, fullExp) {
|
||||
}
|
||||
|
||||
fn.sharedGetter = true;
|
||||
fn.assign = function(self, value) {
|
||||
return setter(self, path, value, path);
|
||||
fn.assign = function(self, value, locals) {
|
||||
return setter(self, locals, path, value, path);
|
||||
};
|
||||
getterFnCache[path] = fn;
|
||||
return fn;
|
||||
|
||||
+1
-3
@@ -3,12 +3,10 @@
|
||||
function $$RAFProvider() { //rAF
|
||||
this.$get = ['$window', '$timeout', function($window, $timeout) {
|
||||
var requestAnimationFrame = $window.requestAnimationFrame ||
|
||||
$window.webkitRequestAnimationFrame ||
|
||||
$window.mozRequestAnimationFrame;
|
||||
$window.webkitRequestAnimationFrame;
|
||||
|
||||
var cancelAnimationFrame = $window.cancelAnimationFrame ||
|
||||
$window.webkitCancelAnimationFrame ||
|
||||
$window.mozCancelAnimationFrame ||
|
||||
$window.webkitCancelRequestAnimationFrame;
|
||||
|
||||
var rafSupported = !!requestAnimationFrame;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* @description
|
||||
* The root element of Angular application. This is either the element where {@link
|
||||
* ng.directive:ngApp ngApp} was declared or the element passed into
|
||||
* {@link angular.bootstrap}. The element represent the root element of application. It is also the
|
||||
* location where the applications {@link auto.$injector $injector} service gets
|
||||
* published, it can be retrieved using `$rootElement.injector()`.
|
||||
* {@link angular.bootstrap}. The element represents the root element of application. It is also the
|
||||
* location where the application's {@link auto.$injector $injector} service gets
|
||||
* published, and can be retrieved using `$rootElement.injector()`.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
+4
-4
@@ -105,7 +105,6 @@ function $RootScopeProvider() {
|
||||
var child = parent.$new();
|
||||
|
||||
parent.salutation = "Hello";
|
||||
child.name = "World";
|
||||
expect(child.salutation).toEqual('Hello');
|
||||
|
||||
child.salutation = "Welcome";
|
||||
@@ -743,7 +742,7 @@ function $RootScopeProvider() {
|
||||
while (asyncQueue.length) {
|
||||
try {
|
||||
asyncTask = asyncQueue.shift();
|
||||
asyncTask.scope.$eval(asyncTask.expression);
|
||||
asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
|
||||
} catch (e) {
|
||||
$exceptionHandler(e);
|
||||
}
|
||||
@@ -958,8 +957,9 @@ function $RootScopeProvider() {
|
||||
* - `string`: execute using the rules as defined in {@link guide/expression expression}.
|
||||
* - `function(scope)`: execute the function with the current `scope` parameter.
|
||||
*
|
||||
* @param {(object)=} locals Local variables object, useful for overriding values in scope.
|
||||
*/
|
||||
$evalAsync: function(expr) {
|
||||
$evalAsync: function(expr, locals) {
|
||||
// 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 && !asyncQueue.length) {
|
||||
@@ -970,7 +970,7 @@ function $RootScopeProvider() {
|
||||
});
|
||||
}
|
||||
|
||||
asyncQueue.push({scope: this, expression: expr});
|
||||
asyncQueue.push({scope: this, expression: expr, locals: locals});
|
||||
},
|
||||
|
||||
$$postDigest: function(fn) {
|
||||
|
||||
+3
-1
@@ -67,7 +67,9 @@ function $SnifferProvider() {
|
||||
// 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
|
||||
// when cut operation is performed.
|
||||
if (event == 'input' && msie == 9) return false;
|
||||
// IE10+ implements 'input' event but it erroneously fires under various situations,
|
||||
// e.g. when placeholder changes, or a form is focused.
|
||||
if (event === 'input' && msie <= 11) return false;
|
||||
|
||||
if (isUndefined(eventSupport[event])) {
|
||||
var divElm = document.createElement('div');
|
||||
|
||||
@@ -9,7 +9,7 @@ var $compileMinErr = minErr('$compile');
|
||||
* @description
|
||||
* The `$templateRequest` service downloads the provided template using `$http` and, upon success,
|
||||
* stores the contents inside of `$templateCache`. If the HTTP request fails or the response data
|
||||
* of the HTTP request is empty then a `$compile` error will be thrown (the exception can be thwarted
|
||||
* of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted
|
||||
* by setting the 2nd parameter of the function to true).
|
||||
*
|
||||
* @param {string} tpl The HTTP request template URL
|
||||
@@ -28,14 +28,9 @@ function $TemplateRequestProvider() {
|
||||
var transformResponse = $http.defaults && $http.defaults.transformResponse;
|
||||
|
||||
if (isArray(transformResponse)) {
|
||||
var original = transformResponse;
|
||||
transformResponse = [];
|
||||
for (var i = 0; i < original.length; ++i) {
|
||||
var transformer = original[i];
|
||||
if (transformer !== defaultHttpResponseTransform) {
|
||||
transformResponse.push(transformer);
|
||||
}
|
||||
}
|
||||
transformResponse = transformResponse.filter(function(transformer) {
|
||||
return transformer !== defaultHttpResponseTransform;
|
||||
});
|
||||
} else if (transformResponse === defaultHttpResponseTransform) {
|
||||
transformResponse = null;
|
||||
}
|
||||
@@ -47,18 +42,16 @@ function $TemplateRequestProvider() {
|
||||
|
||||
return $http.get(tpl, httpOptions)
|
||||
.then(function(response) {
|
||||
var html = response.data;
|
||||
self.totalPendingRequests--;
|
||||
$templateCache.put(tpl, html);
|
||||
return html;
|
||||
return response.data;
|
||||
}, handleError);
|
||||
|
||||
function handleError() {
|
||||
function handleError(resp) {
|
||||
self.totalPendingRequests--;
|
||||
if (!ignoreRequestError) {
|
||||
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
|
||||
}
|
||||
return $q.reject();
|
||||
return $q.reject(resp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+122
-120
@@ -13,7 +13,7 @@
|
||||
* # Usage
|
||||
*
|
||||
* To see animations in action, all that is required is to define the appropriate CSS classes
|
||||
* or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
|
||||
* or to register a JavaScript animation via the `myModule.animation()` function. The directives that support animation automatically are:
|
||||
* `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
|
||||
* by using the `$animate` service.
|
||||
*
|
||||
@@ -155,8 +155,8 @@
|
||||
* ### Structural transition animations
|
||||
*
|
||||
* Structural transitions (such as enter, leave and move) will always apply a `0s none` transition
|
||||
* value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave
|
||||
* or .ng-move) class. This means that any active transition animations operating on the element
|
||||
* value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave`
|
||||
* or `.ng-move`) class. This means that any active transition animations operating on the element
|
||||
* will be cut off to make way for the enter, leave or move animation.
|
||||
*
|
||||
* ### Class-based transition animations
|
||||
@@ -473,11 +473,12 @@ angular.module('ngAnimate', ['ng'])
|
||||
function isMatchingElement(elm1, elm2) {
|
||||
return extractElementNode(elm1) == extractElementNode(elm2);
|
||||
}
|
||||
|
||||
var $$jqLite;
|
||||
$provide.decorator('$animate',
|
||||
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
|
||||
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
|
||||
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite',
|
||||
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest, $$$jqLite) {
|
||||
|
||||
$$jqLite = $$$jqLite;
|
||||
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
|
||||
|
||||
// Wait until all directive and route-related templates are downloaded and
|
||||
@@ -832,7 +833,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
* promise that was returned when the animation was started.
|
||||
*
|
||||
* ```js
|
||||
* var promise = $animate.addClass(element, 'super-long-animation').then(function() {
|
||||
* var promise = $animate.addClass(element, 'super-long-animation');
|
||||
* promise.then(function() {
|
||||
* //this will still be called even if cancelled
|
||||
* });
|
||||
*
|
||||
@@ -871,22 +873,22 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during the `animate` animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | 1. $animate.animate(...) is called | class="my-animation" |
|
||||
* | 2. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 3. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 4. the className class value is added to the element | class="my-animation ng-animate className" |
|
||||
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate className" |
|
||||
* | 6. $animate blocks all CSS transitions on the element to ensure the .className class styling is applied right away| class="my-animation ng-animate className" |
|
||||
* | 7. $animate applies the provided collection of `from` CSS styles to the element | class="my-animation ng-animate className" |
|
||||
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate className" |
|
||||
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate className" |
|
||||
* | 10. the className-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate className className-active" |
|
||||
* | 11. $animate applies the collection of `to` CSS styles to the element which are then handled by the transition | class="my-animation ng-animate className className-active" |
|
||||
* | 12. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate className className-active" |
|
||||
* | 13. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 14. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
* | 1. `$animate.animate(...)` is called | `class="my-animation"` |
|
||||
* | 2. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 3. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 4. the `className` class value is added to the element | `class="my-animation ng-animate className"` |
|
||||
* | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate className"` |
|
||||
* | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"` |
|
||||
* | 7. `$animate` applies the provided collection of `from` CSS styles to the element | `class="my-animation ng-animate className"` |
|
||||
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate className"` |
|
||||
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate className"` |
|
||||
* | 10. the `className-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate className className-active"` |
|
||||
* | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition | `class="my-animation ng-animate className className-active"` |
|
||||
* | 12. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate className className-active"` |
|
||||
* | 13. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 14. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the enter animation
|
||||
* @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation
|
||||
@@ -917,21 +919,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during enter animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
* | 1. $animate.enter(...) is called | class="my-animation" |
|
||||
* | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
|
||||
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
|
||||
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" |
|
||||
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" |
|
||||
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" |
|
||||
* | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" |
|
||||
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 13. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | 1. `$animate.enter(...)` is called | `class="my-animation"` |
|
||||
* | 2. element is inserted into the `parentElement` element or beside the `afterElement` element | `class="my-animation"` |
|
||||
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 5. the `.ng-enter` class is added to the element | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-enter"` |
|
||||
* | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
|
||||
* | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 13. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the enter animation
|
||||
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
|
||||
@@ -963,21 +965,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during leave animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
* | 1. $animate.leave(...) is called | class="my-animation" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
|
||||
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
|
||||
* | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave" |
|
||||
* | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" |
|
||||
* | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave" |
|
||||
* | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" |
|
||||
* | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" |
|
||||
* | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 12. The element is removed from the DOM | ... |
|
||||
* | 13. The returned promise is resolved. | ... |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
|
||||
* | 1. `$animate.leave(...)` is called | `class="my-animation"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 4. the `.ng-leave` class is added to the element | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 7. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 8. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-leave"` |
|
||||
* | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
|
||||
* | 10. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
|
||||
* | 11. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 12. The element is removed from the DOM | ... |
|
||||
* | 13. The returned promise is resolved. | ... |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the leave animation
|
||||
* @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
|
||||
@@ -1008,21 +1010,21 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during move animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
|
||||
* | 1. $animate.move(...) is called | class="my-animation" |
|
||||
* | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
|
||||
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
|
||||
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
|
||||
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move" |
|
||||
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" |
|
||||
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move" |
|
||||
* | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" |
|
||||
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 13. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
|
||||
* | 1. `$animate.move(...)` is called | `class="my-animation"` |
|
||||
* | 2. element is moved into the parentElement element or beside the afterElement element | `class="my-animation"` |
|
||||
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
|
||||
* | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 5. the `.ng-move` class is added to the element | `class="my-animation ng-animate ng-move"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-move"` |
|
||||
* | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"` |
|
||||
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-move"` |
|
||||
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-move"` |
|
||||
* | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-move ng-move-active"` |
|
||||
* | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-move ng-move-active"` |
|
||||
* | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 13. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be the focus of the move animation
|
||||
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
|
||||
@@ -1056,18 +1058,18 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during addClass animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
* | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
|
||||
* | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" |
|
||||
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" |
|
||||
* | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super super-add super-add-active" |
|
||||
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super super-add super-add-active" |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
|
||||
* | 9. The super class is kept on the element | class="my-animation super" |
|
||||
* | 10. The returned promise is resolved. | class="my-animation super" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
* | 1. `$animate.addClass(element, 'super')` is called | `class="my-animation"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
|
||||
* | 3. the `.super-add` class is added to the element | `class="my-animation ng-animate super-add"` |
|
||||
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate super-add"` |
|
||||
* | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super super-add super-add-active"` |
|
||||
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super super-add super-add-active"` |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation super"` |
|
||||
* | 9. The super class is kept on the element | `class="my-animation super"` |
|
||||
* | 10. The returned promise is resolved. | `class="my-animation super"` |
|
||||
*
|
||||
* @param {DOMElement} element the element that will be animated
|
||||
* @param {string} className the CSS class that will be added to the element and then animated
|
||||
@@ -1090,17 +1092,17 @@ angular.module('ngAnimate', ['ng'])
|
||||
*
|
||||
* Below is a breakdown of each step that occurs during removeClass animation:
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
* | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" |
|
||||
* | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" |
|
||||
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" |
|
||||
* | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-remove super-remove-active" |
|
||||
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
|
||||
* | 9. The returned promise is resolved. | class="my-animation" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
* | 1. `$animate.removeClass(element, 'super')` is called | `class="my-animation super"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation super ng-animate"` |
|
||||
* | 3. the `.super-remove` class is added to the element | `class="my-animation super ng-animate super-remove"` |
|
||||
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation super ng-animate super-remove"` |
|
||||
* | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super-remove super-remove-active"` |
|
||||
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super-remove super-remove-active"` |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
|
||||
* | 9. The returned promise is resolved. | `class="my-animation"` |
|
||||
*
|
||||
*
|
||||
* @param {DOMElement} element the element that will be animated
|
||||
@@ -1118,19 +1120,19 @@ angular.module('ngAnimate', ['ng'])
|
||||
* @name $animate#setClass
|
||||
*
|
||||
* @description Adds and/or removes the given CSS classes to and from the element.
|
||||
* Once complete, the done() callback will be fired (if provided).
|
||||
* Once complete, the `done()` callback will be fired (if provided).
|
||||
*
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
|
||||
* | 1. $animate.setClass(element, 'on', 'off') is called | class="my-animation off" |
|
||||
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate off" |
|
||||
* | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off" |
|
||||
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off" |
|
||||
* | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
|
||||
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
|
||||
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" |
|
||||
* | 9. The returned promise is resolved. | class="my-animation on" |
|
||||
* | Animation Step | What the element class attribute looks like |
|
||||
* |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
|
||||
* | 1. `$animate.setClass(element, 'on', 'off')` is called | `class="my-animation off"` |
|
||||
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate off"` |
|
||||
* | 3. the `.on-add` and `.off-remove` classes are added to the element | `class="my-animation ng-animate on-add off-remove off"` |
|
||||
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate on-add off-remove off"` |
|
||||
* | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
|
||||
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
|
||||
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
|
||||
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation on"` |
|
||||
* | 9. The returned promise is resolved. | `class="my-animation on"` |
|
||||
*
|
||||
* @param {DOMElement} element the element which will have its CSS classes changed
|
||||
* removed from it
|
||||
@@ -1268,7 +1270,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
all animations call this shared animation triggering function internally.
|
||||
The animationEvent variable refers to the JavaScript animation event that will be triggered
|
||||
and the className value is the name of the animation that will be applied within the
|
||||
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
|
||||
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, options, doneCallback) {
|
||||
@@ -1380,10 +1382,10 @@ 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);
|
||||
$$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME);
|
||||
if (options && options.tempClasses) {
|
||||
forEach(options.tempClasses, function(className) {
|
||||
element.addClass(className);
|
||||
$$jqLite.addClass(element, className);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1461,7 +1463,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
closeAnimation.hasBeenRun = true;
|
||||
if (options && options.tempClasses) {
|
||||
forEach(options.tempClasses, function(className) {
|
||||
element.removeClass(className);
|
||||
$$jqLite.removeClass(element, className);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1523,7 +1525,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
if (removeAnimations || !data.totalActive) {
|
||||
element.removeClass(NG_ANIMATE_CLASS_NAME);
|
||||
$$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME);
|
||||
element.removeData(NG_ANIMATE_STATE);
|
||||
}
|
||||
}
|
||||
@@ -1764,14 +1766,14 @@ angular.module('ngAnimate', ['ng'])
|
||||
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
|
||||
var applyClasses = !lookupCache[staggerCacheKey];
|
||||
|
||||
applyClasses && element.addClass(staggerClassName);
|
||||
applyClasses && $$jqLite.addClass(element, staggerClassName);
|
||||
|
||||
stagger = getElementAnimationDetails(element, staggerCacheKey);
|
||||
|
||||
applyClasses && element.removeClass(staggerClassName);
|
||||
applyClasses && $$jqLite.removeClass(element, staggerClassName);
|
||||
}
|
||||
|
||||
element.addClass(className);
|
||||
$$jqLite.addClass(element, className);
|
||||
|
||||
var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
|
||||
var timings = getElementAnimationDetails(element, eventCacheKey);
|
||||
@@ -1779,7 +1781,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var animationDuration = timings.animationDuration;
|
||||
|
||||
if (structural && transitionDuration === 0 && animationDuration === 0) {
|
||||
element.removeClass(className);
|
||||
$$jqLite.removeClass(element, className);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1851,7 +1853,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
if (!staggerTime) {
|
||||
element.addClass(activeClassName);
|
||||
$$jqLite.addClass(element, activeClassName);
|
||||
if (elementData.blockTransition) {
|
||||
blockTransitions(node, false);
|
||||
}
|
||||
@@ -1861,7 +1863,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
var timings = getElementAnimationDetails(element, eventCacheKey);
|
||||
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
|
||||
if (maxDuration === 0) {
|
||||
element.removeClass(activeClassName);
|
||||
$$jqLite.removeClass(element, activeClassName);
|
||||
animateClose(element, className);
|
||||
activeAnimationComplete();
|
||||
return;
|
||||
@@ -1896,7 +1898,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
|
||||
var staggerTimeout;
|
||||
if (staggerTime > 0) {
|
||||
element.addClass(pendingClassName);
|
||||
$$jqLite.addClass(element, pendingClassName);
|
||||
staggerTimeout = $timeout(function() {
|
||||
staggerTimeout = null;
|
||||
|
||||
@@ -1907,8 +1909,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
blockAnimations(node, false);
|
||||
}
|
||||
|
||||
element.addClass(activeClassName);
|
||||
element.removeClass(pendingClassName);
|
||||
$$jqLite.addClass(element, activeClassName);
|
||||
$$jqLite.removeClass(element, pendingClassName);
|
||||
|
||||
if (styles) {
|
||||
if (timings.transitionDuration === 0) {
|
||||
@@ -1935,8 +1937,8 @@ angular.module('ngAnimate', ['ng'])
|
||||
// timeout done method.
|
||||
function onEnd() {
|
||||
element.off(css3AnimationEvents, onAnimationProgress);
|
||||
element.removeClass(activeClassName);
|
||||
element.removeClass(pendingClassName);
|
||||
$$jqLite.removeClass(element, activeClassName);
|
||||
$$jqLite.removeClass(element, pendingClassName);
|
||||
if (staggerTimeout) {
|
||||
$timeout.cancel(staggerTimeout);
|
||||
}
|
||||
@@ -2024,7 +2026,7 @@ angular.module('ngAnimate', ['ng'])
|
||||
}
|
||||
|
||||
function animateClose(element, className) {
|
||||
element.removeClass(className);
|
||||
$$jqLite.removeClass(element, className);
|
||||
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
|
||||
if (data) {
|
||||
if (data.running) {
|
||||
|
||||
+45
-27
@@ -26,7 +26,7 @@
|
||||
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
|
||||
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
|
||||
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex |
|
||||
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event |
|
||||
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
|
||||
* | {@link module:ngMessages ngMessages} | aria-live |
|
||||
*
|
||||
@@ -82,7 +82,8 @@ function $AriaProvider() {
|
||||
ariaInvalid: true,
|
||||
ariaMultiline: true,
|
||||
ariaValue: true,
|
||||
tabindex: true
|
||||
tabindex: true,
|
||||
bindKeypress: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -99,6 +100,7 @@ function $AriaProvider() {
|
||||
* - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
|
||||
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
|
||||
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
|
||||
* - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on ng-click
|
||||
*
|
||||
* @description
|
||||
* Enables/disables various ARIA attributes
|
||||
@@ -107,16 +109,9 @@ function $AriaProvider() {
|
||||
config = angular.extend(config, newConfig);
|
||||
};
|
||||
|
||||
function camelCase(input) {
|
||||
return input.replace(/-./g, function(letter, pos) {
|
||||
return letter[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function watchExpr(attrName, ariaAttr, negate) {
|
||||
var ariaCamelName = camelCase(ariaAttr);
|
||||
return function(scope, elem, attr) {
|
||||
var ariaCamelName = attr.$normalize(ariaAttr);
|
||||
if (config[ariaCamelName] && !attr[ariaCamelName]) {
|
||||
scope.$watch(attr[attrName], function(boolVal) {
|
||||
if (negate) {
|
||||
@@ -176,20 +171,13 @@ function $AriaProvider() {
|
||||
this.$get = function() {
|
||||
return {
|
||||
config: function(key) {
|
||||
return config[camelCase(key)];
|
||||
return config[key];
|
||||
},
|
||||
$$watchExpr: watchExpr
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
var ngAriaTabindex = ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
|
||||
@@ -199,8 +187,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}])
|
||||
.directive('ngModel', ['$aria', function($aria) {
|
||||
|
||||
function shouldAttachAttr(attr, elem) {
|
||||
return $aria.config(attr) && !elem.attr(attr);
|
||||
function shouldAttachAttr(attr, normalizedAttr, elem) {
|
||||
return $aria.config(normalizedAttr) && !elem.attr(attr);
|
||||
}
|
||||
|
||||
function getShape(attr, elem) {
|
||||
@@ -218,7 +206,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
require: '?ngModel',
|
||||
link: function(scope, elem, attr, ngModel) {
|
||||
var shape = getShape(attr, elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', elem);
|
||||
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
|
||||
|
||||
function ngAriaWatchModelValue() {
|
||||
return ngModel.$modelValue;
|
||||
@@ -246,7 +234,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
switch (shape) {
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
if (shouldAttachAttr('aria-checked', elem)) {
|
||||
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
|
||||
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
|
||||
getRadioReaction() : ngAriaCheckboxReaction);
|
||||
}
|
||||
@@ -267,7 +255,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
break;
|
||||
case 'multiline':
|
||||
if (shouldAttachAttr('aria-multiline', elem)) {
|
||||
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
|
||||
elem.attr('aria-multiline', true);
|
||||
}
|
||||
break;
|
||||
@@ -277,7 +265,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', elem)) {
|
||||
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
|
||||
scope.$watch(function ngAriaRequiredWatch() {
|
||||
return ngModel.$error.required;
|
||||
}, function ngAriaRequiredReaction(newVal) {
|
||||
@@ -285,7 +273,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
});
|
||||
}
|
||||
|
||||
if (shouldAttachAttr('aria-invalid', elem)) {
|
||||
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
|
||||
scope.$watch(function ngAriaInvalidWatch() {
|
||||
return ngModel.$invalid;
|
||||
}, function ngAriaInvalidReaction(newVal) {
|
||||
@@ -309,5 +297,35 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('ngClick', ngAriaTabindex)
|
||||
.directive('ngDblclick', ngAriaTabindex);
|
||||
.directive('ngClick',['$aria', '$parse', function($aria, $parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function(elem, attr) {
|
||||
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
|
||||
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
|
||||
elem.on('keypress', function(event) {
|
||||
if (event.keyCode === 32 || event.keyCode === 13) {
|
||||
scope.$apply(callback);
|
||||
}
|
||||
|
||||
function callback() {
|
||||
fn(scope, { $event: event });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
.directive('ngDblclick', ['$aria', function($aria) {
|
||||
return function(scope, elem, attr) {
|
||||
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
|
||||
elem.attr('tabindex', 0);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
Vendored
+34
-32
@@ -243,31 +243,31 @@ angular.mock.$ExceptionHandlerProvider = function() {
|
||||
*
|
||||
* @param {string} mode Mode of operation, defaults to `rethrow`.
|
||||
*
|
||||
* - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
|
||||
* is a bug in the application or test, so this mock will make these tests fail.
|
||||
* - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
|
||||
* mode stores an array of errors in `$exceptionHandler.errors`, to allow later
|
||||
* assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
|
||||
* {@link ngMock.$log#reset reset()}
|
||||
* - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
|
||||
* is a bug in the application or test, so this mock will make these tests fail.
|
||||
* For any implementations that expect exceptions to be thrown, the `rethrow` mode
|
||||
* will also maintain a log of thrown errors.
|
||||
*/
|
||||
this.mode = function(mode) {
|
||||
switch (mode) {
|
||||
case 'rethrow':
|
||||
handler = function(e) {
|
||||
throw e;
|
||||
};
|
||||
break;
|
||||
case 'log':
|
||||
var errors = [];
|
||||
|
||||
switch (mode) {
|
||||
case 'log':
|
||||
case 'rethrow':
|
||||
var errors = [];
|
||||
handler = function(e) {
|
||||
if (arguments.length == 1) {
|
||||
errors.push(e);
|
||||
} else {
|
||||
errors.push([].slice.call(arguments, 0));
|
||||
}
|
||||
if (mode === "rethrow") {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
handler.errors = errors;
|
||||
break;
|
||||
default:
|
||||
@@ -1112,7 +1112,7 @@ angular.mock.dump = function(object) {
|
||||
```
|
||||
*/
|
||||
angular.mock.$HttpBackendProvider = function() {
|
||||
this.$get = ['$rootScope', createHttpBackendMock];
|
||||
this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1129,7 +1129,7 @@ angular.mock.$HttpBackendProvider = function() {
|
||||
* @param {Object=} $browser Auto-flushing enabled if specified
|
||||
* @return {Object} Instance of $httpBackend mock
|
||||
*/
|
||||
function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
|
||||
var definitions = [],
|
||||
expectations = [],
|
||||
responses = [],
|
||||
@@ -1142,7 +1142,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
return function() {
|
||||
return angular.isNumber(status)
|
||||
? [status, data, headers, statusText]
|
||||
: [200, status, data];
|
||||
: [200, status, data, headers];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1159,7 +1159,9 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
}
|
||||
|
||||
function wrapResponse(wrapped) {
|
||||
if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
|
||||
if (!$browser && timeout) {
|
||||
timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
|
||||
}
|
||||
|
||||
return handleResponse;
|
||||
|
||||
@@ -1274,7 +1276,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1288,7 +1290,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1302,7 +1304,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1318,7 +1320,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1334,7 +1336,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
|
||||
* data string and returns true if the data is as expected.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1347,7 +1349,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1368,7 +1370,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* is in JSON format.
|
||||
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
|
||||
* object and returns true if the headers match the current expectation.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*
|
||||
@@ -1403,7 +1405,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled. See #expect for more info.
|
||||
*/
|
||||
@@ -1417,7 +1419,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1431,7 +1433,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1448,7 +1450,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1465,7 +1467,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1482,7 +1484,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
* receives data string and returns true if the data is as expected, or Object if request body
|
||||
* is in JSON format.
|
||||
* @param {Object=} headers HTTP headers.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -1495,7 +1497,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
|
||||
*
|
||||
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
|
||||
* and returns true if the url match the current definition.
|
||||
* @returns {requestHandler} Returns an object with `respond` method that control how a matched
|
||||
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
|
||||
* request is handled. You can save this object for later use and invoke `respond` again in
|
||||
* order to change how a matched request is handled.
|
||||
*/
|
||||
@@ -2033,7 +2035,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
|
||||
*/
|
||||
angular.mock.e2e = {};
|
||||
angular.mock.e2e.$httpBackendDecorator =
|
||||
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
|
||||
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
|
||||
|
||||
|
||||
/**
|
||||
@@ -2047,7 +2049,7 @@ angular.mock.e2e.$httpBackendDecorator =
|
||||
*
|
||||
* In addition to all the regular `Scope` methods, the following helper methods are available:
|
||||
*/
|
||||
angular.mock.$RootScopeDecorator = function($delegate) {
|
||||
angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
|
||||
|
||||
var $rootScopePrototype = Object.getPrototypeOf($delegate);
|
||||
|
||||
@@ -2119,7 +2121,7 @@ angular.mock.$RootScopeDecorator = function($delegate) {
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
|
||||
if (window.jasmine || window.mocha) {
|
||||
|
||||
@@ -111,7 +111,7 @@ function shallowClearAndCopy(src, dst) {
|
||||
* example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
|
||||
* will be `data.someProp`.
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
|
||||
* the default set of resource actions. The declaration should be created in the format of {@link
|
||||
* ng.$http#usage $http.config}:
|
||||
*
|
||||
|
||||
@@ -79,7 +79,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
||||
.view-animate-container {
|
||||
position:relative;
|
||||
height:100px!important;
|
||||
position:relative;
|
||||
background:white;
|
||||
border:1px solid black;
|
||||
height:40px;
|
||||
|
||||
@@ -175,7 +175,7 @@ function $RouteProvider() {
|
||||
* @description
|
||||
*
|
||||
* A boolean property indicating if routes defined
|
||||
* using this provider should be matched using a case sensitive
|
||||
* using this provider should be matched using a case insensitive
|
||||
* algorithm. Defaults to `false`.
|
||||
*/
|
||||
this.caseInsensitiveMatch = false;
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
*/
|
||||
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
var LINKY_URL_REGEXP =
|
||||
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
|
||||
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
|
||||
MAILTO_REGEXP = /^mailto:/;
|
||||
|
||||
return function(text, target) {
|
||||
@@ -117,8 +117,10 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
while ((match = raw.match(LINKY_URL_REGEXP))) {
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2] == match[3]) url = 'mailto:' + url;
|
||||
// if we did not match ftp/http/www/mailto then assume mailto
|
||||
if (!match[2] && !match[4]) {
|
||||
url = (match[3] ? 'http://' : 'mailto:') + url;
|
||||
}
|
||||
i = match.index;
|
||||
addText(raw.substr(0, i));
|
||||
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
||||
@@ -142,7 +144,7 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
||||
'" ');
|
||||
}
|
||||
html.push('href="',
|
||||
url.replace('"', '"'),
|
||||
url.replace(/"/g, '"'),
|
||||
'">');
|
||||
addText(text);
|
||||
html.push('</a>');
|
||||
|
||||
@@ -199,7 +199,7 @@ angular.scenario.dsl('binding', function() {
|
||||
*/
|
||||
angular.scenario.dsl('input', function() {
|
||||
var chain = {};
|
||||
var supportInputEvent = 'oninput' in document.createElement('div') && msie != 9;
|
||||
var supportInputEvent = 'oninput' in document.createElement('div') && !(msie && msie <= 11);
|
||||
|
||||
chain.enter = function(value, event) {
|
||||
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'",
|
||||
|
||||
+10
-2
@@ -1191,9 +1191,17 @@ describe('angular', function() {
|
||||
|
||||
it('should format objects pretty', function() {
|
||||
expect(toJson({a: 1, b: 2}, true)).
|
||||
toBeOneOf('{\n "a": 1,\n "b": 2\n}', '{\n "a":1,\n "b":2\n}');
|
||||
toBe('{\n "a": 1,\n "b": 2\n}');
|
||||
expect(toJson({a: {b: 2}}, true)).
|
||||
toBeOneOf('{\n "a": {\n "b": 2\n }\n}', '{\n "a":{\n "b":2\n }\n}');
|
||||
toBe('{\n "a": {\n "b": 2\n }\n}');
|
||||
expect(toJson({a: 1, b: 2}, false)).
|
||||
toBe('{"a":1,"b":2}');
|
||||
expect(toJson({a: 1, b: 2}, 0)).
|
||||
toBe('{"a":1,"b":2}');
|
||||
expect(toJson({a: 1, b: 2}, 1)).
|
||||
toBe('{\n "a": 1,\n "b": 2\n}');
|
||||
expect(toJson({a: 1, b: 2}, {})).
|
||||
toBe('{\n "a": 1,\n "b": 2\n}');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ describe('injector', function() {
|
||||
var providers;
|
||||
var injector;
|
||||
var providerInjector;
|
||||
var controllerProvider;
|
||||
|
||||
beforeEach(module(function($provide, $injector) {
|
||||
beforeEach(module(function($provide, $injector, $controllerProvider) {
|
||||
providers = function(name, factory, annotations) {
|
||||
$provide.factory(name, extend(factory, annotations || {}));
|
||||
};
|
||||
providerInjector = $injector;
|
||||
controllerProvider = $controllerProvider;
|
||||
}));
|
||||
beforeEach(inject(function($injector) {
|
||||
injector = $injector;
|
||||
@@ -74,6 +76,22 @@ describe('injector', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should provide the caller name if given', function(done) {
|
||||
expect(function() {
|
||||
injector.get('idontexist', 'callerName');
|
||||
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- callerName");
|
||||
});
|
||||
|
||||
|
||||
it('should provide the caller name for controllers', function(done) {
|
||||
controllerProvider.register('myCtrl', function(idontexist) {});
|
||||
var $controller = injector.get('$controller');
|
||||
expect(function() {
|
||||
$controller('myCtrl', {$scope: {}});
|
||||
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- myCtrl");
|
||||
});
|
||||
|
||||
|
||||
it('should not corrupt the cache when an object fails to get instantiated', function() {
|
||||
expect(function() {
|
||||
injector.get('idontexist');
|
||||
|
||||
@@ -339,3 +339,67 @@ window.dump = function() {
|
||||
return angular.mock.dump(arg);
|
||||
}));
|
||||
};
|
||||
|
||||
function getInputCompileHelper(currentSpec) {
|
||||
|
||||
var helper = {};
|
||||
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.directive('attrCapture', function() {
|
||||
return function(scope, element, $attrs) {
|
||||
helper.attrs = $attrs;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
inject(function($compile, $rootScope, $sniffer) {
|
||||
|
||||
helper.compileInput = function(inputHtml, mockValidity, scope) {
|
||||
|
||||
scope = helper.scope = scope || $rootScope;
|
||||
|
||||
// Create the input element and dealoc when done
|
||||
helper.inputElm = jqLite(inputHtml);
|
||||
|
||||
// Set up mock validation if necessary
|
||||
if (isObject(mockValidity)) {
|
||||
VALIDITY_STATE_PROPERTY = 'ngMockValidity';
|
||||
helper.inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
|
||||
currentSpec.after(function() {
|
||||
VALIDITY_STATE_PROPERTY = 'validity';
|
||||
});
|
||||
}
|
||||
|
||||
// Create the form element and dealoc when done
|
||||
helper.formElm = jqLite('<form name="form"></form>');
|
||||
helper.formElm.append(helper.inputElm);
|
||||
|
||||
// Compile the lot and return the input element
|
||||
$compile(helper.formElm)(scope);
|
||||
|
||||
spyOn(scope.form, '$addControl').andCallThrough();
|
||||
spyOn(scope.form, '$$renameControl').andCallThrough();
|
||||
|
||||
scope.$digest();
|
||||
|
||||
return helper.inputElm;
|
||||
};
|
||||
|
||||
helper.changeInputValueTo = function(value) {
|
||||
helper.inputElm.val(value);
|
||||
browserTrigger(helper.inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
|
||||
};
|
||||
|
||||
helper.changeGivenInputTo = function(inputElm, value) {
|
||||
inputElm.val(value);
|
||||
browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
|
||||
};
|
||||
|
||||
helper.dealoc = function() {
|
||||
dealoc(helper.inputElm);
|
||||
dealoc(helper.formElm);
|
||||
};
|
||||
});
|
||||
|
||||
return helper;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* global getHash:true, stripHash:true */
|
||||
|
||||
var historyEntriesLength;
|
||||
var sniffer = {};
|
||||
|
||||
@@ -51,6 +53,12 @@ function MockWindow(options) {
|
||||
mockWindow.history.state = null;
|
||||
historyEntriesLength++;
|
||||
},
|
||||
get hash() {
|
||||
return getHash(locationHref);
|
||||
},
|
||||
set hash(value) {
|
||||
locationHref = stripHash(locationHref) + '#' + value;
|
||||
},
|
||||
replace: function(url) {
|
||||
locationHref = url;
|
||||
mockWindow.history.state = null;
|
||||
@@ -550,6 +558,17 @@ describe('browser', function() {
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should retain the # character when the only change is clearing the hash fragment, to prevent page reload", function() {
|
||||
sniffer.history = true;
|
||||
|
||||
browser.url('http://server/#123');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/#123');
|
||||
|
||||
browser.url('http://server/');
|
||||
expect(fakeWindow.location.href).toEqual('http://server/#');
|
||||
|
||||
});
|
||||
|
||||
it('should use location.replace when history.replaceState not available', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://new.org', true);
|
||||
@@ -561,6 +580,7 @@ describe('browser', function() {
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
|
||||
it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://server/#123', true);
|
||||
@@ -572,6 +592,7 @@ describe('browser', function() {
|
||||
expect(fakeWindow.location.href).toEqual('http://server/');
|
||||
});
|
||||
|
||||
|
||||
it('should return $browser to allow chaining', function() {
|
||||
expect(browser.url('http://any.com')).toBe(browser);
|
||||
});
|
||||
|
||||
+36
-2
@@ -6542,16 +6542,21 @@ describe('$compile', function() {
|
||||
expect(element.attr('href')).toBe('test/test');
|
||||
}));
|
||||
|
||||
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
|
||||
it('should work if they are prefixed with x- or data- and different prefixes', inject(function($compile, $rootScope) {
|
||||
$rootScope.name = "Misko";
|
||||
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}"></span>')($rootScope);
|
||||
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}" ' +
|
||||
'x_ng-attr-test5="{{name}}" data:ng-attr-test6="{{name}}"></span>')($rootScope);
|
||||
expect(element.attr('test2')).toBeUndefined();
|
||||
expect(element.attr('test3')).toBeUndefined();
|
||||
expect(element.attr('test4')).toBeUndefined();
|
||||
expect(element.attr('test5')).toBeUndefined();
|
||||
expect(element.attr('test6')).toBeUndefined();
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('test2')).toBe('Misko');
|
||||
expect(element.attr('test3')).toBe('Misko');
|
||||
expect(element.attr('test4')).toBe('Misko');
|
||||
expect(element.attr('test5')).toBe('Misko');
|
||||
expect(element.attr('test6')).toBe('Misko');
|
||||
}));
|
||||
|
||||
describe('when an attribute has a dash-separated name', function() {
|
||||
@@ -6619,6 +6624,35 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
|
||||
describe('when an attribute has an underscore-separated name', function() {
|
||||
|
||||
it('should work with different prefixes', inject(function($compile, $rootScope) {
|
||||
$rootScope.dimensions = "0 0 0 0";
|
||||
element = $compile('<svg ng:attr:view_box="{{dimensions}}"></svg>')($rootScope);
|
||||
expect(element.attr('viewBox')).toBeUndefined();
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('viewBox')).toBe('0 0 0 0');
|
||||
}));
|
||||
|
||||
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
|
||||
$rootScope.dimensions = "0 0 0 0";
|
||||
$rootScope.number = 0.42;
|
||||
$rootScope.scale = 1;
|
||||
element = $compile('<svg data-ng-attr-view_box="{{dimensions}}">' +
|
||||
'<filter x-ng-attr-filter_units="{{number}}">' +
|
||||
'<feDiffuseLighting data-ng:attr_surface_scale="{{scale}}">' +
|
||||
'</feDiffuseLighting>' +
|
||||
'<feSpecularLighting x-ng:attr_surface_scale="{{scale}}">' +
|
||||
'</feSpecularLighting></filter></svg>')($rootScope);
|
||||
expect(element.attr('viewBox')).toBeUndefined();
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('viewBox')).toBe('0 0 0 0');
|
||||
expect(element.find('filter').attr('filterUnits')).toBe('0.42');
|
||||
expect(element.find('feDiffuseLighting').attr('surfaceScale')).toBe('1');
|
||||
expect(element.find('feSpecularLighting').attr('surfaceScale')).toBe('1');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('multi-element directive', function() {
|
||||
it('should group on link function', inject(function($compile, $rootScope) {
|
||||
$rootScope.show = false;
|
||||
|
||||
+1130
-3510
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
/* globals getInputCompileHelper: false */
|
||||
|
||||
describe('ngChange', function() {
|
||||
|
||||
var helper, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
helper = getInputCompileHelper(this);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.dealoc();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should $eval expression after new value is set in the model', function() {
|
||||
helper.compileInput('<input type="text" ng-model="value" ng-change="change()" />');
|
||||
|
||||
$rootScope.change = jasmine.createSpy('change').andCallFake(function() {
|
||||
expect($rootScope.value).toBe('new value');
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('new value');
|
||||
expect($rootScope.change).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should not $eval the expression if changed from model', function() {
|
||||
helper.compileInput('<input type="text" ng-model="value" ng-change="change()" />');
|
||||
|
||||
$rootScope.change = jasmine.createSpy('change');
|
||||
$rootScope.$apply('value = true');
|
||||
|
||||
expect($rootScope.change).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should $eval ngChange expression on checkbox', function() {
|
||||
var inputElm = helper.compileInput('<input type="checkbox" ng-model="foo" ng-change="changeFn()">');
|
||||
|
||||
$rootScope.changeFn = jasmine.createSpy('changeFn');
|
||||
expect($rootScope.changeFn).not.toHaveBeenCalled();
|
||||
|
||||
browserTrigger(inputElm, 'click');
|
||||
expect($rootScope.changeFn).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should be able to change the model and via that also update the view', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-change="value=\'b\'" />');
|
||||
|
||||
helper.changeInputValueTo('a');
|
||||
expect(inputElm.val()).toBe('b');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
|
||||
/* globals getInputCompileHelper: false */
|
||||
|
||||
describe('ngList', function() {
|
||||
|
||||
var helper, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
helper = getInputCompileHelper(this);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.dealoc();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should parse text into an array', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list />');
|
||||
|
||||
// model -> view
|
||||
$rootScope.$apply("list = ['x', 'y', 'z']");
|
||||
expect(inputElm.val()).toBe('x, y, z');
|
||||
|
||||
// view -> model
|
||||
helper.changeInputValueTo('1, 2, 3');
|
||||
expect($rootScope.list).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
|
||||
it("should not clobber text if model changes due to itself", function() {
|
||||
// When the user types 'a,b' the 'a,' stage parses to ['a'] but if the
|
||||
// $parseModel function runs it will change to 'a', in essence preventing
|
||||
// the user from ever typing ','.
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list />');
|
||||
|
||||
helper.changeInputValueTo('a ');
|
||||
expect(inputElm.val()).toEqual('a ');
|
||||
expect($rootScope.list).toEqual(['a']);
|
||||
|
||||
helper.changeInputValueTo('a ,');
|
||||
expect(inputElm.val()).toEqual('a ,');
|
||||
expect($rootScope.list).toEqual(['a']);
|
||||
|
||||
helper.changeInputValueTo('a , ');
|
||||
expect(inputElm.val()).toEqual('a , ');
|
||||
expect($rootScope.list).toEqual(['a']);
|
||||
|
||||
helper.changeInputValueTo('a , b');
|
||||
expect(inputElm.val()).toEqual('a , b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
|
||||
it('should convert empty string to an empty array', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list />');
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect($rootScope.list).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should be invalid if required and empty', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-list ng-model="list" required>');
|
||||
helper.changeInputValueTo('');
|
||||
expect($rootScope.list).toBeUndefined();
|
||||
expect(inputElm).toBeInvalid();
|
||||
helper.changeInputValueTo('a,b');
|
||||
expect($rootScope.list).toEqual(['a','b']);
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
describe('with a custom separator', function() {
|
||||
it('should split on the custom separator', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list=":" />');
|
||||
|
||||
helper.changeInputValueTo('a,a');
|
||||
expect($rootScope.list).toEqual(['a,a']);
|
||||
|
||||
helper.changeInputValueTo('a:b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
|
||||
it("should join the list back together with the custom separator", function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list=" : " />');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.list = ['x', 'y', 'z'];
|
||||
});
|
||||
expect(inputElm.val()).toBe('x : y : z');
|
||||
});
|
||||
});
|
||||
|
||||
describe('(with ngTrim undefined or true)', function() {
|
||||
|
||||
it('should ignore separator whitespace when splitting', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list=" | " />');
|
||||
|
||||
helper.changeInputValueTo('a|b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('should trim whitespace from each list item', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-list="|" />');
|
||||
|
||||
helper.changeInputValueTo('a | b');
|
||||
expect($rootScope.list).toEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(with ngTrim set to false)', function() {
|
||||
|
||||
it('should use separator whitespace when splitting', function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list=" | " />');
|
||||
|
||||
helper.changeInputValueTo('a|b');
|
||||
expect($rootScope.list).toEqual(['a|b']);
|
||||
|
||||
helper.changeInputValueTo('a | b');
|
||||
expect($rootScope.list).toEqual(['a','b']);
|
||||
|
||||
});
|
||||
|
||||
it("should not trim whitespace from each list item", function() {
|
||||
helper.compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list="|" />');
|
||||
helper.changeInputValueTo('a | b');
|
||||
expect($rootScope.list).toEqual(['a ',' b']);
|
||||
});
|
||||
|
||||
it("should support splitting on newlines", function() {
|
||||
helper.compileInput('<textarea type="text" ng-model="list" ng-trim="false" ng-list=" "></textarea');
|
||||
helper.changeInputValueTo('a\nb');
|
||||
expect($rootScope.list).toEqual(['a','b']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -146,6 +146,16 @@ describe('ngRepeat', function() {
|
||||
expect(element.text()).toEqual('misko:swe|shyam:set|');
|
||||
});
|
||||
|
||||
it('should iterate over on object/map where (key,value) contains whitespaces', function() {
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
'<li ng-repeat="( key , value ) in items">{{key}}:{{value}}|</li>' +
|
||||
'</ul>')(scope);
|
||||
scope.items = {me:'swe', you:'set'};
|
||||
scope.$digest();
|
||||
expect(element.text()).toEqual('me:swe|you:set|');
|
||||
});
|
||||
|
||||
it('should iterate over an object/map with identical values', function() {
|
||||
element = $compile(
|
||||
'<ul>' +
|
||||
|
||||
@@ -40,6 +40,23 @@ describe('select', function() {
|
||||
return equals(expectedValues, actualValues);
|
||||
},
|
||||
|
||||
toEqualSelectWithOptions: function(expected) {
|
||||
var actualValues = {};
|
||||
var optionGroup;
|
||||
|
||||
forEach(this.actual.find('option'), function(option) {
|
||||
optionGroup = option.parentNode.label || '';
|
||||
actualValues[optionGroup] = actualValues[optionGroup] || [];
|
||||
actualValues[optionGroup].push(option.label);
|
||||
});
|
||||
|
||||
this.message = function() {
|
||||
return 'Expected ' + toJson(actualValues) + ' to equal ' + toJson(expected) + '.';
|
||||
};
|
||||
|
||||
return equals(expected, actualValues);
|
||||
},
|
||||
|
||||
toEqualOption: function(value, text, label) {
|
||||
var errors = [];
|
||||
if (this.actual.attr('value') !== value) {
|
||||
@@ -233,6 +250,31 @@ describe('select', function() {
|
||||
expect(scope.robot).toBe('');
|
||||
});
|
||||
|
||||
it('should not be set when an option is selected and options are set asynchronously',
|
||||
inject(function($timeout) {
|
||||
compile('<select ng-model="model" ng-options="opt.id as opt.label for opt in options">' +
|
||||
'</select>');
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.model = 0;
|
||||
});
|
||||
|
||||
$timeout(function() {
|
||||
scope.options = [
|
||||
{id: 0, label: 'x'},
|
||||
{id: 1, label: 'y'}
|
||||
];
|
||||
}, 0);
|
||||
|
||||
$timeout.flush();
|
||||
|
||||
var options = element.find('option');
|
||||
|
||||
expect(options.length).toEqual(2);
|
||||
expect(options.eq(0)).toEqualOption('0', 'x');
|
||||
expect(options.eq(1)).toEqualOption('1', 'y');
|
||||
})
|
||||
);
|
||||
|
||||
describe('interactions with repeated options', function() {
|
||||
|
||||
@@ -447,6 +489,7 @@ describe('select', function() {
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should return false for options popped via ngOptions', function() {
|
||||
scope.robots = [
|
||||
{value: 1, label: 'c3p0'},
|
||||
@@ -467,6 +510,7 @@ describe('select', function() {
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
it('should return true for options added via ngOptions', function() {
|
||||
scope.robots = [
|
||||
{value: 2, label: 'r2d2'}
|
||||
@@ -485,6 +529,169 @@ describe('select', function() {
|
||||
expect(selectCtrl.hasOption('c3p0')).toBe(true);
|
||||
expect(selectCtrl.hasOption('r2d2')).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should keep all the options when changing the model', function() {
|
||||
compile('<select ng-model="mySelect" ng-options="o for o in [\'A\',\'B\',\'C\']"></select>');
|
||||
var selectCtrl = element.controller('select');
|
||||
scope.$apply(function() {
|
||||
scope.mySelect = 'C';
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(true);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(true);
|
||||
expect(element).toEqualSelectWithOptions({'': ['A', 'B', 'C']});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to detect when elements move from a previous group', function() {
|
||||
scope.values = [
|
||||
{name: 'A'},
|
||||
{name: 'B', group: 'first'},
|
||||
{name: 'C', group: 'first'},
|
||||
{name: 'D', group: 'first'},
|
||||
{name: 'E', group: 'second'}
|
||||
];
|
||||
|
||||
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values[3] = {name: 'D', group: 'second'};
|
||||
scope.values.shift();
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(false);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(true);
|
||||
expect(selectCtrl.hasOption('D')).toBe(true);
|
||||
expect(selectCtrl.hasOption('E')).toBe(true);
|
||||
expect(element).toEqualSelectWithOptions({'': [''], 'first':['B', 'C'], 'second': ['D', 'E']});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to detect when elements move from a following group', function() {
|
||||
scope.values = [
|
||||
{name: 'A'},
|
||||
{name: 'B', group: 'first'},
|
||||
{name: 'C', group: 'first'},
|
||||
{name: 'D', group: 'second'},
|
||||
{name: 'E', group: 'second'}
|
||||
];
|
||||
|
||||
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values[3].group = 'first';
|
||||
scope.values.shift();
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(false);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(true);
|
||||
expect(selectCtrl.hasOption('D')).toBe(true);
|
||||
expect(selectCtrl.hasOption('E')).toBe(true);
|
||||
expect(element).toEqualSelectWithOptions({'': [''], 'first':['B', 'C', 'D'], 'second': ['E']});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to detect when an element is replaced with an element from a previous group', function() {
|
||||
scope.values = [
|
||||
{name: 'A'},
|
||||
{name: 'B', group: 'first'},
|
||||
{name: 'C', group: 'first'},
|
||||
{name: 'D', group: 'first'},
|
||||
{name: 'E', group: 'second'},
|
||||
{name: 'F', group: 'second'}
|
||||
];
|
||||
|
||||
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values[3].group = 'second';
|
||||
scope.values.pop();
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(true);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(true);
|
||||
expect(selectCtrl.hasOption('D')).toBe(true);
|
||||
expect(selectCtrl.hasOption('E')).toBe(true);
|
||||
expect(selectCtrl.hasOption('F')).toBe(false);
|
||||
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'C'], 'second': ['D', 'E']});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to detect when element is replaced with an element from a following group', function() {
|
||||
scope.values = [
|
||||
{name: 'A'},
|
||||
{name: 'B', group: 'first'},
|
||||
{name: 'C', group: 'first'},
|
||||
{name: 'D', group: 'second'},
|
||||
{name: 'E', group: 'second'}
|
||||
];
|
||||
|
||||
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values[3].group = 'first';
|
||||
scope.values.splice(2, 1);
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(true);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(false);
|
||||
expect(selectCtrl.hasOption('D')).toBe(true);
|
||||
expect(selectCtrl.hasOption('E')).toBe(true);
|
||||
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'D'], 'second': ['E']});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to detect when an element is removed', function() {
|
||||
scope.values = [
|
||||
{name: 'A'},
|
||||
{name: 'B', group: 'first'},
|
||||
{name: 'C', group: 'first'},
|
||||
{name: 'D', group: 'second'},
|
||||
{name: 'E', group: 'second'}
|
||||
];
|
||||
|
||||
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.splice(3, 1);
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(true);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(true);
|
||||
expect(selectCtrl.hasOption('D')).toBe(false);
|
||||
expect(selectCtrl.hasOption('E')).toBe(true);
|
||||
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'C'], 'second': ['E']});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to detect when a group is removed', function() {
|
||||
scope.values = [
|
||||
{name: 'A'},
|
||||
{name: 'B', group: 'first'},
|
||||
{name: 'C', group: 'first'},
|
||||
{name: 'D', group: 'second'},
|
||||
{name: 'E', group: 'second'}
|
||||
];
|
||||
|
||||
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
|
||||
var selectCtrl = element.data().$selectController;
|
||||
|
||||
scope.$apply(function() {
|
||||
scope.values.splice(3, 2);
|
||||
});
|
||||
expect(selectCtrl.hasOption('A')).toBe(true);
|
||||
expect(selectCtrl.hasOption('B')).toBe(true);
|
||||
expect(selectCtrl.hasOption('C')).toBe(true);
|
||||
expect(selectCtrl.hasOption('D')).toBe(false);
|
||||
expect(selectCtrl.hasOption('E')).toBe(false);
|
||||
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'C']});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
'use strict';
|
||||
|
||||
/* globals getInputCompileHelper: false */
|
||||
|
||||
describe('validators', function() {
|
||||
|
||||
var helper, $rootScope;
|
||||
|
||||
beforeEach(function() {
|
||||
helper = getInputCompileHelper(this);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
helper.dealoc();
|
||||
});
|
||||
|
||||
|
||||
beforeEach(inject(function(_$rootScope_) {
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
describe('pattern', function() {
|
||||
|
||||
it('should validate in-lined pattern', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^\\d\\d\\d-\\d\\d-\\d\\d\\d\\d$/" />');
|
||||
|
||||
helper.changeInputValueTo('x000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('123-45-6789');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should listen on ng-pattern when pattern is observed', function() {
|
||||
var value, patternVal = /^\w+$/;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="pat" attr-capture />');
|
||||
helper.attrs.$observe('pattern', function(v) {
|
||||
value = helper.attrs.pattern;
|
||||
});
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.pat = patternVal;
|
||||
});
|
||||
|
||||
expect(value).toBe(patternVal);
|
||||
});
|
||||
|
||||
|
||||
it('should validate in-lined pattern with modifiers', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^abc?$/i" />');
|
||||
|
||||
helper.changeInputValueTo('aB');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('xx');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should validate pattern from scope', function() {
|
||||
$rootScope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
|
||||
|
||||
helper.changeInputValueTo('x000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('000-00-0000x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('123-45-6789');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('x');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.regexp = /abc?/;
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('ab');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('xx');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should perform validations when the ngPattern scope value changes', function() {
|
||||
$rootScope.regexp = /^[a-z]+$/;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
|
||||
|
||||
helper.changeInputValueTo('abcdef');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.regexp = /^\d+$/;
|
||||
});
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('abcdef');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.regexp = '';
|
||||
});
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should register "pattern" with the model validations when the pattern attribute is used', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" pattern="^\\d+$" />');
|
||||
|
||||
helper.changeInputValueTo('abcd');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.pattern).toBe(true);
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.input.$error.pattern).not.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should not throw an error when scope pattern can\'t be found', function() {
|
||||
expect(function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
|
||||
$rootScope.$apply("foo = 'bar'");
|
||||
}).not.toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
|
||||
});
|
||||
|
||||
|
||||
it('should throw an error when the scope pattern is not a regular expression', function() {
|
||||
expect(function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.fooRegexp = {};
|
||||
$rootScope.foo = 'bar';
|
||||
});
|
||||
}).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
|
||||
});
|
||||
|
||||
|
||||
it('should be invalid if entire string does not match pattern', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should be cope with patterns that start with ^', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="^\\d{4}">');
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should be cope with patterns that end with $', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}$">');
|
||||
helper.changeInputValueTo('1234');
|
||||
expect($rootScope.form.test.$error.pattern).not.toBe(true);
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('123');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.form.test.$error.pattern).toBe(true);
|
||||
expect(inputElm).not.toBeValid();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('minlength', function() {
|
||||
|
||||
it('should invalidate values that are shorter than the given minlength', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="3" />');
|
||||
|
||||
helper.changeInputValueTo('aa');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('aaa');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should listen on ng-minlength when minlength is observed', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="min" attr-capture />');
|
||||
helper.attrs.$observe('minlength', function(v) {
|
||||
value = int(helper.attrs.minlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply('min = 5');
|
||||
|
||||
expect(value).toBe(5);
|
||||
});
|
||||
|
||||
|
||||
it('should observe the standard minlength attribute and register it as a validator on the model', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="{{ min }}" />');
|
||||
$rootScope.$apply('min = 10');
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.minlength).toBe(true);
|
||||
|
||||
$rootScope.$apply('min = 5');
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.input.$error.minlength).not.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should validate when the model is initalized as a number', function() {
|
||||
$rootScope.value = 12345;
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
|
||||
expect($rootScope.value).toBe(12345);
|
||||
expect($rootScope.form.input.$error.minlength).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('maxlength', function() {
|
||||
|
||||
it('should invalidate values that are longer than the given maxlength', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="5" />');
|
||||
|
||||
helper.changeInputValueTo('aaaaaaaa');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
helper.changeInputValueTo('aaa');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should only accept empty values when maxlength is 0', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="0" />');
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('a');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should accept values of any length when maxlength is negative', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="-1" />');
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('aaaaaaaaaa');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should accept values of any length when maxlength is non-numeric', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="{{maxlength}}" />');
|
||||
helper.changeInputValueTo('aaaaaaaaaa');
|
||||
|
||||
$rootScope.$apply('maxlength = "5"');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply('maxlength = "abc"');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.$apply('maxlength = ""');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.$apply('maxlength = null');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.someObj = {};
|
||||
$rootScope.$apply('maxlength = someObj');
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should listen on ng-maxlength when maxlength is observed', function() {
|
||||
var value = 0;
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="max" attr-capture />');
|
||||
helper.attrs.$observe('maxlength', function(v) {
|
||||
value = int(helper.attrs.maxlength);
|
||||
});
|
||||
|
||||
$rootScope.$apply('max = 10');
|
||||
|
||||
expect(value).toBe(10);
|
||||
});
|
||||
|
||||
|
||||
it('should observe the standard maxlength attribute and register it as a validator on the model', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
$rootScope.$apply('max = 1');
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.maxlength).toBe(true);
|
||||
|
||||
$rootScope.$apply('max = 6');
|
||||
|
||||
expect(inputElm).toBeValid();
|
||||
expect($rootScope.form.input.$error.maxlength).not.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
it('should assign the correct model after an observed validator became valid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
|
||||
$rootScope.$apply('max = 1');
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
|
||||
$rootScope.$apply('max = 6');
|
||||
expect($rootScope.value).toBe('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should assign the correct model after an observed validator became invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
|
||||
$rootScope.$apply('max = 6');
|
||||
helper.changeInputValueTo('12345');
|
||||
expect($rootScope.value).toBe('12345');
|
||||
|
||||
$rootScope.$apply('max = 1');
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
|
||||
$rootScope.$apply('max = 1');
|
||||
|
||||
helper.changeInputValueTo('12345');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.maxlength).toBe(true);
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
|
||||
$rootScope.$apply('max = 3');
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect($rootScope.form.input.$error.maxlength).toBe(true);
|
||||
expect($rootScope.value).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should not notify if observed maxlength changed, but is still invalid', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
|
||||
'maxlength="{{ max }}" />');
|
||||
|
||||
$rootScope.$apply('max = 1');
|
||||
helper.changeInputValueTo('12345');
|
||||
|
||||
$rootScope.ngChangeSpy = jasmine.createSpy();
|
||||
$rootScope.$apply('max = 3');
|
||||
|
||||
expect($rootScope.ngChangeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should leave the model untouched when validating before model initialization', function() {
|
||||
$rootScope.value = '12345';
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
|
||||
expect($rootScope.value).toBe('12345');
|
||||
});
|
||||
|
||||
|
||||
it('should validate when the model is initalized as a number', function() {
|
||||
$rootScope.value = 12345;
|
||||
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
|
||||
expect($rootScope.value).toBe(12345);
|
||||
expect($rootScope.form.input.$error.maxlength).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('required', function() {
|
||||
|
||||
it('should allow bindings via ngRequired', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-required="required" />');
|
||||
|
||||
$rootScope.$apply("required = false");
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
|
||||
$rootScope.$apply("required = true");
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply("value = 'some'");
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply("required = false");
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
|
||||
|
||||
it('should invalid initial value with bound required', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" required="{{required}}" />');
|
||||
|
||||
$rootScope.$apply('required = true');
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should be $invalid but $pristine if not touched', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="name" name="alias" required />');
|
||||
|
||||
$rootScope.$apply("name = null");
|
||||
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm).toBePristine();
|
||||
|
||||
helper.changeInputValueTo('');
|
||||
expect(inputElm).toBeInvalid();
|
||||
expect(inputElm).toBeDirty();
|
||||
});
|
||||
|
||||
|
||||
it('should allow empty string if not required', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="foo" />');
|
||||
helper.changeInputValueTo('a');
|
||||
helper.changeInputValueTo('');
|
||||
expect($rootScope.foo).toBe('');
|
||||
});
|
||||
|
||||
|
||||
it('should set $invalid when model undefined', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="notDefined" required />');
|
||||
expect(inputElm).toBeInvalid();
|
||||
});
|
||||
|
||||
|
||||
it('should consider bad input as an error before any other errors are considered', function() {
|
||||
var inputElm = helper.compileInput('<input type="text" ng-model="value" required />', { badInput: true });
|
||||
var ctrl = inputElm.controller('ngModel');
|
||||
ctrl.$parsers.push(function() {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
helper.changeInputValueTo('abc123');
|
||||
|
||||
expect(ctrl.$error.parse).toBe(true);
|
||||
expect(inputElm).toHaveClass('ng-invalid-parse');
|
||||
expect(inputElm).toBeInvalid(); // invalid because of the number validator
|
||||
});
|
||||
|
||||
|
||||
it('should allow `false` as a valid value when the input type is not "checkbox"', function() {
|
||||
var inputElm = helper.compileInput('<input type="radio" ng-value="true" ng-model="answer" required />' +
|
||||
'<input type="radio" ng-value="false" ng-model="answer" required />');
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(inputElm).toBeInvalid();
|
||||
|
||||
$rootScope.$apply("answer = true");
|
||||
expect(inputElm).toBeValid();
|
||||
|
||||
$rootScope.$apply("answer = false");
|
||||
expect(inputElm).toBeValid();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -65,6 +65,66 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should match primitive array values against top-level `$` property in object expression',
|
||||
function() {
|
||||
var items, expr;
|
||||
|
||||
items = ['something', 'something else', 'another thing'];
|
||||
expr = {$: 'some'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
|
||||
items = [{val: 'something'}, {val: 'something else'}, {val: 'another thing'}];
|
||||
expr = {$: 'some'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
|
||||
items = [123, 456, 789];
|
||||
expr = {$: 1};
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[0]]);
|
||||
|
||||
items = [true, false, 'true'];
|
||||
expr = {$: true, ignored: 'false'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[2]]);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should match items with array properties containing one or more matching items', function() {
|
||||
var items, expr;
|
||||
|
||||
items = [
|
||||
{tags: ['web', 'html', 'css', 'js']},
|
||||
{tags: ['hybrid', 'html', 'css', 'js', 'ios', 'android']},
|
||||
{tags: ['mobile', 'ios', 'android']}
|
||||
];
|
||||
expr = {tags: 'html'};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
|
||||
items = [
|
||||
{nums: [1, 345, 12]},
|
||||
{nums: [0, 46, 78]},
|
||||
{nums: [123, 4, 67]}
|
||||
];
|
||||
expr = {nums: 12};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[2]]);
|
||||
|
||||
items = [
|
||||
{customers: [{name: 'John'}, {name: 'Elena'}, {name: 'Bill'}]},
|
||||
{customers: [{name: 'Sam'}, {name: 'Klara'}, {name: 'Bill'}]},
|
||||
{customers: [{name: 'Molli'}, {name: 'Elena'}, {name: 'Lora'}]}
|
||||
];
|
||||
expr = {customers: {name: 'Bill'}};
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[1]]);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
it('should take object as predicate', function() {
|
||||
var items = [{first: 'misko', last: 'hevery'},
|
||||
{first: 'adam', last: 'abrons'}];
|
||||
@@ -98,6 +158,19 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support deep expression objects with multiple properties', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{person: {name: 'Joan', email: 'joan@example.net'}},
|
||||
{person: {name: 'John', email: 'john@example.com'}},
|
||||
{person: {name: 'Rita', email: 'rita@example.com'}}];
|
||||
var expr = {person: {name: 'Jo', email: '!example.com'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[2]]);
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties for given "$" property', function() {
|
||||
var items = [{first: 'tom', last: 'hevery'},
|
||||
{first: 'adam', last: 'hevery', alias: 'tom', done: false},
|
||||
@@ -110,6 +183,59 @@ describe('Filter: filter', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties in the nested object for given deep "$" property', function() {
|
||||
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{person: {name: 'Joan', email: 'joan@example.net'}},
|
||||
{person: {name: 'John', email: 'john@example.com'}},
|
||||
{person: {name: 'Rita', email: 'rita@example.com'}}];
|
||||
var expr = {person: {$: 'net'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(2);
|
||||
expect(filter(items, expr)).toEqual([items[0], items[2]]);
|
||||
});
|
||||
|
||||
|
||||
it('should match named properties only against named properties on the same level', function() {
|
||||
var expr = {person: {name: 'John'}};
|
||||
var items = [{person: 'John'}, // No match (1 level higher)
|
||||
{person: {name: 'John'}}, // Match (same level)
|
||||
{person: {name: {first: 'John', last: 'Doe'}}}]; // No match (1 level deeper)
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[1]]);
|
||||
});
|
||||
|
||||
|
||||
it('should match any properties on same or deeper level for given "$" property', function() {
|
||||
var items = [{level1: 'test', foo1: 'bar1'},
|
||||
{level1: {level2: 'test', foo2:'bar2'}, foo1: 'bar1'},
|
||||
{level1: {level2: {level3: 'test', foo3: 'bar3'}, foo2: 'bar2'}, foo1: 'bar1'}];
|
||||
|
||||
expect(filter(items, {$: 'ES'}).length).toBe(3);
|
||||
expect(filter(items, {$: 'ES'})).toEqual([items[0], items[1], items[2]]);
|
||||
|
||||
expect(filter(items, {level1: {$: 'ES'}}).length).toBe(2);
|
||||
expect(filter(items, {level1: {$: 'ES'}})).toEqual([items[1], items[2]]);
|
||||
|
||||
expect(filter(items, {level1: {level2: {$: 'ES'}}}).length).toBe(1);
|
||||
expect(filter(items, {level1: {level2: {$: 'ES'}}})).toEqual([items[2]]);
|
||||
});
|
||||
|
||||
|
||||
it('should respect the nesting level of "$"', function() {
|
||||
var items = [{supervisor: 'me', person: {name: 'Annet', email: 'annet@example.com'}},
|
||||
{supervisor: 'me', person: {name: 'Billy', email: 'me@billy.com'}},
|
||||
{supervisor: 'me', person: {name: 'Joan', email: 'joan@example.net'}},
|
||||
{supervisor: 'me', person: {name: 'John', email: 'john@example.com'}},
|
||||
{supervisor: 'me', person: {name: 'Rita', email: 'rita@example.com'}}];
|
||||
var expr = {$: {$: 'me'}};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)).toEqual([items[1]]);
|
||||
});
|
||||
|
||||
|
||||
it('should support boolean properties', function() {
|
||||
var items = [{name: 'tom', current: true},
|
||||
{name: 'demi', current: false},
|
||||
@@ -129,8 +255,159 @@ describe('Filter: filter', function() {
|
||||
expect(filter(items, '!isk')[0]).toEqual(items[1]);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore function properties in items', function() {
|
||||
// Own function properties
|
||||
var items = [
|
||||
{text: 'hello', func: noop},
|
||||
{text: 'goodbye'},
|
||||
{text: 'kittens'},
|
||||
{text: 'puppies'}
|
||||
];
|
||||
var expr = {text: 'hello'};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
|
||||
// Inherited function proprties
|
||||
function Item(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Item.prototype.func = noop;
|
||||
|
||||
items = [
|
||||
new Item('hello'),
|
||||
new Item('goodbye'),
|
||||
new Item('kittens'),
|
||||
new Item('puppies')
|
||||
];
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore function properties in expression', function() {
|
||||
// Own function properties
|
||||
var items = [
|
||||
{text: 'hello'},
|
||||
{text: 'goodbye'},
|
||||
{text: 'kittens'},
|
||||
{text: 'puppies'}
|
||||
];
|
||||
var expr = {text: 'hello', func: noop};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
|
||||
// Inherited function proprties
|
||||
function Expr(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Expr.prototype.func = noop;
|
||||
|
||||
expr = new Expr('hello');
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should consider inherited properties in items', function() {
|
||||
function Item(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Item.prototype.doubleL = 'maybe';
|
||||
|
||||
var items = [
|
||||
new Item('hello'),
|
||||
new Item('goodbye'),
|
||||
new Item('kittens'),
|
||||
new Item('puppies')
|
||||
];
|
||||
var expr = {text: 'hello', doubleL: 'perhaps'};
|
||||
|
||||
expect(filter(items, expr).length).toBe(0);
|
||||
expect(filter(items, expr, true).length).toBe(0);
|
||||
|
||||
expr = {text: 'hello', doubleL: 'maybe'};
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr, true)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should consider inherited properties in expression', function() {
|
||||
function Expr(text) {
|
||||
this.text = text;
|
||||
}
|
||||
Expr.prototype.doubleL = true;
|
||||
|
||||
var items = [
|
||||
{text: 'hello', doubleL: true},
|
||||
{text: 'goodbye'},
|
||||
{text: 'kittens'},
|
||||
{text: 'puppies'}
|
||||
];
|
||||
var expr = new Expr('e');
|
||||
|
||||
expect(filter(items, expr).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
|
||||
expr = new Expr('hello');
|
||||
|
||||
expect(filter(items, expr, true).length).toBe(1);
|
||||
expect(filter(items, expr)[0]).toBe(items[0]);
|
||||
});
|
||||
|
||||
|
||||
it('should not be affected by `Object.prototype` when using a string expression', function() {
|
||||
Object.prototype.someProp = 'oo';
|
||||
|
||||
var items = [
|
||||
createMap(),
|
||||
createMap(),
|
||||
createMap(),
|
||||
createMap()
|
||||
];
|
||||
items[0].someProp = 'hello';
|
||||
items[1].someProp = 'goodbye';
|
||||
items[2].someProp = 'kittens';
|
||||
items[3].someProp = 'puppies';
|
||||
|
||||
// Affected by `Object.prototype`
|
||||
expect(filter(items, {}).length).toBe(1);
|
||||
expect(filter(items, {})[0]).toBe(items[1]);
|
||||
|
||||
expect(filter(items, {$: 'll'}).length).toBe(0);
|
||||
|
||||
// Not affected by `Object.prototype`
|
||||
expect(filter(items, 'll').length).toBe(1);
|
||||
expect(filter(items, 'll')[0]).toBe(items[0]);
|
||||
|
||||
delete Object.prototype.someProp;
|
||||
});
|
||||
|
||||
|
||||
describe('should support comparator', function() {
|
||||
|
||||
it('not consider `object === "[object Object]"` in non-strict comparison', function() {
|
||||
var items = [{test: {}}];
|
||||
var expr = '[object';
|
||||
expect(filter(items, expr).length).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('as equality when true', function() {
|
||||
var items = ['misko', 'adam', 'adamson'];
|
||||
var expr = 'adam';
|
||||
@@ -177,5 +454,46 @@ describe('Filter: filter', function() {
|
||||
expr = 10;
|
||||
expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]);
|
||||
});
|
||||
|
||||
|
||||
it('and use it correctly with deep expression objects', function() {
|
||||
var items = [
|
||||
{id: 0, details: {email: 'admin@example.com', role: 'admin'}},
|
||||
{id: 1, details: {email: 'user1@example.com', role: 'user'}},
|
||||
{id: 2, details: {email: 'user2@example.com', role: 'user'}}
|
||||
];
|
||||
var expr, comp;
|
||||
|
||||
expr = {details: {email: 'user@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr)).toEqual([]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr)).toEqual([items[0]]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr, true)).toEqual([]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'admin'}};
|
||||
expect(filter(items, expr, true)).toEqual([items[0]]);
|
||||
|
||||
expr = {details: {email: 'user', role: 'us'}};
|
||||
expect(filter(items, expr)).toEqual([items[1], items[2]]);
|
||||
|
||||
expr = {id: 0, details: {email: 'user', role: 'us'}};
|
||||
expect(filter(items, expr)).toEqual([]);
|
||||
|
||||
expr = {id: 1, details: {email: 'user', role: 'us'}};
|
||||
expect(filter(items, expr)).toEqual([items[1]]);
|
||||
|
||||
comp = function(actual, expected) {
|
||||
return isString(actual) && isString(expected) && (actual.indexOf(expected) === 0);
|
||||
};
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'min'}};
|
||||
expect(filter(items, expr, comp)).toEqual([]);
|
||||
|
||||
expr = {details: {email: 'admin@example.com', role: 'adm'}};
|
||||
expect(filter(items, expr, comp)).toEqual([items[0]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,8 +85,10 @@ describe('filters', function() {
|
||||
});
|
||||
|
||||
it('should format numbers that round to zero as nonnegative', function() {
|
||||
var num = formatNumber(-0.01, pattern, ',', '.', 1);
|
||||
expect(num).toBe('0.0');
|
||||
expect(formatNumber(-0.01, pattern, ',', '.', 1)).toBe('0.0');
|
||||
expect(formatNumber(-1e-10, pattern, ',', '.', 1)).toBe('0.0');
|
||||
expect(formatNumber(-0.0001, pattern, ',', '.', 3)).toBe('0.000');
|
||||
expect(formatNumber(-0.0000001, pattern, ',', '.', 6)).toBe('0.000000');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,10 +197,12 @@ describe('filters', function() {
|
||||
expect(number(1e-50, 0)).toEqual('0');
|
||||
expect(number(1e-6, 6)).toEqual('0.000001');
|
||||
expect(number(1e-7, 6)).toEqual('0.000000');
|
||||
expect(number(9e-7, 6)).toEqual('0.000001');
|
||||
|
||||
expect(number(-1e-50, 0)).toEqual('0');
|
||||
expect(number(-1e-6, 6)).toEqual('-0.000001');
|
||||
expect(number(-1e-7, 6)).toEqual('-0.000000');
|
||||
expect(number(-1e-7, 6)).toEqual('0.000000');
|
||||
expect(number(-1e-8, 9)).toEqual('-0.000000010');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -206,6 +210,9 @@ describe('filters', function() {
|
||||
it('should do basic filter', function() {
|
||||
expect(filter('json')({a:"b"})).toEqual(toJson({a:"b"}, true));
|
||||
});
|
||||
it('should allow custom indentation', function() {
|
||||
expect(filter('json')({a:"b"}, 4)).toEqual(toJson({a:"b"}, 4));
|
||||
});
|
||||
});
|
||||
|
||||
describe('lowercase', function() {
|
||||
@@ -319,6 +326,36 @@ describe('filters', function() {
|
||||
toEqual('2010-09-03T06:35:08-0530');
|
||||
});
|
||||
|
||||
it('should correctly calculate week number', function() {
|
||||
function formatWeek(dateToFormat) {
|
||||
return date(new angular.mock.TzDate(+5, dateToFormat + 'T12:00:00.000Z'), 'ww (EEE)');
|
||||
}
|
||||
|
||||
expect(formatWeek('2007-01-01')).toEqual('01 (Mon)');
|
||||
expect(formatWeek('2007-12-31')).toEqual('53 (Mon)');
|
||||
|
||||
expect(formatWeek('2008-01-01')).toEqual('01 (Tue)');
|
||||
expect(formatWeek('2008-12-31')).toEqual('53 (Wed)');
|
||||
|
||||
expect(formatWeek('2014-01-01')).toEqual('01 (Wed)');
|
||||
expect(formatWeek('2014-12-31')).toEqual('53 (Wed)');
|
||||
|
||||
expect(formatWeek('2009-01-01')).toEqual('01 (Thu)');
|
||||
expect(formatWeek('2009-12-31')).toEqual('53 (Thu)');
|
||||
|
||||
expect(formatWeek('2010-01-01')).toEqual('00 (Fri)');
|
||||
expect(formatWeek('2010-12-31')).toEqual('52 (Fri)');
|
||||
|
||||
expect(formatWeek('2011-01-01')).toEqual('00 (Sat)');
|
||||
expect(formatWeek('2011-01-02')).toEqual('01 (Sun)');
|
||||
expect(formatWeek('2011-01-03')).toEqual('01 (Mon)');
|
||||
expect(formatWeek('2011-12-31')).toEqual('52 (Sat)');
|
||||
|
||||
expect(formatWeek('2012-01-01')).toEqual('01 (Sun)');
|
||||
expect(formatWeek('2012-01-02')).toEqual('01 (Mon)');
|
||||
expect(formatWeek('2012-12-31')).toEqual('53 (Mon)');
|
||||
});
|
||||
|
||||
it('should treat single quoted strings as string literals', function() {
|
||||
expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")).
|
||||
toEqual('2010de axdd adZ 12=0:5:8AM-0500');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user