The NgAnimate module is a port with modifications of the original
AngularJS animation module. The default implementation does nothing.
@@ -22,6 +23,7 @@
About
added it allows you define and run css animations on your elements with
pure CSS.
Check out the demos above.
+
ng-repeat Demo
diff --git a/example/web/animation/css_demo.dart b/example/web/animation/css_demo.dart
index a09018595..5312556f3 100644
--- a/example/web/animation/css_demo.dart
+++ b/example/web/animation/css_demo.dart
@@ -4,25 +4,24 @@ part of animation;
selector: 'css-demo',
template: '''
-
+
Toggle A
-
+
Toggle B
-
+
Toggle C
BOX
+ 'a': stateA,
+ 'b': stateB,
+ 'c': stateC}">BOX
''',
- publishAs: 'ctrl',
applyAuthorStyles: true)
class CssDemo {
bool stateA = false;
diff --git a/example/web/animation/repeat_demo.dart b/example/web/animation/repeat_demo.dart
index 5857d72fe..8352fee81 100644
--- a/example/web/animation/repeat_demo.dart
+++ b/example/web/animation/repeat_demo.dart
@@ -4,19 +4,17 @@ part of animation;
selector: 'repeat-demo',
template: '''
""",
cssUrl: "/css/shadow_dom_components.css")
diff --git a/example/web/todo.dart b/example/web/todo.dart
index b4c1c7bbb..c6c187a5c 100644
--- a/example/web/todo.dart
+++ b/example/web/todo.dart
@@ -53,10 +53,7 @@ class HttpServer implements Server {
}
}
-
-@Controller(
- selector: '[todo-controller]',
- publishAs: 'todo')
+@Injectable()
class Todo {
var items = [];
Item newItem;
@@ -94,18 +91,12 @@ class Todo {
main() {
print(window.location.search);
- var module = new Module()
- ..bind(Todo)
- ..bind(PlaybackHttpBackendConfig);
+ var module = new Module()..bind(PlaybackHttpBackendConfig);
// If these is a query in the URL, use the server-backed
// TodoController. Otherwise, use the stored-data controller.
var query = window.location.search;
- if (query.contains('?')) {
- module.bind(Server, toImplementation: HttpServer);
- } else {
- module.bind(Server, toImplementation: NoOpServer);
- }
+ module.bind(Server, toImplementation: query.contains('?') ? HttpServer : NoOpServer);
if (query == '?record') {
print('Using recording HttpBackend');
@@ -119,5 +110,8 @@ main() {
module.bind(HttpBackend, toImplementation: PlaybackHttpBackend);
}
- applicationFactory().addModule(module).run();
+ applicationFactory()
+ .addModule(module)
+ .rootContextType(Todo)
+ .run();
}
diff --git a/example/web/todo.html b/example/web/todo.html
index cbd32cc92..2dcb69f4a 100644
--- a/example/web/todo.html
+++ b/example/web/todo.html
@@ -11,19 +11,19 @@
Wait, Dart is loading this awesome app...
-
+
Things To Do ;-)
- mark all done
- archive done
+ mark all done
+ archive done
-
Remaining {{todo.remaining()}} of {{todo.items.length}} items.
+
Remaining {{ remaining() }} of {{ items.length }} items.
-
+
@@ -31,9 +31,9 @@
Things To Do ;-)
diff --git a/lib/application.dart b/lib/application.dart
index 56ee41488..32adf89a0 100644
--- a/lib/application.dart
+++ b/lib/application.dart
@@ -8,15 +8,9 @@
* import 'package:angular/angular.dart';
* import 'package:angular/application_factory.dart';
*
- * class MyModule extends Module {
- * MyModule() {
- * bind(HelloWorldController);
- * }
- * }
- *
* main() {
* applicationFactory()
- * .addModule(new MyModule())
+ * .rootContextType(HelloWorldController)
* .run();
* }
*
@@ -159,6 +153,11 @@ abstract class Application {
return this;
}
+ Application rootContextType(Type rootContext) {
+ modules.add(new Module()..bind(Object, toImplementation: rootContext));
+ return this;
+ }
+
Injector run() {
publishToJavaScript();
return zone.run(() {
diff --git a/lib/application_factory.dart b/lib/application_factory.dart
index 25b95cd40..4971042ef 100644
--- a/lib/application_factory.dart
+++ b/lib/application_factory.dart
@@ -51,7 +51,6 @@ import 'dart:html';
metaTargets: const [
Injectable,
Decorator,
- Controller,
Component,
Formatter
])
diff --git a/lib/application_factory_static.dart b/lib/application_factory_static.dart
index 3379177ff..ad663a4f7 100644
--- a/lib/application_factory_static.dart
+++ b/lib/application_factory_static.dart
@@ -11,15 +11,9 @@
* import 'package:angular/angular.dart';
* import 'package:angular/application_factory_static.dart';
*
- * class MyModule extends Module {
- * MyModule() {
- * bind(HelloWorldController);
- * }
- * }
- *
* main() {
* staticApplicationFactory()
- * .addModule(new MyModule())
+ * .rootContextType(HelloWorldController)
* .run();
* }
*
@@ -56,7 +50,8 @@ class _StaticApplication extends Application {
ngModule
..bind(MetadataExtractor, toValue: new StaticMetadataExtractor(metadata))
..bind(FieldGetterFactory, toValue: new StaticFieldGetterFactory(fieldGetters))
- ..bind(ClosureMap, toValue: new StaticClosureMap(fieldGetters, fieldSetters, symbols));
+ ..bind(ClosureMap, toFactory: (_) =>
+ new StaticClosureMap(fieldGetters, fieldSetters, symbols));
}
Injector createInjector() =>
@@ -64,32 +59,31 @@ class _StaticApplication extends Application {
}
/**
- * Bootstraps Angular as part of the `main()` function.
- *
- * `staticApplication()` replaces `dynamicApplication()` in the main function during pub build,
- * and is populated with the getters, setters, annotations, and factories generated by
- * Angular's transformers for dart2js compilation. It is not typically called directly.
- *
- * For example,
- *
- * main() {
- * applicationFactory()
- * .addModule(new Module()..bind(HelloWorld))
- * .run();
- * }
- *
- * becomes:
- *
- * main() {
- * staticApplication(generated_static_injector.factories,
- * generated_static_metadata.typeAnnotations,
- * generated_static_expressions.getters,
- * generated_static_expressions.setters,
- * generated_static_expressions.symbols)
- * .addModule(new Module()..bind(HelloWorldController))
- * .run();
- *
- */
+* Bootstraps Angular as part of the `main()` function.
+*
+* `staticApplication()` replaces `dynamicApplication()` in the main function during pub build,
+* and is populated with the getters, setters, annotations, and factories generated by
+* Angular's transformers for dart2js compilation. It is not typically called directly.
+*
+* For example,
+*
+* main() {
+* applicationFactory()
+* .rootContextType(HelloWorld)
+* .run();
+* }
+*
+* becomes:
+*
+* main() {
+* staticApplication(generated_static_injector.factories,
+* generated_static_metadata.typeAnnotations,
+* generated_static_expressions.getters,
+* generated_static_expressions.setters,
+* generated_static_expressions.symbols)
+* .rootContextType(HelloWorld)
+* .run();
+*/
Application staticApplicationFactory(
Map typeFactories,
Map metadata,
diff --git a/lib/change_detection/context_locals.dart b/lib/change_detection/context_locals.dart
new file mode 100644
index 000000000..05098d68a
--- /dev/null
+++ b/lib/change_detection/context_locals.dart
@@ -0,0 +1,24 @@
+part of angular.watch_group;
+
+class ContextLocals {
+ final Map _locals = {};
+
+ final Object _parentContext;
+ Object get parentContext => _parentContext;
+
+ ContextLocals(this._parentContext, [Map locals = null]) {
+ assert(_parentContext != null);
+ if (locals != null) _locals.addAll(locals);
+ }
+
+ static ContextLocals wrapper(context, Map locals) =>
+ new ContextLocals(context, locals);
+
+ bool hasProperty(String prop) => _locals.containsKey(prop);
+
+ void operator[]=(String prop, value) {
+ _locals[prop] = value;
+ }
+
+ dynamic operator[](String prop) => _locals[prop];
+}
diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart
index 867a302c5..adb54e0d6 100644
--- a/lib/change_detection/dirty_checking_change_detector.dart
+++ b/lib/change_detection/dirty_checking_change_detector.dart
@@ -2,6 +2,7 @@ library dirty_checking_change_detector;
import 'dart:collection';
import 'package:angular/change_detection/change_detection.dart';
+import 'package:angular/change_detection/watch_group.dart';
/**
* [DirtyCheckingChangeDetector] determines which object properties have changed
@@ -369,7 +370,7 @@ class _ChangeIterator implements Iterator>{
* removing efficient. [DirtyCheckingRecord] also has a [nextChange] field which
* creates a single linked list of all of the changes for efficient traversal.
*/
-class DirtyCheckingRecord implements Record, WatchRecord {
+class DirtyCheckingRecord implements WatchRecord {
static const List _MODE_NAMES = const [
'MARKER',
'NOOP',
@@ -423,9 +424,10 @@ class DirtyCheckingRecord implements Record, WatchRecord {
* [DirtyCheckingRecord] into different access modes. If Object it sets up
* reflection. If [Map] then it sets up map accessor.
*/
- void set object(obj) {
- _object = obj;
- if (obj == null) {
+ void set object(Object object) {
+ _object = object;
+
+ if (object == null) {
_mode = _MODE_IDENTITY_;
_getter = null;
return;
@@ -433,7 +435,8 @@ class DirtyCheckingRecord implements Record, WatchRecord {
if (field == null) {
_getter = null;
- if (obj is Map) {
+
+ if (object is Map) {
if (_mode != _MODE_MAP_) {
_mode = _MODE_MAP_;
currentValue = new _MapChangeRecord();
@@ -445,8 +448,7 @@ class DirtyCheckingRecord implements Record, WatchRecord {
// new reference.
currentValue._revertToPreviousState();
}
-
- } else if (obj is Iterable) {
+ } else if (object is Iterable) {
if (_mode != _MODE_ITERABLE_) {
_mode = _MODE_ITERABLE_;
currentValue = new _CollectionChangeRecord();
@@ -465,13 +467,17 @@ class DirtyCheckingRecord implements Record, WatchRecord {
return;
}
- if (obj is Map) {
- _mode = _MODE_MAP_FIELD_;
- _getter = null;
- } else {
- _mode = _MODE_GETTER_OR_METHOD_CLOSURE_;
- _getter = _fieldGetterFactory.getter(obj, field);
+ while (object is ContextLocals) {
+ var ctx = object as ContextLocals;
+ if (ctx.hasProperty(field)) {
+ _mode = _MODE_MAP_FIELD_;
+ _getter = null;
+ return;
+ }
+ object = ctx.parentContext;
}
+ _mode = _MODE_GETTER_OR_METHOD_CLOSURE_;
+ _getter = _fieldGetterFactory.getter(object, field);
}
bool check() {
diff --git a/lib/change_detection/prototype_map.dart b/lib/change_detection/prototype_map.dart
deleted file mode 100644
index 130444184..000000000
--- a/lib/change_detection/prototype_map.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-part of angular.watch_group;
-
-class PrototypeMap implements Map {
- final Map prototype;
- final Map self = new Map();
-
- PrototypeMap(this.prototype);
-
- void operator []=(name, value) {
- self[name] = value;
- }
- V operator [](name) => self.containsKey(name) ? self[name] : prototype[name];
-
- bool get isEmpty => self.isEmpty && prototype.isEmpty;
- bool get isNotEmpty => self.isNotEmpty || prototype.isNotEmpty;
- // todo(vbe) include prototype keys ?
- Iterable get keys => self.keys;
- // todo(vbe) include prototype values ?
- Iterable get values => self.values;
- int get length => self.length;
-
- void forEach(fn) {
- // todo(vbe) include prototype ?
- self.forEach(fn);
- }
- V remove(key) => self.remove(key);
- clear() => self.clear;
- // todo(vbe) include prototype ?
- bool containsKey(key) => self.containsKey(key);
- // todo(vbe) include prototype ?
- bool containsValue(key) => self.containsValue(key);
- void addAll(map) {
- self.addAll(map);
- }
- // todo(vbe) include prototype ?
- V putIfAbsent(key, fn) => self.putIfAbsent(key, fn);
-}
diff --git a/lib/change_detection/watch_group.dart b/lib/change_detection/watch_group.dart
index 14d327d5f..c6cb65dcd 100644
--- a/lib/change_detection/watch_group.dart
+++ b/lib/change_detection/watch_group.dart
@@ -4,7 +4,7 @@ import 'package:angular/change_detection/change_detection.dart';
part 'linked_list.dart';
part 'ast.dart';
-part 'prototype_map.dart';
+part 'context_locals.dart';
/**
* A function that is notified of changes to the model.
@@ -774,24 +774,31 @@ class _EvalWatchRecord implements WatchRecord<_Handler> {
get object => _object;
- set object(value) {
+ void set object(object) {
assert(mode != _MODE_DELETED_);
assert(mode != _MODE_MARKER_);
assert(mode != _MODE_FUNCTION_);
assert(mode != _MODE_PURE_FUNCTION_);
assert(mode != _MODE_PURE_FUNCTION_APPLY_);
- _object = value;
+ _object = object;
- if (value == null) {
+ if (object == null) {
mode = _MODE_NULL_;
+ } else if (object is Map) {
+ mode = _MODE_MAP_CLOSURE_;
} else {
- if (value is Map) {
- mode = _MODE_MAP_CLOSURE_;
- } else {
- mode = _MODE_FIELD_OR_METHOD_CLOSURE_;
- fn = _fieldGetterFactory.getter(value, name);
+ while (object is ContextLocals) {
+ var ctx = object as ContextLocals;
+ if (ctx.hasProperty(name)) {
+ mode = _MODE_MAP_CLOSURE_;
+ return;
+ }
+ object = ctx.parentContext;
}
+ mode = _MODE_FIELD_OR_METHOD_CLOSURE_;
+ fn = _fieldGetterFactory.getter(object, name);
}
+
}
bool check() {
diff --git a/lib/core/annotation.dart b/lib/core/annotation.dart
index 768def9aa..dcd2f23fd 100644
--- a/lib/core/annotation.dart
+++ b/lib/core/annotation.dart
@@ -15,7 +15,6 @@ export "package:angular/core/annotation_src.dart" show
Directive,
Component,
- Controller,
Decorator,
DirectiveAnnotation,
diff --git a/lib/core/annotation_src.dart b/lib/core/annotation_src.dart
index a8c1fb043..32bbd3ed4 100644
--- a/lib/core/annotation_src.dart
+++ b/lib/core/annotation_src.dart
@@ -34,7 +34,7 @@ class Injectable {
}
/**
- * Abstract supper class of [Controller], [Component], and [Decorator].
+ * Abstract supper class of [Component], and [Decorator].
*/
abstract class Directive {
@@ -301,14 +301,6 @@ class Component extends Directive {
}
final bool _resetStyleInheritance;
- /**
- * An expression under which the component's controller instance will be
- * published into. This allows the expressions in the template to be referring
- * to controller instance and its properties.
- */
- @deprecated
- final String publishAs;
-
/**
* If set to true, this component will always use shadow DOM.
* If set to false, this component will never use shadow DOM.
@@ -322,7 +314,6 @@ class Component extends Directive {
cssUrl,
applyAuthorStyles,
resetStyleInheritance,
- this.publishAs,
module,
map,
selector,
@@ -352,7 +343,6 @@ class Component extends Directive {
cssUrl: cssUrls,
applyAuthorStyles: applyAuthorStyles,
resetStyleInheritance: resetStyleInheritance,
- publishAs: publishAs,
map: newMap,
module: module,
selector: selector,
@@ -402,62 +392,6 @@ class Decorator extends Directive {
exportExpressionAttrs: exportExpressionAttrs);
}
-/**
- * Annotation placed on a class which should act as a controller for your
- * application.
- *
- * Controllers are essentially [Decorator]s with few key differences:
- *
- * * Controllers create a new scope at the element.
- * * Controllers should not do any DOM manipulation.
- * * Controllers are meant for application-logic
- * (rather then DOM manipulation logic which directives are meant for.)
- *
- * Controllers can implement [AttachAware], [DetachAware] and
- * declare these optional methods:
- *
- * * `attach()` - Called on first [Scope.apply()].
- * * `detach()` - Called on when owning scope is destroyed.
- */
-@deprecated
-class Controller extends Decorator {
- /**
- * An expression under which the controller instance will be published into.
- * This allows the expressions in the template to be referring to controller
- * instance and its properties.
- */
- final String publishAs;
-
- const Controller({
- children: Directive.COMPILE_CHILDREN,
- this.publishAs,
- map,
- module,
- selector,
- visibility,
- exportExpressions,
- exportExpressionAttrs
- })
- : super(selector: selector,
- children: children,
- visibility: visibility,
- map: map,
- module: module,
- exportExpressions: exportExpressions,
- exportExpressionAttrs: exportExpressionAttrs);
-
- Directive _cloneWithNewMap(newMap) =>
- new Controller(
- children: children,
- publishAs: publishAs,
- module: module,
- map: newMap,
- selector: selector,
- visibility: visibility,
- exportExpressions: exportExpressions,
- exportExpressionAttrs: exportExpressionAttrs);
-}
-
/**
* Abstract supper class of [NgAttr], [NgCallback], [NgOneWay], [NgOneWayOneTime], and [NgTwoWay].
*/
diff --git a/lib/core/module.dart b/lib/core/module.dart
index d4b3d6a24..0dd28d432 100644
--- a/lib/core/module.dart
+++ b/lib/core/module.dart
@@ -65,9 +65,10 @@ export "package:angular/core/module_internal.dart" show
Interpolate,
VmTurnZone,
WebPlatform,
- PrototypeMap,
RootScope,
+ ContextLocals,
Scope,
+ ScopeAware,
ScopeDigestTTL,
ScopeEvent,
ScopeStats,
diff --git a/lib/core/parser/dynamic_parser.dart b/lib/core/parser/dynamic_parser.dart
index 041ebb33d..6bacf45ee 100644
--- a/lib/core/parser/dynamic_parser.dart
+++ b/lib/core/parser/dynamic_parser.dart
@@ -1,7 +1,9 @@
library angular.core.parser.dynamic_parser;
import 'package:angular/core/annotation_src.dart' hide Formatter;
-import 'package:angular/core/module_internal.dart' show FormatterMap;
+import 'package:angular/core/module_internal.dart' show
+ FormatterMap,
+ ContextLocals;
import 'package:angular/core/parser/parser.dart';
import 'package:angular/core/parser/lexer.dart';
@@ -68,7 +70,7 @@ class DynamicExpression extends Expression {
@Injectable()
class DynamicParserBackend extends ParserBackend {
final ClosureMap _closures;
- DynamicParserBackend(this._closures);
+ DynamicParserBackend(ClosureMap _closures): _closures = new ClosureMapLocalsAware(_closures);
bool isAssignable(Expression expression) => expression.isAssignable;
@@ -137,3 +139,61 @@ class DynamicParserBackend extends ParserBackend {
}
}
+// todo(vicb) Would probably be better to remove this from the parser
+class ClosureMapLocalsAware implements ClosureMap {
+ final ClosureMap wrappedClsMap;
+
+ ClosureMapLocalsAware(this.wrappedClsMap);
+
+ Getter lookupGetter(String name) {
+ return (o) {
+ while (o is ContextLocals) {
+ var ctx = o as ContextLocals;
+ if (ctx.hasProperty(name)) return ctx[name];
+ o = ctx.parentContext;
+ }
+ var getter = wrappedClsMap.lookupGetter(name);
+ return getter(o);
+ };
+ }
+
+ Setter lookupSetter(String name) {
+ return (o, value) {
+ while (o is ContextLocals) {
+ var ctx = o as ContextLocals;
+ if (ctx.hasProperty(name)) return ctx[name] = value;
+ o = ctx.parentContext;
+ }
+ var setter = wrappedClsMap.lookupSetter(name);
+ return setter(o, value);
+ };
+ }
+
+ MethodClosure lookupFunction(String name, CallArguments arguments) {
+ return (o, pArgs, nArgs) {
+ while (o is ContextLocals) {
+ var ctx = o as ContextLocals;
+ if (ctx.hasProperty(name)) {
+ var fn = ctx[name];
+ if (fn is Function) {
+ var snArgs = {};
+ nArgs.forEach((name, value) {
+ var symbol = wrappedClsMap.lookupGetter(name);
+ snArgs[symbol] = value;
+ });
+ return Function.apply(fn, pArgs, snArgs);
+ } else {
+ throw "Property '$name' is not of type function.";
+ }
+ }
+ o = ctx.parentContext;
+ }
+ var fn = wrappedClsMap.lookupFunction(name, arguments);
+ return fn(o, pArgs, nArgs);
+ };
+ }
+
+ Symbol lookupSymbol(String name) => wrappedClsMap.lookupSymbol(name);
+}
+
+
diff --git a/lib/core/parser/eval_access.dart b/lib/core/parser/eval_access.dart
index 0fdfa61c0..34eb1cb29 100644
--- a/lib/core/parser/eval_access.dart
+++ b/lib/core/parser/eval_access.dart
@@ -38,22 +38,23 @@ class AccessKeyed extends syntax.AccessKeyed {
* where we have a pair of pre-compiled getter and setter functions that we
* use to do the access the field.
*/
+// todo(vicb) - parser should not depend on ContextLocals
abstract class AccessFast {
String get name;
Getter get getter;
Setter get setter;
- _eval(holder) {
+ dynamic _eval(holder) {
if (holder == null) return null;
- return (holder is Map) ? holder[name] : getter(holder);
+ return getter(holder);
}
- _assign(scope, holder, value) {
+ dynamic _assign(scope, holder, value) {
if (holder == null) {
_assignToNonExisting(scope, value);
return value;
} else {
- return (holder is Map) ? (holder[name] = value) : setter(holder, value);
+ return setter(holder, value);
}
}
diff --git a/lib/core/parser/parser_dynamic.dart b/lib/core/parser/parser_dynamic.dart
index 967db7bed..761cb3ca0 100644
--- a/lib/core/parser/parser_dynamic.dart
+++ b/lib/core/parser/parser_dynamic.dart
@@ -8,24 +8,14 @@ class DynamicClosureMap implements ClosureMap {
final Map symbols = {};
Getter lookupGetter(String name) {
var symbol = new Symbol(name);
- return (o) {
- if (o is Map) {
- return o[name];
- } else {
- return reflect(o).getField(symbol).reflectee;
- }
- };
+ return (o) => reflect(o).getField(symbol).reflectee;
}
Setter lookupSetter(String name) {
var symbol = new Symbol(name);
return (o, value) {
- if (o is Map) {
- return o[name] = value;
- } else {
- reflect(o).setField(symbol, value);
- return value;
- }
+ reflect(o).setField(symbol, value);
+ return value;
};
}
@@ -37,19 +27,10 @@ class DynamicClosureMap implements ClosureMap {
var symbol = symbols.putIfAbsent(name, () => new Symbol(name));
sNamedArgs[symbol] = value;
});
- if (o is Map) {
- var fn = o[name];
- if (fn is Function) {
- return Function.apply(fn, posArgs, sNamedArgs);
- } else {
- throw "Property '$name' is not of type function.";
- }
- } else {
- try {
- return reflect(o).invoke(symbol, posArgs, sNamedArgs).reflectee;
- } on NoSuchMethodError catch (e) {
- throw 'Undefined function $name';
- }
+ try {
+ return reflect(o).invoke(symbol, posArgs, sNamedArgs).reflectee;
+ } on NoSuchMethodError catch (e) {
+ throw 'Undefined function $name';
}
};
}
diff --git a/lib/core/parser/parser_static.dart b/lib/core/parser/parser_static.dart
index cf1c53255..84c99f557 100644
--- a/lib/core/parser/parser_static.dart
+++ b/lib/core/parser/parser_static.dart
@@ -27,16 +27,7 @@ class StaticClosureMap extends ClosureMap {
return (o, posArgs, namedArgs) {
var sNamedArgs = {};
namedArgs.forEach((name, value) => sNamedArgs[symbols[name]] = value);
- if (o is Map) {
- var fn = o[name];
- if (fn is Function) {
- return Function.apply(fn, posArgs, sNamedArgs);
- } else {
- throw "Property '$name' is not of type function.";
- }
- } else {
- return Function.apply(fn(o), posArgs, sNamedArgs);
- }
+ return Function.apply(fn(o), posArgs, sNamedArgs);
};
}
diff --git a/lib/core/parser/utils.dart b/lib/core/parser/utils.dart
index 82a898d7b..1105a4c63 100644
--- a/lib/core/parser/utils.dart
+++ b/lib/core/parser/utils.dart
@@ -80,6 +80,11 @@ getKeyed(object, key) {
} else if (object == null) {
throw new EvalError('Accessing null object');
} else {
+ while (object is ContextLocals) {
+ var ctx = object as ContextLocals;
+ if (ctx.hasProperty(key)) break;
+ object = ctx.parentContext;
+ }
return object[key];
}
}
@@ -93,6 +98,11 @@ setKeyed(object, key, value) {
} else if (object is Map) {
object["$key"] = value; // toString dangerous?
} else {
+ while (object is ContextLocals) {
+ var ctx = object as ContextLocals;
+ if (ctx.hasProperty(key)) break;
+ object = ctx.parentContext;
+ }
object[key] = value;
}
return value;
diff --git a/lib/core/scope.dart b/lib/core/scope.dart
index 145d55be9..6b52647d7 100644
--- a/lib/core/scope.dart
+++ b/lib/core/scope.dart
@@ -84,44 +84,34 @@ class ScopeDigestTTL {
ScopeDigestTTL.value(this.ttl);
}
-//TODO(misko): I don't think this should be in scope.
-class ScopeLocals implements Map {
- static wrapper(scope, Map locals) =>
- new ScopeLocals(scope, locals);
-
- Map _scope;
- Map _locals;
-
- ScopeLocals(this._scope, this._locals);
-
- void operator []=(String name, value) {
- _scope[name] = value;
- }
- dynamic operator [](String name) {
- // Map needed to clear Dart2js warning
- Map map = _locals.containsKey(name) ? _locals : _scope;
- return map[name];
- }
-
- bool get isEmpty => _scope.isEmpty && _locals.isEmpty;
- bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty;
- List get keys => _scope.keys;
- List get values => _scope.values;
- int get length => _scope.length;
-
- void forEach(fn) {
- _scope.forEach(fn);
- }
- dynamic remove(key) => _scope.remove(key);
- void clear() {
- _scope.clear;
- }
- bool containsKey(key) => _scope.containsKey(key);
- bool containsValue(key) => _scope.containsValue(key);
- void addAll(map) {
- _scope.addAll(map);
- }
- dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn);
+/**
+ * When a [Component] or the root context class implements [ScopeAware] the context setter will be
+ * called to set the [Scope] on this component.
+ *
+ * Typically classes implementing [ScopeAware] will declare a `Scope scope` property which will get
+ * initialized after the [Scope] is available. For this reason the `scope` property will not be
+ * initialized during the execution of the constructor - it will be immediately after.
+ *
+ * However if you need to execute some code as soon as the scope is available you should implement
+ * a `scope` setter:
+ *
+ * @Component(...)
+ * class MyComponent implements ScopeAware {
+ * Watch watch;
+ *
+ * MyComponent(Dependency myDep) {
+ * // It is an error to add a Scope / RootScope argument to the ctor and will result in a DI
+ * // circular dependency error - the scope is never accessible in the class constructor
+ * }
+ *
+ * void set scope(Scope scope) {
+ * // This setter gets called to initialize the scope
+ * watch = scope.rootScope.watch("expression", (v, p) => ...);
+ * }
+ * }
+ */
+abstract class ScopeAware {
+ void set scope(Scope scope);
}
/**
@@ -186,7 +176,10 @@ class Scope {
Scope(Object this.context, this.rootScope, this._parentScope,
this._readWriteGroup, this._readOnlyGroup, this.id,
- this._stats);
+ this._stats)
+ {
+ if (context is ScopeAware) (context as ScopeAware).scope = this;
+ }
/**
* Use [watch] to set up change detection on an expression.
@@ -252,8 +245,8 @@ class Scope {
expression is String ||
expression is Function);
if (expression is String && expression.isNotEmpty) {
- var obj = locals == null ? context : new ScopeLocals(context, locals);
- return rootScope._parser(expression).eval(obj);
+ var ctx = locals == null ? context : new ContextLocals(context, locals);
+ return rootScope._parser(expression).eval(ctx);
}
assert(locals == null);
@@ -296,8 +289,8 @@ class Scope {
var child = new Scope(childContext, rootScope, this,
_readWriteGroup.newGroup(childContext),
_readOnlyGroup.newGroup(childContext),
- '$id:${_childScopeNextId++}',
- _stats);
+ '$id:${_childScopeNextId++}',
+ _stats);
var prev = _childTail;
child._prev = prev;
diff --git a/lib/core_dom/common.dart b/lib/core_dom/common.dart
index a60a050ee..ca982f99d 100644
--- a/lib/core_dom/common.dart
+++ b/lib/core_dom/common.dart
@@ -39,7 +39,7 @@ Injector forceNewDirectivesAndFormatters(Injector injector, List modules
modules.add(new Module()
..bind(Scope, toFactory: (i) {
var scope = i.parent.get(Scope);
- return scope.createChild(new PrototypeMap(scope.context));
+ return scope.createChild(scope.context);
}));
return injector.createChild(modules,
diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart
index 79be68c1c..5ff18e144 100644
--- a/lib/core_dom/element_binder.dart
+++ b/lib/core_dom/element_binder.dart
@@ -121,7 +121,7 @@ class ElementBinder {
}
void _bindCallback(dstPathFn, controller, expression, scope) {
- dstPathFn.assign(controller, _parser(expression).bind(scope.context, ScopeLocals.wrapper));
+ dstPathFn.assign(controller, _parser(expression).bind(scope.context, ContextLocals.wrapper));
}
@@ -211,13 +211,9 @@ class ElementBinder {
probe.directives.add(directive);
assert((linkMapTimer = _perf.startTimer('ng.view.link.map', ref.type)) != false);
- if (ref.annotation is Controller) {
- scope.context[(ref.annotation as Controller).publishAs] = directive;
- }
-
- var tasks = new _TaskList(directive is AttachAware ? () {
- if (scope.isAttached) directive.attach();
- } : null);
+ var tasks = new _TaskList(directive is AttachAware ?
+ () {if (scope.isAttached) directive.attach();} :
+ null);
if (ref.mappings.isNotEmpty) {
if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);
@@ -313,16 +309,11 @@ class ElementBinder {
directiveRefs.forEach((DirectiveRef ref) {
Directive annotation = ref.annotation;
- var visibility = ref.annotation.visibility;
- if (ref.annotation is Controller) {
- scope = scope.createChild(new PrototypeMap(scope.context));
- nodeModule.bind(Scope, toValue: scope);
- }
_createDirectiveFactories(ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs,
- visibility);
- if (ref.annotation.module != null) {
- nodeModule.install(ref.annotation.module());
+ annotation.visibility);
+ if (annotation.module != null) {
+ nodeModule.install(annotation.module());
}
});
diff --git a/lib/core_dom/event_handler.dart b/lib/core_dom/event_handler.dart
index 1a0f5d9b5..e3ce28275 100644
--- a/lib/core_dom/event_handler.dart
+++ b/lib/core_dom/event_handler.dart
@@ -4,7 +4,7 @@ typedef void EventFunction(event);
/**
* [EventHandler] is responsible for handling events bound using on-* syntax
- * (i.e. `on-click="ctrl.doSomething();"`). The root of the application has an
+ * (i.e. `on-click="doSomething();"`). The root of the application has an
* EventHandler attached as does every [Component].
*
* Events bound within [Component] are handled by EventHandler attached to
@@ -16,12 +16,14 @@ typedef void EventFunction(event);
* Example:
*
*
- * Button;
+ * Button;
*
*
- * @Component(selector: '[foo]', publishAs: ctrl)
- * class FooController {
- * say(String something) => print(something);
+ * @Component(selector: '[foo]')
+ * class FooComponent {
+ * void say(String something) {
+ * print(something);
+ * }
* }
*
* When button is clicked, "Hello" will be printed in the console.
diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart
index d4077c25b..78125647b 100644
--- a/lib/core_dom/module_internal.dart
+++ b/lib/core_dom/module_internal.dart
@@ -14,7 +14,7 @@ import 'package:angular/core/module_internal.dart';
import 'package:angular/core/parser/parser.dart';
import 'package:angular/core_dom/dom_util.dart' as util;
-import 'package:angular/change_detection/watch_group.dart' show Watch, PrototypeMap;
+import 'package:angular/change_detection/watch_group.dart' show Watch, ContextLocals;
import 'package:angular/core/registry.dart';
import 'package:angular/directive/module.dart' show NgBaseCss;
@@ -66,7 +66,6 @@ class CoreDomModule extends Module {
bind(ContentPort, toValue: null);
bind(ComponentCssRewriter);
bind(WebPlatform);
-
bind(Http);
bind(UrlRewriter);
bind(HttpBackend);
diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart
index dbfab856e..1cff13369 100644
--- a/lib/core_dom/shadow_dom_component_factory.dart
+++ b/lib/core_dom/shadow_dom_component_factory.dart
@@ -52,23 +52,12 @@ class ShadowDomComponentFactory implements ComponentFactory {
_expando,
baseCss,
_styleElementCache);
- var controller = componentFactory.call(injector, scope, viewCache, http, templateCache,
- directives);
-
- componentFactory.shadowScope.context[component.publishAs] = controller;
- return controller;
+ return componentFactory.call(injector, scope, viewCache, http, templateCache, directives);
};
}
}
-
-/**
- * ComponentFactory is responsible for setting up components. This includes
- * the shadowDom, fetching template, importing styles, setting up attribute
- * mappings, publishing the controller, and compiling and caching the template.
- */
class _ComponentFactory implements Function {
-
final dom.Element element;
final Type type;
final Component component;
@@ -93,10 +82,9 @@ class _ComponentFactory implements Function {
ViewCache viewCache, Http http, TemplateCache templateCache,
DirectiveMap directives) {
shadowDom = element.createShadowRoot()
- ..applyAuthorStyles = component.applyAuthorStyles
- ..resetStyleInheritance = component.resetStyleInheritance;
+ ..applyAuthorStyles = component.applyAuthorStyles
+ ..resetStyleInheritance = component.resetStyleInheritance;
- shadowScope = scope.createChild({}); // Isolate
// TODO(pavelgj): fetching CSS with Http is mainly an attempt to
// work around an unfiled Chrome bug when reloading same CSS breaks
// styles all over the page. We shouldn't be doing browsers work,
@@ -109,8 +97,7 @@ class _ComponentFactory implements Function {
cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent(
new _ComponentAssetKey(tag, cssUrl), () =>
http.get(cssUrl, cache: templateCache)
- .then((resp) => resp.responseText,
- onError: (e) => '/*\n$e\n*/\n')
+ .then((resp) => resp.responseText, onError: (e) => '/*\n$e\n*/\n')
.then((String css) {
// Shim CSS if required
@@ -120,15 +107,14 @@ class _ComponentFactory implements Function {
// If a css rewriter is installed, run the css through a rewriter
var styleElement = new dom.StyleElement()
- ..appendText(componentCssRewriter(css, selector: tag,
- cssUrl: cssUrl));
+ ..appendText(componentCssRewriter(css, selector: tag, cssUrl: cssUrl));
// ensure there are no invalid tags or modifications
treeSanitizer.sanitizeTree(styleElement);
// If the css shim is required, it means that scoping does not
// work, and adding the style to the head of the document is
- // preferrable.
+ // preferable.
if (platform.cssShimRequired) {
dom.document.head.append(styleElement);
}
@@ -164,7 +150,9 @@ class _ComponentFactory implements Function {
}
return shadowDom;
}));
- controller = createShadowInjector(injector, templateLoader).get(type);
+
+ var shadowInjector = createShadowInjector(scope, injector, templateLoader);
+ var controller = shadowInjector.get(type);
ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
return controller;
}
@@ -175,19 +163,23 @@ class _ComponentFactory implements Function {
return shadowDom;
}
- Injector createShadowInjector(injector, TemplateLoader templateLoader) {
+ Injector createShadowInjector(Scope scope, Injector injector, TemplateLoader templateLoader) {
var probe;
var shadowModule = new Module()
- ..bind(type)
- ..bind(NgElement)
- ..bind(EventHandler, toImplementation: ShadowRootEventHandler)
- ..bind(Scope, toValue: shadowScope)
- ..bind(TemplateLoader, toValue: templateLoader)
- ..bind(dom.ShadowRoot, toValue: shadowDom)
- ..bind(ElementProbe, toFactory: (_) => probe);
+ ..bind(type)
+ ..bind(NgElement)
+ ..bind(EventHandler, toImplementation: ShadowRootEventHandler)
+ ..bind(Scope, toFactory: (Injector inj) => scope.createChild(inj.get(type)))
+ ..bind(TemplateLoader, toValue: templateLoader)
+ ..bind(dom.ShadowRoot, toValue: shadowDom)
+ ..bind(ElementProbe, toFactory: (_) => probe);
+
shadowInjector = injector.createChild([shadowModule], name: SHADOW_DOM_INJECTOR_NAME);
+ shadowScope = shadowInjector.get(Scope);
+
probe = _expando[shadowDom] = new ElementProbe(
injector.get(ElementProbe), shadowDom, shadowInjector, shadowScope);
+
return shadowInjector;
}
}
@@ -220,4 +212,4 @@ class ComponentCssRewriter {
String call(String css, { String selector, String cssUrl} ) {
return css;
}
-}
\ No newline at end of file
+}
diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart
index f5732c512..94cee6e0f 100644
--- a/lib/core_dom/transcluding_component_factory.dart
+++ b/lib/core_dom/transcluding_component_factory.dart
@@ -12,7 +12,7 @@ class Content implements AttachAware, DetachAware {
if (_port == null) return;
_beginComment = _port.content(_element);
}
-
+
void detach() {
if (_port == null) return;
_port.detachContent(_beginComment);
@@ -101,21 +101,20 @@ class TranscludingComponentFactory implements ComponentFactory {
}
TemplateLoader templateLoader = new TemplateLoader(elementFuture);
- Scope shadowScope = scope.createChild({});
-
var probe;
var childModule = new Module()
..bind(ref.type)
..bind(NgElement)
..bind(ContentPort, toValue: contentPort)
- ..bind(Scope, toValue: shadowScope)
+ ..bind(Scope, toFactory: (Injector inj) => scope.createChild(inj.get(ref.type)))
..bind(TemplateLoader, toValue: templateLoader)
..bind(dom.ShadowRoot, toValue: new ShadowlessShadowRoot(element))
..bind(ElementProbe, toFactory: (_) => probe);
childInjector = injector.createChild([childModule], name: SHADOW_DOM_INJECTOR_NAME);
var controller = childInjector.get(ref.type);
- shadowScope.context[component.publishAs] = controller;
+ var shadowScope = childInjector.get(Scope);
+
ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
return controller;
};
diff --git a/lib/directive/module.dart b/lib/directive/module.dart
index 3231baeb9..f62147de3 100644
--- a/lib/directive/module.dart
+++ b/lib/directive/module.dart
@@ -12,7 +12,8 @@
*
* For example:
*
- * this text is conditionally visible
+ * this text is conditionally visible
+ *
*/
library angular.directive;
diff --git a/lib/directive/ng_class.dart b/lib/directive/ng_class.dart
index 1ece01593..373a1cc4c 100644
--- a/lib/directive/ng_class.dart
+++ b/lib/directive/ng_class.dart
@@ -171,7 +171,8 @@ abstract class _NgClassBase {
nodeAttrs.observe('class', (String cls) {
if (prevCls != cls) {
prevCls = cls;
- _applyChanges(_scope.context[r'$index']);
+ var index = _hasLocal(_scope, r'$index') ? _getLocal(_scope, r'$index') : null;
+ _applyChanges(index);
}
});
}
@@ -180,7 +181,8 @@ abstract class _NgClassBase {
if (_watchExpression != null) _watchExpression.remove();
_watchExpression = _scope.watch(expression, (v, _) {
_computeChanges(v);
- _applyChanges(_scope.context[r'$index']);
+ var index = _hasLocal(_scope, r'$index') ? _getLocal(_scope, r'$index') : null;
+ _applyChanges(index);
},
canChangeModel: false,
collection: true);
@@ -276,3 +278,21 @@ abstract class _NgClassBase {
_previousSet = _currentSet.toSet();
}
}
+
+bool _hasLocal(context, name) {
+ var ctx = context;
+ while (ctx is ContextLocals) {
+ if (ctx.hasProperty(name)) return true;
+ ctx = ctx.parentScope;
+ }
+ return false;
+}
+
+dynamic _getLocal(context, name) {
+ var ctx = context;
+ while (ctx is ContextLocals) {
+ if (ctx.hasProperty(name)) return ctx[name];
+ ctx = ctx.parentScope;
+ }
+ return null;
+}
diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart
index a13b623c7..649d11ebb 100644
--- a/lib/directive/ng_control.dart
+++ b/lib/directive/ng_control.dart
@@ -3,9 +3,9 @@ part of angular.directive;
/**
* Contains info and error states used during form and input validation.
*
- * NgControl is a common superclass for forms and input controls that handles info and error states, as well as
- * status flags. NgControl is used with the form and fieldset as well as all other directives that are used for
- * user input with NgModel.
+ * [NgControl] is a common superclass for forms and input controls that handles info and error
+ * states, as well as status flags. NgControl is used with the form and fieldset as well as all
+ * other directives that are used for user input with NgModel.
*/
abstract class NgControl implements AttachAware, DetachAware {
static const NG_VALID = "ng-valid";
diff --git a/lib/directive/ng_events.dart b/lib/directive/ng_events.dart
index 14c422d3f..5ccdc3b23 100644
--- a/lib/directive/ng_events.dart
+++ b/lib/directive/ng_events.dart
@@ -206,4 +206,4 @@ class NgEvent {
set onTouchMove(value) => initListener(element.onTouchMove, value);
set onTouchStart(value) => initListener(element.onTouchStart, value);
set onTransitionEnd(value) => initListener(element.onTransitionEnd, value);
-}
+}
\ No newline at end of file
diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart
index 302de1c49..1b778470a 100644
--- a/lib/directive/ng_form.dart
+++ b/lib/directive/ng_form.dart
@@ -58,7 +58,6 @@ class NgForm extends NgControl {
set name(String value) {
if (value != null) {
super.name = value;
- _scope.context[name] = this;
}
}
diff --git a/lib/directive/ng_if.dart b/lib/directive/ng_if.dart
index 562bfa181..1ce6ba1be 100644
--- a/lib/directive/ng_if.dart
+++ b/lib/directive/ng_if.dart
@@ -10,23 +10,16 @@ abstract class _NgUnlessIfAttrDirectiveBase {
View _view;
- /**
- * The new child scope. This child scope is recreated whenever the `ng-if`
- * subtree is inserted into the DOM and destroyed when it's removed from the
- * DOM. Refer
- * https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-prototypical-Inheritance prototypical inheritance
- */
Scope _childScope;
- _NgUnlessIfAttrDirectiveBase(this._boundViewFactory, this._viewPort,
- this._scope);
+ _NgUnlessIfAttrDirectiveBase(this._boundViewFactory, this._viewPort, this._scope);
// Override in subclass.
void set condition(value);
void _ensureViewExists() {
if (_view == null) {
- _childScope = _scope.createChild(new PrototypeMap(_scope.context));
+ _childScope = _scope.createChild(_scope.context);
_view = _boundViewFactory(_childScope);
var view = _view;
_scope.rootScope.domWrite(() {
@@ -42,8 +35,8 @@ abstract class _NgUnlessIfAttrDirectiveBase {
_viewPort.remove(view);
});
_childScope.destroy();
- _view = null;
_childScope = null;
+ _view = null;
}
}
}
diff --git a/lib/directive/ng_include.dart b/lib/directive/ng_include.dart
index be450524f..923902271 100644
--- a/lib/directive/ng_include.dart
+++ b/lib/directive/ng_include.dart
@@ -27,7 +27,7 @@ class NgInclude {
final DirectiveMap directives;
View _view;
- Scope _scope;
+ Scope _childScope;
NgInclude(this.element, this.scope, this.viewCache, this.injector, this.directives);
@@ -35,19 +35,18 @@ class NgInclude {
if (_view == null) return;
_view.nodes.forEach((node) => node.remove);
- _scope.destroy();
+ _childScope.destroy();
+ _childScope = null;
element.innerHtml = '';
_view = null;
- _scope = null;
+
}
_updateContent(createView) {
// create a new scope
- _scope = scope.createChild(new PrototypeMap(scope.context));
- _view = createView(injector.createChild([new Module()
- ..bind(Scope, toValue: _scope)]));
-
+ _childScope = scope.createChild(scope.context);
+ _view = createView(injector.createChild([new Module()..bind(Scope, toValue: _childScope)]));
_view.nodes.forEach((node) => element.append(node));
}
diff --git a/lib/directive/ng_repeat.dart b/lib/directive/ng_repeat.dart
index f4b480b7c..15e857e26 100644
--- a/lib/directive/ng_repeat.dart
+++ b/lib/directive/ng_repeat.dart
@@ -112,8 +112,7 @@ class NgRepeat {
..[r'$index'] = index
..[r'$id'] = (obj) => obj;
if (_keyIdentifier != null) context[_keyIdentifier] = key;
- return relaxFnArgs(trackBy.eval)(new ScopeLocals(_scope.context,
- context));
+ return relaxFnArgs(trackBy.eval)(new ContextLocals(_scope.context, context));
});
}
@@ -151,17 +150,12 @@ class NgRepeat {
var domIndex;
var addRow = (int index, value, View previousView) {
- var childContext = _updateContext(new PrototypeMap(_scope.context), index,
- length)..[_valueIdentifier] = value;
+ var childContext = new ContextLocals(_scope.context);
+ childContext = _updateContext(childContext, index, length);
+ childContext[_valueIdentifier] = value;
var childScope = _scope.createChild(childContext);
var view = _boundViewFactory(childScope);
- var nodes = view.nodes;
- rows[index] = new _Row(_generateId(index, value, index))
- ..view = view
- ..scope = childScope
- ..nodes = nodes
- ..startNode = nodes.first
- ..endNode = nodes.last;
+ rows[index] = new _Row(_generateId(index, value, index), childScope, view);
_viewPort.insert(view, insertAfter: previousView);
};
@@ -224,8 +218,7 @@ class NgRepeat {
if (changeFn == null) {
rows[targetIndex] = _rows[targetIndex];
domIndex--;
- // The element has not moved but `$last` and `$middle` might still need
- // to be updated
+ // The element has not moved but `$last` and `$middle` might still need to be updated
_updateContext(rows[targetIndex].scope.context, targetIndex, length);
} else {
changeFn(targetIndex, previousView);
@@ -236,9 +229,10 @@ class NgRepeat {
_rows = rows;
}
- PrototypeMap _updateContext(PrototypeMap context, int index, int length) {
- var first = (index == 0);
- var last = (index == length - 1);
+ ContextLocals _updateContext(ContextLocals context, int index, int len) {
+ var first = index == 0;
+ var last = index == len - 1;
+
return context
..[r'$index'] = index
..[r'$first'] = first
@@ -253,9 +247,6 @@ class _Row {
final id;
Scope scope;
View view;
- dom.Element startNode;
- dom.Element endNode;
- List nodes;
- _Row(this.id);
+ _Row(this.id, this.scope, this.view);
}
diff --git a/lib/directive/ng_switch.dart b/lib/directive/ng_switch.dart
index c9aec1275..01d2193eb 100644
--- a/lib/directive/ng_switch.dart
+++ b/lib/directive/ng_switch.dart
@@ -57,56 +57,30 @@ part of angular.directive;
},
visibility: Directive.DIRECT_CHILDREN_VISIBILITY)
class NgSwitch {
- Map> cases = new Map>();
- List<_ViewScopePair> currentViews = <_ViewScopePair>[];
+ final _cases = >{'?': <_Case>[]};
+ final _currentViews = <_ViewRef>[];
Function onChange;
- final Scope scope;
+ final Scope _scope;
- NgSwitch(this.scope) {
- cases['?'] = <_Case>[];
- }
+ NgSwitch(this._scope);
- addCase(String value, ViewPort anchor, BoundViewFactory viewFactory) {
- cases.putIfAbsent(value, () => <_Case>[]);
- cases[value].add(new _Case(anchor, viewFactory));
+ void addCase(String value, ViewPort anchor, BoundViewFactory viewFactory) {
+ _cases.putIfAbsent(value, () => <_Case>[]).add(new _Case(anchor, viewFactory));
}
- set value(val) {
- currentViews
- ..forEach((_ViewScopePair pair) {
- pair.port.remove(pair.view);
- pair.scope.destroy();
- })
- ..clear();
+ void set value(val) {
+ _currentViews..forEach((_ViewRef view) => view.remove())
+ ..clear();
val = '!$val';
- (cases.containsKey(val) ? cases[val] : cases['?'])
- .forEach((_Case caze) {
- Scope childScope = scope.createChild(new PrototypeMap(scope.context));
- var view = caze.viewFactory(childScope);
- caze.anchor.insert(view);
- currentViews.add(new _ViewScopePair(view, caze.anchor,
- childScope));
- });
- if (onChange != null) {
- onChange();
- }
- }
-}
-
-class _ViewScopePair {
- final View view;
- final ViewPort port;
- final Scope scope;
-
- _ViewScopePair(this.view, this.port, this.scope);
-}
+ var cases = _cases.containsKey(val) ? _cases[val] : _cases['?'];
+ cases.forEach((_Case c) {
+ var childScope = _scope.createChild(_scope.context);
+ _currentViews.add(c.createView(childScope));
+ });
-class _Case {
- final ViewPort anchor;
- final BoundViewFactory viewFactory;
-
- _Case(this.anchor, this.viewFactory);
+ if (onChange != null) onChange();
+ }
}
@Decorator(
@@ -114,23 +88,46 @@ class _Case {
children: Directive.TRANSCLUDE_CHILDREN,
map: const {'.': '@value'})
class NgSwitchWhen {
- final NgSwitch ngSwitch;
- final ViewPort port;
- final BoundViewFactory viewFactory;
- final Scope scope;
+ final NgSwitch _ngSwitch;
+ final ViewPort _port;
+ final BoundViewFactory _viewFactory;
- NgSwitchWhen(this.ngSwitch, this.port, this.viewFactory, this.scope);
+ NgSwitchWhen(this._ngSwitch, this._port, this._viewFactory);
- set value(String value) => ngSwitch.addCase('!$value', port, viewFactory);
+ void set value(String value) => _ngSwitch.addCase('!$value', _port, _viewFactory);
}
@Decorator(
children: Directive.TRANSCLUDE_CHILDREN,
selector: '[ng-switch-default]')
class NgSwitchDefault {
-
- NgSwitchDefault(NgSwitch ngSwitch, ViewPort port,
- BoundViewFactory viewFactory, Scope scope) {
+ NgSwitchDefault(NgSwitch ngSwitch, ViewPort port, BoundViewFactory viewFactory) {
ngSwitch.addCase('?', port, viewFactory);
}
}
+
+class _ViewRef {
+ final View _view;
+ final ViewPort _port;
+ final Scope _scope;
+
+ _ViewRef(this._view, this._port, this._scope);
+
+ void remove() {
+ _port.remove(_view);
+ _scope.destroy();
+ }
+}
+
+class _Case {
+ final ViewPort port;
+ final BoundViewFactory viewFactory;
+
+ _Case(this.port, this.viewFactory);
+
+ _ViewRef createView(Scope scope) {
+ var view = viewFactory(scope);
+ port.insert(view);
+ return new _ViewRef(view, port, scope);
+ }
+}
diff --git a/lib/formatter/filter.dart b/lib/formatter/filter.dart
index 5cc8fe503..a47e8b9ee 100644
--- a/lib/formatter/filter.dart
+++ b/lib/formatter/filter.dart
@@ -1,8 +1,7 @@
part of angular.formatter_internal;
-// Too bad you can't stick typedef's inside a class.
-typedef bool _Predicate(e);
-typedef bool _Equals(a, b);
+typedef bool _PredicateFn(e);
+typedef bool _CompareFn(a, b);
/**
* Selects a subset of items from the provided [List] and returns it as a new
@@ -17,11 +16,11 @@ typedef bool _Equals(a, b);
*
* The expression can be of the following types:
*
- * - [String], [bool] and [num]:Â Only items in the list that directly
+ * - [String], [bool] and [num]:Â Only items in the list that directly
* match this expression, items that are Maps with any value matching this
* item, and items that are lists containing a matching items are returned.
*
- * - [Map]: This defines a pattern map. Filters specific properties on objects
+ * - [Map]: This defines a pattern map. Filters specific properties on objects
* contained in the input list. For example `{name:"M", phone:"1"}` predicate
* will return a list of items which have property `name` containing "M" and
* property `phone` containing "1". A special property name, `$`, can be used
@@ -29,25 +28,25 @@ typedef bool _Equals(a, b);
* That's equivalent to the simple substring match with a `String` as
* described above.
*
- * - [Function]:Â This allows you to supply a custom function to filter the
- * List. The function is called for each element of the List. The returned
+ * - [Function]:Â This allows you to supply a custom function to filter the
+ * List. The function is called for each element of the List. The returned
* List contains exactly those elements for which this function returned
* `true`.
*
*
* The comparator is optional and can be one of the following:
*
- * - `bool comparator(expected, actual)`:Â The function will be called with the
+ * - `bool comparator(expected, actual)`:Â The function will be called with the
* object value and the predicate value to compare and should return true if
* the item should be included in filtered result.
*
* - `true`:Â Specifies that only identical objects matching the expression
- * exactly should be considered matches. Two strings are considered identical
- * if they are equal. Two numbers are considered identical if they are either
- * equal or both are `NaN`. All other objects are identical iff
+ * exactly should be considered matches. Two strings are considered identical
+ * if they are equal. Two numbers are considered identical if they are either
+ * equal or both are `NaN`. All other objects are identical iff
* identical(expected, actual) is true.
*
- * - `false|null`:Â Specifies case insensitive substring matching.
+ * - `false|null`: Specifies case insensitive substring matching.
*
*
* # Example ([view in plunker](http://plnkr.co/edit/6Mxz6r?p=info)):
@@ -105,27 +104,30 @@ typedef bool _Equals(a, b);
@Formatter(name: 'filter')
class Filter implements Function {
Parser _parser;
- _Equals _comparator;
- _Equals _stringComparator;
+ _CompareFn _comparator;
+ _CompareFn _stringComparator;
static _nop(e) => e;
+
static _ensureBool(val) => (val is bool && val);
+
static _isSubstringCaseInsensitive(String a, String b) =>
a != null && b != null && a.toLowerCase().contains(b.toLowerCase());
+
static _identical(a, b) => identical(a, b) ||
(a is String && b is String && a == b) ||
(a is num && b is num && a.isNaN && b.isNaN);
Filter(this._parser);
- void _configureComparator(var comparatorExpression) {
+ void _configureComparator(comparatorExpression) {
if (comparatorExpression == null || comparatorExpression == false) {
_stringComparator = _isSubstringCaseInsensitive;
_comparator = _defaultComparator;
} else if (comparatorExpression == true) {
_stringComparator = _identical;
_comparator = _defaultComparator;
- } else if (comparatorExpression is _Equals) {
+ } else if (comparatorExpression is _CompareFn) {
_comparator = (a, b) => _ensureBool(comparatorExpression(a, b));
} else {
_comparator = null;
@@ -135,67 +137,58 @@ class Filter implements Function {
// Preconditions
// - what: NOT a Map
// - item: neither a Map nor a List
- bool _defaultComparator(var item, var what) {
- if (what == null) {
- return false;
- } else if (item == null) {
- return what == '';
- } else if (what is String && what.startsWith('!')) {
- return !_search(item, what.substring(1));
- } else if (item is String) {
- return (what is String) && _stringComparator(item, what);
- } else if (item is bool) {
+ bool _defaultComparator(item, what) {
+ if (what == null) return false;
+ if (item == null) return what == '';
+ if (what is String && what.startsWith('!')) return !_search(item, what.substring(1));
+ if (item is String) return (what is String) && _stringComparator(item, what);
+ if (item is bool) {
if (what is bool) {
return item == what;
} else if (what is String) {
what = (what as String).toLowerCase();
return item ? (what == "true" || what == "yes" || what == "on")
: (what == "false" || what == "no" || what == "off");
- } else {
- return false;
}
- } else if (item is num) {
- if (what is num) {
- return item == what || (item.isNaN && what.isNaN);
- } else {
- return what is String && _stringComparator('$item', what);
- }
- } else {
- return false; // Unsupported item type.
+ return false;
}
+ if (item is num) {
+ return what is num ?
+ item == what || (item.isNaN && what.isNaN) :
+ what is String && _stringComparator('$item', what);
+ }
+ return false; // Unsupported item type.
}
- bool _search(var item, var what) {
+ bool _search(item, what) {
if (what is Map) {
return what.keys.every((key) => _search(
(key == r'$') ? item : _parser(key).eval(item), what[key]));
- } else if (item is Map) {
- return item.keys.any((k) => !k.startsWith(r'$') && _search(item[k], what));
- } else if (item is List) {
- return item.any((i) => _search(i, what));
- } else {
- return _comparator(item, what);
}
+ if (item is Map) return item.keys.any((k) => !k.startsWith(r'$') && _search(item[k], what));
+ if (item is List) return item.any((i) => _search(i, what));
+ return _comparator(item, what);
}
- _Predicate _toPredicate(var expression) {
- if (expression is _Predicate) {
- return (item) => _ensureBool(expression(item));
- } else if (_comparator == null) {
- return (item) => false; // Bad comparator → no items for you!
- } else {
- return (item) => _search(item, expression);
- }
+ _PredicateFn _toPredicate(var expression) {
+ if (expression is _PredicateFn) return (item) => _ensureBool(expression(item));
+ if (_comparator == null) return (_) => false; // Bad comparator → no items for you!
+ return (item) => _search(item, expression);
}
- List call(List items, var expression, [var comparator]) {
- if (expression == null) {
- return items.toList(growable: false); // Missing expression → passthrough.
- } else if (expression is! Map && expression is! Function &&
- expression is! String && expression is! bool &&
- expression is! num) {
- return const []; // Bad expression → no items for you!
+ List call(List items, expression, [comparator]) {
+ // Missing expression → passthrough.
+ if (expression == null) return items.toList(growable: false);
+
+ // Bad expression → no items for you!
+ if (expression is! Map &&
+ expression is! Function &&
+ expression is! String &&
+ expression is! bool &&
+ expression is! num) {
+ return const [];
}
+
_configureComparator(comparator);
List results = items.where(_toPredicate(expression)).toList(growable: false);
_comparator = null;
diff --git a/lib/mock/module.dart b/lib/mock/module.dart
index 29a430b01..6ba1f4c0a 100644
--- a/lib/mock/module.dart
+++ b/lib/mock/module.dart
@@ -17,6 +17,7 @@ import 'dart:async' as dart_async;
import 'dart:collection' show ListBase;
import 'dart:html';
import 'dart:js' as js;
+import 'dart:mirrors' as mirrors;
import 'package:angular/angular.dart';
import 'package:angular/core/module_internal.dart';
@@ -62,13 +63,50 @@ class AngularMockModule extends Module {
bind(Element, toValue: document.body);
bind(Node, toValue: document.body);
bind(HttpBackend, toFactory: (Injector i) => i.get(MockHttpBackend));
- bind(VmTurnZone, toFactory: (_) {
- return new VmTurnZone()
- ..onError = (e, s, LongStackTrace ls) => dump('EXCEPTION: $e\n$s\n$ls');
- });
+ bind(VmTurnZone, toFactory: (_) =>
+ new VmTurnZone()..onError = (e, s, LongStackTrace ls) => dump('EXCEPTION: $e\n$s\n$ls'));
bind(Window, toImplementation: MockWindow);
var mockPlatform = new MockWebPlatform();
bind(MockWebPlatform, toValue: mockPlatform);
bind(WebPlatform, toValue: mockPlatform);
+ bind(Object, toImplementation: TestContext);
}
}
+
+/**
+ * [DynamicObject] helps testing angular.dart.
+ *
+ * Setting the test context to an instance of [DynamicObject] avoid having to write a specific class
+ * for every new test by allowing the dynamic addition of properties through the use of
+ * [Object.noSuchMethod]
+ *
+ */
+@proxy
+class DynamicObject {
+ Map _locals = {};
+
+ void addProperties(Map locals) {
+ assert(locals != null);
+ _locals.addAll(locals);
+ }
+
+ noSuchMethod(Invocation invocation) {
+ var pArgs = invocation.positionalArguments;
+ var field = mirrors.MirrorSystem.getName(invocation.memberName);
+ if (invocation.isGetter) {
+ return _locals[field];
+ }
+ if (invocation.isSetter) {
+ field = field.substring(0, field.length - 1);
+ return _locals[field] = pArgs[0];
+ }
+ if (invocation.isMethod) {
+ return Function.apply(_locals[field], pArgs, invocation.namedArguments);
+ }
+ throw new UnimplementedError(field);
+ }
+}
+
+class TestContext extends DynamicObject {
+ final $probes = {};
+}
diff --git a/lib/mock/probe.dart b/lib/mock/probe.dart
index 09bc60a81..fa7b8a3a5 100644
--- a/lib/mock/probe.dart
+++ b/lib/mock/probe.dart
@@ -1,13 +1,12 @@
part of angular.mock;
/*
- * Use Probe directive to capture the Scope, Injector and Element from any DOM
- * location into root-scope. This is useful for testing to get a hold of
- * any directive.
+ * Use Probe directive to capture the Scope, Injector and Element from any DOM location into
+ * root-scope. This is useful for testing to get a hold of any directive.
*
*
');
- microLeap();
-
- _.rootScope.apply();
- expect(log.result()).toEqual('IncludeTransclude; SimpleTransclude');
- }));
-
- it('should expose a parent controller to the scope of its children', (TestBed _) {
- var element = _.compile('
'
- '
{{ my_parent.data() }}
'
- '
');
-
- _.rootScope.apply();
-
- expect(element.text).toContain('my data');
- });
-
- it('should expose a ancestor controller to the scope of its children thru a undecorated element', (TestBed _) {
- var element = _.compile(
- '
''');
_.triggerEvent(e.querySelector('[on-abc]'), 'abc');
- expect(_.getScope(e).context['ctrl'].invoked).toEqual(true);
+ expect(_.rootScope.context.invoked).toEqual(true);
}));
it('shoud register and handle event with long name', inject((TestBed _) {
var e = compile(_,
- '''
-
+ '''
+
''');
_.triggerEvent(e.querySelector('[on-my-new-event]'), 'myNewEvent');
- var fooScope = _.getScope(e);
- expect(fooScope.context['ctrl'].invoked).toEqual(true);
+ expect(_.rootScope.context.invoked).toEqual(true);
}));
it('shoud have model updates applied correctly', inject((TestBed _) {
var e = compile(_,
- '''
-
{{ctrl.description}}
+ '''
+
{{description}}
''');
var el = document.querySelector('[on-abc]');
el.dispatchEvent(new Event('abc'));
@@ -85,15 +76,15 @@ main() {
var shadowRoot = e.shadowRoot;
var span = shadowRoot.querySelector('span');
span.dispatchEvent(new CustomEvent('abc'));
- var ctrl = _.rootScope.context['ctrl'];
+ var ctrl = _.rootScope.context.ctrl;
expect(ctrl.invoked).toEqual(true);
}));
it('shoud handle event within content only once', async(inject((TestBed _) {
var e = compile(_,
- '''
+ '''
-
+
''');
@@ -102,10 +93,9 @@ main() {
document.querySelector('[on-abc]').dispatchEvent(new Event('abc'));
var shadowRoot = document.querySelector('bar').shadowRoot;
var shadowRootScope = _.getScope(shadowRoot);
- expect(shadowRootScope.context['ctrl'].invoked).toEqual(false);
+ expect(shadowRootScope.context.invoked).toEqual(false);
- var fooScope = _.getScope(document.querySelector('[foo]'));
- expect(fooScope.context['ctrl'].invoked).toEqual(true);
+ expect(_.rootScope.context.invoked).toEqual(true);
})));
});
}
diff --git a/test/core_dom/mustache_spec.dart b/test/core_dom/mustache_spec.dart
index ba47f09b6..f49030f46 100644
--- a/test/core_dom/mustache_spec.dart
+++ b/test/core_dom/mustache_spec.dart
@@ -17,7 +17,7 @@ main() {
var element = es('