Compare commits

...

68 Commits

Author SHA1 Message Date
50f8892c6b chore(release): bump version
Somehow the version bump from alpha.16 was not submitted to master…
2015-03-27 16:21:41 -07:00
3bfbfa8ae0 chore(release): bump version 2015-03-27 16:18:36 -07:00
8598c87ef4 docs(bench press): add initial docs 2015-03-27 16:16:35 -07:00
33bfc4c24a feat(bench press): replace microIterations with microMetrics 2015-03-27 16:16:35 -07:00
3afb744e77 chore(ci): reorganize e2e/perf test running
Now, running protractor configs by default only runs e2e tests. If
the --benchmark flag is added, it runs only the perf tests, and always
restarts the browser in between tests. If the --dryrun test is added,
the perf tests are run only once.

This should make it easier to run perf tests versus example e2e tests,
and help stabilize the travis build because perf tests always
run with a clean browser.
2015-03-27 13:29:21 -07:00
e92918bbfe feat(change_detector): split light dom and shadow dom children 2015-03-27 13:26:37 -07:00
723e8fde93 feat(change_detection): added a directive lifecycle hook that is called after children are checked 2015-03-27 13:26:36 -07:00
507f7ea70a chore(package.json): upgrade zone.js to v0.4.2
Closes #1142
2015-03-27 16:24:07 -04:00
6b985d56a5 cleanup(forms): added missing type annotations
Closes #1054
2015-03-27 11:30:39 -07:00
c8385ad998 refactor(cd): remove dead code 2015-03-27 16:59:23 +01:00
9d21a6f40d chore(package.json): upgrade traceur to v0.0.87
Fix in source-map test to follow through the sourcemap chain.
2015-03-26 18:37:03 -07:00
d304f41197 docs(core): improved docs on directive lifecycle 2015-03-26 18:18:25 -07:00
8d85b839b6 feat(change_detection): pass binding propagation config to pipe registry 2015-03-26 16:57:04 -07:00
dd235f38a3 fix(build): try to eliminate build flakes by running dartstyle:format sequentially 2015-03-26 16:23:09 -07:00
5306b6dd0c fix(change_detection): expose values when detecting changes in key-value pairs
Fixes #1118

Closes #1123
2015-03-26 21:18:14 +01:00
b09624024b example(forms): added a example of using forms 2015-03-26 11:36:14 -07:00
edc3709451 fix(ElementBinderBuilder): properly bind CSS classes with "-" in their names
Fixes #1057

Closes #1059
2015-03-26 19:25:31 +01:00
e706f3477b Remove invalid super() call
Unless I'm missing something?
2015-03-26 11:10:39 -07:00
6298cb3999 chore(ci): upgrade to new version of protractor and selenium-webdriver
Protractor 2.0.0
selenium-webdriver 2.45.1
2015-03-26 10:00:46 -07:00
878fce6482 fix(ts): ts doesn't like ";;" 2015-03-26 17:32:48 +01:00
b02bd65871 feat(forms): made forms works with single controls 2015-03-26 07:48:17 -07:00
ee36aaf163 fix(tests): fixed a broken test 2015-03-26 07:46:26 -07:00
ff84506bd5 feat(forms): added support for arrays of controls 2015-03-26 07:43:25 -07:00
0ae33b7e3c refactor(compiler): factorize common util code dash <-> camel
Closes #1114
Fixes #1097
2015-03-26 15:22:35 +01:00
b1dc6239ef feat(core): @Attribute annotation
Closes #1091
Fixes #622
2015-03-26 10:51:44 +01:00
3ce0f1146f chore(dgeni): set log level to 'warning' 2015-03-26 09:31:36 +01:00
3ec837bfdb test(di): Add a test for sync binding + resolved async dependency 2015-03-26 08:38:29 +01:00
18ff2be9bb feat(ts2dart): include srcFolderInsertion in ts2dart step. 2015-03-25 21:31:40 -07:00
c0d296334c feature(ts2dart): ts2dart runs on all .js files.
Update the experimental ts2dart task to also read the .es6 files, which are the next step.
2015-03-25 17:14:06 -07:00
9a0a2e319c chore(ts2dart) remove extra semi
This breaks our ts2dart transpilation.
2015-03-25 17:06:47 -07:00
a0d86ac2bb chore(ts2dart): ts2dart doesn't support mixed typed/untyped var decl lists. 2015-03-25 16:41:33 -07:00
99045b2f6a refactor: update Dart package dependencies 2015-03-25 15:54:12 -07:00
c34ca36778 chore: build the stable branch of Dart
Now that Dart 1.9 is stable
2015-03-25 21:17:07 +01:00
58dd75a1c8 feat(compiler): Add support for setting attributes to Component host element
Fixes #1008
Fixes #1009
Closes #1052
2015-03-25 17:32:07 +01:00
f995b07876 docs: annotations edits 2015-03-24 23:28:24 +00:00
101a4aa3cf feat(PrivateComponentLoader): Explicit error message when loading a non-component
fixes #1062
2015-03-24 22:11:41 +01:00
65d759316b fix(PrivateComponentLoader): add the loader to the app injector
fixes #1063
2015-03-24 22:10:26 +01:00
19c1773133 feat(forms): added an observable of value changes to Control 2015-03-24 13:45:47 -07:00
9b3b3d325f feat(facade): added support for observables 2015-03-24 13:45:39 -07:00
43f4374944 feature(build): Include ts2dart transpile step in the Angular build.
This only transpiles one package to start with: di/
It ensures that package transpiles without errors, so no one can
introduce non-TypeScript syntax.
Next step is to widen the task inputs to cover additional packages.

See design doc for the migration:
https://docs.google.com/document/d/14RJLhu6uuv7NchFkAb6PKzOOO0L7l3Z507eKWzkEUhQ/edit

A convenience task 'ts2dart' is added for developing ts2dart, and
it runs all of the angular code through the transpiler to collect errors.
2015-03-24 10:34:46 -07:00
81e6d13241 chore: bump up the version to 2.0.0-alpha.15 2015-03-24 07:50:39 -07:00
f8e7a37c0d fix(view): fixed view instantiation to use the component template's change detector when creating BindingPropagationConfig 2015-03-24 07:49:28 -07:00
c686e7ea30 chore(doc-gen): ignore non-jsdoc style comments
Now the visitor will find the last jsdoc style comment (e.g. `/** jsdoc comment */`)
before the current code item, ignoring any inline style comments (e.g. `// inline comment`)
in between.

Closes #1072
2015-03-24 11:25:58 +00:00
7e89af8190 chore(doc-gen): move @publicModule tag-def to base package
This prevents unwanted "unknown tag" warnings when generating the non-public docs.
2015-03-24 10:57:42 +00:00
539e8e2cce chore(doc-gen): add specific template for displaying variable exports
Closes #1071
2015-03-24 10:57:42 +00:00
aab084866c doc(test): add a comment on why tests are disabled 2015-03-24 09:52:41 +01:00
0e61a86763 docs: annotations 2015-03-24 00:42:58 +00:00
1c9938ed98 chore(packaging): bump version to alpha.14 2015-03-23 17:14:55 -07:00
47c1a0f381 feat(forms): added value accessor for input=text 2015-03-23 08:53:27 -07:00
514529b5d9 refactor(formed): changed forms to use event and property setters instead of NgElement 2015-03-23 08:52:54 -07:00
a12dc7d75a refactor(forms): wrapped all validators into the Validator class 2015-03-23 08:50:56 -07:00
41b53e71e1 feat(selector): support , for multiple targets
Fixes #867
Closes #1019
2015-03-23 10:06:33 +01:00
0fb9f3bd6c fix(ElementBinderBuilder): properly bind to web component properties
Fixes #776

Closes #1024
2015-03-22 14:14:36 +01:00
81f3f32217 refactor(DirectiveParser): remove checks for missing directives
Based on the discussion in #776 we can't reliably check if a given
element has a particular property at the compilation time. As such
the existing algorithm detecting "missing" directives can't be used.

We need to see if there is a different / better algorithm or maybe
those checks need to be moved later in the process (runtime). Leaving
integration tests in place (disabled) so we can come back to the
topic after unblocking the situation.

This commit effectivelly reverts 94e203b9df
2015-03-22 14:14:36 +01:00
b35f288794 refactor(dart/transform): Use package:guinness in tests
`guinness` is a Dart port of Jasmine. Since the rest of Angular 2 uses
Jasmine, use it for the transformer too.

Closes #8

Closes #1037

Closes #1000
2015-03-21 15:18:15 -07:00
4e82cc0861 refactor(dart/transform): Test directive_linker as a unit
Formerly, it was tested only as a piece of the transformer pipeline. Add
its own directory and test the linker on its own.
2015-03-21 15:18:15 -07:00
c735644c57 refactor(dart/transform): Minor logging changes
Enable easier testing by providing a null log implementation and a way
to use it.
2015-03-21 15:18:15 -07:00
5d479fa0ae refactor(dart/transform): Remove ngData
Now that we have `Parser`, `ngData` is redundant & unnecessary.
2015-03-21 15:18:15 -07:00
8baedca972 style(dart/transform): Remove src from library directives
Conform to Angular 2 style by removing `src` from library directives.
Completed with:
```
find -name "*.dart" | xargs sed -i -e 's!library\(.*\)src\.\(.*\)!library \1\2!'
```

Closes #1005

Closes #1038
2015-03-21 14:55:11 -07:00
02aa8e7945 feat(compiler): support bindings for any attribute
Closes #1029
2015-03-21 14:55:11 -07:00
ee523efcb4 feat(ShadowCss): Support the new deep combinator syntax >>>
fixes #990

ref http://dev.w3.org/csswg/css-scoping-1/#deep-combinator

Closes #1028
2015-03-21 14:55:11 -07:00
eef5f7e06d README - don't forget to build app before start
Don't forget to build app before start HTTP server

Closes #1014
2015-03-21 14:55:11 -07:00
83402930f2 chore(install+test): single cmd to full install/test & test JS w/o Dart
* `npm install` now does a full install; auxiliary installation steps
have been integrated into the `postinstall` script.
* Updated developer docs `DEVELOPER.md` accordingly; also added
instructions to dev docs for performing full tests (via `npm test`) --
same as those run on Travis.
* Reorg in tests so that JS tests can run without a Dart env.

Partly fixes #945 **under the assumption that when running JS tests
locally, `ChromeCanary` is the desired browser to use**. Note that CI
tests (Travis) still uses `DartiumWithWebPlatform` across the board
(Maybe because ChromeCanary isn't being installed?)

Fixes #1012.

Closes #1010
2015-03-21 14:55:11 -07:00
bd48c927d0 fix(ViewContainer) removeChild called with null parent
In view_container.js, templateElement.parentNode can be null
when two template tags are nested in one another.
Accessing the parent node through view.nodes[0].parentNode fixes
the problem.

closes #997

Closes #999
2015-03-21 14:55:10 -07:00
b61b8d60b7 refactor(forEach): change to for-of with iterable
rename: foreach -> for
rename: array -> iterable
update: DartParseTreeWriter
update: naive_infinite_scroll
update: todo
fix: tests in foreach_spec

Closes #919
2015-03-21 14:19:21 -07:00
f1fca5abb6 (docs) decorator events property
As from what i understand shouldn't the event property rather be events: https://github.com/angular/angular/blob/master/modules/angular2/src/core/annotations/annotations.js#L161

Closes #1018
2015-03-21 18:26:13 +00:00
045ce3c77a Fix which dependency is injected w/ current elem.
Docs for the "Injecting a directive from the current element" indicate that having a dependency of `dependency: Dependency` should cause the current element's dependency to be injected, but then uses the ID value from the parent element in the example.

Closes #1032
2015-03-21 18:14:43 +00:00
f822066e2a docs: annotations 2015-03-21 18:05:12 +00:00
200 changed files with 3952 additions and 1575 deletions

View File

@ -10,8 +10,7 @@ env:
- ARCH=linux-x64
matrix:
- MODE=js DART_CHANNEL=dev
# Dissabled until Dart v1.9 hits stable
# - MODE=dart DART_CHANNEL=stable
- MODE=dart DART_CHANNEL=stable
- MODE=dart DART_CHANNEL=dev
before_install:

View File

@ -95,23 +95,22 @@ 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 get
```
**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:
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by either:
*Option 1*: 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.
Since global installs can become stale, and required versions can vary by project, we avoid their
use in these instructions.
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Build commands
@ -133,22 +132,35 @@ $(npm bin)/gulp clean
## Running Tests Locally
### Basic tests
### Full test suite
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; runs in **watch mode**
3. `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
* `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests as
those run on Travis.
You can selectively run either the JS or Dart versions as follows:
* `$(npm bin)/gulp test.all.js`
* `$(npm bin)/gulp test.all.dart`
### Unit tests
You can run just the unit tests as follows:
* `$(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).
* `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**.
* `$(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.cjs/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**: 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
@ -232,4 +244,3 @@ 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.

View File

@ -36,6 +36,7 @@ comments in the source `modules/examples/src/hello_world/index.js`.
You can build this example as either JS or Dart app:
* JS:
* `$(npm bin)/gulp build.js.dev`, and
* `$(npm bin)/gulp serve.js.dev`, and
* open `localhost:8000/examples/src/hello_world/` in Chrome.
* Dart:

View File

@ -45,7 +45,7 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
// Configure the log service
.config(function(log) {
log.level = 'info';
log.level = 'warning';
})
@ -89,6 +89,10 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
];
})
// Add in a custom tag that we use when generating public docs
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
})
// Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) {
@ -144,4 +148,4 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage])
pathTemplate: '${id}',
outputPathTemplate: GUIDES_PATH + '/${id}.html'
});
});
});

View File

@ -30,11 +30,18 @@ module.exports = function AttachCommentTreeVisitor(ParseTreeVisitor, log) {
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;
var commentText = this.currentComment.range.toString();
// Only store the comment if it is JSDOC style (e.g. /** some comment */)
if (/^\/\*\*([\w\W]*)\*\/$/.test(commentText)) {
log.info('comment: ' + this.currentComment.range.start.line + ' - ' +
this.currentComment.range.end.line + ' : ' +
commentText);
tree.commentBefore = this.currentComment;
this.currentComment.treeAfter = tree;
}
this.index++;
this.currentComment = this.comments[this.index];
}

View File

