Merge branch 'master' into ts2dart

This commit is contained in:
Alex Eagle 2015-03-18 16:01:24 -07:00
commit e37f58a228
275 changed files with 8932 additions and 3085 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
packages/
.buildlog
node_modules
bower_components
.pub
.DS_STORE

View File

@ -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
View 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
View 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
View File

@ -1,125 +1,52 @@
Angular [![Build Status](https://travis-ci.org/angular/angular.svg?branch=master)](https://travis-ci.org/angular/angular)
Angular [![Build Status](https://travis-ci.org/angular/angular.svg?branch=master)](https://travis-ci.org/angular/angular) [![Join the chat at https://gitter.im/angular/angular](https://badges.gitter.im/Join%20Chat.svg)](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
View File

@ -0,0 +1,7 @@
{
"name": "angular2",
"version": "0.0.0",
"dependencies": {
"polymer": "dart-lang/polymer_js#0.8.0-preview"
}
}

View File

@ -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));
}
};
});
});

View File

@ -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' }

View File

@ -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

View File

@ -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];

View File

@ -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);

View File

@ -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

View File

@ -0,0 +1,3 @@
module.exports = function modules() {
return {};
};

View 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';
});

View 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);
}
};

View File

@ -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
);
});

View File

@ -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';

View File

@ -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';

View File

@ -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.

View File

@ -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.

View File

@ -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';

View File

@ -16,4 +16,4 @@ dependencies:
html5lib: '^0.12.0'
stack_trace: '^1.1.1'
dev_dependencies:
guinness: "^0.1.16"
guinness: "^0.1.17"

View File

@ -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})`);

View File

@ -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;
}

View File

@ -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];

View File

@ -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() {}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 {

View 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);
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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()

View File

@ -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) => {

View File

@ -1,5 +1,8 @@
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from 'angular2/change_detection';
/**
* @publicModule angular2/angular2
*/
export class BindingPropagationConfig {
_cd:ChangeDetector;

View File

@ -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;
}

View File

@ -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:

View File

@ -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>;

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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

View File

@ -1,3 +1,6 @@
/**
* @publicModule angular2/angular2
*/
export class OnChange {
onChange(changes) {
throw "OnChange.onChange is not implemented";

View File

@ -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"));

View File

@ -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) {}
}

View File

@ -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;
}

View File

@ -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}`);
}
}
});
}

View File

@ -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) {

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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;
}
}

View 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();
});
}

View File

@ -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, '');
}

View 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);
});
}
}

View 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);
}
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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(

View File

@ -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();

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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() {
}
}

View File

@ -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';

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View 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;
}
}

View File

@ -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';
}
}

View 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"];

View File

@ -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();
}
}

View File

@ -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();
}
}

View 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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
View 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);
}
}

View 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);
}
}

View File

@ -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) {

View File

@ -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);
}
}
}

View 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;
}

View File

@ -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');
}
}
}

View 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);
}
}

View 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);
}

View 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