chore(packaging): move files to match target file structure

This commit is contained in:
Yegor Jbanov
2015-02-04 23:05:13 -08:00
parent 7ce4f66cdc
commit 3820609f24
149 changed files with 0 additions and 77 deletions

View File

@ -0,0 +1,59 @@
import {isBlank} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {DOM, Element} from 'facade/src/dom';
import {CompileElement} from './compile_element';
import {CompileStep} from './compile_step';
/**
* Controls the processing order of elements.
* Right now it only allows to add a parent element.
*/
export class CompileControl {
_steps:List<CompileStep>;
_currentStepIndex:number;
_parent:CompileElement;
_results;
_additionalChildren;
constructor(steps) {
this._steps = steps;
this._currentStepIndex = 0;
this._parent = null;
this._results = null;
this._additionalChildren = null;
}
// only public so that it can be used by compile_pipeline
internalProcess(results, startStepIndex, parent:CompileElement, current:CompileElement) {
this._results = results;
var previousStepIndex = this._currentStepIndex;
var previousParent = this._parent;
for (var i=startStepIndex; i<this._steps.length; i++) {
var step = this._steps[i];
this._parent = parent;
this._currentStepIndex = i;
step.process(parent, current, this);
parent = this._parent;
}
ListWrapper.push(results, current);
this._currentStepIndex = previousStepIndex;
this._parent = previousParent;
var localAdditionalChildren = this._additionalChildren;
this._additionalChildren = null;
return localAdditionalChildren;
}
addParent(newElement:CompileElement) {
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
this._parent = newElement;
}
addChild(element:CompileElement) {
if (isBlank(this._additionalChildren)) {
this._additionalChildren = ListWrapper.create();
}
ListWrapper.push(this._additionalChildren, element);
}
}

View File

@ -0,0 +1,159 @@
import {List, Map, ListWrapper, MapWrapper} from 'facade/src/collection';
import {Element, DOM} from 'facade/src/dom';
import {int, isBlank, isPresent, Type} from 'facade/src/lang';
import {DirectiveMetadata} from '../directive_metadata';
import {Decorator} from '../../annotations/annotations';
import {Component} from '../../annotations/annotations';
import {Template} from '../../annotations/annotations';
import {ElementBinder} from '../element_binder';
import {ProtoElementInjector} from '../element_injector';
import {ProtoView} from '../view';
import {AST} from 'change_detection/change_detection';
/**
* Collects all data that is needed to process an element
* in the compile process. Fields are filled
* by the CompileSteps starting out with the pure HTMLElement.
*/
export class CompileElement {
element:Element;
_attrs:Map;
_classList:List;
textNodeBindings:Map;
propertyBindings:Map;
eventBindings:Map;
/// Store directive name to template name mapping.
/// Directive name is what the directive exports the variable as
/// Template name is how it is reffered to it in template
variableBindings:Map;
decoratorDirectives:List<DirectiveMetadata>;
templateDirective:DirectiveMetadata;
componentDirective:DirectiveMetadata;
_allDirectives:List<DirectiveMetadata>;
isViewRoot:boolean;
hasBindings:boolean;
inheritedProtoView:ProtoView;
inheritedProtoElementInjector:ProtoElementInjector;
inheritedElementBinder:ElementBinder;
distanceToParentInjector:number;
compileChildren: boolean;
constructor(element:Element) {
this.element = element;
this._attrs = null;
this._classList = null;
this.textNodeBindings = null;
this.propertyBindings = null;
this.eventBindings = null;
this.variableBindings = null;
this.decoratorDirectives = null;
this.templateDirective = null;
this.componentDirective = null;
this._allDirectives = null;
this.isViewRoot = false;
this.hasBindings = false;
// inherited down to children if they don't have
// an own protoView
this.inheritedProtoView = null;
// inherited down to children if they don't have
// an own protoElementInjector
this.inheritedProtoElementInjector = null;
// inherited down to children if they don't have
// an own elementBinder
this.inheritedElementBinder = null;
this.distanceToParentInjector = 0;
this.compileChildren = true;
}
refreshAttrs() {
this._attrs = null;
}
attrs():Map<string,string> {
if (isBlank(this._attrs)) {
this._attrs = DOM.attributeMap(this.element);
}
return this._attrs;
}
refreshClassList() {
this._classList = null;
}
classList():List<string> {
if (isBlank(this._classList)) {
this._classList = ListWrapper.create();
var elClassList = DOM.classList(this.element);
for (var i = 0; i < elClassList.length; i++) {
ListWrapper.push(this._classList, elClassList[i]);
}
}
return this._classList;
}
addTextNodeBinding(indexInParent:int, expression:AST) {
if (isBlank(this.textNodeBindings)) {
this.textNodeBindings = MapWrapper.create();
}
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
}
addPropertyBinding(property:string, expression:AST) {
if (isBlank(this.propertyBindings)) {
this.propertyBindings = MapWrapper.create();
}
MapWrapper.set(this.propertyBindings, property, expression);
}
addVariableBinding(directiveName:string, templateName:string) {
if (isBlank(this.variableBindings)) {
this.variableBindings = MapWrapper.create();
}
MapWrapper.set(this.variableBindings, templateName, directiveName);
}
addEventBinding(eventName:string, expression:AST) {
if (isBlank(this.eventBindings)) {
this.eventBindings = MapWrapper.create();
}
MapWrapper.set(this.eventBindings, eventName, expression);
}
addDirective(directive:DirectiveMetadata) {
var annotation = directive.annotation;
this._allDirectives = null;
if (annotation instanceof Decorator) {
if (isBlank(this.decoratorDirectives)) {
this.decoratorDirectives = ListWrapper.create();
}
ListWrapper.push(this.decoratorDirectives, directive);
if (!annotation.compileChildren) {
this.compileChildren = false;
}
} else if (annotation instanceof Template) {
this.templateDirective = directive;
} else if (annotation instanceof Component) {
this.componentDirective = directive;
}
}
getAllDirectives(): List<DirectiveMetadata> {
if (this._allDirectives === null) {
// Collect all the directives
// When present the component directive must be first
var directives = ListWrapper.create();
if (isPresent(this.componentDirective)) {
ListWrapper.push(directives, this.componentDirective);
}
if (isPresent(this.templateDirective)) {
ListWrapper.push(directives, this.templateDirective);
}
if (isPresent(this.decoratorDirectives)) {
directives = ListWrapper.concat(directives, this.decoratorDirectives);
}
this._allDirectives = directives;
}
return this._allDirectives;
}
}