@ -34,13 +34,18 @@ module.exports = function atParser(AttachCommentTreeVisitor, SourceFile, Traceur
var sourceFile = new SourceFile(moduleName, fileInfo.content);
var comments = [];
var moduleTree;
var parser = new TraceurParser(sourceFile);
var errorReporter = {
reportError: function(position, message) {
}
};
traceurOptions.setFromObject(service.traceurOptions);
var parser = new TraceurParser(sourceFile, errorReporter, traceurOptions);
// Configure the parser
parser.handleComment = function(range) {
comments.push({ range: range });
};
traceurOptions.setFromObject(service.traceurOptions);
try {
// Parse the file as a module, attaching the comments

View File

@ -1,3 +1,4 @@
module.exports = function traceurOptions() {
return System.get(System.map.traceur + '/src/Options.js').options;
var Options = System.get(System.map.traceur + "/src/Options.js").Options;
return new Options();
};

View File

@ -0,0 +1,8 @@
{% extends 'layout/base.template.html' %}
{% block body %}
<h1>{$ doc.name $} <span class="type">variable</span></h1>
<p class="module">exported from <a href="/{$ doc.moduleDoc.path $}">{$ doc.moduleDoc.id $}</a></p>
<p>{$ doc.description | marked $}</p>
{% endblock %}

View File

@ -6,10 +6,6 @@ module.exports = new Package('angular-public', [basePackage])
.processor(require('./processors/filterPublicDocs'))
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push({ name: 'publicModule' });
})
.config(function(processClassDocs, filterPublicDocs, EXPORT_DOC_TYPES) {
processClassDocs.ignorePrivateMembers = true;
filterPublicDocs.docTypes = EXPORT_DOC_TYPES;

View File

@ -1,5 +1,6 @@
var gulp = require('gulp');
var gulpPlugins = require('gulp-load-plugins')();
var shell = require('gulp-shell');
var runSequence = require('run-sequence');
var madge = require('madge');
var merge = require('merge');
@ -24,8 +25,10 @@ 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 ts2dart = require('gulp-ts2dart');
var util = require('./tools/build/util');
// Note: when DART_SDK is not found, all gulp tasks ending with `.dart` will be skipped.
var DART_SDK = require('./tools/build/dartdetect')(gulp);
// -----------------------
// configuration
@ -47,6 +50,8 @@ var _HTML_DEFAULT_SCRIPTS_JS = [
{src: 'node_modules/zone.js/long-stack-trace-zone.js', mimeType: 'text/javascript', copy: true},
{src: 'node_modules/systemjs/dist/system.src.js', mimeType: 'text/javascript', copy: true},
{src: 'node_modules/systemjs/lib/extension-register.js', mimeType: 'text/javascript', copy: true},
{src: 'node_modules/systemjs/lib/extension-cjs.js', mimeType: 'text/javascript', copy: true},
{src: 'node_modules/rx/dist/rx.all.js', mimeType: 'text/javascript', copy: true},
{src: 'tools/build/snippets/runtime_paths.js', mimeType: 'text/javascript', copy: true},
{
inline: 'System.import(\'$MODULENAME$\').then(function(m) { m.main(); }, console.error.bind(console))',
@ -345,6 +350,28 @@ gulp.task('build/transpile.dart', transpile(gulp, gulpPlugins, {
srcFolderInsertion: CONFIG.srcFolderInsertion.dart
}));
gulp.task('build/transpile.dart.ts2dart', function() {
return gulp.src(CONFIG.transpile.src.dart)
.pipe(ts2dart.transpile())
.pipe(gulp.dest('dist/dart.ts2dart'))
});
gulp.task('build/format.dart.ts2dart', rundartpackage(gulp, gulpPlugins, {
pub: DART_SDK.PUB,
packageName: CONFIG.formatDart.packageName,
args: ['dart_style:format', '-w', 'dist/dart.ts2dart']
}));
// Temporary tasks for development on ts2dart. Will likely fail.
gulp.task('build/transpile.dart.ts2dart.all', function() {
return gulp.src(CONFIG.transpile.src.js)
.pipe(ts2dart.transpile())
.pipe(util.insertSrcFolder(gulpPlugins, CONFIG.srcFolderInsertion.dart))
.pipe(gulp.dest('dist/dart.ts2dart'));
});
gulp.task('ts2dart', function(done) {
runSequence('build/transpile.dart.ts2dart.all', 'build/format.dart.ts2dart', done);
});
// ------------
// html
@ -431,7 +458,11 @@ gulp.task('build/multicopy.dart', copy.multicopy(gulp, gulpPlugins, {
// ------------
// pubspec
gulp.task('build/pubspec.dart', pubget(gulp, gulpPlugins, {
// Run a top-level `pub get` for this project.
gulp.task('pubget.dart', pubget.dir(gulp, gulpPlugins, { dir: '.', command: DART_SDK.PUB }));
// Run `pub get` over CONFIG.dest.dart
gulp.task('build/pubspec.dart', pubget.subDir(gulp, gulpPlugins, {
dir: CONFIG.dest.dart,
command: DART_SDK.PUB
}));
@ -588,6 +619,22 @@ createDocsTasks(true);
createDocsTasks(false);
// ------------------
// CI tests suites
gulp.task('test.js', function(done) {
runSequence('test.transpiler.unittest', 'docs/test', 'test.unit.js/ci',
'test.unit.cjs/ci', done);
});
gulp.task('test.dart', function(done) {
runSequence('test.transpiler.unittest', 'docs/test', 'test.unit.dart/ci', done);
});
// Reuse the Travis scripts
// TODO: rename test_*.sh to test_all_*.sh
gulp.task('test.all.js', shell.task(['./scripts/ci/test_js.sh']))
gulp.task('test.all.dart', shell.task(['./scripts/ci/test_dart.sh']))
// karma tests
// These tests run in the browser and are allowed to access
// HTML DOM APIs.
@ -627,14 +674,14 @@ gulp.task('test.unit.cjs', ['build.js.cjs'], function () {
});
//Watcher to run tests when dist/js/cjs/angular2 is updated by the first watcher (after clearing the node cache)
gulp.watch(CONFIG.dest.js.cjs + '/angular2/**/*.js', function(event) {
for (var id in require.cache) {
for (var id in require.cache) {
if (id.replace(/\\/g, "/").indexOf(CONFIG.dest.js.cjs) > -1) {
delete require.cache[id];
delete require.cache[id];
}
}
runSequence('test.unit.cjs/ci', function() {});
});
});
// ------------------
@ -668,8 +715,11 @@ gulp.task('tests/transform.dart', function() {
// Builds all Dart packages, but does not compile them
gulp.task('build/packages.dart', function(done) {
runSequence(
['build/transpile.dart', 'build/html.dart', 'build/copy.dart', 'build/multicopy.dart'],
['build/transpile.dart.ts2dart', 'build/transpile.dart', 'build/html.dart', 'build/copy.dart', 'build/multicopy.dart'],
'tests/transform.dart',
// the two format steps don't need to be sequential, but we have seen flakiness in
// dartstyle:format with connecting to localhost.
'build/format.dart.ts2dart',
'build/format.dart',
'build/pubspec.dart',
done

View File

@ -18,6 +18,8 @@ module.exports = function(config) {
// Including systemjs because it defines `__eval`, which produces correct stack traces.
'node_modules/systemjs/dist/system.src.js',
'node_modules/systemjs/lib/extension-register.js',
'node_modules/systemjs/lib/extension-cjs.js',
'node_modules/rx/dist/rx.all.js',
'node_modules/zone.js/zone.js',
'node_modules/zone.js/long-stack-trace-zone.js',

View File

@ -11,6 +11,8 @@ export {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector,
from './src/change_detection/proto_change_detector';
export {DynamicChangeDetector}
from './src/change_detection/dynamic_change_detector';
export {BindingPropagationConfig}
from './src/change_detection/binding_propagation_config';
export * from './src/change_detection/pipes/pipe_registry';
export {uninitialized} from './src/change_detection/change_detection_util';
export * from './src/change_detection/pipes/pipe';
@ -18,7 +20,7 @@ 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';
import {ArrayChangesFactory} from './src/change_detection/pipes/array_changes';
import {IterableChangesFactory} from './src/change_detection/pipes/iterable_changes';
import {KeyValueChangesFactory} from './src/change_detection/pipes/keyvalue_changes';
import {NullPipeFactory} from './src/change_detection/pipes/null_pipe';
@ -31,7 +33,7 @@ export class ChangeDetection {
export var defaultPipes = {
"iterableDiff" : [
new ArrayChangesFactory(),
new IterableChangesFactory(),
new NullPipeFactory()
],
"keyValDiff" : [

View File

@ -2,12 +2,12 @@ export * from './src/core/annotations/visibility';
export * from './src/core/compiler/interfaces';
export * from './src/core/annotations/template';
export * from './src/core/application';
export * from './src/core/annotations/di';
export * from './src/core/compiler/compiler';
export * from './src/core/compiler/template_loader';
export * from './src/core/compiler/view';
export * from './src/core/compiler/view_container';
export * from './src/core/compiler/binding_propagation_config';
export * from './src/core/dom/element';

View File

@ -1,4 +1,4 @@
export * from './src/directives/foreach';
export * from './src/directives/for';
export * from './src/directives/if';
export * from './src/directives/non_bindable';
export * from './src/directives/switch';

View File

@ -187,7 +187,7 @@ Example:
<pre>
```
<ul>
<li template="foreach: #item in items">
<li template="for: #item of items">
{{item}}
</li>
</ul>
@ -201,8 +201,8 @@ Example:
<pre>
```
<ul>
<template def-foreach:"item"
bind-foreach-in="items">
<template def-for:"item"
bind-for-in="items">
<li>
{{item}}
</li>
@ -221,8 +221,8 @@ Example:
<pre>
```
<template #foreach="item"
[foreach-in]="items">
<template #for="item"
[for-in]="items">
_some_content_to_repeat_
</template>
```
@ -234,8 +234,8 @@ Example:
Example:
<pre>
```
<template def-foreach="item"
bind-foreach-in="items">
<template def-for="item"
bind-for-in="items">
_some_content_to_repeat_
</template>
```
@ -408,22 +408,22 @@ NOTE: Only Viewport directives can be placed on the template element. (Decorator
### Template Microsyntax
Often times it is necessary to encode a lot of different bindings into a template to control how the instantiation
of the templates occurs. One such example is `foreach`.
of the templates occurs. One such example is `for`.
```
<form #foo=form>
</form>
<ul>
<template foreach #person [in]="people" #i="index">
<template for #person [in]="people" #i="index">
<li>{{i}}. {{person}}<li>
</template>
</ul>
```
Where:
* `foreach` triggers the foreach directive.
* `[in]="people"` binds an iterable object to the `foreach` controller.
* `#person` exports the implicit `foreach` item.
* `for` triggers the for directive.
* `[in]="people"` binds an iterable object to the `for` controller.
* `#person` exports the implicit `for` item.
* `#i=index` exports item index as `i`.
The above example is explicit but quite wordy. For this reason in most situations a short hand version of the
@ -431,7 +431,7 @@ syntax is preferable.
```
<ul>
<li template="foreach; #person; in=people; #i=index;">{{i}}. {{person}}<li>
<li template="for; #person; in=people; #i=index;">{{i}}. {{person}}<li>
</ul>
```
@ -441,19 +441,28 @@ which allows us to further shorten the text.
```
<ul>
<li template="foreach #person in people #i=index">{{i}}. {{person}}<li>
<li template="for #person of people #i=index">{{i}}. {{person}}<li>
</ul>
```
We can also optionally use `var` instead of `#` and add `:` to `foreach` which creates the following recommended
microsyntax for `foreach`.
We can also optionally use `var` instead of `#` and add `:` to `for` which creates the following recommended
microsyntax for `for`.
```
<ul>
<li template="foreach: var person in people; var i=index">{{i}}. {{person}}<li>
<li template="for: var person of people; var i=index">{{i}}. {{person}}<li>
</ul>
```
Finally, we can move the `for` keyword to the left hand side and prefix it with `*` as so:
```
<ul>
<li *for="var person of people; var i=index">{{i}}. {{person}}<li>
</ul>
```
The format is intentionally defined freely, so that developers of directives can build an expressive microsyntax for
their directives. The following code describes a more formal definition.

View File

@ -70,7 +70,7 @@ Here is a trivial example of a tooltip decorator. The directive will log a toolt
bind: { // List which properties need to be bound
text: 'tooltip' // - DOM element tooltip property should be
}, // mapped to the directive text property.
event: { // List which events need to be mapped.
events: { // List which events need to be mapped.
mouseover: 'show' // - Invoke the show() method every time
} // the mouseover event is fired.
})

View File

@ -9,6 +9,7 @@
"dependencies": {
"traceur": "<%= packageJson.dependencies.traceur %>",
"rtts_assert": "<%= packageJson.version %>",
"rx": "<%= packageJson.dependencies['rx'] %>",
"zone.js": "<%= packageJson.dependencies['zone.js'] %>"
},
"devDependencies": <%= JSON.stringify(packageJson.devDependencies) %>

View File

@ -9,11 +9,11 @@ homepage: <%= packageJson.homepage %>
environment:
sdk: '>=1.9.0-dev.8.0'
dependencies:
analyzer: '^0.22.4'
analyzer: '>=0.22.4 <0.25.0'
barback: '^0.15.2+2'
code_transformers: '^0.2.5'
dart_style: '^0.1.3'
html5lib: '^0.12.0'
html: '^0.12.0'
stack_trace: '^1.1.1'
dev_dependencies:
guinness: "^0.1.17"

View File

@ -1,25 +1,35 @@
import {isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {BindingPropagationConfig} from './binding_propagation_config';
import {ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
export class AbstractChangeDetector extends ChangeDetector {
children:List;
lightDomChildren:List;
shadowDomChildren:List;
parent:ChangeDetector;
mode:string;
bindingPropagationConfig:BindingPropagationConfig;
constructor() {
super();
this.children = [];
this.lightDomChildren = [];
this.shadowDomChildren = [];
this.bindingPropagationConfig = new BindingPropagationConfig(this);
this.mode = CHECK_ALWAYS;
}
addChild(cd:ChangeDetector) {
ListWrapper.push(this.children, cd);
ListWrapper.push(this.lightDomChildren, cd);
cd.parent = this;
}
removeChild(cd:ChangeDetector) {
ListWrapper.remove(this.children, cd);
ListWrapper.remove(this.lightDomChildren, cd);
}
addShadowDomChild(cd:ChangeDetector) {
ListWrapper.push(this.shadowDomChildren, cd);
cd.parent = this;
}
remove() {
@ -38,17 +48,30 @@ export class AbstractChangeDetector extends ChangeDetector {
if (this.mode === DETACHED || this.mode === CHECKED) return;
this.detectChangesInRecords(throwOnChange);
this._detectChangesInChildren(throwOnChange);
this._detectChangesInLightDomChildren(throwOnChange);
this.notifyOnAllChangesDone();
this._detectChangesInShadowDomChildren(throwOnChange);
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
}
detectChangesInRecords(throwOnChange:boolean){}
notifyOnAllChangesDone(){}
_detectChangesInChildren(throwOnChange:boolean) {
var children = this.children;
for(var i = 0; i < children.length; ++i) {
children[i]._detectChanges(throwOnChange);
_detectChangesInLightDomChildren(throwOnChange:boolean) {
var c = this.lightDomChildren;
for(var i = 0; i < c.length; ++i) {
c[i]._detectChanges(throwOnChange);
}
}
_detectChangesInShadowDomChildren(throwOnChange:boolean) {
var c = this.shadowDomChildren;
for(var i = 0; i < c.length; ++i) {
c[i]._detectChanges(throwOnChange);
}
}

View File

@ -1,4 +1,4 @@
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from 'angular2/change_detection';
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './interfaces';
/**
* @publicModule angular2/angular2

View File

@ -1,7 +1,7 @@
library change_detectoin.change_detection_jit_generator;
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, records) {
ChangeDetectorJITGenerator(typeName, records, directiveMementos) {
}
generate() {

View File

@ -15,6 +15,7 @@ import {
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
@ -63,6 +64,7 @@ import {
* }
* }
*
* ChangeDetector0.prototype.notifyOnAllChangesDone = function() {}
*
* ChangeDetector0.prototype.hydrate = function(context, locals) {
* this.context = context;
@ -95,31 +97,35 @@ var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher";
var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry";
var PROTOS_ACCESSOR = "this.protos";
var MEMENTOS_ACCESSOR = "this.directiveMementos";
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 {
function typeTemplate(type:string, cons:string, detectChanges:string,
notifyOnAllChangesDone:string, setContext:string):string {
return `
${cons}
${detectChanges}
${notifyOnAllChangesDone}
${setContext};
return function(dispatcher, pipeRegistry) {
return new ${type}(dispatcher, pipeRegistry, protos);
return new ${type}(dispatcher, pipeRegistry, protos, directiveMementos);
}
`;
}
function constructorTemplate(type:string, fieldsDefinitions:string):string {
return `
var ${type} = function ${type}(dispatcher, pipeRegistry, protos) {
var ${type} = function ${type}(dispatcher, pipeRegistry, protos, directiveMementos) {
${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher;
${PIPE_REGISTRY_ACCESSOR} = pipeRegistry;
${PROTOS_ACCESSOR} = protos;
${MEMENTOS_ACCESSOR} = directiveMementos;
${fieldsDefinitions}
}
@ -156,6 +162,18 @@ ${type}.prototype.detectChangesInRecords = function(throwOnChange) {
`;
}
function notifyOnAllChangesDoneTemplate(type:string, body:string):string {
return `
${type}.prototype.notifyOnAllChangesDone = function() {
${body}
}
`;
}
function onAllChangesDoneTemplate(index:number):string {
return `${DISPATCHER_ACCESSOR}.onAllChangesDone(${MEMENTOS_ACCESSOR}[${index}]);`;
}
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
return `
@ -180,14 +198,14 @@ if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
`;
}
function pipeCheckTemplate(context:string, pipe:string, pipeType:string,
function pipeCheckTemplate(context:string, bindingPropagationConfig:string, pipe:string, pipeType:string,
value:string, change:string, addRecord:string, notify:string):string{
return `
if (${pipe} === ${UTIL}.unitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context});
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
} else if (!${pipe}.supports(${context})) {
${pipe}.onDestroy();
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context});
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
}
${CHANGE_LOCAL} = ${pipe}.transform(${context});
@ -246,14 +264,16 @@ function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newVa
export class ChangeDetectorJITGenerator {
typeName:string;
records:List<ProtoRecord>;
directiveMementos:List;
localNames:List<String>;
changeNames:List<String>;
fieldNames:List<String>;
pipeNames:List<String>;
constructor(typeName:string, records:List<ProtoRecord>) {
constructor(typeName:string, records:List<ProtoRecord>, directiveMementos:List) {
this.typeName = typeName;
this.records = records;
this.directiveMementos = directiveMementos;
this.localNames = this.getLocalNames(records);
this.changeNames = this.getChangeNames(this.localNames);
@ -283,8 +303,10 @@ export class ChangeDetectorJITGenerator {
}
generate():Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genHydrate());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, this.records);
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(),
this.genNotifyOnAllChangesDone(), this.genHydrate());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', 'directiveMementos', text)
(AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveMementos);
}
genConstructor():string {
@ -293,20 +315,20 @@ export class ChangeDetectorJITGenerator {
genHydrate():string {
return hydrateTemplate(this.typeName, this.genFieldDefinitions(),
pipeOnDestroyTemplate(this.getnonNullPipeNames()));
pipeOnDestroyTemplate(this.getNonNullPipeNames()));
}
genFieldDefinitions() {
var fields = [];
fields = fields.concat(this.fieldNames);
fields = fields.concat(this.getnonNullPipeNames());
fields = fields.concat(this.getNonNullPipeNames());
return fieldDefinitionsTemplate(fields);
}
getnonNullPipeNames():List<String> {
getNonNullPipeNames():List<String> {
var pipes = [];
this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_PIPE) {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
pipes.push(this.pipeNames[r.selfIndex]);
}
});
@ -318,6 +340,20 @@ export class ChangeDetectorJITGenerator {
return detectChangesTemplate(this.typeName, body);
}
genNotifyOnAllChangesDone():string {
var notifications = [];
var mementos = this.directiveMementos;
for (var i = mementos.length - 1; i >= 0; --i) {
var memento = mementos[i];
if (memento.notifyOnAllChangesDone) {
notifications.push(onAllChangesDoneTemplate(i));
}
}
return notifyOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n"));
}
genBody():string {
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
@ -332,7 +368,7 @@ export class ChangeDetectorJITGenerator {
}
genRecord(r:ProtoRecord):string {
if (r.mode === RECORD_TYPE_PIPE) {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
return this.genPipeCheck (r);
} else {
return this.genReferenceCheck(r);
@ -345,11 +381,12 @@ export class ChangeDetectorJITGenerator {
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
var change = this.changeNames[r.selfIndex];
var bpc = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.bindingPropagationConfig" : "null";
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
return pipeCheckTemplate(context, pipe, r.name, newValue, change, addRecord, notify);
return pipeCheckTemplate(context, bpc, pipe, r.name, newValue, change, addRecord, notify);
}
genReferenceCheck(r:ProtoRecord):string {
@ -360,7 +397,7 @@ export class ChangeDetectorJITGenerator {
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify);;
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify);
if (r.isPureFunction()) {
return this.ifChangedGuard(r, check);
} else {

View File

@ -3,7 +3,7 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {AbstractChangeDetector} from './abstract_change_detector';
import {PipeRegistry} from './pipes/pipe_registry';
import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
import {ChangeDetectionUtil, uninitialized} from './change_detection_util';
import {
@ -17,6 +17,7 @@ import {
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
@ -33,8 +34,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
prevContexts:List;
protos:List<ProtoRecord>;
directiveMementos:List;
constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>) {
constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>, directiveMementos:List) {
super();
this.dispatcher = dispatcher;
this.pipeRegistry = pipeRegistry;
@ -51,6 +53,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
this.locals = null;
this.protos = protoRecords;
this.directiveMementos = directiveMementos;
}
hydrate(context:any, locals:any) {
@ -101,9 +104,19 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
}
}
notifyOnAllChangesDone() {
var mementos = this.directiveMementos;
for (var i = mementos.length - 1; i >= 0; --i) {
var memento = mementos[i];
if (memento.notifyOnAllChangesDone) {
this.dispatcher.onAllChangesDone(memento);
}
}
}
_check(proto:ProtoRecord) {
try {
if (proto.mode == RECORD_TYPE_PIPE) {
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
return this._pipeCheck(proto);
} else {
return this._referenceCheck(proto);
@ -202,7 +215,14 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
if (isPresent(storedPipe)) {
storedPipe.onDestroy();
}
var pipe = this.pipeRegistry.get(proto.name, context);
// Currently, only pipes that used in bindings in the template get
// the bindingPropagationConfig of the encompassing component.
//
// In the future, pipes declared in the bind configuration should
// be able to access the bindingPropagationConfig of that component.
var bpc = proto.mode === RECORD_TYPE_BINDING_PIPE ? this.bindingPropagationConfig : null;
var pipe = this.pipeRegistry.get(proto.name, context, bpc);
this._writePipe(proto, pipe);
return pipe;
}
@ -255,8 +275,6 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
}
}
var _singleElementList = [null];
function isSame(a, b) {
if (a === b) return true;
if (a instanceof String && b instanceof String && a == b) return true;

View File

@ -168,11 +168,13 @@ export class Pipe extends AST {
exp:AST;
name:string;
args:List<AST>;
constructor(exp:AST, name:string, args:List) {
inBinding:boolean;
constructor(exp:AST, name:string, args:List, inBinding:boolean) {
super();
this.exp = exp;
this.name = name;
this.args = args;
this.inBinding = inBinding;
}
visit(visitor) {
@ -418,7 +420,6 @@ export class TemplateBinding {
name:string;
expression:ASTWithSource;
constructor(key:string, keyIsVar:boolean, name:string, expression:ASTWithSource) {
super();
this.key = key;
this.keyIsVar = keyIsVar;
// only either name or expression will be filled.

View File

@ -58,7 +58,7 @@ export class Parser {
if (ListWrapper.isEmpty(pipes)) return bindingAst;
var res = ListWrapper.reduce(pipes,
(result, currentPipeName) => new Pipe(result, currentPipeName, []),
(result, currentPipeName) => new Pipe(result, currentPipeName, [], false),
bindingAst.ast);
return new ASTWithSource(res, bindingAst.source, bindingAst.location);
}
@ -220,7 +220,7 @@ class _ParseAST {
while (this.optionalCharacter($COLON)) {
ListWrapper.push(args, this.parseExpression());
}
result = new Pipe(result, name, args);
result = new Pipe(result, name, args, true);
}
return result;
}

View File

@ -16,17 +16,17 @@ import {
import {NO_CHANGE, Pipe} from './pipe';
export class ArrayChangesFactory {
export class IterableChangesFactory {
supports(obj):boolean {
return ArrayChanges.supportsObj(obj);
return IterableChanges.supportsObj(obj);
}
create():Pipe {
return new ArrayChanges();
create(bpc):Pipe {
return new IterableChanges();
}
}
export class ArrayChanges extends Pipe {
export class IterableChanges extends Pipe {
_collection;
_length:int;
_linkedRecords:_DuplicateMap;
@ -66,7 +66,7 @@ export class ArrayChanges extends Pipe {
}
supports(obj):boolean {
return ArrayChanges.supportsObj(obj);
return IterableChanges.supportsObj(obj);
}
get collection() {
@ -126,7 +126,8 @@ export class ArrayChanges extends Pipe {
var record:CollectionChangeRecord = this._itHead;
var mayBeDirty:boolean = false;
var index:int, item;
var index:int;
var item;
if (ListWrapper.isList(collection)) {
var list = collection;

View File

@ -8,7 +8,7 @@ export class KeyValueChangesFactory {
return KeyValueChanges.supportsObj(obj);
}
create():Pipe {
create(bpc):Pipe {
return new KeyValueChanges();
}
}
@ -107,9 +107,9 @@ export class KeyValueChanges extends Pipe {
var newSeqRecord;
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
newSeqRecord = oldSeqRecord;
if (!looseIdentical(value, oldSeqRecord._currentValue)) {
oldSeqRecord._previousValue = oldSeqRecord._currentValue;
oldSeqRecord._currentValue = value;
if (!looseIdentical(value, oldSeqRecord.currentValue)) {
oldSeqRecord.previousValue = oldSeqRecord.currentValue;
oldSeqRecord.currentValue = value;
this._addToChanges(oldSeqRecord);
}
} else {
@ -124,7 +124,7 @@ export class KeyValueChanges extends Pipe {
} else {
newSeqRecord = new KVChangeRecord(key);
MapWrapper.set(records, key, newSeqRecord);
newSeqRecord._currentValue = value;
newSeqRecord.currentValue = value;
this._addToAdditions(newSeqRecord);
}
}
@ -158,11 +158,11 @@ export class KeyValueChanges extends Pipe {
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
record._previousValue = record._currentValue;
record.previousValue = record.currentValue;
}
for (record = this._additionsHead; record != null; record = record._nextAdded) {
record._previousValue = record._currentValue;
record.previousValue = record.currentValue;
}
// todo(vicb) once assert is supported
@ -215,8 +215,8 @@ export class KeyValueChanges extends Pipe {
}
for (var rec:KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
rec._previousValue = rec._currentValue;
rec._currentValue = null;
rec.previousValue = rec.currentValue;
rec.currentValue = null;
MapWrapper.delete(this._records, rec.key);
}
}
@ -351,8 +351,8 @@ export class KeyValueChanges extends Pipe {
export class KVChangeRecord {
key;
_previousValue;
_currentValue;
previousValue;
currentValue;
_nextPrevious:KVChangeRecord;
_next:KVChangeRecord;
@ -363,8 +363,8 @@ export class KVChangeRecord {
constructor(key) {
this.key = key;
this._previousValue = null;
this._currentValue = null;
this.previousValue = null;
this.currentValue = null;
this._nextPrevious = null;
this._next = null;
@ -375,9 +375,9 @@ export class KVChangeRecord {
}
toString():string {
return looseIdentical(this._previousValue, this._currentValue) ?
return looseIdentical(this.previousValue, this.currentValue) ?
stringify(this.key) :
(stringify(this.key) + '[' + stringify(this._previousValue) + '->' +
stringify(this._currentValue) + ']');
(stringify(this.key) + '[' + stringify(this.previousValue) + '->' +
stringify(this.currentValue) + ']');
}
}

View File

@ -6,7 +6,7 @@ export class NullPipeFactory {
return NullPipe.supportsObj(obj);
}
create():Pipe {
create(bpc):Pipe {
return new NullPipe();
}
}

View File

@ -1,6 +1,7 @@
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {Pipe} from './pipe';
import {BindingPropagationConfig} from '../binding_propagation_config';
export class PipeRegistry {
config;
@ -9,7 +10,7 @@ export class PipeRegistry {
this.config = config;
}
get(type:string, obj):Pipe {
get(type:string, obj, bpc:BindingPropagationConfig):Pipe {
var listOfConfigs = this.config[type];
if (isBlank(listOfConfigs)) {
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
@ -22,6 +23,6 @@ export class PipeRegistry {
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
}
return matchingConfig.create();
return matchingConfig.create(bpc);
}
}

View File

@ -41,12 +41,13 @@ import {
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List):ChangeDetector{
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMemento:List):ChangeDetector{
return null;
}
}
@ -72,9 +73,9 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
this._pipeRegistry = pipeRegistry;
}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List) {
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List) {
this._createRecordsIfNecessary(bindingRecords, variableBindings);
return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records);
return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records, directiveMementos);
}
_createRecordsIfNecessary(bindingRecords:List, variableBindings:List) {
@ -99,12 +100,12 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
this._factory = null;
}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List) {
this._createFactoryIfNecessary(bindingRecords, variableBindings);
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List) {
this._createFactoryIfNecessary(bindingRecords, variableBindings, directiveMementos);
return this._factory(dispatcher, this._pipeRegistry);
}
_createFactoryIfNecessary(bindingRecords:List, variableBindings:List) {
_createFactoryIfNecessary(bindingRecords:List, variableBindings:List, directiveMementos:List) {
if (isBlank(this._factory)) {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEach(bindingRecords, (r) => {
@ -113,7 +114,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
var c = _jitProtoChangeDetectorClassCounter++;
var records = coalesce(recordBuilder.records);
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
this._factory = new ChangeDetectorJITGenerator(typeName, records, directiveMementos).generate();
}
}
}
@ -239,7 +240,8 @@ class _ConvertAstIntoProtoRecords {
visitPipe(ast:Pipe) {
var value = ast.exp.visit(this);
return this._addRecord(RECORD_TYPE_PIPE, ast.name, ast.name, [], null, value);
var type = ast.inBinding ? RECORD_TYPE_BINDING_PIPE : RECORD_TYPE_PIPE;
return this._addRecord(type, ast.name, ast.name, [], null, value);
}
visitKeyedAccess(ast:KeyedAccess) {

View File

@ -9,7 +9,8 @@ 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;
export const RECORD_TYPE_BINDING_PIPE = 9;
export const RECORD_TYPE_INTERPOLATE = 10;
export class ProtoRecord {
mode:number;

View File

@ -7,58 +7,62 @@ import {Injectable} from 'angular2/di';
/**
* Directives allow you to attach behavior to elements in the DOM.
*
* Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator]
* Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator]
* or [Viewport].
*
* A directive consists of a single directive annotation and a controller class. When the directive's [selector] matches
* elements in the DOM, the following steps occur:
*
* 1. For each directive, the [ElementInjector] attempts to resolve the directive's constructor arguments.
* 2. Angular instantiates directives for each matched element using [ElementInjector].
* 2. Angular instantiates directives for each matched element using [ElementInjector] in a depth-first order,
* as declared in the HTML.
*
* ## Understanding How Injection Works
*
*
* There are three stages of injection resolution.
* - *Pre-existing Injectors*:
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
* - *Pre-existing Injectors*:
* - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was
* specified as `@Optional`, returns `null`.
* - The primordial injector resolves browser singleton resources, such as: cookies, title, location, and others.
* - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy
* - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy
* as the components in the DOM.
* - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector]
* - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector]
* which follow the same parent-child hiercachy as the DOM elements themselves.
*
* When resolving dependencies, the current injector is asked to resolve the dependency first, and if it does not
* have it, it delegates to the parent injector.
*
*
* When a template is instantiated, it also must instantiate the corresponding directives in a depth-first order. The
* current [ElementInjector] resolves the constructor dependencies for each directive.
*
* Angular then resolves dependencies as follows, according to the order in which they appear in the [View]:
*
* 1. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
* 2. Dependencies on component injectors and their parents until it encounters the root component
* 3. Dependencies on pre-existing injectors
*
*
* The [ElementInjector] can inject other directives, element-specific special objects, or can delegate to the parent
*
* 1. Dependencies on the current element
* 2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary
* 3. Dependencies on component injectors and their parents until it encounters the root component
* 4. Dependencies on pre-existing injectors
*
*
* The [ElementInjector] can inject other directives, element-specific special objects, or it can delegate to the parent
* injector.
*
*
* To inject other directives, declare the constructor parameter as:
* - `directive:DirectiveType`: a directive on the current element only
* - `@Ancestor() d:Type`: any directive that matches the type between the current element (excluded) and the Shadow DOM root [TODO: what does (excluded) mean? Does this apply to the @Parent annotation also?]
* - `@Parent() d:Type`: any directive that matches the type on a direct parent element only
* - `@Children query:Query<Type>`: A live collection of direct child directives
* - `@Descendants query:Query<Type>`: A live collection of any child directives
*
* - `directive:DirectiveType`: a directive on the current element only
* - `@Ancestor() directive:DirectiveType`: any directive that matches the type between the current element and the
* Shadow DOM root. Current Element is not included in the resolution, therefor even if it could resolve it, it will
* be ignored.
* - `@Parent() directive:DirectiveType`: any directive that matches the type on a direct parent element only.
* - `@Children query:Query<DirectiveType>`: A live collection of direct child directives [TO BE IMPLEMENTED].
* - `@Descendants query:Query<DirectiveType>`: A live collection of any child directives [TO BE IMPLEMENTED].
*
* To inject element-specific special objects, declare the constructor parameter as:
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way
*
* - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming)
* - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only
* - `bindingPropagation: BindingPropagation` to control change detection in a more granular way.
*
* ## Example
*
* The following example demonstrates how dependency injection resolves constructor arguments in practice.
*
*
* Assume this HTML structure:
* Assume this HTML template:
*
* ```
* <div dependency="1">
@ -93,9 +97,10 @@ import {Injectable} from 'angular2/di';
*
* Let's step through the different ways in which `MyDirective` could be declared...
*
*
* ### No injection
*
* Here the constructor is declared with no arguments, so nothing is injected into `MyDirective`.
* Here the constructor is declared with no arguments, therefore nothing is injected into `MyDirective`.
*
* ```
* @Decorator({ selector: '[my-directive]' })
@ -105,9 +110,8 @@ import {Injectable} from 'angular2/di';
* }
* ```
*
* This directive would return nothing for the example code above. [TODO: True? We spent a lot of time talking about
* errors but in this case, there's nothing to error on, right? I don't understand the diff between "returns" and "injects"
* when the example is showing a directive not the template. Which is the correct verb?]
* This directive would be instantiated with no dependencies.
*
*
* ### Component-level injection
*
@ -124,8 +128,9 @@ import {Injectable} from 'angular2/di';
* }
* ```
*
* This directive would return `dependency=3` for the example code above. [TODO: True? Is "return" the right verb?]
*
* This directive would be instantiated with a dependency on `SomeService`.
*
*
* ### Injecting a directive from the current element
*
* Directives can inject other directives declared on the current element.
@ -134,18 +139,18 @@ import {Injectable} from 'angular2/di';
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(dependency: Dependency) {
* expect(dependency.id).toEqual(2);
* expect(dependency.id).toEqual(3);
* }
* }
* ```
* This directive would also return `dependency=3` for the example code above. [TODO: True? Why is this the same?]
*
* This directive would be instantiated with `Dependency` declared at the same element, in this case `dependency="3"`.
*
*
* ### Injecting a directive from a direct parent element
*
* Directives can inject other directives declared on a direct parent element. By definition, a directive with a
* Directives can inject other directives declared on a direct parent element. By definition, a directive with a
* `@Parent` annotation does not attempt to resolve dependencies for the current element, even if this would satisfy
* the dependency. [TODO: did I get the subject/verb right?]
* the dependency.
*
* ```
* @Decorator({ selector: '[my-directive]' })
@ -155,17 +160,15 @@ import {Injectable} from 'angular2/di';
* }
* }
* ```
* This directive would return `dependency=2` for the example code above. [TODO: True?]
*
* This directive would be instantiated with `Dependency` declared at the parent element, in this case `dependency="2"`.
*
*
* ### Injecting a directive from any ancestor elements
*
* Directives can inject other directives declared on any ancestor element, i.e. on the parent element and its parents.
* By definition, a directive with an `@Ancestor` annotation does not attempt to resolve dependencies for the current
* element, even if this would satisfy the dependency. [TODO: did I get the subject/verb right? ]
* Directives can inject other directives declared on any ancestor element (in the current Shadow DOM), i.e. on the
* parent element and its parents. By definition, a directive with an `@Ancestor` annotation does not attempt to
* resolve dependencies for the current element, even if this would satisfy the dependency.
*
* Unlike the `@Parent` which only checks the parent `@Ancestor` checks the parent, as well as its
* parents recursivly. If `dependency="2"` would not be present this injection would return `dependency="1"`.
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
@ -174,59 +177,61 @@ import {Injectable} from 'angular2/di';
* }
* }
* ```
*
* This directive would also return `dependency=2` for the example code above. If `dependency=2` hadn't been declared
* on the parent `div`, this directive would return `d[TODO: True?]
*
* ### Injecting query of child directives. [PENDING IMPLEMENTATION]
* Unlike the `@Parent` which only checks the parent, `@Ancestor` checks the parent, as well as its
* parents recursively. If `dependency="2"` didn't exist on the direct parent, this injection would have returned
* `dependency="1"`.
*
* In some cases the directive may be interersted in injecting its child directives. This is not directly possible
* since parent directives are guarteed to be created before child directives. Instead we can injecto a container
* which can than be filled once the data is needed.
*
* ### Injecting a live collection of direct child directives [PENDING IMPLEMENTATION]
*
* A directive can also query for other child directives. Since parent directives are instantiated before child
* directives, a directive can't simply inject the list of child directives. Instead, the directive asynchronously
* injects a [Query], which updates as children are added, removed, or moved by any [ViewPort] directive such as a
* `for`, an `if`, or a `switch`.
*
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Children() dependencys:Query<Maker>) {
* // dependencys will eventuall contain: [4, 6]
* // this will upbate if children are added/removed/moved,
* // for example by having for or if.
* constructor(@Children() dependencies:Query<Maker>) {
* }
* }
* ```
*
* This directive would be instantiated with a [Query] which contains `Dependency` 4 and 6. Here, `Dependency` 5 would
* not be included, because it is not a direct child.
*
* ### Injecting query of descendant directives. [PENDING IMPLEMENTATION]
* ### Injecting a live collection of direct descendant directives [PENDING IMPLEMENTATION]
*
* Similar to `@Children` but also includ childre of those children.
* Similar to `@Children` above, but also includes the children of the child elements.
*
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Children() dependencys:Query<Maker>) {
* // dependencys will eventuall contain: [4, 5, 6]
* // this will upbate if children are added/removed/moved,
* // for example by having for or if.
* constructor(@Children() dependencies:Query<Maker>) {
* }
* }
* ```
*
* This directive would be instantiated with a Query which would contain `Dependency` 4, 5 and 6.
*
* ### Optional injection
*
* Finally there may be times when we would like to inject a component which may or may not be there. For this
* use case angular supports `@Optional` injection.
* The normal behavior of directives is to return an error when a specified dependency cannot be resolved. If you
* would like to inject `null` on unresolved dependency instead, you can annotate that dependency with `@Optional()`.
* This explicitly permits the author of a template to treat some of the surrounding directives as optional.
*
* ```
* @Decorator({ selector: '[my-directive]' })
* class MyDirective {
* constructor(@Optional() @Ancestor() form:Form) {
* // this will search for a Form directive above itself,
* // and inject null if not found
* constructor(@Optional() dependency:Dependency) {
* }
* }
* ```
*
* This directive would be instantiated with a `Dependency` directive found on the current element. If none can be
* found, the injector supplies `null` instead of throwing an error.
*
* @publicModule angular2/annotations
*/
@ABSTRACT()
@ -235,13 +240,16 @@ 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`.
* `selector` may be declared as one of the following:
*
* - `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`.
* - `selector1, selector2`: select if either `selector1` or `selector2` matches.
*
*
* ## Example
*
@ -270,7 +278,8 @@ export class Directive extends Injectable {
* - `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.
* change detection of the value. These pipes will be evaluated in the context of this component.
*
*
* ## Syntax
*
@ -285,42 +294,44 @@ export class Directive extends Injectable {
* ```
*
*
* ## Basic Property Binding:
* ## Basic Property Binding
*
* We can easily build a simple `Tooltip` directive that exposes a `tooltip` property, which can be used in templates
* with standard Angular syntax. For example:
*
* ```
* @Decorator({
* selector: '[tooltip]',
* bind: {
* 'tooltipText': 'tooltip'
* 'text': 'tooltip'
* }
* })
* class Tooltip {
* set tooltipText(text) {
* set text(text) {
* // This will get called every time the 'tooltip' binding changes with the new value.
* }
* }
* ```
*
* As used in this example:
* We can then bind to the `tooltip' property as either an expression (`someExpression`) or as a string literal, as
* shown in the HTML template below:
*
* ```html
* <div [tooltip]="someExpression">
* <div [tooltip]="someExpression">...</div>
* <div tooltip="Some Text">...</div>
* ```
*
* 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">
* ```
* ## Bindings With Pipes
*
* The `Tooltip`'s `tooltipText` property gets initialized to the `Some Text` literal.
* You can also use pipes when writing binding definitions for a directive.
*
*
* ## Bindings With Pipes:
* For example, we could write a binding that updates the directive on structural changes, rather than on reference
* changes, as normally occurs in change detection. (See: [Pipe] and [keyValueDiff] documentaition for more details.)
*
* ```
* @Decorator({
@ -336,29 +347,30 @@ export class Directive extends Injectable {
* }
* ```
*
* As used in this example:
* The template that this directive is used in may also contain its own pipes. For example:
*
* ```html
* <div [class-set]="someExpression">
* <div [class-set]="someExpression | somePipe">
* ```
*
* 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.
* In this case, the two pipes compose as if they were inlined: `someExpression | somePipe | keyValDiff`.
*
*/
bind:any; // StringMap
/**
* Specifies which DOM events the directive listens to and what the action should be when they occur.
* Specifies which DOM events a directive listens to.
*
* 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.
* - `event1`: the DOM event that the directive listens to.
* - `statement`: the statment to execute when the event occurs.
*
*
* When writing a directive event binding, you can also refer to the following local variables:
* - `$event`: Current event object which triggerd the event.
* - `$target`: The source of the event. This will be either a DOM element or an Angular directive.
* [TO BE IMPLEMENTED]
*
*
* ## Syntax
@ -366,7 +378,7 @@ export class Directive extends Injectable {
* ```
* @Directive({
* events: {
* 'event1': 'onMethod1',
* 'event1': 'onMethod1(arguments)',
* ...
* }
* }
@ -374,20 +386,24 @@ export class Directive extends Injectable {
*
* ## Basic Event Binding:
*
* Suppose you want to write a directive that triggers on `change` events in the DOM. You would define the event
* binding as follows:
*
* ```
* @Decorator({
* selector: 'input',
* events: {
* 'change': 'onChange'
* 'change': 'onChange($event)'
* }
* })
* class InputDecorator {
* onChange(event:Event) {
* // invoked whenever the DOM element fires the 'change' event.
* }
* }
* ```
*
* Here `InputDecorator` is invoked whenever the DOM element fires the 'change' event.
*
*/
events:any; // StringMap
@ -420,6 +436,8 @@ export class Directive extends Injectable {
/**
* Returns true if a directive participates in a given [LifecycleEvent].
*
* See: [onChange], [onDestroy] for details.
*/
hasLifecycleHook(hook:string):boolean {
return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false;
@ -427,98 +445,87 @@ export class Directive extends Injectable {
}
/**
* Components are angular directives with Shadow DOM views.
* Declare template views for an Angular application.
*
* Componests are used to encapsulate state and template into reusable building blocks. An angular component requires
* an `@Component` and at least one `@Template` annotation (see [Template] for more datails.) Components instances are
* used as the context for evaluation of the Shadow DOM view.
* Each angular component requires a single `@Component` and at least one `@Template` annotation. This allows Angular to
* encapsulate state information and templates. These form the fundamental reusable building blocks for developing an
* application. There can only be one component per DOM element.
*
* Restrictions:
* - Thre can anly be one component per DOM element.
* When a component is instantiated, Angular
* - creates a shadow DOM for the component.
* - loads the selected template into the shadow DOM.
* - creates a child [Injector] which is configured with the [Component.services].
*
* All template expressions and statments are then evaluted against the component instance.
*
* For details on the `@Template` annotation, see [Template].
*
* ## Example
* @Component({
* selector: 'greet'
* })
* @Template({
* inline: 'Hello {{name}}'
* })
* class Greet {
* name: string;
*
* constructor() {
* this.name = 'World';
* }
* }
* ```
* @Component({
* selector: 'greet'
* })
* @Template({
* inline: 'Hello {{name}}!'
* })
* class Greet {
* name: string;
*
* constructor() {
* this.name = 'World';
* }
* }
* ```
*
* @publicModule angular2/annotations
*/
export class Component extends Directive {
/**
* Defines the set of injectables that are visible to a Component and its children.
* Defines the set of injectable objects that are visible to a Component and its children.
*
* When a [Component] defines [injectables], Angular creates a new application-level [Injector] for the component
* and its children. Injectables are defined as a list of [Binding]s, (or as [Type]s as short hand). These bindings
* are passed to the [Injector] constructor when making a new child [Injector]. The injectables are available for
* all child directives of the Component (but not the declaring component's light DOM directives).
* The [services] defined in the Component annotation allow you to configure a set of bindings for the component's
* injector.
*
* ## Example
* // Example of a class which we would like to inject.
* class Greeter {
* salutation:string;
*
* constructor(salutation:string) {
* this.salutation = salutation;
* }
*
* greet(name:string) {
* return this.salutation + ' ' + name + '!';
* }
* }
*
* @Component({
* selector: 'greet',
* services: [
* bind(String).toValue('Hello'), // Configure injection of string
* Greeter // Make Greeter available for injection
* ]
* })
* @Template({
* inline: '<child></child>',
* directives: Child
* })
* class Greet {
* greeter: Greeter;
*
* constructor(greeter: Greeter) {
* // Greeter can be injected here becouse it was declared as injectable
* // in this component, or parent component.
* this.greeter = greeter;
* }
* }
*
* @Decorator({
* selector: 'child'
* })
* class Child {
* greeter: Greeter;
*
* constructor(greeter: Greeter) {
* // Greeter can be injected here becouse it was declared as injectable
* // in a an ancestor component.
* this.greeter = greeter;
* }
* }
* When a component is instantiated, Angular creates a new child Injector, which is configured with the bindings in
* the Component [services] annotation. The injectable objects then become available for injection to the component
* itself and any of the directives in the component's template, i.e. they are not available to the directives which
* are children in the component's light DOM.
*
*
* Let's look at the [services] part of the example above.
* The syntax for configuring the [services] injectable is identical to [Injector] injectable configuration. See
* [Injector] for additional detail.
*
* services: [
* bind(String).toValue('Hello'),
* Greeter
* ]
*
* Here the `Greeter` is a short hand for `bind(Greeter).toClass(Greeter)`. See [bind] DSL for more details.
* ## Simple Example
*
* Here is an example of a class that can be injected:
*
* ```
* class Greeter {
* greet(name:string) {
* return 'Hello ' + name + '!';
* }
* }
*
* @Component({
* selector: 'greet',
* services: [
* Greeter
* ]
* })
* @Template({
* inline: `{{greeter.greet('world')}}!`,
* directives: Child
* })
* class HelloWorld {
* greeter:Greeter;
*
* constructor(greeter:Greeter) {
* this.greeter = greeter;
* }
* }
* ```
*/
services:List;
@ -549,27 +556,34 @@ export class Component extends Directive {
}
/**
* DynamicComponents allow loading child components impretivly.
*
* A Component can be made of other compontents. This recursive nature must be resolved synchronously during the
* component template processing. This means that all templates are resolved synchronously. This prevents lazy loading
* of code or delayed binding of views to the components.
*
* A DynamicComponent is a placeholder into which a regular component can be loaded imperativly and thus breaking
* the all components must be resolved synchronously restriction. Once loaded the component is premanent.
*
*
* Directive used for dynamically loading components.
*
* Regular angular components are statically resolved. DynamicComponent allows to you resolve a component at runtime
* instead by providing a placeholder into which a regular angular component can be dynamically loaded. Once loaded,
* the dynamically-loaded component becomes permanent and cannot be changed.
*
*
* ## Example
*
* Here we have `DynamicComp` which acts as the placeholder for `HelloCmp`. At runtime, the dynamic component
* `DynamicComp` requests loading of the `HelloCmp` component.
*
* There is nothing special about `HelloCmp`, which is a regular angular component. It can also be used in other static
* locations.
*
* ```
* @DynamicComponent({
* selector: 'dynamic-comp'
* })
* class DynamicComp {
* done;
* helloCmp:HelloCmp;
* constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) {
* this.done = loader.load(HelloCmp, location);
* loader.load(HelloCmp, location).then((helloCmp) => {
* this.helloCmp = helloCmp;
* });
* }
* }
*
*
* @Component({
* selector: 'hello-cmp'
* })
@ -582,11 +596,17 @@ export class Component extends Directive {
* this.greeting = "hello";
* }
* }
*
*
* ```
*
*
*
* @publicModule angular2/annotations
*/
export class DynamicComponent extends Directive {
/**
* Same as [Component.services].
*/
// TODO(vsankin): Please extract into AbstractComponent
services:any; //List;
@CONST()
@ -615,26 +635,24 @@ export class DynamicComponent extends Directive {
}
/**
* Decorators allow attaching behavior to DOM elements in a composable manner.
* Directive that attaches behavior to DOM elements.
*
* A decorator directive attaches behavior to a DOM element in a composable manner.
* (see: http://en.wikipedia.org/wiki/Composition_over_inheritance)
*
* Decorators:
* - are simplest form of [Directive]s.
* - are besed used as compostinion pattern ()
* - are best used as a composition pattern ()
*
* Decoraters differ from [Component]s in that they:
* - can have any number of decorators per element
* Decorators differ from [Component]s in that they:
* - can have multiple decorators per element
* - do not create their own evaluation context
* - do not have template (and therefor do not create Shadow DOM)
*
*
* ## Example
*
* Let's say we would like to add tool-tip behavior to any alement.
*
* ```
* <div tooltip="some text here"></div>
* ```
*
* We could have a decorator directive like so:
* Here we use a decorator directive to simply define basic tool-tip behavior.
*
* ```
* @Decorator({
@ -643,8 +661,8 @@ export class DynamicComponent extends Directive {
* 'text': 'tooltip'
* },
* event: {
* 'onmouseenter': 'onMouseEnter',
* 'onmouseleave': 'onMouseLeave'
* 'onmouseenter': 'onMouseEnter()',
* 'onmouseleave': 'onMouseLeave()'
* }
* })
* class Tooltip{
@ -667,10 +685,23 @@ export class DynamicComponent extends Directive {
* }
* }
* ```
* In our HTML template, we can then add this behavior to a `<div>` or any other element with the `tooltip` selector,
* like so:
*
* ```
* <div tooltip="some text here"></div>
* ```
*
* @publicModule angular2/annotations
*/
export class Decorator extends Directive {
/**
* If set to true the compiler does not compile the children of this directive.
*/
//TODO(vsavkin): This would better fall under the Macro directive concept.
compileChildren: boolean;
@CONST()
constructor({
selector,
@ -686,33 +717,56 @@ export class Decorator extends Directive {
compileChildren:boolean
}={})
{
this.compileChildren = compileChildren;
super({
selector: selector,
bind: bind,
events: events,
lifecycle: lifecycle
});
this.compileChildren = compileChildren;
}
}
/**
* Viewport is used for controlling the instatiation of inline templates.
* Directive that controls the instantiation, destruction, and positioning of inline template elements.
*
* Viewport consist of a controller which can inject [ViewContainer]. A [ViewContainer] rerpsents a location in the
* current view where child views can be inserted.
* A viewport directive uses a [ViewContainer] to instantiate, insert, move, and destroy views at runtime.
* The [ViewContainer] is created as a result of `<template>` element, and represents a location in the current view
* where these actions are performed.
*
* ## Example
* Views are always created as children of the current [View], and as siblings of the `<template>` element. Thus a
* directive in a child view cannot inject the viewport directive that created it.
*
* Given folowing inline template, let's implement the `unless` behavior.
* Since viewport directives are common in Angular, and using the full `<template>` element syntax is wordy, Angular
* also supports a shorthand notation: `<li *foo="bar">` and `<li template="foo: bar">` are equivalent.
*
* Thus,
*
* ```
* <ul>
* <li *unless="expr"></li>
* <li *foo="bar" title="text"></li>
* </ul>
* ```
*
* Can be implemented using:
* Expands in use to:
*
* ```
* <ul>
* <template [foo]="bar">
* <li title="text"></li>
* </template>
* </ul>
* ```
*
* Notice that although the shorthand places `*foo="bar"` within the `<li>` element, the binding for the `Viewport`
* controller is correctly instantiated on the `<template>` element rather than the `<li>` element.
*
*
* ## Example
*
* Let's suppose we want to implement the `unless` behavior, to conditionally include a template.
*
* Here is a simple viewport directive that triggers on an `unless` selector:
*
* ```
* @Viewport({
@ -721,7 +775,7 @@ export class Decorator extends Directive {
* 'condition': 'unless'
* }
* })
* export class If {
* export class Unless {
* viewContainer: ViewContainer;
* prevCondition: boolean;
*
@ -742,6 +796,27 @@ export class Decorator extends Directive {
* }
* ```
*
* We can then use this `unless` selector in a template:
* ```
* <ul>
* <li *unless="expr"></li>
* </ul>
* ```
*
* Once the viewport instantiates the child view, the shorthand notation for the template expands and the result is:
*
* ```
* <ul>
* <template [unless]="exp">
* <li></li>
* </template>
* <li></li>
* </ul>
* ```
*
* Note also that although the `<li></li>` template still exists inside the `<template></template>`, the instantiated
* view occurs on the second `<li></li>` which is a sibling to the `<template>` element.
*
*
* @publicModule angular2/annotations
*/
@ -770,7 +845,7 @@ export class Viewport extends Directive {
//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.
* Notify a directive whenever a [View] that contains it is destroyed.
*
* ## Example
*
@ -779,7 +854,7 @@ export class Viewport extends Directive {
* ...,
* lifecycle: [ onDestroy ]
* })
* class ClassSet implements OnDestroy {
* class ClassSet {
* onDestroy() {
* // invoked to notify directive of the containing view destruction.
* }
@ -791,7 +866,12 @@ export const onDestroy = "onDestroy";
/**
* Specify that a directive should be notified when any of its bindings have changed.
* Notify a directive when any of its bindings have changed.
*
* This method is called right after the directive's bindings have been checked,
* and before any of its children's bindings have been checked.
*
* It is invoked only if at least one of the directive's bindings has changed.
*
* ## Example:
*
@ -820,3 +900,23 @@ export const onDestroy = "onDestroy";
* @publicModule angular2/annotations
*/
export const onChange = "onChange";
/**
* Notify a directive when the bindings of all its children have been changed.
*
* ## Example:
*
* ```
* @Decorator({
* selector: '[class-set]',
* })
* class ClassSet {
*
* onAllChangesDone() {
* }
*
* }
* ```
* @publicModule angular2/annotations
*/
export const onAllChangesDone = "onAllChangesDone";

View File

@ -26,3 +26,15 @@ export class PropertySetter extends DependencyAnnotation {
this.propName = propName;
}
}
/**
* The directive can inject the value of an attribute of the host element
*/
export class Attribute extends DependencyAnnotation {
attributeName: string;
@CONST()
constructor(attributeName) {
super();
this.attributeName = attributeName;
}
}

View File

@ -1,7 +1,7 @@
import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
/**
* @publicModule angular2/angular2
* @publicModule angular2/annotations
*/
export class Template {
url:any; //string;

View File

@ -2,8 +2,43 @@ import {CONST} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/di';
/**
* The directive can only be injected from the current element
* or from its parent.
* Specifies that an injector should retrieve a dependency from the direct parent.
*
* ## Example
*
* Here is a simple directive that retrieves a dependency from its parent element.
*
* ```
* @Decorator({
* selector: '[dependency]',
* bind: {
* 'id':'dependency'
* }
* })
* class Dependency {
* id:string;
* }
*
*
* @Decorator({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Parent() dependency:Dependency) {
* expect(dependency.id).toEqual(1);
* };
* }
* ```
*
* We use this with the following HTML template:
*
* ```
* <div dependency="1">
* <div dependency="2" my-directive></div>
* </div>
* ```
* The `@Parent()` annotation in our constructor forces the injector to retrieve the dependency from the
* parent element (even thought the current element could resolve it): Angular injects `dependency=1`.
*
* @publicModule angular2/annotations
*/
@ -15,8 +50,56 @@ export class Parent extends DependencyAnnotation {
}
/**
* The directive can only be injected from the current element
* or from its ancestor.
* Specifies that an injector should retrieve a dependency from any ancestor element.
*
* An ancestor is any element between the parent element and shadow root.
*
*
* ## Example
*
* Here is a simple directive that retrieves a dependency from an ancestor element.
*
* ```
* @Decorator({
* selector: '[dependency]',
* bind: {
* 'id':'dependency'
* }
* })
* class Dependency {
* id:string;
* }
*
*
* @Decorator({
* selector: '[my-directive]'
* })
* class Dependency {
* constructor(@Ancestor() dependency:Dependency) {
* expect(dependency.id).toEqual(2);
* };
* }
* ```
*
* We use this with the following HTML template:
*
* ```
* <div dependency="1">
* <div dependency="2">
* <div>
* <div dependency="3" my-directive></div>
* </div>
* </div>
* </div>
* ```
*
* The `@Ancestor()` annotation in our constructor forces the injector to retrieve the dependency from the
* nearest ancestor element:
* - The current element `dependency="3"` is skipped because it is not an ancestor.
* - Next parent has no directives `<div>`
* - Next parent has the `Dependency` directive and so the dependency is satisfied.
*
* Angular injects `dependency=2`.
*
* @publicModule angular2/annotations
*/

View File

@ -26,6 +26,7 @@ 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 {Component} from 'angular2/src/core/annotations/annotations';
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
var _rootInjector: Injector;
@ -107,6 +108,7 @@ function _injectorBindings(appComponentType): List<Binding> {
StyleUrlResolver,
StyleInliner,
bind(CssProcessor).toFactory(() => new CssProcessor(null), []),
PrivateComponentLoader,
];
}

View File

@ -3,14 +3,14 @@ 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, PropertySetter} from 'angular2/src/core/annotations/di';
import {EventEmitter, PropertySetter, Attribute} 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, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
import {BindingPropagationConfig} from 'angular2/change_detection';
import * as pclModule from 'angular2/src/core/compiler/private_component_location';
import {reflector} from 'angular2/src/reflection/reflection';
import {setterFactory} from './property_setter_factory';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
@ -90,19 +90,22 @@ export class DirectiveDependency extends Dependency {
depth:int;
eventEmitterName:string;
propSetterName:string;
attributeName:string;
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean,
properties:List, depth:int, eventEmitterName: string, propSetterName: string) {
properties:List, depth:int, eventEmitterName: string, propSetterName: string, attributeName:string) {
super(key, asPromise, lazy, optional, properties);
this.depth = depth;
this.eventEmitterName = eventEmitterName;
this.propSetterName = propSetterName;
this.attributeName = attributeName;
}
static createFrom(d:Dependency):Dependency {
var depth = 0;
var eventName = null;
var propName = null;
var attributeName = null;
var properties = d.properties;
for (var i = 0; i < properties.length; i++) {
@ -115,22 +118,26 @@ export class DirectiveDependency extends Dependency {
eventName = property.eventName;
} else if (property instanceof PropertySetter) {
propName = property.propName;
} else if (property instanceof Attribute) {
attributeName = property.attributeName;
}
}
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, d.properties, depth,
eventName, propName);
eventName, propName, attributeName);
}
}
export class DirectiveBinding extends Binding {
callOnDestroy:boolean;
callOnChange:boolean;
callOnAllChangesDone:boolean;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
super(key, factory, dependencies, providedAsPromise);
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
this.callOnAllChangesDone = isPresent(annotation) && annotation.hasLifecycleHook(onAllChangesDone);
}
static createFromBinding(b:Binding, annotation:Directive):Binding {
@ -209,6 +216,9 @@ export class ProtoElementInjector {
index:int;
view:viewModule.View;
distanceToParent:number;
attributes:Map;
numberOfDirectives:number;
/** Whether the element is exported as $implicit. */
exportElement:boolean;
@ -238,6 +248,7 @@ export class ProtoElementInjector {
this._binding8 = null; this._keyId8 = null;
this._binding9 = null; this._keyId9 = null;
this.numberOfDirectives = bindings.length;
var length = bindings.length;
if (length > 0) {this._binding0 = this._createBinding(bindings[0]); this._keyId0 = this._binding0.key.id;}
@ -276,6 +287,20 @@ export class ProtoElementInjector {
return isPresent(this._binding0);
}
getDirectiveBindingAtIndex(index:int) {
if (index == 0) return this._binding0;
if (index == 1) return this._binding1;
if (index == 2) return this._binding2;
if (index == 3) return this._binding3;
if (index == 4) return this._binding4;
if (index == 5) return this._binding5;
if (index == 6) return this._binding6;
if (index == 7) return this._binding7;
if (index == 8) return this._binding8;
if (index == 9) return this._binding9;
throw new OutOfBoundsAccess(index);
}
hasEventEmitter(eventName: string) {
var p = this;
if (isPresent(p._binding0) && DirectiveBinding._hasEventEmitter(eventName, p._binding0)) return true;
@ -514,6 +539,7 @@ 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);
if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
}
@ -527,10 +553,19 @@ export class ElementInjector extends TreeNode {
_buildPropSetter(dep) {
var ngElement = this._getPreBuiltObjectByKeyId(StaticKeys.instance().ngElementId);
var domElement = ngElement.domElement;
var setter = reflector.setter(dep.propSetterName);
var setter = setterFactory(dep.propSetterName);
return function(v) { setter(domElement, v) };
}
_buildAttribute(dep): string {
var attributes = this._proto.attributes;
if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) {
return MapWrapper.get(attributes, dep.attributeName);
} else {
return null;
}
}
/*
* It is fairly easy to annotate keys with metadata.
* For example, key.metadata = 'directive'.
@ -632,18 +667,7 @@ export class ElementInjector extends TreeNode {
}
getDirectiveBindingAtIndex(index:int) {
var p = this._proto;
if (index == 0) return p._binding0;
if (index == 1) return p._binding1;
if (index == 2) return p._binding2;
if (index == 3) return p._binding3;
if (index == 4) return p._binding4;
if (index == 5) return p._binding5;
if (index == 6) return p._binding6;
if (index == 7) return p._binding7;
if (index == 8) return p._binding8;
if (index == 9) return p._binding9;
throw new OutOfBoundsAccess(index);
return this._proto.getDirectiveBindingAtIndex(index);
}
hasInstances() {

View File

@ -6,7 +6,7 @@ import {Decorator, Component, Viewport, DynamicComponent} from '../../annotation
import {ElementBinder} from '../element_binder';
import {ProtoElementInjector} from '../element_injector';
import * as viewModule from '../view';
import {dashCaseToCamelCase} from './util';
import {dashCaseToCamelCase} from '../string_utils';
import {AST} from 'angular2/change_detection';
@ -22,6 +22,7 @@ export class CompileElement {
textNodeBindings:Map;
propertyBindings:Map;
eventBindings:Map;
attributes:Map;
/// Store directive name to template name mapping.
/// Directive name is what the directive exports the variable as
@ -144,6 +145,13 @@ export class CompileElement {
MapWrapper.set(this.eventBindings, eventName, expression);
}
addAttribute(attributeName:string, attributeValue:string) {
if (isBlank(this.attributes)) {
this.attributes = MapWrapper.create();
}
MapWrapper.set(this.attributes, attributeName, attributeValue);
}
addDirective(directive:DirectiveMetadata) {
var annotation = directive.annotation;
this._allDirectives = null;

View File

@ -1,5 +1,5 @@
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
import {List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {List, MapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector';
@ -10,9 +10,6 @@ import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {isSpecialProperty} from './element_binder_builder';
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
/**
@ -39,8 +36,8 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i];
selector=CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectable(selector, directiveMetadata);
selector = CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectables(selector, directiveMetadata);
}
}
@ -60,53 +57,15 @@ export class DirectiveParser extends CompileStep {
});
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
// only be present on <template> elements!
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);
current.addDirective(directive);
current.addDirective(checkDirectiveValidity(directive, current, isTemplateElement));
});
// raise error if some directives are missing
checkMissingDirectives(current, matchedProperties, isTemplateElement);
}
}
// calculate all the properties that are used or interpreted by all directives
// those properties correspond to the directive selectors and the directive bindings
function updateMatchedProperties(matchedProperties, selector, directive) {
if (assertionsEnabled()) {
var attrs = selector.attrs;
if (!isPresent(matchedProperties)) {
matchedProperties = StringMapWrapper.create();
}
if (isPresent(attrs)) {
for (var idx = 0; idx<attrs.length; idx+=2) {
// attribute name is stored on even indexes
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 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, dashCaseToCamelCase(bindProp[1]), true);
}
});
}
}
return matchedProperties;
}
// 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;
@ -125,26 +84,6 @@ function checkDirectiveValidity(directive, current, isTemplateElement) {
} else if (isComponent && alreadyHasComponent) {
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
}
}
// validates that there is no missing directive - dev mode only
function checkMissingDirectives(current, matchedProperties, isTemplateElement) {
if (assertionsEnabled()) {
var ppBindings=current.propertyBindings;
if (isPresent(ppBindings)) {
// check that each property corresponds to a real property or has been matched by a directive
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 '${camelCaseToDashCase(prop)}' in ${current.elementDescription}`);
}
}
});
}
// template only store directives as attribute when they are not bound to expressions
// so we have to validate the expression case too (e.g. !if="condition")
if (isTemplateElement && !current.isViewRoot && !isPresent(current.viewportDirective)) {
throw new BaseException(`Missing directive to handle: ${current.elementDescription}`);
}
}
return directive;
}

View File

@ -1,5 +1,4 @@
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, RegExpWrapper, isString, stringify} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {int, isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {reflector} from 'angular2/src/reflection/reflection';
@ -11,96 +10,8 @@ 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';
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, ariaAttrName, stringify(value));
} else {
DOM.removeAttribute(element, ariaAttrName);
}
};
StringMapWrapper.set(ariaSettersCache, attrName, setterFn);
}
return setterFn;
}
const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();
function classSetterFactory(className:string) {
var setterFn = StringMapWrapper.get(classSettersCache, className);
if (isBlank(setterFn)) {
setterFn = function(element, value) {
if (value) {
DOM.addClass(element, className);
} else {
DOM.removeClass(element, className);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}
return setterFn;
}
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, dashCasedStyleName, valAsStr + stylesuffix);
} else {
DOM.removeStyle(element, dashCasedStyleName);
}
};
StringMapWrapper.set(classSettersCache, cacheKey, setterFn);
}
return setterFn;
}
const ROLE_ATTR = 'role';
function roleSetter(element, value) {
if (isString(value)) {
DOM.setAttribute(element, ROLE_ATTR, value);
} else {
DOM.removeAttribute(element, ROLE_ATTR);
if (isPresent(value)) {
throw new BaseException("Invalid role attribute, only string values are allowed, got '" + stringify(value) + "'");
}
}
}
// tells if an attribute is handled by the ElementBinderBuilder step
export function isSpecialProperty(propName:string) {
return StringWrapper.startsWith(propName, ARIA_PREFIX)
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
|| StringMapWrapper.contains(DOM.attrToPropMap, propName);
}
import {dashCaseToCamelCase} from '../string_utils';
import {setterFactory} from '../property_setter_factory'
/**
* Creates the ElementBinders and adds watches to the
@ -186,31 +97,8 @@ export class ElementBinderBuilder extends CompileStep {
_bindElementProperties(protoView, compileElement) {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn, styleParts, styleSuffix;
if (StringWrapper.startsWith(property, ARIA_PREFIX)) {
setterFn = ariaSetterFactory(property);
} else if (StringWrapper.equals(property, ROLE_ATTR)) {
setterFn = roleSetter;
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
styleParts = StringWrapper.split(property, DOT_REGEXP);
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
} else {
property = this._resolvePropertyName(property);
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789
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);
}
}
if (isPresent(setterFn)) {
protoView.bindElementProperty(expression.ast, property, setterFn);
}
var setterFn = setterFactory(property);
protoView.bindElementProperty(expression.ast, property, setterFn);
});
}
@ -273,9 +161,4 @@ export class ElementBinderBuilder extends CompileStep {
_splitBindConfig(bindConfig:string) {
return ListWrapper.map(bindConfig.split('|'), (s) => s.trim());
}
_resolvePropertyName(attrName:string) {
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
return isPresent(mappedPropName) ? mappedPropName : attrName;
}
}

View File

@ -72,6 +72,8 @@ export class PropertyBindingParser extends CompileStep {
var ast = this._parseInterpolation(attrValue, desc);
if (isPresent(ast)) {
current.addPropertyBinding(attrName, ast);
} else {
current.addAttribute(attrName, attrValue);
}
}
});

View File

@ -59,6 +59,7 @@ export class ProtoElementInjectorBuilder extends CompileStep {
current.inheritedProtoElementInjector.exportImplicitName = exportImplicitName;
}
}
current.inheritedProtoElementInjector.attributes = current.attributes;
} else {
current.inheritedProtoElementInjector = parentProtoElementInjector;

View File

@ -2,8 +2,9 @@ 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 {Component} from 'angular2/src/core/annotations/annotations';
import {PrivateComponentLocation} from './private_component_location';
import {Type} from 'angular2/src/facade/lang';
import {Type, stringify, BaseException} from 'angular2/src/facade/lang';
export class PrivateComponentLoader {
@ -23,6 +24,11 @@ export class PrivateComponentLoader {
load(type:Type, location:PrivateComponentLocation) {
var annotation = this.directiveMetadataReader.read(type).annotation;
if (!(annotation instanceof Component)) {
throw new BaseException(`Could not load '${stringify(type)}' because it is not a component.`);
}
return this.compiler.compile(type).then((componentProtoView) => {
location.createComponent(
type, annotation,

View File

@ -0,0 +1,125 @@
import {StringWrapper, RegExpWrapper, BaseException, isPresent, isBlank, isString, stringify} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {camelCaseToDashCase, dashCaseToCamelCase} from './string_utils';
import {reflector} from 'angular2/src/reflection/reflection';
const STYLE_SEPARATOR = '.';
var propertySettersCache = StringMapWrapper.create();
var innerHTMLSetterCache;
export function setterFactory(property: string): Function {
var setterFn, styleParts, styleSuffix;
if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
styleParts = property.split(STYLE_SEPARATOR);
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
} else if (StringWrapper.equals(property, 'innerHtml')) {
if (isBlank(innerHTMLSetterCache)) {
innerHTMLSetterCache = (el, value) => DOM.setInnerHTML(el, value);
}
setterFn = innerHTMLSetterCache;
} else {
property = resolvePropertyName(property);
setterFn = StringMapWrapper.get(propertySettersCache, property);
if (isBlank(setterFn)) {
var propertySetterFn = reflector.setter(property);
setterFn = function(receiver, value) {
if (DOM.hasProperty(receiver, property)) {
return propertySetterFn(receiver, value);
}
};
StringMapWrapper.set(propertySettersCache, property, setterFn);
}
}
return setterFn;
}
const ATTRIBUTE_PREFIX = 'attr.';
var attributeSettersCache = StringMapWrapper.create();
function _isValidAttributeValue(attrName:string, value: any): boolean {
if (attrName == "role") {
return isString(value);
} else {
return isPresent(value);
}
}
function attributeSetterFactory(attrName:string): Function {
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
var dashCasedAttributeName;
if (isBlank(setterFn)) {
dashCasedAttributeName = camelCaseToDashCase(attrName);
setterFn = function(element, value) {
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
} else {
if (isPresent(value)) {
throw new BaseException("Invalid " + dashCasedAttributeName +
" attribute, only string values are allowed, got '" + stringify(value) + "'");
}
DOM.removeAttribute(element, dashCasedAttributeName);
}
};
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
}
return setterFn;
}
const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();
function classSetterFactory(className:string): Function {
var setterFn = StringMapWrapper.get(classSettersCache, className);
var dashCasedClassName;
if (isBlank(setterFn)) {
dashCasedClassName = camelCaseToDashCase(className);
setterFn = function(element, value) {
if (value) {
DOM.addClass(element, dashCasedClassName);
} else {
DOM.removeClass(element, dashCasedClassName);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}
return setterFn;
}
const STYLE_PREFIX = 'style.';
var styleSettersCache = StringMapWrapper.create();
function styleSetterFactory(styleName:string, styleSuffix:string): Function {
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, dashCasedStyleName, valAsStr + styleSuffix);
} else {
DOM.removeStyle(element, dashCasedStyleName);
}
};
StringMapWrapper.set(styleSettersCache, cacheKey, setterFn);
}
return setterFn;
}
function resolvePropertyName(attrName:string): string {
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
return isPresent(mappedPropName) ? mappedPropName : attrName;
}

View File

@ -9,7 +9,9 @@ var _SELECTOR_REGEXP =
RegExpWrapper.create('(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(?:\\))|' + // ")"
'(\\s*,\\s*)'); // ","
/**
* A css selector contains an element name,
@ -21,7 +23,15 @@ export class CssSelector {
classNames:List;
attrs:List;
notSelector: CssSelector;
static parse(selector:string): CssSelector {
static parse(selector:string): List<CssSelector> {
var results = ListWrapper.create();
var _addResult = (res, cssSel) => {
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element)
&& ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = "*";
}
ListWrapper.push(res, cssSel);
}
var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
@ -43,13 +53,13 @@ export class CssSelector {
if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]);
}
if (isPresent(match[6])) {
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
}
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element)
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) {
cssSelector.element = "*";
}
return cssSelector;
_addResult(results, cssSelector);
return results;
}
constructor() {
@ -119,6 +129,7 @@ export class SelectorMatcher {
_classPartialMap:Map;
_attrValueMap:Map;
_attrValuePartialMap:Map;
_listContexts:List;
constructor() {
this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create();
@ -128,6 +139,19 @@ export class SelectorMatcher {
this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create();
this._listContexts = ListWrapper.create();
}
addSelectables(cssSelectors:List<CssSelector>, callbackCtxt) {
var listContext = null;
if (cssSelectors.length > 1) {
listContext= new SelectorListContext(cssSelectors);
ListWrapper.push(this._listContexts, listContext);
}
for (var i = 0; i < cssSelectors.length; i++) {
this.addSelectable(cssSelectors[i], callbackCtxt, listContext);
}
}
/**
@ -135,12 +159,12 @@ export class SelectorMatcher {
* @param cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
*/
addSelectable(cssSelector:CssSelector, callbackCtxt) {
addSelectable(cssSelector, callbackCtxt, listContext: SelectorListContext) {
var matcher = this;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
var selectable = new SelectorContext(cssSelector, callbackCtxt);
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (isPresent(element)) {
@ -215,6 +239,10 @@ export class SelectorMatcher {
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
for (var i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false;
}
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
@ -282,26 +310,41 @@ export class SelectorMatcher {
}
class SelectorListContext {
selectors: List<CssSelector>;
alreadyMatched: boolean;
constructor(selectors:List<CssSelector>) {
this.selectors = selectors;
this.alreadyMatched = false;
}
}
// Store context to pass back selector and context when a selector is matched
class SelectorContext {
selector:CssSelector;
notSelector:CssSelector;
cbContext; // callback context
listContext: SelectorListContext;
constructor(selector:CssSelector, cbContext) {
constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) {
this.selector = selector;
this.notSelector = selector.notSelector;
this.cbContext = cbContext;
this.listContext = listContext;
}
finalize(cssSelector: CssSelector, callback) {
var result = true;
if (isPresent(this.notSelector)) {
if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = new SelectorMatcher();
notMatcher.addSelectable(this.notSelector, null);
notMatcher.addSelectable(this.notSelector, null, null);
result = !notMatcher.match(cssSelector, null);
}
if (result && isPresent(callback)) {
if (result && isPresent(callback) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
if (isPresent(this.listContext)) {
this.listContext.alreadyMatched = true;
}
callback(this.selector, this.cbContext);
}
return result;

View File

@ -512,11 +512,13 @@ var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, '
var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
var _shadowDOMSelectorsRe = [
RegExpWrapper.create('/shadow/'),
RegExpWrapper.create('/shadow-deep/'),
RegExpWrapper.create('>>>'),
RegExpWrapper.create('::shadow'),
RegExpWrapper.create('/deep/'),
RegExpWrapper.create('::content'),
// Deprecated selectors
RegExpWrapper.create('/deep/'), // former >>>
RegExpWrapper.create('/shadow-deep/'), // former /deep/
RegExpWrapper.create('/shadow/'), // former ::shadow
];
var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');

View File

@ -3,13 +3,13 @@ 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) {
export function dashCaseToCamelCase(input:string): string {
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP, (m) => {
return m[1].toUpperCase();
});
}
export function camelCaseToDashCase(input:string) {
export function camelCaseToDashCase(input:string): string {
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => {
return '-' + m[1].toLowerCase();
});

View File

@ -2,10 +2,9 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {Promise} from 'angular2/src/facade/async';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector,
ChangeRecord, BindingRecord, uninitialized} from 'angular2/change_detection';
ChangeRecord, BindingRecord, BindingPropagationConfig, 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';
@ -237,6 +236,11 @@ export class View {
}
}
onAllChangesDone(directiveMemento) {
var dir = directiveMemento.directive(this.elementInjectors);
dir.onAllChangesDone();
}
_invokeMementos(records:List) {
for(var i = 0; i < records.length; ++i) {
this._invokeMementoFor(records[i]);
@ -304,6 +308,9 @@ export class ProtoView {
parentProtoView:ProtoView;
_variableBindings:List;
_directiveMementosMap:Map;
_directiveMementos:List;
constructor(
template,
protoChangeDetector:ProtoChangeDetector,
@ -325,7 +332,9 @@ export class ProtoView {
this.stylePromises = [];
this.eventHandlers = [];
this.bindingRecords = [];
this._directiveMementosMap = MapWrapper.create();
this._variableBindings = null;
this._directiveMementos = null;
}
// TODO(rado): hostElementInjector should be moved to hydrate phase.
@ -341,6 +350,7 @@ export class ProtoView {
}
}
//TODO: Tobias or Victor. Moving it into the constructor.
// this work should be done the constructor of ProtoView once we separate
// ProtoView and ProtoViewBuilder
_getVariableBindings() {
@ -358,6 +368,28 @@ export class ProtoView {
return this._variableBindings;
}
//TODO: Tobias or Victor. Moving it into the constructor.
// this work should be done the constructor of ProtoView once we separate
// ProtoView and ProtoViewBuilder
_getDirectiveMementos() {
if (isPresent(this._directiveMementos)) {
return this._directiveMementos;
}
this._directiveMementos = [];
for (var injectorIndex = 0; injectorIndex < this.elementBinders.length; ++injectorIndex) {
var pei = this.elementBinders[injectorIndex].protoElementInjector;
if (isPresent(pei)) {
for (var directiveIndex = 0; directiveIndex < pei.numberOfDirectives; ++directiveIndex) {
ListWrapper.push(this._directiveMementos, this._getDirectiveMemento(injectorIndex, directiveIndex));
}
}
}
return this._directiveMementos;
}
_instantiate(hostElementInjector: ElementInjector, eventManager: EventManager): View {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.importIntoDoc(this.element);
var elementsWithBindingsDynamic;
@ -386,7 +418,9 @@ export class ProtoView {
}
var view = new View(this, viewNodes, this.protoLocals);
var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords, this._getVariableBindings());
var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords,
this._getVariableBindings(), this._getDirectiveMementos());
var binders = this.elementBinders;
var elementInjectors = ListWrapper.createFixedSize(binders.length);
var eventHandlers = ListWrapper.createFixedSize(binders.length);
@ -449,7 +483,7 @@ export class ProtoView {
lightDom = strategy.constructLightDom(view, childView, element);
strategy.attachTemplate(element, childView);
bindingPropagationConfig = new BindingPropagationConfig(changeDetector);
bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector);
ListWrapper.push(componentChildViews, childView);
}
@ -611,15 +645,29 @@ export class ProtoView {
setterName:string,
setter:SetterFn) {
var elementIndex = this.elementBinders.length-1;
var bindingMemento = new DirectiveBindingMemento(
this.elementBinders.length-1,
elementIndex,
directiveIndex,
setterName,
setter
);
var directiveMemento = DirectiveMemento.get(bindingMemento);
var directiveMemento = this._getDirectiveMemento(elementIndex, directiveIndex);
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, bindingMemento, directiveMemento));
}
_getDirectiveMemento(elementInjectorIndex:number, directiveIndex:number) {
var id = elementInjectorIndex * 100 + directiveIndex;
var protoElementInjector = this.elementBinders[elementInjectorIndex].protoElementInjector;
if (!MapWrapper.contains(this._directiveMementosMap, id)) {
var binding = protoElementInjector.getDirectiveBindingAtIndex(directiveIndex);
MapWrapper.set(this._directiveMementosMap, id,
new DirectiveMemento(elementInjectorIndex, directiveIndex, binding.callOnAllChangesDone));
}
return MapWrapper.get(this._directiveMementosMap, id);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView.
@ -689,26 +737,15 @@ export class DirectiveBindingMemento {
}
}
var _directiveMementos = MapWrapper.create();
class DirectiveMemento {
_elementInjectorIndex:number;
_directiveIndex:number;
notifyOnAllChangesDone:boolean;
constructor(elementInjectorIndex:number, directiveIndex:number) {
constructor(elementInjectorIndex:number, directiveIndex:number, notifyOnAllChangesDone:boolean) {
this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex;
}
static get(memento:DirectiveBindingMemento) {
var elementInjectorIndex = memento._elementInjectorIndex;
var directiveIndex = memento._directiveIndex;
var id = elementInjectorIndex * 100 + directiveIndex;
if (!MapWrapper.contains(_directiveMementos, id)) {
MapWrapper.set(_directiveMementos, id, new DirectiveMemento(elementInjectorIndex, directiveIndex));
}
return MapWrapper.get(_directiveMementos, id);
this.notifyOnAllChangesDone = notifyOnAllChangesDone;
}
directive(elementInjectors:List<ElementInjector>) {

View File

@ -130,7 +130,7 @@ export class ViewContainer {
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
ViewContainer.removeViewNodes(detachedView);
} else {
this._lightDom.redistribute();
}
@ -173,8 +173,11 @@ export class ViewContainer {
}
}
static removeViewNodesFromParent(parent, view) {
for (var i = view.nodes.length - 1; i >= 0; --i) {
static removeViewNodes(view) {
var len = view.nodes.length;
if (len == 0) return;
var parent = view.nodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.nodes[i]);
}
}

View File

@ -136,6 +136,8 @@ function _extractToken(typeOrFunc, annotations) {
} else if (paramAnnotation instanceof DependencyAnnotation) {
ListWrapper.push(depProps, paramAnnotation);
} else if (paramAnnotation.name === "string") {
token = paramAnnotation;
}
}

View File

@ -5,15 +5,14 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
@Viewport({
selector: '[foreach][in]',
selector: '[for][of]',
bind: {
'iterableChanges': 'in | iterableDiff'
'iterableChanges': 'of | iterableDiff'
}
})
export class Foreach {
export class For {
viewContainer: ViewContainer;
constructor(viewContainer:ViewContainer) {
super();
this.viewContainer = viewContainer;
}
@ -34,13 +33,13 @@ export class Foreach {
(movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null))
);
var insertTuples = Foreach.bulkRemove(recordViewTuples, this.viewContainer);
var insertTuples = For.bulkRemove(recordViewTuples, this.viewContainer);
changes.forEachAddedItem(
(addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null))
);
Foreach.bulkInsert(insertTuples, this.viewContainer);
For.bulkInsert(insertTuples, this.viewContainer);
for (var i = 0; i < insertTuples.length; i++) {
this.perViewChange(insertTuples[i].view, insertTuples[i].record);

View File

@ -1,8 +1,8 @@
library angular2.dom.html5adapter;
library angular2.dom.htmlAdapter;
import 'dom_adapter.dart';
import 'package:html5lib/parser.dart' as parser;
import 'package:html5lib/dom.dart';
import 'package:html/parser.dart' as parser;
import 'package:html/dom.dart';
class Html5LibDomAdapter implements DomAdapter {
static void makeCurrent() {

View File

@ -52,7 +52,7 @@ export class Parse5DomAdapter extends DomAdapter {
}
};
var matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector));
matcher.addSelectables(CssSelector.parse(selector));
_recursive(res, el, selector, matcher);
return res;
}
@ -64,7 +64,7 @@ export class Parse5DomAdapter extends DomAdapter {
var result = false;
if (matcher == null) {
matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector));
matcher.addSelectables(CssSelector.parse(selector));
}
var cssSelector = new CssSelector();

View File

@ -1,7 +1,7 @@
library angular.core.facade.async;
import 'dart:async';
export 'dart:async' show Future;
export 'dart:async' show Future, Stream, StreamController, StreamSubscription;
class PromiseWrapper {
static Future resolve(obj) => new Future.value(obj);
@ -32,6 +32,32 @@ class PromiseWrapper {
}
}
class ObservableWrapper {
static StreamSubscription subscribe(Stream s, Function onNext, [onError, onComplete]) {
return s.listen(onNext, onError: onError, onDone: onComplete, cancelOnError: true);
}
static StreamController createController() {
return new StreamController.broadcast();
}
static Stream createObservable(StreamController controller) {
return controller.stream;
}
static void callNext(StreamController controller, value) {
controller.add(value);
}
static void callThrow(StreamController controller, error) {
controller.addError(error);
}
static void callReturn(StreamController controller) {
controller.close();
}
}
class _Completer {
final Completer c;

View File

@ -1,5 +1,6 @@
import {int, global} from 'angular2/src/facade/lang';
import {int, global, isPresent} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
import Rx from 'rx/dist/rx.all';
export var Promise = global.Promise;
@ -51,3 +52,47 @@ export class PromiseWrapper {
return maybePromise instanceof Promise;
}
}
/**
* Use Rx.Observable but provides an adapter to make it work as specified here:
* https://github.com/jhusain/observable-spec
*
* Once a reference implementation of the spec is available, switch to it.
*/
export var Observable = Rx.Observable;
export var ObservableController = Rx.Subject;
export class ObservableWrapper {
static createController():Rx.Subject {
return new Rx.Subject();
}
static createObservable(subject:Rx.Subject):Observable {
return subject;
}
static subscribe(observable:Observable, generatorOrOnNext, onThrow = null, onReturn = null) {
if (isPresent(generatorOrOnNext.next)) {
return observable.observeOn(Rx.Scheduler.timeout).subscribe(
(value) => generatorOrOnNext.next(value),
(error) => generatorOrOnNext.throw(error),
() => generatorOrOnNext.return()
);
} else {
return observable.observeOn(Rx.Scheduler.timeout).subscribe(generatorOrOnNext, onThrow, onReturn);
}
}
static callNext(subject:Rx.Subject, value:any) {
subject.onNext(value);
}
static callThrow(subject:Rx.Subject, error:any) {
subject.onError(error);
}
static callReturn(subject:Rx.Subject) {
subject.onCompleted();
}
}

View File

@ -1,58 +1,55 @@
import {Template, Component, Decorator, NgElement, Ancestor, onChange} from 'angular2/angular2';
import {Template, Component, Decorator, Ancestor, onChange, PropertySetter} from 'angular2/angular2';
import {Optional} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang';
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {ControlGroup, Control} from './model';
import * as validators from './validators';
import {Validators} from './validators';
@CONST()
export class ControlValueAccessor {
readValue(el){}
writeValue(el, value):void {}
}
//export interface ControlValueAccessor {
// writeValue(value):void{}
// set onChange(fn){}
//}
@CONST()
class DefaultControlValueAccessor extends ControlValueAccessor {
constructor() {
super();
@Decorator({
selector: '[control]',
events: {
'change' : 'onChange($event.target.value)',
'input' : 'onChange($event.target.value)'
}
})
export class DefaultValueAccessor {
_setValueProperty:Function;
onChange:Function;
constructor(@PropertySetter('value') setValueProperty:Function) {
this._setValueProperty = setValueProperty;
this.onChange = (_) => {};
}
readValue(el) {
return DOM.getValue(el);
}
writeValue(el, value):void {
DOM.setValue(el,value);
writeValue(value) {
this._setValueProperty(value);
}
}
@CONST()
class CheckboxControlValueAccessor extends ControlValueAccessor {
constructor() {
super();
@Decorator({
selector: 'input[type=checkbox]', //should be input[type=checkbox][control]
// change the selector once https://github.com/angular/angular/issues/1025 is fixed
events: {
'change' : 'onChange($event.target.checked)'
}
})
export class CheckboxControlValueAccessor {
_setCheckedProperty:Function;
onChange:Function;
constructor(cd:ControlDirective, @PropertySetter('checked') setCheckedProperty:Function) {
this._setCheckedProperty = setCheckedProperty;
this.onChange = (_) => {};
cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
}
readValue(el):boolean {
return DOM.getChecked(el);
}
writeValue(el, value:boolean):void {
DOM.setChecked(el, value);
}
}
var controlValueAccessors = {
"checkbox" : new CheckboxControlValueAccessor(),
"text" : new DefaultControlValueAccessor()
};
function controlValueAccessorFor(controlType:string):ControlValueAccessor {
var accessor = StringMapWrapper.get(controlValueAccessors, controlType);
if (isPresent(accessor)) {
return accessor;
} else {
return StringMapWrapper.get(controlValueAccessors, "text");
writeValue(value) {
this._setCheckedProperty(value);
}
}
@ -60,26 +57,22 @@ function controlValueAccessorFor(controlType:string):ControlValueAccessor {
lifecycle: [onChange],
selector: '[control]',
bind: {
'controlName' : 'control',
'type' : 'type'
'controlOrName' : 'control'
}
})
export class ControlDirective {
_groupDirective:ControlGroupDirective;
_el:NgElement;
controlName:string;
type:string;
valueAccessor:ControlValueAccessor;
controlOrName:any;
valueAccessor:any; //ControlValueAccessor
validator:Function;
constructor(@Ancestor() groupDirective:ControlGroupDirective, el:NgElement) {
constructor(@Optional() @Ancestor() groupDirective:ControlGroupDirective, valueAccessor:DefaultValueAccessor) {
this._groupDirective = groupDirective;
this._el = el;
this.controlName = null;
this.type = null;
this.validator = validators.nullValidator;
this.controlOrName = null;
this.valueAccessor = valueAccessor;
this.validator = Validators.nullValidator;
}
// TODO: vsavkin this should be moved into the constructor once static bindings
@ -89,29 +82,31 @@ export class ControlDirective {
}
_initialize() {
this._groupDirective.addDirective(this);
var c = this._control();
c.validator = validators.compose([c.validator, this.validator]);
if (isBlank(this.valueAccessor)) {
this.valueAccessor = controlValueAccessorFor(this.type);
if(isPresent(this._groupDirective)) {
this._groupDirective.addDirective(this);
}
var c = this._control();
c.validator = Validators.compose([c.validator, this.validator]);
this._updateDomValue();
DOM.on(this._el.domElement, "change", (_) => this._updateControlValue());
this._setUpUpdateControlValue();
}
_updateDomValue() {
this.valueAccessor.writeValue(this._el.domElement, this._control().value);
this.valueAccessor.writeValue(this._control().value);
}
_updateControlValue() {
this._control().updateValue(this.valueAccessor.readValue(this._el.domElement));
_setUpUpdateControlValue() {
this.valueAccessor.onChange = (newValue) => this._control().updateValue(newValue);
}
_control() {
return this._groupDirective.findControl(this.controlName);
if (isString(this.controlOrName)) {
return this._groupDirective.findControl(this.controlOrName);
} else {
return this.controlOrName;
}
}
}
@ -129,7 +124,6 @@ export class ControlGroupDirective {
_directives:List<ControlDirective>;
constructor(@Optional() @Ancestor() groupDirective:ControlGroupDirective) {
super();
this._groupDirective = groupDirective;
this._directives = ListWrapper.create();
}
@ -165,5 +159,5 @@ export class ControlGroupDirective {
}
export var FormDirectives = [
ControlGroupDirective, ControlDirective
ControlGroupDirective, ControlDirective, CheckboxControlValueAccessor, DefaultValueAccessor
];

View File

@ -1,4 +1,4 @@
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {isPresent} from 'angular2/src/facade/lang';
import * as modelModule from './model';
@ -24,6 +24,15 @@ export class FormBuilder {
}
}
array(controlsConfig:List, validator:Function = null):modelModule.ControlArray {
var controls = ListWrapper.map(controlsConfig, (c) => this._createControl(c));
if (isPresent(validator)) {
return new modelModule.ControlArray(controls, validator);
} else {
return new modelModule.ControlArray(controls);
}
}
_reduceControls(controlsConfig) {
var controls = {};
StringMapWrapper.forEach(controlsConfig, (controlConfig, controlName) => {
@ -33,7 +42,9 @@ export class FormBuilder {
}
_createControl(controlConfig) {
if (controlConfig instanceof modelModule.Control || controlConfig instanceof modelModule.ControlGroup) {
if (controlConfig instanceof modelModule.Control ||
controlConfig instanceof modelModule.ControlGroup ||
controlConfig instanceof modelModule.ControlArray) {
return controlConfig;
} else if (ListWrapper.isList(controlConfig)) {

View File

@ -1,6 +1,7 @@
import {isPresent} from 'angular2/src/facade/lang';
import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {nullValidator, controlGroupValidator} from './validators';
import {Observable, ObservableController, ObservableWrapper} from 'angular2/src/facade/async';
import {StringMap, StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {Validators} from './validators';
export const VALID = "VALID";
export const INVALID = "INVALID";
@ -20,44 +21,40 @@ export const INVALID = "INVALID";
export class AbstractControl {
_value:any;
_status:string;
_errors;
_updateNeeded:boolean;
_errors:StringMap;
_pristine:boolean;
_parent:ControlGroup;
_parent:any; /* ControlGroup | ControlArray */
validator:Function;
constructor(validator:Function = nullValidator) {
valueChanges:Observable;
_valueChangesController:ObservableController;
constructor(validator:Function) {
this.validator = validator;
this._updateNeeded = true;
this._pristine = true;
}
get value() {
this._updateIfNeeded();
get value():any {
return this._value;
}
get status() {
this._updateIfNeeded();
get status():string {
return this._status;
}
get valid() {
this._updateIfNeeded();
get valid():boolean {
return this._status === VALID;
}
get errors() {
this._updateIfNeeded();
get errors():StringMap {
return this._errors;
}
get pristine() {
this._updateIfNeeded();
get pristine():boolean {
return this._pristine;
}
get dirty() {
get dirty():boolean {
return ! this.pristine;
}
@ -65,60 +62,65 @@ export class AbstractControl {
this._parent = parent;
}
_updateIfNeeded() {
}
_updateParent() {
if (isPresent(this._parent)){
this._parent._controlChanged();
this._parent._updateValue();
}
}
}
export class Control extends AbstractControl {
constructor(value:any, validator:Function = nullValidator) {
constructor(value:any, validator:Function = Validators.nullValidator) {
super(validator);
this._value = value;
this._setValueErrorsStatus(value);
this._valueChangesController = ObservableWrapper.createController();
this.valueChanges = ObservableWrapper.createObservable(this._valueChangesController);
}
updateValue(value:any) {
this._value = value;
this._updateNeeded = true;
updateValue(value:any):void {
this._setValueErrorsStatus(value);
this._pristine = false;
ObservableWrapper.callNext(this._valueChangesController, this._value);
this._updateParent();
}
_updateIfNeeded() {
if (this._updateNeeded) {
this._updateNeeded = false;
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
_setValueErrorsStatus(value) {
this._value = value;
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
}
export class ControlGroup extends AbstractControl {
controls;
optionals;
controls:StringMap;
_optionals:StringMap;
constructor(controls, optionals = null, validator:Function = controlGroupValidator) {
constructor(controls:StringMap, optionals:StringMap = null, validator:Function = Validators.group) {
super(validator);
this.controls = controls;
this.optionals = isPresent(optionals) ? optionals : {};
this._optionals = isPresent(optionals) ? optionals : {};
this._valueChangesController = ObservableWrapper.createController();
this.valueChanges = ObservableWrapper.createObservable(this._valueChangesController);
this._setParentForControls();
this._setValueErrorsStatus();
}
include(controlName:string) {
this._updateNeeded = true;
StringMapWrapper.set(this.optionals, controlName, true);
include(controlName:string):void {
StringMapWrapper.set(this._optionals, controlName, true);
this._updateValue();
}
exclude(controlName:string) {
this._updateNeeded = true;
StringMapWrapper.set(this.optionals, controlName, false);
exclude(controlName:string):void {
StringMapWrapper.set(this._optionals, controlName, false);
this._updateValue();
}
contains(controlName:string) {
contains(controlName:string):boolean {
var c = StringMapWrapper.contains(this.controls, controlName);
return c && this._included(controlName);
}
@ -129,14 +131,19 @@ export class ControlGroup extends AbstractControl {
});
}
_updateIfNeeded() {
if (this._updateNeeded) {
this._updateNeeded = false;
this._value = this._reduceValue();
this._pristine = this._reducePristine();
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
_updateValue() {
this._setValueErrorsStatus();
this._pristine = false;
ObservableWrapper.callNext(this._valueChangesController, this._value);
this._updateParent();
}
_setValueErrorsStatus() {
this._value = this._reduceValue();
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
_reduceValue() {
@ -146,12 +153,7 @@ export class ControlGroup extends AbstractControl {
});
}
_reducePristine() {
return this._reduceChildren(true,
(acc, control, name) => acc && control.pristine);
}
_reduceChildren(initValue, fn:Function) {
_reduceChildren(initValue:any, fn:Function) {
var res = initValue;
StringMapWrapper.forEach(this.controls, (control, name) => {
if (this._included(name)) {
@ -161,13 +163,69 @@ export class ControlGroup extends AbstractControl {
return res;
}
_controlChanged() {
this._updateNeeded = true;
_included(controlName:string):boolean {
var isOptional = StringMapWrapper.contains(this._optionals, controlName);
return !isOptional || StringMapWrapper.get(this._optionals, controlName);
}
}
export class ControlArray extends AbstractControl {
controls:List;
constructor(controls:List<AbstractControl>, validator:Function = Validators.array) {
super(validator);
this.controls = controls;
this._valueChangesController = ObservableWrapper.createController();
this.valueChanges = ObservableWrapper.createObservable(this._valueChangesController);
this._setParentForControls();
this._setValueErrorsStatus();
}
at(index:number):AbstractControl {
return this.controls[index];
}
push(control:AbstractControl):void {
ListWrapper.push(this.controls, control);
control.setParent(this);
this._updateValue();
}
insert(index:number, control:AbstractControl):void {
ListWrapper.insert(this.controls, index, control);
control.setParent(this);
this._updateValue();
}
removeAt(index:number):void {
ListWrapper.removeAt(this.controls, index);
this._updateValue();
}
get length():number {
return this.controls.length;
}
_updateValue() {
this._setValueErrorsStatus();
this._pristine = false;
ObservableWrapper.callNext(this._valueChangesController, this._value);
this._updateParent();
}
_included(controlName:string):boolean {
var isOptional = StringMapWrapper.contains(this.optionals, controlName);
return !isOptional || StringMapWrapper.get(this.optionals, controlName);
_setParentForControls() {
ListWrapper.forEach(this.controls, (control) => {
control.setParent(this);
});
}
}
_setValueErrorsStatus() {
this._value = ListWrapper.map(this.controls, (c) => c.value);
this._errors = this.validator(this);
this._status = isPresent(this._errors) ? INVALID : VALID;
}
}

View File

@ -1,13 +1,12 @@
import {Decorator} from 'angular2/angular2';
import {ControlDirective} from 'angular2/forms';
import * as validators from 'angular2/forms';
import {ControlDirective, Validators} from 'angular2/forms';
@Decorator({
selector: '[required]'
})
export class RequiredValidatorDirective {
constructor(c:ControlDirective) {
c.validator = validators.compose([c.validator, validators.required]);
c.validator = Validators.compose([c.validator, Validators.required]);
}
}

View File

@ -3,35 +3,51 @@ import {List, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collectio
import * as modelModule from './model';
export function required(c:modelModule.Control) {
return isBlank(c.value) || c.value == "" ? {"required" : true} : null;
}
export class Validators {
static required(c:modelModule.Control) {
return isBlank(c.value) || c.value == "" ? {"required": true} : null;
}
export function nullValidator(c:modelModule.Control) {
return null;
}
static nullValidator(c:any) {
return null;
}
export function compose(validators:List<Function>):Function {
return function(c:modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
}, {});
static compose(validators:List<Function>):Function {
return function (c:modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c);
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
}, {});
return StringMapWrapper.isEmpty(res) ? null : res;
}
}
static group(c:modelModule.ControlGroup) {
var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) {
Validators._mergeErrors(control, res);
}
});
return StringMapWrapper.isEmpty(res) ? null : res;
}
}
export function controlGroupValidator(c:modelModule.ControlGroup) {
var res = {};
StringMapWrapper.forEach(c.controls, (control, name) => {
if (c.contains(name) && isPresent(control.errors)) {
StringMapWrapper.forEach(control.errors, (value, error) => {
if (! StringMapWrapper.contains(res, error)) {
res[error] = [];
}
ListWrapper.push(res[error], control);
});
}
});
return StringMapWrapper.isEmpty(res) ? null : res;
static array(c:modelModule.ControlArray) {
var res = {};
ListWrapper.forEach(c.controls, (control) => {
if (isPresent(control.errors)) {
Validators._mergeErrors(control, res);
}
});
return StringMapWrapper.isEmpty(res) ? null : res;
}
static _mergeErrors(control, res) {
StringMapWrapper.forEach(control.errors, (value, error) => {
if (!StringMapWrapper.contains(res, error)) {
res[error] = [];
}
ListWrapper.push(res[error], control);
});
}
}

View File

@ -1,5 +1,5 @@
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
import {document} from 'angular2/src/facade/browser';
import {document, window} from 'angular2/src/facade/browser';
import {NumberWrapper, BaseException, isBlank} from 'angular2/src/facade/lang';
var DOM = new BrowserDomAdapter();
@ -34,4 +34,11 @@ export function bindAction(selector:string, callback:Function) {
DOM.on(el, 'click', function(_) {
callback();
});
}
}
export function microBenchmark(name, iterationCount, callback) {
var durationName = `${name}/${iterationCount}`;
window.console.time(durationName);
callback();
window.console.timeEnd(durationName);
}

View File

@ -23,26 +23,21 @@ function runBenchmark(config) {
return getScaleFactor(browser.params.benchmark.scaling).then(function(scaleFactor) {
var description = {};
var urlParams = [];
var microIterations = config.microIterations || 0;
var params = config.params || [];
if (microIterations) {
params = params.concat([{
name: 'iterations', value: microIterations, scale: 'linear'
}]);
if (config.params) {
config.params.forEach(function(param) {
var name = param.name;
var value = applyScaleFactor(param.value, scaleFactor, param.scale);
urlParams.push(name + '=' + value);
description[name] = value;
});
}
params.forEach(function(param) {
var name = param.name;
var value = applyScaleFactor(param.value, scaleFactor, param.scale);
urlParams.push(name + '=' + value);
description[name] = value;
});
var url = encodeURI(config.url + '?' + urlParams.join('&'));
browser.get(url);
return benchpressRunner.sample({
id: config.id,
execute: config.work,
prepare: config.prepare,
microIterations: microIterations,
microMetrics: config.microMetrics,
bindings: [
benchpress.bind(benchpress.Options.SAMPLE_DESCRIPTION).toValue(description)
]

View File

@ -1,4 +1,4 @@
library angular2.src.transform.bind_generator.generator;
library angular2.transform.bind_generator.generator;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.bind_generator.transformer;
library angular2.transform.bind_generator.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.bind_generator.visitor;
library angular2.transform.bind_generator.visitor;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/logging.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.asset_reader;
library angular2.transform.common.asset_reader;
import 'dart:async';
import 'dart:convert';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.formatter;
library angular2.transform.common.formatter;
import 'package:dart_style/dart_style.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.logging;
library angular2.transform.common.logging;
import 'package:barback/barback.dart';
import 'package:code_transformers/messages/build_logger.dart';
@ -10,6 +10,11 @@ void init(Transform t) {
_logger = new BuildLogger(t);
}
/// Sets [logger] directly. Used for testing - in general use [init].
void setLogger(BuildLogger logger) {
_logger = logger;
}
/// The logger the transformer should use for messaging.
BuildLogger get logger {
if (_logger == null) {

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.names;
library angular2.transform.common.names;
const SETUP_METHOD_NAME = 'setupReflection';
const REFLECTOR_VAR_NAME = 'reflector';

View File

@ -1,35 +0,0 @@
library angular2.src.transform.common.ng_data;
import 'dart:convert';
const NG_DATA_VERSION = 1;
class NgData extends Object {
int importOffset = 0;
int registerOffset = 0;
List<String> imports = [];
NgData();
factory NgData.fromJson(String json) {
var data = JSON.decode(json);
return new NgData()
..importOffset = data['importOffset']
..registerOffset = data['registerOffset']
..imports = data['imports'];
}
String toJson() {
return JSON.encode({
'version': NG_DATA_VERSION,
'importOffset': importOffset,
'registerOffset': registerOffset,
'imports': imports
});
}
@override
String toString() {
return '[NgData: ${toJson()}]';
}
}

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.options;
library angular2.transform.common.options;
const ENTRY_POINT_PARAM = 'entry_point';
const REFLECTION_ENTRY_POINT_PARAM = 'reflection_entry_point';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.parser;
library angular2.transform.common.parser;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common.registered_type;
library angular2.transform.common.registered_type;
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/transform/common/names.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.common;
library angular2.transform.common;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';

View File

@ -1,35 +1,45 @@
library angular2.src.transform.directive_linker.linker;
library angular2.transform.directive_linker.linker;
import 'dart:async';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ngdata.dart';
import 'package:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
Future<String> linkNgDeps(Transform transform, String code, String path) async {
var commentIdx = code.lastIndexOf('//');
if (commentIdx < 0) return code;
Future<String> linkNgDeps(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint);
var ngData = new NgData.fromJson(code.substring(commentIdx + 2));
if (ngDeps == null) return null;
if (ngDeps.imports.isEmpty) return ngDeps.code;
StringBuffer importBuf =
new StringBuffer(code.substring(0, ngData.importOffset));
StringBuffer declarationBuf = new StringBuffer(
code.substring(ngData.importOffset, ngData.registerOffset));
String tail = code.substring(ngData.registerOffset, commentIdx);
var allDeps = ngDeps.imports.toList()..addAll(ngDeps.exports);
var depList = await _processNgImports(
reader, entryPoint, allDeps.map((node) => node.uri.stringValue));
var ngDeps = await _processNgImports(transform, ngData.imports);
if (depList.isEmpty) return ngDeps.code;
for (var i = 0; i < ngDeps.length; ++i) {
importBuf.write('import \'${ngDeps[i]}\' as i${i};');
var importBuf = new StringBuffer();
var declarationBuf = new StringBuffer();
for (var i = 0; i < depList.length; ++i) {
importBuf.write('''
import '${depList[i]}' as i${i};
''');
declarationBuf.write('i${i}.${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME});');
}
return '${importBuf}${declarationBuf}${tail}';
var code = ngDeps.code;
var importSeamIdx = ngDeps.imports.last.end;
var declarationSeamIdx = ngDeps.setupMethod.end - 1;
return '${code.substring(0, importSeamIdx)}'
'$importBuf'
'${code.substring(importSeamIdx, declarationSeamIdx)}'
'$declarationBuf'
'${code.substring(declarationSeamIdx)}';
}
String _toDepsUri(String importUri) =>
@ -40,17 +50,16 @@ bool _isNotDartImport(String importUri) {
}
Future<List<String>> _processNgImports(
Transform transform, List<String> imports) async {
AssetReader reader, AssetId entryPoint, Iterable<String> imports) {
final nullFuture = new Future.value(null);
var retVal = <String>[];
return Future
.wait(imports.where(_isNotDartImport).map(_toDepsUri).map((ngDepsUri) {
var importAsset = uriToAssetId(
transform.primaryInput.id, ngDepsUri, logger, null /* span */);
return transform.hasInput(importAsset).then((hasInput) {
if (hasInput) {
retVal.add(ngDepsUri);
}
var importAsset =
uriToAssetId(entryPoint, ngDepsUri, logger, null /* span */);
if (importAsset == entryPoint) return nullFuture;
return reader.hasInput(importAsset).then((hasInput) {
if (hasInput) retVal.add(ngDepsUri);
});
})).then((_) => retVal);
}

View File

@ -1,7 +1,8 @@
library angular2.src.transform.directive_linker.transformer;
library angular2.transform.directive_linker.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';
@ -27,12 +28,11 @@ class DirectiveLinker extends Transformer {
log.init(transform);
try {
var assetCode = await transform.primaryInput.readAsString();
var assetPath = transform.primaryInput.id.path;
var transformedCode = await linkNgDeps(transform, assetCode, assetPath);
var formattedCode = formatter.format(transformedCode, uri: assetPath);
transform.addOutput(
new Asset.fromString(transform.primaryInput.id, formattedCode));
var assetId = transform.primaryInput.id;
var transformedCode =
await linkNgDeps(new AssetReader.fromTransform(transform), assetId);
var formattedCode = formatter.format(transformedCode, uri: assetId.path);
transform.addOutput(new Asset.fromString(assetId, formattedCode));
} catch (ex, stackTrace) {
log.logger.error('Linking ng directives failed.\n'
'Exception: $ex\n'

View File

@ -1,10 +1,9 @@
library angular2.src.transform.directive_processor;
library angular2.transform.directive_processor;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/ngdata.dart';
import 'package:angular2/src/transform/common/visitor_mixin.dart';
import 'package:path/path.dart' as path;
@ -42,7 +41,6 @@ class CreateNgDepsVisitor extends Object
final FactoryTransformVisitor _factoryVisitor;
final ParameterTransformVisitor _paramsVisitor;
final AnnotationsTransformVisitor _metaVisitor;
final NgData _ngData = new NgData();
/// The path to the file which we are parsing.
final String importPath;
@ -73,19 +71,16 @@ class CreateNgDepsVisitor extends Object
_writeImport();
wroteImport = true;
}
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor);
}
@override
Object visitExportDirective(ExportDirective node) {
_ngData.imports.add(node.uri.stringValue);
return node.accept(_copyVisitor);
}
void _openFunctionWrapper() {
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.importOffset = writer.toString().length;
writer.print('bool _visited = false;'
'void ${SETUP_METHOD_NAME}(${REFLECTOR_VAR_NAME}) {'
'if (_visited) return; _visited = true;');
@ -95,10 +90,7 @@ class CreateNgDepsVisitor extends Object
if (foundNgDirectives) {
writer.print(';');
}
// TODO(kegluneq): Use a [PrintWriter] with a length getter.
_ngData.registerOffset = writer.toString().length;
writer.print('}');
writer.print('// ${_ngData.toJson()}');
}
ConstructorDeclaration _getCtor(ClassDeclaration node) {

View File

@ -1,4 +1,4 @@
library angular2.src.transform.directive_processor.transformer;
library angular2.transform.directive_processor.transformer;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.directive_processor;
library angular2.transform.directive_processor;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/java_core.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'dart:collection' show Queue;
import 'package:analyzer/src/generated/ast.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
@ -55,7 +55,7 @@ abstract class DirectiveRegistry {
const setupReflectionMethodName = 'setupReflection';
const _libraryDeclaration = '''
library angular2.src.transform.generated;
library angular2.transform.generated;
''';
const _reflectorImport = '''

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:barback/barback.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.ast_tester;
library angular2.transform.reflection_remover.ast_tester;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.codegen;
library angular2.transform.reflection_remover.codegen;
import 'package:analyzer/src/generated/ast.dart';
import 'package:barback/barback.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.remove_reflection_capabilities;
library angular2.transform.reflection_remover.remove_reflection_capabilities;
import 'package:analyzer/analyzer.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.rewriter;
library angular2.transform.reflection_remover.rewriter;
import 'package:analyzer/src/generated/ast.dart';
import 'package:angular2/src/transform/common/logging.dart';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.reflection_remover.transformer;
library angular2.transform.reflection_remover.transformer;
import 'dart:async';
import 'package:angular2/src/transform/common/logging.dart' as log;

View File

@ -1,4 +1,4 @@
library angular2.src.transform.template_compiler.generator;
library angular2.transform.template_compiler.generator;
import 'dart:async';

View File

@ -1,4 +1,4 @@
library angular2.src.transform.template_compiler.recording_reflection_capabilities;
library angular2.transform.template_compiler.recording_reflection_capabilities;
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.dart';

View File

@ -1,8 +1,8 @@
library angular2.src.transform.template_compiler.transformer;
library angular2.transform.template_compiler.transformer;
import 'dart:async';
import 'package:angular2/src/dom/html5lib_adapter.dart';
import 'package:angular2/src/dom/html_adapter.dart';
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;

View File

@ -1,4 +1,4 @@
library angular2.src.transform;
library angular2.transform;
import 'package:barback/barback.dart';
import 'package:dart_style/dart_style.dart';

View File

@ -44,7 +44,8 @@ export function main() {
var dispatcher = new TestDispatcher();
var variableBindings = convertLocalsToVariableBindings(locals);
var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast(exp), memo, memo)], variableBindings);
var records = [new BindingRecord(ast(exp), memo, new FakeDirectiveMemento(memo, false))];
var cd = pcd.instantiate(dispatcher, records, variableBindings, []);
cd.hydrate(context, locals);
return {"changeDetector" : cd, "dispatcher" : dispatcher};
@ -56,7 +57,17 @@ export function main() {
return res["dispatcher"].log;
}
function instantiate(protoChangeDetector, dispatcher, bindings) {
return protoChangeDetector.instantiate(dispatcher, bindings, null, []);
}
describe(`${name} change detection`, () => {
var dispatcher;
beforeEach(() => {
dispatcher = new TestDispatcher();
});
it('should do simple watching', () => {
var person = new Person("misko");
var c = createChangeDetector('name', 'name', person);
@ -192,15 +203,14 @@ export function main() {
var pcd = createProtoChangeDetector();
var ast = parser.parseInterpolation("B{{a}}A", "location");
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [new BindingRecord(ast, "memo", "memo")], null);
var cd = instantiate(pcd, dispatcher, [new BindingRecord(ast, "memo", null)]);
cd.hydrate(new TestData("value"), null);
cd.detectChanges();
expect(dispatcher.log).toEqual(["memo=BvalueA"]);
});
describe("change notification", () => {
describe("simple checks", () => {
it("should pass a change record to the dispatcher", () => {
@ -239,15 +249,17 @@ export function main() {
});
describe("group changes", () => {
var dirMemento1 = new FakeDirectiveMemento(1);
var dirMemento2 = new FakeDirectiveMemento(2);
it("should notify the dispatcher when a group of records changes", () => {
var pcd = createProtoChangeDetector();
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [
new BindingRecord(ast("1 + 2"), "memo", "1"),
new BindingRecord(ast("10 + 20"), "memo", "1"),
new BindingRecord(ast("100 + 200"), "memo", "2")
], null);
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("1 + 2"), "memo", dirMemento1),
new BindingRecord(ast("10 + 20"), "memo", dirMemento1),
new BindingRecord(ast("100 + 200"), "memo", dirMemento2)
]);
cd.detectChanges();
@ -256,12 +268,12 @@ export function main() {
it("should notify the dispatcher before switching to the next group", () => {
var pcd = createProtoChangeDetector();
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [
new BindingRecord(ast("a()"), "a", "1"),
new BindingRecord(ast("b()"), "b", "2"),
new BindingRecord(ast("c()"), "c", "2")
], null);
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("a()"), "a", dirMemento1),
new BindingRecord(ast("b()"), "b", dirMemento2),
new BindingRecord(ast("c()"), "c", dirMemento2)
]);
var tr = new TestRecord();
tr.a = () => {
@ -283,17 +295,76 @@ export function main() {
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
});
});
describe("onAllChangesDone", () => {
it("should notify the dispatcher about processing all the children", () => {
var memento1 = new FakeDirectiveMemento(1, false);
var memento2 = new FakeDirectiveMemento(2, true);
var pcd = createProtoChangeDetector();
var cd = pcd.instantiate(dispatcher, [], null, [memento1, memento2]);
cd.hydrate(null, null);
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual([
["onAllChangesDone", memento2]
]);
});
it("should notify in reverse order so the child is always notified before the parent", () => {
var memento1 = new FakeDirectiveMemento(1, true);
var memento2 = new FakeDirectiveMemento(2, true);
var pcd = createProtoChangeDetector();
var cd = pcd.instantiate(dispatcher, [], null, [memento1, memento2]);
cd.hydrate(null, null);
cd.detectChanges();
expect(dispatcher.loggedValues).toEqual([
["onAllChangesDone", memento2],
["onAllChangesDone", memento1]
]);
});
it("should notify the dispatcher before processing shadow dom children", () => {
var memento = new FakeDirectiveMemento(1, true);
var pcd = createProtoChangeDetector();
var shadowDomChildPCD = createProtoChangeDetector();
var parent = pcd.instantiate(dispatcher, [], null, [memento]);
var child = shadowDomChildPCD.instantiate(dispatcher, [
new BindingRecord(ast("1"), "a", memento)], null, []);
parent.addShadowDomChild(child);
parent.hydrate(null, null);
child.hydrate(null, null);
parent.detectChanges();
// loggedValues contains everything that the dispatcher received
// the first value is the directive memento passed into onAllChangesDone
expect(dispatcher.loggedValues).toEqual([
["onAllChangesDone", memento],
[1]
]);
});
});
});
describe("enforce no new changes", () => {
it("should throw when a record gets changed after it has been checked", () => {
var pcd = createProtoChangeDetector();
pcd.addAst(ast("a"), "a", 1);
var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, [
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("a"), "a", 1)
], null);
]);
cd.hydrate(new TestData('value'), null);
expect(() => {
@ -308,7 +379,7 @@ export function main() {
var pcd = createProtoChangeDetector();
var cd = pcd.instantiate(new TestDispatcher(), [
new BindingRecord(ast("invalidProp", "someComponent"), "a", 1)
], null);
], null, []);
cd.hydrate(null, null);
try {
@ -363,24 +434,31 @@ export function main() {
beforeEach(() => {
var protoParent = createProtoChangeDetector();
parent = protoParent.instantiate(null, [], null);
parent = instantiate(protoParent, null, []);
var protoChild = createProtoChangeDetector();
child = protoChild.instantiate(null, [], null);
child = instantiate(protoChild, null, []);
});
it("should add children", () => {
it("should add light dom children", () => {
parent.addChild(child);
expect(parent.children.length).toEqual(1);
expect(parent.children[0]).toBe(child);
expect(parent.lightDomChildren.length).toEqual(1);
expect(parent.lightDomChildren[0]).toBe(child);
});
it("should remove children", () => {
it("should add shadow dom children", () => {
parent.addShadowDomChild(child);
expect(parent.shadowDomChildren.length).toEqual(1);
expect(parent.shadowDomChildren[0]).toBe(child);
});
it("should remove light dom children", () => {
parent.addChild(child);
parent.removeChild(child);
expect(parent.children).toEqual([]);
expect(parent.lightDomChildren).toEqual([]);
});
});
});
@ -409,7 +487,7 @@ export function main() {
});
it("should change CHECK_ONCE to CHECKED", () => {
var cd = createProtoChangeDetector().instantiate(null, [], null);
var cd = instantiate(createProtoChangeDetector(), null, []);
cd.mode = CHECK_ONCE;
cd.detectChanges();
@ -418,7 +496,7 @@ export function main() {
});
it("should not change the CHECK_ALWAYS", () => {
var cd = createProtoChangeDetector().instantiate(null, [], null);
var cd = instantiate(createProtoChangeDetector(), null, []);
cd.mode = CHECK_ALWAYS;
cd.detectChanges();
@ -429,7 +507,7 @@ export function main() {
describe("markPathToRootAsCheckOnce", () => {
function changeDetector(mode, parent) {
var cd = createProtoChangeDetector().instantiate(null, [], null);
var cd = instantiate(createProtoChangeDetector(), null, []);
cd.mode = mode;
if (isPresent(parent)) parent.addChild(cd);
return cd;
@ -535,6 +613,18 @@ export function main() {
expect(pipe.destroyCalled).toEqual(true);
});
it("should inject the binding propagation configuration " +
"of the encompassing component into a pipe", () => {
var registry = new FakePipeRegistry('pipe', () => new IdentityPipe());
var c = createChangeDetector("memo", "name | pipe", new Person('bob'), null, registry);
var cd = c["changeDetector"];
cd.detectChanges();
expect(registry.bpc).toBe(cd.bindingPropagationConfig);
});
});
it("should do nothing when returns NO_CHANGE", () => {
@ -583,7 +673,7 @@ class OncePipe extends Pipe {
constructor() {
super();
this.called = false;;
this.called = false;
this.destroyCalled = false;
}
@ -622,6 +712,7 @@ class FakePipeRegistry extends PipeRegistry {
numberOfLookups:number;
pipeType:string;
factory:Function;
bpc:any;
constructor(pipeType, factory) {
super({});
@ -630,9 +721,10 @@ class FakePipeRegistry extends PipeRegistry {
this.numberOfLookups = 0;
}
get(type:string, obj) {
get(type:string, obj, bpc) {
if (type != this.pipeType) return null;
this.numberOfLookups ++;
this.bpc = bpc;
return this.factory();
}
}
@ -686,16 +778,28 @@ class TestData {
}
}
class FakeDirectiveMemento {
value:any;
notifyOnAllChangesDone:boolean;
constructor(value, notifyOnAllChangesDone:boolean = false) {
this.value = value;
this.notifyOnAllChangesDone = notifyOnAllChangesDone;
}
}
class TestDispatcher extends ChangeDispatcher {
log:List;
loggedValues:List;
changeRecords:List;
loggedOnAllChangesDone:List;
onChange:Function;
constructor() {
super();
this.log = null;
this.loggedValues = null;
this.loggedOnAllChangesDone = null;
this.onChange = (_, __) => {};
this.clear();
}
@ -703,6 +807,7 @@ class TestDispatcher extends ChangeDispatcher {
clear() {
this.log = ListWrapper.create();
this.loggedValues = ListWrapper.create();
this.loggedOnAllChangesDone = ListWrapper.create();
this.changeRecords = ListWrapper.create();
}
@ -710,7 +815,7 @@ class TestDispatcher extends ChangeDispatcher {
ListWrapper.push(this.loggedValues, value);
}
onRecordChange(group, changeRecords:List) {
onRecordChange(directiveMemento, changeRecords:List) {
var value = changeRecords[0].change.currentValue;
var memento = changeRecords[0].bindingMemento;
ListWrapper.push(this.log, memento + '=' + this._asString(value));
@ -720,9 +825,12 @@ class TestDispatcher extends ChangeDispatcher {
ListWrapper.push(this.changeRecords, changeRecords);
this.onChange(group, changeRecords);
this.onChange(directiveMemento, changeRecords);
}
onAllChangesDone(directiveMemento) {
ListWrapper.push(this.loggedValues, ["onAllChangesDone", directiveMemento]);
}
_asString(value) {
return (isBlank(value) ? 'null' : value.toString());

View File

@ -1,11 +1,11 @@
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import {ArrayChanges} from 'angular2/src/change_detection/pipes/array_changes';
import {IterableChanges} from 'angular2/src/change_detection/pipes/iterable_changes';
import {NumberWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {TestIterable} from './iterable';
import {arrayChangesAsString} from './util';
import {iterableChangesAsString} from './util';
// todo(vicb): UnmodifiableListView / frozen object when implemented
export function main() {
@ -15,7 +15,7 @@ export function main() {
var l;
beforeEach(() => {
changes = new ArrayChanges();
changes = new IterableChanges();
});
afterEach(() => {
@ -23,30 +23,30 @@ export function main() {
});
it('should support list and iterables', () => {
expect(ArrayChanges.supportsObj([])).toBeTruthy();
expect(ArrayChanges.supportsObj(new TestIterable())).toBeTruthy();
expect(ArrayChanges.supportsObj(MapWrapper.create())).toBeFalsy();
expect(ArrayChanges.supportsObj(null)).toBeFalsy();
expect(IterableChanges.supportsObj([])).toBeTruthy();
expect(IterableChanges.supportsObj(new TestIterable())).toBeTruthy();
expect(IterableChanges.supportsObj(MapWrapper.create())).toBeFalsy();
expect(IterableChanges.supportsObj(null)).toBeFalsy();
});
it('should support iterables', () => {
l = new TestIterable();
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: []
}));
l.list = [1];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['1[null->0]'],
additions: ['1[null->0]']
}));
l.list = [2, 1];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['2[null->0]', '1[0->1]'],
previous: ['1[0->1]'],
additions: ['2[null->0]'],
@ -57,20 +57,20 @@ export function main() {
it('should detect additions', () => {
l = [];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: []
}));
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a[null->0]'],
additions: ['a[null->0]']
}));
ListWrapper.push(l, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b[null->1]'],
previous: ['a'],
additions: ['b[null->1]']
@ -83,7 +83,7 @@ export function main() {
l = [1, 0];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['1[null->0]', '0[0->1]'],
previous: ['0[0->1]'],
additions: ['1[null->0]'],
@ -92,7 +92,7 @@ export function main() {
l = [2, 1, 0];
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['2[null->0]', '1[0->1]', '0[1->2]'],
previous: ['1[0->1]', '0[1->2]'],
additions: ['2[null->0]'],
@ -108,7 +108,7 @@ export function main() {
ListWrapper.push(l, 2);
ListWrapper.push(l, 1);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['2[1->0]', '1[0->1]'],
previous: ['1[0->1]', '2[1->0]'],
moves: ['2[1->0]', '1[0->1]']
@ -122,7 +122,7 @@ export function main() {
ListWrapper.removeAt(l, 1);
ListWrapper.insert(l, 0, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b[1->0]', 'a[0->1]', 'c'],
previous: ['a[0->1]', 'b[1->0]', 'c'],
moves: ['b[1->0]', 'a[0->1]']
@ -131,7 +131,7 @@ export function main() {
ListWrapper.removeAt(l, 1);
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b', 'c[2->1]', 'a[1->2]'],
previous: ['b', 'a[1->2]', 'c[2->1]'],
moves: ['c[2->1]', 'a[1->2]']
@ -144,14 +144,14 @@ export function main() {
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a[null->0]'],
additions: ['a[null->0]']
}));
ListWrapper.push(l, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b[null->1]'],
previous: ['a'],
additions: ['b[null->1]']
@ -160,7 +160,7 @@ export function main() {
ListWrapper.push(l, 'c');
ListWrapper.push(l, 'd');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b', 'c[null->2]', 'd[null->3]'],
previous: ['a', 'b'],
additions: ['c[null->2]', 'd[null->3]']
@ -168,7 +168,7 @@ export function main() {
ListWrapper.removeAt(l, 2);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b', 'd[3->2]'],
previous: ['a', 'b', 'c[2->null]', 'd[3->2]'],
moves: ['d[3->2]'],
@ -181,7 +181,7 @@ export function main() {
ListWrapper.push(l, 'b');
ListWrapper.push(l, 'a');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['d[2->0]', 'c[null->1]', 'b[1->2]', 'a[0->3]'],
previous: ['a[0->3]', 'b[1->2]', 'd[2->0]'],
additions: ['c[null->1]'],
@ -197,7 +197,7 @@ export function main() {
var oo = 'oo';
ListWrapper.set(l, 1, b + oo);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'boo'],
previous: ['a', 'boo']
}));
@ -207,7 +207,7 @@ export function main() {
l = [NumberWrapper.NaN];
changes.check(l);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: [NumberWrapper.NaN],
previous: [NumberWrapper.NaN]
}));
@ -219,7 +219,7 @@ export function main() {
ListWrapper.insert(l, 0, 'foo');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'],
previous: ['NaN[0->1]', 'NaN[1->2]'],
additions: ['foo[null->0]'],
@ -233,7 +233,7 @@ export function main() {
ListWrapper.removeAt(l, 1);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'c[2->1]'],
previous: ['a', 'b[1->null]', 'c[2->1]'],
moves: ['c[2->1]'],
@ -242,7 +242,7 @@ export function main() {
ListWrapper.insert(l, 1, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'b[null->1]', 'c[1->2]'],
previous: ['a', 'c[1->2]'],
additions: ['b[null->1]'],
@ -256,7 +256,7 @@ export function main() {
ListWrapper.removeAt(l, 0);
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['a', 'a', 'b[3->2]', 'b[4->3]'],
previous: ['a', 'a', 'a[2->null]', 'b[3->2]', 'b[4->3]'],
moves: ['b[3->2]', 'b[4->3]'],
@ -270,7 +270,7 @@ export function main() {
ListWrapper.insert(l, 0, 'b');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'],
previous: ['a[0->1]', 'a[1->2]', 'b[2->0]', 'b'],
additions: ['b[null->4]'],
@ -287,7 +287,7 @@ export function main() {
ListWrapper.push(l, 'a');
ListWrapper.push(l, 'c');
changes.check(l);
expect(changes.toString()).toEqual(arrayChangesAsString({
expect(changes.toString()).toEqual(iterableChangesAsString({
collection: ['b[1->0]', 'a[0->1]', 'c'],
previous: ['a[0->1]', 'b[1->0]', 'c'],
moves: ['b[1->0]', 'a[0->1]']

View File

@ -54,6 +54,24 @@ export function main() {
}));
});
it('should expose previous and current value', () => {
var previous, current;
MapWrapper.set(m, 1, 10);
changes.check(m);
MapWrapper.set(m, 1, 20);
changes.check(m);
changes.forEachChangedItem((record) => {
previous = record.previousValue;
current = record.currentValue;
})
expect(previous).toEqual(10);
expect(current).toEqual(20);
});
it('should do basic map watching', () => {
changes.check(m);

View File

@ -16,12 +16,12 @@ export function main() {
]
});
expect(r.get("type", "some object")).toBe(secondPipe);
expect(r.get("type", "some object", null)).toBe(secondPipe);
});
it("should throw when no matching type", () => {
var r = new PipeRegistry({});
expect(() => r.get("unknown", "some object")).toThrowError(
expect(() => r.get("unknown", "some object", null)).toThrowError(
`Cannot find a pipe for type 'unknown' object 'some object'`
);
});
@ -31,7 +31,7 @@ export function main() {
"type" : []
});
expect(() => r.get("type", "some object")).toThrowError(
expect(() => r.get("type", "some object", null)).toThrowError(
`Cannot find a pipe for type 'type' object 'some object'`
);
});
@ -51,7 +51,7 @@ class PipeFactory {
return this.shouldSupport;
}
create():Pipe {
create(bpc):Pipe {
return this.pipe;
}
}

View File

@ -1,6 +1,6 @@
import {isBlank} from 'angular2/src/facade/lang';
export function arrayChangesAsString({collection, previous, additions, moves, removals}) {
export function iterableChangesAsString({collection, previous, additions, moves, removals}) {
if (isBlank(collection)) collection = [];
if (isBlank(previous)) previous = [];
if (isBlank(additions)) additions = [];

View File

@ -1,6 +1,6 @@
library angular2.compiler.html5lib_dom_adapter.test;
import 'package:angular2/src/dom/html5lib_adapter.dart';
import 'package:angular2/src/dom/html_adapter.dart';
import 'package:angular2/src/test_lib/test_lib.dart' show testSetup;
import 'compiler_common_tests.dart';

Some files were not shown because too many files have changed in this diff Show More