diff --git a/lib/application_factory.dart b/lib/application_factory.dart index 13768b6cb..8ff1bc881 100644 --- a/lib/application_factory.dart +++ b/lib/application_factory.dart @@ -48,6 +48,7 @@ import 'dart:html'; ], metaTargets: const [ NgInjectableService, + NgTemplate, NgDirective, NgController, NgComponent, diff --git a/lib/core/annotation.dart b/lib/core/annotation.dart index a869ec7c5..6e7a7d8a2 100644 --- a/lib/core/annotation.dart +++ b/lib/core/annotation.dart @@ -14,6 +14,8 @@ export "package:angular/core/annotation_src.dart" show NgInjectableService, AbstractNgAnnotation, + AbstractNgAttrAnnotation, + NgTemplate, NgComponent, NgController, NgDirective, @@ -35,4 +37,3 @@ export "package:angular/core/annotation_src.dart" show abstract class NgShadowRootAware { void onShadowRoot(ShadowRoot shadowRoot); } - diff --git a/lib/core/annotation_src.dart b/lib/core/annotation_src.dart index c1f51d9cc..aba52034b 100644 --- a/lib/core/annotation_src.dart +++ b/lib/core/annotation_src.dart @@ -6,21 +6,27 @@ RegExp _ATTR_NAME = new RegExp(r'\[([^\]]+)\]$'); const String SHADOW_DOM_INJECTOR_NAME = 'SHADOW_INJECTOR'; -skipShadow(Injector injector) - => injector.name == SHADOW_DOM_INJECTOR_NAME ? injector.parent : injector; +skipShadow(Injector injector) => + injector.name == SHADOW_DOM_INJECTOR_NAME ? injector.parent : injector; -localVisibility (Injector requesting, Injector defining) - => identical(skipShadow(requesting), defining); +localVisibility (Injector requesting, Injector defining) => + identical(skipShadow(requesting), defining); directChildrenVisibility(Injector requesting, Injector defining) { requesting = skipShadow(requesting); - return identical(requesting.parent, defining) || localVisibility(requesting, defining); + return identical(requesting.parent, defining) || + localVisibility(requesting, defining); } -AbstractNgAnnotation cloneWithNewMap(AbstractNgAnnotation annotation, map) - => annotation._cloneWithNewMap(map); +AbstractNgAttrAnnotation cloneWithNewMap(AbstractNgAttrAnnotation annotation, + Map map) => + annotation._cloneWithNewMap(map); -String mappingSpec(AbstractNgFieldAnnotation annotation) => annotation._mappingSpec; +NgTemplate cloneWithNewMapping(NgTemplate template, String mapping) => + template._cloneWithNewMapping(mapping); + +String mappingSpec(AbstractNgFieldAnnotation annotation) => + annotation._mappingSpec; /** @@ -34,7 +40,7 @@ class NgInjectableService { } /** - * Abstract supper class of [NgController], [NgComponent], and [NgDirective]. + * Abstract supper class of [NgTemplate] & [AbstractNgAttrAnnotation] */ abstract class AbstractNgAnnotation { /** @@ -56,30 +62,11 @@ abstract class AbstractNgAnnotation { * Specifies the compiler action to be taken on the child nodes of the * element which this currently being compiled. The values are: * - * * [COMPILE_CHILDREN] (*default*) - * * [TRANSCLUDE_CHILDREN] - * * [IGNORE_CHILDREN] - */ - @deprecated - final String children; - - /** - * Compile the child nodes of the element. This is the default. - */ - @deprecated - static const String COMPILE_CHILDREN = 'compile'; - /** - * Compile the child nodes for transclusion and makes available - * [BoundViewFactory], [ViewFactory] and [ViewPort] for injection. - */ - @deprecated - static const String TRANSCLUDE_CHILDREN = 'transclude'; - /** - * Do not compile/visit the child nodes. Angular markup on descendant nodes - * will not be processed. + * * [:true:] Compile the child nodes of the element. (default) + * * [:false:] Do not compile/visit the child nodes. Angular markup on + * descendant nodes will not be processed. */ - @deprecated - static const String IGNORE_CHILDREN = 'ignore'; + final bool compileChildren; /** * A directive/component controller class can be injected into other @@ -122,15 +109,93 @@ abstract class AbstractNgAnnotation { final Function module; /** - * Use map to define the mapping of DOM attributes to fields. + * Use the list to specify expressions containing attributes which are not + * included under [map] with '=' or '@' specification. This is used by + * angular transformer during deployment. + */ + final List exportExpressionAttrs; + + /** + * Use the list to specify expressions which are evaluated dynamically + * (ex. via [Scope.eval]) and are otherwise not statically discoverable. + * This is used by angular transformer during deployment. + */ + final List exportExpressions; + + const AbstractNgAnnotation({ + this.selector, + this.compileChildren: true, + this.visibility: NgDirective.LOCAL_VISIBILITY, + this.module, + this.exportExpressions: const [], + this.exportExpressionAttrs: const [] + }); + + String toString() => selector; + int get hashCode => selector.hashCode; + operator==(AbstractNgAnnotation other) => + other is AbstractNgAnnotation && selector == other.selector; +} + +/** + * Meta-data marker placed on a class which should act as template. + * + * Angular templates are instantiated using dependency injection, and can + * ask for any injectable object in their constructor. Templates can also ask + * for other components or directives declared on the DOM element. + * + * Templates can implement [NgAttachAware], [NgDetachAware] and declare these + * optional methods: + * + * * `attach()` - Called on first [Scope.apply()]. + * * `detach()` - Called on when owning scope is destroyed. + */ +class NgTemplate extends AbstractNgAnnotation { + /** + * Use the [mapping] to define the mapping of the expression to a field. + * The value consists of a mode prefix followed by an expression. + * The destination expression will be evaluated against the instance of the + * template class. + * + * see [AbstractNgAttrAnnotation.map] for more details. + */ + final String mapping; + + const NgTemplate({this.mapping, + String selector, + Function module, + Visibility visibility, + List exportExpressions, + List exportExpressionAttrs}) + : super(selector: selector, + visibility: visibility, + module: module, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); + + NgTemplate _cloneWithNewMapping(String newMapping) => + new NgTemplate(mapping: newMapping, + module: module, + selector: selector, + visibility: visibility, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); +} + +/** + * Abstract supper class of [NgController], [NgComponent], and [NgDirective]. + */ +abstract class AbstractNgAttrAnnotation extends AbstractNgAnnotation { + /** + * Use the [map] to define the mapping of DOM attributes to fields. * The map's key is the DOM attribute name (DOM attribute is in dash-case). * The Map's value consists of a mode prefix followed by an expression. * The destination expression will be evaluated against the instance of the * directive / component class. * * * `@` - Map the DOM attribute string. The attribute string will be taken - * literally or interpolated if it contains binding {{}} systax and assigned - * to the expression. (cost: 0 watches) + * literally or interpolated if it contains binding `{{}}` syntax and + * assigned to the expression. (cost: 0 watches) * * * `=>` - Treat the DOM attribute value as an expression. Set up a watch, * which will read the expression in the attribute and assign the value @@ -186,39 +251,24 @@ abstract class AbstractNgAnnotation { */ final Map map; - /** - * Use the list to specify expressions containing attributes which are not - * included under [map] with '=' or '@' specification. This is used by - * angular transformer during deployment. - */ - final List exportExpressionAttrs; - - /** - * Use the list to specify expressions which are evaluated dynamically - * (ex. via [Scope.eval]) and are otherwise not statically discoverable. - * This is used by angular transformer during deployment. - */ - final List exportExpressions; - - const AbstractNgAnnotation({ - this.selector, - this.children: AbstractNgAnnotation.COMPILE_CHILDREN, - this.visibility: NgDirective.LOCAL_VISIBILITY, - this.module, - this.map: const {}, - this.exportExpressions: const [], - this.exportExpressionAttrs: const [] - }); - - toString() => selector; - get hashCode => selector.hashCode; - operator==(other) => - other is AbstractNgAnnotation && selector == other.selector; + const AbstractNgAttrAnnotation({ + String selector, + bool compileChildren: true, + Visibility visibility: NgDirective.LOCAL_VISIBILITY, + Function module, + this.map: const {}, + List exportExpressions: const [], + List exportExpressionAttrs: const []}) + : super(selector: selector, + compileChildren: compileChildren, + visibility: visibility, + module: module, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); - AbstractNgAnnotation _cloneWithNewMap(newMap); + AbstractNgAnnotation _cloneWithNewMap(Map newMap); } - bool _applyAuthorStylesDeprecationWarningPrinted = false; bool _resetStyleInheritanceDeprecationWarningPrinted = false; @@ -238,7 +288,7 @@ bool _resetStyleInheritanceDeprecationWarningPrinted = false; * * `detach()` - Called on when owning scope is destroyed. * * `onShadowRoot(ShadowRoot shadowRoot)` - Called when [ShadowRoot] is loaded. */ -class NgComponent extends AbstractNgAnnotation { +class NgComponent extends AbstractNgAttrAnnotation { /** * Inlined HTML template for the component. */ @@ -295,48 +345,46 @@ class NgComponent extends AbstractNgAnnotation { @deprecated final String publishAs; - const NgComponent({ - this.template, - this.templateUrl, - cssUrl, - applyAuthorStyles, - resetStyleInheritance, - this.publishAs, - module, - map, - selector, - visibility, - exportExpressions, - exportExpressionAttrs}) + const NgComponent({this.template, + this.templateUrl, + cssUrl, + applyAuthorStyles, + resetStyleInheritance, + this.publishAs, + module, + map, + selector, + visibility, + exportExpressions, + exportExpressionAttrs}) : _cssUrls = cssUrl, _applyAuthorStyles = applyAuthorStyles, _resetStyleInheritance = resetStyleInheritance, super(selector: selector, - children: AbstractNgAnnotation.COMPILE_CHILDREN, - visibility: visibility, - map: map, - module: module, - exportExpressions: exportExpressions, - exportExpressionAttrs: exportExpressionAttrs); + compileChildren: true, + visibility: visibility, + map: map, + module: module, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); List get cssUrls => _cssUrls == null ? const [] : _cssUrls is List ? _cssUrls : [_cssUrls]; - AbstractNgAnnotation _cloneWithNewMap(newMap) => - new NgComponent( - template: template, - templateUrl: templateUrl, - cssUrl: cssUrls, - applyAuthorStyles: applyAuthorStyles, - resetStyleInheritance: resetStyleInheritance, - publishAs: publishAs, - map: newMap, - module: module, - selector: selector, - visibility: visibility, - exportExpressions: exportExpressions, - exportExpressionAttrs: exportExpressionAttrs); + NgComponent _cloneWithNewMap(Map newMap) => + new NgComponent(template: template, + templateUrl: templateUrl, + cssUrl: cssUrls, + applyAuthorStyles: applyAuthorStyles, + resetStyleInheritance: resetStyleInheritance, + publishAs: publishAs, + map: newMap, + module: module, + selector: selector, + visibility: visibility, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); } /** @@ -352,7 +400,7 @@ class NgComponent extends AbstractNgAnnotation { * * `attach()` - Called on first [Scope.apply()]. * * `detach()` - Called on when owning scope is destroyed. */ -class NgDirective extends AbstractNgAnnotation { +class NgDirective extends AbstractNgAttrAnnotation { /// The directive can only be injected to other directives on the same element. static const Visibility LOCAL_VISIBILITY = localVisibility; @@ -366,30 +414,29 @@ class NgDirective extends AbstractNgAnnotation { */ static const Visibility DIRECT_CHILDREN_VISIBILITY = directChildrenVisibility; - const NgDirective({children: AbstractNgAnnotation.COMPILE_CHILDREN, - map, - selector, - module, - visibility, - exportExpressions, - exportExpressionAttrs}) + const NgDirective({compileChildren: true, + map, + selector, + module, + visibility, + exportExpressions, + exportExpressionAttrs}) : super(selector: selector, - children: children, + compileChildren: compileChildren, visibility: visibility, map: map, module: module, exportExpressions: exportExpressions, exportExpressionAttrs: exportExpressionAttrs); - AbstractNgAnnotation _cloneWithNewMap(newMap) => - new NgDirective( - children: children, - map: newMap, - module: module, - selector: selector, - visibility: visibility, - exportExpressions: exportExpressions, - exportExpressionAttrs: exportExpressionAttrs); + NgDirective _cloneWithNewMap(newMap) => + new NgDirective(compileChildren: compileChildren, + map: newMap, + module: module, + selector: selector, + visibility: visibility, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); } /** @@ -409,7 +456,7 @@ class NgDirective extends AbstractNgAnnotation { * * `attach()` - Called on first [Scope.apply()]. * * `detach()` - Called on when owning scope is destroyed. */ -class NgController extends NgDirective { +class NgController extends AbstractNgAttrAnnotation { /** * An expression under which the controller instance will be published into. * This allows the expressions in the template to be referring to controller @@ -417,34 +464,31 @@ class NgController extends NgDirective { */ final String publishAs; - const NgController({ - children: AbstractNgAnnotation.COMPILE_CHILDREN, - this.publishAs, - map, - module, - selector, - visibility, - exportExpressions, - exportExpressionAttrs - }) + const NgController({compileChildren: true, + this.publishAs, + map, + module, + selector, + visibility, + exportExpressions, + exportExpressionAttrs}) : super(selector: selector, - children: children, + compileChildren: compileChildren, visibility: visibility, map: map, module: module, exportExpressions: exportExpressions, exportExpressionAttrs: exportExpressionAttrs); - AbstractNgAnnotation _cloneWithNewMap(newMap) => - new NgController( - children: children, - publishAs: publishAs, - module: module, - map: newMap, - selector: selector, - visibility: visibility, - exportExpressions: exportExpressions, - exportExpressionAttrs: exportExpressionAttrs); + NgController _cloneWithNewMap(newMap) => + new NgController(compileChildren: compileChildren, + publishAs: publishAs, + module: module, + map: newMap, + selector: selector, + visibility: visibility, + exportExpressions: exportExpressions, + exportExpressionAttrs: exportExpressionAttrs); } /** diff --git a/lib/core/filter.dart b/lib/core/filter.dart index 318e437c5..f3967489d 100644 --- a/lib/core/filter.dart +++ b/lib/core/filter.dart @@ -1,17 +1,17 @@ part of angular.core_internal; - /** * Registry of filters at runtime. */ @NgInjectableService() class FilterMap extends AnnotationMap { - Injector _injector; + final Injector _injector; + FilterMap(Injector injector, MetadataExtractor extractMetadata) : this._injector = injector, super(injector, extractMetadata); - call(String name) { + Function call(String name) { var filter = new NgFilter(name: name); var filterType = this[filter]; return _injector.get(filterType); diff --git a/lib/core/registry.dart b/lib/core/registry.dart index 9d66de3ed..e0582a3e9 100644 --- a/lib/core/registry.dart +++ b/lib/core/registry.dart @@ -2,6 +2,13 @@ library angular.core.registry; import 'package:di/di.dart' show Injector; +/** + * The [AnnotationMap] maps annotations to [Type]s. + * + * The [AnnotationMap] contains all annotated [Type]s provided by the [Injector] + * given in the constructor argument. Every single annotation maps to only one + * [Type]. + */ abstract class AnnotationMap { final Map _map = {}; @@ -15,16 +22,19 @@ abstract class AnnotationMap { }); } + /// Returns the [Type] annotated with [annotation]. Type operator[](K annotation) { var value = _map[annotation]; if (value == null) throw 'No $annotation found!'; return value; } - void forEach(fn(K, Type)) { - _map.forEach(fn); + /// Executes the [function] for all registered annotations. + void forEach(function(K, Type)) { + _map.forEach(function); } + /// Returns a list of all the annotations applied to the [Type]. List annotationsFor(Type type) { final res = []; forEach((ann, annType) { @@ -34,37 +44,47 @@ abstract class AnnotationMap { } } +/** + * The [AnnotationsMap] maps annotations to [Type]s. + * + * The [AnnotationsMap] contains all annotated [Type]s provided by the [Injector] + * given in the constructor argument. Every single annotation can maps to only + * multiple [Type]s. + */ abstract class AnnotationsMap { - final Map> map = {}; + final map = >{}; AnnotationsMap(Injector injector, MetadataExtractor extractMetadata) { injector.types.forEach((type) { extractMetadata(type) .where((annotation) => annotation is K) .forEach((annotation) { - map.putIfAbsent(annotation, () => []).add(type); + map.putIfAbsent(annotation, () => []).add(type); }); }); } - List operator[](K annotation) { + /// Returns a list of [Type]s annotated with [annotation]. + List operator[](K annotation) { var value = map[annotation]; if (value == null) throw 'No $annotation found!'; return value; } - void forEach(fn(K, Type)) { - map.forEach((annotation, types) { - types.forEach((type) { - fn(annotation, type); + /// Executes the [function] for all registered (annotation, type) pairs. + void forEach(function(K, Type)) { + map.forEach((K annotation, List types) { + types.forEach((Type type) { + function(annotation, type); }); }); } + /// Returns a list of all the annotations applied to the [Type]. List annotationsFor(Type type) { var res = []; - forEach((ann, annType) { - if (annType == type) res.add(ann); + map.forEach((K ann, List types) { + if (types.contains(type)) res.add(ann); }); return res; } diff --git a/lib/core/registry_dynamic.dart b/lib/core/registry_dynamic.dart index 4af594079..d41da084c 100644 --- a/lib/core/registry_dynamic.dart +++ b/lib/core/registry_dynamic.dart @@ -4,83 +4,116 @@ import 'dart:mirrors'; import 'package:angular/core/annotation_src.dart'; import 'package:angular/core/registry.dart'; -export 'package:angular/core/registry.dart' show - MetadataExtractor; - -var _fieldMetadataCache = new Map>(); +export 'package:angular/core/registry.dart' show MetadataExtractor; class DynamicMetadataExtractor implements MetadataExtractor { - final _fieldAnnotations = [ - reflectType(NgAttr), - reflectType(NgOneWay), - reflectType(NgOneWayOneTime), - reflectType(NgTwoWay), - reflectType(NgCallback) + final _fieldAnnotations = [ + reflectType(NgAttr), + reflectType(NgOneWay), + reflectType(NgOneWayOneTime), + reflectType(NgTwoWay), + reflectType(NgCallback) ]; + static final _fieldMetadataCache = + >{}; + Iterable call(Type type) { if (reflectType(type) is TypedefMirror) return []; var metadata = reflectClass(type).metadata; - if (metadata == null) { - metadata = []; - } else { - metadata = metadata.map((InstanceMirror im) => map(type, im.reflectee)); - } - return metadata; + return metadata == null ? + const [] : + metadata.map((InstanceMirror im) => _merge(type, im.reflectee)); } - map(Type type, obj) { - if (obj is AbstractNgAnnotation) { - return mapDirectiveAnnotation(type, obj); - } else { - return obj; + /// Merges the attribute annotations with the directive definition. + dynamic _merge(Type type, annotation) { + if (annotation is NgTemplate) { + return _mergeTemplateMapping(type, annotation); + } else if (annotation is AbstractNgAnnotation) { + return _mergeDirectiveMap(type, annotation); } + return annotation; } - AbstractNgAnnotation mapDirectiveAnnotation(Type type, AbstractNgAnnotation annotation) { + /** + * Merges the field annotations with the [AbstractNgAttrAnnotation.map] + * definition from the directive. + * + * [_mergeDirectiveMap] throws when the definition for an annotated is + * duplicated in the [AbstractNgAttrAnnotation.map] + */ + AbstractNgAttrAnnotation _mergeDirectiveMap(Type type, + AbstractNgAttrAnnotation annotation) { var match; - var fieldMetadata = fieldMetadataExtractor(type); - if (fieldMetadata.isNotEmpty) { + var metadata = _fieldMetadataExtractor(type); + if (metadata.isNotEmpty) { var newMap = annotation.map == null ? {} : new Map.from(annotation.map); - fieldMetadata.forEach((String fieldName, AbstractNgFieldAnnotation ann) { + metadata.forEach((String fieldName, AbstractNgFieldAnnotation ann) { var attrName = ann.attrName; if (newMap.containsKey(attrName)) { throw 'Mapping for attribute $attrName is already defined (while ' - 'processing annottation for field $fieldName of $type)'; + 'processing annotation for field $fieldName of $type)'; } - newMap[attrName] = '${mappingSpec(ann)}$fieldName'; + newMap[attrName] = mappingSpec(ann) + fieldName; }); annotation = cloneWithNewMap(annotation, newMap); } return annotation; } + /** + * Merges the mapping from both the [NgTemplate] annotation and the one + * defined at the attribute level. + * + * The mapping could only be specified once at one of the 2 places. When + * defined at both the places [_mergeTemplateMapping] will throw. + */ + NgTemplate _mergeTemplateMapping(Type type, NgTemplate annotation) { + var match; + var metadata = _fieldMetadataExtractor(type); + if (metadata.length > 1) { + throw 'There could be only one attribute annotation for @NgTemplate ' + 'annotated classes, ${metadata.length} found on ' + '${metadata.keys.join(", ")}'; + } + if (metadata.length == 1) { + if (annotation.mapping != null) { + throw 'The mapping must be defined either in the @NgTemplate annotation' + ' or on an attribute'; + } + var mapping = mappingSpec(metadata.values[0]) + metadata.keys[0]; + annotation = cloneWithNewMapping(annotation, mapping); + } + return annotation; + } - Map fieldMetadataExtractor(Type type) => - _fieldMetadataCache.putIfAbsent(type, () => _fieldMetadataExtractor(type)); - + /// Extract metadata defined on fields via an [AbstractNgFieldAnnotation] Map _fieldMetadataExtractor(Type type) { - ClassMirror cm = reflectType(type); - final fields = {}; - cm.declarations.forEach((Symbol name, DeclarationMirror decl) { - if (decl is VariableMirror || - decl is MethodMirror && (decl.isGetter || decl.isSetter)) { - var fieldName = MirrorSystem.getName(name); - if (decl is MethodMirror && decl.isSetter) { - // Remove "=" from the end of the setter. - fieldName = fieldName.substring(0, fieldName.length - 1); - } - decl.metadata.forEach((InstanceMirror meta) { - if (_fieldAnnotations.contains(meta.type)) { - if (fields.containsKey(fieldName)) { - throw 'Attribute annotation for $fieldName is defined more ' - 'than once in $type'; - } - fields[fieldName] = meta.reflectee as AbstractNgFieldAnnotation; + if (!_fieldMetadataCache.containsKey(type)) { + ClassMirror cm = reflectType(type); + final fields = {}; + cm.declarations.forEach((Symbol name, DeclarationMirror decl) { + if (decl is VariableMirror || + decl is MethodMirror && (decl.isGetter || decl.isSetter)) { + var fieldName = MirrorSystem.getName(name); + if (decl is MethodMirror && decl.isSetter) { + // Remove "=" from the end of the setter. + fieldName = fieldName.substring(0, fieldName.length - 1); } - }); - } - }); - return fields; + decl.metadata.forEach((InstanceMirror meta) { + if (_fieldAnnotations.contains(meta.type)) { + if (fields.containsKey(fieldName)) { + throw 'Attribute annotation for $fieldName is defined more ' + 'than once in $type'; + } + fields[fieldName] = meta.reflectee as AbstractNgFieldAnnotation; + } + }); + } + }); + _fieldMetadataCache[type] = fields; + } + return _fieldMetadataCache[type]; } } diff --git a/lib/core/registry_static.dart b/lib/core/registry_static.dart index 5fee6990e..e019608da 100644 --- a/lib/core/registry_static.dart +++ b/lib/core/registry_static.dart @@ -6,7 +6,7 @@ import 'package:angular/core/registry.dart'; @NgInjectableService() class StaticMetadataExtractor extends MetadataExtractor { final Map metadataMap; - final List empty = const []; + final empty = const []; StaticMetadataExtractor(this.metadataMap); diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 5736adcd7..966ebd314 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -219,7 +219,7 @@ class Scope { } else if (expression.startsWith(':')) { expression = expression.substring(1); fn = (value, last) { - if (value != null) reactionFn(value, last); + if (value != null) reactionFn(value, last); }; } } diff --git a/lib/core_dom/common.dart b/lib/core_dom/common.dart index 98e5905a8..d85a2c670 100644 --- a/lib/core_dom/common.dart +++ b/lib/core_dom/common.dart @@ -1,8 +1,6 @@ part of angular.core.dom_internal; -List cloneElements(elements) { - return elements.map((el) => el.clone(true)).toList(); -} +List cloneElements(els) => els.map((el) => el.clone(true)).toList(); class MappingParts { final String attrName; @@ -10,7 +8,8 @@ class MappingParts { final String dstExpression; final String originalValue; - const MappingParts(this.attrName, this.mode, this.dstExpression, this.originalValue); + const MappingParts(this.attrName, this.mode, this.dstExpression, + this.originalValue); } class DirectiveRef { @@ -18,7 +17,7 @@ class DirectiveRef { final Type type; final AbstractNgAnnotation annotation; final String value; - final mappings = new List(); + final mappings = []; DirectiveRef(this.element, this.type, this.annotation, [ this.value ]); @@ -37,8 +36,8 @@ class DirectiveRef { */ Injector forceNewDirectivesAndFilters(Injector injector, List modules) { modules.add(new Module() - ..factory(Scope, (i) { - var scope = i.parent.get(Scope); + ..factory(Scope, (inj) { + var scope = inj.parent.get(Scope); return scope.createChild(new PrototypeMap(scope.context)); })); diff --git a/lib/core_dom/directive_map.dart b/lib/core_dom/directive_map.dart index c093d9df9..e00a59fae 100644 --- a/lib/core_dom/directive_map.dart +++ b/lib/core_dom/directive_map.dart @@ -2,11 +2,13 @@ part of angular.core.dom_internal; @NgInjectableService() class DirectiveMap extends AnnotationsMap { - DirectiveSelectorFactory _directiveSelectorFactory; + final DirectiveSelectorFactory _directiveSelectorFactory; DirectiveSelector _selector; DirectiveSelector get selector { - if (_selector != null) return _selector; - return _selector = _directiveSelectorFactory.selector(this); + if (_selector == null) { + _selector = _directiveSelectorFactory.selector(this); + } + return _selector; } DirectiveMap(Injector injector, diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index b246231b6..5ab6e7e12 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -22,12 +22,11 @@ class TemplateElementBinder extends ElementBinder { _registerViewFactory(node, parentInjector, nodeModule) { assert(templateViewFactory != null); - nodeModule - ..factory(ViewPort, (_) => - new ViewPort(node, parentInjector.get(NgAnimate))) - ..value(ViewFactory, templateViewFactory) - ..factory(BoundViewFactory, (Injector injector) => - templateViewFactory.bind(injector)); + nodeModule..factory(ViewPort, (_) => + new ViewPort(node, parentInjector.get(NgAnimate))) + ..value(ViewFactory, templateViewFactory) + ..factory(BoundViewFactory, (Injector injector) => + templateViewFactory.bind(injector)); } } @@ -49,46 +48,55 @@ class ElementBinder { final DirectiveRef component; - // Can be either COMPILE_CHILDREN or IGNORE_CHILDREN - final String childMode; + final bool compileChildren; - ElementBinder(this._perf, this._expando, this._parser, this.component, this.decorators, - this.onEvents, this.bindAttrs, this.childMode); + ElementBinder(this._perf, this._expando, this._parser, this.component, + this.decorators, this.onEvents, this.bindAttrs, + this.compileChildren); final bool hasTemplate = false; - bool get shouldCompileChildren => - childMode == AbstractNgAnnotation.COMPILE_CHILDREN; - var _directiveCache; + List get _usableDirectiveRefs { - if (_directiveCache != null) return _directiveCache; - if (component != null) return _directiveCache = new List.from(decorators)..add(component); - return _directiveCache = decorators; + if (_directiveCache == null) { + _directiveCache = component != null ? + (new List.from(decorators)..add(component)): + decorators; + } + return _directiveCache; } bool get hasDirectivesOrEvents => _usableDirectiveRefs.isNotEmpty || onEvents.isNotEmpty; - _createAttrMappings(controller, scope, DirectiveRef ref, nodeAttrs, filters, tasks) { + void _createAttrMappings(controller, scope, DirectiveRef ref, nodeAttrs, filters, tasks) { + var expression = ref.value; + ref.mappings.forEach((MappingParts p) { var attrName = p.attrName; + var isTemplate = nodeAttrs == null; var dstExpression = p.dstExpression; - if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref); Expression dstPathFn = _parser(dstExpression); if (!dstPathFn.isAssignable) { - throw "Expression '$dstExpression' is not assignable in mapping '${p.originalValue}' " - "for attribute '$attrName'."; + var msg = "Expression '$dstExpression' is not assignable in mapping " + "'${p.originalValue}'"; + throw attrName == null ? msg : msg + " for attribute '$attrName'."; } switch (p.mode) { case '@': // string var taskId = tasks.registerTask(); - nodeAttrs.observe(attrName, (value) { - dstPathFn.assign(controller, value); + if (isTemplate) { + dstPathFn.assign(controller, expression); tasks.completeTask(taskId); - }); + } else { + nodeAttrs.observe(attrName, (value) { + dstPathFn.assign(controller, value); + tasks.completeTask(taskId); + }); + } break; case '<=>': // two-way @@ -121,37 +129,41 @@ class ElementBinder { break; case '=>': // one-way - if (nodeAttrs[attrName] == null) return; + if (!isTemplate) expression = nodeAttrs[attrName]; + if (expression == null) return; var taskId = tasks.registerTask(); - Expression attrExprFn = _parser(nodeAttrs[attrName]); - scope.watch(nodeAttrs[attrName], (v, _) { + scope.watch(expression, (v, _) { dstPathFn.assign(controller, v); tasks.completeTask(taskId); }, filters: filters); break; case '=>!': // one-way, one-time - if (nodeAttrs[attrName] == null) return; + if (!isTemplate) expression = nodeAttrs[attrName]; + if (expression == null) return; - Expression attrExprFn = _parser(nodeAttrs[attrName]); var watch; - watch = scope.watch(nodeAttrs[attrName], (value, _) { - if (dstPathFn.assign(controller, value) != null) { + watch = scope.watch(expression, (v, _) { + if (dstPathFn.assign(controller, v) != null) { watch.remove(); } }, filters: filters); break; case '&': // callback + if (!isTemplate) expression = nodeAttrs[attrName]; dstPathFn.assign(controller, - _parser(nodeAttrs[attrName]).bind(scope.context, ScopeLocals.wrapper)); + _parser(expression).bind(scope.context, ScopeLocals.wrapper)); break; + + default: + throw "Unsupported mode '${p.mode}'"; } }); } - _link(nodeInjector, probe, scope, nodeAttrs, filters) { + void _link(nodeInjector, probe, scope, nodeAttrs, filters) { _usableDirectiveRefs.forEach((DirectiveRef ref) { var linkTimer; try { @@ -165,20 +177,20 @@ class ElementBinder { scope.context[(ref.annotation as NgController).publishAs] = controller; } - var tasks = new _TaskList(controller is NgAttachAware ? () { - if (scope.isAttached) controller.attach(); - } : null); + var tasks = new _TaskList(controller is NgAttachAware ? + () { if (scope.isAttached) controller.attach(); } : + null); _createAttrMappings(controller, scope, ref, nodeAttrs, filters, tasks); if (controller is NgAttachAware) { var taskId = tasks.registerTask(); Watch watch; - watch = scope.watch('1', // Cheat a bit. + watch = scope.watch( + '::1', // Cheat a bit. (_, __) { - watch.remove(); - tasks.completeTask(taskId); - }); + tasks.completeTask(taskId); + }); } tasks.doneRegistering(); @@ -194,7 +206,7 @@ class ElementBinder { }); } - _createDirectiveFactories(DirectiveRef ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs, + void _createDirectiveFactories(DirectiveRef ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs, visibility) { if (ref.type == NgTextMustacheDirective) { nodeModule.factory(NgTextMustacheDirective, (Injector injector) { @@ -240,7 +252,7 @@ class ElementBinder { } // Overridden in TemplateElementBinder - _registerViewFactory(node, parentInjector, nodeModule) { + void _registerViewFactory(node, parentInjector, nodeModule) { nodeModule..factory(ViewPort, null) ..factory(ViewFactory, null) ..factory(BoundViewFactory, null); @@ -307,12 +319,12 @@ class ElementBinder { * Private class used for managing controller.attach() calls */ class _TaskList { - var onDone; + final Function _onDone; final List _tasks = []; bool isDone = false; - _TaskList(this.onDone) { - if (onDone == null) isDone = true; + _TaskList(this._onDone) { + if (_onDone == null) isDone = true; } int registerTask() { @@ -325,12 +337,12 @@ class _TaskList { if (isDone) return; _tasks[id] = true; if (_tasks.every((a) => a)) { - onDone(); + _onDone(); isDone = true; } } - doneRegistering() { + void doneRegistering() { completeTask(registerTask()); } } @@ -355,7 +367,7 @@ class TaggedTextBinder { final int offsetIndex; TaggedTextBinder(this.binder, this.offsetIndex); - toString() => "[TaggedTextBinder binder:$binder offset:$offsetIndex]"; + String toString() => "[TaggedTextBinder binder:$binder offset:$offsetIndex]"; } // Used for the tagging compiler diff --git a/lib/core_dom/element_binder_builder.dart b/lib/core_dom/element_binder_builder.dart index c74f7c1a7..bf88ff522 100644 --- a/lib/core_dom/element_binder_builder.dart +++ b/lib/core_dom/element_binder_builder.dart @@ -13,10 +13,10 @@ class ElementBinderFactory { ElementBinder binder(ElementBinderBuilder b) => new ElementBinder(_perf, _expando, _parser, - b.component, b.decorators, b.onEvents, b.bindAttrs, b.childMode); + b.component, b.decorators, b.onEvents, b.bindAttrs, b.compileChildren); TemplateElementBinder templateBinder(ElementBinderBuilder b, ElementBinder transclude) => new TemplateElementBinder(_perf, _expando, _parser, - b.template, transclude, b.onEvents, b.bindAttrs, b.childMode); + b.template, transclude, b.onEvents, b.bindAttrs, b.compileChildren); } /** @@ -24,7 +24,7 @@ class ElementBinderFactory { * building ElementBinders. */ class ElementBinderBuilder { - static RegExp _MAPPING = new RegExp(r'^(\@|=\>\!|\=\>|\<\=\>|\&)\s*(.*)$'); + static RegExp _MAPPING = new RegExp(r'^(@|=>!|=>|<=>|&)\s*(.*)$'); ElementBinderFactory _factory; @@ -37,49 +37,59 @@ class ElementBinderBuilder { DirectiveRef component; - // Can be either COMPILE_CHILDREN or IGNORE_CHILDREN - String childMode = AbstractNgAnnotation.COMPILE_CHILDREN; + bool compileChildren = true; ElementBinderBuilder(this._factory); - addDirective(DirectiveRef ref) { + void addDirective(DirectiveRef ref) { var annotation = ref.annotation; - var children = annotation.children; - if (annotation.children == AbstractNgAnnotation.TRANSCLUDE_CHILDREN) { + compileChildren = annotation.compileChildren; + + if (annotation is NgTemplate) { template = ref; - } else if (annotation is NgComponent) { - component = ref; + _addMapping(ref, (annotation as NgTemplate).mapping); } else { - decorators.add(ref); - } - - if (annotation.children == AbstractNgAnnotation.IGNORE_CHILDREN) { - childMode = annotation.children; - } - - if (annotation.map != null) annotation.map.forEach((attrName, mapping) { - Match match = _MAPPING.firstMatch(mapping); - if (match == null) { - throw "Unknown mapping '$mapping' for attribute '$attrName'."; + if (annotation is NgComponent) { + component = ref; + } else { + decorators.add(ref); } - var mode = match[1]; - var dstPath = match[2]; - - String dstExpression = dstPath.isEmpty ? attrName : dstPath; - - ref.mappings.add(new MappingParts(attrName, mode, dstExpression, mapping)); - }); + annotation = annotation as AbstractNgAttrAnnotation; + if (annotation.map != null) { + annotation.map.forEach((attrName, mapping) { + _addMapping(ref, mapping, attrName); + }); + } + } } ElementBinder get binder { if (template != null) { var transclude = _factory.binder(this); return _factory.templateBinder(this, transclude); - } else { return _factory.binder(this); } + } + + void _addMapping(DirectiveRef ref, String mapping, [String attrName]) { + if (mapping == null) return; + Match match = _MAPPING.firstMatch(mapping); + if (match == null) { + throw "Unknown mapping '$mapping' for attribute '$attrName'."; + } + var mode = match[1]; + var dstPath = match[2]; + if (dstPath.isEmpty && attrName != null) dstPath = attrName; + + ref.mappings.add(new MappingParts(attrName, mode, dstPath, mapping)); + } + + void _addRefs(List<_Directive> directives, dom.Node node, [String attrValue]) { + directives.forEach((directive) { + addDirective(new DirectiveRef(node, directive.type, directive.annotation, attrValue)); + }); } } diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart index b527d8a8a..e3a0f3628 100644 --- a/lib/core_dom/module_internal.dart +++ b/lib/core_dom/module_internal.dart @@ -38,6 +38,7 @@ part 'tagging_view_factory.dart'; part 'template_cache.dart'; part 'tree_sanitizer.dart'; part 'walking_compiler.dart'; +part 'walking_view_factory.dart'; part 'ng_element.dart'; class NgCoreDomModule extends Module { diff --git a/lib/core_dom/node_cursor.dart b/lib/core_dom/node_cursor.dart index a88fe2dfe..fb8112b6f 100644 --- a/lib/core_dom/node_cursor.dart +++ b/lib/core_dom/node_cursor.dart @@ -12,12 +12,11 @@ class NodeCursor { dom.Node get current => index < elements.length ? elements[index] : null; bool descend() { - var childNodes = elements[index].nodes; - var hasChildren = childNodes != null && childNodes.isNotEmpty; + var hasChildren = elements[index].hasChildNodes(); if (hasChildren) { stack..add(index)..add(elements); - elements = new List.from(childNodes); + elements = new List.from(elements[index].nodes); index = 0; } @@ -29,6 +28,15 @@ class NodeCursor { index = stack.removeLast(); } + bool processChildNodes(Function fn) { + var hasChildren = descend(); + if (hasChildren) { + fn(); + ascend(); + } + return hasChildren; + } + void insertAnchorBefore(String name) { var parent = current.parentNode; var anchor = new dom.Comment('ANCHOR: $name'); @@ -45,5 +53,5 @@ class NodeCursor { NodeCursor remove() => new NodeCursor([elements.removeAt(index)..remove()]); - toString() => "[NodeCursor: $elements $index]"; + String toString() => "[NodeCursor: $elements $index]"; } diff --git a/lib/core_dom/selector.dart b/lib/core_dom/selector.dart index 7f7cbed7a..5ba5e77af 100644 --- a/lib/core_dom/selector.dart +++ b/lib/core_dom/selector.dart @@ -16,15 +16,12 @@ part of angular.core.dom_internal; * * Examples: * - *
- *   element
- *   .class
- *   [attribute]
- *   [attribute=value]
- *   element[attribute1][attribute2=value]
- *   :contains(/abc/)
- * 
- * + * * `element` + * * `.class` + * * `[attribute]` + * * `[attribute=value]` + * * `element[attribute1][attribute2=value]` + * * `:contains(/abc/)` * */ class _Directive { @@ -33,7 +30,7 @@ class _Directive { _Directive(this.type, this.annotation); - toString() => annotation.selector; + String toString() => annotation.selector; } @@ -45,11 +42,11 @@ class _ContainsSelector { : regexp = new RegExp(regexp); } -var _SELECTOR_REGEXP = new RegExp(r'^(?:([-\w]+)|(?:\.([-\w]+))|' +final _SELECTOR_REGEXP = new RegExp(r'^(?:([-\w]+)|(?:\.([-\w]+))|' r'(?:\[([-\w*]+)(?:=([^\]]*))?\]))'); -var _COMMENT_COMPONENT_REGEXP = new RegExp(r'^\[([-\w]+)(?:\=(.*))?\]$'); -var _CONTAINS_REGEXP = new RegExp(r'^:contains\(\/(.+)\/\)$'); // -var _ATTR_CONTAINS_REGEXP = new RegExp(r'^\[\*=\/(.+)\/\]$'); // +final _COMMENT_COMPONENT_REGEXP = new RegExp(r'^\[([-\w]+)(?:\=(.*))?\]$'); +final _CONTAINS_REGEXP = new RegExp(r'^:contains\(\/(.+)\/\)$'); // +final _ATTR_CONTAINS_REGEXP = new RegExp(r'^\[\*=\/(.+)\/\]$'); // class _SelectorPart { final String element; @@ -74,13 +71,6 @@ class _SelectorPart { : element; } -_addRefs(ElementBinderBuilder builder, List<_Directive> directives, dom.Node node, - [String attrValue]) { - directives.forEach((directive) { - builder.addDirective(new DirectiveRef(node, directive.type, directive.annotation, attrValue)); - }); -} - class _ElementSelector { final String name; @@ -95,7 +85,7 @@ class _ElementSelector { _ElementSelector(this.name); - addDirective(List<_SelectorPart> selectorParts, _Directive directive) { + void addDirective(List<_SelectorPart> selectorParts, _Directive directive) { var selectorPart = selectorParts.removeAt(0); var terminal = selectorParts.isEmpty; var name; @@ -141,12 +131,10 @@ class _ElementSelector { List<_ElementSelector> partialSelection, dom.Node node, String nodeName) { if (elementMap.containsKey(nodeName)) { - _addRefs(builder, elementMap[nodeName], node); + builder._addRefs(elementMap[nodeName], node); } if (elementPartialMap.containsKey(nodeName)) { - if (partialSelection == null) { - partialSelection = new List<_ElementSelector>(); - } + if (partialSelection == null) partialSelection = <_ElementSelector>[]; partialSelection.add(elementPartialMap[nodeName]); } return partialSelection; @@ -156,12 +144,10 @@ class _ElementSelector { List<_ElementSelector> partialSelection, dom.Node node, String className) { if (classMap.containsKey(className)) { - _addRefs(builder, classMap[className], node); + builder._addRefs(classMap[className], node); } if (classPartialMap.containsKey(className)) { - if (partialSelection == null) { - partialSelection = new List<_ElementSelector>(); - } + if (partialSelection == null) partialSelection = <_ElementSelector>[]; partialSelection.add(classPartialMap[className]); } return partialSelection; @@ -177,25 +163,21 @@ class _ElementSelector { if (matchingKey != null) { Map> valuesMap = attrValueMap[matchingKey]; if (valuesMap.containsKey('')) { - _addRefs(builder, valuesMap[''], node, attrValue); + builder._addRefs(valuesMap[''], node, attrValue); } if (attrValue != '' && valuesMap.containsKey(attrValue)) { - _addRefs(builder, valuesMap[attrValue], node, attrValue); + builder._addRefs(valuesMap[attrValue], node, attrValue); } } if (attrValuePartialMap.containsKey(attrName)) { Map valuesPartialMap = attrValuePartialMap[attrName]; if (valuesPartialMap.containsKey('')) { - if (partialSelection == null) { - partialSelection = new List<_ElementSelector>(); - } + if (partialSelection == null) partialSelection = <_ElementSelector>[]; partialSelection.add(valuesPartialMap['']); } if (attrValue != '' && valuesPartialMap.containsKey(attrValue)) { - if (partialSelection == null) { - partialSelection = new List<_ElementSelector>(); - } + if (partialSelection == null) partialSelection = <_ElementSelector>[]; partialSelection.add(valuesPartialMap[attrValue]); } } @@ -209,10 +191,10 @@ class _ElementSelector { String _matchingKey(Iterable keys, String attrName) => keys.firstWhere((key) => _matchingKeyCache.putIfAbsent(key, - () => new RegExp('^${key.replaceAll('*', r'[\w\-]+')}\$')) - .hasMatch(attrName), orElse: () => null); + () => new RegExp('^${key.replaceAll('*', r'[\w\-]+')}\$')) + .hasMatch(attrName), orElse: () => null); - toString() => 'ElementSelector($name)'; + String toString() => 'ElementSelector($name)'; } List<_SelectorPart> _splitCss(String selector, Type type) { @@ -290,15 +272,15 @@ class DirectiveSelector { } // Select node - partialSelection = elementSelector.selectNode(builder, - partialSelection, element, nodeName); + partialSelection = elementSelector.selectNode(builder, partialSelection, + element, nodeName); // Select .name if ((element.classes) != null) { for (var name in element.classes) { classes[name] = true; partialSelection = elementSelector.selectClass(builder, - partialSelection, element, name); + partialSelection, element, name); } } @@ -307,9 +289,7 @@ class DirectiveSelector { if (attrName.startsWith("on-")) { builder.onEvents[attrName] = value; - } - - if (attrName.startsWith("bind-")) { + } else if (attrName.startsWith("bind-")) { builder.bindAttrs[attrName] = value; } @@ -337,11 +317,11 @@ class DirectiveSelector { elementSelectors.forEach((_ElementSelector elementSelector) { classes.forEach((className, _) { partialSelection = elementSelector.selectClass(builder, - partialSelection, node, className); + partialSelection, node, className); }); attrs.forEach((attrName, value) { partialSelection = elementSelector.selectAttr(builder, - partialSelection, node, attrName, value); + partialSelection, node, attrName, value); }); }); } @@ -357,27 +337,24 @@ class DirectiveSelector { if (selectorRegExp.regexp.hasMatch(value)) { _directives[selectorRegExp.annotation].forEach((type) { builder.addDirective(new DirectiveRef(node, type, - selectorRegExp.annotation, value)); + selectorRegExp.annotation, value)); }); } } return builder.binder; } - ElementBinder matchComment(dom.Node node) { - return _binderFactory.builder().binder; - } + ElementBinder matchComment(dom.Node node) =>_binderFactory.builder().binder; } /** * Factory for creating a [DirectiveSelector]. */ @NgInjectableService() class DirectiveSelectorFactory { - ElementBinderFactory _binderFactory; + final ElementBinderFactory _binderFactory; DirectiveSelectorFactory(this._binderFactory); - DirectiveSelector selector(DirectiveMap directives) { - return new DirectiveSelector(directives, _binderFactory); - } + DirectiveSelector selector(DirectiveMap directives) => + new DirectiveSelector(directives, _binderFactory); } diff --git a/lib/core_dom/tagging_compiler.dart b/lib/core_dom/tagging_compiler.dart index d12a2d2e7..39d251b07 100644 --- a/lib/core_dom/tagging_compiler.dart +++ b/lib/core_dom/tagging_compiler.dart @@ -19,8 +19,8 @@ class TaggingCompiler implements Compiler { List elementBinders) { var node = domCursor.current; - if (node.nodeType == 1) { - // If nodetype is a element, call selector matchElement. + if (node.nodeType == dom.Node.ELEMENT_NODE) { + // If the node is an element, call selector matchElement. // If text, call selector.matchText ElementBinder elementBinder = useExistingElementBinder == null ? @@ -39,13 +39,13 @@ class TaggingCompiler implements Compiler { return null; } - _compileNode(NodeCursor domCursor, - ElementBinder elementBinder, - DirectiveMap directives, - List elementBinders, - int parentElementBinderOffset, - bool isTopLevel, - TaggedElementBinder directParentElementBinder) { + void _compileNode(NodeCursor domCursor, + ElementBinder elementBinder, + DirectiveMap directives, + List elementBinders, + int parentElementBinderOffset, + bool isTopLevel, + TaggedElementBinder directParentElementBinder) { var node = domCursor.current; if (node.nodeType == dom.Node.ELEMENT_NODE) { TaggedElementBinder taggedElementBinder; @@ -60,8 +60,8 @@ class TaggingCompiler implements Compiler { taggedElementBinderIndex = parentElementBinderOffset; } - if (elementBinder.shouldCompileChildren) { - if (domCursor.descend()) { + if (elementBinder.compileChildren) { + domCursor.processChildNodes(() { var addedDummy = false; if (taggedElementBinder == null) { addedDummy = true; @@ -81,8 +81,7 @@ class TaggingCompiler implements Compiler { // end of the compilation process. node.classes.add('ng-binding'); } - domCursor.ascend(); - } + }); } } else if (node.nodeType == dom.Node.TEXT_NODE || node.nodeType == dom.Node.COMMENT_NODE) { @@ -156,18 +155,20 @@ class TaggingCompiler implements Compiler { return viewFactory; } - _isDummyBinder(TaggedElementBinder binder) => - binder.binder == null && binder.textBinders == null && !binder.isTopLevel; + bool _isDummyBinder(TaggedElementBinder binder) => + binder.binder == null && + binder.textBinders == null && + !binder.isTopLevel; - _removeUnusedBinders(List binders) { + List _removeUnusedBinders(List binders) { // In order to support text nodes with directiveless parents, we // add dummy ElementBinders to the list. After the entire template // has been compiled, we remove the dummies and update the offset indices - final output = []; - final List offsetMap = []; + final output = []; + final offsetMap = []; int outputIndex = 0; - for (var i = 0, ii = binders.length; i < ii; i++) { + for (var i = 0; i < binders.length; i++) { TaggedElementBinder binder = binders[i]; if (_isDummyBinder(binder)) { offsetMap.add(-2); diff --git a/lib/core_dom/tagging_view_factory.dart b/lib/core_dom/tagging_view_factory.dart index d6ebe1bbf..87bae610a 100644 --- a/lib/core_dom/tagging_view_factory.dart +++ b/lib/core_dom/tagging_view_factory.dart @@ -8,12 +8,10 @@ class TaggingViewFactory implements ViewFactory { TaggingViewFactory(this.templateNodes, this.elementBinders, this._perf); BoundViewFactory bind(Injector injector) => - new BoundViewFactory(this, injector); + new BoundViewFactory(this, injector); View call(Injector injector, [List nodes /* TODO: document fragment */]) { - if (nodes == null) { - nodes = cloneElements(templateNodes); - } + if (nodes == null) nodes = cloneElements(templateNodes); var timerId; try { assert((timerId = _perf.startTimer('ng.view')) != false); @@ -27,13 +25,17 @@ class TaggingViewFactory implements ViewFactory { _bindTagged(TaggedElementBinder tagged, rootInjector, elementBinders, View view, boundNode) { var binder = tagged.binder; - var parentInjector = tagged.parentBinderOffset == -1 ? rootInjector : elementBinders[tagged.parentBinderOffset].injector; + var parentInjector = tagged.parentBinderOffset == -1 ? + rootInjector: + elementBinders[tagged.parentBinderOffset].injector; assert(parentInjector != null); - tagged.injector = binder != null ? binder.bind(view, parentInjector, boundNode) : parentInjector; + tagged.injector = binder != null ? + binder.bind(view, parentInjector, boundNode): + parentInjector; if (tagged.textBinders != null) { - for (var k = 0, kk = tagged.textBinders.length; k < kk; k++) { + for (var k = 0; k < tagged.textBinders.length; k++) { TaggedTextBinder taggedText = tagged.textBinders[k]; taggedText.binder.bind(view, tagged.injector, boundNode.childNodes[taggedText.offsetIndex]); } @@ -44,7 +46,7 @@ class TaggingViewFactory implements ViewFactory { var directiveDefsByName = {}; var elementBinderIndex = 0; - for (int i = 0, ii = nodeList.length; i < ii; i++) { + for (int i = 0; i < nodeList.length; i++) { var node = nodeList[i]; // if node isn't attached to the DOM, create a parent for it. @@ -56,17 +58,18 @@ class TaggingViewFactory implements ViewFactory { parentNode.append(node); } - if (node.nodeType == 1) { + if (node.nodeType == dom.Node.ELEMENT_NODE) { var elts = node.querySelectorAll('.ng-binding'); // HACK: querySelectorAll doesn't return the node. var startIndex = node.classes.contains('ng-binding') ? -1 : 0; - for (int j = startIndex, jj = elts.length; j < jj; j++, elementBinderIndex++) { + for (int j = startIndex; j < elts.length; j++, elementBinderIndex++) { TaggedElementBinder tagged = elementBinders[elementBinderIndex]; var boundNode = j == -1 ? node : elts[j]; _bindTagged(tagged, rootInjector, elementBinders, view, boundNode); } - } else if (node.nodeType == 3 || node.nodeType == 8) { + } else if (node.nodeType == dom.Node.COMMENT_NODE || + node.nodeType == dom.Node.TEXT_NODE) { TaggedElementBinder tagged = elementBinders[elementBinderIndex]; assert(tagged.binder != null || tagged.isTopLevel); if (tagged.binder != null) { @@ -77,10 +80,8 @@ class TaggingViewFactory implements ViewFactory { throw "nodeType sadness ${node.nodeType}}"; } - if (fakeParent) { - // extract the node from the parentNode. - nodeList[i] = parentNode.nodes[0]; - } + // extract the node from the parentNode. + if (fakeParent) nodeList[i] = parentNode.nodes[0]; } return view; } diff --git a/lib/core_dom/view.dart b/lib/core_dom/view.dart index 72bde2651..983af5f38 100644 --- a/lib/core_dom/view.dart +++ b/lib/core_dom/view.dart @@ -38,7 +38,7 @@ class ViewPort { _viewsInsertAfter(view, insertAfter); _animate.insert(view.nodes, placeholder.parentNode, - insertBefore: previousNode.nextNode); + insertBefore: previousNode.nextNode); } void remove(View view) { @@ -61,7 +61,5 @@ class ViewPort { } dom.Node _lastNode(View insertAfter) => - insertAfter == null - ? placeholder - : insertAfter.nodes.last; + insertAfter == null ? placeholder : insertAfter.nodes.last; } diff --git a/lib/core_dom/view_factory.dart b/lib/core_dom/view_factory.dart index 603e444cf..bc0b6395f 100644 --- a/lib/core_dom/view_factory.dart +++ b/lib/core_dom/view_factory.dart @@ -25,91 +25,6 @@ abstract class ViewFactory implements Function { View call(Injector injector, [List elements]); } -/** - * [WalkingViewFactory] is used to create new [View]s. WalkingViewFactory is - * created by the [Compiler] as a result of compiling a template. - */ -class WalkingViewFactory implements ViewFactory { - final List elementBinders; - final List templateElements; - final Profiler _perf; - final Expando _expando; - - WalkingViewFactory(this.templateElements, this.elementBinders, this._perf, - this._expando) { - assert(elementBinders.every((ElementBinderTreeRef eb) => - eb is ElementBinderTreeRef)); - } - - BoundViewFactory bind(Injector injector) => - new BoundViewFactory(this, injector); - - View call(Injector injector, [List nodes]) { - if (nodes == null) nodes = cloneElements(templateElements); - var timerId; - try { - assert((timerId = _perf.startTimer('ng.view')) != false); - var view = new View(nodes, injector.get(EventHandler)); - _link(view, nodes, elementBinders, injector); - return view; - } finally { - assert(_perf.stopTimer(timerId) != false); - } - } - - View _link(View view, List nodeList, List elementBinders, - Injector parentInjector) { - - var preRenderedIndexOffset = 0; - var directiveDefsByName = {}; - - for (int i = 0; i < elementBinders.length; i++) { - // querySelectorAll('.ng-binding') should return a list of nodes in the - // same order as the elementBinders list. - - // keep a injector array -- - - var eb = elementBinders[i]; - int index = eb.offsetIndex; - - ElementBinderTree tree = eb.subtree; - - //List childElementBinders = eb.childElementBinders; - int nodeListIndex = index + preRenderedIndexOffset; - dom.Node node = nodeList[nodeListIndex]; - var binder = tree.binder; - - var timerId; - try { - assert((timerId = _perf.startTimer('ng.view.link', _html(node))) != false); - // if node isn't attached to the DOM, create a parent for it. - var parentNode = node.parentNode; - var fakeParent = false; - if (parentNode == null) { - fakeParent = true; - parentNode = new dom.DivElement()..append(node); - } - - var childInjector = binder != null ? - binder.bind(view, parentInjector, node) : - parentInjector; - - if (fakeParent) { - // extract the node from the parentNode. - nodeList[nodeListIndex] = parentNode.nodes[0]; - } - - if (tree.subtrees != null) { - _link(view, node.nodes, tree.subtrees, childInjector); - } - } finally { - assert(_perf.stopTimer(timerId) != false); - } - } - return view; - } -} - /** * ViewCache is used to cache the compilation of templates into [View]s. * It can be used synchronously if HTML is known or asynchronously if the @@ -247,25 +162,9 @@ class _ComponentFactory implements Function { } } -class _AnchorAttrs extends NodeAttrs { - DirectiveRef _directiveRef; - - _AnchorAttrs(DirectiveRef this._directiveRef): super(null); - - String operator [](name) => name == '.' ? _directiveRef.value : null; - - void observe(String attributeName, _AttributeChanged notifyFn) { - notifyFn(attributeName == '.' ? _directiveRef.value : null); - } -} - String _html(obj) { - if (obj is String) { - return obj; - } - if (obj is List) { - return (obj as List).map((e) => _html(e)).join(); - } + if (obj is String) return obj; + if (obj is List) return (obj as List).map((e) => _html(e)).join(); if (obj is dom.Element) { var text = (obj as dom.Element).outerHtml; return text.substring(0, text.indexOf('>') + 1); diff --git a/lib/core_dom/walking_compiler.dart b/lib/core_dom/walking_compiler.dart index 3a49fa190..e75a6aa39 100644 --- a/lib/core_dom/walking_compiler.dart +++ b/lib/core_dom/walking_compiler.dart @@ -45,15 +45,12 @@ class WalkingCompiler implements Compiler { templateBinder.template, templateBinder.templateBinder, directives); } - if (elementBinder.shouldCompileChildren) { - if (domCursor.descend()) { + if (elementBinder.compileChildren) { + domCursor.processChildNodes(() { templateCursor.descend(); - subtrees = _compileView(domCursor, templateCursor, null, directives); - - domCursor.ascend(); templateCursor.ascend(); - } + }); } if (elementBinder.hasDirectivesOrEvents) { diff --git a/lib/core_dom/walking_view_factory.dart b/lib/core_dom/walking_view_factory.dart new file mode 100644 index 000000000..cc56f1f03 --- /dev/null +++ b/lib/core_dom/walking_view_factory.dart @@ -0,0 +1,83 @@ +part of angular.core.dom_internal; + +/** + - * [WalkingViewFactory] is used to create new [View]s. WalkingViewFactory is + - * created by the [Compiler] as a result of compiling a template. + - */ +class WalkingViewFactory implements ViewFactory { + final List elementBinders; + final List templateElements; + final Profiler _perf; + final Expando _expando; + + WalkingViewFactory(this.templateElements, this.elementBinders, this._perf, + this._expando) { + assert(elementBinders.every((eb) => eb is ElementBinderTreeRef)); + } + + BoundViewFactory bind(Injector injector) => + new BoundViewFactory(this, injector); + + View call(Injector injector, [List nodes]) { + if (nodes == null) nodes = cloneElements(templateElements); + var timerId; + try { + assert((timerId = _perf.startTimer('ng.view')) != false); + var view = new View(nodes, injector.get(EventHandler)); + _link(view, nodes, elementBinders, injector); + return view; + } finally { + assert(_perf.stopTimer(timerId) != false); + } + } + + View _link(View view, List nodeList, List elementBinders, + Injector parentInjector) { + + var preRenderedIndexOffset = 0; + var directiveDefsByName = {}; + + for (int i = 0; i < elementBinders.length; i++) { + // querySelectorAll('.ng-binding') should return a list of nodes in the + // same order as the elementBinders list. + + // keep a injector array -- + + var eb = elementBinders[i]; + int index = eb.offsetIndex; + + ElementBinderTree tree = eb.subtree; + + //List childElementBinders = eb.childElementBinders; + int nodeListIndex = index + preRenderedIndexOffset; + dom.Node node = nodeList[nodeListIndex]; + var binder = tree.binder; + + var timerId; + try { + assert((timerId = _perf.startTimer('ng.view.link', _html(node))) != false); + // if node isn't attached to the DOM, create a parent for it. + var parentNode = node.parentNode; + var fakeParent = false; + if (parentNode == null) { + fakeParent = true; + parentNode = new dom.DivElement()..append(node); + } + + var childInjector = binder != null ? + binder.bind(view, parentInjector, node) : + parentInjector; + + // extract the node from the parentNode. + if (fakeParent) nodeList[nodeListIndex] = parentNode.nodes[0]; + + if (tree.subtrees != null) { + _link(view, node.nodes, tree.subtrees, childInjector); + } + } finally { + assert(_perf.stopTimer(timerId) != false); + } + } + return view; + } +} \ No newline at end of file diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 8c5385419..b84071357 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -33,7 +33,7 @@ part 'ng_control.dart'; part 'ng_model.dart'; part 'ng_pluralize.dart'; part 'ng_repeat.dart'; -part 'ng_template.dart'; +part 'ng_plain_template.dart'; part 'ng_show_hide.dart'; part 'ng_src_boolean.dart'; part 'ng_style.dart'; @@ -50,8 +50,7 @@ class NgDirectiveModule extends Module { value(NgBind, null); value(NgBindTemplate, null); value(NgBindHtml, null); - factory(dom.NodeValidator, (_) => - new dom.NodeValidatorBuilder.common()); + factory(dom.NodeValidator, (_) => new dom.NodeValidatorBuilder.common()); value(NgClass, null); value(NgClassOdd, null); value(NgClassEven, null); @@ -87,7 +86,7 @@ class NgDirectiveModule extends Module { value(NgEvent, null); value(NgStyle, null); value(NgNonBindable, null); - value(NgTemplate, null); + value(NgTemplateDirective, null); value(NgControl, new NgNullControl()); value(NgForm, new NgNullForm()); diff --git a/lib/directive/ng_if.dart b/lib/directive/ng_if.dart index f0a0ce18e..07f7a5b4d 100644 --- a/lib/directive/ng_if.dart +++ b/lib/directive/ng_if.dart @@ -89,10 +89,9 @@ abstract class _NgUnlessIfAttrDirectiveBase { * * */ -@NgDirective( - children: AbstractNgAnnotation.TRANSCLUDE_CHILDREN, +@NgTemplate( selector:'[ng-if]', - map: const {'.': '=>condition'}) + mapping: '=>condition') class NgIf extends _NgUnlessIfAttrDirectiveBase { NgIf(BoundViewFactory boundViewFactory, ViewPort viewPort, @@ -150,10 +149,9 @@ class NgIf extends _NgUnlessIfAttrDirectiveBase { * * */ -@NgDirective( - children: AbstractNgAnnotation.TRANSCLUDE_CHILDREN, +@NgTemplate( selector:'[ng-unless]', - map: const {'.': '=>condition'}) + mapping: '=>condition') class NgUnless extends _NgUnlessIfAttrDirectiveBase { NgUnless(BoundViewFactory boundViewFactory, diff --git a/lib/directive/ng_non_bindable.dart b/lib/directive/ng_non_bindable.dart index 975e154d8..cf77be1ec 100644 --- a/lib/directive/ng_non_bindable.dart +++ b/lib/directive/ng_non_bindable.dart @@ -20,5 +20,5 @@ part of angular.directive; */ @NgDirective( selector: '[ng-non-bindable]', - children: AbstractNgAnnotation.IGNORE_CHILDREN) + compileChildren: false) class NgNonBindable {} diff --git a/lib/directive/ng_template.dart b/lib/directive/ng_plain_template.dart similarity index 85% rename from lib/directive/ng_template.dart rename to lib/directive/ng_plain_template.dart index 4ea85ac63..e44ae1e40 100644 --- a/lib/directive/ng_template.dart +++ b/lib/directive/ng_plain_template.dart @@ -1,7 +1,7 @@ part of angular.directive; /** - * The [NgTemplateElement] allows one to preload an Angular template + * The [NgTemplateDirective] allows one to preload an Angular template * into the [TemplateCache]. It works on `