View File

@ -0,0 +1,47 @@
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {Element, Node, DOM} from 'facade/src/dom';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step';
import {DirectiveMetadata} from '../directive_metadata';
/**
* CompilePipeline for executing CompileSteps recursively for
* all elements in a template.
*/
export class CompilePipeline {
_control:CompileControl;
constructor(steps:List<CompileStep>) {
this._control = new CompileControl(steps);
}
process(rootElement:Element):List {
var results = ListWrapper.create();
this._process(results, null, new CompileElement(rootElement));
return results;
}
_process(results, parent:CompileElement, current:CompileElement) {
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
if (current.compileChildren) {
var node = DOM.templateAwareRoot(current.element).firstChild;
while (isPresent(node)) {
// compiliation can potentially move the node, so we need to store the
// next sibling before recursing.
var nextNode = DOM.nextSibling(node);
if (node.nodeType === Node.ELEMENT_NODE) {
this._process(results, current, new CompileElement(node));
}
node = nextNode;
}
}
if (isPresent(additionalChildren)) {
for (var i=0; i<additionalChildren.length; i++) {
this._process(results, current, additionalChildren[i]);
}
}
}
}

View File

@ -0,0 +1,11 @@
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../directive_metadata';
/**
* One part of the compile process.
* Is guaranteed to be called in depth first order
*/
export class CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {}
}

View File

