Compare commits
2 Commits
2.0.0-beta
...
2.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
f34fdaf162 | |||
6c951a5e67 |
42
.github/ISSUE_TEMPLATE.md
vendored
42
.github/ISSUE_TEMPLATE.md
vendored
@ -1,16 +1,38 @@
|
||||
**IMPORTANT**: This repository's issues are reserved for feature requests and bug reports. Do not submit support requests here, see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question.
|
||||
**Note: for support questions, please use one of these channels:** https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.
|
||||
|
||||
* **I'm submitting a ... **
|
||||
[ ] bug report
|
||||
[ ] feature request
|
||||
[ ] support request => Please do not submit support request here, see note at the top of this template.
|
||||
|
||||
|
||||
**Steps to reproduce and a minimal demo of the problem**
|
||||
|
||||
_Use https://plnkr.co or similar -- try this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5_
|
||||
|
||||
_What steps should we try in your demo to see the problem?_
|
||||
|
||||
**Current behavior**
|
||||
* **Do you want to request a *feature* or report a *bug*?**
|
||||
|
||||
|
||||
**Expected/desired behavior**
|
||||
|
||||
* **What is the current behavior?**
|
||||
|
||||
|
||||
**Other information**
|
||||
|
||||
* **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** via
|
||||
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
|
||||
|
||||
|
||||
|
||||
* **What is the expected behavior?**
|
||||
|
||||
|
||||
|
||||
* **What is the motivation / use case for changing the behavior?**
|
||||
|
||||
|
||||
|
||||
* **Please tell us about your environment:**
|
||||
|
||||
- Angular version: 2.0.0-beta.X
|
||||
- Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
|
||||
- Language: [all | TypeScript X.X | ES6/7 | ES5 | Dart]
|
||||
|
||||
|
||||
|
||||
* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc)
|
||||
|
109
CHANGELOG.md
109
CHANGELOG.md
@ -1,112 +1,3 @@
|
||||
<a name="2.0.0-beta.13"></a>
|
||||
# 2.0.0-beta.13 (2016-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** MetadataCollector correctly collects property metadata ([111afcd](https://github.com/angular/angular/commit/111afcd)), closes [#7772](https://github.com/angular/angular/issues/7772) [#7773](https://github.com/angular/angular/issues/7773)
|
||||
* **codegen:** stringify using an opaque ID when toString contains parens. ([90c87fa](https://github.com/angular/angular/commit/90c87fa)), closes [#7825](https://github.com/angular/angular/issues/7825)
|
||||
* **ngFor:** give more instructive error when binding to non-iterable ([49527ab](https://github.com/angular/angular/commit/49527ab))
|
||||
* **Router:** handling of special chars in dynamic segments ([0bcfcde](https://github.com/angular/angular/commit/0bcfcde)), closes [#7804](https://github.com/angular/angular/issues/7804)
|
||||
* **upgrade:** make ngUpgrade work with testability API ([430f367](https://github.com/angular/angular/commit/430f367)), closes [#7603](https://github.com/angular/angular/issues/7603)
|
||||
|
||||
### Features
|
||||
|
||||
* **build:** Persisting decorator metadata ([ae876d1](https://github.com/angular/angular/commit/ae876d1))
|
||||
* **compiler:** assert that Component.style is an array ([6de68e2](https://github.com/angular/angular/commit/6de68e2)), closes [#7559](https://github.com/angular/angular/issues/7559)
|
||||
* **compiler:** Resolvers now use DI to create reflector ([506f4ce](https://github.com/angular/angular/commit/506f4ce)), closes [#7762](https://github.com/angular/angular/issues/7762)
|
||||
* **Compiler:** Allow overriding the projection selector ([aa966f5](https://github.com/angular/angular/commit/aa966f5)), closes [#6303](https://github.com/angular/angular/issues/6303) [#7742](https://github.com/angular/angular/issues/7742)
|
||||
* **dart:** Add a dev-mode check for undeclared lifecycle interfaces ([1c20a62](https://github.com/angular/angular/commit/1c20a62)), closes [#6849](https://github.com/angular/angular/issues/6849)
|
||||
* **facade:** add ListWrapper.flatten ([a1880c3](https://github.com/angular/angular/commit/a1880c3))
|
||||
* **facade:** add RegExpWrapper.replaceAll to replace all matches using the provided function ([91999e0](https://github.com/angular/angular/commit/91999e0))
|
||||
* **html_parser:** change HtmlElementAst to store both the start and the end positions ([17c8ec8](https://github.com/angular/angular/commit/17c8ec8))
|
||||
* **i18n:** implement an i18n-aware html parser ([d272f96](https://github.com/angular/angular/commit/d272f96)), closes [#7738](https://github.com/angular/angular/issues/7738)
|
||||
* **i18n:** implement xmb deserialization ([d7e1175](https://github.com/angular/angular/commit/d7e1175))
|
||||
* **i18n:** reexport I18nHtmlParser through the i18n barrel ([d2ca7d8](https://github.com/angular/angular/commit/d2ca7d8))
|
||||
* **i18n:** update I18nHtmlParser to accept parsed messages ([756121a](https://github.com/angular/angular/commit/756121a))
|
||||
* **i18n:** update transformers to read a xmb file when provided and use I18nHtmlParser in t ([8430927](https://github.com/angular/angular/commit/8430927)), closes [#7790](https://github.com/angular/angular/issues/7790)
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* For static content projection, elements with *-directives are now matched against the element itself vs the template before.
|
||||
<p *ngIf="condition" foo></p>
|
||||
Before:
|
||||
// Use the implicit template for projection
|
||||
<ng-content select="template"></ng-content>
|
||||
After:
|
||||
// Use the actual element for projection
|
||||
<ng-content select="p[foo]"></ng-content>
|
||||
|
||||
|
||||
<a name="2.0.0-beta.12"></a>
|
||||
# 2.0.0-beta.12 (2016-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **angular_1_router:** ng-link is generating wrong hrefs ([69c1405](https://github.com/angular/angular/commit/69c1405)), closes [#7423](https://github.com/angular/angular/issues/7423)
|
||||
* **angular1_router:** support link generation with custom hashPrefixes ([0f8efce](https://github.com/angular/angular/commit/0f8efce))
|
||||
* **package.json:** remove es6-promise from the peerDependency list ([8b67b07](https://github.com/angular/angular/commit/8b67b07))
|
||||
|
||||
### Features
|
||||
|
||||
* **dart/transform:** Use angular2/platform/browser as bootstrap lib ([b6507e3](https://github.com/angular/angular/commit/b6507e3)), closes [#7647](https://github.com/angular/angular/issues/7647)
|
||||
|
||||
|
||||
<a name="2.0.0-beta.11"></a>
|
||||
# 2.0.0-beta.11 (2016-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make sure that Zone does not show up in angular2.d.ts ([d4e9b55](https://github.com/angular/angular/commit/d4e9b55fb69d87f948d02905d34fc78221adb11a))
|
||||
* **common:** remove @internal annotation on SwitchView ([967ae3e](https://github.com/angular/angular/commit/967ae3e)), closes [#7657](https://github.com/angular/angular/issues/7657)
|
||||
* **router:** RouterOutlet loads component twice in a race condition ([2f581ff](https://github.com/angular/angular/commit/2f581ff)), closes [#7497](https://github.com/angular/angular/issues/7497) [#7545](https://github.com/angular/angular/issues/7545)
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** add a simple dart script extracting all i18n messages from a package ([8326ab3](https://github.com/angular/angular/commit/8326ab3)), closes [#7620](https://github.com/angular/angular/issues/7620)
|
||||
* **i18n:** create i18n barrel ([a7fe983](https://github.com/angular/angular/commit/a7fe983))
|
||||
* **i18n:** implement xmb serializer ([e1f8e54](https://github.com/angular/angular/commit/e1f8e54))
|
||||
|
||||
|
||||
<a name="2.0.0-beta.10"></a>
|
||||
# 2.0.0-beta.10 (2016-03-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **change_detection:** fix a memory leak ([128acbb](https://github.com/angular/angular/commit/128acbb))
|
||||
* **closure:** don't throw from top-level ([5824866](https://github.com/angular/angular/commit/5824866))
|
||||
* **router:** handle URL that does not match a route ([8e3e450](https://github.com/angular/angular/commit/8e3e450)), closes [#7349](https://github.com/angular/angular/issues/7349) [#7203](https://github.com/angular/angular/issues/7203)
|
||||
* **router/instruction:** ensure toLinkUrl includes extra params ([0d58b13](https://github.com/angular/angular/commit/0d58b13)), closes [#7367](https://github.com/angular/angular/issues/7367)
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** change html parser to preserve comments ([70d18b5](https://github.com/angular/angular/commit/70d18b5))
|
||||
* **core:** introduce a CSS lexer/parser ([b72bab4](https://github.com/angular/angular/commit/b72bab4))
|
||||
* **core:** introduce a CSS lexer/parser ([293fa55](https://github.com/angular/angular/commit/293fa55))
|
||||
* **facade:** add .values to StringMapWrapper ([f1796d6](https://github.com/angular/angular/commit/f1796d6))
|
||||
* **i18n:** add ngPlural directive ([df1f78e](https://github.com/angular/angular/commit/df1f78e))
|
||||
* **i18n:** implement a simple version of message extractor ([095db67](https://github.com/angular/angular/commit/095db67)), closes [#7454](https://github.com/angular/angular/issues/7454)
|
||||
* **shadow_css:** support `/deep/` and `>>>` ([cb38d72](https://github.com/angular/angular/commit/cb38d72)), closes [#7562](https://github.com/angular/angular/issues/7562) [#7563](https://github.com/angular/angular/issues/7563)
|
||||
* **TAG_DEFINITIONS:** include <meta> and <base> ([2c7c3e3](https://github.com/angular/angular/commit/2c7c3e3)), closes [#7455](https://github.com/angular/angular/issues/7455)
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
Removed deprecated API from NgZone
|
||||
- `NgZone.overrideOnTurnStart`
|
||||
- `NgZone.overrideOnTurnDone`
|
||||
- `NgZone.overrideOnEventDone`
|
||||
- `NgZone.overrideOnErrorHandler`
|
||||
|
||||
Rename NgZone API
|
||||
- `NgZone.onTurnStart` => `NgZone.onUnstable`
|
||||
- `NgZone.onTurnDone` => `NgZone.onMicrotaskEmpty`
|
||||
- `NgZone.onEventDone` => `NgZone.onStable`
|
||||
|
||||
|
||||
<a name="2.0.0-beta.9"></a>
|
||||
# 2.0.0-beta.9 (2016-03-09)
|
||||
|
||||
|
@ -117,7 +117,7 @@ speed things up is to use plain class fields in your expressions and avoid any
|
||||
kinds of computation. Example:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
@View({
|
||||
template: '<button [enabled]="isEnabled">{{title}}</button>'
|
||||
})
|
||||
class FancyButton {
|
||||
|
@ -4,9 +4,7 @@
|
||||
var CIconfiguration = {
|
||||
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
// FirefoxBeta should be required:true
|
||||
// https://github.com/angular/angular/issues/7560
|
||||
'FirefoxBeta': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: false}},
|
||||
'FirefoxBeta': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
|
||||
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
|
||||
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
|
||||
'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
|
148
gulpfile.js
148
gulpfile.js
@ -40,9 +40,9 @@ if (cliArgs.projects) {
|
||||
cliArgs.projects.split(',').sort().join(',');
|
||||
}
|
||||
|
||||
// --projects=angular2 => {angular2: true}
|
||||
// --projects=angular2,angular2_material => {angular2: true, angular2_material: true}
|
||||
var allProjects =
|
||||
'angular1_router,angular2,benchmarks,benchmarks_external,benchpress,playground,payload_tests,bundle_deps';
|
||||
'angular1_router,angular2,angular2_material,benchmarks,benchmarks_external,benchpress,playground,payload_tests,bundle_deps';
|
||||
var cliArgsProjects = (cliArgs.projects || allProjects)
|
||||
.split(',')
|
||||
.reduce((map, projectName) => {
|
||||
@ -57,7 +57,7 @@ function printModulesWarning() {
|
||||
console.warn(
|
||||
"Pro Tip: Did you know that you can speed up your build by specifying project name(s)?");
|
||||
console.warn(" It's like pressing the turbo button in the old days, but better!");
|
||||
console.warn(" Examples: --project=angular2 or --project=angular2");
|
||||
console.warn(" Examples: --project=angular2 or --project=angular2,angular2_material");
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,8 +132,7 @@ var CONFIG = {
|
||||
dev: {es6: 'dist/js/dev/es6', es5: 'dist/js/dev/es5'},
|
||||
prod: {es6: 'dist/js/prod/es6', es5: 'dist/js/prod/es5'},
|
||||
cjs: 'dist/js/cjs',
|
||||
dart2js: 'dist/js/dart2js',
|
||||
dart_dev_compiler: 'dist/js/ddc'
|
||||
dart2js: 'dist/js/dart2js'
|
||||
},
|
||||
dart: 'dist/dart',
|
||||
docs: 'dist/docs',
|
||||
@ -370,10 +369,6 @@ function jsServeDartJs() {
|
||||
return jsserve(gulp, gulpPlugins, {path: CONFIG.dest.js.dart2js, port: 8002})();
|
||||
}
|
||||
|
||||
function jsServeDartDevCompiler() {
|
||||
return jsserve(gulp, gulpPlugins, {path: CONFIG.dest.js.dart_dev_compiler, port: 8003})();
|
||||
}
|
||||
|
||||
function proxyServeDart() {
|
||||
return jsserve(gulp, gulpPlugins, {
|
||||
port: 8002,
|
||||
@ -387,7 +382,7 @@ function proxyServeDart() {
|
||||
|
||||
// ------------------
|
||||
// web servers
|
||||
gulp.task('serve.js.dev', ['build.js.dev', 'build.js.cjs'], function(neverDone) {
|
||||
gulp.task('serve.js.dev', ['build.js.dev'], function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
watch('modules/**', {ignoreInitial: true}, '!broccoli.js.dev');
|
||||
@ -396,24 +391,24 @@ gulp.task('serve.js.dev', ['build.js.dev', 'build.js.cjs'], function(neverDone)
|
||||
|
||||
gulp.task('serve.js.prod', jsServeProd);
|
||||
|
||||
gulp.task('serve.e2e.dev', ['build.js.dev', 'build.js.cjs'], function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
gulp.task('serve.e2e.dev', ['build.js.dev', 'build.js.cjs', 'build.css.material'],
|
||||
function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.dev', '!build.js.cjs']);
|
||||
jsServeDev();
|
||||
});
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.dev', '!build.js.cjs']);
|
||||
jsServeDev();
|
||||
});
|
||||
|
||||
gulp.task('serve.e2e.prod', ['build.js.prod', 'build.js.cjs'], function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
gulp.task('serve.e2e.prod', ['build.js.prod', 'build.js.cjs', 'build.css.material'],
|
||||
function(neverDone) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.prod', '!build.js.cjs']);
|
||||
jsServeProd();
|
||||
});
|
||||
watch('modules/**', {ignoreInitial: true}, ['!broccoli.js.prod', '!build.js.cjs']);
|
||||
jsServeProd();
|
||||
});
|
||||
|
||||
gulp.task('serve.js.dart2js', jsServeDartJs);
|
||||
|
||||
gulp.task('serve.js.ddc', jsServeDartDevCompiler);
|
||||
|
||||
gulp.task('!proxyServeDart', proxyServeDart);
|
||||
|
||||
gulp.task('serve.dart', function(done) {
|
||||
@ -445,7 +440,7 @@ gulp.task('serve.e2e.dart', ['build.js.cjs'], function(neverDone) {
|
||||
|
||||
// Note: we are not using build.dart as the dart analyzer takes too long...
|
||||
watch('modules/**', {ignoreInitial: true}, ['!build/tree.dart', '!build.js.cjs']);
|
||||
runSequence('build/packages.dart', 'build/pubspec.dart', 'serve.dart');
|
||||
runSequence('build/packages.dart', 'build/pubspec.dart', 'build.dart.material.css', 'serve.dart');
|
||||
});
|
||||
|
||||
|
||||
@ -711,7 +706,7 @@ gulp.task('!build.payload.js.webpack', function() {
|
||||
.then(function() { // pad bundle with mandatory dependencies
|
||||
return new Promise(function(resolve, reject) {
|
||||
gulp.src([
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/zone-microtask.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
'node_modules/reflect-metadata/Reflect.js',
|
||||
CASE_PATH + '/app-bundle.js'
|
||||
@ -779,7 +774,8 @@ gulp.task('!checkAndReport.payload.js', function() {
|
||||
|
||||
gulp.task('watch.dart.dev', function(done) {
|
||||
runSequence('build/tree.dart', 'build/pure-packages.dart', '!build/pubget.angular2.dart',
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', function(error) {
|
||||
'!build/change_detect.dart', '!build/remove-pub-symlinks', 'build.dart.material.css',
|
||||
function(error) {
|
||||
var watch = require('./tools/build/watch');
|
||||
|
||||
// if initial build failed (likely due to build or formatting step) then exit
|
||||
@ -984,35 +980,18 @@ gulp.task('static-checks', ['!build.tools'], function(done) {
|
||||
// Make sure the two typings tests are isolated, by running this one in a tempdir
|
||||
var tmpdir = path.join(os.tmpdir(), 'test.typings', new Date().getTime().toString());
|
||||
gulp.task('!pre.test.typings.layoutNodeModule', ['build.js.cjs'], function() {
|
||||
return gulp.src(['dist/js/cjs/angular2/**/*', 'node_modules/rxjs/**/*'], {base: 'dist/js/cjs'})
|
||||
return gulp.src(['dist/js/cjs/angular2/**/*', 'node_modules/rxjs/**'], {base: 'dist/js/cjs'})
|
||||
.pipe(gulp.dest(path.join(tmpdir, 'node_modules')));
|
||||
});
|
||||
|
||||
gulp.task('!pre.test.typings.copyDeps', function() {
|
||||
return gulp.src(
|
||||
[
|
||||
'modules/angular2/typings/angular-protractor/*.ts',
|
||||
'modules/angular2/typings/jasmine/*.ts',
|
||||
'modules/angular2/typings/selenium-webdriver/*.ts',
|
||||
],
|
||||
{base: 'modules/angular2/typings'})
|
||||
.pipe(gulp.dest(tmpdir));
|
||||
});
|
||||
|
||||
gulp.task('!pre.test.typings.copyTypingsSpec', function() {
|
||||
return gulp.src(['modules/angular2/examples/**/*.ts']).pipe(gulp.dest(tmpdir));
|
||||
return gulp.src(['typing_spec/*.ts'], {base: 'typing_spec'}).pipe(gulp.dest(tmpdir));
|
||||
});
|
||||
|
||||
gulp.task('test.typings',
|
||||
[
|
||||
'!pre.test.typings.layoutNodeModule',
|
||||
'!pre.test.typings.copyTypingsSpec',
|
||||
'!pre.test.typings.copyDeps'
|
||||
],
|
||||
function() {
|
||||
['!pre.test.typings.layoutNodeModule', '!pre.test.typings.copyTypingsSpec'], function() {
|
||||
var tsc = require('gulp-typescript');
|
||||
|
||||
return gulp.src([tmpdir + '/**/*.ts', '!' + tmpdir + '/node_modules/**/*'])
|
||||
return gulp.src([tmpdir + '/*.ts'])
|
||||
.pipe(tsc({
|
||||
target: 'ES6',
|
||||
module: 'commonjs',
|
||||
@ -1074,7 +1053,7 @@ gulp.task('build/packages.dart', function(done) {
|
||||
// Builds and compiles all Dart packages
|
||||
gulp.task('build.dart', function(done) {
|
||||
runSequence('build/packages.dart', 'build/pubspec.dart', 'build/analyze.dart',
|
||||
'build/check.apidocs.dart', sequenceComplete(done));
|
||||
'build/check.apidocs.dart', 'build.dart.material.css', sequenceComplete(done));
|
||||
});
|
||||
|
||||
|
||||
@ -1093,26 +1072,25 @@ gulp.task('!build.tools', function() {
|
||||
.pipe(tsc({
|
||||
target: 'ES5',
|
||||
module: 'commonjs',
|
||||
declaration: true,
|
||||
// Don't use the version of typescript that gulp-typescript depends on
|
||||
// see https://github.com/ivogabe/gulp-typescript#typescript-version
|
||||
typescript: require('typescript')
|
||||
}));
|
||||
stream =
|
||||
merge2([stream.js.pipe(gulp.dest('dist/tools')), stream.dts.pipe(gulp.dest('dist/tools'))])
|
||||
.on('error',
|
||||
function(error) {
|
||||
// nodejs doesn't propagate errors from the src stream into the final
|
||||
// stream so we are
|
||||
// forwarding the error into the final stream
|
||||
stream.emit('error', error);
|
||||
})
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.on('end', function() {
|
||||
var AngularBuilder = require('./dist/tools/broccoli/angular_builder').AngularBuilder;
|
||||
angularBuilder =
|
||||
new AngularBuilder({outputPath: 'dist', dartSDK: DART_SDK, logs: logs});
|
||||
});
|
||||
}))
|
||||
.on('error',
|
||||
function(error) {
|
||||
// nodejs doesn't propagate errors from the src stream into the final
|
||||
// stream so we are
|
||||
// forwarding the error into the final stream
|
||||
stream.emit('error', error);
|
||||
})
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('dist/tools'))
|
||||
.on('end', function() {
|
||||
var AngularBuilder =
|
||||
require('./dist/tools/broccoli/angular_builder').AngularBuilder;
|
||||
angularBuilder =
|
||||
new AngularBuilder({outputPath: 'dist', dartSDK: DART_SDK, logs: logs});
|
||||
});
|
||||
|
||||
return stream;
|
||||
});
|
||||
@ -1134,8 +1112,9 @@ gulp.task('!broccoli.js.prod', () => angularBuilder.rebuildBrowserProdTree({
|
||||
useBundles: cliArgs.useBundles
|
||||
}));
|
||||
|
||||
gulp.task('build.js.dev', ['build/clean.js'],
|
||||
function(done) { runSequence('broccoli.js.dev', sequenceComplete(done)); });
|
||||
gulp.task('build.js.dev', ['build/clean.js'], function(done) {
|
||||
runSequence('broccoli.js.dev', 'build.css.material', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('build.js.prod', ['build.tools'],
|
||||
function(done) { runSequence('!broccoli.js.prod', sequenceComplete(done)); });
|
||||
@ -1376,7 +1355,7 @@ gulp.task('!bundle.ng.polyfills', ['clean'],
|
||||
|
||||
var JS_DEV_DEPS = [
|
||||
licenseWrap('node_modules/zone.js/LICENSE', true),
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/zone-microtask.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
licenseWrap('node_modules/reflect-metadata/LICENSE', true),
|
||||
'node_modules/reflect-metadata/Reflect.js'
|
||||
@ -1492,6 +1471,41 @@ gulp.task('!build/change_detect.dart', function(done) {
|
||||
});
|
||||
|
||||
// ------------
|
||||
// angular material testing rules
|
||||
gulp.task('build.css.material', function() {
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
var sass = require('gulp-sass');
|
||||
|
||||
return gulp.src('modules/*/src/**/*.scss')
|
||||
.pipe(sass())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest(CONFIG.dest.js.prod.es5))
|
||||
.pipe(gulp.dest(CONFIG.dest.js.dev.es5))
|
||||
.pipe(gulp.dest(CONFIG.dest.js.dart2js + '/examples/packages'));
|
||||
});
|
||||
|
||||
|
||||
gulp.task('build.js.material', function(done) {
|
||||
runSequence('build.js.dev', 'build.css.material', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('build.dart2js.material', function(done) {
|
||||
runSequence('build.dart', 'build.css.material', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('build.dart.material.css', function() {
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
var sass = require('gulp-sass');
|
||||
|
||||
return gulp.src('dist/dart/angular2_material/src/**/*.scss')
|
||||
.pipe(sass())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('dist/dart/angular2_material/lib/src'));
|
||||
});
|
||||
|
||||
gulp.task('build.dart.material', ['build/packages.dart'], function(done) {
|
||||
runSequence('build/packages.dart', 'build.dart.material.css', sequenceComplete(done));
|
||||
});
|
||||
|
||||
gulp.task('cleanup.builder', function() { return angularBuilder.cleanup(); });
|
||||
|
||||
|
@ -17,7 +17,8 @@ module.exports = function(config) {
|
||||
// include Angular v1 for upgrade module testing
|
||||
'node_modules/angular/angular.min.js',
|
||||
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
// zone-microtask must be included first as it contains a Promise monkey patch
|
||||
'node_modules/zone.js/dist/zone-microtask.js',
|
||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||
'node_modules/zone.js/dist/jasmine-patch.js',
|
||||
|
||||
|
6
modules/angular1_router/build.js
vendored
6
modules/angular1_router/build.js
vendored
@ -40,10 +40,8 @@ function main(modulesDirectory) {
|
||||
return prev + transform(fs.readFileSync(dir + file, 'utf8'));
|
||||
}, '');
|
||||
|
||||
// we have to use a function callback for replace to prevent it from interpreting `$`
|
||||
// as a replacement command character
|
||||
var out = moduleTemplate.replace('//{{FACADES}}', function() { return facades; })
|
||||
.replace('//{{SHARED_CODE}}', function() { return sharedCode; });
|
||||
var out = moduleTemplate.replace('//{{FACADES}}', facades)
|
||||
.replace('//{{SHARED_CODE}}', sharedCode);
|
||||
return PRELUDE + transform(directives) + out + POSTLUDE;
|
||||
}
|
||||
|
||||
|
@ -316,9 +316,3 @@ Location.prototype.path = function () {
|
||||
Location.prototype.go = function (path, query) {
|
||||
return $location.url(path + query);
|
||||
};
|
||||
Location.prototype.prepareExternalUrl = function(url) {
|
||||
if (url.length > 0 && !url.startsWith('/')) {
|
||||
url = '/' + url;
|
||||
}
|
||||
return $location.$$html5 ? '.' + url : '#' + $locationHashPrefix + url;
|
||||
};
|
||||
|
78
modules/angular1_router/src/module_template.js
vendored
78
modules/angular1_router/src/module_template.js
vendored
@ -4,33 +4,9 @@ angular.module('ngComponentRouter').
|
||||
// Because Angular 1 has no notion of a root component, we use an object with unique identity
|
||||
// to represent this. Can be overloaded with a component name
|
||||
value('$routerRootComponent', new Object()).
|
||||
factory('$rootRouter', ['$q', '$location', '$$directiveIntrospector', '$browser', '$rootScope', '$injector', '$routerRootComponent', routerFactory]);
|
||||
|
||||
// Unfortunately, $location doesn't expose what the current hashPrefix is
|
||||
// So we have to monkey patch the $locationProvider to capture this value
|
||||
provider('$locationHashPrefix', ['$locationProvider', $locationHashPrefixProvider]).
|
||||
factory('$rootRouter', ['$q', '$location', '$browser', '$rootScope', '$injector', '$routerRootComponent', '$locationHashPrefix', routerFactory]);
|
||||
|
||||
function $locationHashPrefixProvider($locationProvider) {
|
||||
|
||||
// Get hold of the original hashPrefix method
|
||||
var hashPrefixFn = $locationProvider.hashPrefix.bind($locationProvider);
|
||||
|
||||
// Read the current hashPrefix (in case it was set before this monkey-patch occurred)
|
||||
var hashPrefix = hashPrefixFn();
|
||||
|
||||
// Override the helper so that we can read any changes to the prefix (after this monkey-patch)
|
||||
$locationProvider.hashPrefix = function(prefix) {
|
||||
if (angular.isDefined(prefix)) {
|
||||
hashPrefix = prefix;
|
||||
}
|
||||
return hashPrefixFn(prefix);
|
||||
}
|
||||
|
||||
// Return the final hashPrefix as the value of this service
|
||||
this.$get = function() { return hashPrefix; };
|
||||
}
|
||||
|
||||
function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRootComponent, $locationHashPrefix) {
|
||||
function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootScope, $injector, $routerRootComponent) {
|
||||
|
||||
// When this file is processed, the line below is replaced with
|
||||
// the contents of `../lib/facades.es5`.
|
||||
@ -47,24 +23,11 @@ function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRo
|
||||
// the contents of the compiled TypeScript classes.
|
||||
//{{SHARED_CODE}}
|
||||
|
||||
function getComponentConstructor(name) {
|
||||
var serviceName = name + 'Directive';
|
||||
if ($injector.has(serviceName)) {
|
||||
var definitions = $injector.get(serviceName);
|
||||
if (definitions.length > 1) {
|
||||
throw new BaseException('too many directives named "' + name + '"');
|
||||
}
|
||||
return definitions[0].controller;
|
||||
} else {
|
||||
throw new BaseException('directive "' + name + '" is not registered');
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: this is a hack to replace the exiting implementation at run-time
|
||||
exports.getCanActivateHook = function (directiveName) {
|
||||
var controller = getComponentConstructor(directiveName);
|
||||
return controller.$canActivate && function (next, prev) {
|
||||
return $injector.invoke(controller.$canActivate, null, {
|
||||
var factory = $$directiveIntrospector.getTypeByName(directiveName);
|
||||
return factory && factory.$canActivate && function (next, prev) {
|
||||
return $injector.invoke(factory.$canActivate, null, {
|
||||
$nextInstruction: next,
|
||||
$prevInstruction: prev
|
||||
});
|
||||
@ -82,32 +45,17 @@ function routerFactory($q, $location, $browser, $rootScope, $injector, $routerRo
|
||||
var RouteRegistry = exports.RouteRegistry;
|
||||
var RootRouter = exports.RootRouter;
|
||||
|
||||
// Override this method to actually get hold of the child routes
|
||||
RouteRegistry.prototype.configFromComponent = function (component) {
|
||||
var that = this;
|
||||
if (isString(component)) {
|
||||
// Don't read the annotations component a type more than once –
|
||||
// this prevents an infinite loop if a component routes recursively.
|
||||
if (this._rules.has(component)) {
|
||||
return;
|
||||
}
|
||||
var controller = getComponentConstructor(component);
|
||||
if (angular.isArray(controller.$routeConfig)) {
|
||||
controller.$routeConfig.forEach(function (config) {
|
||||
var loader = config.loader;
|
||||
if (isPresent(loader)) {
|
||||
config = angular.extend({}, config, { loader: () => $injector.invoke(loader) });
|
||||
}
|
||||
that.config(component, config);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var registry = new RouteRegistry($routerRootComponent);
|
||||
var location = new Location();
|
||||
|
||||
$$directiveIntrospector(function (name, factory) {
|
||||
if (angular.isArray(factory.$routeConfig)) {
|
||||
factory.$routeConfig.forEach(function (config) {
|
||||
registry.config(name, config);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var router = new RootRouter(registry, location, $routerRootComponent);
|
||||
$rootScope.$watch(function () { return $location.url(); }, function (path) {
|
||||
if (router.lastNavigationAttempt !== path) {
|
||||
|
@ -1,6 +1,51 @@
|
||||
///<reference path="../typings/angularjs/angular.d.ts"/>
|
||||
|
||||
/*
|
||||
* decorates $compileProvider so that we have access to routing metadata
|
||||
*/
|
||||
function compilerProviderDecorator($compileProvider,
|
||||
$$directiveIntrospectorProvider: DirectiveIntrospectorProvider) {
|
||||
let directive = $compileProvider.directive;
|
||||
$compileProvider.directive = function(name: string, factory: Function) {
|
||||
$$directiveIntrospectorProvider.register(name, factory);
|
||||
return directive.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* private service that holds route mappings for each controller
|
||||
*/
|
||||
class DirectiveIntrospectorProvider {
|
||||
private directiveBuffer: any[] = [];
|
||||
private directiveFactoriesByName: {[name: string]: Function} = {};
|
||||
private onDirectiveRegistered: (name: string, factory: Function) => any = null;
|
||||
|
||||
register(name: string, factory: Function) {
|
||||
if (angular.isArray(factory)) {
|
||||
factory = factory[factory.length - 1];
|
||||
}
|
||||
this.directiveFactoriesByName[name] = factory;
|
||||
if (this.onDirectiveRegistered) {
|
||||
this.onDirectiveRegistered(name, factory);
|
||||
} else {
|
||||
this.directiveBuffer.push({name: name, factory: factory});
|
||||
}
|
||||
}
|
||||
|
||||
$get() {
|
||||
let fn: any = newOnControllerRegistered => {
|
||||
this.onDirectiveRegistered = newOnControllerRegistered;
|
||||
while (this.directiveBuffer.length > 0) {
|
||||
let directive = this.directiveBuffer.pop();
|
||||
this.onDirectiveRegistered(directive.name, directive.factory);
|
||||
}
|
||||
};
|
||||
|
||||
fn.getTypeByName = name => this.directiveFactoriesByName[name];
|
||||
|
||||
return fn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @name ngOutlet
|
||||
@ -207,13 +252,13 @@ function ngLinkDirective($rootRouter, $parse) {
|
||||
return;
|
||||
}
|
||||
|
||||
let navigationInstruction = null;
|
||||
let instruction = null;
|
||||
let link = attrs.ngLink || '';
|
||||
|
||||
function getLink(params) {
|
||||
navigationInstruction = router.generate(params);
|
||||
instruction = router.generate(params);
|
||||
|
||||
scope.$watch(function() { return router.isRouteActive(navigationInstruction); },
|
||||
scope.$watch(function() { return router.isRouteActive(instruction); },
|
||||
function(active) {
|
||||
if (active) {
|
||||
element.addClass('ng-link-active');
|
||||
@ -222,8 +267,7 @@ function ngLinkDirective($rootRouter, $parse) {
|
||||
}
|
||||
});
|
||||
|
||||
const navigationHref = navigationInstruction.toLinkUrl();
|
||||
return $rootRouter._location.prepareExternalUrl(navigationHref);
|
||||
return './' + angular.stringifyInstruction(instruction);
|
||||
}
|
||||
|
||||
let routeParamsGetter = $parse(link);
|
||||
@ -237,11 +281,11 @@ function ngLinkDirective($rootRouter, $parse) {
|
||||
}
|
||||
|
||||
element.on('click', event => {
|
||||
if (event.which !== 1 || !navigationInstruction) {
|
||||
if (event.which !== 1 || !instruction) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootRouter.navigateByInstruction(navigationInstruction);
|
||||
$rootRouter.navigateByInstruction(instruction);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
@ -259,3 +303,10 @@ angular.module('ngComponentRouter', [])
|
||||
.directive('ngOutlet', ['$compile', ngOutletFillContentDirective])
|
||||
.directive('ngLink', ['$rootRouter', '$parse', ngLinkDirective])
|
||||
.directive('$router', ['$q', routerTriggerDirective]);
|
||||
|
||||
/*
|
||||
* A module for inspecting controller constructors
|
||||
*/
|
||||
angular.module('ng')
|
||||
.provider('$$directiveIntrospector', DirectiveIntrospectorProvider)
|
||||
.config(['$compileProvider', '$$directiveIntrospectorProvider', compilerProviderDecorator]);
|
||||
|
2
modules/angular1_router/src/ng_route_shim.js
vendored
2
modules/angular1_router/src/ng_route_shim.js
vendored
@ -150,7 +150,7 @@
|
||||
}];
|
||||
|
||||
// we resolve the locals in a canActivate block
|
||||
componentDefinition.controller.$canActivate = function() {
|
||||
componentDefinition.$canActivate = function() {
|
||||
var locals = angular.extend({}, routeCopy.resolve);
|
||||
|
||||
angular.forEach(locals, function(value, key) {
|
||||
|
38
modules/angular1_router/test/directive_introspector_spec.js
vendored
Normal file
38
modules/angular1_router/test/directive_introspector_spec.js
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
describe('$$directiveIntrospector', function () {
|
||||
|
||||
var $compileProvider;
|
||||
|
||||
beforeEach(function() {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function(_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the introspector function whenever a directive factory is registered', inject(function ($$directiveIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$directiveIntrospector(spy);
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', myDir);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('myDir', myDir);
|
||||
}));
|
||||
|
||||
it('should call the introspector function whenever a directive factory is registered with array annotations', inject(function ($$directiveIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$directiveIntrospector(spy);
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', ['foo', myDir]);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('myDir', myDir);
|
||||
}));
|
||||
|
||||
it('should retrieve a factory based on directive name', inject(function ($$directiveIntrospector) {
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', ['foo', myDir]);
|
||||
expect($$directiveIntrospector.getTypeByName('myDir')).toBe(myDir);
|
||||
}));
|
||||
});
|
@ -466,10 +466,10 @@ describe('Navigation lifecycle', function () {
|
||||
}
|
||||
|
||||
if (options.$canActivate) {
|
||||
controller.$canActivate = options.$canActivate;
|
||||
factory.$canActivate = options.$canActivate;
|
||||
}
|
||||
if (options.$routeConfig) {
|
||||
controller.$routeConfig = options.$routeConfig;
|
||||
factory.$routeConfig = options.$routeConfig;
|
||||
}
|
||||
|
||||
$compileProvider.directive(name, factory);
|
||||
|
@ -306,15 +306,14 @@ describe('navigation', function () {
|
||||
}));
|
||||
|
||||
function registerDirective(name, options) {
|
||||
var controller = getController(options);
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: controller
|
||||
controller: getController(options)
|
||||
};
|
||||
}
|
||||
applyStaticProperties(controller, options);
|
||||
applyStaticProperties(factory, options);
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
@ -324,7 +323,7 @@ describe('navigation', function () {
|
||||
template: options.template || '',
|
||||
controller: getController(options),
|
||||
}
|
||||
applyStaticProperties(definition.controller, options);
|
||||
applyStaticProperties(definition, options);
|
||||
$compileProvider.component(name, definition);
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,30 @@
|
||||
|
||||
describe('router', function () {
|
||||
|
||||
var elt, testMod;
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$rootRouter,
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
testMod = angular.module('testMod', ['ngComponentRouter'])
|
||||
.value('$routerRootComponent', 'app');
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function($provide) {
|
||||
$provide.value('$routerRootComponent', 'app');
|
||||
});
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with a provided root component', function() {
|
||||
|
||||
it('should work with a provided root component', inject(function($location) {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home'
|
||||
});
|
||||
@ -21,17 +37,14 @@ describe('router', function () {
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
compileApp();
|
||||
compile('<app></app>');
|
||||
|
||||
inject(function($location, $rootScope) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home');
|
||||
});
|
||||
});
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home');
|
||||
}));
|
||||
|
||||
it('should bind the component to the current router', function() {
|
||||
it('should bind the component to the current router', inject(function($location) {
|
||||
var router;
|
||||
registerComponent('homeCmp', {
|
||||
bindings: { $router: '=' },
|
||||
@ -50,48 +63,41 @@ describe('router', function () {
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
compileApp();
|
||||
compile('<app></app>');
|
||||
|
||||
inject(function($location, $rootScope) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect(homeElement.isolateScope().$ctrl.$router).toBeDefined();
|
||||
expect(router).toBeDefined();
|
||||
})
|
||||
});
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect(homeElement.isolateScope().$ctrl.$router).toBeDefined();
|
||||
expect(router).toBeDefined();
|
||||
}));
|
||||
|
||||
it('should work when an async route is provided route data', function() {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home ({{$ctrl.isAdmin}})',
|
||||
it('should work when an async route is provided route data', inject(function($location, $q) {
|
||||
registerDirective('homeCmp', {
|
||||
template: 'Home ({{homeCmp.isAdmin}})',
|
||||
$routerOnActivate: function(next, prev) {
|
||||
this.isAdmin = next.routeData.data.isAdmin;
|
||||
}
|
||||
});
|
||||
|
||||
registerComponent('app', {
|
||||
registerDirective('app', {
|
||||
template: '<div ng-outlet></div>',
|
||||
$routeConfig: [
|
||||
{ path: '/', loader: function($q) { return $q.when('homeCmp'); }, data: { isAdmin: true } }
|
||||
{ path: '/', loader: function() { return $q.when('homeCmp'); }, data: { isAdmin: true } }
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
compileApp();
|
||||
compile('<app></app>');
|
||||
|
||||
inject(function($location, $rootScope) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home (true)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with a templateUrl component', function() {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('Home (true)');
|
||||
}));
|
||||
|
||||
it('should work with a templateUrl component', inject(function($location, $httpBackend) {
|
||||
var $routerOnActivate = jasmine.createSpy('$routerOnActivate');
|
||||
|
||||
$httpBackend.expectGET('homeCmp.html').respond('Home');
|
||||
registerComponent('homeCmp', {
|
||||
templateUrl: 'homeCmp.html',
|
||||
$routerOnActivate: $routerOnActivate
|
||||
@ -104,24 +110,17 @@ describe('router', function () {
|
||||
]
|
||||
});
|
||||
|
||||
module('testMod');
|
||||
compile('<app></app>');
|
||||
|
||||
inject(function($location, $rootScope, $httpBackend) {
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect($routerOnActivate).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
$httpBackend.expectGET('homeCmp.html').respond('Home');
|
||||
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
$httpBackend.flush();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.text()).toBe('Home');
|
||||
expect($routerOnActivate).toHaveBeenCalled();
|
||||
})
|
||||
});
|
||||
|
||||
it('should provide the current instruction', function() {
|
||||
it('should provide the current instruction', inject(function($location, $q) {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home ({{homeCmp.isAdmin}})'
|
||||
});
|
||||
@ -132,20 +131,15 @@ describe('router', function () {
|
||||
{ path: '/', component: 'homeCmp', name: 'Home' }
|
||||
]
|
||||
});
|
||||
compile('<app></app>');
|
||||
|
||||
module('testMod');
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var instruction = $rootRouter.generate(['/Home']);
|
||||
expect($rootRouter.currentInstruction).toEqual(instruction);
|
||||
}));
|
||||
|
||||
inject(function($rootScope, $rootRouter, $location) {
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var instruction = $rootRouter.generate(['/Home']);
|
||||
expect($rootRouter.currentInstruction).toEqual(instruction);
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide the root level router', function() {
|
||||
it('should provide the root level router', inject(function($location, $q) {
|
||||
registerComponent('homeCmp', {
|
||||
template: 'Home ({{homeCmp.isAdmin}})',
|
||||
bindings: {
|
||||
@ -159,18 +153,25 @@ describe('router', function () {
|
||||
{ path: '/', component: 'homeCmp', name: 'Home' }
|
||||
]
|
||||
});
|
||||
compile('<app></app>');
|
||||
|
||||
module('testMod');
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.isolateScope().$ctrl.$router.root).toEqual($rootRouter);
|
||||
}));
|
||||
|
||||
inject(function($rootScope, $rootRouter, $location) {
|
||||
compileApp();
|
||||
|
||||
$location.path('/');
|
||||
$rootScope.$digest();
|
||||
var homeElement = elt.find('home-cmp');
|
||||
expect(homeElement.isolateScope().$ctrl.$router.root).toEqual($rootRouter);
|
||||
});
|
||||
});
|
||||
function registerDirective(name, options) {
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: getController(options)
|
||||
};
|
||||
}
|
||||
applyStaticProperties(factory, options);
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
function registerComponent(name, options) {
|
||||
|
||||
@ -181,15 +182,13 @@ describe('router', function () {
|
||||
if (options.template) definition.template = options.template;
|
||||
if (options.templateUrl) definition.templateUrl = options.templateUrl;
|
||||
|
||||
applyStaticProperties(definition.controller, options);
|
||||
angular.module('testMod').component(name, definition);
|
||||
applyStaticProperties(definition, options);
|
||||
$compileProvider.component(name, definition);
|
||||
}
|
||||
|
||||
function compileApp() {
|
||||
inject(function($compile, $rootScope) {
|
||||
elt = $compile('<div><app></app</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
function compile(template) {
|
||||
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
return elt;
|
||||
}
|
||||
|
||||
|
346
modules/angular1_router/test/ng_link_spec.js
vendored
346
modules/angular1_router/test/ng_link_spec.js
vendored
@ -2,191 +2,183 @@
|
||||
|
||||
describe('ngLink', function () {
|
||||
|
||||
describe('html5Mode enabled', function () {
|
||||
runHrefTestsAndExpectPrefix('/', true);
|
||||
});
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$rootRouter,
|
||||
$compileProvider;
|
||||
|
||||
describe('html5Mode disabled', function () {
|
||||
runHrefTestsAndExpectPrefix('', false, '');
|
||||
});
|
||||
|
||||
describe('html5Mode disabled, with hash prefix', function () {
|
||||
runHrefTestsAndExpectPrefix('', false, '!');
|
||||
});
|
||||
|
||||
describe('html5Mode enabled', function () {
|
||||
runHrefTestsAndExpectPrefix('/moo', true);
|
||||
});
|
||||
|
||||
describe('html5Mode disabled', function () {
|
||||
runHrefTestsAndExpectPrefix('/moo', false, '');
|
||||
});
|
||||
|
||||
describe('html5Mode disabled, with hash prefix', function () {
|
||||
runHrefTestsAndExpectPrefix('/moo', false, '!');
|
||||
});
|
||||
|
||||
function runHrefTestsAndExpectPrefix(baseHref, html5Mode, hashPrefix) {
|
||||
var prefix = html5Mode ? '.' : '#' + hashPrefix;
|
||||
|
||||
it('should allow linking from the parent to the child', function () {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
configureRouter([
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
|
||||
var elt = compile('<a ng-link="[\'/Two\']">link</a> | outer { <div ng-outlet></div> }');
|
||||
navigateTo('/a');
|
||||
expect(elt.find('a').attr('href')).toBe(prefix + '/b');
|
||||
});
|
||||
|
||||
it('should allow linking from the child and the parent', function () {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
configureRouter([
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
|
||||
var elt = compile('outer { <div ng-outlet></div> }');
|
||||
navigateTo('/b');
|
||||
expect(elt.find('a').attr('href')).toBe(prefix + '/b');
|
||||
});
|
||||
|
||||
|
||||
it('should allow params in routerLink directive', function () {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'two'});
|
||||
configureRouter([
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
|
||||
var elt = compile('<div ng-outlet></div>');
|
||||
navigateTo('/a');
|
||||
expect(elt.find('a').attr('href')).toBe(prefix + '/b/lol');
|
||||
});
|
||||
|
||||
|
||||
it('should update the href of links with bound params', function () {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: $ctrl.number}]">{{$ctrl.number}}</a></div>', function () {this.number = 43});
|
||||
configureRouter([
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
|
||||
var elt = compile('<div ng-outlet></div>');
|
||||
navigateTo('/a');
|
||||
expect(elt.find('a').text()).toBe('43');
|
||||
expect(elt.find('a').attr('href')).toBe(prefix + '/b/43');
|
||||
});
|
||||
|
||||
|
||||
it('should navigate on left-mouse click when a link url matches a route', function () {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
configureRouter([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
|
||||
var elt = compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
|
||||
expect(elt.text()).toBe('link | one');
|
||||
expect(elt.find('a').attr('href')).toBe(prefix + '/two');
|
||||
|
||||
elt.find('a')[0].click();
|
||||
inject(function($rootScope) { $rootScope.$digest(); });
|
||||
expect(elt.text()).toBe('link | two');
|
||||
});
|
||||
|
||||
|
||||
it('should not navigate on non-left mouse click when a link url matches a route', function() {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
configureRouter([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
|
||||
var elt = compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
|
||||
expect(elt.text()).toBe('link | one');
|
||||
elt.find('a').triggerHandler({ type: 'click', which: 3 });
|
||||
inject(function($rootScope) { $rootScope.$digest(); });
|
||||
expect(elt.text()).toBe('link | one');
|
||||
});
|
||||
|
||||
|
||||
// See https://github.com/angular/router/issues/206
|
||||
it('should not navigate a link without an href', function () {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
configureRouter([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
expect(function () {
|
||||
var elt = compile('<a>link</a>');
|
||||
expect(elt.text()).toBe('link');
|
||||
elt.find('a')[0].click();
|
||||
inject(function($rootScope) { $rootScope.$digest(); });
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should add an ng-link-active class on the current link', function() {
|
||||
setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
|
||||
configureRouter([
|
||||
{ path: '/', component: 'oneCmp', name: 'One' }
|
||||
]);
|
||||
|
||||
var elt = compile('<a ng-link="[\'/One\']">one</a> | <div ng-outlet></div>');
|
||||
navigateTo('/');
|
||||
expect(elt.find('a').attr('class')).toBe('ng-link-active');
|
||||
});
|
||||
}
|
||||
|
||||
function registerComponent(name, template, controller) {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.component(name, {
|
||||
template: template,
|
||||
controller: controller
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setup(config) {
|
||||
module(function($provide) {
|
||||
$provide.decorator('$browser', function($delegate) {
|
||||
$delegate.baseHref = function() { return config.baseHref; };
|
||||
return $delegate;
|
||||
});
|
||||
});
|
||||
beforeEach(function () {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function($locationProvider) {
|
||||
$locationProvider.html5Mode(config.html5Mode);
|
||||
$locationProvider.hashPrefix(config.hashPrefix);
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
registerComponent('userCmp', '<div>hello {{$ctrl.$routeParams.name}}</div>', function () {});
|
||||
registerComponent('oneCmp', '<div>{{$ctrl.number}}</div>', function () {this.number = 'one'});
|
||||
registerComponent('twoCmp', '<div><a ng-link="[\'/Two\']">{{$ctrl.number}}</a></div>', function () {this.number = 'two'});
|
||||
}
|
||||
|
||||
function configureRouter(routeConfig) {
|
||||
inject(function($rootRouter) {
|
||||
$rootRouter.config(routeConfig);
|
||||
inject(function (_$compile_, _$rootScope_, _$rootRouter_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$rootRouter = _$rootRouter_;
|
||||
});
|
||||
|
||||
registerComponent('userCmp', '<div>hello {{userCmp.$routeParams.name}}</div>', function () {});
|
||||
registerComponent('oneCmp', '<div>{{oneCmp.number}}</div>', function () {this.number = 'one'});
|
||||
registerComponent('twoCmp', '<div><a ng-link="[\'/Two\']">{{twoCmp.number}}</a></div>', function () {this.number = 'two'});
|
||||
});
|
||||
|
||||
|
||||
it('should allow linking from the parent to the child', function () {
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | outer { <div ng-outlet></div> }');
|
||||
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b');
|
||||
});
|
||||
|
||||
it('should allow linking from the child and the parent', function () {
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$rootRouter.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b');
|
||||
});
|
||||
|
||||
|
||||
it('should allow params in routerLink directive', function () {
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'two'});
|
||||
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b/lol');
|
||||
});
|
||||
|
||||
|
||||
it('should update the href of links with bound params', function () {
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: twoLinkCmp.number}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'param'});
|
||||
$rootRouter.config([
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', name: 'Two' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$rootRouter.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b/param');
|
||||
});
|
||||
|
||||
|
||||
it('should navigate on left-mouse click when a link url matches a route', function () {
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link | one');
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./two');
|
||||
|
||||
elt.find('a')[0].click();
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link | two');
|
||||
});
|
||||
|
||||
|
||||
it('should not navigate on non-left mouse click when a link url matches a route', inject(function ($rootRouter) {
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link | one');
|
||||
elt.find('a').triggerHandler({ type: 'click', which: 3 });
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link | one');
|
||||
}));
|
||||
|
||||
|
||||
// See https://github.com/angular/router/issues/206
|
||||
it('should not navigate a link without an href', function () {
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', name: 'Two'}
|
||||
]);
|
||||
expect(function () {
|
||||
compile('<a>link</a>');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link');
|
||||
elt.find('a')[0].click();
|
||||
$rootScope.$digest();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should add an ng-link-active class on the current link', inject(function ($rootRouter) {
|
||||
$rootRouter.config([
|
||||
{ path: '/', component: 'oneCmp', name: 'One' }
|
||||
]);
|
||||
|
||||
compile('<a ng-link="[\'/One\']">one</a> | <div ng-outlet></div>');
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootRouter.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('class')).toBe('ng-link-active');
|
||||
}));
|
||||
|
||||
|
||||
function registerComponent(name, template, config) {
|
||||
var controller = function () {};
|
||||
|
||||
function factory() {
|
||||
return {
|
||||
template: template,
|
||||
controllerAs: name,
|
||||
controller: controller
|
||||
};
|
||||
}
|
||||
|
||||
if (!template) {
|
||||
template = '';
|
||||
}
|
||||
if (angular.isArray(config)) {
|
||||
factory.annotations = [new angular.annotations.RouteConfig(config)];
|
||||
} else if (typeof config === 'function') {
|
||||
controller = config;
|
||||
} else if (typeof config === 'object') {
|
||||
if (config.canActivate) {
|
||||
controller.$canActivate = config.canActivate;
|
||||
}
|
||||
}
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
function compile(template) {
|
||||
var elt;
|
||||
inject(function($compile, $rootScope) {
|
||||
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
return elt;
|
||||
}
|
||||
|
||||
function navigateTo(url) {
|
||||
inject(function($rootRouter, $rootScope) {
|
||||
$rootRouter.navigateByUrl(url);
|
||||
$rootScope.$digest();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -72,7 +72,7 @@ An example of an Angular 2 project built with WebPack can be found in the [angul
|
||||
|
||||
Polyfills are required for Angular 2 to function properly (the exact list depends on the browser used) and external dependencies ([zone.js](https://github.com/angular/zone.js)).
|
||||
To ease setup of Angular 2 applications there is one file - `angular2-polyfills.js` - that combines:
|
||||
* a polyfill mandatory for all browsers: [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
|
||||
* a pollyfill mandatory for all browsers: [reflect-metadata](https://www.npmjs.com/package/reflect-metadata)
|
||||
* [zone.js](https://github.com/angular/zone.js)
|
||||
|
||||
**Note**: `angular2-polyfills.js` contains code that should be loaded into the browser as the very first code of the web application even before the module loader. The preferred solution is to load the mentioned file in a `script` tag as early as possible.
|
||||
|
@ -4,7 +4,7 @@ Bootstrapping
|
||||
@description
|
||||
{@target ts}`import {bootstrap} from 'angular2/platform/browser';`{@endtarget}
|
||||
{@target js}Available from the `ng.platform.browser` namespace{@endtarget}
|
||||
{@target dart}`import 'package:angular2/platform/browser.dart';`{@endtarget}
|
||||
{@target dart}`import 'package:angular2/bootstrap.dart';`{@endtarget}
|
||||
|
||||
@cheatsheetItem
|
||||
syntax(ts dart):
|
||||
|
@ -112,7 +112,9 @@ Example of a component:
|
||||
'title', | - title mapped to component title
|
||||
'open' | - open attribute mapped to component's open property
|
||||
], |
|
||||
templateUrl: 'pane.html' | URL of template HTML
|
||||
}) |
|
||||
@View({ | View annotation
|
||||
templateUrl: 'pane.html' | - URL of template HTML
|
||||
}) |
|
||||
class Pane { | Component controller class
|
||||
title:string; | - title property
|
||||
@ -225,9 +227,11 @@ class MyService {} | Assume a service which needs to be inject
|
||||
|
|
||||
@Component({ | Assume a top level application component which
|
||||
selector: 'my-app', | configures the services to be injected.
|
||||
viewBindings: [MyService], |
|
||||
templateUrl: 'my_app.html', | Assume we have a template that needs to be
|
||||
directives: [House] | configured with directives to be injected.
|
||||
viewBindings: [MyService] |
|
||||
}) |
|
||||
@View({ | Assume we have a template that needs to be
|
||||
templateUrl: 'my_app.html', | configured with directives to be injected.
|
||||
directives: [House] |
|
||||
}) |
|
||||
class MyApp {} |
|
||||
|
|
||||
@ -269,6 +273,8 @@ Here is an example of the kinds of injections which can be achieved:
|
||||
```
|
||||
@Component({ |
|
||||
selector: 'my-app' |
|
||||
}) |
|
||||
@View({ |
|
||||
templateUrl: 'my_app.html', |
|
||||
directives: [Form, FieldSet, |
|
||||
Field, Primary] |
|
||||
@ -323,6 +329,8 @@ Shadow DOM provides an encapsulation for components, so as a general rule it doe
|
||||
```
|
||||
@Component({
|
||||
selector: '[kid]'
|
||||
})
|
||||
@View({
|
||||
templateUrl: 'kid.html',
|
||||
directives: []
|
||||
})
|
||||
@ -339,6 +347,8 @@ class Kid {
|
||||
|
||||
@Component({
|
||||
selector: '[dad]'
|
||||
})
|
||||
@View({
|
||||
templateUrl: 'dad.html',
|
||||
directives: [Kid]
|
||||
})
|
||||
@ -351,7 +361,9 @@ class Dad {
|
||||
|
||||
@Component({
|
||||
selector: '[grandpa]',
|
||||
viewBindings: [],
|
||||
viewBindings: []
|
||||
})
|
||||
@View({
|
||||
templateUrl: 'grandpa.html',
|
||||
directives: [Dad]
|
||||
})
|
||||
|
@ -179,7 +179,9 @@ Both `MyComponent` and `MyDirective` are created on the same element.
|
||||
],
|
||||
viewBindings: [
|
||||
bind('viewService').toValue('View_MyComponentService')
|
||||
],
|
||||
]
|
||||
})
|
||||
@View({
|
||||
template: `<needs-view-service></needs-view-service>`,
|
||||
directives: [NeedsViewService]
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {UrlResolver} from 'angular2/compiler';
|
||||
|
||||
var MyApp: any;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {NG_VALIDATORS} from 'angular2/common';
|
||||
import {Provider} from 'angular2/core';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {Observable, Subscriber} from 'rxjs/Rx';
|
||||
|
||||
// #docregion AsyncPipe
|
||||
@ -38,7 +38,7 @@ export class AsyncPipeExample {
|
||||
@Component({selector: "task-cmp", template: "Time: {{ time | async }}"})
|
||||
class Task {
|
||||
time = new Observable<number>((observer: Subscriber<number>) => {
|
||||
setInterval(() => observer.next(new Date().getTime()), 500);
|
||||
setInterval(_ => observer.next(new Date().getTime()), 500);
|
||||
});
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
|
||||
// #docregion DatePipe
|
||||
@Component({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
|
||||
// #docregion JsonPipe
|
||||
@Component({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
|
||||
// #docregion LowerUpperPipe
|
||||
@Component({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
|
||||
// #docregion NumberPipe
|
||||
@Component({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
|
||||
// #docregion SlicePipe_string
|
||||
@Component({
|
||||
|
@ -1,6 +1,6 @@
|
||||
// #docregion enableProdMode
|
||||
import {enableProdMode} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {MyComponent} from './my_component';
|
||||
|
||||
enableProdMode();
|
||||
|
@ -2,7 +2,7 @@
|
||||
import {Observable, Subscriber} from 'rxjs/Rx';
|
||||
var obs = new Observable<number>((obs: Subscriber<number>) => {
|
||||
var i = 0;
|
||||
setInterval(() => { obs.next(++i); }, 1000);
|
||||
setInterval(_ => { obs.next(++i); }, 1000);
|
||||
});
|
||||
obs.subscribe(i => console.log(`${i} seconds elapsed`));
|
||||
// #enddocregion
|
||||
|
@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
|
||||
|
||||
var obs = new Observable<number>((obs: Subscriber<any>) => {
|
||||
var i = 0;
|
||||
setInterval(() => obs.next(++i), 1000);
|
||||
setInterval(_ => obs.next(++i), 1000);
|
||||
});
|
||||
obs.map((i: number) => `${i} seconds elapsed`).subscribe(msg => console.log(msg));
|
||||
// #enddocregion
|
||||
|
@ -4,7 +4,7 @@ import {map} from 'rxjs/operator/map';
|
||||
|
||||
var obs = new Observable<number>((sub: Subscriber<number>) => {
|
||||
var i = 0;
|
||||
setInterval(() => sub.next(++i), 1000);
|
||||
setInterval(_ => sub.next(++i), 1000);
|
||||
});
|
||||
map.call(obs, (i: number) => `${i} seconds elapsed`).subscribe((msg: string) => console.log(msg));
|
||||
// #enddocregion
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {provide, Component} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {
|
||||
CanActivate,
|
||||
RouteConfig,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {verifyNoBrowserErrors, browser} from 'angular2/src/testing/e2e_util';
|
||||
import {expect} from 'angular2/testing';
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {provide, Component} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {
|
||||
CanDeactivate,
|
||||
RouteConfig,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {verifyNoBrowserErrors, browser} from 'angular2/src/testing/e2e_util';
|
||||
import {expect} from 'angular2/testing';
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
|
24
modules/angular2/examples/router/ts/on_activate/index.html
Normal file
24
modules/angular2/examples/router/ts/on_activate/index.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Routing Reuse Lifecycle Example</title>
|
||||
<base href="/">
|
||||
|
||||
<script src="http://cdn.rawgit.com/google/traceur-compiler/90da568c7aa8e53ea362db1fc211fbb4f65b5e94/bin/traceur-runtime.js"></script>
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/systemjs/0.18.4/system.js"></script>
|
||||
<script>System.config({ baseURL: '/', defaultJSExtensions: true});</script>
|
||||
<script src="/bundle/angular2.dev.js"></script>
|
||||
<script src="/bundle/router.dev.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<example-app>
|
||||
Loading...
|
||||
</example-app>
|
||||
<script>
|
||||
var filename = 'angular2/examples/router/ts/on_activate/on_activate_example';
|
||||
System.import(filename).then(function(m) {
|
||||
m.main();
|
||||
}, console.error.bind(console));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {
|
||||
OnActivate,
|
||||
ComponentInstruction,
|
||||
@ -8,28 +8,14 @@ import {
|
||||
APP_BASE_HREF
|
||||
} from 'angular2/router';
|
||||
|
||||
// #docregion routerOnActivate
|
||||
@Component({template: `Child`})
|
||||
class ChildCmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2>Parent</h2> (<router-outlet></router-outlet>)
|
||||
<p>{{log}}</p>`,
|
||||
directives: [ROUTER_DIRECTIVES]
|
||||
})
|
||||
@RouteConfig([{path: '/child', name: 'Child', component: ChildCmp}])
|
||||
class ParentCmp implements OnActivate {
|
||||
// #docregion routerOnActivate
|
||||
@Component({selector: 'my-cmp', template: `<div>routerOnActivate: {{log}}</div>`})
|
||||
class MyCmp implements OnActivate {
|
||||
log: string = '';
|
||||
|
||||
routerOnActivate(next: ComponentInstruction, prev: ComponentInstruction) {
|
||||
this.log = `Finished navigating from "${prev ? prev.urlPath : 'null'}" to "${next.urlPath}"`;
|
||||
|
||||
return new Promise(resolve => {
|
||||
// The ChildCmp gets instantiated only when the Promise is resolved
|
||||
setTimeout(() => resolve(null), 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
@ -38,19 +24,23 @@ class ParentCmp implements OnActivate {
|
||||
@Component({
|
||||
selector: 'example-app',
|
||||
template: `
|
||||
<h1>My app</h1>
|
||||
|
||||
<h1>My App</h1>
|
||||
<nav>
|
||||
<a [routerLink]="['Parent', 'Child']">Child</a>
|
||||
<a [routerLink]="['/HomeCmp']" id="home-link">Navigate Home</a> |
|
||||
<a [routerLink]="['/ParamCmp', {param: 1}]" id="param-link">Navigate with a Param</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
directives: [ROUTER_DIRECTIVES]
|
||||
})
|
||||
@RouteConfig([{path: '/parent/...', name: 'Parent', component: ParentCmp}])
|
||||
export class AppCmp {
|
||||
@RouteConfig([
|
||||
{path: '/', component: MyCmp, name: 'HomeCmp'},
|
||||
{path: '/:param', component: MyCmp, name: 'ParamCmp'}
|
||||
])
|
||||
class AppCmp {
|
||||
}
|
||||
|
||||
|
||||
export function main() {
|
||||
return bootstrap(
|
||||
AppCmp, [provide(APP_BASE_HREF, {useValue: '/angular2/examples/router/ts/on_activate'})]);
|
||||
|
@ -0,0 +1,33 @@
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
}
|
||||
|
||||
describe('on activate example app', function() {
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
var URL = 'angular2/examples/router/ts/on_activate/';
|
||||
|
||||
it('should update the text when navigating between routes', function() {
|
||||
browser.get(URL);
|
||||
waitForElement('my-cmp');
|
||||
|
||||
expect(element(by.css('my-cmp')).getText())
|
||||
.toContain('routerOnActivate: Finished navigating from "null" to ""');
|
||||
|
||||
element(by.css('#param-link')).click();
|
||||
waitForElement('my-cmp');
|
||||
|
||||
expect(element(by.css('my-cmp')).getText())
|
||||
.toContain('routerOnActivate: Finished navigating from "" to "1"');
|
||||
|
||||
browser.navigate().back();
|
||||
waitForElement('my-cmp');
|
||||
|
||||
expect(element(by.css('my-cmp')).getText())
|
||||
.toContain('routerOnActivate: Finished navigating from "1" to ""');
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import {Component, Injectable, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {
|
||||
OnDeactivate,
|
||||
ComponentInstruction,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {verifyNoBrowserErrors, browser} from 'angular2/src/testing/e2e_util';
|
||||
import {expect} from 'angular2/testing';
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Component, provide} from 'angular2/core';
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {bootstrap} from 'angular2/bootstrap';
|
||||
import {
|
||||
CanActivate,
|
||||
RouteConfig,
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {verifyNoBrowserErrors, browser} from 'angular2/src/testing/e2e_util';
|
||||
import {expect} from 'angular2/testing';
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
var EC = (<any>protractor).ExpectedConditions;
|
||||
|
@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Entry point to i18n
|
||||
*/
|
||||
export * from './src/i18n/message';
|
||||
export * from './src/i18n/xmb_serializer';
|
||||
export * from './src/i18n/message_extractor';
|
||||
export * from './src/i18n/i18n_html_parser';
|
@ -9,6 +9,7 @@
|
||||
"repository": <%= JSON.stringify(packageJson.repository) %>,
|
||||
"devDependencies": <%= JSON.stringify(packageJson.defaultDevDependencies) %>,
|
||||
"peerDependencies": {
|
||||
"es6-promise": "<%= packageJson.dependencies['es6-promise'] %>",
|
||||
"es6-shim": "<%= packageJson.dependencies['es6-shim'] %>",
|
||||
"reflect-metadata": "<%= packageJson.dependencies['reflect-metadata'] %>",
|
||||
"rxjs": "<%= packageJson.dependencies['rxjs'] %>",
|
||||
|
@ -17,11 +17,9 @@ dependencies:
|
||||
intl: '^0.12.4'
|
||||
logging: '>=0.9.0 <0.12.0'
|
||||
observe: '^0.13.1'
|
||||
path: '^1.0.0'
|
||||
protobuf: '^0.5.0'
|
||||
source_span: '^1.0.0'
|
||||
stack_trace: '^1.1.1'
|
||||
build: '>=0.0.1'
|
||||
dev_dependencies:
|
||||
transformer_test: '^0.2.0'
|
||||
guinness: '^0.1.18'
|
||||
|
@ -9,7 +9,7 @@ import {CORE_DIRECTIVES} from './directives';
|
||||
* NgModel).
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
|
||||
* property of the `@Component` decorator.
|
||||
* property of the `@Component` or `@View` decorators.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
|
@ -8,6 +8,5 @@ export {NgFor} from './directives/ng_for';
|
||||
export {NgIf} from './directives/ng_if';
|
||||
export {NgStyle} from './directives/ng_style';
|
||||
export {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './directives/ng_switch';
|
||||
export {NgPlural, NgPluralCase, NgLocalization} from './directives/ng_plural';
|
||||
export * from './directives/observable_list_diff';
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
||||
export {CORE_DIRECTIVES} from './directives/core_directives';
|
@ -4,14 +4,13 @@ import {NgFor} from './ng_for';
|
||||
import {NgIf} from './ng_if';
|
||||
import {NgStyle} from './ng_style';
|
||||
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './ng_switch';
|
||||
import {NgPlural, NgPluralCase} from './ng_plural';
|
||||
|
||||
/**
|
||||
* A collection of Angular core directives that are likely to be used in each and every Angular
|
||||
* application.
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in directives in the `directives`
|
||||
* property of the `@Component` annotation.
|
||||
* property of the `@View` annotation.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/yakGwpCdUkg0qfzX5m8g?p=preview))
|
||||
*
|
||||
@ -46,14 +45,5 @@ import {NgPlural, NgPluralCase} from './ng_plural';
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const CORE_DIRECTIVES: Type[] = CONST_EXPR([
|
||||
NgClass,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgStyle,
|
||||
NgSwitch,
|
||||
NgSwitchWhen,
|
||||
NgSwitchDefault,
|
||||
NgPlural,
|
||||
NgPluralCase
|
||||
]);
|
||||
export const CORE_DIRECTIVES: Type[] =
|
||||
CONST_EXPR([NgClass, NgFor, NgIf, NgStyle, NgSwitch, NgSwitchWhen, NgSwitchDefault]);
|
||||
|
@ -9,12 +9,11 @@ import {
|
||||
EmbeddedViewRef,
|
||||
TrackByFn
|
||||
} from 'angular2/core';
|
||||
import {isPresent, isBlank, stringify, getTypeNameForDebugging} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
DefaultIterableDiffer,
|
||||
CollectionChangeRecord
|
||||
} from "../../core/change_detection/differs/default_iterable_differ";
|
||||
import {BaseException} from "../../facade/exceptions";
|
||||
|
||||
/**
|
||||
* The `NgFor` directive instantiates a template once per item from an iterable. The context for
|
||||
@ -78,12 +77,7 @@ export class NgFor implements DoCheck {
|
||||
set ngForOf(value: any) {
|
||||
this._ngForOf = value;
|
||||
if (isBlank(this._differ) && isPresent(value)) {
|
||||
try {
|
||||
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
|
||||
} catch (e) {
|
||||
throw new BaseException(
|
||||
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
|
||||
}
|
||||
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,146 +0,0 @@
|
||||
import {
|
||||
Directive,
|
||||
ViewContainerRef,
|
||||
TemplateRef,
|
||||
ContentChildren,
|
||||
QueryList,
|
||||
Attribute,
|
||||
AfterContentInit,
|
||||
Input
|
||||
} from 'angular2/core';
|
||||
import {isPresent, NumberWrapper} from 'angular2/src/facade/lang';
|
||||
import {Map} from 'angular2/src/facade/collection';
|
||||
import {SwitchView} from './ng_switch';
|
||||
|
||||
const _CATEGORY_DEFAULT = 'other';
|
||||
|
||||
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
|
||||
|
||||
/**
|
||||
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
|
||||
* value, or failing that, DOM sub-trees that match the switch expression's pluralization category.
|
||||
*
|
||||
* To use this directive, you must provide an extension of `NgLocalization` that maps values to
|
||||
* category names. You then define a container element that sets the `[ngPlural]` attribute to a
|
||||
* switch expression.
|
||||
* - Inner elements defined with an `[ngPluralCase]` attribute will display based on their
|
||||
* expression.
|
||||
* - If `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
|
||||
* matches the switch expression exactly.
|
||||
* - Otherwise, the view will be treated as a "category match", and will only display if exact
|
||||
* value matches aren't found and the value maps to its category using the `getPluralCategory`
|
||||
* function provided.
|
||||
*
|
||||
* If no matching views are found for a switch expression, inner elements marked
|
||||
* `[ngPluralCase]="other"` will be displayed.
|
||||
*
|
||||
* ```typescript
|
||||
* class MyLocalization extends NgLocalization {
|
||||
* getPluralCategory(value: any) {
|
||||
* if(value < 5) {
|
||||
* return 'few';
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* providers: [provide(NgLocalization, {useClass: MyLocalization})]
|
||||
* })
|
||||
* @View({
|
||||
* template: `
|
||||
* <p>Value = {{value}}</p>
|
||||
* <button (click)="inc()">Increment</button>
|
||||
*
|
||||
* <div [ngPlural]="value">
|
||||
* <template ngPluralCase="=0">there is nothing</template>
|
||||
* <template ngPluralCase="=1">there is one</template>
|
||||
* <template ngPluralCase="few">there are a few</template>
|
||||
* <template ngPluralCase="other">there is some number</template>
|
||||
* </div>
|
||||
* `,
|
||||
* directives: [NgPlural, NgPluralCase]
|
||||
* })
|
||||
* export class App {
|
||||
* value = 'init';
|
||||
*
|
||||
* inc() {
|
||||
* this.value = this.value === 'init' ? 0 : this.value + 1;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
|
||||
@Directive({selector: '[ngPluralCase]'})
|
||||
export class NgPluralCase {
|
||||
_view: SwitchView;
|
||||
constructor(@Attribute('ngPluralCase') public value: string, template: TemplateRef,
|
||||
viewContainer: ViewContainerRef) {
|
||||
this._view = new SwitchView(viewContainer, template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: '[ngPlural]'})
|
||||
export class NgPlural implements AfterContentInit {
|
||||
private _switchValue: number;
|
||||
private _activeView: SwitchView;
|
||||
private _caseViews = new Map<any, SwitchView>();
|
||||
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
|
||||
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
@Input()
|
||||
set ngPlural(value: number) {
|
||||
this._switchValue = value;
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.cases.forEach((pluralCase: NgPluralCase): void => {
|
||||
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
|
||||
});
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateView(): void {
|
||||
this._clearViews();
|
||||
|
||||
var view: SwitchView = this._caseViews.get(this._switchValue);
|
||||
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
|
||||
|
||||
this._activateView(view);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_clearViews() {
|
||||
if (isPresent(this._activeView)) this._activeView.destroy();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_activateView(view: SwitchView) {
|
||||
if (!isPresent(view)) return;
|
||||
this._activeView = view;
|
||||
this._activeView.create();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getCategoryView(value: number): SwitchView {
|
||||
var category: string = this._localization.getPluralCategory(value);
|
||||
var categoryView: SwitchView = this._caseViews.get(category);
|
||||
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === "="; }
|
||||
|
||||
/** @internal */
|
||||
_formatValue(pluralCase: NgPluralCase): any {
|
||||
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
|
||||
}
|
@ -4,6 +4,7 @@ import {ListWrapper, Map} from 'angular2/src/facade/collection';
|
||||
|
||||
const _WHEN_DEFAULT = CONST_EXPR(new Object());
|
||||
|
||||
/** @internal */
|
||||
export class SwitchView {
|
||||
constructor(private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef) {}
|
||||
|
||||
@ -31,8 +32,8 @@ export class SwitchView {
|
||||
* ### Example ([live demo](http://plnkr.co/edit/DQMTII95CbuqWrl3lYAs?p=preview))
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
* selector: 'app',
|
||||
* @Component({selector: 'app'})
|
||||
* @View({
|
||||
* template: `
|
||||
* <p>Value = {{value}}</p>
|
||||
* <button (click)="inc()">Increment</button>
|
||||
|
@ -50,7 +50,7 @@ export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||
|
||||
/**
|
||||
*
|
||||
* A list of all the form directives used as part of a `@Component` annotation.
|
||||
* A list of all the form directives used as part of a `@View` annotation.
|
||||
*
|
||||
* This is a shorthand for importing them each individually.
|
||||
*
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {unimplemented} from 'angular2/src/facade/exceptions';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
|
||||
/**
|
||||
* A base class that all control directive extend.
|
||||
@ -13,8 +12,8 @@ export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
|
||||
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
|
||||
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
|
||||
get validator(): Function { return unimplemented(); }
|
||||
get asyncValidator(): Function { return unimplemented(); }
|
||||
|
||||
abstract viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
@ -16,8 +16,7 @@ import {ControlContainer} from './control_container';
|
||||
import {controlPath, composeValidators, composeAsyncValidators} from './shared';
|
||||
import {ControlGroup} from '../model';
|
||||
import {Form} from './form_interface';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
|
||||
const controlGroupProvider =
|
||||
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgControlGroup)}));
|
||||
@ -33,6 +32,8 @@ const controlGroupProvider =
|
||||
* @Component({
|
||||
* selector: 'my-app',
|
||||
* directives: [FORM_DIRECTIVES],
|
||||
* })
|
||||
* @View({
|
||||
* template: `
|
||||
* <div>
|
||||
* <h2>Angular2 Control & ControlGroup Example</h2>
|
||||
@ -105,7 +106,7 @@ export class NgControlGroup extends ControlContainer implements OnInit,
|
||||
*/
|
||||
get formDirective(): Form { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
}
|
||||
|
@ -27,8 +27,7 @@ import {
|
||||
selectValueAccessor
|
||||
} from './shared';
|
||||
import {Control} from '../model';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
|
||||
|
||||
const controlNameBinding =
|
||||
@ -137,9 +136,9 @@ export class NgControlName extends NgControl implements OnChanges,
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
get control(): Control { return this.formDirective.getControl(this); }
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
isPropertyUpdated,
|
||||
selectValueAccessor
|
||||
} from './shared';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
const formControlBinding =
|
||||
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgFormControl)}));
|
||||
@ -111,9 +110,9 @@ export class NgFormControl extends NgControl implements OnChanges {
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
get control(): Control { return this.form; }
|
||||
|
||||
|
@ -3,6 +3,7 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {
|
||||
OnChanges,
|
||||
SimpleChange,
|
||||
Query,
|
||||
Directive,
|
||||
forwardRef,
|
||||
Provider,
|
||||
@ -13,7 +14,7 @@ import {
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
import {NgControl} from './ng_control';
|
||||
import {Control} from '../model';
|
||||
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
|
||||
import {
|
||||
setUpControl,
|
||||
isPropertyUpdated,
|
||||
@ -21,7 +22,6 @@ import {
|
||||
composeValidators,
|
||||
composeAsyncValidators
|
||||
} from './shared';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
|
||||
const formControlBinding =
|
||||
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgModel)}));
|
||||
@ -88,9 +88,9 @@ export class NgModel extends NgControl implements OnChanges {
|
||||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): Function { return composeValidators(this._validators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): Function { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
|
@ -10,11 +10,3 @@ Function normalizeValidator(dynamic validator){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Function normalizeAsyncValidator(dynamic validator){
|
||||
if (validator is Validator) {
|
||||
return (c) => validator.validate(c);
|
||||
} else {
|
||||
return validator;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,14 @@
|
||||
import {AbstractControl} from "../model";
|
||||
import {Validator, ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
import {Validator} from './validators';
|
||||
import {Control} from "../model";
|
||||
|
||||
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
|
||||
export type ctrlFunc = ((c: Control) => {
|
||||
[key: string]: any
|
||||
});
|
||||
|
||||
export function normalizeValidator(validator: (ctrlFunc | Validator)): ctrlFunc {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||
return (c: Control) => (<Validator>validator).validate(c);
|
||||
} else {
|
||||
return <ValidatorFn>validator;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
|
||||
if ((<Validator>validator).validate !== undefined) {
|
||||
return (c: AbstractControl) => Promise.resolve((<Validator>validator).validate(c));
|
||||
} else {
|
||||
return <AsyncValidatorFn>validator;
|
||||
return <ctrlFunc>validator;
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,7 @@ import {NumberValueAccessor} from './number_value_accessor';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
||||
import {normalizeValidator, normalizeAsyncValidator} from './normalize_validator';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './validators';
|
||||
import {normalizeValidator} from './normalize_validator';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
@ -57,14 +56,13 @@ function _throwError(dir: AbstractControlDirective, message: string): void {
|
||||
throw new BaseException(`${message} '${path}'`);
|
||||
}
|
||||
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
|
||||
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): Function {
|
||||
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
|
||||
}
|
||||
|
||||
export function composeAsyncValidators(
|
||||
validators: /* Array<Validator|Function> */ any[]): AsyncValidatorFn {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
|
||||
null;
|
||||
validators: /* Array<Validator|Function> */ any[]): Function {
|
||||
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeValidator)) : null;
|
||||
}
|
||||
|
||||
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
|
||||
|
@ -1,12 +1,11 @@
|
||||
import {forwardRef, Provider, Attribute, Directive} from 'angular2/core';
|
||||
import {forwardRef, Provider, OpaqueToken, Attribute, Directive} from 'angular2/core';
|
||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
import {Validators, NG_VALIDATORS} from '../validators';
|
||||
import {AbstractControl} from '../model';
|
||||
import {Control} from '../model';
|
||||
import * as modelModule from '../model';
|
||||
import {NumberWrapper} from "angular2/src/facade/lang";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface that can be implemented by classes that can act as validators.
|
||||
*
|
||||
@ -24,7 +23,7 @@ import {NumberWrapper} from "angular2/src/facade/lang";
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface Validator { validate(c: modelModule.AbstractControl): {[key: string]: any}; }
|
||||
export interface Validator { validate(c: modelModule.Control): {[key: string]: any}; }
|
||||
|
||||
const REQUIRED_VALIDATOR =
|
||||
CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.required, multi: true}));
|
||||
@ -46,11 +45,6 @@ const REQUIRED_VALIDATOR =
|
||||
export class RequiredValidator {
|
||||
}
|
||||
|
||||
export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; }
|
||||
export interface AsyncValidatorFn {
|
||||
(c: AbstractControl): any /*Promise<{[key: string]: any}>|Observable<{[key: string]: any}>*/;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provivder which adds {@link MinLengthValidator} to {@link NG_VALIDATORS}.
|
||||
*
|
||||
@ -70,13 +64,13 @@ const MIN_LENGTH_VALIDATOR = CONST_EXPR(
|
||||
providers: [MIN_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MinLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
private _validator: Function;
|
||||
|
||||
constructor(@Attribute("minlength") minLength: string) {
|
||||
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
validate(c: Control): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,13 +92,13 @@ const MAX_LENGTH_VALIDATOR = CONST_EXPR(
|
||||
providers: [MAX_LENGTH_VALIDATOR]
|
||||
})
|
||||
export class MaxLengthValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
private _validator: Function;
|
||||
|
||||
constructor(@Attribute("maxlength") maxLength: string) {
|
||||
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
validate(c: Control): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
||||
|
||||
@ -127,11 +121,11 @@ const PATTERN_VALIDATOR = CONST_EXPR(
|
||||
providers: [PATTERN_VALIDATOR]
|
||||
})
|
||||
export class PatternValidator implements Validator {
|
||||
private _validator: ValidatorFn;
|
||||
private _validator: Function;
|
||||
|
||||
constructor(@Attribute("pattern") pattern: string) {
|
||||
this._validator = Validators.pattern(pattern);
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
|
||||
validate(c: Control): {[key: string]: any} { return this._validator(c); }
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import {Injectable} from 'angular2/core';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isArray, CONST_EXPR, Type} from 'angular2/src/facade/lang';
|
||||
import * as modelModule from './model';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
|
||||
/**
|
||||
@ -57,18 +56,16 @@ export class FormBuilder {
|
||||
group(controlsConfig: {[key: string]: any},
|
||||
extra: {[key: string]: any} = null): modelModule.ControlGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
var optionals = <{[key: string]: boolean}>(
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
|
||||
var asyncValidator: AsyncValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
|
||||
var optionals = isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null;
|
||||
var validator = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
|
||||
var asyncValidator = isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
|
||||
return new modelModule.ControlGroup(controls, optionals, validator, asyncValidator);
|
||||
}
|
||||
/**
|
||||
* Construct a new {@link Control} with the given `value`,`validator`, and `asyncValidator`.
|
||||
*/
|
||||
control(value: Object, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.Control {
|
||||
control(value: Object, validator: Function = null,
|
||||
asyncValidator: Function = null): modelModule.Control {
|
||||
return new modelModule.Control(value, validator, asyncValidator);
|
||||
}
|
||||
|
||||
@ -76,8 +73,8 @@ export class FormBuilder {
|
||||
* Construct an array of {@link Control}s from the given `controlsConfig` array of
|
||||
* configuration, with the given optional `validator` and `asyncValidator`.
|
||||
*/
|
||||
array(controlsConfig: any[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null): modelModule.ControlArray {
|
||||
array(controlsConfig: any[], validator: Function = null,
|
||||
asyncValidator: Function = null): modelModule.ControlArray {
|
||||
var controls = controlsConfig.map(c => this._createControl(c));
|
||||
return new modelModule.ControlArray(controls, validator, asyncValidator);
|
||||
}
|
||||
@ -101,12 +98,12 @@ export class FormBuilder {
|
||||
|
||||
} else if (isArray(controlConfig)) {
|
||||
var value = controlConfig[0];
|
||||
var validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
var asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null;
|
||||
var validator = controlConfig.length > 1 ? controlConfig[1] : null;
|
||||
var asyncValidator = controlConfig.length > 2 ? controlConfig[2] : null;
|
||||
return this.control(value, validator, asyncValidator);
|
||||
|
||||
} else {
|
||||
return this.control(controlConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import {isPresent, isBlank, normalizeBool} from 'angular2/src/facade/lang';
|
||||
import {StringWrapper, isPresent, isBlank, normalizeBool} from 'angular2/src/facade/lang';
|
||||
import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/promise';
|
||||
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
/**
|
||||
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
|
||||
@ -65,7 +64,7 @@ export abstract class AbstractControl {
|
||||
private _parent: ControlGroup | ControlArray;
|
||||
private _asyncValidationSubscription: any;
|
||||
|
||||
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
|
||||
constructor(public validator: Function, public asyncValidator: Function) {}
|
||||
|
||||
get value(): any { return this._value; }
|
||||
|
||||
@ -138,17 +137,15 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
private _runValidator(): {[key: string]: any} {
|
||||
return isPresent(this.validator) ? this.validator(this) : null;
|
||||
}
|
||||
private _runValidator() { return isPresent(this.validator) ? this.validator(this) : null; }
|
||||
|
||||
private _runAsyncValidator(emitEvent: boolean): void {
|
||||
if (isPresent(this.asyncValidator)) {
|
||||
this._status = PENDING;
|
||||
this._cancelExistingSubscription();
|
||||
var obs = toObservable(this.asyncValidator(this));
|
||||
this._asyncValidationSubscription = ObservableWrapper.subscribe(
|
||||
obs, (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent}));
|
||||
this._asyncValidationSubscription =
|
||||
ObservableWrapper.subscribe(obs, res => this.setErrors(res, {emitEvent: emitEvent}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,8 +268,7 @@ export class Control extends AbstractControl {
|
||||
/** @internal */
|
||||
_onChange: Function;
|
||||
|
||||
constructor(value: any = null, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
constructor(value: any = null, validator: Function = null, asyncValidator: Function = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._value = value;
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
@ -321,10 +317,9 @@ export class Control extends AbstractControl {
|
||||
/**
|
||||
* Defines a part of a form, of fixed length, that can contain other controls.
|
||||
*
|
||||
* A `ControlGroup` aggregates the values of each {@link Control} in the group.
|
||||
* The status of a `ControlGroup` depends on the status of its children.
|
||||
* If one of the controls in a group is invalid, the entire group is invalid.
|
||||
* Similarly, if a control changes its value, the entire group changes as well.
|
||||
* A `ControlGroup` aggregates the values and errors of each {@link Control} in the group. Thus, if
|
||||
* one of the controls in a group is invalid, the entire group is invalid. Similarly, if a control
|
||||
* changes its value, the entire group changes as well.
|
||||
*
|
||||
* `ControlGroup` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with {@link Control} and {@link ControlArray}. {@link ControlArray} can also contain other
|
||||
@ -336,8 +331,8 @@ export class ControlGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(public controls: {[key: string]: AbstractControl},
|
||||
optionals: {[key: string]: boolean} = null, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
optionals: {[key: string]: boolean} = null, validator: Function = null,
|
||||
asyncValidator: Function = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._initObservables();
|
||||
@ -430,10 +425,9 @@ export class ControlGroup extends AbstractControl {
|
||||
/**
|
||||
* Defines a part of a form, of variable length, that can contain other controls.
|
||||
*
|
||||
* A `ControlArray` aggregates the values of each {@link Control} in the group.
|
||||
* The status of a `ControlArray` depends on the status of its children.
|
||||
* If one of the controls in a group is invalid, the entire array is invalid.
|
||||
* Similarly, if a control changes its value, the entire array changes as well.
|
||||
* A `ControlArray` aggregates the values and errors of each {@link Control} in the group. Thus, if
|
||||
* one of the controls in a group is invalid, the entire group is invalid. Similarly, if a control
|
||||
* changes its value, the entire group changes as well.
|
||||
*
|
||||
* `ControlArray` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with {@link Control} and {@link ControlGroup}. {@link ControlGroup} can also contain
|
||||
@ -450,8 +444,8 @@ export class ControlGroup extends AbstractControl {
|
||||
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
|
||||
*/
|
||||
export class ControlArray extends AbstractControl {
|
||||
constructor(public controls: AbstractControl[], validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
constructor(public controls: AbstractControl[], validator: Function = null,
|
||||
asyncValidator: Function = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
|
@ -5,7 +5,6 @@ import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {OpaqueToken} from 'angular2/core';
|
||||
|
||||
import * as modelModule from './model';
|
||||
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link Control}s in a form.
|
||||
@ -44,7 +43,7 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: modelModule.AbstractControl): {[key: string]: boolean} {
|
||||
static required(control: modelModule.Control): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == "") ?
|
||||
{"required": true} :
|
||||
null;
|
||||
@ -53,8 +52,8 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a value of a minimum length.
|
||||
*/
|
||||
static minLength(minLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
static minLength(minLength: number): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length < minLength ?
|
||||
@ -66,8 +65,8 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires controls to have a value of a maximum length.
|
||||
*/
|
||||
static maxLength(maxLength: number): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
static maxLength(maxLength: number): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length > maxLength ?
|
||||
@ -79,8 +78,8 @@ export class Validators {
|
||||
/**
|
||||
* Validator that requires a control to match a regex to its value.
|
||||
*/
|
||||
static pattern(pattern: string): ValidatorFn {
|
||||
return (control: modelModule.AbstractControl): {[key: string]: any} => {
|
||||
static pattern(pattern: string): Function {
|
||||
return (control: modelModule.Control): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
let regex = new RegExp(`^${pattern}$`);
|
||||
let v: string = control.value;
|
||||
@ -92,13 +91,13 @@ export class Validators {
|
||||
/**
|
||||
* No-op validator.
|
||||
*/
|
||||
static nullValidator(c: modelModule.AbstractControl): {[key: string]: boolean} { return null; }
|
||||
static nullValidator(c: any): {[key: string]: boolean} { return null; }
|
||||
|
||||
/**
|
||||
* Compose multiple validators into a single function that returns the union
|
||||
* of the individual error maps.
|
||||
*/
|
||||
static compose(validators: ValidatorFn[]): ValidatorFn {
|
||||
static compose(validators: Function[]): Function {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
@ -108,13 +107,13 @@ export class Validators {
|
||||
};
|
||||
}
|
||||
|
||||
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn {
|
||||
static composeAsync(validators: Function[]): Function {
|
||||
if (isBlank(validators)) return null;
|
||||
var presentValidators = validators.filter(isPresent);
|
||||
if (presentValidators.length == 0) return null;
|
||||
|
||||
return function(control: modelModule.AbstractControl) {
|
||||
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
|
||||
let promises = _executeValidators(control, presentValidators).map(_convertToPromise);
|
||||
return PromiseWrapper.all(promises).then(_mergeErrors);
|
||||
};
|
||||
}
|
||||
@ -124,20 +123,13 @@ function _convertToPromise(obj: any): any {
|
||||
return PromiseWrapper.isPromise(obj) ? obj : ObservableWrapper.toPromise(obj);
|
||||
}
|
||||
|
||||
function _executeValidators(control: modelModule.AbstractControl,
|
||||
validators: ValidatorFn[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _executeAsyncValidators(control: modelModule.AbstractControl,
|
||||
validators: AsyncValidatorFn[]): any[] {
|
||||
function _executeValidators(control: modelModule.AbstractControl, validators: Function[]): any[] {
|
||||
return validators.map(v => v(control));
|
||||
}
|
||||
|
||||
function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} {
|
||||
var res: {[key: string]: any} =
|
||||
arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => {
|
||||
return isPresent(errors) ? StringMapWrapper.merge(res, errors) : res;
|
||||
}, {});
|
||||
var res = arrayOfErrors.reduce((res, errors) => {
|
||||
return isPresent(errors) ? StringMapWrapper.merge(<any>res, <any>errors) : res;
|
||||
}, {});
|
||||
return StringMapWrapper.isEmpty(res) ? null : res;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class ObservableStrategy {
|
||||
}
|
||||
|
||||
class PromiseStrategy {
|
||||
createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): any {
|
||||
createSubscription(async: any, updateLatestValue: any): any {
|
||||
return async.then(updateLatestValue);
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||
* application.
|
||||
*
|
||||
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
|
||||
* property of the `@Component` decorator.
|
||||
* property of the `@Component` or `@View` decorators.
|
||||
*/
|
||||
export const COMMON_PIPES = CONST_EXPR([
|
||||
AsyncPipe,
|
||||
|
@ -48,7 +48,7 @@ export class I18nPluralPipe implements PipeTransform {
|
||||
transform(value: number, args: any[] = null): string {
|
||||
var key: string;
|
||||
var valueStr: string;
|
||||
var pluralMap: {[count: string]: string} = <{[count: string]: string}>(args[0]);
|
||||
var pluralMap: {[count: string]: string} = args[0];
|
||||
|
||||
if (!isStringMap(pluralMap)) {
|
||||
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
|
||||
|
@ -37,7 +37,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
|
||||
@Injectable()
|
||||
export class I18nSelectPipe implements PipeTransform {
|
||||
transform(value: string, args: any[] = null): string {
|
||||
var mapping: {[key: string]: string} = <{[count: string]: string}>(args[0]);
|
||||
var mapping: {[key: string]: string} = args[0];
|
||||
if (!isStringMap(mapping)) {
|
||||
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
library angular2.core.util.asserions;
|
||||
|
||||
void assertArrayOfStrings(String identifier, Object value) {}
|
@ -1,16 +0,0 @@
|
||||
import {isArray, isString, isBlank, assertionsEnabled} from '../facade/lang';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
|
||||
export function assertArrayOfStrings(identifier: string, value: any) {
|
||||
if (!assertionsEnabled() || isBlank(value)) {
|
||||
return;
|
||||
}
|
||||
if (!isArray(value)) {
|
||||
throw new BaseException(`Expected '${identifier}' to be an array of strings.`);
|
||||
}
|
||||
for (var i = 0; i < value.length; i += 1) {
|
||||
if (!isString(value[i])) {
|
||||
throw new BaseException(`Expected '${identifier}' to be an array of strings.`);
|
||||
}
|
||||
}
|
||||
}
|
@ -146,9 +146,8 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber);
|
||||
var directiveMetadata = ast.directive;
|
||||
var outputsArray = [];
|
||||
StringMapWrapper.forEach(
|
||||
ast.directive.outputs,
|
||||
(eventName: string, dirProperty: string) => outputsArray.push([dirProperty, eventName]));
|
||||
StringMapWrapper.forEach(ast.directive.outputs, (eventName, dirProperty) => outputsArray.push(
|
||||
[dirProperty, eventName]));
|
||||
var directiveRecord = new DirectiveRecord({
|
||||
directiveIndex: directiveIndex,
|
||||
callAfterContentInit:
|
||||
|
@ -1,64 +0,0 @@
|
||||
export const $EOF = 0;
|
||||
export const $TAB = 9;
|
||||
export const $LF = 10;
|
||||
export const $VTAB = 11;
|
||||
export const $FF = 12;
|
||||
export const $CR = 13;
|
||||
export const $SPACE = 32;
|
||||
export const $BANG = 33;
|
||||
export const $DQ = 34;
|
||||
export const $HASH = 35;
|
||||
export const $$ = 36;
|
||||
export const $PERCENT = 37;
|
||||
export const $AMPERSAND = 38;
|
||||
export const $SQ = 39;
|
||||
export const $LPAREN = 40;
|
||||
export const $RPAREN = 41;
|
||||
export const $STAR = 42;
|
||||
export const $PLUS = 43;
|
||||
export const $COMMA = 44;
|
||||
export const $MINUS = 45;
|
||||
export const $PERIOD = 46;
|
||||
export const $SLASH = 47;
|
||||
export const $COLON = 58;
|
||||
export const $SEMICOLON = 59;
|
||||
export const $LT = 60;
|
||||
export const $EQ = 61;
|
||||
export const $GT = 62;
|
||||
export const $QUESTION = 63;
|
||||
|
||||
export const $0 = 48;
|
||||
export const $9 = 57;
|
||||
|
||||
export const $A = 65;
|
||||
export const $E = 69;
|
||||
export const $Z = 90;
|
||||
|
||||
export const $LBRACKET = 91;
|
||||
export const $BACKSLASH = 92;
|
||||
export const $RBRACKET = 93;
|
||||
export const $CARET = 94;
|
||||
export const $_ = 95;
|
||||
|
||||
export const $a = 97;
|
||||
export const $e = 101;
|
||||
export const $f = 102;
|
||||
export const $n = 110;
|
||||
export const $r = 114;
|
||||
export const $t = 116;
|
||||
export const $u = 117;
|
||||
export const $v = 118;
|
||||
export const $z = 122;
|
||||
|
||||
export const $LBRACE = 123;
|
||||
export const $BAR = 124;
|
||||
export const $RBRACE = 125;
|
||||
export const $NBSP = 160;
|
||||
|
||||
export const $PIPE = 124;
|
||||
export const $TILDA = 126;
|
||||
export const $AT = 64;
|
||||
|
||||
export function isWhitespace(code: number): boolean {
|
||||
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
|
||||
}
|
@ -1,751 +0,0 @@
|
||||
import {NumberWrapper, StringWrapper, isPresent, resolveEnumToken} from "angular2/src/facade/lang";
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
|
||||
import {
|
||||
isWhitespace,
|
||||
$EOF,
|
||||
$HASH,
|
||||
$TILDA,
|
||||
$CARET,
|
||||
$PERCENT,
|
||||
$$,
|
||||
$_,
|
||||
$COLON,
|
||||
$SQ,
|
||||
$DQ,
|
||||
$EQ,
|
||||
$SLASH,
|
||||
$BACKSLASH,
|
||||
$PERIOD,
|
||||
$STAR,
|
||||
$PLUS,
|
||||
$LPAREN,
|
||||
$RPAREN,
|
||||
$LBRACE,
|
||||
$RBRACE,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$PIPE,
|
||||
$COMMA,
|
||||
$SEMICOLON,
|
||||
$MINUS,
|
||||
$BANG,
|
||||
$QUESTION,
|
||||
$AT,
|
||||
$AMPERSAND,
|
||||
$GT,
|
||||
$a,
|
||||
$A,
|
||||
$z,
|
||||
$Z,
|
||||
$0,
|
||||
$9,
|
||||
$FF,
|
||||
$CR,
|
||||
$LF,
|
||||
$VTAB
|
||||
} from "angular2/src/compiler/chars";
|
||||
|
||||
export {
|
||||
$EOF,
|
||||
$AT,
|
||||
$RBRACE,
|
||||
$LBRACE,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$LPAREN,
|
||||
$RPAREN,
|
||||
$COMMA,
|
||||
$COLON,
|
||||
$SEMICOLON,
|
||||
isWhitespace
|
||||
} from "angular2/src/compiler/chars";
|
||||
|
||||
export enum CssTokenType {
|
||||
EOF,
|
||||
String,
|
||||
Comment,
|
||||
Identifier,
|
||||
Number,
|
||||
IdentifierOrNumber,
|
||||
AtKeyword,
|
||||
Character,
|
||||
Whitespace,
|
||||
Invalid
|
||||
}
|
||||
|
||||
export enum CssLexerMode {
|
||||
ALL,
|
||||
ALL_TRACK_WS,
|
||||
SELECTOR,
|
||||
PSEUDO_SELECTOR,
|
||||
ATTRIBUTE_SELECTOR,
|
||||
AT_RULE_QUERY,
|
||||
MEDIA_QUERY,
|
||||
BLOCK,
|
||||
KEYFRAME_BLOCK,
|
||||
STYLE_BLOCK,
|
||||
STYLE_VALUE,
|
||||
STYLE_VALUE_FUNCTION,
|
||||
STYLE_CALC_FUNCTION
|
||||
}
|
||||
|
||||
export class LexedCssResult {
|
||||
constructor(public error: CssScannerError, public token: CssToken) {}
|
||||
}
|
||||
|
||||
export function generateErrorMessage(input, message, errorValue, index, row, column) {
|
||||
return `${message} at column ${row}:${column} in expression [` +
|
||||
findProblemCode(input, errorValue, index, column) + ']';
|
||||
}
|
||||
|
||||
export function findProblemCode(input, errorValue, index, column) {
|
||||
var endOfProblemLine = index;
|
||||
var current = charCode(input, index);
|
||||
while (current > 0 && !isNewline(current)) {
|
||||
current = charCode(input, ++endOfProblemLine);
|
||||
}
|
||||
var choppedString = input.substring(0, endOfProblemLine);
|
||||
var pointerPadding = "";
|
||||
for (var i = 0; i < column; i++) {
|
||||
pointerPadding += " ";
|
||||
}
|
||||
var pointerString = "";
|
||||
for (var i = 0; i < errorValue.length; i++) {
|
||||
pointerString += "^";
|
||||
}
|
||||
return choppedString + "\n" + pointerPadding + pointerString + "\n";
|
||||
}
|
||||
|
||||
export class CssToken {
|
||||
numValue: number;
|
||||
constructor(public index: number, public column: number, public line: number,
|
||||
public type: CssTokenType, public strValue: string) {
|
||||
this.numValue = charCode(strValue, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssLexer {
|
||||
scan(text: string, trackComments: boolean = false): CssScanner {
|
||||
return new CssScanner(text, trackComments);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssScannerError extends BaseException {
|
||||
public rawMessage: string;
|
||||
public message: string;
|
||||
|
||||
constructor(public token: CssToken, message) {
|
||||
super('Css Parse Error: ' + message);
|
||||
this.rawMessage = message;
|
||||
}
|
||||
|
||||
toString(): string { return this.message; }
|
||||
}
|
||||
|
||||
function _trackWhitespace(mode: CssLexerMode) {
|
||||
switch (mode) {
|
||||
case CssLexerMode.SELECTOR:
|
||||
case CssLexerMode.ALL_TRACK_WS:
|
||||
case CssLexerMode.STYLE_VALUE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class CssScanner {
|
||||
peek: number;
|
||||
peekPeek: number;
|
||||
length: number = 0;
|
||||
index: number = -1;
|
||||
column: number = -1;
|
||||
line: number = 0;
|
||||
|
||||
_currentMode: CssLexerMode = CssLexerMode.BLOCK;
|
||||
_currentError: CssScannerError = null;
|
||||
|
||||
constructor(public input: string, private _trackComments: boolean = false) {
|
||||
this.length = this.input.length;
|
||||
this.peekPeek = this.peekAt(0);
|
||||
this.advance();
|
||||
}
|
||||
|
||||
getMode(): CssLexerMode { return this._currentMode; }
|
||||
|
||||
setMode(mode: CssLexerMode) {
|
||||
if (this._currentMode != mode) {
|
||||
if (_trackWhitespace(this._currentMode)) {
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
this._currentMode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
advance(): void {
|
||||
if (isNewline(this.peek)) {
|
||||
this.column = 0;
|
||||
this.line++;
|
||||
} else {
|
||||
this.column++;
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.peek = this.peekPeek;
|
||||
this.peekPeek = this.peekAt(this.index + 1);
|
||||
}
|
||||
|
||||
peekAt(index): number {
|
||||
return index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, index);
|
||||
}
|
||||
|
||||
consumeEmptyStatements(): void {
|
||||
this.consumeWhitespace();
|
||||
while (this.peek == $SEMICOLON) {
|
||||
this.advance();
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
}
|
||||
|
||||
consumeWhitespace(): void {
|
||||
while (isWhitespace(this.peek) || isNewline(this.peek)) {
|
||||
this.advance();
|
||||
if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) {
|
||||
this.advance(); // /
|
||||
this.advance(); // *
|
||||
while (!isCommentEnd(this.peek, this.peekPeek)) {
|
||||
if (this.peek == $EOF) {
|
||||
this.error('Unterminated comment');
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
this.advance(); // *
|
||||
this.advance(); // /
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
consume(type: CssTokenType, value: string = null): LexedCssResult {
|
||||
var mode = this._currentMode;
|
||||
this.setMode(CssLexerMode.ALL);
|
||||
|
||||
var previousIndex = this.index;
|
||||
var previousLine = this.line;
|
||||
var previousColumn = this.column;
|
||||
|
||||
var output = this.scan();
|
||||
|
||||
// just incase the inner scan method returned an error
|
||||
if (isPresent(output.error)) {
|
||||
this.setMode(mode);
|
||||
return output;
|
||||
}
|
||||
|
||||
var next = output.token;
|
||||
if (!isPresent(next)) {
|
||||
next = new CssToken(0, 0, 0, CssTokenType.EOF, "end of file");
|
||||
}
|
||||
|
||||
var isMatchingType;
|
||||
if (type == CssTokenType.IdentifierOrNumber) {
|
||||
// TODO (matsko): implement array traversal for lookup here
|
||||
isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier;
|
||||
} else {
|
||||
isMatchingType = next.type == type;
|
||||
}
|
||||
|
||||
// before throwing the error we need to bring back the former
|
||||
// mode so that the parser can recover...
|
||||
this.setMode(mode);
|
||||
|
||||
var error = null;
|
||||
if (!isMatchingType || (isPresent(value) && value != next.strValue)) {
|
||||
var errorMessage = resolveEnumToken(CssTokenType, next.type) + " does not match expected " +
|
||||
resolveEnumToken(CssTokenType, type) + " value";
|
||||
|
||||
if (isPresent(value)) {
|
||||
errorMessage += ' ("' + next.strValue + '" should match "' + value + '")';
|
||||
}
|
||||
|
||||
error = new CssScannerError(
|
||||
next, generateErrorMessage(this.input, errorMessage, next.strValue, previousIndex,
|
||||
previousLine, previousColumn));
|
||||
}
|
||||
|
||||
return new LexedCssResult(error, next);
|
||||
}
|
||||
|
||||
|
||||
scan(): LexedCssResult {
|
||||
var trackWS = _trackWhitespace(this._currentMode);
|
||||
if (this.index == 0 && !trackWS) { // first scan
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
|
||||
var token = this._scan();
|
||||
if (token == null) return null;
|
||||
|
||||
var error = this._currentError;
|
||||
this._currentError = null;
|
||||
|
||||
if (!trackWS) {
|
||||
this.consumeWhitespace();
|
||||
}
|
||||
return new LexedCssResult(error, token);
|
||||
}
|
||||
|
||||
_scan(): CssToken {
|
||||
var peek = this.peek;
|
||||
var peekPeek = this.peekPeek;
|
||||
if (peek == $EOF) return null;
|
||||
|
||||
if (isCommentStart(peek, peekPeek)) {
|
||||
// even if comments are not tracked we still lex the
|
||||
// comment so we can move the pointer forward
|
||||
var commentToken = this.scanComment();
|
||||
if (this._trackComments) {
|
||||
return commentToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (_trackWhitespace(this._currentMode) && (isWhitespace(peek) || isNewline(peek))) {
|
||||
return this.scanWhitespace();
|
||||
}
|
||||
|
||||
peek = this.peek;
|
||||
peekPeek = this.peekPeek;
|
||||
if (peek == $EOF) return null;
|
||||
|
||||
if (isStringStart(peek, peekPeek)) {
|
||||
return this.scanString();
|
||||
}
|
||||
|
||||
// something like url(cool)
|
||||
if (this._currentMode == CssLexerMode.STYLE_VALUE_FUNCTION) {
|
||||
return this.scanCssValueFunction();
|
||||
}
|
||||
|
||||
var isModifier = peek == $PLUS || peek == $MINUS;
|
||||
var digitA = isModifier ? false : isDigit(peek);
|
||||
var digitB = isDigit(peekPeek);
|
||||
if (digitA || (isModifier && (peekPeek == $PERIOD || digitB)) || (peek == $PERIOD && digitB)) {
|
||||
return this.scanNumber();
|
||||
}
|
||||
|
||||
if (peek == $AT) {
|
||||
return this.scanAtExpression();
|
||||
}
|
||||
|
||||
if (isIdentifierStart(peek, peekPeek)) {
|
||||
return this.scanIdentifier();
|
||||
}
|
||||
|
||||
if (isValidCssCharacter(peek, this._currentMode)) {
|
||||
return this.scanCharacter();
|
||||
}
|
||||
|
||||
return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`);
|
||||
}
|
||||
|
||||
scanComment() {
|
||||
if (this.assertCondition(isCommentStart(this.peek, this.peekPeek),
|
||||
"Expected comment start value")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
var startingLine = this.line;
|
||||
|
||||
this.advance(); // /
|
||||
this.advance(); // *
|
||||
|
||||
while (!isCommentEnd(this.peek, this.peekPeek)) {
|
||||
if (this.peek == $EOF) {
|
||||
this.error('Unterminated comment');
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
|
||||
this.advance(); // *
|
||||
this.advance(); // /
|
||||
|
||||
var str = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, startingLine, CssTokenType.Comment, str);
|
||||
}
|
||||
|
||||
scanWhitespace() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
var startingLine = this.line;
|
||||
while (isWhitespace(this.peek) && this.peek != $EOF) {
|
||||
this.advance();
|
||||
}
|
||||
var str = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, startingLine, CssTokenType.Whitespace, str);
|
||||
}
|
||||
|
||||
scanString() {
|
||||
if (this.assertCondition(isStringStart(this.peek, this.peekPeek),
|
||||
"Unexpected non-string starting value")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var target = this.peek;
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
var startingLine = this.line;
|
||||
var previous = target;
|
||||
this.advance();
|
||||
|
||||
while (!isCharMatch(target, previous, this.peek)) {
|
||||
if (this.peek == $EOF || isNewline(this.peek)) {
|
||||
this.error('Unterminated quote');
|
||||
}
|
||||
previous = this.peek;
|
||||
this.advance();
|
||||
}
|
||||
|
||||
if (this.assertCondition(this.peek == target, "Unterminated quote")) {
|
||||
return null;
|
||||
}
|
||||
this.advance();
|
||||
|
||||
var str = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, startingLine, CssTokenType.String, str);
|
||||
}
|
||||
|
||||
scanNumber() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
if (this.peek == $PLUS || this.peek == $MINUS) {
|
||||
this.advance();
|
||||
}
|
||||
var periodUsed = false;
|
||||
while (isDigit(this.peek) || this.peek == $PERIOD) {
|
||||
if (this.peek == $PERIOD) {
|
||||
if (periodUsed) {
|
||||
this.error('Unexpected use of a second period value');
|
||||
}
|
||||
periodUsed = true;
|
||||
}
|
||||
this.advance();
|
||||
}
|
||||
var strValue = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Number, strValue);
|
||||
}
|
||||
|
||||
scanIdentifier() {
|
||||
if (this.assertCondition(isIdentifierStart(this.peek, this.peekPeek),
|
||||
'Expected identifier starting value')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
while (isIdentifierPart(this.peek)) {
|
||||
this.advance();
|
||||
}
|
||||
var strValue = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
|
||||
}
|
||||
|
||||
scanCssValueFunction() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
while (this.peek != $EOF && this.peek != $RPAREN) {
|
||||
this.advance();
|
||||
}
|
||||
var strValue = this.input.substring(start, this.index);
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
|
||||
}
|
||||
|
||||
scanCharacter() {
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
if (this.assertCondition(isValidCssCharacter(this.peek, this._currentMode),
|
||||
charStr(this.peek) + ' is not a valid CSS character')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var c = this.input.substring(start, start + 1);
|
||||
this.advance();
|
||||
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.Character, c);
|
||||
}
|
||||
|
||||
scanAtExpression() {
|
||||
if (this.assertCondition(this.peek == $AT, 'Expected @ value')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var start = this.index;
|
||||
var startingColumn = this.column;
|
||||
this.advance();
|
||||
if (isIdentifierStart(this.peek, this.peekPeek)) {
|
||||
var ident = this.scanIdentifier();
|
||||
var strValue = '@' + ident.strValue;
|
||||
return new CssToken(start, startingColumn, this.line, CssTokenType.AtKeyword, strValue);
|
||||
} else {
|
||||
return this.scanCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
assertCondition(status: boolean, errorMessage: string): boolean {
|
||||
if (!status) {
|
||||
this.error(errorMessage);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
error(message: string, errorTokenValue: string = null, doNotAdvance: boolean = false): CssToken {
|
||||
var index: number = this.index;
|
||||
var column: number = this.column;
|
||||
var line: number = this.line;
|
||||
errorTokenValue =
|
||||
isPresent(errorTokenValue) ? errorTokenValue : StringWrapper.fromCharCode(this.peek);
|
||||
var invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue);
|
||||
var errorMessage =
|
||||
generateErrorMessage(this.input, message, errorTokenValue, index, line, column);
|
||||
if (!doNotAdvance) {
|
||||
this.advance();
|
||||
}
|
||||
this._currentError = new CssScannerError(invalidToken, errorMessage);
|
||||
return invalidToken;
|
||||
}
|
||||
}
|
||||
|
||||
function isAtKeyword(current: CssToken, next: CssToken): boolean {
|
||||
return current.numValue == $AT && next.type == CssTokenType.Identifier;
|
||||
}
|
||||
|
||||
function isCharMatch(target: number, previous: number, code: number) {
|
||||
return code == target && previous != $BACKSLASH;
|
||||
}
|
||||
|
||||
function isDigit(code: number): boolean {
|
||||
return $0 <= code && code <= $9;
|
||||
}
|
||||
|
||||
function isCommentStart(code: number, next: number) {
|
||||
return code == $SLASH && next == $STAR;
|
||||
}
|
||||
|
||||
function isCommentEnd(code: number, next: number) {
|
||||
return code == $STAR && next == $SLASH;
|
||||
}
|
||||
|
||||
function isStringStart(code: number, next: number): boolean {
|
||||
var target = code;
|
||||
if (target == $BACKSLASH) {
|
||||
target = next;
|
||||
}
|
||||
return target == $DQ || target == $SQ;
|
||||
}
|
||||
|
||||
function isIdentifierStart(code: number, next: number): boolean {
|
||||
var target = code;
|
||||
if (target == $MINUS) {
|
||||
target = next;
|
||||
}
|
||||
|
||||
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
|
||||
target == $MINUS || target == $_;
|
||||
}
|
||||
|
||||
function isIdentifierPart(target: number) {
|
||||
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
|
||||
target == $MINUS || target == $_ || isDigit(target);
|
||||
}
|
||||
|
||||
function isValidPseudoSelectorCharacter(code: number) {
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidKeyframeBlockCharacter(code: number) {
|
||||
return code == $PERCENT;
|
||||
}
|
||||
|
||||
function isValidAttributeSelectorCharacter(code: number) {
|
||||
// value^*|$~=something
|
||||
switch (code) {
|
||||
case $$:
|
||||
case $PIPE:
|
||||
case $CARET:
|
||||
case $TILDA:
|
||||
case $STAR:
|
||||
case $EQ:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidSelectorCharacter(code: number) {
|
||||
// selector [ key = value ]
|
||||
// IDENT C IDENT C IDENT C
|
||||
// #id, .class, *+~>
|
||||
// tag:PSEUDO
|
||||
switch (code) {
|
||||
case $HASH:
|
||||
case $PERIOD:
|
||||
case $TILDA:
|
||||
case $STAR:
|
||||
case $PLUS:
|
||||
case $GT:
|
||||
case $COLON:
|
||||
case $PIPE:
|
||||
case $COMMA:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidStyleBlockCharacter(code: number) {
|
||||
// key:value;
|
||||
// key:calc(something ... )
|
||||
switch (code) {
|
||||
case $HASH:
|
||||
case $SEMICOLON:
|
||||
case $COLON:
|
||||
case $PERCENT:
|
||||
case $SLASH:
|
||||
case $BACKSLASH:
|
||||
case $BANG:
|
||||
case $PERIOD:
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidMediaQueryRuleCharacter(code: number) {
|
||||
// (min-width: 7.5em) and (orientation: landscape)
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $COLON:
|
||||
case $PERCENT:
|
||||
case $PERIOD:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidAtRuleCharacter(code: number) {
|
||||
// @document url(http://www.w3.org/page?something=on#hash),
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $COLON:
|
||||
case $PERCENT:
|
||||
case $PERIOD:
|
||||
case $SLASH:
|
||||
case $BACKSLASH:
|
||||
case $HASH:
|
||||
case $EQ:
|
||||
case $QUESTION:
|
||||
case $AMPERSAND:
|
||||
case $STAR:
|
||||
case $COMMA:
|
||||
case $MINUS:
|
||||
case $PLUS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidStyleFunctionCharacter(code: number) {
|
||||
switch (code) {
|
||||
case $PERIOD:
|
||||
case $MINUS:
|
||||
case $PLUS:
|
||||
case $STAR:
|
||||
case $SLASH:
|
||||
case $LPAREN:
|
||||
case $RPAREN:
|
||||
case $COMMA:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidBlockCharacter(code: number) {
|
||||
// @something { }
|
||||
// IDENT
|
||||
return code == $AT;
|
||||
}
|
||||
|
||||
function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
|
||||
switch (mode) {
|
||||
case CssLexerMode.ALL:
|
||||
case CssLexerMode.ALL_TRACK_WS:
|
||||
return true;
|
||||
|
||||
case CssLexerMode.SELECTOR:
|
||||
return isValidSelectorCharacter(code);
|
||||
|
||||
case CssLexerMode.PSEUDO_SELECTOR:
|
||||
return isValidPseudoSelectorCharacter(code);
|
||||
|
||||
case CssLexerMode.ATTRIBUTE_SELECTOR:
|
||||
return isValidAttributeSelectorCharacter(code);
|
||||
|
||||
case CssLexerMode.MEDIA_QUERY:
|
||||
return isValidMediaQueryRuleCharacter(code);
|
||||
|
||||
case CssLexerMode.AT_RULE_QUERY:
|
||||
return isValidAtRuleCharacter(code);
|
||||
|
||||
case CssLexerMode.KEYFRAME_BLOCK:
|
||||
return isValidKeyframeBlockCharacter(code);
|
||||
|
||||
case CssLexerMode.STYLE_BLOCK:
|
||||
case CssLexerMode.STYLE_VALUE:
|
||||
return isValidStyleBlockCharacter(code);
|
||||
|
||||
case CssLexerMode.STYLE_CALC_FUNCTION:
|
||||
return isValidStyleFunctionCharacter(code);
|
||||
|
||||
case CssLexerMode.BLOCK:
|
||||
return isValidBlockCharacter(code);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function charCode(input, index): number {
|
||||
return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index);
|
||||
}
|
||||
|
||||
function charStr(code: number): string {
|
||||
return StringWrapper.fromCharCode(code);
|
||||
}
|
||||
|
||||
export function isNewline(code): boolean {
|
||||
switch (code) {
|
||||
case $FF:
|
||||
case $CR:
|
||||
case $LF:
|
||||
case $VTAB:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,721 +0,0 @@
|
||||
import {
|
||||
ParseSourceSpan,
|
||||
ParseSourceFile,
|
||||
ParseLocation,
|
||||
ParseError
|
||||
} from "angular2/src/compiler/parse_util";
|
||||
|
||||
import {
|
||||
bitWiseOr,
|
||||
bitWiseAnd,
|
||||
NumberWrapper,
|
||||
StringWrapper,
|
||||
isPresent
|
||||
} from "angular2/src/facade/lang";
|
||||
|
||||
import {
|
||||
CssLexerMode,
|
||||
CssToken,
|
||||
CssTokenType,
|
||||
CssScanner,
|
||||
CssScannerError,
|
||||
generateErrorMessage,
|
||||
$AT,
|
||||
$EOF,
|
||||
$RBRACE,
|
||||
$LBRACE,
|
||||
$LBRACKET,
|
||||
$RBRACKET,
|
||||
$LPAREN,
|
||||
$RPAREN,
|
||||
$COMMA,
|
||||
$COLON,
|
||||
$SEMICOLON,
|
||||
isNewline
|
||||
} from "angular2/src/compiler/css/lexer";
|
||||
|
||||
export {CssToken} from "angular2/src/compiler/css/lexer";
|
||||
|
||||
export enum BlockType {
|
||||
Import,
|
||||
Charset,
|
||||
Namespace,
|
||||
Supports,
|
||||
Keyframes,
|
||||
MediaQuery,
|
||||
Selector,
|
||||
FontFace,
|
||||
Page,
|
||||
Document,
|
||||
Viewport,
|
||||
Unsupported
|
||||
}
|
||||
|
||||
const EOF_DELIM = 1;
|
||||
const RBRACE_DELIM = 2;
|
||||
const LBRACE_DELIM = 4;
|
||||
const COMMA_DELIM = 8;
|
||||
const COLON_DELIM = 16;
|
||||
const SEMICOLON_DELIM = 32;
|
||||
const NEWLINE_DELIM = 64;
|
||||
const RPAREN_DELIM = 128;
|
||||
|
||||
function mergeTokens(tokens: CssToken[], separator: string = ""): CssToken {
|
||||
var mainToken = tokens[0];
|
||||
var str = mainToken.strValue;
|
||||
for (var i = 1; i < tokens.length; i++) {
|
||||
str += separator + tokens[i].strValue;
|
||||
}
|
||||
|
||||
return new CssToken(mainToken.index, mainToken.column, mainToken.line, mainToken.type, str);
|
||||
}
|
||||
|
||||
function getDelimFromToken(token: CssToken): number {
|
||||
return getDelimFromCharacter(token.numValue);
|
||||
}
|
||||
|
||||
function getDelimFromCharacter(code: number): number {
|
||||
switch (code) {
|
||||
case $EOF:
|
||||
return EOF_DELIM;
|
||||
case $COMMA:
|
||||
return COMMA_DELIM;
|
||||
case $COLON:
|
||||
return COLON_DELIM;
|
||||
case $SEMICOLON:
|
||||
return SEMICOLON_DELIM;
|
||||
case $RBRACE:
|
||||
return RBRACE_DELIM;
|
||||
case $LBRACE:
|
||||
return LBRACE_DELIM;
|
||||
case $RPAREN:
|
||||
return RPAREN_DELIM;
|
||||
default:
|
||||
return isNewline(code) ? NEWLINE_DELIM : 0;
|
||||
}
|
||||
}
|
||||
|
||||
function characterContainsDelimiter(code: number, delimiters: number) {
|
||||
return bitWiseAnd([getDelimFromCharacter(code), delimiters]) > 0;
|
||||
}
|
||||
|
||||
export class CssAST {
|
||||
visit(visitor: CssASTVisitor, context?: any): void {}
|
||||
}
|
||||
|
||||
export interface CssASTVisitor {
|
||||
visitCssValue(ast: CssStyleValueAST, context?: any): void;
|
||||
visitInlineCssRule(ast: CssInlineRuleAST, context?: any): void;
|
||||
visitCssKeyframeRule(ast: CssKeyframeRuleAST, context?: any): void;
|
||||
visitCssKeyframeDefinition(ast: CssKeyframeDefinitionAST, context?: any): void;
|
||||
visitCssMediaQueryRule(ast: CssMediaQueryRuleAST, context?: any): void;
|
||||
visitCssSelectorRule(ast: CssSelectorRuleAST, context?: any): void;
|
||||
visitCssSelector(ast: CssSelectorAST, context?: any): void;
|
||||
visitCssDefinition(ast: CssDefinitionAST, context?: any): void;
|
||||
visitCssBlock(ast: CssBlockAST, context?: any): void;
|
||||
visitCssStyleSheet(ast: CssStyleSheetAST, context?: any): void;
|
||||
visitUnkownRule(ast: CssUnknownTokenListAST, context?: any): void;
|
||||
}
|
||||
|
||||
export class ParsedCssResult {
|
||||
constructor(public errors: CssParseError[], public ast: CssStyleSheetAST) {}
|
||||
}
|
||||
|
||||
export class CssParser {
|
||||
private _errors: CssParseError[] = [];
|
||||
private _file: ParseSourceFile;
|
||||
|
||||
constructor(private _scanner: CssScanner, private _fileName: string) {
|
||||
this._file = new ParseSourceFile(this._scanner.input, _fileName);
|
||||
}
|
||||
|
||||
_resolveBlockType(token: CssToken): BlockType {
|
||||
switch (token.strValue) {
|
||||
case '@-o-keyframes':
|
||||
case '@-moz-keyframes':
|
||||
case '@-webkit-keyframes':
|
||||
case '@keyframes':
|
||||
return BlockType.Keyframes;
|
||||
|
||||
case '@charset':
|
||||
return BlockType.Charset;
|
||||
|
||||
case '@import':
|
||||
return BlockType.Import;
|
||||
|
||||
case '@namespace':
|
||||
return BlockType.Namespace;
|
||||
|
||||
case '@page':
|
||||
return BlockType.Page;
|
||||
|
||||
case '@document':
|
||||
return BlockType.Document;
|
||||
|
||||
case '@media':
|
||||
return BlockType.MediaQuery;
|
||||
|
||||
case '@font-face':
|
||||
return BlockType.FontFace;
|
||||
|
||||
case '@viewport':
|
||||
return BlockType.Viewport;
|
||||
|
||||
case '@supports':
|
||||
return BlockType.Supports;
|
||||
|
||||
default:
|
||||
return BlockType.Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
parse(): ParsedCssResult {
|
||||
var delimiters: number = EOF_DELIM;
|
||||
var ast = this._parseStyleSheet(delimiters);
|
||||
|
||||
var errors = this._errors;
|
||||
this._errors = [];
|
||||
|
||||
return new ParsedCssResult(errors, ast);
|
||||
}
|
||||
|
||||
_parseStyleSheet(delimiters): CssStyleSheetAST {
|
||||
var results = [];
|
||||
this._scanner.consumeEmptyStatements();
|
||||
while (this._scanner.peek != $EOF) {
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
results.push(this._parseRule(delimiters));
|
||||
}
|
||||
return new CssStyleSheetAST(results);
|
||||
}
|
||||
|
||||
_parseRule(delimiters: number): CssRuleAST {
|
||||
if (this._scanner.peek == $AT) {
|
||||
return this._parseAtRule(delimiters);
|
||||
}
|
||||
return this._parseSelectorRule(delimiters);
|
||||
}
|
||||
|
||||
_parseAtRule(delimiters: number): CssRuleAST {
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
|
||||
var token = this._scan();
|
||||
|
||||
this._assertCondition(token.type == CssTokenType.AtKeyword,
|
||||
`The CSS Rule ${token.strValue} is not a valid [@] rule.`, token);
|
||||
|
||||
var block, type = this._resolveBlockType(token);
|
||||
switch (type) {
|
||||
case BlockType.Charset:
|
||||
case BlockType.Namespace:
|
||||
case BlockType.Import:
|
||||
var value = this._parseValue(delimiters);
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
return new CssInlineRuleAST(type, value);
|
||||
|
||||
case BlockType.Viewport:
|
||||
case BlockType.FontFace:
|
||||
block = this._parseStyleBlock(delimiters);
|
||||
return new CssBlockRuleAST(type, block);
|
||||
|
||||
case BlockType.Keyframes:
|
||||
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
|
||||
// keyframes only have one identifier name
|
||||
var name = tokens[0];
|
||||
return new CssKeyframeRuleAST(name, this._parseKeyframeBlock(delimiters));
|
||||
|
||||
case BlockType.MediaQuery:
|
||||
this._scanner.setMode(CssLexerMode.MEDIA_QUERY);
|
||||
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
|
||||
return new CssMediaQueryRuleAST(tokens, this._parseBlock(delimiters));
|
||||
|
||||
case BlockType.Document:
|
||||
case BlockType.Supports:
|
||||
case BlockType.Page:
|
||||
this._scanner.setMode(CssLexerMode.AT_RULE_QUERY);
|
||||
var tokens = this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]));
|
||||
return new CssBlockDefinitionRuleAST(type, tokens, this._parseBlock(delimiters));
|
||||
|
||||
// if a custom @rule { ... } is used it should still tokenize the insides
|
||||
default:
|
||||
var listOfTokens = [];
|
||||
this._scanner.setMode(CssLexerMode.ALL);
|
||||
this._error(generateErrorMessage(
|
||||
this._scanner.input,
|
||||
`The CSS "at" rule "${token.strValue}" is not allowed to used here`,
|
||||
token.strValue, token.index, token.line, token.column),
|
||||
token);
|
||||
|
||||
this._collectUntilDelim(bitWiseOr([delimiters, LBRACE_DELIM, SEMICOLON_DELIM]))
|
||||
.forEach((token) => { listOfTokens.push(token); });
|
||||
if (this._scanner.peek == $LBRACE) {
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
this._collectUntilDelim(bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]))
|
||||
.forEach((token) => { listOfTokens.push(token); });
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
}
|
||||
return new CssUnknownTokenListAST(token, listOfTokens);
|
||||
}
|
||||
}
|
||||
|
||||
_parseSelectorRule(delimiters: number): CssSelectorRuleAST {
|
||||
var selectors = this._parseSelectors(delimiters);
|
||||
var block = this._parseStyleBlock(delimiters);
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
return new CssSelectorRuleAST(selectors, block);
|
||||
}
|
||||
|
||||
_parseSelectors(delimiters: number): CssSelectorAST[] {
|
||||
delimiters = bitWiseOr([delimiters, LBRACE_DELIM]);
|
||||
|
||||
var selectors = [];
|
||||
var isParsingSelectors = true;
|
||||
while (isParsingSelectors) {
|
||||
selectors.push(this._parseSelector(delimiters));
|
||||
|
||||
isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters);
|
||||
|
||||
if (isParsingSelectors) {
|
||||
this._consume(CssTokenType.Character, ',');
|
||||
isParsingSelectors = !characterContainsDelimiter(this._scanner.peek, delimiters);
|
||||
}
|
||||
}
|
||||
|
||||
return selectors;
|
||||
}
|
||||
|
||||
_scan(): CssToken {
|
||||
var output = this._scanner.scan();
|
||||
var token = output.token;
|
||||
var error = output.error;
|
||||
if (isPresent(error)) {
|
||||
this._error(error.rawMessage, token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
_consume(type: CssTokenType, value: string = null): CssToken {
|
||||
var output = this._scanner.consume(type, value);
|
||||
var token = output.token;
|
||||
var error = output.error;
|
||||
if (isPresent(error)) {
|
||||
this._error(error.rawMessage, token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
_parseKeyframeBlock(delimiters: number): CssBlockAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM]);
|
||||
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
|
||||
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
|
||||
var definitions = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
definitions.push(this._parseKeyframeDefinition(delimiters));
|
||||
}
|
||||
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
|
||||
return new CssBlockAST(definitions);
|
||||
}
|
||||
|
||||
_parseKeyframeDefinition(delimiters: number): CssKeyframeDefinitionAST {
|
||||
var stepTokens = [];
|
||||
delimiters = bitWiseOr([delimiters, LBRACE_DELIM]);
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
stepTokens.push(this._parseKeyframeLabel(bitWiseOr([delimiters, COMMA_DELIM])));
|
||||
if (this._scanner.peek != $LBRACE) {
|
||||
this._consume(CssTokenType.Character, ',');
|
||||
}
|
||||
}
|
||||
var styles = this._parseStyleBlock(bitWiseOr([delimiters, RBRACE_DELIM]));
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
return new CssKeyframeDefinitionAST(stepTokens, styles);
|
||||
}
|
||||
|
||||
_parseKeyframeLabel(delimiters: number): CssToken {
|
||||
this._scanner.setMode(CssLexerMode.KEYFRAME_BLOCK);
|
||||
return mergeTokens(this._collectUntilDelim(delimiters));
|
||||
}
|
||||
|
||||
_parseSelector(delimiters: number): CssSelectorAST {
|
||||
delimiters = bitWiseOr([delimiters, COMMA_DELIM, LBRACE_DELIM]);
|
||||
this._scanner.setMode(CssLexerMode.SELECTOR);
|
||||
|
||||
var selectorCssTokens = [];
|
||||
var isComplex = false;
|
||||
var wsCssToken;
|
||||
|
||||
var previousToken;
|
||||
var parenCount = 0;
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
var code = this._scanner.peek;
|
||||
switch (code) {
|
||||
case $LPAREN:
|
||||
parenCount++;
|
||||
break;
|
||||
|
||||
case $RPAREN:
|
||||
parenCount--;
|
||||
break;
|
||||
|
||||
case $COLON:
|
||||
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR);
|
||||
previousToken = this._consume(CssTokenType.Character, ':');
|
||||
selectorCssTokens.push(previousToken);
|
||||
continue;
|
||||
|
||||
case $LBRACKET:
|
||||
// if we are already inside an attribute selector then we can't
|
||||
// jump into the mode again. Therefore this error will get picked
|
||||
// up when the scan method is called below.
|
||||
if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) {
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, '['));
|
||||
this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case $RBRACKET:
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, ']'));
|
||||
this._scanner.setMode(CssLexerMode.SELECTOR);
|
||||
continue;
|
||||
}
|
||||
|
||||
var token = this._scan();
|
||||
|
||||
// special case for the ":not(" selector since it
|
||||
// contains an inner selector that needs to be parsed
|
||||
// in isolation
|
||||
if (this._scanner.getMode() == CssLexerMode.PSEUDO_SELECTOR && isPresent(previousToken) &&
|
||||
previousToken.numValue == $COLON && token.strValue == "not" &&
|
||||
this._scanner.peek == $LPAREN) {
|
||||
selectorCssTokens.push(token);
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, '('));
|
||||
|
||||
// the inner selector inside of :not(...) can only be one
|
||||
// CSS selector (no commas allowed) therefore we parse only
|
||||
// one selector by calling the method below
|
||||
this._parseSelector(bitWiseOr([delimiters, RPAREN_DELIM]))
|
||||
.tokens.forEach(
|
||||
(innerSelectorToken) => { selectorCssTokens.push(innerSelectorToken); });
|
||||
|
||||
selectorCssTokens.push(this._consume(CssTokenType.Character, ')'));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
previousToken = token;
|
||||
|
||||
if (token.type == CssTokenType.Whitespace) {
|
||||
wsCssToken = token;
|
||||
} else {
|
||||
if (isPresent(wsCssToken)) {
|
||||
selectorCssTokens.push(wsCssToken);
|
||||
wsCssToken = null;
|
||||
isComplex = true;
|
||||
}
|
||||
selectorCssTokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._scanner.getMode() == CssLexerMode.ATTRIBUTE_SELECTOR) {
|
||||
this._error(
|
||||
`Unbalanced CSS attribute selector at column ${previousToken.line}:${previousToken.column}`,
|
||||
previousToken);
|
||||
} else if (parenCount > 0) {
|
||||
this._error(
|
||||
`Unbalanced pseudo selector function value at column ${previousToken.line}:${previousToken.column}`,
|
||||
previousToken);
|
||||
}
|
||||
|
||||
return new CssSelectorAST(selectorCssTokens, isComplex);
|
||||
}
|
||||
|
||||
_parseValue(delimiters: number): CssStyleValueAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM, SEMICOLON_DELIM, NEWLINE_DELIM]);
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
|
||||
|
||||
var strValue = "";
|
||||
var tokens = [];
|
||||
var previous: CssToken;
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
var token;
|
||||
if (isPresent(previous) && previous.type == CssTokenType.Identifier &&
|
||||
this._scanner.peek == $LPAREN) {
|
||||
token = this._consume(CssTokenType.Character, '(');
|
||||
tokens.push(token);
|
||||
strValue += token.strValue;
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_VALUE_FUNCTION);
|
||||
|
||||
token = this._scan();
|
||||
tokens.push(token);
|
||||
strValue += token.strValue;
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_VALUE);
|
||||
|
||||
token = this._consume(CssTokenType.Character, ')');
|
||||
tokens.push(token);
|
||||
strValue += token.strValue;
|
||||
} else {
|
||||
token = this._scan();
|
||||
if (token.type != CssTokenType.Whitespace) {
|
||||
tokens.push(token);
|
||||
}
|
||||
strValue += token.strValue;
|
||||
}
|
||||
|
||||
previous = token;
|
||||
}
|
||||
|
||||
this._scanner.consumeWhitespace();
|
||||
|
||||
var code = this._scanner.peek;
|
||||
if (code == $SEMICOLON) {
|
||||
this._consume(CssTokenType.Character, ';');
|
||||
} else if (code != $RBRACE) {
|
||||
this._error(
|
||||
generateErrorMessage(this._scanner.input,
|
||||
`The CSS key/value definition did not end with a semicolon`,
|
||||
previous.strValue, previous.index, previous.line, previous.column),
|
||||
previous);
|
||||
}
|
||||
|
||||
return new CssStyleValueAST(tokens, strValue);
|
||||
}
|
||||
|
||||
_collectUntilDelim(delimiters: number, assertType: CssTokenType = null): CssToken[] {
|
||||
var tokens = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
var val = isPresent(assertType) ? this._consume(assertType) : this._scan();
|
||||
tokens.push(val);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
_parseBlock(delimiters: number): CssBlockAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM]);
|
||||
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
var results = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
results.push(this._parseRule(delimiters));
|
||||
}
|
||||
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
|
||||
this._scanner.setMode(CssLexerMode.BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
return new CssBlockAST(results);
|
||||
}
|
||||
|
||||
_parseStyleBlock(delimiters: number): CssBlockAST {
|
||||
delimiters = bitWiseOr([delimiters, RBRACE_DELIM, LBRACE_DELIM]);
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
||||
|
||||
this._consume(CssTokenType.Character, '{');
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
var definitions = [];
|
||||
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
|
||||
definitions.push(this._parseDefinition(delimiters));
|
||||
this._scanner.consumeEmptyStatements();
|
||||
}
|
||||
|
||||
this._consume(CssTokenType.Character, '}');
|
||||
|
||||
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
||||
this._scanner.consumeEmptyStatements();
|
||||
|
||||
return new CssBlockAST(definitions);
|
||||
}
|
||||
|
||||
_parseDefinition(delimiters: number): CssDefinitionAST {
|
||||
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
|
||||
|
||||
var prop = this._consume(CssTokenType.Identifier);
|
||||
var parseValue, value = null;
|
||||
|
||||
// the colon value separates the prop from the style.
|
||||
// there are a few cases as to what could happen if it
|
||||
// is missing
|
||||
switch (this._scanner.peek) {
|
||||
case $COLON:
|
||||
this._consume(CssTokenType.Character, ':');
|
||||
parseValue = true;
|
||||
break;
|
||||
|
||||
case $SEMICOLON:
|
||||
case $RBRACE:
|
||||
case $EOF:
|
||||
parseValue = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
var propStr = [prop.strValue];
|
||||
if (this._scanner.peek != $COLON) {
|
||||
// this will throw the error
|
||||
var nextValue = this._consume(CssTokenType.Character, ':');
|
||||
propStr.push(nextValue.strValue);
|
||||
|
||||
var remainingTokens = this._collectUntilDelim(
|
||||
bitWiseOr([delimiters, COLON_DELIM, SEMICOLON_DELIM]), CssTokenType.Identifier);
|
||||
if (remainingTokens.length > 0) {
|
||||
remainingTokens.forEach((token) => { propStr.push(token.strValue); });
|
||||
}
|
||||
|
||||
prop = new CssToken(prop.index, prop.column, prop.line, prop.type, propStr.join(" "));
|
||||
}
|
||||
|
||||
// this means we've reached the end of the definition and/or block
|
||||
if (this._scanner.peek == $COLON) {
|
||||
this._consume(CssTokenType.Character, ':');
|
||||
parseValue = true;
|
||||
} else {
|
||||
parseValue = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (parseValue) {
|
||||
value = this._parseValue(delimiters);
|
||||
} else {
|
||||
this._error(generateErrorMessage(this._scanner.input,
|
||||
`The CSS property was not paired with a style value`,
|
||||
prop.strValue, prop.index, prop.line, prop.column),
|
||||
prop);
|
||||
}
|
||||
|
||||
return new CssDefinitionAST(prop, value);
|
||||
}
|
||||
|
||||
_assertCondition(status: boolean, errorMessage: string, problemToken: CssToken): boolean {
|
||||
if (!status) {
|
||||
this._error(errorMessage, problemToken);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_error(message: string, problemToken: CssToken) {
|
||||
var length = problemToken.strValue.length;
|
||||
var error = CssParseError.create(this._file, 0, problemToken.line, problemToken.column, length,
|
||||
message);
|
||||
this._errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssStyleValueAST extends CssAST {
|
||||
constructor(public tokens: CssToken[], public strValue: string) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssValue(this); }
|
||||
}
|
||||
|
||||
export class CssRuleAST extends CssAST {}
|
||||
|
||||
export class CssBlockRuleAST extends CssRuleAST {
|
||||
constructor(public type: BlockType, public block: CssBlockAST, public name: CssToken = null) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); }
|
||||
}
|
||||
|
||||
export class CssKeyframeRuleAST extends CssBlockRuleAST {
|
||||
constructor(name: CssToken, block: CssBlockAST) { super(BlockType.Keyframes, block, name); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssKeyframeRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssKeyframeDefinitionAST extends CssBlockRuleAST {
|
||||
public steps;
|
||||
constructor(_steps: CssToken[], block: CssBlockAST) {
|
||||
super(BlockType.Keyframes, block, mergeTokens(_steps, ","));
|
||||
this.steps = _steps;
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) {
|
||||
visitor.visitCssKeyframeDefinition(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssBlockDefinitionRuleAST extends CssBlockRuleAST {
|
||||
public strValue: string;
|
||||
constructor(type: BlockType, public query: CssToken[], block: CssBlockAST) {
|
||||
super(type, block);
|
||||
this.strValue = query.map(token => token.strValue).join("");
|
||||
var firstCssToken: CssToken = query[0];
|
||||
this.name = new CssToken(firstCssToken.index, firstCssToken.column, firstCssToken.line,
|
||||
CssTokenType.Identifier, this.strValue);
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this.block, context); }
|
||||
}
|
||||
|
||||
export class CssMediaQueryRuleAST extends CssBlockDefinitionRuleAST {
|
||||
constructor(query: CssToken[], block: CssBlockAST) { super(BlockType.MediaQuery, query, block); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssMediaQueryRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssInlineRuleAST extends CssRuleAST {
|
||||
constructor(public type: BlockType, public value: CssStyleValueAST) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitInlineCssRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssSelectorRuleAST extends CssBlockRuleAST {
|
||||
public strValue: string;
|
||||
|
||||
constructor(public selectors: CssSelectorAST[], block: CssBlockAST) {
|
||||
super(BlockType.Selector, block);
|
||||
this.strValue = selectors.map(selector => selector.strValue).join(",");
|
||||
}
|
||||
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelectorRule(this, context); }
|
||||
}
|
||||
|
||||
export class CssDefinitionAST extends CssAST {
|
||||
constructor(public property: CssToken, public value: CssStyleValueAST) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssDefinition(this, context); }
|
||||
}
|
||||
|
||||
export class CssSelectorAST extends CssAST {
|
||||
public strValue;
|
||||
constructor(public tokens: CssToken[], public isComplex: boolean = false) {
|
||||
super();
|
||||
this.strValue = tokens.map(token => token.strValue).join("");
|
||||
}
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssSelector(this, context); }
|
||||
}
|
||||
|
||||
export class CssBlockAST extends CssAST {
|
||||
constructor(public entries: CssAST[]) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssBlock(this, context); }
|
||||
}
|
||||
|
||||
export class CssStyleSheetAST extends CssAST {
|
||||
constructor(public rules: CssAST[]) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitCssStyleSheet(this, context); }
|
||||
}
|
||||
|
||||
export class CssParseError extends ParseError {
|
||||
static create(file: ParseSourceFile, offset: number, line: number, col: number, length: number,
|
||||
errMsg: string): CssParseError {
|
||||
var start = new ParseLocation(file, offset, line, col);
|
||||
var end = new ParseLocation(file, offset, line, col + length);
|
||||
var span = new ParseSourceSpan(start, end);
|
||||
return new CssParseError(span, "CSS Parse Error: " + errMsg);
|
||||
}
|
||||
|
||||
constructor(span: ParseSourceSpan, message: string) { super(span, message); }
|
||||
}
|
||||
|
||||
export class CssUnknownTokenListAST extends CssRuleAST {
|
||||
constructor(public name, public tokens: CssToken[]) { super(); }
|
||||
visit(visitor: CssASTVisitor, context?: any) { visitor.visitUnkownRule(this, context); }
|
||||
}
|
@ -31,7 +31,7 @@ export abstract class CompileMetadataWithIdentifier {
|
||||
|
||||
abstract toJson(): {[key: string]: any};
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
||||
get identifier(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
}
|
||||
|
||||
export abstract class CompileMetadataWithType extends CompileMetadataWithIdentifier {
|
||||
@ -41,9 +41,9 @@ export abstract class CompileMetadataWithType extends CompileMetadataWithIdentif
|
||||
|
||||
abstract toJson(): {[key: string]: any};
|
||||
|
||||
get type(): CompileTypeMetadata { return <CompileTypeMetadata>unimplemented(); }
|
||||
get type(): CompileTypeMetadata { return unimplemented(); }
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
||||
get identifier(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
}
|
||||
|
||||
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
|
||||
@ -628,4 +628,4 @@ function objFromJson(obj: any, fn: (a: {[key: string]: any}) => any): any {
|
||||
|
||||
function objToJson(obj: any): string | {[key: string]: any} {
|
||||
return (isString(obj) || isBlank(obj)) ? obj : obj.toJson();
|
||||
}
|
||||
}
|
@ -19,21 +19,14 @@ export class HtmlAttrAst implements HtmlAst {
|
||||
|
||||
export class HtmlElementAst implements HtmlAst {
|
||||
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[],
|
||||
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan,
|
||||
public endSourceSpan: ParseSourceSpan) {}
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); }
|
||||
}
|
||||
|
||||
export class HtmlCommentAst implements HtmlAst {
|
||||
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitComment(this, context); }
|
||||
}
|
||||
|
||||
export interface HtmlAstVisitor {
|
||||
visitElement(ast: HtmlElementAst, context: any): any;
|
||||
visitAttr(ast: HtmlAttrAst, context: any): any;
|
||||
visitText(ast: HtmlTextAst, context: any): any;
|
||||
visitComment(ast: HtmlCommentAst, context: any): any;
|
||||
}
|
||||
|
||||
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlCommentAst, HtmlElementAst} from './html_ast';
|
||||
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast';
|
||||
|
||||
import {Injectable} from 'angular2/src/core/di';
|
||||
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
|
||||
@ -98,11 +98,9 @@ class TreeBuilder {
|
||||
this._advanceIf(HtmlTokenType.CDATA_END);
|
||||
}
|
||||
|
||||
private _consumeComment(token: HtmlToken) {
|
||||
var text = this._advanceIf(HtmlTokenType.RAW_TEXT);
|
||||
private _consumeComment(startToken: HtmlToken) {
|
||||
this._advanceIf(HtmlTokenType.RAW_TEXT);
|
||||
this._advanceIf(HtmlTokenType.COMMENT_END);
|
||||
var value = isPresent(text) ? text.parts[0].trim() : null;
|
||||
this._addToParent(new HtmlCommentAst(value, token.sourceSpan));
|
||||
}
|
||||
|
||||
private _consumeText(token: HtmlToken) {
|
||||
@ -154,12 +152,11 @@ class TreeBuilder {
|
||||
selfClosing = false;
|
||||
}
|
||||
var end = this.peek.sourceSpan.start;
|
||||
let span = new ParseSourceSpan(startTagToken.sourceSpan.start, end);
|
||||
var el = new HtmlElementAst(fullName, attrs, [], span, span, null);
|
||||
var el = new HtmlElementAst(fullName, attrs, [],
|
||||
new ParseSourceSpan(startTagToken.sourceSpan.start, end));
|
||||
this._pushElement(el);
|
||||
if (selfClosing) {
|
||||
this._popElement(fullName);
|
||||
el.endSourceSpan = span;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,8 +171,7 @@ class TreeBuilder {
|
||||
var tagDef = getHtmlTagDefinition(el.name);
|
||||
var parentEl = this._getParentElement();
|
||||
if (tagDef.requireExtraParent(isPresent(parentEl) ? parentEl.name : null)) {
|
||||
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan,
|
||||
el.startSourceSpan, el.endSourceSpan);
|
||||
var newParent = new HtmlElementAst(tagDef.parentToAdd, [], [el], el.sourceSpan);
|
||||
this._addToParent(newParent);
|
||||
this.elementStack.push(newParent);
|
||||
this.elementStack.push(el);
|
||||
@ -189,8 +185,6 @@ class TreeBuilder {
|
||||
var fullName =
|
||||
getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
|
||||
|
||||
this._getParentElement().endSourceSpan = endTagToken.sourceSpan;
|
||||
|
||||
if (getHtmlTagDefinition(fullName).isVoid) {
|
||||
this.errors.push(
|
||||
HtmlTreeError.create(fullName, endTagToken.sourceSpan,
|
||||
|
@ -327,8 +327,6 @@ export class HtmlTagDefinition {
|
||||
// see http://www.w3.org/TR/html51/syntax.html#optional-tags
|
||||
// This implementation does not fully conform to the HTML5 spec.
|
||||
var TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
|
||||
'base': new HtmlTagDefinition({isVoid: true}),
|
||||
'meta': new HtmlTagDefinition({isVoid: true}),
|
||||
'area': new HtmlTagDefinition({isVoid: true}),
|
||||
'embed': new HtmlTagDefinition({isVoid: true}),
|
||||
'link': new HtmlTagDefinition({isVoid: true}),
|
||||
|
@ -8,14 +8,7 @@ import {
|
||||
isPresent
|
||||
} from 'angular2/src/facade/lang';
|
||||
|
||||
import {
|
||||
HtmlAstVisitor,
|
||||
HtmlAttrAst,
|
||||
HtmlElementAst,
|
||||
HtmlTextAst,
|
||||
HtmlCommentAst,
|
||||
HtmlAst
|
||||
} from './html_ast';
|
||||
import {HtmlAstVisitor, HtmlAttrAst, HtmlElementAst, HtmlTextAst, HtmlAst} from './html_ast';
|
||||
import {HtmlParser, HtmlParseTreeResult} from './html_parser';
|
||||
|
||||
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
@ -44,14 +37,11 @@ export class LegacyHtmlAstTransformer implements HtmlAstVisitor {
|
||||
|
||||
constructor(private dashCaseSelectors?: string[]) {}
|
||||
|
||||
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
|
||||
|
||||
visitElement(ast: HtmlElementAst, context: any): HtmlElementAst {
|
||||
this.visitingTemplateEl = ast.name.toLowerCase() == 'template';
|
||||
let attrs = ast.attrs.map(attr => attr.visit(this, null));
|
||||
let children = ast.children.map(child => child.visit(this, null));
|
||||
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan, ast.startSourceSpan,
|
||||
ast.endSourceSpan);
|
||||
return new HtmlElementAst(ast.name, attrs, children, ast.sourceSpan);
|
||||
}
|
||||
|
||||
visitAttr(originalAst: HtmlAttrAst, context: any): HtmlAttrAst {
|
||||
|
@ -234,7 +234,7 @@ class ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> implement
|
||||
attrAsts: TemplateAst[]): string[][] {
|
||||
var attrs = visitAndReturnContext(this, attrAsts, {});
|
||||
directives.forEach(directiveMeta => {
|
||||
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value: string, name: string) => {
|
||||
StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => {
|
||||
var prevValue = attrs[name];
|
||||
attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
|
||||
});
|
||||
@ -330,14 +330,12 @@ class ProtoViewBuilderVisitor<APP_PROTO_VIEW, APP_PROTO_EL, STATEMENT> implement
|
||||
}
|
||||
|
||||
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
|
||||
var entryArray: string[][] = [];
|
||||
StringMapWrapper.forEach(data,
|
||||
(value: string, name: string) => { entryArray.push([name, value]); });
|
||||
var entryArray = [];
|
||||
StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); });
|
||||
// We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
ListWrapper.sort<string[]>(entryArray, (entry1: string[], entry2: string[]) =>
|
||||
StringWrapper.compare(entry1[0], entry2[0]));
|
||||
var keyValueArray: string[][] = [];
|
||||
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
|
||||
var keyValueArray = [];
|
||||
entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); });
|
||||
return keyValueArray;
|
||||
}
|
||||
|
@ -20,37 +20,18 @@ import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {Injectable, Inject, Optional} from 'angular2/src/core/di';
|
||||
import {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes';
|
||||
import {MODULE_SUFFIX} from './util';
|
||||
import {assertArrayOfStrings} from './assertions';
|
||||
import {getUrlScheme} from 'angular2/src/compiler/url_resolver';
|
||||
|
||||
@Injectable()
|
||||
export class RuntimeMetadataResolver {
|
||||
private _directiveCache = new Map<Type, cpl.CompileDirectiveMetadata>();
|
||||
private _pipeCache = new Map<Type, cpl.CompilePipeMetadata>();
|
||||
private _anonymousTypes = new Map<Object, number>();
|
||||
private _anonymousTypeIndex = 0;
|
||||
|
||||
constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
|
||||
private _viewResolver: ViewResolver,
|
||||
@Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[],
|
||||
@Optional() @Inject(PLATFORM_PIPES) private _platformPipes: Type[]) {}
|
||||
|
||||
/**
|
||||
* Wrap the stringify method to avoid naming things `function (arg1...) {`
|
||||
*/
|
||||
private sanitizeName(obj: any): string {
|
||||
let result = stringify(obj);
|
||||
if (result.indexOf('(') < 0) {
|
||||
return result;
|
||||
}
|
||||
let found = this._anonymousTypes.get(obj);
|
||||
if (!found) {
|
||||
this._anonymousTypes.set(obj, this._anonymousTypeIndex++);
|
||||
found = this._anonymousTypes.get(obj);
|
||||
}
|
||||
return `anonymous_type_${found}_`;
|
||||
}
|
||||
|
||||
getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
|
||||
var meta = this._directiveCache.get(directiveType);
|
||||
if (isBlank(meta)) {
|
||||
@ -60,11 +41,9 @@ export class RuntimeMetadataResolver {
|
||||
var changeDetectionStrategy = null;
|
||||
|
||||
if (dirMeta instanceof md.ComponentMetadata) {
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
var cmpMeta = <md.ComponentMetadata>dirMeta;
|
||||
moduleUrl = calcModuleUrl(directiveType, cmpMeta);
|
||||
var viewMeta = this._viewResolver.resolve(directiveType);
|
||||
assertArrayOfStrings('styles', viewMeta.styles);
|
||||
templateMeta = new cpl.CompileTemplateMetadata({
|
||||
encapsulation: viewMeta.encapsulation,
|
||||
template: viewMeta.template,
|
||||
@ -80,7 +59,7 @@ export class RuntimeMetadataResolver {
|
||||
isComponent: isPresent(templateMeta),
|
||||
dynamicLoadable: true,
|
||||
type: new cpl.CompileTypeMetadata(
|
||||
{name: this.sanitizeName(directiveType), moduleUrl: moduleUrl, runtime: directiveType}),
|
||||
{name: stringify(directiveType), moduleUrl: moduleUrl, runtime: directiveType}),
|
||||
template: templateMeta,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
@ -100,7 +79,7 @@ export class RuntimeMetadataResolver {
|
||||
var moduleUrl = reflector.importUri(pipeType);
|
||||
meta = new cpl.CompilePipeMetadata({
|
||||
type: new cpl.CompileTypeMetadata(
|
||||
{name: this.sanitizeName(pipeType), moduleUrl: moduleUrl, runtime: pipeType}),
|
||||
{name: stringify(pipeType), moduleUrl: moduleUrl, runtime: pipeType}),
|
||||
name: pipeMeta.name,
|
||||
pure: pipeMeta.pure
|
||||
});
|
||||
|
@ -343,16 +343,14 @@ export class ShadowCss {
|
||||
strict: boolean): string {
|
||||
var r = [], parts = selector.split(',');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var p = parts[i].trim();
|
||||
var deepParts = StringWrapper.split(p, _shadowDeepSelectors);
|
||||
var shallowPart = deepParts[0];
|
||||
if (this._selectorNeedsScoping(shallowPart, scopeSelector)) {
|
||||
deepParts[0] = strict && !StringWrapper.contains(shallowPart, _polyfillHostNoCombinator) ?
|
||||
this._applyStrictSelectorScope(shallowPart, scopeSelector) :
|
||||
this._applySelectorScope(shallowPart, scopeSelector, hostSelector);
|
||||
var p = parts[i];
|
||||
p = p.trim();
|
||||
if (this._selectorNeedsScoping(p, scopeSelector)) {
|
||||
p = strict && !StringWrapper.contains(p, _polyfillHostNoCombinator) ?
|
||||
this._applyStrictSelectorScope(p, scopeSelector) :
|
||||
this._applySelectorScope(p, scopeSelector, hostSelector);
|
||||
}
|
||||
// replace /deep/ with a space for child selectors
|
||||
r.push(deepParts.join(' '));
|
||||
r.push(p);
|
||||
}
|
||||
return r.join(', ');
|
||||
}
|
||||
@ -436,16 +434,17 @@ var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, '
|
||||
var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
|
||||
var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
|
||||
var _shadowDOMSelectorsRe = [
|
||||
/>>>/g,
|
||||
/::shadow/g,
|
||||
/::content/g,
|
||||
// Deprecated selectors
|
||||
// TODO(vicb): see https://github.com/angular/clang-format/issues/16
|
||||
// clang-format off
|
||||
/\/deep\//g, // former >>>
|
||||
/\/shadow-deep\//g, // former /deep/
|
||||
/\/shadow\//g, // former ::shadow
|
||||
// clanf-format on
|
||||
];
|
||||
var _shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)/g;
|
||||
var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
|
||||
var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
|
||||
var _colonHostRe = /:host/gim;
|
||||
|
@ -53,9 +53,9 @@ export class StyleCompiler {
|
||||
|
||||
private _loadStyles(plainStyles: string[], absUrls: string[],
|
||||
encapsulate: boolean): Promise<Array<string | any[]>> {
|
||||
var promises: Promise<string[]>[] = absUrls.map((absUrl: string): Promise<string[]> => {
|
||||
var promises = absUrls.map((absUrl) => {
|
||||
var cacheKey = `${absUrl}${encapsulate ? '.shim' : ''}`;
|
||||
var result: Promise<string[]> = this._styleCache.get(cacheKey);
|
||||
var result = this._styleCache.get(cacheKey);
|
||||
if (isBlank(result)) {
|
||||
result = this._xhr.get(absUrl).then((style) => {
|
||||
var styleWithImports = extractStyleUrls(this._urlResolver, absUrl, style);
|
||||
@ -66,7 +66,7 @@ export class StyleCompiler {
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return PromiseWrapper.all<string[]>(promises).then((nestedStyles: string[][]) => {
|
||||
return PromiseWrapper.all(promises).then((nestedStyles: string[][]) => {
|
||||
var result: Array<string | any[]> =
|
||||
plainStyles.map(plainStyle => this._shimIfNeeded(plainStyle, encapsulate));
|
||||
nestedStyles.forEach(styles => result.push(styles));
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
HtmlTextAst,
|
||||
HtmlAttrAst,
|
||||
HtmlAst,
|
||||
HtmlCommentAst,
|
||||
htmlVisitAll
|
||||
} from './html_ast';
|
||||
import {HtmlParser} from './html_parser';
|
||||
@ -127,7 +126,6 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
||||
visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
|
||||
visitText(ast: HtmlTextAst, context: any): any { return null; }
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ import {
|
||||
HtmlElementAst,
|
||||
HtmlAttrAst,
|
||||
HtmlTextAst,
|
||||
HtmlCommentAst,
|
||||
htmlVisitAll
|
||||
} from './html_ast';
|
||||
|
||||
@ -210,8 +209,6 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
return new AttrAst(ast.name, ast.value, ast.sourceSpan);
|
||||
}
|
||||
|
||||
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
||||
|
||||
visitElement(element: HtmlElementAst, component: Component): any {
|
||||
var nodeName = element.name;
|
||||
var preparsedElement = preparseElement(element);
|
||||
@ -264,40 +261,29 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives);
|
||||
var children = htmlVisitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this,
|
||||
element.children, Component.create(directives));
|
||||
|
||||
// Override the actual selector when the `ngProjectAs` attribute is provided
|
||||
var projectionSelector = isPresent(preparsedElement.projectAs) ?
|
||||
CssSelector.parse(preparsedElement.projectAs)[0] :
|
||||
elementCssSelector;
|
||||
var ngContentIndex = component.findNgContentIndex(projectionSelector);
|
||||
var elementNgContentIndex =
|
||||
hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector);
|
||||
var parsedElement;
|
||||
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
if (isPresent(element.children) && element.children.length > 0) {
|
||||
this._reportError(
|
||||
`<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content>`,
|
||||
element.sourceSpan);
|
||||
}
|
||||
|
||||
parsedElement = new NgContentAst(
|
||||
this.ngContentCount++, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
parsedElement =
|
||||
new NgContentAst(this.ngContentCount++, elementNgContentIndex, element.sourceSpan);
|
||||
} else if (isTemplateElement) {
|
||||
this._assertAllEventsPublishedByDirectives(directives, events);
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps,
|
||||
element.sourceSpan);
|
||||
|
||||
parsedElement =
|
||||
new EmbeddedTemplateAst(attrs, events, vars, directives, children,
|
||||
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
parsedElement = new EmbeddedTemplateAst(attrs, events, vars, directives, children,
|
||||
elementNgContentIndex, element.sourceSpan);
|
||||
} else {
|
||||
this._assertOnlyOneComponent(directives, element.sourceSpan);
|
||||
var elementExportAsVars = vars.filter(varAst => varAst.value.length === 0);
|
||||
let ngContentIndex =
|
||||
hasInlineTemplates ? null : component.findNgContentIndex(projectionSelector);
|
||||
|
||||
parsedElement =
|
||||
new ElementAst(nodeName, attrs, elementProps, events, elementExportAsVars, directives,
|
||||
children, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
children, elementNgContentIndex, element.sourceSpan);
|
||||
}
|
||||
if (hasInlineTemplates) {
|
||||
var templateCssSelector = createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
||||
@ -308,9 +294,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
element.name, templateElementOrDirectiveProps, templateDirectives);
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
|
||||
element.sourceSpan);
|
||||
|
||||
parsedElement = new EmbeddedTemplateAst([], [], templateVars, templateDirectives,
|
||||
[parsedElement], ngContentIndex, element.sourceSpan);
|
||||
parsedElement = new EmbeddedTemplateAst(
|
||||
[], [], templateVars, templateDirectives, [parsedElement],
|
||||
component.findNgContentIndex(templateCssSelector), element.sourceSpan);
|
||||
}
|
||||
return parsedElement;
|
||||
}
|
||||
@ -519,7 +505,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
sourceSpan: ParseSourceSpan,
|
||||
targetPropertyAsts: BoundElementPropertyAst[]) {
|
||||
if (isPresent(hostProps)) {
|
||||
StringMapWrapper.forEach(hostProps, (expression: string, propName: string) => {
|
||||
StringMapWrapper.forEach(hostProps, (expression, propName) => {
|
||||
var exprAst = this._parseBinding(expression, sourceSpan);
|
||||
targetPropertyAsts.push(
|
||||
this._createElementPropertyAst(elementName, propName, exprAst, sourceSpan));
|
||||
@ -531,7 +517,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
sourceSpan: ParseSourceSpan,
|
||||
targetEventAsts: BoundEventAst[]) {
|
||||
if (isPresent(hostListeners)) {
|
||||
StringMapWrapper.forEach(hostListeners, (expression: string, propName: string) => {
|
||||
StringMapWrapper.forEach(hostListeners, (expression, propName) => {
|
||||
this._parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
||||
});
|
||||
}
|
||||
@ -659,7 +645,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
var allDirectiveEvents = new Set<string>();
|
||||
directives.forEach(directive => {
|
||||
StringMapWrapper.forEach(directive.directive.outputs,
|
||||
(eventName: string, _) => { allDirectiveEvents.add(eventName); });
|
||||
(eventName, _) => { allDirectiveEvents.add(eventName); });
|
||||
});
|
||||
events.forEach(event => {
|
||||
if (isPresent(event.target) || !SetWrapper.has(allDirectiveEvents, event.name)) {
|
||||
@ -690,7 +676,6 @@ class NonBindableVisitor implements HtmlAstVisitor {
|
||||
return new ElementAst(ast.name, htmlVisitAll(this, ast.attrs), [], [], [], [], children,
|
||||
ngContentIndex, ast.sourceSpan);
|
||||
}
|
||||
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
|
||||
visitAttr(ast: HtmlAttrAst, context: any): AttrAst {
|
||||
return new AttrAst(ast.name, ast.value, ast.sourceSpan);
|
||||
}
|
||||
|
@ -11,14 +11,12 @@ const LINK_STYLE_REL_VALUE = 'stylesheet';
|
||||
const STYLE_ELEMENT = 'style';
|
||||
const SCRIPT_ELEMENT = 'script';
|
||||
const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
|
||||
const NG_PROJECT_AS = 'ngProjectAs';
|
||||
|
||||
export function preparseElement(ast: HtmlElementAst): PreparsedElement {
|
||||
var selectAttr = null;
|
||||
var hrefAttr = null;
|
||||
var relAttr = null;
|
||||
var nonBindable = false;
|
||||
var projectAs: string = null;
|
||||
ast.attrs.forEach(attr => {
|
||||
let lcAttrName = attr.name.toLowerCase();
|
||||
if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
|
||||
@ -29,10 +27,6 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement {
|
||||
relAttr = attr.value;
|
||||
} else if (attr.name == NG_NON_BINDABLE_ATTR) {
|
||||
nonBindable = true;
|
||||
} else if (attr.name == NG_PROJECT_AS) {
|
||||
if (attr.value.length > 0) {
|
||||
projectAs = attr.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
selectAttr = normalizeNgContentSelect(selectAttr);
|
||||
@ -47,7 +41,7 @@ export function preparseElement(ast: HtmlElementAst): PreparsedElement {
|
||||
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
|
||||
type = PreparsedElementType.STYLESHEET;
|
||||
}
|
||||
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable, projectAs);
|
||||
return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable);
|
||||
}
|
||||
|
||||
export enum PreparsedElementType {
|
||||
@ -60,7 +54,7 @@ export enum PreparsedElementType {
|
||||
|
||||
export class PreparsedElement {
|
||||
constructor(public type: PreparsedElementType, public selectAttr: string, public hrefAttr: string,
|
||||
public nonBindable: boolean, public projectAs: string) {}
|
||||
public nonBindable: boolean) {}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {NgZone, NgZoneError} from 'angular2/src/core/zone/ng_zone';
|
||||
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
||||
import {
|
||||
Type,
|
||||
isBlank,
|
||||
@ -128,7 +128,7 @@ function _createPlatform(providers?: Array<Type | Provider | any[]>): PlatformRe
|
||||
}
|
||||
|
||||
function _runPlatformInitializers(injector: Injector): void {
|
||||
let inits: Function[] = <Function[]>injector.getOptional(PLATFORM_INITIALIZER);
|
||||
let inits: Function[] = injector.getOptional(PLATFORM_INITIALIZER);
|
||||
if (isPresent(inits)) inits.forEach(init => init());
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ export abstract class PlatformRef {
|
||||
* Retrieve the platform {@link Injector}, which is the parent injector for
|
||||
* every Angular application on the page and provides singleton providers.
|
||||
*/
|
||||
get injector(): Injector { throw unimplemented(); };
|
||||
get injector(): Injector { return unimplemented(); };
|
||||
|
||||
/**
|
||||
* Instantiate a new Angular application on the page.
|
||||
@ -222,7 +222,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||
asyncApplication(bindingFn: (zone: NgZone) => Promise<Array<Type | Provider | any[]>>,
|
||||
additionalProviders?: Array<Type | Provider | any[]>): Promise<ApplicationRef> {
|
||||
var zone = createNgZone();
|
||||
var completer = PromiseWrapper.completer<ApplicationRef>();
|
||||
var completer = PromiseWrapper.completer();
|
||||
if (bindingFn === null) {
|
||||
completer.resolve(this._initApp(zone, additionalProviders));
|
||||
} else {
|
||||
@ -254,9 +254,7 @@ export class PlatformRef_ extends PlatformRef {
|
||||
try {
|
||||
injector = this.injector.resolveAndCreateChild(providers);
|
||||
exceptionHandler = injector.get(ExceptionHandler);
|
||||
ObservableWrapper.subscribe(zone.onError, (error: NgZoneError) => {
|
||||
exceptionHandler.call(error.error, error.stackTrace);
|
||||
});
|
||||
zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
|
||||
} catch (e) {
|
||||
if (isPresent(exceptionHandler)) {
|
||||
exceptionHandler.call(e, e.stack);
|
||||
@ -344,12 +342,12 @@ export abstract class ApplicationRef {
|
||||
/**
|
||||
* Retrieve the application {@link Injector}.
|
||||
*/
|
||||
get injector(): Injector { return <Injector>unimplemented(); };
|
||||
get injector(): Injector { return unimplemented(); };
|
||||
|
||||
/**
|
||||
* Retrieve the application {@link NgZone}.
|
||||
*/
|
||||
get zone(): NgZone { return <NgZone>unimplemented(); };
|
||||
get zone(): NgZone { return unimplemented(); };
|
||||
|
||||
/**
|
||||
* Dispose of this application and all of its components.
|
||||
@ -371,7 +369,7 @@ export abstract class ApplicationRef {
|
||||
/**
|
||||
* Get a list of component types registered to this application.
|
||||
*/
|
||||
get componentTypes(): Type[] { return <Type[]>unimplemented(); };
|
||||
get componentTypes(): Type[] { return unimplemented(); };
|
||||
}
|
||||
|
||||
export class ApplicationRef_ extends ApplicationRef {
|
||||
@ -396,7 +394,7 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
constructor(private _platform: PlatformRef_, private _zone: NgZone, private _injector: Injector) {
|
||||
super();
|
||||
if (isPresent(this._zone)) {
|
||||
ObservableWrapper.subscribe(this._zone.onMicrotaskEmpty,
|
||||
ObservableWrapper.subscribe(this._zone.onTurnDone,
|
||||
(_) => { this._zone.run(() => { this.tick(); }); });
|
||||
}
|
||||
this._enforceNoNewChanges = assertionsEnabled();
|
||||
@ -436,22 +434,28 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
|
||||
var tickResult = PromiseWrapper.then(compRefToken, tick);
|
||||
|
||||
PromiseWrapper.then(tickResult, null, (err, stackTrace) => {
|
||||
completer.reject(err, stackTrace);
|
||||
exceptionHandler.call(err, stackTrace);
|
||||
});
|
||||
// THIS MUST ONLY RUN IN DART.
|
||||
// This is required to report an error when no components with a matching selector found.
|
||||
// Otherwise the promise will never be completed.
|
||||
// Doing this in JS causes an extra error message to appear.
|
||||
if (IS_DART) {
|
||||
PromiseWrapper.then(tickResult, (_) => {});
|
||||
}
|
||||
|
||||
PromiseWrapper.then(tickResult, null,
|
||||
(err, stackTrace) => completer.reject(err, stackTrace));
|
||||
} catch (e) {
|
||||
exceptionHandler.call(e, e.stack);
|
||||
completer.reject(e, e.stack);
|
||||
}
|
||||
});
|
||||
return completer.promise.then<ComponentRef>((ref: ComponentRef) => {
|
||||
return completer.promise.then(_ => {
|
||||
let c = this._injector.get(Console);
|
||||
if (assertionsEnabled()) {
|
||||
c.log(
|
||||
"Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.");
|
||||
}
|
||||
return ref;
|
||||
return _;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -73,8 +73,36 @@ export class SimpleChange {
|
||||
isFirstChange(): boolean { return this.previousValue === ChangeDetectionUtil.uninitialized; }
|
||||
}
|
||||
|
||||
var _simpleChangesIndex = 0;
|
||||
var _simpleChanges = [
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null),
|
||||
new SimpleChange(null, null)
|
||||
];
|
||||
|
||||
function _simpleChange(previousValue, currentValue): SimpleChange {
|
||||
return new SimpleChange(previousValue, currentValue);
|
||||
var index = _simpleChangesIndex++ % 20;
|
||||
var s = _simpleChanges[index];
|
||||
s.previousValue = previousValue;
|
||||
s.currentValue = currentValue;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* tslint:disable:requireParameterType */
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {isBlank, isPresent, CONST, getTypeNameForDebugging} from 'angular2/src/facade/lang';
|
||||
import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
@ -86,8 +86,7 @@ export class IterableDiffers {
|
||||
if (isPresent(factory)) {
|
||||
return factory;
|
||||
} else {
|
||||
throw new BaseException(
|
||||
`Cannot find a differ supporting object '${iterable}' of type '${getTypeNameForDebugging(iterable)}'`);
|
||||
throw new BaseException(`Cannot find a differ supporting object '${iterable}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,6 +153,7 @@ export const $BAR = 124;
|
||||
export const $RBRACE = 125;
|
||||
const $NBSP = 160;
|
||||
|
||||
|
||||
export class ScannerError extends BaseException {
|
||||
constructor(public message) { super(); }
|
||||
|
||||
|
@ -57,10 +57,6 @@ class ParseException extends BaseException {
|
||||
}
|
||||
}
|
||||
|
||||
export class SplitInterpolation {
|
||||
constructor(public strings: string[], public expressions: string[]) {}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class Parser {
|
||||
/** @internal */
|
||||
@ -122,21 +118,6 @@ export class Parser {
|
||||
}
|
||||
|
||||
parseInterpolation(input: string, location: any): ASTWithSource {
|
||||
let split = this.splitInterpolation(input, location);
|
||||
if (split == null) return null;
|
||||
|
||||
let expressions = [];
|
||||
|
||||
for (let i = 0; i < split.expressions.length; ++i) {
|
||||
var tokens = this._lexer.tokenize(split.expressions[i]);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
|
||||
expressions.push(ast);
|
||||
}
|
||||
|
||||
return new ASTWithSource(new Interpolation(split.strings, expressions), input, location);
|
||||
}
|
||||
|
||||
splitInterpolation(input: string, location: string): SplitInterpolation {
|
||||
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
@ -150,14 +131,16 @@ export class Parser {
|
||||
// fixed string
|
||||
strings.push(part);
|
||||
} else if (part.trim().length > 0) {
|
||||
expressions.push(part);
|
||||
var tokens = this._lexer.tokenize(part);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
|
||||
expressions.push(ast);
|
||||
} else {
|
||||
throw new ParseException('Blank expressions are not allowed in interpolated strings', input,
|
||||
`at column ${this._findInterpolationErrorColumn(parts, i)} in`,
|
||||
location);
|
||||
}
|
||||
}
|
||||
return new SplitInterpolation(strings, expressions);
|
||||
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
|
||||
}
|
||||
|
||||
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
|
||||
|
@ -64,9 +64,8 @@ export class DynamicProtoChangeDetector implements ProtoChangeDetector {
|
||||
|
||||
export function createPropertyRecords(definition: ChangeDetectorDefinition): ProtoRecord[] {
|
||||
var recordBuilder = new ProtoRecordBuilder();
|
||||
ListWrapper.forEachWithIndex(
|
||||
definition.bindingRecords,
|
||||
(b: BindingRecord, index: number) => recordBuilder.add(b, definition.variableNames, index));
|
||||
ListWrapper.forEachWithIndex(definition.bindingRecords,
|
||||
(b, index) => recordBuilder.add(b, definition.variableNames, index));
|
||||
return coalesce(recordBuilder.records);
|
||||
}
|
||||
|
||||
|
@ -91,19 +91,19 @@ export class DebugElement extends DebugNode {
|
||||
}
|
||||
|
||||
queryAll(predicate: Predicate<DebugElement>): DebugElement[] {
|
||||
var matches: DebugElement[] = [];
|
||||
var matches = [];
|
||||
_queryElementChildren(this, predicate, matches);
|
||||
return matches;
|
||||
}
|
||||
|
||||
queryAllNodes(predicate: Predicate<DebugNode>): DebugNode[] {
|
||||
var matches: DebugNode[] = [];
|
||||
var matches = [];
|
||||
_queryNodeChildren(this, predicate, matches);
|
||||
return matches;
|
||||
}
|
||||
|
||||
get children(): DebugElement[] {
|
||||
var children: DebugElement[] = [];
|
||||
var children = [];
|
||||
this.childNodes.forEach((node) => {
|
||||
if (node instanceof DebugElement) {
|
||||
children.push(node);
|
||||
|
@ -73,7 +73,7 @@ export class DebugDomRenderer implements Renderer {
|
||||
if (isPresent(debugNode)) {
|
||||
var debugParent = debugNode.parent;
|
||||
if (viewRootNodes.length > 0 && isPresent(debugParent)) {
|
||||
var debugViewRootNodes: DebugNode[] = [];
|
||||
var debugViewRootNodes = [];
|
||||
viewRootNodes.forEach((rootNode) => debugViewRootNodes.push(getDebugNode(rootNode)));
|
||||
debugParent.insertChildrenAfter(debugNode, debugViewRootNodes);
|
||||
}
|
||||
|
@ -513,7 +513,7 @@ export class ProviderBuilder {
|
||||
*/
|
||||
export function resolveFactory(provider: Provider): ResolvedFactory {
|
||||
var factoryFn: Function;
|
||||
var resolvedDeps: Dependency[];
|
||||
var resolvedDeps;
|
||||
if (isPresent(provider.useClass)) {
|
||||
var useClass = resolveForwardRef(provider.useClass);
|
||||
factoryFn = reflector.factory(useClass);
|
||||
@ -619,7 +619,7 @@ function _constructDependencies(factoryFunction: Function, dependencies: any[]):
|
||||
}
|
||||
|
||||
function _dependenciesFor(typeOrFunc): Dependency[] {
|
||||
var params: any[][] = reflector.parameters(typeOrFunc);
|
||||
var params = reflector.parameters(typeOrFunc);
|
||||
if (isBlank(params)) return [];
|
||||
if (params.some(isBlank)) {
|
||||
throw new NoAnnotationError(typeOrFunc, params);
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
ViewChildMetadata
|
||||
} from 'angular2/src/core/metadata';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
|
||||
|
||||
function _isDirectiveMetadata(type: any): boolean {
|
||||
return type instanceof DirectiveMetadata;
|
||||
@ -31,25 +30,15 @@ function _isDirectiveMetadata(type: any): boolean {
|
||||
*/
|
||||
@Injectable()
|
||||
export class DirectiveResolver {
|
||||
private _reflector: ReflectorReader;
|
||||
|
||||
constructor(_reflector?: ReflectorReader) {
|
||||
if (isPresent(_reflector)) {
|
||||
this._reflector = _reflector;
|
||||
} else {
|
||||
this._reflector = reflector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link DirectiveMetadata} for a given `Type`.
|
||||
*/
|
||||
resolve(type: Type): DirectiveMetadata {
|
||||
var typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
||||
var typeMetadata = reflector.annotations(resolveForwardRef(type));
|
||||
if (isPresent(typeMetadata)) {
|
||||
var metadata = typeMetadata.find(_isDirectiveMetadata);
|
||||
if (isPresent(metadata)) {
|
||||
var propertyMetadata = this._reflector.propMetadata(type);
|
||||
var propertyMetadata = reflector.propMetadata(type);
|
||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
||||
}
|
||||
}
|
||||
@ -166,4 +155,4 @@ export class DirectiveResolver {
|
||||
}
|
||||
}
|
||||
|
||||
export var CODEGEN_DIRECTIVE_RESOLVER = new DirectiveResolver(reflector);
|
||||
export var CODEGEN_DIRECTIVE_RESOLVER = new DirectiveResolver();
|
||||
|
@ -59,7 +59,7 @@ export abstract class ComponentRef {
|
||||
*
|
||||
* TODO(i): rename to destroy to be consistent with AppViewManager and ViewContainerRef
|
||||
*/
|
||||
abstract dispose(): void;
|
||||
abstract dispose();
|
||||
}
|
||||
|
||||
export class ComponentRef_ extends ComponentRef {
|
||||
@ -84,7 +84,7 @@ export class ComponentRef_ extends ComponentRef {
|
||||
*/
|
||||
get hostComponentType(): Type { return this.componentType; }
|
||||
|
||||
dispose(): void { this._dispose(); }
|
||||
dispose() { this._dispose(); }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@ import {resolveForwardRef, Injectable} from 'angular2/src/core/di';
|
||||
import {Type, isPresent, stringify} from 'angular2/src/facade/lang';
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {PipeMetadata} from 'angular2/src/core/metadata';
|
||||
import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
||||
function _isPipeMetadata(type: any): boolean {
|
||||
@ -18,20 +17,11 @@ function _isPipeMetadata(type: any): boolean {
|
||||
*/
|
||||
@Injectable()
|
||||
export class PipeResolver {
|
||||
private _reflector: ReflectorReader;
|
||||
constructor(_reflector?: ReflectorReader) {
|
||||
if (isPresent(_reflector)) {
|
||||
this._reflector = _reflector;
|
||||
} else {
|
||||
this._reflector = reflector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link PipeMetadata} for a given `Type`.
|
||||
*/
|
||||
resolve(type: Type): PipeMetadata {
|
||||
var metas = this._reflector.annotations(resolveForwardRef(type));
|
||||
var metas = reflector.annotations(resolveForwardRef(type));
|
||||
if (isPresent(metas)) {
|
||||
var annotation = metas.find(_isPipeMetadata);
|
||||
if (isPresent(annotation)) {
|
||||
@ -42,4 +32,4 @@ export class PipeResolver {
|
||||
}
|
||||
}
|
||||
|
||||
export var CODEGEN_PIPE_RESOLVER = new PipeResolver(reflector);
|
||||
export var CODEGEN_PIPE_RESOLVER = new PipeResolver();
|
||||
|
@ -115,9 +115,8 @@ export class AppView implements ChangeDispatcher {
|
||||
this.disposables = disposables;
|
||||
this.appElements = appElements;
|
||||
var localsMap = new Map<string, any>();
|
||||
StringMapWrapper.forEach(
|
||||
this.proto.templateVariableBindings,
|
||||
(templateName: string, _: string) => { localsMap.set(templateName, null); });
|
||||
StringMapWrapper.forEach(this.proto.templateVariableBindings,
|
||||
(templateName, _) => { localsMap.set(templateName, null); });
|
||||
for (var i = 0; i < appElements.length; i++) {
|
||||
var appEl = appElements[i];
|
||||
var providerTokens = [];
|
||||
@ -126,14 +125,13 @@ export class AppView implements ChangeDispatcher {
|
||||
providerTokens.push(appEl.proto.protoInjector.getProviderAtIndex(j).key.token);
|
||||
}
|
||||
}
|
||||
StringMapWrapper.forEach(appEl.proto.directiveVariableBindings,
|
||||
(directiveIndex: number, name: string) => {
|
||||
if (isBlank(directiveIndex)) {
|
||||
localsMap.set(name, appEl.nativeElement);
|
||||
} else {
|
||||
localsMap.set(name, appEl.getDirectiveAtIndex(directiveIndex));
|
||||
}
|
||||
});
|
||||
StringMapWrapper.forEach(appEl.proto.directiveVariableBindings, (directiveIndex, name) => {
|
||||
if (isBlank(directiveIndex)) {
|
||||
localsMap.set(name, appEl.nativeElement);
|
||||
} else {
|
||||
localsMap.set(name, appEl.getDirectiveAtIndex(directiveIndex));
|
||||
}
|
||||
});
|
||||
this.renderer.setElementDebugInfo(
|
||||
appEl.nativeElement, new RenderDebugInfo(appEl.getInjector(), appEl.getComponent(),
|
||||
providerTokens, localsMap));
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user