Skip to content

Commit dd65866

Browse files
committed
feat(routing): allow routing to view html
Fixes dart-archive#425
1 parent 3be8dbd commit dd65866

10 files changed

+231
-110
lines changed

lib/routing/ng_view.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
100100
locationService._unregisterPortal(this);
101101
}
102102

103-
_show(String templateUrl, Route route, List<Module> modules) {
103+
_show(_View viewDef, Route route, List<Module> modules) {
104104
assert(route.isActive);
105105

106106
if (_viewRoute != null) return;
@@ -120,7 +120,10 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
120120
}
121121

122122
var newDirectives = viewInjector.get(DirectiveMap);
123-
viewCache.fromUrl(templateUrl, newDirectives).then((viewFactory) {
123+
var viewFuture = viewDef.templateHtml != null ?
124+
new Future.value(viewCache.fromHtml(viewDef.templateHtml, newDirectives)) :
125+
viewCache.fromUrl(viewDef.template, newDirectives);
126+
viewFuture.then((viewFactory) {
124127
_cleanUp();
125128
_scope = scope.createChild(new PrototypeMap(scope.context));
126129
_view = viewFactory(

lib/routing/routing.dart

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class RouteViewFactory {
1212
(RouteEnterEvent event) => _enterHandler(event, templateUrl);
1313

1414
_enterHandler(RouteEnterEvent event, String templateUrl,
15-
[List<Module> modules]) =>
15+
{List<Module> modules, String templateHtml}) =>
1616
locationService._route(event.route, templateUrl, fromEvent: true,
17-
modules: modules);
17+
modules: modules, templateHtml: templateHtml);
1818

1919
configure(Map<String, NgRouteCfg> config) =>
2020
_configure(locationService.router.root, config);
@@ -28,8 +28,9 @@ class RouteViewFactory {
2828
path: cfg.path,
2929
defaultRoute: cfg.defaultRoute,
3030
enter: (RouteEnterEvent e) {
31-
if (cfg.view != null) {
32-
_enterHandler(e, cfg.view, newModules);
31+
if (cfg.view != null || cfg.viewHtml != null) {
32+
_enterHandler(e, cfg.view,
33+
modules: newModules, templateHtml: cfg.viewHtml);
3334
}
3435
if (cfg.enter != null) {
3536
cfg.enter(e);
@@ -62,25 +63,27 @@ class RouteViewFactory {
6263
}
6364
}
6465

65-
NgRouteCfg ngRoute({String path, String view, Map<String, NgRouteCfg> mount,
66-
modules(), bool defaultRoute: false, RoutePreEnterEventHandler preEnter,
67-
RouteEnterEventHandler enter, RouteLeaveEventHandler leave}) =>
68-
new NgRouteCfg(path: path, view: view, mount: mount, modules: modules,
69-
defaultRoute: defaultRoute, preEnter: preEnter, enter: enter,
70-
leave: leave);
66+
NgRouteCfg ngRoute({String path, String view, String viewHtml,
67+
Map<String, NgRouteCfg> mount, modules(), bool defaultRoute: false,
68+
RoutePreEnterEventHandler preEnter, RouteEnterEventHandler enter,
69+
RouteLeaveEventHandler leave}) =>
70+
new NgRouteCfg(path: path, view: view, viewHtml: viewHtml, mount: mount,
71+
modules: modules, defaultRoute: defaultRoute, preEnter: preEnter,
72+
enter: enter, leave: leave);
7173

7274
class NgRouteCfg {
7375
final String path;
7476
final String view;
77+
final String viewHtml;
7578
final Map<String, NgRouteCfg> mount;
7679
final Function modules;
7780
final bool defaultRoute;
7881
final RouteEnterEventHandler enter;
7982
final RoutePreEnterEventHandler preEnter;
8083
final RouteLeaveEventHandler leave;
8184

82-
NgRouteCfg({this.view, this.path, this.mount, this.modules, this.defaultRoute,
83-
this.enter, this.preEnter, this.leave});
85+
NgRouteCfg({this.view, this.viewHtml, this.path, this.mount, this.modules,
86+
this.defaultRoute, this.enter, this.preEnter, this.leave});
8487
}
8588

8689
/**
@@ -159,15 +162,16 @@ class NgRoutingHelper {
159162
_routePath(route).startsWith(_routePath(v._route));
160163
}, orElse: () => null);
161164
if (view != null && !alreadyActiveViews.contains(view)) {
162-
view._show(templateUrl, route, viewDef.modules);
165+
view._show(viewDef, route, viewDef.modules);
163166
alreadyActiveViews.add(view);
164167
break;
165168
}
166169
}
167170
}
168171

169-
_route(Route route, String template, {bool fromEvent, List<Module> modules}) {
170-
_templates[_routePath(route)] = new _View(template, modules);
172+
_route(Route route, String template, {bool fromEvent, List<Module> modules,
173+
String templateHtml}) {
174+
_templates[_routePath(route)] = new _View(template, templateHtml, modules);
171175
}
172176

173177
_registerPortal(NgViewDirective ngView) {
@@ -181,9 +185,10 @@ class NgRoutingHelper {
181185

182186
class _View {
183187
final String template;
188+
final String templateHtml;
184189
final List<Module> modules;
185190

186-
_View(this.template, this.modules);
191+
_View(this.template, this.templateHtml, this.modules);
187192
}
188193

189194
String _routePath(Route route) {

lib/tools/html_extractor.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ RegExp _MUSTACHE_REGEXP = new RegExp(r'{{([^}]*)}}');
1313
RegExp _NG_REPEAT_SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$');
1414

1515
class HtmlExpressionExtractor {
16-
List<DirectiveInfo> directiveInfos;
16+
Iterable<DirectiveInfo> directiveInfos;
1717

1818
HtmlExpressionExtractor(this.directiveInfos) {
1919
for (DirectiveInfo directiveInfo in directiveInfos) {
@@ -55,7 +55,8 @@ class HtmlExpressionExtractor {
5555
}
5656

5757
for (DirectiveInfo directiveInfo in directiveInfos) {
58-
if (matchesNode(node, directiveInfo.selector)) {
58+
if (directiveInfo.selector != null &&
59+
matchesNode(node, directiveInfo.selector)) {
5960
directiveInfo.expressionAttrs.forEach((attr) {
6061
if (node.attributes[attr] != null && attr != 'ng-repeat') {
6162
expressions.add(node.attributes[attr]);

lib/tools/source_metadata_extractor.dart

Lines changed: 103 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -107,92 +107,120 @@ class SourceMetadataExtractor {
107107
});
108108
dirInfo.expressionAttrs = reprocessedAttrs;
109109
directives.add(dirInfo);
110-
111110
});
112111

112+
directives.addAll(metadataVisitor.templates.map(
113+
(tmpl) => new DirectiveInfo()..template = tmpl));
114+
113115
return directives;
114116
}
115117
}
116118

117-
class DirectiveMetadataCollectingVisitor {
118-
List<DirectiveMetadata> metadata = <DirectiveMetadata>[];
119+
class DirectiveMetadataCollectingAstVisitor extends RecursiveAstVisitor {
120+
final List<DirectiveMetadata> metadata;
121+
final List<String> templates;
122+
123+
DirectiveMetadataCollectingAstVisitor(this.metadata, this.templates);
124+
125+
visitMethodInvocation(MethodInvocation node) {
126+
if (node.methodName.name == 'ngRoute') {
127+
NamedExpression viewHtmlExpression =
128+
node.argumentList.arguments
129+
.firstWhere((e) => e is NamedExpression &&
130+
e.name.label.name == 'viewHtml', orElse: () => null);
131+
if (viewHtmlExpression != null) {
132+
if (viewHtmlExpression.expression is! StringLiteral) {
133+
throw 'viewHtml must be a string literal';
134+
}
135+
templates.add(
136+
(viewHtmlExpression.expression as StringLiteral).stringValue);
137+
}
138+
}
139+
super.visitMethodInvocation(node);
140+
}
119141

120-
call(CompilationUnit cu) {
121-
cu.declarations.forEach((CompilationUnitMember declaration) {
122-
// We only care about classes.
123-
if (declaration is! ClassDeclaration) return;
124-
ClassDeclaration clazz = declaration;
125-
// Check class annotations for presense of NgComponent/NgDirective.
126-
DirectiveMetadata meta;
127-
clazz.metadata.forEach((Annotation ann) {
128-
if (ann.arguments == null) return; // Ignore non-class annotations.
129-
// TODO(pavelj): this is not a safe check for the type of the
130-
// annotations, but good enough for now.
131-
if (ann.name.name != 'NgComponent'
132-
&& ann.name.name != 'NgDirective') return;
133-
134-
bool isComponent = ann.name.name == 'NgComponent';
135-
136-
meta = new DirectiveMetadata()
137-
..className = clazz.name.name
138-
..type = isComponent ? COMPONENT : DIRECTIVE;
139-
metadata.add(meta);
140-
141-
ann.arguments.arguments.forEach((Expression arg) {
142-
if (arg is NamedExpression) {
143-
NamedExpression namedArg = arg;
144-
var paramName = namedArg.name.label.name;
145-
if (paramName == 'selector') {
146-
meta.selector = assertString(namedArg.expression).stringValue;
147-
}
148-
if (paramName == 'template') {
149-
meta.template = assertString(namedArg.expression).stringValue;
150-
}
151-
if (paramName == 'map') {
152-
MapLiteral map = namedArg.expression;
153-
map.entries.forEach((MapLiteralEntry entry) {
154-
meta.attributeMappings[assertString(entry.key).stringValue] =
155-
assertString(entry.value).stringValue;
156-
});
157-
}
158-
if (paramName == 'exportExpressions') {
159-
meta.exportExpressions = getStringValues(namedArg.expression);
160-
}
161-
if (paramName == 'exportExpressionAttrs') {
162-
meta.exportExpressionAttrs = getStringValues(namedArg.expression);
163-
}
142+
visitClassDeclaration(ClassDeclaration clazz) {
143+
// Check class annotations for presense of NgComponent/NgDirective.
144+
DirectiveMetadata meta;
145+
clazz.metadata.forEach((Annotation ann) {
146+
if (ann.arguments == null) return; // Ignore non-class annotations.
147+
// TODO(pavelj): this is not a safe check for the type of the
148+
// annotations, but good enough for now.
149+
if (ann.name.name != 'NgComponent'
150+
&& ann.name.name != 'NgDirective') return;
151+
152+
bool isComponent = ann.name.name == 'NgComponent';
153+
154+
meta = new DirectiveMetadata()
155+
..className = clazz.name.name
156+
..type = isComponent ? COMPONENT : DIRECTIVE;
157+
metadata.add(meta);
158+
159+
ann.arguments.arguments.forEach((Expression arg) {
160+
if (arg is NamedExpression) {
161+
NamedExpression namedArg = arg;
162+
var paramName = namedArg.name.label.name;
163+
if (paramName == 'selector') {
164+
meta.selector = assertString(namedArg.expression).stringValue;
164165
}
165-
});
166-
});
167-
168-
// Check fields/getters/setter for presense of attr mapping annotations.
169-
if (meta != null) {
170-
clazz.members.forEach((ClassMember member) {
171-
if (member is FieldDeclaration ||
172-
(member is MethodDeclaration &&
173-
(member.isSetter || member.isGetter))) {
174-
member.metadata.forEach((Annotation ann) {
175-
if (_attrAnnotationsToSpec.containsKey(ann.name.name)) {
176-
String fieldName;
177-
if (member is FieldDeclaration) {
178-
fieldName = member.fields.variables.first.name.name;
179-
} else { // MethodDeclaration
180-
fieldName = (member as MethodDeclaration).name.name;
181-
}
182-
StringLiteral attNameLiteral = ann.arguments.arguments.first;
183-
if (meta.attributeMappings
184-
.containsKey(attNameLiteral.stringValue)) {
185-
throw 'Attribute mapping already defined for '
186-
'${clazz.name}.$fieldName';
187-
}
188-
meta.attributeMappings[attNameLiteral.stringValue] =
189-
_attrAnnotationsToSpec[ann.name.name] + fieldName;
190-
}
166+
if (paramName == 'template') {
167+
meta.template = assertString(namedArg.expression).stringValue;
168+
}
169+
if (paramName == 'map') {
170+
MapLiteral map = namedArg.expression;
171+
map.entries.forEach((MapLiteralEntry entry) {
172+
meta.attributeMappings[assertString(entry.key).stringValue] =
173+
assertString(entry.value).stringValue;
191174
});
192175
}
193-
});
194-
}
176+
if (paramName == 'exportExpressions') {
177+
meta.exportExpressions = getStringValues(namedArg.expression);
178+
}
179+
if (paramName == 'exportExpressionAttrs') {
180+
meta.exportExpressionAttrs = getStringValues(namedArg.expression);
181+
}
182+
}
183+
});
195184
});
185+
186+
// Check fields/getters/setter for presense of attr mapping annotations.
187+
if (meta != null) {
188+
clazz.members.forEach((ClassMember member) {
189+
if (member is FieldDeclaration ||
190+
(member is MethodDeclaration &&
191+
(member.isSetter || member.isGetter))) {
192+
member.metadata.forEach((Annotation ann) {
193+
if (_attrAnnotationsToSpec.containsKey(ann.name.name)) {
194+
String fieldName;
195+
if (member is FieldDeclaration) {
196+
fieldName = member.fields.variables.first.name.name;
197+
} else { // MethodDeclaration
198+
fieldName = (member as MethodDeclaration).name.name;
199+
}
200+
StringLiteral attNameLiteral = ann.arguments.arguments.first;
201+
if (meta.attributeMappings
202+
.containsKey(attNameLiteral.stringValue)) {
203+
throw 'Attribute mapping already defined for '
204+
'${clazz.name}.$fieldName';
205+
}
206+
meta.attributeMappings[attNameLiteral.stringValue] =
207+
_attrAnnotationsToSpec[ann.name.name] + fieldName;
208+
}
209+
});
210+
}
211+
});
212+
}
213+
214+
return super.visitClassDeclaration(clazz);
215+
}
216+
}
217+
218+
class DirectiveMetadataCollectingVisitor {
219+
List<DirectiveMetadata> metadata = <DirectiveMetadata>[];
220+
List<String> templates = <String>[];
221+
222+
call(CompilationUnit cu) {
223+
cu.accept(new DirectiveMetadataCollectingAstVisitor(metadata, templates));
196224
}
197225
}
198226

test/io/expression_extractor_spec.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import 'package:unittest/unittest.dart';
1313

1414
void main() {
1515
describe('expression_extractor', () {
16-
it('should extract all expressions from source and templates', () {
17-
Module module = new Module();
1816

17+
List<String> _extractExpressions(file) {
18+
Module module = new Module();
1919
Injector injector = new DynamicInjector(modules: [module],
2020
allowImplicitInjection: true);
2121

@@ -24,11 +24,16 @@ void main() {
2424
var sourceMetadataExtractor = new SourceMetadataExtractor();
2525
List<DirectiveInfo> directives =
2626
sourceMetadataExtractor
27-
.gatherDirectiveInfo('test/io/test_files/main.dart', sourceCrawler);
27+
.gatherDirectiveInfo(file, sourceCrawler);
2828
var htmlExtractor = new HtmlExpressionExtractor(directives);
2929
htmlExtractor.crawl('test/io/test_files/', ioService);
3030

31-
var expressions = htmlExtractor.expressions;
31+
return htmlExtractor.expressions;
32+
}
33+
34+
it('should extract all expressions from source and templates', () {
35+
var expressions = _extractExpressions('test/io/test_files/main.dart');
36+
3237
expect(expressions, unorderedEquals([
3338
'ctrl.expr',
3439
'ctrl.anotherExpression',
@@ -45,5 +50,11 @@ void main() {
4550
'ctrl.if'
4651
]));
4752
});
53+
54+
it('should extract expressions from ngRoute viewHtml', () {
55+
var expressions = _extractExpressions('test/io/test_files/routing.dart');
56+
expect(expressions, contains('foo'));
57+
expect(expressions, contains('bar'));
58+
});
4859
});
4960
}

0 commit comments

Comments
 (0)