@ -0,0 +1,38 @@
import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {List} from 'facade/src/collection';
import {PropertyBindingParser} from './property_binding_parser';
import {TextInterpolationParser} from './text_interpolation_parser';
import {DirectiveParser} from './directive_parser';
import {ViewSplitter} from './view_splitter';
import {ElementBindingMarker} from './element_binding_marker';
import {ProtoViewBuilder} from './proto_view_builder';
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
import {ElementBinderBuilder} from './element_binder_builder';
import {DirectiveMetadata} from 'core/src/compiler/directive_metadata';
import {stringify} from 'facade/src/lang';
/**
* Default steps used for compiling a template.
* Takes in an HTMLElement and produces the ProtoViews,
* ProtoElementInjectors and ElementBinders in the end.
*/
export function createDefaultSteps(
changeDetection:ChangeDetection,
parser:Parser,
compiledComponent: DirectiveMetadata,
directives: List<DirectiveMetadata>) {
var compilationUnit = stringify(compiledComponent.type);
return [
new ViewSplitter(parser, compilationUnit),
new PropertyBindingParser(parser, compilationUnit),
new DirectiveParser(directives),
new TextInterpolationParser(parser, compilationUnit),
new ElementBindingMarker(),
new ProtoViewBuilder(changeDetection),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder()
];
}

View File

@ -0,0 +1,82 @@
import {isPresent, BaseException} from 'facade/src/lang';
import {List, MapWrapper} from 'facade/src/collection';
import {TemplateElement} from 'facade/src/dom';
import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector';
import {DirectiveMetadata} from '../directive_metadata';
import {Template} from '../../annotations/annotations';
import {Component} from '../../annotations/annotations';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Parses the directives on a single element. Assumes ViewSplitter has already created
* <template> elements for template directives.
*
* Fills:
* - CompileElement#decoratorDirectives
* - CompileElement#templateDirecitve
* - CompileElement#componentDirective.
*
* Reads:
* - CompileElement#propertyBindings (to find directives contained
* in the property bindings)
* - CompileElement#variableBindings (to find directives contained
* in the variable bindings)
*/
export class DirectiveParser extends CompileStep {
_selectorMatcher:SelectorMatcher;
constructor(directives:List<DirectiveMetadata>) {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i];
this._selectorMatcher.addSelectable(
CssSelector.parse(directiveMetadata.annotation.selector),
directiveMetadata
);
}
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
var classList = current.classList();
var cssSelector = new CssSelector();
cssSelector.setElement(current.element.nodeName);
for (var i=0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]);
}
MapWrapper.forEach(attrs, (attrValue, attrName) => {
cssSelector.addAttribute(attrName, attrValue);
});
if (isPresent(current.propertyBindings)) {
MapWrapper.forEach(current.propertyBindings, (expression, prop) => {
cssSelector.addAttribute(prop, expression.source);
});
}
if (isPresent(current.variableBindings)) {
MapWrapper.forEach(current.variableBindings, (value, name) => {
cssSelector.addAttribute(name, value);
});
}
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
var isTemplateElement = current.element instanceof TemplateElement;
this._selectorMatcher.match(cssSelector, (directive) => {
if (directive.annotation instanceof Template) {
if (!isTemplateElement) {
throw new BaseException('Template directives need to be placed on <template> elements or elements with template attribute!');
} else if (isPresent(current.templateDirective)) {
throw new BaseException('Only one template directive per element is allowed!');
}
} else if (isTemplateElement) {
throw new BaseException('Only template directives are allowed on <template> elements!');
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
throw new BaseException('Only one component directive per element is allowed!');
}
current.addDirective(directive);
});
}
}

View File

