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

Commit

Permalink
Merge pull request #12 from pavelgj/directive-scoping
Browse files Browse the repository at this point in the history
Implemented directive scoping
  • Loading branch information
pavelgj committed Jun 22, 2013
2 parents 8d98d0b + e83854a commit 49c9bde
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 46 deletions.
10 changes: 10 additions & 0 deletions lib/angular.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
library angular;

import "dart:collection";
import "dart:mirrors";
import "dart:async" as async;
import "dart:json" as json;
Expand Down Expand Up @@ -110,6 +111,15 @@ class AngularModule extends Module {

AngularModule() {
value(DirectiveRegistry, _directives);
type(Compiler, Compiler);
type(BlockFactory, BlockFactory);
type(BlockTypeFactory, BlockTypeFactory);
type(BlockListFactory, BlockListFactory);
type(ExceptionHandler, ExceptionHandler);
type(Scope, Scope);
type(Parser, Parser);
type(Interpolate, Interpolate);
type(Http, Http);
}

directive(Type directive) {
Expand Down
135 changes: 96 additions & 39 deletions lib/block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class BlockCache {

flush([Function callback]) {
groupCache.forEach((blocks) {
while(!blocks.isEmpty) {
while(blocks.isNotEmpty) {
Block block = blocks.removeLast();
if (?callback) callback(block);
if (callback != null) callback(block);
}
});
}
Expand Down Expand Up @@ -84,10 +84,10 @@ class Block implements ElementWrapper {
ASSERT(elements != null);
ASSERT(directivePositions != null);
ASSERT(blockCaches != null);
_link(elements, directivePositions, blockCaches);
_link(elements, directivePositions, blockCaches, $injector);
}

_link(List<dom.Node> nodeList, List directivePositions, List<BlockCache> blockCaches) {
_link(List<dom.Node> nodeList, List directivePositions, List<BlockCache> blockCaches, Injector parentInjector) {
var stack;
try {throw '';} catch(e,s) {stack = s;}
var preRenderedIndexOffset = 0;
Expand All @@ -112,7 +112,7 @@ class Block implements ElementWrapper {

Map<String, BlockListFactory> anchorsByName = {};
List<String> directiveNames = [];

var injector = parentInjector;
if (directiveRefs != null) {
for (var j = 0, jj = directiveRefs.length; j < jj; j++) {
var blockCache;
Expand All @@ -135,10 +135,10 @@ class Block implements ElementWrapper {
anchorsByName[name] = $blockListFactory([node], directiveRef.blockTypes, blockCache);
}
}
_instantiateDirectives(directiveDefsByName, directiveNames, node, anchorsByName);
injector = _instantiateDirectives(directiveDefsByName, directiveNames, node, anchorsByName, parentInjector);
}
if (childDirectivePositions != null) {
_link(node.nodes, childDirectivePositions, blockCaches);
_link(node.nodes, childDirectivePositions, blockCaches, injector);
}

if (fakeParent) {
Expand All @@ -148,10 +148,11 @@ class Block implements ElementWrapper {
}
}

_instantiateDirectives(Map<String, DirectiveRef> directiveDefsByName,
Injector _instantiateDirectives(Map<String, DirectiveRef> directiveDefsByName,
List<String> directiveNames,
dom.Node node,
Map<String, BlockList> anchorsByName) {
Map<String, BlockList> anchorsByName,
Injector parentInjector) {
var elementModule = new Module();
elementModule.value(Block, this);
elementModule.value(dom.Element, node);
Expand All @@ -160,45 +161,88 @@ class Block implements ElementWrapper {
def.directive.type, def.directive.type));

for (var i = 0, ii = directiveNames.length; i < ii; i++) {
var directiveName = directiveNames[i];
DirectiveRef directiveRef = directiveDefsByName[directiveName];

var directiveModule = new Module();

directiveModule.value(DirectiveValue,
new DirectiveValue.fromString(directiveRef.value));

directiveModule.value(BlockList, anchorsByName[directiveName]);

DirectiveRef directiveRef = directiveDefsByName[directiveNames[i]];
Type directiveType = directiveRef.directive.type;
var visibility = local;
if (directiveRef.directive.$visibility == DirectiveVisibility.CHILDREN) {
visibility = null;
} else if (directiveRef.directive.$visibility == DirectiveVisibility.DIRECT_CHILDREN) {
visibility = directChildren;
}
elementModule.type(directiveType, directiveType, creation: directOnly, visibility: visibility);
}

var injector = $injector.createChild(
[elementModule, directiveModule],
[directiveType]);

try {
var directiveInstance = injector.get(directiveType);
if (directiveRef.directive.isComponent) {
directiveInstance = new ComponentWrapper(directiveRef, directiveInstance, node,
$injector.get(Parser), $injector.get(Compiler), $injector.get(Http));
var injector = parentInjector.createChild([elementModule]);

int prevInstantiatedCount;
List<String> alreadyInstantiated = <String>[];
// TODO(pavelgj): this is a workaround for the lack of directive
// instantiation ordering. A better way is to sort directives in the
// order they must be instantiated in.
do {
prevInstantiatedCount = alreadyInstantiated.length;
for (var i = 0, ii = directiveNames.length; i < ii; i++) {
var directiveName = directiveNames[i];
if (alreadyInstantiated.contains(directiveName)) continue;
DirectiveRef directiveRef = directiveDefsByName[directiveName];

Map<Type, dynamic> locals = new HashMap<Type, dynamic>();
locals[DirectiveValue] =
new DirectiveValue.fromString(directiveRef.value);
locals[BlockList] = anchorsByName[directiveName];

Type directiveType = directiveRef.directive.type;

try {
var directiveInstance = injector.instantiate(directiveType, locals);
alreadyInstantiated.add(directiveName);
if (directiveRef.directive.isComponent) {
directiveInstance = new ComponentWrapper(directiveRef, directiveInstance, node,
$injector.get(Parser), $injector.get(Compiler), $injector.get(Http));

}
directives.add(directiveInstance);
} catch (e,s) {
var msg;
if (e is MirroredUncaughtExceptionError) {
//TODO(misko): why is this here? Injector should never throw this exception
msg = e.exception_string + "\n ORIGINAL Stack trace:\n" + e.stacktrace.toString();
} else {
msg = "Creating $directiveName: " + e.toString() +
}
directives.add(directiveInstance);
} catch (e, s) {
if (e is MirroredUncaughtExceptionError) {
//TODO(misko): why is this here? Injector should never throw this exception
throw e.exception_string + "\n ORIGINAL Stack trace:\n" + e.stacktrace.toString();
} else if (e is IndirectInstantiationError) {
// ignore.
} else {
throw "Creating $directiveName: " + e.toString() +
"\n ORIGINAL Stack trace:\n" + s.toString();
}
}

throw msg;
}
} while(alreadyInstantiated.length != prevInstantiatedCount);

if (alreadyInstantiated.length != directiveNames.length) {
throw 'Cyclic dependency in directives on $node.';
}
return injector;
}


/// DI creation strategy that only allows 'explicit' injection.
dynamic directOnly(Symbol type,
Injector requesting,
Injector defining,
bool directInstantation,
Factory factory) {
if (!directInstantation) {
throw new IndirectInstantiationError(type);
}
return factory();
}

/// DI visibility callback allowin node-local visibility.
bool local(Injector requesting, Injector defining) =>
identical(requesting, defining);

/// DI visibility callback allowin visibility from direct child into parent.
bool directChildren(Injector requesting, Injector defining) =>
local(requesting, defining) || identical(requesting.parent, defining);

attach(Scope scope) {
// Attach directives
for(var i = 0, ii = directives.length; i < ii; i++) {
Expand Down Expand Up @@ -336,6 +380,19 @@ class Block implements ElementWrapper {
}
}

class IndirectInstantiationError {
IndirectInstantiationError(type)
: exception_string = '$type must be directly instantated before being '
'injected from child injection';

/** The result of toString() for the exception object. */
final String exception_string;

String toString() {
return exception_string;
}
}

class ComponentWrapper {
DirectiveRef directiveRef;
dynamic controller;
Expand Down
11 changes: 11 additions & 0 deletions lib/directive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Directive {
String $templateUrl;
String $cssUrl;
Map<String, String> $map;
String $visibility;

bool isComponent = false;
bool isStructural = false;
Expand Down Expand Up @@ -46,6 +47,10 @@ class Directive {
$templateUrl = reflectStaticField(type, '\$templateUrl');
$cssUrl = reflectStaticField(type, '\$cssUrl');
$priority = reflectStaticField(type, '\$priority');
$visibility = reflectStaticField(type, '\$visibility');
if ($visibility == null) {
$visibility = DirectiveVisibility.LOCAL;
}
$map = reflectStaticField(type, '\$map');
if ($priority == null) {
$priority = 0;
Expand Down Expand Up @@ -108,6 +113,12 @@ class DirectiveValue {
DirectiveValue.fromString(this.value);
}

abstract class DirectiveVisibility {
static const String LOCAL = 'local';
static const String CHILDREN = 'children';
static const String DIRECT_CHILDREN = 'direct_children';
}

class Controller {

}
7 changes: 5 additions & 2 deletions test/_specs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:angular/angular.dart';
import 'jasmine_syntax.dart';
import 'package:di/di.dart';
import 'package:unittest/mock.dart';
import "_log.dart";

export 'package:unittest/unittest.dart';
export 'package:angular/debug.dart';
Expand Down Expand Up @@ -149,14 +150,16 @@ class SpecInjector {
List<Module> modules = [new Module()..value(Expando, new Expando('specExpando'))];

module(Function fn) {
Module module = new AngularModule();
Module module = new AngularModule()
..type(Log, Log)
..type(Logger, Logger);
modules.add(module);
fn(module);
}

inject(Function fn, declarationStack) {
if (injector == null) {
injector = new Injector(modules);
injector = new Injector(modules, false); // Implicit injection is disabled.
}
try {
injector.invoke(fn);
Expand Down
65 changes: 61 additions & 4 deletions test/compiler_spec.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import "_specs.dart";
import "_log.dart";
import "dart:mirrors";


class TabComponent {
static String $visibility = DirectiveVisibility.DIRECT_CHILDREN;
int id = 0;
Log log;
LocalAttrDirective local;
TabComponent(Log this.log, LocalAttrDirective this.local);
attach(Scope scope) {
log('TabComponent-${id++}');
local.ping();
}
}

class PaneComponent {
TabComponent tabComponent;
LocalAttrDirective localDirective;
Log log;
PaneComponent(TabComponent this.tabComponent, LocalAttrDirective this.localDirective, Log this.log);
attach(Scope scope) {
log('PaneComponent-${tabComponent.id++}');
localDirective.ping();
}
}

class LocalAttrDirective {
static String $visibility = DirectiveVisibility.LOCAL;
int id = 0;
Log log;
LocalAttrDirective(Log this.log);
attach(Scope scope) {}
ping() {
log('LocalAttrDirective-${id++}');
}
}


main() {

describe('dte.compiler', () {
Expand All @@ -9,10 +46,12 @@ main() {
DirectiveRegistry directives;

beforeEach(inject((Injector injector) {
directives = injector.get(DirectiveRegistry);

directives.register(NgBindAttrDirective);
directives.register(NgRepeatAttrDirective);
directives = injector.get(DirectiveRegistry)
..register(NgBindAttrDirective)
..register(NgRepeatAttrDirective)
..register(TabComponent)
..register(PaneComponent)
..register(LocalAttrDirective);

$rootScope = injector.get(Scope);
}));
Expand Down Expand Up @@ -458,6 +497,24 @@ main() {
component.scope.ondone();
expect($rootScope.done).toEqual(true);
}));

});

describe('controller scoping', () {

it('shoud make controllers available to sibling and child controllers', inject((Compiler $compile, Scope $rootScope, Log log) {
var element = $('<tab local><pane local></pane><pane local></pane></tab>');
$compile(element)(element)..attach($rootScope);
expect(log.result()).toEqual('TabComponent-0; LocalAttrDirective-0; PaneComponent-1; LocalAttrDirective-0; PaneComponent-2; LocalAttrDirective-0');
}));

it('should throw an exception if required directive is missing', inject((Compiler $compile, Scope $rootScope) {
expect(() {
var element = $('<tab local><pane></pane><pane local></pane></tab>');
$compile(element)(element)..attach($rootScope);
}, throwsA(startsWith('Creating pane: Illegal argument(s): No provider found for LocalAttrDirective! (resolving LocalAttrDirective)')));
}));

});
});
}
Expand Down
2 changes: 1 addition & 1 deletion test/selector_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class DirectiveInfosMatcher extends BaseMatcher {
return description;
}

bool matches(directiveRefs, MatchState matchState) {
bool matches(directiveRefs, Map matchState) {
var pass = expected.length == directiveRefs.length;
if (pass) {
for(var i = 0, ii = expected.length; i < ii; i++) {
Expand Down

0 comments on commit 49c9bde

Please sign in to comment.