Compare commits
68 Commits
starting
...
2.0.0-alph
Author | SHA1 | Date | |
---|---|---|---|
50f8892c6b | |||
3bfbfa8ae0 | |||
8598c87ef4 | |||
33bfc4c24a | |||
3afb744e77 | |||
e92918bbfe | |||
723e8fde93 | |||
507f7ea70a | |||
6b985d56a5 | |||
c8385ad998 | |||
9d21a6f40d | |||
d304f41197 | |||
8d85b839b6 | |||
dd235f38a3 | |||
5306b6dd0c | |||
b09624024b | |||
edc3709451 | |||
e706f3477b | |||
6298cb3999 | |||
878fce6482 | |||
b02bd65871 | |||
ee36aaf163 | |||
ff84506bd5 | |||
0ae33b7e3c | |||
b1dc6239ef | |||
3ce0f1146f | |||
3ec837bfdb | |||
18ff2be9bb | |||
c0d296334c | |||
9a0a2e319c | |||
a0d86ac2bb | |||
99045b2f6a | |||
c34ca36778 | |||
58dd75a1c8 | |||
f995b07876 | |||
101a4aa3cf | |||
65d759316b | |||
19c1773133 | |||
9b3b3d325f | |||
43f4374944 | |||
81e6d13241 | |||
f8e7a37c0d | |||
c686e7ea30 | |||
7e89af8190 | |||
539e8e2cce | |||
aab084866c | |||
0e61a86763 | |||
1c9938ed98 | |||
47c1a0f381 | |||
514529b5d9 | |||
a12dc7d75a | |||
41b53e71e1 | |||
0fb9f3bd6c | |||
81f3f32217 | |||
b35f288794 | |||
4e82cc0861 | |||
c735644c57 | |||
5d479fa0ae | |||
8baedca972 | |||
02aa8e7945 | |||
ee523efcb4 | |||
eef5f7e06d | |||
83402930f2 | |||
bd48c927d0 | |||
b61b8d60b7 | |||
f1fca5abb6 | |||
045ce3c77a | |||
f822066e2a |
@ -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:
|
||||
|
49
DEVELOPER.md
49
DEVELOPER.md
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
};
|
8
docs/dgeni-package/templates/var.template.html
Normal file
8
docs/dgeni-package/templates/var.template.html
Normal 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 %}
|
@ -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;
|
||||
|
60
gulpfile.js
60
gulpfile.js
@ -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
|
||||
|
@ -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',
|
||||
|
||||
|
6
modules/angular2/change_detection.js
vendored
6
modules/angular2/change_detection.js
vendored
@ -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" : [
|
||||
|
2
modules/angular2/core.js
vendored
2
modules/angular2/core.js
vendored
@ -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';
|
||||
|
2
modules/angular2/directives.js
vendored
2
modules/angular2/directives.js
vendored
@ -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';
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
})
|
||||
|
@ -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) %>
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -1,7 +1,7 @@
|
||||
library change_detectoin.change_detection_jit_generator;
|
||||
|
||||
class ChangeDetectorJITGenerator {
|
||||
ChangeDetectorJITGenerator(typeName, records) {
|
||||
ChangeDetectorJITGenerator(typeName, records, directiveMementos) {
|
||||
}
|
||||
|
||||
generate() {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
@ -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) + ']');
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export class NullPipeFactory {
|
||||
return NullPipe.supportsObj(obj);
|
||||
}
|
||||
|
||||
create():Pipe {
|
||||
create(bpc):Pipe {
|
||||
return new NullPipe();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
540
modules/angular2/src/core/annotations/annotations.js
vendored
540
modules/angular2/src/core/annotations/annotations.js
vendored
@ -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";
|
||||
|
12
modules/angular2/src/core/annotations/di.js
vendored
12
modules/angular2/src/core/annotations/di.js
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
*/
|
||||
|
2
modules/angular2/src/core/application.js
vendored
2
modules/angular2/src/core/application.js
vendored
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -59,6 +59,7 @@ export class ProtoElementInjectorBuilder extends CompileStep {
|
||||
current.inheritedProtoElementInjector.exportImplicitName = exportImplicitName;
|
||||
}
|
||||
}
|
||||
current.inheritedProtoElementInjector.attributes = current.attributes;
|
||||
|
||||
} else {
|
||||
current.inheritedProtoElementInjector = parentProtoElementInjector;
|
||||
|
@ -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,
|
||||
|
125
modules/angular2/src/core/compiler/property_setter_factory.js
vendored
Normal file
125
modules/angular2/src/core/compiler/property_setter_factory.js
vendored
Normal 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;
|
||||
}
|
71
modules/angular2/src/core/compiler/selector.js
vendored
71
modules/angular2/src/core/compiler/selector.js
vendored
@ -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;
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
});
|
77
modules/angular2/src/core/compiler/view.js
vendored
77
modules/angular2/src/core/compiler/view.js
vendored
@ -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>) {
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
2
modules/angular2/src/di/binding.js
vendored
2
modules/angular2/src/di/binding.js
vendored
@ -136,6 +136,8 @@ function _extractToken(typeOrFunc, annotations) {
|
||||
|
||||
} else if (paramAnnotation instanceof DependencyAnnotation) {
|
||||
ListWrapper.push(depProps, paramAnnotation);
|
||||
} else if (paramAnnotation.name === "string") {
|
||||
token = paramAnnotation;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
@ -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() {
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
128
modules/angular2/src/forms/directives.js
vendored
128
modules/angular2/src/forms/directives.js
vendored
@ -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
|
||||
];
|
||||
|
15
modules/angular2/src/forms/form_builder.js
vendored
15
modules/angular2/src/forms/form_builder.js
vendored
@ -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)) {
|
||||
|
186
modules/angular2/src/forms/model.js
vendored
186
modules/angular2/src/forms/model.js
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
68
modules/angular2/src/forms/validators.js
vendored
68
modules/angular2/src/forms/validators.js
vendored
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
11
modules/angular2/src/test_lib/benchmark_util.js
vendored
11
modules/angular2/src/test_lib/benchmark_util.js
vendored
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.bind_generator.generator;
|
||||
library angular2.transform.bind_generator.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.asset_reader;
|
||||
library angular2.transform.common.asset_reader;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.formatter;
|
||||
library angular2.transform.common.formatter;
|
||||
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
|
@ -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()}]';
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.common.parser;
|
||||
library angular2.transform.common.parser;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.directive_processor.transformer;
|
||||
library angular2.transform.directive_processor.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'dart:collection' show Queue;
|
||||
import 'package:analyzer/src/generated/ast.dart';
|
||||
|
@ -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 = '''
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform.template_compiler.generator;
|
||||
library angular2.transform.template_compiler.generator;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
library angular2.src.transform;
|
||||
library angular2.transform;
|
||||
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:dart_style/dart_style.dart';
|
||||
|
@ -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());
|
||||
|
@ -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]']
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 = [];
|
||||
|
@ -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
Reference in New Issue
Block a user