Compare commits
154 Commits
2.0.0-beta
...
2.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
c194f6695d | |||
967ae3e1b8 | |||
a5e6eaaebc | |||
d4e9b55fb6 | |||
048bd280dd | |||
8326ab3240 | |||
a7fe983be2 | |||
e1f8e54e34 | |||
2b165944ea | |||
ea11b3f1f8 | |||
3bd87147ab | |||
2f581ffc88 | |||
d61aaac400 | |||
310620fd12 | |||
f9fb72fb0e | |||
095db673c5 | |||
70d18b5b53 | |||
f1796d67f4 | |||
cb38d72ff4 | |||
b72bab49aa | |||
201475e8d8 | |||
c25b9fcf97 | |||
8755a8e188 | |||
127fbfd5a6 | |||
f33dda79e9 | |||
293fa5505b | |||
df1f78e302 | |||
43bb31c6c6 | |||
169869a195 | |||
b691da26af | |||
8e3e45097a | |||
aa43d2f87b | |||
128acbb6eb | |||
5824866a83 | |||
0d58b137a7 | |||
b5c769e1e4 | |||
7f22bd62ab | |||
83f0e7c975 | |||
adef68b4d6 | |||
14f0e9ada8 | |||
ef9e40e82b | |||
41e38e4330 | |||
2c7c3e3c69 | |||
756f5d884f | |||
45fd6f0a41 | |||
75ae4a9159 | |||
37d18d0112 | |||
773fe8f8c5 | |||
4da2b19ea0 | |||
9f3547e35d | |||
5a79358727 | |||
85bfbc13c1 | |||
1a01af9e68 | |||
dd95e901df | |||
9782d8c32e | |||
80764c6f71 | |||
d9e78e4fa8 | |||
10fedd0dfc | |||
315e73c47c | |||
912717ff31 | |||
b857fd1eeb | |||
6dce4f49c2 | |||
15e16148f4 | |||
ae49085481 | |||
11e8aa26f6 | |||
81beb1c788 | |||
6402d61f69 | |||
5a59e44765 | |||
7455b907d1 | |||
579b890446 | |||
19a08f3a43 | |||
a3d7629134 | |||
bc9644e86e | |||
a10c02cb41 | |||
9936e347ff | |||
7d44b8230e | |||
75343eb340 | |||
2548ce86db | |||
5586c29492 | |||
1174473e9c | |||
1d49b3e36b | |||
2830df4190 | |||
143cf89b5f | |||
69c1694900 | |||
01fe7f5fac | |||
9aedef208f | |||
39b6e0efba | |||
f60fa14767 | |||
d900f5c075 | |||
391a9edabb | |||
28a78117eb | |||
eeb594c010 | |||
0bb10d6bb6 | |||
59629a0801 | |||
b5e6319fa9 | |||
c9a3df970b | |||
f72f137261 | |||
ee3c580e88 | |||
05c185a7b1 | |||
b47f80ec76 | |||
ebd438ff5e | |||
331b9c1317 | |||
4a93f58b8b | |||
ebe531bf92 | |||
1779caf5f8 | |||
6ef2121e6a | |||
38cb526f60 | |||
f6a8d04c32 | |||
4b3b5d7c53 | |||
abff302e52 | |||
e1f6679c75 | |||
aaafdf03ce | |||
ee298baa1b | |||
d1abada5b7 | |||
ab36ea097b | |||
8bb66a5eb3 | |||
cfc1e56dd8 | |||
a1c3be21ec | |||
edad8e3f56 | |||
d4a4d81173 | |||
e7470d557d | |||
b634a25ae0 | |||
c1a0af514f | |||
c6afea61f1 | |||
ce10fe92b2 | |||
b81b1fb81c | |||
280b86ec55 | |||
2f5a2ba671 | |||
c45ec6f1be | |||
530470e0ce | |||
ce72ccf9e8 | |||
46d9c87ddc | |||
d736c31fea | |||
a7e9bc97f6 | |||
265703b950 | |||
ae275fa4e4 | |||
3478d5d450 | |||
e72dc16dbe | |||
40a043275d | |||
f161b5cc28 | |||
117d57e121 | |||
3dcce706fd | |||
efb89b83e1 | |||
3d96c2337f | |||
19cfb4eb12 | |||
3d715a2f7b | |||
c7261c295c | |||
1a26f8edd6 | |||
fc887774da | |||
7cbf88a691 | |||
1cb1c139cf | |||
1fd924f7d5 | |||
eb688f2c8e | |||
61cf499b0b |
38
.github/ISSUE_TEMPLATE.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
**Note: for support questions, please use one of these channels:** https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.
|
||||
|
||||
* **I'm submitting a ... **
|
||||
[ ] bug report
|
||||
[ ] feature request
|
||||
[ ] support request => Please do not submit support request here, see note at the top of this template.
|
||||
|
||||
|
||||
* **Do you want to request a *feature* or report a *bug*?**
|
||||
|
||||
|
||||
|
||||
* **What is the current behavior?**
|
||||
|
||||
|
||||
|
||||
* **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** via
|
||||
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
|
||||
|
||||
|
||||
|
||||
* **What is the expected behavior?**
|
||||
|
||||
|
||||
|
||||
* **What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
|
||||
* **Please tell us about your environment:**
|
||||
|
||||
- Angular version: 2.0.0-beta.X
|
||||
- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
|
||||
- Language: [all | TypeScript X.X | ES6/7 | ES5 | Dart]
|
||||
|
||||
|
||||
|
||||
* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc)
|
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
24
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
* **Please check if the PR fulfills these requirements**
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
|
||||
* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
|
||||
|
||||
|
||||
|
||||
* **What is the current behavior?** (You can also link to an open issue here)
|
||||
|
||||
|
||||
|
||||
* **What is the new behavior (if this is a feature change)?**
|
||||
|
||||
|
||||
|
||||
* **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
|
||||
|
||||
|
||||
|
||||
* **Other information**:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,6 +22,7 @@ tmp
|
||||
*.js.map
|
||||
|
||||
# Or type definitions we mirror from github
|
||||
# (NB: these lines are removed in publish-build-artifacts.sh)
|
||||
**/typings/**/*.d.ts
|
||||
**/typings/tsd.cached.json
|
||||
|
||||
|
159
.travis.yml
159
.travis.yml
@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '5.4.1'
|
||||
- '5.4.1'
|
||||
|
||||
branches:
|
||||
except:
|
||||
@ -9,73 +9,94 @@ branches:
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.pub-cache
|
||||
- $HOME/.chrome/chromium
|
||||
|
||||
before_cache:
|
||||
# Undo the pollution of the typescript_next build before the cache is primed for future use
|
||||
- if [[ "$MODE" == "typescript_next" ]]; then npm install typescript; fi
|
||||
|
||||
env:
|
||||
global:
|
||||
- KARMA_BROWSERS=DartiumWithWebPlatform
|
||||
- E2E_BROWSERS=Dartium
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
- BROWSER_STACK_USERNAME=angularteam1
|
||||
- BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
|
||||
- ARCH=linux-x64
|
||||
- DART_DEV_VERSION=latest
|
||||
- DART_STABLE_VERSION=latest
|
||||
# Token for tsd to increase github rate limit
|
||||
# See https://github.com/DefinitelyTyped/tsd#tsdrc
|
||||
# This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables
|
||||
# because those are not visible for pull requests, and those should also be reliable.
|
||||
# This SSO token belongs to github account angular-github-ratelimit-token which has no access
|
||||
# (password is in Valentine)
|
||||
- TSDRC='{"token":"ef474500309daea53d5991b3079159a29520a40b"}'
|
||||
# GITHUB_TOKEN_ANGULAR
|
||||
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
|
||||
# Use newer verison of GCC to that is required to compile native npm modules for Node v4+ on Ubuntu Precise
|
||||
# more info: https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements
|
||||
- CXX=g++-4.8
|
||||
- KARMA_DART_BROWSERS=DartiumWithWebPlatform
|
||||
# No sandbox mode is needed for Chromium in Travis, it crashes otherwise: https://sites.google.com/a/chromium.org/chromedriver/help/chrome-doesn-t-start
|
||||
- KARMA_JS_BROWSERS=ChromeNoSandbox
|
||||
- E2E_BROWSERS=ChromeOnTravis
|
||||
- LOGS_DIR=/tmp/angular-build/logs
|
||||
- SAUCE_USERNAME=angular-ci
|
||||
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
|
||||
- BROWSER_STACK_USERNAME=angularteam1
|
||||
- BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
|
||||
- ARCH=linux-x64
|
||||
- DART_DEV_VERSION=latest
|
||||
- DART_STABLE_VERSION=latest
|
||||
- DART_CHANNEL=stable
|
||||
- DART_VERSION=$DART_STABLE_VERSION
|
||||
# Token for tsd to increase github rate limit
|
||||
# See https://github.com/DefinitelyTyped/tsd#tsdrc
|
||||
# This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables
|
||||
# because those are not visible for pull requests, and those should also be reliable.
|
||||
# This SSO token belongs to github account angular-github-ratelimit-token which has no access
|
||||
# (password is in Valentine)
|
||||
- TSDRC='{"token":"ef474500309daea53d5991b3079159a29520a40b"}'
|
||||
# GITHUB_TOKEN_ANGULAR
|
||||
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
|
||||
matrix:
|
||||
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
|
||||
- MODE=dart DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION
|
||||
# Disable dart dev build, which is timing out after 2h. #6823
|
||||
# - MODE=dart DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=saucelabs_required DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=browserstack_required DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=saucelabs_optional DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=browserstack_optional DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=dart_ddc DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=js DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=router DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=build_only DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION
|
||||
- MODE=lint DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION
|
||||
- MODE=payload DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION
|
||||
- MODE=dart
|
||||
- MODE=dart DART_CHANNEL=dev
|
||||
- MODE=saucelabs_required
|
||||
- MODE=browserstack_required
|
||||
- MODE=saucelabs_optional
|
||||
- MODE=browserstack_optional
|
||||
- MODE=dart_ddc
|
||||
- MODE=js
|
||||
- MODE=router
|
||||
- MODE=build_only
|
||||
- MODE=typescript_next
|
||||
- MODE=lint
|
||||
- MODE=payload
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: "MODE=saucelabs_optional DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION"
|
||||
- env: "MODE=browserstack_optional DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION"
|
||||
# TODO(alxhub): remove when dartdoc #1039 is in dev channel
|
||||
- env: "MODE=dart DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION"
|
||||
- env: "MODE=saucelabs_optional"
|
||||
- env: "MODE=browserstack_optional"
|
||||
# Tracked in https://github.com/angular/angular/issues/7050
|
||||
- env: "MODE=typescript_next"
|
||||
|
||||
addons:
|
||||
firefox: "38.0"
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
|
||||
before_install:
|
||||
- npm install -g npm@3.5.3
|
||||
- node tools/analytics/build-analytics start ci job
|
||||
- node tools/analytics/build-analytics start ci before_install
|
||||
- echo ${TSDRC} > .tsdrc
|
||||
- export DISPLAY=:99.0
|
||||
- export GIT_SHA=$(git rev-parse HEAD)
|
||||
- ./scripts/ci/init_android.sh
|
||||
- ./scripts/ci/install_dart.sh ${DART_CHANNEL} ${DART_VERSION} ${ARCH}
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- if [[ -e SKIP_TRAVIS_TESTS ]]; then { cat SKIP_TRAVIS_TESTS ; exit 0; } fi
|
||||
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ] && SAUCE_USERNAME="angular2-ci" && SAUCE_ACCESS_KEY="693ebc16208a-0b5b-1614-8d66-a2662f4e" || true'
|
||||
- node tools/analytics/build-analytics success ci before_install
|
||||
- node tools/analytics/build-analytics start ci job
|
||||
- node tools/analytics/build-analytics start ci before_install
|
||||
- echo ${TSDRC} > .tsdrc
|
||||
- export CHROME_BIN=$HOME/.chrome/chromium/chrome-linux/chrome
|
||||
- export DISPLAY=:99.0
|
||||
- export GIT_SHA=$(git rev-parse HEAD)
|
||||
- ./scripts/ci/init_android.sh
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
# Use a separate SauseLabs account for upstream/master builds in order for Sauce to create a badge representing the status of just upstream/master
|
||||
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_BRANCH}" = "master" ] && SAUCE_USERNAME="angular2-ci" && SAUCE_ACCESS_KEY="693ebc16208a-0b5b-1614-8d66-a2662f4e" || true'
|
||||
- node tools/analytics/build-analytics success ci before_install
|
||||
|
||||
install:
|
||||
- node tools/analytics/build-analytics start ci install
|
||||
# Check the size of caches
|
||||
# Install version of npm that we are locked against
|
||||
- npm install -g npm@3.5.3
|
||||
# Install version of Chromium that we are locked against
|
||||
- ./scripts/ci/install_chromium.sh
|
||||
# Install version of Dart based on the matrix build variables
|
||||
- ./scripts/ci/install_dart.sh ${DART_CHANNEL} ${DART_VERSION} ${ARCH}
|
||||
# Print the size of caches to ease debugging
|
||||
- du -sh ./node_modules || true
|
||||
# Install npm dependecies
|
||||
# check-node-modules will exit(1) if we don't need to install
|
||||
@ -84,33 +105,33 @@ install:
|
||||
- node tools/analytics/build-analytics success ci install
|
||||
|
||||
before_script:
|
||||
- node tools/analytics/build-analytics start ci before_script
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./scripts/ci/presubmit-queue-setup.sh
|
||||
- node tools/analytics/build-analytics success ci before_script
|
||||
- node tools/analytics/build-analytics start ci before_script
|
||||
- mkdir -p $LOGS_DIR
|
||||
- ./scripts/ci/presubmit-queue-setup.sh
|
||||
- node tools/analytics/build-analytics success ci before_script
|
||||
|
||||
script:
|
||||
- node tools/analytics/build-analytics start ci script
|
||||
- ./scripts/ci/build_and_test.sh ${MODE}
|
||||
- node tools/analytics/build-analytics success ci script
|
||||
- node tools/analytics/build-analytics start ci script
|
||||
- ./scripts/ci/build_and_test.sh ${MODE}
|
||||
- node tools/analytics/build-analytics success ci script
|
||||
|
||||
after_script:
|
||||
- node tools/analytics/build-analytics start ci after_script
|
||||
- ./scripts/ci/print-logs.sh
|
||||
- ./scripts/ci/after-script.sh
|
||||
- ./scripts/publish/publish-build-artifacts.sh
|
||||
- node tools/analytics/build-analytics success ci after_script
|
||||
- if [[ $TRAVIS_TEST_RESULT -eq 0 ]]; then node tools/analytics/build-analytics success ci job; else node tools/analytics/build-analytics error ci job; fi
|
||||
- node tools/analytics/build-analytics start ci after_script
|
||||
- ./scripts/ci/print-logs.sh
|
||||
- ./scripts/ci/after-script.sh
|
||||
- ./scripts/publish/publish-build-artifacts.sh
|
||||
- node tools/analytics/build-analytics success ci after_script
|
||||
- tools/analytics/build-analytics $TRAVIS_TEST_RESULT ci job
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/1ef62e23078036f9cee4
|
||||
# trigger Buildtime Trend Service to parse Travis CI log
|
||||
- https://buildtimetrend.herokuapp.com/travis
|
||||
- https://webhooks.gitter.im/e/1ef62e23078036f9cee4
|
||||
# trigger Buildtime Trend Service to parse Travis CI log
|
||||
- https://buildtimetrend.herokuapp.com/travis
|
||||
- http://104.197.9.155:8484/hubot/travis/activity
|
||||
on_success: always # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
on_start: never # default: never
|
||||
slack:
|
||||
secure: EP4MzZ8JMyNQJ4S3cd5LEPWSMjC7ZRdzt3veelDiOeorJ6GwZfCDHncR+4BahDzQAuqyE/yNpZqaLbwRWloDi15qIUsm09vgl/1IyNky1Sqc6lEknhzIXpWSalo4/T9ZP8w870EoDvM/UO+LCV99R3wS8Nm9o99eLoWVb2HIUu0=
|
||||
|
||||
|
251
CHANGELOG.md
251
CHANGELOG.md
@ -1,3 +1,254 @@
|
||||
<a name="2.0.0-beta.11"></a>
|
||||
# 2.0.0-beta.11 (2016-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure that Zone does not show up in angular2.d.ts ([d4e9b55](https://github.com/angular/angular/commit/d4e9b55fb69d87f948d02905d34fc78221adb11a))
|
||||
* **common:** remove @internal annotation on SwitchView ([967ae3e](https://github.com/angular/angular/commit/967ae3e)), closes [#7657](https://github.com/angular/angular/issues/7657)
|
||||
* **router:** RouterOutlet loads component twice in a race condition ([2f581ff](https://github.com/angular/angular/commit/2f581ff)), closes [#7497](https://github.com/angular/angular/issues/7497) [#7545](https://github.com/angular/angular/issues/7545)
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** add a simple dart script extracting all i18n messages from a package ([8326ab3](https://github.com/angular/angular/commit/8326ab3)), closes [#7620](https://github.com/angular/angular/issues/7620)
|
||||
* **i18n:** create i18n barrel ([a7fe983](https://github.com/angular/angular/commit/a7fe983))
|
||||
* **i18n:** implement xmb serializer ([e1f8e54](https://github.com/angular/angular/commit/e1f8e54))
|
||||
|
||||
|
||||
<a name="2.0.0-beta.10"></a>
|
||||
# 2.0.0-beta.10 (2016-03-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **change_detection:** fix a memory leak ([128acbb](https://github.com/angular/angular/commit/128acbb))
|
||||
* **closure:** don't throw from top-level ([5824866](https://github.com/angular/angular/commit/5824866))
|
||||
* **router:** handle URL that does not match a route ([8e3e450](https://github.com/angular/angular/commit/8e3e450)), closes [#7349](https://github.com/angular/angular/issues/7349) [#7203](https://github.com/angular/angular/issues/7203)
|
||||
* **router/instruction:** ensure toLinkUrl includes extra params ([0d58b13](https://github.com/angular/angular/commit/0d58b13)), closes [#7367](https://github.com/angular/angular/issues/7367)
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** change html parser to preserve comments ([70d18b5](https://github.com/angular/angular/commit/70d18b5))
|
||||
* **core:** introduce a CSS lexer/parser ([b72bab4](https://github.com/angular/angular/commit/b72bab4))
|
||||
* **core:** introduce a CSS lexer/parser ([293fa55](https://github.com/angular/angular/commit/293fa55))
|
||||
* **facade:** add .values to StringMapWrapper ([f1796d6](https://github.com/angular/angular/commit/f1796d6))
|
||||
* **i18n:** add ngPlural directive ([df1f78e](https://github.com/angular/angular/commit/df1f78e))
|
||||
* **i18n:** implement a simple version of message extractor ([095db67](https://github.com/angular/angular/commit/095db67)), closes [#7454](https://github.com/angular/angular/issues/7454)
|
||||
* **shadow_css:** support `/deep/` and `>>>` ([cb38d72](https://github.com/angular/angular/commit/cb38d72)), closes [#7562](https://github.com/angular/angular/issues/7562) [#7563](https://github.com/angular/angular/issues/7563)
|
||||
* **TAG_DEFINITIONS:** include <meta> and <base> ([2c7c3e3](https://github.com/angular/angular/commit/2c7c3e3)), closes [#7455](https://github.com/angular/angular/issues/7455)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
Removed deprecated API from NgZone
|
||||
- `NgZone.overrideOnTurnStart`
|
||||
- `NgZone.overrideOnTurnDone`
|
||||
- `NgZone.overrideOnEventDone`
|
||||
- `NgZone.overrideOnErrorHandler`
|
||||
|
||||
Rename NgZone API
|
||||
- `NgZone.onTurnStart` => `NgZone.onUnstable`
|
||||
- `NgZone.onTurnDone` => `NgZone.onMicrotaskEmpty`
|
||||
- `NgZone.onEventDone` => `NgZone.onStable`
|
||||
|
||||
|
||||
<a name="2.0.0-beta.9"></a>
|
||||
# 2.0.0-beta.9 (2016-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular_1_router:** Renamed require statements after TypeScript files are transpiled ([ae49085](https://github.com/angular/angular/commit/ae49085)), closes [#7049](https://github.com/angular/angular/issues/7049)
|
||||
* **angular1_router:** rename `router` component binding to `$router` ([2548ce8](https://github.com/angular/angular/commit/2548ce8))
|
||||
* **angular1_router:** rename `router` component binding to `$router` ([1174473](https://github.com/angular/angular/commit/1174473))
|
||||
* **angular1_router:** support templateUrl components ([5586c29](https://github.com/angular/angular/commit/5586c29))
|
||||
* **build:** Use fixed version of Chromium Canary that will be updated manually instead of au ([1d49b3e](https://github.com/angular/angular/commit/1d49b3e))
|
||||
* **router:** support outlets within dynamic components ([7d44b82](https://github.com/angular/angular/commit/7d44b82))
|
||||
|
||||
### Features
|
||||
|
||||
* **angular1_router:** Add ng-link-active class to active ng-link ([11e8aa2](https://github.com/angular/angular/commit/11e8aa2)), closes [#6882](https://github.com/angular/angular/issues/6882)
|
||||
* **compiler:** Added spans to HTML parser errors ([19a08f3](https://github.com/angular/angular/commit/19a08f3))
|
||||
* **dart:** Add a dev-mode check for undeclared lifecycle interfaces ([a3d7629](https://github.com/angular/angular/commit/a3d7629)), closes [#6849](https://github.com/angular/angular/issues/6849)
|
||||
* **dart/transform:** Create standalone transformers for phases ([15e1614](https://github.com/angular/angular/commit/15e1614))
|
||||
* **iterable_differ:** support immutable lists ([a10c02c](https://github.com/angular/angular/commit/a10c02c)), closes [#7127](https://github.com/angular/angular/issues/7127)
|
||||
* **router:** add regex matchers ([75343eb](https://github.com/angular/angular/commit/75343eb)), closes [#7325](https://github.com/angular/angular/issues/7325) [#7126](https://github.com/angular/angular/issues/7126)
|
||||
* **router:** Added method to get current instruction ([6dce4f4](https://github.com/angular/angular/commit/6dce4f4))
|
||||
* **transformers:** change 'Missing Identifier' to be an error ([45fd6f0](https://github.com/angular/angular/commit/45fd6f0)), closes [#7403](https://github.com/angular/angular/issues/7403)
|
||||
* **transformers:** collect provider information ([81beb1c](https://github.com/angular/angular/commit/81beb1c))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* The recently added binding of the current router to the current component
|
||||
has been renamed from `router` to `$router`.
|
||||
So now the recommended set up for your bindings in your routed component
|
||||
is:
|
||||
```js
|
||||
{
|
||||
...
|
||||
bindings: {
|
||||
$router: '<'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* The recently added binding of the current router to the current component
|
||||
has been renamed from `router` to `$router`.
|
||||
So now the recommended set up for your bindings in your routed component
|
||||
is:
|
||||
```js
|
||||
{
|
||||
...
|
||||
bindings: {
|
||||
$router: '<'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-beta.8"></a>
|
||||
# 2.0.0-beta.8 (2016-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular1_router:** rename `$route` service to `$rootRouter` ([a1c3be2](https://github.com/angular/angular/commit/a1c3be2))
|
||||
* **angular1_router:** rename `router` component binding to `$router` ([edad8e3](https://github.com/angular/angular/commit/edad8e3))
|
||||
* **angular1_router:** support templateUrl components ([d4a4d81](https://github.com/angular/angular/commit/d4a4d81))
|
||||
* **change_detection:** allow to destroy `OnPush` components inside of a host event. ([280b86e](https://github.com/angular/angular/commit/280b86e))
|
||||
* **change_detection:** allow to destroy `OnPush` components inside of a host event. ([ebd438f](https://github.com/angular/angular/commit/ebd438f)), closes [#7192](https://github.com/angular/angular/issues/7192)
|
||||
* **core:** support `ngFor` that has an `ngIf` as last node ([1779caf](https://github.com/angular/angular/commit/1779caf)), closes [#6304](https://github.com/angular/angular/issues/6304) [#6878](https://github.com/angular/angular/issues/6878)
|
||||
* **dart/payload:** Fix runtime error in hello_world payload app ([eeb594c](https://github.com/angular/angular/commit/eeb594c)), closes [#7358](https://github.com/angular/angular/issues/7358)
|
||||
* **differ:** clean up stale identity change refs ([ab36ea0](https://github.com/angular/angular/commit/ab36ea0)), closes [#7193](https://github.com/angular/angular/issues/7193)
|
||||
* **DomRenderer:** correctly handle namespaced attributes ([c6afea6](https://github.com/angular/angular/commit/c6afea6))
|
||||
* **Router:** Query strings are copied for HashLocationStrategy ([b47f80e](https://github.com/angular/angular/commit/b47f80e)), closes [#7298](https://github.com/angular/angular/issues/7298)
|
||||
* **test:** fix a broken test ([9aedef2](https://github.com/angular/angular/commit/9aedef2))
|
||||
* **transformers:** record reflection info about abstract classes ([05c185a](https://github.com/angular/angular/commit/05c185a)), closes [#7347](https://github.com/angular/angular/issues/7347)
|
||||
* **transformers:** replace an error with a warning when cannot resolve a symbol ([ee3c580](https://github.com/angular/angular/commit/ee3c580))
|
||||
* **transformers:** special case types some built-in types, so they can be resolved ([331b9c1](https://github.com/angular/angular/commit/331b9c1))
|
||||
* **web_worker:** wait for bindings in kitchen sink spec ([4a93f58](https://github.com/angular/angular/commit/4a93f58))
|
||||
* **web_workers:** make waitForElementText function more stable ([f6a8d04](https://github.com/angular/angular/commit/f6a8d04))
|
||||
* **WebWorker:** Fix PostMessageBusSink and Source undefined error. ([01fe7f5](https://github.com/angular/angular/commit/01fe7f5)), closes [#7156](https://github.com/angular/angular/issues/7156)
|
||||
* **WebWorker:** Make MessageBus EventEmitter synchronous ([69c1694](https://github.com/angular/angular/commit/69c1694))
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** Add `QueryList.forEach` to public api. ([e7470d5](https://github.com/angular/angular/commit/e7470d5))
|
||||
* **core:** Add `QueryList#forEach` ([b634a25](https://github.com/angular/angular/commit/b634a25))
|
||||
* **core:** add more debug APIs to inspect the application form a browser ([b5e6319](https://github.com/angular/angular/commit/b5e6319)), closes [#7045](https://github.com/angular/angular/issues/7045) [#7161](https://github.com/angular/angular/issues/7161)
|
||||
* **core:** drop `ChangeDetectionStrategy.OnPushObserve` ([f60fa14](https://github.com/angular/angular/commit/f60fa14))
|
||||
* **di:** drop support for injecting types with generics in Dart ([c9a3df9](https://github.com/angular/angular/commit/c9a3df9)), closes [#7262](https://github.com/angular/angular/issues/7262)
|
||||
* **forms/validators:** pattern validator ([38cb526](https://github.com/angular/angular/commit/38cb526)), closes [#5561](https://github.com/angular/angular/issues/5561)
|
||||
* **i18n:** added i18nPlural and i18nSelect pipes ([59629a0](https://github.com/angular/angular/commit/59629a0)), closes [#7268](https://github.com/angular/angular/issues/7268)
|
||||
* **pipes:** add ReplacePipe for string manipulation ([6ef2121](https://github.com/angular/angular/commit/6ef2121))
|
||||
* **test:** add withProviders for per test providers ([c1a0af5](https://github.com/angular/angular/commit/c1a0af5)), closes [#5128](https://github.com/angular/angular/issues/5128)
|
||||
* **transformers:** collect data needed for the template compiler ([ebe531b](https://github.com/angular/angular/commit/ebe531b)), closes [#7299](https://github.com/angular/angular/issues/7299)
|
||||
* **transformers:** collect information for CompileDiDependencyMetadata ([39b6e0e](https://github.com/angular/angular/commit/39b6e0e))
|
||||
* **transformers:** makes the map of resolved identifiers configurable ([0bb10d6](https://github.com/angular/angular/commit/0bb10d6)), closes [#7359](https://github.com/angular/angular/issues/7359)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `OnPushObserve` was an experimental
|
||||
feature for Dart and had
|
||||
conceptual performance problems,
|
||||
as setting up observables is slow.
|
||||
Use `OnPush` instead.
|
||||
|
||||
* In Dart we used to support injecting types with generics. As this feature is hard to implement with the upcoming codegen we are dropping it.
|
||||
Merge cl/115454020 in G3 with this change.
|
||||
|
||||
* The `$router` injectable service has been renamed to `$rootRouter`
|
||||
|
||||
* The recently added binding of the current router to the current component
|
||||
has been renamed from `router` to `$router`.
|
||||
So now the recommended set up for your bindings in your routed component
|
||||
is:
|
||||
```js
|
||||
{
|
||||
...
|
||||
bindings: {
|
||||
$router: '<'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="2.0.0-beta.7"></a>
|
||||
# 2.0.0-beta.7 (2016-02-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular_1_router:** Added DI string tokens ([3478d5d](https://github.com/angular/angular/commit/3478d5d)), closes [#4269](https://github.com/angular/angular/issues/4269) [#7031](https://github.com/angular/angular/issues/7031)
|
||||
* **typing:** Remove re-export of the Promise built-in type. ([265703b](https://github.com/angular/angular/commit/265703b)), closes [#6468](https://github.com/angular/angular/issues/6468)
|
||||
|
||||
<a name="2.0.0-beta.6"></a>
|
||||
# 2.0.0-beta.6 (2016-02-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular1-router:** add missing wrapper methods ([55122cd](https://github.com/angular/angular/commit/55122cd)), closes [#6763](https://github.com/angular/angular/issues/6763) [#6861](https://github.com/angular/angular/issues/6861) [#6861](https://github.com/angular/angular/issues/6861)
|
||||
* **angular1-router:** add support for using the component helper ([d86be24](https://github.com/angular/angular/commit/d86be24)), closes [angular/angular.js#13860](https://github.com/angular/angular.js/issues/13860) [#6076](https://github.com/angular/angular/issues/6076) [#5278](https://github.com/angular/angular/issues/5278)
|
||||
* **async:** handle synchronous initial value in async pipe ([26e60d6](https://github.com/angular/angular/commit/26e60d6)), closes [#5996](https://github.com/angular/angular/issues/5996)
|
||||
* **build:** don't try to copy .d.ts files into the npm distro ([16b5217](https://github.com/angular/angular/commit/16b5217)), closes [#6921](https://github.com/angular/angular/issues/6921)
|
||||
* **compiler:** fix interpolation regexp ([9b0e10e](https://github.com/angular/angular/commit/9b0e10e)), closes [#6056](https://github.com/angular/angular/issues/6056)
|
||||
* **compiler:** use event names for matching directives ([231773e](https://github.com/angular/angular/commit/231773e)), closes [#6870](https://github.com/angular/angular/issues/6870)
|
||||
* **core:** add detail to dehydrated detector exception ([e7ad03c](https://github.com/angular/angular/commit/e7ad03c)), closes [#6939](https://github.com/angular/angular/issues/6939)
|
||||
* **core:** mute mode printing in console in prod mode ([74be3d3](https://github.com/angular/angular/commit/74be3d3)), closes [#6873](https://github.com/angular/angular/issues/6873)
|
||||
* **di:** throw if a token uses more than 20 dependencies. ([de77700](https://github.com/angular/angular/commit/de77700)), closes [#6690](https://github.com/angular/angular/issues/6690) [#6869](https://github.com/angular/angular/issues/6869)
|
||||
* **forms:** add RadioButtonValueAccessor to the list of default value accessors ([8f47aa3](https://github.com/angular/angular/commit/8f47aa3))
|
||||
* **forms:** add support for radio buttons ([e725542](https://github.com/angular/angular/commit/e725542)), closes [#6877](https://github.com/angular/angular/issues/6877)
|
||||
* **forms:** use strict runtimeType checks instead of instanceof ([50548fb](https://github.com/angular/angular/commit/50548fb)), closes [#6981](https://github.com/angular/angular/issues/6981)
|
||||
* **Headers:** serializable toJSON ([b55f176](https://github.com/angular/angular/commit/b55f176)), closes [#6073](https://github.com/angular/angular/issues/6073) [#6714](https://github.com/angular/angular/issues/6714)
|
||||
* **ngFor:** update view locals if identity changes ([0f10624](https://github.com/angular/angular/commit/0f10624)), closes [#6923](https://github.com/angular/angular/issues/6923)
|
||||
* **router:** Added route data to normalized async route ([df7885c](https://github.com/angular/angular/commit/df7885c)), closes [#6802](https://github.com/angular/angular/issues/6802)
|
||||
* **router:** don't prepend `/` unnecessarily to Location paths ([c603643](https://github.com/angular/angular/commit/c603643)), closes [#6729](https://github.com/angular/angular/issues/6729) [#5502](https://github.com/angular/angular/issues/5502)
|
||||
* **router:** fix incorrect url param value coercion of 1 to true ([995a9e0](https://github.com/angular/angular/commit/995a9e0)), closes [#5346](https://github.com/angular/angular/issues/5346) [#6286](https://github.com/angular/angular/issues/6286)
|
||||
* **router:** fix url path for star segment in path recognizer ([6f1ef33](https://github.com/angular/angular/commit/6f1ef33)), closes [#6976](https://github.com/angular/angular/issues/6976)
|
||||
* **router:** fixed the location wrapper for angular1 ([e73fee7](https://github.com/angular/angular/commit/e73fee7)), closes [#6943](https://github.com/angular/angular/issues/6943)
|
||||
* **typings:** Don't expose typing dependencies to users. ([2a70f4e](https://github.com/angular/angular/commit/2a70f4e)), closes [#5973](https://github.com/angular/angular/issues/5973) [#5807](https://github.com/angular/angular/issues/5807) [#6266](https://github.com/angular/angular/issues/6266) [#5242](https://github.com/angular/angular/issues/5242) [#6817](https://github.com/angular/angular/issues/6817) [#6267](https://github.com/angular/angular/issues/6267)
|
||||
* **upgrade:** fix infinite $rootScope.$digest() ([7e0f02f](https://github.com/angular/angular/commit/7e0f02f)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6386](https://github.com/angular/angular/issues/6386)
|
||||
* **Validators:** fix Validators.required marking number zero as invalid ([c2ceb7f](https://github.com/angular/angular/commit/c2ceb7f)), closes [#6617](https://github.com/angular/angular/issues/6617)
|
||||
* **WebWorkers:** Fix flaky WebWorker test ([da1fcfd](https://github.com/angular/angular/commit/da1fcfd)), closes [#6851](https://github.com/angular/angular/issues/6851)
|
||||
|
||||
### Features
|
||||
|
||||
* **angular1_router:** allow component to bind to router ([0f22dce](https://github.com/angular/angular/commit/0f22dce))
|
||||
* **typings:** install es6-shim typings to a location users can reference. ([f1f5b45](https://github.com/angular/angular/commit/f1f5b45))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
Transitive typings are no longer included in the distribution.
|
||||
|
||||
If you use `--target=es5`, you will need to add a line somewhere in your
|
||||
application (for example, at the top of the `.ts` file where you call `bootstrap`):
|
||||
```
|
||||
///<reference path="node_modules/angular2/typings/browser.d.ts"/>
|
||||
```
|
||||
(Note that if your file is not in the same directory as `node_modules`, you'll
|
||||
need to add one or more `../` to the start of that path.)
|
||||
|
||||
If you have unit tests, you need to install typings in your project using
|
||||
http://github.com/typings/typings
|
||||
And install typings such as `jasmine`, `angular-protractor`, or `selenium-webdriver`
|
||||
to satisfy the type-checker.
|
||||
|
||||
If you rely on es6 APIs other than Promises and Collections, you will need to
|
||||
install the es6-shim typing instead of using the <reference> tag above.
|
||||
Angular previously exposed typings for the entire ES6 API.
|
||||
|
||||
<a name="2.0.0-beta.5"></a>
|
||||
# 2.0.0-beta.5 (2016-02-10)
|
||||
|
||||
This release was incorrect; replaced with beta.6.
|
||||
|
||||
<a name="2.0.0-beta.4"></a>
|
||||
# 2.0.0-beta.4 (2016-02-10)
|
||||
|
||||
This release was incorrect; replaced with beta.6.
|
||||
|
||||
<a name="2.0.0-beta.3"></a>
|
||||
# 2.0.0-beta.3 (2016-02-03)
|
||||
|
||||
|
@ -180,8 +180,8 @@ Must be one of the following:
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
* **perf**: A code change that improves performance
|
||||
* **test**: Adding missing tests or correcting existing tests
|
||||
* **build**: Changes that affect the build system, CI configuration or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Any changes to our CI configuration files and scripts (Travis, Circle CI, BrowserStack, SauceLabs)
|
||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
||||
* **chore**: Other changes that don't modify `src` or `test` files
|
||||
|
||||
### Scope
|
||||
|
@ -117,7 +117,7 @@ speed things up is to use plain class fields in your expressions and avoid any
|
||||
kinds of computation. Example:
|
||||
|
||||
```typescript
|
||||
@View({
|
||||
@Component({
|
||||
template: '<button [enabled]="isEnabled">{{title}}</button>'
|
||||
})
|
||||
class FancyButton {
|
||||
|
@ -4,8 +4,9 @@
|
||||
var CIconfiguration = {
|
||||
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'ChromeBeta': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'FirefoxBeta': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
// FirefoxBeta should be required:true
|
||||
// https://github.com/angular/angular/issues/7560
|
||||
'FirefoxBeta': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: false}},
|
||||
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
|
||||
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
|
||||
'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
|
187
gulpfile.js
187
gulpfile.js
@ -40,9 +40,9 @@ if (cliArgs.projects) {
|
||||
cliArgs.projects.split(',').sort().join(',');
|
||||
}
|
||||
|
||||
// --projects=angular2,angular2_material => {angular2: true, angular2_material: true}
|
||||
// --projects=angular2 => {angular2: true}
|
||||
var allProjects =
|
||||
'angular1_router,angular2,angular2_material,benchmarks,benchmarks_external,benchpress,playground,payload_tests,bundle_deps';
|
||||
'angular1_router,angular2,benchmarks,benchmarks_external,benchpress,playground,payload_tests,bundle_deps';
|
||||
var cliArgsProjects = (cliArgs.projects || allProjects)
|
||||
.split(',')
|
||||
.reduce((map, projectName) => {
|
||||
@ -57,7 +57,7 @@ function printModulesWarning() {
|
||||
console.warn(
|
||||
"Pro Tip: Did you know that you can speed up your build by specifying project name(s)?");
|
||||
console.warn(" It's like pressing the turbo button in the old days, but better!");
|
||||
console.warn(" Examples: --project=angular2 or --project=angular2,angular2_material");
|
||||
console.warn(" Examples: --project=angular2 or --project=angular2");
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,9 +344,8 @@ gulp.task('lint', ['build.tools'], function() {
|
||||
gulp.task('build/checkCircularDependencies', function(done) {
|
||||
var madge = require('madge');
|
||||
|
||||
var dependencyObject = madge(CONFIG.dest.js.dev.es5, {
|
||||
var dependencyObject = madge([CONFIG.dest.js.dev.es5], {
|
||||
format: 'cjs',
|
||||
paths: [CONFIG.dest.js.dev.es5],
|
||||
extensions: ['.js'],
|
||||
onParseFile: function(data) { data.src = data.src.replace(/\/\* circular \*\//g, "//"); }
|
||||
});
|
||||
@ -383,7 +382,7 @@ function proxyServeDart() {
|
||||
|
||||
// ------------------
|
||||
// web servers
|
||||
gulp.task('serve.js.dev', ['build.js.dev'], function(neverDone) {
|
||||
gulp.task('serve.js.dev', ['build.js.dev', 'build.js.cjs'], function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
watch('modules/**', {ignoreInitial: true}, '!broccoli.js.dev');
|
||||
@ -392,21 +391,19 @@ gulp.task('serve.js.dev', ['build.js.dev'], function(neverDone) {
|
||||
|
||||
gulp.task('serve.js.prod', jsServeProd);
|
||||
|
||||
gulp.task('serve.e2e.dev', ['build.js.dev', 'build.js.cjs', 'build.css.material'],
|
||||
function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
gulp.task('serve.e2e.dev', ['build.js.dev', 'build.js.cjs'], function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.dev', '!build.js.cjs']);
|
||||
jsServeDev();
|
||||
});
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.dev', '!build.js.cjs']);
|
||||
jsServeDev();
|
||||
});
|
||||
|
||||
gulp.task('serve.e2e.prod', ['build.js.prod', 'build.js.cjs', 'build.css.material'],
|
||||
function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
gulp.task('serve.e2e.prod', ['build.js.prod', 'build.js.cjs'], function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.prod', '!build.js.cjs']);
|
||||
jsServeProd();
|
||||
});
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.prod', '!build.js.cjs']);
|
||||
jsServeProd();
|
||||
});
|
||||
|
||||
gulp.task('serve.js.dart2js', jsServeDartJs);
|
||||
|
||||
@ -441,7 +438,7 @@ gulp.task('serve.e2e.dart', ['build.js.cjs'], function(neverDone) {
|
||||
|
||||
// Note: we are not using build.dart as the dart analyzer takes too long...
|
||||
watch('modules/**', {ignoreInitial: true}, ['!build/tree.dart', '!build.js.cjs']);
|
||||
runSequence('build/packages.dart', 'build/pubspec.dart', 'build.dart.material.css', 'serve.dart');
|
||||
runSequence('build/packages.dart', 'build/pubspec.dart', 'serve.dart');
|
||||
});
|
||||
|
||||
|
||||
@ -468,7 +465,7 @@ gulp.task('test.js', function(done) {
|
||||
|
||||
gulp.task('test.dart', function(done) {
|
||||
runSequence('versions.dart', 'test.transpiler.unittest', 'test.unit.dart/ci',
|
||||
'test.dart.angular2_testing/ci', sequenceComplete(done));
|
||||
sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('versions.dart', function() { dartSdk.logVersion(DART_SDK); });
|
||||
@ -636,8 +633,7 @@ gulp.task('buildRouter.dev', function() {
|
||||
gulp.task('test.unit.dart', function(done) {
|
||||
printModulesWarning();
|
||||
runSequence('build/tree.dart', 'build/pure-packages.dart', '!build/pubget.angular2.dart',
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', 'build.dart.material.css',
|
||||
'!test.unit.dart/karma-server', '!test.unit.dart/karma-run', function(error) {
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', function(error) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
// if initial build failed (likely due to build or formatting step) then exit
|
||||
@ -646,9 +642,10 @@ gulp.task('test.unit.dart', function(done) {
|
||||
done(error);
|
||||
return;
|
||||
}
|
||||
// treatTestErrorsAsFatal = false;
|
||||
|
||||
watch(['modules/angular2/**'], {ignoreInitial: true},
|
||||
['!build/tree.dart', '!test.unit.dart/karma-run']);
|
||||
watch(['modules/angular2/**'],
|
||||
['!build/tree.dart', '!test.unit.dart/run/angular2']);
|
||||
});
|
||||
});
|
||||
|
||||
@ -707,7 +704,7 @@ gulp.task('!build.payload.js.webpack', function() {
|
||||
.then(function() { // pad bundle with mandatory dependencies
|
||||
return new Promise(function(resolve, reject) {
|
||||
gulp.src([
|
||||
'node_modules/zone.js/dist/zone-microtask.js',
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
'node_modules/reflect-metadata/Reflect.js',
|
||||
CASE_PATH + '/app-bundle.js'
|
||||
@ -775,8 +772,7 @@ gulp.task('!checkAndReport.payload.js', function() {
|
||||
|
||||
gulp.task('watch.dart.dev', function(done) {
|
||||
runSequence('build/tree.dart', 'build/pure-packages.dart', '!build/pubget.angular2.dart',
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', 'build.dart.material.css',
|
||||
function(error) {
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', function(error) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
// if initial build failed (likely due to build or formatting step) then exit
|
||||
@ -790,20 +786,6 @@ gulp.task('watch.dart.dev', function(done) {
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('!test.unit.dart/karma-run', function(done) {
|
||||
// run the run command in a new process to avoid duplicate logging by both server and runner from
|
||||
// a single process
|
||||
runKarma('karma-dart.conf.js', done);
|
||||
});
|
||||
|
||||
|
||||
gulp.task('!test.unit.dart/karma-server', function() {
|
||||
var karma = require('karma');
|
||||
|
||||
new karma.Server({configFile: __dirname + '/karma-dart.conf.js', reporters: 'dots'}).start();
|
||||
});
|
||||
|
||||
|
||||
gulp.task('test.unit.router/ci', function(done) {
|
||||
var karma = require('karma');
|
||||
|
||||
@ -851,20 +833,56 @@ gulp.task('test.unit.js.browserstack/ci', function(done) {
|
||||
});
|
||||
|
||||
gulp.task('test.unit.dart/ci', function(done) {
|
||||
var karma = require('karma');
|
||||
|
||||
var browserConf = getBrowsersFromCLI(null, true);
|
||||
new karma.Server(
|
||||
{
|
||||
configFile: __dirname + '/karma-dart.conf.js',
|
||||
singleRun: true,
|
||||
reporters: ['dots'],
|
||||
browsers: browserConf.browsersToRun
|
||||
},
|
||||
done)
|
||||
.start();
|
||||
runSequence('test.dart.dartium_symlink', '!test.unit.dart/run/angular2',
|
||||
'!test.unit.dart/run/angular2_testing', '!test.unit.dart/run/benchpress',
|
||||
sequenceComplete(done));
|
||||
});
|
||||
|
||||
// At the moment, dart test requires dartium to be an executable on the path.
|
||||
// Make a temporary directory and symlink dartium from there (just for this command)
|
||||
// so that it can run.
|
||||
// TODO(juliemr): this won't work with windows - remove the hack and make this platform agnostic.
|
||||
var dartiumTmpdir = path.join(os.tmpdir(), 'dartium' + new Date().getTime().toString());
|
||||
var dartiumPathPrefix = 'PATH=$PATH:' + dartiumTmpdir + ' ';
|
||||
gulp.task(
|
||||
'test.dart.dartium_symlink',
|
||||
shell.task(['mkdir ' + dartiumTmpdir, 'ln -s $DARTIUM_BIN ' + dartiumTmpdir + '/dartium']));
|
||||
|
||||
gulp.task('!test.unit.dart/run/angular2', function() {
|
||||
var pubtest = require('./tools/build/pubtest');
|
||||
return pubtest({
|
||||
dir: path.join(CONFIG.dest.dart, 'angular2'),
|
||||
dartiumTmpdir: dartiumTmpdir,
|
||||
command: DART_SDK.PUB,
|
||||
files: '**/*_spec.dart',
|
||||
bunchFiles: true,
|
||||
useExclusiveTests: true
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('!test.unit.dart/run/angular2_testing', function() {
|
||||
var pubtest = require('./tools/build/pubtest');
|
||||
|
||||
return pubtest({
|
||||
dir: path.join(CONFIG.dest.dart, 'angular2_testing'),
|
||||
dartiumTmpdir: dartiumTmpdir,
|
||||
command: DART_SDK.PUB,
|
||||
files: '**/*_test.dart',
|
||||
useExclusiveTests: true
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('!test.unit.dart/run/benchpress', function() {
|
||||
var pubtest = require('./tools/build/pubtest');
|
||||
|
||||
return pubtest({
|
||||
dir: path.join(CONFIG.dest.dart, 'benchpress'),
|
||||
dartiumTmpdir: dartiumTmpdir,
|
||||
command: DART_SDK.PUB,
|
||||
files: '**/*_spec.dart',
|
||||
useExclusiveTests: true
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('test.unit.cjs/ci', function(done) {
|
||||
runJasmineTests(['dist/js/cjs/{angular2,benchpress}/test/**/*_spec.js'], done);
|
||||
@ -931,24 +949,6 @@ gulp.task('test.server.dart', runServerDartTests(gulp, gulpPlugins, {dest: 'dist
|
||||
gulp.task('test.transpiler.unittest',
|
||||
function(done) { runJasmineTests(['tools/transpiler/unittest/**/*.js'], done); });
|
||||
|
||||
// At the moment, dart test requires dartium to be an executable on the path.
|
||||
// Make a temporary directory and symlink dartium from there (just for this command)
|
||||
// so that it can run.
|
||||
var dartiumTmpdir = path.join(os.tmpdir(), 'dartium' + new Date().getTime().toString());
|
||||
gulp.task('test.dart.angular2_testing/ci', ['build/pubspec.dart'], function(done) {
|
||||
runSequence('test.dart.angular2_testing_symlink', 'test.dart.angular2_testing',
|
||||
sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
'test.dart.angular2_testing_symlink',
|
||||
shell.task(['mkdir ' + dartiumTmpdir, 'ln -s $DARTIUM_BIN ' + dartiumTmpdir + '/dartium']));
|
||||
|
||||
gulp.task('test.dart.angular2_testing',
|
||||
shell.task(['PATH=$PATH:' + dartiumTmpdir + ' pub run test -p dartium'],
|
||||
{'cwd': 'dist/dart/angular2_testing'}));
|
||||
|
||||
|
||||
// -----------------
|
||||
// Pre-test checks
|
||||
|
||||
@ -1018,6 +1018,7 @@ gulp.task('build/pure-packages.dart/standalone', function() {
|
||||
'modules_dart/**/*',
|
||||
'!modules_dart/**/*.proto',
|
||||
'!modules_dart/**/packages{,/**}',
|
||||
'!modules_dart/**/.packages',
|
||||
'!modules_dart/payload{,/**}',
|
||||
'!modules_dart/transform{,/**}',
|
||||
])
|
||||
@ -1049,7 +1050,7 @@ gulp.task('build/packages.dart', function(done) {
|
||||
// Builds and compiles all Dart packages
|
||||
gulp.task('build.dart', function(done) {
|
||||
runSequence('build/packages.dart', 'build/pubspec.dart', 'build/analyze.dart',
|
||||
'build/check.apidocs.dart', 'build.dart.material.css', sequenceComplete(done));
|
||||
'build/check.apidocs.dart', sequenceComplete(done));
|
||||
});
|
||||
|
||||
|
||||
@ -1108,9 +1109,8 @@ gulp.task('!broccoli.js.prod', () => angularBuilder.rebuildBrowserProdTree({
|
||||
useBundles: cliArgs.useBundles
|
||||
}));
|
||||
|
||||
gulp.task('build.js.dev', ['build/clean.js'], function(done) {
|
||||
runSequence('broccoli.js.dev', 'build.css.material', sequenceComplete(done));
|
||||
});
|
||||
gulp.task('build.js.dev', ['build/clean.js'],
|
||||
function(done) { runSequence('broccoli.js.dev', sequenceComplete(done)); });
|
||||
|
||||
gulp.task('build.js.prod', ['build.tools'],
|
||||
function(done) { runSequence('!broccoli.js.prod', sequenceComplete(done)); });
|
||||
@ -1351,7 +1351,7 @@ gulp.task('!bundle.ng.polyfills', ['clean'],
|
||||
|
||||
var JS_DEV_DEPS = [
|
||||
licenseWrap('node_modules/zone.js/LICENSE', true),
|
||||
'node_modules/zone.js/dist/zone-microtask.js',
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
licenseWrap('node_modules/reflect-metadata/LICENSE', true),
|
||||
'node_modules/reflect-metadata/Reflect.js'
|
||||
@ -1467,41 +1467,6 @@ gulp.task('!build/change_detect.dart', function(done) {
|
||||
});
|
||||
|
||||
// ------------
|
||||
// angular material testing rules
|
||||
gulp.task('build.css.material', function() {
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
var sass = require('gulp-sass');
|
||||
|
||||
return gulp.src('modules/*/src/**/*.scss')
|
||||
.pipe(sass())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest(CONFIG.dest.js.prod.es5))
|
||||
.pipe(gulp.dest(CONFIG.dest.js.dev.es5))
|
||||
.pipe(gulp.dest(CONFIG.dest.js.dart2js + '/examples/packages'));
|
||||
});
|
||||
|
||||
|
||||
gulp.task('build.js.material', function(done) {
|
||||
runSequence('build.js.dev', 'build.css.material', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('build.dart2js.material', function(done) {
|
||||
runSequence('build.dart', 'build.css.material', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('build.dart.material.css', function() {
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
var sass = require('gulp-sass');
|
||||
|
||||
return gulp.src('dist/dart/angular2_material/src/**/*.scss')
|
||||
.pipe(sass())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('dist/dart/angular2_material/lib/src'));
|
||||
});
|
||||
|
||||
gulp.task('build.dart.material', ['build/packages.dart'], function(done) {
|
||||
runSequence('build/packages.dart', 'build.dart.material.css', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('cleanup.builder', function() { return angularBuilder.cleanup(); });
|
||||
|
||||
|
@ -1,86 +0,0 @@
|
||||
// This module provides a customFileHandler for karma
|
||||
// that serves files with urls like /packages_<timestamp>/...
|
||||
// with maximum cache.
|
||||
// We are using these urls when we spawn isolates
|
||||
// so that the isolates don't reload files every time.
|
||||
|
||||
var common = require('karma/lib/middleware/common');
|
||||
var fs = require('fs');
|
||||
|
||||
var DART_EVAL_PATH_RE = /.*\/packages_\d+\/(.*)$/;
|
||||
|
||||
module.exports = createFactory;
|
||||
|
||||
function createFactory(proxyPaths) {
|
||||
return {
|
||||
'framework:dart-evalcache': ['factory', dartEvalCacheFactory]
|
||||
};
|
||||
|
||||
function dartEvalCacheFactory(emitter, logger, customFileHandlers) {
|
||||
var filesPromise = new common.PromiseContainer();
|
||||
emitter.on('file_list_modified', function(files) {
|
||||
filesPromise.set(Promise.resolve(files));
|
||||
});
|
||||
|
||||
var serveFile = common.createServeFile(fs);
|
||||
var log = logger.create('dart-evalcache');
|
||||
|
||||
customFileHandlers.push({
|
||||
urlRegex: DART_EVAL_PATH_RE,
|
||||
handler: handler
|
||||
});
|
||||
|
||||
// See source_files handler
|
||||
function handler(request, response, fa, fb, basePath) {
|
||||
return filesPromise.then(function(files) {
|
||||
try {
|
||||
var requestedFilePath = mapUrlToFile(request.url, proxyPaths, basePath, log);
|
||||
// TODO(vojta): change served to be a map rather then an array
|
||||
var file = findByPath(files.served, requestedFilePath);
|
||||
if (file) {
|
||||
serveFile(file.contentPath || file.path, response, function() {
|
||||
common.setHeavyCacheHeaders(response);
|
||||
}, file.content);
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end('Not found');
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(e.stack);
|
||||
response.writeHead(500);
|
||||
response.end('Error', e.stack);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mapUrlToFile(url, proxyPaths, basePath, log) {
|
||||
var originalUrl = url;
|
||||
url = url.indexOf('?') > -1 ? url.substring(0, url.indexOf('?')) : url;
|
||||
var match = DART_EVAL_PATH_RE.exec(url);
|
||||
var packagePath = match[1];
|
||||
var result = null;
|
||||
var lastProxyFromLength = 0;
|
||||
Object.keys(proxyPaths).forEach(function(proxyFrom) {
|
||||
if (startsWith(packagePath, proxyFrom) && proxyFrom.length > lastProxyFromLength) {
|
||||
lastProxyFromLength = proxyFrom.length;
|
||||
result = proxyPaths[proxyFrom] + packagePath.substring(proxyFrom.length);
|
||||
}
|
||||
});
|
||||
return basePath + '/' + result;
|
||||
}
|
||||
|
||||
function startsWith(string, subString) {
|
||||
return string.length >= subString.length && string.slice(0, subString.length) === subString;
|
||||
}
|
||||
|
||||
function findByPath(files, path) {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (files[i].path === path) {
|
||||
return files[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
var browserProvidersConf = require('./browser-providers.conf.js');
|
||||
|
||||
var packageSources = {
|
||||
// Dependencies installed with `pub install`.
|
||||
'unittest': 'packages/unittest',
|
||||
'guinness': 'packages/guinness',
|
||||
'matcher': 'packages/matcher',
|
||||
'stack_trace': 'packages/stack_trace',
|
||||
'collection': 'packages/collection',
|
||||
'path': 'packages/path',
|
||||
'observe': 'packages/observe',
|
||||
'quiver': 'packages/quiver',
|
||||
'intl': 'packages/intl',
|
||||
'smoke': 'packages/smoke',
|
||||
'logging': 'packages/logging',
|
||||
'utf': 'packages/utf',
|
||||
|
||||
// Local dependencies, transpiled from the source.
|
||||
'angular2': 'dist/dart/angular2/lib',
|
||||
'angular2/test/': 'dist/dart/angular2/test/',
|
||||
'http': 'dist/dart/http/lib',
|
||||
'angular2_material': 'dist/dart/angular2_material/lib',
|
||||
'benchpress': 'dist/dart/benchpress/lib',
|
||||
'examples': 'dist/dart/examples/lib'
|
||||
};
|
||||
|
||||
var proxyPaths = {};
|
||||
Object.keys(packageSources).map(function(packageName) {
|
||||
var filePath = packageSources[packageName];
|
||||
proxyPaths['/packages/'+packageName] = '/base/'+filePath;
|
||||
});
|
||||
|
||||
// Karma configuration
|
||||
// Generated on Thu Sep 25 2014 11:52:02 GMT-0700 (PDT)
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
frameworks: ['dart-unittest', 'dart-evalcache'],
|
||||
|
||||
files: [
|
||||
// Init and configure guiness.
|
||||
{pattern: 'test-init.dart', included: true},
|
||||
// Unit test files needs to be included.
|
||||
{pattern: 'dist/dart/**/*_spec.dart', included: true, watched: false},
|
||||
|
||||
// Karma-dart via the dart-unittest framework generates
|
||||
// `__adapter_unittest.dart` that imports these files.
|
||||
{pattern: 'dist/dart/**', included: false, watched: false},
|
||||
|
||||
// Dependencies, installed with `pub install`.
|
||||
{pattern: 'packages/**/*.dart', included: false, watched: false},
|
||||
|
||||
// Init and configure guiness.
|
||||
{pattern: 'test-main.dart', included: true},
|
||||
{pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false},
|
||||
],
|
||||
|
||||
exclude: [
|
||||
'dist/dart/**/packages/**',
|
||||
'modules/angular1_router/**'
|
||||
],
|
||||
|
||||
karmaDartImports: {
|
||||
guinness: 'package:guinness/guinness_html.dart'
|
||||
},
|
||||
|
||||
// Map packages to the correct urls where Karma serves them.
|
||||
proxies: proxyPaths,
|
||||
|
||||
customLaunchers: browserProvidersConf.customLaunchers,
|
||||
browsers: ['DartiumWithWebPlatform'],
|
||||
|
||||
port: 9877,
|
||||
|
||||
plugins: [
|
||||
require('karma-dart'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-sauce-launcher'),
|
||||
require('./karma-dart-evalcache')(packageSources)
|
||||
]
|
||||
});
|
||||
};
|
@ -17,8 +17,7 @@ module.exports = function(config) {
|
||||
// include Angular v1 for upgrade module testing
|
||||
'node_modules/angular/angular.min.js',
|
||||
|
||||
// zone-microtask must be included first as it contains a Promise monkey patch
|
||||
'node_modules/zone.js/dist/zone-microtask.js',
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
'node_modules/zone.js/dist/jasmine-patch.js',
|
||||
|
||||
|
33
modules/angular1_router/build.js
vendored
33
modules/angular1_router/build.js
vendored
@ -4,17 +4,20 @@ var fs = require('fs');
|
||||
var ts = require('typescript');
|
||||
|
||||
var files = [
|
||||
'lifecycle_annotations_impl.ts',
|
||||
'utils.ts',
|
||||
'url_parser.ts',
|
||||
'route_recognizer.ts',
|
||||
'route_config_impl.ts',
|
||||
'async_route_handler.ts',
|
||||
'sync_route_handler.ts',
|
||||
'component_recognizer.ts',
|
||||
'lifecycle/lifecycle_annotations_impl.ts',
|
||||
'lifecycle/route_lifecycle_reflector.ts',
|
||||
'route_config/route_config_impl.ts',
|
||||
'route_config/route_config_normalizer.ts',
|
||||
'rules/route_handlers/async_route_handler.ts',
|
||||
'rules/route_handlers/sync_route_handler.ts',
|
||||
'rules/rules.ts',
|
||||
'rules/rule_set.ts',
|
||||
'rules/route_paths/route_path.ts',
|
||||
'rules/route_paths/param_route_path.ts',
|
||||
'rules/route_paths/regex_route_path.ts',
|
||||
'instruction.ts',
|
||||
'path_recognizer.ts',
|
||||
'route_config_nomalizer.ts',
|
||||
'route_lifecycle_reflector.ts',
|
||||
'route_registry.ts',
|
||||
'router.ts'
|
||||
];
|
||||
@ -48,9 +51,10 @@ function main(modulesDirectory) {
|
||||
*/
|
||||
var IMPORT_RE = new RegExp("import \\{?([\\w\\n_, ]+)\\}? from '(.+)';?", 'g');
|
||||
var INJECT_RE = new RegExp("@Inject\\(ROUTER_PRIMARY_COMPONENT\\)", 'g');
|
||||
var IMJECTABLE_RE = new RegExp("@Injectable\\(\\)", 'g');
|
||||
var INJECTABLE_RE = new RegExp("@Injectable\\(\\)", 'g');
|
||||
var REQUIRE_RE = new RegExp("require\\('(.*?)'\\);", 'g');
|
||||
function transform(contents) {
|
||||
contents = contents.replace(INJECT_RE, '').replace(IMJECTABLE_RE, '');
|
||||
contents = contents.replace(INJECT_RE, '').replace(INJECTABLE_RE, '');
|
||||
contents = contents.replace(IMPORT_RE, function (match, imports, includePath) {
|
||||
//TODO: remove special-case
|
||||
if (isFacadeModule(includePath) || includePath === './router_outlet') {
|
||||
@ -58,10 +62,15 @@ function transform(contents) {
|
||||
}
|
||||
return match;
|
||||
});
|
||||
return ts.transpile(contents, {
|
||||
contents = ts.transpile(contents, {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.CommonJS
|
||||
});
|
||||
|
||||
// Rename require functions from transpiled imports
|
||||
contents = contents.replace(REQUIRE_RE, 'routerRequire(\'$1\');');
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
function isFacadeModule(modulePath) {
|
||||
|
@ -12,9 +12,9 @@
|
||||
<script src="../../dist/angular_1_router.js"></script>
|
||||
<script>
|
||||
angular.module('myApp', ['ngComponentRouter'])
|
||||
.controller('MyCtrl', ['$router', function ($router) {
|
||||
console.log($router);
|
||||
$router.navigateByUrl('/')
|
||||
.controller('MyCtrl', ['$rootRouter', function ($rootRouter) {
|
||||
console.log($rootRouter);
|
||||
$rootRouter.navigateByUrl('/')
|
||||
.then(console.log.bind(console, 'resolve'), console.log.bind(console, 'reject'));
|
||||
}]);
|
||||
</script>
|
||||
|
56
modules/angular1_router/src/module_template.js
vendored
56
modules/angular1_router/src/module_template.js
vendored
@ -4,9 +4,9 @@ angular.module('ngComponentRouter').
|
||||
// Because Angular 1 has no notion of a root component, we use an object with unique identity
|
||||
// to represent this. Can be overloaded with a component name
|
||||
value('$routerRootComponent', new Object()).
|
||||
factory('$router', ['$q', '$location', '$$directiveIntrospector', '$browser', '$rootScope', '$injector', '$routerRootComponent', routerFactory]);
|
||||
factory('$rootRouter', ['$q', '$location', '$browser', '$rootScope', '$injector', '$routerRootComponent', routerFactory]);
|
||||
|
||||
function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootScope, $injector, $routerRootComponent) {
|
||||
function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRootComponent) {
|
||||
|
||||
// When this file is processed, the line below is replaced with
|
||||
// the contents of `../lib/facades.es5`.
|
||||
@ -17,17 +17,30 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
|
||||
OpaqueToken: function () {},
|
||||
Inject: function () {}
|
||||
};
|
||||
var require = function () {return exports;};
|
||||
var routerRequire = function () {return exports;};
|
||||
|
||||
// When this file is processed, the line below is replaced with
|
||||
// the contents of the compiled TypeScript classes.
|
||||
//{{SHARED_CODE}}
|
||||
|
||||
function getComponentConstructor(name) {
|
||||
var serviceName = name + 'Directive';
|
||||
if ($injector.has(serviceName)) {
|
||||
var definitions = $injector.get(serviceName);
|
||||
if (definitions.length > 1) {
|
||||
throw new BaseException('too many directives named "' + name + '"');
|
||||
}
|
||||
return definitions[0].controller;
|
||||
} else {
|
||||
throw new BaseException('directive "' + name + '" is not registered');
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: this is a hack to replace the exiting implementation at run-time
|
||||
exports.getCanActivateHook = function (directiveName) {
|
||||
var factory = $$directiveIntrospector.getTypeByName(directiveName);
|
||||
return factory && factory.$canActivate && function (next, prev) {
|
||||
return $injector.invoke(factory.$canActivate, null, {
|
||||
var controller = getComponentConstructor(directiveName);
|
||||
return controller.$canActivate && function (next, prev) {
|
||||
return $injector.invoke(controller.$canActivate, null, {
|
||||
$nextInstruction: next,
|
||||
$prevInstruction: prev
|
||||
});
|
||||
@ -45,17 +58,32 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
|
||||
var RouteRegistry = exports.RouteRegistry;
|
||||
var RootRouter = exports.RootRouter;
|
||||
|
||||
// Override this method to actually get hold of the child routes
|
||||
RouteRegistry.prototype.configFromComponent = function (component) {
|
||||
var that = this;
|
||||
if (isString(component)) {
|
||||
// Don't read the annotations component a type more than once –
|
||||
// this prevents an infinite loop if a component routes recursively.
|
||||
if (this._rules.has(component)) {
|
||||
return;
|
||||
}
|
||||
var controller = getComponentConstructor(component);
|
||||
if (angular.isArray(controller.$routeConfig)) {
|
||||
controller.$routeConfig.forEach(function (config) {
|
||||
var loader = config.loader;
|
||||
if (isPresent(loader)) {
|
||||
config = angular.extend({}, config, { loader: () => $injector.invoke(loader) });
|
||||
}
|
||||
that.config(component, config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var registry = new RouteRegistry($routerRootComponent);
|
||||
var location = new Location();
|
||||
|
||||
$$directiveIntrospector(function (name, factory) {
|
||||
if (angular.isArray(factory.$routeConfig)) {
|
||||
factory.$routeConfig.forEach(function (config) {
|
||||
registry.config(name, config);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var router = new RootRouter(registry, location, $routerRootComponent);
|
||||
$rootScope.$watch(function () { return $location.url(); }, function (path) {
|
||||
if (router.lastNavigationAttempt !== path) {
|
||||
|
@ -1,51 +1,6 @@
|
||||
///<reference path="../typings/angularjs/angular.d.ts"/>
|
||||
|
||||
/*
|
||||
* decorates $compileProvider so that we have access to routing metadata
|
||||
*/
|
||||
function compilerProviderDecorator($compileProvider,
|
||||
$$directiveIntrospectorProvider: DirectiveIntrospectorProvider) {
|
||||
let directive = $compileProvider.directive;
|
||||
$compileProvider.directive = function(name: string, factory: Function) {
|
||||
$$directiveIntrospectorProvider.register(name, factory);
|
||||
return directive.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* private service that holds route mappings for each controller
|
||||
*/
|
||||
class DirectiveIntrospectorProvider {
|
||||
private directiveBuffer: any[] = [];
|
||||
private directiveFactoriesByName: {[name: string]: Function} = {};
|
||||
private onDirectiveRegistered: (name: string, factory: Function) => any = null;
|
||||
|
||||
register(name: string, factory: Function) {
|
||||
if (angular.isArray(factory)) {
|
||||
factory = factory[factory.length - 1];
|
||||
}
|
||||
this.directiveFactoriesByName[name] = factory;
|
||||
if (this.onDirectiveRegistered) {
|
||||
this.onDirectiveRegistered(name, factory);
|
||||
} else {
|
||||
this.directiveBuffer.push({name: name, factory: factory});
|
||||
}
|
||||
}
|
||||
|
||||
$get() {
|
||||
let fn: any = newOnControllerRegistered => {
|
||||
this.onDirectiveRegistered = newOnControllerRegistered;
|
||||
while (this.directiveBuffer.length > 0) {
|
||||
let directive = this.directiveBuffer.pop();
|
||||
this.onDirectiveRegistered(directive.name, directive.factory);
|
||||
}
|
||||
};
|
||||
|
||||
fn.getTypeByName = name => this.directiveFactoriesByName[name];
|
||||
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ngOutlet
|
||||
@ -61,8 +16,8 @@ class DirectiveIntrospectorProvider {
|
||||
*
|
||||
* The value for the `ngOutlet` attribute is optional.
|
||||
*/
|
||||
function ngOutletDirective($animate, $q: ng.IQService, $router) {
|
||||
let rootRouter = $router;
|
||||
function ngOutletDirective($animate, $q: ng.IQService, $rootRouter) {
|
||||
let rootRouter = $rootRouter;
|
||||
|
||||
return {
|
||||
restrict: 'AE',
|
||||
@ -145,7 +100,7 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) {
|
||||
}
|
||||
|
||||
activate(instruction) {
|
||||
let previousInstruction = this.currentInstruction;
|
||||
this.previousInstruction = this.currentInstruction;
|
||||
this.currentInstruction = instruction;
|
||||
|
||||
let componentName = this.controller.$$componentName = instruction.componentType;
|
||||
@ -154,13 +109,14 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) {
|
||||
throw new Error('Component is not a string for ' + instruction.urlPath);
|
||||
}
|
||||
|
||||
this.controller.$$routeParams = instruction.params;
|
||||
this.controller.$$template =
|
||||
'<' + dashCase(componentName) + ' router="$$router"></' + dashCase(componentName) + '>';
|
||||
this.controller.$$template = '<' + dashCase(componentName) + ' $router="::$$router"></' +
|
||||
dashCase(componentName) + '>';
|
||||
this.controller.$$router = this.router.childRouter(instruction.componentType);
|
||||
this.controller.$$outlet = this;
|
||||
|
||||
let newScope = scope.$new();
|
||||
newScope.$$router = this.controller.$$router;
|
||||
this.deferredActivation = $q.defer();
|
||||
|
||||
let clone = $transclude(newScope, clone => {
|
||||
$animate.enter(clone, null, this.currentElement || element);
|
||||
@ -169,15 +125,7 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) {
|
||||
|
||||
this.currentElement = clone;
|
||||
this.currentScope = newScope;
|
||||
|
||||
// TODO: prefer the other directive retrieving the controller
|
||||
// by debug mode
|
||||
this.currentController = this.currentElement.children().eq(0).controller(componentName);
|
||||
|
||||
if (this.currentController && this.currentController.$routerOnActivate) {
|
||||
return this.currentController.$routerOnActivate(instruction, previousInstruction);
|
||||
}
|
||||
return $q.when();
|
||||
return this.deferredActivation.promise;
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,21 +148,32 @@ function ngOutletFillContentDirective($compile) {
|
||||
link: (scope, element, attrs, ctrl) => {
|
||||
let template = ctrl.$$template;
|
||||
element.html(template);
|
||||
let link = $compile(element.contents());
|
||||
link(scope);
|
||||
|
||||
// TODO: move to primary directive
|
||||
let componentInstance = scope[ctrl.$$componentName];
|
||||
if (componentInstance) {
|
||||
ctrl.$$currentComponent = componentInstance;
|
||||
|
||||
componentInstance.$router = ctrl.$$router;
|
||||
componentInstance.$routeParams = ctrl.$$routeParams;
|
||||
}
|
||||
$compile(element.contents())(scope);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
function routerTriggerDirective($q) {
|
||||
return {
|
||||
require: '^ngOutlet',
|
||||
priority: -1000,
|
||||
link: function(scope, element, attr, ngOutletCtrl) {
|
||||
var promise = $q.when();
|
||||
var outlet = ngOutletCtrl.$$outlet;
|
||||
var currentComponent = outlet.currentController =
|
||||
element.controller(ngOutletCtrl.$$componentName);
|
||||
if (currentComponent.$routerOnActivate) {
|
||||
promise = $q.when(currentComponent.$routerOnActivate(outlet.currentInstruction,
|
||||
outlet.previousInstruction));
|
||||
}
|
||||
promise.then(outlet.deferredActivation.resolve, outlet.deferredActivation.reject);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @name ngLink
|
||||
* @description
|
||||
@ -227,8 +186,8 @@ function ngOutletFillContentDirective($compile) {
|
||||
*
|
||||
* ```js
|
||||
* angular.module('myApp', ['ngComponentRouter'])
|
||||
* .controller('AppController', ['$router', function($router) {
|
||||
* $router.config({ path: '/user/:id', component: 'user' });
|
||||
* .controller('AppController', ['$rootRouter', function($rootRouter) {
|
||||
* $rootRouter.config({ path: '/user/:id', component: 'user' });
|
||||
* this.user = { name: 'Brian', id: 123 };
|
||||
* });
|
||||
* ```
|
||||
@ -239,13 +198,11 @@ function ngOutletFillContentDirective($compile) {
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
function ngLinkDirective($router, $parse) {
|
||||
let rootRouter = $router;
|
||||
|
||||
function ngLinkDirective($rootRouter, $parse) {
|
||||
return {require: '?^^ngOutlet', restrict: 'A', link: ngLinkDirectiveLinkFn};
|
||||
|
||||
function ngLinkDirectiveLinkFn(scope, element, attrs, ctrl) {
|
||||
let router = (ctrl && ctrl.$$router) || rootRouter;
|
||||
let router = (ctrl && ctrl.$$router) || $rootRouter;
|
||||
if (!router) {
|
||||
return;
|
||||
}
|
||||
@ -255,6 +212,16 @@ function ngLinkDirective($router, $parse) {
|
||||
|
||||
function getLink(params) {
|
||||
instruction = router.generate(params);
|
||||
|
||||
scope.$watch(function() { return router.isRouteActive(instruction); },
|
||||
function(active) {
|
||||
if (active) {
|
||||
element.addClass('ng-link-active');
|
||||
} else {
|
||||
element.removeClass('ng-link-active');
|
||||
}
|
||||
});
|
||||
|
||||
return './' + angular.stringifyInstruction(instruction);
|
||||
}
|
||||
|
||||
@ -273,7 +240,7 @@ function ngLinkDirective($router, $parse) {
|
||||
return;
|
||||
}
|
||||
|
||||
$router.navigateByInstruction(instruction);
|
||||
$rootRouter.navigateByInstruction(instruction);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
@ -287,13 +254,7 @@ function dashCase(str: string): string {
|
||||
* A module for adding new a routing system Angular 1.
|
||||
*/
|
||||
angular.module('ngComponentRouter', [])
|
||||
.directive('ngOutlet', ngOutletDirective)
|
||||
.directive('ngOutlet', ngOutletFillContentDirective)
|
||||
.directive('ngLink', ngLinkDirective);
|
||||
|
||||
/*
|
||||
* A module for inspecting controller constructors
|
||||
*/
|
||||
angular.module('ng')
|
||||
.provider('$$directiveIntrospector', DirectiveIntrospectorProvider)
|
||||
.config(compilerProviderDecorator);
|
||||
.directive('ngOutlet', ['$animate', '$q', '$rootRouter', ngOutletDirective])
|
||||
.directive('ngOutlet', ['$compile', ngOutletFillContentDirective])
|
||||
.directive('ngLink', ['$rootRouter', '$parse', ngLinkDirective])
|
||||
.directive('$router', ['$q', routerTriggerDirective]);
|
||||
|
52
modules/angular1_router/src/ng_route_shim.js
vendored
52
modules/angular1_router/src/ng_route_shim.js
vendored
@ -24,12 +24,12 @@
|
||||
.directive('a', anchorLinkDirective)
|
||||
|
||||
// Connects the legacy $routeProvider config shim to Component Router's config.
|
||||
.run(['$route', '$router', function ($route, $router) {
|
||||
.run(['$route', '$rootRouter', function ($route, $rootRouter) {
|
||||
$route.$$subscribe(function (routeDefinition) {
|
||||
if (!angular.isArray(routeDefinition)) {
|
||||
routeDefinition = [routeDefinition];
|
||||
}
|
||||
$router.config(routeDefinition);
|
||||
$rootRouter.config(routeDefinition);
|
||||
});
|
||||
}]);
|
||||
|
||||
@ -116,53 +116,41 @@
|
||||
console.warn('Route for "' + path + '" should use "controllerAs".');
|
||||
}
|
||||
|
||||
var directiveName = routeObjToRouteName(routeCopy, path);
|
||||
var componentName = routeObjToRouteName(routeCopy, path);
|
||||
|
||||
if (!directiveName) {
|
||||
if (!componentName) {
|
||||
throw new Error('Could not determine a name for route "' + path + '".');
|
||||
}
|
||||
|
||||
routeDefinition.component = directiveName;
|
||||
routeDefinition.name = route.name || upperCase(directiveName);
|
||||
routeDefinition.component = componentName;
|
||||
routeDefinition.name = route.name || upperCase(componentName);
|
||||
|
||||
var directiveController = routeCopy.controller;
|
||||
|
||||
var directiveDefinition = {
|
||||
scope: false,
|
||||
var componentDefinition = {
|
||||
controller: directiveController,
|
||||
controllerAs: routeCopy.controllerAs,
|
||||
templateUrl: routeCopy.templateUrl,
|
||||
template: routeCopy.template
|
||||
};
|
||||
controllerAs: routeCopy.controllerAs
|
||||
|
||||
var directiveFactory = function () {
|
||||
return directiveDefinition;
|
||||
};
|
||||
if (routeCopy.templateUrl) componentDefinition.templateUrl = routeCopy.templateUrl;
|
||||
if (routeCopy.template) componentDefinition.template = routeCopy.template;
|
||||
|
||||
|
||||
// if we have route resolve options, prepare a wrapper controller
|
||||
if (directiveController && routeCopy.resolve) {
|
||||
var originalController = directiveController;
|
||||
var resolvedLocals = {};
|
||||
|
||||
directiveDefinition.controller = ['$injector', '$scope', function ($injector, $scope) {
|
||||
componentDefinition.controller = ['$injector', '$scope', function ($injector, $scope) {
|
||||
var locals = angular.extend({
|
||||
$scope: $scope
|
||||
}, resolvedLocals);
|
||||
|
||||
var ctrl = $injector.instantiate(originalController, locals);
|
||||
|
||||
if (routeCopy.controllerAs) {
|
||||
locals.$scope[routeCopy.controllerAs] = ctrl;
|
||||
}
|
||||
|
||||
return ctrl;
|
||||
return $injector.instantiate(originalController, locals);
|
||||
}];
|
||||
|
||||
// we take care of controllerAs in the directive controller wrapper
|
||||
delete directiveDefinition.controllerAs;
|
||||
|
||||
// we resolve the locals in a canActivate block
|
||||
directiveFactory.$canActivate = function() {
|
||||
componentDefinition.controller.$canActivate = function() {
|
||||
var locals = angular.extend({}, routeCopy.resolve);
|
||||
|
||||
angular.forEach(locals, function(value, key) {
|
||||
@ -179,7 +167,7 @@
|
||||
}
|
||||
|
||||
// register the dynamically created directive
|
||||
$compileProvider.directive(directiveName, directiveFactory);
|
||||
$compileProvider.component(componentName, componentDefinition);
|
||||
}
|
||||
if (subscriptionFn) {
|
||||
subscriptionFn(routeDefinition);
|
||||
@ -253,12 +241,12 @@
|
||||
|
||||
}
|
||||
|
||||
function $routeParamsFactory($router, $rootScope) {
|
||||
function $routeParamsFactory($rootRouter, $rootScope) {
|
||||
// the identity of this object cannot change
|
||||
var paramsObj = {};
|
||||
|
||||
$rootScope.$on('$routeChangeSuccess', function () {
|
||||
var newParams = $router._currentInstruction && $router._currentInstruction.component.params;
|
||||
var newParams = $rootRouter.currentInstruction && $rootRouter.currentInstruction.component.params;
|
||||
|
||||
angular.forEach(paramsObj, function (val, name) {
|
||||
delete paramsObj[name];
|
||||
@ -274,7 +262,7 @@
|
||||
/**
|
||||
* Allows normal anchor links to kick off routing.
|
||||
*/
|
||||
function anchorLinkDirective($router) {
|
||||
function anchorLinkDirective($rootRouter) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function (scope, element) {
|
||||
@ -293,8 +281,8 @@
|
||||
}
|
||||
|
||||
var href = element.attr(hrefAttrName);
|
||||
if (href && $router.recognize(href)) {
|
||||
$router.navigateByUrl(href);
|
||||
if (href && $rootRouter.recognize(href)) {
|
||||
$rootRouter.navigateByUrl(href);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
@ -1,38 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
describe('$$directiveIntrospector', function () {
|
||||
|
||||
var $compileProvider;
|
||||
|
||||
beforeEach(function() {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function(_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the introspector function whenever a directive factory is registered', inject(function ($$directiveIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$directiveIntrospector(spy);
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', myDir);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('myDir', myDir);
|
||||
}));
|
||||
|
||||
it('should call the introspector function whenever a directive factory is registered with array annotations', inject(function ($$directiveIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$directiveIntrospector(spy);
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', ['foo', myDir]);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('myDir', myDir);
|
||||
}));
|
||||
|
||||
it('should retrieve a factory based on directive name', inject(function ($$directiveIntrospector) {
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', ['foo', myDir]);
|
||||
expect($$directiveIntrospector.getTypeByName('myDir')).toBe(myDir);
|
||||
}));
|
||||
});
|
@ -5,7 +5,7 @@ describe('ngOutlet animations', function () {
|
||||
$animate,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$rootRouter,
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -17,15 +17,18 @@ describe('ngOutlet animations', function () {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$animate_, _$compile_, _$rootScope_, _$router_) {
|
||||
inject(function (_$animate_, _$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$animate = _$animate_;
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
|
||||
registerComponent('userCmp', {
|
||||
template: '<div>hello {{userCmp.$routeParams.name}}</div>'
|
||||
template: '<div>hello {{userCmp.$routeParams.name}}</div>',
|
||||
$routerOnActivate: function(next) {
|
||||
this.$routeParams = next.params;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -38,11 +41,11 @@ describe('ngOutlet animations', function () {
|
||||
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('hello brian');
|
||||
|
||||
@ -51,7 +54,7 @@ describe('ngOutlet animations', function () {
|
||||
expect(item.event).toBe('enter');
|
||||
|
||||
// navigate to pete
|
||||
$router.navigateByUrl('/user/pete');
|
||||
$rootRouter.navigateByUrl('/user/pete');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('hello pete');
|
||||
|
||||
|
@ -4,7 +4,7 @@ describe('Navigation lifecycle', function () {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$rootRouter,
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -14,10 +14,10 @@ describe('Navigation lifecycle', function () {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
|
||||
registerComponent('oneCmp', {
|
||||
@ -38,12 +38,12 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
@ -56,12 +56,12 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('userCmp'), undefined);
|
||||
@ -75,15 +75,15 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'oneCmp' },
|
||||
{ path: '/post/:id', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/post/123');
|
||||
$rootRouter.navigateByUrl('/post/123');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('activateCmp'),
|
||||
instructionFor('oneCmp'));
|
||||
@ -98,12 +98,12 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user', component: 'userCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user');
|
||||
$rootRouter.navigateByUrl('/user');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(injectedScope).toBeDefined();
|
||||
@ -116,15 +116,15 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnDeactivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'deactivateCmp' },
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/b');
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
@ -136,15 +136,15 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnDeactivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'deactivateCmp' },
|
||||
{ path: '/post/:id', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/post/123');
|
||||
$rootRouter.navigateByUrl('/post/123');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('oneCmp'),
|
||||
instructionFor('deactivateCmp'));
|
||||
@ -166,15 +166,15 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'deactivateCmp' },
|
||||
{ path: '/b', component: 'activateCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/b');
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(log).toEqual(['deactivate', 'activate']);
|
||||
@ -203,19 +203,19 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/on-reuse/:number/...', component: 'reuseCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/on-reuse/1/a');
|
||||
$rootRouter.navigateByUrl('/on-reuse/1/a');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual([]);
|
||||
expect(cmpInstanceCount).toBe(1);
|
||||
expect(elt.text()).toBe('outer { reuse {one} }');
|
||||
|
||||
$router.navigateByUrl('/on-reuse/2/b');
|
||||
$rootRouter.navigateByUrl('/on-reuse/2/b');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(['reuse: on-reuse/1 -> on-reuse/2']);
|
||||
expect(cmpInstanceCount).toBe(1);
|
||||
@ -245,19 +245,19 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/never-reuse/:number/...', component: 'reuseCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/never-reuse/1/a');
|
||||
$rootRouter.navigateByUrl('/never-reuse/1/a');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual([]);
|
||||
expect(cmpInstanceCount).toBe(1);
|
||||
expect(elt.text()).toBe('outer { reuse {one} }');
|
||||
|
||||
$router.navigateByUrl('/never-reuse/2/b');
|
||||
$rootRouter.navigateByUrl('/never-reuse/2/b');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual([]);
|
||||
expect(cmpInstanceCount).toBe(2);
|
||||
@ -274,12 +274,12 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
@ -296,12 +296,12 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: activateSpy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(canActivateSpy).toHaveBeenCalled();
|
||||
@ -320,12 +320,12 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
@ -341,17 +341,17 @@ describe('Navigation lifecycle', function () {
|
||||
|
||||
spy.$inject = ['$nextInstruction', '$http'];
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
var args = spy.calls.mostRecent().args;
|
||||
expect(args[0].params).toEqual({name: 'brian'});
|
||||
expect(args[0].params).toEqual(jasmine.objectContaining({name: 'brian'}));
|
||||
expect(args[1]).toBe($http);
|
||||
}));
|
||||
|
||||
@ -364,17 +364,17 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' },
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('outer { hi }');
|
||||
|
||||
$router.navigateByUrl('/b');
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('outer { hi }');
|
||||
});
|
||||
@ -388,17 +388,17 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' },
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('outer { hi }');
|
||||
|
||||
$router.navigateByUrl('/b');
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('outer { one }');
|
||||
});
|
||||
@ -414,12 +414,12 @@ describe('Navigation lifecycle', function () {
|
||||
$routerOnActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
@ -433,15 +433,15 @@ describe('Navigation lifecycle', function () {
|
||||
$routerCanDeactivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'deactivateCmp' },
|
||||
{ path: '/post/:id', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/post/123');
|
||||
$rootRouter.navigateByUrl('/post/123');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('oneCmp'),
|
||||
instructionFor('deactivateCmp'));
|
||||
@ -466,10 +466,10 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
|
||||
if (options.$canActivate) {
|
||||
factory.$canActivate = options.$canActivate;
|
||||
controller.$canActivate = options.$canActivate;
|
||||
}
|
||||
if (options.$routeConfig) {
|
||||
factory.$routeConfig = options.$routeConfig;
|
||||
controller.$routeConfig = options.$routeConfig;
|
||||
}
|
||||
|
||||
$compileProvider.directive(name, factory);
|
||||
|
@ -5,7 +5,7 @@ describe('navigation', function () {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$rootRouter,
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -15,14 +15,17 @@ describe('navigation', function () {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
|
||||
registerDirective('userCmp', {
|
||||
template: '<div>hello {{userCmp.$routeParams.name}}</div>'
|
||||
template: '<div>hello {{userCmp.$routeParams.name}}</div>',
|
||||
$routerOnActivate: function(next) {
|
||||
this.$routeParams = next.params;
|
||||
}
|
||||
});
|
||||
registerDirective('oneCmp', {
|
||||
template: '<div>{{oneCmp.number}}</div>',
|
||||
@ -49,11 +52,11 @@ describe('navigation', function () {
|
||||
it('should work in a simple case', function () {
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('one');
|
||||
@ -63,11 +66,11 @@ describe('navigation', function () {
|
||||
it('should work with components created by the `mod.component()` helper', function () {
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'threeCmp' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('three');
|
||||
@ -75,16 +78,16 @@ describe('navigation', function () {
|
||||
|
||||
|
||||
it('should navigate between components with different parameters', function () {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
]);
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('hello brian');
|
||||
|
||||
$router.navigateByUrl('/user/igor');
|
||||
$rootRouter.navigateByUrl('/user/igor');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('hello igor');
|
||||
});
|
||||
@ -103,17 +106,17 @@ describe('navigation', function () {
|
||||
controller: ParentController
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/parent/...', component: 'parentCmp' }
|
||||
]);
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/parent/user/brian');
|
||||
$rootRouter.navigateByUrl('/parent/user/brian');
|
||||
$rootScope.$digest();
|
||||
expect(instanceCount).toBe(1);
|
||||
expect(elt.text()).toBe('parent { hello brian }');
|
||||
|
||||
$router.navigateByUrl('/parent/user/igor');
|
||||
$rootRouter.navigateByUrl('/parent/user/igor');
|
||||
$rootScope.$digest();
|
||||
expect(instanceCount).toBe(1);
|
||||
expect(elt.text()).toBe('parent { hello igor }');
|
||||
@ -128,12 +131,12 @@ describe('navigation', function () {
|
||||
]
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a/...', component: 'childCmp' }
|
||||
]);
|
||||
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||
|
||||
$router.navigateByUrl('/a/b');
|
||||
$rootRouter.navigateByUrl('/a/b');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('outer { inner { one } }');
|
||||
@ -147,12 +150,12 @@ describe('navigation', function () {
|
||||
]
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/...', component: 'childCmp' }
|
||||
]);
|
||||
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||
|
||||
$router.navigateByUrl('/b');
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('outer { inner { one } }');
|
||||
@ -168,26 +171,26 @@ describe('navigation', function () {
|
||||
{ path: '/end', component: 'oneCmp' }
|
||||
]});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/recur', component: 'recurCmp' },
|
||||
{ path: '/', component: 'oneCmp' }
|
||||
]);
|
||||
|
||||
compile('<div>root { <div ng-outlet></div> }</div>');
|
||||
$router.navigateByUrl('/recur/recur/end');
|
||||
$rootRouter.navigateByUrl('/recur/recur/end');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('root { one }');
|
||||
});
|
||||
|
||||
|
||||
it('should change location path', inject(function ($location) {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user', component: 'userCmp' }
|
||||
]);
|
||||
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user');
|
||||
$rootRouter.navigateByUrl('/user');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/user');
|
||||
@ -195,13 +198,13 @@ describe('navigation', function () {
|
||||
|
||||
|
||||
it('should pass through query terms to the location', inject(function ($location) {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/user', component: 'userCmp' }
|
||||
]);
|
||||
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user?x=y');
|
||||
$rootRouter.navigateByUrl('/user?x=y');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/user');
|
||||
@ -212,12 +215,12 @@ describe('navigation', function () {
|
||||
it('should change location to the canonical route', inject(function ($location) {
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/', redirectTo: ['/User'] },
|
||||
{ path: '/user', component: 'userCmp', name: 'User' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/user');
|
||||
@ -233,7 +236,7 @@ describe('navigation', function () {
|
||||
]
|
||||
});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/old-parent/old-child', redirectTo: ['/NewParent', 'NewChild'] },
|
||||
{ path: '/old-parent/old-child-two', redirectTo: ['/NewParent', 'NewChildTwo'] },
|
||||
{ path: '/new-parent/...', component: 'childRouter', name: 'NewParent' }
|
||||
@ -241,13 +244,13 @@ describe('navigation', function () {
|
||||
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/old-parent/old-child');
|
||||
$rootRouter.navigateByUrl('/old-parent/old-child');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/new-parent/new-child');
|
||||
expect(elt.text()).toBe('inner { one }');
|
||||
|
||||
$router.navigateByUrl('/old-parent/old-child-two');
|
||||
$rootRouter.navigateByUrl('/old-parent/old-child-two');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($location.path()).toBe('/new-parent/new-child-two');
|
||||
@ -256,7 +259,7 @@ describe('navigation', function () {
|
||||
|
||||
|
||||
it('should navigate when the location path changes', inject(function ($location) {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/one', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
@ -269,7 +272,7 @@ describe('navigation', function () {
|
||||
|
||||
|
||||
it('should navigate when the location query changes', inject(function ($location) {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/get/params', component: 'getParams' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
@ -281,7 +284,7 @@ describe('navigation', function () {
|
||||
}));
|
||||
|
||||
|
||||
it('should expose a "navigating" property on $router', inject(function ($q) {
|
||||
it('should expose a "navigating" property on $rootRouter', inject(function ($q) {
|
||||
var defer;
|
||||
registerDirective('pendingActivate', {
|
||||
$canActivate: function () {
|
||||
@ -289,28 +292,29 @@ describe('navigation', function () {
|
||||
return defer.promise;
|
||||
}
|
||||
});
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/pending-activate', component: 'pendingActivate' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/pending-activate');
|
||||
$rootRouter.navigateByUrl('/pending-activate');
|
||||
$rootScope.$digest();
|
||||
expect($router.navigating).toBe(true);
|
||||
expect($rootRouter.navigating).toBe(true);
|
||||
defer.resolve();
|
||||
$rootScope.$digest();
|
||||
expect($router.navigating).toBe(false);
|
||||
expect($rootRouter.navigating).toBe(false);
|
||||
}));
|
||||
|
||||
function registerDirective(name, options) {
|
||||
var controller = getController(options);
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: getController(options)
|
||||
controller: controller
|
||||
};
|
||||
}
|
||||
applyStaticProperties(factory, options);
|
||||
applyStaticProperties(controller, options);
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
@ -320,7 +324,7 @@ describe('navigation', function () {
|
||||
template: options.template || '',
|
||||
controller: getController(options),
|
||||
}
|
||||
applyStaticProperties(definition, options);
|
||||
applyStaticProperties(definition.controller, options);
|
||||
$compileProvider.component(name, definition);
|
||||
}
|
||||
|
||||
|
@ -2,30 +2,14 @@
|
||||
|
||||
describe('router', function () {
|
||||
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$compileProvider;
|
||||
|
||||
var elt, testMod;
|
||||
beforeEach(function () {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function($provide) {
|
||||
$provide.value('$routerRootComponent', 'app');
|
||||
});
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
});
|
||||
testMod = angular.module('testMod', ['ngComponentRouter'])
|
||||
.value('$routerRootComponent', 'app');
|
||||
});
|
||||
|
||||
it('should work with a provided root component', inject(function($location) {
|
||||
it('should work with a provided root component', function() {
|
||||
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home'
|
||||
});
|
||||
@ -37,20 +21,23 @@ describe('router', function () {
|
||||
]
|
||||
});
|
||||
|
||||
compile('<app></app>');
|
||||
module('testMod');
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home');
|
||||
}));
|
||||
inject(function($location, $rootScope) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home');
|
||||
});
|
||||
});
|
||||
|
||||
it('should bind the component to the current router', inject(function($location) {
|
||||
it('should bind the component to the current router', function() {
|
||||
var router;
|
||||
registerComponent('homeCmp', {
|
||||
bindings: { router: '=' },
|
||||
bindings: { $router: '=' },
|
||||
controller: function($scope, $element) {
|
||||
this.$routerOnActivate = function() {
|
||||
router = this.router;
|
||||
router = this.$router;
|
||||
};
|
||||
},
|
||||
template: 'Home'
|
||||
@ -63,64 +50,146 @@ describe('router', function () {
|
||||
]
|
||||
});
|
||||
|
||||
compile('<app></app>');
|
||||
module('testMod');
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect(homeElement.isolateScope().$ctrl.router).toBeDefined();
|
||||
expect(router).toBeDefined();
|
||||
}));
|
||||
inject(function($location, $rootScope) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect(homeElement.isolateScope().$ctrl.$router).toBeDefined();
|
||||
expect(router).toBeDefined();
|
||||
})
|
||||
});
|
||||
|
||||
it('should work when an async route is provided route data', inject(function($location, $q) {
|
||||
registerDirective('homeCmp', {
|
||||
template: 'Home ({{homeCmp.isAdmin}})',
|
||||
it('should work when an async route is provided route data', function() {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home ({{$ctrl.isAdmin}})',
|
||||
$routerOnActivate: function(next, prev) {
|
||||
this.isAdmin = next.routeData.data.isAdmin;
|
||||
}
|
||||
});
|
||||
|
||||
registerDirective('app', {
|
||||
registerComponent('app', {
|
||||
template: '<div ng-outlet></div>',
|
||||
$routeConfig: [
|
||||
{ path: '/', loader: function() { return $q.when('homeCmp'); }, data: { isAdmin: true } }
|
||||
{ path: '/', loader: function($q) { return $q.when('homeCmp'); }, data: { isAdmin: true } }
|
||||
]
|
||||
});
|
||||
|
||||
compile('<app></app>');
|
||||
module('testMod');
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home (true)');
|
||||
}));
|
||||
inject(function($location, $rootScope) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home (true)');
|
||||
});
|
||||
});
|
||||
|
||||
function registerDirective(name, options) {
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: getController(options)
|
||||
};
|
||||
}
|
||||
applyStaticProperties(factory, options);
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
it('should work with a templateUrl component', function() {
|
||||
|
||||
var $routerOnActivate = jasmine.createSpy('$routerOnActivate');
|
||||
|
||||
registerComponent('homeCmp', {
|
||||
templateUrl: 'homeCmp.html',
|
||||
$routerOnActivate: $routerOnActivate
|
||||
});
|
||||
|
||||
registerComponent('app', {
|
||||
template: '<div ng-outlet></div>',
|
||||
$routeConfig: [
|
||||
{ path: '/', component: 'homeCmp' }
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
|
||||
inject(function($location, $rootScope, $httpBackend) {
|
||||
|
||||
$httpBackend.expectGET('homeCmp.html').respond('Home');
|
||||
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect($routerOnActivate).toHaveBeenCalled();
|
||||
})
|
||||
});
|
||||
|
||||
it('should provide the current instruction', function() {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home ({{homeCmp.isAdmin}})'
|
||||
});
|
||||
|
||||
registerComponent('app', {
|
||||
template: '<div ng-outlet></div>',
|
||||
$routeConfig: [
|
||||
{ path: '/', component: 'homeCmp', name: 'Home' }
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
|
||||
inject(function($rootScope, $rootRouter, $location) {
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var instruction = $rootRouter.generate(['/Home']);
|
||||
expect($rootRouter.currentInstruction).toEqual(instruction);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide the root level router', function() {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home ({{homeCmp.isAdmin}})',
|
||||
bindings: {
|
||||
$router: '<'
|
||||
}
|
||||
});
|
||||
|
||||
registerComponent('app', {
|
||||
template: '<div ng-outlet></div>',
|
||||
$routeConfig: [
|
||||
{ path: '/', component: 'homeCmp', name: 'Home' }
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
|
||||
inject(function($rootScope, $rootRouter, $location) {
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.isolateScope().$ctrl.$router.root).toEqual($rootRouter);
|
||||
});
|
||||
});
|
||||
|
||||
function registerComponent(name, options) {
|
||||
|
||||
var definition = {
|
||||
bindings: options.bindings,
|
||||
template: options.template || '',
|
||||
controller: getController(options),
|
||||
}
|
||||
applyStaticProperties(definition, options);
|
||||
$compileProvider.component(name, definition);
|
||||
};
|
||||
if (options.template) definition.template = options.template;
|
||||
if (options.templateUrl) definition.templateUrl = options.templateUrl;
|
||||
|
||||
applyStaticProperties(definition.controller, options);
|
||||
angular.module('testMod').component(name, definition);
|
||||
}
|
||||
|
||||
function compile(template) {
|
||||
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
function compileApp() {
|
||||
inject(function($compile, $rootScope) {
|
||||
elt = $compile('<div><app></app</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
return elt;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ describe('ngRoute shim', function () {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$rootRouter,
|
||||
$compileProvider,
|
||||
$routeProvider;
|
||||
|
||||
@ -18,10 +18,10 @@ describe('ngRoute shim', function () {
|
||||
$routeProvider = _$routeProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('one');
|
||||
@ -55,7 +55,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('root {<ng-outlet></ng-outlet>}');
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('root {one}');
|
||||
}));
|
||||
@ -76,7 +76,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('value: 42');
|
||||
@ -94,11 +94,11 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootRouter.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('hello brian');
|
||||
|
||||
$router.navigateByUrl('/user/igor');
|
||||
$rootRouter.navigateByUrl('/user/igor');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('hello igor');
|
||||
});
|
||||
@ -115,7 +115,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/home/foo/bar/123');
|
||||
$rootRouter.navigateByUrl('/home/foo/bar/123');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('rest: foo/bar/123');
|
||||
});
|
||||
@ -129,7 +129,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('root {<ng-outlet></ng-outlet>}');
|
||||
|
||||
$router.navigateByUrl('/home/test');
|
||||
$rootRouter.navigateByUrl('/home/test');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('root {}');
|
||||
expect(console.warn)
|
||||
@ -150,7 +150,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('root {<ng-outlet></ng-outlet>}');
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('root {welcome home!}');
|
||||
expect($location.path()).toBe('/home');
|
||||
@ -169,7 +169,7 @@ describe('ngRoute shim', function () {
|
||||
|
||||
compile('root {<ng-outlet></ng-outlet>}');
|
||||
|
||||
$router.navigateByUrl('/somewhere');
|
||||
$rootRouter.navigateByUrl('/somewhere');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('root {welcome home!}');
|
||||
expect($location.path()).toBe('/home');
|
||||
|
44
modules/angular1_router/test/ng_link_spec.js
vendored
44
modules/angular1_router/test/ng_link_spec.js
vendored
@ -5,7 +5,7 @@ describe('ngLink', function () {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$rootRouter,
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
@ -15,10 +15,10 @@ describe('ngLink', function () {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
|
||||
registerComponent('userCmp', '<div>hello {{userCmp.$routeParams.name}}</div>', function () {});
|
||||
@ -28,26 +28,26 @@ describe('ngLink', function () {
|
||||
|
||||
|
||||
it('should allow linking from the parent to the child', function () {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b');
|
||||
});
|
||||
|
||||
it('should allow linking from the child and the parent', function () {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/b');
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b');
|
||||
@ -57,13 +57,13 @@ describe('ngLink', function () {
|
||||
it('should allow params in routerLink directive', function () {
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'two'});
|
||||
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b/lol');
|
||||
@ -72,13 +72,13 @@ describe('ngLink', function () {
|
||||
|
||||
it('should update the href of links with bound params', function () {
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: twoLinkCmp.number}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'param'});
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b/param');
|
||||
@ -86,7 +86,7 @@ describe('ngLink', function () {
|
||||
|
||||
|
||||
it('should navigate on left-mouse click when a link url matches a route', function () {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
@ -104,8 +104,8 @@ describe('ngLink', function () {
|
||||
});
|
||||
|
||||
|
||||
it('should not navigate on non-left mouse click when a link url matches a route', inject(function ($router) {
|
||||
$router.config([
|
||||
it('should not navigate on non-left mouse click when a link url matches a route', inject(function ($rootRouter) {
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
@ -122,7 +122,7 @@ describe('ngLink', function () {
|
||||
|
||||
// See https://github.com/angular/router/issues/206
|
||||
it('should not navigate a link without an href', function () {
|
||||
$router.config([
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
@ -135,6 +135,20 @@ describe('ngLink', function () {
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should add an ng-link-active class on the current link', inject(function ($rootRouter) {
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp', name: 'One' }
|
||||
]);
|
||||
|
||||
compile('<a ng-link="[\'/One\']">one</a> | <div ng-outlet></div>');
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('class')).toBe('ng-link-active');
|
||||
}));
|
||||
|
||||
|
||||
function registerComponent(name, template, config) {
|
||||
var controller = function () {};
|
||||
|
@ -22,7 +22,7 @@ function provideHelpers(fn, preInject) {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$rootRouter,
|
||||
$templateCache,
|
||||
$controllerProvider;
|
||||
|
||||
@ -32,10 +32,10 @@ function provideHelpers(fn, preInject) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
});
|
||||
|
||||
inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||
inject(function(_$compile_, _$rootScope_, _$rootRouter_, _$templateCache_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
$templateCache = _$templateCache_;
|
||||
});
|
||||
|
||||
@ -72,7 +72,7 @@ function provideHelpers(fn, preInject) {
|
||||
|
||||
fn({
|
||||
registerComponent: registerComponent,
|
||||
$router: $router,
|
||||
$rootRouter: $rootRouter,
|
||||
put: put,
|
||||
compile: compile
|
||||
})
|
||||
|
@ -112,9 +112,7 @@ Example of a component:
|
||||
'title', | - title mapped to component title
|
||||
'open' | - open attribute mapped to component's open property
|
||||
], |
|
||||
}) |
|
||||
@View({ | View annotation
|
||||
templateUrl: 'pane.html' | - URL of template HTML
|
||||
templateUrl: 'pane.html' | URL of template HTML
|
||||
}) |
|
||||
class Pane { | Component controller class
|
||||
title:string; | - title property
|
||||
@ -227,11 +225,9 @@ class MyService {} | Assume a service which needs to be inject
|
||||
|
|
||||
@Component({ | Assume a top level application component which
|
||||
selector: 'my-app', | configures the services to be injected.
|
||||
viewBindings: [MyService] |
|
||||
}) |
|
||||
@View({ | Assume we have a template that needs to be
|
||||
templateUrl: 'my_app.html', | configured with directives to be injected.
|
||||
directives: [House] |
|
||||
viewBindings: [MyService], |
|
||||
templateUrl: 'my_app.html', | Assume we have a template that needs to be
|
||||
directives: [House] | configured with directives to be injected.
|
||||
}) |
|
||||
class MyApp {} |
|
||||
|
|
||||
@ -273,8 +269,6 @@ Here is an example of the kinds of injections which can be achieved:
|
||||
```
|
||||
@Component({ |
|
||||
selector: 'my-app' |
|
||||
}) |
|
||||
@View({ |
|
||||
templateUrl: 'my_app.html', |
|
||||
directives: [Form, FieldSet, |
|
||||
Field, Primary] |
|
||||
@ -329,8 +323,6 @@ Shadow DOM provides an encapsulation for components, so as a general rule it doe
|
||||
```
|
||||
@Component({
|
||||
selector: '[kid]'
|
||||
})
|
||||
@View({
|
||||
templateUrl: 'kid.html',
|
||||
directives: []
|
||||
})
|
||||
@ -347,8 +339,6 @@ class Kid {
|
||||
|
||||
@Component({
|
||||
selector: '[dad]'
|
||||
})
|
||||
@View({
|
||||
templateUrl: 'dad.html',
|
||||
directives: [Kid]
|
||||
})
|
||||
@ -361,9 +351,7 @@ class Dad {
|
||||
|
||||
@Component({
|
||||
selector: '[grandpa]',
|
||||
viewBindings: []
|
||||
})
|
||||
@View({
|
||||
viewBindings: [],
|
||||
templateUrl: 'grandpa.html',
|
||||
directives: [Dad]
|
||||
})
|
||||
|
@ -179,9 +179,7 @@ Both `MyComponent` and `MyDirective` are created on the same element.
|
||||
],
|
||||
viewBindings: [
|
||||
bind('viewService').toValue('View_MyComponentService')
|
||||
]
|
||||
})
|
||||
@View({
|
||||
],
|
||||
template: `<needs-view-service></needs-view-service>`,
|
||||
directives: [NeedsViewService]
|
||||
})
|
||||
|
@ -2,7 +2,7 @@ import {provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {UrlResolver} from 'angular2/compiler';
|
||||
|
||||
var MyApp;
|
||||
var MyApp: any;
|
||||
|
||||
// #docregion url_resolver
|
||||
class MyUrlResolver extends UrlResolver {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {DebugElement} from 'angular2/core';
|
||||
|
||||
var debugElement: DebugElement;
|
||||
var predicate;
|
||||
var predicate: any;
|
||||
|
||||
// #docregion scope_all
|
||||
debugElement.query(predicate);
|
||||
|
@ -2,8 +2,8 @@ import {bootstrap} from 'angular2/bootstrap';
|
||||
import {NG_VALIDATORS} from 'angular2/common';
|
||||
import {Provider} from 'angular2/core';
|
||||
|
||||
let MyApp = null;
|
||||
let myValidator = null;
|
||||
let MyApp: Function = null;
|
||||
let myValidator: any = null;
|
||||
|
||||
// #docregion ng_validators
|
||||
bootstrap(MyApp, [new Provider(NG_VALIDATORS, {useValue: myValidator, multi: true})]);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observable, Subscriber} from 'rxjs/Rx';
|
||||
|
||||
// #docregion AsyncPipe
|
||||
@Component({
|
||||
@ -37,8 +37,9 @@ export class AsyncPipeExample {
|
||||
// #docregion AsyncPipeObservable
|
||||
@Component({selector: "task-cmp", template: "Time: {{ time | async }}"})
|
||||
class Task {
|
||||
time = new Observable<number>(
|
||||
observer => { setInterval(_ => observer.next(new Date().getTime()), 500); });
|
||||
time = new Observable<number>((observer: Subscriber<number>) => {
|
||||
setInterval(_ => observer.next(new Date().getTime()), 500);
|
||||
});
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {bootstrap} from 'angular2/bootstrap';
|
||||
})
|
||||
export class LowerUpperPipeExample {
|
||||
value: string;
|
||||
change(value) { this.value = value; }
|
||||
change(value: string) { this.value = value; }
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Component, Attribute, Directive, Pipe} from 'angular2/core';
|
||||
|
||||
var CustomDirective;
|
||||
var CustomDirective: Function;
|
||||
|
||||
// #docregion component
|
||||
@Component({selector: 'greet', template: 'Hello {{name}}!', directives: [CustomDirective]})
|
||||
@ -20,7 +20,7 @@ class Page {
|
||||
// #docregion attributeMetadata
|
||||
@Directive({selector: 'input'})
|
||||
class InputAttrDirective {
|
||||
constructor(@Attribute('type') type) {
|
||||
constructor(@Attribute('type') type: string) {
|
||||
// type would be 'text' in this example
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,6 @@ class InputDirective {
|
||||
// #docregion pipe
|
||||
@Pipe({name: 'lowercase'})
|
||||
class Lowercase {
|
||||
transform(v, args) { return v.toLowerCase(); }
|
||||
transform(v: string, args: any[]) { return v.toLowerCase(); }
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -1,7 +1,7 @@
|
||||
// #docregion enableProdMode
|
||||
import {enableProdMode} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {MyComponent} from 'my_component';
|
||||
import {MyComponent} from './my_component';
|
||||
|
||||
enableProdMode();
|
||||
bootstrap(MyComponent);
|
||||
|
@ -1,6 +1,6 @@
|
||||
// #docregion Observable
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
var obs = new Observable<number>(obs => {
|
||||
import {Observable, Subscriber} from 'rxjs/Rx';
|
||||
var obs = new Observable<number>((obs: Subscriber<number>) => {
|
||||
var i = 0;
|
||||
setInterval(_ => { obs.next(++i); }, 1000);
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
// #docregion Observable
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observable, Subscriber} from 'rxjs/Rx';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
var obs = new Observable(obs => {
|
||||
var obs = new Observable<number>((obs: Subscriber<any>) => {
|
||||
var i = 0;
|
||||
setInterval(_ => obs.next(++i), 1000);
|
||||
});
|
||||
obs.map(i => `${i} seconds elapsed`).subscribe(msg => console.log(msg));
|
||||
obs.map((i: number) => `${i} seconds elapsed`).subscribe(msg => console.log(msg));
|
||||
// #enddocregion
|
||||
|
@ -1,10 +1,10 @@
|
||||
// #docregion Observable
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observable, Subscriber} from 'rxjs/Rx';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
|
||||
var obs = new Observable(obs => {
|
||||
var obs = new Observable<number>((sub: Subscriber<number>) => {
|
||||
var i = 0;
|
||||
setInterval(_ => obs.next(++i), 1000);
|
||||
setInterval(_ => sub.next(++i), 1000);
|
||||
});
|
||||
map.call(obs, i => `${i} seconds elapsed`).subscribe(msg => console.log(msg));
|
||||
map.call(obs, (i: number) => `${i} seconds elapsed`).subscribe((msg: string) => console.log(msg));
|
||||
// #enddocregion
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
function waitForElement(selector) {
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
function waitForElement(selector) {
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
function waitForElement(selector) {
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
function waitForElement(selector) {
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
function waitForElement(selector) {
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
|
@ -73,7 +73,7 @@ describe('some component', () => {
|
||||
// #docregion beforeEachProviders
|
||||
describe('some component', () => {
|
||||
beforeEachProviders(() => [provide(MyService, {useClass: MyMockService})]);
|
||||
it('uses MyService', inject([MyService], (service) => {
|
||||
it('uses MyService', inject([MyService], (service: MyMockService) => {
|
||||
// service is an instance of MyMockService.
|
||||
}));
|
||||
});
|
||||
@ -81,7 +81,7 @@ describe('some component', () => {
|
||||
|
||||
// #docregion afterEach
|
||||
describe('some component', () => {
|
||||
afterEach((done) => { db.reset().then((_) => done()); });
|
||||
afterEach((done: Function) => { db.reset().then((_: any) => done()); });
|
||||
it('uses the db', () => {
|
||||
// This test can leave the database in a dirty state.
|
||||
// The afterEach will ensure it gets reset.
|
||||
|
8
modules/angular2/i18n.ts
Normal file
8
modules/angular2/i18n.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Entry point to i18n
|
||||
*/
|
||||
export * from './src/i18n/message';
|
||||
export * from './src/i18n/xmb_serializer';
|
||||
export * from './src/i18n/message_extractor';
|
@ -8,12 +8,6 @@
|
||||
"license": "<%= packageJson.license %>",
|
||||
"repository": <%= JSON.stringify(packageJson.repository) %>,
|
||||
"devDependencies": <%= JSON.stringify(packageJson.defaultDevDependencies) %>,
|
||||
"dependencies": {
|
||||
"typings": "0.6.6"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "typings install --ambient --name es6-promise github:DefinitelyTyped/DefinitelyTyped/es6-promise/es6-promise.d.ts#830e8ebd9ef137d039d5c7ede24a421f08595f83; typings install --ambient --name es6-collections github:DefinitelyTyped/DefinitelyTyped/es6-collections/es6-collections.d.ts#9f97e2a2bc1f502550c9b4fcaad1c48df5521d37"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"es6-promise": "<%= packageJson.dependencies['es6-promise'] %>",
|
||||
"es6-shim": "<%= packageJson.dependencies['es6-shim'] %>",
|
||||
|
@ -13,7 +13,6 @@ export {
|
||||
} from 'angular2/src/platform/browser_common';
|
||||
|
||||
import {Type, isPresent, CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {Promise} from 'angular2/src/facade/promise';
|
||||
import {
|
||||
BROWSER_PROVIDERS,
|
||||
BROWSER_APP_COMMON_PROVIDERS
|
||||
|
@ -12,7 +12,6 @@ export {
|
||||
} from 'angular2/src/platform/browser_common';
|
||||
|
||||
import {Type, isPresent} from 'angular2/src/facade/lang';
|
||||
import {Promise} from 'angular2/src/facade/promise';
|
||||
import {
|
||||
BROWSER_PROVIDERS,
|
||||
BROWSER_APP_COMMON_PROVIDERS
|
||||
|
@ -15,7 +15,7 @@ import {MockAnimationBuilder} from 'angular2/src/mock/animation_builder_mock';
|
||||
import {MockDirectiveResolver} from 'angular2/src/mock/directive_resolver_mock';
|
||||
import {MockViewResolver} from 'angular2/src/mock/view_resolver_mock';
|
||||
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
|
||||
import {LocationStrategy} from 'angular2/src/router/location_strategy';
|
||||
import {LocationStrategy} from 'angular2/src/router/location/location_strategy';
|
||||
import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
|
||||
|
||||
import {XHRImpl} from "angular2/src/platform/browser/xhr_impl";
|
||||
|
@ -16,7 +16,7 @@ import {MockAnimationBuilder} from 'angular2/src/mock/animation_builder_mock';
|
||||
import {MockDirectiveResolver} from 'angular2/src/mock/directive_resolver_mock';
|
||||
import {MockViewResolver} from 'angular2/src/mock/view_resolver_mock';
|
||||
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
|
||||
import {LocationStrategy} from 'angular2/src/router/location_strategy';
|
||||
import {LocationStrategy} from 'angular2/src/router/location/location_strategy';
|
||||
import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
|
||||
|
||||
import {TestComponentBuilder} from 'angular2/src/testing/test_component_builder';
|
||||
|
@ -18,15 +18,17 @@ dependencies:
|
||||
logging: '>=0.9.0 <0.12.0'
|
||||
observe: '^0.13.1'
|
||||
protobuf: '^0.5.0'
|
||||
quiver: '^0.21.4'
|
||||
source_span: '^1.0.0'
|
||||
stack_trace: '^1.1.1'
|
||||
build: '>=0.0.1'
|
||||
dev_dependencies:
|
||||
code_transformers: '>=0.2.9+4 <0.4.0'
|
||||
transformer_test: '^0.2.0'
|
||||
guinness: '^0.1.18'
|
||||
guinness2: '0.0.5'
|
||||
quiver: '^0.21.4'
|
||||
test: '^0.12.6'
|
||||
transformers:
|
||||
- angular2
|
||||
- angular2/transform/codegen
|
||||
- $dart2js:
|
||||
commandLineOptions:
|
||||
- --show-package-warnings
|
||||
|
@ -5,26 +5,26 @@
|
||||
*/
|
||||
|
||||
export {Router} from './src/router/router';
|
||||
export {RouterOutlet} from './src/router/router_outlet';
|
||||
export {RouterLink} from './src/router/router_link';
|
||||
export {RouterOutlet} from './src/router/directives/router_outlet';
|
||||
export {RouterLink} from './src/router/directives/router_link';
|
||||
export {RouteParams, RouteData} from './src/router/instruction';
|
||||
export {PlatformLocation} from './src/router/platform_location';
|
||||
export {PlatformLocation} from './src/router/location/platform_location';
|
||||
export {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from './src/router/route_registry';
|
||||
export {LocationStrategy, APP_BASE_HREF} from './src/router/location_strategy';
|
||||
export {HashLocationStrategy} from './src/router/hash_location_strategy';
|
||||
export {PathLocationStrategy} from './src/router/path_location_strategy';
|
||||
export {Location} from './src/router/location';
|
||||
export * from './src/router/route_config_decorator';
|
||||
export {LocationStrategy, APP_BASE_HREF} from './src/router/location/location_strategy';
|
||||
export {HashLocationStrategy} from './src/router/location/hash_location_strategy';
|
||||
export {PathLocationStrategy} from './src/router/location/path_location_strategy';
|
||||
export {Location} from './src/router/location/location';
|
||||
export * from './src/router/route_config/route_config_decorator';
|
||||
export * from './src/router/route_definition';
|
||||
export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/router/interfaces';
|
||||
export {CanActivate} from './src/router/lifecycle_annotations';
|
||||
export {CanActivate} from './src/router/lifecycle/lifecycle_annotations';
|
||||
export {Instruction, ComponentInstruction} from './src/router/instruction';
|
||||
export {OpaqueToken} from 'angular2/core';
|
||||
export {ROUTER_PROVIDERS_COMMON} from 'angular2/src/router/router_providers_common';
|
||||
export {ROUTER_PROVIDERS, ROUTER_BINDINGS} from 'angular2/src/router/router_providers';
|
||||
|
||||
import {RouterOutlet} from './src/router/router_outlet';
|
||||
import {RouterLink} from './src/router/router_link';
|
||||
import {RouterOutlet} from './src/router/directives/router_outlet';
|
||||
import {RouterLink} from './src/router/directives/router_link';
|
||||
import {CONST_EXPR} from './src/facade/lang';
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {TEMPLATE_TRANSFORMS} from 'angular2/compiler';
|
||||
import {Provider} from 'angular2/core';
|
||||
import {RouterLinkTransform} from 'angular2/src/router/router_link_transform';
|
||||
import {RouterLinkTransform} from 'angular2/src/router/directives/router_link_transform';
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
|
||||
export {RouterLinkTransform} from 'angular2/src/router/router_link_transform';
|
||||
export {RouterLinkTransform} from 'angular2/src/router/directives/router_link_transform';
|
||||
|
||||
/**
|
||||
* Enables the router link DSL.
|
||||
|
@ -52,7 +52,7 @@ export class Animation {
|
||||
this.startTime = DateWrapper.toMillis(DateWrapper.now());
|
||||
this._stringPrefix = DOM.getAnimationPrefix();
|
||||
this.setup();
|
||||
this.wait(timestamp => this.start());
|
||||
this.wait((timestamp: any) => this.start());
|
||||
}
|
||||
|
||||
wait(callback: Function) {
|
||||
@ -97,7 +97,7 @@ export class Animation {
|
||||
* @param styles
|
||||
*/
|
||||
applyStyles(styles: {[key: string]: any}): void {
|
||||
StringMapWrapper.forEach(styles, (value, key) => {
|
||||
StringMapWrapper.forEach(styles, (value: any, key: string) => {
|
||||
var dashCaseKey = camelCaseToDashCase(key);
|
||||
if (isPresent(DOM.getStyle(this.element, dashCaseKey))) {
|
||||
DOM.setStyle(this.element, dashCaseKey, value.toString());
|
||||
|
@ -17,7 +17,7 @@ export class BrowserDetails {
|
||||
DOM.setAttribute(div, 'style', `position: absolute; top: -9999px; left: -9999px; width: 1px;
|
||||
height: 1px; transition: all 1ms linear 1ms;`);
|
||||
// Firefox requires that we wait for 2 frames for some reason
|
||||
this.raf(timestamp => {
|
||||
this.raf((timestamp: any) => {
|
||||
DOM.on(div, 'transitionend', (event: any) => {
|
||||
var elapsed = Math.round(event.elapsedTime * 1000);
|
||||
this.elapsedTimeIncludesDelay = elapsed == 2;
|
||||
@ -37,7 +37,8 @@ class RafQueue {
|
||||
currentFrameId: number;
|
||||
constructor(public callback: Function, public frames: number) { this._raf(); }
|
||||
private _raf() {
|
||||
this.currentFrameId = DOM.requestAnimationFrame(timestamp => this._nextFrame(timestamp));
|
||||
this.currentFrameId =
|
||||
DOM.requestAnimationFrame((timestamp: number) => this._nextFrame(timestamp));
|
||||
}
|
||||
private _nextFrame(timestamp: number) {
|
||||
this.frames--;
|
||||
|
@ -9,7 +9,7 @@ import {CORE_DIRECTIVES} from './directives';
|
||||
* NgModel).
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
|
||||
* property of the `@Component` or `@View` decorators.
|
||||
* property of the `@Component` decorator.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
|
@ -8,5 +8,6 @@ export {NgFor} from './directives/ng_for';
|
||||
export {NgIf} from './directives/ng_if';
|
||||
export {NgStyle} from './directives/ng_style';
|
||||
export {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './directives/ng_switch';
|
||||
export {NgPlural, NgPluralCase, NgLocalization} from './directives/ng_plural';
|
||||
export * from './directives/observable_list_diff';
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
||||
|
@ -4,13 +4,14 @@ import {NgFor} from './ng_for';
|
||||
import {NgIf} from './ng_if';
|
||||
import {NgStyle} from './ng_style';
|
||||
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './ng_switch';
|
||||
import {NgPlural, NgPluralCase} from './ng_plural';
|
||||
|
||||
/**
|
||||
* A collection of Angular core directives that are likely to be used in each and every Angular
|
||||
* application.
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
|
||||
* property of the `@View` annotation.
|
||||
* property of the `@Component` annotation.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/yakGwpCdUkg0qfzX5m8g?p=preview))
|
||||
*
|
||||
@ -45,5 +46,14 @@ import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './ng_switch';
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const CORE_DIRECTIVES: Type[] =
|
||||
CONST_EXPR([NgClass, NgFor, NgIf, NgStyle, NgSwitch, NgSwitchWhen, NgSwitchDefault]);
|
||||
export const CORE_DIRECTIVES: Type[] = CONST_EXPR([
|
||||
NgClass,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgStyle,
|
||||
NgSwitch,
|
||||
NgSwitchWhen,
|
||||
NgSwitchDefault,
|
||||
NgPlural,
|
||||
NgPluralCase
|
||||
]);
|
||||
|
@ -1,14 +1,16 @@
|
||||
import {isPresent, isString, StringWrapper, isBlank, isArray} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isString, isArray} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
DoCheck,
|
||||
OnDestroy,
|
||||
Directive,
|
||||
ElementRef,
|
||||
IterableDiffer,
|
||||
IterableDiffers,
|
||||
KeyValueDiffer,
|
||||
KeyValueDiffers,
|
||||
Renderer
|
||||
Renderer,
|
||||
IterableDiffer,
|
||||
KeyValueDiffer,
|
||||
CollectionChangeRecord,
|
||||
KeyValueChangeRecord
|
||||
} from 'angular2/core';
|
||||
import {StringMapWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
|
||||
|
||||
@ -73,66 +75,68 @@ import {StringMapWrapper, isListLikeIterable} from 'angular2/src/facade/collecti
|
||||
*/
|
||||
@Directive({selector: '[ngClass]', inputs: ['rawClass: ngClass', 'initialClasses: class']})
|
||||
export class NgClass implements DoCheck, OnDestroy {
|
||||
private _differ: any;
|
||||
private _mode: string;
|
||||
private _initialClasses = [];
|
||||
private _rawClass;
|
||||
private _iterableDiffer: IterableDiffer;
|
||||
private _keyValueDiffer: KeyValueDiffer;
|
||||
private _initialClasses: string[] = [];
|
||||
private _rawClass: string[] | Set<string>;
|
||||
|
||||
constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||
private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
set initialClasses(v) {
|
||||
set initialClasses(v: string) {
|
||||
this._applyInitialClasses(true);
|
||||
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
|
||||
this._applyInitialClasses(false);
|
||||
this._applyClasses(this._rawClass, false);
|
||||
}
|
||||
|
||||
set rawClass(v) {
|
||||
set rawClass(v: string | string[] | Set<string>| {[key: string]: any}) {
|
||||
this._cleanupClasses(this._rawClass);
|
||||
|
||||
if (isString(v)) {
|
||||
v = v.split(' ');
|
||||
v = (<string>v).split(' ');
|
||||
}
|
||||
|
||||
this._rawClass = v;
|
||||
this._rawClass = <string[] | Set<string>>v;
|
||||
this._iterableDiffer = null;
|
||||
this._keyValueDiffer = null;
|
||||
if (isPresent(v)) {
|
||||
if (isListLikeIterable(v)) {
|
||||
this._differ = this._iterableDiffers.find(v).create(null);
|
||||
this._mode = 'iterable';
|
||||
this._iterableDiffer = this._iterableDiffers.find(v).create(null);
|
||||
} else {
|
||||
this._differ = this._keyValueDiffers.find(v).create(null);
|
||||
this._mode = 'keyValue';
|
||||
this._keyValueDiffer = this._keyValueDiffers.find(v).create(null);
|
||||
}
|
||||
} else {
|
||||
this._differ = null;
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (isPresent(this._differ)) {
|
||||
var changes = this._differ.diff(this._rawClass);
|
||||
if (isPresent(this._iterableDiffer)) {
|
||||
var changes = this._iterableDiffer.diff(this._rawClass);
|
||||
if (isPresent(changes)) {
|
||||
if (this._mode == 'iterable') {
|
||||
this._applyIterableChanges(changes);
|
||||
} else {
|
||||
this._applyKeyValueChanges(changes);
|
||||
}
|
||||
this._applyIterableChanges(changes);
|
||||
}
|
||||
}
|
||||
if (isPresent(this._keyValueDiffer)) {
|
||||
var changes = this._keyValueDiffer.diff(this._rawClass);
|
||||
if (isPresent(changes)) {
|
||||
this._applyKeyValueChanges(changes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._cleanupClasses(this._rawClass); }
|
||||
|
||||
private _cleanupClasses(rawClassVal): void {
|
||||
private _cleanupClasses(rawClassVal: string[] | Set<string>| {[key: string]: any}): void {
|
||||
this._applyClasses(rawClassVal, true);
|
||||
this._applyInitialClasses(false);
|
||||
}
|
||||
|
||||
private _applyKeyValueChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem((record) => {
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => { this._toggleClass(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => { this._toggleClass(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem((record: KeyValueChangeRecord) => {
|
||||
if (record.previousValue) {
|
||||
this._toggleClass(record.key, false);
|
||||
}
|
||||
@ -140,15 +144,17 @@ export class NgClass implements DoCheck, OnDestroy {
|
||||
}
|
||||
|
||||
private _applyIterableChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record) => { this._toggleClass(record.item, true); });
|
||||
changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); });
|
||||
changes.forEachAddedItem(
|
||||
(record: CollectionChangeRecord) => { this._toggleClass(record.item, true); });
|
||||
changes.forEachRemovedItem(
|
||||
(record: CollectionChangeRecord) => { this._toggleClass(record.item, false); });
|
||||
}
|
||||
|
||||
private _applyInitialClasses(isCleanup: boolean) {
|
||||
this._initialClasses.forEach(className => this._toggleClass(className, !isCleanup));
|
||||
}
|
||||
|
||||
private _applyClasses(rawClassVal: string[] | Set<string>| {[key: string]: string},
|
||||
private _applyClasses(rawClassVal: string[] | Set<string>| {[key: string]: any},
|
||||
isCleanup: boolean) {
|
||||
if (isPresent(rawClassVal)) {
|
||||
if (isArray(rawClassVal)) {
|
||||
@ -156,14 +162,15 @@ export class NgClass implements DoCheck, OnDestroy {
|
||||
} else if (rawClassVal instanceof Set) {
|
||||
(<Set<string>>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
|
||||
} else {
|
||||
StringMapWrapper.forEach(<{[k: string]: string}>rawClassVal, (expVal, className) => {
|
||||
if (expVal) this._toggleClass(className, !isCleanup);
|
||||
});
|
||||
StringMapWrapper.forEach(<{[k: string]: any}>rawClassVal,
|
||||
(expVal: any, className: string) => {
|
||||
if (isPresent(expVal)) this._toggleClass(className, !isCleanup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleClass(className: string, enabled): void {
|
||||
private _toggleClass(className: string, enabled: boolean): void {
|
||||
className = className.trim();
|
||||
if (className.length > 0) {
|
||||
if (className.indexOf(' ') > -1) {
|
||||
|
@ -10,6 +10,10 @@ import {
|
||||
TrackByFn
|
||||
} from 'angular2/core';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
DefaultIterableDiffer,
|
||||
CollectionChangeRecord
|
||||
} from "../../core/change_detection/differs/default_iterable_differ";
|
||||
|
||||
/**
|
||||
* The `NgFor` directive instantiates a template once per item from an iterable. The context for
|
||||
@ -92,19 +96,19 @@ export class NgFor implements DoCheck {
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes) {
|
||||
private _applyChanges(changes: DefaultIterableDiffer) {
|
||||
// TODO(rado): check if change detection can produce a change record that is
|
||||
// easier to consume than current.
|
||||
var recordViewTuples = [];
|
||||
changes.forEachRemovedItem((removedRecord) =>
|
||||
var recordViewTuples: RecordViewTuple[] = [];
|
||||
changes.forEachRemovedItem((removedRecord: CollectionChangeRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
|
||||
|
||||
changes.forEachMovedItem((movedRecord) =>
|
||||
changes.forEachMovedItem((movedRecord: CollectionChangeRecord) =>
|
||||
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
|
||||
|
||||
var insertTuples = this._bulkRemove(recordViewTuples);
|
||||
|
||||
changes.forEachAddedItem((addedRecord) =>
|
||||
changes.forEachAddedItem((addedRecord: CollectionChangeRecord) =>
|
||||
insertTuples.push(new RecordViewTuple(addedRecord, null)));
|
||||
|
||||
this._bulkInsert(insertTuples);
|
||||
@ -124,7 +128,7 @@ export class NgFor implements DoCheck {
|
||||
});
|
||||
}
|
||||
|
||||
private _perViewChange(view, record) {
|
||||
private _perViewChange(view: EmbeddedViewRef, record: CollectionChangeRecord) {
|
||||
view.setLocal('\$implicit', record.item);
|
||||
view.setLocal('index', record.currentIndex);
|
||||
view.setLocal('even', (record.currentIndex % 2 == 0));
|
||||
@ -132,8 +136,9 @@ export class NgFor implements DoCheck {
|
||||
}
|
||||
|
||||
private _bulkRemove(tuples: RecordViewTuple[]): RecordViewTuple[] {
|
||||
tuples.sort((a, b) => a.record.previousIndex - b.record.previousIndex);
|
||||
var movedTuples = [];
|
||||
tuples.sort((a: RecordViewTuple, b: RecordViewTuple) =>
|
||||
a.record.previousIndex - b.record.previousIndex);
|
||||
var movedTuples: RecordViewTuple[] = [];
|
||||
for (var i = tuples.length - 1; i >= 0; i--) {
|
||||
var tuple = tuples[i];
|
||||
// separate moved views from removed views.
|
||||
@ -165,7 +170,7 @@ export class NgFor implements DoCheck {
|
||||
class RecordViewTuple {
|
||||
view: EmbeddedViewRef;
|
||||
record: any;
|
||||
constructor(record, view) {
|
||||
constructor(record: any, view: EmbeddedViewRef) {
|
||||
this.record = record;
|
||||
this.view = view;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class NgIf {
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef) {}
|
||||
|
||||
set ngIf(newCondition /* boolean */) {
|
||||
set ngIf(newCondition: any /* boolean */) {
|
||||
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) {
|
||||
this._prevCondition = true;
|
||||
this._viewContainer.createEmbeddedView(this._templateRef);
|
||||
|
146
modules/angular2/src/common/directives/ng_plural.ts
Normal file
146
modules/angular2/src/common/directives/ng_plural.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import {
|
||||
Directive,
|
||||
ViewContainerRef,
|
||||
TemplateRef,
|
||||
ContentChildren,
|
||||
QueryList,
|
||||
Attribute,
|
||||
AfterContentInit,
|
||||
Input
|
||||
} from 'angular2/core';
|
||||
import {isPresent, NumberWrapper} from 'angular2/src/facade/lang';
|
||||
import {Map} from 'angular2/src/facade/collection';
|
||||
import {SwitchView} from './ng_switch';
|
||||
|
||||
const _CATEGORY_DEFAULT = 'other';
|
||||
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
|
||||
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category.
|
||||
*
|
||||
* To use this directive, you must provide an extension of `NgLocalization` that maps values to
|
||||
* category names. You then define a container element that sets the `[ngPlural]` attribute to a
|
||||
* switch expression.
|
||||
* - Inner elements defined with an `[ngPluralCase]` attribute will display based on their
|
||||
* expression.
|
||||
* - If `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
|
||||
* matches the switch expression exactly.
|
||||
* - Otherwise, the view will be treated as a "category match", and will only display if exact
|
||||
* value matches aren't found and the value maps to its category using the `getPluralCategory`
|
||||
* function provided.
|
||||
*
|
||||
* If no matching views are found for a switch expression, inner elements marked
|
||||
* `[ngPluralCase]="other"` will be displayed.
|
||||
*
|
||||
* ```typescript
|
||||
* class MyLocalization extends NgLocalization {
|
||||
* getPluralCategory(value: any) {
|
||||
* if(value < 5) {
|
||||
* return 'few';
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* providers: [provide(NgLocalization, {useClass: MyLocalization})]
|
||||
* })
|
||||
* @View({
|
||||
* template: `
|
||||
* <p>Value = {{value}}</p>
|
||||
* <button (click)="inc()">Increment</button>
|
||||
*
|
||||
* <div [ngPlural]="value">
|
||||
* <template ngPluralCase="=0">there is nothing</template>
|
||||
* <template ngPluralCase="=1">there is one</template>
|
||||
* <template ngPluralCase="few">there are a few</template>
|
||||
* <template ngPluralCase="other">there is some number</template>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [NgPlural, NgPluralCase]
|
||||
* })
|
||||
* export class App {
|
||||
* value = 'init';
|
||||
*
|
||||
* inc() {
|
||||
* this.value = this.value === 'init' ? 0 : this.value + 1;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
|
||||
@Directive({selector: '[ngPluralCase]'})
|
||||
export class NgPluralCase {
|
||||
_view: SwitchView;
|
||||
constructor(@Attribute('ngPluralCase') public value: string, template: TemplateRef,
|
||||
viewContainer: ViewContainerRef) {
|
||||
this._view = new SwitchView(viewContainer, template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: '[ngPlural]'})
|
||||
export class NgPlural implements AfterContentInit {
|
||||
private _switchValue: number;
|
||||
private _activeView: SwitchView;
|
||||
private _caseViews = new Map<any, SwitchView>();
|
||||
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
|
||||
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
@Input()
|
||||
set ngPlural(value: number) {
|
||||
this._switchValue = value;
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.cases.forEach((pluralCase: NgPluralCase): void => {
|
||||
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
|
||||
});
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateView(): void {
|
||||
this._clearViews();
|
||||
|
||||
var view: SwitchView = this._caseViews.get(this._switchValue);
|
||||
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
|
||||
|
||||
this._activateView(view);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_clearViews() {
|
||||
if (isPresent(this._activeView)) this._activeView.destroy();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_activateView(view: SwitchView) {
|
||||
if (!isPresent(view)) return;
|
||||
this._activeView = view;
|
||||
this._activeView.create();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getCategoryView(value: number): SwitchView {
|
||||
var category: string = this._localization.getPluralCategory(value);
|
||||
var categoryView: SwitchView = this._caseViews.get(category);
|
||||
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === "="; }
|
||||
|
||||
/** @internal */
|
||||
_formatValue(pluralCase: NgPluralCase): any {
|
||||
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
|
||||
}
|
@ -7,6 +7,7 @@ import {
|
||||
Renderer
|
||||
} from 'angular2/core';
|
||||
import {isPresent, isBlank, print} from 'angular2/src/facade/lang';
|
||||
import {KeyValueChangeRecord} from "../../core/change_detection/differs/default_keyvalue_differ";
|
||||
|
||||
/**
|
||||
* The `NgStyle` directive changes styles based on a result of expression evaluation.
|
||||
@ -62,14 +63,14 @@ import {isPresent, isBlank, print} from 'angular2/src/facade/lang';
|
||||
@Directive({selector: '[ngStyle]', inputs: ['rawStyle: ngStyle']})
|
||||
export class NgStyle implements DoCheck {
|
||||
/** @internal */
|
||||
_rawStyle;
|
||||
_rawStyle: {[key: string]: string};
|
||||
/** @internal */
|
||||
_differ: KeyValueDiffer;
|
||||
|
||||
constructor(private _differs: KeyValueDiffers, private _ngEl: ElementRef,
|
||||
private _renderer: Renderer) {}
|
||||
|
||||
set rawStyle(v) {
|
||||
set rawStyle(v: {[key: string]: string}) {
|
||||
this._rawStyle = v;
|
||||
if (isBlank(this._differ) && isPresent(v)) {
|
||||
this._differ = this._differs.find(this._rawStyle).create(null);
|
||||
@ -86,9 +87,12 @@ export class NgStyle implements DoCheck {
|
||||
}
|
||||
|
||||
private _applyChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem((record) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem((record) => { this._setStyle(record.key, null); });
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
|
||||
changes.forEachRemovedItem(
|
||||
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
|
||||
}
|
||||
|
||||
private _setStyle(name: string, val: string): void {
|
||||
|
@ -4,7 +4,6 @@ import {ListWrapper, Map} from 'angular2/src/facade/collection';
|
||||
|
||||
const _WHEN_DEFAULT = CONST_EXPR(new Object());
|
||||
|
||||
/** @internal */
|
||||
export class SwitchView {
|
||||
constructor(private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef) {}
|
||||
|
||||
@ -32,8 +31,8 @@ export class SwitchView {
|
||||
* ### Example ([live demo](http://plnkr.co/edit/DQMTII95CbuqWrl3lYAs?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({selector: 'app'})
|
||||
* @View({
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* template: `
|
||||
* <p>Value = {{value}}</p>
|
||||
* <button (click)="inc()">Increment</button>
|
||||
@ -76,7 +75,7 @@ export class NgSwitch {
|
||||
private _valueViews = new Map<any, SwitchView[]>();
|
||||
private _activeViews: SwitchView[] = [];
|
||||
|
||||
set ngSwitch(value) {
|
||||
set ngSwitch(value: any) {
|
||||
// Empty the currently active ViewContainers
|
||||
this._emptyAllActiveViews();
|
||||
|
||||
@ -93,7 +92,7 @@ export class NgSwitch {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_onWhenValueChanged(oldWhen, newWhen, view: SwitchView): void {
|
||||
_onWhenValueChanged(oldWhen: any, newWhen: any, view: SwitchView): void {
|
||||
this._deregisterView(oldWhen, view);
|
||||
this._registerView(newWhen, view);
|
||||
|
||||
@ -137,7 +136,7 @@ export class NgSwitch {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_registerView(value, view: SwitchView): void {
|
||||
_registerView(value: any, view: SwitchView): void {
|
||||
var views = this._valueViews.get(value);
|
||||
if (isBlank(views)) {
|
||||
views = [];
|
||||
@ -147,7 +146,7 @@ export class NgSwitch {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_deregisterView(value, view: SwitchView): void {
|
||||
_deregisterView(value: any, view: SwitchView): void {
|
||||
// `_WHEN_DEFAULT` is used a marker for non-registered whens
|
||||
if (value === _WHEN_DEFAULT) return;
|
||||
var views = this._valueViews.get(value);
|
||||
@ -182,7 +181,7 @@ export class NgSwitchWhen {
|
||||
this._view = new SwitchView(viewContainer, templateRef);
|
||||
}
|
||||
|
||||
set ngSwitchWhen(value) {
|
||||
set ngSwitchWhen(value: any) {
|
||||
this._switch._onWhenValueChanged(this._value, value, this._view);
|
||||
this._value = value;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ export {
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator,
|
||||
Validator
|
||||
} from './forms/directives/validators';
|
||||
export {FormBuilder} from './forms/form_builder';
|
||||
|
@ -14,7 +14,12 @@ import {
|
||||
SelectControlValueAccessor,
|
||||
NgSelectOption
|
||||
} from './directives/select_control_value_accessor';
|
||||
import {RequiredValidator, MinLengthValidator, MaxLengthValidator} from './directives/validators';
|
||||
import {
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator
|
||||
} from './directives/validators';
|
||||
|
||||
export {NgControlName} from './directives/ng_control_name';
|
||||
export {NgFormControl} from './directives/ng_form_control';
|
||||
@ -34,13 +39,18 @@ export {
|
||||
SelectControlValueAccessor,
|
||||
NgSelectOption
|
||||
} from './directives/select_control_value_accessor';
|
||||
export {RequiredValidator, MinLengthValidator, MaxLengthValidator} from './directives/validators';
|
||||
export {
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
PatternValidator
|
||||
} from './directives/validators';
|
||||
export {NgControl} from './directives/ng_control';
|
||||
export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
|
||||
/**
|
||||
*
|
||||
* A list of all the form directives used as part of a `@View` annotation.
|
||||
* A list of all the form directives used as part of a `@Component` annotation.
|
||||
*
|
||||
* This is a shorthand for importing them each individually.
|
||||
*
|
||||
@ -73,5 +83,6 @@ export const FORM_DIRECTIVES: Type[] = CONST_EXPR([
|
||||
|
||||
RequiredValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator
|
||||
MaxLengthValidator,
|
||||
PatternValidator
|
||||
]);
|
||||
|
@ -21,7 +21,7 @@ const CHECKBOX_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
||||
providers: [CHECKBOX_VALUE_ACCESSOR]
|
||||
})
|
||||
export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_) => {};
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
@ -24,7 +24,7 @@ const DEFAULT_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
||||
bindings: [DEFAULT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_) => {};
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {unimplemented} from 'angular2/src/facade/exceptions';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
/**
|
||||
* A base class that all control directive extend.
|
||||
@ -12,8 +13,8 @@ export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
|
||||
get validator(): Function { return unimplemented(); }
|
||||
get asyncValidator(): Function { return unimplemented(); }
|
||||
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
|
||||
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
|
||||
|
||||
abstract viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ import {ControlContainer} from './control_container';
|
||||
import {controlPath, composeValidators, composeAsyncValidators} from './shared';
|
||||
import {ControlGroup} from '../model';
|
||||
import {Form} from './form_interface';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
const controlGroupProvider =
|
||||
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgControlGroup)}));
|
||||
@ -32,8 +33,6 @@ const controlGroupProvider =
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* })
|
||||
* @View({
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular2 Control & ControlGroup Example</h2>
|
||||
@ -106,7 +105,7 @@ export class NgControlGroup extends ControlContainer implements OnInit,
|
||||
*/
|
||||
get formDirective(): Form { return this._parent.formDirective; }
|
||||
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ import {
|
||||
selectValueAccessor
|
||||
} from './shared';
|
||||
import {Control} from '../model';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
|
||||
const controlNameBinding =
|
||||
@ -136,9 +137,9 @@ export class NgControlName extends NgControl implements OnChanges,
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
get control(): Control { return this.formDirective.getControl(this); }
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
isPropertyUpdated,
|
||||
selectValueAccessor
|
||||
} from './shared';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
const formControlBinding =
|
||||
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgFormControl)}));
|
||||
@ -110,9 +111,9 @@ export class NgFormControl extends NgControl implements OnChanges {
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
get control(): Control { return this.form; }
|
||||
|
||||
|
@ -3,7 +3,6 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {
|
||||
OnChanges,
|
||||
SimpleChange,
|
||||
Query,
|
||||
Directive,
|
||||
forwardRef,
|
||||
Provider,
|
||||
@ -14,7 +13,7 @@ import {
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {Control} from '../model';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {
|
||||
setUpControl,
|
||||
isPropertyUpdated,
|
||||
@ -22,6 +21,7 @@ import {
|
||||
composeValidators,
|
||||
composeAsyncValidators
|
||||
} from './shared';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
const formControlBinding =
|
||||
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgModel)}));
|
||||
@ -88,9 +88,9 @@ export class NgModel extends NgControl implements OnChanges {
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
|
@ -10,3 +10,11 @@ Function normalizeValidator(dynamic validator){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Function normalizeAsyncValidator(dynamic validator){
|
||||
if (validator is Validator) {
|
||||
return (c) => validator.validate(c);
|
||||
} else {
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,18 @@
|
||||
import {Validator} from './validators';
|
||||
import {AbstractControl} from "../model";
|
||||
import {Validator, ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
export function normalizeValidator(validator: Function | Validator): Function {
|
||||
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c) => (<Validator>validator).validate(c);
|
||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||
} else {
|
||||
return <Function>validator;
|
||||
return <ValidatorFn>validator;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => Promise.resolve((<Validator>validator).validate(c));
|
||||
} else {
|
||||
return <AsyncValidatorFn>validator;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ const NUMBER_VALUE_ACCESSOR = CONST_EXPR(new Provider(
|
||||
bindings: [NUMBER_VALUE_ACCESSOR]
|
||||
})
|
||||
export class NumberValueAccessor implements ControlValueAccessor {
|
||||
onChange = (_) => {};
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
@ -41,7 +41,7 @@ export class NgSelectOption {
|
||||
})
|
||||
export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
value: string;
|
||||
onChange = (_) => {};
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
|
@ -14,7 +14,8 @@ import {NumberValueAccessor} from './number_value_accessor';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
||||
import {normalizeValidator} from './normalize_validator';
|
||||
import {normalizeValidator, normalizeAsyncValidator} from './normalize_validator';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
@ -32,14 +33,14 @@ export function setUpControl(control: Control, dir: NgControl): void {
|
||||
dir.valueAccessor.writeValue(control.value);
|
||||
|
||||
// view -> model
|
||||
dir.valueAccessor.registerOnChange(newValue => {
|
||||
dir.valueAccessor.registerOnChange((newValue: any) => {
|
||||
dir.viewToModelUpdate(newValue);
|
||||
control.updateValue(newValue, {emitModelToViewChange: false});
|
||||
control.markAsDirty();
|
||||
});
|
||||
|
||||
// model -> view
|
||||
control.registerOnChange(newValue => dir.valueAccessor.writeValue(newValue));
|
||||
control.registerOnChange((newValue: any) => dir.valueAccessor.writeValue(newValue));
|
||||
|
||||
// touched
|
||||
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
||||
@ -56,13 +57,14 @@ function _throwError(dir: AbstractControlDirective, message: string): void {
|
||||
throw new BaseException(`${message} '${path}'`);
|
||||
}
|
||||
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): Function {
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
|
||||
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
|
||||
}
|
||||
|
||||
export function composeAsyncValidators(
|
||||
validators: /* Array<Validator|Function> */ any[]): Function {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeValidator)) : null;
|
||||
validators: /* Array<Validator|Function> */ any[]): AsyncValidatorFn {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
|
||||
null;
|
||||
}
|
||||
|
||||
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
|
||||
@ -78,10 +80,10 @@ export function selectValueAccessor(dir: NgControl,
|
||||
valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
||||
if (isBlank(valueAccessors)) return null;
|
||||
|
||||
var defaultAccessor;
|
||||
var builtinAccessor;
|
||||
var customAccessor;
|
||||
valueAccessors.forEach(v => {
|
||||
var defaultAccessor: ControlValueAccessor;
|
||||
var builtinAccessor: ControlValueAccessor;
|
||||
var customAccessor: ControlValueAccessor;
|
||||
valueAccessors.forEach((v: ControlValueAccessor) => {
|
||||
if (hasConstructor(v, DefaultValueAccessor)) {
|
||||
defaultAccessor = v;
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import {forwardRef, Provider, OpaqueToken, Attribute, Directive} from 'angular2/core';
|
||||
import {forwardRef, Provider, Attribute, Directive} from 'angular2/core';
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {Validators, NG_VALIDATORS} from '../validators';
|
||||
import {Control} from '../model';
|
||||
import {AbstractControl} from '../model';
|
||||
import * as modelModule from '../model';
|
||||
import {NumberWrapper} from "angular2/src/facade/lang";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface that can be implemented by classes that can act as validators.
|
||||
*
|
||||
@ -23,7 +24,7 @@ import {NumberWrapper} from "angular2/src/facade/lang";
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface Validator { validate(c: modelModule.Control): {[key: string]: any}; }
|
||||
export interface Validator { validate(c: modelModule.AbstractControl): {[key: string]: any}; }
|
||||
|
||||
const REQUIRED_VALIDATOR =
|
||||
CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.required, multi: true}));
|
||||
@ -45,6 +46,11 @@ const REQUIRED_VALIDATOR =
|
||||
export class RequiredValidator {
|
||||
}
|
||||
|
||||
export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; }
|
||||
export interface AsyncValidatorFn {
|
||||
(c: AbstractControl): any /*Promise<{[key: string]: any}>|Observable<{[key: string]: any}>*/;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provivder which adds {@link MinLengthValidator} to {@link NG_VALIDATORS}.
|
||||
*
|
||||
@ -64,13 +70,13 @@ const MIN_LENGTH_VALIDATOR = CONST_EXPR(
|
||||
providers: [MIN_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MinLengthValidator implements Validator {
|
||||
private _validator: Function;
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute("minlength") minLength: string) {
|
||||
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
|
||||
}
|
||||
|
||||
validate(c: Control): {[key: string]: any} { return this._validator(c); }
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,11 +98,40 @@ const MAX_LENGTH_VALIDATOR = CONST_EXPR(
|
||||
providers: [MAX_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MaxLengthValidator implements Validator {
|
||||
private _validator: Function;
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute("maxlength") maxLength: string) {
|
||||
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
|
||||
}
|
||||
|
||||
validate(c: Control): {[key: string]: any} { return this._validator(c); }
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A Directive that adds the `pattern` validator to any controls marked with the
|
||||
* `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value
|
||||
* as the regex to validate Control value against. Follows pattern attribute
|
||||
* semantics; i.e. regex must match entire Control value.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* <input [ngControl]="fullName" pattern="[a-zA-Z ]*">
|
||||
* ```
|
||||
*/
|
||||
const PATTERN_VALIDATOR = CONST_EXPR(
|
||||
new Provider(NG_VALIDATORS, {useExisting: forwardRef(() => PatternValidator), multi: true}));
|
||||
@Directive({
|
||||
selector: '[pattern][ngControl],[pattern][ngFormControl],[pattern][ngModel]',
|
||||
providers: [PATTERN_VALIDATOR]
|
||||
})
|
||||
export class PatternValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
|
||||
constructor(@Attribute("pattern") pattern: string) {
|
||||
this._validator = Validators.pattern(pattern);
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import {Injectable} from 'angular2/core';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isArray, CONST_EXPR, Type} from 'angular2/src/facade/lang';
|
||||
import * as modelModule from './model';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
@ -56,16 +57,18 @@ export class FormBuilder {
|
||||
group(controlsConfig: {[key: string]: any},
|
||||
extra: {[key: string]: any} = null): modelModule.ControlGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null;
|
||||
var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
|
||||
var asyncValidator = isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
|
||||
var optionals = <{[key: string]: boolean}>(
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
|
||||
var asyncValidator: AsyncValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
|
||||
return new modelModule.ControlGroup(controls, optionals, validator, asyncValidator);
|
||||
}
|
||||
/**
|
||||
* Construct a new {@link Control} with the given `value`,`validator`, and `asyncValidator`.
|
||||
*/
|
||||
control(value: Object, validator: Function = null,
|
||||
asyncValidator: Function = null): modelModule.Control {
|
||||
control(value: Object, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.Control {
|
||||
return new modelModule.Control(value, validator, asyncValidator);
|
||||
}
|
||||
|
||||
@ -73,16 +76,17 @@ export class FormBuilder {
|
||||
* Construct an array of {@link Control}s from the given `controlsConfig` array of
|
||||
* configuration, with the given optional `validator` and `asyncValidator`.
|
||||
*/
|
||||
array(controlsConfig: any[], validator: Function = null,
|
||||
asyncValidator: Function = null): modelModule.ControlArray {
|
||||
array(controlsConfig: any[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.ControlArray {
|
||||
var controls = controlsConfig.map(c => this._createControl(c));
|
||||
return new modelModule.ControlArray(controls, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceControls(controlsConfig: any): {[key: string]: modelModule.AbstractControl} {
|
||||
_reduceControls(controlsConfig: {[k: string]:
|
||||
any}): {[key: string]: modelModule.AbstractControl} {
|
||||
var controls: {[key: string]: modelModule.AbstractControl} = {};
|
||||
StringMapWrapper.forEach(controlsConfig, (controlConfig, controlName) => {
|
||||
StringMapWrapper.forEach(controlsConfig, (controlConfig: any, controlName: string) => {
|
||||
controls[controlName] = this._createControl(controlConfig);
|
||||
});
|
||||
return controls;
|
||||
@ -97,12 +101,12 @@ export class FormBuilder {
|
||||
|
||||
} else if (isArray(controlConfig)) {
|
||||
var value = controlConfig[0];
|
||||
var validator = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
var asyncValidator = controlConfig.length > 2 ? controlConfig[2] : null;
|
||||
var validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
var asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null;
|
||||
return this.control(value, validator, asyncValidator);
|
||||
|
||||
} else {
|
||||
return this.control(controlConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import {StringWrapper, isPresent, isBlank, normalizeBool} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isBlank, normalizeBool} from 'angular2/src/facade/lang';
|
||||
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/promise';
|
||||
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
/**
|
||||
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
|
||||
@ -62,9 +63,9 @@ export abstract class AbstractControl {
|
||||
private _pristine: boolean = true;
|
||||
private _touched: boolean = false;
|
||||
private _parent: ControlGroup | ControlArray;
|
||||
private _asyncValidationSubscription;
|
||||
private _asyncValidationSubscription: any;
|
||||
|
||||
constructor(public validator: Function, public asyncValidator: Function) {}
|
||||
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
|
||||
|
||||
get value(): any { return this._value; }
|
||||
|
||||
@ -137,15 +138,17 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
private _runValidator() { return isPresent(this.validator) ? this.validator(this) : null; }
|
||||
private _runValidator(): {[key: string]: any} {
|
||||
return isPresent(this.validator) ? this.validator(this) : null;
|
||||
}
|
||||
|
||||
private _runAsyncValidator(emitEvent: boolean): void {
|
||||
if (isPresent(this.asyncValidator)) {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
var obs = toObservable(this.asyncValidator(this));
|
||||
this._asyncValidationSubscription =
|
||||
ObservableWrapper.subscribe(obs, res => this.setErrors(res, {emitEvent: emitEvent}));
|
||||
this._asyncValidationSubscription = ObservableWrapper.subscribe(
|
||||
obs, (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +271,8 @@ export class Control extends AbstractControl {
|
||||
/** @internal */
|
||||
_onChange: Function;
|
||||
|
||||
constructor(value: any = null, validator: Function = null, asyncValidator: Function = null) {
|
||||
constructor(value: any = null, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._value = value;
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
@ -331,8 +335,8 @@ export class ControlGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(public controls: {[key: string]: AbstractControl},
|
||||
optionals: {[key: string]: boolean} = null, validator: Function = null,
|
||||
asyncValidator: Function = null) {
|
||||
optionals: {[key: string]: boolean} = null, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._initObservables();
|
||||
@ -379,7 +383,8 @@ export class ControlGroup extends AbstractControl {
|
||||
|
||||
/** @internal */
|
||||
_setParentForControls() {
|
||||
StringMapWrapper.forEach(this.controls, (control, name) => { control.setParent(this); });
|
||||
StringMapWrapper.forEach(
|
||||
this.controls, (control: AbstractControl, name: string) => { control.setParent(this); });
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -388,7 +393,7 @@ export class ControlGroup extends AbstractControl {
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
var res = false;
|
||||
StringMapWrapper.forEach(this.controls, (control, name) => {
|
||||
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => {
|
||||
res = res || (this.contains(name) && control.status == status);
|
||||
});
|
||||
return res;
|
||||
@ -396,16 +401,17 @@ export class ControlGroup extends AbstractControl {
|
||||
|
||||
/** @internal */
|
||||
_reduceValue() {
|
||||
return this._reduceChildren({}, (acc, control, name) => {
|
||||
acc[name] = control.value;
|
||||
return acc;
|
||||
});
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.value;
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceChildren(initValue: any, fn: Function) {
|
||||
var res = initValue;
|
||||
StringMapWrapper.forEach(this.controls, (control, name) => {
|
||||
StringMapWrapper.forEach(this.controls, (control: AbstractControl, name: string) => {
|
||||
if (this._included(name)) {
|
||||
res = fn(res, control, name);
|
||||
}
|
||||
@ -442,8 +448,8 @@ export class ControlGroup extends AbstractControl {
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*/
|
||||
export class ControlArray extends AbstractControl {
|
||||
constructor(public controls: AbstractControl[], validator: Function = null,
|
||||
asyncValidator: Function = null) {
|
||||
constructor(public controls: AbstractControl[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
|
@ -5,6 +5,7 @@ import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {OpaqueToken} from 'angular2/core';
|
||||
|
||||
import * as modelModule from './model';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link Control}s in a form.
|
||||
@ -43,7 +44,7 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: modelModule.Control): {[key: string]: boolean} {
|
||||
static required(control: modelModule.AbstractControl): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == "") ?
|
||||
{"required": true} :
|
||||
null;
|
||||
@ -52,8 +53,8 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a value of a minimum length.
|
||||
*/
|
||||
static minLength(minLength: number): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
static minLength(minLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length < minLength ?
|
||||
@ -65,8 +66,8 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a value of a maximum length.
|
||||
*/
|
||||
static maxLength(maxLength: number): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
static maxLength(maxLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length > maxLength ?
|
||||
@ -75,16 +76,29 @@ export class Validators {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator that requires a control to match a regex to its value.
|
||||
*/
|
||||
static pattern(pattern: string): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
let regex = new RegExp(`^${pattern}$`);
|
||||
let v: string = control.value;
|
||||
return regex.test(v) ? null :
|
||||
{"pattern": {"requiredPattern": `^${pattern}$`, "actualValue": v}};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op validator.
|
||||
*/
|
||||
static nullValidator(c: any): {[key: string]: boolean} { return null; }
|
||||
static nullValidator(c: modelModule.AbstractControl): {[key: string]: boolean} { return null; }
|
||||
|
||||
/**
|
||||
* Compose multiple validators into a single function that returns the union
|
||||
* of the individual error maps.
|
||||
*/
|
||||
static compose(validators: Function[]): Function {
|
||||
static compose(validators: ValidatorFn[]): ValidatorFn {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
@ -94,13 +108,13 @@ export class Validators {
|
||||
};
|
||||
}
|
||||
|
||||
static composeAsync(validators: Function[]): Function {
|
||||
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: modelModule.AbstractControl) {
|
||||
let promises = _executeValidators(control, presentValidators).map(_convertToPromise);
|
||||
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
|
||||
return PromiseWrapper.all(promises).then(_mergeErrors);
|
||||
};
|
||||
}
|
||||
@ -110,13 +124,20 @@ function _convertToPromise(obj: any): any {
|
||||
return PromiseWrapper.isPromise(obj) ? obj : ObservableWrapper.toPromise(obj);
|
||||
}
|
||||
|
||||
function _executeValidators(control: modelModule.AbstractControl, validators: Function[]): any[] {
|
||||
function _executeValidators(control: modelModule.AbstractControl,
|
||||
validators: ValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _executeAsyncValidators(control: modelModule.AbstractControl,
|
||||
validators: AsyncValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} {
|
||||
var res = arrayOfErrors.reduce((res, errors) => {
|
||||
return isPresent(errors) ? StringMapWrapper.merge(<any>res, <any>errors) : res;
|
||||
}, {});
|
||||
var res: {[key: string]: any} =
|
||||
arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => {
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
}, {});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
|
@ -3,14 +3,6 @@
|
||||
* @description
|
||||
* This module provides a set of common Pipes.
|
||||
*/
|
||||
import {AsyncPipe} from './pipes/async_pipe';
|
||||
import {UpperCasePipe} from './pipes/uppercase_pipe';
|
||||
import {LowerCasePipe} from './pipes/lowercase_pipe';
|
||||
import {JsonPipe} from './pipes/json_pipe';
|
||||
import {SlicePipe} from './pipes/slice_pipe';
|
||||
import {DatePipe} from './pipes/date_pipe';
|
||||
import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
|
||||
export {AsyncPipe} from './pipes/async_pipe';
|
||||
export {DatePipe} from './pipes/date_pipe';
|
||||
@ -19,22 +11,7 @@ export {SlicePipe} from './pipes/slice_pipe';
|
||||
export {LowerCasePipe} from './pipes/lowercase_pipe';
|
||||
export {NumberPipe, DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
|
||||
export {UpperCasePipe} from './pipes/uppercase_pipe';
|
||||
|
||||
/**
|
||||
* A collection of Angular core pipes that are likely to be used in each and every
|
||||
* application.
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
|
||||
* property of the `@Component` or `@View` decorators.
|
||||
*/
|
||||
export const COMMON_PIPES = CONST_EXPR([
|
||||
AsyncPipe,
|
||||
UpperCasePipe,
|
||||
LowerCasePipe,
|
||||
JsonPipe,
|
||||
SlicePipe,
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
CurrencyPipe,
|
||||
DatePipe
|
||||
]);
|
||||
export {ReplacePipe} from './pipes/replace_pipe';
|
||||
export {I18nPluralPipe} from './pipes/i18n_plural_pipe';
|
||||
export {I18nSelectPipe} from './pipes/i18n_select_pipe';
|
||||
export {COMMON_PIPES} from './pipes/common_pipes';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang';
|
||||
import {Promise, ObservableWrapper, Observable, EventEmitter} from 'angular2/src/facade/async';
|
||||
import {ObservableWrapper, Observable, EventEmitter} from 'angular2/src/facade/async';
|
||||
import {
|
||||
Pipe,
|
||||
Injectable,
|
||||
@ -22,7 +22,7 @@ class ObservableStrategy {
|
||||
}
|
||||
|
||||
class PromiseStrategy {
|
||||
createSubscription(async: any, updateLatestValue: any): any {
|
||||
createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): any {
|
||||
return async.then(updateLatestValue);
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ class PromiseStrategy {
|
||||
|
||||
var _promiseStrategy = new PromiseStrategy();
|
||||
var _observableStrategy = new ObservableStrategy();
|
||||
|
||||
var __unused: Promise<any>; // avoid unused import when Promise union types are erased
|
||||
|
||||
/**
|
||||
* The `async` pipe subscribes to an Observable or Promise and returns the latest value it has
|
||||
@ -102,8 +102,8 @@ export class AsyncPipe implements PipeTransform, OnDestroy {
|
||||
_subscribe(obj: Observable<any>| Promise<any>| EventEmitter<any>): void {
|
||||
this._obj = obj;
|
||||
this._strategy = this._selectStrategy(obj);
|
||||
this._subscription =
|
||||
this._strategy.createSubscription(obj, value => this._updateLatestValue(obj, value));
|
||||
this._subscription = this._strategy.createSubscription(
|
||||
obj, (value: Object) => this._updateLatestValue(obj, value));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -10,6 +10,9 @@ import {JsonPipe} from './json_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
import {DatePipe} from './date_pipe';
|
||||
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
|
||||
import {ReplacePipe} from './replace_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
@ -17,7 +20,7 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
* application.
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
|
||||
* property of the `@Component` or `@View` decorators.
|
||||
* property of the `@Component` decorator.
|
||||
*/
|
||||
export const COMMON_PIPES = CONST_EXPR([
|
||||
AsyncPipe,
|
||||
@ -28,5 +31,8 @@ export const COMMON_PIPES = CONST_EXPR([
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
CurrencyPipe,
|
||||
DatePipe
|
||||
DatePipe,
|
||||
ReplacePipe,
|
||||
I18nPluralPipe,
|
||||
I18nSelectPipe
|
||||
]);
|
||||
|
62
modules/angular2/src/common/pipes/i18n_plural_pipe.ts
Normal file
62
modules/angular2/src/common/pipes/i18n_plural_pipe.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
CONST,
|
||||
isStringMap,
|
||||
StringWrapper,
|
||||
isPresent,
|
||||
RegExpWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {Injectable, PipeTransform, Pipe} from 'angular2/core';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
var interpolationExp: RegExp = RegExpWrapper.create('#');
|
||||
|
||||
/**
|
||||
*
|
||||
* Maps a value to a string that pluralizes the value properly.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* expression | i18nPlural:mapping
|
||||
*
|
||||
* where `expression` is a number and `mapping` is an object that indicates the proper text for
|
||||
* when the `expression` evaluates to 0, 1, or some other number. You can interpolate the actual
|
||||
* value into the text using the `#` sign.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* <div>
|
||||
* {{ messages.length | i18nPlural: messageMapping }}
|
||||
* </div>
|
||||
*
|
||||
* class MyApp {
|
||||
* messages: any[];
|
||||
* messageMapping: any = {
|
||||
* '=0': 'No messages.',
|
||||
* '=1': 'One message.',
|
||||
* 'other': '# messages.'
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'i18nPlural', pure: true})
|
||||
@Injectable()
|
||||
export class I18nPluralPipe implements PipeTransform {
|
||||
transform(value: number, args: any[] = null): string {
|
||||
var key: string;
|
||||
var valueStr: string;
|
||||
var pluralMap: {[count: string]: string} = <{[count: string]: string}>(args[0]);
|
||||
|
||||
if (!isStringMap(pluralMap)) {
|
||||
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
|
||||
}
|
||||
|
||||
key = value === 0 || value === 1 ? `=${value}` : 'other';
|
||||
valueStr = isPresent(value) ? value.toString() : '';
|
||||
|
||||
return StringWrapper.replaceAll(pluralMap[key], interpolationExp, valueStr);
|
||||
}
|
||||
}
|
47
modules/angular2/src/common/pipes/i18n_select_pipe.ts
Normal file
47
modules/angular2/src/common/pipes/i18n_select_pipe.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import {CONST, isStringMap} from 'angular2/src/facade/lang';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {Injectable, PipeTransform, Pipe} from 'angular2/core';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
/**
|
||||
*
|
||||
* Generic selector that displays the string that matches the current value.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* expression | i18nSelect:mapping
|
||||
*
|
||||
* where `mapping` is an object that indicates the text that should be displayed
|
||||
* for different values of the provided `expression`.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* <div>
|
||||
* {{ gender | i18nSelect: inviteMap }}
|
||||
* </div>
|
||||
*
|
||||
* class MyApp {
|
||||
* gender: string = 'male';
|
||||
* inviteMap: any = {
|
||||
* 'male': 'Invite her.',
|
||||
* 'female': 'Invite him.',
|
||||
* 'other': 'Invite them.'
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
@Pipe({name: 'i18nSelect', pure: true})
|
||||
@Injectable()
|
||||
export class I18nSelectPipe implements PipeTransform {
|
||||
transform(value: string, args: any[] = null): string {
|
||||
var mapping: {[key: string]: string} = <{[count: string]: string}>(args[0]);
|
||||
if (!isStringMap(mapping)) {
|
||||
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
|
||||
}
|
||||
|
||||
return StringMapWrapper.contains(mapping, value) ? mapping[value] : mapping['other'];
|
||||
}
|
||||
}
|
91
modules/angular2/src/common/pipes/replace_pipe.ts
Normal file
91
modules/angular2/src/common/pipes/replace_pipe.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import {
|
||||
isBlank,
|
||||
isString,
|
||||
isNumber,
|
||||
isFunction,
|
||||
RegExpWrapper,
|
||||
StringWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {Injectable, PipeTransform, Pipe} from 'angular2/core';
|
||||
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
|
||||
/**
|
||||
* Creates a new String with some or all of the matches of a pattern replaced by
|
||||
* a replacement.
|
||||
*
|
||||
* The pattern to be matched is specified by the 'pattern' parameter.
|
||||
*
|
||||
* The replacement to be set is specified by the 'replacement' parameter.
|
||||
*
|
||||
* An optional 'flags' parameter can be set.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
* expression | replace:pattern:replacement
|
||||
*
|
||||
* All behavior is based on the expected behavior of the JavaScript API
|
||||
* String.prototype.replace() function.
|
||||
*
|
||||
* Where the input expression is a [String] or [Number] (to be treated as a string),
|
||||
* the `pattern` is a [String] or [RegExp],
|
||||
* the 'replacement' is a [String] or [Function].
|
||||
*
|
||||
* --Note--: The 'pattern' parameter will be converted to a RegExp instance. Make sure to escape the
|
||||
* string properly if you are matching for regular expression special characters like parenthesis,
|
||||
* brackets etc.
|
||||
*/
|
||||
|
||||
@Pipe({name: 'replace'})
|
||||
@Injectable()
|
||||
export class ReplacePipe implements PipeTransform {
|
||||
transform(value: any, args: any[]): any {
|
||||
if (isBlank(args) || args.length !== 2) {
|
||||
throw new BaseException('ReplacePipe requires two arguments');
|
||||
}
|
||||
|
||||
if (isBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!this._supportedInput(value)) {
|
||||
throw new InvalidPipeArgumentException(ReplacePipe, value);
|
||||
}
|
||||
|
||||
var input = value.toString();
|
||||
var pattern = args[0];
|
||||
var replacement = args[1];
|
||||
|
||||
|
||||
if (!this._supportedPattern(pattern)) {
|
||||
throw new InvalidPipeArgumentException(ReplacePipe, pattern);
|
||||
}
|
||||
if (!this._supportedReplacement(replacement)) {
|
||||
throw new InvalidPipeArgumentException(ReplacePipe, replacement);
|
||||
}
|
||||
// template fails with literal RegExp e.g /pattern/igm
|
||||
// var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern);
|
||||
|
||||
if (isFunction(replacement)) {
|
||||
var rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern;
|
||||
|
||||
return StringWrapper.replaceAllMapped(input, rgxPattern, replacement);
|
||||
}
|
||||
if (pattern instanceof RegExp) {
|
||||
// use the replaceAll variant
|
||||
return StringWrapper.replaceAll(input, pattern, replacement);
|
||||
}
|
||||
|
||||
return StringWrapper.replace(input, pattern, replacement);
|
||||
}
|
||||
|
||||
private _supportedInput(input: any): boolean { return isString(input) || isNumber(input); }
|
||||
|
||||
private _supportedPattern(pattern: any): boolean {
|
||||
return isString(pattern) || pattern instanceof RegExp;
|
||||
}
|
||||
|
||||
private _supportedReplacement(replacement: any): boolean {
|
||||
return isString(replacement) || isFunction(replacement);
|
||||
}
|
||||
}
|
@ -146,8 +146,9 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber);
|
||||
var directiveMetadata = ast.directive;
|
||||
var outputsArray = [];
|
||||
StringMapWrapper.forEach(ast.directive.outputs, (eventName, dirProperty) => outputsArray.push(
|
||||
[dirProperty, eventName]));
|
||||
StringMapWrapper.forEach(
|
||||
ast.directive.outputs,
|
||||
(eventName: string, dirProperty: string) => outputsArray.push([dirProperty, eventName]));
|
||||
var directiveRecord = new DirectiveRecord({
|
||||
directiveIndex: directiveIndex,
|
||||
callAfterContentInit:
|
||||
|
64
modules/angular2/src/compiler/chars.ts
Normal file
64
modules/angular2/src/compiler/chars.ts
Normal file
@ -0,0 +1,64 @@
|
||||
export const $EOF = 0;
|
||||
export const $TAB = 9;
|
||||
export const $LF = 10;
|
||||
export const $VTAB = 11;
|
||||
export const $FF = 12;
|
||||
export const $CR = 13;
|
||||
export const $SPACE = 32;
|
||||
export const $BANG = 33;
|
||||
export const $DQ = 34;
|
||||
export const $HASH = 35;
|
||||
export const $$ = 36;
|
||||
export const $PERCENT = 37;
|
||||
export const $AMPERSAND = 38;
|
||||
export const $SQ = 39;
|
||||
export const $LPAREN = 40;
|
||||
export const $RPAREN = 41;
|
||||
export const $STAR = 42;
|
||||
export const $PLUS = 43;
|
||||
export const $COMMA = 44;
|
||||
export const $MINUS = 45;
|
||||
export const $PERIOD = 46;
|
||||
export const $SLASH = 47;
|
||||
export const $COLON = 58;
|
||||
export const $SEMICOLON = 59;
|
||||
export const $LT = 60;
|
||||
export const $EQ = 61;
|
||||
export const $GT = 62;
|
||||
export const $QUESTION = 63;
|
||||
|
||||
export const $0 = 48;
|
||||
export const $9 = 57;
|
||||
|
||||
export const $A = 65;
|
||||
export const $E = 69;
|
||||
export const $Z = 90;
|
||||
|
||||
export const $LBRACKET = 91;
|
||||
export const $BACKSLASH = 92;
|
||||
export const $RBRACKET = 93;
|
||||
export const $CARET = 94;
|
||||
export const $_ = 95;
|
||||
|
||||
export const $a = 97;
|
||||
export const $e = 101;
|
||||
export const $f = 102;
|
||||
export const $n = 110;
|
||||
export const $r = 114;
|
||||
export const $t = 116;
|
||||
export const $u = 117;
|
||||
export const $v = 118;
|
||||
export const $z = 122;
|
||||
|
||||
export const $LBRACE = 123;
|
||||
export const $BAR = 124;
|
||||
export const $RBRACE = 125;
|
||||
export const $NBSP = 160;
|
||||
|
||||
export const $PIPE = 124;
|
||||
export const $TILDA = 126;
|
||||
export const $AT = 64;
|
||||
|
||||
export function isWhitespace(code: number): boolean {
|
||||
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
|
||||
}
|
751
modules/angular2/src/compiler/css/lexer.ts
Normal file
751
modules/angular2/src/compiler/css/lexer.ts
Normal file
@ -0,0 +1,751 @@
|
||||
import {NumberWrapper, StringWrapper, isPresent, resolveEnumToken} from "angular2/src/facade/lang";
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
|
||||
import {
|
||||
isWhitespace,
|
||||
$EOF,
|
||||
$HASH,
|
||||
$TILDA,
|
||||
$CARET,
|
||||
$PERCENT,
|
||||
$$,
|
||||
$_,
|
||||
$COLON,
|
||||
$SQ,
|
||||
$DQ,
|
||||
$EQ,
|
||||
$SLASH,
|
||||
$BACKSLASH,
|
||||
$PERIOD,
|
||||
$STAR,
|
||||
$PLUS,
|
||||
$LPAREN,
|
||||
$RPAREN,
|
||||
$LBRACE,
|
||||
$RBRACE,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$PIPE,
|
||||
$COMMA,
|
||||
$SEMICOLON,
|
||||
$MINUS,
|
||||
$BANG,
|
||||
$QUESTION,
|
||||
$AT,
|
||||
$AMPERSAND,
|
||||
$GT,
|
||||
$a,
|
||||
$A,
|
||||
$z,
|
||||
$Z,
|
||||
$0,
|
||||
$9,
|
||||
$FF,
|
||||
$CR,
|
||||
$LF,
|
||||
$VTAB
|
||||
} from "angular2/src/compiler/chars";
|
||||
|
||||
export {
|
||||
$EOF,
|
||||
$AT,
|
||||
$RBRACE,
|
||||
$LBRACE,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$LPAREN,
|
||||
$RPAREN,
|
||||
$COMMA,
|
||||
$COLON,
|
||||
$SEMICOLON,
|
||||
isWhitespace
|
||||
} from "angular2/src/compiler/chars";
|
||||
|
||||
export enum CssTokenType {
|
||||
EOF,
|
||||
String,
|
||||
Comment,
|
||||
Identifier,
|
||||
Number,
|
||||
IdentifierOrNumber,
|
||||
AtKeyword,
|
||||
Character,
|
||||
Whitespace,
|
||||
Invalid
|
||||
}
|
||||
|
||||
export enum CssLexerMode {
|
||||
ALL,
|
||||
ALL_TRACK_WS,
|
||||
SELECTOR,
|
||||
PSEUDO_SELECTOR,
|
||||
ATTRIBUTE_SELECTOR,
|
||||
AT_RULE_QUERY,
|
||||
MEDIA_QUERY,
|
||||
BLOCK,
|
||||
KEYFRAME_BLOCK,
|
||||
STYLE_BLOCK,
|
||||
STYLE_VALUE,
|
||||
STYLE_VALUE_FUNCTION,
|
||||
STYLE_CALC_FUNCTION
|
||||
}
|
||||
|
||||
export class LexedCssResult {
|
||||
constructor(public error: CssScannerError, public token: CssToken) {}
|
||||
}
|
||||
|
||||
export function generateErrorMessage(input, message, errorValue, index, row, column) {
|
||||
return `${message} at column ${row}:${column} in expression [` +
|
||||
findProblemCode(input, errorValue, index, column) + ']';
|
||||
}
|
||||
|
||||
export function findProblemCode(input, errorValue, index, column) {
|
||||
var endOfProblemLine = index;
|
||||
var current = charCode(input, index);
|
||||
while (current > 0 && !isNewline(current)) {
|
||||
current = charCode(input, ++endOfProblemLine);
|
||||
}
|
||||
var choppedString = input.substring(0, endOfProblemLine);
|
||||
var pointerPadding = "";
|
||||
for (var i = 0; i < column; i++) {
|
||||
pointerPadding += " ";
|
||||
}
|
||||
var pointerString = "";
|
||||
for (var i = 0; i < errorValue.length; i++) {
|
||||
pointerString += "^";
|
||||
}
|
||||
return choppedString + "\n" + pointerPadding + pointerString + "\n";
|
||||
}
|
||||
|
||||
export class CssToken {
|
||||
numValue: number;
|
||||
constructor(public index: number, public column: number, public line: number,
|
||||
public type: CssTokenType, public strValue: string) {
|
||||
this.numValue = charCode(strValue, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssLexer {
|
||||
scan(text: string, trackComments: boolean = false): CssScanner {
|
||||
return new CssScanner(text, trackComments);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssScannerError extends BaseException {
|
||||
public rawMessage: string;
|
||||
public message: string;
|
||||
|
||||
constructor(public token: CssToken, message) {
|
||||
super('Css Parse Error: ' + message);
|
||||
this.rawMessage = message;
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
function _trackWhitespace(mode: CssLexerMode) {
|
||||
switch (mode) {
|
||||
case CssLexerMode.SELECTOR:
|
||||
case CssLexerMode.ALL_TRACK_WS:
|
||||
case CssLexerMode.STYLE_VALUE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class CssScanner {
|
||||
peek: number;
|
||||
peekPeek: number;
|
||||
length: number = 0;
|
||||
index: number = -1;
|
||||
column: number = -1;
|
||||
line: number = 0;
|
||||
|
||||
_currentMode: CssLexerMode = CssLexerMode.BLOCK;
|
||||
_currentError: CssScannerError = null;
|
||||
|
||||
constructor(public input: string, private _trackComments: boolean = false) {
|
||||
this.length = this.input.length;
|
||||
this.peekPeek = this.peekAt(0);
|
||||
this.advance();
|
||||
}
|
||||
|
||||
getMode(): CssLexerMode { return this._currentMode; }
|
||||
|
||||
setMode(mode: CssLexerMode) {
|
||||
if (this._currentMode != mode) {
|
||||
if (_trackWhitespace(this._currentMode)) {
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
this._currentMode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
advance(): void {
|
||||
if (isNewline(this.peek)) {
|
||||
this.column = 0;
|
||||
this.line++;
|
||||
} else {
|
||||
this.column++;
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.peek = this.peekPeek;
|
||||
this.peekPeek = this.peekAt(this.index + 1);
|
||||
}
|
||||
|
||||
peekAt(index): number {
|
||||
return index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, index);
|
||||
}
|
||||
|
||||
consumeEmptyStatements(): void {
|
||||
this.consumeWhitespace();
|
||||
while (this.peek == $SEMICOLON) {
|
||||
this.advance();
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
}
|
||||
|
||||
consumeWhitespace(): void {
|
||||
while (isWhitespace(this.peek) || isNewline(this.peek)) {
|
||||
this.advance();
|
||||
if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) {
|
||||
this.advance(); // /
|
||||
this.advance(); // *
|
||||
while (!isCommentEnd(this.peek, this.peekPeek)) {
|
||||
if (this.peek == $EOF) {
|
||||
this.error('Unterminated comment');
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
this.advance(); // *
|
||||
this.advance(); // /
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
consume(type: CssTokenType, value: string = null): LexedCssResult {
|
||||
var mode = this._currentMode;
|
||||
this.setMode(CssLexerMode.ALL);
|
||||
|
||||
var previousIndex = this.index;
|
||||
var previousLine = this.line;
|
||||
var previousColumn = this.column;
|
||||
|
||||
var output = this.scan();
|
||||
|
||||
// just incase the inner scan method returned an error
|
||||
if (isPresent(output.error)) {
|
||||
this.setMode(mode);
|
||||
return output;
|
||||
}
|
||||
|
||||
var next = output.token;
|
||||
if (!isPresent(next)) {
|
||||
next = new CssToken(0, 0, 0, CssTokenType.EOF, "end of file");
|
||||
}
|
||||
|
||||
var isMatchingType;
|
||||
if (type == CssTokenType.IdentifierOrNumber) {
|
||||
// TODO (matsko): implement array traversal for lookup here
|
||||
isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier;
|
||||
} else {
|
||||
isMatchingType = next.type == type;
|
||||
}
|
||||
|
||||
// before throwing the error we need to bring back the former
|
||||
// mode so that the parser can recover...
|
||||
this.setMode(mode);
|
||||
|
||||
var error = null;
|
||||
if (!isMatchingType || (isPresent(value) && value != next.strValue)) {
|
||||
var errorMessage = resolveEnumToken(CssTokenType, next.type) + " does not match expected " +
|
||||
resolveEnumToken(CssTokenType, type) + " value";
|
||||
|
||||
if (isPresent(value)) {
|
||||
errorMessage += ' ("' + next.strValue + '" should match "' + value + '")';
|
||||
}
|
||||
|
||||
error = new CssScannerError(
|
||||
next, generateErrorMessage(this.input, errorMessage, next.strValue, previousIndex,
|
||||
previousLine, previousColumn));
|
||||
}
|
||||
|
||||
return new LexedCssResult(error, next);
|
||||
}
|
||||
|
||||
|
||||
scan(): LexedCssResult {
|
||||
var trackWS = _trackWhitespace(this._currentMode);
|
||||
if (this.index == 0 && !trackWS) { // first scan
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
|
||||
var token = this._scan();
|
||||
if (token == null) return null;
|
||||
|
||||
var error = this._currentError;
|
||||
this._currentError = null;
|
||||
|
||||
if (!trackWS) {
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
return new LexedCssResult(error, token);
|
||||
}
|
||||
|
||||
_scan(): CssToken {
|
||||
var peek = this.peek;
|
||||
var peekPeek = this.peekPeek;
|
||||
if (peek == $EOF) return null;
|
||||
|
||||
if (isCommentStart(peek, peekPeek)) {
|
||||
// even if comments are not tracked we still lex the
|
||||
// comment so we can move the pointer forward
|
||||
var commentToken = this.scanComment();
|
||||
if (this._trackComments) {
|
||||
return commentToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (_trackWhitespace(this._currentMode) && (isWhitespace(peek) || isNewline(peek))) {
|
||||
return this.scanWhitespace();
|
||||
}
|
||||
|
||||
peek = this.peek;
|
||||
peekPeek = this.peekPeek;
|
||||
if (peek == $EOF) return null;
|
||||
|
||||
if (isStringStart(peek, peekPeek)) {
|
||||
return this.scanString();
|
||||
}
|
||||
|
||||
// something like url(cool)
|
||||
if (this._currentMode == CssLexerMode.STYLE_VALUE_FUNCTION) {
|
||||
return this.scanCssValueFunction();
|
||||
}
|
||||
|
||||
var isModifier = peek == $PLUS || peek == $MINUS;
|
||||
var digitA = isModifier ? false : isDigit(peek);
|
||||
var digitB = isDigit(peekPeek);
|
||||
if (digitA || (isModifier && (peekPeek == $PERIOD || digitB)) || (peek == $PERIOD && digitB)) {
|
||||
return this.scanNumber();
|
||||
}
|
||||
|
||||
if (peek == $AT) {
|
||||
return this.scanAtExpression();
|
||||
}
|
||||
|
||||
if (isIdentifierStart(peek, peekPeek)) {
|
||||
return this.scanIdentifier();
|
||||
}
|
||||
|
||||
if (isValidCssCharacter(peek, this._currentMode)) {
|
||||
return this.scanCharacter();
|
||||
}
|
||||
|
||||
return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`);
|
||||
}
|
||||
|
||||
scanComment() {
|
||||
if (this.assertCondition(isCommentStart(this.peek, this.peekPeek),
|
||||
"Expected comment start value")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
var startingLine = this.line;
|
||||
|
||||
this.advance(); // /
|
||||
this.advance(); // *
|
||||
|
||||
while (!isCommentEnd(this.peek, this.peekPeek)) {
|
||||
if (this.peek == $EOF) {
|
||||
this.error('Unterminated comment');
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
|
||||
this.advance(); // *
|
||||
this.advance(); // /
|
||||
|
||||
var str = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, startingLine, CssTokenType.Comment, str);
|
||||
}
|
||||
|
||||
scanWhitespace() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
var startingLine = this.line;
|
||||
while (isWhitespace(this.peek) && this.peek != $EOF) {
|
||||
this.advance();
|
||||
}
|
||||
var str = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, startingLine, CssTokenType.Whitespace, str);
|
||||
}
|
||||
|
||||
scanString() {
|
||||
if (this.assertCondition(isStringStart(this.peek, this.peekPeek),
|
||||
"Unexpected non-string starting value")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var target = this.peek;
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
var startingLine = this.line;
|
||||
var previous = target;
|
||||
this.advance();
|
||||
|
||||
while (!isCharMatch(target, previous, this.peek)) {
|
||||
if (this.peek == $EOF || isNewline(this.peek)) {
|
||||
this.error('Unterminated quote');
|
||||
}
|
||||
previous = this.peek;
|
||||
this.advance();
|
||||
}
|
||||
|
||||
if (this.assertCondition(this.peek == target, "Unterminated quote")) {
|
||||
return null;
|
||||
}
|
||||
this.advance();
|
||||
|
||||
var str = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, startingLine, CssTokenType.String, str);
|
||||
}
|
||||
|
||||
scanNumber() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
if (this.peek == $PLUS || this.peek == $MINUS) {
|
||||
this.advance();
|
||||
}
|
||||
var periodUsed = false;
|
||||
while (isDigit(this.peek) || this.peek == $PERIOD) {
|
||||
if (this.peek == $PERIOD) {
|
||||
if (periodUsed) {
|
||||
this.error('Unexpected use of a second period value');
|
||||
}
|
||||
periodUsed = true;
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
var strValue = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Number, strValue);
|
||||
}
|
||||
|
||||
scanIdentifier() {
|
||||
if (this.assertCondition(isIdentifierStart(this.peek, this.peekPeek),
|
||||
'Expected identifier starting value')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
while (isIdentifierPart(this.peek)) {
|
||||
this.advance();
|
||||
}
|
||||
var strValue = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
|
||||
}
|
||||
|
||||
scanCssValueFunction() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
while (this.peek != $EOF && this.peek != $RPAREN) {
|
||||
this.advance();
|
||||
}
|
||||
var strValue = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
|
||||
}
|
||||
|
||||
scanCharacter() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
if (this.assertCondition(isValidCssCharacter(this.peek, this._currentMode),
|
||||
charStr(this.peek) + ' is not a valid CSS character')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var c = this.input.substring(start, start + 1);
|
||||
this.advance();
|
||||
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Character, c);
|
||||
}
|
||||
|
||||
scanAtExpression() {
|
||||
if (this.assertCondition(this.peek == $AT, 'Expected @ value')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
this.advance();
|
||||
if (isIdentifierStart(this.peek, this.peekPeek)) {
|
||||
var ident = this.scanIdentifier();
|
||||
var strValue = '@' + ident.strValue;
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.AtKeyword, strValue);
|
||||
} else {
|
||||
return this.scanCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
assertCondition(status: boolean, errorMessage: string): boolean {
|
||||
if (!status) {
|
||||
this.error(errorMessage);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
error(message: string, errorTokenValue: string = null, doNotAdvance: boolean = false): CssToken {
|
||||
var index: number = this.index;
|
||||
var column: number = this.column;
|
||||
var line: number = this.line;
|
||||
errorTokenValue =
|
||||
isPresent(errorTokenValue) ? errorTokenValue : StringWrapper.fromCharCode(this.peek);
|
||||
var invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue);
|
||||
var errorMessage =
|
||||
generateErrorMessage(this.input, message, errorTokenValue, index, line, column);
|
||||
if (!doNotAdvance) {
|
||||
this.advance();
|
||||
}
|
||||
this._currentError = new CssScannerError(invalidToken, errorMessage);
|
||||
return invalidToken;
|
||||
}
|
||||
}
|
||||
|
||||
function isAtKeyword(current: CssToken, next: CssToken): boolean {
|
||||
return current.numValue == $AT && next.type == CssTokenType.Identifier;
|
||||
}
|
||||
|
||||
function isCharMatch(target: number, previous: number, code: number) {
|
||||
return code == target && previous != $BACKSLASH;
|
||||
}
|
||||
|
||||
function isDigit(code: number): boolean {
|
||||
return $0 <= code && code <= $9;
|
||||
}
|
||||
|
||||
function isCommentStart(code: number, next: number) {
|
||||
return code == $SLASH && next == $STAR;
|
||||
}
|
||||
|
||||
function isCommentEnd(code: number, next: number) {
|
||||
return code == $STAR && next == $SLASH;
|
||||
}
|
||||
|
||||
function isStringStart(code: number, next: number): boolean {
|
||||
var target = code;
|
||||
if (target == $BACKSLASH) {
|
||||
target = next;
|
||||
}
|
||||
return target == $DQ || target == $SQ;
|
||||
}
|
||||
|
||||
function isIdentifierStart(code: number, next: number): boolean {
|
||||
var target = code;
|
||||
if (target == $MINUS) {
|
||||
target = next;
|
||||
}
|
||||
|
||||
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
|
||||
target == $MINUS || target == $_;
|
||||
}
|
||||
|
||||
function isIdentifierPart(target: number) {
|
||||
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
|
||||
target == $MINUS || target == $_ || isDigit(target);
|
||||
}
|
||||
|
||||
function isValidPseudoSelectorCharacter(code: number) {
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidKeyframeBlockCharacter(code: number) {
|
||||
return code == $PERCENT;
|
||||
}
|
||||
|
||||
function isValidAttributeSelectorCharacter(code: number) {
|
||||
// value^*|$~=something
|
||||
switch (code) {
|
||||
case $$:
|
||||
case $PIPE:
|
||||
case $CARET:
|
||||
case $TILDA:
|
||||
case $STAR:
|
||||
case $EQ:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidSelectorCharacter(code: number) {
|
||||
// selector [ key = value ]
|
||||
// IDENT C IDENT C IDENT C
|
||||
// #id, .class, *+~>
|
||||
// tag:PSEUDO
|
||||
switch (code) {
|
||||
case $HASH:
|
||||
case $PERIOD:
|
||||
case $TILDA:
|
||||
case $STAR:
|
||||
case $PLUS:
|
||||
case $GT:
|
||||
case $COLON:
|
||||
case $PIPE:
|
||||
case $COMMA:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidStyleBlockCharacter(code: number) {
|
||||
// key:value;
|
||||
// key:calc(something ... )
|
||||
switch (code) {
|
||||
case $HASH:
|
||||
case $SEMICOLON:
|
||||
case $COLON:
|
||||
case $PERCENT:
|
||||
case $SLASH:
|
||||
case $BACKSLASH:
|
||||
case $BANG:
|
||||
case $PERIOD:
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidMediaQueryRuleCharacter(code: number) {
|
||||
// (min-width: 7.5em) and (orientation: landscape)
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $COLON:
|
||||
case $PERCENT:
|
||||
case $PERIOD:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidAtRuleCharacter(code: number) {
|
||||
// @document url(http://www.w3.org/page?something=on#hash),
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $COLON:
|
||||
case $PERCENT:
|
||||
case $PERIOD:
|
||||
case $SLASH:
|
||||
case $BACKSLASH:
|
||||
case $HASH:
|
||||
case $EQ:
|
||||
case $QUESTION:
|
||||
case $AMPERSAND:
|
||||
case $STAR:
|
||||
case $COMMA:
|
||||
case $MINUS:
|
||||
case $PLUS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidStyleFunctionCharacter(code: number) {
|
||||
switch (code) {
|
||||
case $PERIOD:
|
||||
case $MINUS:
|
||||
case $PLUS:
|
||||
case $STAR:
|
||||
case $SLASH:
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $COMMA:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidBlockCharacter(code: number) {
|
||||
// @something { }
|
||||
// IDENT
|
||||
return code == $AT;
|
||||
}
|
||||
|
||||
function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
|
||||
switch (mode) {
|
||||
case CssLexerMode.ALL:
|
||||
case CssLexerMode.ALL_TRACK_WS:
|
||||
return true;
|
||||
|
||||
case CssLexerMode.SELECTOR:
|
||||
return isValidSelectorCharacter(code);
|
||||
|
||||
case CssLexerMode.PSEUDO_SELECTOR:
|
||||
return isValidPseudoSelectorCharacter(code);
|
||||
|
||||
case CssLexerMode.ATTRIBUTE_SELECTOR:
|
||||
return isValidAttributeSelectorCharacter(code);
|
||||
|
||||
case CssLexerMode.MEDIA_QUERY:
|
||||
return isValidMediaQueryRuleCharacter(code);
|
||||
|
||||
case CssLexerMode.AT_RULE_QUERY:
|
||||
return isValidAtRuleCharacter(code);
|
||||
|
||||
case CssLexerMode.KEYFRAME_BLOCK:
|
||||
return isValidKeyframeBlockCharacter(code);
|
||||
|
||||
case CssLexerMode.STYLE_BLOCK:
|
||||
case CssLexerMode.STYLE_VALUE:
|
||||
return isValidStyleBlockCharacter(code);
|
||||
|
||||
case CssLexerMode.STYLE_CALC_FUNCTION:
|
||||
return isValidStyleFunctionCharacter(code);
|
||||
|
||||
case CssLexerMode.BLOCK:
|
||||
return isValidBlockCharacter(code);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function charCode(input, index): number {
|
||||
return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index);
|
||||
}
|
||||
|
||||
function charStr(code: number): string {
|
||||
return StringWrapper.fromCharCode(code);
|
||||
}
|
||||
|
||||
export function isNewline(code): boolean {
|
||||
switch (code) {
|
||||
case $FF:
|
||||
case $CR:
|
||||
case $LF:
|
||||
case $VTAB:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
721
modules/angular2/src/compiler/css/parser.ts
Normal file
721
modules/angular2/src/compiler/css/parser.ts
Normal file
@ -0,0 +1,721 @@
|
||||
import {
|
||||
ParseSourceSpan,
|
||||
ParseSourceFile,
|
||||
ParseLocation,
|
||||
ParseError
|
||||
} from "angular2/src/compiler/parse_util";
|
||||
|
||||
import {
|
||||
bitWiseOr,
|
||||
bitWiseAnd,
|
||||
NumberWrapper,
|
||||
StringWrapper,
|
||||
isPresent
|
||||
} from "angular2/src/facade/lang";
|
||||
|
||||
import {
|
||||
CssLexerMode,
|
||||
CssToken,
|
||||
CssTokenType,
|
||||
CssScanner,
|
||||
CssScannerError,
|
||||
generateErrorMessage,
|
||||
$AT,
|
||||
$EOF,
|
||||
$RBRACE,
|
||||
$LBRACE,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$LPAREN,
|
||||
$RPAREN,
|
||||
$COMMA,
|
||||
$COLON,
|
||||
$SEMICOLON,
|
||||
isNewline
|
||||
} from "angular2/src/compiler/css/lexer";
|
||||
|
||||
export {CssToken} from "angular2/src/compiler/css/lexer";
|
||||
|
||||
export enum BlockType {
|
||||
Import,
|
||||
Charset,
|
||||
Namespace,
|
||||
Supports,
|
||||
Keyframes,
|
||||
MediaQuery,
|
||||
Selector,
|
||||
FontFace,
|
||||
Page,
|
||||
Document,
|
||||
Viewport,
|
||||
Unsupported
|
||||
}
|
||||
|
||||
const EOF_DELIM = 1;
|
||||
const RBRACE_DELIM = 2;
|
||||
const LBRACE_DELIM = 4;
|
||||
const COMMA_DELIM = 8;
|
||||
const COLON_DELIM = 16;
|
||||
const SEMICOLON_DELIM = 32;
|
||||
const NEWLINE_DELIM = 64;
|
||||
const RPAREN_DELIM = 128;
|
||||
|
||||
function mergeTokens(tokens: CssToken[], separator: string = ""): CssToken {
|
||||
var mainToken = tokens[0];
|
||||
var str = mainToken.strValue;
|
||||
for (var i = 1; i < tokens.length; i++) {
|
||||
str += separator + tokens[i].strValue;
|
||||
}
|
||||
|
||||
return new CssToken(mainToken.index, mainToken.column, mainToken.line, mainToken.type, str);
|
||||
}
|
||||
|
||||
function getDelimFromToken(token: CssToken): number {
|
||||
return getDelimFromCharacter(token.numValue);
|
||||
}
|
||||
|
||||
function getDelimFromCharacter(code: number): number {
|
||||
switch (code) {
|
||||
case $EOF:
|
||||
return EOF_DELIM;
|
||||
case $COMMA:
|
||||
return COMMA_DELIM;
|
||||
case $COLON:
|
||||
return COLON_DELIM;
|
||||
case $SEMICOLON:
|
||||
return SEMICOLON_DELIM;
|
||||
case $RBRACE:
|
||||
return RBRACE_DELIM;
|
||||
case $LBRACE:
|
||||
return LBRACE_DELIM;
|
||||
case $RPAREN:
|
||||
return RPAREN_DELIM;
|
||||
default:
|
||||
return isNewline(code) ? NEWLINE_DELIM : 0;
|
||||
}
|
||||
}
|
||||
|
||||
function characterContainsDelimiter(code: number, delimiters: number) {
|
||||
return bitWiseAnd([getDelimFromCharacter(code), delimiters]) > 0;
|
||||
}
|
||||
|
||||
export class CssAST {
|
||||
visit(visitor: CssASTVisitor, context?: any): void {}
|
||||
}
|
||||
|
||||
export interface CssASTVisitor {
|
||||
visitCssValue(ast: CssStyleValueAST, context?: any): void;
|
||||
visitInlineCssRule(ast: CssInlineRuleAST, context?: any): void;
|
||||
visitCssKeyframeRule(ast: CssKeyframeRuleAST, context?: any): void;
|
||||
visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAST, context?: any): void;
|
||||
visitCssMediaQueryRule(ast: CssMediaQueryRuleAST, context?: any): void;
|
||||
visitCssSelectorRule(ast: CssSelectorRuleAST, context?: any): void;
|
||||
visitCssSelector(ast: CssSelectorAST, context?: any): void;
|
||||
visitCssDefinition(ast: CssDefinitionAST, context?: any): void;
|
||||
visitCssBlock(ast: CssBlockAST, context?: any): void;
|
||||
visitCssStyleSheet(ast: CssStyleSheetAST, context?: any): void;
|
||||
visitUnkownRule(ast: CssUnknownTokenListAST, context?: any): void;
|
||||
}
|
||||
|
||||
export class ParsedCssResult {
|
||||
constructor(public errors: CssParseError[], public ast: CssStyleSheetAST) {}
|
||||
}
|
||||
|
||||
export class CssParser {
|
||||
private _errors: CssParseError[] = [];
|
||||
private _file: ParseSourceFile;
|
||||
|
||||
constructor(private _scanner: CssScanner, private _fileName: string) {
|
||||
this._file = new ParseSourceFile(this._scanner.input, _fileName);
|
||||
}
|
||||
|
||||
_resolveBlockType(token: CssToken): BlockType {
|
||||
switch (token.strValue) {
|
||||
case '@-o-keyframes':
|
||||
case '@-moz-keyframes':
|
||||
case '@-webkit-keyframes':
|
||||
case '@keyframes':
|
||||
return BlockType.Keyframes;
|
||||
|
||||
case '@charset':
|
||||
return BlockType.Charset;
|
||||
|
||||
case '@import':
|
||||
return BlockType.Import;
|
||||
|
||||
case '@namespace':
|
||||
return BlockType.Namespace;
|
||||
|
||||
case '@page':
|
||||
return BlockType.Page;
|
||||
|
||||
case '@document':
|
||||
return BlockType.Document;
|
||||
|
||||
case '@media':
|
||||
return BlockType.MediaQuery;
|
||||
|
||||
case '@font-face':
|
||||
return BlockType.FontFace;
|
||||
|
||||
case '@viewport':
|
||||
return BlockType.Viewport;
|
||||
|
||||
case '@supports':
|
||||
return BlockType.Supports;
|
||||
|
||||
default:
|
||||
return BlockType.Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
parse(): ParsedCssResult {
|
||||
var delimiters: number = EOF_DELIM;
|
||||
var ast = this._parseStyleSheet(delimiters);
|
||||
|
||||
var errors = this._errors;
|
||||
this._errors = [];
|
||||
|
||||
return new ParsedCssResult(errors, ast);
|
||||
}
|
||||
|
||||
_parseStyleSheet(delimiters): CssStyleSheetAST {
|
||||
var results = [];
|
||||
this._scanner.consumeEmptyStatements();
|
||||
while (this._scanner.peek != $EOF) {
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
results.push(this._parseRule(delimiters));
|
||||
}
|
||||
return new CssStyleSheetAST(results);
|
||||
}
|
||||
|
||||
_parseRule(delimiters: number): CssRuleAST {
|
||||
if (this._scanner.peek == $AT) {
|
||||
return this._parseAtRule(delimiters);
|
||||
}
|
||||
return this._parseSelectorRule(delimiters);
|
||||
}
|
||||
|
||||
_parseAtRule(delimiters: number): CssRuleAST {
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
|
||||
var token = this._scan();
|
||||
|
||||
this._assertCondition(token.type == CssTokenType.AtKeyword,
|
||||
`The CSS Rule ${token.strValue} is not a valid [@] rule.`, token);
|
||||
|
||||
var block, type = this._resolveBlockType(token);
|
||||
switch (type) {
|
||||
case BlockType.Charset:
|
||||
case BlockType.Namespace:
|
||||
case BlockType.Import:
|
||||
var value = this._parseValue(delimiters);
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
return new CssInlineRuleAST(type, value);
|
||||
|
||||
case BlockType.Viewport:
|
||||
case BlockType.FontFace:
|
||||
block = this._parseStyleBlock(delimiters);
|
||||
return new CssBlockRuleAST(type, block);
|
||||
|
||||
case BlockType.Keyframes:
|
||||
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
|
||||
// keyframes only have one identifier name
|
||||
var name = tokens[0];
|
||||
return new CssKeyframeRuleAST(name, this._parseKeyframeBlock(delimiters));
|
||||
|
||||
case BlockType.MediaQuery:
|
||||
this._scanner.setMode(CssLexerMode.MEDIA_QUERY);
|
||||
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
|
||||
return new CssMediaQueryRuleAST(tokens, this._parseBlock(delimiters));
|
||||
|
||||
case BlockType.Document:
|
||||
case BlockType.Supports:
|
||||
case BlockType.Page:
|
||||
this._scanner.setMode(CssLexerMode.AT_RULE_QUERY);
|
||||
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
|
||||
return new CssBlockDefinitionRuleAST(type, tokens, this._parseBlock(delimiters));
|
||||
|
||||
// if a custom @rule { ... } is used it should still tokenize the insides
|
||||
default:
|
||||
var listOfTokens = [];
|
||||
this._scanner.setMode(CssLexerMode.ALL);
|
||||
this._error(generateErrorMessage(
|
||||
this._scanner.input,
|
||||
`The CSS "at" rule "${token.strValue}" is not allowed to used here`,
|
||||
token.strValue, token.index, token.line, token.column),
|
||||
token);
|
||||
|
||||
this._collectUntilDelim(bitWiseOr([delimiters, LBRACE_DELIM, SEMICOLON_DELIM]))
|
||||
.forEach((token) => { listOfTokens.push(token); });
|
||||
if (this._scanner.peek == $LBRACE) {
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]))
|
||||
.forEach((token) => { listOfTokens.push(token); });
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
}
|
||||
return new CssUnknownTokenListAST(token, listOfTokens);
|
||||
}
|
||||
}
|
||||
|
||||
_parseSelectorRule(delimiters: number): CssSelectorRuleAST {
|
||||
var selectors = this._parseSelectors(delimiters);
|
||||
var block = this._parseStyleBlock(delimiters);
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
return new CssSelectorRuleAST(selectors, block);
|
||||
}
|
||||
|
||||
_parseSelectors(delimiters: number): CssSelectorAST[] {
|
||||
delimiters = bitWiseOr([delimiters, LBRACE_DELIM]);
|
||||
|
||||
var selectors = [];
|
||||
var isParsingSelectors = true;
|
||||
while (isParsingSelectors) {
|
||||
selectors.push(this._parseSelector(delimiters));
|
||||
|
||||
isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters);
|
||||
|
||||
if (isParsingSelectors) {
|
||||
this._consume(CssTokenType.Character, ',');
|
||||
isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters);
|
||||
}
|
||||
}
|
||||
|
||||
return selectors;
|
||||
}
|
||||
|
||||
_scan(): CssToken {
|
||||
var output = this._scanner.scan();
|
||||
var token = output.token;
|
||||
var error = output.error;
|
||||
if (isPresent(error)) {
|
||||
this._error(error.rawMessage, token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
_consume(type: CssTokenType, value: string = null): CssToken {
|
||||
var output = this._scanner.consume(type, value);
|
||||
var token = output.token;
|
||||
var error = output.error;
|
||||
if (isPresent(error)) {
|
||||
this._error(error.rawMessage, token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
_parseKeyframeBlock(delimiters: number): CssBlockAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM]);
|
||||
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
|
||||
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
|
||||
var definitions = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
definitions.push(this._parseKeyframeDefinition(delimiters));
|
||||
}
|
||||
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
|
||||
return new CssBlockAST(definitions);
|
||||
}
|
||||
|
||||
_parseKeyframeDefinition(delimiters: number): CssKeyframeDefinitionAST {
|
||||
var stepTokens = [];
|
||||
delimiters = bitWiseOr([delimiters, LBRACE_DELIM]);
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
stepTokens.push(this._parseKeyframeLabel(bitWiseOr([delimiters, COMMA_DELIM])));
|
||||
if (this._scanner.peek != $LBRACE) {
|
||||
this._consume(CssTokenType.Character, ',');
|
||||
}
|
||||
}
|
||||
var styles = this._parseStyleBlock(bitWiseOr([delimiters, RBRACE_DELIM]));
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
return new CssKeyframeDefinitionAST(stepTokens, styles);
|
||||
}
|
||||
|
||||
_parseKeyframeLabel(delimiters: number): CssToken {
|
||||
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
|
||||
return mergeTokens(this._collectUntilDelim(delimiters));
|
||||
}
|
||||
|
||||
_parseSelector(delimiters: number): CssSelectorAST {
|
||||
delimiters = bitWiseOr([delimiters, COMMA_DELIM, LBRACE_DELIM]);
|
||||
this._scanner.setMode(CssLexerMode.SELECTOR);
|
||||
|
||||
var selectorCssTokens = [];
|
||||
var isComplex = false;
|
||||
var wsCssToken;
|
||||
|
||||
var previousToken;
|
||||
var parenCount = 0;
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
var code = this._scanner.peek;
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
parenCount++;
|
||||
break;
|
||||
|
||||
case $RPAREN:
|
||||
parenCount--;
|
||||
break;
|
||||
|
||||
case $COLON:
|
||||
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR);
|
||||
previousToken = this._consume(CssTokenType.Character, ':');
|
||||
selectorCssTokens.push(previousToken);
|
||||
continue;
|
||||
|
||||
case $LBRACKET:
|
||||
// if we are already inside an attribute selector then we can't
|
||||
// jump into the mode again. Therefore this error will get picked
|
||||
// up when the scan method is called below.
|
||||
if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) {
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, '['));
|
||||
this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case $RBRACKET:
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, ']'));
|
||||
this._scanner.setMode(CssLexerMode.SELECTOR);
|
||||
continue;
|
||||
}
|
||||
|
||||
var token = this._scan();
|
||||
|
||||
// special case for the ":not(" selector since it
|
||||
// contains an inner selector that needs to be parsed
|
||||
// in isolation
|
||||
if (this._scanner.getMode() == CssLexerMode.PSEUDO_SELECTOR && isPresent(previousToken) &&
|
||||
previousToken.numValue == $COLON && token.strValue == "not" &&
|
||||
this._scanner.peek == $LPAREN) {
|
||||
selectorCssTokens.push(token);
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, '('));
|
||||
|
||||
// the inner selector inside of :not(...) can only be one
|
||||
// CSS selector (no commas allowed) therefore we parse only
|
||||
// one selector by calling the method below
|
||||
this._parseSelector(bitWiseOr([delimiters, RPAREN_DELIM]))
|
||||
.tokens.forEach(
|
||||
(innerSelectorToken) => { selectorCssTokens.push(innerSelectorToken); });
|
||||
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, ')'));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
previousToken = token;
|
||||
|
||||
if (token.type == CssTokenType.Whitespace) {
|
||||
wsCssToken = token;
|
||||
} else {
|
||||
if (isPresent(wsCssToken)) {
|
||||
selectorCssTokens.push(wsCssToken);
|
||||
wsCssToken = null;
|
||||
isComplex = true;
|
||||
}
|
||||
selectorCssTokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._scanner.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR) {
|
||||
this._error(
|
||||
`Unbalanced CSS attribute selector at column ${previousToken.line}:${previousToken.column}`,
|
||||
previousToken);
|
||||
} else if (parenCount > 0) {
|
||||
this._error(
|
||||
`Unbalanced pseudo selector function value at column ${previousToken.line}:${previousToken.column}`,
|
||||
previousToken);
|
||||
}
|
||||
|
||||
return new CssSelectorAST(selectorCssTokens, isComplex);
|
||||
}
|
||||
|
||||
_parseValue(delimiters: number): CssStyleValueAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM, SEMICOLON_DELIM, NEWLINE_DELIM]);
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
|
||||
|
||||
var strValue = "";
|
||||
var tokens = [];
|
||||
var previous: CssToken;
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
var token;
|
||||
if (isPresent(previous) && previous.type == CssTokenType.Identifier &&
|
||||
this._scanner.peek == $LPAREN) {
|
||||
token = this._consume(CssTokenType.Character, '(');
|
||||
tokens.push(token);
|
||||
strValue += token.strValue;
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_VALUE_FUNCTION);
|
||||
|
||||
token = this._scan();
|
||||
tokens.push(token);
|
||||
strValue += token.strValue;
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
|
||||
|
||||
token = this._consume(CssTokenType.Character, ')');
|
||||
tokens.push(token);
|
||||
strValue += token.strValue;
|
||||
} else {
|
||||
token = this._scan();
|
||||
if (token.type != CssTokenType.Whitespace) {
|
||||
tokens.push(token);
|
||||
}
|
||||
strValue += token.strValue;
|
||||
}
|
||||
|
||||
previous = token;
|
||||
}
|
||||
|
||||
this._scanner.consumeWhitespace();
|
||||
|
||||
var code = this._scanner.peek;
|
||||
if (code == $SEMICOLON) {
|
||||
this._consume(CssTokenType.Character, ';');
|
||||
} else if (code != $RBRACE) {
|
||||
this._error(
|
||||
generateErrorMessage(this._scanner.input,
|
||||
`The CSS key/value definition did not end with a semicolon`,
|
||||
previous.strValue, previous.index, previous.line, previous.column),
|
||||
previous);
|
||||
}
|
||||
|
||||
return new CssStyleValueAST(tokens, strValue);
|
||||
}
|
||||
|
||||
_collectUntilDelim(delimiters: number, assertType: CssTokenType = null): CssToken[] {
|
||||
var tokens = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
var val = isPresent(assertType) ? this._consume(assertType) : this._scan();
|
||||
tokens.push(val);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
_parseBlock(delimiters: number): CssBlockAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM]);
|
||||
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
var results = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
results.push(this._parseRule(delimiters));
|
||||
}
|
||||
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
return new CssBlockAST(results);
|
||||
}
|
||||
|
||||
_parseStyleBlock(delimiters: number): CssBlockAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]);
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
||||
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
var definitions = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
definitions.push(this._parseDefinition(delimiters));
|
||||
this._scanner.consumeEmptyStatements();
|
||||
}
|
||||
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
return new CssBlockAST(definitions);
|
||||
}
|
||||
|
||||
_parseDefinition(delimiters: number): CssDefinitionAST {
|
||||
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
||||
|
||||
var prop = this._consume(CssTokenType.Identifier);
|
||||
var parseValue, value = null;
|
||||
|
||||
// the colon value separates the prop from the style.
|
||||
// there are a few cases as to what could happen if it
|
||||
// is missing
|
||||
switch (this._scanner.peek) {
|
||||
case $COLON:
|
||||
this._consume(CssTokenType.Character, ':');
|
||||
parseValue = true;
|
||||
break;
|
||||
|
||||
case $SEMICOLON:
|
||||
case $RBRACE:
|
||||
case $EOF:
|
||||
parseValue = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
var propStr = [prop.strValue];
|
||||
if (this._scanner.peek != $COLON) {
|
||||
// this will throw the error
|
||||
var nextValue = this._consume(CssTokenType.Character, ':');
|
||||
propStr.push(nextValue.strValue);
|
||||
|
||||
var remainingTokens = this._collectUntilDelim(
|
||||
bitWiseOr([delimiters, COLON_DELIM, SEMICOLON_DELIM]), CssTokenType.Identifier);
|
||||
if (remainingTokens.length > 0) {
|
||||
remainingTokens.forEach((token) => { propStr.push(token.strValue); });
|
||||
}
|
||||
|
||||
prop = new CssToken(prop.index, prop.column, prop.line, prop.type, propStr.join(" "));
|
||||
}
|
||||
|
||||
// this means we've reached the end of the definition and/or block
|
||||
if (this._scanner.peek == $COLON) {
|
||||
this._consume(CssTokenType.Character, ':');
|
||||
parseValue = true;
|
||||
} else {
|
||||
parseValue = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (parseValue) {
|
||||
value = this._parseValue(delimiters);
|
||||
} else {
|
||||
this._error(generateErrorMessage(this._scanner.input,
|
||||
`The CSS property was not paired with a style value`,
|
||||
prop.strValue, prop.index, prop.line, prop.column),
|
||||
prop);
|
||||
}
|
||||
|
||||
return new CssDefinitionAST(prop, value);
|
||||
}
|
||||
|
||||
_assertCondition(status: boolean, errorMessage: string, problemToken: CssToken): boolean {
|
||||
if (!status) {
|
||||
this._error(errorMessage, problemToken);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_error(message: string, problemToken: CssToken) {
|
||||
var length = problemToken.strValue.length;
|
||||
var error = CssParseError.create(this._file, 0, problemToken.line, problemToken.column, length,
|
||||
message);
|
||||
this._errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssStyleValueAST extends CssAST {
|
||||
constructor(public tokens: CssToken[], public strValue: string) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssValue(this); }
|
||||
}
|
||||
|
||||
export class CssRuleAST extends CssAST {}
|
||||
|
||||
export class CssBlockRuleAST extends CssRuleAST {
|
||||
constructor(public type: BlockType, public block: CssBlockAST, public name: CssToken = null) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); }
|
||||
}
|
||||
|
||||
export class CssKeyframeRuleAST extends CssBlockRuleAST {
|
||||
constructor(name: CssToken, block: CssBlockAST) { super(BlockType.Keyframes, block, name); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssKeyframeRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssKeyframeDefinitionAST extends CssBlockRuleAST {
|
||||
public steps;
|
||||
constructor(_steps: CssToken[], block: CssBlockAST) {
|
||||
super(BlockType.Keyframes, block, mergeTokens(_steps, ","));
|
||||
this.steps = _steps;
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) {
|
||||
visitor.visitCssKeyframeDefinition(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssBlockDefinitionRuleAST extends CssBlockRuleAST {
|
||||
public strValue: string;
|
||||
constructor(type: BlockType, public query: CssToken[], block: CssBlockAST) {
|
||||
super(type, block);
|
||||
this.strValue = query.map(token => token.strValue).join("");
|
||||
var firstCssToken: CssToken = query[0];
|
||||
this.name = new CssToken(firstCssToken.index, firstCssToken.column, firstCssToken.line,
|
||||
CssTokenType.Identifier, this.strValue);
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); }
|
||||
}
|
||||
|
||||
export class CssMediaQueryRuleAST extends CssBlockDefinitionRuleAST {
|
||||
constructor(query: CssToken[], block: CssBlockAST) { super(BlockType.MediaQuery, query, block); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssMediaQueryRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssInlineRuleAST extends CssRuleAST {
|
||||
constructor(public type: BlockType, public value: CssStyleValueAST) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitInlineCssRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssSelectorRuleAST extends CssBlockRuleAST {
|
||||
public strValue: string;
|
||||
|
||||
constructor(public selectors: CssSelectorAST[], block: CssBlockAST) {
|
||||
super(BlockType.Selector, block);
|
||||
this.strValue = selectors.map(selector => selector.strValue).join(",");
|
||||
}
|
||||
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelectorRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssDefinitionAST extends CssAST {
|
||||
constructor(public property: CssToken, public value: CssStyleValueAST) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssDefinition(this, context); }
|
||||
}
|
||||
|
||||
export class CssSelectorAST extends CssAST {
|
||||
public strValue;
|
||||
constructor(public tokens: CssToken[], public isComplex: boolean = false) {
|
||||
super();
|
||||
this.strValue = tokens.map(token => token.strValue).join("");
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelector(this, context); }
|
||||
}
|
||||
|
||||
export class CssBlockAST extends CssAST {
|
||||
constructor(public entries: CssAST[]) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this, context); }
|
||||
}
|
||||
|
||||
export class CssStyleSheetAST extends CssAST {
|
||||
constructor(public rules: CssAST[]) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssStyleSheet(this, context); }
|
||||
}
|
||||
|
||||
export class CssParseError extends ParseError {
|
||||
static create(file: ParseSourceFile, offset: number, line: number, col: number, length: number,
|
||||
errMsg: string): CssParseError {
|
||||
var start = new ParseLocation(file, offset, line, col);
|
||||
var end = new ParseLocation(file, offset, line, col + length);
|
||||
var span = new ParseSourceSpan(start, end);
|
||||
return new CssParseError(span, "CSS Parse Error: " + errMsg);
|
||||
}
|
||||
|
||||
constructor(span: ParseSourceSpan, message: string) { super(span, message); }
|
||||
}
|
||||
|
||||
export class CssUnknownTokenListAST extends CssRuleAST {
|
||||
constructor(public name, public tokens: CssToken[]) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitUnkownRule(this, context); }
|
||||
}
|
@ -2,8 +2,10 @@ import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
normalizeBool,
|
||||
normalizeBlank,
|
||||
serializeEnum,
|
||||
Type,
|
||||
isString,
|
||||
RegExpWrapper,
|
||||
StringWrapper
|
||||
} from 'angular2/src/facade/lang';
|
||||
@ -22,43 +24,291 @@ import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/i
|
||||
// group 2: "event" from "(event)"
|
||||
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
|
||||
|
||||
export abstract class CompileMetadataWithType {
|
||||
export abstract class CompileMetadataWithIdentifier {
|
||||
static fromJson(data: {[key: string]: any}): CompileMetadataWithIdentifier {
|
||||
return _COMPILE_METADATA_FROM_JSON[data['class']](data);
|
||||
}
|
||||
|
||||
abstract toJson(): {[key: string]: any};
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
||||
}
|
||||
|
||||
export abstract class CompileMetadataWithType extends CompileMetadataWithIdentifier {
|
||||
static fromJson(data: {[key: string]: any}): CompileMetadataWithType {
|
||||
return _COMPILE_METADATA_FROM_JSON[data['class']](data);
|
||||
}
|
||||
|
||||
abstract toJson(): {[key: string]: any};
|
||||
|
||||
get type(): CompileTypeMetadata { return unimplemented(); }
|
||||
get type(): CompileTypeMetadata { return <CompileTypeMetadata>unimplemented(); }
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a type.
|
||||
*/
|
||||
export class CompileTypeMetadata {
|
||||
runtime: Type;
|
||||
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
|
||||
runtime: any;
|
||||
name: string;
|
||||
prefix: string;
|
||||
moduleUrl: string;
|
||||
isHost: boolean;
|
||||
constructor({runtime, name, moduleUrl, isHost}:
|
||||
{runtime?: Type, name?: string, moduleUrl?: string, isHost?: boolean} = {}) {
|
||||
constConstructor: boolean;
|
||||
constructor({runtime, name, moduleUrl, prefix, constConstructor}: {
|
||||
runtime?: any,
|
||||
name?: string,
|
||||
moduleUrl?: string,
|
||||
prefix?: string,
|
||||
constConstructor?: boolean
|
||||
} = {}) {
|
||||
this.runtime = runtime;
|
||||
this.name = name;
|
||||
this.prefix = prefix;
|
||||
this.moduleUrl = moduleUrl;
|
||||
this.isHost = normalizeBool(isHost);
|
||||
this.constConstructor = constConstructor;
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileTypeMetadata {
|
||||
return new CompileTypeMetadata(
|
||||
{name: data['name'], moduleUrl: data['moduleUrl'], isHost: data['isHost']});
|
||||
static fromJson(data: {[key: string]: any}): CompileIdentifierMetadata {
|
||||
return new CompileIdentifierMetadata({
|
||||
name: data['name'],
|
||||
prefix: data['prefix'],
|
||||
moduleUrl: data['moduleUrl'],
|
||||
constConstructor: data['constConstructor']
|
||||
});
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
// Note: Runtime type can't be serialized...
|
||||
'class': 'Identifier',
|
||||
'name': this.name,
|
||||
'moduleUrl': this.moduleUrl,
|
||||
'isHost': this.isHost
|
||||
'prefix': this.prefix,
|
||||
'constConstructor': this.constConstructor
|
||||
};
|
||||
}
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return this; }
|
||||
}
|
||||
|
||||
export class CompileDiDependencyMetadata {
|
||||
isAttribute: boolean;
|
||||
isSelf: boolean;
|
||||
isHost: boolean;
|
||||
isSkipSelf: boolean;
|
||||
isOptional: boolean;
|
||||
query: CompileQueryMetadata;
|
||||
viewQuery: CompileQueryMetadata;
|
||||
token: CompileIdentifierMetadata | string;
|
||||
|
||||
constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, query, viewQuery, token}: {
|
||||
isAttribute?: boolean,
|
||||
isSelf?: boolean,
|
||||
isHost?: boolean,
|
||||
isSkipSelf?: boolean,
|
||||
isOptional?: boolean,
|
||||
query?: CompileQueryMetadata,
|
||||
viewQuery?: CompileQueryMetadata,
|
||||
token?: CompileIdentifierMetadata | string
|
||||
} = {}) {
|
||||
this.isAttribute = normalizeBool(isAttribute);
|
||||
this.isSelf = normalizeBool(isSelf);
|
||||
this.isHost = normalizeBool(isHost);
|
||||
this.isSkipSelf = normalizeBool(isSkipSelf);
|
||||
this.isOptional = normalizeBool(isOptional);
|
||||
this.query = query;
|
||||
this.viewQuery = viewQuery;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileDiDependencyMetadata {
|
||||
return new CompileDiDependencyMetadata({
|
||||
token: objFromJson(data['token'], CompileIdentifierMetadata.fromJson),
|
||||
query: objFromJson(data['query'], CompileQueryMetadata.fromJson),
|
||||
viewQuery: objFromJson(data['viewQuery'], CompileQueryMetadata.fromJson),
|
||||
isAttribute: data['isAttribute'],
|
||||
isSelf: data['isSelf'],
|
||||
isHost: data['isHost'],
|
||||
isSkipSelf: data['isSkipSelf'],
|
||||
isOptional: data['isOptional']
|
||||
});
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
// Note: Runtime type can't be serialized...
|
||||
'token': objToJson(this.token),
|
||||
'query': objToJson(this.query),
|
||||
'viewQuery': objToJson(this.viewQuery),
|
||||
'isAttribute': this.isAttribute,
|
||||
'isSelf': this.isSelf,
|
||||
'isHost': this.isHost,
|
||||
'isSkipSelf': this.isSkipSelf,
|
||||
'isOptional': this.isOptional
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileProviderMetadata {
|
||||
token: CompileIdentifierMetadata | string;
|
||||
useClass: CompileTypeMetadata;
|
||||
useValue: any;
|
||||
useExisting: CompileIdentifierMetadata | string;
|
||||
useFactory: CompileFactoryMetadata;
|
||||
deps: CompileDiDependencyMetadata[];
|
||||
multi: boolean;
|
||||
|
||||
constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
|
||||
token?: CompileIdentifierMetadata | string,
|
||||
useClass?: CompileTypeMetadata,
|
||||
useValue?: any,
|
||||
useExisting?: CompileIdentifierMetadata | string,
|
||||
useFactory?: CompileFactoryMetadata,
|
||||
deps?: CompileDiDependencyMetadata[],
|
||||
multi?: boolean
|
||||
}) {
|
||||
this.token = token;
|
||||
this.useClass = useClass;
|
||||
this.useValue = useValue;
|
||||
this.useExisting = useExisting;
|
||||
this.useFactory = useFactory;
|
||||
this.deps = deps;
|
||||
this.multi = multi;
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileProviderMetadata {
|
||||
return new CompileProviderMetadata({
|
||||
token: objFromJson(data['token'], CompileIdentifierMetadata.fromJson),
|
||||
useClass: objFromJson(data['useClass'], CompileTypeMetadata.fromJson)
|
||||
});
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
// Note: Runtime type can't be serialized...
|
||||
'token': objToJson(this.token),
|
||||
'useClass': objToJson(this.useClass)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileFactoryMetadata implements CompileIdentifierMetadata {
|
||||
runtime: Function;
|
||||
name: string;
|
||||
prefix: string;
|
||||
moduleUrl: string;
|
||||
constConstructor: boolean;
|
||||
diDeps: CompileDiDependencyMetadata[];
|
||||
|
||||
constructor({runtime, name, moduleUrl, constConstructor, diDeps}: {
|
||||
runtime?: Function,
|
||||
name?: string,
|
||||
moduleUrl?: string,
|
||||
constConstructor?: boolean,
|
||||
diDeps?: CompileDiDependencyMetadata[]
|
||||
}) {
|
||||
this.runtime = runtime;
|
||||
this.name = name;
|
||||
this.moduleUrl = moduleUrl;
|
||||
this.diDeps = diDeps;
|
||||
this.constConstructor = constConstructor;
|
||||
}
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return this; }
|
||||
|
||||
toJson() { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a type.
|
||||
*/
|
||||
export class CompileTypeMetadata implements CompileIdentifierMetadata, CompileMetadataWithType {
|
||||
runtime: Type;
|
||||
name: string;
|
||||
prefix: string;
|
||||
moduleUrl: string;
|
||||
isHost: boolean;
|
||||
constConstructor: boolean;
|
||||
diDeps: CompileDiDependencyMetadata[];
|
||||
|
||||
constructor({runtime, name, moduleUrl, prefix, isHost, constConstructor, diDeps}: {
|
||||
runtime?: Type,
|
||||
name?: string,
|
||||
moduleUrl?: string,
|
||||
prefix?: string,
|
||||
isHost?: boolean,
|
||||
constConstructor?: boolean,
|
||||
diDeps?: CompileDiDependencyMetadata[]
|
||||
} = {}) {
|
||||
this.runtime = runtime;
|
||||
this.name = name;
|
||||
this.moduleUrl = moduleUrl;
|
||||
this.prefix = prefix;
|
||||
this.isHost = normalizeBool(isHost);
|
||||
this.constConstructor = constConstructor;
|
||||
this.diDeps = normalizeBlank(diDeps);
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileTypeMetadata {
|
||||
return new CompileTypeMetadata({
|
||||
name: data['name'],
|
||||
moduleUrl: data['moduleUrl'],
|
||||
prefix: data['prefix'],
|
||||
isHost: data['isHost'],
|
||||
constConstructor: data['constConstructor'],
|
||||
diDeps: arrayFromJson(data['diDeps'], CompileDiDependencyMetadata.fromJson)
|
||||
});
|
||||
}
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return this; }
|
||||
get type(): CompileTypeMetadata { return this; }
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
// Note: Runtime type can't be serialized...
|
||||
'class': 'Type',
|
||||
'name': this.name,
|
||||
'moduleUrl': this.moduleUrl,
|
||||
'prefix': this.prefix,
|
||||
'isHost': this.isHost,
|
||||
'constConstructor': this.constConstructor,
|
||||
'diDeps': arrayToJson(this.diDeps)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileQueryMetadata {
|
||||
selectors: Array<CompileIdentifierMetadata | string>;
|
||||
descendants: boolean;
|
||||
first: boolean;
|
||||
propertyName: string;
|
||||
|
||||
constructor({selectors, descendants, first, propertyName}: {
|
||||
selectors?: Array<CompileIdentifierMetadata | string>,
|
||||
descendants?: boolean,
|
||||
first?: boolean,
|
||||
propertyName?: string
|
||||
} = {}) {
|
||||
this.selectors = selectors;
|
||||
this.descendants = descendants;
|
||||
this.first = normalizeBool(first);
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileQueryMetadata {
|
||||
return new CompileQueryMetadata({
|
||||
selectors: arrayFromJson(data['selectors'], CompileIdentifierMetadata.fromJson),
|
||||
descendants: data['descendants'],
|
||||
first: data['first'],
|
||||
propertyName: data['propertyName']
|
||||
});
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
// Note: Runtime type can't be serialized...
|
||||
'selectors': arrayToJson(this.selectors),
|
||||
'descendants': this.descendants,
|
||||
'first': this.first,
|
||||
'propertyName': this.propertyName
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -120,7 +370,8 @@ export class CompileTemplateMetadata {
|
||||
*/
|
||||
export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs,
|
||||
outputs, host, lifecycleHooks, template}: {
|
||||
outputs, host, lifecycleHooks, providers, viewProviders, queries, viewQueries,
|
||||
template}: {
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
dynamicLoadable?: boolean,
|
||||
@ -131,6 +382,10 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
outputs?: string[],
|
||||
host?: {[key: string]: string},
|
||||
lifecycleHooks?: LifecycleHooks[],
|
||||
providers?: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>,
|
||||
viewProviders?: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>,
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
template?: CompileTemplateMetadata
|
||||
} = {}): CompileDirectiveMetadata {
|
||||
var hostListeners: {[key: string]: string} = {};
|
||||
@ -180,10 +435,13 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
hostProperties: hostProperties,
|
||||
hostAttributes: hostAttributes,
|
||||
lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [],
|
||||
providers: providers,
|
||||
viewProviders: viewProviders,
|
||||
queries: queries,
|
||||
viewQueries: viewQueries,
|
||||
template: template
|
||||
});
|
||||
}
|
||||
|
||||
type: CompileTypeMetadata;
|
||||
isComponent: boolean;
|
||||
dynamicLoadable: boolean;
|
||||
@ -196,9 +454,14 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
hostProperties: {[key: string]: string};
|
||||
hostAttributes: {[key: string]: string};
|
||||
lifecycleHooks: LifecycleHooks[];
|
||||
providers: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>;
|
||||
viewProviders: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>;
|
||||
queries: CompileQueryMetadata[];
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
template: CompileTemplateMetadata;
|
||||
constructor({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs,
|
||||
outputs, hostListeners, hostProperties, hostAttributes, lifecycleHooks, template}: {
|
||||
outputs, hostListeners, hostProperties, hostAttributes, lifecycleHooks, providers,
|
||||
viewProviders, queries, viewQueries, template}: {
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
dynamicLoadable?: boolean,
|
||||
@ -211,6 +474,10 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
hostProperties?: {[key: string]: string},
|
||||
hostAttributes?: {[key: string]: string},
|
||||
lifecycleHooks?: LifecycleHooks[],
|
||||
providers?: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>,
|
||||
viewProviders?: Array<CompileProviderMetadata | CompileTypeMetadata | any[]>,
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
template?: CompileTemplateMetadata
|
||||
} = {}) {
|
||||
this.type = type;
|
||||
@ -225,9 +492,15 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
this.hostProperties = hostProperties;
|
||||
this.hostAttributes = hostAttributes;
|
||||
this.lifecycleHooks = lifecycleHooks;
|
||||
this.providers = normalizeBlank(providers);
|
||||
this.viewProviders = normalizeBlank(viewProviders);
|
||||
this.queries = queries;
|
||||
this.viewQueries = viewQueries;
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileDirectiveMetadata {
|
||||
return new CompileDirectiveMetadata({
|
||||
isComponent: data['isComponent'],
|
||||
@ -246,7 +519,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
lifecycleHooks:
|
||||
(<any[]>data['lifecycleHooks']).map(hookValue => LIFECYCLE_HOOKS_VALUES[hookValue]),
|
||||
template: isPresent(data['template']) ? CompileTemplateMetadata.fromJson(data['template']) :
|
||||
data['template']
|
||||
data['template'],
|
||||
providers: arrayFromJson(data['providers'], CompileProviderMetadata.fromJson)
|
||||
});
|
||||
}
|
||||
|
||||
@ -266,7 +540,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||
'hostProperties': this.hostProperties,
|
||||
'hostAttributes': this.hostAttributes,
|
||||
'lifecycleHooks': this.lifecycleHooks.map(hook => serializeEnum(hook)),
|
||||
'template': isPresent(this.template) ? this.template.toJson() : this.template
|
||||
'template': isPresent(this.template) ? this.template.toJson() : this.template,
|
||||
'providers': arrayToJson(this.providers)
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -293,7 +568,11 @@ export function createHostComponentMeta(componentType: CompileTypeMetadata,
|
||||
lifecycleHooks: [],
|
||||
isComponent: true,
|
||||
dynamicLoadable: false,
|
||||
selector: '*'
|
||||
selector: '*',
|
||||
providers: [],
|
||||
viewProviders: [],
|
||||
queries: [],
|
||||
viewQueries: []
|
||||
});
|
||||
}
|
||||
|
||||
@ -308,6 +587,7 @@ export class CompilePipeMetadata implements CompileMetadataWithType {
|
||||
this.name = name;
|
||||
this.pure = normalizeBool(pure);
|
||||
}
|
||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompilePipeMetadata {
|
||||
return new CompilePipeMetadata({
|
||||
@ -329,5 +609,23 @@ export class CompilePipeMetadata implements CompileMetadataWithType {
|
||||
|
||||
var _COMPILE_METADATA_FROM_JSON = {
|
||||
'Directive': CompileDirectiveMetadata.fromJson,
|
||||
'Pipe': CompilePipeMetadata.fromJson
|
||||
'Pipe': CompilePipeMetadata.fromJson,
|
||||
'Type': CompileTypeMetadata.fromJson,
|
||||
'Identifier': CompileIdentifierMetadata.fromJson
|
||||
};
|
||||
|
||||
function arrayFromJson(obj: any[], fn: (a: {[key: string]: any}) => any): any {
|
||||
return isBlank(obj) ? null : obj.map(o => objFromJson(o, fn));
|
||||
}
|
||||
|
||||
function arrayToJson(obj: any[]): string | {[key: string]: any} {
|
||||
return isBlank(obj) ? null : obj.map(objToJson);
|
||||
}
|
||||
|
||||
function objFromJson(obj: any, fn: (a: {[key: string]: any}) => any): any {
|
||||
return (isString(obj) || isBlank(obj)) ? obj : fn(obj);
|
||||
}
|
||||
|
||||
function objToJson(obj: any): string | {[key: string]: any} {
|
||||
return (isString(obj) || isBlank(obj)) ? obj : obj.toJson();
|
||||
}
|
||||
|
@ -23,10 +23,16 @@ export class HtmlElementAst implements HtmlAst {
|
||||
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); }
|
||||
}
|
||||
|
||||
export class HtmlCommentAst implements HtmlAst {
|
||||
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitComment(this, context); }
|
||||
}
|
||||
|
||||
export interface HtmlAstVisitor {
|
||||
visitElement(ast: HtmlElementAst, context: any): any;
|
||||
visitAttr(ast: HtmlAttrAst, context: any): any;
|
||||
visitText(ast: HtmlTextAst, context: any): any;
|
||||
visitComment(ast: HtmlCommentAst, context: any): any;
|
||||
}
|
||||
|
||||
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
|
||||
|
@ -34,8 +34,8 @@ export class HtmlToken {
|
||||
}
|
||||
|
||||
export class HtmlTokenError extends ParseError {
|
||||
constructor(errorMsg: string, public tokenType: HtmlTokenType, location: ParseLocation) {
|
||||
super(location, errorMsg);
|
||||
constructor(errorMsg: string, public tokenType: HtmlTokenType, span: ParseSourceSpan) {
|
||||
super(span, errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +125,8 @@ class _HtmlTokenizer {
|
||||
|
||||
private _processCarriageReturns(content: string): string {
|
||||
// http://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
|
||||
// In order to keep the original position in the source, we can not pre-process it.
|
||||
// In order to keep the original position in the source, we can not
|
||||
// pre-process it.
|
||||
// Instead CRs are processed right before instantiating the tokens.
|
||||
return StringWrapper.replaceAll(content, CR_OR_CRLF_REGEXP, '\n');
|
||||
}
|
||||
@ -168,6 +169,16 @@ class _HtmlTokenizer {
|
||||
return new ParseLocation(this.file, this.index, this.line, this.column);
|
||||
}
|
||||
|
||||
private _getSpan(start?: ParseLocation, end?: ParseLocation): ParseSourceSpan {
|
||||
if (isBlank(start)) {
|
||||
start = this._getLocation();
|
||||
}
|
||||
if (isBlank(end)) {
|
||||
end = this._getLocation();
|
||||
}
|
||||
return new ParseSourceSpan(start, end);
|
||||
}
|
||||
|
||||
private _beginToken(type: HtmlTokenType, start: ParseLocation = null) {
|
||||
if (isBlank(start)) {
|
||||
start = this._getLocation();
|
||||
@ -188,8 +199,8 @@ class _HtmlTokenizer {
|
||||
return token;
|
||||
}
|
||||
|
||||
private _createError(msg: string, position: ParseLocation): ControlFlowError {
|
||||
var error = new HtmlTokenError(msg, this.currentTokenType, position);
|
||||
private _createError(msg: string, span: ParseSourceSpan): ControlFlowError {
|
||||
var error = new HtmlTokenError(msg, this.currentTokenType, span);
|
||||
this.currentTokenStart = null;
|
||||
this.currentTokenType = null;
|
||||
return new ControlFlowError(error);
|
||||
@ -197,7 +208,7 @@ class _HtmlTokenizer {
|
||||
|
||||
private _advance() {
|
||||
if (this.index >= this.length) {
|
||||
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getLocation());
|
||||
throw this._createError(unexpectedCharacterErrorMsg($EOF), this._getSpan());
|
||||
}
|
||||
if (this.peek === $LF) {
|
||||
this.line++;
|
||||
@ -228,7 +239,8 @@ class _HtmlTokenizer {
|
||||
private _requireCharCode(charCode: number) {
|
||||
var location = this._getLocation();
|
||||
if (!this._attemptCharCode(charCode)) {
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), location);
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek),
|
||||
this._getSpan(location, location));
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +265,7 @@ class _HtmlTokenizer {
|
||||
private _requireStr(chars: string) {
|
||||
var location = this._getLocation();
|
||||
if (!this._attemptStr(chars)) {
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), location);
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(location));
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +279,7 @@ class _HtmlTokenizer {
|
||||
var start = this._getLocation();
|
||||
this._attemptCharCodeUntilFn(predicate);
|
||||
if (this.index - start.offset < len) {
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), start);
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan(start, start));
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +307,7 @@ class _HtmlTokenizer {
|
||||
let numberStart = this._getLocation().offset;
|
||||
this._attemptCharCodeUntilFn(isDigitEntityEnd);
|
||||
if (this.peek != $SEMICOLON) {
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getLocation());
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
|
||||
}
|
||||
this._advance();
|
||||
let strNum = this.input.substring(numberStart, this.index - 1);
|
||||
@ -304,7 +316,7 @@ class _HtmlTokenizer {
|
||||
return StringWrapper.fromCharCode(charCode);
|
||||
} catch (e) {
|
||||
let entity = this.input.substring(start.offset + 1, this.index - 1);
|
||||
throw this._createError(unknownEntityErrorMsg(entity), start);
|
||||
throw this._createError(unknownEntityErrorMsg(entity), this._getSpan(start));
|
||||
}
|
||||
} else {
|
||||
let startPosition = this._savePosition();
|
||||
@ -317,7 +329,7 @@ class _HtmlTokenizer {
|
||||
let name = this.input.substring(start.offset + 1, this.index - 1);
|
||||
let char = NAMED_ENTITIES[name];
|
||||
if (isBlank(char)) {
|
||||
throw this._createError(unknownEntityErrorMsg(name), start);
|
||||
throw this._createError(unknownEntityErrorMsg(name), this._getSpan(start));
|
||||
}
|
||||
return char;
|
||||
}
|
||||
@ -394,7 +406,7 @@ class _HtmlTokenizer {
|
||||
let lowercaseTagName;
|
||||
try {
|
||||
if (!isAsciiLetter(this.peek)) {
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getLocation());
|
||||
throw this._createError(unexpectedCharacterErrorMsg(this.peek), this._getSpan());
|
||||
}
|
||||
var nameStart = this.index;
|
||||
this._consumeTagOpenStart(start);
|
||||
|
@ -11,21 +11,19 @@ import {
|
||||
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast';
|
||||
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlCommentAst, HtmlElementAst} from './html_ast';
|
||||
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
|
||||
import {ParseError, ParseLocation, ParseSourceSpan} from './parse_util';
|
||||
import {HtmlTagDefinition, getHtmlTagDefinition, getNsPrefix} from './html_tags';
|
||||
import {HtmlTagDefinition, getHtmlTagDefinition, getNsPrefix, mergeNsAndName} from './html_tags';
|
||||
|
||||
export class HtmlTreeError extends ParseError {
|
||||
static create(elementName: string, location: ParseLocation, msg: string): HtmlTreeError {
|
||||
return new HtmlTreeError(elementName, location, msg);
|
||||
static create(elementName: string, span: ParseSourceSpan, msg: string): HtmlTreeError {
|
||||
return new HtmlTreeError(elementName, span, msg);
|
||||
}
|
||||
|
||||
constructor(public elementName: string, location: ParseLocation, msg: string) {
|
||||
super(location, msg);
|
||||
}
|
||||
constructor(public elementName: string, span: ParseSourceSpan, msg: string) { super(span, msg); }
|
||||
}
|
||||
|
||||
export class HtmlParseTreeResult {
|
||||
@ -100,9 +98,11 @@ class TreeBuilder {
|
||||
this._advanceIf(HtmlTokenType.CDATA_END);
|
||||
}
|
||||
|
||||
private _consumeComment(startToken: HtmlToken) {
|
||||
this._advanceIf(HtmlTokenType.RAW_TEXT);
|
||||
private _consumeComment(token: HtmlToken) {
|
||||
var text = this._advanceIf(HtmlTokenType.RAW_TEXT);
|
||||
this._advanceIf(HtmlTokenType.COMMENT_END);
|
||||
var value = isPresent(text) ? text.parts[0].trim() : null;
|
||||
this._addToParent(new HtmlCommentAst(value, token.sourceSpan));
|
||||
}
|
||||
|
||||
private _consumeText(token: HtmlToken) {
|
||||
@ -146,7 +146,7 @@ class TreeBuilder {
|
||||
selfClosing = true;
|
||||
if (getNsPrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) {
|
||||
this.errors.push(HtmlTreeError.create(
|
||||
fullName, startTagToken.sourceSpan.start,
|
||||
fullName, startTagToken.sourceSpan,
|
||||
`Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`));
|
||||
}
|
||||
} else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) {
|
||||
@ -189,10 +189,10 @@ class TreeBuilder {
|
||||
|
||||
if (getHtmlTagDefinition(fullName).isVoid) {
|
||||
this.errors.push(
|
||||
HtmlTreeError.create(fullName, endTagToken.sourceSpan.start,
|
||||
HtmlTreeError.create(fullName, endTagToken.sourceSpan,
|
||||
`Void elements do not have end tags "${endTagToken.parts[1]}"`));
|
||||
} else if (!this._popElement(fullName)) {
|
||||
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start,
|
||||
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan,
|
||||
`Unexpected closing tag "${endTagToken.parts[1]}"`));
|
||||
}
|
||||
}
|
||||
@ -238,10 +238,6 @@ class TreeBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
function mergeNsAndName(prefix: string, localName: string): string {
|
||||
return isPresent(prefix) ? `@${prefix}:${localName}` : localName;
|
||||
}
|
||||
|
||||
function getElementFullName(prefix: string, localName: string,
|
||||
parentElement: HtmlElementAst): string {
|
||||
if (isBlank(prefix)) {
|
||||
|
@ -327,6 +327,8 @@ export class HtmlTagDefinition {
|
||||
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
|
||||
// This implementation does not fully conform to the HTML5 spec.
|
||||
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
|
||||
'base': new HtmlTagDefinition({isVoid: true}),
|
||||
'meta': new HtmlTagDefinition({isVoid: true}),
|
||||
'area': new HtmlTagDefinition({isVoid: true}),
|
||||
'embed': new HtmlTagDefinition({isVoid: true}),
|
||||
'link': new HtmlTagDefinition({isVoid: true}),
|
||||
@ -420,3 +422,7 @@ export function splitNsName(elementName: string): string[] {
|
||||
export function getNsPrefix(elementName: string): string {
|
||||
return splitNsName(elementName)[0];
|
||||
}
|
||||
|
||||
export function mergeNsAndName(prefix: string, localName: string): string {
|
||||
return isPresent(prefix) ? `@${prefix}:${localName}` : localName;
|
||||
}
|
||||
|
@ -8,7 +8,14 @@ import {
|
||||
isPresent
|
||||
} from 'angular2/src/facade/lang';
|
||||
|
||||
import {HtmlAstVisitor, HtmlAttrAst, HtmlElementAst, HtmlTextAst, HtmlAst} from './html_ast';
|
||||
import {
|
||||
HtmlAstVisitor,
|
||||
HtmlAttrAst,
|
||||
HtmlElementAst,
|
||||
HtmlTextAst,
|
||||
HtmlCommentAst,
|
||||
HtmlAst
|
||||
} from './html_ast';
|
||||
import {HtmlParser, HtmlParseTreeResult} from './html_parser';
|
||||
|
||||
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
@ -37,6 +44,8 @@ export class LegacyHtmlAstTransformer implements HtmlAstVisitor {
|
||||
|
||||
constructor(private dashCaseSelectors?: string[]) {}
|
||||
|
||||
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
|
||||
|
||||
visitElement(ast: HtmlElementAst, context: any): HtmlElementAst {
|
||||
this.visitingTemplateEl = ast.name.toLowerCase() == 'template';
|
||||
let attrs = ast.attrs.map(attr => attr.visit(this, null));
|
||||
|
@ -9,12 +9,20 @@ export class ParseSourceFile {
|
||||
constructor(public content: string, public url: string) {}
|
||||
}
|
||||
|
||||
export abstract class ParseError {
|
||||
constructor(public location: ParseLocation, public msg: string) {}
|
||||
export class ParseSourceSpan {
|
||||
constructor(public start: ParseLocation, public end: ParseLocation) {}
|
||||
|
||||
toString(): string {
|
||||
var source = this.location.file.content;
|
||||
var ctxStart = this.location.offset;
|
||||
return this.start.file.content.substring(this.start.offset, this.end.offset);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ParseError {
|
||||
constructor(public span: ParseSourceSpan, public msg: string) {}
|
||||
|
||||
toString(): string {
|
||||
var source = this.span.start.file.content;
|
||||
var ctxStart = this.span.start.offset;
|
||||
if (ctxStart > source.length - 1) {
|
||||
ctxStart = source.length - 1;
|
||||
}
|
||||
@ -44,17 +52,9 @@ export abstract class ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
let context = source.substring(ctxStart, this.location.offset) + '[ERROR ->]' +
|
||||
source.substring(this.location.offset, ctxEnd + 1);
|
||||
let context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
|
||||
source.substring(this.span.start.offset, ctxEnd + 1);
|
||||
|
||||
return `${this.msg} ("${context}"): ${this.location}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseSourceSpan {
|
||||
constructor(public start: ParseLocation, public end: ParseLocation) {}
|
||||
|
||||
toString(): string {
|
||||
return this.start.file.content.substring(this.start.offset, this.end.offset);
|
||||
return `${this.msg} ("${context}"): ${this.span.start}`;
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +234,7 @@ class ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> implement
|
||||
attrAsts: TemplateAst[]): string[][] {
|
||||
var attrs = visitAndReturnContext(this, attrAsts, {});
|
||||
directives.forEach(directiveMeta => {
|
||||
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
|
||||
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value: string, name: string) => {
|
||||
var prevValue = attrs[name];
|
||||
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
|
||||
});
|
||||
@ -330,12 +330,14 @@ class ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> implement
|
||||
}
|
||||
|
||||
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
|
||||
var entryArray = [];
|
||||
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
|
||||
var entryArray: string[][] = [];
|
||||
StringMapWrapper.forEach(data,
|
||||
(value: string, name: string) => { entryArray.push([name, value]); });
|
||||
// We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
|
||||
var keyValueArray = [];
|
||||
ListWrapper.sort<string[]>(entryArray, (entry1: string[], entry2: string[]) =>
|
||||
StringWrapper.compare(entry1[0], entry2[0]));
|
||||
var keyValueArray: string[][] = [];
|
||||
entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); });
|
||||
return keyValueArray;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import {TemplateCompiler} from './template_compiler';
|
||||
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
export abstract class RuntimeCompiler extends Compiler {
|
||||
abstract compileInHost(componentType: Type): Promise<HostViewFactoryRef>;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user