| Index: third_party/pkg/angular/lib/core_dom/element_binder.dart
|
| diff --git a/third_party/pkg/angular/lib/core_dom/element_binder.dart b/third_party/pkg/angular/lib/core_dom/element_binder.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..dd01377ac78e04ce11e6b9f1abd796616577cf1e
|
| --- /dev/null
|
| +++ b/third_party/pkg/angular/lib/core_dom/element_binder.dart
|
| @@ -0,0 +1,383 @@
|
| +part of angular.core.dom_internal;
|
| +
|
| +class TemplateElementBinder extends ElementBinder {
|
| + final DirectiveRef template;
|
| + ViewFactory templateViewFactory;
|
| +
|
| + final bool hasTemplate = true;
|
| +
|
| + final ElementBinder templateBinder;
|
| +
|
| + var _directiveCache;
|
| + List<DirectiveRef> get _usableDirectiveRefs {
|
| + if (_directiveCache != null) return _directiveCache;
|
| + return _directiveCache = [template];
|
| + }
|
| +
|
| + TemplateElementBinder(perf, expando, parser, componentFactory,
|
| + transcludingComponentFactory, shadowDomComponentFactory,
|
| + this.template, this.templateBinder,
|
| + onEvents, bindAttrs, childMode)
|
| + : super(perf, expando, parser, componentFactory,
|
| + transcludingComponentFactory, shadowDomComponentFactory,
|
| + null, null, onEvents, bindAttrs, childMode);
|
| +
|
| + String toString() => "[TemplateElementBinder template:$template]";
|
| +
|
| + _registerViewFactory(node, parentInjector, nodeModule) {
|
| + assert(templateViewFactory != null);
|
| + nodeModule
|
| + ..factory(ViewPort, (_) =>
|
| + new ViewPort(node, parentInjector.get(Animate)))
|
| + ..value(ViewFactory, templateViewFactory)
|
| + ..factory(BoundViewFactory, (Injector injector) =>
|
| + templateViewFactory.bind(injector));
|
| + }
|
| +}
|
| +
|
| +
|
| +/**
|
| + * ElementBinder is created by the Selector and is responsible for instantiating
|
| + * individual directives and binding element properties.
|
| + */
|
| +class ElementBinder {
|
| + // DI Services
|
| + final Profiler _perf;
|
| + final Expando _expando;
|
| + final Parser _parser;
|
| +
|
| + // The default component factory
|
| + final ComponentFactory _componentFactory;
|
| + final TranscludingComponentFactory _transcludingComponentFactory;
|
| + final ShadowDomComponentFactory _shadowDomComponentFactory;
|
| + final Map onEvents;
|
| + final Map bindAttrs;
|
| +
|
| + // Member fields
|
| + final decorators;
|
| +
|
| + final DirectiveRef component;
|
| +
|
| + // Can be either COMPILE_CHILDREN or IGNORE_CHILDREN
|
| + final String childMode;
|
| +
|
| + ElementBinder(this._perf, this._expando, this._parser,
|
| + this._componentFactory,
|
| + this._transcludingComponentFactory,
|
| + this._shadowDomComponentFactory,
|
| + this.component, this.decorators,
|
| + this.onEvents, this.bindAttrs, this.childMode);
|
| +
|
| + final bool hasTemplate = false;
|
| +
|
| + bool get shouldCompileChildren =>
|
| + childMode == Directive.COMPILE_CHILDREN;
|
| +
|
| + var _directiveCache;
|
| + List<DirectiveRef> get _usableDirectiveRefs {
|
| + if (_directiveCache != null) return _directiveCache;
|
| + if (component != null) return _directiveCache = new List.from(decorators)..add(component);
|
| + return _directiveCache = decorators;
|
| + }
|
| +
|
| + bool get hasDirectivesOrEvents =>
|
| + _usableDirectiveRefs.isNotEmpty || onEvents.isNotEmpty;
|
| +
|
| + _createAttrMappings(controller, scope, DirectiveRef ref, nodeAttrs, formatters, tasks) {
|
| + ref.mappings.forEach((MappingParts p) {
|
| + var attrName = p.attrName;
|
| + var dstExpression = p.dstExpression;
|
| + if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref);
|
| +
|
| + Expression dstPathFn = _parser(dstExpression);
|
| + if (!dstPathFn.isAssignable) {
|
| + throw "Expression '$dstExpression' is not assignable in mapping '${p.originalValue}' "
|
| + "for attribute '$attrName'.";
|
| + }
|
| +
|
| + switch (p.mode) {
|
| + case '@': // string
|
| + var taskId = tasks.registerTask();
|
| + nodeAttrs.observe(attrName, (value) {
|
| + dstPathFn.assign(controller, value);
|
| + tasks.completeTask(taskId);
|
| + });
|
| + break;
|
| +
|
| + case '<=>': // two-way
|
| + if (nodeAttrs[attrName] == null) return;
|
| +
|
| + var taskId = tasks.registerTask();
|
| + String expression = nodeAttrs[attrName];
|
| + Expression expressionFn = _parser(expression);
|
| + var viewOutbound = false;
|
| + var viewInbound = false;
|
| + scope.watch(expression, (inboundValue, _) {
|
| + if (!viewInbound) {
|
| + viewOutbound = true;
|
| + scope.rootScope.runAsync(() => viewOutbound = false);
|
| + var value = dstPathFn.assign(controller, inboundValue);
|
| + tasks.completeTask(taskId);
|
| + return value;
|
| + }
|
| + }, formatters: formatters);
|
| + if (expressionFn.isAssignable) {
|
| + scope.watch(dstExpression, (outboundValue, _) {
|
| + if (!viewOutbound) {
|
| + viewInbound = true;
|
| + scope.rootScope.runAsync(() => viewInbound = false);
|
| + expressionFn.assign(scope.context, outboundValue);
|
| + tasks.completeTask(taskId);
|
| + }
|
| + }, context: controller, formatters: formatters);
|
| + }
|
| + break;
|
| +
|
| + case '=>': // one-way
|
| + if (nodeAttrs[attrName] == null) return;
|
| + var taskId = tasks.registerTask();
|
| +
|
| + Expression attrExprFn = _parser(nodeAttrs[attrName]);
|
| + scope.watch(nodeAttrs[attrName], (v, _) {
|
| + dstPathFn.assign(controller, v);
|
| + tasks.completeTask(taskId);
|
| + }, formatters: formatters);
|
| + break;
|
| +
|
| + case '=>!': // one-way, one-time
|
| + if (nodeAttrs[attrName] == null) return;
|
| +
|
| + Expression attrExprFn = _parser(nodeAttrs[attrName]);
|
| + var watch;
|
| + watch = scope.watch(nodeAttrs[attrName], (value, _) {
|
| + if (dstPathFn.assign(controller, value) != null) {
|
| + watch.remove();
|
| + }
|
| + }, formatters: formatters);
|
| + break;
|
| +
|
| + case '&': // callback
|
| + dstPathFn.assign(controller,
|
| + _parser(nodeAttrs[attrName]).bind(scope.context, ScopeLocals.wrapper));
|
| + break;
|
| + }
|
| + });
|
| + }
|
| +
|
| + _link(nodeInjector, probe, scope, nodeAttrs, formatters) {
|
| + _usableDirectiveRefs.forEach((DirectiveRef ref) {
|
| + var linkTimer;
|
| + try {
|
| + var linkMapTimer;
|
| + assert((linkTimer = _perf.startTimer('ng.view.link', ref.type)) != false);
|
| + var controller = nodeInjector.get(ref.type);
|
| + probe.directives.add(controller);
|
| + assert((linkMapTimer = _perf.startTimer('ng.view.link.map', ref.type)) != false);
|
| +
|
| + if (ref.annotation is Controller) {
|
| + scope.context[(ref.annotation as Controller).publishAs] = controller;
|
| + }
|
| +
|
| + var tasks = new _TaskList(controller is AttachAware ? () {
|
| + if (scope.isAttached) controller.attach();
|
| + } : null);
|
| +
|
| + _createAttrMappings(controller, scope, ref, nodeAttrs, formatters, tasks);
|
| +
|
| + if (controller is AttachAware) {
|
| + var taskId = tasks.registerTask();
|
| + Watch watch;
|
| + watch = scope.watch('1', // Cheat a bit.
|
| + (_, __) {
|
| + watch.remove();
|
| + tasks.completeTask(taskId);
|
| + });
|
| + }
|
| +
|
| + tasks.doneRegistering();
|
| +
|
| + if (controller is DetachAware) {
|
| + scope.on(ScopeEvent.DESTROY).listen((_) => controller.detach());
|
| + }
|
| +
|
| + assert(_perf.stopTimer(linkMapTimer) != false);
|
| + } finally {
|
| + assert(_perf.stopTimer(linkTimer) != false);
|
| + }
|
| + });
|
| + }
|
| +
|
| + _createDirectiveFactories(DirectiveRef ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs,
|
| + visibility) {
|
| + if (ref.type == TextMustache) {
|
| + nodeModule.factory(TextMustache, (Injector injector) {
|
| + return new TextMustache(node, ref.value, injector.get(Interpolate),
|
| + injector.get(Scope), injector.get(FormatterMap));
|
| + });
|
| + } else if (ref.type == AttrMustache) {
|
| + if (nodesAttrsDirectives.isEmpty) {
|
| + nodeModule.factory(AttrMustache, (Injector injector) {
|
| + var scope = injector.get(Scope);
|
| + var interpolate = injector.get(Interpolate);
|
| + for (var ref in nodesAttrsDirectives) {
|
| + new AttrMustache(nodeAttrs, ref.value, interpolate, scope,
|
| + injector.get(FormatterMap));
|
| + }
|
| + });
|
| + }
|
| + nodesAttrsDirectives.add(ref);
|
| + } else if (ref.annotation is Component) {
|
| + var factory;
|
| + var annotation = ref.annotation as Component;
|
| + if (annotation.useShadowDom == true) {
|
| + factory = _shadowDomComponentFactory;
|
| + } else if (annotation.useShadowDom == false) {
|
| + factory = _transcludingComponentFactory;
|
| + } else {
|
| + factory = _componentFactory;
|
| + }
|
| + nodeModule.factory(ref.type, factory.call(node, ref), visibility: visibility);
|
| + } else {
|
| + nodeModule.type(ref.type, visibility: visibility);
|
| + }
|
| + }
|
| +
|
| + // Overridden in TemplateElementBinder
|
| + _registerViewFactory(node, parentInjector, nodeModule) {
|
| + nodeModule..factory(ViewPort, null)
|
| + ..factory(ViewFactory, null)
|
| + ..factory(BoundViewFactory, null);
|
| + }
|
| +
|
| + Injector bind(View view, Injector parentInjector, dom.Node node) {
|
| + Injector nodeInjector;
|
| + Scope scope = parentInjector.get(Scope);
|
| + FormatterMap formatters = parentInjector.get(FormatterMap);
|
| + var nodeAttrs = node is dom.Element ? new NodeAttrs(node) : null;
|
| + ElementProbe probe;
|
| +
|
| + var timerId;
|
| + assert((timerId = _perf.startTimer('ng.view.link.setUp', _html(node))) != false);
|
| + var directiveRefs = _usableDirectiveRefs;
|
| + try {
|
| + if (!hasDirectivesOrEvents) return parentInjector;
|
| +
|
| + var nodesAttrsDirectives = [];
|
| + var nodeModule = new Module()
|
| + ..type(NgElement)
|
| + ..value(View, view)
|
| + ..value(dom.Element, node)
|
| + ..value(dom.Node, node)
|
| + ..value(NodeAttrs, nodeAttrs)
|
| + ..factory(ElementProbe, (_) => probe);
|
| +
|
| + directiveRefs.forEach((DirectiveRef ref) {
|
| + Directive annotation = ref.annotation;
|
| + var visibility = ref.annotation.visibility;
|
| + if (ref.annotation is Controller) {
|
| + scope = scope.createChild(new PrototypeMap(scope.context));
|
| + nodeModule.value(Scope, scope);
|
| + }
|
| +
|
| + _createDirectiveFactories(ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs,
|
| + visibility);
|
| + if (ref.annotation.module != null) {
|
| + nodeModule.install(ref.annotation.module());
|
| + }
|
| + });
|
| +
|
| + _registerViewFactory(node, parentInjector, nodeModule);
|
| +
|
| + nodeInjector = parentInjector.createChild([nodeModule]);
|
| + probe = _expando[node] = new ElementProbe(
|
| + parentInjector.get(ElementProbe), node, nodeInjector, scope);
|
| + } finally {
|
| + assert(_perf.stopTimer(timerId) != false);
|
| + }
|
| +
|
| + _link(nodeInjector, probe, scope, nodeAttrs, formatters);
|
| +
|
| + onEvents.forEach((event, value) {
|
| + view.registerEvent(EventHandler.attrNameToEventName(event));
|
| + });
|
| + return nodeInjector;
|
| + }
|
| +
|
| + String toString() => "[ElementBinder decorators:$decorators]";
|
| +}
|
| +
|
| +/**
|
| + * Private class used for managing controller.attach() calls
|
| + */
|
| +class _TaskList {
|
| + var onDone;
|
| + final List _tasks = [];
|
| + bool isDone = false;
|
| +
|
| + _TaskList(this.onDone) {
|
| + if (onDone == null) isDone = true;
|
| + }
|
| +
|
| + int registerTask() {
|
| + if (isDone) return null; // Do nothing if there is nothing to do.
|
| + _tasks.add(false);
|
| + return _tasks.length - 1;
|
| + }
|
| +
|
| + void completeTask(id) {
|
| + if (isDone) return;
|
| + _tasks[id] = true;
|
| + if (_tasks.every((a) => a)) {
|
| + onDone();
|
| + isDone = true;
|
| + }
|
| + }
|
| +
|
| + doneRegistering() {
|
| + completeTask(registerTask());
|
| + }
|
| +}
|
| +
|
| +// Used for walking the DOM
|
| +class ElementBinderTreeRef {
|
| + final int offsetIndex;
|
| + final ElementBinderTree subtree;
|
| +
|
| + ElementBinderTreeRef(this.offsetIndex, this.subtree);
|
| +}
|
| +
|
| +class ElementBinderTree {
|
| + final ElementBinder binder;
|
| + final List<ElementBinderTreeRef> subtrees;
|
| +
|
| + ElementBinderTree(this.binder, this.subtrees);
|
| +}
|
| +
|
| +class TaggedTextBinder {
|
| + final ElementBinder binder;
|
| + final int offsetIndex;
|
| +
|
| + TaggedTextBinder(this.binder, this.offsetIndex);
|
| + toString() => "[TaggedTextBinder binder:$binder offset:$offsetIndex]";
|
| +}
|
| +
|
| +// Used for the tagging compiler
|
| +class TaggedElementBinder {
|
| + final ElementBinder binder;
|
| + int parentBinderOffset;
|
| + var injector;
|
| + bool isTopLevel;
|
| +
|
| + List<TaggedTextBinder> textBinders;
|
| +
|
| + TaggedElementBinder(this.binder, this.parentBinderOffset, this.isTopLevel);
|
| +
|
| + void addText(TaggedTextBinder tagged) {
|
| + if (textBinders == null) textBinders = [];
|
| + textBinders.add(tagged);
|
| + }
|
| +
|
| + String toString() => "[TaggedElementBinder binder:$binder parentBinderOffset:"
|
| + "$parentBinderOffset textBinders:$textBinders "
|
| + "injector:$injector]";
|
| +}
|
|
|