Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
fix(directive): Support multiple directives with same selector.
Browse files Browse the repository at this point in the history
  • Loading branch information
mhevery authored and jbdeboer committed Jul 11, 2014
1 parent 148b79a commit 0148897
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 233 deletions.
33 changes: 24 additions & 9 deletions lib/core/formatter.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
library angular.core_internal.formatter_map;

import 'dart:collection';
import 'package:di/di.dart';
import 'package:angular/core/annotation_src.dart';
import 'package:angular/core/registry.dart';
Expand All @@ -8,16 +9,30 @@ import 'package:angular/core/registry.dart';
* Registry of formatters at runtime.
*/
@Injectable()
class FormatterMap extends AnnotationMap<Formatter> {
Injector _injector;
FormatterMap(Injector injector, MetadataExtractor extractMetadata)
: this._injector = injector,
super(injector, extractMetadata);
class FormatterMap {
final Map<String, Type> _map = new HashMap<String, Type>();
final Injector _injector;

call(String name) {
var formatter = new Formatter(name: name);
var formatterType = this[formatter];
return _injector.get(formatterType);
FormatterMap(this._injector, MetadataExtractor extractMetadata) {
_injector.types.forEach((type) {
extractMetadata(type)
.where((annotation) => annotation is Formatter)
.forEach((Formatter formatter) {
_map[formatter.name] = type;
});
});
}

call(String name) => _injector.get(this[name]);

Type operator[](String name) {
Type formatterType = _map[name];
if (formatterType == null) throw "No formatter '$name' found!";
return formatterType;
}

void forEach(fn(K, Type)) {
_map.forEach(fn);
}
}

70 changes: 0 additions & 70 deletions lib/core/registry.dart
Original file line number Diff line number Diff line change
@@ -1,75 +1,5 @@
library angular.core.registry;

import 'package:di/di.dart' show Injector;

abstract class AnnotationMap<K> {
final Map<K, Type> _map = {};

AnnotationMap(Injector injector, MetadataExtractor extractMetadata) {
injector.types.forEach((type) {
extractMetadata(type)
.where((annotation) => annotation is K)
.forEach((annotation) {
_map[annotation] = type;
});
});
}

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);
}

List<K> annotationsFor(Type type) {
final res = <K>[];
forEach((ann, annType) {
if (annType == type) res.add(ann);
});
return res;
}
}

abstract class AnnotationsMap<K> {
final Map<K, List<Type>> map = {};

AnnotationsMap(Injector injector, MetadataExtractor extractMetadata) {
injector.types.forEach((type) {
extractMetadata(type)
.where((annotation) => annotation is K)
.forEach((annotation) {
map.putIfAbsent(annotation, () => []).add(type);
});
});
}

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);
});
});
}

List<K> annotationsFor(Type type) {
var res = <K>[];
forEach((ann, annType) {
if (annType == type) res.add(ann);
});
return res;
}
}

abstract class MetadataExtractor {
Iterable call(Type type);
}
2 changes: 1 addition & 1 deletion lib/core/registry_static.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
library angular.core_static;

import 'package:angular/core/annotation_src.dart' show Injectable;
import 'package:angular/core/annotation_src.dart';
import 'package:angular/core/registry.dart';

@Injectable()
Expand Down
42 changes: 36 additions & 6 deletions lib/core_dom/directive_map.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
part of angular.core.dom_internal;

class DirectiveTypeTuple {
final Directive directive;
final Type type;
DirectiveTypeTuple(this.directive, this.type);
toString() => '@$directive#$type';
}

