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

feat(routing): allow routing to view html #908

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/routing/ng_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class NgViewDirective implements NgDetachAware, RouteProvider {
locationService._unregisterPortal(this);
}

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

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

var newDirectives = viewInjector.get(DirectiveMap);
viewCache.fromUrl(templateUrl, newDirectives).then((viewFactory) {
var viewFuture = viewDef.templateHtml != null ?
new Future.value(viewCache.fromHtml(viewDef.templateHtml, newDirectives)) :
viewCache.fromUrl(viewDef.template, newDirectives);
viewFuture.then((viewFactory) {
_cleanUp();
_scope = scope.createChild(new PrototypeMap(scope.context));
_view = viewFactory(
Expand Down
37 changes: 21 additions & 16 deletions lib/routing/routing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ class RouteViewFactory {
(RouteEnterEvent event) => _enterHandler(event, templateUrl);

_enterHandler(RouteEnterEvent event, String templateUrl,
[List<Module> modules]) =>
{List<Module> modules, String templateHtml}) =>
locationService._route(event.route, templateUrl, fromEvent: true,
modules: modules);
modules: modules, templateHtml: templateHtml);

configure(Map<String, NgRouteCfg> config) =>
_configure(locationService.router.root, config);
Expand All @@ -28,8 +28,9 @@ class RouteViewFactory {
path: cfg.path,
defaultRoute: cfg.defaultRoute,
enter: (RouteEnterEvent e) {
if (cfg.view != null) {
_enterHandler(e, cfg.view, newModules);
if (cfg.view != null || cfg.viewHtml != null) {
_enterHandler(e, cfg.view,
modules: newModules, templateHtml: cfg.viewHtml);
}
if (cfg.enter != null) {
cfg.enter(e);
Expand Down Expand Up @@ -62,25 +63,27 @@ class RouteViewFactory {
}
}

NgRouteCfg ngRoute({String path, String view, Map<String, NgRouteCfg> mount,
modules(), bool defaultRoute: false, RoutePreEnterEventHandler preEnter,
RouteEnterEventHandler enter, RouteLeaveEventHandler leave}) =>
new NgRouteCfg(path: path, view: view, mount: mount, modules: modules,
defaultRoute: defaultRoute, preEnter: preEnter, enter: enter,
leave: leave);
NgRouteCfg ngRoute({String path, String view, String viewHtml,
Map<String, NgRouteCfg> mount, modules(), bool defaultRoute: false,
RoutePreEnterEventHandler preEnter, RouteEnterEventHandler enter,
RouteLeaveEventHandler leave}) =>
new NgRouteCfg(path: path, view: view, viewHtml: viewHtml, mount: mount,
modules: modules, defaultRoute: defaultRoute, preEnter: preEnter,
enter: enter, leave: leave);

class NgRouteCfg {
final String path;
final String view;
final String viewHtml;
final Map<String, NgRouteCfg> mount;
final Function modules;
final bool defaultRoute;
final RouteEnterEventHandler enter;
final RoutePreEnterEventHandler preEnter;
final RouteLeaveEventHandler leave;

NgRouteCfg({this.view, this.path, this.mount, this.modules, this.defaultRoute,
this.enter, this.preEnter, this.leave});
NgRouteCfg({this.view, this.viewHtml, this.path, this.mount, this.modules,
this.defaultRoute, this.enter, this.preEnter, this.leave});
}

/**
Expand Down Expand Up @@ -159,15 +162,16 @@ class NgRoutingHelper {
_routePath(route).startsWith(_routePath(v._route));
}, orElse: () => null);
if (view != null && !alreadyActiveViews.contains(view)) {
view._show(templateUrl, route, viewDef.modules);
view._show(viewDef, route, viewDef.modules);
alreadyActiveViews.add(view);
break;
}
}
}

_route(Route route, String template, {bool fromEvent, List<Module> modules}) {
_templates[_routePath(route)] = new _View(template, modules);
_route(Route route, String template, {bool fromEvent, List<Module> modules,
String templateHtml}) {
_templates[_routePath(route)] = new _View(template, templateHtml, modules);
}

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

class _View {
final String template;
final String templateHtml;
final List<Module> modules;

_View(this.template, this.modules);
_View(this.template, this.templateHtml, this.modules);
}

String _routePath(Route route) {
Expand Down
5 changes: 3 additions & 2 deletions lib/tools/html_extractor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ RegExp _MUSTACHE_REGEXP = new RegExp(r'{{([^}]*)}}');
RegExp _NG_REPEAT_SYNTAX = new RegExp(r'^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$');

class HtmlExpressionExtractor {
List<DirectiveInfo> directiveInfos;
Iterable<DirectiveInfo> directiveInfos;

HtmlExpressionExtractor(this.directiveInfos) {
for (DirectiveInfo directiveInfo in directiveInfos) {
Expand Down Expand Up @@ -55,7 +55,8 @@ class HtmlExpressionExtractor {
}

for (DirectiveInfo directiveInfo in directiveInfos) {
if (matchesNode(node, directiveInfo.selector)) {
if (directiveInfo.selector != null &&
matchesNode(node, directiveInfo.selector)) {
directiveInfo.expressionAttrs.forEach((attr) {
if (node.attributes[attr] != null && attr != 'ng-repeat') {
expressions.add(node.attributes[attr]);
Expand Down
178 changes: 103 additions & 75 deletions lib/tools/source_metadata_extractor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,92 +107,120 @@ class SourceMetadataExtractor {
});
dirInfo.expressionAttrs = reprocessedAttrs;
directives.add(dirInfo);

});

directives.addAll(metadataVisitor.templates.map(
(tmpl) => new DirectiveInfo()..template = tmpl));

return directives;
}
}

class DirectiveMetadataCollectingVisitor {
List<DirectiveMetadata> metadata = <DirectiveMetadata>[];
class DirectiveMetadataCollectingAstVisitor extends RecursiveAstVisitor {
final List<DirectiveMetadata> metadata;
final List<String> templates;

DirectiveMetadataCollectingAstVisitor(this.metadata, this.templates);

visitMethodInvocation(MethodInvocation node) {
if (node.methodName.name == 'ngRoute') {
NamedExpression viewHtmlExpression =
node.argumentList.arguments
.firstWhere((e) => e is NamedExpression &&
e.name.label.name == 'viewHtml', orElse: () => null);
if (viewHtmlExpression != null) {
if (viewHtmlExpression.expression is! StringLiteral) {
throw 'viewHtml must be a string literal';
}
templates.add(
(viewHtmlExpression.expression as StringLiteral).stringValue);
}
}
super.visitMethodInvocation(node);
}

call(CompilationUnit cu) {
cu.declarations.forEach((CompilationUnitMember declaration) {
// We only care about classes.
if (declaration is! ClassDeclaration) return;
ClassDeclaration clazz = declaration;
// Check class annotations for presense of NgComponent/NgDirective.
DirectiveMetadata meta;
clazz.metadata.forEach((Annotation ann) {
if (ann.arguments == null) return; // Ignore non-class annotations.
// TODO(pavelj): this is not a safe check for the type of the
// annotations, but good enough for now.
if (ann.name.name != 'NgComponent'
&& ann.name.name != 'NgDirective') return;

bool isComponent = ann.name.name == 'NgComponent';

meta = new DirectiveMetadata()
..className = clazz.name.name
..type = isComponent ? COMPONENT : DIRECTIVE;
metadata.add(meta);

ann.arguments.arguments.forEach((Expression arg) {
if (arg is NamedExpression) {
NamedExpression namedArg = arg;
var paramName = namedArg.name.label.name;
if (paramName == 'selector') {
meta.selector = assertString(namedArg.expression).stringValue;
}
if (paramName == 'template') {
meta.template = assertString(namedArg.expression).stringValue;
}
if (paramName == 'map') {
MapLiteral map = namedArg.expression;
map.entries.forEach((MapLiteralEntry entry) {
meta.attributeMappings[assertString(entry.key).stringValue] =
assertString(entry.value).stringValue;
});
}
if (paramName == 'exportExpressions') {
meta.exportExpressions = getStringValues(namedArg.expression);
}
if (paramName == 'exportExpressionAttrs') {
meta.exportExpressionAttrs = getStringValues(namedArg.expression);
}
visitClassDeclaration(ClassDeclaration clazz) {
// Check class annotations for presense of NgComponent/NgDirective.
DirectiveMetadata meta;
clazz.metadata.forEach((Annotation ann) {
if (ann.arguments == null) return; // Ignore non-class annotations.
// TODO(pavelj): this is not a safe check for the type of the
// annotations, but good enough for now.
if (ann.name.name != 'NgComponent'
&& ann.name.name != 'NgDirective') return;

bool isComponent = ann.name.name == 'NgComponent';

meta = new DirectiveMetadata()
..className = clazz.name.name
..type = isComponent ? COMPONENT : DIRECTIVE;
metadata.add(meta);

ann.arguments.arguments.forEach((Expression arg) {
if (arg is NamedExpression) {
NamedExpression namedArg = arg;
var paramName = namedArg.name.label.name;
if (paramName == 'selector') {
meta.selector = assertString(namedArg.expression).stringValue;
}
});
});

// Check fields/getters/setter for presense of attr mapping annotations.
if (meta != null) {
clazz.members.forEach((ClassMember member) {
if (member is FieldDeclaration ||
(member is MethodDeclaration &&
(member.isSetter || member.isGetter))) {
member.metadata.forEach((Annotation ann) {
if (_attrAnnotationsToSpec.containsKey(ann.name.name)) {
String fieldName;
if (member is FieldDeclaration) {
fieldName = member.fields.variables.first.name.name;
} else { // MethodDeclaration
fieldName = (member as MethodDeclaration).name.name;
}
StringLiteral attNameLiteral = ann.arguments.arguments.first;
if (meta.attributeMappings
.containsKey(attNameLiteral.stringValue)) {
throw 'Attribute mapping already defined for '
'${clazz.name}.$fieldName';
}
meta.attributeMappings[attNameLiteral.stringValue] =
_attrAnnotationsToSpec[ann.name.name] + fieldName;
}
if (paramName == 'template') {
meta.template = assertString(namedArg.expression).stringValue;
}
if (paramName == 'map') {
MapLiteral map = namedArg.expression;
map.entries.forEach((MapLiteralEntry entry) {
meta.attributeMappings[assertString(entry.key).stringValue] =
assertString(entry.value).stringValue;
});
}
});
}
if (paramName == 'exportExpressions') {
meta.exportExpressions = getStringValues(namedArg.expression);
}
if (paramName == 'exportExpressionAttrs') {
meta.exportExpressionAttrs = getStringValues(namedArg.expression);
}
}
});
});

// Check fields/getters/setter for presense of attr mapping annotations.
if (meta != null) {
clazz.members.forEach((ClassMember member) {
if (member is FieldDeclaration ||
(member is MethodDeclaration &&
(member.isSetter || member.isGetter))) {
member.metadata.forEach((Annotation ann) {
if (_attrAnnotationsToSpec.containsKey(ann.name.name)) {
String fieldName;
if (member is FieldDeclaration) {
fieldName = member.fields.variables.first.name.name;
} else { // MethodDeclaration
fieldName = (member as MethodDeclaration).name.name;
}
StringLiteral attNameLiteral = ann.arguments.arguments.first;
if (meta.attributeMappings
.containsKey(attNameLiteral.stringValue)) {
throw 'Attribute mapping already defined for '
'${clazz.name}.$fieldName';
}
meta.attributeMappings[attNameLiteral.stringValue] =
_attrAnnotationsToSpec[ann.name.name] + fieldName;
}
});
}
});
}

return super.visitClassDeclaration(clazz);
}
}

class DirectiveMetadataCollectingVisitor {
List<DirectiveMetadata> metadata = <DirectiveMetadata>[];
List<String> templates = <String>[];

call(CompilationUnit cu) {
cu.accept(new DirectiveMetadataCollectingAstVisitor(metadata, templates));
}
}

Expand Down
19 changes: 15 additions & 4 deletions test/io/expression_extractor_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import 'package:unittest/unittest.dart';

void main() {
describe('expression_extractor', () {
it('should extract all expressions from source and templates', () {
Module module = new Module();

Iterable<String> _extractExpressions(file) {
Module module = new Module();
Injector injector = new DynamicInjector(modules: [module],
allowImplicitInjection: true);

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

var expressions = htmlExtractor.expressions;
return htmlExtractor.expressions;
}

it('should extract all expressions from source and templates', () {
var expressions = _extractExpressions('test/io/test_files/main.dart');

expect(expressions, unorderedEquals([
'ctrl.expr',
'ctrl.anotherExpression',
Expand All @@ -45,5 +50,11 @@ void main() {
'ctrl.if'
]));
});

it('should extract expressions from ngRoute viewHtml', () {
var expressions = _extractExpressions('test/io/test_files/routing.dart');
expect(expressions, contains('foo'));
expect(expressions, contains('bar'));
});
});
}
Loading