@ -0,0 +1,145 @@
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, stringify} from 'facade/src/lang';
import {Element, DOM} from 'facade/src/dom';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {reflector} from 'reflection/src/reflection';
import {Parser, ProtoChangeDetector} from 'change_detection/change_detection';
import {Component, Directive} from '../../annotations/annotations';
import {DirectiveMetadata} from '../directive_metadata';
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from '../view';
import {ProtoElementInjector} from '../element_injector';
import {ElementBinder} from '../element_binder';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();
function classSetterFactory(className:string) {
var setterFn = StringMapWrapper.get(classSettersCache, className);
if (isBlank(setterFn)) {
setterFn = function(element:Element, value) {
if (value) {
DOM.addClass(element, className);
} else {
DOM.removeClass(element, className);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}
return setterFn;
}
/**
* Creates the ElementBinders and adds watches to the
* ProtoChangeDetector.
*
* Fills:
* - CompileElement#inheritedElementBinder
*
* Reads:
* - (in parent) CompileElement#inheritedElementBinder
* - CompileElement#hasBindings
* - CompileElement#inheritedProtoView
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#eventBindings
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*
* Note: This actually only needs the CompileElements with the flags
* `hasBindings` and `isViewRoot`,
* and only needs the actual HTMLElement for the ones
* with the flag `isViewRoot`.
*/
export class ElementBinderBuilder extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var elementBinder = null;
if (current.hasBindings) {
var protoView = current.inheritedProtoView;
elementBinder = protoView.bindElement(current.inheritedProtoElementInjector,
current.componentDirective, current.templateDirective);
if (isPresent(current.textNodeBindings)) {
this._bindTextNodes(protoView, current);
}
if (isPresent(current.propertyBindings)) {
this._bindElementProperties(protoView, current);
}
if (isPresent(current.eventBindings)) {
this._bindEvents(protoView, current);
}
this._bindDirectiveProperties(current.getAllDirectives(), current);
} else if (isPresent(parent)) {
elementBinder = parent.inheritedElementBinder;
}
current.inheritedElementBinder = elementBinder;
}
_bindTextNodes(protoView, compileElement) {
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
protoView.bindTextNode(indexInParent, expression);
});
}
_bindElementProperties(protoView, compileElement) {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn;
if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (DOM.hasProperty(compileElement.element, property)) {
setterFn = reflector.setter(property);
}
if (isPresent(setterFn)) {
protoView.bindElementProperty(expression.ast, property, setterFn);
}
});
}
_bindEvents(protoView, compileElement) {
MapWrapper.forEach(compileElement.eventBindings, (expression, eventName) => {
protoView.bindEvent(eventName, expression);
});
}
_bindDirectiveProperties(directives: List<DirectiveMetadata>,
compileElement: CompileElement) {
var protoView = compileElement.inheritedProtoView;
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
var directive = ListWrapper.get(directives, directiveIndex);
var annotation = directive.annotation;
if (isBlank(annotation.bind)) continue;
StringMapWrapper.forEach(annotation.bind, function (dirProp, elProp) {
var expression = isPresent(compileElement.propertyBindings) ?
MapWrapper.get(compileElement.propertyBindings, elProp) :
null;
if (isBlank(expression)) {
throw new BaseException("No element binding found for property '" + elProp
+ "' which is required by directive '" + stringify(directive.type) + "'");
}
var len = dirProp.length;
var dirBindingName = dirProp;
var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']';
if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2);
protoView.bindDirectiveProperty(
directiveIndex,
expression,
dirBindingName,
reflector.setter(dirBindingName),
isContentWatch
);
});
}
}
}

View File

@ -0,0 +1,44 @@
import {isPresent} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {DOM} from 'facade/src/dom';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
const NG_BINDING_CLASS = 'ng-binding';
/**
* Marks elements that have bindings with a css class
* and sets the CompileElement.hasBindings flag.
*
* Fills:
* - CompileElement#hasBindings
*
* Reads:
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#variableBindings
* - CompileElement#eventBindings
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*/
export class ElementBindingMarker extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var hasBindings =
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
(isPresent(current.variableBindings) && MapWrapper.size(current.variableBindings)>0) ||
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>0) ||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
isPresent(current.templateDirective) ||
isPresent(current.componentDirective);
if (hasBindings) {
var element = current.element;
DOM.addClass(element, NG_BINDING_CLASS);
current.hasBindings = true;
}
}
}

View File