@Injectable()
class DirectiveMap extends AnnotationsMap<Directive> {
class DirectiveMap {
final Map<String, List<DirectiveTypeTuple>> map = new HashMap<String, List<DirectiveTypeTuple>>();
DirectiveSelectorFactory _directiveSelectorFactory;
FormatterMap _formatters;
DirectiveSelector _selector;

DirectiveMap(Injector injector,
this._formatters,
MetadataExtractor metadataExtractor,
this._directiveSelectorFactory) {
injector.types.forEach((type) {
metadataExtractor(type)
.where((annotation) => annotation is Directive)
.forEach((Directive directive) {
map.putIfAbsent(directive.selector, () => []).add(new DirectiveTypeTuple(directive, type));
});
});
}

DirectiveSelector get selector {
if (_selector != null) return _selector;
return _selector = _directiveSelectorFactory.selector(this, _formatters);
}

DirectiveMap(Injector injector,
this._formatters,
MetadataExtractor metadataExtractor,
this._directiveSelectorFactory)
: super(injector, metadataExtractor);
List<DirectiveTypeTuple> operator[](String key) {
var value = map[key];
if (value == null) throw 'No Directive selector $key found!';
return value;
}

void forEach(fn(K, Type)) {
map.forEach((_, types) {
types.forEach((tuple) {
fn(tuple.directive, tuple.type);
});
});
}
}
18 changes: 9 additions & 9 deletions lib/core_dom/selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class DirectiveSelector {
}

if ((match = _CONTAINS_REGEXP.firstMatch(selector)) != null) {
textSelector.add(new _ContainsSelector(annotation, match[1]));
textSelector.add(new _ContainsSelector(selector, match[1]));
} else if ((match = _ATTR_CONTAINS_REGEXP.firstMatch(selector)) != null) {
attrSelector.add(new _ContainsSelector(annotation, match[1]));
attrSelector.add(new _ContainsSelector(selector, match[1]));
} else if ((selectorParts = _splitCss(selector, type)) != null){
elementSelector.addDirective(selectorParts, new _Directive(type, annotation));
} else {
Expand Down Expand Up @@ -97,12 +97,12 @@ class DirectiveSelector {
// this directive is matched on any attribute name, and so
// we need to pass the name to the directive by prefixing it to
// the value. Yes it is a bit of a hack.
_directives[selectorRegExp.annotation].forEach((type) {
_directives[selectorRegExp.selector].forEach((DirectiveTypeTuple tuple) {
// Pre-compute the AST to watch this value.
String expression = _interpolate(value);
AST valueAST = _astParser(expression, formatters: _formatters);
builder.addDirective(new DirectiveRef(
node, type, selectorRegExp.annotation, new Key(type), attrName, valueAST));
node, tuple.type, tuple.directive, new Key(tuple.type), attrName, valueAST));
});
}
}
Expand Down Expand Up @@ -135,13 +135,13 @@ class DirectiveSelector {
for (var k = 0; k < textSelector.length; k++) {
var selectorRegExp = textSelector[k];
if (selectorRegExp.regexp.hasMatch(value)) {
_directives[selectorRegExp.annotation].forEach((type) {
_directives[selectorRegExp.selector].forEach((tuple) {
// Pre-compute the AST to watch this value.
String expression = _interpolate(value);
var valueAST = _astParser(expression, formatters: _formatters);

builder.addDirective(new DirectiveRef(node, type,
selectorRegExp.annotation, new Key(type), value, valueAST));
builder.addDirective(new DirectiveRef(node, tuple.type,
tuple.directive, new Key(tuple.type), value, valueAST));
});
}
}
Expand Down Expand Up @@ -187,10 +187,10 @@ class _Directive {
}

class _ContainsSelector {
final Directive annotation;
final String selector;
final RegExp regexp;

_ContainsSelector(this.annotation, String regexp)
_ContainsSelector(this.selector, String regexp)
: regexp = new RegExp(regexp);
}

Expand Down
18 changes: 9 additions & 9 deletions test/core/core_directive_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ void main() {
});

it('should extract attr map from annotated component', (DirectiveMap directives) {
var annotations = directives.annotationsFor(AnnotatedIoComponent);
expect(annotations.length).toEqual(1);
expect(annotations[0] is Component).toBeTruthy();
var tuples = directives['annotated-io'];
expect(tuples.length).toEqual(1);
expect(tuples[0].directive is Component).toBeTruthy();

Component annotation = annotations[0];
Component annotation = tuples[0].directive;
expect(annotation.selector).toEqual('annotated-io');
expect(annotation.visibility).toEqual(Directive.LOCAL_VISIBILITY);
expect(annotation.exportExpressions).toEqual(['exportExpressions']);
Expand Down Expand Up @@ -77,11 +77,11 @@ void main() {
});

it("should extract attr map from annotated component which inherits other component", (DirectiveMap directives) {
var annotations = directives.annotationsFor(Sub);
expect(annotations.length).toEqual(1);
expect(annotations[0] is Directive).toBeTruthy();
var tupls = directives['[sub]'];
expect(tupls.length).toEqual(1);
expect(tupls[0].directive is Directive).toBeTruthy();

Directive annotation = annotations[0];
Directive annotation = tupls[0].directive;
expect(annotation.selector).toEqual('[sub]');
expect(annotation.map).toEqual({
"foo": "=>foo",
Expand Down Expand Up @@ -112,7 +112,7 @@ class NullParser implements Parser {
'foo': '=>foo'
})
class AnnotatedIoComponent {
static module() => new Module()..bind(String, toFactory: (i) => i.get(AnnotatedIoComponent),
static module(i) => i.bind(String, toFactory: (i) => i.get(AnnotatedIoComponent),
visibility: Directive.LOCAL_VISIBILITY);

AnnotatedIoComponent(Scope scope) {
Expand Down
12 changes: 6 additions & 6 deletions test/core/parser/parser_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ main() {
});

xdescribe('reserved words', () {
iit('should support reserved words in member get access', () {
it('should support reserved words in member get access', () {
for (String reserved in RESERVED_WORDS) {
expect(parser("o.$reserved").eval({ 'o': new Object() })).toEqual(null);
expect(parser("o.$reserved").eval({ 'o': { reserved: reserved }})).toEqual(reserved);
Expand Down Expand Up @@ -1139,10 +1139,10 @@ main() {
it('should parse formatters', () {
expect(() {
eval("1|nonexistent");
}).toThrow('No Formatter: nonexistent found!');
}).toThrow('No formatter \'nonexistent\' found!');
expect(() {
eval("1|nonexistent", formatters);
}).toThrow('No Formatter: nonexistent found!');
}).toThrow('No formatter \'nonexistent\' found!');

context['offset'] = 3;
expect(eval("'abcd'|substring:1:offset")).toEqual("bc");
Expand All @@ -1153,12 +1153,12 @@ main() {
var expression = parser("'World'|hello");
expect(() {
expression.eval({}, formatters);
}).toThrow('No Formatter: hello found!');
}).toThrow('No formatter \'hello\' found!');

var module = new Module()
..bind(FormatterMap)
..bind(HelloFormatter);
var childInjector = injector.createChild([module],
forceNewInstances: [FormatterMap]);
var childInjector = injector.createChild([module]);
var newFormatters = childInjector.get(FormatterMap);

expect(expression.eval({}, newFormatters)).toEqual('Hello, World!');
Expand Down
64 changes: 0 additions & 64 deletions test/core/registry_spec.dart

This file was deleted.

Loading

0 comments on commit 0148897

Please sign in to comment.