| Index: third_party/pkg/angular/lib/core_dom/block_factory.dart
|
| diff --git a/third_party/pkg/angular/lib/core_dom/block_factory.dart b/third_party/pkg/angular/lib/core_dom/block_factory.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c48af151a1c2585d78ef5caf904a7cce7169d812
|
| --- /dev/null
|
| +++ b/third_party/pkg/angular/lib/core_dom/block_factory.dart
|
| @@ -0,0 +1,438 @@
|
| +part of angular.core.dom;
|
| +
|
| +
|
| +/**
|
| + * BoundBlockFactory is a [BlockFactory] which does not need Injector because
|
| + * it is pre-bound to an injector from the parent. This means that this
|
| + * BoundBlockFactory can only be used from within a specific Directive such
|
| + * as [NgRepeat], but it can not be stored in a cache.
|
| + *
|
| + * The BoundBlockFactory needs [Scope] to be created.
|
| + */
|
| +class BoundBlockFactory {
|
| + BlockFactory blockFactory;
|
| +
|
| + Injector injector;
|
| +
|
| + BoundBlockFactory(this.blockFactory, this.injector);
|
| +
|
| + Block call(Scope scope) {
|
| + return blockFactory(injector.createChild([new Module()..value(Scope, scope)]));
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * BlockFactory is used to create new [Block]s. BlockFactory is created by the
|
| + * [Compiler] as a result of compiling a template.
|
| + */
|
| +class BlockFactory {
|
| + final List directivePositions;
|
| + final List<dom.Node> templateElements;
|
| + final Profiler _perf;
|
| + final Expando _expando;
|
| +
|
| + BlockFactory(this.templateElements, this.directivePositions, this._perf, this._expando);
|
| +
|
| + BoundBlockFactory bind(Injector injector) =>
|
| + new BoundBlockFactory(this, injector);
|
| +
|
| + Block call(Injector injector, [List<dom.Node> elements]) {
|
| + if (elements == null) {
|
| + elements = cloneElements(templateElements);
|
| + }
|
| + var timerId;
|
| + try {
|
| + assert((timerId = _perf.startTimer('ng.block')) != false);
|
| + var block = new Block(elements);
|
| + _link(block, elements, directivePositions, injector);
|
| + return block;
|
| + } finally {
|
| + assert(_perf.stopTimer(timerId) != false);
|
| + }
|
| + }
|
| +
|
| + _link(Block block, List<dom.Node> nodeList, List directivePositions, Injector parentInjector) {
|
| + var preRenderedIndexOffset = 0;
|
| + var directiveDefsByName = {};
|
| +
|
| + for (int i = 0; i < directivePositions.length;) {
|
| + int index = directivePositions[i++];
|
| +
|
| + List<DirectiveRef> directiveRefs = directivePositions[i++];
|
| + List childDirectivePositions = directivePositions[i++];
|
| + int nodeListIndex = index + preRenderedIndexOffset;
|
| + dom.Node node = nodeList[nodeListIndex];
|
| +
|
| + var timerId;
|
| + try {
|
| + assert((timerId = _perf.startTimer('ng.block.link', _html(node))) != false);
|
| + // if node isn't attached to the DOM, create a parent for it.
|
| + var parentNode = node.parentNode;
|
| + var fakeParent = false;
|
| + if (parentNode == null) {
|
| + fakeParent = true;
|
| + parentNode = new dom.DivElement();
|
| + parentNode.append(node);
|
| + }
|
| +
|
| + var childInjector = _instantiateDirectives(block, parentInjector, node,
|
| + directiveRefs, parentInjector.get(Parser));
|
| +
|
| + if (childDirectivePositions != null) {
|
| + _link(block, node.nodes, childDirectivePositions, childInjector);
|
| + }
|
| +
|
| + if (fakeParent) {
|
| + // extract the node from the parentNode.
|
| + nodeList[nodeListIndex] = parentNode.nodes[0];
|
| + }
|
| + } finally {
|
| + assert(_perf.stopTimer(timerId) != false);
|
| + }
|
| + }
|
| + }
|
| +
|
| + Injector _instantiateDirectives(Block block, Injector parentInjector,
|
| + dom.Node node, List<DirectiveRef> directiveRefs,
|
| + Parser parser) {
|
| + var timerId;
|
| + assert((timerId = _perf.startTimer('ng.block.link.setUp', _html(node))) != false);
|
| + Injector nodeInjector;
|
| + Scope scope = parentInjector.get(Scope);
|
| + FilterMap filters = parentInjector.get(FilterMap);
|
| + Map<Type, _ComponentFactory> fctrs;
|
| + var nodeAttrs = node is dom.Element ? new NodeAttrs(node) : null;
|
| + ElementProbe probe;
|
| +
|
| + try {
|
| + if (directiveRefs == null || directiveRefs.length == 0) return parentInjector;
|
| + var nodeModule = new Module();
|
| + var blockHoleFactory = (_) => null;
|
| + var blockFactory = (_) => null;
|
| + var boundBlockFactory = (_) => null;
|
| + var nodesAttrsDirectives = null;
|
| +
|
| + nodeModule.value(Block, block);
|
| + nodeModule.value(dom.Element, node);
|
| + nodeModule.value(dom.Node, node);
|
| + nodeModule.value(NodeAttrs, nodeAttrs);
|
| + directiveRefs.forEach((DirectiveRef ref) {
|
| + NgAnnotation annotation = ref.annotation;
|
| + var visibility = _elementOnly;
|
| + if (ref.annotation is NgController) {
|
| + scope = scope.createChild(new PrototypeMap(scope.context));
|
| + nodeModule.value(Scope, scope);
|
| + }
|
| + if (ref.annotation.visibility == NgDirective.CHILDREN_VISIBILITY) {
|
| + visibility = null;
|
| + } else if (ref.annotation.visibility == NgDirective.DIRECT_CHILDREN_VISIBILITY) {
|
| + visibility = _elementDirectChildren;
|
| + }
|
| + if (ref.type == NgTextMustacheDirective) {
|
| + nodeModule.factory(NgTextMustacheDirective, (Injector injector) {
|
| + return new NgTextMustacheDirective(
|
| + node, ref.value, injector.get(Interpolate), injector.get(Scope),
|
| + injector.get(AstParser), injector.get(FilterMap));
|
| + });
|
| + } else if (ref.type == NgAttrMustacheDirective) {
|
| + if (nodesAttrsDirectives == null) {
|
| + nodesAttrsDirectives = [];
|
| + nodeModule.factory(NgAttrMustacheDirective, (Injector injector) {
|
| + var scope = injector.get(Scope);
|
| + var interpolate = injector.get(Interpolate);
|
| + for (var ref in nodesAttrsDirectives) {
|
| + new NgAttrMustacheDirective(nodeAttrs, ref.value, interpolate,
|
| + scope, injector.get(AstParser), injector.get(FilterMap));
|
| + }
|
| + });
|
| + }
|
| + nodesAttrsDirectives.add(ref);
|
| + } else if (ref.annotation is NgComponent) {
|
| + //nodeModule.factory(type, new ComponentFactory(node, ref.directive), visibility: visibility);
|
| + // TODO(misko): there should be no need to wrap function like this.
|
| + nodeModule.factory(ref.type, (Injector injector) {
|
| + Compiler compiler = injector.get(Compiler);
|
| + Scope scope = injector.get(Scope);
|
| + BlockCache blockCache = injector.get(BlockCache);
|
| + Http http = injector.get(Http);
|
| + TemplateCache templateCache = injector.get(TemplateCache);
|
| + DirectiveMap directives = injector.get(DirectiveMap);
|
| + // This is a bit of a hack since we are returning different type then we are.
|
| + var componentFactory = new _ComponentFactory(node, ref.type, ref.annotation as NgComponent, injector.get(dom.NodeTreeSanitizer));
|
| + if (fctrs == null) fctrs = new Map<Type, _ComponentFactory>();
|
| + fctrs[ref.type] = componentFactory;
|
| + return componentFactory.call(injector, compiler, scope, blockCache, http, templateCache, directives);
|
| + }, visibility: visibility);
|
| + } else {
|
| + nodeModule.type(ref.type, visibility: visibility);
|
| + }
|
| + for (var publishType in ref.annotation.publishTypes) {
|
| + nodeModule.factory(publishType, (Injector injector) => injector.get(ref.type), visibility: visibility);
|
| + }
|
| + if (annotation.children == NgAnnotation.TRANSCLUDE_CHILDREN) {
|
| + // Currently, transclude is only supported for NgDirective.
|
| + assert(annotation is NgDirective);
|
| + blockHoleFactory = (_) => new BlockHole([node]);
|
| + blockFactory = (_) => ref.blockFactory;
|
| + boundBlockFactory = (Injector injector) => ref.blockFactory.bind(injector);
|
| + }
|
| + });
|
| + nodeModule.factory(BlockHole, blockHoleFactory);
|
| + nodeModule.factory(BlockFactory, blockFactory);
|
| + nodeModule.factory(BoundBlockFactory, boundBlockFactory);
|
| + nodeInjector = parentInjector.createChild([nodeModule]);
|
| + probe = _expando[node] = new ElementProbe(node, nodeInjector, scope);
|
| + } finally {
|
| + assert(_perf.stopTimer(timerId) != false);
|
| + }
|
| + directiveRefs.forEach((DirectiveRef ref) {
|
| + var linkTimer;
|
| + try {
|
| + var linkMapTimer;
|
| + assert((linkTimer = _perf.startTimer('ng.block.link', ref.type)) != false);
|
| + var controller = nodeInjector.get(ref.type);
|
| + probe.directives.add(controller);
|
| + assert((linkMapTimer = _perf.startTimer('ng.block.link.map', ref.type)) != false);
|
| + var shadowScope = (fctrs != null && fctrs.containsKey(ref.type)) ? fctrs[ref.type].shadowScope : null;
|
| + if (ref.annotation is NgController) {
|
| + scope.context[(ref.annotation as NgController).publishAs] = controller;
|
| + } else if (ref.annotation is NgComponent) {
|
| + shadowScope.context[(ref.annotation as NgComponent).publishAs] = controller;
|
| + }
|
| + if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);
|
| + var attachDelayStatus = controller is NgAttachAware ? [false] : null;
|
| + checkAttachReady() {
|
| + if (attachDelayStatus.reduce((a, b) => a && b)) {
|
| + attachDelayStatus = null;
|
| + if (scope.isAttached) {
|
| + controller.attach();
|
| + }
|
| + }
|
| + }
|
| + for (var map in ref.mappings) {
|
| + var notify;
|
| + if (attachDelayStatus != null) {
|
| + var index = attachDelayStatus.length;
|
| + attachDelayStatus.add(false);
|
| + notify = () {
|
| + if (attachDelayStatus != null) {
|
| + attachDelayStatus[index] = true;
|
| + checkAttachReady();
|
| + }
|
| + };
|
| + } else {
|
| + notify = () => null;
|
| + }
|
| + map(nodeAttrs, scope, controller, filters, notify);
|
| + }
|
| + if (attachDelayStatus != null) {
|
| + Watch watch;
|
| + watch = scope.watch(
|
| + '1', // Cheat a bit.
|
| + (_, __) {
|
| + watch.remove();
|
| + attachDelayStatus[0] = true;
|
| + checkAttachReady();
|
| + });
|
| + }
|
| + if (controller is NgDetachAware) {
|
| + scope.on(ScopeEvent.DESTROY).listen((_) => controller.detach());
|
| + }
|
| + assert(_perf.stopTimer(linkMapTimer) != false);
|
| + } finally {
|
| + assert(_perf.stopTimer(linkTimer) != false);
|
| + }
|
| + });
|
| + return nodeInjector;
|
| + }
|
| +
|
| + // DI visibility callback allowing node-local visibility.
|
| +
|
| + static final Function _elementOnly = (Injector requesting, Injector defining) {
|
| + if (requesting.name == _SHADOW) {
|
| + requesting = requesting.parent;
|
| + }
|
| + return identical(requesting, defining);
|
| + };
|
| +
|
| + // DI visibility callback allowing visibility from direct child into parent.
|
| +
|
| + static final Function _elementDirectChildren = (Injector requesting, Injector defining) {
|
| + if (requesting.name == _SHADOW) {
|
| + requesting = requesting.parent;
|
| + }
|
| + return _elementOnly(requesting, defining) || identical(requesting.parent, defining);
|
| + };
|
| +}
|
| +
|
| +/**
|
| + * BlockCache is used to cache the compilation of templates into [Block]s.
|
| + * It can be used synchronously if HTML is known or asynchronously if the
|
| + * template HTML needs to be looked up from the URL.
|
| + */
|
| +@NgInjectableService()
|
| +class BlockCache {
|
| + // _blockFactoryCache is unbounded
|
| + Cache<String, BlockFactory> _blockFactoryCache =
|
| + new LruCache<String, BlockFactory>(capacity: 0);
|
| +
|
| + Http $http;
|
| +
|
| + TemplateCache $templateCache;
|
| +
|
| + Compiler compiler;
|
| +
|
| + dom.NodeTreeSanitizer treeSanitizer;
|
| +
|
| + BlockCache(this.$http, this.$templateCache, this.compiler, this.treeSanitizer);
|
| +
|
| + BlockFactory fromHtml(String html, DirectiveMap directives) {
|
| + BlockFactory blockFactory = _blockFactoryCache.get(html);
|
| + if (blockFactory == null) {
|
| + var div = new dom.Element.tag('div');
|
| + div.setInnerHtml(html, treeSanitizer: treeSanitizer);
|
| + blockFactory = compiler(div.nodes, directives);
|
| + _blockFactoryCache.put(html, blockFactory);
|
| + }
|
| + return blockFactory;
|
| + }
|
| +
|
| + async.Future<BlockFactory> fromUrl(String url, DirectiveMap directives) {
|
| + return $http.getString(url, cache: $templateCache).then(
|
| + (html) => fromHtml(html, directives));
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * ComponentFactory is responsible for setting up components. This includes
|
| + * the shadowDom, fetching template, importing styles, setting up attribute
|
| + * mappings, publishing the controller, and compiling and caching the template.
|
| + */
|
| +class _ComponentFactory {
|
| +
|
| + final dom.Element element;
|
| + final Type type;
|
| + final NgComponent component;
|
| + final dom.NodeTreeSanitizer treeSanitizer;
|
| +
|
| + dom.ShadowRoot shadowDom;
|
| + Scope shadowScope;
|
| + Injector shadowInjector;
|
| + Compiler compiler;
|
| + var controller;
|
| +
|
| + _ComponentFactory(this.element, this.type, this.component, this.treeSanitizer);
|
| +
|
| + dynamic call(Injector injector, Compiler compiler, Scope scope, BlockCache $blockCache, Http $http, TemplateCache $templateCache, DirectiveMap directives) {
|
| + this.compiler = compiler;
|
| + shadowDom = element.createShadowRoot();
|
| + shadowDom.applyAuthorStyles = component.applyAuthorStyles;
|
| + shadowDom.resetStyleInheritance = component.resetStyleInheritance;
|
| +
|
| + shadowScope = scope.createChild({}); // Isolate
|
| + // TODO(pavelgj): fetching CSS with Http is mainly an attempt to
|
| + // work around an unfiled Chrome bug when reloading same CSS breaks
|
| + // styles all over the page. We shouldn't be doing browsers work,
|
| + // so change back to using @import once Chrome bug is fixed or a
|
| + // better work around is found.
|
| + List<async.Future<String>> cssFutures = new List();
|
| + var cssUrls = component.cssUrls;
|
| + if (cssUrls.isNotEmpty) {
|
| + cssUrls.forEach((css) => cssFutures.add(
|
| + $http.getString(css, cache: $templateCache).catchError((e) => '/*\n$e\n*/\n')
|
| + ) );
|
| + } else {
|
| + cssFutures.add( new async.Future.value(null) );
|
| + }
|
| + var blockFuture;
|
| + if (component.template != null) {
|
| + blockFuture = new async.Future.value($blockCache.fromHtml(component.template, directives));
|
| + } else if (component.templateUrl != null) {
|
| + blockFuture = $blockCache.fromUrl(component.templateUrl, directives);
|
| + }
|
| + TemplateLoader templateLoader = new TemplateLoader( async.Future.wait(cssFutures).then((Iterable<String> cssList) {
|
| + if (cssList != null) {
|
| + var filteredCssList = cssList.where((css) => css != null );
|
| + shadowDom.setInnerHtml('<style>${filteredCssList.join('')}</style>', treeSanitizer: treeSanitizer);
|
| + }
|
| + if (blockFuture != null) {
|
| + return blockFuture.then((BlockFactory blockFactory) {
|
| + if (!shadowScope.isAttached) return shadowDom;
|
| + return attachBlockToShadowDom(blockFactory);
|
| + });
|
| + }
|
| + return shadowDom;
|
| + }));
|
| + controller = createShadowInjector(injector, templateLoader).get(type);
|
| + if (controller is NgShadowRootAware) {
|
| + templateLoader.template.then((_) {
|
| + if (!shadowScope.isAttached) return;
|
| + (controller as NgShadowRootAware).onShadowRoot(shadowDom);
|
| + });
|
| + }
|
| + return controller;
|
| + }
|
| +
|
| + attachBlockToShadowDom(BlockFactory blockFactory) {
|
| + var block = blockFactory(shadowInjector);
|
| + shadowDom.nodes.addAll(block.elements);
|
| + return shadowDom;
|
| + }
|
| +
|
| + createShadowInjector(injector, TemplateLoader templateLoader) {
|
| + var shadowModule = new Module()
|
| + ..type(type)
|
| + ..value(Scope, shadowScope)
|
| + ..value(TemplateLoader, templateLoader)
|
| + ..value(dom.ShadowRoot, shadowDom);
|
| + shadowInjector = injector.createChild([shadowModule], name: _SHADOW);
|
| + return shadowInjector;
|
| + }
|
| +}
|
| +
|
| +class _AnchorAttrs extends NodeAttrs {
|
| + DirectiveRef _directiveRef;
|
| +
|
| + _AnchorAttrs(DirectiveRef this._directiveRef):super(null);
|
| +
|
| + operator [](name) => name == '.' ? _directiveRef.value : null;
|
| +
|
| + observe(String attributeName, AttributeChanged notifyFn) {
|
| + if (attributeName == '.') {
|
| + notifyFn(_directiveRef.value);
|
| + } else {
|
| + notifyFn(null);
|
| + }
|
| + }
|
| +}
|
| +
|
| +String _SHADOW = 'SHADOW_INJECTOR';
|
| +
|
| +String _html(obj) {
|
| + if (obj is String) {
|
| + return obj;
|
| + } else if (obj is List) {
|
| + return (obj as List).fold('', (v, e) => v + _html(e));
|
| + } else if (obj is dom.Element) {
|
| + var text = (obj as dom.Element).outerHtml;
|
| + return text.substring(0, text.indexOf('>') + 1);
|
| + } else {
|
| + return obj.nodeName;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * [ElementProbe] is attached to each [Element] in the DOM. Its sole purpose is to
|
| + * allow access to the [Injector], [Scope], and Directives for debugging and automated
|
| + * test purposes. The information here is not used by Angular in any way.
|
| + *
|
| + * SEE: [ngInjector], [ngScope], [ngDirectives]
|
| + */
|
| +class ElementProbe {
|
| + final dom.Node element;
|
| + final Injector injector;
|
| + final Scope scope;
|
| + final directives = [];
|
| +
|
| + ElementProbe(this.element, this.injector, this.scope);
|
| +}
|
|
|