@ -0,0 +1,76 @@
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {TemplateElement} from 'facade/src/dom';
import {Parser, AST, ExpressionWithSource} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)');
/**
* Parses the property bindings on a single element.
*
* Fills:
* - CompileElement#propertyBindings
* - CompileElement#eventBindings
* - CompileElement#variableBindings
*/
export class PropertyBindingParser extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
MapWrapper.forEach(attrs, (attrValue, attrName) => {
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
if (isPresent(bindParts)) {
if (isPresent(bindParts[1])) {
// match: bind-prop
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue));
} else if (isPresent(bindParts[2])) {
// match: let-prop
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
if (!(current.element instanceof TemplateElement)) {
throw new BaseException('var-* is only allowed on <template> elements!');
}
current.addVariableBinding(bindParts[4], attrValue);
} else if (isPresent(bindParts[3])) {
// match: on-prop
current.addEventBinding(bindParts[4], this._parseAction(attrValue));
} else if (isPresent(bindParts[5])) {
// match: [prop]
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue));
} else if (isPresent(bindParts[6])) {
// match: (prop)
current.addEventBinding(bindParts[6], this._parseBinding(attrValue));
}
} else {
var ast = this._parseInterpolation(attrValue);
if (isPresent(ast)) {
current.addPropertyBinding(attrName, ast);
}
}
});
}
_parseInterpolation(input:string):AST {
return this._parser.parseInterpolation(input, this._compilationUnit);
}
_parseBinding(input:string):AST {
return this._parser.parseBinding(input, this._compilationUnit);
}
_parseAction(input:string):AST {
return this._parser.parseAction(input, this._compilationUnit);
}
}

View File

@ -0,0 +1,72 @@
import {isPresent, isBlank} from 'facade/src/lang';
import {ListWrapper} from 'facade/src/collection';
import {Key} from 'di/di';
import {ProtoElementInjector, ComponentKeyMetaData, DirectiveBinding} from '../element_injector';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../directive_metadata';
/**
* Creates the ProtoElementInjectors.
*
* Fills:
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#distanceToParentInjector
*
* Reads:
* - (in parent) CompileElement#inheritedProtoElementInjector
* - (in parent) CompileElement#distanceToParentInjector
* - CompileElement#isViewRoot
* - CompileElement#inheritedProtoView
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*/
export class ProtoElementInjectorBuilder extends CompileStep {
// public so that we can overwrite it in tests
internalCreateProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance) {
return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance);
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var distanceToParentInjector = this._getDistanceToParentInjector(parent, current);
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
var injectorBindings = ListWrapper.map(current.getAllDirectives(), this._createBinding);
// TODO: add lightDomServices as well,
// but after the directives as we rely on that order
// in the element_binder_builder.
if (injectorBindings.length > 0) {
var protoView = current.inheritedProtoView;
var hasComponent = isPresent(current.componentDirective);
current.inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings,
hasComponent, distanceToParentInjector
);
current.distanceToParentInjector = 0;
} else {
current.inheritedProtoElementInjector = parentProtoElementInjector;
current.distanceToParentInjector = distanceToParentInjector;
}
}
_getDistanceToParentInjector(parent, current) {
return isPresent(parent) ? parent.distanceToParentInjector + 1 : 0;
}
_getParentProtoElementInjector(parent, current) {
if (isPresent(parent) && !current.isViewRoot) {
return parent.inheritedProtoElementInjector;
}
return null;
}
_createBinding(d:DirectiveMetadata): DirectiveBinding {
return DirectiveBinding.createFromType(d.type, d.annotation);
}
}

View File

@ -0,0 +1,50 @@
import {isPresent, BaseException} from 'facade/src/lang';
import {ListWrapper, MapWrapper} from 'facade/src/collection';
import {ProtoView} from '../view';
import {ChangeDetection} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Creates ProtoViews and forwards variable bindings from parent to children.
*
* Fills:
* - (in parent): CompileElement#inheritedElementBinder.nestedProtoView
* - CompileElement#inheritedProtoView
*
* Reads:
* - (in parent): CompileElement#inheritedProtoView
* - (in parent): CompileElement#variableBindings
* - CompileElement#isViewRoot
*/
export class ProtoViewBuilder extends CompileStep {
changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection) {
this.changeDetection = changeDetection;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null;
if (current.isViewRoot) {
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
inheritedProtoView = new ProtoView(current.element, protoChangeDetector);
if (isPresent(parent)) {
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed');
}
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
if (isPresent(parent.variableBindings)) {
MapWrapper.forEach(parent.variableBindings, (mappedName, varName) => {
inheritedProtoView.bindVariable(varName, mappedName);
});
}
}
} else if (isPresent(parent)) {
inheritedProtoView = parent.inheritedProtoView;
}
current.inheritedProtoView = inheritedProtoView;
}
}

View File

