Merge branch 'master' into ts2dart
This commit is contained in:
commit
e37f58a228
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
packages/
|
||||
.buildlog
|
||||
node_modules
|
||||
bower_components
|
||||
.pub
|
||||
.DS_STORE
|
||||
|
||||
|
@ -27,3 +27,12 @@ script:
|
||||
- ./scripts/ci/build_and_test.sh ${MODE}
|
||||
after_script:
|
||||
- ./scripts/ci/print-logs.sh
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/1ef62e23078036f9cee4
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
slack:
|
||||
secure: EP4MzZ8JMyNQJ4S3cd5LEPWSMjC7ZRdzt3veelDiOeorJ6GwZfCDHncR+4BahDzQAuqyE/yNpZqaLbwRWloDi15qIUsm09vgl/1IyNky1Sqc6lEknhzIXpWSalo4/T9ZP8w870EoDvM/UO+LCV99R3wS8Nm9o99eLoWVb2HIUu0=
|
||||
|
233
CONTRIBUTING.md
Normal file
233
CONTRIBUTING.md
Normal file
@ -0,0 +1,233 @@
|
||||
# Contributing to Angular 2
|
||||
|
||||
We would love for you to contribute to Angular 2 and help make it even better than it is
|
||||
today! As a contributor, here are the guidelines we would like you to follow:
|
||||
|
||||
- [Code of Conduct](#coc)
|
||||
- [Question or Problem?](#question)
|
||||
- [Issues and Bugs](#issue)
|
||||
- [Feature Requests](#feature)
|
||||
- [Submission Guidelines](#submit)
|
||||
- [Coding Rules](#rules)
|
||||
- [Commit Message Guidelines](#commit)
|
||||
- [Signing the CLA](#cla)
|
||||
|
||||
## <a name="coc"></a> Code of Conduct
|
||||
Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct][coc].
|
||||
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
If you have questions about how to *use* Angular, please direct them to the [Google Group][angular-group]
|
||||
discussion list or [StackOverflow][stackoverflow]. We are also available on [Gitter][gitter].
|
||||
|
||||
## <a name="issue"></a> Found an Issue?
|
||||
If you find a bug in the source code or a mistake in the documentation, you can help us by
|
||||
[submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can
|
||||
[submit a Pull Request](#submit-pr) with a fix.
|
||||
|
||||
## <a name="feature"></a> Want a Feature?
|
||||
You can *request* a new feature by [submitting an issue](#submit-issue) to our [GitHub
|
||||
Repository][github]. If you would like to *implement* a new feature then consider what kind of
|
||||
change it is:
|
||||
|
||||
* For a **Major Feature**, first open an issue and outline your proposal so that it can be
|
||||
discussed. This will also allow us to better coordinate our efforts, prevent duplication of work,
|
||||
and help you to craft the change so that it is successfully accepted into the project.
|
||||
* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
|
||||
|
||||
## <a name="docs"></a> Want a Doc Fix?
|
||||
If you want to help improve the docs, then consider what kind of improvement it is:
|
||||
|
||||
* For **Major Changes**, it's a good idea to let others know what you're working on to
|
||||
minimize duplication of effort. Before starting, check out the issue queue for
|
||||
issues labeled [#docs](https://github.com/angular/angular/labels/%23docs).
|
||||
Comment on an issue to let others know what you're working on, or [create a new issue](#submit-issue)
|
||||
if your work doesn't fit within the scope of any of the existing doc issues.
|
||||
Please build and test the documentation before [submitting the Pull Request](#submit-pr), to be sure
|
||||
you haven't accidentally introduced any layout or formatting issues. Also ensure that your commit
|
||||
message is labeled "docs" and follows the [Commit Message Guidelines](#commit) given below.
|
||||
* For **Small Changes**, there is no need to file an issue first. Simply [submit a Pull Request](#submit-pr).
|
||||
|
||||
## <a name="submit"></a> Submission Guidelines
|
||||
|
||||
### <a name="submit-issue"></a> Submitting an Issue
|
||||
Before you submit an issue, search the archive, maybe your question was already answered.
|
||||
|
||||
If your issue appears to be a bug, and hasn't been reported, open a new issue.
|
||||
Help us to maximize the effort we can spend fixing issues and adding new
|
||||
features, by not reporting duplicate issues. Providing the following information will increase the
|
||||
chances of your issue being dealt with quickly:
|
||||
|
||||
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Motivation for or Use Case** - explain why this is a bug for you
|
||||
* **Angular Version(s)** - is it a regression?
|
||||
* **Browsers and Operating System** - is this a problem with all browsers?
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker],
|
||||
[JSFiddle][jsfiddle] or [Runnable][runnable]) or a unambiguous set of steps.
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
|
||||
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
||||
Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
* Search [GitHub](https://github.com/angular/angular.dart/pulls) for an open or closed PR
|
||||
that relates to your submission. You don't want to duplicate effort.
|
||||
* Please sign our [Contributor License Agreement (CLA)](#cla) before sending PRs.
|
||||
We cannot accept code without this.
|
||||
* Make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch master
|
||||
```
|
||||
|
||||
* Create your patch, **including appropriate test cases**.
|
||||
* Follow our [Coding Rules](#rules).
|
||||
* Run the full Angular test suite, as described in the [developer documentation][dev-doc],
|
||||
and ensure that all tests pass.
|
||||
* Commit your changes using a descriptive commit message that follows our
|
||||
[commit message conventions](#commit). Adherence to these conventions
|
||||
is necessary because release notes are automatically generated from these messages.
|
||||
|
||||
```shell
|
||||
git commit -a
|
||||
```
|
||||
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
* Push your branch to GitHub:
|
||||
|
||||
```shell
|
||||
git push origin my-fix-branch
|
||||
```
|
||||
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular 2 test suites for JS and Dart to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push -f
|
||||
```
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
#### After your pull request is merged
|
||||
|
||||
After your pull request is merged, you can safely delete your branch and pull the changes
|
||||
from the main (upstream) repository:
|
||||
|
||||
* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
|
||||
|
||||
```shell
|
||||
git push origin --delete my-fix-branch
|
||||
```
|
||||
|
||||
* Check out the master branch:
|
||||
|
||||
```shell
|
||||
git checkout master -f
|
||||
```
|
||||
|
||||
* Delete the local branch:
|
||||
|
||||
```shell
|
||||
git branch -D my-fix-branch
|
||||
```
|
||||
|
||||
* Update your master with the latest upstream version:
|
||||
|
||||
```shell
|
||||
git pull --ff upstream master
|
||||
```
|
||||
|
||||
## <a name="rules"></a> Coding Rules
|
||||
To ensure consistency throughout the source code, keep these rules in mind as you are working:
|
||||
|
||||
* All features or bug fixes **must be tested** by one or more specs (unit-tests).
|
||||
* All public API methods **must be documented**. (Details TBC).
|
||||
* With the exceptions listed below, we follow the rules contained in
|
||||
[Google's JavaScript Style Guide][js-style-guide]:
|
||||
* Wrap all code at **100 characters**.
|
||||
|
||||
## <a name="commit"></a> Commit Message Guidelines
|
||||
|
||||
We have very precise rules over how our git commit messages can be formatted. This leads to **more
|
||||
readable messages** that are easy to follow when looking through the **project history**. But also,
|
||||
we use the git commit messages to **generate the Angular change log**.
|
||||
|
||||
### Commit Message Format
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||
format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
|
||||
to read on GitHub as well as in various git tools.
|
||||
|
||||
### Type
|
||||
Must be one of the following:
|
||||
|
||||
* **feat**: A new feature
|
||||
* **fix**: A bug fix
|
||||
* **docs**: Documentation only changes
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||
semi-colons, etc)
|
||||
* **refactor**: A code change that neither fixes a bug or adds a feature
|
||||
* **perf**: A code change that improves performance
|
||||
* **test**: Adding missing tests
|
||||
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||
generation
|
||||
|
||||
### Scope
|
||||
The scope could be anything specifying place of the commit change. For example
|
||||
`Compiler`, `ElementInjector`, etc.
|
||||
|
||||
### Subject
|
||||
The subject contains succinct description of the change:
|
||||
|
||||
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
* don't capitalize first letter
|
||||
* no dot (.) at the end
|
||||
|
||||
### Body
|
||||
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
|
||||
The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
### Footer
|
||||
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||
reference GitHub issues that this commit **Closes**.
|
||||
|
||||
|
||||
A detailed explanation can be found in this [document][commit-message-format].
|
||||
|
||||
## <a name="cla"></a> Signing the CLA
|
||||
|
||||
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code
|
||||
changes to be accepted, the CLA must be signed. It's a quick process, we promise!
|
||||
|
||||
* For individuals we have a [simple click-through form][individual-cla].
|
||||
* For corporations we'll need you to
|
||||
[print, sign and one of scan+email, fax or mail the form][corporate-cla].
|
||||
|
||||
|
||||
[angular-group]: https://groups.google.com/forum/#!forum/angular
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
|
||||
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[dev-doc]: https://github.com/angular/angular/blob/master/DEVELOPER.md
|
||||
[github]: https://github.com/angular/angular
|
||||
[gitter]: https://gitter.im/angular/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
|
||||
[jsfiddle]: http://jsfiddle.net/
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[runnable]: http://runnable.com/
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/angular
|
235
DEVELOPER.md
Normal file
235
DEVELOPER.md
Normal file
@ -0,0 +1,235 @@
|
||||
# Building and Testing Angular 2 for JS and Dart
|
||||
|
||||
This document describes how to set up your development environment to build and test Angular, both
|
||||
JS and Dart versions. It also explains the basic mechanics of using `git`, `node`, and `npm`.
|
||||
|
||||
See the [contributing guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md)
|
||||
for how to contribute your own code to
|
||||
|
||||
1. [Prerequisite Software](#prerequisite-software)
|
||||
2. [Getting the Sources](#getting-the-sources)
|
||||
3. [Environment Variable Setup](#environment-variable-setup)
|
||||
4. [Installing NPM Modules and Dart Packages](#installing-npm-modules-and-dart-packages)
|
||||
5. [Running Tests Locally](#running-tests-locally)
|
||||
6. [Project Information](#project-information)
|
||||
7. [CI using Travis](#ci-using-travis)
|
||||
8. [Debugging](#debugging)
|
||||
|
||||
## Prerequisite Software
|
||||
|
||||
Before you can build and test Angular, you must install and configure the
|
||||
following products on your development machine:
|
||||
|
||||
* [Dart](https://www.dartlang.org) (version `>=1.9.0-dev.8.0`), specifically the Dart-SDK and
|
||||
Dartium (a version of [Chromium](http://www.chromium.org) with native support for Dart through
|
||||
the Dart VM). One of the **simplest** ways to get both is to install the **Dart Editor bundle**,
|
||||
which includes the editor, SDK and Dartium. See the [Dart tools](https://www.dartlang.org/tools)
|
||||
download [page for instructions](https://www.dartlang.org/tools/download.html); note that you can
|
||||
download both **stable** and **dev** channel versions from the [download
|
||||
archive](https://www.dartlang.org/tools/download-archive).
|
||||
|
||||
* [Git](http://git-scm.com) and/or the **Github app** (for [Mac](http://mac.github.com) or
|
||||
[Windows](http://windows.github.com)): the [Github Guide to Installing
|
||||
Git](https://help.github.com/articles/set-up-git) is a good source of information.
|
||||
|
||||
* [Node.js](http://nodejs.org) which is used to run a development web server, run tests, and
|
||||
generate distributable files. We also use Node's Package Manager (`npm`). Depending on your
|
||||
system, you can install Node either from source or as a pre-packaged bundle.
|
||||
|
||||
* [Chrome Canary](https://www.google.com/chrome/browser/canary.html), a version of Chrome with
|
||||
bleeding edge functionality, built especially for developers (and early adopters).
|
||||
|
||||
|
||||
## Getting the Sources
|
||||
|
||||
Forking and cloning the Angular repository:
|
||||
|
||||
1. Login to your Github account or create one by following the instructions given
|
||||
[here](https://github.com/signup/free).
|
||||
2. [Fork](http://help.github.com/forking) the [main Angular
|
||||
repository](https://github.com/angular/angular).
|
||||
3. Clone your fork of the Angular repository and define an `upstream` remote pointing back to
|
||||
the Angular repository that you forked in the first place:
|
||||
|
||||
```shell
|
||||
# Clone your Github repository:
|
||||
git clone git@github.com:<github username>/angular.git
|
||||
|
||||
# Go to the Angular directory:
|
||||
cd angular
|
||||
|
||||
# Add the main Angular repository as an upstream remote to your repository:
|
||||
git remote add upstream https://github.com/angular/angular.git
|
||||
```
|
||||
|
||||
## Environment Variable Setup
|
||||
|
||||
Define the environment variables listed below. These are mainly needed for the testing. The
|
||||
notation shown here is for [`bash`](http://www.gnu.org/software/bash); adapt as appropriate for
|
||||
your favorite shell.
|
||||
|
||||
Examples given below of possible values for initializing the environment variables assume **Mac OS
|
||||
X** and that you have installed the Dart Editor in the directory named by
|
||||
`DART_EDITOR_DIR=/Applications/dart`. This is only for illustrative purposes.
|
||||
|
||||
```shell
|
||||
# DARTIUM_BIN: path to a Dartium browser executable; used by Karma to run Dart tests
|
||||
export DARTIUM_BIN="$DART_EDITOR_DIR/chromium/Chromium.app/Contents/MacOS/Chromium"
|
||||
```
|
||||
|
||||
Add the Dart SDK `bin` directory to your path and/or define `DART_SDK` (this is also detailed
|
||||
[here](https://www.dartlang.org/tools/pub/installing.html)):
|
||||
|
||||
```shell
|
||||
# DART_SDK: path to a Dart SDK directory
|
||||
export DART_SDK="$DART_EDITOR_DIR/dart-sdk"
|
||||
|
||||
# Update PATH to include the Dart SDK bin directory
|
||||
PATH+=":$DART_SDK/bin"
|
||||
```
|
||||
|
||||
## Installing NPM Modules and Dart Packages
|
||||
|
||||
Next, install the modules and packages needed to build Angular and run tests:
|
||||
|
||||
```shell
|
||||
# Install Angular project dependencies (package.json)
|
||||
npm install
|
||||
|
||||
# Ensure protractor has the latest webdriver
|
||||
$(npm bin)/webdriver-manager update
|
||||
|
||||
# Install Dart packages
|
||||
pub install
|
||||
```
|
||||
|
||||
**Optional**: In this document, we make use of project local `npm` package scripts and binaries
|
||||
(stored under `./node_modules/.bin`) by prefixing these command invocations with `$(npm bin)`; in
|
||||
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by
|
||||
globally installing these two packages as follows:
|
||||
|
||||
* `npm install -g gulp` (you might need to prefix this command with `sudo`)
|
||||
* `npm install -g protractor` (you might need to prefix this command with `sudo`)
|
||||
|
||||
Since global installs can become stale, we avoid their use in these instructions.
|
||||
|
||||
## Build commands
|
||||
|
||||
To build Angular and prepare tests run
|
||||
|
||||
```shell
|
||||
$(npm bin)/gulp build
|
||||
```
|
||||
|
||||
Notes:
|
||||
* Results are put in the `dist` folder.
|
||||
* This will also run `pub get` for the subfolders in `modules` and run `dartanalyzer` for
|
||||
every file that matches `<module>/src/<module>.dart`, e.g. `di/src/di.dart`
|
||||
|
||||
To clean out the `dist` folder use:
|
||||
```shell
|
||||
$(npm bin)/gulp clean
|
||||
```
|
||||
|
||||
## Running Tests Locally
|
||||
|
||||
### Basic tests
|
||||
|
||||
1. `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e. karma
|
||||
watches the test files for changes and re-runs tests when files are updated).
|
||||
2. `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS (requires a build before)
|
||||
3. `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
|
||||
|
||||
If you prefer running tests in "single-run" mode rather than watch mode use
|
||||
|
||||
* `$(npm bin)/gulp test.unit.js/ci`
|
||||
* `$(npm bin)/gulp test.unit.dart/ci`
|
||||
|
||||
**Note**: If you want to only run a single test you can alter the test you wish
|
||||
to run by changing `it` to `iit` or `describe` to `ddescribe`. This will only
|
||||
run that individual test and make it much easier to debug. `xit` and `xdescribe`
|
||||
can also be useful to exclude a test and a group of tests respectively.
|
||||
|
||||
**Note** for transpiler tests: The karma preprocessor is setup in a way so that after every test
|
||||
run the transpiler is reloaded. With that it is possible to make changes to the preprocessor and
|
||||
run the tests without exiting karma (just touch a test file that you would like to run).
|
||||
|
||||
### E2e tests
|
||||
|
||||
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder).
|
||||
2. `$(npm bin)/gulp serve.js.prod serve.js.dart2js` (runs local webserver).
|
||||
3. `$(npm bin)/protractor protractor-js.conf.js`: JS e2e tests.
|
||||
4. `$(npm bin)/protractor protractor-dart2js.conf.js`: Dart2JS e2e tests.
|
||||
|
||||
Angular specific command line options when running protractor:
|
||||
- `$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
### Performance tests
|
||||
|
||||
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder)
|
||||
2. `$(npm bin)/gulp serve.js.prod serve.js.dart2js` (runs local webserver)
|
||||
3. `$(npm bin)/protractor protractor-js.conf.js --benchmark`: JS performance tests
|
||||
4. `$(npm bin)/protractor protractor-dart2js.conf.js --benchmark`: Dart2JS performance tests
|
||||
|
||||
Angular specific command line options when running protractor (e.g. force gc, ...):
|
||||
`$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
## Project Information
|
||||
|
||||
### Folder structure
|
||||
|
||||
* `modules/*`: modules that will be loaded in the browser
|
||||
* `tools/*`: tools that are needed to build Angular
|
||||
* `dist/*`: build files are placed here.
|
||||
|
||||
### File endings
|
||||
|
||||
* `*.js`: javascript files that get transpiled to Dart and EcmaScript 5
|
||||
* `*.es6`: javascript files that get transpiled only to EcmaScript 5
|
||||
* `*.es5`: javascript files that don't get transpiled
|
||||
* `*.dart`: dart files that don't get transpiled
|
||||
|
||||
## CI using Travis
|
||||
|
||||
For instructions on setting up Continuous Integration using Travis, see the instructions given
|
||||
[here](https://github.com/angular/angular.dart/blob/master/travis.md).
|
||||
|
||||
## Debugging
|
||||
|
||||
### Debug the transpiler
|
||||
|
||||
If you need to debug the transpiler:
|
||||
|
||||
- add a `debugger;` statement in the transpiler code,
|
||||
- from the root folder, execute `node debug $(npm bin)/gulp build` to enter the node
|
||||
debugger
|
||||
- press "c" to execute the program until you reach the `debugger;` statement,
|
||||
- you can then type "repl" to enter the REPL and inspect variables in the context.
|
||||
|
||||
See the [Node.js manual](http://nodejs.org/api/debugger.html) for more information.
|
||||
|
||||
Notes:
|
||||
- You can also execute `node $(npm bin)/karma start karma-dart.conf.js` depending on which
|
||||
code you want to debug (the former will process the "modules" folder while the later processes
|
||||
the transpiler specs).
|
||||
- You can also add `debugger;` statements in the specs (JavaScript). The execution will halt when
|
||||
the developer tools are opened in the browser running Karma.
|
||||
|
||||
### Debug the tests
|
||||
|
||||
If you need to debug the tests:
|
||||
|
||||
- add a `debugger;` statement to the test you want to debug (oe the source code),
|
||||
- execute karma `$(npm bin)/gulp test.js`,
|
||||
- press the top right "DEBUG" button,
|
||||
- open the dev tools and press F5,
|
||||
- the execution halt at the `debugger;` statement
|
||||
|
||||
**Note (WebStorm users)**:
|
||||
You can create a Karma run config from WebStorm.
|
||||
Then in the "Run" menu, press "Debug 'karma-js.conf.js'", WebStorm will stop in the generated code
|
||||
on the `debugger;` statement.
|
||||
You can then step into the code and add watches.
|
||||
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
|
||||
the original source files are not supported at the moment.
|
||||
|
139
README.md
139
README.md
@ -1,125 +1,52 @@
|
||||
Angular [](https://travis-ci.org/angular/angular)
|
||||
Angular [](https://travis-ci.org/angular/angular) [](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
=========
|
||||
|
||||
This is the repository for the upcoming 2.0 version. If you're looking for the current official version of Angular you
|
||||
should go to [angular/angular.js](https://github.com/angular/angular.js)
|
||||
Angular is a development platform for building mobile and desktop web applications. This is the
|
||||
repository for [Angular 2][ng2], both the JavaScript (JS) and [Dart][dart] versions.
|
||||
|
||||
## Build
|
||||
Angular 2 is currently in **Alpha Preview**. We recommend using Angular 1.X for production
|
||||
applications:
|
||||
|
||||
### Prerequisites
|
||||
* [AngularJS][ngJS]: [angular/angular.js](http://github.com/angular/angular.js).
|
||||
* [AngularDart][ngDart]: [angular/angular.dart](http://github.com/angular/angular.dart).
|
||||
|
||||
If you don't already have `npm`, get it by installing [node.js](http://nodejs.org/).
|
||||
|
||||
1. `npm install`
|
||||
2. `npm install -g gulp` (you might need to prefix this command with `sudo`)
|
||||
3. `npm install -g protractor` (you might need to prefix this command with `sudo`)
|
||||
4. `webdriver-manager update`
|
||||
5. If you plan to use Dart:
|
||||
1. [Install the Dart SDK](https://www.dartlang.org/tools/sdk/) - Includes the `pub` command line tool. This repository requires `pub` in version `>=1.9.0-dev.8.0 <2.0.0`
|
||||
2. [Add the Dart SDK's `bin` directory to your system path](https://www.dartlang.org/tools/pub/installing.html)
|
||||
3. Get the pub packages you need: `pub get`
|
||||
6. `gulp build`
|
||||
## Setup & Install Angular 2
|
||||
|
||||
### Folder structure
|
||||
Follow the instructions given on the [Angular download page][download].
|
||||
|
||||
* `modules/*`: modules that will be loaded in the browser
|
||||
* `tools/*`: tools that are needed to build Angular
|
||||
|
||||
### File endings
|
||||
## Want to help?
|
||||
|
||||
* `*.js`: javascript files that get transpiled to Dart and EcmaScript 5
|
||||
* `*.es6`: javascript files that get transpiled only to EcmaScript 5
|
||||
* `*.es5`: javascript files that don't get transpiled
|
||||
* `*.dart`: dart files that don't get transpiled
|
||||
Want to file a bug, or contribute some code or improve documentation? Excellent! Read up on our
|
||||
guidelines for [contributing][contributing].
|
||||
|
||||
### Build
|
||||
|
||||
1. `gulp build` -> result is in `dist` folder
|
||||
## Examples
|
||||
|
||||
* will also run `pub get` for the subfolders in `modules`
|
||||
and run `dartanalyzer` for every file that matches
|
||||
`<module>/src/<module>.dart`, e.g. `di/src/di.dart`
|
||||
To see the examples, first build the project as described
|
||||
[here](http://github.com/angular/angular/blob/master/DEVELOPER.md).
|
||||
|
||||
2. `gulp clean` -> cleans the `dist` folder
|
||||
### Hello World Example
|
||||
|
||||
### Unit tests
|
||||
|
||||
1. `gulp test.unit.js`: JS tests
|
||||
2. `gulp test.unit.dart`: Dart tests
|
||||
|
||||
Notes for transpiler tests:
|
||||
|
||||
The karma preprocessor is setup in a way so that after every test run
|
||||
the transpiler is reloaded. With that it is possible to make changes
|
||||
to the preprocessor and run the tests without exiting karma
|
||||
(just touch a test file that you would like to run).
|
||||
|
||||
### E2e tests
|
||||
|
||||
1. `gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder)
|
||||
2. `gulp serve.js.prod serve.js.dart2js` (runs local webserver)
|
||||
3. `protractor protractor-js.conf.js`: JS e2e tests
|
||||
4. `protractor protractor-dart2js.conf.js`: Dart2JS e2e tests
|
||||
|
||||
Angular specific command line options when running protractor:
|
||||
- `protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
### Performance tests
|
||||
|
||||
1. `gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder)
|
||||
2. `gulp serve.js.prod serve.js.dart2js` (runs local webserver)
|
||||
3. `protractor protractor-js.conf.js --benchmark`: JS performance tests
|
||||
4. `protractor protractor-dart2js.conf.js --benchmark`: Dart2JS performance tests
|
||||
|
||||
Angular specific command line options when running protractor (e.g. force gc, ...):
|
||||
`protractor protractor-{js|dart2js}-conf.js --ng-help`
|
||||
|
||||
### Examples
|
||||
|
||||
To see the examples, first build the project as described above.
|
||||
|
||||
#### Hello World Example
|
||||
This example consists of three basic pieces - a component, a decorator and a service.
|
||||
They are all constructed via injection. For more information see the comments in the
|
||||
source `modules/examples/src/hello_world/index.js`.
|
||||
This example consists of three basic pieces - a component, a decorator and a
|
||||
service. They are all constructed via injection. For more information see the
|
||||
comments in the source `modules/examples/src/hello_world/index.js`.
|
||||
|
||||
You can build this example as either JS or Dart app:
|
||||
* (JS) `gulp serve.js.dev` and open `localhost:8000/examples/src/hello_world/` in Chrome.
|
||||
* (Dart) `gulp serve/examples.dart` and open `localhost:8080/src/hello_world` in Chrome (for dart2js) or Dartium (for Dart VM).
|
||||
|
||||
## Debug the transpiler
|
||||
* JS:
|
||||
* `$(npm bin)/gulp serve.js.dev`, and
|
||||
* open `localhost:8000/examples/src/hello_world/` in Chrome.
|
||||
* Dart:
|
||||
* `$(npm bin)/gulp serve/examples.dart`, and
|
||||
* open `localhost:8080/src/hello_world` in Chrome (for dart2js) or
|
||||
[Dartium][dartium] (for Dart VM).
|
||||
|
||||
If you need to debug the transpiler:
|
||||
|
||||
- add a `debugger;` statement in the transpiler code,
|
||||
- from the root folder, execute `node debug node_modules/.bin/gulp build` to enter the node
|
||||
debugger
|
||||
- press "c" to execute the program until you reach the `debugger;` statement,
|
||||
- you can then type "repl" to enter the REPL and inspect variables in the context.
|
||||
|
||||
See the [Node.js manual](http://nodejs.org/api/debugger.html) for more information.
|
||||
|
||||
Notes:
|
||||
- You can also execute `node node_modules/.bin/karma start karma-dart.conf.js` depending on which
|
||||
code you want to debug (the former will process the "modules" folder while the later processes
|
||||
the transpiler specs),
|
||||
- You can also add `debugger;` statements in the specs (JavaScript). The execution will halt when
|
||||
the developer tools are opened in the browser running Karma.
|
||||
|
||||
## Debug the tests
|
||||
|
||||
If you need to debug the tests:
|
||||
|
||||
- add a `debugger;` statement to the test you want to debug (oe the source code),
|
||||
- execute karma `gulp test.js`,
|
||||
- press the top right "DEBUG" button,
|
||||
- open the dev tools and press F5,
|
||||
- the execution halt at the `debugger;` statement
|
||||
|
||||
Note (WebStorm users):
|
||||
You can create a Karma run config from WebStorm.
|
||||
Then in the "Run" menu, press "Debug 'karma-js.conf.js'", WebStorm will stop in the generated code
|
||||
on the `debugger;` statement.
|
||||
You can then step into the code and add watches.
|
||||
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
|
||||
the original source files are not supported at the moment.
|
||||
[contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
[dart]: http://www.dartlang.org
|
||||
[dartium]: http://www.dartlang.org/tools/dartium
|
||||
[download]: http://angular.io/download
|
||||
[ng2]: http://angular.io
|
||||
[ngDart]: http://angulardart.org
|
||||
[ngJS]: http://angularjs.org
|
||||
|
7
bower.json
Normal file
7
bower.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "angular2",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"polymer": "dart-lang/polymer_js#0.8.0-preview"
|
||||
}
|
||||
}
|
@ -5,11 +5,11 @@ angular.module('code', [])
|
||||
restrict: 'E',
|
||||
terminal: true,
|
||||
compile: function(element) {
|
||||
var linenums = element.hasClass('linenum');// || element.parent()[0].nodeName === 'PRE';
|
||||
var linenums = element.hasClass('linenum');
|
||||
var match = /lang-(\S+)/.exec(element[0].className);
|
||||
var lang = match && match[1];
|
||||
var html = element.html();
|
||||
element.html(window.prettyPrintOne(html, lang, linenums));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
@ -13,6 +13,7 @@ var GUIDES_PATH = PARTIAL_PATH + '/guides';
|
||||
module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
|
||||
|
||||
// Register the services and file readers
|
||||
.factory(require('./services/modules'))
|
||||
.factory(require('./services/atParser'))
|
||||
.factory(require('./services/getJSDocComment'))
|
||||
.factory(require('./services/SourceFile'))
|
||||
@ -44,6 +45,7 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
|
||||
readFilesProcessor.fileReaders = [atScriptFileReader, ngdocFileReader];
|
||||
readFilesProcessor.basePath = path.resolve(__dirname, '../..');
|
||||
readFilesProcessor.sourceFiles = [
|
||||
{ include: 'modules/*/*.js', basePath: 'modules' },
|
||||
{ include: 'modules/*/src/**/*.js', basePath: 'modules' },
|
||||
{ include: 'modules/*/docs/**/*.md', basePath: 'modules' },
|
||||
{ include: 'docs/content/**/*.md', basePath: 'docs/content' }
|
||||
|
@ -5,8 +5,10 @@ module.exports = function processClassDocs(log, getJSDocComment) {
|
||||
return {
|
||||
$runAfter: ['processModuleDocs'],
|
||||
$runBefore: ['parsing-tags', 'generateDocsFromComments'],
|
||||
ignorePrivateMembers: false,
|
||||
$process: function(docs) {
|
||||
var memberDocs = [];
|
||||
var ignorePrivateMembers = this.ignorePrivateMembers;
|
||||
_.forEach(docs, function(classDoc) {
|
||||
if ( classDoc.docType === 'class' ) {
|
||||
|
||||
@ -15,6 +17,8 @@ module.exports = function processClassDocs(log, getJSDocComment) {
|
||||
// Create a new doc for each member of the class
|
||||
_.forEach(classDoc.elements, function(memberDoc) {
|
||||
|
||||
if (ignorePrivateMembers && memberDoc.name.literalToken.value.charAt(0) === '_') return;
|
||||
|
||||
classDoc.members.push(memberDoc);
|
||||
memberDocs.push(memberDoc);
|
||||
|
||||
@ -22,6 +26,7 @@ module.exports = function processClassDocs(log, getJSDocComment) {
|
||||
memberDoc.classDoc = classDoc;
|
||||
memberDoc.name = memberDoc.name.literalToken.value;
|
||||
|
||||
|
||||
if (memberDoc.commentBefore ) {
|
||||
// If this export has a comment, remove it from the list of
|
||||
// comments collected in the module
|
||||
|
@ -7,7 +7,7 @@ var path = require('canonical-path');
|
||||
* This file reader will create a simple doc for each
|
||||
* file including a code AST of the AtScript in the file.
|
||||
*/
|
||||
module.exports = function atScriptFileReader(log, atParser) {
|
||||
module.exports = function atScriptFileReader(log, atParser, modules) {
|
||||
var reader = {
|
||||
name: 'atScriptFileReader',
|
||||
defaultPattern: /\.js$/,
|
||||
@ -18,6 +18,8 @@ module.exports = function atScriptFileReader(log, atParser) {
|
||||
moduleDoc.id = moduleDoc.moduleTree.moduleName;
|
||||
moduleDoc.aliases = [moduleDoc.id];
|
||||
|
||||
modules[moduleDoc.id] = moduleDoc;
|
||||
|
||||
// Readers return a collection of docs read from the file
|
||||
// but in this read there is only one document (module) to return
|
||||
return [moduleDoc];
|
||||
|
@ -16,7 +16,8 @@ module.exports = function AttachCommentTreeVisitor(ParseTreeVisitor, log) {
|
||||
|
||||
if (this.currentComment) log.silly('comment: ' +
|
||||
this.currentComment.range.start.line + ' - ' +
|
||||
this.currentComment.range.end.line);
|
||||
this.currentComment.range.end.line + ' : ' +
|
||||
this.currentComment.range.toString());
|
||||
|
||||
ParseTreeVisitor.prototype.visit.call(this, tree);
|
||||
},
|
||||
@ -24,14 +25,18 @@ module.exports = function AttachCommentTreeVisitor(ParseTreeVisitor, log) {
|
||||
// Really we ought to subclass ParseTreeVisitor but this is fiddly in ES5 so
|
||||
// it is easier to simply override the prototype's method on the instance
|
||||
visitAny: function(tree) {
|
||||
if (tree && tree.location && tree.location.start && this.currentComment) {
|
||||
if (this.currentComment.range.end.offset < tree.location.start.offset) {
|
||||
log.silly('tree: ' + tree.constructor.name + ' - ' + tree.location.start.line);
|
||||
if (tree && tree.location && tree.location.start && this.currentComment &&
|
||||
this.currentComment.range.end.offset < tree.location.start.offset) {
|
||||
log.silly('tree: ' + tree.constructor.name + ' - ' + tree.location.start.line);
|
||||
while (this.currentComment &&
|
||||
this.currentComment.range.end.offset < tree.location.start.offset) {
|
||||
log.silly('comment: ' + this.currentComment.range.start.line + ' - ' +
|
||||
this.currentComment.range.end.line + ' : ' +
|
||||
this.currentComment.range.toString());
|
||||
tree.commentBefore = this.currentComment;
|
||||
this.currentComment.treeAfter = tree;
|
||||
this.index++;
|
||||
this.currentComment = this.comments[this.index];
|
||||
if (this.currentComment) log.silly('comment: ' + this.currentComment.range.start.line + ' - ' + this.currentComment.range.end.line);
|
||||
}
|
||||
}
|
||||
return ParseTreeVisitor.prototype.visitAny.call(this, tree);
|
||||
|
@ -17,8 +17,10 @@ module.exports = function ExportTreeVisitor(ParseTreeVisitor, log) {
|
||||
ParseTreeVisitor.prototype.visitExportDeclaration.call(this, tree);
|
||||
log.silly('exit', this.currentExport);
|
||||
|
||||
// We are exiting the export declaration - store the export object
|
||||
this.exports.push(this.currentExport);
|
||||
if(this.currentExport) {
|
||||
// We are exiting the export declaration - store the export object
|
||||
this.exports.push(this.currentExport);
|
||||
}
|
||||
this.currentExport = null;
|
||||
},
|
||||
|
||||
@ -78,14 +80,15 @@ module.exports = function ExportTreeVisitor(ParseTreeVisitor, log) {
|
||||
},
|
||||
|
||||
visitNamedExport: function(tree) {
|
||||
if ( this.currentExport ) {
|
||||
this.updateExport(tree);
|
||||
this.currentExport = null;
|
||||
// if ( this.currentExport ) {
|
||||
// this.updateExport(tree);
|
||||
|
||||
this.currentExport.namedExport = tree;
|
||||
this.currentExport.name = 'NAMED_EXPORT';
|
||||
// TODO: work out this bit!!
|
||||
// We need to cope with any export specifiers in the named export
|
||||
}
|
||||
// this.currentExport.namedExport = tree;
|
||||
// this.currentExport.name = 'NAMED_EXPORT';
|
||||
// // TODO: work out this bit!!
|
||||
// // We need to cope with any export specifiers in the named export
|
||||
// }
|
||||
},
|
||||
|
||||
// TODO - if the export is an expression, find the thing that is being
|
||||
|
3
docs/dgeni-package/services/modules.js
Normal file
3
docs/dgeni-package/services/modules.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function modules() {
|
||||
return {};
|
||||
};
|
20
docs/public-docs-package/index.js
Normal file
20
docs/public-docs-package/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
var Package = require('dgeni').Package;
|
||||
var basePackage = require('../dgeni-package');
|
||||
|
||||
|
||||
module.exports = new Package('angular-public', [basePackage])
|
||||
|
||||
.processor(require('./processors/filterPublicDocs'))
|
||||
|
||||
.config(function(parseTagsProcessor) {
|
||||
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
|
||||
})
|
||||
|
||||
.config(function(processClassDocs) {
|
||||
processClassDocs.ignorePrivateMembers = true;
|
||||
})
|
||||
|
||||
// Configure file writing
|
||||
.config(function(writeFilesProcessor) {
|
||||
writeFilesProcessor.outputFolder = 'dist/public_docs';
|
||||
});
|
49
docs/public-docs-package/processors/filterPublicDocs.js
Normal file
49
docs/public-docs-package/processors/filterPublicDocs.js
Normal file
@ -0,0 +1,49 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function filterPublicDocs(modules) {
|
||||
return {
|
||||
$runAfter: ['tags-parsed'],
|
||||
$runBefore: ['computing-ids'],
|
||||
$process: function(docs) {
|
||||
|
||||
//console.log('filterPublicDocs', Object.keys(modules));
|
||||
|
||||
|
||||
docs = _.filter(docs, function(doc) {
|
||||
if (doc.docType !== 'class') return true;
|
||||
if (!doc.publicModule) return false;
|
||||
|
||||
//console.log('CLASS:', doc.name, doc.moduleDoc.id);
|
||||
updateModule(doc);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
docs = _.filter(docs, function(doc) {
|
||||
return doc.docType !== 'module' || doc.isPublic;
|
||||
});
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function updateModule(classDoc) {
|
||||
|
||||
var originalModule = classDoc.moduleDoc;
|
||||
var publicModule = modules[classDoc.publicModule];
|
||||
|
||||
if (!publicModule) {
|
||||
throw new Error('Missing module definition: "' + classDoc.publicModule + '"\n' +
|
||||
'Referenced in class: "' + classDoc.moduleDoc.id + '/' + classDoc.name + '"');
|
||||
}
|
||||
|
||||
publicModule.isPublic = true;
|
||||
|
||||
//console.log('UPDATE CLASS', classDoc.id, originalModule.id, publicModule.id);
|
||||
|
||||
_.remove(classDoc.moduleDoc.exports, function(doc) { return doc === classDoc; });
|
||||
classDoc.moduleDoc = publicModule;
|
||||
publicModule.exports.push(classDoc);
|
||||
|
||||
}
|
||||
};
|
143
gulpfile.js
143
gulpfile.js
@ -1,6 +1,7 @@
|
||||
var gulp = require('gulp');
|
||||
var gulpPlugins = require('gulp-load-plugins')();
|
||||
var runSequence = require('run-sequence');
|
||||
var madge = require('madge');
|
||||
var merge = require('merge');
|
||||
var gulpTraceur = require('./tools/transpiler/gulp-traceur');
|
||||
|
||||
@ -19,6 +20,7 @@ var karma = require('karma').server;
|
||||
var minimist = require('minimist');
|
||||
var es5build = require('./tools/build/es5build');
|
||||
var runServerDartTests = require('./tools/build/run_server_dart_tests');
|
||||
var transformCJSTests = require('./tools/build/transformCJSTests');
|
||||
var util = require('./tools/build/util');
|
||||
|
||||
var DART_SDK = require('./tools/build/dartdetect')(gulp);
|
||||
@ -34,7 +36,7 @@ var _COMPILER_CONFIG_JS_DEFAULT = {
|
||||
modules: 'instantiate'
|
||||
};
|
||||
|
||||
var _HTLM_DEFAULT_SCRIPTS_JS = [
|
||||
var _HTML_DEFAULT_SCRIPTS_JS = [
|
||||
{src: gulpTraceur.RUNTIME_PATH, mimeType: 'text/javascript', copy: true},
|
||||
{src: 'node_modules/es6-module-loader/dist/es6-module-loader-sans-promises.src.js',
|
||||
mimeType: 'text/javascript', copy: true},
|
||||
@ -205,16 +207,21 @@ var CONFIG = {
|
||||
},
|
||||
scriptsPerFolder: {
|
||||
js: {
|
||||
'**': _HTLM_DEFAULT_SCRIPTS_JS,
|
||||
'**': _HTML_DEFAULT_SCRIPTS_JS,
|
||||
'benchmarks/**':
|
||||
[
|
||||
{ src: 'tools/build/snippets/url_params_to_form.js', mimeType: 'text/javascript', copy: true }
|
||||
].concat(_HTLM_DEFAULT_SCRIPTS_JS),
|
||||
].concat(_HTML_DEFAULT_SCRIPTS_JS),
|
||||
'benchmarks_external/**':
|
||||
[
|
||||
{ src: 'node_modules/angular/angular.js', mimeType: 'text/javascript', copy: true },
|
||||
{ src: 'tools/build/snippets/url_params_to_form.js', mimeType: 'text/javascript', copy: true }
|
||||
].concat(_HTLM_DEFAULT_SCRIPTS_JS)
|
||||
].concat(_HTML_DEFAULT_SCRIPTS_JS),
|
||||
'benchmarks_external/**/*polymer*/**':
|
||||
[
|
||||
{ src: 'bower_components/polymer/lib/polymer.html', copyOnly: true },
|
||||
{ src: 'tools/build/snippets/url_params_to_form.js', mimeType: 'text/javascript', copy: true }
|
||||
]
|
||||
},
|
||||
dart: {
|
||||
'**': _HTML_DEFAULT_SCRIPTS_DART,
|
||||
@ -228,8 +235,26 @@ var CONFIG = {
|
||||
formatDart: {
|
||||
packageName: 'dart_style',
|
||||
args: ['dart_style:format', '-w', 'dist/dart']
|
||||
},
|
||||
test: {
|
||||
js: {
|
||||
cjs: [
|
||||
'/angular2/test/change_detection/**/*_spec.js',
|
||||
'/angular2/test/core/annotations/**/*_spec.js',
|
||||
'/angular2/test/core/compiler/**/*_spec.js',
|
||||
'/angular2/test/di/**/*_spec.js',
|
||||
'/angular2/test/directives/**/*_spec.js',
|
||||
'/angular2/test/facade/**/*_spec.js',
|
||||
'/angular2/test/forms/**/*_spec.js',
|
||||
'/angular2/test/mock/**/*_spec.js',
|
||||
'/angular2/test/reflection/**/*_spec.js',
|
||||
'/angular2/test/services/**/*_spec.js',
|
||||
'/angular2/test/test_lib/**/*_spec.js'
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
CONFIG.test.js.cjs = CONFIG.test.js.cjs.map(function(s) {return CONFIG.dest.js.cjs + s});
|
||||
|
||||
// ------------
|
||||
// clean
|
||||
@ -299,12 +324,15 @@ gulp.task('build/transpile.js.prod', function(done) {
|
||||
});
|
||||
|
||||
gulp.task('build/transpile.js.cjs', transpile(gulp, gulpPlugins, {
|
||||
src: CONFIG.transpile.src.js,
|
||||
src: CONFIG.transpile.src.js.concat(['modules/**/*.cjs']),
|
||||
dest: CONFIG.dest.js.cjs,
|
||||
outputExt: 'js',
|
||||
options: CONFIG.transpile.options.js.cjs,
|
||||
srcFolderInsertion: CONFIG.srcFolderInsertion.js
|
||||
}));
|
||||
gulp.task('build/transformCJSTests', function() {
|
||||
return gulp.src(CONFIG.dest.js.cjs + '/angular2/test/**/*_spec.js').pipe(transformCJSTests()).pipe(gulp.dest(CONFIG.dest.js.cjs + '/angular2/test/'));
|
||||
});
|
||||
|
||||
gulp.task('build/transpile.dart', transpile(gulp, gulpPlugins, {
|
||||
src: CONFIG.transpile.src.dart,
|
||||
@ -446,6 +474,25 @@ gulp.task('build/format.dart', rundartpackage(gulp, gulpPlugins, {
|
||||
args: CONFIG.formatDart.args
|
||||
}));
|
||||
|
||||
// ------------
|
||||
// check circular dependencies in Node.js context
|
||||
gulp.task('build/checkCircularDependencies', function (done) {
|
||||
var dependencyObject = madge(CONFIG.dest.js.dev.es6, {
|
||||
format: 'es6',
|
||||
paths: [CONFIG.dest.js.dev.es6],
|
||||
extensions: ['.js', '.es6'],
|
||||
onParseFile: function(data) {
|
||||
data.src = data.src.replace(/import \* as/g, "//import * as");
|
||||
}
|
||||
});
|
||||
var circularDependencies = dependencyObject.circular().getArray();
|
||||
if (circularDependencies.length > 0) {
|
||||
console.log(circularDependencies);
|
||||
process.exit(1);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
// ------------------
|
||||
// web servers
|
||||
gulp.task('serve.js.dev', jsserve(gulp, gulpPlugins, {
|
||||
@ -481,17 +528,10 @@ gulp.task('serve/benchmarks_external.dart', pubserve(gulp, gulpPlugins, {
|
||||
// --------------
|
||||
// doc generation
|
||||
var Dgeni = require('dgeni');
|
||||
gulp.task('docs/dgeni', function() {
|
||||
try {
|
||||
var dgeni = new Dgeni([require('./docs/dgeni-package')]);
|
||||
return dgeni.generate();
|
||||
} catch(x) {
|
||||
console.log(x.stack);
|
||||
throw x;
|
||||
}
|
||||
});
|
||||
|
||||
var bower = require('bower');
|
||||
var jasmine = require('gulp-jasmine');
|
||||
var webserver = require('gulp-webserver');
|
||||
|
||||
gulp.task('docs/bower', function() {
|
||||
var bowerTask = bower.commands.install(undefined, undefined, { cwd: 'docs' });
|
||||
bowerTask.on('log', function (result) {
|
||||
@ -503,36 +543,54 @@ gulp.task('docs/bower', function() {
|
||||
return bowerTask;
|
||||
});
|
||||
|
||||
gulp.task('docs/assets', ['docs/bower'], function() {
|
||||
return gulp.src('docs/bower_components/**/*')
|
||||
.pipe(gulp.dest('dist/docs/lib'));
|
||||
});
|
||||
|
||||
gulp.task('docs/app', function() {
|
||||
return gulp.src('docs/app/**/*')
|
||||
.pipe(gulp.dest('dist/docs'));
|
||||
});
|
||||
function createDocsTasks(public) {
|
||||
var dgeniPackage = public ? './docs/public-docs-package' : './docs/dgeni-package';
|
||||
var distDocsPath = public ? 'dist/public_docs' : 'dist/docs';
|
||||
var taskPrefix = public ? 'public_docs' : 'docs';
|
||||
|
||||
gulp.task('docs', ['docs/assets', 'docs/app', 'docs/dgeni']);
|
||||
gulp.task('docs/watch', function() {
|
||||
return gulp.watch('docs/app/**/*', ['docs/app']);
|
||||
});
|
||||
gulp.task(taskPrefix + '/dgeni', function() {
|
||||
try {
|
||||
var dgeni = new Dgeni([require(dgeniPackage)]);
|
||||
return dgeni.generate();
|
||||
} catch(x) {
|
||||
console.log(x.stack);
|
||||
throw x;
|
||||
}
|
||||
});
|
||||
|
||||
var jasmine = require('gulp-jasmine');
|
||||
gulp.task('docs/test', function () {
|
||||
return gulp.src('docs/**/*.spec.js')
|
||||
.pipe(jasmine({
|
||||
includeStackTrace: true
|
||||
gulp.task(taskPrefix + '/assets', ['docs/bower'], function() {
|
||||
return gulp.src('docs/bower_components/**/*')
|
||||
.pipe(gulp.dest(distDocsPath + '/lib'));
|
||||
});
|
||||
|
||||
gulp.task(taskPrefix + '/app', function() {
|
||||
return gulp.src('docs/app/**/*')
|
||||
.pipe(gulp.dest(distDocsPath));
|
||||
});
|
||||
|
||||
gulp.task(taskPrefix, [taskPrefix + '/assets', taskPrefix + '/app', taskPrefix + '/dgeni']);
|
||||
gulp.task(taskPrefix + '/watch', function() {
|
||||
return gulp.watch('docs/app/**/*', [taskPrefix + '/app']);
|
||||
});
|
||||
|
||||
gulp.task(taskPrefix + '/test', function () {
|
||||
return gulp.src('docs/**/*.spec.js')
|
||||
.pipe(jasmine({
|
||||
includeStackTrace: true
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task(taskPrefix + '/serve', function() {
|
||||
gulp.src(distDocsPath + '/')
|
||||
.pipe(webserver({
|
||||
fallback: 'index.html'
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var webserver = require('gulp-webserver');
|
||||
gulp.task('docs/serve', function() {
|
||||
gulp.src('dist/docs/')
|
||||
.pipe(webserver({
|
||||
fallback: 'index.html'
|
||||
}));
|
||||
});
|
||||
createDocsTasks(true);
|
||||
createDocsTasks(false);
|
||||
|
||||
// ------------------
|
||||
// karma tests
|
||||
@ -556,6 +614,9 @@ gulp.task('test.unit.dart/ci', function (done) {
|
||||
karma.start({configFile: __dirname + '/karma-dart.conf.js',
|
||||
singleRun: true, reporters: ['dots'], browsers: getBrowsersFromCLI()}, done);
|
||||
});
|
||||
gulp.task('test.unit.cjs', function (done) {
|
||||
return gulp.src(CONFIG.test.js.cjs).pipe(jasmine(/*{verbose: true, includeStackTrace: true}*/));
|
||||
});
|
||||
|
||||
// ------------------
|
||||
// server tests
|
||||
@ -609,6 +670,7 @@ gulp.task('build.dart', function(done) {
|
||||
gulp.task('build.js.dev', function(done) {
|
||||
runSequence(
|
||||
['build/transpile.js.dev', 'build/html.js.dev', 'build/copy.js.dev', 'build/multicopy.js.dev.es6'],
|
||||
'build/checkCircularDependencies',
|
||||
done
|
||||
);
|
||||
});
|
||||
@ -624,6 +686,7 @@ gulp.task('build.js.cjs', function(done) {
|
||||
runSequence(
|
||||
['build/transpile.js.cjs', 'build/copy.js.cjs', 'build/multicopy.js.cjs'],
|
||||
['build/linknodemodules.js.cjs'],
|
||||
'build/transformCJSTests',
|
||||
done
|
||||
);
|
||||
});
|
||||
|
8
modules/angular2/change_detection.js
vendored
8
modules/angular2/change_detection.js
vendored
@ -1,20 +1,20 @@
|
||||
export {AST} from './src/change_detection/parser/ast';
|
||||
export {Lexer} from './src/change_detection/parser/lexer';
|
||||
export {Parser} from './src/change_detection/parser/parser';
|
||||
export {ContextWithVariableBindings}
|
||||
from './src/change_detection/parser/context_with_variable_bindings';
|
||||
export {Locals}
|
||||
from './src/change_detection/parser/locals';
|
||||
export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError}
|
||||
from './src/change_detection/exceptions';
|
||||
export {ChangeRecord, ChangeDispatcher, ChangeDetector,
|
||||
CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED} from './src/change_detection/interfaces';
|
||||
export {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
|
||||
export {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector, BindingRecord}
|
||||
from './src/change_detection/proto_change_detector';
|
||||
export {DynamicChangeDetector}
|
||||
from './src/change_detection/dynamic_change_detector';
|
||||
export * from './src/change_detection/pipes/pipe_registry';
|
||||
export {uninitialized} from './src/change_detection/change_detection_util';
|
||||
export * from './src/change_detection/pipes/pipe';
|
||||
|
||||
|
||||
import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
|
||||
from './src/change_detection/proto_change_detector';
|
||||
import {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
|
||||
|
2
modules/angular2/di.js
vendored
2
modules/angular2/di.js
vendored
@ -1,4 +1,4 @@
|
||||
export {Inject, InjectPromise, InjectLazy, Optional, DependencyAnnotation} from './src/di/annotations';
|
||||
export {Inject, InjectPromise, InjectLazy, Injectable, Optional, DependencyAnnotation} from './src/di/annotations';
|
||||
export {Injector} from './src/di/injector';
|
||||
export {Binding, Dependency, bind} from './src/di/binding';
|
||||
export {Key, KeyRegistry} from './src/di/key';
|
||||
|
@ -220,7 +220,7 @@ To better understand the kinds of injections which are supported in Angular we h
|
||||
|
||||
### Injecting Services
|
||||
|
||||
Service injection is the most straight forward kind of injection which Angular supports. It involves a component configuring the `componentServices` and then letting the directive ask for the configured service.
|
||||
Service injection is the most straight forward kind of injection which Angular supports. It involves a component configuring the `services` and then letting the directive ask for the configured service.
|
||||
|
||||
This example illustrates how to inject `MyService` into `House` directive.
|
||||
|
||||
@ -231,7 +231,7 @@ 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.
|
||||
componentServices: [MyService] |
|
||||
services: [MyService] |
|
||||
}) |
|
||||
@Template({ | Assume we have a template that needs to be
|
||||
url: 'my_app.html', | configured with directives to be injected.
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Zones
|
||||
|
||||
A Zone is an execution context that persists across async tasks. You can think of it as thread-local storage for
|
||||
JavaScript. Zones are used to intercept all async operation callbacks in the browser. By intercepting async
|
||||
callbacks angular can automatically execute the change detection at the end of the VM turn to update the application
|
||||
JavaScript. Zones are used to intercept all async operation callbacks in the browser. By intercepting async
|
||||
callbacks Angular can automatically execute the change detection at the end of the VM turn to update the application
|
||||
UI bindings. Zones means that in Angular v2 you don't have to remember to call `rootScope.$apply()` in your async call.
|
||||
|
||||
## Execution Context
|
||||
@ -55,8 +55,8 @@ execution which was registered in the `run` block.
|
||||
|
||||
## Putting it all together in Angular
|
||||
|
||||
In Angular2 it is not necessary to notify Angular of changes manually after async callback, because angular relevant
|
||||
async callbacks are intercepted. The question is how do we know which callbacks are angular relevant?
|
||||
In Angular2 it is not necessary to notify Angular of changes manually after async callback, because a relevant
|
||||
async callbacks are intercepted. The question is how do we know which callbacks are Angular relevant?
|
||||
|
||||
```
|
||||
/// Some other code running on page can do async operation
|
||||
@ -97,9 +97,9 @@ Mouse clicked.
|
||||
ANGULAR AUTO-DIGEST!
|
||||
```
|
||||
|
||||
Notice how the place where the listener was register will effect weather or not angular will be notified of the
|
||||
Notice how the place where the listener was registered will effect whether or not Angular will be notified of the
|
||||
async call and cause a change detection to run to update the UI.
|
||||
|
||||
Being able to globally intercept the async operation is important to have a seamless integration with all existing
|
||||
libraries. But it is equally important to be able to differentiate between Angular and none Angular code running
|
||||
libraries. But it is equally important to be able to differentiate between Angular and non-Angular code running
|
||||
on the same page concurrently.
|
||||
|
3
modules/angular2/forms.js
vendored
3
modules/angular2/forms.js
vendored
@ -1,4 +1,5 @@
|
||||
export * from './src/forms/model';
|
||||
export * from './src/forms/directives';
|
||||
export * from './src/forms/validators';
|
||||
export * from './src/forms/validator_directives';
|
||||
export * from './src/forms/validator_directives';
|
||||
export * from './src/forms/form_builder';
|
@ -16,4 +16,4 @@ dependencies:
|
||||
html5lib: '^0.12.0'
|
||||
stack_trace: '^1.1.1'
|
||||
dev_dependencies:
|
||||
guinness: "^0.1.16"
|
||||
guinness: "^0.1.17"
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||
import {ChangeDetectionUtil} from './change_detection_util';
|
||||
|
||||
@ -9,6 +8,7 @@ import {
|
||||
ProtoRecord,
|
||||
RECORD_TYPE_SELF,
|
||||
RECORD_TYPE_PROPERTY,
|
||||
RECORD_TYPE_LOCAL,
|
||||
RECORD_TYPE_INVOKE_METHOD,
|
||||
RECORD_TYPE_CONST,
|
||||
RECORD_TYPE_INVOKE_CLOSURE,
|
||||
@ -44,13 +44,7 @@ import {
|
||||
* var temp;
|
||||
* var context = this.context;
|
||||
*
|
||||
* temp = ChangeDetectionUtil.findContext("address", context);
|
||||
* if (temp instanceof ContextWithVariableBindings) {
|
||||
* address0 = temp.get('address');
|
||||
* } else {
|
||||
* address0 = temp.address;
|
||||
* }
|
||||
*
|
||||
* address0 = context.address;
|
||||
* if (address0 !== this.address0) {
|
||||
* this.address0 = address0;
|
||||
* }
|
||||
@ -70,14 +64,16 @@ import {
|
||||
* }
|
||||
*
|
||||
*
|
||||
* ChangeDetector0.prototype.hydrate = function(context) {
|
||||
* ChangeDetector0.prototype.hydrate = function(context, locals) {
|
||||
* this.context = context;
|
||||
* this.locals = locals;
|
||||
* }
|
||||
*
|
||||
* ChangeDetector0.prototype.dehydrate = function(context) {
|
||||
* this.context = ChangeDetectionUtil.unitialized();
|
||||
* this.address0 = ChangeDetectionUtil.unitialized();
|
||||
* this.city1 = ChangeDetectionUtil.unitialized();
|
||||
* this.locals = null;
|
||||
* }
|
||||
*
|
||||
* ChangeDetector0.prototype.hydrated = function() {
|
||||
@ -99,8 +95,10 @@ var UTIL = "ChangeDetectionUtil";
|
||||
var DISPATCHER_ACCESSOR = "this.dispatcher";
|
||||
var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry";
|
||||
var PROTOS_ACCESSOR = "this.protos";
|
||||
var CONTEXT_ACCESSOR = "this.context";
|
||||
var CHANGE_LOCAL = "change";
|
||||
var CHANGES_LOCAL = "changes";
|
||||
var LOCALS_ACCESSOR = "this.locals";
|
||||
var TEMP_LOCAL = "temp";
|
||||
|
||||
function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
|
||||
@ -135,15 +133,17 @@ function pipeOnDestroyTemplate(pipeNames:List) {
|
||||
|
||||
function hydrateTemplate(type:string, fieldsDefinitions:string, pipeOnDestroy:string):string {
|
||||
return `
|
||||
${type}.prototype.hydrate = function(context) {
|
||||
this.context = context;
|
||||
${type}.prototype.hydrate = function(context, locals) {
|
||||
${CONTEXT_ACCESSOR} = context;
|
||||
${LOCALS_ACCESSOR} = locals;
|
||||
}
|
||||
${type}.prototype.dehydrate = function() {
|
||||
${pipeOnDestroy}
|
||||
${fieldsDefinitions}
|
||||
${LOCALS_ACCESSOR} = null;
|
||||
}
|
||||
${type}.prototype.hydrated = function() {
|
||||
return this.context !== ${UTIL}.unitialized();
|
||||
return ${CONTEXT_ACCESSOR} !== ${UTIL}.unitialized();
|
||||
}
|
||||
`;
|
||||
}
|
||||
@ -165,7 +165,7 @@ var ${TEMP_LOCAL};
|
||||
var ${CHANGE_LOCAL};
|
||||
var ${CHANGES_LOCAL} = null;
|
||||
|
||||
context = this.context;
|
||||
context = ${CONTEXT_ACCESSOR};
|
||||
${records}
|
||||
`;
|
||||
}
|
||||
@ -216,28 +216,6 @@ function assignmentTemplate(field:string, value:string) {
|
||||
return `${field} = ${value};`;
|
||||
}
|
||||
|
||||
function propertyReadTemplate(name:string, context:string, newValue:string) {
|
||||
return `
|
||||
${TEMP_LOCAL} = ${UTIL}.findContext("${name}", ${context});
|
||||
if (${TEMP_LOCAL} instanceof ContextWithVariableBindings) {
|
||||
${newValue} = ${TEMP_LOCAL}.get('${name}');
|
||||
} else {
|
||||
${newValue} = ${TEMP_LOCAL}.${name};
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function invokeMethodTemplate(name:string, args:string, context:string, newValue:string) {
|
||||
return `
|
||||
${TEMP_LOCAL} = ${UTIL}.findContext("${name}", ${context});
|
||||
if (${TEMP_LOCAL} instanceof ContextWithVariableBindings) {
|
||||
${newValue} = ${TEMP_LOCAL}.get('${name}').apply(null, [${args}]);
|
||||
} else {
|
||||
${newValue} = ${context}.${name}(${args});
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function localDefinitionsTemplate(names:List):string {
|
||||
return names.map((n) => `var ${n};`).join("\n");
|
||||
}
|
||||
@ -306,7 +284,7 @@ export class ChangeDetectorJITGenerator {
|
||||
|
||||
generate():Function {
|
||||
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genHydrate());
|
||||
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'ContextWithVariableBindings', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, ContextWithVariableBindings, this.records);
|
||||
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, this.records);
|
||||
}
|
||||
|
||||
genConstructor():string {
|
||||
@ -403,18 +381,13 @@ export class ChangeDetectorJITGenerator {
|
||||
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
|
||||
|
||||
case RECORD_TYPE_PROPERTY:
|
||||
if (r.contextIndex == 0) { // only the first property read can be a local
|
||||
return propertyReadTemplate(r.name, context, newValue);
|
||||
} else {
|
||||
return assignmentTemplate(newValue, `${context}.${r.name}`);
|
||||
}
|
||||
return assignmentTemplate(newValue, `${context}.${r.name}`);
|
||||
|
||||
case RECORD_TYPE_LOCAL:
|
||||
return assignmentTemplate(newValue, `${LOCALS_ACCESSOR}.get('${r.name}')`);
|
||||
|
||||
case RECORD_TYPE_INVOKE_METHOD:
|
||||
if (r.contextIndex == 0) { // only the first property read can be a local
|
||||
return invokeMethodTemplate(r.name, args, context, newValue);
|
||||
} else {
|
||||
return assignmentTemplate(newValue, `${context}.${r.name}(${args})`);
|
||||
}
|
||||
return assignmentTemplate(newValue, `${context}.${r.name}(${args})`);
|
||||
|
||||
case RECORD_TYPE_INVOKE_CLOSURE:
|
||||
return assignmentTemplate(newValue, `${context}(${args})`);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
import {ProtoRecord} from './proto_record';
|
||||
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
|
||||
import {NO_CHANGE} from './pipes/pipe';
|
||||
@ -144,16 +143,6 @@ export class ChangeDetectionUtil {
|
||||
return obj[args[0]];
|
||||
}
|
||||
|
||||
static findContext(name:string, c){
|
||||
while (c instanceof ContextWithVariableBindings) {
|
||||
if (c.hasBinding(name)) {
|
||||
return c;
|
||||
}
|
||||
c = c.parent;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static noChangeMarker(value):boolean {
|
||||
return value === NO_CHANGE;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
|
||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||
import {PipeRegistry} from './pipes/pipe_registry';
|
||||
@ -11,6 +10,7 @@ import {
|
||||
ProtoRecord,
|
||||
RECORD_TYPE_SELF,
|
||||
RECORD_TYPE_PROPERTY,
|
||||
RECORD_TYPE_LOCAL,
|
||||
RECORD_TYPE_INVOKE_METHOD,
|
||||
RECORD_TYPE_CONST,
|
||||
RECORD_TYPE_INVOKE_CLOSURE,
|
||||
@ -26,6 +26,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
dispatcher:any;
|
||||
pipeRegistry;
|
||||
|
||||
locals:any;
|
||||
values:List;
|
||||
changes:List;
|
||||
pipes:List;
|
||||
@ -47,12 +48,14 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
ListWrapper.fill(this.pipes, null);
|
||||
ListWrapper.fill(this.prevContexts, uninitialized);
|
||||
ListWrapper.fill(this.changes, false);
|
||||
this.locals = null;
|
||||
|
||||
this.protos = protoRecords;
|
||||
}
|
||||
|
||||
hydrate(context:any) {
|
||||
hydrate(context:any, locals:any) {
|
||||
this.values[0] = context;
|
||||
this.locals = locals;
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
@ -61,6 +64,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
ListWrapper.fill(this.changes, false);
|
||||
ListWrapper.fill(this.pipes, null);
|
||||
ListWrapper.fill(this.prevContexts, uninitialized);
|
||||
this.locals = null;
|
||||
}
|
||||
|
||||
_destroyPipes() {
|
||||
@ -143,26 +147,15 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
|
||||
case RECORD_TYPE_PROPERTY:
|
||||
var context = this._readContext(proto);
|
||||
var c = ChangeDetectionUtil.findContext(proto.name, context);
|
||||
if (c instanceof ContextWithVariableBindings) {
|
||||
return c.get(proto.name);
|
||||
} else {
|
||||
var propertyGetter:Function = proto.funcOrValue;
|
||||
return propertyGetter(c);
|
||||
}
|
||||
break;
|
||||
return proto.funcOrValue(context);
|
||||
|
||||
case RECORD_TYPE_LOCAL:
|
||||
return this.locals.get(proto.name);
|
||||
|
||||
case RECORD_TYPE_INVOKE_METHOD:
|
||||
var context = this._readContext(proto);
|
||||
var args = this._readArgs(proto);
|
||||
var c = ChangeDetectionUtil.findContext(proto.name, context);
|
||||
if (c instanceof ContextWithVariableBindings) {
|
||||
return FunctionWrapper.apply(c.get(proto.name), args);
|
||||
} else {
|
||||
var methodInvoker:Function = proto.funcOrValue;
|
||||
return methodInvoker(c, args);
|
||||
}
|
||||
break;
|
||||
return proto.funcOrValue(context, args);
|
||||
|
||||
case RECORD_TYPE_KEYED_ACCESS:
|
||||
var arg = this._readArgs(proto)[0];
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import {Locals} from './parser/locals';
|
||||
|
||||
export class ChangeRecord {
|
||||
bindingMemento:any;
|
||||
@ -55,7 +56,7 @@ export class ChangeDetector {
|
||||
addChild(cd:ChangeDetector) {}
|
||||
removeChild(cd:ChangeDetector) {}
|
||||
remove() {}
|
||||
hydrate(context:any) {}
|
||||
hydrate(context:any, locals:Locals) {}
|
||||
dehydrate() {}
|
||||
markPathToRootAsCheckOnce() {}
|
||||
|
||||
|
146
modules/angular2/src/change_detection/parser/ast.js
vendored
146
modules/angular2/src/change_detection/parser/ast.js
vendored
@ -1,9 +1,8 @@
|
||||
import {FIELD, autoConvertAdd, isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
|
||||
import {autoConvertAdd, isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
|
||||
import {List, Map, ListWrapper, StringMapWrapper} from "angular2/src/facade/collection";
|
||||
import {ContextWithVariableBindings} from "./context_with_variable_bindings";
|
||||
|
||||
export class AST {
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
throw new BaseException("Not supported");
|
||||
}
|
||||
|
||||
@ -11,7 +10,7 @@ export class AST {
|
||||
return false;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
assign(context, locals, value) {
|
||||
throw new BaseException("Not supported");
|
||||
}
|
||||
|
||||
@ -24,7 +23,7 @@ export class AST {
|
||||
}
|
||||
|
||||
export class EmptyExpr extends AST {
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -34,7 +33,7 @@ export class EmptyExpr extends AST {
|
||||
}
|
||||
|
||||
export class ImplicitReceiver extends AST {
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
return context;
|
||||
}
|
||||
|
||||
@ -53,10 +52,10 @@ export class Chain extends AST {
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
var result;
|
||||
for (var i = 0; i < this.expressions.length; i++) {
|
||||
var last = this.expressions[i].eval(context);
|
||||
var last = this.expressions[i].eval(context, locals);
|
||||
if (isPresent(last)) result = last;
|
||||
}
|
||||
return result;
|
||||
@ -78,11 +77,11 @@ export class Conditional extends AST {
|
||||
this.falseExp = falseExp;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
if(this.condition.eval(context)) {
|
||||
return this.trueExp.eval(context);
|
||||
eval(context, locals) {
|
||||
if(this.condition.eval(context, locals)) {
|
||||
return this.trueExp.eval(context, locals);
|
||||
} else {
|
||||
return this.falseExp.eval(context);
|
||||
return this.falseExp.eval(context, locals);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,34 +103,29 @@ export class AccessMember extends AST {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var evaluatedContext = this.receiver.eval(context);
|
||||
|
||||
while (evaluatedContext instanceof ContextWithVariableBindings) {
|
||||
if (evaluatedContext.hasBinding(this.name)) {
|
||||
return evaluatedContext.get(this.name);
|
||||
}
|
||||
evaluatedContext = evaluatedContext.parent;
|
||||
eval(context, locals) {
|
||||
if (this.receiver instanceof ImplicitReceiver &&
|
||||
isPresent(locals) && locals.contains(this.name)) {
|
||||
return locals.get(this.name);
|
||||
} else {
|
||||
var evaluatedReceiver = this.receiver.eval(context, locals);
|
||||
return this.getter(evaluatedReceiver);
|
||||
}
|
||||
|
||||
return this.getter(evaluatedContext);
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
var evaluatedContext = this.receiver.eval(context);
|
||||
assign(context, locals, value) {
|
||||
var evaluatedContext = this.receiver.eval(context, locals);
|
||||
|
||||
while (evaluatedContext instanceof ContextWithVariableBindings) {
|
||||
if (evaluatedContext.hasBinding(this.name)) {
|
||||
throw new BaseException(`Cannot reassign a variable binding ${this.name}`)
|
||||
}
|
||||
evaluatedContext = evaluatedContext.parent;
|
||||
if (this.receiver instanceof ImplicitReceiver &&
|
||||
isPresent(locals) && locals.contains(this.name)) {
|
||||
throw new BaseException(`Cannot reassign a variable binding ${this.name}`);
|
||||
} else {
|
||||
return this.setter(evaluatedContext, value);
|
||||
}
|
||||
|
||||
return this.setter(evaluatedContext, value);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -148,9 +142,9 @@ export class KeyedAccess extends AST {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var obj = this.obj.eval(context);
|
||||
var key = this.key.eval(context);
|
||||
eval(context, locals) {
|
||||
var obj = this.obj.eval(context, locals);
|
||||
var key = this.key.eval(context, locals);
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
@ -158,9 +152,9 @@ export class KeyedAccess extends AST {
|
||||
return true;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
var obj = this.obj.eval(context);
|
||||
var key = this.key.eval(context);
|
||||
assign(context, locals, value) {
|
||||
var obj = this.obj.eval(context, locals);
|
||||
var key = this.key.eval(context, locals);
|
||||
obj[key] = value;
|
||||
return value;
|
||||
}
|
||||
@ -193,7 +187,7 @@ export class LiteralPrimitive extends AST {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@ -209,8 +203,8 @@ export class LiteralArray extends AST {
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return ListWrapper.map(this.expressions, (e) => e.eval(context));
|
||||
eval(context, locals) {
|
||||
return ListWrapper.map(this.expressions, (e) => e.eval(context, locals));
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -227,10 +221,10 @@ export class LiteralMap extends AST {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
var res = StringMapWrapper.create();
|
||||
for(var i = 0; i < this.keys.length; ++i) {
|
||||
StringMapWrapper.set(res, this.keys[i], this.values[i].eval(context));
|
||||
StringMapWrapper.set(res, this.keys[i], this.values[i].eval(context, locals));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@ -249,7 +243,7 @@ export class Interpolation extends AST {
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
eval(context, locals) {
|
||||
throw new BaseException("evaluating an Interpolation is not supported");
|
||||
}
|
||||
|
||||
@ -269,13 +263,13 @@ export class Binary extends AST {
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var left = this.left.eval(context);
|
||||
eval(context, locals) {
|
||||
var left = this.left.eval(context, locals);
|
||||
switch (this.operation) {
|
||||
case '&&': return left && this.right.eval(context);
|
||||
case '||': return left || this.right.eval(context);
|
||||
case '&&': return left && this.right.eval(context, locals);
|
||||
case '||': return left || this.right.eval(context, locals);
|
||||
}
|
||||
var right = this.right.eval(context);
|
||||
var right = this.right.eval(context, locals);
|
||||
|
||||
switch (this.operation) {
|
||||
case '+' : return left + right;
|
||||
@ -307,8 +301,8 @@ export class PrefixNot extends AST {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return !this.expression.eval(context);
|
||||
eval(context, locals) {
|
||||
return !this.expression.eval(context, locals);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -325,8 +319,8 @@ export class Assignment extends AST {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return this.target.assign(context, this.value.eval(context));
|
||||
eval(context, locals) {
|
||||
return this.target.assign(context, locals, this.value.eval(context, locals));
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -347,19 +341,16 @@ export class MethodCall extends AST {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var evaluatedContext = this.receiver.eval(context);
|
||||
var evaluatedArgs = evalList(context, this.args);
|
||||
|
||||
while (evaluatedContext instanceof ContextWithVariableBindings) {
|
||||
if (evaluatedContext.hasBinding(this.name)) {
|
||||
var fn = evaluatedContext.get(this.name);
|
||||
return FunctionWrapper.apply(fn, evaluatedArgs);
|
||||
}
|
||||
evaluatedContext = evaluatedContext.parent;
|
||||
eval(context, locals) {
|
||||
var evaluatedArgs = evalList(context, locals, this.args);
|
||||
if (this.receiver instanceof ImplicitReceiver &&
|
||||
isPresent(locals) && locals.contains(this.name)) {
|
||||
var fn = locals.get(this.name);
|
||||
return FunctionWrapper.apply(fn, evaluatedArgs);
|
||||
} else {
|
||||
var evaluatedReceiver = this.receiver.eval(context, locals);
|
||||
return this.fn(evaluatedReceiver, evaluatedArgs);
|
||||
}
|
||||
|
||||
return this.fn(evaluatedContext, evaluatedArgs);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -376,12 +367,12 @@ export class FunctionCall extends AST {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var obj = this.target.eval(context);
|
||||
eval(context, locals) {
|
||||
var obj = this.target.eval(context, locals);
|
||||
if (! (obj instanceof Function)) {
|
||||
throw new BaseException(`${obj} is not a function`);
|
||||
}
|
||||
return FunctionWrapper.apply(obj, evalList(context, this.args));
|
||||
return FunctionWrapper.apply(obj, evalList(context, locals, this.args));
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -400,16 +391,16 @@ export class ASTWithSource extends AST {
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return this.ast.eval(context);
|
||||
eval(context, locals) {
|
||||
return this.ast.eval(context, locals);
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return this.ast.isAssignable;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
return this.ast.assign(context, value);
|
||||
assign(context, locals, value) {
|
||||
return this.ast.assign(context, locals, value);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
@ -454,12 +445,19 @@ export class AstVisitor {
|
||||
visitPrefixNot(ast:PrefixNot) {}
|
||||
}
|
||||
|
||||
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];
|
||||
function evalList(context, exps:List){
|
||||
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0],
|
||||
[0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0],
|
||||
[0,0,0,0,0,0,0,0,0]];
|
||||
|
||||
function evalList(context, locals, exps:List){
|
||||
var length = exps.length;
|
||||
if (length > 10) {
|
||||
throw new BaseException("Cannot have more than 10 argument");
|
||||
}
|
||||
|
||||
var result = _evalListCache[length];
|
||||
for (var i = 0; i < length; i++) {
|
||||
result[i] = exps[i].eval(context);
|
||||
result[i] = exps[i].eval(context, locals);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {BaseException} from 'angular2/src/facade/lang';
|
||||
|
||||
export class ContextWithVariableBindings {
|
||||
parent:any;
|
||||
/// varBindings' keys are read-only. adding/removing keys is not supported.
|
||||
varBindings:Map;
|
||||
|
||||
constructor(parent:any, varBindings:Map) {
|
||||
this.parent = parent;
|
||||
this.varBindings = varBindings;
|
||||
}
|
||||
|
||||
hasBinding(name:string):boolean {
|
||||
return MapWrapper.contains(this.varBindings, name);
|
||||
}
|
||||
|
||||
get(name:string) {
|
||||
return MapWrapper.get(this.varBindings, name);
|
||||
}
|
||||
|
||||
set(name:string, value) {
|
||||
// TODO(rado): consider removing this check if we can guarantee this is not
|
||||
// exposed to the public API.
|
||||
if (this.hasBinding(name)) {
|
||||
MapWrapper.set(this.varBindings, name, value);
|
||||
} else {
|
||||
throw new BaseException(
|
||||
'VariableBindings do not support setting of new keys post-construction.');
|
||||
}
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
MapWrapper.clearValues(this.varBindings);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {List, ListWrapper, SetWrapper} from "angular2/src/facade/collection";
|
||||
import {int, FIELD, NumberWrapper, StringJoiner, StringWrapper} from "angular2/src/facade/lang";
|
||||
import {int, NumberWrapper, StringJoiner, StringWrapper} from "angular2/src/facade/lang";
|
||||
|
||||
export const TOKEN_TYPE_CHARACTER = 1;
|
||||
export const TOKEN_TYPE_IDENTIFIER = 2;
|
||||
@ -8,6 +9,7 @@ export const TOKEN_TYPE_STRING = 4;
|
||||
export const TOKEN_TYPE_OPERATOR = 5;
|
||||
export const TOKEN_TYPE_NUMBER = 6;
|
||||
|
||||
@Injectable()
|
||||
export class Lexer {
|
||||
text:string;
|
||||
tokenize(text:string):List {
|
||||
|
51
modules/angular2/src/change_detection/parser/locals.js
vendored
Normal file
51
modules/angular2/src/change_detection/parser/locals.js
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
import {isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
export class Locals {
|
||||
parent:Locals;
|
||||
current:Map;
|
||||
|
||||
constructor(parent:Locals, current:Map) {
|
||||
this.parent = parent;
|
||||
this.current = current;
|
||||
}
|
||||
|
||||
contains(name:string):boolean {
|
||||
if (MapWrapper.contains(this.current, name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isPresent(this.parent)) {
|
||||
return this.parent.contains(name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
get(name:string) {
|
||||
if (MapWrapper.contains(this.current, name)) {
|
||||
return MapWrapper.get(this.current, name);
|
||||
}
|
||||
|
||||
if (isPresent(this.parent)) {
|
||||
return this.parent.get(name);
|
||||
}
|
||||
|
||||
throw new BaseException(`Cannot find '${name}'`);
|
||||
}
|
||||
|
||||
set(name:string, value) {
|
||||
// TODO(rado): consider removing this check if we can guarantee this is not
|
||||
// exposed to the public API.
|
||||
// TODO: vsavkin maybe it should check only the local map
|
||||
if (MapWrapper.contains(this.current, name)) {
|
||||
MapWrapper.set(this.current, name, value);
|
||||
} else {
|
||||
throw new BaseException('Setting of new keys post-construction is not supported.');
|
||||
}
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
MapWrapper.clearValues(this.current);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import {FIELD, int, isBlank, isPresent, BaseException, StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {int, isBlank, isPresent, BaseException, StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET,
|
||||
$COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer';
|
||||
@ -32,6 +33,7 @@ var _implicitReceiver = new ImplicitReceiver();
|
||||
var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
|
||||
var QUOTE_REGEXP = RegExpWrapper.create("'");
|
||||
|
||||
@Injectable()
|
||||
export class Parser {
|
||||
_lexer:Lexer;
|
||||
_reflector:Reflector;
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
ProtoRecord,
|
||||
RECORD_TYPE_SELF,
|
||||
RECORD_TYPE_PROPERTY,
|
||||
RECORD_TYPE_LOCAL,
|
||||
RECORD_TYPE_INVOKE_METHOD,
|
||||
RECORD_TYPE_CONST,
|
||||
RECORD_TYPE_INVOKE_CLOSURE,
|
||||
@ -45,36 +46,44 @@ import {
|
||||
|
||||
export class ProtoChangeDetector {
|
||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
|
||||
instantiate(dispatcher:any):ChangeDetector{
|
||||
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List):ChangeDetector{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class BindingRecord {
|
||||
ast:AST;
|
||||
bindingMemento:any;
|
||||
directiveMemento:any;
|
||||
|
||||
constructor(ast:AST, bindingMemento:any, directiveMemento:any) {
|
||||
this.ast = ast;
|
||||
this.bindingMemento = bindingMemento;
|
||||
this.directiveMemento = directiveMemento;
|
||||
}
|
||||
}
|
||||
|
||||
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||
_records:List<ProtoRecord>;
|
||||
_recordBuilder:ProtoRecordBuilder;
|
||||
_pipeRegistry:PipeRegistry;
|
||||
_records:List<ProtoRecord>;
|
||||
|
||||
constructor(pipeRegistry:PipeRegistry) {
|
||||
super();
|
||||
this._pipeRegistry = pipeRegistry;
|
||||
this._records = null;
|
||||
this._recordBuilder = new ProtoRecordBuilder();
|
||||
}
|
||||
|
||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
|
||||
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
|
||||
}
|
||||
|
||||
instantiate(dispatcher:any) {
|
||||
this._createRecordsIfNecessary();
|
||||
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List) {
|
||||
this._createRecordsIfNecessary(bindingRecords, variableBindings);
|
||||
return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records);
|
||||
}
|
||||
|
||||
_createRecordsIfNecessary() {
|
||||
_createRecordsIfNecessary(bindingRecords:List, variableBindings:List) {
|
||||
if (isBlank(this._records)) {
|
||||
var records = this._recordBuilder.records;
|
||||
this._records = coalesce(records);
|
||||
var recordBuilder = new ProtoRecordBuilder();
|
||||
ListWrapper.forEach(bindingRecords, (r) => {
|
||||
recordBuilder.addAst(r.ast, r.bindingMemento, r.directiveMemento, variableBindings);
|
||||
});
|
||||
this._records = coalesce(recordBuilder.records);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,29 +91,27 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||
var _jitProtoChangeDetectorClassCounter:number = 0;
|
||||
export class JitProtoChangeDetector extends ProtoChangeDetector {
|
||||
_factory:Function;
|
||||
_recordBuilder:ProtoRecordBuilder;
|
||||
_pipeRegistry;
|
||||
|
||||
constructor(pipeRegistry) {
|
||||
super();
|
||||
this._pipeRegistry = pipeRegistry;
|
||||
this._factory = null;
|
||||
this._recordBuilder = new ProtoRecordBuilder();
|
||||
}
|
||||
|
||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
|
||||
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
|
||||
}
|
||||
|
||||
instantiate(dispatcher:any) {
|
||||
this._createFactoryIfNecessary();
|
||||
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List) {
|
||||
this._createFactoryIfNecessary(bindingRecords, variableBindings);
|
||||
return this._factory(dispatcher, this._pipeRegistry);
|
||||
}
|
||||
|
||||
_createFactoryIfNecessary() {
|
||||
_createFactoryIfNecessary(bindingRecords:List, variableBindings:List) {
|
||||
if (isBlank(this._factory)) {
|
||||
var recordBuilder = new ProtoRecordBuilder();
|
||||
ListWrapper.forEach(bindingRecords, (r) => {
|
||||
recordBuilder.addAst(r.ast, r.bindingMemento, r.directiveMemento, variableBindings);
|
||||
});
|
||||
var c = _jitProtoChangeDetectorClassCounter++;
|
||||
var records = coalesce(this._recordBuilder.records);
|
||||
var records = coalesce(recordBuilder.records);
|
||||
var typeName = `ChangeDetector${c}`;
|
||||
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
|
||||
}
|
||||
@ -118,13 +125,13 @@ class ProtoRecordBuilder {
|
||||
this.records = [];
|
||||
}
|
||||
|
||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
|
||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, variableBindings:List = null) {
|
||||
var last = ListWrapper.last(this.records);
|
||||
if (isPresent(last) && last.directiveMemento == directiveMemento) {
|
||||
last.lastInDirective = false;
|
||||
}
|
||||
|
||||
var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, directiveMemento, this.records.length);
|
||||
var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, directiveMemento, this.records.length, variableBindings);
|
||||
if (! ListWrapper.isEmpty(pr)) {
|
||||
var last = ListWrapper.last(pr);
|
||||
last.lastInBinding = true;
|
||||
@ -139,19 +146,21 @@ class _ConvertAstIntoProtoRecords {
|
||||
protoRecords:List;
|
||||
bindingMemento:any;
|
||||
directiveMemento:any;
|
||||
variableBindings:List;
|
||||
contextIndex:number;
|
||||
expressionAsString:string;
|
||||
|
||||
constructor(bindingMemento:any, directiveMemento:any, contextIndex:number, expressionAsString:string) {
|
||||
constructor(bindingMemento:any, directiveMemento:any, contextIndex:number, expressionAsString:string, variableBindings:List) {
|
||||
this.protoRecords = [];
|
||||
this.bindingMemento = bindingMemento;
|
||||
this.directiveMemento = directiveMemento;
|
||||
this.contextIndex = contextIndex;
|
||||
this.expressionAsString = expressionAsString;
|
||||
this.variableBindings = variableBindings;
|
||||
}
|
||||
|
||||
static convert(ast:AST, bindingMemento:any, directiveMemento:any, contextIndex:number) {
|
||||
var c = new _ConvertAstIntoProtoRecords(bindingMemento, directiveMemento, contextIndex, ast.toString());
|
||||
static convert(ast:AST, bindingMemento:any, directiveMemento:any, contextIndex:number, variableBindings:List) {
|
||||
var c = new _ConvertAstIntoProtoRecords(bindingMemento, directiveMemento, contextIndex, ast.toString(), variableBindings);
|
||||
ast.visit(c);
|
||||
return c.protoRecords;
|
||||
}
|
||||
@ -172,13 +181,22 @@ class _ConvertAstIntoProtoRecords {
|
||||
|
||||
visitAccessMember(ast:AccessMember) {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
|
||||
if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name)) {
|
||||
return this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver);
|
||||
} else {
|
||||
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
|
||||
}
|
||||
}
|
||||
|
||||
visitMethodCall(ast:MethodCall) {
|
||||
visitMethodCall(ast:MethodCall) {;
|
||||
var receiver = ast.receiver.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
|
||||
if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name)) {
|
||||
var target = this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver);
|
||||
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
|
||||
} else {
|
||||
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
|
||||
}
|
||||
}
|
||||
|
||||
visitFunctionCall(ast:FunctionCall) {
|
||||
|
@ -4,9 +4,10 @@ export const RECORD_TYPE_SELF = 0;
|
||||
export const RECORD_TYPE_CONST = 1;
|
||||
export const RECORD_TYPE_PRIMITIVE_OP = 2;
|
||||
export const RECORD_TYPE_PROPERTY = 3;
|
||||
export const RECORD_TYPE_INVOKE_METHOD = 4;
|
||||
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
|
||||
export const RECORD_TYPE_KEYED_ACCESS = 6;
|
||||
export const RECORD_TYPE_LOCAL = 4;
|
||||
export const RECORD_TYPE_INVOKE_METHOD = 5;
|
||||
export const RECORD_TYPE_INVOKE_CLOSURE = 6;
|
||||
export const RECORD_TYPE_KEYED_ACCESS = 7;
|
||||
export const RECORD_TYPE_PIPE = 8;
|
||||
export const RECORD_TYPE_INTERPOLATE = 9;
|
||||
|
||||
|
324
modules/angular2/src/core/annotations/annotations.js
vendored
324
modules/angular2/src/core/annotations/annotations.js
vendored
@ -1,32 +1,199 @@
|
||||
import {ABSTRACT, CONST, normalizeBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
// type StringMap = {[idx: string]: string};
|
||||
|
||||
/**
|
||||
* Directives allow you to attach behavior to the DOM elements.
|
||||
*
|
||||
* Directive is an abstract concept, instead use concrete directives such as: [Component], [Decorator] or [Viewport].
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
@ABSTRACT()
|
||||
export class Directive {
|
||||
selector:any; //string;
|
||||
bind:any;
|
||||
lightDomServices:any; //List;
|
||||
implementsTypes:any; //List;
|
||||
lifecycle:any; //List
|
||||
export class Directive extends Injectable {
|
||||
/**
|
||||
* The CSS selector that triggers the instantiation of a directive.
|
||||
*
|
||||
* Angular only allows directives to trigger on CSS selectors that do not cross element boundaries.
|
||||
* The supported selectors are:
|
||||
*
|
||||
* - `element-name` select by element name.
|
||||
* - `.class` select by class name.
|
||||
* - `[attribute]` select by attribute name.
|
||||
* - `[attribute=value]` select by attribute name and value.
|
||||
* - `:not(sub_selector)` select only if the element does not match the `sub_selector`.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Suppose we have a directive with an `input[type=text]` selector.
|
||||
*
|
||||
* And the following HTML:
|
||||
*
|
||||
* ```html
|
||||
* <form>
|
||||
* <input type="text">
|
||||
* <input type="radio">
|
||||
* <form>
|
||||
* ```
|
||||
*
|
||||
* The directive would only be instantiated on the `<input type="text">` element.
|
||||
*
|
||||
*/
|
||||
selector:string;
|
||||
|
||||
/**
|
||||
* Enumerates the set of properties that accept data binding for a directive.
|
||||
*
|
||||
* The `bind` property defines a set of `directiveProperty` to `bindingProperty` key-value pairs:
|
||||
*
|
||||
* - `directiveProperty` specifies the component property where the value is written.
|
||||
* - `bindingProperty` specifies the DOM property where the value is read from.
|
||||
*
|
||||
* You can include [Pipes] when specifying a `bindingProperty` to allow for data transformation and structural
|
||||
* change detection of the value.
|
||||
*
|
||||
* ## Syntax
|
||||
*
|
||||
* ```
|
||||
* @Directive({
|
||||
* bind: {
|
||||
* 'directiveProperty1': 'bindingProperty1',
|
||||
* 'directiveProperty2': 'bindingProperty2 | pipe1 | ...',
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* ## Basic Property Binding:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[tooltip]',
|
||||
* bind: {
|
||||
* 'tooltipText': 'tooltip'
|
||||
* }
|
||||
* })
|
||||
* class Tooltip {
|
||||
* set tooltipText(text) {
|
||||
* // This will get called every time the 'tooltip' binding changes with the new value.
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* As used in this example:
|
||||
*
|
||||
* ```html
|
||||
* <div [tooltip]="someExpression">
|
||||
* ```
|
||||
*
|
||||
* Whenever the `someExpression` expression changes, the `bind` declaration instructs Angular to update the
|
||||
* `Tooltip`'s `tooltipText` property.
|
||||
*
|
||||
*
|
||||
* Similarly in this example:
|
||||
*
|
||||
* ```html
|
||||
* <div tooltip="Some Text">
|
||||
* ```
|
||||
*
|
||||
* The `Tooltip`'s `tooltipText` property gets initialized to the `Some Text` literal.
|
||||
*
|
||||
*
|
||||
* ## Bindings With Pipes:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[class-set]',
|
||||
* bind: {
|
||||
* 'classChanges': 'classSet | keyValDiff'
|
||||
* }
|
||||
* })
|
||||
* class ClassSet {
|
||||
* set classChanges(changes:KeyValueChanges) {
|
||||
* // This will get called every time the `class-set` expressions changes its structure.
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* As used in this example:
|
||||
*
|
||||
* ```html
|
||||
* <div [class-set]="someExpression">
|
||||
* ```
|
||||
*
|
||||
* In the above example, the `ClassSet` uses the `keyValDiff` [Pipe] for watching structural changes. This means that
|
||||
* the `classChanges` setter gets invoked if the expression changes to a different reference, or if the
|
||||
* structure of the expression changes. (Shallow property watching of the object)
|
||||
*
|
||||
* NOTE: The `someExpression` can also contain its own [Pipe]s. In this case, the two pipes compose as if they were
|
||||
* inlined.
|
||||
*
|
||||
*/
|
||||
bind:any; // StringMap
|
||||
|
||||
/**
|
||||
* Specifies which DOM events the directive listens to and what the action should be.
|
||||
*
|
||||
* The `events` property defines a set of `event` to `method` key-value pairs:
|
||||
*
|
||||
* - `event1` specifies the DOM event that the directive listens to.
|
||||
* - `onMethod1` specifies the method to execute when the event occurs.
|
||||
*
|
||||
*
|
||||
* ## Syntax
|
||||
*
|
||||
* ```
|
||||
* @Directive({
|
||||
* events: {
|
||||
* 'event1': 'onMethod1',
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## Basic Event Binding:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: 'input',
|
||||
* events: {
|
||||
* 'change': 'onChange'
|
||||
* }
|
||||
* })
|
||||
* class InputDecorator {
|
||||
* onChange(event:Event) {
|
||||
* // invoked whenever the DOM element fires the 'change' event.
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
events:any; // StringMap
|
||||
|
||||
/**
|
||||
* Specifies a set of lifecycle events in which the directive participates.
|
||||
*/
|
||||
lifecycle:any; //List<LifecycleEvent>
|
||||
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes,
|
||||
events,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:string,
|
||||
bind:any,
|
||||
lightDomServices:List,
|
||||
implementsTypes:List,
|
||||
events: any,
|
||||
lifecycle:List
|
||||
}={})
|
||||
{
|
||||
super();
|
||||
this.selector = selector;
|
||||
this.lightDomServices = lightDomServices;
|
||||
this.implementsTypes = implementsTypes;
|
||||
this.bind = bind;
|
||||
this.events = events;
|
||||
this.lifecycle = lifecycle;
|
||||
}
|
||||
|
||||
@ -35,62 +202,86 @@ export class Directive {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class Component extends Directive {
|
||||
//TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works
|
||||
lightDomServices:any; //List;
|
||||
shadowDomServices:any; //List;
|
||||
componentServices:any; //List;
|
||||
lifecycle:any; //List
|
||||
services:any; //List;
|
||||
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
shadowDomServices,
|
||||
componentServices,
|
||||
implementsTypes,
|
||||
events,
|
||||
services,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:String,
|
||||
bind:Object,
|
||||
lightDomServices:List,
|
||||
shadowDomServices:List,
|
||||
componentServices:List,
|
||||
implementsTypes:List,
|
||||
events:Object,
|
||||
services:List,
|
||||
lifecycle:List
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
lightDomServices: lightDomServices,
|
||||
implementsTypes: implementsTypes,
|
||||
events: events,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
|
||||
this.lightDomServices = lightDomServices;
|
||||
this.shadowDomServices = shadowDomServices;
|
||||
this.componentServices = componentServices;
|
||||
this.lifecycle = lifecycle;
|
||||
this.services = services;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class DynamicComponent extends Directive {
|
||||
services:any; //List;
|
||||
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
events,
|
||||
services,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:string,
|
||||
bind:Object,
|
||||
events:Object,
|
||||
services:List,
|
||||
lifecycle:List
|
||||
}={}) {
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
events: events,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
|
||||
this.services = services;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class Decorator extends Directive {
|
||||
compileChildren: boolean;
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes,
|
||||
events,
|
||||
lifecycle,
|
||||
compileChildren = true,
|
||||
}:{
|
||||
selector:string,
|
||||
bind:any,
|
||||
lightDomServices:List,
|
||||
implementsTypes:List,
|
||||
events:any,
|
||||
lifecycle:List,
|
||||
compileChildren:boolean
|
||||
}={})
|
||||
@ -99,38 +290,87 @@ export class Decorator extends Directive {
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
lightDomServices: lightDomServices,
|
||||
implementsTypes: implementsTypes,
|
||||
events: events,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class Viewport extends Directive {
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes,
|
||||
events,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:string,
|
||||
bind:any,
|
||||
lightDomServices:List,
|
||||
implementsTypes:List,
|
||||
lifecycle:List
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
lightDomServices: lightDomServices,
|
||||
implementsTypes: implementsTypes,
|
||||
events: events,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//TODO(misko): turn into LifecycleEvent class once we switch to TypeScript;
|
||||
|
||||
/**
|
||||
* Specify that a directive should be notified whenever a [View] that contains it is destroyed.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* ...,
|
||||
* lifecycle: [ onDestroy ]
|
||||
* })
|
||||
* class ClassSet implements OnDestroy {
|
||||
* onDestroy() {
|
||||
* // invoked to notify directive of the containing view destruction.
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export const onDestroy = "onDestroy";
|
||||
|
||||
|
||||
/**
|
||||
* Specify that a directive should be notified when any of its bindings have changed.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: '[class-set]',
|
||||
* bind: {
|
||||
* 'propA': 'propA'
|
||||
* 'propB': 'propB'
|
||||
* }
|
||||
* })
|
||||
* class ClassSet {
|
||||
* propA;
|
||||
* propB;
|
||||
* onChange(changes:{[idx: string, PropertyUpdate]}) {
|
||||
* // This will get called after any of the properties have been updated.
|
||||
* if (changes['propA']) {
|
||||
* // if propA was updated
|
||||
* }
|
||||
* if (changes['propA']) {
|
||||
* // if propB was updated
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export const onChange = "onChange";
|
||||
|
@ -13,3 +13,16 @@ export class EventEmitter extends DependencyAnnotation {
|
||||
this.eventName = eventName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The directive can inject a property setter that would allow setting this property on the
|
||||
* host element
|
||||
*/
|
||||
export class PropertySetter extends DependencyAnnotation {
|
||||
propName: string;
|
||||
@CONST()
|
||||
constructor(propName) {
|
||||
super();
|
||||
this.propName = propName;
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class Template {
|
||||
url:any; //string;
|
||||
inline:any; //string;
|
||||
|
@ -4,6 +4,7 @@ import {DependencyAnnotation} from 'angular2/di';
|
||||
/**
|
||||
* The directive can only be injected from the current element
|
||||
* or from its parent.
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class Parent extends DependencyAnnotation {
|
||||
@CONST()
|
||||
@ -15,6 +16,7 @@ export class Parent extends DependencyAnnotation {
|
||||
/**
|
||||
* The directive can only be injected from the current element
|
||||
* or from its ancestor.
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class Ancestor extends DependencyAnnotation {
|
||||
@CONST()
|
||||
|
128
modules/angular2/src/core/application.js
vendored
128
modules/angular2/src/core/application.js
vendored
@ -1,5 +1,5 @@
|
||||
import {Injector, bind, OpaqueToken} from 'angular2/di';
|
||||
import {Type, FIELD, isBlank, isPresent, BaseException, assertionsEnabled, print} from 'angular2/src/facade/lang';
|
||||
import {Type, isBlank, isPresent, BaseException, assertionsEnabled, print} from 'angular2/src/facade/lang';
|
||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Compiler, CompilerCache} from './compiler/compiler';
|
||||
@ -14,7 +14,7 @@ import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
|
||||
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
|
||||
import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl';
|
||||
import {EventManager, DomEventsPlugin} from 'angular2/src/core/events/event_manager';
|
||||
@ -43,9 +43,7 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
return [
|
||||
bind(appDocumentToken).toValue(DOM.defaultDoc()),
|
||||
bind(appComponentAnnotatedTypeToken).toFactory((reader) => {
|
||||
// TODO(rado): inspect annotation here and warn if there are bindings,
|
||||
// lightDomServices, and other component annotations that are skipped
|
||||
// for bootstrapping components.
|
||||
// TODO(rado): investigate whether to support bindings on root component.
|
||||
return reader.read(appComponentType);
|
||||
}, [DirectiveMetadataReader]),
|
||||
|
||||
@ -69,7 +67,7 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
// the angular application. Thus the context and lightDomInjector are
|
||||
// empty.
|
||||
var view = appProtoView.instantiate(null, eventManager);
|
||||
view.hydrate(injector, null, new Object());
|
||||
view.hydrate(injector, null, null, new Object(), null);
|
||||
return view;
|
||||
});
|
||||
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken,
|
||||
@ -84,7 +82,9 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
var plugins = [new HammerGesturesPlugin(), new DomEventsPlugin()];
|
||||
return new EventManager(plugins, zone);
|
||||
}, [VmTurnZone]),
|
||||
bind(ShadowDomStrategy).toClass(NativeShadowDomStrategy),
|
||||
bind(ShadowDomStrategy).toFactory(
|
||||
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
|
||||
[StyleUrlResolver, appDocumentToken]),
|
||||
Compiler,
|
||||
CompilerCache,
|
||||
TemplateResolver,
|
||||
@ -117,18 +117,122 @@ function _createVmZone(givenReporter:Function): VmTurnZone {
|
||||
return zone;
|
||||
}
|
||||
|
||||
// Multiple calls to this method are allowed. Each application would only share
|
||||
// _rootInjector, which is not user-configurable by design, thus safe to share.
|
||||
export function bootstrap(appComponentType: Type, bindings: List<Binding>=null, givenBootstrapErrorReporter: Function=null): Promise {
|
||||
/**
|
||||
* Bootstrapping for Angular applications.
|
||||
*
|
||||
* You instantiate an Angular application by explicitly specifying a component to use as the root component for your
|
||||
* application via the `bootstrap()` method.
|
||||
*
|
||||
* ## Simple Example
|
||||
*
|
||||
* Assuming this `index.html`:
|
||||
*
|
||||
* ```html
|
||||
* <html>
|
||||
* <!-- load Angular script tags here. -->
|
||||
* <body>
|
||||
* <my-app>loading...</my-app>
|
||||
* </body>
|
||||
* </html>
|
||||
* ```
|
||||
*
|
||||
* An application is bootstrapped inside an existing browser DOM, typically `index.html`. Unlike Angular 1, Angular 2
|
||||
* does not compile/process bindings in `index.html`. This is mainly for security reasons, as well as architectural
|
||||
* changes in Angular 2. This means that `index.html` can safely be processed using server-side technologies such as
|
||||
* bindings. (which may use double-curly `{{ syntax }}` without collision from Angular 2 component double-curly
|
||||
* `{{ syntax }}`.)
|
||||
*
|
||||
* We can use this script code:
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* selector: 'my-app'
|
||||
* })
|
||||
* @Template({
|
||||
* inline: 'Hello {{ name }}!'
|
||||
* })
|
||||
* class MyApp {
|
||||
* name:string;
|
||||
*
|
||||
* constructor() {
|
||||
* this.name = 'World';
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* main() {
|
||||
* return bootstrap(MyApp);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* When the app developer invokes `bootstrap()` with the root component `MyApp` as its argument, Angular performs the
|
||||
* following tasks:
|
||||
*
|
||||
* 1. It uses the component's `selector` property to locate the DOM element which needs to be upgraded into
|
||||
* the angular component.
|
||||
* 2. It creates a new child injector (from the primordial injector) and configures the injector with the component's
|
||||
* `services`. Optionally, you can also override the injector configuration for an app by invoking
|
||||
* `bootstrap` with the `componentServiceBindings` argument.
|
||||
* 3. It creates a new [Zone] and connects it to the angular application's change detection domain instance.
|
||||
* 4. It creates a shadow DOM on the selected component's host element and loads the template into it.
|
||||
* 5. It instantiates the specified component.
|
||||
* 6. Finally, Angular performs change detection to apply the initial data bindings for the application.
|
||||
*
|
||||
*
|
||||
* ## Instantiating Multiple Applications on a Single Page
|
||||
*
|
||||
* There are two ways to do this.
|
||||
*
|
||||
*
|
||||
* ### Isolated Applications
|
||||
*
|
||||
* Angular creates a new application each time that the `bootstrap()` method is invoked. When multiple applications
|
||||
* are created for a page, Angular treats each application as independent within an isolated change detection and
|
||||
* [Zone] domain. If you need to share data between applications, use the strategy described in the next
|
||||
* section, "Applications That Share Change Detection."
|
||||
*
|
||||
*
|
||||
* ### Applications That Share Change Detection
|
||||
*
|
||||
* If you need to bootstrap multiple applications that share common data, the applications must share a common
|
||||
* change detection and zone. To do that, create a meta-component that lists the application components in its template.
|
||||
* By only invoking the `bootstrap()` method once, with the meta-component as its argument, you ensure that only a single
|
||||
* change detection zone is created and therefore data can be shared across the applications.
|
||||
*
|
||||
*
|
||||
* ## Primordial Injector
|
||||
*
|
||||
* When working within a browser window, there are many singleton resources: cookies, title, location, and others.
|
||||
* Angular services that represent these resources must likewise be shared across all Angular applications that
|
||||
* occupy the same browser window. For this reason, Angular creates exactly one global primordial injector which stores
|
||||
* all shared services, and each angular application injector has the primordial injector as its parent.
|
||||
*
|
||||
* Each application has its own private injector as well. When there are multiple applications on a page, Angular treats
|
||||
* each application injector's services as private to that application.
|
||||
*
|
||||
*
|
||||
* # API
|
||||
* - [appComponentType]: The root component which should act as the application. This is a reference to a [Type]
|
||||
* which is annotated with `@Component(...)`.
|
||||
* - [componentServiceBindings]: An additional set of bindings that can be added to the [Component.services] to
|
||||
* override default injection behavior.
|
||||
* - [errorReporter]: `function(exception:any, stackTrace:string)` a default error reporter for unhandled exceptions.
|
||||
*
|
||||
* Returns a [Promise] with the application`s private [Injector].
|
||||
*
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export function bootstrap(appComponentType: Type,
|
||||
componentServiceBindings: List<Binding>=null,
|
||||
errorReporter: Function=null): Promise<Injector> {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
var bootstrapProcess = PromiseWrapper.completer();
|
||||
|
||||
var zone = _createVmZone(givenBootstrapErrorReporter);
|
||||
var zone = _createVmZone(errorReporter);
|
||||
zone.run(() => {
|
||||
// TODO(rado): prepopulate template cache, so applications with only
|
||||
// index.html and main.js are possible.
|
||||
|
||||
var appInjector = _createAppInjector(appComponentType, bindings, zone);
|
||||
var appInjector = _createAppInjector(appComponentType, componentServiceBindings, zone);
|
||||
|
||||
PromiseWrapper.then(appInjector.asyncGet(appViewToken),
|
||||
(rootView) => {
|
||||
|
@ -1,5 +1,8 @@
|
||||
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from 'angular2/change_detection';
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class BindingPropagationConfig {
|
||||
_cd:ChangeDetector;
|
||||
|
||||
|
28
modules/angular2/src/core/compiler/compiler.js
vendored
28
modules/angular2/src/core/compiler/compiler.js
vendored
@ -1,3 +1,4 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from 'angular2/src/facade/lang';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||
@ -11,7 +12,6 @@ import {CompileElement} from './pipeline/compile_element';
|
||||
import {createDefaultSteps} from './pipeline/default_steps';
|
||||
import {TemplateLoader} from './template_loader';
|
||||
import {TemplateResolver} from './template_resolver';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {Template} from '../annotations/template';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {CompileStep} from './pipeline/compile_step';
|
||||
@ -22,7 +22,9 @@ import {CssProcessor} from './css_processor';
|
||||
/**
|
||||
* Cache that stores the ProtoView of the template of a component.
|
||||
* Used to prevent duplicate work and resolve cyclic dependencies.
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
@Injectable()
|
||||
export class CompilerCache {
|
||||
_cache:Map;
|
||||
constructor() {
|
||||
@ -47,7 +49,9 @@ export class CompilerCache {
|
||||
* The compiler loads and translates the html templates of components into
|
||||
* nested ProtoViews. To decompose its functionality it uses
|
||||
* the CompilePipeline and the CompileSteps.
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
@Injectable()
|
||||
export class Compiler {
|
||||
_reader: DirectiveMetadataReader;
|
||||
_parser:Parser;
|
||||
@ -56,7 +60,6 @@ export class Compiler {
|
||||
_templateLoader:TemplateLoader;
|
||||
_compiling:Map<Type, Promise>;
|
||||
_shadowDomStrategy: ShadowDomStrategy;
|
||||
_shadowDomDirectives: List<DirectiveMetadata>;
|
||||
_templateResolver: TemplateResolver;
|
||||
_componentUrlMapper: ComponentUrlMapper;
|
||||
_urlResolver: UrlResolver;
|
||||
@ -80,11 +83,6 @@ export class Compiler {
|
||||
this._templateLoader = templateLoader;
|
||||
this._compiling = MapWrapper.create();
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
this._shadowDomDirectives = [];
|
||||
var types = shadowDomStrategy.polyfillDirectives();
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
ListWrapper.push(this._shadowDomDirectives, reader.read(types[i]));
|
||||
}
|
||||
this._templateResolver = templateResolver;
|
||||
this._componentUrlMapper = componentUrlMapper;
|
||||
this._urlResolver = urlResolver;
|
||||
@ -93,12 +91,8 @@ export class Compiler {
|
||||
}
|
||||
|
||||
createSteps(component:Type, template: Template):List<CompileStep> {
|
||||
// Merge directive metadata (from the template and from the shadow dom strategy)
|
||||
var dirMetadata = [];
|
||||
var tplMetadata = ListWrapper.map(this._flattenDirectives(template),
|
||||
var dirMetadata = ListWrapper.map(this._flattenDirectives(template),
|
||||
(d) => this._reader.read(d));
|
||||
dirMetadata = ListWrapper.concat(dirMetadata, tplMetadata);
|
||||
dirMetadata = ListWrapper.concat(dirMetadata, this._shadowDomDirectives);
|
||||
|
||||
var cmpMetadata = this._reader.read(component);
|
||||
|
||||
@ -172,7 +166,7 @@ export class Compiler {
|
||||
var nestedPVPromises = [];
|
||||
for (var i = 0; i < compileElements.length; i++) {
|
||||
var ce = compileElements[i];
|
||||
if (isPresent(ce.componentDirective)) {
|
||||
if (ce.hasNestedView) {
|
||||
this._compileNestedProtoView(ce, nestedPVPromises);
|
||||
}
|
||||
}
|
||||
@ -199,10 +193,10 @@ export class Compiler {
|
||||
var protoView = this._compile(ce.componentDirective.type);
|
||||
|
||||
if (PromiseWrapper.isPromise(protoView)) {
|
||||
ListWrapper.push(promises, protoView);
|
||||
protoView.then(function (protoView) {
|
||||
ce.inheritedElementBinder.nestedProtoView = protoView;
|
||||
});
|
||||
ListWrapper.push(
|
||||
promises,
|
||||
protoView.then(function(pv) { ce.inheritedElementBinder.nestedProtoView = pv;})
|
||||
);
|
||||
} else {
|
||||
ce.inheritedElementBinder.nestedProtoView = protoView;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Type, isPresent} from 'angular2/src/facade/lang';
|
||||
import {Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
@Injectable()
|
||||
export class ComponentUrlMapper {
|
||||
// Returns the base URL to the component source file.
|
||||
// The returned URL could be:
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
@ -15,6 +16,7 @@ import {DirectiveMetadata} from './directive_metadata';
|
||||
* - Apply any given transformers,
|
||||
* - Apply the shadow DOM strategy style step.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CssProcessor {
|
||||
_transformers: List<CssTransformer>;
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {Directive} from '../annotations/annotations';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
@Injectable()
|
||||
export class DirectiveMetadataReader {
|
||||
read(type:Type):DirectiveMetadata {
|
||||
var annotations = reflector.annotations(type);
|
||||
|
@ -1,22 +1,35 @@
|
||||
import {ProtoElementInjector} from './element_injector';
|
||||
import {int, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as eiModule from './element_injector';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {List, Map} from 'angular2/src/facade/collection';
|
||||
import {ProtoView} from './view';
|
||||
import {List, StringMap} from 'angular2/src/facade/collection';
|
||||
import * as viewModule from './view';
|
||||
|
||||
export class ElementBinder {
|
||||
protoElementInjector:ProtoElementInjector;
|
||||
protoElementInjector:eiModule.ProtoElementInjector;
|
||||
componentDirective:DirectiveMetadata;
|
||||
viewportDirective:DirectiveMetadata;
|
||||
textNodeIndices:List<int>;
|
||||
hasElementPropertyBindings:boolean;
|
||||
nestedProtoView: ProtoView;
|
||||
events:Map;
|
||||
nestedProtoView: viewModule.ProtoView;
|
||||
events:StringMap;
|
||||
contentTagSelector:string;
|
||||
parent:ElementBinder;
|
||||
index:int;
|
||||
distanceToParent:int;
|
||||
constructor(
|
||||
protoElementInjector: ProtoElementInjector, componentDirective:DirectiveMetadata,
|
||||
index:int, parent:ElementBinder, distanceToParent: int,
|
||||
protoElementInjector: eiModule.ProtoElementInjector, componentDirective:DirectiveMetadata,
|
||||
viewportDirective:DirectiveMetadata) {
|
||||
if (isBlank(index)) {
|
||||
throw new BaseException('null index not allowed.');
|
||||
}
|
||||
|
||||
this.protoElementInjector = protoElementInjector;
|
||||
this.componentDirective = componentDirective;
|
||||
this.viewportDirective = viewportDirective;
|
||||
this.parent = parent;
|
||||
this.index = index;
|
||||
this.distanceToParent = distanceToParent;
|
||||
// updated later when events are bound
|
||||
this.events = null;
|
||||
// updated later when text nodes are bound
|
||||
@ -25,5 +38,7 @@ export class ElementBinder {
|
||||
this.hasElementPropertyBindings = false;
|
||||
// updated later, so we are able to resolve cycles
|
||||
this.nestedProtoView = null;
|
||||
// updated later in the compilation pipeline
|
||||
this.contentTagSelector = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang';
|
||||
import {Math} from 'angular2/src/facade/math';
|
||||
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di';
|
||||
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
|
||||
import {EventEmitter} from 'angular2/src/core/annotations/events';
|
||||
import {View, ProtoView} from 'angular2/src/core/compiler/view';
|
||||
import {LightDom, SourceLightDom, DestinationLightDom} from 'angular2/src/core/compiler/shadow_dom_emulation/light_dom';
|
||||
import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di';
|
||||
import * as viewModule from 'angular2/src/core/compiler/view';
|
||||
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
||||
import {NgElement} from 'angular2/src/core/dom/element';
|
||||
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations'
|
||||
import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config'
|
||||
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations';
|
||||
import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config';
|
||||
import * as pclModule from 'angular2/src/core/compiler/private_component_location';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
|
||||
|
||||
@ -23,18 +24,16 @@ class StaticKeys {
|
||||
viewId:number;
|
||||
ngElementId:number;
|
||||
viewContainerId:number;
|
||||
destinationLightDomId:number;
|
||||
sourceLightDomId:number;
|
||||
bindingPropagationConfigId:number;
|
||||
privateComponentLocationId:number;
|
||||
|
||||
constructor() {
|
||||
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
|
||||
this.viewId = Key.get(View).id;
|
||||
this.viewId = Key.get(viewModule.View).id;
|
||||
this.ngElementId = Key.get(NgElement).id;
|
||||
this.viewContainerId = Key.get(ViewContainer).id;
|
||||
this.destinationLightDomId = Key.get(DestinationLightDom).id;
|
||||
this.sourceLightDomId = Key.get(SourceLightDom).id;
|
||||
this.bindingPropagationConfigId = Key.get(BindingPropagationConfig).id;
|
||||
this.privateComponentLocationId = Key.get(pclModule.PrivateComponentLocation).id;
|
||||
}
|
||||
|
||||
static instance() {
|
||||
@ -90,34 +89,37 @@ class TreeNode {
|
||||
export class DirectiveDependency extends Dependency {
|
||||
depth:int;
|
||||
eventEmitterName:string;
|
||||
propSetterName:string;
|
||||
|
||||
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean,
|
||||
properties:List, depth:int, eventEmitterName: string) {
|
||||
properties:List, depth:int, eventEmitterName: string, propSetterName: string) {
|
||||
super(key, asPromise, lazy, optional, properties);
|
||||
this.depth = depth;
|
||||
this.eventEmitterName = eventEmitterName;
|
||||
this.propSetterName = propSetterName;
|
||||
}
|
||||
|
||||
static createFrom(d:Dependency):Dependency {
|
||||
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
|
||||
d.properties, DirectiveDependency._depth(d.properties),
|
||||
DirectiveDependency._eventEmitterName(d.properties));
|
||||
}
|
||||
var depth = 0;
|
||||
var eventName = null;
|
||||
var propName = null;
|
||||
var properties = d.properties;
|
||||
|
||||
static _depth(properties):int {
|
||||
if (properties.length == 0) return 0;
|
||||
if (ListWrapper.any(properties, p => p instanceof Parent)) return 1;
|
||||
if (ListWrapper.any(properties, p => p instanceof Ancestor)) return MAX_DEPTH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static _eventEmitterName(properties):string {
|
||||
for (var i = 0; i < properties.length; i++) {
|
||||
if (properties[i] instanceof EventEmitter) {
|
||||
return properties[i].eventName;
|
||||
var property = properties[i];
|
||||
if (property instanceof Parent) {
|
||||
depth = 1;
|
||||
} else if (property instanceof Ancestor) {
|
||||
depth = MAX_DEPTH;
|
||||
} else if (property instanceof EventEmitter) {
|
||||
eventName = property.eventName;
|
||||
} else if (property instanceof PropertySetter) {
|
||||
propName = property.propName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, d.properties, depth,
|
||||
eventName, propName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,20 +148,17 @@ export class DirectiveBinding extends Binding {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
|
||||
export class PreBuiltObjects {
|
||||
view:View;
|
||||
view:viewModule.View;
|
||||
element:NgElement;
|
||||
viewContainer:ViewContainer;
|
||||
lightDom:LightDom;
|
||||
bindingPropagationConfig:BindingPropagationConfig;
|
||||
constructor(view, element:NgElement, viewContainer:ViewContainer, lightDom:LightDom,
|
||||
constructor(view, element:NgElement, viewContainer:ViewContainer,
|
||||
bindingPropagationConfig:BindingPropagationConfig) {
|
||||
this.view = view;
|
||||
this.element = element;
|
||||
this.viewContainer = viewContainer;
|
||||
this.lightDom = lightDom;
|
||||
this.bindingPropagationConfig = bindingPropagationConfig;
|
||||
}
|
||||
}
|
||||
@ -208,7 +207,7 @@ export class ProtoElementInjector {
|
||||
_keyId9:int;
|
||||
parent:ProtoElementInjector;
|
||||
index:int;
|
||||
view:View;
|
||||
view:viewModule.View;
|
||||
distanceToParent:number;
|
||||
|
||||
/** Whether the element is exported as $implicit. */
|
||||
@ -256,8 +255,8 @@ export class ProtoElementInjector {
|
||||
}
|
||||
}
|
||||
|
||||
instantiate(parent:ElementInjector, host:ElementInjector, eventCallbacks):ElementInjector {
|
||||
return new ElementInjector(this, parent, host, eventCallbacks);
|
||||
instantiate(parent:ElementInjector, host:ElementInjector):ElementInjector {
|
||||
return new ElementInjector(this, parent, host);
|
||||
}
|
||||
|
||||
directParent(): ProtoElementInjector {
|
||||
@ -310,8 +309,10 @@ export class ElementInjector extends TreeNode {
|
||||
_obj9:any;
|
||||
_preBuiltObjects;
|
||||
_constructionCounter;
|
||||
_eventCallbacks;
|
||||
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, eventCallbacks: Map) {
|
||||
_privateComponent;
|
||||
_privateComponentBinding:DirectiveBinding;
|
||||
|
||||
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) {
|
||||
super(parent);
|
||||
if (isPresent(parent) && isPresent(host)) {
|
||||
throw new BaseException('Only either parent or host is allowed');
|
||||
@ -329,7 +330,6 @@ export class ElementInjector extends TreeNode {
|
||||
this._preBuiltObjects = null;
|
||||
this._lightDomAppInjector = null;
|
||||
this._shadowDomAppInjector = null;
|
||||
this._eventCallbacks = eventCallbacks;
|
||||
this._obj0 = null;
|
||||
this._obj1 = null;
|
||||
this._obj2 = null;
|
||||
@ -360,6 +360,9 @@ export class ElementInjector extends TreeNode {
|
||||
if (isPresent(p._binding7) && p._binding7.callOnDestroy) {this._obj7.onDestroy();}
|
||||
if (isPresent(p._binding8) && p._binding8.callOnDestroy) {this._obj8.onDestroy();}
|
||||
if (isPresent(p._binding9) && p._binding9.callOnDestroy) {this._obj9.onDestroy();}
|
||||
if (isPresent(this._privateComponentBinding) && this._privateComponentBinding.callOnDestroy) {
|
||||
this._privateComponent.onDestroy();
|
||||
}
|
||||
|
||||
this._obj0 = null;
|
||||
this._obj1 = null;
|
||||
@ -371,6 +374,7 @@ export class ElementInjector extends TreeNode {
|
||||
this._obj7 = null;
|
||||
this._obj8 = null;
|
||||
this._obj9 = null;
|
||||
this._privateComponent = null;
|
||||
|
||||
this._constructionCounter = 0;
|
||||
}
|
||||
@ -393,6 +397,15 @@ export class ElementInjector extends TreeNode {
|
||||
if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);
|
||||
if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);
|
||||
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
|
||||
if (isPresent(this._privateComponentBinding)) {
|
||||
this._privateComponent = this._new(this._privateComponentBinding);
|
||||
}
|
||||
}
|
||||
|
||||
createPrivateComponent(componentType:Type, annotation:Directive) {
|
||||
this._privateComponentBinding = DirectiveBinding.createFromType(componentType, annotation);
|
||||
this._privateComponent = this._new(this._privateComponentBinding);
|
||||
return this._privateComponent;
|
||||
}
|
||||
|
||||
_checkShadowDomAppInjector(shadowDomAppInjector:Injector) {
|
||||
@ -433,6 +446,14 @@ export class ElementInjector extends TreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
getPrivateComponent() {
|
||||
return this._privateComponent;
|
||||
}
|
||||
|
||||
getShadowDomAppInjector() {
|
||||
return this._shadowDomAppInjector;
|
||||
}
|
||||
|
||||
directParent(): ElementInjector {
|
||||
return this._proto.distanceToParent < 2 ? this.parent : null;
|
||||
}
|
||||
@ -441,6 +462,10 @@ export class ElementInjector extends TreeNode {
|
||||
return this._proto._binding0IsComponent && key.id === this._proto._keyId0;
|
||||
}
|
||||
|
||||
_isPrivateComponentKey(key:Key) {
|
||||
return isPresent(this._privateComponentBinding) && key.id === this._privateComponentBinding.key.id;
|
||||
}
|
||||
|
||||
_new(binding:Binding) {
|
||||
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
|
||||
throw new CyclicDependencyError(binding.key);
|
||||
@ -488,18 +513,22 @@ export class ElementInjector extends TreeNode {
|
||||
|
||||
_getByDependency(dep:DirectiveDependency, requestor:Key) {
|
||||
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
|
||||
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
|
||||
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
|
||||
}
|
||||
|
||||
_buildEventEmitter(dep) {
|
||||
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
|
||||
if (isPresent(this._eventCallbacks)) {
|
||||
var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName);
|
||||
if (isPresent(callback)) {
|
||||
return ProtoView.buildInnerCallback(callback, view);
|
||||
}
|
||||
}
|
||||
return (_) => {};
|
||||
return (event) => {
|
||||
view.triggerEventHandlers(dep.eventEmitterName, event, this._proto.index);
|
||||
};
|
||||
}
|
||||
|
||||
_buildPropSetter(dep) {
|
||||
var ngElement = this._getPreBuiltObjectByKeyId(StaticKeys.instance().ngElementId);
|
||||
var domElement = ngElement.domElement;
|
||||
var setter = reflector.setter(dep.propSetterName);
|
||||
return function(v) { setter(domElement, v) };
|
||||
}
|
||||
|
||||
/*
|
||||
@ -535,6 +564,8 @@ export class ElementInjector extends TreeNode {
|
||||
|
||||
if (isPresent(this._host) && this._host._isComponentKey(key)) {
|
||||
return this._host.getComponent();
|
||||
} else if (isPresent(this._host) && this._host._isPrivateComponentKey(key)) {
|
||||
return this._host.getPrivateComponent();
|
||||
} else if (optional) {
|
||||
return this._appInjector(requestor).getOptional(key);
|
||||
} else {
|
||||
@ -560,12 +591,9 @@ export class ElementInjector extends TreeNode {
|
||||
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
|
||||
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer;
|
||||
if (keyId === staticKeys.bindingPropagationConfigId) return this._preBuiltObjects.bindingPropagationConfig;
|
||||
if (keyId === staticKeys.destinationLightDomId) {
|
||||
var p:ElementInjector = this.directParent();
|
||||
return isPresent(p) ? p._preBuiltObjects.lightDom : null;
|
||||
}
|
||||
if (keyId === staticKeys.sourceLightDomId) {
|
||||
return this._host._preBuiltObjects.lightDom;
|
||||
|
||||
if (keyId === staticKeys.privateComponentLocationId) {
|
||||
return new pclModule.PrivateComponentLocation(this, this._preBuiltObjects.element, this._preBuiltObjects.view);
|
||||
}
|
||||
|
||||
//TODO add other objects as needed
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class OnChange {
|
||||
onChange(changes) {
|
||||
throw "OnChange.onChange is not implemented";
|
||||
|
@ -2,10 +2,11 @@ import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {int, isBlank, isPresent, Type, StringJoiner, assertionsEnabled} from 'angular2/src/facade/lang';
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
import {Decorator, Component, Viewport} from '../../annotations/annotations';
|
||||
import {Decorator, Component, Viewport, DynamicComponent} from '../../annotations/annotations';
|
||||
import {ElementBinder} from '../element_binder';
|
||||
import {ProtoElementInjector} from '../element_injector';
|
||||
import {ProtoView} from '../view';
|
||||
import * as viewModule from '../view';
|
||||
import {dashCaseToCamelCase} from './util';
|
||||
|
||||
import {AST} from 'angular2/change_detection';
|
||||
|
||||
@ -29,16 +30,19 @@ export class CompileElement {
|
||||
decoratorDirectives:List<DirectiveMetadata>;
|
||||
viewportDirective:DirectiveMetadata;
|
||||
componentDirective:DirectiveMetadata;
|
||||
hasNestedView:boolean;
|
||||
_allDirectives:List<DirectiveMetadata>;
|
||||
isViewRoot:boolean;
|
||||
hasBindings:boolean;
|
||||
inheritedProtoView:ProtoView;
|
||||
inheritedProtoView:viewModule.ProtoView;
|
||||
inheritedProtoElementInjector:ProtoElementInjector;
|
||||
inheritedElementBinder:ElementBinder;
|
||||
distanceToParentInjector:number;
|
||||
distanceToParentInjector:int;
|
||||
distanceToParentBinder:int;
|
||||
compileChildren: boolean;
|
||||
ignoreBindings: boolean;
|
||||
elementDescription: string; // e.g. '<div [class]="foo">' : used to provide context in case of error
|
||||
contentTagSelector: string;
|
||||
|
||||
constructor(element, compilationUnit = '') {
|
||||
this.element = element;
|
||||
@ -51,6 +55,7 @@ export class CompileElement {
|
||||
this.decoratorDirectives = null;
|
||||
this.viewportDirective = null;
|
||||
this.componentDirective = null;
|
||||
this.hasNestedView = false;
|
||||
this._allDirectives = null;
|
||||
this.isViewRoot = false;
|
||||
this.hasBindings = false;
|
||||
@ -64,9 +69,11 @@ export class CompileElement {
|
||||
// an own elementBinder
|
||||
this.inheritedElementBinder = null;
|
||||
this.distanceToParentInjector = 0;
|
||||
this.distanceToParentBinder = 0;
|
||||
this.compileChildren = true;
|
||||
// set to true to ignore all the bindings on the element
|
||||
this.ignoreBindings = false;
|
||||
this.contentTagSelector = null;
|
||||
// description is calculated here as compilation steps may change the element
|
||||
var tplDesc = assertionsEnabled()? getElementDescription(element) : null;
|
||||
if (compilationUnit !== '') {
|
||||
@ -114,7 +121,7 @@ export class CompileElement {
|
||||
if (isBlank(this.propertyBindings)) {
|
||||
this.propertyBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.propertyBindings, property, expression);
|
||||
MapWrapper.set(this.propertyBindings, dashCaseToCamelCase(property), expression);
|
||||
}
|
||||
|
||||
addVariableBinding(variableName:string, variableValue:string) {
|
||||
@ -127,7 +134,7 @@ export class CompileElement {
|
||||
// by the "value", or exported identifier. For example, ng-repeat sets a view local of "index".
|
||||
// When this occurs, a lookup keyed by "index" must occur to find if there is a var referencing
|
||||
// it.
|
||||
MapWrapper.set(this.variableBindings, variableValue, variableName);
|
||||
MapWrapper.set(this.variableBindings, variableValue, dashCaseToCamelCase(variableName));
|
||||
}
|
||||
|
||||
addEventBinding(eventName:string, expression:AST) {
|
||||
@ -152,6 +159,9 @@ export class CompileElement {
|
||||
this.viewportDirective = directive;
|
||||
} else if (annotation instanceof Component) {
|
||||
this.componentDirective = directive;
|
||||
this.hasNestedView = true;
|
||||
} else if (annotation instanceof DynamicComponent) {
|
||||
this.componentDirective = directive;
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,7 +193,7 @@ function getElementDescription(domElement):string {
|
||||
|
||||
buf.add("<");
|
||||
buf.add(DOM.tagName(domElement).toLowerCase());
|
||||
|
||||
|
||||
// show id and class first to ease element identification
|
||||
addDescriptionAttribute(buf, "id", MapWrapper.get(atts, "id"));
|
||||
addDescriptionAttribute(buf, "class", MapWrapper.get(atts, "class"));
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import * as ccModule from './compile_control';
|
||||
|
||||
/**
|
||||
* One part of the compile process.
|
||||
* Is guaranteed to be called in depth first order
|
||||
*/
|
||||
export class CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {}
|
||||
process(parent:CompileElement, current:CompileElement, control:ccModule.CompileControl) {}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import {PropertyBindingParser} from './property_binding_parser';
|
||||
import {TextInterpolationParser} from './text_interpolation_parser';
|
||||
@ -32,6 +31,7 @@ export function createDefaultSteps(
|
||||
var steps = [
|
||||
new ViewSplitter(parser),
|
||||
cssProcessor.getCompileStep(compiledComponent, shadowDomStrategy, templateUrl),
|
||||
shadowDomStrategy.getTemplateCompileStep(compiledComponent),
|
||||
new PropertyBindingParser(parser),
|
||||
new DirectiveParser(directives),
|
||||
new TextInterpolationParser(parser),
|
||||
@ -41,10 +41,5 @@ export function createDefaultSteps(
|
||||
new ElementBinderBuilder(parser),
|
||||
];
|
||||
|
||||
var shadowDomStep = shadowDomStrategy.getTemplateCompileStep(compiledComponent);
|
||||
if (isPresent(shadowDomStep)) {
|
||||
ListWrapper.push(steps, shadowDomStep);
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
@ -5,12 +5,13 @@ import {SelectorMatcher} from '../selector';
|
||||
import {CssSelector} from '../selector';
|
||||
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
import {Component, Viewport} from '../../annotations/annotations';
|
||||
import {DynamicComponent, Component, Viewport} from '../../annotations/annotations';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
import {isSpecialProperty} from './element_binder_builder';;
|
||||
import {isSpecialProperty} from './element_binder_builder';
|
||||
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
|
||||
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
|
||||
|
||||
@ -48,31 +49,21 @@ export class DirectiveParser extends CompileStep {
|
||||
var classList = current.classList();
|
||||
|
||||
var cssSelector = new CssSelector();
|
||||
cssSelector.setElement(DOM.nodeName(current.element));
|
||||
var nodeName = DOM.nodeName(current.element);
|
||||
cssSelector.setElement(nodeName);
|
||||
for (var i=0; i < classList.length; i++) {
|
||||
cssSelector.addClassName(classList[i]);
|
||||
}
|
||||
|
||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||
if (isBlank(current.propertyBindings) ||
|
||||
isPresent(current.propertyBindings) && !MapWrapper.contains(current.propertyBindings, attrName)) {
|
||||
cssSelector.addAttribute(attrName, attrValue);
|
||||
}
|
||||
cssSelector.addAttribute(attrName, attrValue);
|
||||
});
|
||||
if (isPresent(current.propertyBindings)) {
|
||||
MapWrapper.forEach(current.propertyBindings, (expression, prop) => {
|
||||
cssSelector.addAttribute(prop, expression.source);
|
||||
});
|
||||
}
|
||||
if (isPresent(current.variableBindings)) {
|
||||
MapWrapper.forEach(current.variableBindings, (value, name) => {
|
||||
cssSelector.addAttribute(name, value);
|
||||
});
|
||||
}
|
||||
|
||||
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
||||
// only be present on <template> elements any more!
|
||||
var isTemplateElement = DOM.isTemplateElement(current.element);
|
||||
var matchedProperties; // StringMap - used in dev mode to store all properties that have been matched
|
||||
|
||||
|
||||
this._selectorMatcher.match(cssSelector, (selector, directive) => {
|
||||
matchedProperties = updateMatchedProperties(matchedProperties, selector, directive);
|
||||
checkDirectiveValidity(directive, current, isTemplateElement);
|
||||
@ -95,20 +86,20 @@ function updateMatchedProperties(matchedProperties, selector, directive) {
|
||||
if (isPresent(attrs)) {
|
||||
for (var idx = 0; idx<attrs.length; idx+=2) {
|
||||
// attribute name is stored on even indexes
|
||||
StringMapWrapper.set(matchedProperties, attrs[idx], true);
|
||||
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(attrs[idx]), true);
|
||||
}
|
||||
}
|
||||
// some properties can be used by the directive, so we need to register them
|
||||
if (isPresent(directive.annotation) && isPresent(directive.annotation.bind)) {
|
||||
var bindMap = directive.annotation.bind;
|
||||
StringMapWrapper.forEach(bindMap, (value, key) => {
|
||||
// value is the name of the property that is intepreted
|
||||
// value is the name of the property that is interpreted
|
||||
// e.g. 'myprop' or 'myprop | double' when a pipe is used to transform the property
|
||||
|
||||
// keep the property name and remove the pipe
|
||||
var bindProp = RegExpWrapper.firstMatch(PROPERTY_BINDING_REGEXP, value);
|
||||
if (isPresent(bindProp) && isPresent(bindProp[1])) {
|
||||
StringMapWrapper.set(matchedProperties, bindProp[1], true);
|
||||
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(bindProp[1]), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -118,6 +109,9 @@ function updateMatchedProperties(matchedProperties, selector, directive) {
|
||||
|
||||
// check if the directive is compatible with the current element
|
||||
function checkDirectiveValidity(directive, current, isTemplateElement) {
|
||||
var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent;
|
||||
var alreadyHasComponent = isPresent(current.componentDirective);
|
||||
|
||||
if (directive.annotation instanceof Viewport) {
|
||||
if (!isTemplateElement) {
|
||||
throw new BaseException(`Viewport directives need to be placed on <template> elements or elements ` +
|
||||
@ -127,7 +121,8 @@ function checkDirectiveValidity(directive, current, isTemplateElement) {
|
||||
}
|
||||
} else if (isTemplateElement) {
|
||||
throw new BaseException(`Only template directives are allowed on template elements - check ${current.elementDescription}`);
|
||||
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
|
||||
|
||||
} else if (isComponent && alreadyHasComponent) {
|
||||
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
|
||||
}
|
||||
}
|
||||
@ -141,8 +136,8 @@ function checkMissingDirectives(current, matchedProperties, isTemplateElement) {
|
||||
MapWrapper.forEach(ppBindings, (expression, prop) => {
|
||||
if (!DOM.hasProperty(current.element, prop) && !isSpecialProperty(prop)) {
|
||||
if (!isPresent(matchedProperties) || !isPresent(StringMapWrapper.get(matchedProperties, prop))) {
|
||||
throw new BaseException(`Missing directive to handle '${prop}' in ${current.elementDescription}`);
|
||||
}
|
||||
throw new BaseException(`Missing directive to handle '${camelCaseToDashCase(prop)}' in ${current.elementDescription}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -11,21 +11,24 @@ import {DirectiveMetadata} from '../directive_metadata';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
|
||||
var DOT_REGEXP = RegExpWrapper.create('\\.');
|
||||
|
||||
const ARIA_PREFIX = 'aria-';
|
||||
const ARIA_PREFIX = 'aria';
|
||||
var ariaSettersCache = StringMapWrapper.create();
|
||||
|
||||
function ariaSetterFactory(attrName:string) {
|
||||
var setterFn = StringMapWrapper.get(ariaSettersCache, attrName);
|
||||
var ariaAttrName;
|
||||
|
||||
if (isBlank(setterFn)) {
|
||||
ariaAttrName = camelCaseToDashCase(attrName);
|
||||
setterFn = function(element, value) {
|
||||
if (isPresent(value)) {
|
||||
DOM.setAttribute(element, attrName, stringify(value));
|
||||
DOM.setAttribute(element, ariaAttrName, stringify(value));
|
||||
} else {
|
||||
DOM.removeAttribute(element, attrName);
|
||||
DOM.removeAttribute(element, ariaAttrName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(ariaSettersCache, attrName, setterFn);
|
||||
@ -34,7 +37,6 @@ function ariaSetterFactory(attrName:string) {
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const CLASS_ATTR = 'class';
|
||||
const CLASS_PREFIX = 'class.';
|
||||
var classSettersCache = StringMapWrapper.create();
|
||||
|
||||
@ -55,22 +57,23 @@ function classSetterFactory(className:string) {
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const STYLE_ATTR = 'style';
|
||||
const STYLE_PREFIX = 'style.';
|
||||
var styleSettersCache = StringMapWrapper.create();
|
||||
|
||||
function styleSetterFactory(styleName:string, stylesuffix:string) {
|
||||
var cacheKey = styleName + stylesuffix;
|
||||
var setterFn = StringMapWrapper.get(styleSettersCache, cacheKey);
|
||||
var dashCasedStyleName;
|
||||
|
||||
if (isBlank(setterFn)) {
|
||||
dashCasedStyleName = camelCaseToDashCase(styleName);
|
||||
setterFn = function(element, value) {
|
||||
var valAsStr;
|
||||
if (isPresent(value)) {
|
||||
valAsStr = stringify(value);
|
||||
DOM.setStyle(element, styleName, valAsStr + stylesuffix);
|
||||
DOM.setStyle(element, dashCasedStyleName, valAsStr + stylesuffix);
|
||||
} else {
|
||||
DOM.removeStyle(element, styleName);
|
||||
DOM.removeStyle(element, dashCasedStyleName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(classSettersCache, cacheKey, setterFn);
|
||||
@ -132,6 +135,11 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var elementBinder = null;
|
||||
var parentElementBinder = null;
|
||||
var distanceToParentBinder = this._getDistanceToParentBinder(parent, current);
|
||||
if (isPresent(parent)) {
|
||||
parentElementBinder = parent.inheritedElementBinder;
|
||||
}
|
||||
if (current.hasBindings) {
|
||||
var protoView = current.inheritedProtoView;
|
||||
var protoInjectorWasBuilt = isBlank(parent) ? true :
|
||||
@ -140,8 +148,9 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
var currentProtoElementInjector = protoInjectorWasBuilt ?
|
||||
current.inheritedProtoElementInjector : null;
|
||||
|
||||
elementBinder = protoView.bindElement(currentProtoElementInjector,
|
||||
current.componentDirective, current.viewportDirective);
|
||||
elementBinder = protoView.bindElement(parentElementBinder, distanceToParentBinder,
|
||||
currentProtoElementInjector, current.componentDirective, current.viewportDirective);
|
||||
current.distanceToParentBinder = 0;
|
||||
|
||||
if (isPresent(current.textNodeBindings)) {
|
||||
this._bindTextNodes(protoView, current);
|
||||
@ -152,13 +161,23 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
if (isPresent(current.eventBindings)) {
|
||||
this._bindEvents(protoView, current);
|
||||
}
|
||||
this._bindDirectiveProperties(current.getAllDirectives(), current);
|
||||
if (isPresent(current.contentTagSelector)) {
|
||||
elementBinder.contentTagSelector = current.contentTagSelector;
|
||||
}
|
||||
var directives = current.getAllDirectives();
|
||||
this._bindDirectiveProperties(directives, current);
|
||||
this._bindDirectiveEvents(directives, current);
|
||||
} else if (isPresent(parent)) {
|
||||
elementBinder = parent.inheritedElementBinder;
|
||||
elementBinder = parentElementBinder;
|
||||
current.distanceToParentBinder = distanceToParentBinder;
|
||||
}
|
||||
current.inheritedElementBinder = elementBinder;
|
||||
}
|
||||
|
||||
_getDistanceToParentBinder(parent, current) {
|
||||
return isPresent(parent) ? parent.distanceToParentBinder + 1 : 0;
|
||||
}
|
||||
|
||||
_bindTextNodes(protoView, compileElement) {
|
||||
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
|
||||
protoView.bindTextNode(indexInParent, expression);
|
||||
@ -182,7 +201,9 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
} else {
|
||||
property = this._resolvePropertyName(property);
|
||||
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789
|
||||
if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
|
||||
if (StringWrapper.equals(property, 'innerHTML')) {
|
||||
setterFn = (element, value) => DOM.setInnerHTML(element, value);
|
||||
} else if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
|
||||
setterFn = reflector.setter(property);
|
||||
}
|
||||
}
|
||||
@ -199,6 +220,19 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
});
|
||||
}
|
||||
|
||||
_bindDirectiveEvents(directives: List<DirectiveMetadata>, compileElement: CompileElement) {
|
||||
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
|
||||
var directive = directives[directiveIndex];
|
||||
var annotation = directive.annotation;
|
||||
if (isBlank(annotation.events)) continue;
|
||||
var protoView = compileElement.inheritedProtoView;
|
||||
StringMapWrapper.forEach(annotation.events, (action, eventName) => {
|
||||
var expression = this._parser.parseAction(action, compileElement.elementDescription);
|
||||
protoView.bindEvent(eventName, expression, directiveIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_bindDirectiveProperties(directives: List<DirectiveMetadata>,
|
||||
compileElement: CompileElement) {
|
||||
var protoView = compileElement.inheritedProtoView;
|
||||
@ -208,12 +242,11 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
var annotation = directive.annotation;
|
||||
if (isBlank(annotation.bind)) continue;
|
||||
StringMapWrapper.forEach(annotation.bind, (bindConfig, dirProp) => {
|
||||
var bindConfigParts = this._splitBindConfig(bindConfig);
|
||||
var elProp = bindConfigParts[0];
|
||||
var pipes = ListWrapper.slice(bindConfigParts, 1, bindConfigParts.length);
|
||||
var pipes = this._splitBindConfig(bindConfig);
|
||||
var elProp = ListWrapper.removeAt(pipes, 0);
|
||||
|
||||
var bindingAst = isPresent(compileElement.propertyBindings) ?
|
||||
MapWrapper.get(compileElement.propertyBindings, elProp) :
|
||||
MapWrapper.get(compileElement.propertyBindings, dashCaseToCamelCase(elProp)) :
|
||||
null;
|
||||
|
||||
if (isBlank(bindingAst)) {
|
||||
@ -230,7 +263,7 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
directiveIndex,
|
||||
fullExpAstWithBindPipes,
|
||||
dirProp,
|
||||
reflector.setter(dirProp)
|
||||
reflector.setter(dashCaseToCamelCase(dirProp))
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -238,8 +271,7 @@ export class ElementBinderBuilder extends CompileStep {
|
||||
}
|
||||
|
||||
_splitBindConfig(bindConfig:string) {
|
||||
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
|
||||
return ListWrapper.map(parts, (s) => s.trim());
|
||||
return ListWrapper.map(bindConfig.split('|'), (s) => s.trim());
|
||||
}
|
||||
|
||||
_resolvePropertyName(attrName:string) {
|
||||
|
@ -37,7 +37,8 @@ export class ElementBindingMarker extends CompileStep {
|
||||
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>0) ||
|
||||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
|
||||
isPresent(current.viewportDirective) ||
|
||||
isPresent(current.componentDirective);
|
||||
isPresent(current.componentDirective) ||
|
||||
isPresent(current.contentTagSelector);
|
||||
|
||||
if (hasBindings) {
|
||||
var element = current.element;
|
||||
|
@ -40,28 +40,33 @@ export class PropertyBindingParser extends CompileStep {
|
||||
}
|
||||
|
||||
var attrs = current.attrs();
|
||||
var newAttrs = MapWrapper.create();
|
||||
var desc = current.elementDescription;
|
||||
|
||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
||||
if (isPresent(bindParts)) {
|
||||
if (isPresent(bindParts[1])) {
|
||||
// match: bind-prop
|
||||
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue, desc));
|
||||
MapWrapper.set(newAttrs, bindParts[4], attrValue);
|
||||
} else if (isPresent(bindParts[2]) || isPresent(bindParts[7])) {
|
||||
// match: var-name / var-name="iden" / #name / #name="iden"
|
||||
var identifier = (isPresent(bindParts[4]) && bindParts[4] !== '') ?
|
||||
bindParts[4] : bindParts[8];
|
||||
var value = attrValue == '' ? '\$implicit' : attrValue;
|
||||
current.addVariableBinding(identifier, value);
|
||||
MapWrapper.set(newAttrs, identifier, value);
|
||||
} else if (isPresent(bindParts[3])) {
|
||||
// match: on-prop
|
||||
// match: on-event
|
||||
current.addEventBinding(bindParts[4], this._parseAction(attrValue, desc));
|
||||
} else if (isPresent(bindParts[5])) {
|
||||
// match: [prop]
|
||||
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue, desc));
|
||||
MapWrapper.set(newAttrs, bindParts[5], attrValue);
|
||||
} else if (isPresent(bindParts[6])) {
|
||||
// match: (prop)
|
||||
current.addEventBinding(bindParts[6], this._parseBinding(attrValue, desc));
|
||||
// match: (event)
|
||||
current.addEventBinding(bindParts[6], this._parseAction(attrValue, desc));
|
||||
}
|
||||
} else {
|
||||
var ast = this._parseInterpolation(attrValue, desc);
|
||||
@ -70,6 +75,10 @@ export class PropertyBindingParser extends CompileStep {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MapWrapper.forEach(newAttrs, (attrValue, attrName) => {
|
||||
MapWrapper.set(attrs, attrName, attrValue);
|
||||
});
|
||||
}
|
||||
|
||||
_parseInterpolation(input:string, location:string):AST {
|
||||
|
@ -34,9 +34,6 @@ export class ProtoElementInjectorBuilder extends CompileStep {
|
||||
var distanceToParentInjector = this._getDistanceToParentInjector(parent, current);
|
||||
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
|
||||
var injectorBindings = ListWrapper.map(current.getAllDirectives(), this._createBinding);
|
||||
// TODO: add lightDomServices as well,
|
||||
// but after the directives as we rely on that order
|
||||
// in the element_binder_builder.
|
||||
|
||||
// Create a protoElementInjector for any element that either has bindings *or* has one
|
||||
// or more var- defined. Elements with a var- defined need a their own element injector
|
||||
|
@ -35,7 +35,8 @@ export class ProtoViewBuilder extends CompileStep {
|
||||
if (current.isViewRoot) {
|
||||
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
|
||||
inheritedProtoView = new ProtoView(current.element, protoChangeDetector,
|
||||
this._shadowDomStrategy);
|
||||
this._shadowDomStrategy, this._getParentProtoView(parent));
|
||||
|
||||
if (isPresent(parent)) {
|
||||
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
|
||||
throw new BaseException('Only one nested view per element is allowed');
|
||||
@ -55,16 +56,20 @@ export class ProtoViewBuilder extends CompileStep {
|
||||
inheritedProtoView = parent.inheritedProtoView;
|
||||
}
|
||||
|
||||
// The view's contextWithLocals needs to have a full set of variable names at construction time
|
||||
// The view's locals needs to have a full set of variable names at construction time
|
||||
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
|
||||
// to actually create variable bindings for the $implicit bindings, add to the
|
||||
// protoContextLocals manually.
|
||||
// protoLocals manually.
|
||||
if (isPresent(current.variableBindings)) {
|
||||
MapWrapper.forEach(current.variableBindings, (mappedName, varName) => {
|
||||
MapWrapper.set(inheritedProtoView.protoContextLocals, mappedName, null);
|
||||
MapWrapper.set(inheritedProtoView.protoLocals, mappedName, null);
|
||||
});
|
||||
}
|
||||
|
||||
current.inheritedProtoView = inheritedProtoView;
|
||||
}
|
||||
|
||||
_getParentProtoView(parent:CompileElement) {
|
||||
return isPresent(parent) ? parent.inheritedProtoView : null;
|
||||
}
|
||||
}
|
||||
|
16
modules/angular2/src/core/compiler/pipeline/util.js
vendored
Normal file
16
modules/angular2/src/core/compiler/pipeline/util.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import {StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])');
|
||||
var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])');
|
||||
|
||||
export function dashCaseToCamelCase(input:string) {
|
||||
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP, (m) => {
|
||||
return m[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export function camelCaseToDashCase(input:string) {
|
||||
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => {
|
||||
return '-' + m[1].toLowerCase();
|
||||
});
|
||||
}
|
@ -109,8 +109,10 @@ export class ViewSplitter extends CompileStep {
|
||||
var binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
compileElement.addVariableBinding(binding.key, binding.name);
|
||||
MapWrapper.set(compileElement.attrs(), binding.key, binding.name);
|
||||
} else if (isPresent(binding.expression)) {
|
||||
compileElement.addPropertyBinding(binding.key, binding.expression);
|
||||
MapWrapper.set(compileElement.attrs(), binding.key, binding.expression.source);
|
||||
} else {
|
||||
DOM.setAttribute(compileElement.element, binding.key, '');
|
||||
}
|
||||
|
34
modules/angular2/src/core/compiler/private_component_loader.js
vendored
Normal file
34
modules/angular2/src/core/compiler/private_component_loader.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import {Compiler} from './compiler';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {EventManager} from 'angular2/src/core/events/event_manager';
|
||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||
import {PrivateComponentLocation} from './private_component_location';
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
|
||||
|
||||
export class PrivateComponentLoader {
|
||||
compiler:Compiler;
|
||||
shadowDomStrategy:ShadowDomStrategy;
|
||||
eventManager:EventManager;
|
||||
directiveMetadataReader:DirectiveMetadataReader;
|
||||
|
||||
constructor(compiler:Compiler, shadowDomStrategy:ShadowDomStrategy,
|
||||
eventManager:EventManager, directiveMetadataReader:DirectiveMetadataReader) {
|
||||
|
||||
this.compiler = compiler;
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
this.eventManager = eventManager;
|
||||
this.directiveMetadataReader = directiveMetadataReader;
|
||||
}
|
||||
|
||||
load(type:Type, location:PrivateComponentLocation) {
|
||||
var annotation = this.directiveMetadataReader.read(type).annotation;
|
||||
return this.compiler.compile(type).then((componentProtoView) => {
|
||||
location.createComponent(
|
||||
type, annotation,
|
||||
componentProtoView,
|
||||
this.eventManager,
|
||||
this.shadowDomStrategy);
|
||||
});
|
||||
}
|
||||
}
|
34
modules/angular2/src/core/compiler/private_component_location.js
vendored
Normal file
34
modules/angular2/src/core/compiler/private_component_location.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import {Directive} from 'angular2/src/core/annotations/annotations'
|
||||
import {NgElement} from 'angular2/src/core/dom/element';
|
||||
import * as viewModule from './view';
|
||||
import * as eiModule from './element_injector';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {EventManager} from 'angular2/src/core/events/event_manager';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
|
||||
|
||||
export class PrivateComponentLocation {
|
||||
_elementInjector:eiModule.ElementInjector;
|
||||
_elt:NgElement;
|
||||
_view:viewModule.View;
|
||||
|
||||
constructor(elementInjector:eiModule.ElementInjector, elt:NgElement, view:viewModule.View){
|
||||
this._elementInjector = elementInjector;
|
||||
this._elt = elt;
|
||||
this._view = view;
|
||||
}
|
||||
|
||||
createComponent(type:Type, annotation:Directive, componentProtoView:viewModule.ProtoView,
|
||||
eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
|
||||
var context = this._elementInjector.createPrivateComponent(type, annotation);
|
||||
|
||||
var view = componentProtoView.instantiate(this._elementInjector, eventManager);
|
||||
view.hydrate(this._elementInjector.getShadowDomAppInjector(), this._elementInjector, null, context, null);
|
||||
|
||||
shadowDomStrategy.attachTemplate(this._elt.domElement, view);
|
||||
|
||||
ListWrapper.push(this._view.componentChildViews, view);
|
||||
this._view.changeDetector.addChild(view.changeDetector);
|
||||
}
|
||||
}
|
90
modules/angular2/src/core/compiler/selector.js
vendored
90
modules/angular2/src/core/compiler/selector.js
vendored
@ -1,12 +1,13 @@
|
||||
import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, BaseException} from 'angular2/src/facade/lang';
|
||||
|
||||
const _EMPTY_ATTR_VALUE = '';
|
||||
|
||||
// TODO: Can't use `const` here as
|
||||
// in Dart this is not transpiled into `final` yet...
|
||||
var _SELECTOR_REGEXP =
|
||||
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
|
||||
RegExpWrapper.create('(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
|
||||
|
||||
@ -19,21 +20,35 @@ export class CssSelector {
|
||||
element:string;
|
||||
classNames:List;
|
||||
attrs:List;
|
||||
static parse(selector:string):CssSelector {
|
||||
notSelector: CssSelector;
|
||||
static parse(selector:string): CssSelector {
|
||||
var cssSelector = new CssSelector();
|
||||
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||
var match;
|
||||
var current = cssSelector;
|
||||
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
||||
if (isPresent(match[1])) {
|
||||
cssSelector.setElement(match[1]);
|
||||
if (isPresent(cssSelector.notSelector)) {
|
||||
throw new BaseException('Nesting :not is not allowed in a selector');
|
||||
}
|
||||
current.notSelector = new CssSelector();
|
||||
current = current.notSelector;
|
||||
}
|
||||
if (isPresent(match[2])) {
|
||||
cssSelector.addClassName(match[2]);
|
||||
current.setElement(match[2]);
|
||||
}
|
||||
if (isPresent(match[3])) {
|
||||
cssSelector.addAttribute(match[3], match[4]);
|
||||
current.addClassName(match[3]);
|
||||
}
|
||||
if (isPresent(match[4])) {
|
||||
current.addAttribute(match[4], match[5]);
|
||||
}
|
||||
}
|
||||
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element)
|
||||
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) {
|
||||
cssSelector.element = "*";
|
||||
}
|
||||
|
||||
return cssSelector;
|
||||
}
|
||||
|
||||
@ -41,6 +56,7 @@ export class CssSelector {
|
||||
this.element = null;
|
||||
this.classNames = ListWrapper.create();
|
||||
this.attrs = ListWrapper.create();
|
||||
this.notSelector = null;
|
||||
}
|
||||
|
||||
setElement(element:string = null) {
|
||||
@ -85,6 +101,9 @@ export class CssSelector {
|
||||
res += ']';
|
||||
}
|
||||
}
|
||||
if (isPresent(this.notSelector)) {
|
||||
res += ":not(" + this.notSelector.toString() + ")";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@ -188,20 +207,22 @@ export class SelectorMatcher {
|
||||
* whose css selector is contained in the given css selector.
|
||||
* @param cssSelector A css selector
|
||||
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
|
||||
* @return boolean true if a match was found
|
||||
*/
|
||||
match(cssSelector:CssSelector, matchedCallback:Function) {
|
||||
match(cssSelector:CssSelector, matchedCallback:Function):boolean {
|
||||
var result = false;
|
||||
var element = cssSelector.element;
|
||||
var classNames = cssSelector.classNames;
|
||||
var attrs = cssSelector.attrs;
|
||||
|
||||
this._matchTerminal(this._elementMap, element, matchedCallback);
|
||||
this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback);
|
||||
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
|
||||
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
|
||||
|
||||
if (isPresent(classNames)) {
|
||||
for (var index = 0; index<classNames.length; index++) {
|
||||
var className = classNames[index];
|
||||
this._matchTerminal(this._classMap, className, matchedCallback);
|
||||
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback);
|
||||
result = this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
|
||||
result = this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) || result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,43 +233,51 @@ export class SelectorMatcher {
|
||||
|
||||
var valuesMap = MapWrapper.get(this._attrValueMap, attrName);
|
||||
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
||||
this._matchTerminal(valuesMap, _EMPTY_ATTR_VALUE, matchedCallback);
|
||||
result = this._matchTerminal(valuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) || result;
|
||||
}
|
||||
this._matchTerminal(valuesMap, attrValue, matchedCallback);
|
||||
result = this._matchTerminal(valuesMap, attrValue, cssSelector, matchedCallback) || result;
|
||||
|
||||
valuesMap = MapWrapper.get(this._attrValuePartialMap, attrName)
|
||||
this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback);
|
||||
result = this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback) || result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_matchTerminal(map:Map<string,string> = null, name, matchedCallback) {
|
||||
_matchTerminal(map:Map<string,string> = null, name, cssSelector, matchedCallback):boolean {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectables = MapWrapper.get(map, name);
|
||||
var starSelectables = MapWrapper.get(map, "*");
|
||||
if (isPresent(starSelectables)) {
|
||||
selectables = ListWrapper.concat(selectables, starSelectables);
|
||||
}
|
||||
var selectables = MapWrapper.get(map, name)
|
||||
if (isBlank(selectables)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
var selectable;
|
||||
var result = false;
|
||||
for (var index=0; index<selectables.length; index++) {
|
||||
selectable = selectables[index];
|
||||
matchedCallback(selectable.selector, selectable.cbContext);
|
||||
result = selectable.finalize(cssSelector, matchedCallback) || result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_matchPartial(map:Map<string,string> = null, name, cssSelector, matchedCallback) {
|
||||
_matchPartial(map:Map<string,string> = null, name, cssSelector, matchedCallback):boolean {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
var nestedSelector = MapWrapper.get(map, name)
|
||||
if (isBlank(nestedSelector)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// TODO(perf): get rid of recursion and measure again
|
||||
// TODO(perf): don't pass the whole selector into the recursion,
|
||||
// but only the not processed parts
|
||||
nestedSelector.match(cssSelector, matchedCallback);
|
||||
return nestedSelector.match(cssSelector, matchedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,10 +285,25 @@ export class SelectorMatcher {
|
||||
// Store context to pass back selector and context when a selector is matched
|
||||
class SelectorContext {
|
||||
selector:CssSelector;
|
||||
notSelector:CssSelector;
|
||||
cbContext; // callback context
|
||||
|
||||
constructor(selector:CssSelector, cbContext) {
|
||||
this.selector = selector;
|
||||
this.notSelector = selector.notSelector;
|
||||
this.cbContext = cbContext;
|
||||
}
|
||||
|
||||
finalize(cssSelector: CssSelector, callback) {
|
||||
var result = true;
|
||||
if (isPresent(this.notSelector)) {
|
||||
var notMatcher = new SelectorMatcher();
|
||||
notMatcher.addSelectable(this.notSelector, null);
|
||||
result = !notMatcher.match(cssSelector, null);
|
||||
}
|
||||
if (result && isPresent(callback)) {
|
||||
callback(this.selector, this.cbContext);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
import {Decorator} from '../../annotations/annotations';
|
||||
import {SourceLightDom, DestinationLightDom, LightDom} from './light_dom';
|
||||
import {Inject} from 'angular2/di';
|
||||
import * as ldModule from './light_dom';
|
||||
import {Inject, Injectable} from 'angular2/di';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {NgElement} from 'angular2/src/core/dom/element';
|
||||
|
||||
class ContentStrategy {
|
||||
nodes:List;
|
||||
@ -16,24 +14,18 @@ class ContentStrategy {
|
||||
* It is used when the content tag is not a direct child of another component,
|
||||
* and thus does not affect redistribution.
|
||||
*/
|
||||
@Injectable()
|
||||
class RenderedContent extends ContentStrategy {
|
||||
static _lazyScriptTemplate;
|
||||
beginScript;
|
||||
endScript;
|
||||
|
||||
constructor(contentEl) {
|
||||
super();
|
||||
this._replaceContentElementWithScriptTags(contentEl);
|
||||
this.beginScript = contentEl;
|
||||
this.endScript = DOM.nextSibling(this.beginScript);
|
||||
this.nodes = [];
|
||||
}
|
||||
|
||||
_scriptTemplate() {
|
||||
if (!isPresent(RenderedContent._lazyScriptTemplate)) {
|
||||
RenderedContent._lazyScriptTemplate = DOM.createScriptTag('type', 'ng/content');
|
||||
}
|
||||
return RenderedContent._lazyScriptTemplate;
|
||||
}
|
||||
|
||||
// Inserts the nodes in between the start and end scripts.
|
||||
// Previous content is removed.
|
||||
insert(nodes:List) {
|
||||
@ -42,16 +34,6 @@ class RenderedContent extends ContentStrategy {
|
||||
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
|
||||
}
|
||||
|
||||
// Replaces the content tag with a pair of script tags
|
||||
_replaceContentElementWithScriptTags(contentEl) {
|
||||
this.beginScript = DOM.clone(this._scriptTemplate());
|
||||
this.endScript = DOM.clone(this._scriptTemplate());
|
||||
|
||||
DOM.insertBefore(contentEl, this.beginScript);
|
||||
DOM.insertBefore(contentEl, this.endScript);
|
||||
DOM.removeChild(DOM.parentElement(contentEl), contentEl);
|
||||
}
|
||||
|
||||
_removeNodesUntil(node) {
|
||||
var p = DOM.parentElement(this.beginScript);
|
||||
for (var next = DOM.nextSibling(this.beginScript);
|
||||
@ -68,9 +50,9 @@ class RenderedContent extends ContentStrategy {
|
||||
* and thus does not get rendered but only affect the distribution of its parent component.
|
||||
*/
|
||||
class IntermediateContent extends ContentStrategy {
|
||||
destinationLightDom:LightDom;
|
||||
destinationLightDom:ldModule.LightDom;
|
||||
|
||||
constructor(destinationLightDom:LightDom) {
|
||||
constructor(destinationLightDom:ldModule.LightDom) {
|
||||
super();
|
||||
this.destinationLightDom = destinationLightDom;
|
||||
this.nodes = [];
|
||||
@ -83,18 +65,17 @@ class IntermediateContent extends ContentStrategy {
|
||||
}
|
||||
|
||||
|
||||
@Decorator({
|
||||
selector: 'content'
|
||||
})
|
||||
export class Content {
|
||||
select:string;
|
||||
_strategy:ContentStrategy;
|
||||
contentStartElement;
|
||||
|
||||
constructor(@Inject(DestinationLightDom) destinationLightDom, contentEl:NgElement) {
|
||||
this.select = contentEl.getAttribute('select');
|
||||
constructor(destinationLightDom:ldModule.LightDom, contentStartEl, selector:string) {
|
||||
this.select = selector;
|
||||
this.contentStartElement = contentStartEl;
|
||||
this._strategy = isPresent(destinationLightDom) ?
|
||||
new IntermediateContent(destinationLightDom) :
|
||||
new RenderedContent(contentEl.domElement);
|
||||
new RenderedContent(contentStartEl);
|
||||
}
|
||||
|
||||
nodes():List {
|
||||
|
@ -2,40 +2,40 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import {View} from '../view';
|
||||
import {ElementInjector} from '../element_injector';
|
||||
import {ViewContainer} from '../view_container';
|
||||
import * as viewModule from '../view';
|
||||
import {Content} from './content_tag';
|
||||
|
||||
export class SourceLightDom {}
|
||||
export class DestinationLightDom {}
|
||||
|
||||
|
||||
class _Root {
|
||||
node;
|
||||
injector:ElementInjector;
|
||||
viewContainer;
|
||||
content;
|
||||
|
||||
constructor(node, injector) {
|
||||
constructor(node, viewContainer, content) {
|
||||
this.node = node;
|
||||
this.injector = injector;
|
||||
this.viewContainer = viewContainer;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: LightDom should implement SourceLightDom and DestinationLightDom
|
||||
// TODO: LightDom should implement DestinationLightDom
|
||||
// once interfaces are supported
|
||||
export class LightDom {
|
||||
// The light DOM of the element is enclosed inside the lightDomView
|
||||
lightDomView:View;
|
||||
lightDomView:viewModule.View;
|
||||
// The shadow DOM
|
||||
shadowDomView:View;
|
||||
shadowDomView:viewModule.View;
|
||||
// The nodes of the light DOM
|
||||
nodes:List;
|
||||
roots:List<_Root>;
|
||||
|
||||
constructor(lightDomView:View, shadowDomView:View, element) {
|
||||
constructor(lightDomView:viewModule.View, shadowDomView:viewModule.View, element) {
|
||||
this.lightDomView = lightDomView;
|
||||
this.shadowDomView = shadowDomView;
|
||||
this.nodes = DOM.childNodesAsList(element);
|
||||
|
||||
this.roots = null;
|
||||
}
|
||||
|
||||
@ -51,20 +51,19 @@ export class LightDom {
|
||||
}
|
||||
|
||||
// Collects the Content directives from the view and all its child views
|
||||
_collectAllContentTags(view: View, acc:List<Content>):List<Content> {
|
||||
var eis = view.elementInjectors;
|
||||
for (var i = 0; i < eis.length; ++i) {
|
||||
var ei = eis[i];
|
||||
if (isBlank(ei)) continue;
|
||||
|
||||
if (ei.hasDirective(Content)) {
|
||||
ListWrapper.push(acc, ei.get(Content));
|
||||
|
||||
} else if (ei.hasPreBuiltObject(ViewContainer)) {
|
||||
var vc = ei.get(ViewContainer);
|
||||
_collectAllContentTags(view: viewModule.View, acc:List<Content>):List<Content> {
|
||||
var contentTags = view.contentTags;
|
||||
var vcs = view.viewContainers;
|
||||
for (var i=0; i<vcs.length; i++) {
|
||||
var vc = vcs[i];
|
||||
var contentTag = contentTags[i];
|
||||
if (isPresent(contentTag)) {
|
||||
ListWrapper.push(acc, contentTag);
|
||||
}
|
||||
if (isPresent(vc)) {
|
||||
ListWrapper.forEach(vc.contentTagContainers(), (view) => {
|
||||
this._collectAllContentTags(view, acc);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
@ -76,21 +75,16 @@ export class LightDom {
|
||||
// - plain DOM nodes
|
||||
expandedDomNodes():List {
|
||||
var res = [];
|
||||
|
||||
|
||||
var roots = this._roots();
|
||||
for (var i = 0; i < roots.length; ++i) {
|
||||
|
||||
var root = roots[i];
|
||||
var ei = root.injector;
|
||||
|
||||
if (isPresent(ei) && ei.hasPreBuiltObject(ViewContainer)) {
|
||||
var vc = root.injector.get(ViewContainer);
|
||||
res = ListWrapper.concat(res, vc.nodes());
|
||||
|
||||
} else if (isPresent(ei) && ei.hasDirective(Content)) {
|
||||
var content = root.injector.get(Content);
|
||||
res = ListWrapper.concat(res, content.nodes());
|
||||
|
||||
if (isPresent(root.viewContainer)) {
|
||||
res = ListWrapper.concat(res, root.viewContainer.nodes());
|
||||
} else if (isPresent(root.content)) {
|
||||
res = ListWrapper.concat(res, root.content.nodes());
|
||||
} else {
|
||||
ListWrapper.push(res, root.node);
|
||||
}
|
||||
@ -103,10 +97,24 @@ export class LightDom {
|
||||
_roots() {
|
||||
if (isPresent(this.roots)) return this.roots;
|
||||
|
||||
var viewInj = this.lightDomView.elementInjectors;
|
||||
this.roots = ListWrapper.map(this.nodes, (n) =>
|
||||
new _Root(n, ListWrapper.find(viewInj,
|
||||
(inj) => isPresent(inj) ? inj.forElement(n) : false)));
|
||||
var viewContainers = this.lightDomView.viewContainers;
|
||||
var contentTags = this.lightDomView.contentTags;
|
||||
|
||||
this.roots = ListWrapper.map(this.nodes, (n) => {
|
||||
var foundVc = null;
|
||||
var foundContentTag = null;
|
||||
for (var i=0; i<viewContainers.length; i++) {
|
||||
var vc = viewContainers[i];
|
||||
var contentTag = contentTags[i];
|
||||
if (isPresent(vc) && vc.templateElement === n) {
|
||||
foundVc = vc;
|
||||
}
|
||||
if (isPresent(contentTag) && contentTag.contentStartElement === n) {
|
||||
foundContentTag = contentTag;
|
||||
}
|
||||
}
|
||||
return new _Root(n, foundVc, foundContentTag);
|
||||
});
|
||||
|
||||
return this.roots;
|
||||
}
|
||||
@ -119,10 +127,10 @@ function redistributeNodes(contents:List<Content>, nodes:List) {
|
||||
var select = content.select;
|
||||
var matchSelector = (n) => DOM.elementMatches(n, select);
|
||||
|
||||
if (isBlank(select)) {
|
||||
// Empty selector is identical to <content/>
|
||||
if (select.length === 0) {
|
||||
content.insert(nodes);
|
||||
ListWrapper.clear(nodes);
|
||||
|
||||
} else {
|
||||
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
||||
content.insert(matchingNodes);
|
||||
|
@ -523,28 +523,8 @@ var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
|
||||
var _colonHostRe = RegExpWrapper.create(':host', 'im');
|
||||
var _colonHostContextRe = RegExpWrapper.create(':host-context', 'im');
|
||||
|
||||
function _cssTextToStyle(cssText: string) {
|
||||
return DOM.createStyleElement(cssText);
|
||||
}
|
||||
|
||||
function _cssToRules(cssText: string) {
|
||||
var style = _cssTextToStyle(cssText);
|
||||
DOM.appendChild(DOM.defaultDoc().head, style);
|
||||
var rules = [];
|
||||
if (isPresent(style.sheet)) {
|
||||
// TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
|
||||
// with an @import
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=625013
|
||||
try {
|
||||
rules = style.sheet.cssRules;
|
||||
} catch(e) {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
// console.warn('sheet not found', style);
|
||||
}
|
||||
DOM.remove(style);
|
||||
return rules;
|
||||
return DOM.cssToRules(cssText);
|
||||
}
|
||||
|
||||
function _withCssRules(cssText: string, callback: Function) {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {Type, isBlank, isPresent, int} from 'angular2/src/facade/lang';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Type, isBlank, isPresent, int, StringWrapper, assertionsEnabled} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {View} from './view';
|
||||
import * as viewModule from './view';
|
||||
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {ShadowCss} from './shadow_dom_emulation/shadow_css';
|
||||
|
||||
@ -15,24 +15,34 @@ import {StyleUrlResolver} from './style_url_resolver';
|
||||
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
|
||||
import {CompileStep} from './pipeline/compile_step';
|
||||
import * as NS from './pipeline/compile_step';
|
||||
import {CompileElement} from './pipeline/compile_element';
|
||||
import {CompileControl} from './pipeline/compile_control';
|
||||
|
||||
var _EMPTY_STEP;
|
||||
|
||||
// Note: fill _EMPTY_STEP to prevent
|
||||
// problems from cyclic dependencies
|
||||
function _emptyStep() {
|
||||
if (isBlank(_EMPTY_STEP)) {
|
||||
_EMPTY_STEP = new _EmptyCompileStep();
|
||||
}
|
||||
return _EMPTY_STEP;
|
||||
}
|
||||
|
||||
export class ShadowDomStrategy {
|
||||
attachTemplate(el, view:View) {}
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el): LightDom { return null; }
|
||||
polyfillDirectives():List<Type> { return []; }
|
||||
attachTemplate(el, view:viewModule.View) {}
|
||||
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { return null; }
|
||||
|
||||
/**
|
||||
* An optional step that can modify the template style elements.
|
||||
*
|
||||
* @param {DirectiveMetadata} cmpMetadata
|
||||
* @param {string} templateUrl the template base URL
|
||||
* @returns {CompileStep} a compile step to append to the compiler pipeline, null if not required.
|
||||
* @returns {CompileStep} a compile step to append to the compiler pipeline
|
||||
*/
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): CompileStep {
|
||||
return null;
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return _emptyStep();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,9 +51,9 @@ export class ShadowDomStrategy {
|
||||
* This step could be used to modify the template in order to scope the styles.
|
||||
*
|
||||
* @param {DirectiveMetadata} cmpMetadata
|
||||
* @returns {CompileStep} a compile step to append to the compiler pipeline, null if not required.
|
||||
* @returns {CompileStep} a compile step to append to the compiler pipeline
|
||||
*/
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): CompileStep { return null; }
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep { return _emptyStep(); }
|
||||
|
||||
/**
|
||||
* The application element does not go through the compiler pipeline.
|
||||
@ -68,6 +78,7 @@ export class ShadowDomStrategy {
|
||||
* - styles are **not** scoped to their component and will apply to the whole document,
|
||||
* - you can **not** use shadow DOM specific selectors in the styles
|
||||
*/
|
||||
@Injectable()
|
||||
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_styleHost;
|
||||
@ -78,23 +89,23 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
this._styleHost = styleHost;
|
||||
}
|
||||
|
||||
attachTemplate(el, view:View){
|
||||
attachTemplate(el, view:viewModule.View) {
|
||||
DOM.clearNodes(el);
|
||||
_moveViewNodesIntoParent(el, view);
|
||||
}
|
||||
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el): LightDom {
|
||||
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom {
|
||||
return new LightDom(lightDomView, shadowDomView, el);
|
||||
}
|
||||
|
||||
polyfillDirectives():List<Type> {
|
||||
return [Content];
|
||||
}
|
||||
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): CompileStep {
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return new _EmulatedUnscopedCssStep(cmpMetadata, templateUrl, this._styleUrlResolver,
|
||||
this._styleHost);
|
||||
}
|
||||
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep {
|
||||
return new _BaseEmulatedShadowDomStep();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,6 +120,7 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
* - a common subset of shadow DOM selectors are supported,
|
||||
* - see `ShadowCss` for more information and limitations.
|
||||
*/
|
||||
@Injectable()
|
||||
export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy {
|
||||
_styleInliner: StyleInliner;
|
||||
|
||||
@ -117,12 +129,12 @@ export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomSt
|
||||
this._styleInliner = styleInliner;
|
||||
}
|
||||
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): CompileStep {
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return new _EmulatedScopedCssStep(cmpMetadata, templateUrl, this._styleInliner,
|
||||
this._styleUrlResolver, this._styleHost);
|
||||
}
|
||||
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): CompileStep {
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep {
|
||||
return new _ShimShadowDomStep(cmpMetadata);
|
||||
}
|
||||
|
||||
@ -139,6 +151,7 @@ export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomSt
|
||||
* The templates for the component are inserted in a Shadow Root created on the component element.
|
||||
* Hence they are strictly isolated.
|
||||
*/
|
||||
@Injectable()
|
||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
|
||||
@ -147,16 +160,48 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
this._styleUrlResolver = styleUrlResolver;
|
||||
}
|
||||
|
||||
attachTemplate(el, view:View){
|
||||
attachTemplate(el, view:viewModule.View){
|
||||
_moveViewNodesIntoParent(DOM.createShadowRoot(el), view);
|
||||
}
|
||||
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): CompileStep {
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return new _NativeCssStep(templateUrl, this._styleUrlResolver);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShimShadowDomStep extends CompileStep {
|
||||
class _BaseEmulatedShadowDomStep extends NS.CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
var nodeName = DOM.nodeName(current.element);
|
||||
if (StringWrapper.equals(nodeName.toUpperCase(), 'CONTENT')) {
|
||||
var attrs = current.attrs();
|
||||
var selector = MapWrapper.get(attrs, 'select');
|
||||
current.contentTagSelector = isPresent(selector) ? selector : '';
|
||||
|
||||
var contentStart = DOM.createScriptTag('type', 'ng/contentStart');
|
||||
if (assertionsEnabled()) {
|
||||
DOM.setAttribute(contentStart, 'select', current.contentTagSelector);
|
||||
}
|
||||
var contentEnd = DOM.createScriptTag('type', 'ng/contentEnd');
|
||||
DOM.insertBefore(current.element, contentStart);
|
||||
DOM.insertBefore(current.element, contentEnd);
|
||||
DOM.remove(current.element);
|
||||
|
||||
current.element = contentStart;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _EmptyCompileStep extends NS.CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _ShimShadowDomStep extends _BaseEmulatedShadowDomStep {
|
||||
_contentAttribute: string;
|
||||
|
||||
constructor(cmpMetadata: DirectiveMetadata) {
|
||||
@ -167,6 +212,7 @@ class _ShimShadowDomStep extends CompileStep {
|
||||
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
super.process(parent, current, control);
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
@ -184,7 +230,7 @@ class _ShimShadowDomStep extends CompileStep {
|
||||
}
|
||||
}
|
||||
|
||||
class _EmulatedUnscopedCssStep extends CompileStep {
|
||||
class _EmulatedUnscopedCssStep extends NS.CompileStep {
|
||||
_templateUrl: string;
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_styleHost;
|
||||
@ -213,7 +259,7 @@ class _EmulatedUnscopedCssStep extends CompileStep {
|
||||
}
|
||||
}
|
||||
|
||||
class _EmulatedScopedCssStep extends CompileStep {
|
||||
class _EmulatedScopedCssStep extends NS.CompileStep {
|
||||
_templateUrl: string;
|
||||
_component: Type;
|
||||
_styleInliner: StyleInliner;
|
||||
@ -255,7 +301,7 @@ class _EmulatedScopedCssStep extends CompileStep {
|
||||
}
|
||||
}
|
||||
|
||||
class _NativeCssStep extends CompileStep {
|
||||
class _NativeCssStep extends NS.CompileStep {
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_templateUrl: string;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
|
||||
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
|
||||
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
||||
@ -21,6 +22,7 @@ import {
|
||||
*
|
||||
* When an @import rules is inlined, it's url are rewritten.
|
||||
*/
|
||||
@Injectable()
|
||||
export class StyleInliner {
|
||||
_xhr: XHR;
|
||||
_urlResolver: UrlResolver;
|
||||
|
@ -1,12 +1,14 @@
|
||||
// Some of the code comes from WebComponents.JS
|
||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {UrlResolver} from './url_resolver';
|
||||
|
||||
/**
|
||||
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL.
|
||||
*/
|
||||
@Injectable()
|
||||
export class StyleUrlResolver {
|
||||
_resolver: UrlResolver;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {Map, MapWrapper, StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
@ -10,7 +11,9 @@ import {UrlResolver} from './url_resolver';
|
||||
|
||||
/**
|
||||
* Strategy to load component templates.
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
@Injectable()
|
||||
export class TemplateLoader {
|
||||
_xhr: XHR;
|
||||
_htmlCache: StringMap;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Template} from 'angular2/src/core/annotations/template';
|
||||
|
||||
import {Type, stringify, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
@ -6,6 +7,7 @@ import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TemplateResolver {
|
||||
_cache: Map;
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
@Injectable()
|
||||
export class UrlResolver {
|
||||
static a;
|
||||
|
||||
@ -12,8 +14,8 @@ export class UrlResolver {
|
||||
|
||||
resolve(baseUrl: string, url: string): string {
|
||||
if (isBlank(baseUrl)) {
|
||||
UrlResolver.a.href = url;
|
||||
return UrlResolver.a.href;
|
||||
DOM.resolveAndSetHref(UrlResolver.a, url, null);
|
||||
return DOM.getHref(UrlResolver.a);
|
||||
}
|
||||
|
||||
if (isBlank(url) || url == '') return baseUrl;
|
||||
@ -28,8 +30,8 @@ export class UrlResolver {
|
||||
return url;
|
||||
}
|
||||
|
||||
UrlResolver.a.href = baseUrl + '/../' + url;
|
||||
return UrlResolver.a.href;
|
||||
DOM.resolveAndSetHref(UrlResolver.a, baseUrl, url);
|
||||
return DOM.getHref(UrlResolver.a);
|
||||
}
|
||||
}
|
||||
|
||||
|
304
modules/angular2/src/core/compiler/view.js
vendored
304
modules/angular2/src/core/compiler/view.js
vendored
@ -1,19 +1,20 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord}
|
||||
from 'angular2/change_detection';
|
||||
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector,
|
||||
ChangeRecord, BindingRecord, uninitialized} from 'angular2/change_detection';
|
||||
|
||||
import {ProtoElementInjector, ElementInjector, PreBuiltObjects} from './element_injector';
|
||||
import {BindingPropagationConfig} from './binding_propagation_config';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {FIELD, IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {Injector} from 'angular2/di';
|
||||
import {NgElement} from 'angular2/src/core/dom/element';
|
||||
import {ViewContainer} from './view_container';
|
||||
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {ViewPool} from './view_pool';
|
||||
import {EventManager} from 'angular2/src/core/events/event_manager';
|
||||
@ -27,6 +28,7 @@ var VIEW_POOL_PREFILL = 0;
|
||||
|
||||
/**
|
||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
@IMPLEMENTS(ChangeDispatcher)
|
||||
export class View {
|
||||
@ -41,37 +43,42 @@ export class View {
|
||||
nodes:List;
|
||||
componentChildViews: List<View>;
|
||||
viewContainers: List<ViewContainer>;
|
||||
contentTags: List<Content>;
|
||||
preBuiltObjects: List<PreBuiltObjects>;
|
||||
lightDoms: List<LightDom>;
|
||||
proto: ProtoView;
|
||||
context: any;
|
||||
contextWithLocals:ContextWithVariableBindings;
|
||||
locals:Locals;
|
||||
|
||||
constructor(proto:ProtoView, nodes:List, protoChangeDetector:ProtoChangeDetector, protoContextLocals:Map) {
|
||||
constructor(proto:ProtoView, nodes:List, protoLocals:Map) {
|
||||
this.proto = proto;
|
||||
this.nodes = nodes;
|
||||
this.changeDetector = protoChangeDetector.instantiate(this);
|
||||
this.changeDetector = null;
|
||||
this.elementInjectors = null;
|
||||
this.rootElementInjectors = null;
|
||||
this.textNodes = null;
|
||||
this.bindElements = null;
|
||||
this.componentChildViews = null;
|
||||
this.viewContainers = null;
|
||||
this.contentTags = null;
|
||||
this.preBuiltObjects = null;
|
||||
this.lightDoms = null;
|
||||
this.context = null;
|
||||
this.contextWithLocals = (MapWrapper.size(protoContextLocals) > 0)
|
||||
? new ContextWithVariableBindings(null, MapWrapper.clone(protoContextLocals))
|
||||
: null;
|
||||
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
|
||||
}
|
||||
|
||||
init(elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List,
|
||||
viewContainers:List, preBuiltObjects:List, componentChildViews:List) {
|
||||
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List,
|
||||
viewContainers:List, contentTags:List, preBuiltObjects:List, componentChildViews:List, lightDoms:List<LightDom>) {
|
||||
this.changeDetector = changeDetector;
|
||||
this.elementInjectors = elementInjectors;
|
||||
this.rootElementInjectors = rootElementInjectors;
|
||||
this.textNodes = textNodes;
|
||||
this.bindElements = bindElements;
|
||||
this.viewContainers = viewContainers;
|
||||
this.contentTags = contentTags;
|
||||
this.preBuiltObjects = preBuiltObjects;
|
||||
this.componentChildViews = componentChildViews;
|
||||
this.lightDoms = lightDoms;
|
||||
}
|
||||
|
||||
setLocal(contextName: string, value) {
|
||||
@ -80,29 +87,22 @@ export class View {
|
||||
return;
|
||||
}
|
||||
var templateName = MapWrapper.get(this.proto.variableBindings, contextName);
|
||||
this.context.set(templateName, value);
|
||||
this.locals.set(templateName, value);
|
||||
}
|
||||
|
||||
hydrated() {
|
||||
return isPresent(this.context);
|
||||
}
|
||||
|
||||
_hydrateContext(newContext) {
|
||||
if (isPresent(this.contextWithLocals)) {
|
||||
this.contextWithLocals.parent = newContext;
|
||||
this.context = this.contextWithLocals;
|
||||
} else {
|
||||
this.context = newContext;
|
||||
}
|
||||
// TODO(tbosch): if we have a contextWithLocals we actually only need to
|
||||
// set the contextWithLocals once. Would it be faster to always use a contextWithLocals
|
||||
// even if we don't have locals and not update the recordRange here?
|
||||
this.changeDetector.hydrate(this.context);
|
||||
_hydrateContext(newContext, locals) {
|
||||
this.context = newContext;
|
||||
this.locals.parent = locals;
|
||||
this.changeDetector.hydrate(this.context, this.locals);
|
||||
}
|
||||
|
||||
_dehydrateContext() {
|
||||
if (isPresent(this.contextWithLocals)) {
|
||||
this.contextWithLocals.clearValues();
|
||||
if (isPresent(this.locals)) {
|
||||
this.locals.clearValues();
|
||||
}
|
||||
this.context = null;
|
||||
this.changeDetector.dehydrate();
|
||||
@ -124,14 +124,17 @@ export class View {
|
||||
* A call to hydrate/dehydrate does not attach/detach the view from the view
|
||||
* tree.
|
||||
*/
|
||||
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
|
||||
context: Object) {
|
||||
hydrate(appInjector: Injector, hostElementInjector: ElementInjector, hostLightDom: LightDom,
|
||||
context: Object, locals:Locals) {
|
||||
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
|
||||
this._hydrateContext(context);
|
||||
this._hydrateContext(context, locals);
|
||||
|
||||
// viewContainers
|
||||
for (var i = 0; i < this.viewContainers.length; i++) {
|
||||
this.viewContainers[i].hydrate(appInjector, hostElementInjector);
|
||||
var vc = this.viewContainers[i];
|
||||
if (isPresent(vc)) {
|
||||
vc.hydrate(appInjector, hostElementInjector, hostLightDom);
|
||||
}
|
||||
}
|
||||
|
||||
var binders = this.proto.elementBinders;
|
||||
@ -142,7 +145,7 @@ export class View {
|
||||
|
||||
// shadowDomAppInjector
|
||||
if (isPresent(componentDirective)) {
|
||||
var services = componentDirective.annotation.componentServices;
|
||||
var services = componentDirective.annotation.services;
|
||||
if (isPresent(services))
|
||||
shadowDomAppInjector = appInjector.createChild(services);
|
||||
else {
|
||||
@ -162,26 +165,22 @@ export class View {
|
||||
// name.
|
||||
var exportImplicitName = elementInjector.getExportImplicitName();
|
||||
if (elementInjector.isExportingComponent()) {
|
||||
this.context.set(exportImplicitName, elementInjector.getComponent());
|
||||
this.locals.set(exportImplicitName, elementInjector.getComponent());
|
||||
} else if (elementInjector.isExportingElement()) {
|
||||
this.context.set(exportImplicitName, elementInjector.getNgElement().domElement);
|
||||
this.locals.set(exportImplicitName, elementInjector.getNgElement().domElement);
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(componentDirective)) {
|
||||
if (isPresent(binders[i].nestedProtoView) && isPresent(componentDirective)) {
|
||||
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
|
||||
elementInjector, elementInjector.getComponent());
|
||||
elementInjector, this.lightDoms[i], elementInjector.getComponent(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// this should be moved into DOM write queue
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var componentDirective = binders[i].componentDirective;
|
||||
if (isPresent(componentDirective)) {
|
||||
var lightDom = this.preBuiltObjects[i].lightDom;
|
||||
if (isPresent(lightDom)) {
|
||||
lightDom.redistribute();
|
||||
}
|
||||
for (var i = 0; i < this.lightDoms.length; ++i) {
|
||||
var lightDom = this.lightDoms[i];
|
||||
if (isPresent(lightDom)) {
|
||||
lightDom.redistribute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,13 +203,33 @@ export class View {
|
||||
// viewContainers
|
||||
if (isPresent(this.viewContainers)) {
|
||||
for (var i = 0; i < this.viewContainers.length; i++) {
|
||||
this.viewContainers[i].dehydrate();
|
||||
var vc = this.viewContainers[i];
|
||||
if (isPresent(vc)) {
|
||||
vc.dehydrate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._dehydrateContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the event handlers for the element and the directives.
|
||||
*
|
||||
* This method is intended to be called from directive EventEmitters.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* @param {*} eventObj
|
||||
* @param {int} binderIndex
|
||||
*/
|
||||
triggerEventHandlers(eventName: string, eventObj, binderIndex: int) {
|
||||
var handlers = this.proto.eventHandlers[binderIndex];
|
||||
if (isBlank(handlers)) return;
|
||||
var handler = StringMapWrapper.get(handlers, eventName);
|
||||
if (isBlank(handler)) return;
|
||||
handler(eventObj, this);
|
||||
}
|
||||
|
||||
onRecordChange(directiveMemento, records:List) {
|
||||
this._invokeMementos(records);
|
||||
if (directiveMemento instanceof DirectiveMemento) {
|
||||
@ -262,12 +281,15 @@ export class View {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class ProtoView {
|
||||
element;
|
||||
elementBinders:List<ElementBinder>;
|
||||
protoChangeDetector:ProtoChangeDetector;
|
||||
variableBindings: Map;
|
||||
protoContextLocals:Map;
|
||||
protoLocals:Map;
|
||||
textNodesWithBindingCount:int;
|
||||
elementsWithBindingCount:int;
|
||||
instantiateInPlace:boolean;
|
||||
@ -276,16 +298,22 @@ export class ProtoView {
|
||||
shadowDomStrategy: ShadowDomStrategy;
|
||||
_viewPool: ViewPool;
|
||||
stylePromises: List<Promise>;
|
||||
// List<Map<eventName, handler>>, indexed by binder index
|
||||
eventHandlers:List;
|
||||
bindingRecords:List;
|
||||
parentProtoView:ProtoView;
|
||||
_variableBindings:List;
|
||||
|
||||
constructor(
|
||||
template,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
shadowDomStrategy: ShadowDomStrategy) {
|
||||
shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) {
|
||||
this.element = template;
|
||||
this.elementBinders = [];
|
||||
this.variableBindings = MapWrapper.create();
|
||||
this.protoContextLocals = MapWrapper.create();
|
||||
this.protoLocals = MapWrapper.create();
|
||||
this.protoChangeDetector = protoChangeDetector;
|
||||
this.parentProtoView = parentProtoView;
|
||||
this.textNodesWithBindingCount = 0;
|
||||
this.elementsWithBindingCount = 0;
|
||||
this.instantiateInPlace = false;
|
||||
@ -295,6 +323,9 @@ export class ProtoView {
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
this._viewPool = new ViewPool(VIEW_POOL_CAPACITY);
|
||||
this.stylePromises = [];
|
||||
this.eventHandlers = [];
|
||||
this.bindingRecords = [];
|
||||
this._variableBindings = null;
|
||||
}
|
||||
|
||||
// TODO(rado): hostElementInjector should be moved to hydrate phase.
|
||||
@ -310,6 +341,23 @@ export class ProtoView {
|
||||
}
|
||||
}
|
||||
|
||||
// this work should be done the constructor of ProtoView once we separate
|
||||
// ProtoView and ProtoViewBuilder
|
||||
_getVariableBindings() {
|
||||
if (isPresent(this._variableBindings)) {
|
||||
return this._variableBindings;
|
||||
}
|
||||
|
||||
this._variableBindings = isPresent(this.parentProtoView) ?
|
||||
ListWrapper.clone(this.parentProtoView._getVariableBindings()) : [];
|
||||
|
||||
MapWrapper.forEach(this.protoLocals, (v, local) => {
|
||||
ListWrapper.push(this._variableBindings, local);
|
||||
});
|
||||
|
||||
return this._variableBindings;
|
||||
}
|
||||
|
||||
_instantiate(hostElementInjector: ElementInjector, eventManager: EventManager): View {
|
||||
var rootElementClone = this.instantiateInPlace ? this.element : DOM.importIntoDoc(this.element);
|
||||
var elementsWithBindingsDynamic;
|
||||
@ -320,8 +368,8 @@ export class ProtoView {
|
||||
}
|
||||
|
||||
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
|
||||
for (var i = 0; i < elementsWithBindingsDynamic.length; ++i) {
|
||||
elementsWithBindings[i] = elementsWithBindingsDynamic[i];
|
||||
for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
|
||||
elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
|
||||
}
|
||||
|
||||
var viewNodes;
|
||||
@ -337,23 +385,27 @@ export class ProtoView {
|
||||
viewNodes = [rootElementClone];
|
||||
}
|
||||
|
||||
var view = new View(this, viewNodes, this.protoChangeDetector, this.protoContextLocals);
|
||||
var view = new View(this, viewNodes, this.protoLocals);
|
||||
var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords, this._getVariableBindings());
|
||||
var binders = this.elementBinders;
|
||||
var elementInjectors = ListWrapper.createFixedSize(binders.length);
|
||||
var eventHandlers = ListWrapper.createFixedSize(binders.length);
|
||||
var rootElementInjectors = [];
|
||||
var textNodes = [];
|
||||
var elementsWithPropertyBindings = [];
|
||||
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
||||
var viewContainers = [];
|
||||
var viewContainers = ListWrapper.createFixedSize(binders.length);
|
||||
var contentTags = ListWrapper.createFixedSize(binders.length);
|
||||
var componentChildViews = [];
|
||||
var lightDoms = ListWrapper.createFixedSize(binders.length);
|
||||
|
||||
for (var i = 0; i < binders.length; i++) {
|
||||
var binder = binders[i];
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var binder = binders[binderIdx];
|
||||
var element;
|
||||
if (i === 0 && this.rootBindingOffset === 1) {
|
||||
if (binderIdx === 0 && this.rootBindingOffset === 1) {
|
||||
element = rootElementClone;
|
||||
} else {
|
||||
element = elementsWithBindings[i - this.rootBindingOffset];
|
||||
element = elementsWithBindings[binderIdx - this.rootBindingOffset];
|
||||
}
|
||||
var elementInjector = null;
|
||||
|
||||
@ -362,13 +414,13 @@ export class ProtoView {
|
||||
if (isPresent(protoElementInjector)) {
|
||||
if (isPresent(protoElementInjector.parent)) {
|
||||
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
|
||||
elementInjector = protoElementInjector.instantiate(parentElementInjector, null, binder.events);
|
||||
elementInjector = protoElementInjector.instantiate(parentElementInjector, null);
|
||||
} else {
|
||||
elementInjector = protoElementInjector.instantiate(null, hostElementInjector, binder.events);
|
||||
elementInjector = protoElementInjector.instantiate(null, hostElementInjector);
|
||||
ListWrapper.push(rootElementInjectors, elementInjector);
|
||||
}
|
||||
}
|
||||
elementInjectors[i] = elementInjector;
|
||||
elementInjectors[binderIdx] = elementInjector;
|
||||
|
||||
if (binder.hasElementPropertyBindings) {
|
||||
ListWrapper.push(elementsWithPropertyBindings, element);
|
||||
@ -389,47 +441,64 @@ export class ProtoView {
|
||||
// componentChildViews
|
||||
var lightDom = null;
|
||||
var bindingPropagationConfig = null;
|
||||
if (isPresent(binder.componentDirective)) {
|
||||
if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) {
|
||||
var strategy = this.shadowDomStrategy;
|
||||
var childView = binder.nestedProtoView.instantiate(elementInjector, eventManager);
|
||||
view.changeDetector.addChild(childView.changeDetector);
|
||||
changeDetector.addChild(childView.changeDetector);
|
||||
|
||||
lightDom = strategy.constructLightDom(view, childView, element);
|
||||
strategy.attachTemplate(element, childView);
|
||||
|
||||
bindingPropagationConfig = new BindingPropagationConfig(view.changeDetector);
|
||||
bindingPropagationConfig = new BindingPropagationConfig(changeDetector);
|
||||
|
||||
ListWrapper.push(componentChildViews, childView);
|
||||
}
|
||||
lightDoms[binderIdx] = lightDom;
|
||||
|
||||
var destLightDom = null;
|
||||
if (isPresent(binder.parent) && binder.distanceToParent === 1) {
|
||||
destLightDom = lightDoms[binder.parent.index];
|
||||
}
|
||||
|
||||
// viewContainers
|
||||
var viewContainer = null;
|
||||
if (isPresent(binder.viewportDirective)) {
|
||||
var destLightDom = this._directParentElementLightDom(protoElementInjector, preBuiltObjects);
|
||||
viewContainer = new ViewContainer(view, element, binder.nestedProtoView, elementInjector,
|
||||
eventManager, destLightDom);
|
||||
ListWrapper.push(viewContainers, viewContainer);
|
||||
}
|
||||
viewContainers[binderIdx] = viewContainer;
|
||||
|
||||
// contentTags
|
||||
var contentTag = null;
|
||||
if (isPresent(binder.contentTagSelector)) {
|
||||
contentTag = new Content(destLightDom, element, binder.contentTagSelector);
|
||||
}
|
||||
contentTags[binderIdx] = contentTag;
|
||||
|
||||
// preBuiltObjects
|
||||
if (isPresent(elementInjector)) {
|
||||
preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewContainer,
|
||||
lightDom, bindingPropagationConfig);
|
||||
preBuiltObjects[binderIdx] = new PreBuiltObjects(view, new NgElement(element), viewContainer,
|
||||
bindingPropagationConfig);
|
||||
}
|
||||
|
||||
// events
|
||||
if (isPresent(binder.events)) {
|
||||
MapWrapper.forEach(binder.events, (expr, eventName) => {
|
||||
eventHandlers[binderIdx] = StringMapWrapper.create();
|
||||
StringMapWrapper.forEach(binder.events, (eventMap, eventName) => {
|
||||
var handler = ProtoView.buildEventHandler(eventMap, binderIdx);
|
||||
StringMapWrapper.set(eventHandlers[binderIdx], eventName, handler);
|
||||
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
|
||||
var handler = ProtoView.buildInnerCallback(expr, view);
|
||||
eventManager.addEventListener(element, eventName, handler);
|
||||
eventManager.addEventListener(element, eventName,
|
||||
(event) => { handler(event, view); });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
|
||||
viewContainers, preBuiltObjects, componentChildViews);
|
||||
this.eventHandlers = eventHandlers;
|
||||
|
||||
view.init(changeDetector, elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
|
||||
viewContainers, contentTags, preBuiltObjects, componentChildViews, lightDoms);
|
||||
|
||||
return view;
|
||||
}
|
||||
@ -438,34 +507,43 @@ export class ProtoView {
|
||||
this._viewPool.push(view);
|
||||
}
|
||||
|
||||
static buildInnerCallback(expr:AST, view:View) {
|
||||
/**
|
||||
* Creates an event handler.
|
||||
*
|
||||
* @param {Map} eventMap Map directiveIndexes to expressions
|
||||
* @param {int} injectorIdx
|
||||
* @returns {Function}
|
||||
*/
|
||||
static buildEventHandler(eventMap: Map, injectorIdx: int) {
|
||||
var locals = MapWrapper.create();
|
||||
return (event) => {
|
||||
// Most of the time the event will be fired only when the view is
|
||||
// in the live document. However, in a rare circumstance the
|
||||
// view might get dehydrated, in between the event queuing up and
|
||||
// firing.
|
||||
return (event, view) => {
|
||||
// Most of the time the event will be fired only when the view is in the live document.
|
||||
// However, in a rare circumstance the view might get dehydrated, in between the event
|
||||
// queuing up and firing.
|
||||
if (view.hydrated()) {
|
||||
MapWrapper.set(locals, '$event', event);
|
||||
var context = new ContextWithVariableBindings(view.context, locals);
|
||||
expr.eval(context);
|
||||
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
||||
var context;
|
||||
if (directiveIndex === -1) {
|
||||
context = view.context;
|
||||
} else {
|
||||
context = view.elementInjectors[injectorIdx].getDirectiveAtIndex(directiveIndex);
|
||||
}
|
||||
expr.eval(context, new Locals(view.locals, locals));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_directParentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
|
||||
var p = protoElementInjector.directParent();
|
||||
return isPresent(p) ? preBuiltObjects[p.index].lightDom : null;
|
||||
}
|
||||
|
||||
bindVariable(contextName:string, templateName:string) {
|
||||
MapWrapper.set(this.variableBindings, contextName, templateName);
|
||||
MapWrapper.set(this.protoContextLocals, templateName, null);
|
||||
MapWrapper.set(this.protoLocals, templateName, null);
|
||||
}
|
||||
|
||||
bindElement(protoElementInjector:ProtoElementInjector,
|
||||
bindElement(parent:ElementBinder, distanceToParent:int, protoElementInjector:ProtoElementInjector,
|
||||
componentDirective:DirectiveMetadata = null, viewportDirective:DirectiveMetadata = null):ElementBinder {
|
||||
var elBinder = new ElementBinder(protoElementInjector, componentDirective, viewportDirective);
|
||||
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
|
||||
protoElementInjector, componentDirective, viewportDirective);
|
||||
ListWrapper.push(this.elementBinders, elBinder);
|
||||
return elBinder;
|
||||
}
|
||||
@ -480,7 +558,7 @@ export class ProtoView {
|
||||
}
|
||||
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
|
||||
var memento = this.textNodesWithBindingCount++;
|
||||
this.protoChangeDetector.addAst(expression, memento);
|
||||
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -493,18 +571,35 @@ export class ProtoView {
|
||||
this.elementsWithBindingCount++;
|
||||
}
|
||||
var memento = new ElementBindingMemento(this.elementsWithBindingCount-1, setterName, setter);
|
||||
this.protoChangeDetector.addAst(expression, memento);
|
||||
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event binding for the last created ElementBinder via bindElement
|
||||
* Adds an event binding for the last created ElementBinder via bindElement.
|
||||
*
|
||||
* If the directive index is a positive integer, the event is evaluated in the context of
|
||||
* the given directive.
|
||||
*
|
||||
* If the directive index is -1, the event is evaluated in the context of the enclosing view.
|
||||
*
|
||||
* @param {string} eventName
|
||||
* @param {AST} expression
|
||||
* @param {int} directiveIndex The directive index in the binder or -1 when the event is not bound
|
||||
* to a directive
|
||||
*/
|
||||
bindEvent(eventName:string, expression:AST) {
|
||||
var elBinder = this.elementBinders[this.elementBinders.length-1];
|
||||
if (isBlank(elBinder.events)) {
|
||||
elBinder.events = MapWrapper.create();
|
||||
bindEvent(eventName:string, expression:AST, directiveIndex: int = -1) {
|
||||
var elBinder = this.elementBinders[this.elementBinders.length - 1];
|
||||
var events = elBinder.events;
|
||||
if (isBlank(events)) {
|
||||
events = StringMapWrapper.create();
|
||||
elBinder.events = events;
|
||||
}
|
||||
MapWrapper.set(elBinder.events, eventName, expression);
|
||||
var event = StringMapWrapper.get(events, eventName);
|
||||
if (isBlank(event)) {
|
||||
event = MapWrapper.create();
|
||||
StringMapWrapper.set(events, eventName, event);
|
||||
}
|
||||
MapWrapper.set(event, directiveIndex, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -523,7 +618,7 @@ export class ProtoView {
|
||||
setter
|
||||
);
|
||||
var directiveMemento = DirectiveMemento.get(bindingMemento);
|
||||
this.protoChangeDetector.addAst(expression, bindingMemento, directiveMemento);
|
||||
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, bindingMemento, directiveMemento));
|
||||
}
|
||||
|
||||
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
|
||||
@ -540,7 +635,7 @@ export class ProtoView {
|
||||
var cmpType = rootComponentAnnotatedType.type;
|
||||
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
|
||||
rootProtoView.instantiateInPlace = true;
|
||||
var binder = rootProtoView.bindElement(
|
||||
var binder = rootProtoView.bindElement(null, 0,
|
||||
new ProtoElementInjector(null, 0, [cmpType], true));
|
||||
binder.componentDirective = rootComponentAnnotatedType;
|
||||
binder.nestedProtoView = protoView;
|
||||
@ -549,6 +644,9 @@ export class ProtoView {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class ElementBindingMemento {
|
||||
_elementIndex:int;
|
||||
_setterName:string;
|
||||
@ -565,6 +663,9 @@ export class ElementBindingMemento {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class DirectiveBindingMemento {
|
||||
_elementInjectorIndex:int;
|
||||
_directiveIndex:int;
|
||||
@ -621,7 +722,10 @@ class DirectiveMemento {
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyUpdate {
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class PropertyUpdate {
|
||||
currentValue;
|
||||
previousValue;
|
||||
|
||||
@ -629,4 +733,8 @@ class PropertyUpdate {
|
||||
this.currentValue = currentValue;
|
||||
this.previousValue = previousValue;
|
||||
}
|
||||
|
||||
static createWithoutPrevious(currentValue) {
|
||||
return new PropertyUpdate(currentValue, uninitialized);
|
||||
}
|
||||
}
|
||||
|
@ -6,20 +6,29 @@ import {Injector} from 'angular2/di';
|
||||
import * as eiModule from 'angular2/src/core/compiler/element_injector';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {EventManager} from 'angular2/src/core/events/event_manager';
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class ViewContainer {
|
||||
parentView: viewModule.View;
|
||||
templateElement;
|
||||
defaultProtoView: viewModule.ProtoView;
|
||||
_views: List<viewModule.View>;
|
||||
_lightDom: any;
|
||||
_lightDom: LightDom;
|
||||
_eventManager: EventManager;
|
||||
elementInjector: eiModule.ElementInjector;
|
||||
appInjector: Injector;
|
||||
hostElementInjector: eiModule.ElementInjector;
|
||||
hostLightDom: LightDom;
|
||||
|
||||
constructor(parentView: viewModule.View, templateElement, defaultProtoView: viewModule.ProtoView,
|
||||
elementInjector: eiModule.ElementInjector, eventManager: EventManager, lightDom = null) {
|
||||
constructor(parentView: viewModule.View,
|
||||
templateElement,
|
||||
defaultProtoView: viewModule.ProtoView,
|
||||
elementInjector: eiModule.ElementInjector,
|
||||
eventManager: EventManager,
|
||||
lightDom = null) {
|
||||
this.parentView = parentView;
|
||||
this.templateElement = templateElement;
|
||||
this.defaultProtoView = defaultProtoView;
|
||||
@ -30,17 +39,20 @@ export class ViewContainer {
|
||||
this._views = [];
|
||||
this.appInjector = null;
|
||||
this.hostElementInjector = null;
|
||||
this.hostLightDom = null;
|
||||
this._eventManager = eventManager;
|
||||
}
|
||||
|
||||
hydrate(appInjector: Injector, hostElementInjector: eiModule.ElementInjector) {
|
||||
hydrate(appInjector: Injector, hostElementInjector: eiModule.ElementInjector, hostLightDom: LightDom) {
|
||||
this.appInjector = appInjector;
|
||||
this.hostElementInjector = hostElementInjector;
|
||||
this.hostLightDom = hostLightDom;
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
this.appInjector = null;
|
||||
this.hostElementInjector = null;
|
||||
this.hostLightDom = null;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
@ -76,7 +88,13 @@ export class ViewContainer {
|
||||
var newView = this.defaultProtoView.instantiate(this.hostElementInjector, this._eventManager);
|
||||
// insertion must come before hydration so that element injector trees are attached.
|
||||
this.insert(newView, atIndex);
|
||||
newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context);
|
||||
newView.hydrate(this.appInjector, this.hostElementInjector, this.hostLightDom,
|
||||
this.parentView.context, this.parentView.locals);
|
||||
|
||||
// new content tags might have appeared, we need to redistrubute.
|
||||
if (isPresent(this.hostLightDom)) {
|
||||
this.hostLightDom.redistribute();
|
||||
}
|
||||
return newView;
|
||||
}
|
||||
|
||||
@ -90,6 +108,7 @@ export class ViewContainer {
|
||||
}
|
||||
this.parentView.changeDetector.addChild(view.changeDetector);
|
||||
this._linkElementInjectors(view);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@ -115,6 +134,10 @@ export class ViewContainer {
|
||||
} else {
|
||||
this._lightDom.redistribute();
|
||||
}
|
||||
// content tags might have disappeared we need to do redistribution.
|
||||
if (isPresent(this.hostLightDom)) {
|
||||
this.hostLightDom.redistribute();
|
||||
}
|
||||
detachedView.changeDetector.remove();
|
||||
this._unlinkElementInjectors(detachedView);
|
||||
return detachedView;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import 'package:angular2/di.dart';
|
||||
import './xhr.dart' show XHR;
|
||||
|
||||
@Injectable()
|
||||
class XHRImpl extends XHR {
|
||||
Future<String> get(String url) {
|
||||
return HttpRequest.request(url).then(
|
||||
|
@ -1,6 +1,8 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {XHR} from './xhr';
|
||||
|
||||
@Injectable()
|
||||
export class XHRImpl extends XHR {
|
||||
get(url: string): Promise<string> {
|
||||
var completer = PromiseWrapper.completer();
|
||||
|
3
modules/angular2/src/core/dom/element.js
vendored
3
modules/angular2/src/core/dom/element.js
vendored
@ -1,6 +1,9 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {normalizeBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* @publicModule angular2/angular2
|
||||
*/
|
||||
export class NgElement {
|
||||
domElement;
|
||||
constructor(domElement) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {isPresent, print} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
|
||||
|
||||
@Injectable()
|
||||
export class ExceptionHandler {
|
||||
call(error, stackTrace = null, reason = null) {
|
||||
var longStackTrace = isListLikeIterable(stackTrace) ? ListWrapper.join(stackTrace, "\n\n") : stackTrace;
|
||||
|
@ -1,8 +1,10 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {ChangeDetector} from 'angular2/change_detection';
|
||||
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
|
||||
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
@Injectable()
|
||||
export class LifeCycle {
|
||||
_errorHandler;
|
||||
_changeDetector:ChangeDetector;
|
||||
|
21
modules/angular2/src/di/annotations.js
vendored
21
modules/angular2/src/di/annotations.js
vendored
@ -107,4 +107,23 @@ export class DependencyAnnotation {
|
||||
@CONST()
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class annotation that marks a class as available to `Injector`s for
|
||||
* creation.
|
||||
*
|
||||
* ```
|
||||
* class NeedsService {
|
||||
* constructor(svc:UsefulService) {}
|
||||
* }
|
||||
*
|
||||
* @Injectable
|
||||
* class UsefulService {}
|
||||
* ```
|
||||
*/
|
||||
export class Injectable {
|
||||
@CONST()
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
|
2
modules/angular2/src/di/binding.js
vendored
2
modules/angular2/src/di/binding.js
vendored
@ -1,4 +1,4 @@
|
||||
import {FIELD, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Key} from './key';
|
||||
|
14
modules/angular2/src/di/injector.js
vendored
14
modules/angular2/src/di/injector.js
vendored
@ -7,6 +7,7 @@ import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {Key} from './key';
|
||||
|
||||
var _constructing = new Object();
|
||||
var _notFound = new Object();
|
||||
|
||||
class _Waiting {
|
||||
promise:Promise;
|
||||
@ -72,10 +73,10 @@ export class Injector {
|
||||
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
|
||||
|
||||
var instance = strategy.readFromCache(key);
|
||||
if (isPresent(instance)) return instance;
|
||||
if (instance !== _notFound) return instance;
|
||||
|
||||
instance = strategy.instantiate(key);
|
||||
if (isPresent(instance)) return instance;
|
||||
if (instance !== _notFound) return instance;
|
||||
|
||||
if (isPresent(this._parent)) {
|
||||
return this._parent._getByKey(key, returnPromise, returnLazy, optional);
|
||||
@ -148,13 +149,13 @@ class _SyncInjectorStrategy {
|
||||
} else if (isPresent(instance) && !_isWaiting(instance)) {
|
||||
return instance;
|
||||
} else {
|
||||
return null;
|
||||
return _notFound;
|
||||
}
|
||||
}
|
||||
|
||||
instantiate(key:Key) {
|
||||
var binding = this.injector._getBinding(key);
|
||||
if (isBlank(binding)) return null;
|
||||
if (isBlank(binding)) return _notFound;
|
||||
|
||||
if (binding.providedAsPromise) throw new AsyncBindingError(key);
|
||||
|
||||
@ -198,13 +199,13 @@ class _AsyncInjectorStrategy {
|
||||
} else if (isPresent(instance)) {
|
||||
return PromiseWrapper.resolve(instance);
|
||||
} else {
|
||||
return null;
|
||||
return _notFound;
|
||||
}
|
||||
}
|
||||
|
||||
instantiate(key:Key) {
|
||||
var binding = this.injector._getBinding(key);
|
||||
if (isBlank(binding)) return null;
|
||||
if (isBlank(binding)) return _notFound;
|
||||
|
||||
//add a marker so we can detect cyclic dependencies
|
||||
this.injector._markAsConstructing(key);
|
||||
@ -243,7 +244,6 @@ class _AsyncInjectorStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _flattenBindings(bindings:List, res:Map) {
|
||||
ListWrapper.forEach(bindings, function (b) {
|
||||
if (b instanceof Binding) {
|
||||
|
2
modules/angular2/src/di/key.js
vendored
2
modules/angular2/src/di/key.js
vendored
@ -1,6 +1,6 @@
|
||||
import {KeyMetadataError} from './exceptions';
|
||||
import {MapWrapper, Map} from 'angular2/src/facade/collection';
|
||||
import {FIELD, int, isPresent} from 'angular2/src/facade/lang';
|
||||
import {int, isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
export class Key {
|
||||
token;
|
||||
|
@ -2,7 +2,8 @@ library angular.core.facade.dom;
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:js' show JsObject;
|
||||
import 'dom_adapter.dart' show setRootDomAdapter, DomAdapter;
|
||||
import 'dom_adapter.dart' show setRootDomAdapter;
|
||||
import 'generic_browser_adapter.dart' show GenericBrowserDomAdapter;
|
||||
import '../facade/browser.dart';
|
||||
|
||||
// WARNING: Do not expose outside this class. Parsing HTML using this
|
||||
@ -13,14 +14,14 @@ class _IdentitySanitizer implements NodeTreeSanitizer {
|
||||
|
||||
final _identitySanitizer = new _IdentitySanitizer();
|
||||
|
||||
class BrowserDomAdapter extends DomAdapter {
|
||||
class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
static void makeCurrent() {
|
||||
setRootDomAdapter(new BrowserDomAdapter());
|
||||
}
|
||||
|
||||
@override
|
||||
final attrToPropMap = const {
|
||||
'inner-html': 'innerHtml',
|
||||
'innerHtml': 'innerHtml',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
};
|
||||
@ -150,8 +151,9 @@ class BrowserDomAdapter extends DomAdapter {
|
||||
|
||||
String tagName(Element element) => element.tagName;
|
||||
|
||||
Map<String, String> attributeMap(Element element) =>
|
||||
element.attributes;
|
||||
Map<String, String> attributeMap(Element element) {
|
||||
return new Map.from(element.attributes);
|
||||
}
|
||||
|
||||
String getAttribute(Element element, String attribute) =>
|
||||
element.getAttribute(attribute);
|
||||
@ -173,6 +175,10 @@ class BrowserDomAdapter extends DomAdapter {
|
||||
document.implementation.createHtmlDocument('fakeTitle');
|
||||
|
||||
HtmlDocument defaultDoc() => document;
|
||||
String getTitle() => document.title;
|
||||
void setTitle(String newTitle) {
|
||||
document.title = newTitle;
|
||||
}
|
||||
bool elementMatches(n, String selector) =>
|
||||
n is Element && n.matches(selector);
|
||||
bool isTemplateElement(Element el) =>
|
||||
@ -192,4 +198,7 @@ class BrowserDomAdapter extends DomAdapter {
|
||||
bool isStyleRule(CssRule rule) => rule is CssStyleRule;
|
||||
bool isMediaRule(CssRule rule) => rule is CssMediaRule;
|
||||
bool isKeyframesRule(CssRule rule) => rule is CssKeyframesRule;
|
||||
String getHref(AnchorElement element) {
|
||||
return element.href;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {DomAdapter, setRootDomAdapter} from './dom_adapter';
|
||||
import {setRootDomAdapter} from './dom_adapter';
|
||||
import {GenericBrowserDomAdapter} from './generic_browser_adapter';
|
||||
|
||||
var _attrToPropMap = {
|
||||
'inner-html': 'innerHTML',
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
'tabindex': 'tabIndex'
|
||||
};
|
||||
|
||||
export class BrowserDomAdapter extends DomAdapter {
|
||||
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
static makeCurrent() {
|
||||
setRootDomAdapter(new BrowserDomAdapter());
|
||||
}
|
||||
@ -79,7 +80,9 @@ export class BrowserDomAdapter extends DomAdapter {
|
||||
return res;
|
||||
}
|
||||
clearNodes(el) {
|
||||
el.innerHTML = '';
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
this.remove(el.childNodes[i]);
|
||||
}
|
||||
}
|
||||
appendChild(el, node) {
|
||||
el.appendChild(node);
|
||||
@ -215,6 +218,12 @@ export class BrowserDomAdapter extends DomAdapter {
|
||||
defaultDoc() {
|
||||
return document;
|
||||
}
|
||||
getTitle() {
|
||||
return document.title;
|
||||
}
|
||||
setTitle(newTitle:string) {
|
||||
document.title = newTitle;
|
||||
}
|
||||
elementMatches(n, selector:string):boolean {
|
||||
return n instanceof HTMLElement && n.matches(selector);
|
||||
}
|
||||
@ -258,4 +267,7 @@ export class BrowserDomAdapter extends DomAdapter {
|
||||
isKeyframesRule(rule): boolean {
|
||||
return rule.type === CSSRule.KEYFRAMES_RULE;
|
||||
}
|
||||
getHref(el:Element): string {
|
||||
return el.href;
|
||||
}
|
||||
}
|
||||
|
15
modules/angular2/src/dom/dom_adapter.js
vendored
15
modules/angular2/src/dom/dom_adapter.js
vendored
@ -201,6 +201,12 @@ export class DomAdapter {
|
||||
defaultDoc() {
|
||||
throw _abstract();
|
||||
}
|
||||
getTitle() {
|
||||
throw _abstract();
|
||||
}
|
||||
setTitle(newTitle:string) {
|
||||
throw _abstract();
|
||||
}
|
||||
elementMatches(n, selector:string):boolean {
|
||||
throw _abstract();
|
||||
}
|
||||
@ -234,4 +240,13 @@ export class DomAdapter {
|
||||
isKeyframesRule(rule): boolean {
|
||||
throw _abstract();
|
||||
}
|
||||
getHref(element): string {
|
||||
throw _abstract();
|
||||
}
|
||||
resolveAndSetHref(element, baseUrl:string, href:string) {
|
||||
throw _abstract();
|
||||
}
|
||||
cssToRules(css:string): List {
|
||||
throw _abstract();
|
||||
}
|
||||
}
|
||||
|
37
modules/angular2/src/dom/generic_browser_adapter.js
vendored
Normal file
37
modules/angular2/src/dom/generic_browser_adapter.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
import {ABSTRACT} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {DomAdapter} from './dom_adapter';
|
||||
|
||||
/**
|
||||
* Provides DOM operations in any browser environment.
|
||||
*/
|
||||
@ABSTRACT()
|
||||
export class GenericBrowserDomAdapter extends DomAdapter {
|
||||
resolveAndSetHref(el, baseUrl:string, href:string) {
|
||||
el.href = href == null ? baseUrl : baseUrl + '/../' + href;
|
||||
}
|
||||
cssToRules(css:string): List {
|
||||
var style = this.createStyleElement(css);
|
||||
this.appendChild(this.defaultDoc().head, style);
|
||||
var rules = ListWrapper.create();
|
||||
if (isPresent(style.sheet)) {
|
||||
// TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
|
||||
// with an @import
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=625013
|
||||
try {
|
||||
var rawRules = style.sheet.cssRules;
|
||||
rules = ListWrapper.createFixedSize(rawRules.length);
|
||||
for (var i=0; i<rawRules.length; i++) {
|
||||
rules[i] = rawRules[i];
|
||||
}
|
||||
} catch(e) {
|
||||
//
|
||||
}
|
||||
} else {
|
||||
// console.warn('sheet not found', style);
|
||||
}
|
||||
this.remove(style);
|
||||
return rules;
|
||||
}
|
||||
}
|
@ -226,4 +226,13 @@ class Html5LibDomAdapter implements DomAdapter {
|
||||
bool isKeyframesRule(rule) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
String getHref(element) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
void resolveAndSetHref(element, baseUrl, href) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
List cssToRules(String css) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
}
|
||||
|
482
modules/angular2/src/dom/parse5_adapter.cjs
Normal file
482
modules/angular2/src/dom/parse5_adapter.cjs
Normal file
@ -0,0 +1,482 @@
|
||||
var parse5 = require('parse5');
|
||||
var parser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
||||
var serializer = new parse5.Serializer(parse5.TreeAdapters.htmlparser2);
|
||||
var treeAdapter = parser.treeAdapter;
|
||||
|
||||
var cssParse = require('css-parse');
|
||||
|
||||
var url = require('url');
|
||||
|
||||
import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DomAdapter, setRootDomAdapter} from './dom_adapter';
|
||||
import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {SelectorMatcher, CssSelector} from 'angular2/src/core/compiler/selector';
|
||||
|
||||
var _attrToPropMap = {
|
||||
'innerHtml': 'innerHTML',
|
||||
'readonly': 'readOnly',
|
||||
'tabindex': 'tabIndex',
|
||||
};
|
||||
var defDoc = null;
|
||||
|
||||
function _notImplemented(methodName) {
|
||||
return new BaseException('This method is not implemented in Parse5DomAdapter: ' + methodName);
|
||||
}
|
||||
|
||||
export class Parse5DomAdapter extends DomAdapter {
|
||||
static makeCurrent() {
|
||||
setRootDomAdapter(new Parse5DomAdapter());
|
||||
}
|
||||
|
||||
get attrToPropMap() {
|
||||
return _attrToPropMap;
|
||||
}
|
||||
|
||||
query(selector) {
|
||||
throw _notImplemented('query');
|
||||
}
|
||||
querySelector(el, selector:string) {
|
||||
return this.querySelectorAll(el, selector)[0];
|
||||
}
|
||||
querySelectorAll(el, selector:string) {
|
||||
var res = ListWrapper.create();
|
||||
var _recursive = (result, node, selector, matcher) => {
|
||||
if (this.elementMatches(node, selector, matcher)) {
|
||||
ListWrapper.push(result, node);
|
||||
}
|
||||
var cNodes = node.childNodes;
|
||||
if (cNodes && cNodes.length > 0) {
|
||||
for (var i = 0; i < cNodes.length; i++) {
|
||||
_recursive(result, cNodes[i], selector, matcher);
|
||||
}
|
||||
}
|
||||
};
|
||||
var matcher = new SelectorMatcher();
|
||||
matcher.addSelectable(CssSelector.parse(selector));
|
||||
_recursive(res, el, selector, matcher);
|
||||
return res;
|
||||
}
|
||||
elementMatches(node, selector:string, matcher = null):boolean {
|
||||
var result = false;
|
||||
if (selector && selector.charAt(0) == "#") {
|
||||
result = this.getAttribute(node, 'id') == selector.substring(1);
|
||||
} else if (selector) {
|
||||
var result = false;
|
||||
if (matcher == null) {
|
||||
matcher = new SelectorMatcher();
|
||||
matcher.addSelectable(CssSelector.parse(selector));
|
||||
}
|
||||
|
||||
var cssSelector = new CssSelector();
|
||||
cssSelector.setElement(this.tagName(node));
|
||||
if (node.attribs) {
|
||||
for (var attrName in node.attribs) {
|
||||
cssSelector.addAttribute(attrName, node.attribs[attrName]);
|
||||
}
|
||||
}
|
||||
var classList = this.classList(node);
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
cssSelector.addClassName(classList[i]);
|
||||
}
|
||||
|
||||
matcher.match(cssSelector, function(selector, cb) {result = true;});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
on(el, evt, listener) {
|
||||
//Do nothing, in order to not break forms integration tests
|
||||
}
|
||||
dispatchEvent(el, evt) {
|
||||
throw _notImplemented('dispatchEvent');
|
||||
}
|
||||
createMouseEvent(eventType) {
|
||||
throw _notImplemented('createMouseEvent');
|
||||
}
|
||||
createEvent(eventType) {
|
||||
throw _notImplemented('createEvent');
|
||||
}
|
||||
getInnerHTML(el) {
|
||||
return serializer.serialize(this.templateAwareRoot(el));
|
||||
}
|
||||
getOuterHTML(el) {
|
||||
serializer.html = '';
|
||||
serializer._serializeElement(el);
|
||||
return serializer.html;
|
||||
}
|
||||
nodeName(node):string {
|
||||
return node.tagName;
|
||||
}
|
||||
nodeValue(node):string {
|
||||
return node.nodeValue;
|
||||
}
|
||||
type(node:string) {
|
||||
throw _notImplemented('type');
|
||||
}
|
||||
content(node) {
|
||||
return node.childNodes[0];
|
||||
}
|
||||
firstChild(el) {
|
||||
return el.firstChild;
|
||||
}
|
||||
nextSibling(el) {
|
||||
return el.nextSibling;
|
||||
}
|
||||
parentElement(el) {
|
||||
return el.parent;
|
||||
}
|
||||
childNodes(el) {
|
||||
return el.childNodes;
|
||||
}
|
||||
childNodesAsList(el):List {
|
||||
var childNodes = el.childNodes;
|
||||
var res = ListWrapper.createFixedSize(childNodes.length);
|
||||
for (var i = 0; i < childNodes.length; i++) {
|
||||
res[i] = childNodes[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
clearNodes(el) {
|
||||
while (el.childNodes.length > 0) {
|
||||
this.remove(el.childNodes[0]);
|
||||
}
|
||||
}
|
||||
appendChild(el, node) {
|
||||
this.remove(node);
|
||||
treeAdapter.appendChild(this.templateAwareRoot(el), node);
|
||||
}
|
||||
removeChild(el, node) {
|
||||
if (ListWrapper.contains(el.childNodes, node)) {
|
||||
this.remove(node);
|
||||
}
|
||||
}
|
||||
remove(el) {
|
||||
var parent = el.parent;
|
||||
if (parent) {
|
||||
var index = parent.childNodes.indexOf(el);
|
||||
parent.childNodes.splice(index, 1);
|
||||
}
|
||||
var prev = el.previousSibling;
|
||||
var next = el.nextSibling;
|
||||
if (prev) {
|
||||
prev.next = next;
|
||||
}
|
||||
if (next) {
|
||||
next.prev = prev;
|
||||
}
|
||||
el.prev = null;
|
||||
el.next = null;
|
||||
el.parent = null;
|
||||
return el;
|
||||
}
|
||||
insertBefore(el, node) {
|
||||
this.remove(node);
|
||||
treeAdapter.insertBefore(el.parent, node, el);
|
||||
}
|
||||
insertAllBefore(el, nodes) {
|
||||
ListWrapper.forEach(nodes, (n) => {
|
||||
this.insertBefore(el, n);
|
||||
});
|
||||
}
|
||||
insertAfter(el, node) {
|
||||
if (el.nextSibling) {
|
||||
this.insertBefore(el.nextSibling, node);
|
||||
} else {
|
||||
this.appendChild(el.parent, node);
|
||||
}
|
||||
}
|
||||
setInnerHTML(el, value) {
|
||||
this.clearNodes(el);
|
||||
var content = parser.parseFragment(value);
|
||||
for (var i = 0; i < content.childNodes.length; i++) {
|
||||
treeAdapter.appendChild(el, content.childNodes[i]);
|
||||
}
|
||||
}
|
||||
getText(el) {
|
||||
if (this.isTextNode(el)) {
|
||||
return el.data;
|
||||
} else if (el.childNodes.length == 0) {
|
||||
return "";
|
||||
} else {
|
||||
var textContent = "";
|
||||
for (var i = 0; i < el.childNodes.length; i++) {
|
||||
textContent += this.getText(el.childNodes[i]);
|
||||
}
|
||||
return textContent;
|
||||
}
|
||||
}
|
||||
setText(el, value:string) {
|
||||
if (this.isTextNode(el)) {
|
||||
el.data = value;
|
||||
} else {
|
||||
this.clearNodes(el);
|
||||
treeAdapter.insertText(el, value);
|
||||
}
|
||||
}
|
||||
getValue(el) {
|
||||
return el.value;
|
||||
}
|
||||
setValue(el, value:string) {
|
||||
el.value = value;
|
||||
}
|
||||
getChecked(el) {
|
||||
return el.checked;
|
||||
}
|
||||
setChecked(el, value:boolean) {
|
||||
el.checked = value;
|
||||
}
|
||||
createTemplate(html) {
|
||||
var template = treeAdapter.createElement("template", 'http://www.w3.org/1999/xhtml', []);
|
||||
var content = parser.parseFragment(html);
|
||||
treeAdapter.appendChild(template, content);
|
||||
return template;
|
||||
}
|
||||
createElement(tagName) {
|
||||
return treeAdapter.createElement(tagName, 'http://www.w3.org/1999/xhtml', []);
|
||||
}
|
||||
createTextNode(text: string) {
|
||||
throw _notImplemented('createTextNode');
|
||||
}
|
||||
createScriptTag(attrName:string, attrValue:string) {
|
||||
return treeAdapter.createElement("script", 'http://www.w3.org/1999/xhtml', [{name: attrName, value: attrValue}]);
|
||||
}
|
||||
createStyleElement(css:string) {
|
||||
var style = this.createElement('style');
|
||||
this.setText(style, css);
|
||||
return style;
|
||||
}
|
||||
createShadowRoot(el) {
|
||||
el.shadowRoot = treeAdapter.createDocumentFragment();
|
||||
el.shadowRoot.parent = el;
|
||||
return el.shadowRoot;
|
||||
}
|
||||
getShadowRoot(el) {
|
||||
return el.shadowRoot;
|
||||
}
|
||||
clone(node) {
|
||||
var temp = treeAdapter.createElement("template", null, []);
|
||||
treeAdapter.appendChild(temp, node);
|
||||
var serialized = serializer.serialize(temp);
|
||||
var newParser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
|
||||
return newParser.parseFragment(serialized).childNodes[0];
|
||||
}
|
||||
hasProperty(element, name:string) {
|
||||
return _HTMLElementPropertyList.indexOf(name) > -1;
|
||||
}
|
||||
getElementsByClassName(element, name:string) {
|
||||
return this.querySelectorAll(element, "." + name);
|
||||
}
|
||||
getElementsByTagName(element, name:string) {
|
||||
throw _notImplemented('getElementsByTagName');
|
||||
}
|
||||
classList(element):List {
|
||||
var classAttrValue = null;
|
||||
var attributes = element.attribs;
|
||||
if (attributes && attributes.hasOwnProperty("class")) {
|
||||
classAttrValue = attributes["class"];
|
||||
}
|
||||
return classAttrValue ? classAttrValue.trim().split(/\s+/g) : [];
|
||||
}
|
||||
addClass(element, classname:string) {
|
||||
var classList = this.classList(element);
|
||||
var index = classList.indexOf(classname);
|
||||
if (index == -1) {
|
||||
ListWrapper.push(classList, classname);
|
||||
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
|
||||
}
|
||||
}
|
||||
removeClass(element, classname:string) {
|
||||
var classList = this.classList(element);
|
||||
var index = classList.indexOf(classname);
|
||||
if (index > -1) {
|
||||
classList.splice(index, 1);
|
||||
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
|
||||
}
|
||||
}
|
||||
hasClass(element, classname:string) {
|
||||
return ListWrapper.contains(this.classList(element), classname);
|
||||
}
|
||||
_readStyleAttribute(element) {
|
||||
var styleMap = {};
|
||||
var attributes = element.attribs;
|
||||
if (attributes && attributes.hasOwnProperty("style")) {
|
||||
var styleAttrValue = attributes["style"];
|
||||
var styleList = styleAttrValue.split(/;+/g);
|
||||
for (var i = 0; i < styleList.length; i++) {
|
||||
if (styleList[i].length > 0) {
|
||||
var elems = styleList[i].split(/:+/g);
|
||||
styleMap[elems[0].trim()] = elems[1].trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
return styleMap;
|
||||
}
|
||||
_writeStyleAttribute(element, styleMap) {
|
||||
var styleAttrValue = "";
|
||||
for (var key in styleMap) {
|
||||
var newValue = styleMap[key];
|
||||
if (newValue && newValue.length > 0) {
|
||||
styleAttrValue += key + ":" + styleMap[key] + ";";
|
||||
}
|
||||
}
|
||||
element.attribs["style"] = styleAttrValue;
|
||||
}
|
||||
setStyle(element, stylename:string, stylevalue:string) {
|
||||
var styleMap = this._readStyleAttribute(element);
|
||||
styleMap[stylename] = stylevalue;
|
||||
this._writeStyleAttribute(element, styleMap);
|
||||
}
|
||||
removeStyle(element, stylename:string) {
|
||||
this.setStyle(element, stylename, null);
|
||||
}
|
||||
getStyle(element, stylename:string) {
|
||||
var styleMap = this._readStyleAttribute(element);
|
||||
return styleMap.hasOwnProperty(stylename) ? styleMap[stylename] : "";
|
||||
}
|
||||
tagName(element):string {
|
||||
return element.tagName == "style" ? "STYLE" : element.tagName;
|
||||
}
|
||||
attributeMap(element) {
|
||||
var res = MapWrapper.create();
|
||||
var elAttrs = treeAdapter.getAttrList(element);
|
||||
for (var i = 0; i < elAttrs.length; i++) {
|
||||
var attrib = elAttrs[i];
|
||||
MapWrapper.set(res, attrib.name, attrib.value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
getAttribute(element, attribute:string) {
|
||||
return element.attribs && element.attribs.hasOwnProperty(attribute)? element.attribs[attribute]: null;
|
||||
}
|
||||
setAttribute(element, attribute:string, value:string) {
|
||||
if (attribute) {
|
||||
element.attribs[attribute] = value;
|
||||
}
|
||||
}
|
||||
removeAttribute(element, attribute:string) {
|
||||
if (attribute) {
|
||||
delete element.attribs[attribute];
|
||||
}
|
||||
}
|
||||
templateAwareRoot(el) {
|
||||
return this.isTemplateElement(el) ? this.content(el) : el;
|
||||
}
|
||||
createHtmlDocument() {
|
||||
throw _notImplemented('createHtmlDocument');
|
||||
}
|
||||
defaultDoc() {
|
||||
if (defDoc === null) {
|
||||
defDoc = StringMapWrapper.create();
|
||||
defDoc.title = "Default title";
|
||||
StringMapWrapper.set(defDoc, "head", treeAdapter.createElement("head", null, []));
|
||||
}
|
||||
return defDoc;
|
||||
}
|
||||
getTitle() {
|
||||
return this.defaultDoc().title || "";
|
||||
}
|
||||
setTitle(newTitle:string) {
|
||||
this.defaultDoc().title = newTitle;
|
||||
}
|
||||
isTemplateElement(el:any):boolean {
|
||||
return this.isElementNode(el) && this.tagName(el) === "template";
|
||||
}
|
||||
isTextNode(node):boolean {
|
||||
return treeAdapter.isTextNode(node);
|
||||
}
|
||||
isCommentNode(node):boolean {
|
||||
throw treeAdapter.isCommentNode(node);
|
||||
}
|
||||
isElementNode(node):boolean {
|
||||
return node ? treeAdapter.isElementNode(node) : false;
|
||||
}
|
||||
hasShadowRoot(node):boolean {
|
||||
return isPresent(node.shadowRoot);
|
||||
}
|
||||
importIntoDoc(node) {
|
||||
return this.clone(node);
|
||||
}
|
||||
isPageRule(rule): boolean {
|
||||
return rule.type === 6; //CSSRule.PAGE_RULE
|
||||
}
|
||||
isStyleRule(rule): boolean {
|
||||
return rule.type === 1; //CSSRule.MEDIA_RULE
|
||||
}
|
||||
isMediaRule(rule): boolean {
|
||||
return rule.type === 4; //CSSRule.MEDIA_RULE
|
||||
}
|
||||
isKeyframesRule(rule): boolean {
|
||||
return rule.type === 7; //CSSRule.KEYFRAMES_RULE
|
||||
}
|
||||
getHref(el): string {
|
||||
return el.href;
|
||||
}
|
||||
resolveAndSetHref(el, baseUrl:string, href:string) {
|
||||
if (href == null) {
|
||||
el.href = baseUrl;
|
||||
} else {
|
||||
el.href = url.resolve(baseUrl, href);
|
||||
}
|
||||
}
|
||||
_buildRules(parsedRules, css) {
|
||||
var rules = ListWrapper.create();
|
||||
for (var i = 0; i < parsedRules.length; i++) {
|
||||
var parsedRule = parsedRules[i];
|
||||
var rule = {cssText: css};
|
||||
rule.style = {content: "", cssText: ""};
|
||||
if (parsedRule.type == "rule") {
|
||||
rule.type = 1;
|
||||
rule.selectorText = parsedRule.selectors.join(", ").replace(/\s{2,}/g, " ").replace(/\s*~\s*/g, " ~ ")
|
||||
.replace(/\s*\+\s*/g, " + ").replace(/\s*>\s*/g, " > ").replace(/\[(\w+)=(\w+)\]/g, '[$1="$2"]');
|
||||
if (isBlank(parsedRule.declarations)) {
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < parsedRule.declarations.length; j++) {
|
||||
var declaration = parsedRule.declarations[j];
|
||||
rule.style[declaration.property] = declaration.value;
|
||||
rule.style.cssText += declaration.property + ": " + declaration.value + ";";
|
||||
}
|
||||
} else if (parsedRule.type == "media") {
|
||||
rule.type = 4;
|
||||
rule.media = {mediaText: parsedRule.media};
|
||||
if (parsedRule.rules) {
|
||||
rule.cssRules = this._buildRules(parsedRule.rules);
|
||||
}
|
||||
}
|
||||
ListWrapper.push(rules, rule);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
cssToRules(css:string): List {
|
||||
css = css.replace(/url\(\'(.+)\'\)/g, 'url($1)');
|
||||
var rules = ListWrapper.create();
|
||||
var parsedCSS = cssParse(css, {silent: true});
|
||||
if (parsedCSS.stylesheet && parsedCSS.stylesheet.rules) {
|
||||
rules = this._buildRules(parsedCSS.stylesheet.rules, css);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: build a proper list, this one is all the keys of a HTMLInputElement
|
||||
var _HTMLElementPropertyList = ["webkitEntries","incremental","webkitdirectory","selectionDirection","selectionEnd",
|
||||
"selectionStart","labels","validationMessage","validity","willValidate","width","valueAsNumber","valueAsDate",
|
||||
"value","useMap","defaultValue","type","step","src","size","required","readOnly","placeholder","pattern","name",
|
||||
"multiple","min","minLength","maxLength","max","list","indeterminate","height","formTarget","formNoValidate",
|
||||
"formMethod","formEnctype","formAction","files","form","disabled","dirName","checked","defaultChecked","autofocus",
|
||||
"autocomplete","alt","align","accept","onautocompleteerror","onautocomplete","onwaiting","onvolumechange",
|
||||
"ontoggle","ontimeupdate","onsuspend","onsubmit","onstalled","onshow","onselect","onseeking","onseeked","onscroll",
|
||||
"onresize","onreset","onratechange","onprogress","onplaying","onplay","onpause","onmousewheel","onmouseup",
|
||||
"onmouseover","onmouseout","onmousemove","onmouseleave","onmouseenter","onmousedown","onloadstart",
|
||||
"onloadedmetadata","onloadeddata","onload","onkeyup","onkeypress","onkeydown","oninvalid","oninput","onfocus",
|
||||
"onerror","onended","onemptied","ondurationchange","ondrop","ondragstart","ondragover","ondragleave","ondragenter",
|
||||
"ondragend","ondrag","ondblclick","oncuechange","oncontextmenu","onclose","onclick","onchange","oncanplaythrough",
|
||||
"oncanplay","oncancel","onblur","onabort","spellcheck","isContentEditable","contentEditable","outerText",
|
||||
"innerText","accessKey","hidden","webkitdropzone","draggable","tabIndex","dir","translate","lang","title",
|
||||
"childElementCount","lastElementChild","firstElementChild","children","onwebkitfullscreenerror",
|
||||
"onwebkitfullscreenchange","nextElementSibling","previousElementSibling","onwheel","onselectstart",
|
||||
"onsearch","onpaste","oncut","oncopy","onbeforepaste","onbeforecut","onbeforecopy","shadowRoot","dataset",
|
||||
"classList","className","outerHTML","innerHTML","scrollHeight","scrollWidth","scrollTop","scrollLeft",
|
||||
"clientHeight","clientWidth","clientTop","clientLeft","offsetParent","offsetHeight","offsetWidth","offsetTop",
|
||||
"offsetLeft","localName","prefix","namespaceURI","id","style","attributes","tagName","parentElement","textContent",
|
||||
"baseURI","ownerDocument","nextSibling","previousSibling","lastChild","firstChild","childNodes","parentNode",
|
||||
"nodeType","nodeValue","nodeName","closure_lm_714617","__jsaction"];
|
@ -10,11 +10,6 @@ class Math {
|
||||
static double random() => _random.nextDouble();
|
||||
}
|
||||
|
||||
class FIELD {
|
||||
final String definition;
|
||||
const FIELD(this.definition);
|
||||
}
|
||||
|
||||
class CONST {
|
||||
const CONST();
|
||||
}
|
||||
@ -210,7 +205,13 @@ class DateWrapper {
|
||||
static DateTime fromMillis(int ms) {
|
||||
return new DateTime.fromMillisecondsSinceEpoch(ms);
|
||||
}
|
||||
static int toMillis(DateTime date) {
|
||||
return date.millisecondsSinceEpoch;
|
||||
}
|
||||
static DateTime now() {
|
||||
return new DateTime.now();
|
||||
}
|
||||
static toJson(DateTime date) {
|
||||
return date.toUtc().toIso8601String();
|
||||
}
|
||||
}
|
||||
|
@ -22,12 +22,6 @@ if (assertionsEnabled_) {
|
||||
}
|
||||
export {int};
|
||||
|
||||
export class FIELD {
|
||||
constructor(definition) {
|
||||
this.definition = definition;
|
||||
}
|
||||
}
|
||||
|
||||
export class CONST {}
|
||||
export class ABSTRACT {}
|
||||
export class IMPLEMENTS {}
|
||||
@ -214,6 +208,10 @@ export class RegExpWrapper {
|
||||
return input.match(regExp.single);
|
||||
}
|
||||
static matcher(regExp, input) {
|
||||
// Reset regex state for the case
|
||||
// someone did not loop over all matches
|
||||
// last time.
|
||||
regExp.multiple.lastIndex = 0;
|
||||
return {
|
||||
re: regExp.multiple,
|
||||
input: input
|
||||
@ -275,7 +273,13 @@ export class DateWrapper {
|
||||
static fromMillis(ms) {
|
||||
return new Date(ms);
|
||||
}
|
||||
static toMillis(date:Date) {
|
||||
return date.getTime();
|
||||
}
|
||||
static now() {
|
||||
return new Date();
|
||||
}
|
||||
static toJson(date) {
|
||||
return date.toJSON();
|
||||
}
|
||||
}
|
||||
|
48
modules/angular2/src/forms/form_builder.js
vendored
Normal file
48
modules/angular2/src/forms/form_builder.js
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import * as modelModule from './model';
|
||||
|
||||
|
||||
export class FormBuilder {
|
||||
group(controlsConfig, extra = 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;
|
||||
|
||||
if (isPresent(validator)) {
|
||||
return new modelModule.ControlGroup(controls, optionals, validator);
|
||||
} else {
|
||||
return new modelModule.ControlGroup(controls, optionals);
|
||||
}
|
||||
}
|
||||
|
||||
control(value, validator:Function = null):modelModule.Control {
|
||||
if (isPresent(validator)) {
|
||||
return new modelModule.Control(value, validator);
|
||||
} else {
|
||||
return new modelModule.Control(value);
|
||||
}
|
||||
}
|
||||
|
||||
_reduceControls(controlsConfig) {
|
||||
var controls = {};
|
||||
StringMapWrapper.forEach(controlsConfig, (controlConfig, controlName) => {
|
||||
controls[controlName] = this._createControl(controlConfig);
|
||||
});
|
||||
return controls;
|
||||
}
|
||||
|
||||
_createControl(controlConfig) {
|
||||
if (controlConfig instanceof modelModule.Control || controlConfig instanceof modelModule.ControlGroup) {
|
||||
return controlConfig;
|
||||
|
||||
} else if (ListWrapper.isList(controlConfig)) {
|
||||
var value = ListWrapper.get(controlConfig, 0);
|
||||
var validator = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
return this.control(value, validator);
|
||||
|
||||
} else {
|
||||
return this.control(controlConfig);
|
||||
}
|
||||
}
|
||||
}
|
79
modules/angular2/src/forms/model.js
vendored
79
modules/angular2/src/forms/model.js
vendored
@ -11,7 +11,6 @@ export const INVALID = "INVALID";
|
||||
// get status():string;
|
||||
// get valid():boolean;
|
||||
// get errors():Map;
|
||||
// get active():boolean {}
|
||||
// updateValue(value:any){}
|
||||
// setParent(parent){}
|
||||
//}
|
||||
@ -29,10 +28,6 @@ export class AbstractControl {
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
get active():boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
get value() {
|
||||
this._updateIfNeeded();
|
||||
return this._value;
|
||||
@ -90,13 +85,30 @@ export class Control extends AbstractControl {
|
||||
|
||||
export class ControlGroup extends AbstractControl {
|
||||
controls;
|
||||
optionals;
|
||||
|
||||
constructor(controls, validator:Function = controlGroupValidator) {
|
||||
constructor(controls, optionals = null, validator:Function = controlGroupValidator) {
|
||||
super(validator);
|
||||
this.controls = controls;
|
||||
this.optionals = isPresent(optionals) ? optionals : {};
|
||||
this._setParentForControls();
|
||||
}
|
||||
|
||||
include(controlName:string) {
|
||||
this._dirty = true;
|
||||
StringMapWrapper.set(this.optionals, controlName, true);
|
||||
}
|
||||
|
||||
exclude(controlName:string) {
|
||||
this._dirty = true;
|
||||
StringMapWrapper.set(this.optionals, controlName, false);
|
||||
}
|
||||
|
||||
contains(controlName:string) {
|
||||
var c = StringMapWrapper.contains(this.controls, controlName);
|
||||
return c && this._included(controlName);
|
||||
}
|
||||
|
||||
_setParentForControls() {
|
||||
StringMapWrapper.forEach(this.controls, (control, name) => {
|
||||
control.setParent(this);
|
||||
@ -115,7 +127,7 @@ export class ControlGroup extends AbstractControl {
|
||||
_reduceValue() {
|
||||
var newValue = {};
|
||||
StringMapWrapper.forEach(this.controls, (control, name) => {
|
||||
if (control.active) {
|
||||
if (this._included(name)) {
|
||||
newValue[name] = control.value;
|
||||
}
|
||||
});
|
||||
@ -126,56 +138,9 @@ export class ControlGroup extends AbstractControl {
|
||||
this._dirty = true;
|
||||
this._updateParent();
|
||||
}
|
||||
}
|
||||
|
||||
export class OptionalControl {
|
||||
_control:Control;
|
||||
_cond:boolean;
|
||||
|
||||
constructor(control:Control, cond:boolean) {
|
||||
super();
|
||||
this._control = control;
|
||||
this._cond = cond;
|
||||
}
|
||||
|
||||
get active():boolean {
|
||||
return this._cond;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._control.value;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this._control.status;
|
||||
}
|
||||
|
||||
get errors() {
|
||||
return this._control.errors;
|
||||
}
|
||||
|
||||
set validator(v) {
|
||||
this._control.validator = v;
|
||||
}
|
||||
|
||||
get validator() {
|
||||
return this._control.validator;
|
||||
}
|
||||
|
||||
set cond(value:boolean){
|
||||
this._cond = value;
|
||||
this._control._updateParent();
|
||||
}
|
||||
|
||||
get cond():boolean{
|
||||
return this._cond;
|
||||
}
|
||||
|
||||
updateValue(value:any){
|
||||
this._control.updateValue(value);
|
||||
}
|
||||
|
||||
setParent(parent){
|
||||
this._control.setParent(parent);
|
||||
_included(controlName:string):boolean {
|
||||
var isOptional = StringMapWrapper.contains(this.optionals, controlName);
|
||||
return !isOptional || StringMapWrapper.get(this.optionals, controlName);
|
||||
}
|
||||
}
|
12
modules/angular2/src/forms/validators.js
vendored
12
modules/angular2/src/forms/validators.js
vendored
@ -1,18 +1,18 @@
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {ControlGroup, Control} from 'angular2/forms';
|
||||
import * as modelModule from './model';
|
||||
|
||||
export function required(c:Control) {
|
||||
export function required(c:modelModule.Control) {
|
||||
return isBlank(c.value) || c.value == "" ? {"required" : true} : null;
|
||||
}
|
||||
|
||||
export function nullValidator(c:Control) {
|
||||
export function nullValidator(c:modelModule.Control) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function compose(validators:List<Function>):Function {
|
||||
return function(c:Control) {
|
||||
return function(c:modelModule.Control) {
|
||||
var res = ListWrapper.reduce(validators, (res, validator) => {
|
||||
var errors = validator(c);
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
@ -21,10 +21,10 @@ export function compose(validators:List<Function>):Function {
|
||||
}
|
||||
}
|
||||
|
||||
export function controlGroupValidator(c:ControlGroup) {
|
||||
export function controlGroupValidator(c:modelModule.ControlGroup) {
|
||||
var res = {};
|
||||
StringMapWrapper.forEach(c.controls, (control, name) => {
|
||||
if (control.active && isPresent(control.errors)) {
|
||||
if (c.contains(name) && isPresent(control.errors)) {
|
||||
StringMapWrapper.forEach(control.errors, (value, error) => {
|
||||
if (! StringMapWrapper.contains(res, error)) {
|
||||
res[error] = [];
|
||||
|
12
modules/angular2/src/services/title.js
vendored
Normal file
12
modules/angular2/src/services/title.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export class Title {
|
||||
|
||||
getTitle():string {
|
||||
return DOM.getTitle();
|
||||
}
|
||||
|
||||
setTitle(newTitle:string) {
|
||||
DOM.setTitle(newTitle);
|
||||
}
|
||||
}
|
116
modules/angular2/src/test_lib/test_injector.js
vendored
Normal file
116
modules/angular2/src/test_lib/test_injector.js
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
import {bind} from 'angular2/di';
|
||||
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection} from 'angular2/change_detection';
|
||||
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
||||
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
|
||||
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
|
||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
|
||||
import {XHRMock} from 'angular2/src/mock/xhr_mock';
|
||||
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
||||
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
||||
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
|
||||
import {StyleInliner} from 'angular2/src/core/compiler/style_inliner';
|
||||
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
|
||||
|
||||
import {Injector} from 'angular2/di';
|
||||
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {FunctionWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Returns the root injector bindings.
|
||||
*
|
||||
* This must be kept in sync with the _rootBindings in application.js
|
||||
*
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function _getRootBindings() {
|
||||
return [
|
||||
bind(Reflector).toValue(reflector),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application injector bindings.
|
||||
*
|
||||
* This must be kept in sync with _injectorBindings() in application.js
|
||||
*
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function _getAppBindings() {
|
||||
return [
|
||||
bind(ShadowDomStrategy).toClass(NativeShadowDomStrategy),
|
||||
Compiler,
|
||||
CompilerCache,
|
||||
TemplateResolver,
|
||||
bind(ChangeDetection).toValue(dynamicChangeDetection),
|
||||
TemplateLoader,
|
||||
DirectiveMetadataReader,
|
||||
Parser,
|
||||
Lexer,
|
||||
ExceptionHandler,
|
||||
bind(XHR).toClass(XHRMock),
|
||||
ComponentUrlMapper,
|
||||
UrlResolver,
|
||||
StyleUrlResolver,
|
||||
StyleInliner,
|
||||
bind(CssProcessor).toFactory(() => new CssProcessor(null), []),
|
||||
];
|
||||
}
|
||||
|
||||
export function createTestInjector(bindings: List) {
|
||||
var rootInjector = new Injector(_getRootBindings());
|
||||
return rootInjector.createChild(ListWrapper.concat(_getAppBindings(), bindings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows injecting dependencies in `beforeEach()` and `it()`.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* beforeEach(inject([Dependency, AClass], (dep, object) => {
|
||||
* // some code that uses `dep` and `object`
|
||||
* // ...
|
||||
* }));
|
||||
*
|
||||
* it('...', inject([AClass, AsyncTestCompleter], (object, async) => {
|
||||
* object.doSomething().then(() => {
|
||||
* expect(...);
|
||||
* async.done();
|
||||
* });
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Notes:
|
||||
* - injecting an `AsyncTestCompleter` allow completing async tests - this is the equivalent of
|
||||
* adding a `done` parameter in Jasmine,
|
||||
* - inject is currently a function because of some Traceur limitation the syntax should eventually
|
||||
* becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });`
|
||||
*
|
||||
* @param {Array} tokens
|
||||
* @param {Function} fn
|
||||
* @return {FunctionWithParamTokens}
|
||||
*/
|
||||
export function inject(tokens: List, fn: Function) {
|
||||
return new FunctionWithParamTokens(tokens, fn);
|
||||
}
|
||||
|
||||
export class FunctionWithParamTokens {
|
||||
_tokens: List;
|
||||
_fn: Function;
|
||||
|
||||
constructor(tokens: List, fn: Function) {
|
||||
this._tokens = tokens;
|
||||
this._fn = fn;
|
||||
}
|
||||
|
||||
execute(injector: Injector) {
|
||||
var params = ListWrapper.map(this._tokens, (t) => injector.get(t));
|
||||
FunctionWrapper.apply(this._fn, params);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,74 @@
|
||||
library test_lib.test_lib;
|
||||
|
||||
import 'package:guinness/guinness.dart' as gns;
|
||||
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit;
|
||||
export 'package:guinness/guinness.dart' hide Expect, expect, NotExpect, beforeEach, it, iit, xit;
|
||||
import 'package:unittest/unittest.dart' hide expect;
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'package:collection/equality.dart';
|
||||
|
||||
import 'package:angular2/src/dom/dom_adapter.dart' show DOM;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection.dart';
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
|
||||
import 'package:angular2/src/di/binding.dart' show bind;
|
||||
import 'package:angular2/src/di/injector.dart' show Injector;
|
||||
|
||||
import './test_injector.dart';
|
||||
export './test_injector.dart' show inject;
|
||||
|
||||
bool IS_DARTIUM = true;
|
||||
bool IS_NODEJS = false;
|
||||
|
||||
List _testBindings = [];
|
||||
Injector _injector;
|
||||
bool _isCurrentTestAsync;
|
||||
bool _inIt = false;
|
||||
|
||||
class AsyncTestCompleter {
|
||||
Completer _completer;
|
||||
|
||||
AsyncTestCompleter() {
|
||||
_completer = new Completer();
|
||||
}
|
||||
|
||||
done() {
|
||||
_completer.complete();
|
||||
}
|
||||
|
||||
get future => _completer.future;
|
||||
}
|
||||
|
||||
testSetup() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
// beforeEach configuration:
|
||||
// - Priority 3: clear the bindings before each test,
|
||||
// - Priority 2: collect the bindings before each test, see beforeEachBindings(),
|
||||
// - Priority 1: create the test injector to be used in beforeEach() and it()
|
||||
|
||||
gns.beforeEach(
|
||||
() {
|
||||
_testBindings.clear();
|
||||
},
|
||||
priority: 3
|
||||
);
|
||||
|
||||
var completerBinding = bind(AsyncTestCompleter).toFactory(() {
|
||||
// Mark the test as async when an AsyncTestCompleter is injected in an it(),
|
||||
if (!_inIt) throw 'AsyncTestCompleter can only be injected in an "it()"';
|
||||
_isCurrentTestAsync = true;
|
||||
return new AsyncTestCompleter();
|
||||
});
|
||||
|
||||
gns.beforeEach(
|
||||
() {
|
||||
_isCurrentTestAsync = false;
|
||||
_testBindings.add(completerBinding);
|
||||
_injector = createTestInjector(_testBindings);
|
||||
},
|
||||
priority: 1
|
||||
);
|
||||
}
|
||||
|
||||
Expect expect(actual, [matcher]) {
|
||||
final expect = new Expect(actual);
|
||||
@ -23,10 +81,8 @@ class Expect extends gns.Expect {
|
||||
|
||||
NotExpect get not => new NotExpect(actual);
|
||||
|
||||
// TODO(tbosch) change back when https://github.com/vsavkin/guinness/issues/41 is fixed
|
||||
// void toEqual(expected) => toHaveSameProps(expected);
|
||||
void toEqual(expected) => _expect(actual, new FixedSamePropsMatcher(expected));
|
||||
void toThrowError([message=""]) => this.toThrowWith(message: message);
|
||||
void toEqual(expected) => toHaveSameProps(expected);
|
||||
void toThrowError([message=""]) => toThrowWith(message: message);
|
||||
void toBePromise() => _expect(actual is Future, equals(true));
|
||||
void toImplement(expected) => toBeA(expected);
|
||||
void toBeNaN() => _expect(double.NAN.compareTo(actual) == 0, equals(true));
|
||||
@ -37,106 +93,61 @@ class Expect extends gns.Expect {
|
||||
class NotExpect extends gns.NotExpect {
|
||||
NotExpect(actual) : super(actual);
|
||||
|
||||
// TODO(tbosch) change back when https://github.com/vsavkin/guinness/issues/41 is fixed
|
||||
// void toEqual(expected) => toHaveSameProps(expected);
|
||||
void toEqual(expected) => _expect(actual, isNot(new FixedSamePropsMatcher(expected)));
|
||||
void toEqual(expected) => toHaveSameProps(expected);
|
||||
void toBePromise() => _expect(actual is Future, equals(false));
|
||||
Function get _expect => gns.guinness.matchers.expect;
|
||||
}
|
||||
|
||||
beforeEach(fn) {
|
||||
gns.beforeEach(_enableReflection(fn));
|
||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
|
||||
gns.beforeEach(() {
|
||||
fn.execute(_injector);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding default bindings defined in test_injector.js.
|
||||
*
|
||||
* The given function must return a list of DI bindings.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* beforeEachBindings(() => [
|
||||
* bind(Compiler).toClass(MockCompiler),
|
||||
* bind(SomeToken).toValue(myValue),
|
||||
* ]);
|
||||
*/
|
||||
beforeEachBindings(fn) {
|
||||
gns.beforeEach(
|
||||
() {
|
||||
var bindings = fn();
|
||||
if (bindings != null) _testBindings.addAll(bindings);
|
||||
},
|
||||
priority: 2
|
||||
);
|
||||
}
|
||||
|
||||
_it(gnsFn, name, fn) {
|
||||
if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn);
|
||||
gnsFn(name, () {
|
||||
_inIt = true;
|
||||
fn.execute(_injector);
|
||||
_inIt = false;
|
||||
if (_isCurrentTestAsync) return _injector.get(AsyncTestCompleter).future;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
it(name, fn) {
|
||||
gns.it(name, _enableReflection(_handleAsync(fn)));
|
||||
_it(gns.it, name, fn);
|
||||
}
|
||||
|
||||
iit(name, fn) {
|
||||
gns.iit(name, _enableReflection(_handleAsync(fn)));
|
||||
_it(gns.iit, name, fn);
|
||||
}
|
||||
|
||||
_enableReflection(fn) {
|
||||
return () {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
return fn();
|
||||
};
|
||||
}
|
||||
|
||||
_handleAsync(fn) {
|
||||
ClosureMirror cm = reflect(fn);
|
||||
MethodMirror mm = cm.function;
|
||||
|
||||
var completer = new Completer();
|
||||
|
||||
if (mm.parameters.length == 1) {
|
||||
return () {
|
||||
cm.apply([completer.complete]);
|
||||
return completer.future;
|
||||
};
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
// TODO(tbosch): remove when https://github.com/vsavkin/guinness/issues/41
|
||||
// is fixed
|
||||
class FixedSamePropsMatcher extends Matcher {
|
||||
final Object _expected;
|
||||
|
||||
const FixedSamePropsMatcher(this._expected);
|
||||
|
||||
bool matches(actual, Map matchState) {
|
||||
return compare(toData(_expected), toData(actual));
|
||||
}
|
||||
|
||||
Description describeMismatch(item, Description mismatchDescription,
|
||||
Map matchState, bool verbose) =>
|
||||
mismatchDescription.add('is equal to ${toData(item)}. Expected: ${toData(_expected)}');
|
||||
|
||||
Description describe(Description description) =>
|
||||
description.add('has different properties');
|
||||
|
||||
toData(obj) => new _FixedObjToData().call(obj);
|
||||
compare(d1, d2) => new DeepCollectionEquality().equals(d1, d2);
|
||||
}
|
||||
|
||||
// TODO(tbosch): remove when https://github.com/vsavkin/guinness/issues/41
|
||||
// is fixed
|
||||
class _FixedObjToData {
|
||||
final visitedObjects = new Set();
|
||||
|
||||
call(obj) {
|
||||
if (visitedObjects.contains(obj)) return null;
|
||||
visitedObjects.add(obj);
|
||||
|
||||
if (obj is num || obj is String || obj is bool) return obj;
|
||||
if (obj is Iterable) return obj.map(call).toList();
|
||||
if (obj is Map) return mapToData(obj);
|
||||
return toDataUsingReflection(obj);
|
||||
}
|
||||
|
||||
mapToData(obj) {
|
||||
var res = {};
|
||||
obj.forEach((k,v) {
|
||||
res[call(k)] = call(v);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
toDataUsingReflection(obj) {
|
||||
final clazz = reflectClass(obj.runtimeType);
|
||||
final instance = reflect(obj);
|
||||
|
||||
return clazz.declarations.values.fold({}, (map, decl) {
|
||||
if (decl is VariableMirror && !decl.isPrivate && !decl.isStatic) {
|
||||
final field = instance.getField(decl.simpleName);
|
||||
final name = MirrorSystem.getName(decl.simpleName);
|
||||
map[name] = call(field.reflectee);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
}
|
||||
xit(name, fn) {
|
||||
_it(gns.xit, name, fn);
|
||||
}
|
||||
|
||||
String elementText(n) {
|
||||
|
@ -1,23 +1,166 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
|
||||
|
||||
export {inject} from './test_injector';
|
||||
|
||||
export {proxy} from 'rtts_assert/rtts_assert';
|
||||
export var describe = window.describe;
|
||||
export var xdescribe = window.xdescribe;
|
||||
export var ddescribe = window.ddescribe;
|
||||
export var it = window.it;
|
||||
export var xit = window.xit;
|
||||
export var iit = window.iit;
|
||||
export var beforeEach = window.beforeEach;
|
||||
export var afterEach = window.afterEach;
|
||||
export var expect = window.expect;
|
||||
|
||||
var _global = typeof window === 'undefined' ? global : window;
|
||||
|
||||
export var afterEach = _global.afterEach;
|
||||
export var expect = _global.expect;
|
||||
|
||||
export var IS_DARTIUM = false;
|
||||
export var IS_NODEJS = typeof window === 'undefined';
|
||||
|
||||
export class AsyncTestCompleter {
|
||||
_done: Function;
|
||||
|
||||
constructor(done: Function) {
|
||||
this._done = done;
|
||||
}
|
||||
|
||||
done() {
|
||||
this._done();
|
||||
}
|
||||
}
|
||||
|
||||
var jsmBeforeEach = _global.beforeEach;
|
||||
var jsmDescribe = _global.describe;
|
||||
var jsmDDescribe = _global.ddescribe;
|
||||
var jsmXDescribe = _global.xdescribe;
|
||||
var jsmIt = _global.it;
|
||||
var jsmIIt = _global.iit;
|
||||
var jsmXIt = _global.xit;
|
||||
|
||||
var runnerStack = [];
|
||||
var inIt = false;
|
||||
|
||||
var testBindings;
|
||||
|
||||
class BeforeEachRunner {
|
||||
constructor(parent: BeforeEachRunner) {
|
||||
this._fns = [];
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
beforeEach(fn: FunctionWithParamTokens) {
|
||||
this._fns.push(fn);
|
||||
}
|
||||
|
||||
run(injector) {
|
||||
if (this._parent) this._parent.run();
|
||||
this._fns.forEach((fn) => fn.execute(injector));
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the test bindings before each test
|
||||
jsmBeforeEach(() => { testBindings = []; });
|
||||
|
||||
function _describe(jsmFn, ...args) {
|
||||
var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1];
|
||||
var runner = new BeforeEachRunner(parentRunner);
|
||||
runnerStack.push(runner);
|
||||
var suite = jsmFn(...args);
|
||||
runnerStack.pop();
|
||||
return suite;
|
||||
}
|
||||
|
||||
export function describe(...args) {
|
||||
return _describe(jsmDescribe, ...args);
|
||||
}
|
||||
|
||||
export function ddescribe(...args) {
|
||||
return _describe(jsmDDescribe, ...args);
|
||||
}
|
||||
|
||||
export function xdescribe(...args) {
|
||||
return _describe(jsmXDescribe, ...args);
|
||||
}
|
||||
|
||||
export function beforeEach(fn) {
|
||||
if (runnerStack.length > 0) {
|
||||
// Inside a describe block, beforeEach() uses a BeforeEachRunner
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
runner.beforeEach(fn);
|
||||
} else {
|
||||
// Top level beforeEach() are delegated to jasmine
|
||||
jsmBeforeEach(fn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding default bindings defined in test_injector.js.
|
||||
*
|
||||
* The given function must return a list of DI bindings.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* beforeEachBindings(() => [
|
||||
* bind(Compiler).toClass(MockCompiler),
|
||||
* bind(SomeToken).toValue(myValue),
|
||||
* ]);
|
||||
*/
|
||||
export function beforeEachBindings(fn) {
|
||||
jsmBeforeEach(() => {
|
||||
var bindings = fn();
|
||||
if (!bindings) return;
|
||||
testBindings = [...testBindings, ...bindings];
|
||||
});
|
||||
}
|
||||
|
||||
function _it(jsmFn, name, fn) {
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
|
||||
jsmFn(name, function(done) {
|
||||
var async = false;
|
||||
|
||||
var completerBinding = bind(AsyncTestCompleter).toFactory(() => {
|
||||
// Mark the test as async when an AsyncTestCompleter is injected in an it()
|
||||
if (!inIt) throw new Error('AsyncTestCompleter can only be injected in an "it()"');
|
||||
async = true;
|
||||
return new AsyncTestCompleter(done);
|
||||
});
|
||||
|
||||
var injector = createTestInjector([...testBindings, completerBinding]);
|
||||
|
||||
runner.run(injector);
|
||||
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
inIt = true;
|
||||
fn.execute(injector);
|
||||
inIt = false;
|
||||
|
||||
if (!async) done();
|
||||
});
|
||||
}
|
||||
|
||||
export function it(name, fn) {
|
||||
return _it(jsmIt, name, fn);
|
||||
}
|
||||
|
||||
export function xit(name, fn) {
|
||||
return _it(jsmXIt, name, fn);
|
||||
}
|
||||
|
||||
export function iit(name, fn) {
|
||||
return _it(jsmIIt, name, fn);
|
||||
}
|
||||
|
||||
// To make testing consistent between dart and js
|
||||
window.print = function(msg) {
|
||||
if (window.dump) {
|
||||
window.dump(msg);
|
||||
_global.print = function(msg) {
|
||||
if (_global.dump) {
|
||||
_global.dump(msg);
|
||||
} else {
|
||||
window.console.log(msg);
|
||||
_global.console.log(msg);
|
||||
}
|
||||
};
|
||||
|
||||
@ -25,7 +168,7 @@ window.print = function(msg) {
|
||||
// gives us bad error messages in tests.
|
||||
// The only way to do this in Jasmine is to monkey patch a method
|
||||
// to the object :-(
|
||||
window.Map.prototype.jasmineToString = function() {
|
||||
_global.Map.prototype.jasmineToString = function() {
|
||||
var m = this;
|
||||
if (!m) {
|
||||
return ''+m;
|
||||
@ -37,7 +180,7 @@ window.Map.prototype.jasmineToString = function() {
|
||||
return `{ ${res.join(',')} }`;
|
||||
}
|
||||
|
||||
window.beforeEach(function() {
|
||||
_global.beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
// Custom handler for Map as Jasmine does not support it yet
|
||||
toEqual: function(util, customEqualityTesters) {
|
||||
@ -148,17 +291,25 @@ export class SpyObject {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function elementText(n) {
|
||||
var hasNodes = (n) => {var children = DOM.childNodes(n); return children && children.length > 0;}
|
||||
if (!IS_NODEJS) {
|
||||
if (n instanceof Comment) return '';
|
||||
|
||||
if (n instanceof Comment) return '';
|
||||
if (n instanceof Array) return n.map((nn) => elementText(nn)).join("");
|
||||
if (n instanceof Element && DOM.tagName(n) == 'CONTENT')
|
||||
return elementText(Array.prototype.slice.apply(n.getDistributedNodes()));
|
||||
if (DOM.hasShadowRoot(n)) return elementText(DOM.childNodesAsList(n.shadowRoot));
|
||||
if (hasNodes(n)) return elementText(DOM.childNodesAsList(n));
|
||||
|
||||
if (n instanceof Array) return n.map((nn) => elementText(nn)).join("");
|
||||
if (n instanceof Element && DOM.tagName(n) == 'CONTENT')
|
||||
return elementText(Array.prototype.slice.apply(n.getDistributedNodes()));
|
||||
if (DOM.hasShadowRoot(n)) return elementText(DOM.childNodesAsList(n.shadowRoot));
|
||||
if (hasNodes(n)) return elementText(DOM.childNodesAsList(n));
|
||||
|
||||
return n.textContent;
|
||||
return n.textContent;
|
||||
} else {
|
||||
if (n instanceof Array) {
|
||||
return n.map((nn) => elementText(nn)).join("");
|
||||
} else if (hasNodes(n)) {
|
||||
return elementText(DOM.childNodesAsList(n));
|
||||
} else {
|
||||
return DOM.getText(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
modules/angular2/src/transform/bind_generator/generator.dart
Normal file
53
modules/angular2/src/transform/bind_generator/generator.dart
Normal file
@ -0,0 +1,53 @@
|
||||
library angular2.src.transform.bind_generator.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/parser.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'visitor.dart';
|
||||
|
||||
Future<String> createNgSetters(AssetReader reader, AssetId entryPoint) async {
|
||||
var parser = new Parser(reader);
|
||||
NgDeps ngDeps = await parser.parse(entryPoint);
|
||||
|
||||
String code = ngDeps.code;
|
||||
var setters = _generateSetters(_createBindMap(ngDeps));
|
||||
|
||||
if (setters.length == 0) return code;
|
||||
var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end;
|
||||
return '${code.substring(0, codeInjectIdx)}'
|
||||
'..registerSetters({${setters.join(', ')}})'
|
||||
'${code.substring(codeInjectIdx)}';
|
||||
}
|
||||
|
||||
/// Consumes the map generated by [_createBindMap] to codegen setters.
|
||||
List<String> _generateSetters(Map<String, String> bindMap) {
|
||||
var setters = [];
|
||||
// TODO(kegluneq): Include types for receivers. See #886.
|
||||
bindMap.forEach((prop, type) {
|
||||
setters.add(''''$prop': (o, String v) => o.$prop = v''');
|
||||
});
|
||||
return setters;
|
||||
}
|
||||
|
||||
/// Collapses all `bindProperties` in [ngDeps] into a map where the keys are
|
||||
/// the bind properties and the values are either the one and only type
|
||||
/// binding to that property or the empty string.
|
||||
Map<String, String> _createBindMap(NgDeps ngDeps) {
|
||||
var visitor = new ExtractSettersVisitor();
|
||||
var bindMap = {};
|
||||
ngDeps.registeredTypes.forEach((RegisteredType t) {
|
||||
visitor.bindMappings.clear();
|
||||
t.annotations.accept(visitor);
|
||||
visitor.bindMappings.forEach((String prop, _) {
|
||||
if (bindMap.containsKey(prop)) {
|
||||
bindMap[prop] = '';
|
||||
} else {
|
||||
bindMap[prop] = '${t.typeName}';
|
||||
}
|
||||
});
|
||||
});
|
||||
return bindMap;
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
library angular2.src.transform.bind_generator.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'generator.dart';
|
||||
|
||||
/// Transformer responsible for reading .ng_deps.dart files and generating
|
||||
/// setters from the "annotations" information in the generated
|
||||
/// `registerType` calls.
|
||||
///
|
||||
/// These setters are registered in the same `setupReflection` function with
|
||||
/// the `registerType` calls.
|
||||
class BindGenerator extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
BindGenerator(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
log.init(transform);
|
||||
|
||||
try {
|
||||
var id = transform.primaryInput.id;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var transformedCode = await createNgSetters(reader, id);
|
||||
transform.addOutput(new Asset.fromString(
|
||||
id, formatter.format(transformedCode, uri: id.path)));
|
||||
} catch (ex, stackTrace) {
|
||||
log.logger.error('Creating ng setters failed.\n'
|
||||
'Exception: $ex\n'
|
||||
'Stack Trace: $stackTrace');
|
||||
}
|
||||
}
|
||||
}
|
39
modules/angular2/src/transform/bind_generator/visitor.dart
Normal file
39
modules/angular2/src/transform/bind_generator/visitor.dart
Normal file
@ -0,0 +1,39 @@
|
||||
library angular2.src.transform.bind_generator.visitor;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
|
||||
/// Visitor responsible for crawling the "annotations" value in a
|
||||
/// `registerType` call and pulling out the properties of any "bind"
|
||||
/// values found.
|
||||
class ExtractSettersVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||
final Map<String, String> bindMappings = {};
|
||||
|
||||
void _extractFromMapLiteral(MapLiteral map) {
|
||||
map.entries.forEach((entry) {
|
||||
// TODO(kegluneq): Remove this restriction
|
||||
if (entry.key is SimpleStringLiteral &&
|
||||
entry.value is SimpleStringLiteral) {
|
||||
bindMappings[stringLiteralToString(entry.key)] =
|
||||
stringLiteralToString(entry.value);
|
||||
} else {
|
||||
logger.error('`bind` currently only supports string literals '
|
||||
'(${entry})');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
if ('${node.name.label}' == 'bind') {
|
||||
// TODO(kegluneq): Remove this restriction.
|
||||
if (node.expression is MapLiteral) {
|
||||
_extractFromMapLiteral(node.expression);
|
||||
} else {
|
||||
logger.error('`bind` currently only supports map literals');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return super.visitNamedExpression(node);
|
||||
}
|
||||
}
|
28
modules/angular2/src/transform/common/asset_reader.dart
Normal file
28
modules/angular2/src/transform/common/asset_reader.dart
Normal file
@ -0,0 +1,28 @@
|
||||
library angular2.src.transform.common.asset_reader;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
/// A class that allows fetching code using [AssetId]s without all the
|
||||
/// additional baggage of a [Transform].
|
||||
abstract class AssetReader {
|
||||
Future<String> readAsString(AssetId id, {Encoding encoding});
|
||||
Future<bool> hasInput(AssetId id);
|
||||
|
||||
/// Creates an [AssetReader] using the `transform`, which should be a
|
||||
/// [Transform] or [AggregateTransform].
|
||||
factory AssetReader.fromTransform(dynamic transform) =>
|
||||
new _TransformAssetReader(transform);
|
||||
}
|
||||
|
||||
class _TransformAssetReader implements AssetReader {
|
||||
final dynamic t;
|
||||
_TransformAssetReader(this.t);
|
||||
|
||||
Future<String> readAsString(AssetId id, {Encoding encoding}) =>
|
||||
t.readInputAsString(id, encoding: encoding);
|
||||
|
||||
Future<bool> hasInput(AssetId id) => t.hasInput(id);
|
||||
}
|
22
modules/angular2/src/transform/common/formatter.dart
Normal file
22
modules/angular2/src/transform/common/formatter.dart
Normal file
@ -0,0 +1,22 @@
|
||||
library angular2.src.transform.common.formatter;
|
||||
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
import 'logging.dart';
|
||||
|
||||
DartFormatter _formatter = null;
|
||||
|
||||
void init(DartFormatter formatter) {
|
||||
if (_formatter != null) {
|
||||
logger.warning('Formatter is being overwritten.');
|
||||
}
|
||||
_formatter = formatter;
|
||||
}
|
||||
|
||||
DartFormatter get formatter {
|
||||
if (_formatter == null) {
|
||||
logger.info('Formatter never initialized, using default formatter.');
|
||||
_formatter = new DartFormatter();
|
||||
}
|
||||
return _formatter;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user