Skip to content

Commit

Permalink
feat(routing): allow routing to view html
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelgj committed Apr 16, 2014
1 parent 3be8dbd commit 42d12a5
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 110 deletions.
7 changes: 5 additions & 2 deletions lib/routing/ng_view.dart
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
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
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
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
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'));
});
});
}

0 comments on commit 42d12a5

Please sign in to comment.