@ -0,0 +1,45 @@
import {RegExpWrapper, StringWrapper, isPresent} from 'facade/src/lang';
import {Node, DOM} from 'facade/src/dom';
import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Parses interpolations in direct text child nodes of the current element.
*
* Fills:
* - CompileElement#textNodeBindings
*/
export class TextInterpolationParser extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (!current.compileChildren) {
return;
}
var element = current.element;
var childNodes = DOM.templateAwareRoot(element).childNodes;
for (var i=0; i<childNodes.length; i++) {
var node = childNodes[i];
if (node.nodeType === Node.TEXT_NODE) {
this._parseTextNode(current, node, i);
}
}
}
_parseTextNode(pipelineElement, node, nodeIndex) {
var ast = this._parser.parseInterpolation(node.nodeValue, this._compilationUnit);
if (isPresent(ast)) {
DOM.setText(node, ' ');
pipelineElement.addTextNodeBinding(nodeIndex, ast);
}
}
}

View File

@ -0,0 +1,113 @@
import {isBlank, isPresent, BaseException} from 'facade/src/lang';
import {DOM, TemplateElement} from 'facade/src/dom';
import {MapWrapper, ListWrapper} from 'facade/src/collection';
import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {StringWrapper} from 'facade/src/lang';
import {$BANG} from 'change_detection/src/parser/lexer';
/**
* Splits views at `<template>` elements or elements with `template` attribute:
* For `<template>` elements:
* - moves the content into a new and disconnected `<template>` element
* that is marked as view root.
*
* For elements with a `template` attribute:
* - replaces the element with an empty `<template>` element,
* parses the content of the `template` attribute and adds the information to that
* `<template>` element. Marks the elements as view root.
*
* Note: In both cases the root of the nested view is disconnected from its parent element.
* This is needed for browsers that don't support the `<template>` element
* as we want to do locate elements with bindings using `getElementsByClassName` later on,
* which should not descend into the nested view.
*
* Fills:
* - CompileElement#isViewRoot
* - CompileElement#variableBindings
* - CompileElement#propertyBindings
*/
export class ViewSplitter extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (isBlank(parent)) {
current.isViewRoot = true;
} else {
if (current.element instanceof TemplateElement) {
if (!current.isViewRoot) {
var viewRoot = new CompileElement(DOM.createTemplate(''));
var currentElement:TemplateElement = current.element;
var viewRootElement:TemplateElement = viewRoot.element;
this._moveChildNodes(currentElement.content, viewRootElement.content);
viewRoot.isViewRoot = true;
control.addChild(viewRoot);
}
} else {
var attrs = current.attrs();
var templateBindings = MapWrapper.get(attrs, 'template');
var hasTemplateBinding = isPresent(templateBindings);
// look for template shortcuts such as !if="condition" and treat them as template="if condition"
MapWrapper.forEach(attrs, (attrValue, attrName) => {
if (StringWrapper.charCodeAt(attrName, 0) == $BANG) {
var key = StringWrapper.substring(attrName, 1); // remove the bang
if (hasTemplateBinding) {
// 2nd template binding detected
throw new BaseException(`Only one template directive per element is allowed: ` +
`${templateBindings} and ${key} cannot be used simultaneously!`);
} else {
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
hasTemplateBinding = true;
}
}
});
if (hasTemplateBinding) {
var newParent = new CompileElement(DOM.createTemplate(''));
current.isViewRoot = true;
this._parseTemplateBindings(templateBindings, newParent);
this._addParentElement(current.element, newParent.element);
control.addParent(newParent);
current.element.remove();
}
}
}
}
_moveChildNodes(source, target) {
while (isPresent(source.firstChild)) {
DOM.appendChild(target, source.firstChild);
}
}
_addParentElement(currentElement, newParentElement) {
DOM.insertBefore(currentElement, newParentElement);
DOM.appendChild(newParentElement, currentElement);
}
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
var bindings = this._parser.parseTemplateBindings(templateBindings, this._compilationUnit);
for (var i=0; i<bindings.length; i++) {
var binding = bindings[i];
if (binding.keyIsVar) {
compileElement.addVariableBinding(binding.key, binding.name);
} else if (isPresent(binding.expression)) {
compileElement.addPropertyBinding(binding.key, binding.expression);
} else {
compileElement.element.setAttribute(binding.key, '');
}
}
}
}