diff --git a/lib/block.dart b/lib/block.dart index 7f9c77a8d..2e3f29f87 100644 --- a/lib/block.dart +++ b/lib/block.dart @@ -98,9 +98,9 @@ class Block implements ElementWrapper { directiveRefs.forEach((DirectiveRef ref) { Type type = ref.directive.type; var visibility = elementOnly; - if (ref.directive.$visibility == DirectiveVisibility.CHILDREN) { + if (ref.directive.$visibility == NgDirective.CHILDREN_VISIBILITY) { visibility = null; - } else if (ref.directive.$visibility == DirectiveVisibility.DIRECT_CHILDREN) { + } else if (ref.directive.$visibility == NgDirective.DIRECT_CHILDREN_VISIBILITY) { visibility = elementDirectChildren; } if (ref.directive.isComponent) { @@ -269,9 +269,9 @@ class ComponentFactory { this.compiler = compiler; shadowDom = element.createShadowRoot(); shadowDom.applyAuthorStyles = - directive.$shadowRootOptions.$applyAuthorStyles; + directive.$shadowRootOptions.applyAuthorStyles; shadowDom.resetStyleInheritance = - directive.$shadowRootOptions.$resetStyleInheritance; + directive.$shadowRootOptions.resetStyleInheritance; shadowScope = scope.$new(true); createAttributeMapping(scope, shadowScope, parser); diff --git a/lib/directive.dart b/lib/directive.dart index f48265bc7..7fc4378a0 100644 --- a/lib/directive.dart +++ b/lib/directive.dart @@ -4,6 +4,58 @@ String _COMPONENT = '-component'; String _DIRECTIVE = '-directive'; String _ATTR_DIRECTIVE = '-attr' + _DIRECTIVE; +class NgComponent { + final String template; + final String templateUrl; + final String cssUrl; + final String visibility; + final Map map; + final String publishAs; + final bool applyAuthorStyles; + final bool resetStyleInheritance; + + const NgComponent({ + this.template, + this.templateUrl, + this.cssUrl, + this.visibility: NgDirective.LOCAL_VISIBILITY, + this.map, + this.publishAs, + this.applyAuthorStyles, + this.resetStyleInheritance + }); +} + +class NgDirective { + static const String LOCAL_VISIBILITY = 'local'; + static const String CHILDREN_VISIBILITY = 'children'; + static const String DIRECT_CHILDREN_VISIBILITY = 'direct_children'; + + final String selector; + final String transclude; + final int priority; + final String visibility; + + const NgDirective({ + this.selector, + this.transclude, + this.priority : 0, + this.visibility: LOCAL_VISIBILITY + }); +} + +/** + * See: + * http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/#toc-style-inheriting + */ +class NgShadowRootOptions { + final bool applyAuthorStyles; + final bool resetStyleInheritance; + const NgShadowRootOptions([this.applyAuthorStyles = false, + this.resetStyleInheritance = false]); +} + +// TODO(pavelgj): Get rid of Directive and use NgComponent/NgDirective directly. class Directive { Type type; // TODO(misko): this should be renamed to selector once we change over to meta-data. @@ -11,14 +63,13 @@ class Directive { Function $generate; String $transclude; int $priority = 0; - Type $controllerType; String $template; String $templateUrl; String $cssUrl; String $publishAs; Map $map; String $visibility; - ShadowRootOptions $shadowRootOptions; + NgShadowRootOptions $shadowRootOptions = new NgShadowRootOptions(); bool isComponent = false; bool isStructural = false; @@ -31,9 +82,32 @@ class Directive { onMatch: (m) => '-' + m.group(0).toLowerCase()) .substring(1); - var $selector = reflectStaticField(type, r'$selector'); - if ($selector != null) { - $name = $selector; + var directive = _reflectSingleMetadata(type, NgDirective); + var component = _reflectSingleMetadata(type, NgComponent); + if (directive != null && component != null) { + throw 'Cannot have both NgDirective and NgComponent annotations.'; + } + + var selector; + if (directive != null) { + selector = directive.selector; + $transclude = directive.transclude; + $priority = directive.priority; + $visibility = directive.visibility; + } + if (component != null) { + $template = component.template; + $templateUrl = component.templateUrl; + $cssUrl = component.cssUrl; + $visibility = component.visibility; + $map = component.map; + $publishAs = component.publishAs; + $shadowRootOptions = new NgShadowRootOptions(component.applyAuthorStyles, + component.resetStyleInheritance); + } + + if (selector != null) { + $name = selector; } else if ($name.endsWith(_ATTR_DIRECTIVE)) { $name = '[${$name.substring(0, $name.length - _ATTR_DIRECTIVE.length)}]'; } else if ($name.endsWith(_DIRECTIVE)) { @@ -45,30 +119,24 @@ class Directive { throw "Directive name '$name' must end with $_DIRECTIVE, $_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; } - // Check the $transclude. - // TODO(deboer): I'm not a fan of 'null' as a configuration value. - // It would be awesome if $transclude could be an enum. - $transclude = reflectStaticField(type, '\$transclude'); - $template = reflectStaticField(type, '\$template'); - $templateUrl = reflectStaticField(type, '\$templateUrl'); - $cssUrl = reflectStaticField(type, '\$cssUrl'); - $priority = _defaultIfNull(reflectStaticField(type, '\$priority'), 0); - $visibility = _defaultIfNull( - reflectStaticField(type, '\$visibility'), DirectiveVisibility.LOCAL); - $map = reflectStaticField(type, '\$map'); - $publishAs = reflectStaticField(type, r'$publishAs'); isStructural = $transclude != null; if (isComponent && $map == null) { $map = new Map(); } - if (isComponent) { - $shadowRootOptions = _defaultIfNull( - reflectStaticField(type, '\$shadowRootOptions'), - new ShadowRootOptions()); - } } } +_reflectSingleMetadata(Type type, Type metadataType) { + var metadata = reflectMetadata(type, metadataType); + if (metadata.length == 0) { + return null; + } + if (metadata.length > 1) { + throw 'Expecting not more than one annotation of type $metadataType'; + } + return metadata.first; +} + dynamic _defaultIfNull(dynamic value, dynamic defaultValue) => value == null ? defaultValue : value; @@ -113,23 +181,6 @@ class DirectiveRegistry { } } -abstract class DirectiveVisibility { - static const String LOCAL = 'local'; - static const String CHILDREN = 'children'; - static const String DIRECT_CHILDREN = 'direct_children'; -} - -/** - * See: - * http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/#toc-style-inheriting - */ -class ShadowRootOptions { - bool $applyAuthorStyles = false; - bool $resetStyleInheritance = false; - ShadowRootOptions([this.$applyAuthorStyles = false, - this.$resetStyleInheritance = false]); -} - class Controller { } diff --git a/lib/directives/ng_controller.dart b/lib/directives/ng_controller.dart index 5204cba69..951e6572d 100644 --- a/lib/directives/ng_controller.dart +++ b/lib/directives/ng_controller.dart @@ -1,7 +1,7 @@ part of angular; +@NgDirective(transclude: '.') class NgControllerAttrDirective { - static String $transclude = 'element'; static RegExp CTRL_REGEXP = new RegExp(r'^(\S+)(\s+as\s+(\w+))?$'); Symbol ctrlSymbol; diff --git a/lib/directives/ng_if.dart b/lib/directives/ng_if.dart index a203c5d69..e99dcb3bb 100644 --- a/lib/directives/ng_if.dart +++ b/lib/directives/ng_if.dart @@ -1,9 +1,7 @@ part of angular; - +@NgDirective(transclude: '.', priority: 100) class NgIfAttrDirective { - static var $priority = 100; - static String $transclude = 'element'; NgIfAttrDirective(NodeAttrs attrs, Injector injector, BlockList blockList, dom.Node node, Scope scope) { var _block; diff --git a/lib/directives/ng_model.dart b/lib/directives/ng_model.dart index 549fbd844..4621f7624 100644 --- a/lib/directives/ng_model.dart +++ b/lib/directives/ng_model.dart @@ -10,9 +10,8 @@ part of angular; * knwos how to (in)validate the model and the form in which it is declared * (to be implemented) */ +@NgDirective(selector: '[ng-model]') class NgModel { - static String $selector = '[ng-model]'; - Scope scope; ParsedFn getter; ParsedAssignFn setter; diff --git a/lib/directives/ng_mustache.dart b/lib/directives/ng_mustache.dart index f2f6b90c2..b56157fca 100644 --- a/lib/directives/ng_mustache.dart +++ b/lib/directives/ng_mustache.dart @@ -1,8 +1,7 @@ part of angular; +@NgDirective(selector: r':contains(/{{.*}}/)') class NgTextMustacheDirective { - static String $selector = r':contains(/{{.*}}/)'; - dom.Node element; ParsedFn interpolateFn; @@ -14,8 +13,8 @@ class NgTextMustacheDirective { } +@NgDirective(selector: r'[*=/{{.*}}/]') class NgAttrMustacheDirective { - static String $selector = r'[*=/{{.*}}/]'; static RegExp ATTR_NAME_VALUE_REGEXP = new RegExp(r'^([^=]+)=(.*)$'); NgAttrMustacheDirective(dom.Element element, NodeAttrs attrs, Interpolate interpolate, Scope scope) { diff --git a/lib/directives/ng_repeat.dart b/lib/directives/ng_repeat.dart index 695319796..6e474a6f6 100644 --- a/lib/directives/ng_repeat.dart +++ b/lib/directives/ng_repeat.dart @@ -11,9 +11,8 @@ class Row { Row(this.id); } +@NgDirective(transclude: '.') class NgRepeatAttrDirective { - static var $transclude = "."; - static RegExp SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$'); static RegExp LHS_SYNTAX = new RegExp(r'^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$'); diff --git a/lib/mirrors.dart b/lib/mirrors.dart index 80909f5b2..d7be90456 100644 --- a/lib/mirrors.dart +++ b/lib/mirrors.dart @@ -28,3 +28,9 @@ reflectStaticField(Type type, String field) { if (fieldReflection == null) return null; return fieldReflection.reflectee; } + +// TODO(pavelgj): cache. +Iterable reflectMetadata(Type type, Type metadata) => + fastReflectClass(type).metadata.where( + (InstanceMirror im) => im.reflectee.runtimeType == metadata) + .map((InstanceMirror im) => im.reflectee); diff --git a/test/_test_bed.dart b/test/_test_bed.dart index 0573afb8a..040abbbc1 100644 --- a/test/_test_bed.dart +++ b/test/_test_bed.dart @@ -51,9 +51,8 @@ beforeEachTestBed(assign) { * * rootScope.myProbe.directive(SomeAttrDirective); */ +@NgDirective(selector: '[probe]') class Probe { - static String $selector = '[probe]'; - Scope scope; Injector injector; Element element; diff --git a/test/block_spec.dart b/test/block_spec.dart index e7e4c6379..35cabf2a0 100644 --- a/test/block_spec.dart +++ b/test/block_spec.dart @@ -1,8 +1,7 @@ import "_specs.dart"; +@NgDirective(transclude: '.', selector: 'foo') class LoggerBlockDirective { - static String $name = 'foo'; - static String $transclude = '.'; LoggerBlockDirective(BlockList list, Logger logger) { if (list == null) { throw new ArgumentError('BlockList must be injected.'); @@ -12,16 +11,16 @@ class LoggerBlockDirective { } class ReplaceBlockDirective { - ReplaceBlockDirective(BlockList list, Node node) { - var block = list.newBlock(); + ReplaceBlockDirective(BlockList list, Node node, Scope scope) { + var block = list.newBlock(scope); block.insertAfter(list); node.remove(); } } class ShadowBlockDirective { - ShadowBlockDirective(BlockList list, Element element) { - var block = list.newBlock(); + ShadowBlockDirective(BlockList list, Element element, Scope scope) { + var block = list.newBlock(scope); var shadowRoot = element.createShadowRoot(); for (var i = 0, ii = block.elements.length; i < ii; i++) { shadowRoot.append(block.elements[i]); diff --git a/test/compiler_spec.dart b/test/compiler_spec.dart index b70c07dbe..c11ea2239 100644 --- a/test/compiler_spec.dart +++ b/test/compiler_spec.dart @@ -3,8 +3,8 @@ import "_log.dart"; import "dart:mirrors"; +@NgComponent(visibility: NgDirective.DIRECT_CHILDREN_VISIBILITY) class TabComponent { - static String $visibility = DirectiveVisibility.DIRECT_CHILDREN; int id = 0; Log log; LocalAttrDirective local; @@ -24,8 +24,8 @@ class PaneComponent { } } +@NgDirective(visibility: NgDirective.LOCAL_VISIBILITY) class LocalAttrDirective { - static String $visibility = DirectiveVisibility.LOCAL; int id = 0; Log log; LocalAttrDirective(Log this.log); @@ -34,10 +34,8 @@ class LocalAttrDirective { } } +@NgDirective(visibility: NgDirective.CHILDREN_VISIBILITY, transclude: '.') class SimpleTranscludeInAttachAttrDirective { - static String $transclude = '.'; - static String $visibility = DirectiveVisibility.CHILDREN; - Log log; BlockList blockList; @@ -510,16 +508,24 @@ main() { }); } +@NgComponent( + template: r'{{name}}{{sep}}{{$id}}(SHADOW-CONTENT)' +) class SimpleComponent { - static String $template = r'{{name}}{{sep}}{{$id}}(SHADOW-CONTENT)'; - SimpleComponent(Scope scope, TemplateLoader templateLoader) { + SimpleComponent(Scope scope) { scope.name = 'INNER'; } } +@NgComponent( + template: r'', + map: const { + 'attr': '@', + 'expr': '=', + 'ondone': '&', + } +) class IoComponent { - static String $template = r''; - static Map $map = {"attr": "@", "expr": "=", "ondone": "&"}; Scope scope; IoComponent(Scope scope) { this.scope = scope; @@ -527,21 +533,30 @@ class IoComponent { } } +@NgComponent( + map: const { + 'camelCase': '@', + } +) class CamelCaseMapComponent { - static Map $map = {"camelCase": "@"}; CamelCaseMapComponent(Scope scope) { scope.$root.camelCase = scope; } } +@NgComponent( + template: '
inside {{fromParent()}}
', + map: const { + 'fromParent': '&', + } +) class ParentExpressionComponent { - static String $template = '
inside {{fromParent()}}
'; - static Map $map = {"fromParent": "&"}; } +@NgComponent( + template: r'{{ctrlName.value}}', + publishAs: 'ctrlName' +) class PublishMeComponent { - static String $template = r'{{ctrlName.value}}'; - static String $publishAs = 'ctrlName'; - String value = 'WORKED'; } diff --git a/test/directive_spec.dart b/test/directive_spec.dart index 69f241082..9b4d767c8 100644 --- a/test/directive_spec.dart +++ b/test/directive_spec.dart @@ -4,19 +4,22 @@ import "_specs.dart"; class SomeDirective { } class AnotherAttrDirective { } +@NgDirective(transclude: 'true') class TranscludeDirective { - static var $transclude = "true"; } +@NgDirective(transclude: null) class ExplicitNullTranscludeDirective { - static var $transclude = null; } class WithDefaultShadowRootOptionsComponent { } +@NgComponent( + applyAuthorStyles: true, + resetStyleInheritance: true +) class WithCustomShadowRootOptionsComponent { - static var $shadowRootOptions = new ShadowRootOptions(true, true); } main() { @@ -48,14 +51,14 @@ main() { it('should default \$shadowRootOptions to false/false', () { Directive factory = new Directive(WithDefaultShadowRootOptionsComponent); - expect(factory.$shadowRootOptions.$applyAuthorStyles, isFalse); - expect(factory.$shadowRootOptions.$resetStyleInheritance, isFalse); + expect(factory.$shadowRootOptions.applyAuthorStyles, isFalse); + expect(factory.$shadowRootOptions.resetStyleInheritance, isFalse); }); it('should override \$shadowRootOptions with values provided by component type', () { Directive factory = new Directive(WithCustomShadowRootOptionsComponent); - expect(factory.$shadowRootOptions.$applyAuthorStyles, isTrue); - expect(factory.$shadowRootOptions.$resetStyleInheritance, isTrue); + expect(factory.$shadowRootOptions.applyAuthorStyles, isTrue); + expect(factory.$shadowRootOptions.resetStyleInheritance, isTrue); }); }); } diff --git a/test/shadow_root_options_spec.dart b/test/shadow_root_options_spec.dart index afe881b50..741eb2d9b 100644 --- a/test/shadow_root_options_spec.dart +++ b/test/shadow_root_options_spec.dart @@ -1,25 +1,30 @@ import "_specs.dart"; +@NgComponent( + template: '
Reset me foo
', + applyAuthorStyles: false, + resetStyleInheritance: true +) class ResetStyleInheritanceComponent { - static String $template = '
Reset me foo
'; - static var $shadowRootOptions = new ShadowRootOptions(false, true); static var lastTemplateLoader; ResetStyleInheritanceComponent(Element elt, TemplateLoader tl) { lastTemplateLoader = tl.template; } } +@NgComponent( + template: '
Style me foo
', + applyAuthorStyles: true +) class ApplyAuthorStyleComponent { - static String $template = '
Style me foo
'; - static var $shadowRootOptions = new ShadowRootOptions(true); static var lastTemplateLoader; ApplyAuthorStyleComponent(Element elt, TemplateLoader tl) { lastTemplateLoader = tl.template; } } +@NgComponent(template: '
Style me foo
') class DefaultOptionsComponent { - static String $template = '
Style me foo
'; static var lastTemplateLoader; DefaultOptionsComponent(Element elt, TemplateLoader tl) { lastTemplateLoader = tl.template; diff --git a/test/templateurl_spec.dart b/test/templateurl_spec.dart index 7a1db784a..1cfaa1884 100644 --- a/test/templateurl_spec.dart +++ b/test/templateurl_spec.dart @@ -2,30 +2,28 @@ import "_specs.dart"; import "_log.dart"; import "_http.dart"; +@NgDirective(priority: 0) class LogAttrDirective { - static var $priority = 0; Log log; LogAttrDirective(Log this.log, NodeAttrs attrs) { log(attrs[this] == "" ? "LOG" : attrs[this]); } } +@NgComponent(templateUrl: 'simple.html') class SimpleUrlComponent { - static String $templateUrl = 'simple.html'; } +@NgComponent(templateUrl: 'simple.html', cssUrl: 'simple.css') class HtmlAndCssComponent { - static String $templateUrl = 'simple.html'; - static String $cssUrl = 'simple.css'; } +@NgComponent(template: '
inline!
', cssUrl: 'simple.css') class InlineWithCssComponent { - static String $template = '
inline!
'; - static String $cssUrl = 'simple.css'; } +@NgComponent(cssUrl: 'simple.css') class OnlyCssComponent { - static String $cssUrl = 'simple.css'; } class PrefixedUrlRewriter extends UrlRewriter { @@ -70,7 +68,7 @@ main() { module.directive(OnlyCssComponent); module.directive(InlineWithCssComponent); })); - + afterEach(inject((MockHttp $http) { $http.assertAllGetsCalled(); }));