Compare commits
357 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 996c901ff9 | |||
| 6234cda41c | |||
| f8103a82bf | |||
| 0d764b581d | |||
| 57a37fcc20 | |||
| 7f6ba5534f | |||
| b24bfae585 | |||
| d3e123b0a6 | |||
| bf1acf7b21 | |||
| fdaf4d5e27 | |||
| 1c47abc462 | |||
| 5222703444 | |||
| 4b38b44c91 | |||
| aa28e48e17 | |||
| f31586db41 | |||
| 8337b9b2d4 | |||
| 85a53ea9cd | |||
| 2d87ef8f23 | |||
| 3df8b637f3 | |||
| 28d3126fe5 | |||
| ad21f8feaf | |||
| 7f2df141cd | |||
| 4e735e5363 | |||
| a9db6073c9 | |||
| e48e27aa2b | |||
| 832b383cbc | |||
| c4bff290e5 | |||
| 55b00148dd | |||
| 23550b5e27 | |||
| 97d2a08c5a | |||
| 7f6efb2822 | |||
| b50867001b | |||
| 8b9ce885d3 | |||
| 56dae6fa1b | |||
| 611dcbc035 | |||
| 2c9066e012 | |||
| 8ecc9357ef | |||
| 71dca7c4c2 | |||
| ce77c25b06 | |||
| a1188721e1 | |||
| 1917ff86c4 | |||
| c60e1960c6 | |||
| 268e71eb8b | |||
| 03e6fb3df5 | |||
| 4fc9cf6289 | |||
| 8dee8f1b9e | |||
| 1eef631ab5 | |||
| 2ef92c329b | |||
| 1467e15bca | |||
| 389349edc3 | |||
| 722e97e8e6 | |||
| d3933a4181 | |||
| 8d02b07af4 | |||
| 24af9e2a6e | |||
| 4879e49c93 | |||
| aec25f1829 | |||
| f87e8288fb | |||
| 9b1beb8e09 | |||
| 681e6246e3 | |||
| 13e2ea73b0 | |||
| f5295ea448 | |||
| bb01b8bf89 | |||
| 711aba7727 | |||
| 88322c1af8 | |||
| 18f055eea5 | |||
| 24a7f28f1e | |||
| 146f9c1611 | |||
| e55829a1cd | |||
| 571e323f7d | |||
| bf11cf3095 | |||
| 5a1e148da6 | |||
| eec095a751 | |||
| 7b2c7cbab8 | |||
| b830f5b68e | |||
| 7e7a0693e5 | |||
| 02929f82f3 | |||
| b9d3625e92 | |||
| 863a4232a6 | |||
| 4a39ad475b | |||
| 696d65dcba | |||
| 947cb4d145 | |||
| 77fc41f499 | |||
| d98a12a6f2 | |||
| ef2c6e39be | |||
| c219a87ad7 | |||
| fad4dc07d7 | |||
| 373f054d5e | |||
| beb00e44de | |||
| 6a4403a118 | |||
| 77cdc37c65 | |||
| ab95ba65c0 | |||
| 86416bcbee | |||
| d04c38c489 | |||
| bdc5a1cde1 | |||
| eccd618d76 | |||
| 7f11af6b5e | |||
| 437ba49a52 | |||
| 9a576fa0fa | |||
| b5c317d672 | |||
| 05741ebf0b | |||
| 9e2c215779 | |||
| 8a1f600c29 | |||
| 8519b8a60f | |||
| 4bc3031497 | |||
| ab5c7698bb | |||
| 89690502d1 | |||
| f47e218006 | |||
| 8dc4c75ade | |||
| e91811095b | |||
| afb298b7ff | |||
| 571afd6558 | |||
| df6e731506 | |||
| af9c2d71e2 | |||
| dc158e7e40 | |||
| 0b7fff303f | |||
| 96d62cc0fc | |||
| 5cb7d0e046 | |||
| a60bbc12e8 | |||
| ca23d5f68f | |||
| a398773b0c | |||
| 1ef741563d | |||
| 2d44a681eb | |||
| 543af651d0 | |||
| e48666aeaf | |||
| 87f80379ea | |||
| 9cee054920 | |||
| e69f35507e | |||
| 379b8d9583 | |||
| bf79770706 | |||
| 7b0a865c97 | |||
| ce13cfd30a | |||
| c51fbcb7de | |||
| 796f7ab414 | |||
| 94d34beed4 | |||
| f476060de6 | |||
| 6a953bb0cb | |||
| 3092fd31bb | |||
| dae4a2e736 | |||
| 2632250a42 | |||
| fdbd92ff99 | |||
| 3b27dd37a2 | |||
| 76c6493c2b | |||
| da03497f6b | |||
| a1fd2239c9 | |||
| a39baef657 | |||
| 56508a1d5f | |||
| d7d8708a9b | |||
| 2ffbfb0ad0 | |||
| 946d9ae90b | |||
| a84393eadb | |||
| c429ad82f5 | |||
| 900c7cd923 | |||
| 2d3303ddda | |||
| a985adfdab | |||
| ca41996ef7 | |||
| 9a60408c80 | |||
| 09f6061a8e | |||
| 2d1ee4bffd | |||
| a412622f69 | |||
| 63ffe5c360 | |||
| 2fc954d33a | |||
| 512c081187 | |||
| 2563ff7ba9 | |||
| 0a641c0181 | |||
| da04571dbc | |||
| 7f1cd3e6a2 | |||
| 6d85f24e20 | |||
| 0afd775433 | |||
| 8eb01216b4 | |||
| 323f9ab736 | |||
| 5ba4419e26 | |||
| 529b2507bd | |||
| 6d19aa2e7c | |||
| bf35d53855 | |||
| 6c4581fcb6 | |||
| 620a20d1b3 | |||
| 1f8431bfaa | |||
| b8773a71b5 | |||
| 8a63071ab0 | |||
| e2a0368726 | |||
| d9157849df | |||
| d558dc5f95 | |||
| 616695eb05 | |||
| 9ac4e5a64b | |||
| fcb6e1e96b | |||
| cf972fd0bd | |||
| ffc3115705 | |||
| 9590bcf062 | |||
| 689c01f599 | |||
| 2334a5101d | |||
| ed7777d3e1 | |||
| a1648737bd | |||
| 420586bd09 | |||
| d89afc488f | |||
| 0a8a6fa36b | |||
| e1a182b105 | |||
| 214f6822c3 | |||
| 9c5a1cd43c | |||
| 8120ab2f77 | |||
| 2f08eae48f | |||
| 6610ae816f | |||
| fa56f8eadb | |||
| 7882c1c6ae | |||
| 84a6ef4d21 | |||
| 4d680c3fad | |||
| bab3069dff | |||
| 4cb8ac61c7 | |||
| 9d28a79219 | |||
| c4489eb3cb | |||
| 7caf91300f | |||
| 0c1b54f04c | |||
| 85e392f354 | |||
| c98e08fd87 | |||
| 33cc75e6fc | |||
| 474865242c | |||
| 0292e6a1a8 | |||
| de193422a3 | |||
| ff228fb524 | |||
| 8709539d4c | |||
| 2995b54afd | |||
| e26256fb70 | |||
| e45f9b66fa | |||
| 158f1aec86 | |||
| f53a7f62bb | |||
| d2b08a0465 | |||
| 43c4029c8a | |||
| 55ac985373 | |||
| 37b6ed3225 | |||
| c5bf9daef6 | |||
| be01cebfae | |||
| 7a81e6fe2d | |||
| 9c49eb131a | |||
| 374a302b90 | |||
| b3da88077f | |||
| 6454f51741 | |||
| e509ab5be6 | |||
| 592bf516e5 | |||
| 8cdafe46e3 | |||
| 45c5688d42 | |||
| 7bb2414bf6 | |||
| da11c1bcee | |||
| a80697938e | |||
| 1d9ad76f1f | |||
| 01387ba3c3 | |||
| f163c90555 | |||
| 4e94864e54 | |||
| 54c4041ebc | |||
| f780aba434 | |||
| 057f78de8b | |||
| 7a36128efc | |||
| d2cd8b9bb6 | |||
| 937942f5ad | |||
| 75e876424d | |||
| 19fab4a1d7 | |||
| d1293540e1 | |||
| 22f66025db | |||
| 6f8ddb6d43 | |||
| 34590e15d4 | |||
| 83098b9add | |||
| 5d8861fb2f | |||
| b9f7c453e0 | |||
| 750344129e | |||
| 74da034077 | |||
| 91ef94d284 | |||
| ab9b021572 | |||
| b268c0b7b4 | |||
| b0c19f8b06 | |||
| bbc2a0ae48 | |||
| ca53dfcc18 | |||
| ce6a96b0d7 | |||
| d4b359f4b2 | |||
| 8d841c3405 | |||
| 2b285c75f4 | |||
| 6e4464331d | |||
| 2f8db1bf01 | |||
| 838cf4be3c | |||
| de2a56bbc8 | |||
| 55ad192e4a | |||
| 5b4713e43e | |||
| 3fa9aba0cc | |||
| 1bba358a75 | |||
| 7a4124c298 | |||
| 2512a81e09 | |||
| 44c9d1616a | |||
| 5758d73964 | |||
| 6bd6dbff49 | |||
| 7170f9d9ca | |||
| 1c0f721368 | |||
| 2a5a52a76c | |||
| c690946469 | |||
| 87b0055c80 | |||
| 2116857a2a | |||
| 0f58334b7b | |||
| bcc257b459 | |||
| 980fb395e4 | |||
| 62ed26a84f | |||
| 59f1f4e19a | |||
| cb51116dbd | |||
| c1f34e8eeb | |||
| 7bf5429e3b | |||
| d3da55c40f | |||
| 70edec947c | |||
| fe17c0e066 | |||
| e403682444 | |||
| 27d441b0d6 | |||
| 8a944b0872 | |||
| 786a1a4429 | |||
| 8e5c4e92f7 | |||
| 46d24ae4c8 | |||
| 9dd33c09b1 | |||
| fea8240c81 | |||
| 3d2b1be211 | |||
| f08a0c5ad1 | |||
| 6f1e0ba563 | |||
| 540338f9a5 | |||
| 0e6a700807 | |||
| 4fc40bc932 | |||
| 216724b4cb | |||
| 9bd1645970 | |||
| 3397a031a1 | |||
| 5ec5aa7751 | |||
| bf5ac5261d | |||
| 91b7cd9b74 | |||
| 7b2ecf42c6 | |||
| 256d9a948c | |||
| 99fc6cda98 | |||
| 690b69b9cd | |||
| 4262f15e16 | |||
| 3a8d1354ce | |||
| f9387c6890 | |||
| 48d0ffcbc4 | |||
| 3485ba1e2b | |||
| 2f61145475 | |||
| 8c618d896b | |||
| 68d4dc5b71 | |||
| 03a4a96cf9 | |||
| 655c52a621 | |||
| fa3ddba5f2 | |||
| c4a1b6124e | |||
| e52d731bfd | |||
| 9b72843018 | |||
| 9c1f8ea70b | |||
| 9fde5648e4 | |||
| ea829620b2 | |||
| 1731d091f8 | |||
| 9d3704ca46 | |||
| 215dff34dd | |||
| fa8c399fad | |||
| 7295c60ffb | |||
| fa01571036 | |||
| dbc698517f | |||
| a7f3761eda | |||
| 5a98e806ef | |||
| 808f984ec0 | |||
| 698af191de | |||
| 7a413df5e4 | |||
| 4994acd26e |
+16
-21
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '0.10'
|
||||
- '4.4'
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@@ -15,28 +15,26 @@ branches:
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- JOB=ci-checks
|
||||
- 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:
|
||||
- CXX=g++-4.8 # node 4 likes the G++ v4.8 compiler
|
||||
- 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/browsersprovider-tunnel-ready
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
|
||||
- env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
|
||||
# node 4 likes the G++ v4.8 compiler
|
||||
# see https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
install:
|
||||
# Check the size of caches
|
||||
@@ -46,21 +44,18 @@ install:
|
||||
- npm config set spin false
|
||||
# Log HTTP requests
|
||||
- npm config set loglevel http
|
||||
- npm install -g npm@2.5
|
||||
# Instal npm dependecies and ensure that npm cache is not stale
|
||||
- scripts/npm/install-dependencies.sh
|
||||
#- npm install -g npm@2.5
|
||||
# Install npm dependencies and ensure that npm cache is not stale
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./scripts/travis/start_browser_provider.sh
|
||||
- npm install -g grunt-cli
|
||||
- grunt package
|
||||
- ./scripts/travis/wait_for_browser_provider.sh
|
||||
- ./scripts/travis/before_build.sh
|
||||
|
||||
script:
|
||||
- ./scripts/travis/build.sh
|
||||
|
||||
after_script:
|
||||
- ./scripts/travis/tear_down_browser_provider.sh
|
||||
- ./scripts/travis/print_logs.sh
|
||||
|
||||
notifications:
|
||||
|
||||
+538
-3
@@ -1,3 +1,411 @@
|
||||
<a name="1.4.11"></a>
|
||||
# 1.4.11 relentless-syncomania (2016-05-27)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** properly handle setting `srcset` to undefined
|
||||
([85a53ea9](https://github.com/angular/angular.js/commit/85a53ea9cd08476496ef680d20f64ae6a7ae7499),
|
||||
[#14470](https://github.com/angular/angular.js/issues/14470), [#14493](https://github.com/angular/angular.js/issues/14493))
|
||||
- **$templateRequest:** trust empty templates in `$templateCache` as well
|
||||
([ad21f8fe](https://github.com/angular/angular.js/commit/ad21f8feaf8659158e8f2092c90eb42c4f13c141),
|
||||
[#14479](https://github.com/angular/angular.js/issues/14479), [#14496](https://github.com/angular/angular.js/issues/14496))
|
||||
- **filters:** always call `splice()` with 2 arguments or more
|
||||
([4e735e53](https://github.com/angular/angular.js/commit/4e735e53638a1c831f5c0455cd123a4127a11505),
|
||||
[#14467](https://github.com/angular/angular.js/issues/14467), [#14489](https://github.com/angular/angular.js/issues/14489))
|
||||
- **formatNumber:** handle small numbers correctly when `gSize` !== `lgSize`
|
||||
([a1188721](https://github.com/angular/angular.js/commit/a1188721e1ac24e1c34e61b4f622f397416ccc7c),
|
||||
[#14289](https://github.com/angular/angular.js/issues/14289), [#14290](https://github.com/angular/angular.js/issues/14290))
|
||||
- **ng-bind-html:** watch the unwrapped value using `$sce.valueOf()` (instead of `toString()`)
|
||||
([f31586db](https://github.com/angular/angular.js/commit/f31586db4115baabaa854c095278f367850a11c5),
|
||||
[#14526](https://github.com/angular/angular.js/issues/14526), [#14527](https://github.com/angular/angular.js/issues/14527))
|
||||
- **ngAnimate:**
|
||||
- safe-guard against missing document
|
||||
([0d764b58](https://github.com/angular/angular.js/commit/0d764b581d8b494190fa280f8870eee8fd039933))
|
||||
- properly handle empty jqLite collections
|
||||
([fdaf4d5e](https://github.com/angular/angular.js/commit/fdaf4d5e27a2ddebf2999db4d35bef7915d32c86),
|
||||
[#14558](https://github.com/angular/angular.js/issues/14558), [#14559](https://github.com/angular/angular.js/issues/14559))
|
||||
- fire callbacks when document is hidden
|
||||
([c4bff290](https://github.com/angular/angular.js/commit/c4bff290e5009e8ae5aa93839ad80a677a9e878f),
|
||||
[#14120](https://github.com/angular/angular.js/issues/14120))
|
||||
- fire callbacks in the correct order for certain skipped animations
|
||||
([23550b5e](https://github.com/angular/angular.js/commit/23550b5e278b4fb3cf1300f1399d8ad6d6771725))
|
||||
- **ngClass:** fix watching of an array expression containing an object
|
||||
([b5086700](https://github.com/angular/angular.js/commit/b50867001bf71998a2f7e381095cb9001166e806),
|
||||
[#14405](https://github.com/angular/angular.js/issues/14405))
|
||||
- **ngMessages:** don't crash when nested messages are removed
|
||||
([71dca7c4](https://github.com/angular/angular.js/commit/71dca7c4c23df95c6c0e5974f0758a3ca34ce819),
|
||||
[#14183](https://github.com/angular/angular.js/issues/14183), [#14242](https://github.com/angular/angular.js/issues/14242))
|
||||
- **ngMock:** fix collecting stack trace in `inject()` on IE10+, PhantomJS
|
||||
([56dae6fa](https://github.com/angular/angular.js/commit/56dae6fa1bee88eaffae766a6cdcb8b6c4b89958),
|
||||
[#13591](https://github.com/angular/angular.js/issues/13591), [#13592](https://github.com/angular/angular.js/issues/13592), [#13593](https://github.com/angular/angular.js/issues/13593))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** use createMap() for directive bindings to allow fast forEach
|
||||
([832b383c](https://github.com/angular/angular.js/commit/832b383cbc63c157e62df523d574a75171a8c11c),
|
||||
[#12529](https://github.com/angular/angular.js/issues/12529))
|
||||
|
||||
|
||||
<a name="1.4.10"></a>
|
||||
# 1.4.10 benignant-oscillation (2016-03-16)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **core:** only call `console.log` when `window.console` exists
|
||||
([beb00e44](https://github.com/angular/angular.js/commit/beb00e44de947981dbe35d5cf7a116e10ea8dc67),
|
||||
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
|
||||
- **$animateCss:** cancel fallback timeout when animation ends normally
|
||||
([a60bbc12](https://github.com/angular/angular.js/commit/a60bbc12e8c5170e70d95f1b2c3e309b3b95cb84),
|
||||
[#13787](https://github.com/angular/angular.js/issues/13787))
|
||||
- **$compile:**
|
||||
- allow directives to have decorators
|
||||
([77cdc37c](https://github.com/angular/angular.js/commit/77cdc37c65491b551fcf01a18ab848a693c293d7))
|
||||
- properly denormalize templates when only one of the start/end symbols is different
|
||||
([2d44a681](https://github.com/angular/angular.js/commit/2d44a681eb912a81a8bc8e16a278c45dae91fa24),
|
||||
[#13848](https://github.com/angular/angular.js/issues/13848))
|
||||
- handle boolean attributes in `@` bindings
|
||||
([2ffbfb0a](https://github.com/angular/angular.js/commit/2ffbfb0ad0647d103ff339ee4b772b62d4823bf3),
|
||||
[#13767](https://github.com/angular/angular.js/issues/13767), [#13769](https://github.com/angular/angular.js/issues/13769))
|
||||
- **$parse:**
|
||||
- prevent assignment on constructor properties
|
||||
([f47e2180](https://github.com/angular/angular.js/commit/f47e218006029f39b4785d820b430de3a0eebcb0),
|
||||
[#13417](https://github.com/angular/angular.js/issues/13417))
|
||||
- preserve expensive checks when runnning `$eval` inside an expression
|
||||
([96d62cc0](https://github.com/angular/angular.js/commit/96d62cc0fc77248d7e3ec4aa458bac0d3e072629))
|
||||
- copy `inputs` for expressions with expensive checks
|
||||
([0b7fff30](https://github.com/angular/angular.js/commit/0b7fff303f46202bbae1ff3ca9d0e5fa76e0fc9a))
|
||||
- **$rootScope:** set no context when calling helper functions for `$watch`
|
||||
([ab5c7698](https://github.com/angular/angular.js/commit/ab5c7698bb106669ca31b5f79a95afa54d65c5f1))
|
||||
- **$route:** allow preventing a route reload
|
||||
([4bc30314](https://github.com/angular/angular.js/commit/4bc3031497447ad527356f12bd0ceee1d7d09db5),
|
||||
[#9824](https://github.com/angular/angular.js/issues/9824), [#13894](https://github.com/angular/angular.js/issues/13894))
|
||||
- **$routeProvider:** properly handle optional eager path named groups
|
||||
([6a4403a1](https://github.com/angular/angular.js/commit/6a4403a11845173d6a96232f77d73aa544b182af),
|
||||
[#14011](https://github.com/angular/angular.js/issues/14011))
|
||||
- **copy:** add support for copying `Blob` objects
|
||||
([863a4232](https://github.com/angular/angular.js/commit/863a4232a6faa92428df45cd54d5a519be2434de),
|
||||
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
|
||||
- **dateFilter:** follow the CLDR on pattern escape sequences
|
||||
([f476060d](https://github.com/angular/angular.js/commit/f476060de6cc016380c0343490a184543f853652),
|
||||
[#12839](https://github.com/angular/angular.js/issues/12839))
|
||||
- **dateFilter, input:** fix Date parsing in IE/Edge when timezone offset contains `:`
|
||||
([571afd65](https://github.com/angular/angular.js/commit/571afd6558786d7b99e2aebd307b4a94c9f2bb87),
|
||||
[#13880](https://github.com/angular/angular.js/issues/13880), [#13887](https://github.com/angular/angular.js/issues/13887))
|
||||
- **input:** re-validate when partially editing date-family inputs
|
||||
([02929f82](https://github.com/angular/angular.js/commit/02929f82f30449301ff18fea84a6396a017683b1),
|
||||
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
|
||||
- **select:** handle corner case of adding options via a custom directive
|
||||
([df6e7315](https://github.com/angular/angular.js/commit/df6e731506831a3dc7f44c9a90abe17515450b3e),
|
||||
[#13874](https://github.com/angular/angular.js/issues/13874), [#13878](https://github.com/angular/angular.js/issues/13878))
|
||||
- **ngOptions:** always set the 'selected' attribute for selected options
|
||||
([f87e8288](https://github.com/angular/angular.js/commit/f87e8288fb69526fd240a66a046f5de52ed204de),
|
||||
[#14115](https://github.com/angular/angular.js/issues/14115))
|
||||
- **ngAnimate:** properly cancel previously running class-based animations
|
||||
([3b27dd37](https://github.com/angular/angular.js/commit/3b27dd37a2cc8a52992784ece6b371023dadf792),
|
||||
[#10156](https://github.com/angular/angular.js/issues/10156), [#13822](https://github.com/angular/angular.js/issues/13822))
|
||||
- **ngAnimateChildren:** make it compatible with `ngIf`
|
||||
([dc158e7e](https://github.com/angular/angular.js/commit/dc158e7e40624ef94c66560386522ef7e991a9ce),
|
||||
[#13865](https://github.com/angular/angular.js/issues/13865), [#13876](https://github.com/angular/angular.js/issues/13876))
|
||||
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
|
||||
([947cb4d1](https://github.com/angular/angular.js/commit/947cb4d1451afa4f5090a693df5b1968dd0df70c),
|
||||
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$locale:** Include original locale ID in $locale
|
||||
([e69f3550](https://github.com/angular/angular.js/commit/e69f35507e10c994708ce4f1efba7573951d1acd),
|
||||
[#13390](https://github.com/angular/angular.js/issues/13390))
|
||||
- **ngAnimate:** provide ng-[event]-prepare class for structural animations
|
||||
([796f7ab4](https://github.com/angular/angular.js/commit/796f7ab41487e124b5b0c02dbf0a03bd581bf073))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** avoid needless overhead when wrapping text nodes
|
||||
([946d9ae9](https://github.com/angular/angular.js/commit/946d9ae90bb31fe911ebbe1b80cd4c8af5a665c6))
|
||||
- **ngRepeat:** avoid duplicate jqLite wrappers
|
||||
([d04c38c4](https://github.com/angular/angular.js/commit/d04c38c48968db777c3ea6a177ce2ff0116df7b4))
|
||||
- **ngAnimate:**
|
||||
- avoid jqLite/jQuery for upward DOM traversal
|
||||
([ab95ba65](https://github.com/angular/angular.js/commit/ab95ba65c08b38cace83de6717b7681079182b45))
|
||||
- avoid `$.fn.data` overhead with jQuery
|
||||
([86416bcb](https://github.com/angular/angular.js/commit/86416bcbee2192fa31c017163c5d856763182ade))
|
||||
|
||||
|
||||
<a name="1.4.9"></a>
|
||||
# 1.4.9 implicit-superannuation (2016-01-21)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Animation**
|
||||
- ensure that animate promises resolve when the document is hidden
|
||||
([9a60408c](https://github.com/angular/angular.js/commit/9a60408c804a62a9517857bdb9a42182ab6769e3))
|
||||
- do not trigger animations if the document is hidden
|
||||
([09f6061a](https://github.com/angular/angular.js/commit/09f6061a8ee41cae4268e8d44d727d3bf52e22a9),
|
||||
[#12842](https://github.com/angular/angular.js/issues/12842), [#13776](https://github.com/angular/angular.js/issues/13776))
|
||||
- only copy over the animation options once
|
||||
([2fc954d3](https://github.com/angular/angular.js/commit/2fc954d33a3a4c5d4f355be1e15a381664e02f1b),
|
||||
[#13722](https://github.com/angular/angular.js/issues/13722), [#13578](https://github.com/angular/angular.js/issues/13578))
|
||||
- allow event listeners on document in IE
|
||||
([5ba4419e](https://github.com/angular/angular.js/commit/5ba4419e265ff34c6c23bf3533a3332c99c5f014),
|
||||
[#13548](https://github.com/angular/angular.js/issues/13548), [#13696](https://github.com/angular/angular.js/issues/13696))
|
||||
- allow removing classes that are added by a running animation
|
||||
([6c4581fc](https://github.com/angular/angular.js/commit/6c4581fcb692b17295a41b8918c6038333e7bc3d),
|
||||
[#13339](https://github.com/angular/angular.js/issues/13339), [#13380](https://github.com/angular/angular.js/issues/13380), [#13414](https://github.com/angular/angular.js/issues/13414), [#13472](https://github.com/angular/angular.js/issues/13472), [#13678](https://github.com/angular/angular.js/issues/13678))
|
||||
- do not use `event.timeStamp` anymore for time tracking
|
||||
([620a20d1](https://github.com/angular/angular.js/commit/620a20d1b3376d95f85004ffa494e36bb19a2e4d),
|
||||
[#13494](https://github.com/angular/angular.js/issues/13494), [#13495](https://github.com/angular/angular.js/issues/13495))
|
||||
- ignore children without animation data when closing them
|
||||
([be01cebf](https://github.com/angular/angular.js/commit/be01cebfae9ca2383105e535820442b39a96b240),
|
||||
[#11992](https://github.com/angular/angular.js/issues/11992), [#13424](https://github.com/angular/angular.js/issues/13424))
|
||||
- do not alter the provided options data
|
||||
([7a81e6fe](https://github.com/angular/angular.js/commit/7a81e6fe2db084172e34d509f0baad2b33a8722c),
|
||||
[#13040](https://github.com/angular/angular.js/issues/13040), [#13175](https://github.com/angular/angular.js/issues/13175))
|
||||
- correctly handle `$animate.pin()` host elements
|
||||
([a985adfd](https://github.com/angular/angular.js/commit/a985adfdabd871f3f3f3ee59f371da50cd9611d9),
|
||||
[#13783](https://github.com/angular/angular.js/issues/13783))
|
||||
- allow animations when pinned element is parent element
|
||||
([4cb8ac61](https://github.com/angular/angular.js/commit/4cb8ac61c7574ab4039852c358dd5946268b69fb),
|
||||
[#13466](https://github.com/angular/angular.js/issues/13466))
|
||||
- allow enabled children to animate on disabled parents
|
||||
([6d85f24e](https://github.com/angular/angular.js/commit/6d85f24e2081d2a69c80697d90ebd45f228d9682),
|
||||
[#13179](https://github.com/angular/angular.js/issues/13179), [#13695](https://github.com/angular/angular.js/issues/13695))
|
||||
- correctly access `minErr`
|
||||
([0c1b54f0](https://github.com/angular/angular.js/commit/0c1b54f04cf5bd7c1fe42ac49b4fbfdf35c60979))
|
||||
- ensure animate runner is the same with and without animations
|
||||
([937942f5](https://github.com/angular/angular.js/commit/937942f5ada6de1bdacdf0ba465f6f118c270119),
|
||||
[#13205](https://github.com/angular/angular.js/issues/13205), [#13347](https://github.com/angular/angular.js/issues/13347))
|
||||
- remove animation end event listeners on close
|
||||
([d9157849](https://github.com/angular/angular.js/commit/d9157849df224a3a8d2e0bf03099d137f51499f6),
|
||||
[#13672](https://github.com/angular/angular.js/issues/13672))
|
||||
- consider options.delay value for closing timeout
|
||||
([592bf516](https://github.com/angular/angular.js/commit/592bf516e50b9729e446d9aa01f4d9ebdd72d187),
|
||||
[#13355](https://github.com/angular/angular.js/issues/13355), [#13363](https://github.com/angular/angular.js/issues/13363))
|
||||
- **$controller:** allow identifiers containing `$`
|
||||
([2563ff7b](https://github.com/angular/angular.js/commit/2563ff7ba92d84af978e7e4131253190d4d00c20),
|
||||
[#13736](https://github.com/angular/angular.js/issues/13736))
|
||||
- **$http:** throw if url passed is not a string
|
||||
([c5bf9dae](https://github.com/angular/angular.js/commit/c5bf9daef6dfdb3e4a2942c21155a9f67d92e237),
|
||||
[#12925](https://github.com/angular/angular.js/issues/12925), [#13444](https://github.com/angular/angular.js/issues/13444))
|
||||
- **$parse:** handle interceptors with `undefined` expressions
|
||||
([7bb2414b](https://github.com/angular/angular.js/commit/7bb2414bf6461aa45a983fd322ae875f81814cc4))
|
||||
- **$resource:** don't allow using promises as `timeout` and log a warning
|
||||
([47486524](https://github.com/angular/angular.js/commit/474865242c89ba3e8143f0cd52f8c292979ea730))
|
||||
- **formatNumber:** cope with large and small number corner cases
|
||||
([9c49eb13](https://github.com/angular/angular.js/commit/9c49eb131a6100d58c965d01fb08bcd319032229),
|
||||
[#13394](https://github.com/angular/angular.js/issues/13394), [#8674](https://github.com/angular/angular.js/issues/8674), [#12709](https://github.com/angular/angular.js/issues/12709), [#8705](https://github.com/angular/angular.js/issues/8705), [#12707](https://github.com/angular/angular.js/issues/12707), [#10246](https://github.com/angular/angular.js/issues/10246), [#10252](https://github.com/angular/angular.js/issues/10252))
|
||||
- **input:**
|
||||
- fix URL validation being too strict
|
||||
([6610ae81](https://github.com/angular/angular.js/commit/6610ae816f78ee8fc1080b93a55bf19e4ce48d3e),
|
||||
[#13528](https://github.com/angular/angular.js/issues/13528), [#13544](https://github.com/angular/angular.js/issues/13544))
|
||||
- add missing chars to URL validation regex
|
||||
([2995b54a](https://github.com/angular/angular.js/commit/2995b54afdb9a3a2a81b0076a6ac0a9001041163),
|
||||
[#13379](https://github.com/angular/angular.js/issues/13379), [#13460](https://github.com/angular/angular.js/issues/13460))
|
||||
- **isArrayLike:** recognize empty instances of an Array subclass
|
||||
([323f9ab7](https://github.com/angular/angular.js/commit/323f9ab73696f223c245ddefd62a769fe102615e),
|
||||
[#13560](https://github.com/angular/angular.js/issues/13560), [#13708](https://github.com/angular/angular.js/issues/13708))
|
||||
- **ngInclude:** do not compile template if original scope is destroyed
|
||||
([9590bcf0](https://github.com/angular/angular.js/commit/9590bcf0620cd507a7795c55f9a6f4a48bfedbc1))
|
||||
- **ngOptions:**
|
||||
- don't skip `optgroup` elements with `value === ''`
|
||||
([85e392f3](https://github.com/angular/angular.js/commit/85e392f3543ef5285c7e90e843af0ab522cb0531),
|
||||
[#13487](https://github.com/angular/angular.js/issues/13487), [#13489](https://github.com/angular/angular.js/issues/13489))
|
||||
- don't `$dirty` multiple select after compilation
|
||||
([f163c905](https://github.com/angular/angular.js/commit/f163c90555774426ccb14752d089fc707cb4029c),
|
||||
[#13211](https://github.com/angular/angular.js/issues/13211), [#13326](https://github.com/angular/angular.js/issues/13326))
|
||||
- **select:** re-define `ngModelCtrl.$render` in the `select` directive's postLink function
|
||||
([529b2507](https://github.com/angular/angular.js/commit/529b2507bdb4fcc22dfa0f7ab462c79fc78d1413),
|
||||
[#13583](https://github.com/angular/angular.js/issues/13583), [#13583](https://github.com/angular/angular.js/issues/13583), [#13663](https://github.com/angular/angular.js/issues/13663))
|
||||
|
||||
## Minor Features
|
||||
|
||||
- **ngLocale:** add support for standalone months
|
||||
([54c4041e](https://github.com/angular/angular.js/commit/54c4041ebc0cc4df70cf6996f43a6aaaf56d46bd),
|
||||
[#3744](https://github.com/angular/angular.js/issues/3744), [#10247](https://github.com/angular/angular.js/issues/10247), [#12642](https://github.com/angular/angular.js/issues/12642), [#12844](https://github.com/angular/angular.js/issues/12844))
|
||||
- **ngMock:** add support for `$animate.closeAndFlush()`
|
||||
([512c0811](https://github.com/angular/angular.js/commit/512c08118786a419fabbd063fa17d224aba125cf))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **ngAnimate:** speed up `areAnimationsAllowed` check
|
||||
([2d3303dd](https://github.com/angular/angular.js/commit/2d3303ddda6330c4f45b381b6b17346f6cfe2d97))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
While we do not deem the following to be a real breaking change we are highlighting it here in the
|
||||
changelog to ensure that it does not surprise anyone.
|
||||
|
||||
- **$resource:** due to [47486524](https://github.com/angular/angular.js/commit/474865242c89ba3e8143f0cd52f8c292979ea730),
|
||||
|
||||
**Possible breaking change** for users who updated their code to provide a `timeout`
|
||||
promise for a `$resource` request in version 1.4.8.
|
||||
|
||||
Up to v1.4.7 (included), using a promise as a timeout in `$resource`, would silently
|
||||
fail (i.e. have no effect).
|
||||
|
||||
In v1.4.8, using a promise as timeout would have the (buggy) behaviour described
|
||||
in https://github.com/angular/angular.js/pull/12657#issuecomment-152108887
|
||||
(i.e. it will work as expected for the first time you resolve the promise and will
|
||||
cancel all subsequent requests after that - one has to re-create the resource
|
||||
class. This is feature was not documented.)
|
||||
|
||||
With this change, using a promise as timeout in 1.4.9 onwsards is not allowed.
|
||||
It will log a warning and ignore the timeout value.
|
||||
|
||||
If you need support for cancellable `$resource` actions, you should upgrade to
|
||||
version 1.5 or higher.
|
||||
|
||||
|
||||
<a name="1.4.8"></a>
|
||||
# 1.4.8 ice-manipulation (2015-11-19)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$animate:** ensure leave animation calls `close` callback
|
||||
([6bd6dbff](https://github.com/angular/angular.js/commit/6bd6dbff4961a601c03e9465442788781d329ba6),
|
||||
[#12278](https://github.com/angular/angular.js/issues/12278), [#12096](https://github.com/angular/angular.js/issues/12096), [#13054](https://github.com/angular/angular.js/issues/13054))
|
||||
- **$cacheFactory:** check key exists before decreasing cache size count
|
||||
([2a5a52a7](https://github.com/angular/angular.js/commit/2a5a52a76ccf60c6e8c5d881e90e11a2666a6d3c),
|
||||
[#12321](https://github.com/angular/angular.js/issues/12321), [#12329](https://github.com/angular/angular.js/issues/12329))
|
||||
- **$compile:**
|
||||
- bind all directive controllers correctly when using `bindToController`
|
||||
([5d8861fb](https://github.com/angular/angular.js/commit/5d8861fb2f203e8a688b6044cbd1140cd79fd049),
|
||||
[#11343](https://github.com/angular/angular.js/issues/11343), [#11345](https://github.com/angular/angular.js/issues/11345))
|
||||
- evaluate against the correct scope with bindToController on new scope
|
||||
([b9f7c453](https://github.com/angular/angular.js/commit/b9f7c453e00d6938106f414952f74d5e5fdcb993),
|
||||
[#13021](https://github.com/angular/angular.js/issues/13021), [#13025](https://github.com/angular/angular.js/issues/13025))
|
||||
- fix scoping of transclusion directives inside replace directive
|
||||
([74da0340](https://github.com/angular/angular.js/commit/74da03407782d679951cd8f693860cea214f2580),
|
||||
[#12975](https://github.com/angular/angular.js/issues/12975), [#12936](https://github.com/angular/angular.js/issues/12936), [#13244](https://github.com/angular/angular.js/issues/13244))
|
||||
- **$http:** apply `transformResponse` even when `data` is empty
|
||||
([c6909464](https://github.com/angular/angular.js/commit/c690946469e09cfe6b774e63dbe14ace92ce6cb7),
|
||||
[#12976](https://github.com/angular/angular.js/issues/12976), [#12979](https://github.com/angular/angular.js/issues/12979))
|
||||
- **$location:** ensure `$locationChangeSuccess` fires even if URL ends with `#`
|
||||
([6f8ddb6d](https://github.com/angular/angular.js/commit/6f8ddb6d4329441e8d4a856978413aa9b9bd918f),
|
||||
[#12175](https://github.com/angular/angular.js/issues/12175), [#13251](https://github.com/angular/angular.js/issues/13251))
|
||||
- **$parse:** evaluate once simple expressions only once
|
||||
([e4036824](https://github.com/angular/angular.js/commit/e403682444fa08af4f3491badf2f3a10d7595699),
|
||||
[#12983](https://github.com/angular/angular.js/issues/12983), [#13002](https://github.com/angular/angular.js/issues/13002))
|
||||
- **$resource:** allow XHR request to be cancelled via a timeout promise
|
||||
([7170f9d9](https://github.com/angular/angular.js/commit/7170f9d9ca765c578f8d3eb4699860a9330a0a11),
|
||||
[#12657](https://github.com/angular/angular.js/issues/12657), [#12675](https://github.com/angular/angular.js/issues/12675), [#10890](https://github.com/angular/angular.js/issues/10890), [#9332](https://github.com/angular/angular.js/issues/9332))
|
||||
- **$rootScope:** prevent IE9 memory leak when destroying scopes
|
||||
([87b0055c](https://github.com/angular/angular.js/commit/87b0055c80f40589c5bcf3765e59e872bcfae119),
|
||||
[#10706](https://github.com/angular/angular.js/issues/10706), [#11786](https://github.com/angular/angular.js/issues/11786))
|
||||
- **Angular.js:** fix `isArrayLike` for unusual cases
|
||||
([70edec94](https://github.com/angular/angular.js/commit/70edec947c7b189694ae66b129568182e3369cab),
|
||||
[#10186](https://github.com/angular/angular.js/issues/10186), [#8000](https://github.com/angular/angular.js/issues/8000), [#4855](https://github.com/angular/angular.js/issues/4855), [#4751](https://github.com/angular/angular.js/issues/4751), [#10272](https://github.com/angular/angular.js/issues/10272))
|
||||
- **isArrayLike:** handle jQuery objects of length 0
|
||||
([d3da55c4](https://github.com/angular/angular.js/commit/d3da55c40f1e1ddceced5da51e364888ff9d82ff))
|
||||
- **jqLite:**
|
||||
- deregister special `mouseenter` / `mouseleave` events correctly
|
||||
([22f66025](https://github.com/angular/angular.js/commit/22f66025db262417ebb78c1ce1f4d7058dca3fd3),
|
||||
[#12795](https://github.com/angular/angular.js/issues/12795), [#12799](https://github.com/angular/angular.js/issues/12799))
|
||||
- ensure mouseenter works with svg elements on IE
|
||||
([c1f34e8e](https://github.com/angular/angular.js/commit/c1f34e8eeb5105767f6cbf4727b8c5664be2a261),
|
||||
[#10259](https://github.com/angular/angular.js/issues/10259), [#10276](https://github.com/angular/angular.js/issues/10276))
|
||||
- **limitTo:** start at 0 if `begin` is negative and exceeds input length
|
||||
([4fc40bc9](https://github.com/angular/angular.js/commit/4fc40bc9320a1d5902e648b70fa79c7cf7e794c7),
|
||||
[#12775](https://github.com/angular/angular.js/issues/12775), [#12781](https://github.com/angular/angular.js/issues/12781))
|
||||
- **merge:**
|
||||
- ensure that jqlite->jqlite and DOM->DOM
|
||||
([2f8db1bf](https://github.com/angular/angular.js/commit/2f8db1bf01173b546a2868fc7b8b188c2383fbff))
|
||||
- clone elements instead of treating them like simple objects
|
||||
([838cf4be](https://github.com/angular/angular.js/commit/838cf4be3c671903796dbb69d95c0e5ac1516a06),
|
||||
[#12286](https://github.com/angular/angular.js/issues/12286))
|
||||
- **ngAria:** don't add tabindex to radio and checkbox inputs
|
||||
([59f1f4e1](https://github.com/angular/angular.js/commit/59f1f4e19a02e6e6f4c41c15b0e9f3372d85cecc),
|
||||
[#12492](https://github.com/angular/angular.js/issues/12492), [#13095](https://github.com/angular/angular.js/issues/13095))
|
||||
- **ngInput:** change URL_REGEXP to better match RFC3987
|
||||
([cb51116d](https://github.com/angular/angular.js/commit/cb51116dbd225ccfdbc9a565a66a170e65d26331),
|
||||
[#11341](https://github.com/angular/angular.js/issues/11341), [#11381](https://github.com/angular/angular.js/issues/11381))
|
||||
- **ngMock:** reset cache before every test
|
||||
([91b7cd9b](https://github.com/angular/angular.js/commit/91b7cd9b74d72a48d844c5c3e0e9dee03405e0ca),
|
||||
[#13013](https://github.com/angular/angular.js/issues/13013))
|
||||
- **ngOptions:**
|
||||
- skip comments and empty options when looking for options
|
||||
([0f58334b](https://github.com/angular/angular.js/commit/0f58334b7b9a9d3d6ff34e9754961b6f67731fae),
|
||||
[#12190](https://github.com/angular/angular.js/issues/12190), [#13029](https://github.com/angular/angular.js/issues/13029), [#13033](https://github.com/angular/angular.js/issues/13033))
|
||||
- override select option registration to allow compilation of empty option
|
||||
([7b2ecf42](https://github.com/angular/angular.js/commit/7b2ecf42c697eb8d51a0f2d73b324bd900139e05),
|
||||
[#11685](https://github.com/angular/angular.js/issues/11685), [#12972](https://github.com/angular/angular.js/issues/12972), [#12968](https://github.com/angular/angular.js/issues/12968), [#13012](https://github.com/angular/angular.js/issues/13012))
|
||||
|
||||
|
||||
## Performance Improvements
|
||||
|
||||
- **$compile:** use static jquery data method to avoid creating new instances
|
||||
([55ad192e](https://github.com/angular/angular.js/commit/55ad192e4ab79295ab15ecaaf8f6b9e7932a0336))
|
||||
- **copy:**
|
||||
- avoid regex in `isTypedArray`
|
||||
([19fab4a1](https://github.com/angular/angular.js/commit/19fab4a1d79d2445795273f1622344353cf4d104))
|
||||
- only validate/clear if the user specifies a destination
|
||||
([d1293540](https://github.com/angular/angular.js/commit/d1293540e13573eb9ea5f90730bb9c9710c345db),
|
||||
[#12068](https://github.com/angular/angular.js/issues/12068))
|
||||
- **merge:** remove unnecessary wrapping of jqLite element
|
||||
([ce6a96b0](https://github.com/angular/angular.js/commit/ce6a96b0d76dd2e5ab2247ca3059d284575bc6f0),
|
||||
[#13236](https://github.com/angular/angular.js/issues/13236))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
|
||||
<a name="1.4.7"></a>
|
||||
# 1.4.7 dark-luminescence (2015-09-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** use createMap() for $$observe listeners when initialized from attr interpolation
|
||||
([5a98e806](https://github.com/angular/angular.js/commit/5a98e806ef3c59916bb4668268125610b11effe8),
|
||||
[#10446](https://github.com/angular/angular.js/issues/10446))
|
||||
- **$parse:**
|
||||
- block assigning to fields of a constructor
|
||||
([a7f3761e](https://github.com/angular/angular.js/commit/a7f3761eda5309f76b73c6fb1d3173a270112899),
|
||||
[#12860](https://github.com/angular/angular.js/issues/12860))
|
||||
- do not convert to string computed properties multiple times
|
||||
([698af191](https://github.com/angular/angular.js/commit/698af191ded2465ca4e0f97959b75fede5a531ab))
|
||||
- **filters:** ensure `formatNumber` observes i18n decimal separators
|
||||
([4994acd2](https://github.com/angular/angular.js/commit/4994acd26e582eec8a92b139bfc09ca79a9b8835),
|
||||
[#10342](https://github.com/angular/angular.js/issues/10342), [#12850](https://github.com/angular/angular.js/issues/12850))
|
||||
- **jqLite:** properly handle dash-delimited node names in `jqLiteBuildFragment`
|
||||
([cdd1227a](https://github.com/angular/angular.js/commit/cdd1227a308edd34d31b67f338083b6e0c4c0db9),
|
||||
[#10617](https://github.com/angular/angular.js/issues/10617), [#12759](https://github.com/angular/angular.js/issues/12759))
|
||||
- **ngAnimate:**
|
||||
- ensure anchoring uses body as a container when needed
|
||||
([9d3704ca](https://github.com/angular/angular.js/commit/9d3704ca467081f16b71b011eb50c53d5cdb2f34),
|
||||
[#12872](https://github.com/angular/angular.js/issues/12872))
|
||||
- callback detection should only use RAF when necessary
|
||||
([fa8c399f](https://github.com/angular/angular.js/commit/fa8c399fadc30b78710868fe59d2930fdc17c7a5))
|
||||
- **ngMessages:** prevent race condition with ngAnimate
|
||||
([7295c60f](https://github.com/angular/angular.js/commit/7295c60ffb9f2e4f32043c538ace740b187f565a),
|
||||
[#12856](https://github.com/angular/angular.js/issues/12856), [#12903](https://github.com/angular/angular.js/issues/12903))
|
||||
- **ngOptions:**
|
||||
- prevent frozen select ui in IE
|
||||
([dbc69851](https://github.com/angular/angular.js/commit/dbc698517ff620b3a6279f65d4a9b6e3c15087b9),
|
||||
[#11314](https://github.com/angular/angular.js/issues/11314), [#11795](https://github.com/angular/angular.js/issues/11795))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$animateCss:** add support for temporary styles via `cleanupStyles`
|
||||
([e52d731b](https://github.com/angular/angular.js/commit/e52d731bfd1fbb6c616125fbde2fb365722254b7),
|
||||
[#12930](https://github.com/angular/angular.js/issues/12930))
|
||||
- **$http:** add `$xhrFactory` service to enable creation of custom xhr objects
|
||||
([7a413df5](https://github.com/angular/angular.js/commit/7a413df5e47e04e20a1c93d35922050bbcbfb492),
|
||||
[#2318](https://github.com/angular/angular.js/issues/2318), [#9319](https://github.com/angular/angular.js/issues/9319), [#12159](https://github.com/angular/angular.js/issues/12159))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
|
||||
<a name="1.4.6"></a>
|
||||
# 1.4.6 multiplicative-elevation (2015-09-17)
|
||||
|
||||
@@ -170,8 +578,6 @@ the built-in pattern validator:
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.4.5"></a>
|
||||
# 1.4.5 permanent-internship (2015-08-28)
|
||||
|
||||
@@ -463,6 +869,43 @@ describe('$q.when', function() {
|
||||
});
|
||||
```
|
||||
|
||||
- **form:** Due to [94533e57](https://github.com/angular/angular.js/commit/94533e570673e6b2eb92073955541fa289aabe02),
|
||||
the `name` attribute of `form` elements can now only contain characters that can be evaluated as part
|
||||
of an Angular expression. This is because Angular uses the value of `name` as an assignable expression
|
||||
to set the form on the `$scope`. For example, `name="myForm"` assigns the form to `$scope.myForm` and
|
||||
`name="myObj.myForm"` assigns it to `$scope.myObj.myForm`.
|
||||
|
||||
Previously, it was possible to also use names such `name="my:name"`, because Angular used a special setter
|
||||
function for the form name. Now the general, more robust `$parse` setter is used.
|
||||
|
||||
The easiest way to migrate your code is therefore to remove all special characters from the `name` attribute.
|
||||
|
||||
If you need to keep the special characters, you can use the following directive, which will replace
|
||||
the `name` with a value that can be evaluated as an expression in the compile function, and then
|
||||
re-set the original name in the postLink function. This ensures that (1), the form is published on
|
||||
the scope, and (2), the form has the original name, which might be important if you are doing server-side
|
||||
form submission.
|
||||
|
||||
```js
|
||||
angular.module('myApp').directive('form', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
compile: function(element, attrs) {
|
||||
var unsupportedCharacter = ':'; // change accordingly
|
||||
var originalName = attrs.name;
|
||||
if (attrs.name && attrs.name.indexOf(unsupportedCharacter) > 0) {
|
||||
attrs.$set('name', 'this["' + originalName + '"]');
|
||||
}
|
||||
|
||||
return postLinkFunction(scope, element) {
|
||||
// Don't trigger $observers
|
||||
element.setAttribute('name', originalName);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
<a name="1.4.3"></a>
|
||||
# 1.4.3 foam-acceleration (2015-07-15)
|
||||
@@ -513,6 +956,44 @@ describe('$q.when', function() {
|
||||
[#11580](https://github.com/angular/angular.js/issues/11580), [#12234](https://github.com/angular/angular.js/issues/12234))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **ngInclude:** due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
|
||||
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
|
||||
|
||||
Before:
|
||||
```html
|
||||
<div ng-include="findTemplate('https://example.com/myTemplate.html')"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
$scope.findTemplate = function(templateName) {
|
||||
return $sce.trustAsResourceUrl(templateName);
|
||||
};
|
||||
```
|
||||
|
||||
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
|
||||
whitelist in the `config()` function:
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
var templateCache = {};
|
||||
$scope.findTemplate = function(templateName) {
|
||||
if (!templateCache[templateName]) {
|
||||
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
|
||||
}
|
||||
|
||||
return templateCache[templateName];
|
||||
};
|
||||
|
||||
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`:
|
||||
|
||||
angular.module('myApp', []).config(function($sceDelegateProvider) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/**'])
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<a name="1.3.17"></a>
|
||||
# 1.3.17 tsktskskly-euouae (2015-07-06)
|
||||
@@ -1340,7 +1821,6 @@ mechanism.
|
||||
|
||||
- **ngMessages:** due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
|
||||
|
||||
The `ngMessagesInclude` attribute is now its own directive and that must
|
||||
be placed as a **child** element within the element with the ngMessages
|
||||
directive. (Keep in mind that the former behaviour of the
|
||||
@@ -1363,6 +1843,26 @@ end of the container containing the ngMessages directive).
|
||||
</div>
|
||||
```
|
||||
|
||||
- **ngMessages:** due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
|
||||
it is no longer possible to use interpolation inside the `ngMessages` attribute expression. This technique
|
||||
is generally not recommended, and can easily break when a directive implementation changes. In cases
|
||||
where a simple expression is not possible, you can delegate accessing the object to a function:
|
||||
|
||||
```html
|
||||
<div ng-messages="ctrl.form['field_{{$index}}'].$error">...</div>
|
||||
```
|
||||
would become
|
||||
```html
|
||||
<div ng-messages="ctrl.getMessages($index)">...</div>
|
||||
```
|
||||
where `ctrl.getMessages()`
|
||||
```javascript
|
||||
ctrl.getMessages = function($index) {
|
||||
return ctrl.form['field_' + $index].$error;
|
||||
}
|
||||
```
|
||||
|
||||
- **$http:** due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
|
||||
|
||||
`transformRequest` functions can no longer modify request headers.
|
||||
@@ -1818,6 +2318,12 @@ But in practice this is not what people want and so this change iterates over pr
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
setting the ngOptions attribute expression after the element is compiled, will no longer trigger the ngOptions behavior.
|
||||
This worked previously because the ngOptions logic was part of the select directive, while
|
||||
it is now implemented in the ngOptions directive itself.
|
||||
|
||||
- **select:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
|
||||
the `select` directive will now use strict comparison of the `ngModel` scope value against `option`
|
||||
@@ -2420,7 +2926,36 @@ We also added a documentation page focused on security, which contains some of t
|
||||
[#9578](https://github.com/angular/angular.js/issues/9578), [#9751](https://github.com/angular/angular.js/issues/9751))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- **$observe:** Due to [531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
observers no longer register on undefined attributes. For example, if you were using `$observe` on
|
||||
an absent optional attribute to set a default value, the following would not work anymore:
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
```
|
||||
|
||||
```js
|
||||
// link function for directive myDir
|
||||
link: function(scope, element, attr) {
|
||||
attr.$observe('myAttr', function(newVal) {
|
||||
scope.myValue = newVal ? newVal : 'myDefaultValue';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Instead, check if the attribute is set before registering the observer:
|
||||
|
||||
```js
|
||||
link: function(scope, element, attr) {
|
||||
if (attr.myAttr) {
|
||||
// register the observer
|
||||
} else {
|
||||
// set the default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="1.3.0"></a>
|
||||
# 1.3.0 superluminal-nudge (2014-10-13)
|
||||
|
||||
+8
-2
@@ -71,7 +71,7 @@ chances of your issue being dealt with quickly:
|
||||
* **Angular Version(s)** - is it a regression?
|
||||
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
|
||||
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
|
||||
[JSFiddle][jsfiddle]) or an unambiguous set of steps.
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
@@ -123,13 +123,19 @@ Before you submit your pull request consider the following guidelines:
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular test suite to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
* Commit your changes to your branch (e.g. `my-fix-branch`).
|
||||
* Push the changes to your GitHub repository (this will update your Pull Request).
|
||||
|
||||
If the PR gets too outdated we may ask you to rebase and force push to update the PR:
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push origin my-fix-branch -f
|
||||
```
|
||||
|
||||
*WARNING. Squashing or reverting commits and forced push thereafter may remove GitHub comments
|
||||
on code that were previously made by you and others in your commits.*
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
#### After your pull request is merged
|
||||
|
||||
+14
-12
@@ -162,7 +162,7 @@ module.exports = function(grunt) {
|
||||
'!src/angular.bind.js' // we ignore this file since contains an early return statement
|
||||
],
|
||||
options: {
|
||||
config: ".jscsrc"
|
||||
config: '.jscsrc'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -231,9 +231,9 @@ module.exports = function(grunt) {
|
||||
dest: 'build/angular-aria.js',
|
||||
src: util.wrap(files['angularModules']['ngAria'], 'module')
|
||||
},
|
||||
"promises-aplus-adapter": {
|
||||
'promises-aplus-adapter': {
|
||||
dest:'tmp/promises-aplus-adapter++.js',
|
||||
src:['src/ng/q.js','lib/promises-aplus/promises-aplus-test-adapter.js']
|
||||
src:['src/ng/q.js', 'lib/promises-aplus/promises-aplus-test-adapter.js']
|
||||
}
|
||||
},
|
||||
|
||||
@@ -253,7 +253,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
|
||||
"ddescribe-iit": {
|
||||
'ddescribe-iit': {
|
||||
files: [
|
||||
'src/**/*.js',
|
||||
'test/**/*.js',
|
||||
@@ -274,7 +274,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
|
||||
"merge-conflict": {
|
||||
'merge-conflict': {
|
||||
files: [
|
||||
'src/**/*',
|
||||
'test/**/*',
|
||||
@@ -304,11 +304,11 @@ module.exports = function(grunt) {
|
||||
},
|
||||
|
||||
shell: {
|
||||
"npm-install": {
|
||||
command: path.normalize('scripts/npm/install-dependencies.sh')
|
||||
'npm-install': {
|
||||
command: 'node scripts/npm/check-node-modules.js'
|
||||
},
|
||||
|
||||
"promises-aplus-tests": {
|
||||
'promises-aplus-tests': {
|
||||
options: {
|
||||
stdout: false,
|
||||
stderr: true,
|
||||
@@ -339,8 +339,10 @@ module.exports = function(grunt) {
|
||||
grunt.task.run('shell:npm-install');
|
||||
}
|
||||
|
||||
|
||||
|
||||
//alias tasks
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package', 'test:unit', 'test:promises-aplus', 'tests:docs', 'test:protractor']);
|
||||
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
|
||||
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
|
||||
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
|
||||
@@ -350,11 +352,11 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
|
||||
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
|
||||
grunt.registerTask('test:e2e', 'Alias for test:protractor', ['test:protractor']);
|
||||
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter','shell:promises-aplus-tests']);
|
||||
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter', 'shell:promises-aplus-tests']);
|
||||
|
||||
grunt.registerTask('minify', ['bower','clean', 'build', 'minall']);
|
||||
grunt.registerTask('minify', ['bower', 'clean', 'build', 'minall']);
|
||||
grunt.registerTask('webserver', ['connect:devserver']);
|
||||
grunt.registerTask('package', ['bower','clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
|
||||
grunt.registerTask('package', ['bower', 'validate-angular-files', 'clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
|
||||
grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint', 'jscs']);
|
||||
grunt.registerTask('default', ['package']);
|
||||
};
|
||||
|
||||
@@ -18,10 +18,10 @@ piece of cake. Best of all?? It makes development fun!
|
||||
* Developer Guide: http://docs.angularjs.org/guide
|
||||
* Contribution guidelines: [CONTRIBUTING.md](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md)
|
||||
* Dashboard: http://dashboard.angularjs.org
|
||||
|
||||
|
||||
Building AngularJS
|
||||
---------
|
||||
[Once you have your environment setup](http://docs.angularjs.org/misc/contribute) just run:
|
||||
[Once you have your environment set up](http://docs.angularjs.org/misc/contribute) just run:
|
||||
|
||||
grunt package
|
||||
|
||||
@@ -43,3 +43,26 @@ To learn more about the grunt tasks, run `grunt --help` and also read our
|
||||
|
||||
[](https://github.com/igrigorik/ga-beacon)
|
||||
|
||||
What to Use AngularJS for and When to Use it
|
||||
---------
|
||||
AngularJS is the next generation framework where each component is designed to work with every other component in an interconnected way like a well-oiled machine. AngularJS is JavaScript MVC made easy and done right. (Well it is not really MVC, read on, to understand what this means.)
|
||||
|
||||
#### MVC, no, MV* done the right way!
|
||||
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-_Whatever_. The _Whatever_ is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
|
||||
|
||||
Unlike other frameworks in any programming language, where MVC, the three separate components, each one has to be written and then connected by the programmer, AngularJS helps the programmer by asking him/her to just create these and everything else will be taken care of by AngularJS.
|
||||
|
||||
#### Interconnection with HTML at the root level
|
||||
AngularJS uses HTML to define the user's interface. AngularJS also enables the programmer to write new HTML tags (AngularJS Directives) and increase the readability and understandability of the HTML code. Directives are AngularJS’s way of bringing additional functionality to HTML. Directives achieve this by enabling us to invent our own HTML elements. This also helps in making the code DRY (Don't Repeat Yourself), which means once created, a new directive can be used anywhere within the application.
|
||||
|
||||
#### Data Handling made simple
|
||||
Data and Data Models in AngularJS are plain JavaScript objects and one can add and change properties directly on it and loop over objects and arrays at will.
|
||||
|
||||
#### Two-way Data Binding
|
||||
One of AngularJS's strongest features. Two-way Data Binding means that if something changes in the Model, the change gets reflected in the View instantaneously, and the same happens the other way around. This is also referred to as Reactive Programming, i.e. suppose `a = b + c` is being programmed and after this, if the value of `b` and/or `c` is changed then the value of `a` will be automatically updated to reflect the change. AngularJS uses its "scopes" as a glue between the Model and View and makes these updates in one available for the other.
|
||||
|
||||
#### Less Written Code and Easily Maintable Code
|
||||
Everything in AngularJS is created to enable the programmer ends up writing less code that is easily maintainable and readable by any other new person on the team. Believe it or not, one can write a complete working two-way data binded application in less than 10 lines of code. Try and see for yourself!
|
||||
|
||||
#### Testing Ready
|
||||
AngularJS has Dependency Injection, i.e. it takes care of providing all the necessary dependencies to its controllers whenever required. This helps in making the AngularJS code ready for unit testing by making use of mock dependencies created and injected. This makes AngularJS more modular and easily testable thus in turn helping a team create more robust applications.
|
||||
|
||||
Vendored
+3
-3
@@ -14,6 +14,7 @@ var angularFiles = {
|
||||
|
||||
'src/ng/anchorScroll.js',
|
||||
'src/ng/animate.js',
|
||||
'src/ng/animateRunner.js',
|
||||
'src/ng/animateCss.js',
|
||||
'src/ng/browser.js',
|
||||
'src/ng/cacheFactory.js',
|
||||
@@ -33,6 +34,7 @@ var angularFiles = {
|
||||
'src/ng/q.js',
|
||||
'src/ng/raf.js',
|
||||
'src/ng/rootScope.js',
|
||||
'src/ng/rootElement.js',
|
||||
'src/ng/sanitizeUri.js',
|
||||
'src/ng/sce.js',
|
||||
'src/ng/sniffer.js',
|
||||
@@ -84,7 +86,7 @@ var angularFiles = {
|
||||
],
|
||||
|
||||
'angularLoader': [
|
||||
'stringify.js',
|
||||
'src/stringify.js',
|
||||
'src/minErr.js',
|
||||
'src/loader.js'
|
||||
],
|
||||
@@ -92,7 +94,6 @@ var angularFiles = {
|
||||
'angularModules': {
|
||||
'ngAnimate': [
|
||||
'src/ngAnimate/shared.js',
|
||||
'src/ngAnimate/body.js',
|
||||
'src/ngAnimate/rafScheduler.js',
|
||||
'src/ngAnimate/animateChildrenDirective.js',
|
||||
'src/ngAnimate/animateCss.js',
|
||||
@@ -100,7 +101,6 @@ var angularFiles = {
|
||||
'src/ngAnimate/animateJs.js',
|
||||
'src/ngAnimate/animateJsDriver.js',
|
||||
'src/ngAnimate/animateQueue.js',
|
||||
'src/ngAnimate/animateRunner.js',
|
||||
'src/ngAnimate/animation.js',
|
||||
'src/ngAnimate/module.js'
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "AngularJS",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"jquery": "2.1.1",
|
||||
"closure-compiler": "https://dl.google.com/closure-compiler/compiler-20140814.zip",
|
||||
|
||||
@@ -124,7 +124,7 @@ h1,h2,h3,h4,h5,h6 {
|
||||
font-size:1.2em;
|
||||
padding:0;
|
||||
margin:0;
|
||||
border-bottom:1px soild #aaa;
|
||||
border-bottom:1px solid #aaa;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
@@ -589,6 +589,12 @@ ul.events > li {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.table > tbody > tr.head > td,
|
||||
.table > tbody > tr.head > th {
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 769px) and (max-width: 991px) {
|
||||
.main-body-grid {
|
||||
margin-top: 160px;
|
||||
|
||||
-442
@@ -1,442 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var directive = {};
|
||||
|
||||
directive.runnableExample = ['$templateCache', '$document', function($templateCache, $document) {
|
||||
var exampleClassNameSelector = '.runnable-example-file';
|
||||
var doc = $document[0];
|
||||
var tpl =
|
||||
'<nav class="runnable-example-tabs" ng-if="tabs">' +
|
||||
' <a ng-class="{active:$index==activeTabIndex}"' +
|
||||
'ng-repeat="tab in tabs track by $index" ' +
|
||||
'href="" ' +
|
||||
'class="btn"' +
|
||||
'ng-click="setTab($index)">' +
|
||||
' {{ tab }}' +
|
||||
' </a>' +
|
||||
'</nav>';
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
scope : true,
|
||||
controller : ['$scope', function($scope) {
|
||||
$scope.setTab = function(index) {
|
||||
var tab = $scope.tabs[index];
|
||||
$scope.activeTabIndex = index;
|
||||
$scope.$broadcast('tabChange', index, tab);
|
||||
};
|
||||
}],
|
||||
compile : function(element) {
|
||||
element.html(tpl + element.html());
|
||||
return function(scope, element) {
|
||||
var node = element[0];
|
||||
var examples = node.querySelectorAll(exampleClassNameSelector);
|
||||
var tabs = [], now = Date.now();
|
||||
angular.forEach(examples, function(child, index) {
|
||||
tabs.push(child.getAttribute('name'));
|
||||
});
|
||||
|
||||
if(tabs.length > 0) {
|
||||
scope.tabs = tabs;
|
||||
scope.$on('tabChange', function(e, index, title) {
|
||||
angular.forEach(examples, function(child) {
|
||||
child.style.display = 'none';
|
||||
});
|
||||
var selected = examples[index];
|
||||
selected.style.display = 'block';
|
||||
});
|
||||
scope.setTab(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
directive.dropdownToggle =
|
||||
['$document', '$location', '$window',
|
||||
function ($document, $location, $window) {
|
||||
var openElement = null, close;
|
||||
return {
|
||||
restrict: 'C',
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
|
||||
close && close();
|
||||
});
|
||||
|
||||
element.parent().on('click', function(event) {
|
||||
close && close();
|
||||
});
|
||||
|
||||
element.on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var iWasOpen = false;
|
||||
|
||||
if (openElement) {
|
||||
iWasOpen = openElement === element;
|
||||
close();
|
||||
}
|
||||
|
||||
if (!iWasOpen){
|
||||
element.parent().addClass('open');
|
||||
openElement = element;
|
||||
|
||||
close = function (event) {
|
||||
event && event.preventDefault();
|
||||
event && event.stopPropagation();
|
||||
$document.off('click', close);
|
||||
element.parent().removeClass('open');
|
||||
close = null;
|
||||
openElement = null;
|
||||
}
|
||||
|
||||
$document.on('click', close);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}];
|
||||
|
||||
directive.syntax = function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
function makeLink(type, text, link, icon) {
|
||||
return '<a href="' + link + '" class="btn syntax-' + type + '" target="_blank" rel="nofollow">' +
|
||||
'<span class="' + icon + '"></span> ' + text +
|
||||
'</a>';
|
||||
};
|
||||
|
||||
var html = '';
|
||||
var types = {
|
||||
'github' : {
|
||||
text : 'View on Github',
|
||||
key : 'syntaxGithub',
|
||||
icon : 'icon-github'
|
||||
},
|
||||
'plunkr' : {
|
||||
text : 'View on Plunkr',
|
||||
key : 'syntaxPlunkr',
|
||||
icon : 'icon-arrow-down'
|
||||
},
|
||||
'jsfiddle' : {
|
||||
text : 'View on JSFiddle',
|
||||
key : 'syntaxFiddle',
|
||||
icon : 'icon-cloud'
|
||||
}
|
||||
};
|
||||
for(var type in types) {
|
||||
var data = types[type];
|
||||
var link = attrs[data.key];
|
||||
if(link) {
|
||||
html += makeLink(type, data.text, link, data.icon);
|
||||
}
|
||||
};
|
||||
|
||||
var nav = document.createElement('nav');
|
||||
nav.className = 'syntax-links';
|
||||
nav.innerHTML = html;
|
||||
|
||||
var node = element[0];
|
||||
var par = node.parentNode;
|
||||
par.insertBefore(nav, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
directive.tabbable = function() {
|
||||
return {
|
||||
restrict: 'C',
|
||||
compile: function(element) {
|
||||
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
|
||||
tabContent = angular.element('<div class="tab-content"></div>');
|
||||
|
||||
tabContent.append(element.contents());
|
||||
element.append(navTabs).append(tabContent);
|
||||
},
|
||||
controller: ['$scope', '$element', function($scope, $element) {
|
||||
var navTabs = $element.contents().eq(0),
|
||||
ngModel = $element.controller('ngModel') || {},
|
||||
tabs = [],
|
||||
selectedTab;
|
||||
|
||||
ngModel.$render = function() {
|
||||
var $viewValue = this.$viewValue;
|
||||
|
||||
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
|
||||
if(selectedTab) {
|
||||
selectedTab.paneElement.removeClass('active');
|
||||
selectedTab.tabElement.removeClass('active');
|
||||
selectedTab = null;
|
||||
}
|
||||
if($viewValue) {
|
||||
for(var i = 0, ii = tabs.length; i < ii; i++) {
|
||||
if ($viewValue == tabs[i].value) {
|
||||
selectedTab = tabs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selectedTab) {
|
||||
selectedTab.paneElement.addClass('active');
|
||||
selectedTab.tabElement.addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.addPane = function(element, attr) {
|
||||
var li = angular.element('<li><a href></a></li>'),
|
||||
a = li.find('a'),
|
||||
tab = {
|
||||
paneElement: element,
|
||||
paneAttrs: attr,
|
||||
tabElement: li
|
||||
};
|
||||
|
||||
tabs.push(tab);
|
||||
|
||||
attr.$observe('value', update)();
|
||||
attr.$observe('title', function(){ update(); a.text(tab.title); })();
|
||||
|
||||
function update() {
|
||||
tab.title = attr.title;
|
||||
tab.value = attr.value || attr.title;
|
||||
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
|
||||
// we are not part of angular
|
||||
ngModel.$viewValue = tab.value;
|
||||
}
|
||||
ngModel.$render();
|
||||
}
|
||||
|
||||
navTabs.append(li);
|
||||
li.on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (ngModel.$setViewValue) {
|
||||
$scope.$apply(function() {
|
||||
ngModel.$setViewValue(tab.value);
|
||||
ngModel.$render();
|
||||
});
|
||||
} else {
|
||||
// we are not part of angular
|
||||
ngModel.$viewValue = tab.value;
|
||||
ngModel.$render();
|
||||
}
|
||||
});
|
||||
|
||||
return function() {
|
||||
tab.tabElement.remove();
|
||||
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
|
||||
if (tab == tabs[i]) {
|
||||
tabs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
directive.table = function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
if (!attrs['class']) {
|
||||
element.addClass('table table-bordered table-striped code-table');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var popoverElement = function() {
|
||||
var object = {
|
||||
init : function() {
|
||||
this.element = angular.element(
|
||||
'<div class="popover popover-incode top">' +
|
||||
'<div class="arrow"></div>' +
|
||||
'<div class="popover-inner">' +
|
||||
'<div class="popover-title"><code></code></div>' +
|
||||
'<div class="popover-content"></div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
this.node = this.element[0];
|
||||
this.element.css({
|
||||
'display':'block',
|
||||
'position':'absolute'
|
||||
});
|
||||
angular.element(document.body).append(this.element);
|
||||
|
||||
var inner = this.element.children()[1];
|
||||
this.titleElement = angular.element(inner.childNodes[0].firstChild);
|
||||
this.contentElement = angular.element(inner.childNodes[1]);
|
||||
|
||||
//stop the click on the tooltip
|
||||
this.element.on('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
var self = this;
|
||||
angular.element(document.body).on('click',function(event) {
|
||||
if(self.visible()) self.hide();
|
||||
});
|
||||
},
|
||||
|
||||
show : function(x,y) {
|
||||
this.element.addClass('visible');
|
||||
this.position(x || 0, y || 0);
|
||||
},
|
||||
|
||||
hide : function() {
|
||||
this.element.removeClass('visible');
|
||||
this.position(-9999,-9999);
|
||||
},
|
||||
|
||||
visible : function() {
|
||||
return this.position().y >= 0;
|
||||
},
|
||||
|
||||
isSituatedAt : function(element) {
|
||||
return this.besideElement ? element[0] == this.besideElement[0] : false;
|
||||
},
|
||||
|
||||
title : function(value) {
|
||||
return this.titleElement.html(value);
|
||||
},
|
||||
|
||||
content : function(value) {
|
||||
if(value && value.length > 0) {
|
||||
value = marked(value);
|
||||
}
|
||||
return this.contentElement.html(value);
|
||||
},
|
||||
|
||||
positionArrow : function(position) {
|
||||
this.node.className = 'popover ' + position;
|
||||
},
|
||||
|
||||
positionAway : function() {
|
||||
this.besideElement = null;
|
||||
this.hide();
|
||||
},
|
||||
|
||||
positionBeside : function(element) {
|
||||
this.besideElement = element;
|
||||
|
||||
var elm = element[0];
|
||||
var x = elm.offsetLeft;
|
||||
var y = elm.offsetTop;
|
||||
x -= 30;
|
||||
y -= this.node.offsetHeight + 10;
|
||||
this.show(x,y);
|
||||
},
|
||||
|
||||
position : function(x,y) {
|
||||
if(x != null && y != null) {
|
||||
this.element.css('left',x + 'px');
|
||||
this.element.css('top', y + 'px');
|
||||
}
|
||||
else {
|
||||
return {
|
||||
x : this.node.offsetLeft,
|
||||
y : this.node.offsetTop
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
object.init();
|
||||
object.hide();
|
||||
|
||||
return object;
|
||||
};
|
||||
|
||||
directive.popover = ['popoverElement', function(popover) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority : 500,
|
||||
link: function(scope, element, attrs) {
|
||||
element.on('click',function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if(popover.isSituatedAt(element) && popover.visible()) {
|
||||
popover.title('');
|
||||
popover.content('');
|
||||
popover.positionAway();
|
||||
}
|
||||
else {
|
||||
popover.title(attrs.title);
|
||||
popover.content(attrs.content);
|
||||
popover.positionBeside(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
directive.tabPane = function() {
|
||||
return {
|
||||
require: '^tabbable',
|
||||
restrict: 'C',
|
||||
link: function(scope, element, attrs, tabsCtrl) {
|
||||
element.on('$remove', tabsCtrl.addPane(element, attrs));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
directive.foldout = ['$http', '$animate','$window', function($http, $animate, $window) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority : 500,
|
||||
link: function(scope, element, attrs) {
|
||||
var container, loading, url = attrs.url;
|
||||
if(/\/build\//.test($window.location.href)) {
|
||||
url = '/build/docs' + url;
|
||||
}
|
||||
element.on('click',function() {
|
||||
scope.$apply(function() {
|
||||
if(!container) {
|
||||
if(loading) return;
|
||||
|
||||
loading = true;
|
||||
var par = element.parent();
|
||||
container = angular.element('<div class="foldout">loading...</div>');
|
||||
$animate.enter(container, null, par);
|
||||
|
||||
$http.get(url, { cache : true }).success(function(html) {
|
||||
loading = false;
|
||||
|
||||
html = '<div class="foldout-inner">' +
|
||||
'<div calss="foldout-arrow"></div>' +
|
||||
html +
|
||||
'</div>';
|
||||
container.html(html);
|
||||
|
||||
//avoid showing the element if the user has already closed it
|
||||
if(container.css('display') == 'block') {
|
||||
container.css('display','none');
|
||||
$animate.addClass(container, 'ng-hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
container.hasClass('ng-hide') ? $animate.removeClass(container, 'ng-hide') : $animate.addClass(container, 'ng-hide');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
angular.module('bootstrap', [])
|
||||
.directive(directive)
|
||||
.factory('popoverElement', popoverElement)
|
||||
.run(function() {
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
tables: true
|
||||
});
|
||||
});
|
||||
+3
-1
@@ -54,7 +54,9 @@ angular.module('ui.bootstrap.dropdown', [])
|
||||
}
|
||||
};
|
||||
|
||||
var closeDropdown = function() {
|
||||
var closeDropdown = function(evt) {
|
||||
if (evt && evt.which === 3) return;
|
||||
|
||||
openScope.$apply(function() {
|
||||
openScope.isOpen = false;
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ angular.module('docsApp', [
|
||||
'search',
|
||||
'tutorials',
|
||||
'versions',
|
||||
'bootstrap',
|
||||
'ui.bootstrap.dropdown'
|
||||
])
|
||||
|
||||
|
||||
@@ -34,4 +34,15 @@ angular.module('directives', [])
|
||||
return function(scope, element) {
|
||||
$anchorScroll.yOffset = element;
|
||||
};
|
||||
}]);
|
||||
}])
|
||||
|
||||
.directive('table', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, element, attrs) {
|
||||
if (!attrs['class']) {
|
||||
element.addClass('table table-bordered table-striped code-table');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+10
-3
@@ -13,10 +13,10 @@ angular.module('errors', ['ngSanitize'])
|
||||
};
|
||||
|
||||
return function (text, target) {
|
||||
var targetHtml = target ? ' target="' + target + '"' : '';
|
||||
|
||||
if (!text) return text;
|
||||
|
||||
var targetHtml = target ? ' target="' + target + '"' : '';
|
||||
|
||||
return $sanitize(text.replace(LINKY_URL_REGEXP, function (url) {
|
||||
if (STACK_TRACE_REGEXP.test(url)) {
|
||||
return url;
|
||||
@@ -34,6 +34,10 @@ angular.module('errors', ['ngSanitize'])
|
||||
|
||||
|
||||
.directive('errorDisplay', ['$location', 'errorLinkFilter', function ($location, errorLinkFilter) {
|
||||
var encodeAngleBrackets = function (text) {
|
||||
return text.replace(/</g, '<').replace(/>/g, '>');
|
||||
};
|
||||
|
||||
var interpolate = function (formatString) {
|
||||
var formatArgs = arguments;
|
||||
return formatString.replace(/\{\d+\}/g, function (match) {
|
||||
@@ -51,12 +55,15 @@ angular.module('errors', ['ngSanitize'])
|
||||
link: function (scope, element, attrs) {
|
||||
var search = $location.search(),
|
||||
formatArgs = [attrs.errorDisplay],
|
||||
formattedText,
|
||||
i;
|
||||
|
||||
for (i = 0; angular.isDefined(search['p'+i]); i++) {
|
||||
formatArgs.push(search['p'+i]);
|
||||
}
|
||||
element.html(errorLinkFilter(interpolate.apply(null, formatArgs), '_blank'));
|
||||
|
||||
formattedText = encodeAngleBrackets(interpolate.apply(null, formatArgs));
|
||||
element.html(errorLinkFilter(formattedText, '_blank'));
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
angular.module('examples', [])
|
||||
|
||||
.directive('runnableExample', ['$templateCache', '$document', function($templateCache, $document) {
|
||||
var exampleClassNameSelector = '.runnable-example-file';
|
||||
var doc = $document[0];
|
||||
var tpl =
|
||||
'<nav class="runnable-example-tabs" ng-if="tabs">' +
|
||||
' <a ng-class="{active:$index==activeTabIndex}"' +
|
||||
'ng-repeat="tab in tabs track by $index" ' +
|
||||
'href="" ' +
|
||||
'class="btn"' +
|
||||
'ng-click="setTab($index)">' +
|
||||
' {{ tab }}' +
|
||||
' </a>' +
|
||||
'</nav>';
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
scope : true,
|
||||
controller : ['$scope', function($scope) {
|
||||
$scope.setTab = function(index) {
|
||||
var tab = $scope.tabs[index];
|
||||
$scope.activeTabIndex = index;
|
||||
$scope.$broadcast('tabChange', index, tab);
|
||||
};
|
||||
}],
|
||||
compile : function(element) {
|
||||
element.html(tpl + element.html());
|
||||
return function(scope, element) {
|
||||
var node = element[0];
|
||||
var examples = node.querySelectorAll(exampleClassNameSelector);
|
||||
var tabs = [], now = Date.now();
|
||||
angular.forEach(examples, function(child, index) {
|
||||
tabs.push(child.getAttribute('name'));
|
||||
});
|
||||
|
||||
if(tabs.length > 0) {
|
||||
scope.tabs = tabs;
|
||||
scope.$on('tabChange', function(e, index, title) {
|
||||
angular.forEach(examples, function(child) {
|
||||
child.style.display = 'none';
|
||||
});
|
||||
var selected = examples[index];
|
||||
selected.style.display = 'block';
|
||||
});
|
||||
scope.setTab(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('formPostData', ['$document', function($document) {
|
||||
return function(url, newWindow, fields) {
|
||||
/**
|
||||
|
||||
@@ -11,8 +11,7 @@ angular.module('tutorials', [])
|
||||
scope: {},
|
||||
template:
|
||||
'<a ng-href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
|
||||
'<a ng-href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/1.4-step-{{diffLo}}...1.4-step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
|
||||
'<a ng-href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>',
|
||||
link: function(scope, element, attrs) {
|
||||
var seq = 1 * attrs.docTutorialNav;
|
||||
@@ -38,12 +37,11 @@ angular.module('tutorials', [])
|
||||
'<p><button class="btn" ng-click="show=!show">Workspace Reset Instructions ➤</button></p>\n' +
|
||||
'<div class="alert alert-info" ng-show="show">\n' +
|
||||
' <p>Reset the workspace to step {{step}}.</p>' +
|
||||
' <p><pre>git checkout -f step-{{step}}</pre></p>\n' +
|
||||
' <p>Refresh your browser or check out this step online: '+
|
||||
'<a href="http://angular.github.io/angular-phonecat/step-{{step}}/app">Step {{step}} Live Demo</a>.</p>\n' +
|
||||
' <p><pre>git checkout -f 1.4-step-{{step}}</pre></p>\n' +
|
||||
' <p>Refresh your browser to see the changes.</p>\n' +
|
||||
'</div>\n' +
|
||||
'<p>The most important changes are listed below. You can see the full diff on ' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}" title="See diff on Github">GitHub</a>\n' +
|
||||
'<a ng-href="https://github.com/angular/angular-phonecat/compare/1.4-step-{{step ? (step - 1) : \'0~1\'}}...1.4-step-{{step}}" title="See diff on Github">GitHub</a>\n' +
|
||||
'</p>'
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../../.jshintrc-base",
|
||||
"browser": true,
|
||||
"globals": {
|
||||
// AngularJS
|
||||
"angular": false,
|
||||
|
||||
// ngMocks
|
||||
"module": false,
|
||||
"inject": true,
|
||||
|
||||
// Jasmine
|
||||
"jasmine": false,
|
||||
"describe": false,
|
||||
"ddescribe": false,
|
||||
"xdescribe": false,
|
||||
"it": false,
|
||||
"iit": false,
|
||||
"xit": false,
|
||||
"beforeEach": false,
|
||||
"afterEach": false,
|
||||
"spyOn": false,
|
||||
"expect": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
'use strict';
|
||||
|
||||
describe('errors', function() {
|
||||
// Mock `ngSanitize` module
|
||||
angular.
|
||||
module('ngSanitize', []).
|
||||
value('$sanitize', jasmine.createSpy('$sanitize').andCallFake(angular.identity));
|
||||
|
||||
beforeEach(module('errors'));
|
||||
|
||||
|
||||
describe('errorDisplay', function() {
|
||||
var $sanitize;
|
||||
var errorLinkFilter;
|
||||
|
||||
beforeEach(inject(function(_$sanitize_, _errorLinkFilter_) {
|
||||
$sanitize = _$sanitize_;
|
||||
errorLinkFilter = _errorLinkFilter_;
|
||||
}));
|
||||
|
||||
|
||||
it('should return empty input unchanged', function() {
|
||||
var inputs = [undefined, null, false, 0, ''];
|
||||
var remaining = inputs.length;
|
||||
|
||||
inputs.forEach(function(falsyValue) {
|
||||
expect(errorLinkFilter(falsyValue)).toBe(falsyValue);
|
||||
remaining--;
|
||||
});
|
||||
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should recognize URLs and convert them to `<a>`', function() {
|
||||
var urls = [
|
||||
['ftp://foo/bar?baz#qux'],
|
||||
['http://foo/bar?baz#qux'],
|
||||
['https://foo/bar?baz#qux'],
|
||||
['mailto:foo_bar@baz.qux', null, 'foo_bar@baz.qux'],
|
||||
['foo_bar@baz.qux', 'mailto:foo_bar@baz.qux', 'foo_bar@baz.qux']
|
||||
];
|
||||
var remaining = urls.length;
|
||||
|
||||
urls.forEach(function(values) {
|
||||
var actualUrl = values[0];
|
||||
var expectedUrl = values[1] || actualUrl;
|
||||
var expectedText = values[2] || expectedUrl;
|
||||
var anchor = '<a href="' + expectedUrl + '">' + expectedText + '</a>';
|
||||
|
||||
var input = 'start ' + actualUrl + ' end';
|
||||
var output = 'start ' + anchor + ' end';
|
||||
|
||||
expect(errorLinkFilter(input)).toBe(output);
|
||||
remaining--;
|
||||
});
|
||||
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should not recognize stack-traces as URLs', function() {
|
||||
var urls = [
|
||||
'ftp://foo/bar?baz#qux:4:2',
|
||||
'http://foo/bar?baz#qux:4:2',
|
||||
'https://foo/bar?baz#qux:4:2',
|
||||
'mailto:foo_bar@baz.qux:4:2',
|
||||
'foo_bar@baz.qux:4:2'
|
||||
];
|
||||
var remaining = urls.length;
|
||||
|
||||
urls.forEach(function(url) {
|
||||
var input = 'start ' + url + ' end';
|
||||
|
||||
expect(errorLinkFilter(input)).toBe(input);
|
||||
remaining--;
|
||||
});
|
||||
|
||||
expect(remaining).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it('should should set `[target]` if specified', function() {
|
||||
var url = 'https://foo/bar?baz#qux';
|
||||
var target = '_blank';
|
||||
var outputWithoutTarget = '<a href="' + url + '">' + url + '</a>';
|
||||
var outputWithTarget = '<a target="' + target + '" href="' + url + '">' + url + '</a>';
|
||||
|
||||
expect(errorLinkFilter(url)).toBe(outputWithoutTarget);
|
||||
expect(errorLinkFilter(url, target)).toBe(outputWithTarget);
|
||||
});
|
||||
|
||||
|
||||
it('should truncate the contents of the generated `<a>` to 60 characters', function() {
|
||||
var looongUrl = 'https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo';
|
||||
var truncatedUrl = 'https://foooooooooooooooooooooooooooooooooooooooooooooooo...';
|
||||
var output = '<a href="' + looongUrl + '">' + truncatedUrl + '</a>';
|
||||
|
||||
expect(looongUrl.length).toBeGreaterThan(60);
|
||||
expect(truncatedUrl.length).toBe(60);
|
||||
expect(errorLinkFilter(looongUrl)).toBe(output);
|
||||
});
|
||||
|
||||
|
||||
it('should pass the final string through `$sanitize`', function() {
|
||||
$sanitize.reset();
|
||||
|
||||
var input = 'start https://foo/bar?baz#qux end';
|
||||
var output = errorLinkFilter(input);
|
||||
|
||||
expect($sanitize.callCount).toBe(1);
|
||||
expect($sanitize).toHaveBeenCalledWith(output);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('errorDisplay', function() {
|
||||
var $compile;
|
||||
var $location;
|
||||
var $rootScope;
|
||||
var errorLinkFilter;
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.decorator('errorLinkFilter', function() {
|
||||
errorLinkFilter = jasmine.createSpy('errorLinkFilter');
|
||||
errorLinkFilter.andCallFake(angular.identity);
|
||||
|
||||
return errorLinkFilter;
|
||||
});
|
||||
}));
|
||||
beforeEach(inject(function(_$compile_, _$location_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$location = _$location_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
|
||||
it('should set the element\s HTML', function() {
|
||||
var elem = $compile('<span error-display="bar">foo</span>')($rootScope);
|
||||
expect(elem.html()).toBe('bar');
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate the contents against `$location.search()`', function() {
|
||||
spyOn($location, 'search').andReturn({p0: 'foo', p1: 'bar'});
|
||||
|
||||
var elem = $compile('<span error-display="foo = {0}, bar = {1}"></span>')($rootScope);
|
||||
expect(elem.html()).toBe('foo = foo, bar = bar');
|
||||
});
|
||||
|
||||
|
||||
it('should pass the interpolated text through `errorLinkFilter`', function() {
|
||||
$location.search = jasmine.createSpy('search').andReturn({p0: 'foo'});
|
||||
|
||||
var elem = $compile('<span error-display="foo = {0}"></span>')($rootScope);
|
||||
expect(errorLinkFilter.callCount).toBe(1);
|
||||
expect(errorLinkFilter).toHaveBeenCalledWith('foo = foo', '_blank');
|
||||
});
|
||||
|
||||
|
||||
it('should encode `<` and `>`', function() {
|
||||
var elem = $compile('<span error-display="<xyz>"></span>')($rootScope);
|
||||
expect(elem.text()).toBe('<xyz>');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -170,4 +170,8 @@ module.exports = new Package('angularjs', [
|
||||
jqueryDeployment,
|
||||
productionDeployment
|
||||
];
|
||||
})
|
||||
|
||||
.config(function(generateKeywordsProcessor) {
|
||||
generateKeywordsProcessor.docTypesToIgnore = ['componentGroup'];
|
||||
});
|
||||
|
||||
@@ -16,9 +16,11 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
ignoreWordsFile: undefined,
|
||||
areasToSearch: ['api', 'guide', 'misc', 'error', 'tutorial'],
|
||||
propertiesToIgnore: [],
|
||||
docTypesToIgnore: [],
|
||||
$validate: {
|
||||
ignoreWordsFile: { },
|
||||
areasToSearch: { presence: true },
|
||||
docTypesToIgnore: { },
|
||||
propertiesToIgnore: { }
|
||||
},
|
||||
$runAfter: ['memberDocsProcessor'],
|
||||
@@ -28,6 +30,7 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
// Keywords to ignore
|
||||
var wordsToIgnore = [];
|
||||
var propertiesToIgnore;
|
||||
var docTypesToIgnore;
|
||||
var areasToSearch;
|
||||
|
||||
// Keywords start with "ng:" or one of $, _ or a letter
|
||||
@@ -47,6 +50,8 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
areasToSearch = _.indexBy(this.areasToSearch);
|
||||
propertiesToIgnore = _.indexBy(this.propertiesToIgnore);
|
||||
log.debug('Properties to ignore', propertiesToIgnore);
|
||||
docTypesToIgnore = _.indexBy(this.docTypesToIgnore);
|
||||
log.debug('Doc types to ignore', docTypesToIgnore);
|
||||
|
||||
var ignoreWordsMap = _.indexBy(wordsToIgnore);
|
||||
|
||||
@@ -78,34 +83,36 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
|
||||
// We are only interested in docs that live in the right area
|
||||
docs = _.filter(docs, function(doc) { return areasToSearch[doc.area]; });
|
||||
docs = _.filter(docs, function(doc) { return !docTypesToIgnore[doc.docType]; });
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
|
||||
var words = [];
|
||||
var keywordMap = _.clone(ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
// Search each top level property of the document for search terms
|
||||
_.forEach(doc, function(value, key) {
|
||||
var words = [];
|
||||
var keywordMap = _.clone(ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
if ( _.isString(value) && !propertiesToIgnore[key] ) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
// Search each top level property of the document for search terms
|
||||
_.forEach(doc, function(value, key) {
|
||||
|
||||
if ( key === 'methods' || key === 'properties' || key === 'events' ) {
|
||||
_.forEach(value, function(member) {
|
||||
extractWords(member.name, members, membersMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
if ( _.isString(value) && !propertiesToIgnore[key] ) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
|
||||
if ( key === 'methods' || key === 'properties' || key === 'events' ) {
|
||||
_.forEach(value, function(member) {
|
||||
extractWords(member.name, members, membersMap);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: _.sortBy(words).join(' '),
|
||||
members: _.sortBy(members).join(' ')
|
||||
};
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: _.sortBy(words).join(' '),
|
||||
members: _.sortBy(members).join(' ')
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ module.exports = function debugDeployment(getVersion) {
|
||||
'../angular-touch.js',
|
||||
'../angular-animate.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -18,7 +18,6 @@ module.exports = function defaultDeployment(getVersion) {
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ module.exports = function jqueryDeployment(getVersion) {
|
||||
'../angular-touch.min.js',
|
||||
'../angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -21,7 +21,6 @@ module.exports = function productionDeployment(getVersion) {
|
||||
cdnUrl + '/angular-touch.min.js',
|
||||
cdnUrl + '/angular-animate.min.js',
|
||||
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
|
||||
'js/angular-bootstrap/bootstrap.min.js',
|
||||
'js/angular-bootstrap/dropdown-toggle.min.js',
|
||||
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
|
||||
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{# Be aware that we need these extra new lines here or marked will not realise that the <div>
|
||||
{# Be aware that we need these extra new lines here or marked will not realize that the <div>
|
||||
is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
<div>
|
||||
@@ -24,5 +24,5 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Be aware that we need these extra new lines here or marked will not realise that the <div>
|
||||
{# Be aware that we need these extra new lines here or marked will not realize that the <div>
|
||||
above is HTML and wrap each line in a <p> - thus breaking the HTML #}
|
||||
|
||||
@@ -8,7 +8,7 @@ but the required directive controller is not present on the current DOM element
|
||||
|
||||
To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element.
|
||||
|
||||
If the required controller is expected to be on a ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
If the required controller is expected to be on an ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
|
||||
|
||||
If the required controller is optionally requested, use `?` or `^?` to specify that.
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ myModule.directive('directiveName', function factory() {
|
||||
scope: {
|
||||
'attrName': '@', // OK
|
||||
'attrName2': '=localName', // OK
|
||||
'attrName3': 'name', // ERROR: missing mode @&=
|
||||
'attrName4': ' = name', // ERROR: extra spaces
|
||||
'attrName5': 'name=', // ERROR: must be prefixed with @&=
|
||||
'attrName3': '&?localName', // OK
|
||||
'attrName4': ' = name', // OK
|
||||
'attrName5': 'name', // ERROR: missing mode @&=
|
||||
'attrName6': 'name=', // ERROR: must be prefixed with @&=
|
||||
'attrName7': '=name?', // ERROR: ? must come directly after the mode
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
This error occurs when a module fails to load due to some exception. The error
|
||||
message above should provide additional context.
|
||||
|
||||
A common reason why the module fails to load is that you've forgotten to
|
||||
include the file with the defined module or that the file couldn't be loaded.
|
||||
|
||||
### Using `ngRoute`
|
||||
|
||||
In AngularJS `1.2.0` and later, `ngRoute` has been moved to its own module.
|
||||
@@ -24,4 +27,4 @@ angular.module('ng').filter('tel', function (){});
|
||||
|
||||
Instead create your own module and add it as a dependency to your application's top-level module.
|
||||
See [#9692](https://github.com/angular/angular.js/issues/9692) and
|
||||
[#7709](https://github.com/angular/angular.js/issues/7709) for more information
|
||||
[#7709](https://github.com/angular/angular.js/issues/7709) for more information
|
||||
|
||||
@@ -81,3 +81,6 @@ angular.module('myModule', [])
|
||||
// a scope object cannot be injected into a service.
|
||||
}]);
|
||||
```
|
||||
|
||||
If you encounter this error only with minified code, consider using `ngStrictDi` (see
|
||||
{@link ng.directive:ngApp ngApp}) to provoke the error with the non-minified source.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@ngdoc error
|
||||
@name $location:nobase
|
||||
@fullName $location in HTML5 mode requires a <base> tag to be present!
|
||||
@fullName $location in HTML5 mode requires a `<base>` tag to be present!
|
||||
@description
|
||||
|
||||
If you configure {@link ng.$location `$location`} to use
|
||||
@@ -15,7 +15,7 @@ $locationProvider.html5Mode({
|
||||
});
|
||||
```
|
||||
|
||||
Note that removing the requirement for a <base> tag will have adverse side effects when resolving
|
||||
Note that removing the requirement for a `<base>` tag will have adverse side effects when resolving
|
||||
relative paths with `$location` in IE9.
|
||||
|
||||
The base URL is then used to resolve all relative URLs throughout the application regardless of the
|
||||
|
||||
@@ -100,7 +100,7 @@ To resolve this type of issue, either fix the api to be always synchronous or as
|
||||
your callback handler to always run asynchronously by using the `$timeout` service.
|
||||
|
||||
```
|
||||
function MyController($scope, thirdPartyComponent) {
|
||||
function MyController($scope, $timeout, thirdPartyComponent) {
|
||||
thirdPartyComponent.getData(function(someData) {
|
||||
$timeout(function() {
|
||||
$scope.someData = someData;
|
||||
@@ -161,7 +161,7 @@ In this second scenario, we are already inside a `$digest` when the ngFocus dire
|
||||
call to `$apply()`, causing this error to be thrown.
|
||||
|
||||
It is possible to workaround this problem by moving the call to set the focus outside of the digest,
|
||||
by using `$timeout(fn, 0, false)`, where the `false` value tells Angular not to wrap this `fn` in a
|
||||
by using `$timeout(fn, 0, false)`, where the `false` value tells Angular not to wrap this `fn` in an
|
||||
`$apply` block:
|
||||
|
||||
```
|
||||
@@ -200,7 +200,7 @@ the top of the call stack.
|
||||
Once you have identified this call you work your way up the stack to see what the problem is.
|
||||
|
||||
* If the second call was made in your application code then you should look at why this code has been
|
||||
called from within a `$apply`/`$digest`. It may be a simple oversight or maybe it fits with the
|
||||
called from within an `$apply`/`$digest`. It may be a simple oversight or maybe it fits with the
|
||||
sync/async scenario described earlier.
|
||||
|
||||
* If the second call was made inside an Angular directive then it is likely that it matches the second
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
@ngdoc error
|
||||
@name ngModel:nopromise
|
||||
@fullName No promise
|
||||
@description
|
||||
|
||||
The return value of an async validator, must always be a promise. If you want to return a
|
||||
non-promise value, you can convert it to a promise using {@link ng.$q#resolve `$q.resolve()`} or
|
||||
{@link ng.$q#reject `$q.reject()`}.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
.directive('asyncValidator', function($q) {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, elem, attrs, ngModel) {
|
||||
ngModel.$asyncValidators.myAsyncValidation = function(modelValue, viewValue) {
|
||||
if (/* I don't need to hit the backend API */) {
|
||||
return $q.resolve(); // to mark as valid or
|
||||
// return $q.reject(); // to mark as invalid
|
||||
} else {
|
||||
// ...send a request to the backend and return a promise
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})
|
||||
```
|
||||
@@ -330,8 +330,8 @@ reload to the original link.
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url
|
||||
base in the head of your main html file (`<base href="/my-base">`) unless `html5Mode.requireBase` is
|
||||
set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
|
||||
base in the head of your main html file (`<base href="/my-base/index.html">`) unless `html5Mode.requireBase`
|
||||
is set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
|
||||
that, relative urls will always be resolved to this base url, even if the initial url of the
|
||||
document was different.
|
||||
|
||||
@@ -339,6 +339,7 @@ There is one exception: Links that only contain a hash fragment (e.g. `<a href="
|
||||
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling
|
||||
to anchors on the same page without needing to know on which page the user currently is.
|
||||
|
||||
|
||||
### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
@@ -346,6 +347,20 @@ to entry point of your application (e.g. index.html). Requiring a `<base>` tag i
|
||||
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 handled by the application.
|
||||
|
||||
### Base href constraints
|
||||
|
||||
The `$location` service is not able to function properly if the current URL is outside the URL given
|
||||
as the base href. This can have subtle confusing consequences...
|
||||
|
||||
Consider a base href set as follows: `<base href="/base/">` (i.e. the application exists in the "folder"
|
||||
called `/base`). The URL `/base` is actually outside the application (it refers to the `base` file found
|
||||
in the root `/` folder).
|
||||
|
||||
If you wish to be able to navigate to the application via a URL such as `/base` then you should ensure that
|
||||
you server is setup to redirect such requests to `/base/`.
|
||||
|
||||
See https://github.com/angular/angular.js/issues/14018 for more information.
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in
|
||||
@@ -356,15 +371,15 @@ legacy browsers and hashbang links in modern browser:
|
||||
|
||||
### Example
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents the address bar of the browser.
|
||||
Here you can see two `$location` instances that show the difference between **Html5 mode** and **Html5 Fallback mode**.
|
||||
Note that to simulate different levels of browser support, the `$location` instances are connected to
|
||||
a fakeBrowser service, which you don't have to set up in actual projects.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
Note that when you type hashbang url into the first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
= on page reload.
|
||||
|
||||
In these examples we use `<base href="/base/index.html" />`
|
||||
In these examples we use `<base href="/base/index.html" />`. The inputs represent the address bar of the browser.
|
||||
|
||||
#### Browser in HTML5 mode
|
||||
<example module="html5-mode" name="location-html5-mode">
|
||||
@@ -389,6 +404,7 @@ In these examples we use `<base href="/base/index.html" />`
|
||||
<file name="app.js">
|
||||
angular.module('html5-mode', ['fake-browser', 'address-bar'])
|
||||
|
||||
// Configure the fakeBrowser. Do not set these values in actual projects.
|
||||
.constant('initUrl', 'http://www.example.com/base/path?a=b#h')
|
||||
.constant('baseHref', '/base/index.html')
|
||||
.value('$sniffer', { history: true })
|
||||
@@ -538,6 +554,7 @@ In these examples we use `<base href="/base/index.html" />`
|
||||
<file name="app.js">
|
||||
angular.module('hashbang-mode', ['fake-browser', 'address-bar'])
|
||||
|
||||
// Configure the fakeBrowser. Do not set these values in actual projects.
|
||||
.constant('initUrl', 'http://www.example.com/base/index.html#!/path?a=b#h')
|
||||
.constant('baseHref', '/base/index.html')
|
||||
.value('$sniffer', { history: false })
|
||||
@@ -769,8 +786,8 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Navigation outside the app</td>
|
||||
<td>Use lower level API</td>
|
||||
<th>Navigation outside the app</td>
|
||||
<th>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -784,8 +801,8 @@ then uses the information it obtains to compose hashbang URLs (such as
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Read access</td>
|
||||
<td>Change to</td>
|
||||
<th>Read access</td>
|
||||
<th>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
@@ -41,7 +41,7 @@ Currently, ngAria interfaces with the following directives:
|
||||
|
||||
<h2 id="ngmodel">ngModel</h2>
|
||||
|
||||
Much of ngAria's heavy lifting happens in the {@link ngModel ngModel}
|
||||
Much of ngAria's heavy lifting happens in the {@link ng.directive:ngModel ngModel}
|
||||
directive. For elements using ngModel, special attention is paid by ngAria if that element also
|
||||
has a role or type of `checkbox`, `radio`, `range` or `textbox`.
|
||||
|
||||
@@ -134,14 +134,14 @@ attributes (if they have not been explicitly specified by the developer):
|
||||
|
||||
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
|
||||
the keyboard. It is still up to **you** as a developer to **ensure custom controls will be
|
||||
accessible**. As a rule, any time you create a widget involving user interaction, be sure to test
|
||||
accessible**. As a rule, any time you create a widget involving user interaction, be sure to test
|
||||
it with your keyboard and at least one mobile and desktop screen reader.
|
||||
|
||||
<h2 id="ngdisabled">ngDisabled</h2>
|
||||
|
||||
The `disabled` attribute is only valid for certain elements such as `button`, `input` and
|
||||
`textarea`. To properly disable custom element directives such as `<md-checkbox>` or `<taco-tab>`,
|
||||
using ngAria with [ngDisabled](https://docs.angularjs.org/api/ng/directive/ngDisabled) will also
|
||||
using ngAria with {@link ng.directive:ngDisabled ngDisabled} will also
|
||||
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
|
||||
custom controls to be more accessible.
|
||||
|
||||
@@ -162,7 +162,7 @@ Becomes:
|
||||
|
||||
<h2 id="ngshow">ngShow</h2>
|
||||
|
||||
>The [ngShow](https://docs.angularjs.org/api/ng/directive/ngShow) directive shows or hides the
|
||||
>The {@link ng.directive:ngShow ngShow} directive shows or hides the
|
||||
given HTML element based on the expression provided to the `ngShow` attribute. The element is
|
||||
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
@@ -199,7 +199,7 @@ Becomes:
|
||||
|
||||
<h2 id="nghide">ngHide</h2>
|
||||
|
||||
>The [ngHide](https://docs.angularjs.org/api/ng/directive/ngHide) directive shows or hides the
|
||||
>The {@link ng.directive:ngHide ngHide} directive shows or hides the
|
||||
given HTML element based on the expression provided to the `ngHide` attribute. The element is
|
||||
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
|
||||
|
||||
@@ -208,7 +208,7 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
|
||||
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
|
||||
|
||||
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` to any element not in
|
||||
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` to any element not in
|
||||
a node blacklist:
|
||||
|
||||
* Button
|
||||
@@ -218,14 +218,14 @@ a node blacklist:
|
||||
* Select
|
||||
* Details/Summary
|
||||
|
||||
To fix widespread accessibility problems with `ng-click` on `div` elements, ngAria will
|
||||
To fix widespread accessibility problems with `ng-click` on `div` elements, ngAria will
|
||||
dynamically bind a keypress event by default as long as the element isn't in the node blacklist.
|
||||
You can turn this functionality on or off with the `bindKeypress` configuration option.
|
||||
You can turn this functionality on or off with the `bindKeypress` configuration option.
|
||||
|
||||
ngAria will also add the `button` role to communicate to users of assistive technologies. This can
|
||||
be disabled with the `bindRoleForClick` configuration option.
|
||||
|
||||
For `ng-dblclick`, you must still manually add `ng-keypress` and a role to non-interactive elements
|
||||
For `ng-dblclick`, you must still manually add `ng-keypress` and a role to non-interactive elements
|
||||
such as `div` or `taco-button` to enable keyboard access.
|
||||
|
||||
<h3>Example</h3>
|
||||
|
||||
@@ -274,6 +274,37 @@ myModule.directive('my-directive', ['$animate', function($animate) {
|
||||
}]);
|
||||
```
|
||||
|
||||
## Preventing flicker before an animation starts
|
||||
|
||||
When nesting elements with structural animations such as `ngIf` into elements that have class-based
|
||||
animations such as `ngClass`, it sometimes happens that before the actual animation starts, there is a brief flicker or flash of content
|
||||
where the animated element is briefly visible.
|
||||
|
||||
To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as an animation is initialized,
|
||||
but removed before the actual animation starts (after waiting for a $digest). This class is only added for *structural*
|
||||
animations (`enter`, `move`, and `leave`).
|
||||
|
||||
Here's an example where you might see flickering:
|
||||
|
||||
```html
|
||||
<div ng-class="{red: myProp}">
|
||||
<div ng-class="{blue: myProp}">
|
||||
<div class="message" ng-if="myProp"></div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
It is possible that during the `enter` event, the `.message` div will be briefly visible before it starts animating.
|
||||
In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
|
||||
|
||||
```css
|
||||
.message.ng-enter-prepare {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Other animation styles ... */
|
||||
```
|
||||
|
||||
## More about animations
|
||||
|
||||
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
|
||||
|
||||
@@ -76,7 +76,7 @@ stores/updates the value of the input field into/from a variable.
|
||||
The second kind of new markup are the double curly braces `{{ expression | filter }}`:
|
||||
When the compiler encounters this markup, it will replace it with the evaluated value of the markup.
|
||||
An <a name="expression">{@link expression expression}</a> in a template is a JavaScript-like code snippet that allows
|
||||
to read and write variables. Note that those variables are not global variables.
|
||||
Angular to read and write variables. Note that those variables are not global variables.
|
||||
Just like variables in a JavaScript function live in a scope,
|
||||
Angular provides a <a name="scope">{@link scope scope}</a> for the variables accessible to expressions.
|
||||
The values that are stored in variables on the scope are referred to as the <a name="model"></a>*model*
|
||||
@@ -348,8 +348,7 @@ The following example shows how this is done with Angular:
|
||||
|
||||
return {
|
||||
currencies: currencies,
|
||||
convert: convert,
|
||||
refresh: refresh
|
||||
convert: convert
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
@ngdoc overview
|
||||
@name Decorators
|
||||
@sortOrder 345
|
||||
@description
|
||||
|
||||
# Decorators in AngularJS
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**NOTE:** This guide is targeted towards developers who are already familiar with AngularJS basics.
|
||||
If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
|
||||
</div>
|
||||
|
||||
## What are decorators?
|
||||
|
||||
Decorators are a design pattern that is used to separate modification or *decoration* of a class without modifying the
|
||||
original source code. In Angular, decorators are functions that allow a service, directive or filter to be modified
|
||||
prior to its usage.
|
||||
|
||||
## How to use decorators
|
||||
|
||||
There are two ways to register decorators
|
||||
|
||||
- `$provide.decorator`, and
|
||||
- `module.decorator`
|
||||
|
||||
Each provide access to a `$delegate`, which is the instantiated service/directive/filter, prior to being passed to the
|
||||
service that required it.
|
||||
|
||||
### $provide.decorator
|
||||
|
||||
The {@link api/auto/service/$provide#decorator decorator function} allows access to a $delegate of the service once it
|
||||
has been instantiated. For example:
|
||||
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('$log', [
|
||||
'$delegate',
|
||||
function $logDecorator($delegate) {
|
||||
|
||||
var originalWarn = $delegate.warn;
|
||||
$delegate.warn = function decoratedWarn(msg) {
|
||||
msg = 'Decorated Warn: ' + msg;
|
||||
originalWarn.apply($delegate, arguments);
|
||||
};
|
||||
|
||||
return $delegate;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
After the `$log` service has been instantiated the decorator is fired. The decorator function has a `$delegate` object
|
||||
injected to provide access to the service that matches the selector in the decorator. This `$delegate` will be the
|
||||
service you are decorating. The return value of the function *provided to the decorator* will take place of the service,
|
||||
directive, or filter being decorated.
|
||||
|
||||
<hr>
|
||||
|
||||
The `$delegate` may be either modified or completely replaced. Given a service `myService` with a method `someFn`, the
|
||||
following could all be viable solutions:
|
||||
|
||||
|
||||
#### Completely Replace the $delegate
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('myService', [
|
||||
'$delegate',
|
||||
function myServiceDecorator($delegate) {
|
||||
|
||||
var myDecoratedService = {
|
||||
// new service object to replace myService
|
||||
};
|
||||
return myDecoratedService;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
#### Patch the $delegate
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('myService', [
|
||||
'$delegate',
|
||||
function myServiceDecorator($delegate) {
|
||||
|
||||
var someFn = $delegate.someFn;
|
||||
|
||||
function aNewFn() {
|
||||
// new service function
|
||||
someFn.apply($delegate, arguments);
|
||||
}
|
||||
|
||||
$delegate.someFn = aNewFn;
|
||||
return $delegate;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
#### Augment the $delegate
|
||||
```js
|
||||
angular.module('myApp', [])
|
||||
|
||||
.config([ '$provide', function($provide) {
|
||||
|
||||
$provide.decorator('myService', [
|
||||
'$delegate',
|
||||
function myServiceDecorator($delegate) {
|
||||
|
||||
function helperFn() {
|
||||
// an additional fn to add to the service
|
||||
}
|
||||
|
||||
$delegate.aHelpfulAddition = helperFn;
|
||||
return $delegate;
|
||||
}
|
||||
]);
|
||||
}]);
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
Note that whatever is returned by the decorator function will replace that which is being decorated. For example, a
|
||||
missing return statement will wipe out the entire object being decorated.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
Decorators have different rules for different services. This is because services are registered in different ways.
|
||||
Services are selected by name, however filters and directives are selected by appending `"Filter"` or `"Directive"` to
|
||||
the end of the name. The `$delegate` provided is dictated by the type of service.
|
||||
|
||||
| Service Type | Selector | $delegate |
|
||||
|--------------|-------------------------------|-----------------------------------------------------------------------|
|
||||
| Service | `serviceName` | The `object` or `function` returned by the service |
|
||||
| Directive | `directiveName + 'Directive'` | An `Array.<DirectiveObject>`<sub>{@link guide/decorators#drtvArray 1}</sub> |
|
||||
| Filter | `filterName + 'Filter'` | The `function` returned by the filter |
|
||||
|
||||
<small id="drtvArray">1. Multiple directives may be registered to the same selector/name</small>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**NOTE:** Developers should take care in how and why they are modifying the `$delegate` for the service. Not only
|
||||
should expectations for the consumer be kept, but some functionality (such as directive registration) does not take
|
||||
place after decoration, but during creation/registration of the original service. This means, for example, that
|
||||
an action such as pushing a directive object to a directive `$delegate` will likely result in unexpected behavior.
|
||||
|
||||
Furthermore, great care should be taken when decorating core services, directives, or filters as this may unexpectedly
|
||||
or adversely affect the functionality of the framework.
|
||||
</div>
|
||||
|
||||
### module.decorator
|
||||
|
||||
This {@link api/ng/type/angular.Module#decorator function} is the same as the `$provide.decorator` function except it is
|
||||
exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. The
|
||||
main caveat here is that you will need to take note the order in which you create your decorators.
|
||||
|
||||
Unlike in the module config block (which allows configuration of services prior to their creation), the service must be
|
||||
registered prior to the decorator (see {@link guide/providers#provider-recipe Provider Recipe}). For example, the
|
||||
following would not work because you are attempting to decorate outside of the configuration phase and the service
|
||||
hasn't been created yet:
|
||||
|
||||
```js
|
||||
// will cause an error since 'someService' hasn't been registered
|
||||
angular.module('myApp').decorator('someService', ...);
|
||||
|
||||
angular.module('myApp').factory('someService', ...);
|
||||
```
|
||||
|
||||
## Example Applications
|
||||
|
||||
The following sections provide examples each of a service decorator, a directive decorator, and a filter decorator.
|
||||
|
||||
### Service Decorator Example
|
||||
|
||||
This example shows how we can replace the $log service with our own to display log messages.
|
||||
|
||||
<example module="myServiceDecorator" name="service-decorator">
|
||||
<file name="script.js">
|
||||
angular.module('myServiceDecorator', []).
|
||||
|
||||
controller('Ctrl', [
|
||||
'$scope',
|
||||
'$log',
|
||||
'$timeout',
|
||||
function($scope, $log, $timeout) {
|
||||
var types = ['error', 'warn', 'log', 'info' ,'debug'], i;
|
||||
|
||||
for (i = 0; i < types.length; i++) {
|
||||
$log[types[i]](types[i] + ': message ' + (i + 1));
|
||||
}
|
||||
|
||||
$timeout(function() {
|
||||
$log.info('info: message logged in timeout');
|
||||
});
|
||||
}
|
||||
]).
|
||||
|
||||
directive('myLog', [
|
||||
'$log',
|
||||
function($log) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<ul id="myLog"><li ng-repeat="l in myLog" class="{{l.type}}">{{l.message}}</li></ul>',
|
||||
scope: {},
|
||||
compile: function() {
|
||||
return function(scope) {
|
||||
scope.myLog = $log.stack;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
]).
|
||||
|
||||
config([
|
||||
'$provide',
|
||||
function($provide) {
|
||||
|
||||
$provide.decorator('$log', [
|
||||
'$delegate',
|
||||
function logDecorator($delegate) {
|
||||
|
||||
var myLog = {
|
||||
warn: function(msg) {
|
||||
log(msg, 'warn');
|
||||
},
|
||||
error: function(msg) {
|
||||
log(msg, 'error');
|
||||
},
|
||||
info: function(msg) {
|
||||
log(msg, 'info');
|
||||
},
|
||||
debug: function(msg) {
|
||||
log(msg, 'debug');
|
||||
},
|
||||
log: function(msg) {
|
||||
log(msg, 'log');
|
||||
},
|
||||
stack: []
|
||||
};
|
||||
|
||||
function log(msg, type) {
|
||||
myLog.stack.push({ type: type, message: msg.toString() });
|
||||
if (console && console[type]) console[type](msg);
|
||||
}
|
||||
|
||||
return myLog;
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
}
|
||||
]);
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<h1>Logs</h1>
|
||||
<my-log></my-log>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="style.css">
|
||||
li.warn { color: yellow; }
|
||||
li.error { color: red; }
|
||||
li.info { color: blue }
|
||||
li.log { color: black }
|
||||
li.debug { color: green }
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should display log messages in dom', function() {
|
||||
element.all(by.repeater('l in myLog')).count().then(function(count) {
|
||||
expect(count).toEqual(6);
|
||||
});
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
### Directive Decorator Example
|
||||
|
||||
Failed interpolated expressions in `ng-href` attributes can easily go unnoticed. We can decorate `ngHref` to warn us of
|
||||
those conditions.
|
||||
|
||||
<example module="urlDecorator" name="directive-decorator">
|
||||
<file name="script.js">
|
||||
angular.module('urlDecorator', []).
|
||||
|
||||
controller('Ctrl', ['$scope', function ($scope) {
|
||||
$scope.id = 3;
|
||||
$scope.warnCount = 0; // for testing
|
||||
}]).
|
||||
|
||||
config(['$provide', function($provide) {
|
||||
|
||||
// matchExpressions looks for interpolation markup in the directive attribute, extracts the expressions
|
||||
// from that markup (if they exist) and returns an array of those expressions
|
||||
function matchExpressions(str) {
|
||||
var exps = str.match(/{{([^}]+)}}/g);
|
||||
|
||||
// if there isn't any, get out of here
|
||||
if (exps === null) return;
|
||||
|
||||
exps = exps.map(function(exp) {
|
||||
var prop = exp.match(/[^{}]+/);
|
||||
return prop === null ? null : prop[0];
|
||||
});
|
||||
|
||||
return exps;
|
||||
}
|
||||
|
||||
// remember: directives must be selected by appending 'Directive' to the directive selector
|
||||
$provide.decorator('ngHrefDirective', [
|
||||
'$delegate',
|
||||
'$log',
|
||||
'$parse',
|
||||
function($delegate, $log, $parse) {
|
||||
|
||||
// store the original link fn
|
||||
var originalLinkFn = $delegate[0].link;
|
||||
|
||||
// replace the compile fn
|
||||
$delegate[0].compile = function(tElem, tAttr) {
|
||||
|
||||
// store the original exp in the directive attribute for our warning message
|
||||
var originalExp = tAttr.ngHref;
|
||||
|
||||
// get the interpolated expressions
|
||||
var exps = matchExpressions(originalExp);
|
||||
|
||||
// create and store the getters using $parse
|
||||
var getters = exps.map(function(el) {
|
||||
if (el) return $parse(el);
|
||||
});
|
||||
|
||||
return function newLinkFn(scope, elem, attr) {
|
||||
// fire the originalLinkFn
|
||||
originalLinkFn.apply($delegate[0], arguments);
|
||||
|
||||
// observe the directive attr and check the expressions
|
||||
attr.$observe('ngHref', function(val) {
|
||||
|
||||
// if we have getters and getters is an array...
|
||||
if (getters && angular.isArray(getters)) {
|
||||
|
||||
// loop through the getters and process them
|
||||
angular.forEach(getters, function(g, idx) {
|
||||
|
||||
// if val is truthy, then the warning won't log
|
||||
var val = angular.isFunction(g) ? g(scope) : true;
|
||||
if (!val) {
|
||||
$log.warn('NgHref Warning: "' + exps[idx] + '" in the expression "' + originalExp +
|
||||
'" is falsy!');
|
||||
|
||||
scope.warnCount++; // for testing
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// get rid of the old link function since we return a link function in compile
|
||||
delete $delegate[0].link;
|
||||
|
||||
// return the $delegate
|
||||
return $delegate;
|
||||
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<a ng-href="/products/{{ id }}/view" id="id3">View Product {{ id }}</a>
|
||||
- <strong>id == 3</strong>, so no warning<br>
|
||||
<a ng-href="/products/{{ id + 5 }}/view" id="id8">View Product {{ id + 5 }}</a>
|
||||
- <strong>id + 5 == 8</strong>, so no warning<br>
|
||||
<a ng-href="/products/{{ someOtherId }}/view" id="someOtherId">View Product {{ someOtherId }}</a>
|
||||
- <strong style="background-color: #ffff00;">someOtherId == undefined</strong>, so warn<br>
|
||||
<a ng-href="/products/{{ someOtherId + 5 }}/view" id="someOtherId5">View Product {{ someOtherId + 5 }}</a>
|
||||
- <strong>someOtherId + 5 == 5</strong>, so no warning<br>
|
||||
<div>Warn Count: {{ warnCount }}</div>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should warn when an expression in the interpolated value is falsy', function() {
|
||||
var id3 = element(by.id('id3'));
|
||||
var id8 = element(by.id('id8'));
|
||||
var someOther = element(by.id('someOtherId'));
|
||||
var someOther5 = element(by.id('someOtherId5'));
|
||||
|
||||
expect(id3.getText()).toEqual('View Product 3');
|
||||
expect(id3.getAttribute('href')).toContain('/products/3/view');
|
||||
|
||||
expect(id8.getText()).toEqual('View Product 8');
|
||||
expect(id8.getAttribute('href')).toContain('/products/8/view');
|
||||
|
||||
expect(someOther.getText()).toEqual('View Product');
|
||||
expect(someOther.getAttribute('href')).toContain('/products//view');
|
||||
|
||||
expect(someOther5.getText()).toEqual('View Product 5');
|
||||
expect(someOther5.getAttribute('href')).toContain('/products/5/view');
|
||||
|
||||
expect(element(by.binding('warnCount')).getText()).toEqual('Warn Count: 1');
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
|
||||
### Filter Decorator Example
|
||||
|
||||
Let's say we have created an app that uses the default format for many of our `Date` filters. Suddenly requirements have
|
||||
changed (that never happens) and we need all of our default dates to be `'shortDate'` instead of `'mediumDate'`.
|
||||
|
||||
<example module="filterDecorator" name="filter-decorator">
|
||||
<file name="script.js">
|
||||
angular.module('filterDecorator', []).
|
||||
|
||||
controller('Ctrl', ['$scope', function ($scope) {
|
||||
$scope.genesis = new Date(2010, 0, 5);
|
||||
$scope.ngConf = new Date(2016, 4, 4);
|
||||
}]).
|
||||
|
||||
config(['$provide', function($provide) {
|
||||
|
||||
$provide.decorator('dateFilter', [
|
||||
'$delegate',
|
||||
function dateDecorator($delegate) {
|
||||
|
||||
// store the original filter
|
||||
var originalFilter = $delegate;
|
||||
|
||||
// return our filter
|
||||
return shortDateDefault;
|
||||
|
||||
// shortDateDefault sets the format to shortDate if it is falsy
|
||||
function shortDateDefault(date, format, timezone) {
|
||||
if (!format) format = 'shortDate';
|
||||
|
||||
// return the result of the original filter
|
||||
return originalFilter(date, format, timezone);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
}]);
|
||||
</file>
|
||||
|
||||
<file name="index.html">
|
||||
<div ng-controller="Ctrl">
|
||||
<div id="genesis">Initial Commit default to short date: {{ genesis | date }}</div>
|
||||
<div>ng-conf 2016 default short date: {{ ngConf | date }}</div>
|
||||
<div id="ngConf">ng-conf 2016 with full date format: {{ ngConf | date:'fullDate' }}</div>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should default date filter to short date format', function() {
|
||||
expect(element(by.id('genesis')).getText())
|
||||
.toMatch(/Initial Commit default to short date: \d{1,2}\/\d{1,2}\/\d{2}/);
|
||||
});
|
||||
|
||||
it('should still allow dates to be formatted', function() {
|
||||
expect(element(by.id('ngConf')).getText())
|
||||
.toMatch(/ng-conf 2016 with full date format\: [A-Za-z]+, [A-Za-z]+ \d{1,2}, \d{4}/);
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -43,8 +43,7 @@ mirrors the process of compiling source code in
|
||||
Before we can write a directive, we need to know how Angular's {@link guide/compiler HTML compiler}
|
||||
determines when to use a given directive.
|
||||
|
||||
Similar to the terminology used when an [element **matches** a selector]
|
||||
(https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
Similar to the terminology used when an [element **matches** a selector](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), we say an element **matches** a
|
||||
directive when the directive is part of its declaration.
|
||||
|
||||
In the following example, we say that the `<input>` element **matches** the `ngModel` directive
|
||||
@@ -100,8 +99,13 @@ For example, the following forms are all equivalent and match the {@link ngBind}
|
||||
</file>
|
||||
<file name="protractor.js" type="protractor">
|
||||
it('should show off bindings', function() {
|
||||
expect(element(by.css('div[ng-controller="Controller"] span[ng-bind]')).getText())
|
||||
.toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)');
|
||||
var containerElm = element(by.css('div[ng-controller="Controller"]'));
|
||||
var nameBindings = containerElm.all(by.binding('name'));
|
||||
|
||||
expect(nameBindings.count()).toBe(5);
|
||||
nameBindings.each(function(elem) {
|
||||
expect(elem.getText()).toEqual('Max Karl Ernst Ludwig Planck (April 23, 1858 – October 4, 1947)');
|
||||
});
|
||||
});
|
||||
</file>
|
||||
</example>
|
||||
@@ -142,63 +146,6 @@ directives when possible.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
### Text and attribute bindings
|
||||
|
||||
During the compilation process the {@link ng.$compile compiler} matches text and attributes
|
||||
using the {@link ng.$interpolate $interpolate} service to see if they contain embedded
|
||||
expressions. These expressions are registered as {@link ng.$rootScope.Scope#$watch watches}
|
||||
and will update as part of normal {@link ng.$rootScope.Scope#$digest digest} cycle. An
|
||||
example of interpolation is shown below:
|
||||
|
||||
```html
|
||||
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
|
||||
```
|
||||
|
||||
|
||||
### `ngAttr` attribute bindings
|
||||
|
||||
Web browsers are sometimes picky about what values they consider valid for attributes.
|
||||
|
||||
For example, considering this template:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle cx="{{cx}}"></circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
We would expect Angular to be able to bind to this, but when we check the console we see
|
||||
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
|
||||
restrictions, you cannot simply write `cx="{{cx}}"`.
|
||||
|
||||
With `ng-attr-cx` you can work around this problem.
|
||||
|
||||
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
|
||||
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
|
||||
you to bind to attributes that would otherwise be eagerly processed by browsers
|
||||
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
|
||||
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
|
||||
results in `undefined`, the attribute is removed and not added to the element.
|
||||
|
||||
For example, we could fix the example above by instead writing:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle ng-attr-cx="{{cx}}"></circle>
|
||||
</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
|
||||
|
||||
First let's talk about the {@link ng.$compileProvider#directive API for registering directives}. Much like
|
||||
@@ -356,6 +303,7 @@ The `restrict` option is typically set to:
|
||||
* `'A'` - only matches attribute name
|
||||
* `'E'` - only matches element name
|
||||
* `'C'` - only matches class name
|
||||
* `'M'` - only matches comment
|
||||
|
||||
These restrictions can all be combined as needed:
|
||||
|
||||
@@ -459,7 +407,7 @@ This is clearly not a great solution.
|
||||
|
||||
What we want to be able to do is separate the scope inside a directive from the scope
|
||||
outside, and then map the outer scope to a directive's inner scope. We can do this by creating what
|
||||
we call an **isolate scope**. To do this, we can use a directive's `scope` option:
|
||||
we call an **isolate scope**. To do this, we can use a {@link $compile#-scope- directive's `scope`} option:
|
||||
|
||||
<example module="docsIsolateScopeDirective">
|
||||
<file name="script.js">
|
||||
@@ -588,14 +536,24 @@ want to reuse throughout your app.
|
||||
In this example we will build a directive that displays the current time.
|
||||
Once a second, it updates the DOM to reflect the current time.
|
||||
|
||||
Directives that want to modify the DOM typically use the `link` option.
|
||||
`link` takes a function with the following signature, `function link(scope, element, attrs) { ... }`
|
||||
where:
|
||||
Directives that want to modify the DOM typically use the `link` option to register DOM listeners
|
||||
as well as update the DOM. It is executed after the template has been cloned and is where
|
||||
directive logic will be put.
|
||||
|
||||
`link` takes a function with the following signature,
|
||||
`function link(scope, element, attrs, controller, transcludeFn) { ... }`, where:
|
||||
|
||||
* `scope` is an Angular scope object.
|
||||
* `element` is the jqLite-wrapped element that this directive matches.
|
||||
* `attrs` is a hash object with key-value pairs of normalized attribute names and their
|
||||
corresponding attribute values.
|
||||
* `controller` is the directive's required controller instance(s) or its own controller (if any).
|
||||
The exact value depends on the directive's require property.
|
||||
* `transcludeFn` is a transclude linking function pre-bound to the correct transclusion scope.
|
||||
|
||||
<div class="alert alert-info">
|
||||
For more details on the `link` option refer to the {@link ng.$compile#-link- `$compile` API} page.
|
||||
</div>
|
||||
|
||||
In our `link` function, we want to update the displayed time once a second, or whenever a user
|
||||
changes the time formatting string that our directive binds to. We will use the `$interval` service
|
||||
@@ -689,6 +647,7 @@ To do this, we need to use the `transclude` option.
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {},
|
||||
templateUrl: 'my-dialog.html'
|
||||
};
|
||||
});
|
||||
@@ -699,8 +658,7 @@ To do this, we need to use the `transclude` option.
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-dialog.html">
|
||||
<div class="alert" ng-transclude>
|
||||
</div>
|
||||
<div class="alert" ng-transclude></div>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -722,7 +680,7 @@ that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will r
|
||||
transclude: true,
|
||||
scope: {},
|
||||
templateUrl: 'my-dialog.html',
|
||||
link: function (scope, element) {
|
||||
link: function (scope) {
|
||||
scope.name = 'Jeff';
|
||||
}
|
||||
};
|
||||
@@ -734,8 +692,7 @@ that redefines `name` as `Jeff`. What do you think the `{{name}}` binding will r
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-dialog.html">
|
||||
<div class="alert" ng-transclude>
|
||||
</div>
|
||||
<div class="alert" ng-transclude></div>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -746,7 +703,7 @@ The `transclude` option changes the way scopes are nested. It makes it so that t
|
||||
transcluded directive have whatever scope is outside the directive, rather than whatever scope is on
|
||||
the inside. In doing so, it gives the contents access to the outside scope.
|
||||
|
||||
Note that if the directive did not create its own scope, then `scope` in `scope.name = 'Jeff';` would
|
||||
Note that if the directive did not create its own scope, then `scope` in `scope.name = 'Jeff'` would
|
||||
reference the outside scope and we would see `Jeff` in the output.
|
||||
|
||||
This behavior makes sense for a directive that wraps some content, because otherwise you'd have to
|
||||
@@ -819,9 +776,9 @@ function.
|
||||
|
||||
Often it's desirable to pass data from the isolate scope via an expression to the
|
||||
parent scope, this can be done by passing a map of local variable names and values into the expression
|
||||
wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden.
|
||||
This is specified in the directive by calling `close({message: 'closing for now'})`. Then the local
|
||||
variable `message` will be available within the `on-close` expression.
|
||||
wrapper function. For example, the `hideDialog` function takes a message to display when the dialog
|
||||
is hidden. This is specified in the directive by calling `close({message: 'closing for now'})`.
|
||||
Then the local variable `message` will be available within the `on-close` expression.
|
||||
|
||||
<div class="alert alert-success">
|
||||
**Best Practice:** use `&attr` in the `scope` option when you want your directive
|
||||
@@ -880,7 +837,7 @@ element?
|
||||
}]);
|
||||
</file>
|
||||
<file name="index.html">
|
||||
<span my-draggable>Drag ME</span>
|
||||
<span my-draggable>Drag Me</span>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -903,7 +860,7 @@ to which tab is active.
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {},
|
||||
controller: function($scope) {
|
||||
controller: ['$scope', function($scope) {
|
||||
var panes = $scope.panes = [];
|
||||
|
||||
$scope.select = function(pane) {
|
||||
@@ -919,13 +876,13 @@ to which tab is active.
|
||||
}
|
||||
panes.push(pane);
|
||||
};
|
||||
},
|
||||
}],
|
||||
templateUrl: 'my-tabs.html'
|
||||
};
|
||||
})
|
||||
.directive('myPane', function() {
|
||||
return {
|
||||
require: '^myTabs',
|
||||
require: '^^myTabs',
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {
|
||||
@@ -941,11 +898,9 @@ to which tab is active.
|
||||
<file name="index.html">
|
||||
<my-tabs>
|
||||
<my-pane title="Hello">
|
||||
<h4>Hello</h4>
|
||||
<p>Lorem ipsum dolor sit amet</p>
|
||||
</my-pane>
|
||||
<my-pane title="World">
|
||||
<h4>World</h4>
|
||||
<em>Mauris elementum elementum enim at suscipit.</em>
|
||||
<p><a href ng-click="i = i + 1">counter: {{i || 0}}</a></p>
|
||||
</my-pane>
|
||||
@@ -962,22 +917,25 @@ to which tab is active.
|
||||
</div>
|
||||
</file>
|
||||
<file name="my-pane.html">
|
||||
<div class="tab-pane" ng-show="selected" ng-transclude>
|
||||
<div class="tab-pane" ng-show="selected">
|
||||
<h4>{{title}}</h4>
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
</file>
|
||||
</example>
|
||||
|
||||
The `myPane` directive has a `require` option with value `^myTabs`. When a directive uses this
|
||||
option, `$compile` will throw an error unless the specified controller is found. The `^` prefix
|
||||
means that this directive searches for the controller on its parents (without the `^` prefix, the
|
||||
directive would look for the controller on just its own element).
|
||||
The `myPane` directive has a `require` option with value `^^myTabs`. When a directive uses this
|
||||
option, `$compile` will throw an error unless the specified controller is found. The `^^` prefix
|
||||
means that this directive searches for the controller on its parents. (A `^` prefix would make the
|
||||
directive look for the controller on its own element or its parents; without any prefix, the
|
||||
directive would look on its own element only.)
|
||||
|
||||
So where does this `myTabs` controller come from? Directives can specify controllers using
|
||||
the unsurprisingly named `controller` option. As you can see, the `myTabs` directive uses this
|
||||
option. Just like `ngController`, this option attaches a controller to the template of the directive.
|
||||
|
||||
If it is necessary to reference the controller or any functions bound to the controller's scope in
|
||||
the template, you can use the option `controllerAs` to specify the name of the controller as an alias.
|
||||
If it is necessary to reference the controller or any functions bound to the controller from the
|
||||
template, you can use the option `controllerAs` to specify the name of the controller as an alias.
|
||||
The directive needs to define a scope for this configuration to be used. This is particularly useful
|
||||
in the case when the directive is used as a component.
|
||||
|
||||
@@ -992,7 +950,7 @@ The corresponding parameter being sent to the `link` function will also be an ar
|
||||
angular.module('docsTabsExample', [])
|
||||
.directive('myPane', function() {
|
||||
return {
|
||||
require: ['^myTabs', '^ngModel'],
|
||||
require: ['^^myTabs', 'ngModel'],
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
scope: {
|
||||
@@ -1028,4 +986,3 @@ available in the {@link guide/compiler compiler guide}.
|
||||
|
||||
The {@link ng.$compile `$compile` API} page has a comprehensive list of directive options for
|
||||
reference.
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
# Angular Expressions
|
||||
|
||||
Angular expressions are JavaScript-like code snippets that are usually placed in bindings such as
|
||||
`{{ expression }}`.
|
||||
Angular expressions are JavaScript-like code snippets that are mainly placed in
|
||||
interpolation bindings such as `<span title="{{ attrBinding }}">{{ textBinding }}</span>`,
|
||||
but also used directly in directive attributes such as `ng-click="functionExpression()"`.
|
||||
|
||||
For example, these are valid expressions in Angular:
|
||||
|
||||
@@ -35,7 +36,9 @@ Angular expressions are like JavaScript expressions with the following differenc
|
||||
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
|
||||
in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
|
||||
* **No Object Creation With New Operator:** You cannot use `new` operator in an Angular expression.
|
||||
|
||||
* **No Comma And Void Operators:** You cannot use `,` or `void` operators in an Angular expression.
|
||||
|
||||
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
|
||||
displaying it.
|
||||
@@ -280,7 +283,7 @@ result is a non-undefined value (see value stabilization algorithm below).
|
||||
</example>
|
||||
|
||||
|
||||
### Why this feature
|
||||
### Reasons for using one-time binding
|
||||
|
||||
The main purpose of one-time binding expression is to provide a way to create a binding
|
||||
that gets deregistered and frees up resources once the binding is stabilized.
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 280
|
||||
@description
|
||||
|
||||
# Filters
|
||||
|
||||
A filter formats the value of an expression for display to the user. They can be used in view templates,
|
||||
controllers or services and it is easy to define your own filter.
|
||||
|
||||
@@ -32,10 +34,13 @@ E.g. the markup `{{ 1234 | number:2 }}` formats the number 1234 with 2 decimal p
|
||||
|
||||
## Using filters in controllers, services, and directives
|
||||
|
||||
You can also use filters in controllers, services, and directives. For this, inject a dependency
|
||||
with the name `<filterName>Filter` to your controller/service/directive. E.g. using the dependency
|
||||
`numberFilter` will inject the number filter. The injected argument is a function that takes the
|
||||
value to format as first argument and filter parameters starting with the second argument.
|
||||
You can also use filters in controllers, services, and directives.
|
||||
|
||||
<div class="alert alert-info">
|
||||
For this, inject a dependency with the name `<filterName>Filter` into your controller/service/directive.
|
||||
E.g. a filter called `number` is injected by using the dependency `numberFilter`. The injected argument
|
||||
is a function that takes the value to format as first argument, and filter parameters starting with the second argument.
|
||||
</div>
|
||||
|
||||
The example below uses the filter called {@link ng.filter:filter `filter`}.
|
||||
This filter reduces arrays into sub arrays based on
|
||||
@@ -108,6 +113,7 @@ text upper-case.
|
||||
No filter: {{greeting}}<br>
|
||||
Reverse: {{greeting|reverse}}<br>
|
||||
Reverse + uppercase: {{greeting|reverse:true}}<br>
|
||||
Reverse, filtered in controller: {{filteredGreeting}}<br>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
@@ -127,8 +133,9 @@ text upper-case.
|
||||
return out;
|
||||
};
|
||||
})
|
||||
.controller('MyController', ['$scope', function($scope) {
|
||||
.controller('MyController', ['$scope', 'reverseFilter', function($scope, reverseFilter) {
|
||||
$scope.greeting = 'hello';
|
||||
$scope.filteredGreeting = reverseFilter($scope.greeting);
|
||||
}]);
|
||||
</file>
|
||||
</example>
|
||||
|
||||
@@ -383,7 +383,7 @@ In the following example we create two directives:
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, elm, attrs, ctrl) {
|
||||
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
||||
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
|
||||
|
||||
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
|
||||
|
||||
@@ -440,8 +440,7 @@ Note that you can alternatively use `ng-pattern` to further restrict the validat
|
||||
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@example\.com$/i;
|
||||
|
||||
return {
|
||||
require: 'ngModel',
|
||||
restrict: '',
|
||||
require: '?ngModel',
|
||||
link: function(scope, elm, attrs, ctrl) {
|
||||
// only apply the validator if ngModel is present and Angular has added the email validator
|
||||
if (ctrl && ctrl.$validators.email) {
|
||||
|
||||
@@ -91,7 +91,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
### Server-Specific
|
||||
|
||||
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
|
||||
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
|
||||
* **FireBase:** [AngularFire](http://angularfire.com/), [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
|
||||
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
|
||||
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
|
||||
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
|
||||
@@ -101,7 +101,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
|
||||
## Learning Resources
|
||||
|
||||
###Books
|
||||
### Books
|
||||
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
|
||||
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
|
||||
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
|
||||
@@ -111,10 +111,12 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
* [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
|
||||
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
|
||||
|
||||
###Videos:
|
||||
### Videos:
|
||||
* [egghead.io](http://egghead.io/)
|
||||
* [Angular on YouTube](http://youtube.com/angularjs)
|
||||
* [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/)
|
||||
|
||||
### Courses
|
||||
* **Free online:**
|
||||
@@ -122,6 +124,7 @@ This is a short list of libraries with specific support and documentation for wo
|
||||
[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:**
|
||||
[The Angular Course (115 videos that show you how to build a full app)](http://watchandcode.com/courses/angular-course/),
|
||||
[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),
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
@ngdoc overview
|
||||
@name Interpolation
|
||||
@sortOrder 275
|
||||
@description
|
||||
|
||||
# Interpolation and data-binding
|
||||
|
||||
Interpolation markup with embedded {@link guide/expression expressions} is used by Angular to
|
||||
provide data-binding to text nodes and attribute values.
|
||||
|
||||
An example of interpolation is shown below:
|
||||
|
||||
```html
|
||||
<a ng-href="img/{{username}}.jpg">Hello {{username}}!</a>
|
||||
```
|
||||
|
||||
### How text and attribute bindings work
|
||||
|
||||
During the compilation process the {@link ng.$compile compiler} uses the {@link ng.$interpolate $interpolate}
|
||||
service to see if text nodes and element attributes contain interpolation markup with embedded expressions.
|
||||
|
||||
If that is the case, the compiler adds an interpolateDirective to the node and
|
||||
registers {@link ng.$rootScope.Scope#$watch watches} on the computed interpolation function,
|
||||
which will update the corresponding text nodes or attribute values as part of the
|
||||
normal {@link ng.$rootScope.Scope#$digest digest} cycle.
|
||||
|
||||
Note that the interpolateDirective has a priority of 100 and sets up the watch in the preLink function.
|
||||
|
||||
### Binding to boolean attributes
|
||||
|
||||
Attributes such as `disabled` are called `boolean` attributes, because their presence means `true` and
|
||||
their absence means `false`. We cannot use normal attribute bindings with them, because the HTML
|
||||
specification does not require browsers to preserve the values of boolean attributes. This means that
|
||||
if we put an Angular interpolation expression into such an attribute then the binding information
|
||||
would be lost, because the browser ignores the attribute value.
|
||||
|
||||
In the following example, the interpolation information would be ignored and the browser would simply
|
||||
interpret the attribute as present, meaning that the button would always be disabled.
|
||||
|
||||
```html
|
||||
Disabled: <input type="checkbox" ng-model="isDisabled" />
|
||||
<button disabled="{{isDisabled}}">Disabled</button>
|
||||
```
|
||||
|
||||
For this reason, Angular provides special `ng`-prefixed directives for the following boolean attributes:
|
||||
{@link ngDisabled `disabled`}, {@link ngRequired `required`}, {@link ngSelected `selected`},
|
||||
{@link ngChecked `checked`}, {@link ngReadonly `readOnly`} , and {@link ngOpen `open`}.
|
||||
|
||||
These directives take an expression inside the attribute, and set the corresponding boolean attribute
|
||||
to true when the expression evaluates to truthy.
|
||||
|
||||
```html
|
||||
Disabled: <input type="checkbox" ng-model="isDisabled" />
|
||||
<button ng-disabled="isDisabled">Disabled</button>
|
||||
```
|
||||
|
||||
### `ngAttr` for binding to arbitrary attributes
|
||||
|
||||
Web browsers are sometimes picky about what values they consider valid for attributes.
|
||||
|
||||
For example, considering this template:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle cx="{{cx}}"></circle>
|
||||
</svg>
|
||||
```
|
||||
|
||||
We would expect Angular to be able to bind to this, but when we check the console we see
|
||||
something like `Error: Invalid value for attribute cx="{{cx}}"`. Because of the SVG DOM API's
|
||||
restrictions, you cannot simply write `cx="{{cx}}"`.
|
||||
|
||||
With `ng-attr-cx` you can work around this problem.
|
||||
|
||||
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
|
||||
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
|
||||
you to bind to attributes that would otherwise be eagerly processed by browsers
|
||||
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
|
||||
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
|
||||
results in `undefined`, the attribute is removed and not added to the element.
|
||||
|
||||
For example, we could fix the example above by instead writing:
|
||||
|
||||
```html
|
||||
<svg>
|
||||
<circle ng-attr-cx="{{cx}}"></circle>
|
||||
</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>
|
||||
```
|
||||
|
||||
Other attributes may also not work as expected when they contain interpolation markup, and
|
||||
can be used with `ngAttr` instead. The following is a list of known problematic attributes:
|
||||
|
||||
- **size** in `<select>` elements (see [issue 1619](https://github.com/angular/angular.js/issues/1619))
|
||||
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [issue 5025](https://github.com/angular/angular.js/issues/5025))
|
||||
- **type** in `<button>` in Internet Explorer 11 (see [issue 14117](https://github.com/angular/angular.js/issues/5025))
|
||||
|
||||
|
||||
### Embedding interpolation markup inside expressions
|
||||
|
||||
<div class="alert alert-danger">
|
||||
**Note:** Angular directive attributes take either expressions *or* interpolation markup with embedded expressions.
|
||||
It is considered **bad practice** to embed interpolation markup inside an expression:
|
||||
</div>
|
||||
|
||||
```html
|
||||
<div ng-show="form{{$index}}.$invalid"></div>
|
||||
```
|
||||
|
||||
You should instead delegate the computation of complex expressions to the scope, like this:
|
||||
|
||||
```html
|
||||
<div ng-show="getForm($index).$invalid"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
function getForm(index) {
|
||||
return $scope['form' + index];
|
||||
}
|
||||
```
|
||||
|
||||
You can also access the `scope` with `this` in your templates:
|
||||
|
||||
```html
|
||||
<div ng-show="this['form' + $index].$invalid"></div>
|
||||
```
|
||||
|
||||
#### Why mixing interpolation and expressions is bad practice:
|
||||
|
||||
- It increases the complexity of the markup
|
||||
- There is no guarantee that it works for every directive, because interpolation itself is a directive.
|
||||
If another directive accesses attribute data before interpolation has run, it will get the raw
|
||||
interpolation markup and not data.
|
||||
- It impacts performance, as interpolation adds another watcher to the scope.
|
||||
- Since this is not recommended usage, we do not test for this, and changes to
|
||||
Angular core may break your code.
|
||||
@@ -170,6 +170,25 @@ other inline messages situated as children within the `ngMessages` container dir
|
||||
Depending on where the `ngMessagesInclude` directive is placed it will be prioritized inline with the other messages
|
||||
before and after it.
|
||||
|
||||
Also due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
|
||||
it is no longer possible to use interpolation inside the `ngMessages` attribute expression. This technique
|
||||
is generally not recommended, and can easily break when a directive implementation changes. In cases
|
||||
where a simple expression is not possible, you can delegate accessing the object to a function:
|
||||
|
||||
```html
|
||||
<div ng-messages="ctrl.form['field_{{$index}}'].$error">...</div>
|
||||
```
|
||||
would become
|
||||
```html
|
||||
<div ng-messages="ctrl.getMessages($index)">...</div>
|
||||
```
|
||||
where `ctrl.getMessages()`
|
||||
```javascript
|
||||
ctrl.getMessages = function($index) {
|
||||
return ctrl.form['field_' + $index].$error;
|
||||
}
|
||||
```
|
||||
|
||||
### ngOptions
|
||||
|
||||
The `ngOptions` directive has also been refactored and as a result some long-standing bugs
|
||||
@@ -189,6 +208,10 @@ But in practice this is not what people want and so this change iterates over pr
|
||||
in the order they are returned by Object.keys(obj), which is almost always the order
|
||||
in which the properties were defined.
|
||||
|
||||
Also due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
|
||||
setting the ngOptions attribute expression after the element is compiled, will no longer trigger the ngOptions behavior.
|
||||
This worked previously because the ngOptions logic was part of the select directive, while
|
||||
it is now implemented in the ngOptions directive itself.
|
||||
|
||||
### select
|
||||
|
||||
@@ -221,7 +244,47 @@ ngModelCtrl.$formatters.push(function(value) {
|
||||
});
|
||||
```
|
||||
|
||||
## Templating (`ngRepeat`, `$compile`)
|
||||
### form
|
||||
|
||||
Due to [94533e57](https://github.com/angular/angular.js/commit/94533e570673e6b2eb92073955541fa289aabe02),
|
||||
the `name` attribute of `form` elements can now only contain characters that can be evaluated as part
|
||||
of an Angular expression. This is because Angular uses the value of `name` as an assignable expression
|
||||
to set the form on the `$scope`. For example, `name="myForm"` assigns the form to `$scope.myForm` and
|
||||
`name="myObj.myForm"` assigns it to `$scope.myObj.myForm`.
|
||||
|
||||
Previously, it was possible to also use names such `name="my:name"`, because Angular used a special setter
|
||||
function for the form name. Now the general, more robust `$parse` setter is used.
|
||||
|
||||
The easiest way to migrate your code is therefore to remove all special characters from the `name` attribute.
|
||||
|
||||
If you need to keep the special characters, you can use the following directive, which will replace
|
||||
the `name` with a value that can be evaluated as an expression in the compile function, and then
|
||||
re-set the original name in the postLink function. This ensures that (1), the form is published on
|
||||
the scope, and (2), the form has the original name, which might be important if you are doing server-side
|
||||
form submission.
|
||||
|
||||
```js
|
||||
angular.module('myApp').directive('form', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
priority: 1000,
|
||||
compile: function(element, attrs) {
|
||||
var unsupportedCharacter = ':'; // change accordingly
|
||||
var originalName = attrs.name;
|
||||
if (attrs.name && attrs.name.indexOf(unsupportedCharacter) > 0) {
|
||||
attrs.$set('name', 'this["' + originalName + '"]');
|
||||
}
|
||||
|
||||
return postLinkFunction(scope, element) {
|
||||
// Don't trigger $observers
|
||||
element.setAttribute('name', originalName);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
## Templating (`ngRepeat`, `$compile`, `ngInclude`)
|
||||
|
||||
### ngRepeat
|
||||
|
||||
@@ -253,6 +316,45 @@ returning an object from a controller constructor function will now override the
|
||||
controllerAs method will no longer get the this reference, but the returned object.
|
||||
|
||||
|
||||
### ngInclude:
|
||||
Due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
|
||||
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
|
||||
|
||||
Before:
|
||||
```html
|
||||
<div ng-include="findTemplate('https://example.com/templates/myTemplate.html')"></div>
|
||||
```
|
||||
|
||||
```js
|
||||
$scope.findTemplate = function(templateName) {
|
||||
return $sce.trustAsResourceUrl(templateName);
|
||||
};
|
||||
```
|
||||
|
||||
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
|
||||
whitelist in the `config()` function:
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
var templateCache = {};
|
||||
$scope.findTemplate = function(templateName) {
|
||||
if (!templateCache[templateName]) {
|
||||
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
|
||||
}
|
||||
|
||||
return templateCache[templateName];
|
||||
};
|
||||
|
||||
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`, which means you don't
|
||||
// have to use `$sce.trustAsResourceUrl()` at all:
|
||||
|
||||
angular.module('myApp', []).config(function($sceDelegateProvider) {
|
||||
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Cookies (`ngCookies`)
|
||||
|
||||
Due to [38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
|
||||
@@ -290,8 +392,6 @@ has been moved to `$cookies`, to which `$cookieStore` now simply
|
||||
delegates calls.
|
||||
|
||||
|
||||
|
||||
|
||||
## Server Requests (`$http`)
|
||||
|
||||
Due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
|
||||
@@ -323,8 +423,6 @@ $http.get(url, {
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## Filters (`filter`, `limitTo`)
|
||||
|
||||
### `filter` filter
|
||||
@@ -342,8 +440,6 @@ Now, instead of returning empty object/array, it returns unchanged input.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Migrating from 1.2 to 1.3
|
||||
|
||||
## Controllers
|
||||
@@ -561,6 +657,36 @@ After:
|
||||
};
|
||||
});
|
||||
|
||||
- due to [531a8de7](https://github.com/angular/angular.js/commit/531a8de72c439d8ddd064874bf364c00cedabb11),
|
||||
`$observe` no longer registers on undefined attributes. For example, if you were using `$observe` on
|
||||
an absent optional attribute to set a default value, the following would not work anymore:
|
||||
|
||||
```html
|
||||
<my-dir></my-dir>
|
||||
```
|
||||
|
||||
```js
|
||||
// Link function for directive myDir
|
||||
link: function(scope, element, attr) {
|
||||
attr.$observe('myAttr', function(newVal) {
|
||||
scope.myValue = newVal ? newVal : 'myDefaultValue';
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Instead, check if the attribute is set before registering the observer:
|
||||
|
||||
```js
|
||||
link: function(scope, element, attr) {
|
||||
if (attr.myAttr) {
|
||||
// register the observer
|
||||
} else {
|
||||
// set the default
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -633,8 +759,15 @@ $scope.resetWithCancel = function (e) {
|
||||
[#5864](https://github.com/angular/angular.js/issues/5864))
|
||||
|
||||
|
||||
- {@link input[checkbox] `input[checkbox]`} now supports constant expressions in `ngTrueValue` and
|
||||
`ngFalseValue`, making it now possible to e.g. use boolean and integer values. Previously, these attributes would
|
||||
always be treated as strings, whereas they are now parsed as expressions, and will throw if an expression
|
||||
is non-constant. To convert non-constant strings into constant expressions, simply wrap them in an
|
||||
extra pair of quotes, like so:
|
||||
|
||||
`<input type="checkbox" ng-model="..." ng-true-value="'truthyValue'">`
|
||||
|
||||
See [c90cefe1614](https://github.com/angular/angular.js/commit/c90cefe16142d973a123e945fc9058e8a874c357)
|
||||
|
||||
|
||||
## Scopes and Digests (`$scope`)
|
||||
|
||||
@@ -75,9 +75,8 @@ that you break your application to multiple modules like this:
|
||||
* And an application level module which depends on the above modules and contains any
|
||||
initialization code.
|
||||
|
||||
We've also
|
||||
[written a document](http://angularjs.blogspot.com/2014/02/an-angularjs-style-guide-and-best.html)
|
||||
on how we organize large apps at Google.
|
||||
You can find a community
|
||||
[style guide](https://github.com/johnpapa/angular-styleguide) to help yourself when application grows.
|
||||
|
||||
The above is a suggestion. Tailor it to your needs.
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ and {@link angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
|
||||
|
||||
## Strict DI Mode
|
||||
|
||||
Using strict di mode in your production application will throw errors when a injectable
|
||||
Using strict di mode in your production application will throw errors when an injectable
|
||||
function is not
|
||||
{@link di#dependency-annotation annotated explicitly}. Strict di mode is intended to help
|
||||
you make sure that your code will work when minified. However, it also will force you to
|
||||
|
||||
@@ -257,7 +257,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
|
||||
|
||||
2. **Watcher registration**
|
||||
|
||||
During template linking directives register {@link
|
||||
During template linking, directives register {@link
|
||||
ng.$rootScope.Scope#$watch watches} on the scope. These watches will be
|
||||
used to propagate model values to the DOM.
|
||||
|
||||
@@ -391,7 +391,7 @@ implementing custom event callbacks, or when working with third-party library ca
|
||||
5. The {@link ng.$rootScope.Scope#$watch $watch} list is a set of expressions
|
||||
which may have changed since last iteration. If a change is detected then the `$watch`
|
||||
function is called which typically updates the DOM with the new value.
|
||||
6. Once the Angular {@link ng.$rootScope.Scope#$digest $digest} loop finishes
|
||||
6. Once the Angular {@link ng.$rootScope.Scope#$digest $digest} loop finishes,
|
||||
the execution leaves the Angular and JavaScript context. This is followed by the browser
|
||||
re-rendering the DOM to reflect any changes.
|
||||
|
||||
@@ -419,4 +419,4 @@ user enters text into the text field.
|
||||
which in turn updates the DOM.
|
||||
6. Angular exits the execution context, which in turn exits the `keydown` event and with it
|
||||
the JavaScript execution context.
|
||||
7. The browser re-renders the view with update text.
|
||||
7. The browser re-renders the view with the updated text.
|
||||
|
||||
@@ -138,9 +138,13 @@ batchModule.factory('batchLog', ['$interval', '$log', function($interval, $log)
|
||||
*/
|
||||
batchModule.factory('routeTemplateMonitor', ['$route', 'batchLog', '$rootScope',
|
||||
function($route, batchLog, $rootScope) {
|
||||
$rootScope.$on('$routeChangeSuccess', function() {
|
||||
batchLog($route.current ? $route.current.template : null);
|
||||
});
|
||||
return {
|
||||
startMonitoring: function() {
|
||||
$rootScope.$on('$routeChangeSuccess', function() {
|
||||
batchLog($route.current ? $route.current.template : null);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
```
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@sortOrder 260
|
||||
@description
|
||||
|
||||
# Templates
|
||||
|
||||
In Angular, templates are written with HTML that contains Angular-specific elements and attributes.
|
||||
Angular combines the template with information from the model and controller to render the dynamic
|
||||
view that a user sees in the browser.
|
||||
|
||||
@@ -359,7 +359,7 @@ element, to which it can then insert the transcluded content into its template.
|
||||
|
||||
Before compilation:
|
||||
```html
|
||||
<div translude-directive>
|
||||
<div transclude-directive>
|
||||
Some transcluded content
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -46,7 +46,7 @@ Download the version you want and have fun.
|
||||
Each directory under <http://code.angularjs.org/> includes the following set of files:
|
||||
|
||||
* __`angular.js`__ — This file is non-obfuscated, non-minified, and human-readable by
|
||||
opening it it any editor or browser. In order to get better error messages during development, you
|
||||
opening it in any editor or browser. In order to get better error messages during development, you
|
||||
should always use this non-minified angular script.
|
||||
|
||||
* __`angular.min.js`__ — This is a minified and obfuscated version of
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
|
||||
# PhoneCat Tutorial App
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<p>
|
||||
This version of the tutorial is deprecated and no longer maintained. You can find the most
|
||||
recent version of the tutorial at https://docs.angularjs.org/tutorial/.
|
||||
</p>
|
||||
<p>
|
||||
It is recommended to use the latest tutorial version for the following reasons:
|
||||
<ul>
|
||||
<li>It showcases features introduced in AngularJS v1.5+</li>
|
||||
<li>It follows modern best practices in terms of architecture and code organization</li>
|
||||
<li>It has more up-to-date dependencies and tools</li>
|
||||
<li>It is actively maintained</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
|
||||
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
|
||||
of Android devices, lets you filter the list to see only devices that interest you, and then view
|
||||
@@ -61,7 +77,7 @@ a few git commands.
|
||||
|
||||
### Install Git
|
||||
|
||||
You can download and install Git from http://git-scm.com/download. Once installed you should have
|
||||
You can download and install Git from http://git-scm.com/download. Once installed, you should have
|
||||
access to the `git` command line tool. The main commands that you will need to use are:
|
||||
|
||||
- `git clone ...` : clone a remote repository onto your local machine
|
||||
@@ -73,7 +89,7 @@ Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by r
|
||||
command:
|
||||
|
||||
```
|
||||
git clone --depth=14 https://github.com/angular/angular-phonecat.git
|
||||
git clone --depth=14 --branch=1.4-snapshot https://github.com/angular/angular-phonecat.git
|
||||
```
|
||||
|
||||
This command creates the `angular-phonecat` directory in your current directory.
|
||||
@@ -82,6 +98,11 @@ This command creates the `angular-phonecat` directory in your current directory.
|
||||
download much smaller and faster.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
The `--branch=1.4-snapshot` option tells Git to pull down the `1.4-snapshot` branch.
|
||||
The code for this older version of the tutorial is on that branch.
|
||||
</div>
|
||||
|
||||
Change your current directory to `angular-phonecat`.
|
||||
|
||||
```
|
||||
@@ -123,7 +144,7 @@ npm --version
|
||||
</a>.
|
||||
</div>
|
||||
|
||||
Once you have Node.js installed on your machine you can download the tool dependencies by running:
|
||||
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
@@ -198,7 +219,7 @@ http://localhost:8000/app/index.html
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
To serve the web app on a different ip address or port, edit the "start" script within package.json.
|
||||
To serve the web app on a different IP address or port, edit the "start" script within package.json.
|
||||
You can use `-a` to set the address and `-p` to set the port.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
<ul doc-tutorial-nav="0"></ul>
|
||||
|
||||
|
||||
You are now ready to build the AngularJS phonecat app. In this step, you will become familiar
|
||||
with the most important source code files, learn how to start the development servers bundled with
|
||||
In this step of the tutorial, you will become familiar with the most important source code files of
|
||||
the AngularJS phonecat app. You will also learn how to start the development servers bundled with
|
||||
angular-seed, and run the application in the browser.
|
||||
|
||||
Before you continue, make sure you have set up your development environment and installed all necessary
|
||||
dependencies, as described in {@link index#get-started Get Started}.
|
||||
|
||||
In `angular-phonecat` directory, run this command:
|
||||
In the `angular-phonecat` directory, run this command:
|
||||
|
||||
```
|
||||
git checkout -f step-0
|
||||
git checkout -f 1.4-step-0
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ by the value of the expressions.
|
||||
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
|
||||
__controller__ to the <body> tag. At this point:
|
||||
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}` denote
|
||||
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) denote
|
||||
bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
|
||||
controller.
|
||||
|
||||
@@ -132,6 +132,8 @@ The "Angular way" of separating controller from the view, makes it easy to test
|
||||
developed. If our controller is available on the global namespace then we could simply instantiate it
|
||||
with a mock `scope` object:
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
|
||||
```js
|
||||
describe('PhoneListCtrl', function(){
|
||||
|
||||
@@ -195,8 +197,19 @@ to ensure that Karma and its necessary plugins are installed. You can do this by
|
||||
|
||||
To run the tests, and then watch the files for changes: `npm test`.
|
||||
|
||||
* Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
|
||||
the background. Karma will use this browser for test execution.
|
||||
* Karma will start new instances of Chrome and Firefox browsers automatically. Just ignore them and
|
||||
let them run in the background. Karma will use these browsers for test execution.
|
||||
* If you only have one of the browsers installed on your machine (either Chrome or Firefox), make
|
||||
sure to update the karma configuration file before running the test. Locate the configuration file
|
||||
in `test/karma.conf.js`, then update the `browsers` property.
|
||||
|
||||
E.g. if you only have Chrome installed:
|
||||
<pre>
|
||||
...
|
||||
browsers: ['Chrome'],
|
||||
...
|
||||
</pre>
|
||||
|
||||
* You should see the following or similar output in the terminal:
|
||||
|
||||
<pre>
|
||||
@@ -250,7 +263,7 @@ browser is limited, which results in your karma tests running extremely slow.
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
|
||||
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
|
||||
|
||||
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
|
||||
|
||||
@@ -65,7 +65,7 @@ phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
|
||||
`$http` makes an HTTP GET request to our web server, asking for `phones/phones.json` (the url is
|
||||
relative to our `index.html` file). The server responds by providing the data in the json file.
|
||||
(The response might just as well have been dynamically generated by a backend server. To the
|
||||
browser and our app they both look the same. For the sake of simplicity we used a json file in this
|
||||
browser and our app, they both look the same. For the sake of simplicity, we used a json file in this
|
||||
tutorial.)
|
||||
|
||||
The `$http` service returns a {@link ng.$q promise object} with a `success`
|
||||
@@ -114,7 +114,7 @@ as strings, which will not get minified. There are two ways to provide these inj
|
||||
|
||||
* Create a `$inject` property on the controller function which holds an array of strings.
|
||||
Each string in the array is the name of the service to inject for the corresponding parameter.
|
||||
In our example we would write:
|
||||
In our example, we would write:
|
||||
|
||||
```js
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
|
||||
@@ -43,7 +43,7 @@ __`app/index.html`:__
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}"></a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
@@ -59,8 +59,8 @@ 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
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
an 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.
|
||||
|
||||
|
||||
|
||||
@@ -37,12 +37,12 @@ We are using [Bower][bower] to install client-side dependencies. This step upda
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.4.0"
|
||||
"angular-route": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-route": "~1.4.0"` tells bower to install a version of the
|
||||
The new dependency `"angular-route": "1.4.x"` tells bower to install a version of the
|
||||
angular-route component that is compatible with version 1.4.x. We must tell bower to download
|
||||
and install this dependency.
|
||||
|
||||
@@ -53,6 +53,18 @@ preconfigured npm to run bower install for us:
|
||||
npm install
|
||||
```
|
||||
|
||||
<div class="alert alert-warning">
|
||||
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then you may have a
|
||||
problem with the `bower install` due to a conflict between the versions of angular.js that need to
|
||||
be installed. If you get this then simply delete your `app/bower_components` folder before running
|
||||
`npm install`.
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
**Note:** If you have bower installed globally then you can run `bower install` but for this project we have
|
||||
preconfigured `npm install` to run bower for us.
|
||||
</div>
|
||||
|
||||
|
||||
## Multiple Views, Routing and Layout Template
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ phone in the phone list.
|
||||
* When you click on a phone on the list, the phone details page with phone-specific information
|
||||
is displayed.
|
||||
|
||||
To implement the phone details view we used {@link ng.$http $http} to fetch our data, and we
|
||||
fleshed out the `phone-detail.html` view template.
|
||||
To implement the phone details view we are going to use {@link ng.$http $http} to fetch our data,
|
||||
and then flesh out the `phone-detail.html` view template.
|
||||
|
||||
<div doc-tutorial-reset="8"></div>
|
||||
|
||||
|
||||
@@ -32,17 +32,18 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"angular": "1.4.x",
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0"
|
||||
"angular-route": "1.4.x",
|
||||
"angular-resource": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The new dependency `"angular-resource": "~1.3.0"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.3.x. We must ask bower to download
|
||||
The new dependency `"angular-resource": "1.4.x"` tells bower to install a version of the
|
||||
angular-resource component that is compatible with version 1.4.x. We must ask bower to download
|
||||
and install this dependency. We can do this by running:
|
||||
|
||||
```
|
||||
|
||||
@@ -36,20 +36,20 @@ We are using [Bower][bower] to install client side dependencies. This step upda
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.3.0",
|
||||
"angular-mocks": "~1.3.0",
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "~1.3.0",
|
||||
"angular-resource": "~1.3.0",
|
||||
"angular": "1.4.x",
|
||||
"angular-mocks": "1.4.x",
|
||||
"jquery": "~2.1.1",
|
||||
"angular-animate": "~1.3.0"
|
||||
"bootstrap": "~3.1.1",
|
||||
"angular-route": "1.4.x",
|
||||
"angular-resource": "1.4.x",
|
||||
"angular-animate": "1.4.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* `"angular-animate": "~1.3.0"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.3.x.
|
||||
* `"jquery": "2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
* `"angular-animate": "1.4.x"` tells bower to install a version of the
|
||||
angular-animate component that is compatible with version 1.4.x.
|
||||
* `"jquery": "~2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
|
||||
Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd
|
||||
party libraries.
|
||||
|
||||
@@ -111,7 +111,7 @@ __`app/index.html`.__
|
||||
```
|
||||
|
||||
<div class="alert alert-error">
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.3; jQuery 1.x is
|
||||
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.4; jQuery 1.x is
|
||||
not officially supported.
|
||||
Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and
|
||||
animations will not work as expected.
|
||||
@@ -239,9 +239,9 @@ The name of the starting class is the name of the event that is fired (like `ent
|
||||
The active class name is the same as the starting class's but with an `-active` suffix.
|
||||
This two-class CSS naming convention allows the developer to craft an animation, beginning to end.
|
||||
|
||||
In our example above, elements expand from a height of **0** to **120 pixels** when items are added or moved,
|
||||
around and collapsing the items before removing them from the list.
|
||||
There's also a nice fade-in and fade-out effect that also occurs at the same time. All of this is handled
|
||||
In our example above, elements are expanded from a height of **0** to **120 pixels** when they're added to the
|
||||
list and are collapsed back down to **0 pixels** before being removed from the list.
|
||||
There's also a nice fade-in and fade-out effect that occurs at the same time. All of this is handled
|
||||
by the CSS transition declarations at the top of the example code above.
|
||||
|
||||
Although most modern browsers have good support for [CSS transitions](http://caniuse.com/#feat=css-transitions)
|
||||
@@ -357,10 +357,10 @@ For more on CSS animations, see the
|
||||
## Animating `ngClass` with JavaScript
|
||||
|
||||
Let's add another animation to our application. Switching to our `phone-detail.html` page,
|
||||
we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page,
|
||||
we see that we have a nice thumbnail swapper. By hovering over the thumbnails listed on the page,
|
||||
the profile phone image changes. But how can we change this around to add animations?
|
||||
|
||||
Let's think about it first. Basically, when you click on a thumbnail image, you're changing the
|
||||
Let's think about it first. Basically, when you hover over a thumbnail image, you're changing the
|
||||
state of the profile image to reflect the newly selected thumbnail image.
|
||||
The best way to specify state changes within HTML is to use classes.
|
||||
Much like before, how we used a CSS class to specify an animation, this time the animation will
|
||||
@@ -369,7 +369,7 @@ occur whenever the CSS class itself changes.
|
||||
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
|
||||
to the matching profile image and the animation plays.
|
||||
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first. Notice that we
|
||||
Let's get started and tweak our HTML code on the `phone-detail.html` page first. Notice that we
|
||||
have changed the way we display our large image:
|
||||
|
||||
__`app/partials/phone-detail.html`.__
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 76 KiB |
+1
-1
@@ -5,7 +5,7 @@ set -e
|
||||
BASE_DIR=`dirname $0`
|
||||
cd $BASE_DIR
|
||||
|
||||
./run-tests.sh
|
||||
npm run test-i18n
|
||||
|
||||
node src/closureSlurper.js
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
PARENT_DIR="$(dirname "$0")"
|
||||
|
||||
../node_modules/.bin/jasmine-node "$PARENT_DIR"/spec/
|
||||
@@ -4,6 +4,7 @@ findLocaleId = closureI18nExtractor.findLocaleId;
|
||||
extractNumberSymbols = closureI18nExtractor.extractNumberSymbols;
|
||||
extractCurrencySymbols = closureI18nExtractor.extractCurrencySymbols;
|
||||
extractDateTimeSymbols = closureI18nExtractor.extractDateTimeSymbols;
|
||||
outputLocale = closureI18nExtractor.outputLocale;
|
||||
|
||||
|
||||
function newTestLocaleInfo() {
|
||||
@@ -11,6 +12,8 @@ function newTestLocaleInfo() {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
STANDALONEMONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
@@ -70,7 +73,7 @@ describe("findLocaleId", function() {
|
||||
it("should throw an error otherwise", function() {
|
||||
expect(function() {
|
||||
findLocaleId("str", "otherwise")
|
||||
}).toThrow("unknown type in findLocaleId: otherwise");
|
||||
}).toThrowError("unknown type in findLocaleId: otherwise");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -129,7 +132,10 @@ describe("extractCurrencySymbols", function() {
|
||||
].join('\n');
|
||||
|
||||
var localeInfo = {};
|
||||
expect(extractCurrencySymbols(CONTENT)).toEqual({
|
||||
var currencySymbols = extractCurrencySymbols(CONTENT);
|
||||
expect(currencySymbols.GBP).toEqual([2, '£', 'GB£']);
|
||||
expect(currencySymbols.AOA).toEqual([2, 'Kz', 'Kz']);
|
||||
expect(currencySymbols).toEqual({
|
||||
'GBP':[2, '£', 'GB£'],
|
||||
'AOA':[2, 'Kz', 'Kz']
|
||||
});
|
||||
@@ -140,69 +146,71 @@ describe("extractCurrencySymbols", function() {
|
||||
describe("extractDateTimeSymbols", function() {
|
||||
it("should extract date time data", function() {
|
||||
var CONTENT = [
|
||||
"goog.i18n.DateTimeSymbols_fr_CA = {",
|
||||
" ERAS: ['av. J.-C.', 'ap. J.-C.'],",
|
||||
" ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],",
|
||||
" NARROWMONTHS: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'],",
|
||||
" STANDALONENARROWMONTHS: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O',",
|
||||
" 'N', 'D'],",
|
||||
" MONTHS: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',",
|
||||
" 'août', 'septembre', 'octobre', 'novembre', 'décembre'],",
|
||||
" STANDALONEMONTHS: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',",
|
||||
" 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],",
|
||||
" SHORTMONTHS: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.',",
|
||||
" 'août', 'sept.', 'oct.', 'nov.', 'déc.'],",
|
||||
" STANDALONESHORTMONTHS: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',",
|
||||
" 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],",
|
||||
" WEEKDAYS: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi',",
|
||||
" 'samedi'],",
|
||||
" STANDALONEWEEKDAYS: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi',",
|
||||
" 'vendredi', 'samedi'],",
|
||||
" SHORTWEEKDAYS: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],",
|
||||
" STANDALONESHORTWEEKDAYS: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.',",
|
||||
" 'sam.'],",
|
||||
" NARROWWEEKDAYS: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],",
|
||||
" STANDALONENARROWWEEKDAYS: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],",
|
||||
" SHORTQUARTERS: ['T1', 'T2', 'T3', 'T4'],",
|
||||
" QUARTERS: ['1er trimestre', '2e trimestre', '3e trimestre', '4e trimestre'],",
|
||||
" AMPMS: ['AM', 'PM'],",
|
||||
" DATEFORMATS: ['EEEE d MMMM y', 'd MMMM y', 'yyyy-MM-dd', 'yy-MM-dd'],",
|
||||
" TIMEFORMATS: ['HH \\'h\\' mm \\'min\\' ss \\'s\\' zzzz', 'HH:mm:ss z',",
|
||||
" 'HH:mm:ss', 'HH:mm'],",
|
||||
" FIRSTDAYOFWEEK: 6,",
|
||||
" WEEKENDRANGE: [5, 6],",
|
||||
" FIRSTWEEKCUTOFFDAY: 2",
|
||||
"};"
|
||||
"goog.i18n.DateTimeSymbols_fr_CA = {",
|
||||
" ERAS: ['av. J.-C.', 'ap. J.-C.'],",
|
||||
" ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],",
|
||||
" NARROWMONTHS: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'],",
|
||||
" STANDALONENARROWMONTHS: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O',",
|
||||
" 'N', 'D'],",
|
||||
" MONTHS: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',",
|
||||
" 'août', 'septembre', 'octobre', 'novembre', 'décembre'],",
|
||||
" STANDALONEMONTHS: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',",
|
||||
" 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],",
|
||||
" SHORTMONTHS: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.',",
|
||||
" 'août', 'sept.', 'oct.', 'nov.', 'déc.'],",
|
||||
" STANDALONESHORTMONTHS: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',",
|
||||
" 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],",
|
||||
" WEEKDAYS: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi',",
|
||||
" 'samedi'],",
|
||||
" STANDALONEWEEKDAYS: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi',",
|
||||
" 'vendredi', 'samedi'],",
|
||||
" SHORTWEEKDAYS: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],",
|
||||
" STANDALONESHORTWEEKDAYS: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.',",
|
||||
" 'sam.'],",
|
||||
" NARROWWEEKDAYS: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],",
|
||||
" STANDALONENARROWWEEKDAYS: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],",
|
||||
" SHORTQUARTERS: ['T1', 'T2', 'T3', 'T4'],",
|
||||
" QUARTERS: ['1er trimestre', '2e trimestre', '3e trimestre', '4e trimestre'],",
|
||||
" AMPMS: ['AM', 'PM'],",
|
||||
" DATEFORMATS: ['EEEE d MMMM y', 'd MMMM y', 'yyyy-MM-dd', 'yy-MM-dd'],",
|
||||
" TIMEFORMATS: ['HH \\'h\\' mm \\'min\\' ss \\'s\\' zzzz', 'HH:mm:ss z',",
|
||||
" 'HH:mm:ss', 'HH:mm'],",
|
||||
" FIRSTDAYOFWEEK: 6,",
|
||||
" WEEKENDRANGE: [5, 6],",
|
||||
" FIRSTWEEKCUTOFFDAY: 2",
|
||||
"};"
|
||||
].join('\n');
|
||||
var localeInfo = {};
|
||||
var expectedLocaleInfo = {
|
||||
fr_CA: {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
SHORTDAY: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
WEEKENDRANGE: [5, 6],
|
||||
AMPMS: ['AM', 'PM'],
|
||||
ERAS: ['av. J.-C.', 'ap. J.-C.'],
|
||||
ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],
|
||||
medium: 'yyyy-MM-dd HH:mm:ss',
|
||||
short: 'yy-MM-dd HH:mm',
|
||||
fullDate: 'EEEE d MMMM y',
|
||||
longDate: 'd MMMM y',
|
||||
mediumDate: 'yyyy-MM-dd',
|
||||
shortDate: 'yy-MM-dd',
|
||||
mediumTime: 'HH:mm:ss',
|
||||
shortTime: 'HH:mm'
|
||||
}
|
||||
var localeInfo = {};
|
||||
var expectedLocaleInfo = {
|
||||
fr_CA: {
|
||||
DATETIME_FORMATS: {
|
||||
MONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre',
|
||||
'octobre', 'novembre', 'décembre'],
|
||||
STANDALONEMONTH: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
|
||||
'août', 'septembre', 'octobre', 'novembre', 'décembre'],
|
||||
SHORTMONTH: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.',
|
||||
'nov.', 'déc.'],
|
||||
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
|
||||
SHORTDAY: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
|
||||
FIRSTDAYOFWEEK: 6,
|
||||
WEEKENDRANGE: [5, 6],
|
||||
AMPMS: ['AM', 'PM'],
|
||||
ERAS: ['av. J.-C.', 'ap. J.-C.'],
|
||||
ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],
|
||||
medium: 'yyyy-MM-dd HH:mm:ss',
|
||||
short: 'yy-MM-dd HH:mm',
|
||||
fullDate: 'EEEE d MMMM y',
|
||||
longDate: 'd MMMM y',
|
||||
mediumDate: 'yyyy-MM-dd',
|
||||
shortDate: 'yy-MM-dd',
|
||||
mediumTime: 'HH:mm:ss',
|
||||
shortTime: 'HH:mm'
|
||||
}
|
||||
};
|
||||
extractDateTimeSymbols(CONTENT, localeInfo);
|
||||
expect(localeInfo).toEqual(expectedLocaleInfo);
|
||||
})
|
||||
}
|
||||
};
|
||||
extractDateTimeSymbols(CONTENT, localeInfo);
|
||||
expect(localeInfo).toEqual(expectedLocaleInfo);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pluralExtractor", function() {
|
||||
@@ -268,3 +276,10 @@ describe("serializeContent", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("outputLocale", function() {
|
||||
it("should render the correct locale ids", function() {
|
||||
var output = outputLocale(newTestLocaleInfo(), 'fr_CA');
|
||||
expect(output).toContain('"id": "fr-ca"');
|
||||
expect(output).toContain('"localeID": "fr_CA"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ describe("convertNumberData", function() {
|
||||
describe("convertDatetimeData", function() {
|
||||
var convert = converter.convertDatetimeData,
|
||||
dataObj = { MONTHS: ['Enero', 'Pebrero'],
|
||||
STANDALONEMONTHS: ['Enero', 'Pebrero'],
|
||||
SHORTMONTHS: ['Ene', 'Peb'],
|
||||
WEEKDAYS: ['Linggo', 'Lunes'],
|
||||
SHORTWEEKDAYS: ['Lin', 'Lun'],
|
||||
@@ -37,6 +38,7 @@ describe("convertDatetimeData", function() {
|
||||
it('should convert empty datetime obj', function() {
|
||||
var processedData = convert(dataObj);
|
||||
expect(processedData.MONTH).toEqual(['Enero', 'Pebrero']);
|
||||
expect(processedData.STANDALONEMONTH).toEqual(['Enero', 'Pebrero']);
|
||||
expect(processedData.SHORTMONTH).toEqual(['Ene', 'Peb']);
|
||||
expect(processedData.DAY).toEqual(['Linggo', 'Lunes']);
|
||||
expect(processedData.SHORTDAY).toEqual(['Lin', 'Lun']);
|
||||
|
||||
@@ -50,10 +50,10 @@ function extractNumberSymbols(content, localeInfo, currencySymbols) {
|
||||
function extractCurrencySymbols(content) {
|
||||
//eval script in the current context so that we get access to all the symbols
|
||||
eval(content.toString());
|
||||
var currencySymbols = goog.i18n.currency.CurrencyInfo;
|
||||
currencySymbols.__proto__ = goog.i18n.currency.CurrencyInfoTier2;
|
||||
// var currencySymbols = goog.i18n.currency.CurrencyInfo;
|
||||
// currencySymbols.__proto__ = goog.i18n.currency.CurrencyInfoTier2;
|
||||
|
||||
return currencySymbols;
|
||||
return Object.assign({}, goog.i18n.currency.CurrencyInfoTier2, goog.i18n.currency.CurrencyInfo);
|
||||
}
|
||||
|
||||
function extractDateTimeSymbols(content, localeInfo) {
|
||||
@@ -79,7 +79,7 @@ function pluralExtractor(content, localeInfo) {
|
||||
goog.LOCALE = localeIds[i].match(/[^_]+/)[0];
|
||||
try {
|
||||
eval(contentText);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.log("Error in eval(contentText): " + e.stack);
|
||||
}
|
||||
if (!goog.i18n.pluralRules.select) {
|
||||
@@ -133,7 +133,7 @@ function canonicalizeForJsonStringify(unused_key, object) {
|
||||
|
||||
function serializeContent(localeObj) {
|
||||
return JSON.stringify(localeObj, canonicalizeForJsonStringify, ' ')
|
||||
.replace(new RegExp('[\\u007f-\\uffff]', 'g'), function(c) { return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4); })
|
||||
.replace(new RegExp('[\\u007f-\\uffff]', 'g'), function(c) { return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); })
|
||||
.replace(/"@@|@@"/g, '');
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ function outputLocale(localeInfo, localeID) {
|
||||
if (!localeObj.DATETIME_FORMATS) {
|
||||
localeObj.DATETIME_FORMATS = fallBackObj.DATETIME_FORMATS;
|
||||
}
|
||||
localeObj.localeID = localeID;
|
||||
localeObj.id = correctedLocaleId(localeID);
|
||||
|
||||
var getDecimals = [
|
||||
@@ -201,10 +202,11 @@ function outputLocale(localeInfo, localeID) {
|
||||
DATETIME_FORMATS: localeObj.DATETIME_FORMATS,
|
||||
NUMBER_FORMATS: localeObj.NUMBER_FORMATS,
|
||||
pluralCat: localeObj.pluralCat,
|
||||
id: localeObj.id
|
||||
id: localeObj.id,
|
||||
localeID: localeID
|
||||
};
|
||||
|
||||
var content = serializeContent(localeInfo[localeID]);
|
||||
var content = serializeContent(localeObj);
|
||||
if (content.indexOf('getVF(') < 0) {
|
||||
getVF = '';
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ function convertDatetimeData(dataObj) {
|
||||
|
||||
datetimeFormats.MONTH = dataObj.MONTHS;
|
||||
datetimeFormats.SHORTMONTH = dataObj.SHORTMONTHS;
|
||||
datetimeFormats.STANDALONEMONTH = dataObj.STANDALONEMONTHS;
|
||||
datetimeFormats.DAY = dataObj.WEEKDAYS;
|
||||
datetimeFormats.SHORTDAY = dataObj.SHORTWEEKDAYS;
|
||||
datetimeFormats.AMPMS = dataObj.AMPMS;
|
||||
|
||||
@@ -2142,7 +2142,7 @@ queue}</string>
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr
|
||||
|
||||
\f0\fs22 \cf2 $scope\
|
||||
name='Wold'}</string>
|
||||
name='World'}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Browserstack tunnel"
|
||||
echo "TODO: implement me"
|
||||
exit 1
|
||||
@@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var glob = require("glob");
|
||||
var _ = require('lodash');
|
||||
var files = require('../../angularFiles').files;
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('validate-angular-files', function() {
|
||||
var combinedFiles = _.clone(files.angularModules);
|
||||
combinedFiles.ng = files.angularSrc;
|
||||
combinedFiles.angularLoader = files.angularLoader;
|
||||
|
||||
var errorsDetected = false;
|
||||
var directories = [];
|
||||
var detectedFiles = {};
|
||||
|
||||
for (var section in combinedFiles) {
|
||||
var sectionFiles = combinedFiles[section];
|
||||
|
||||
if (section != 'angularLoader') {
|
||||
directories.push('src/' + section);
|
||||
}
|
||||
|
||||
grunt.log.debug('Validating ' + sectionFiles.length + ' files from the "' + section + '" module.');
|
||||
|
||||
sectionFiles.forEach(function(file) {
|
||||
detectedFiles[file] = true;
|
||||
|
||||
if (!fs.existsSync(file)) {
|
||||
grunt.log.error(file + ' does not exist in the local file structure.');
|
||||
errorsDetected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
directories.forEach(function(directory) {
|
||||
glob.sync(directory + '/**/*').forEach(function(filePath) {
|
||||
if (!fs.lstatSync(filePath).isDirectory()) {
|
||||
var fileName = path.basename(filePath);
|
||||
var isHiddenFile = fileName[0] == '.';
|
||||
if (!isHiddenFile && !detectedFiles[filePath]) {
|
||||
grunt.log.error(filePath + ' exists in the local file structure but isn\'t used by any module.');
|
||||
errorsDetected = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (errorsDetected) {
|
||||
throw new Error('Not all files were properly detected in the local file structure.');
|
||||
} else {
|
||||
grunt.log.ok('All files were detected successfully!');
|
||||
}
|
||||
});
|
||||
};
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
|
||||
echo "Shutting down Sauce Connect tunnel"
|
||||
|
||||
killall sc
|
||||
|
||||
while [[ -n `ps -ef | grep "sauce-connect-" | grep -v "grep"` ]]; do
|
||||
printf "."
|
||||
sleep .5
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Sauce Connect tunnel has been shut down"
|
||||
+4577
-872
File diff suppressed because it is too large
Load Diff
Generated
+7591
-1863
File diff suppressed because it is too large
Load Diff
+28
-14
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "angularjs",
|
||||
"license": "MIT",
|
||||
"branchVersion": "^1.4.0-beta.0",
|
||||
"branchVersion": "1.4.x",
|
||||
"branchPattern": "1.4.*",
|
||||
"distTag": "latest",
|
||||
"distTag": "previous_1_4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/angular/angular.js.git"
|
||||
@@ -13,6 +13,12 @@
|
||||
"npm": "~2.5"
|
||||
},
|
||||
"engineStrict": true,
|
||||
"scripts": {
|
||||
"preinstall": "node scripts/npm/check-node-modules.js --purge",
|
||||
"postinstall": "node scripts/npm/copy-npm-shrinkwrap.js",
|
||||
"commit": "git-cz",
|
||||
"test-i18n": "jasmine-node i18n/spec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-benchpress": "0.x.x",
|
||||
"benchmark": "1.x.x",
|
||||
@@ -20,9 +26,12 @@
|
||||
"browserstacktunnel-wrapper": "~1.3.1",
|
||||
"canonical-path": "0.0.2",
|
||||
"cheerio": "^0.17.0",
|
||||
"commitizen": "^2.3.0",
|
||||
"cz-conventional-changelog": "^1.1.4",
|
||||
"dgeni": "^0.4.0",
|
||||
"dgeni-packages": "^0.10.0",
|
||||
"dgeni-packages": "^0.11.0",
|
||||
"event-stream": "~3.1.0",
|
||||
"glob": "^6.0.1",
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-bump": "~0.0.13",
|
||||
"grunt-contrib-clean": "~0.6.0",
|
||||
@@ -43,18 +52,18 @@
|
||||
"gulp-sourcemaps": "^1.2.2",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"jasmine-node": "~1.14.5",
|
||||
"jasmine-node": "^2.0.0",
|
||||
"jasmine-reporters": "~1.0.1",
|
||||
"jshint-stylish": "~1.0.0",
|
||||
"karma": "0.12.32",
|
||||
"karma-browserstack-launcher": "0.1.2",
|
||||
"karma-chrome-launcher": "0.1.5",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-jasmine": "0.1.5",
|
||||
"karma-junit-reporter": "0.2.2",
|
||||
"karma-ng-scenario": "0.1.0",
|
||||
"karma-sauce-launcher": "0.2.10",
|
||||
"karma-script-launcher": "0.1.0",
|
||||
"karma": "^0.13.19",
|
||||
"karma-browserstack-launcher": "^0.1.8",
|
||||
"karma-chrome-launcher": "^0.2.2",
|
||||
"karma-firefox-launcher": "^0.1.7",
|
||||
"karma-jasmine": "^0.1.6",
|
||||
"karma-junit-reporter": "^0.3.8",
|
||||
"karma-ng-scenario": "^0.1.0",
|
||||
"karma-sauce-launcher": "^0.3.0",
|
||||
"karma-script-launcher": "^0.1.0",
|
||||
"load-grunt-tasks": "~0.6.0",
|
||||
"lodash": "~2.4.1",
|
||||
"marked": "~0.3.0",
|
||||
@@ -76,5 +85,10 @@
|
||||
"url": "https://github.com/angular/angular.js/blob/master/LICENSE"
|
||||
}
|
||||
],
|
||||
"dependencies": {}
|
||||
"dependencies": {},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ config.specs = [
|
||||
];
|
||||
|
||||
config.capabilities = {
|
||||
browserName: 'chrome',
|
||||
browserName: 'chrome'
|
||||
};
|
||||
|
||||
exports.config = config;
|
||||
|
||||
@@ -9,7 +9,7 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'chrome',
|
||||
platform: 'MAC',
|
||||
version: '34'
|
||||
version: '49'
|
||||
}),
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'firefox',
|
||||
@@ -18,7 +18,7 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
capabilitiesForBrowserStack({
|
||||
browserName: 'safari',
|
||||
platform: 'MAC',
|
||||
version: '7'
|
||||
version: '9'
|
||||
})
|
||||
];
|
||||
} else {
|
||||
@@ -28,8 +28,8 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
config.multiCapabilities = [
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'chrome',
|
||||
platform: 'OS X 10.9',
|
||||
version: '34'
|
||||
platform: 'OS X 10.11',
|
||||
version: '48'
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'firefox',
|
||||
@@ -37,8 +37,8 @@ if (process.env.BROWSER_PROVIDER === 'browserstack') {
|
||||
}),
|
||||
capabilitiesForSauceLabs({
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
platform: 'OS X 10.11',
|
||||
version: '9'
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ function init {
|
||||
TMP_DIR=$(resolveDir ../../tmp)
|
||||
BUILD_DIR=$(resolveDir ../../build)
|
||||
NEW_VERSION=$(cat $BUILD_DIR/version.txt)
|
||||
PROJECT_DIR=$(resolveDir ../..)
|
||||
# get the npm dist-tag from a custom property (distTag) in package.json
|
||||
DIST_TAG=$(readJsonProp "package.json" "distTag")
|
||||
DIST_TAG=$(readJsonProp "$PROJECT_DIR/package.json" "distTag")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ echo "#################################"
|
||||
echo "#### Jenkins Build ############"
|
||||
echo "#################################"
|
||||
|
||||
source scripts/jenkins/set-node-version.sh
|
||||
|
||||
# Enable tracing and exit on first failure
|
||||
set -xe
|
||||
|
||||
@@ -19,6 +21,7 @@ rm -f angular.js.size
|
||||
|
||||
|
||||
# BUILD #
|
||||
npm install -g grunt-cli
|
||||
npm install --color false
|
||||
grunt ci-checks package --no-color
|
||||
|
||||
@@ -4,9 +4,7 @@ echo "#################################"
|
||||
echo "#### Update master ##############"
|
||||
echo "#################################"
|
||||
|
||||
ARG_DEFS=(
|
||||
"[--no-test=(true|false)]"
|
||||
)
|
||||
ARG_DEFS=()
|
||||
|
||||
function init {
|
||||
if [[ ! $VERBOSE ]]; then
|
||||
@@ -17,14 +15,7 @@ function init {
|
||||
|
||||
function build {
|
||||
cd ../..
|
||||
|
||||
if [[ $NO_TEST == "true" ]]; then
|
||||
npm install --color false
|
||||
grunt ci-checks package --no-color
|
||||
else
|
||||
./jenkins_build.sh
|
||||
fi
|
||||
|
||||
scripts/jenkins/build.sh
|
||||
cd $SCRIPT_DIR
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,10 @@ function init {
|
||||
}
|
||||
|
||||
function build {
|
||||
source ./set-node-version.sh
|
||||
cd ../..
|
||||
|
||||
npm install -g grunt-cli
|
||||
npm install --color false
|
||||
grunt ci-checks package --no-color
|
||||
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install nvm for this shell
|
||||
source ~/.nvm/nvm.sh
|
||||
|
||||
# Use node.js at 4.2.x
|
||||
nvm install 4.4
|
||||
@@ -0,0 +1,74 @@
|
||||
// Implementation based on:
|
||||
// https://github.com/angular/angular/blob/3b9c08676a4c921bbfa847802e08566fb601ba7a/tools/npm/check-node-modules.js
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Constants
|
||||
var PROJECT_ROOT = path.join(__dirname, '../../');
|
||||
var NODE_MODULES_DIR = 'node_modules';
|
||||
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
|
||||
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
var purgeIfStale = process.argv.indexOf('--purge') !== -1;
|
||||
|
||||
process.chdir(PROJECT_ROOT);
|
||||
checkNodeModules(purgeIfStale);
|
||||
}
|
||||
|
||||
function checkNodeModules(purgeIfStale) {
|
||||
var nodeModulesOk = compareMarkerFiles(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE);
|
||||
|
||||
if (nodeModulesOk) {
|
||||
console.log(':-) npm dependencies are looking good!');
|
||||
} else if (purgeIfStale) {
|
||||
console.log(':-( npm dependencies are stale or in an unknown state!');
|
||||
console.log(' Purging \'' + NODE_MODULES_DIR + '\'...');
|
||||
deleteDirSync(NODE_MODULES_DIR);
|
||||
} else {
|
||||
var separator = new Array(81).join('!');
|
||||
|
||||
console.warn(separator);
|
||||
console.warn(':-( npm dependencies are stale or in an unknown state!');
|
||||
console.warn('You can rebuild the dependencies by running `npm install`.');
|
||||
console.warn(separator);
|
||||
}
|
||||
|
||||
return nodeModulesOk;
|
||||
}
|
||||
|
||||
function compareMarkerFiles(markerFilePath, cachedMarkerFilePath) {
|
||||
if (!fs.existsSync(cachedMarkerFilePath)) return false;
|
||||
|
||||
var opts = {encoding: 'utf-8'};
|
||||
var markerContent = fs.readFileSync(markerFilePath, opts);
|
||||
var cachedMarkerContent = fs.readFileSync(cachedMarkerFilePath, opts);
|
||||
|
||||
return markerContent === cachedMarkerContent;
|
||||
}
|
||||
|
||||
// Custom implementation of `rm -rf` that works consistently across OSes
|
||||
function deleteDirSync(path) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.readdirSync(path).forEach(deleteDirOrFileSync);
|
||||
fs.rmdirSync(path);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
function deleteDirOrFileSync(subpath) {
|
||||
var curPath = path + '/' + subpath;
|
||||
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteDirSync(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
// Imports
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Constants
|
||||
var PROJECT_ROOT = path.join(__dirname, '../../');
|
||||
var NODE_MODULES_DIR = 'node_modules';
|
||||
var NPM_SHRINKWRAP_FILE = 'npm-shrinkwrap.json';
|
||||
var NPM_SHRINKWRAP_CACHED_FILE = NODE_MODULES_DIR + '/npm-shrinkwrap.cached.json';
|
||||
|
||||
// Run
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
process.chdir(PROJECT_ROOT);
|
||||
copyFile(NPM_SHRINKWRAP_FILE, NPM_SHRINKWRAP_CACHED_FILE, onCopied);
|
||||
}
|
||||
|
||||
// Implementation based on:
|
||||
// https://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js#answer-21995878
|
||||
function copyFile(srcPath, dstPath, callback) {
|
||||
var callbackCalled = false;
|
||||
|
||||
if (!fs.existsSync(srcPath)) {
|
||||
done(new Error('Missing source file: ' + srcPath));
|
||||
return;
|
||||
}
|
||||
|
||||
var rs = fs.createReadStream(srcPath);
|
||||
rs.on('error', done);
|
||||
|
||||
var ws = fs.createWriteStream(dstPath);
|
||||
ws.on('error', done);
|
||||
ws.on('finish', done);
|
||||
|
||||
rs.pipe(ws);
|
||||
|
||||
// Helpers
|
||||
function done(err) {
|
||||
if (callback && !callbackCalled) {
|
||||
callbackCalled = true;
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCopied(err) {
|
||||
if (err) {
|
||||
var separator = new Array(81).join('!');
|
||||
|
||||
console.error(separator);
|
||||
console.error(
|
||||
'Failed to copy `' + NPM_SHRINKWRAP_FILE + '` to `' + NPM_SHRINKWRAP_CACHED_FILE + '`:');
|
||||
console.error(err);
|
||||
console.error(separator);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SHRINKWRAP_FILE=npm-shrinkwrap.json
|
||||
SHRINKWRAP_CACHED_FILE=node_modules/npm-shrinkwrap.cached.json
|
||||
|
||||
if diff -q $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE; then
|
||||
echo 'No shrinkwrap changes detected. npm install will be skipped...';
|
||||
else
|
||||
echo 'Blowing away node_modules and reinstalling npm dependencies...'
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
cp $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE
|
||||
echo 'npm install successful!'
|
||||
fi
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p $LOGS_DIR
|
||||
|
||||
if [ $JOB != "ci-checks" ]; then
|
||||
echo "start_browser_provider"
|
||||
./scripts/travis/start_browser_provider.sh
|
||||
fi
|
||||
|
||||
npm install -g grunt-cli
|
||||
|
||||
if [ $JOB != "ci-checks" ]; then
|
||||
grunt package
|
||||
echo "wait_for_browser_provider"
|
||||
./scripts/travis/wait_for_browser_provider.sh
|
||||
fi
|
||||
@@ -5,7 +5,9 @@ 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 [ $JOB = "ci-checks" ]; then
|
||||
grunt ci-checks
|
||||
elif [ $JOB = "unit" ]; then
|
||||
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
|
||||
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS"
|
||||
else
|
||||
@@ -14,7 +16,6 @@ if [ $JOB = "unit" ]; then
|
||||
|
||||
grunt test:promises-aplus
|
||||
grunt test:unit --browsers $BROWSERS --reporters dots
|
||||
grunt ci-checks
|
||||
grunt tests:docs --browsers $BROWSERS --reporters dots
|
||||
elif [ $JOB = "docs-e2e" ]; then
|
||||
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
|
||||
@@ -31,5 +32,5 @@ elif [ $JOB = "e2e" ]; then
|
||||
export TARGET_SPECS="test/e2e/tests/**/*.js,$TARGET_SPECS"
|
||||
grunt test:travis-protractor --specs "$TARGET_SPECS"
|
||||
else
|
||||
echo "Unknown job type. Please set JOB=unit or JOB=e2e-*."
|
||||
echo "Unknown job type. Please set JOB=ci-checks, JOB=unit or JOB=e2e-*."
|
||||
fi
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Has to be run from project root directory.
|
||||
|
||||
./lib/${BROWSER_PROVIDER}/teardown_tunnel.sh
|
||||
@@ -140,6 +140,7 @@
|
||||
"jqLiteInheritedData": false,
|
||||
"jqLiteBuildFragment": false,
|
||||
"jqLiteParseHTML": false,
|
||||
"jqLiteWrapNode": false,
|
||||
"getBooleanAttrName": false,
|
||||
"getAliasedAttrName": false,
|
||||
"createEventHandler": false,
|
||||
|
||||
+142
-106
@@ -198,20 +198,25 @@ msie = document.documentMode;
|
||||
* String ...)
|
||||
*/
|
||||
function isArrayLike(obj) {
|
||||
if (obj == null || isWindow(obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `null`, `undefined` and `window` are not array-like
|
||||
if (obj == null || isWindow(obj)) return false;
|
||||
|
||||
// arrays, strings and jQuery/jqLite objects are array like
|
||||
// * jqLite is either the jQuery or jqLite constructor function
|
||||
// * we have to check the existance of jqLite first as this method is called
|
||||
// via the forEach method when constructing the jqLite object in the first place
|
||||
if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true;
|
||||
|
||||
// Support: iOS 8.2 (not reproducible in simulator)
|
||||
// "length" in obj used to prevent JIT error (gh-11508)
|
||||
var length = "length" in Object(obj) && obj.length;
|
||||
|
||||
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
|
||||
return true;
|
||||
}
|
||||
// NodeList objects (with `item` method) and
|
||||
// other objects with suitable length characteristics are array-like
|
||||
return isNumber(length) &&
|
||||
(length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function');
|
||||
|
||||
return isString(obj) || isArray(obj) || length === 0 ||
|
||||
typeof length === 'number' && length > 0 && (length - 1) in obj;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +236,7 @@ function isArrayLike(obj) {
|
||||
*
|
||||
* Unlike ES262's
|
||||
* [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
|
||||
* Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
|
||||
* providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
|
||||
* return the value provided.
|
||||
*
|
||||
```js
|
||||
@@ -308,7 +313,7 @@ function forEachSorted(obj, iterator, context) {
|
||||
* @returns {function(*, string)}
|
||||
*/
|
||||
function reverseParams(iteratorFn) {
|
||||
return function(value, key) { iteratorFn(key, value); };
|
||||
return function(value, key) {iteratorFn(key, value);};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +361,10 @@ function baseExtend(dst, objs, deep) {
|
||||
dst[key] = new Date(src.valueOf());
|
||||
} else if (isRegExp(src)) {
|
||||
dst[key] = new RegExp(src);
|
||||
} else if (src.nodeName) {
|
||||
dst[key] = src.cloneNode(true);
|
||||
} else if (isElement(src)) {
|
||||
dst[key] = src.clone();
|
||||
} else {
|
||||
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
|
||||
baseExtend(dst[key], [src], true);
|
||||
@@ -457,12 +466,22 @@ noop.$inject = [];
|
||||
* functional style.
|
||||
*
|
||||
```js
|
||||
function transformer(transformationFn, value) {
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
function transformer(transformationFn, value) {
|
||||
return (transformationFn || angular.identity)(value);
|
||||
};
|
||||
|
||||
// E.g.
|
||||
function getResult(fn, input) {
|
||||
return (fn || angular.identity)(input);
|
||||
};
|
||||
|
||||
getResult(function(n) { return n * 2; }, 21); // returns 42
|
||||
getResult(null, 21); // returns 21
|
||||
getResult(undefined, 21); // returns 21
|
||||
```
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*
|
||||
* @param {*} value to be returned.
|
||||
* @returns {*} the value passed in.
|
||||
*/
|
||||
function identity($) {return $;}
|
||||
identity.$inject = [];
|
||||
@@ -471,7 +490,7 @@ identity.$inject = [];
|
||||
function valueFn(value) {return function() {return value;};}
|
||||
|
||||
function hasCustomToString(obj) {
|
||||
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
|
||||
return isFunction(obj.toString) && obj.toString !== toString;
|
||||
}
|
||||
|
||||
|
||||
@@ -670,9 +689,9 @@ function isPromiseLike(obj) {
|
||||
}
|
||||
|
||||
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
|
||||
var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
|
||||
function isTypedArray(value) {
|
||||
return TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
|
||||
}
|
||||
|
||||
|
||||
@@ -712,7 +731,7 @@ function isElement(node) {
|
||||
* @returns {object} in the form of {key1:true, key2:true, ...}
|
||||
*/
|
||||
function makeMap(str) {
|
||||
var obj = {}, items = str.split(","), i;
|
||||
var obj = {}, items = str.split(','), i;
|
||||
for (i = 0; i < items.length; i++) {
|
||||
obj[items[i]] = true;
|
||||
}
|
||||
@@ -794,100 +813,113 @@ function arrayRemove(array, value) {
|
||||
</file>
|
||||
</example>
|
||||
*/
|
||||
function copy(source, destination, stackSource, stackDest) {
|
||||
if (isWindow(source) || isScope(source)) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
if (isTypedArray(destination)) {
|
||||
throw ngMinErr('cpta',
|
||||
"Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
function copy(source, destination) {
|
||||
var stackSource = [];
|
||||
var stackDest = [];
|
||||
|
||||
if (!destination) {
|
||||
destination = source;
|
||||
if (isObject(source)) {
|
||||
var index;
|
||||
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
|
||||
return stackDest[index];
|
||||
}
|
||||
|
||||
// TypedArray, Date and RegExp have specific copy functionality and must be
|
||||
// pushed onto the stack before returning.
|
||||
// Array and other objects create the base object and recurse to copy child
|
||||
// objects. The array/object will be pushed onto the stack when recursed.
|
||||
if (isArray(source)) {
|
||||
return copy(source, [], stackSource, stackDest);
|
||||
} else if (isTypedArray(source)) {
|
||||
destination = new source.constructor(source);
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isRegExp(source)) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isFunction(source.cloneNode)) {
|
||||
destination = source.cloneNode(true);
|
||||
} else {
|
||||
var emptyObject = Object.create(getPrototypeOf(source));
|
||||
return copy(source, emptyObject, stackSource, stackDest);
|
||||
}
|
||||
|
||||
if (stackDest) {
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
}
|
||||
if (destination) {
|
||||
if (isTypedArray(destination)) {
|
||||
throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated.");
|
||||
}
|
||||
} else {
|
||||
if (source === destination) throw ngMinErr('cpi',
|
||||
"Can't copy! Source and destination are identical.");
|
||||
|
||||
stackSource = stackSource || [];
|
||||
stackDest = stackDest || [];
|
||||
|
||||
if (isObject(source)) {
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
if (source === destination) {
|
||||
throw ngMinErr('cpi', "Can't copy! Source and destination are identical.");
|
||||
}
|
||||
|
||||
// Empty the destination object
|
||||
if (isArray(destination)) {
|
||||
destination.length = 0;
|
||||
} else {
|
||||
forEach(destination, function(value, key) {
|
||||
if (key !== '$$hashKey') {
|
||||
delete destination[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
return copyRecurse(source, destination);
|
||||
}
|
||||
|
||||
return copyElement(source);
|
||||
|
||||
function copyRecurse(source, destination) {
|
||||
var h = destination.$$hashKey;
|
||||
var result, key;
|
||||
if (isArray(source)) {
|
||||
destination.length = 0;
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
destination.push(copy(source[i], null, stackSource, stackDest));
|
||||
for (var i = 0, ii = source.length; i < ii; i++) {
|
||||
destination.push(copyElement(source[i]));
|
||||
}
|
||||
} else if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var h = destination.$$hashKey;
|
||||
if (isArray(destination)) {
|
||||
destination.length = 0;
|
||||
} else {
|
||||
forEach(destination, function(value, key) {
|
||||
delete destination[key];
|
||||
});
|
||||
}
|
||||
if (isBlankObject(source)) {
|
||||
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
|
||||
for (key in source) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
} else if (source && typeof source.hasOwnProperty === 'function') {
|
||||
// Slow path, which must rely on hasOwnProperty
|
||||
for (key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
destination[key] = copy(source[key], null, stackSource, stackDest);
|
||||
}
|
||||
// Slowest path --- hasOwnProperty can't be called as a method
|
||||
for (key in source) {
|
||||
if (hasOwnProperty.call(source, key)) {
|
||||
destination[key] = copyElement(source[key]);
|
||||
}
|
||||
}
|
||||
setHashKey(destination,h);
|
||||
}
|
||||
setHashKey(destination, h);
|
||||
return destination;
|
||||
}
|
||||
|
||||
function copyElement(source) {
|
||||
// Simple values
|
||||
if (!isObject(source)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
// Already copied values
|
||||
var index = stackSource.indexOf(source);
|
||||
if (index !== -1) {
|
||||
return stackDest[index];
|
||||
}
|
||||
|
||||
if (isWindow(source) || isScope(source)) {
|
||||
throw ngMinErr('cpws',
|
||||
"Can't copy! Making copies of Window or Scope instances is not supported.");
|
||||
}
|
||||
|
||||
var needsRecurse = false;
|
||||
var destination;
|
||||
|
||||
if (isArray(source)) {
|
||||
destination = [];
|
||||
needsRecurse = true;
|
||||
} else if (isTypedArray(source)) {
|
||||
destination = new source.constructor(source);
|
||||
} else if (isDate(source)) {
|
||||
destination = new Date(source.getTime());
|
||||
} else if (isRegExp(source)) {
|
||||
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
|
||||
destination.lastIndex = source.lastIndex;
|
||||
} else if (isBlob(source)) {
|
||||
destination = new source.constructor([source], {type: source.type});
|
||||
} else if (isFunction(source.cloneNode)) {
|
||||
destination = source.cloneNode(true);
|
||||
} else {
|
||||
destination = Object.create(getPrototypeOf(source));
|
||||
needsRecurse = true;
|
||||
}
|
||||
|
||||
stackSource.push(source);
|
||||
stackDest.push(destination);
|
||||
|
||||
return needsRecurse
|
||||
? copyRecurse(source, destination)
|
||||
: destination;
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1158,7 +1190,7 @@ function toJsonReplacer(key, value) {
|
||||
* @returns {string|undefined} JSON-ified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
if (typeof obj === 'undefined') return undefined;
|
||||
if (isUndefined(obj)) return undefined;
|
||||
if (!isNumber(pretty)) {
|
||||
pretty = pretty ? 2 : null;
|
||||
}
|
||||
@@ -1185,7 +1217,10 @@ function fromJson(json) {
|
||||
}
|
||||
|
||||
|
||||
var ALL_COLONS = /:/g;
|
||||
function timezoneToOffset(timezone, fallback) {
|
||||
// IE/Edge do not "understand" colon (`:`) in timezone
|
||||
timezone = timezone.replace(ALL_COLONS, '');
|
||||
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
|
||||
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
|
||||
}
|
||||
@@ -1200,8 +1235,9 @@ function addDateMinutes(date, minutes) {
|
||||
|
||||
function convertTimezoneToLocal(date, timezone, reverse) {
|
||||
reverse = reverse ? -1 : 1;
|
||||
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
|
||||
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
|
||||
var dateTimezoneOffset = date.getTimezoneOffset();
|
||||
var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
|
||||
return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
|
||||
}
|
||||
|
||||
|
||||
@@ -1220,7 +1256,7 @@ function startingTag(element) {
|
||||
return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
|
||||
elemHtml.
|
||||
match(/^(<[^>]+>)/)[1].
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
|
||||
replace(/^<([\w\-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);});
|
||||
} catch (e) {
|
||||
return lowercase(elemHtml);
|
||||
}
|
||||
@@ -1568,7 +1604,7 @@ function bootstrap(element, modules, config) {
|
||||
//Encode angle brackets to prevent input from being sanitized to empty string #8683
|
||||
throw ngMinErr(
|
||||
'btstrpd',
|
||||
"App Already Bootstrapped with this Element '{0}'",
|
||||
"App already bootstrapped with this element '{0}'",
|
||||
tag.replace(/</,'<').replace(/>/,'>'));
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,10 @@
|
||||
$AnchorScrollProvider,
|
||||
$AnimateProvider,
|
||||
$CoreAnimateCssProvider,
|
||||
$$CoreAnimateJsProvider,
|
||||
$$CoreAnimateQueueProvider,
|
||||
$$CoreAnimateRunnerProvider,
|
||||
$$AnimateRunnerFactoryProvider,
|
||||
$$AnimateAsyncRunFactoryProvider,
|
||||
$BrowserProvider,
|
||||
$CacheFactoryProvider,
|
||||
$ControllerProvider,
|
||||
@@ -72,6 +74,7 @@
|
||||
$HttpParamSerializerProvider,
|
||||
$HttpParamSerializerJQLikeProvider,
|
||||
$HttpBackendProvider,
|
||||
$xhrFactoryProvider,
|
||||
$LocationProvider,
|
||||
$LogProvider,
|
||||
$ParseProvider,
|
||||
@@ -215,8 +218,10 @@ function publishExternalAPI(angular) {
|
||||
$anchorScroll: $AnchorScrollProvider,
|
||||
$animate: $AnimateProvider,
|
||||
$animateCss: $CoreAnimateCssProvider,
|
||||
$$animateJs: $$CoreAnimateJsProvider,
|
||||
$$animateQueue: $$CoreAnimateQueueProvider,
|
||||
$$AnimateRunner: $$CoreAnimateRunnerProvider,
|
||||
$$AnimateRunner: $$AnimateRunnerFactoryProvider,
|
||||
$$animateAsyncRun: $$AnimateAsyncRunFactoryProvider,
|
||||
$browser: $BrowserProvider,
|
||||
$cacheFactory: $CacheFactoryProvider,
|
||||
$controller: $ControllerProvider,
|
||||
@@ -230,6 +235,7 @@ function publishExternalAPI(angular) {
|
||||
$httpParamSerializer: $HttpParamSerializerProvider,
|
||||
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
|
||||
$httpBackend: $HttpBackendProvider,
|
||||
$xhrFactory: $xhrFactoryProvider,
|
||||
$location: $LocationProvider,
|
||||
$log: $LogProvider,
|
||||
$parse: $ParseProvider,
|
||||
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
if (window.angular.bootstrap) {
|
||||
//AngularJS is already loaded, so we can return here...
|
||||
console.log('WARNING: Tried to load angular more than once.');
|
||||
if (window.console) {
|
||||
console.log('WARNING: Tried to load angular more than once.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+23
-9
@@ -487,8 +487,20 @@ function annotate(fn, strictDi, name) {
|
||||
*
|
||||
* Register a **service constructor**, which will be invoked with `new` to create the service
|
||||
* instance.
|
||||
* This is short for registering a service where its provider's `$get` property is the service
|
||||
* constructor function that will be used to instantiate the service instance.
|
||||
* This is short for registering a service where its provider's `$get` property is a factory
|
||||
* function that returns an instance instantiated by the injector from the service constructor
|
||||
* function.
|
||||
*
|
||||
* Internally it looks a bit like this:
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* $get: function() {
|
||||
* return $injector.instantiate(constructor);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* You should use {@link auto.$provide#service $provide.service(class)} if you define your service
|
||||
* as a type/class.
|
||||
@@ -528,14 +540,13 @@ function annotate(fn, strictDi, name) {
|
||||
* @description
|
||||
*
|
||||
* Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
|
||||
* number, an array, an object or a function. This is short for registering a service where its
|
||||
* number, an array, an object or a function. This is short for registering a service where its
|
||||
* provider's `$get` property is a factory function that takes no arguments and returns the **value
|
||||
* service**.
|
||||
* service**. That also means it is not possible to inject other services into a value service.
|
||||
*
|
||||
* Value services are similar to constant services, except that they cannot be injected into a
|
||||
* module configuration function (see {@link angular.Module#config}) but they can be overridden by
|
||||
* an Angular
|
||||
* {@link auto.$provide#decorator decorator}.
|
||||
* an Angular {@link auto.$provide#decorator decorator}.
|
||||
*
|
||||
* @param {string} name The name of the instance.
|
||||
* @param {*} value The value.
|
||||
@@ -560,8 +571,11 @@ function annotate(fn, strictDi, name) {
|
||||
* @name $provide#constant
|
||||
* @description
|
||||
*
|
||||
* Register a **constant service**, such as a string, a number, an array, an object or a function,
|
||||
* with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
|
||||
* Register a **constant service** with the {@link auto.$injector $injector}, such as a string,
|
||||
* a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not
|
||||
* possible to inject other services into a constant.
|
||||
*
|
||||
* But unlike {@link auto.$provide#value value}, a constant can be
|
||||
* injected into a module configuration function (see {@link angular.Module#config}) and it cannot
|
||||
* be overridden by an Angular {@link auto.$provide#decorator decorator}.
|
||||
*
|
||||
@@ -589,7 +603,7 @@ function annotate(fn, strictDi, name) {
|
||||
* @description
|
||||
*
|
||||
* Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
|
||||
* intercepts the creation of a service, allowing it to override or modify the behaviour of the
|
||||
* intercepts the creation of a service, allowing it to override or modify the behavior of the
|
||||
* service. The object returned by the decorator may be the original service, or a new service
|
||||
* object which replaces or wraps and delegates to the original service.
|
||||
*
|
||||
|
||||
+86
-48
@@ -33,16 +33,22 @@
|
||||
*
|
||||
* If jQuery is available, `angular.element` is an alias for the
|
||||
* [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
|
||||
* delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
|
||||
* delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**.
|
||||
*
|
||||
* <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.</div>
|
||||
* jqLite is a tiny, API-compatible subset of jQuery that allows
|
||||
* Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most
|
||||
* commonly needed functionality with the goal of having a very small footprint.
|
||||
*
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
|
||||
* To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the
|
||||
* {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a
|
||||
* specific version of jQuery if multiple versions exist on the page.
|
||||
*
|
||||
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
|
||||
* jqLite; they are never raw DOM references.</div>
|
||||
* <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or
|
||||
* jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div>
|
||||
*
|
||||
* <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements
|
||||
* by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)`
|
||||
* or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div>
|
||||
*
|
||||
* ## Angular's jqLite
|
||||
* jqLite provides only the following jQuery methods:
|
||||
@@ -55,7 +61,8 @@
|
||||
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
|
||||
* - [`clone()`](http://api.jquery.com/clone/)
|
||||
* - [`contents()`](http://api.jquery.com/contents/)
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
|
||||
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`.
|
||||
* As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing.
|
||||
* - [`data()`](http://api.jquery.com/data/)
|
||||
* - [`detach()`](http://api.jquery.com/detach/)
|
||||
* - [`empty()`](http://api.jquery.com/empty/)
|
||||
@@ -241,6 +248,24 @@ function jqLiteParseHTML(html, context) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function jqLiteWrapNode(node, wrapper) {
|
||||
var parent = node.parentNode;
|
||||
|
||||
if (parent) {
|
||||
parent.replaceChild(wrapper, node);
|
||||
}
|
||||
|
||||
wrapper.appendChild(node);
|
||||
}
|
||||
|
||||
|
||||
// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
|
||||
var jqLiteContains = Node.prototype.contains || function(arg) {
|
||||
// jshint bitwise: false
|
||||
return !!(this.compareDocumentPosition(arg) & 16);
|
||||
// jshint bitwise: true
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////
|
||||
function JQLite(element) {
|
||||
if (element instanceof JQLite) {
|
||||
@@ -299,17 +324,23 @@ function jqLiteOff(element, type, fn, unsupported) {
|
||||
delete events[type];
|
||||
}
|
||||
} else {
|
||||
forEach(type.split(' '), function(type) {
|
||||
if (isDefined(fn)) {
|
||||
var listenerFns = events[type];
|
||||
arrayRemove(listenerFns || [], fn);
|
||||
if (listenerFns && listenerFns.length > 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListenerFn(element, type, handle);
|
||||
delete events[type];
|
||||
var removeHandler = function(type) {
|
||||
var listenerFns = events[type];
|
||||
if (isDefined(fn)) {
|
||||
arrayRemove(listenerFns || [], fn);
|
||||
}
|
||||
if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
|
||||
removeEventListenerFn(element, type, handle);
|
||||
delete events[type];
|
||||
}
|
||||
};
|
||||
|
||||
forEach(type.split(' '), function(type) {
|
||||
removeHandler(type);
|
||||
if (MOUSE_EVENT_MAP[type]) {
|
||||
removeHandler(MOUSE_EVENT_MAP[type]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -764,6 +795,9 @@ function createEventHandler(element, events) {
|
||||
return event.immediatePropagationStopped === true;
|
||||
};
|
||||
|
||||
// Some events have special handlers that wrap the real handler
|
||||
var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper;
|
||||
|
||||
// Copy event handlers in case event handlers array is modified during execution.
|
||||
if ((eventFnsLength > 1)) {
|
||||
eventFns = shallowCopy(eventFns);
|
||||
@@ -771,7 +805,7 @@ function createEventHandler(element, events) {
|
||||
|
||||
for (var i = 0; i < eventFnsLength; i++) {
|
||||
if (!event.isImmediatePropagationStopped()) {
|
||||
eventFns[i].call(element, event);
|
||||
handlerWrapper(element, event, eventFns[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -782,6 +816,22 @@ function createEventHandler(element, events) {
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
function defaultHandlerWrapper(element, event, handler) {
|
||||
handler.call(element, event);
|
||||
}
|
||||
|
||||
function specialMouseHandlerWrapper(target, event, handler) {
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
var related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if (!related || (related !== target && !jqLiteContains.call(target, related))) {
|
||||
handler.call(target, event);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Functions iterating traversal.
|
||||
// These functions chain results into a single
|
||||
@@ -810,35 +860,28 @@ forEach({
|
||||
var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type];
|
||||
var i = types.length;
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
var addHandler = function(type, specialHandlerWrapper, noEventListener) {
|
||||
var eventFns = events[type];
|
||||
|
||||
if (!eventFns) {
|
||||
events[type] = [];
|
||||
|
||||
if (type === 'mouseenter' || type === 'mouseleave') {
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
|
||||
jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) {
|
||||
var target = this, related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if (!related || (related !== target && !target.contains(related))) {
|
||||
handle(event, type);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if (type !== '$destroy') {
|
||||
addEventListenerFn(element, type, handle);
|
||||
}
|
||||
eventFns = events[type] = [];
|
||||
eventFns.specialHandlerWrapper = specialHandlerWrapper;
|
||||
if (type !== '$destroy' && !noEventListener) {
|
||||
addEventListenerFn(element, type, handle);
|
||||
}
|
||||
eventFns = events[type];
|
||||
}
|
||||
|
||||
eventFns.push(fn);
|
||||
};
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
if (MOUSE_EVENT_MAP[type]) {
|
||||
addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
|
||||
addHandler(type, undefined, true);
|
||||
} else {
|
||||
addHandler(type);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -906,12 +949,7 @@ forEach({
|
||||
},
|
||||
|
||||
wrap: function(element, wrapNode) {
|
||||
wrapNode = jqLite(wrapNode).eq(0).clone()[0];
|
||||
var parent = element.parentNode;
|
||||
if (parent) {
|
||||
parent.replaceChild(wrapNode, element);
|
||||
}
|
||||
wrapNode.appendChild(element);
|
||||
jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]);
|
||||
},
|
||||
|
||||
remove: jqLiteRemove,
|
||||
|
||||
+5
-5
@@ -76,7 +76,7 @@ function setupModuleLoader(window) {
|
||||
* unspecified then the module is being retrieved for further configuration.
|
||||
* @param {Function=} configFn Optional configuration function for the module. Same as
|
||||
* {@link angular.Module#config Module#config()}.
|
||||
* @returns {module} new module with the {@link angular.Module} api.
|
||||
* @returns {angular.Module} new module with the {@link angular.Module} api.
|
||||
*/
|
||||
return function module(name, requires, configFn) {
|
||||
var assertNotHasOwnProperty = function(name, context) {
|
||||
@@ -188,7 +188,7 @@ function setupModuleLoader(window) {
|
||||
* @param {string} name constant name
|
||||
* @param {*} object Constant value.
|
||||
* @description
|
||||
* Because the constant are fixed, they get applied before other provide methods.
|
||||
* Because the constants are fixed, they get applied before other provide methods.
|
||||
* See {@link auto.$provide#constant $provide.constant()}.
|
||||
*/
|
||||
constant: invokeLater('$provide', 'constant', 'unshift'),
|
||||
@@ -197,9 +197,9 @@ function setupModuleLoader(window) {
|
||||
* @ngdoc method
|
||||
* @name angular.Module#decorator
|
||||
* @module ng
|
||||
* @param {string} The name of the service to decorate.
|
||||
* @param {Function} This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @param {string} name The name of the service to decorate.
|
||||
* @param {Function} decorFn This function will be invoked when the service needs to be
|
||||
* instantiated and should return the decorated service instance.
|
||||
* @description
|
||||
* See {@link auto.$provide#decorator $provide.decorator()}.
|
||||
*/
|
||||
|
||||
@@ -41,7 +41,7 @@ function $AnchorScrollProvider() {
|
||||
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
|
||||
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
|
||||
* in the
|
||||
* [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
|
||||
* [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document).
|
||||
*
|
||||
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
|
||||
* match any anchor whenever it changes. This can be disabled by calling
|
||||
|
||||
+26
-27
@@ -53,27 +53,8 @@ function prepareAnimateOptions(options) {
|
||||
: {};
|
||||
}
|
||||
|
||||
var $$CoreAnimateRunnerProvider = function() {
|
||||
this.$get = ['$q', '$$rAF', function($q, $$rAF) {
|
||||
function AnimateRunner() {}
|
||||
AnimateRunner.all = noop;
|
||||
AnimateRunner.chain = noop;
|
||||
AnimateRunner.prototype = {
|
||||
end: noop,
|
||||
cancel: noop,
|
||||
resume: noop,
|
||||
pause: noop,
|
||||
complete: noop,
|
||||
then: function(pass, fail) {
|
||||
return $q(function(resolve) {
|
||||
$$rAF(function() {
|
||||
resolve();
|
||||
});
|
||||
}).then(pass, fail);
|
||||
}
|
||||
};
|
||||
return AnimateRunner;
|
||||
}];
|
||||
var $$CoreAnimateJsProvider = function() {
|
||||
this.$get = function() {};
|
||||
};
|
||||
|
||||
// this is prefixed with Core since it conflicts with
|
||||
@@ -101,7 +82,12 @@ var $$CoreAnimateQueueProvider = function() {
|
||||
addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
|
||||
}
|
||||
|
||||
return new $$AnimateRunner(); // jshint ignore:line
|
||||
var runner = new $$AnimateRunner(); // jshint ignore:line
|
||||
|
||||
// since there are no animations to run the runner needs to be
|
||||
// notified that the animation call is complete.
|
||||
runner.complete();
|
||||
return runner;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,7 +271,7 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* when an animation is detected (and animations are enabled), $animate will do the heavy lifting
|
||||
* to ensure that animation runs with the triggered DOM operation.
|
||||
*
|
||||
* By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
|
||||
* By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
|
||||
* included and only when it is active then the animation hooks that `$animate` triggers will be
|
||||
* functional. Once active then all structural `ng-` directives will trigger animations as they perform
|
||||
* their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
|
||||
@@ -566,10 +552,23 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @kind function
|
||||
*
|
||||
* @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
|
||||
* If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
|
||||
* on the provided styles. For example, if a transition animation is set for the given className then the provided from and
|
||||
* to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
|
||||
* will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
|
||||
* If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
|
||||
* on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
|
||||
* `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
|
||||
* style in `to`, the style in `from` is applied immediately, and no animation is run.
|
||||
* If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
|
||||
* method (or as part of the `options` parameter):
|
||||
*
|
||||
* ```js
|
||||
* ngModule.animation('.my-inline-animation', function() {
|
||||
* return {
|
||||
* animate : function(element, from, to, done, options) {
|
||||
* //animation
|
||||
* done();
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {DOMElement} element the element which the CSS styles will be applied to
|
||||
* @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
|
||||
|
||||
+21
-32
@@ -12,43 +12,32 @@
|
||||
* Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}.
|
||||
*/
|
||||
var $CoreAnimateCssProvider = function() {
|
||||
this.$get = ['$$rAF', '$q', function($$rAF, $q) {
|
||||
this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) {
|
||||
|
||||
var RAFPromise = function() {};
|
||||
RAFPromise.prototype = {
|
||||
done: function(cancel) {
|
||||
this.defer && this.defer[cancel === true ? 'reject' : 'resolve']();
|
||||
},
|
||||
end: function() {
|
||||
this.done();
|
||||
},
|
||||
cancel: function() {
|
||||
this.done(true);
|
||||
},
|
||||
getPromise: function() {
|
||||
if (!this.defer) {
|
||||
this.defer = $q.defer();
|
||||
}
|
||||
return this.defer.promise;
|
||||
},
|
||||
then: function(f1,f2) {
|
||||
return this.getPromise().then(f1,f2);
|
||||
},
|
||||
'catch': function(f1) {
|
||||
return this.getPromise()['catch'](f1);
|
||||
},
|
||||
'finally': function(f1) {
|
||||
return this.getPromise()['finally'](f1);
|
||||
return function(element, initialOptions) {
|
||||
// all of the animation functions should create
|
||||
// a copy of the options data, however, if a
|
||||
// parent service has already created a copy then
|
||||
// we should stick to using that
|
||||
var options = initialOptions || {};
|
||||
if (!options.$$prepared) {
|
||||
options = copy(options);
|
||||
}
|
||||
|
||||
// there is no point in applying the styles since
|
||||
// there is no animation that goes on at all in
|
||||
// this version of $animateCss.
|
||||
if (options.cleanupStyles) {
|
||||
options.from = options.to = null;
|
||||
}
|
||||
};
|
||||
|
||||
return function(element, options) {
|
||||
if (options.from) {
|
||||
element.css(options.from);
|
||||
options.from = null;
|
||||
}
|
||||
|
||||
var closed, runner = new RAFPromise();
|
||||
/* jshint newcap: false*/
|
||||
var closed, runner = new $$AnimateRunner();
|
||||
return {
|
||||
start: run,
|
||||
end: run
|
||||
@@ -56,16 +45,16 @@ var $CoreAnimateCssProvider = function() {
|
||||
|
||||
function run() {
|
||||
$$rAF(function() {
|
||||
close();
|
||||
applyAnimationContents();
|
||||
if (!closed) {
|
||||
runner.done();
|
||||
runner.complete();
|
||||
}
|
||||
closed = true;
|
||||
});
|
||||
return runner;
|
||||
}
|
||||
|
||||
function close() {
|
||||
function applyAnimationContents() {
|
||||
if (options.addClass) {
|
||||
element.addClass(options.addClass);
|
||||
options.addClass = null;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
'use strict';
|
||||
|
||||
var $$AnimateAsyncRunFactoryProvider = function() {
|
||||
this.$get = ['$$rAF', function($$rAF) {
|
||||
var waitQueue = [];
|
||||
|
||||
function waitForTick(fn) {
|
||||
waitQueue.push(fn);
|
||||
if (waitQueue.length > 1) return;
|
||||
$$rAF(function() {
|
||||
for (var i = 0; i < waitQueue.length; i++) {
|
||||
waitQueue[i]();
|
||||
}
|
||||
waitQueue = [];
|
||||
});
|
||||
}
|
||||
|
||||
return function() {
|
||||
var passed = false;
|
||||
waitForTick(function() {
|
||||
passed = true;
|
||||
});
|
||||
return function(callback) {
|
||||
passed ? callback() : waitForTick(callback);
|
||||
};
|
||||
};
|
||||
}];
|
||||
};
|
||||
|
||||
var $$AnimateRunnerFactoryProvider = function() {
|
||||
this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout',
|
||||
function($q, $sniffer, $$animateAsyncRun, $document, $timeout) {
|
||||
|
||||
var INITIAL_STATE = 0;
|
||||
var DONE_PENDING_STATE = 1;
|
||||
var DONE_COMPLETE_STATE = 2;
|
||||
|
||||
AnimateRunner.chain = function(chain, callback) {
|
||||
var index = 0;
|
||||
|
||||
next();
|
||||
function next() {
|
||||
if (index === chain.length) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
chain[index](function(response) {
|
||||
if (response === false) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
AnimateRunner.all = function(runners, callback) {
|
||||
var count = 0;
|
||||
var status = true;
|
||||
forEach(runners, function(runner) {
|
||||
runner.done(onProgress);
|
||||
});
|
||||
|
||||
function onProgress(response) {
|
||||
status = status && response;
|
||||
if (++count === runners.length) {
|
||||
callback(status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function AnimateRunner(host) {
|
||||
this.setHost(host);
|
||||
|
||||
var rafTick = $$animateAsyncRun();
|
||||
var timeoutTick = function(fn) {
|
||||
$timeout(fn, 0, false);
|
||||
};
|
||||
|
||||
this._doneCallbacks = [];
|
||||
this._tick = function(fn) {
|
||||
var doc = $document[0];
|
||||
|
||||
// the document may not be ready or attached
|
||||
// to the module for some internal tests
|
||||
if (doc && doc.hidden) {
|
||||
timeoutTick(fn);
|
||||
} else {
|
||||
rafTick(fn);
|
||||
}
|
||||
};
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
AnimateRunner.prototype = {
|
||||
setHost: function(host) {
|
||||
this.host = host || {};
|
||||
},
|
||||
|
||||
done: function(fn) {
|
||||
if (this._state === DONE_COMPLETE_STATE) {
|
||||
fn();
|
||||
} else {
|
||||
this._doneCallbacks.push(fn);
|
||||
}
|
||||
},
|
||||
|
||||
progress: noop,
|
||||
|
||||
getPromise: function() {
|
||||
if (!this.promise) {
|
||||
var self = this;
|
||||
this.promise = $q(function(resolve, reject) {
|
||||
self.done(function(status) {
|
||||
status === false ? reject() : resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
return this.promise;
|
||||
},
|
||||
|
||||
then: function(resolveHandler, rejectHandler) {
|
||||
return this.getPromise().then(resolveHandler, rejectHandler);
|
||||
},
|
||||
|
||||
'catch': function(handler) {
|
||||
return this.getPromise()['catch'](handler);
|
||||
},
|
||||
|
||||
'finally': function(handler) {
|
||||
return this.getPromise()['finally'](handler);
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
if (this.host.pause) {
|
||||
this.host.pause();
|
||||
}
|
||||
},
|
||||
|
||||
resume: function() {
|
||||
if (this.host.resume) {
|
||||
this.host.resume();
|
||||
}
|
||||
},
|
||||
|
||||
end: function() {
|
||||
if (this.host.end) {
|
||||
this.host.end();
|
||||
}
|
||||
this._resolve(true);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
if (this.host.cancel) {
|
||||
this.host.cancel();
|
||||
}
|
||||
this._resolve(false);
|
||||
},
|
||||
|
||||
complete: function(response) {
|
||||
var self = this;
|
||||
if (self._state === INITIAL_STATE) {
|
||||
self._state = DONE_PENDING_STATE;
|
||||
self._tick(function() {
|
||||
self._resolve(response);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_resolve: function(response) {
|
||||
if (this._state !== DONE_COMPLETE_STATE) {
|
||||
forEach(this._doneCallbacks, function(fn) {
|
||||
fn(response);
|
||||
});
|
||||
this._doneCallbacks.length = 0;
|
||||
this._state = DONE_COMPLETE_STATE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return AnimateRunner;
|
||||
}];
|
||||
};
|
||||
@@ -67,10 +67,10 @@
|
||||
$scope.keys = [];
|
||||
$scope.cache = $cacheFactory('cacheId');
|
||||
$scope.put = function(key, value) {
|
||||
if (isUndefined($scope.cache.get(key))) {
|
||||
if (angular.isUndefined($scope.cache.get(key))) {
|
||||
$scope.keys.push(key);
|
||||
}
|
||||
$scope.cache.put(key, isUndefined(value) ? null : value);
|
||||
$scope.cache.put(key, angular.isUndefined(value) ? null : value);
|
||||
};
|
||||
}]);
|
||||
</file>
|
||||
@@ -93,9 +93,9 @@ function $CacheFactoryProvider() {
|
||||
|
||||
var size = 0,
|
||||
stats = extend({}, options, {id: cacheId}),
|
||||
data = {},
|
||||
data = createMap(),
|
||||
capacity = (options && options.capacity) || Number.MAX_VALUE,
|
||||
lruHash = {},
|
||||
lruHash = createMap(),
|
||||
freshEnd = null,
|
||||
staleEnd = null;
|
||||
|
||||
@@ -223,6 +223,8 @@ function $CacheFactoryProvider() {
|
||||
delete lruHash[key];
|
||||
}
|
||||
|
||||
if (!(key in data)) return;
|
||||
|
||||
delete data[key];
|
||||
size--;
|
||||
},
|
||||
@@ -237,9 +239,9 @@ function $CacheFactoryProvider() {
|
||||
* Clears the cache object of any entries.
|
||||
*/
|
||||
removeAll: function() {
|
||||
data = {};
|
||||
data = createMap();
|
||||
size = 0;
|
||||
lruHash = {};
|
||||
lruHash = createMap();
|
||||
freshEnd = staleEnd = null;
|
||||
},
|
||||
|
||||
@@ -399,4 +401,3 @@ function $TemplateCacheProvider() {
|
||||
return $cacheFactory('templates');
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
+191
-151
@@ -166,35 +166,38 @@
|
||||
* is bound to the parent scope, via matching attributes on the directive's element:
|
||||
*
|
||||
* * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
|
||||
* always a string since DOM attributes are strings. If no `attr` name is specified then the
|
||||
* attribute name is assumed to be the same as the local name.
|
||||
* Given `<widget my-attr="hello {{name}}">` and widget definition
|
||||
* of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect
|
||||
* the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the
|
||||
* `localName` property on the widget scope. The `name` is read from the parent scope (not
|
||||
* component scope).
|
||||
* always a string since DOM attributes are strings. If no `attr` name is specified then the
|
||||
* attribute name is assumed to be the same as the local name. Given `<my-component
|
||||
* my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`,
|
||||
* the directive's scope property `localName` will reflect the interpolated value of `hello
|
||||
* {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's
|
||||
* scope. The `name` is read from the parent scope (not the directive's scope).
|
||||
*
|
||||
* * `=` or `=attr` - set up bi-directional binding between a local scope property and the
|
||||
* parent scope property of name defined via the value of the `attr` attribute. If no `attr`
|
||||
* name is specified then the attribute name is assumed to be the same as the local name.
|
||||
* Given `<widget my-attr="parentModel">` and widget definition of
|
||||
* `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
|
||||
* value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
|
||||
* in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
|
||||
* scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
|
||||
* can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
|
||||
* you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
|
||||
* `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
|
||||
* * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression
|
||||
* passed via the attribute `attr`. The expression is evaluated in the context of the parent scope.
|
||||
* If no `attr` name is specified then the attribute name is assumed to be the same as the local
|
||||
* name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: {
|
||||
* localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the
|
||||
* value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in
|
||||
* `localModel` and vice versa. Optional attributes should be marked as such with a question mark:
|
||||
* `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't
|
||||
* optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`})
|
||||
* will be thrown upon discovering changes to the local value, since it will be impossible to sync
|
||||
* them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
|
||||
* method is used for tracking changes, and the equality check is based on object identity.
|
||||
* However, if an object literal or an array literal is passed as the binding expression, the
|
||||
* equality check is done by value (using the {@link angular.equals} function). It's also possible
|
||||
* to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection
|
||||
* `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional).
|
||||
*
|
||||
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
|
||||
* If no `attr` name is specified then the attribute name is assumed to be the same as the
|
||||
* local name. Given `<widget my-attr="count = count + value">` and widget definition of
|
||||
* `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to
|
||||
* a function wrapper for the `count = count + value` expression. Often it's desirable to
|
||||
* pass data from the isolated scope via an expression to the parent scope, this can be
|
||||
* done by passing a map of local variable names and values into the expression wrapper fn.
|
||||
* For example, if the expression is `increment(amount)` then we can specify the amount value
|
||||
* by calling the `localFn` as `localFn({amount: 22})`.
|
||||
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
|
||||
* no `attr` name is specified then the attribute name is assumed to be the same as the local name.
|
||||
* Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
|
||||
* localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for
|
||||
* the `count = count + value` expression. Often it's desirable to pass data from the isolated scope
|
||||
* via an expression to the parent scope. This can be done by passing a map of local variable names
|
||||
* and values into the expression wrapper fn. For example, if the expression is `increment(amount)`
|
||||
* then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`.
|
||||
*
|
||||
* In general it's possible to apply more than one directive to one element, but there might be limitations
|
||||
* depending on the type of scope required by the directives. The following points will help explain these limitations.
|
||||
@@ -212,10 +215,23 @@
|
||||
*
|
||||
*
|
||||
* #### `bindToController`
|
||||
* When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will
|
||||
* This property is used to bind scope properties directly to the controller. It can be either
|
||||
* `true` or an object hash with the same format as the `scope` property. Additionally, a controller
|
||||
* alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller
|
||||
* definition: `controller: 'myCtrl as myAlias'`.
|
||||
*
|
||||
* When an isolate scope is used for a directive (see above), `bindToController: true` will
|
||||
* allow a component to have its properties bound to the controller, rather than to scope. When the controller
|
||||
* is instantiated, the initial values of the isolate scope bindings are already available.
|
||||
*
|
||||
* It is also possible to set `bindToController` to an object hash with the same format as the `scope` property.
|
||||
* This will set up the scope bindings to the controller directly. Note that `scope` can still be used
|
||||
* to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate
|
||||
* scope (useful for component directives).
|
||||
*
|
||||
* If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`.
|
||||
*
|
||||
*
|
||||
* #### `controller`
|
||||
* Controller constructor function. The controller is instantiated before the
|
||||
* pre-linking phase and can be accessed by other directives (see
|
||||
@@ -372,7 +388,7 @@
|
||||
|
||||
* <div class="alert alert-warning">
|
||||
* **Note:** The compile function cannot handle directives that recursively use themselves in their
|
||||
* own templates or compile functions. Compiling these directives results in an infinite loop and a
|
||||
* own templates or compile functions. Compiling these directives results in an infinite loop and
|
||||
* stack overflow errors.
|
||||
*
|
||||
* This can be avoided by manually using $compile in the postLink function to imperatively compile
|
||||
@@ -551,19 +567,19 @@
|
||||
*
|
||||
* The `$parent` scope hierarchy will look like this:
|
||||
*
|
||||
* ```
|
||||
* - $rootScope
|
||||
* - isolate
|
||||
* - transclusion
|
||||
* ```
|
||||
```
|
||||
- $rootScope
|
||||
- isolate
|
||||
- transclusion
|
||||
```
|
||||
*
|
||||
* but the scopes will inherit prototypically from different scopes to their `$parent`.
|
||||
*
|
||||
* ```
|
||||
* - $rootScope
|
||||
* - transclusion
|
||||
* - isolate
|
||||
* ```
|
||||
```
|
||||
- $rootScope
|
||||
- transclusion
|
||||
- isolate
|
||||
```
|
||||
*
|
||||
*
|
||||
* ### Attributes
|
||||
@@ -571,10 +587,9 @@
|
||||
* The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
|
||||
* `link()` or `compile()` functions. It has a variety of uses.
|
||||
*
|
||||
* accessing *Normalized attribute names:*
|
||||
* Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'.
|
||||
* the attributes object allows for normalized access to
|
||||
* the attributes.
|
||||
* * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways:
|
||||
* 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access
|
||||
* to the attributes.
|
||||
*
|
||||
* * *Directive inter-communication:* All directives share the same instance of the attributes
|
||||
* object which allows the directives to use the attributes object as inter directive
|
||||
@@ -695,8 +710,15 @@
|
||||
* directives; if given, it will be passed through to the link functions of
|
||||
* directives found in `element` during compilation.
|
||||
* * `transcludeControllers` - an object hash with keys that map controller names
|
||||
* to controller instances; if given, it will make the controllers
|
||||
* available to directives.
|
||||
* to a hash with the key `instance`, which maps to the controller instance;
|
||||
* if given, it will make the controllers available to directives on the compileNode:
|
||||
* ```
|
||||
* {
|
||||
* parent: {
|
||||
* instance: parentControllerInstance
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add
|
||||
* the cloned elements; only needed for transcludes that are allowed to contain non html
|
||||
* elements (e.g. SVG elements). See also the directive.controller property.
|
||||
@@ -755,13 +777,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// The assumption is that future DOM event attribute names will begin with
|
||||
// 'on' and be composed of only English letters.
|
||||
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
|
||||
var bindingCache = createMap();
|
||||
|
||||
function parseIsolateBindings(scope, directiveName, isController) {
|
||||
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
|
||||
|
||||
var bindings = {};
|
||||
var bindings = createMap();
|
||||
|
||||
forEach(scope, function(definition, scopeName) {
|
||||
if (definition in bindingCache) {
|
||||
bindings[scopeName] = bindingCache[definition];
|
||||
return;
|
||||
}
|
||||
var match = definition.match(LOCAL_REGEXP);
|
||||
|
||||
if (!match) {
|
||||
@@ -779,6 +806,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
optional: match[3] === '?',
|
||||
attrName: match[4] || scopeName
|
||||
};
|
||||
if (match[4]) {
|
||||
bindingCache[definition] = bindings[scopeName];
|
||||
}
|
||||
});
|
||||
|
||||
return bindings;
|
||||
@@ -871,11 +901,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
directive.name = directive.name || name;
|
||||
directive.require = directive.require || (directive.controller && directive.name);
|
||||
directive.restrict = directive.restrict || 'EA';
|
||||
var bindings = directive.$$bindings =
|
||||
parseDirectiveBindings(directive, directive.name);
|
||||
if (isObject(bindings.isolateScope)) {
|
||||
directive.$$isolateBindings = bindings.isolateScope;
|
||||
}
|
||||
directive.$$moduleName = directiveFactory.$$moduleName;
|
||||
directives.push(directive);
|
||||
} catch (e) {
|
||||
@@ -985,9 +1010,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
|
||||
'$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
|
||||
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
|
||||
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
|
||||
$controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
|
||||
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
|
||||
|
||||
var Attributes = function(element, attributesToCopy) {
|
||||
if (attributesToCopy) {
|
||||
@@ -1128,7 +1153,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
(nodeName === 'img' && key === 'src')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = $$sanitizeUri(value, key === 'src');
|
||||
} else if (nodeName === 'img' && key === 'srcset') {
|
||||
} else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
|
||||
// sanitize img[srcset] values
|
||||
var result = "";
|
||||
|
||||
@@ -1199,7 +1224,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
* @param {string} key Normalized key. (ie ngAttribute) .
|
||||
* @param {function(interpolatedValue)} fn Function that will be called whenever
|
||||
the interpolated value of the attribute changes.
|
||||
* See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info.
|
||||
* See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation
|
||||
* guide} for more info.
|
||||
* @returns {function()} Returns a deregistration function for this observer.
|
||||
*/
|
||||
$observe: function(key, fn) {
|
||||
@@ -1234,12 +1260,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
var startSymbol = $interpolate.startSymbol(),
|
||||
endSymbol = $interpolate.endSymbol(),
|
||||
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
|
||||
denormalizeTemplate = (startSymbol == '{{' && endSymbol == '}}')
|
||||
? identity
|
||||
: function denormalizeTemplate(template) {
|
||||
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
|
||||
},
|
||||
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
|
||||
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
|
||||
|
||||
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
|
||||
var bindings = $element.data('$binding') || [];
|
||||
@@ -1277,13 +1304,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// modify it.
|
||||
$compileNodes = jqLite($compileNodes);
|
||||
}
|
||||
|
||||
var NOT_EMPTY = /\S+/;
|
||||
|
||||
// We can not compile top level text elements since text nodes can be merged and we will
|
||||
// not be able to attach scope data to them, so we will wrap them in <span>
|
||||
forEach($compileNodes, function(node, index) {
|
||||
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
|
||||
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
|
||||
for (var i = 0, len = $compileNodes.length; i < len; i++) {
|
||||
var domNode = $compileNodes[i];
|
||||
|
||||
if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) {
|
||||
jqLiteWrapNode(domNode, $compileNodes[i] = document.createElement('span'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var compositeLinkFn =
|
||||
compileNodes($compileNodes, transcludeFn, $compileNodes,
|
||||
maxPriority, ignoreDirective, previousCompileContext);
|
||||
@@ -1292,6 +1325,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return function publicLinkFn(scope, cloneConnectFn, options) {
|
||||
assertArg(scope, 'scope');
|
||||
|
||||
if (previousCompileContext && previousCompileContext.needsNewScope) {
|
||||
// A parent directive did a replace and a directive on this element asked
|
||||
// for transclusion, which caused us to lose a layer of element on which
|
||||
// we could hold the new transclusion scope, so we will create it manually
|
||||
// here.
|
||||
scope = scope.$parent.$new();
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
|
||||
transcludeControllers = options.transcludeControllers,
|
||||
@@ -1437,11 +1478,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (nodeLinkFn.scope) {
|
||||
childScope = scope.$new();
|
||||
compile.$$addScopeInfo(jqLite(node), childScope);
|
||||
var destroyBindings = nodeLinkFn.$$destroyBindings;
|
||||
if (destroyBindings) {
|
||||
nodeLinkFn.$$destroyBindings = null;
|
||||
childScope.$on('$destroyed', destroyBindings);
|
||||
}
|
||||
} else {
|
||||
childScope = scope;
|
||||
}
|
||||
@@ -1460,8 +1496,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = null;
|
||||
}
|
||||
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
|
||||
nodeLinkFn);
|
||||
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
|
||||
|
||||
} else if (childLinkFn) {
|
||||
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
|
||||
@@ -1530,13 +1565,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
});
|
||||
}
|
||||
|
||||
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
|
||||
if (directiveIsMultiElement(directiveNName)) {
|
||||
if (ngAttrName === directiveNName + 'Start') {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE);
|
||||
if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) {
|
||||
attrStartName = name;
|
||||
attrEndName = name.substr(0, name.length - 5) + 'end';
|
||||
name = name.substr(0, name.length - 6);
|
||||
}
|
||||
|
||||
nName = directiveNormalize(name.toLowerCase());
|
||||
@@ -1775,7 +1808,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
} else {
|
||||
$template = jqLite(jqLiteClone(compileNode)).contents();
|
||||
$compileNode.empty(); // clear contents
|
||||
childTranscludeFn = compile($template, transcludeFn);
|
||||
childTranscludeFn = compile($template, transcludeFn, undefined,
|
||||
undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1817,8 +1851,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
|
||||
var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));
|
||||
|
||||
if (newIsolateScopeDirective) {
|
||||
markDirectivesAsIsolate(templateDirectives);
|
||||
if (newIsolateScopeDirective || newScopeDirective) {
|
||||
// The original directive caused the current element to be replaced but this element
|
||||
// also needs to have a new scope, so we need to tell the template directives
|
||||
// that they would need to get their scope from further up, if they require transclusion
|
||||
markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective);
|
||||
}
|
||||
directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
|
||||
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
|
||||
@@ -1958,23 +1995,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
|
||||
|
||||
// For directives with element transclusion the element is a comment,
|
||||
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
|
||||
// clean up (http://bugs.jquery.com/ticket/8335).
|
||||
// For directives with element transclusion the element is a comment.
|
||||
// In this case .data will not attach any data.
|
||||
// Instead, we save the controllers for the element in a local hash and attach to .data
|
||||
// later, once we have the actual element.
|
||||
elementControllers[directive.name] = controllerInstance;
|
||||
if (!hasElementTranscludeDirective) {
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
|
||||
}
|
||||
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
|
||||
}
|
||||
return elementControllers;
|
||||
}
|
||||
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
|
||||
thisLinkFn) {
|
||||
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
|
||||
attrs;
|
||||
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
|
||||
var linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element,
|
||||
attrs, removeScopeBindingWatches, removeControllerBindingWatches;
|
||||
|
||||
if (compileNode === linkNode) {
|
||||
attrs = templateAttrs;
|
||||
@@ -1984,8 +2017,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
attrs = new Attributes($element, templateAttrs);
|
||||
}
|
||||
|
||||
controllerScope = scope;
|
||||
if (newIsolateScopeDirective) {
|
||||
isolateScope = scope.$new(true);
|
||||
} else if (newScopeDirective) {
|
||||
controllerScope = scope.$parent;
|
||||
}
|
||||
|
||||
if (boundTranscludeFn) {
|
||||
@@ -2006,42 +2042,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile.$$addScopeClass($element, true);
|
||||
isolateScope.$$isolateBindings =
|
||||
newIsolateScopeDirective.$$isolateBindings;
|
||||
initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective, isolateScope);
|
||||
}
|
||||
if (elementControllers) {
|
||||
// Initialize bindToController bindings for new/isolate scopes
|
||||
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
|
||||
var bindings;
|
||||
var controllerForBindings;
|
||||
if (scopeDirective && elementControllers[scopeDirective.name]) {
|
||||
bindings = scopeDirective.$$bindings.bindToController;
|
||||
controller = elementControllers[scopeDirective.name];
|
||||
|
||||
if (controller && controller.identifier && bindings) {
|
||||
controllerForBindings = controller;
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controller.instance,
|
||||
bindings, scopeDirective);
|
||||
}
|
||||
removeScopeBindingWatches = initializeDirectiveBindings(scope, attrs, isolateScope,
|
||||
isolateScope.$$isolateBindings,
|
||||
newIsolateScopeDirective);
|
||||
if (removeScopeBindingWatches) {
|
||||
isolateScope.$on('$destroy', removeScopeBindingWatches);
|
||||
}
|
||||
for (i in elementControllers) {
|
||||
controller = elementControllers[i];
|
||||
var controllerResult = controller();
|
||||
}
|
||||
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers and update the element data
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + i + 'Controller', controllerResult);
|
||||
if (controller === controllerForBindings) {
|
||||
// Remove and re-install bindToController bindings
|
||||
thisLinkFn.$$destroyBindings();
|
||||
thisLinkFn.$$destroyBindings =
|
||||
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
|
||||
}
|
||||
}
|
||||
// Initialize bindToController bindings
|
||||
for (var name in elementControllers) {
|
||||
var controllerDirective = controllerDirectives[name];
|
||||
var controller = elementControllers[name];
|
||||
var bindings = controllerDirective.$$bindings.bindToController;
|
||||
|
||||
if (controller.identifier && bindings) {
|
||||
removeControllerBindingWatches =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
|
||||
var controllerResult = controller();
|
||||
if (controllerResult !== controller.instance) {
|
||||
// If the controller constructor has a return value, overwrite the instance
|
||||
// from setupControllers
|
||||
controller.instance = controllerResult;
|
||||
$element.data('$' + controllerDirective.name + 'Controller', controllerResult);
|
||||
removeControllerBindingWatches && removeControllerBindingWatches();
|
||||
removeControllerBindingWatches =
|
||||
initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2101,10 +2129,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
function markDirectivesAsIsolate(directives) {
|
||||
// mark all directives as needing isolate scope.
|
||||
// Depending upon the context in which a directive finds itself it might need to have a new isolated
|
||||
// or child scope created. For instance:
|
||||
// * if the directive has been pulled into a template because another directive with a higher priority
|
||||
// asked for element transclusion
|
||||
// * if the directive itself asks for transclusion but it is at the root of a template and the original
|
||||
// element was replaced. See https://github.com/angular/angular.js/issues/12936
|
||||
function markDirectiveScope(directives, isolateScope, newScope) {
|
||||
for (var j = 0, jj = directives.length; j < jj; j++) {
|
||||
directives[j] = inherit(directives[j], {$$isolateScope: true});
|
||||
directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2136,6 +2169,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (startAttrName) {
|
||||
directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName});
|
||||
}
|
||||
if (!directive.$$bindings) {
|
||||
var bindings = directive.$$bindings =
|
||||
parseDirectiveBindings(directive, directive.name);
|
||||
if (isObject(bindings.isolateScope)) {
|
||||
directive.$$isolateBindings = bindings.isolateScope;
|
||||
}
|
||||
}
|
||||
tDirectives.push(directive);
|
||||
match = directive;
|
||||
}
|
||||
@@ -2251,7 +2291,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs);
|
||||
|
||||
if (isObject(origAsyncDirective.scope)) {
|
||||
markDirectivesAsIsolate(templateDirectives);
|
||||
// the original directive that caused the template to be loaded async required
|
||||
// an isolate scope
|
||||
markDirectiveScope(templateDirectives, true);
|
||||
}
|
||||
directives = templateDirectives.concat(directives);
|
||||
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
|
||||
@@ -2300,7 +2342,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
childBoundTranscludeFn = boundTranscludeFn;
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
|
||||
childBoundTranscludeFn, afterTemplateNodeLinkFn);
|
||||
childBoundTranscludeFn);
|
||||
}
|
||||
linkQueue = null;
|
||||
});
|
||||
@@ -2317,8 +2359,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
|
||||
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
|
||||
}
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
|
||||
afterTemplateNodeLinkFn);
|
||||
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2427,7 +2468,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
compile: function() {
|
||||
return {
|
||||
pre: function attrInterpolatePreLinkFn(scope, element, attr) {
|
||||
var $$observers = (attr.$$observers || (attr.$$observers = {}));
|
||||
var $$observers = (attr.$$observers || (attr.$$observers = createMap()));
|
||||
|
||||
if (EVENT_HANDLER_ATTR_REGEXP.test(name)) {
|
||||
throw $compileMinErr('nodomevents',
|
||||
@@ -2530,7 +2571,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
|
||||
// data here because there's no public interface in jQuery to do that and copying over
|
||||
// event listeners (which is the main use of private data) wouldn't work anyway.
|
||||
jqLite(newNode).data(jqLite(firstElementToRemove).data());
|
||||
jqLite.data(newNode, jqLite.data(firstElementToRemove));
|
||||
|
||||
// Remove data of the replaced element. We cannot just call .remove()
|
||||
// on the element it since that would deallocate scope that is needed
|
||||
@@ -2578,9 +2619,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
// Set up $watches for isolate scope and controller bindings. This process
|
||||
// only occurs for isolate scopes and new scopes with controllerAs.
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings,
|
||||
directive, newScope) {
|
||||
var onNewScopeDestroyed;
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
|
||||
var removeWatchCollection = [];
|
||||
forEach(bindings, function(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
@@ -2600,10 +2640,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
});
|
||||
attrs.$$observers[attrName].$$scope = scope;
|
||||
if (isString(attrs[attrName])) {
|
||||
lastValue = attrs[attrName];
|
||||
if (isString(lastValue)) {
|
||||
// If the attribute has been provided then we trigger an interpolation to ensure
|
||||
// the value is there for use in the link fn
|
||||
destination[scopeName] = $interpolate(attrs[attrName])(scope);
|
||||
destination[scopeName] = $interpolate(lastValue)(scope);
|
||||
} else if (isBoolean(lastValue)) {
|
||||
// If the attributes is one of the BOOLEAN_ATTR then Angular will have converted
|
||||
// the value to boolean rather than a string, so we special case this situation
|
||||
destination[scopeName] = lastValue;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2624,8 +2669,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// reset the change, or we will throw this exception on every $digest
|
||||
lastValue = destination[scopeName] = parentGet(scope);
|
||||
throw $compileMinErr('nonassign',
|
||||
"Expression '{0}' used with directive '{1}' is non-assignable!",
|
||||
attrs[attrName], directive.name);
|
||||
"Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!",
|
||||
attrs[attrName], attrName, directive.name);
|
||||
};
|
||||
lastValue = destination[scopeName] = parentGet(scope);
|
||||
var parentValueWatch = function parentValueWatch(parentValue) {
|
||||
@@ -2642,14 +2687,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return lastValue = parentValue;
|
||||
};
|
||||
parentValueWatch.$stateful = true;
|
||||
var unwatch;
|
||||
var removeWatch;
|
||||
if (definition.collection) {
|
||||
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
|
||||
} else {
|
||||
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
|
||||
}
|
||||
onNewScopeDestroyed = (onNewScopeDestroyed || []);
|
||||
onNewScopeDestroyed.push(unwatch);
|
||||
removeWatchCollection.push(removeWatch);
|
||||
break;
|
||||
|
||||
case '&':
|
||||
@@ -2665,16 +2709,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
|
||||
for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
|
||||
onNewScopeDestroyed[i]();
|
||||
|
||||
return removeWatchCollection.length && function removeWatches() {
|
||||
for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
|
||||
removeWatchCollection[i]();
|
||||
}
|
||||
} : noop;
|
||||
if (newScope && destroyBindings !== noop) {
|
||||
newScope.$on('$destroy', destroyBindings);
|
||||
return noop;
|
||||
}
|
||||
return destroyBindings;
|
||||
};
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
var $controllerMinErr = minErr('$controller');
|
||||
|
||||
|
||||
var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
|
||||
var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/;
|
||||
function identifierForController(controller, ident) {
|
||||
if (ident && isString(ident)) return ident;
|
||||
if (isString(controller)) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user