feat(compiler): initial version of the compiler.
Supports: - binds text nodes, element properties and directive properties - locates decorator, component and template directives. - inline templates of components The compiler is built using a pipeline design, see core/src/compiler/pipeline package. Integration tests to show how the compiler, change_detection and DI work together: core/test/compiler/integration_spec.js
This commit is contained in:
parent
62efb56b0a
commit
7a70f8f92d
|
@ -1,4 +1,5 @@
|
|||
import {SelectorMatcher, CssSelector} from "core/compiler/selector";
|
||||
import {SelectorMatcher} from "core/compiler/selector";
|
||||
import {CssSelector} from "core/compiler/selector";
|
||||
import {StringWrapper, Math} from 'facade/lang';
|
||||
import {ListWrapper} from 'facade/collection';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {ProtoRecord, Record} from './record';
|
||||
import {FIELD, IMPLEMENTS, isBlank, isPresent} from 'facade/lang';
|
||||
import {AST, AccessMember, ImplicitReceiver, AstVisitor} from './parser/ast';
|
||||
import {AST, AccessMember, ImplicitReceiver, AstVisitor, Binary, LiteralPrimitive} from './parser/ast';
|
||||
|
||||
export class ProtoWatchGroup {
|
||||
@FIELD('headRecord:ProtoRecord')
|
||||
|
@ -126,6 +126,17 @@ class ProtoRecordCreator {
|
|||
//do nothing
|
||||
}
|
||||
|
||||
// TODO: add tests for this method!
|
||||
visitLiteralPrimitive(ast:LiteralPrimitive) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// TODO: add tests for this method!
|
||||
visitBinary(ast:Binary) {
|
||||
ast.left.visit(this);
|
||||
ast.right.visit(this);
|
||||
}
|
||||
|
||||
visitAccessMember(ast:AccessMember) {
|
||||
ast.receiver.visit(this);
|
||||
this.add(new ProtoRecord(this.protoWatchGroup, ast.name, null));
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import {Directive} from './directive';
|
||||
import {ABSTRACT, CONST} from 'facade/lang';
|
||||
import {CONST} from 'facade/lang';
|
||||
|
||||
export class Component extends Directive {
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes,
|
||||
template,
|
||||
|
@ -12,15 +13,17 @@ export class Component extends Directive {
|
|||
componentServices
|
||||
}:{
|
||||
selector:String,
|
||||
bind:Object,
|
||||
template:TemplateConfig,
|
||||
lightDomServices:DomServicesFunction,
|
||||
shadowDomServices:DomServicesFunction,
|
||||
componentServices:ComponentServicesFunction,
|
||||
implementsTypes:Array<Type>
|
||||
})
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
lightDomServices: lightDomServices,
|
||||
implementsTypes: implementsTypes});
|
||||
this.template = template;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import {Directive} from './directive';
|
||||
import {CONST} from 'facade/lang';
|
||||
|
||||
export class Decorator extends Directive {
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes
|
||||
}:{
|
||||
selector:String,
|
||||
bind:Object,
|
||||
lightDomServices:ElementServicesFunction,
|
||||
implementsTypes:Array<Type>
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
lightDomServices: lightDomServices,
|
||||
implementsTypes: implementsTypes
|
||||
});
|
||||
}
|
||||
}
|
|
@ -8,10 +8,12 @@ export class Directive {
|
|||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes
|
||||
}:{
|
||||
selector:String,
|
||||
bind:Object,
|
||||
lightDomServices:ElementServicesFunction,
|
||||
implementsTypes:Array<Type>
|
||||
})
|
||||
|
@ -19,5 +21,6 @@ export class Directive {
|
|||
this.selector = selector;
|
||||
this.lightDomServices = lightDomServices;
|
||||
this.implementsTypes = implementsTypes;
|
||||
this.bind = bind;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import {Directive} from './directive';
|
||||
import {CONST} from 'facade/lang';
|
||||
|
||||
export class Template extends Directive {
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
bind,
|
||||
lightDomServices,
|
||||
implementsTypes
|
||||
}:{
|
||||
selector:String,
|
||||
bind:Object,
|
||||
lightDomServices:ElementServicesFunction,
|
||||
implementsTypes:Array<Type>
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
bind: bind,
|
||||
lightDomServices: lightDomServices,
|
||||
implementsTypes: implementsTypes
|
||||
});
|
||||
}
|
||||
}
|
|
@ -5,17 +5,20 @@ export class TemplateConfig {
|
|||
@CONST()
|
||||
constructor({
|
||||
url,
|
||||
inline,
|
||||
directives,
|
||||
formatters,
|
||||
source
|
||||
}: {
|
||||
url: String,
|
||||
inline: String,
|
||||
directives: List<Type>,
|
||||
formatters: List<Type>,
|
||||
source: List<TemplateConfig>
|
||||
})
|
||||
{
|
||||
this.url = url;
|
||||
this.inline = inline;
|
||||
this.directives = directives;
|
||||
this.formatters = formatters;
|
||||
this.source = source;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import {Type, FIELD} from 'facade/lang';
|
||||
import {Directive} from '../annotations/directive'
|
||||
|
||||
/**
|
||||
* Combination of a type with the Directive annotation
|
||||
*/
|
||||
export class AnnotatedType {
|
||||
constructor(annotation:Directive, type:Type) {
|
||||
constructor(type:Type, annotation:Directive) {
|
||||
this.annotation = annotation;
|
||||
this.type = type;
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import {Type} from 'facade/lang';
|
||||
import {Directive} from '../annotations/directive'
|
||||
|
||||
/**
|
||||
* Interface representing a way of extracting [Directive] annotations from
|
||||
* [Type]. This interface has three native implementations:
|
||||
*
|
||||
* 1) JavaScript native implementation
|
||||
* 2) Dart reflective implementation
|
||||
* 3) Dart transformer generated implementation
|
||||
*/
|
||||
export class AnnotationsExtractor {
|
||||
extract(type:Type):Directive {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,28 +1,71 @@
|
|||
import {Type} from 'facade/lang';
|
||||
import {Promise} from 'facade/async';
|
||||
import {Element} from 'facade/dom';
|
||||
//import {ProtoView} from './view';
|
||||
import {Type, FIELD, isBlank, isPresent} from 'facade/lang';
|
||||
import {Promise, PromiseWrapper} from 'facade/async';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
|
||||
import {Reflector} from './reflector';
|
||||
import {ProtoView} from './view';
|
||||
import {CompilePipeline} from './pipeline/compile_pipeline';
|
||||
import {CompileElement} from './pipeline/compile_element';
|
||||
import {createDefaultSteps} from './pipeline/default_steps';
|
||||
import {TemplateLoader} from './template_loader';
|
||||
import {FIELD} from 'facade/lang';
|
||||
import {AnnotatedType} from './annotated_type';
|
||||
|
||||
/**
|
||||
* The compiler loads and translates the html templates of components into
|
||||
* nested ProtoViews. To decompose its functionality it uses
|
||||
* the CompilePipeline and the CompileSteps.
|
||||
*/
|
||||
export class Compiler {
|
||||
|
||||
@FIELD('final _templateLoader:TemplateLoader')
|
||||
constructor(templateLoader:TemplateLoader) {
|
||||
constructor(templateLoader:TemplateLoader, reflector: Reflector, parser:Parser, closureMap:ClosureMap) {
|
||||
this._templateLoader = templateLoader;
|
||||
this._reflector = reflector;
|
||||
this._parser = parser;
|
||||
this._closureMap = closureMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* # Why promise?
|
||||
* - compilation will load templates. Instantiating views before templates are loaded will
|
||||
* complicate the Directive code. BENEFIT: view instantiation become synchrnous.
|
||||
* # Why result that is independent of injector?
|
||||
* - don't know about injector in deserialization
|
||||
* - compile does not need the injector, only the ViewFactory does
|
||||
*/
|
||||
compile(component:Type, element:Element/* = null*/):Promise/*<ProtoView>*/ {
|
||||
return null;
|
||||
createSteps(component:AnnotatedType):List<CompileStep> {
|
||||
var directives = component.annotation.template.directives;
|
||||
var annotatedDirectives = ListWrapper.create();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
ListWrapper.push(annotatedDirectives, this._reflector.annotatedType(directives[i]));
|
||||
}
|
||||
return createDefaultSteps(this._parser, this._closureMap, annotatedDirectives);
|
||||
}
|
||||
|
||||
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
|
||||
// TODO load all components transitively from the cache first
|
||||
var cache = null;
|
||||
return PromiseWrapper.resolve(this._compileAllCached(
|
||||
this._reflector.annotatedType(component),
|
||||
cache,
|
||||
templateRoot)
|
||||
);
|
||||
}
|
||||
|
||||
_compileAllCached(component:AnnotatedType, cache, templateRoot:Element = null):ProtoView {
|
||||
if (isBlank(templateRoot)) {
|
||||
// TODO: read out the cache if templateRoot = null. Could contain:
|
||||
// - templateRoot string
|
||||
// - precompiled template
|
||||
// - ProtoView
|
||||
templateRoot = DOM.createTemplate(component.annotation.template.inline);
|
||||
}
|
||||
var pipeline = new CompilePipeline(this.createSteps(component));
|
||||
var compileElements = pipeline.process(templateRoot);
|
||||
var rootProtoView = compileElements[0].inheritedProtoView;
|
||||
// TODO: put the rootProtoView into the cache to support recursive templates!
|
||||
|
||||
for (var i=0; i<compileElements.length; i++) {
|
||||
var ce = compileElements[i];
|
||||
if (isPresent(ce.componentDirective)) {
|
||||
ce.inheritedElementBinder.nestedProtoView = this._compileAllCached(ce.componentDirective, cache, null);
|
||||
}
|
||||
}
|
||||
|
||||
return rootProtoView;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import {ProtoElementInjector} from './element_injector';
|
||||
import {FIELD} from 'facade/lang';
|
||||
import {List} from 'facade/collection';
|
||||
// Comment out as dartanalyzer does not look into @FIELD
|
||||
// import {List} from 'facade/collection';
|
||||
// import {ProtoView} from './view';
|
||||
|
||||
export class ElementBinder {
|
||||
@FIELD('final protoElementInjector:ProtoElementInjector')
|
||||
@FIELD('final textNodeIndices:List<int>')
|
||||
@FIELD('final hasElementPropertyBindings:bool')
|
||||
constructor(protoElementInjector: ProtoElementInjector,
|
||||
textNodeIndices:List, hasElementPropertyBindings:boolean) {
|
||||
this.protoElementInjector = protoElementInjector;
|
||||
this.textNodeIndices = textNodeIndices;
|
||||
this.hasElementPropertyBindings = hasElementPropertyBindings;
|
||||
@FIELD('hasElementPropertyBindings:bool')
|
||||
@FIELD('nestedProtoView:ProtoView')
|
||||
constructor(protoElementInjector: ProtoElementInjector) {
|
||||
this.protoElementInjector = protoElementInjector;
|
||||
// updated later when text nodes are bound
|
||||
this.textNodeIndices = [];
|
||||
// updated later when element properties are bound
|
||||
this.hasElementPropertyBindings = false;
|
||||
// updated later, so we are able to resolve cycles
|
||||
this.nestedProtoView = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ import {Math} from 'facade/math';
|
|||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
|
||||
import {Parent, Ancestor} from 'core/annotations/visibility';
|
||||
import {View} from './view';
|
||||
import {StaticKeys} from './static_keys';
|
||||
// Comment out as dartanalyzer does not look into @FIELD
|
||||
// import {View} from './view';
|
||||
|
||||
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import {ListWrapper} from 'facade/collection';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {CompileElement} from './compile_element';
|
||||
|
||||
/**
|
||||
* Controls the processing order of elements.
|
||||
* Right now it only allows to add a parent element.
|
||||
*/
|
||||
export class CompileControl {
|
||||
constructor(steps) {
|
||||
this._steps = steps;
|
||||
this._currentStepIndex = 0;
|
||||
this._parent = null;
|
||||
this._current = null;
|
||||
this._results = 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._current = current;
|
||||
this._currentStepIndex = i;
|
||||
step.process(parent, current, this);
|
||||
parent = this._parent;
|
||||
}
|
||||
ListWrapper.push(results, current);
|
||||
|
||||
this._currentStepIndex = previousStepIndex;
|
||||
this._parent = previousParent;
|
||||
}
|
||||
|
||||
addParent(newElement:CompileElement) {
|
||||
var currEl = this._current.element;
|
||||
var newEl = newElement.element;
|
||||
DOM.parentElement(currEl).insertBefore(newEl, currEl);
|
||||
DOM.appendChild(newEl, currEl);
|
||||
|
||||
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
|
||||
this._parent = newElement;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {Element, DOM} from 'facade/dom';
|
||||
import {int, isBlank, isPresent} from 'facade/lang';
|
||||
import {AnnotatedType} from '../annotated_type';
|
||||
import {Decorator} from '../../annotations/decorator';
|
||||
import {Component} from '../../annotations/component';
|
||||
import {Template} from '../../annotations/template';
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
constructor(element:Element) {
|
||||
this.element = element;
|
||||
this._attrs = null;
|
||||
this._classList = null;
|
||||
this.textNodeBindings = null;
|
||||
this.propertyBindings = null;
|
||||
this.decoratorDirectives = null;
|
||||
this.templateDirective = null;
|
||||
this.componentDirective = 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;
|
||||
}
|
||||
|
||||
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:string) {
|
||||
if (isBlank(this.textNodeBindings)) {
|
||||
this.textNodeBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
|
||||
}
|
||||
|
||||
addPropertyBinding(property:string, expression:string) {
|
||||
if (isBlank(this.propertyBindings)) {
|
||||
this.propertyBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.propertyBindings, property, expression);
|
||||
}
|
||||
|
||||
addDirective(directive:AnnotatedType) {
|
||||
var annotation = directive.annotation;
|
||||
if (annotation instanceof Decorator) {
|
||||
if (isBlank(this.decoratorDirectives)) {
|
||||
this.decoratorDirectives = ListWrapper.create();
|
||||
}
|
||||
ListWrapper.push(this.decoratorDirectives, directive);
|
||||
} else if (annotation instanceof Template) {
|
||||
this.templateDirective = directive;
|
||||
} else if (annotation instanceof Component) {
|
||||
this.componentDirective = directive;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {Element, TemplateElement, Node} from 'facade/dom';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {AnnotatedType} from '../annotated_type';
|
||||
|
||||
/**
|
||||
* CompilePipeline for executing CompileSteps recursively for
|
||||
* all elements in a template.
|
||||
*/
|
||||
export class CompilePipeline {
|
||||
constructor(steps:List<CompileStep>) {
|
||||
this._control = new CompileControl(steps);
|
||||
}
|
||||
|
||||
process(rootElement:Element):List {
|
||||
var results = ListWrapper.create();
|
||||
this._process(results, null, rootElement);
|
||||
return results;
|
||||
}
|
||||
|
||||
_process(results, parent:CompileElement, element:Element) {
|
||||
var current = new CompileElement(element);
|
||||
this._control.internalProcess(results, 0, parent, current);
|
||||
var childNodes;
|
||||
if (element instanceof TemplateElement) {
|
||||
childNodes = element.content.childNodes;
|
||||
} else {
|
||||
childNodes = element.childNodes;
|
||||
}
|
||||
for (var i=0; i<childNodes.length; i++) {
|
||||
var node = childNodes[i];
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
this._process(results, current, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {AnnotatedType} from '../annotated_type';
|
||||
|
||||
/**
|
||||
* 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) {}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {List} from 'facade/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';
|
||||
|
||||
/**
|
||||
* Default steps used for compiling a template.
|
||||
* Takes in an HTMLElement and produces the ProtoViews,
|
||||
* ProtoElementInjectors and ElementBinders in the end.
|
||||
*/
|
||||
export function createDefaultSteps(
|
||||
parser:Parser, closureMap:ClosureMap,
|
||||
directives: List<AnnotatedType>
|
||||
) {
|
||||
return [
|
||||
new PropertyBindingParser(),
|
||||
new TextInterpolationParser(),
|
||||
new DirectiveParser(directives),
|
||||
new ViewSplitter(),
|
||||
new ElementBindingMarker(),
|
||||
new ProtoViewBuilder(),
|
||||
new ProtoElementInjectorBuilder(),
|
||||
new ElementBinderBuilder(parser, closureMap)
|
||||
];
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import {isPresent, BaseException} from 'facade/lang';
|
||||
import {List, MapWrapper} from 'facade/collection';
|
||||
import {SelectorMatcher} from '../selector';
|
||||
import {CssSelector} from '../selector';
|
||||
|
||||
import {AnnotatedType} from '../annotated_type';
|
||||
import {Template} from '../../annotations/template';
|
||||
import {Component} from '../../annotations/component';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {Reflector} from '../reflector';
|
||||
|
||||
/**
|
||||
* Parses the directives on a single element.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#decoratorDirectives
|
||||
* - CompileElement#templateDirecitve
|
||||
* - CompileElement#componentDirective.
|
||||
*
|
||||
* Reads:
|
||||
* - CompileElement#propertyBindings (to find directives contained
|
||||
* in the property bindings)
|
||||
*/
|
||||
export class DirectiveParser extends CompileStep {
|
||||
constructor(directives:List<AnnotatedType>) {
|
||||
this._selectorMatcher = new SelectorMatcher();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
var annotatedType = directives[i];
|
||||
this._selectorMatcher.addSelectable(
|
||||
CssSelector.parse(annotatedType.annotation.selector),
|
||||
annotatedType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
// Allow to find directives even though the attribute is bound
|
||||
if (isPresent(current.propertyBindings)) {
|
||||
MapWrapper.forEach(current.propertyBindings, (expression, boundProp) => {
|
||||
cssSelector.addAttribute(boundProp, expression);
|
||||
});
|
||||
}
|
||||
this._selectorMatcher.match(cssSelector, (directive) => {
|
||||
if (isPresent(current.templateDirective) && (directive.annotation instanceof Template)) {
|
||||
throw new BaseException('Only one template directive per element is allowed!');
|
||||
}
|
||||
if (isPresent(current.componentDirective) && (directive.annotation instanceof Component)) {
|
||||
throw new BaseException('Only one component directive per element is allowed!');
|
||||
}
|
||||
current.addDirective(directive);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
import {int, isPresent, isBlank, Type, BaseException, stringify} from 'facade/lang';
|
||||
import {Element} from 'facade/dom';
|
||||
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/collection';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||
|
||||
import {Directive} from '../../annotations/directive';
|
||||
import {Component} from '../../annotations/component';
|
||||
import {AnnotatedType} from '../annotated_type';
|
||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from '../view';
|
||||
import {ProtoElementInjector} from '../element_injector';
|
||||
import {ElementBinder} from '../element_binder';
|
||||
import {Reflector} from '../reflector';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
/**
|
||||
* Creates the ElementBinders and adds watches to the
|
||||
* ProtoWatchGroup.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#inheritedElementBinder
|
||||
*
|
||||
* Reads:
|
||||
* - (in parent) CompileElement#inheritedElementBinder
|
||||
* - CompileElement#hasBindings
|
||||
* - CompileElement#isViewRoot
|
||||
* - CompileElement#inheritedViewRoot
|
||||
* - CompileElement#inheritedProtoElementInjector
|
||||
* - CompileElement#textNodeBindings
|
||||
* - CompileElement#propertyBindings
|
||||
* - 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 {
|
||||
constructor(parser:Parser, closureMap:ClosureMap) {
|
||||
this._parser = parser;
|
||||
this._closureMap = closureMap;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var elementBinder;
|
||||
if (current.hasBindings) {
|
||||
var protoView = current.inheritedProtoView;
|
||||
elementBinder = protoView.bindElement(current.inheritedProtoElementInjector);
|
||||
|
||||
if (isPresent(current.textNodeBindings)) {
|
||||
this._bindTextNodes(protoView, current.textNodeBindings);
|
||||
}
|
||||
if (isPresent(current.propertyBindings)) {
|
||||
this._bindElementProperties(protoView, current.propertyBindings);
|
||||
}
|
||||
this._bindDirectiveProperties(this._collectDirectives(current), current);
|
||||
} else if (isPresent(parent)) {
|
||||
elementBinder = parent.inheritedElementBinder;
|
||||
}
|
||||
current.inheritedElementBinder = elementBinder;
|
||||
}
|
||||
|
||||
_bindTextNodes(protoView, textNodeBindings) {
|
||||
MapWrapper.forEach(textNodeBindings, (expression, indexInParent) => {
|
||||
protoView.bindTextNode(indexInParent, this._parser.parseBinding(expression));
|
||||
});
|
||||
}
|
||||
|
||||
_bindElementProperties(protoView, propertyBindings) {
|
||||
MapWrapper.forEach(propertyBindings, (expression, property) => {
|
||||
protoView.bindElementProperty(property, this._parser.parseBinding(expression));
|
||||
});
|
||||
}
|
||||
|
||||
_collectDirectives(pipelineElement) {
|
||||
var directives;
|
||||
if (isPresent(pipelineElement.decoratorDirectives)) {
|
||||
directives = ListWrapper.clone(pipelineElement.decoratorDirectives);
|
||||
} else {
|
||||
directives = [];
|
||||
}
|
||||
if (isPresent(pipelineElement.templateDirective)) {
|
||||
ListWrapper.push(directives, pipelineElement.templateDirective);
|
||||
}
|
||||
if (isPresent(pipelineElement.componentDirective)) {
|
||||
ListWrapper.push(directives, pipelineElement.componentDirective);
|
||||
}
|
||||
return directives;
|
||||
}
|
||||
|
||||
_bindDirectiveProperties(typesWithAnnotations, pipelineElement) {
|
||||
var protoView = pipelineElement.inheritedProtoView;
|
||||
var directiveIndex = 0;
|
||||
ListWrapper.forEach(typesWithAnnotations, (typeWithAnnotation) => {
|
||||
var annotation = typeWithAnnotation.annotation;
|
||||
if (isBlank(annotation.bind)) {
|
||||
return;
|
||||
}
|
||||
StringMapWrapper.forEach(annotation.bind, (dirProp, elProp) => {
|
||||
var expression = isPresent(pipelineElement.propertyBindings) ?
|
||||
MapWrapper.get(pipelineElement.propertyBindings, elProp) :
|
||||
null;
|
||||
if (isBlank(expression)) {
|
||||
throw new BaseException('No element binding found for property '+elProp
|
||||
+' which is required by directive '+stringify(typeWithAnnotation.type));
|
||||
}
|
||||
protoView.bindDirectiveProperty(
|
||||
directiveIndex++,
|
||||
this._parser.parseBinding(expression),
|
||||
dirProp,
|
||||
this._closureMap.setter(dirProp)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import {isPresent} from 'facade/lang';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
import {DOM} from 'facade/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#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.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import {isPresent, isBlank, RegExpWrapper} from 'facade/lang';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
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_DASH_REGEXP = RegExpWrapper.create('bind-((?:[^-]|-(?!-))+)(?:--(.+))?');
|
||||
var PROP_BIND_REGEXP = RegExpWrapper.create('\\[([^|]+)(?:\\|(.+))?\\]');
|
||||
|
||||
/**
|
||||
* Parses the property bindings on a single element.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#propertyBindings
|
||||
*/
|
||||
export class PropertyBindingParser extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var attrs = current.attrs();
|
||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||
var parts = RegExpWrapper.firstMatch(BIND_DASH_REGEXP, attrName);
|
||||
if (isBlank(parts)) {
|
||||
parts = RegExpWrapper.firstMatch(PROP_BIND_REGEXP, attrName);
|
||||
}
|
||||
if (isPresent(parts)) {
|
||||
current.addPropertyBinding(parts[1], attrValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import {isPresent,} from 'facade/lang';
|
||||
import {ListWrapper} from 'facade/collection';
|
||||
|
||||
import {ProtoElementInjector} from '../element_injector';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
/**
|
||||
* Creates the ProtoElementInjectors.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#inheriteProtoElementInjector
|
||||
*
|
||||
* Reads:
|
||||
* - (in parent) CompileElement#inheriteProtoElementInjector
|
||||
* - 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) {
|
||||
return new ProtoElementInjector(parent, index, directives);
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var inheritedProtoElementInjector = null;
|
||||
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
|
||||
var injectorBindings = this._collectDirectiveTypes(current);
|
||||
// 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;
|
||||
inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
|
||||
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings
|
||||
);
|
||||
} else {
|
||||
inheritedProtoElementInjector = parentProtoElementInjector;
|
||||
}
|
||||
current.inheritedProtoElementInjector = inheritedProtoElementInjector;
|
||||
}
|
||||
|
||||
_getParentProtoElementInjector(parent, current) {
|
||||
var parentProtoElementInjector = null;
|
||||
if (current.isViewRoot) {
|
||||
parentProtoElementInjector = null;
|
||||
} else if (isPresent(parent)) {
|
||||
parentProtoElementInjector = parent.inheritedProtoElementInjector;
|
||||
}
|
||||
return parentProtoElementInjector;
|
||||
}
|
||||
|
||||
_collectDirectiveTypes(pipelineElement) {
|
||||
var directiveTypes = [];
|
||||
if (isPresent(pipelineElement.decoratorDirectives)) {
|
||||
for (var i=0; i<pipelineElement.decoratorDirectives.length; i++) {
|
||||
ListWrapper.push(directiveTypes, pipelineElement.decoratorDirectives[i].type);
|
||||
}
|
||||
}
|
||||
if (isPresent(pipelineElement.templateDirective)) {
|
||||
ListWrapper.push(directiveTypes, pipelineElement.templateDirective.type);
|
||||
}
|
||||
if (isPresent(pipelineElement.componentDirective)) {
|
||||
ListWrapper.push(directiveTypes, pipelineElement.componentDirective.type);
|
||||
}
|
||||
return directiveTypes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import {isPresent, BaseException} from 'facade/lang';
|
||||
import {ListWrapper} from 'facade/collection';
|
||||
|
||||
import {ProtoView} from '../view';
|
||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
/**
|
||||
* Fills:
|
||||
* - (in parent): CompileElement#inheritedElementBinder.nestedProtoView
|
||||
* - CompileElement#inhertiedViewRoot
|
||||
*
|
||||
* Reads:
|
||||
* - (in parent): CompileElement#inhertiedViewRoot
|
||||
* - CompileElement#isViewRoot
|
||||
*/
|
||||
export class ProtoViewBuilder extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var inheritedProtoView = null;
|
||||
if (current.isViewRoot) {
|
||||
inheritedProtoView = new ProtoView(current.element, new ProtoWatchGroup());
|
||||
if (isPresent(parent)) {
|
||||
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
|
||||
throw new BaseException('Only one nested view per element is allowed');
|
||||
}
|
||||
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
|
||||
}
|
||||
} else if (isPresent(parent)) {
|
||||
inheritedProtoView = parent.inheritedProtoView;
|
||||
}
|
||||
current.inheritedProtoView = inheritedProtoView;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import {RegExpWrapper, StringWrapper} from 'facade/lang';
|
||||
import {TemplateElement, Node, DOM} from 'facade/dom';
|
||||
|
||||
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 INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
|
||||
|
||||
/**
|
||||
* Parses interpolations in direct text child nodes of the current element.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#textNodeBindings
|
||||
*/
|
||||
export class TextInterpolationParser extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var element = current.element;
|
||||
var childNodes;
|
||||
if (element instanceof TemplateElement) {
|
||||
childNodes = element.content.childNodes;
|
||||
} else {
|
||||
childNodes = 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) {
|
||||
// TODO: escape fixed string quotes
|
||||
// TODO: add braces around the expression
|
||||
// TODO: suppress empty strings
|
||||
// TODO: add stringify formatter
|
||||
var parts = StringWrapper.split(node.nodeValue, INTERPOLATION_REGEXP);
|
||||
if (parts.length > 1) {
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
if (i%2 === 0) {
|
||||
// fixed string
|
||||
parts[i] = "'" + parts[i] + "'";
|
||||
} else {
|
||||
// expression
|
||||
parts[i] = "" + parts[i] + "";
|
||||
}
|
||||
}
|
||||
DOM.setText(node, ' ');
|
||||
pipelineElement.addTextNodeBinding(nodeIndex, parts.join('+'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import {isBlank, isPresent} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {MapWrapper, StringMapWrapper} from 'facade/collection';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
/**
|
||||
* Splits views at template directives:
|
||||
* Replaces the element with an empty <template> element that contains the
|
||||
* template directive and all property bindings needed for the template directive.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#isViewRoot
|
||||
*
|
||||
* Updates:
|
||||
* - CompileElement#templateDirective
|
||||
* - CompileElement#propertyBindings
|
||||
*
|
||||
* Reads:
|
||||
* - CompileElement#templateDirective
|
||||
* - CompileElement#propertyBindings
|
||||
*/
|
||||
export class ViewSplitter extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var element = current.element;
|
||||
if (isPresent(current.templateDirective)) {
|
||||
var templateElement = DOM.createTemplate('');
|
||||
var templateBoundProperties = MapWrapper.create();
|
||||
var nonTemplateBoundProperties = MapWrapper.create();
|
||||
this._splitElementPropertyBindings(current, templateBoundProperties, nonTemplateBoundProperties);
|
||||
|
||||
var newParentElement = new CompileElement(templateElement);
|
||||
newParentElement.propertyBindings = templateBoundProperties;
|
||||
newParentElement.templateDirective = current.templateDirective;
|
||||
control.addParent(newParentElement);
|
||||
|
||||
// disconnect child view from their parent view
|
||||
element.remove();
|
||||
|
||||
current.templateDirective = null;
|
||||
current.propertyBindings = nonTemplateBoundProperties;
|
||||
current.isViewRoot = true;
|
||||
} else if (isBlank(parent)) {
|
||||
current.isViewRoot = true;
|
||||
}
|
||||
}
|
||||
|
||||
_splitElementPropertyBindings(compileElement, templateBoundProperties, nonTemplateBoundProperties) {
|
||||
var dirBindings = compileElement.templateDirective.annotation.bind;
|
||||
if (isPresent(dirBindings) && isPresent(compileElement.propertyBindings)) {
|
||||
MapWrapper.forEach(compileElement.propertyBindings, (expr, elProp) => {
|
||||
if (isPresent(StringMapWrapper.get(dirBindings, elProp))) {
|
||||
MapWrapper.set(templateBoundProperties, elProp, expr);
|
||||
} else {
|
||||
MapWrapper.set(nonTemplateBoundProperties, elProp, expr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
library facade.di.reflector;
|
||||
|
||||
import 'dart:mirrors';
|
||||
import '../annotations/directive.dart';
|
||||
import './annotated_type.dart';
|
||||
import 'package:facade/lang.dart';
|
||||
|
||||
/**
|
||||
* Interface representing a way of extracting [Directive] annotations from
|
||||
* [Type]. This interface has three native implementations:
|
||||
*
|
||||
* 1) JavaScript native implementation
|
||||
* 2) Dart reflective implementation
|
||||
* 3) Dart transformer generated implementation
|
||||
*/
|
||||
class Reflector {
|
||||
AnnotatedType annotatedType(Type type) {
|
||||
var directiveAnnotations = reflectType(type).metadata
|
||||
.map( (im) => im.reflectee)
|
||||
.where( (annotation) => annotation is Directive);
|
||||
if (directiveAnnotations.isEmpty) {
|
||||
throw new BaseException('No Directive annotation found on '+stringify(type));
|
||||
}
|
||||
return new AnnotatedType(type, directiveAnnotations.first);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import {Type, isPresent, BaseException} from 'facade/lang';
|
||||
import {Directive} from '../annotations/directive';
|
||||
import {AnnotatedType} from './annotated_type';
|
||||
|
||||
/**
|
||||
* Interface representing a way of extracting [Directive] annotations from
|
||||
* [Type]. This interface has three native implementations:
|
||||
*
|
||||
* 1) JavaScript native implementation
|
||||
* 2) Dart reflective implementation
|
||||
* 3) Dart transformer generated implementation
|
||||
*/
|
||||
export class Reflector {
|
||||
annotatedType(type:Type):AnnotatedType {
|
||||
var annotations = type.annotations;
|
||||
if (annotations) {
|
||||
for (var i=0; i<annotations.length; i++) {
|
||||
var annotation = annotations[i];
|
||||
if (annotation instanceof Directive) {
|
||||
return new AnnotatedType(type, annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new BaseException('No Directive annotation found on ' + type.name);
|
||||
}
|
||||
}
|
|
@ -1,30 +1,107 @@
|
|||
import {List, ListWrapper, StringMapWrapper} from 'facade/collection';
|
||||
import {RegExpWrapper, RegExpMatcherWrapper, CONST, isPresent, isBlank} from 'facade/lang';
|
||||
import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper} from 'facade/lang';
|
||||
|
||||
const _EMPTY_ATTR_VALUE = '';
|
||||
|
||||
// TODO: Can't use `const` here as
|
||||
// in Dart this is not transpiled into `final` yet...
|
||||
var _SELECTOR_REGEXP =
|
||||
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
|
||||
|
||||
/**
|
||||
* A css selector contains an element name,
|
||||
* css classes and attribute/value pairs with the purpose
|
||||
* of selecting subsets out of them.
|
||||
*/
|
||||
export class CssSelector {
|
||||
static parse(selector:string):CssSelector {
|
||||
var cssSelector = new CssSelector();
|
||||
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||
var match;
|
||||
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
||||
if (isPresent(match[1])) {
|
||||
cssSelector.setElement(match[1]);
|
||||
}
|
||||
if (isPresent(match[2])) {
|
||||
cssSelector.addClassName(match[2]);
|
||||
}
|
||||
if (isPresent(match[3])) {
|
||||
cssSelector.addAttribute(match[3], match[4]);
|
||||
}
|
||||
}
|
||||
return cssSelector;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.element = null;
|
||||
this.classNames = ListWrapper.create();
|
||||
this.attrs = ListWrapper.create();
|
||||
}
|
||||
|
||||
setElement(element:string = null) {
|
||||
if (isPresent(element)) {
|
||||
element = element.toLowerCase();
|
||||
}
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
addAttribute(name:string, value:string = _EMPTY_ATTR_VALUE) {
|
||||
ListWrapper.push(this.attrs, name.toLowerCase());
|
||||
if (isPresent(value)) {
|
||||
value = value.toLowerCase();
|
||||
} else {
|
||||
value = _EMPTY_ATTR_VALUE;
|
||||
}
|
||||
ListWrapper.push(this.attrs, value);
|
||||
}
|
||||
|
||||
addClassName(name:string) {
|
||||
ListWrapper.push(this.classNames, name.toLowerCase());
|
||||
}
|
||||
|
||||
toString():string {
|
||||
var res = '';
|
||||
if (isPresent(this.element)) {
|
||||
res += this.element;
|
||||
}
|
||||
if (isPresent(this.classNames)) {
|
||||
for (var i=0; i<this.classNames.length; i++) {
|
||||
res += '.' + this.classNames[i];
|
||||
}
|
||||
}
|
||||
if (isPresent(this.attrs)) {
|
||||
for (var i=0; i<this.attrs.length;) {
|
||||
var attrName = this.attrs[i++];
|
||||
var attrValue = this.attrs[i++]
|
||||
res += '[' + attrName;
|
||||
if (attrValue.length > 0) {
|
||||
res += '=' + attrValue;
|
||||
}
|
||||
res += ']';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a list of CssSelectors and allows to calculate which ones
|
||||
* are contained in a given CssSelector.
|
||||
*/
|
||||
export class SelectorMatcher {
|
||||
/* TODO: Add these fields when the transpiler supports fields
|
||||
_elementMap:Map<String, List>;
|
||||
_elementPartialMap:Map<String, Selector>;
|
||||
|
||||
_classMap:Map<String, List>;
|
||||
_classPartialMap:Map<String, Selector>;
|
||||
|
||||
_attrValueMap:Map<String, Map<String, List>>;
|
||||
_attrValuePartialMap:Map<String, Map<String, Selector>>;
|
||||
*/
|
||||
constructor() {
|
||||
this._selectables = ListWrapper.create();
|
||||
|
||||
this._elementMap = StringMapWrapper.create();
|
||||
this._elementPartialMap = StringMapWrapper.create();
|
||||
this._elementMap = MapWrapper.create();
|
||||
this._elementPartialMap = MapWrapper.create();
|
||||
|
||||
this._classMap = StringMapWrapper.create();
|
||||
this._classPartialMap = StringMapWrapper.create();
|
||||
this._classMap = MapWrapper.create();
|
||||
this._classPartialMap = MapWrapper.create();
|
||||
|
||||
this._attrValueMap = StringMapWrapper.create();
|
||||
this._attrValuePartialMap = StringMapWrapper.create();
|
||||
this._attrValueMap = MapWrapper.create();
|
||||
this._attrValuePartialMap = MapWrapper.create();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,16 +137,15 @@ export class SelectorMatcher {
|
|||
}
|
||||
|
||||
if (isPresent(attrs)) {
|
||||
for (var index = 0; index<attrs.length; index++) {
|
||||
var isTerminal = index === attrs.length - 1;
|
||||
var attr = attrs[index];
|
||||
var attrName = attr.name;
|
||||
var attrValue = isPresent(attr.value) ? attr.value : _EMPTY_ATTR_VALUE;
|
||||
for (var index = 0; index<attrs.length; ) {
|
||||
var isTerminal = index === attrs.length - 2;
|
||||
var attrName = attrs[index++];
|
||||
var attrValue = attrs[index++];
|
||||
var map = isTerminal ? matcher._attrValueMap : matcher._attrValuePartialMap;
|
||||
var valuesMap = StringMapWrapper.get(map, attrName)
|
||||
var valuesMap = MapWrapper.get(map, attrName)
|
||||
if (isBlank(valuesMap)) {
|
||||
valuesMap = StringMapWrapper.create();
|
||||
StringMapWrapper.set(map, attrName, valuesMap);
|
||||
valuesMap = MapWrapper.create();
|
||||
MapWrapper.set(map, attrName, valuesMap);
|
||||
}
|
||||
if (isTerminal) {
|
||||
this._addTerminal(valuesMap, attrValue, selectable);
|
||||
|
@ -79,21 +155,21 @@ export class SelectorMatcher {
|
|||
}
|
||||
}
|
||||
}
|
||||
// TODO: map:StringMap when we have a StringMap type...
|
||||
_addTerminal(map, name:string, selectable) {
|
||||
var terminalList = StringMapWrapper.get(map, name)
|
||||
|
||||
_addTerminal(map:Map<string,string>, name:string, selectable) {
|
||||
var terminalList = MapWrapper.get(map, name)
|
||||
if (isBlank(terminalList)) {
|
||||
terminalList = ListWrapper.create();
|
||||
StringMapWrapper.set(map, name, terminalList);
|
||||
MapWrapper.set(map, name, terminalList);
|
||||
}
|
||||
ListWrapper.push(terminalList, selectable);
|
||||
}
|
||||
// TODO: map:StringMap when we have a StringMap type...
|
||||
_addPartial(map, name:string) {
|
||||
var matcher = StringMapWrapper.get(map, name)
|
||||
|
||||
_addPartial(map:Map<string,string>, name:string) {
|
||||
var matcher = MapWrapper.get(map, name)
|
||||
if (isBlank(matcher)) {
|
||||
matcher = new SelectorMatcher();
|
||||
StringMapWrapper.set(map, name, matcher);
|
||||
MapWrapper.set(map, name, matcher);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
@ -121,25 +197,27 @@ export class SelectorMatcher {
|
|||
}
|
||||
|
||||
if (isPresent(attrs)) {
|
||||
for (var index = 0; index<attrs.length; index++) {
|
||||
var attr = attrs[index];
|
||||
var attrName = attr.name;
|
||||
var attrValue = isPresent(attr.value) ? attr.value : _EMPTY_ATTR_VALUE;
|
||||
for (var index = 0; index<attrs.length;) {
|
||||
var attrName = attrs[index++];
|
||||
var attrValue = attrs[index++];
|
||||
|
||||
var valuesMap = StringMapWrapper.get(this._attrValueMap, attrName)
|
||||
var valuesMap = MapWrapper.get(this._attrValueMap, attrName);
|
||||
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
||||
this._matchTerminal(valuesMap, _EMPTY_ATTR_VALUE, matchedCallback);
|
||||
}
|
||||
this._matchTerminal(valuesMap, attrValue, matchedCallback);
|
||||
|
||||
valuesMap = StringMapWrapper.get(this._attrValuePartialMap, attrName)
|
||||
valuesMap = MapWrapper.get(this._attrValuePartialMap, attrName)
|
||||
this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: map:StringMap when we have a StringMap type...
|
||||
_matchTerminal(map, name, matchedCallback) {
|
||||
|
||||
_matchTerminal(map:Map<string,string> = null, name, matchedCallback) {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return;
|
||||
}
|
||||
var selectables = StringMapWrapper.get(map, name)
|
||||
var selectables = MapWrapper.get(map, name)
|
||||
if (isBlank(selectables)) {
|
||||
return;
|
||||
}
|
||||
|
@ -147,12 +225,12 @@ export class SelectorMatcher {
|
|||
matchedCallback(selectables[index]);
|
||||
}
|
||||
}
|
||||
// TODO: map:StringMap when we have a StringMap type...
|
||||
_matchPartial(map, name, cssSelector, matchedCallback) {
|
||||
|
||||
_matchPartial(map:Map<string,string> = null, name, cssSelector, matchedCallback) {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return;
|
||||
}
|
||||
var nestedSelector = StringMapWrapper.get(map, name)
|
||||
var nestedSelector = MapWrapper.get(map, name)
|
||||
if (isBlank(nestedSelector)) {
|
||||
return;
|
||||
}
|
||||
|
@ -162,71 +240,3 @@ export class SelectorMatcher {
|
|||
nestedSelector.match(cssSelector, matchedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
export class Attr {
|
||||
@CONST()
|
||||
constructor(name:string, value:string = null) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Can't use `const` here as
|
||||
// in Dart this is not transpiled into `final` yet...
|
||||
var _SELECTOR_REGEXP =
|
||||
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
|
||||
|
||||
export class CssSelector {
|
||||
static parse(selector:string):CssSelector {
|
||||
var element = null;
|
||||
var classNames = ListWrapper.create();
|
||||
var attrs = ListWrapper.create();
|
||||
selector = selector.toLowerCase();
|
||||
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||
var match;
|
||||
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
||||
if (isPresent(match[1])) {
|
||||
element = match[1];
|
||||
}
|
||||
if (isPresent(match[2])) {
|
||||
ListWrapper.push(classNames, match[2]);
|
||||
}
|
||||
if (isPresent(match[3])) {
|
||||
ListWrapper.push(attrs, new Attr(match[3], match[4]));
|
||||
}
|
||||
}
|
||||
return new CssSelector(element, classNames, attrs);
|
||||
}
|
||||
// TODO: do a toLowerCase() for all arguments
|
||||
@CONST()
|
||||
constructor(element:string, classNames:List<string>, attrs:List<Attr>) {
|
||||
this.element = element;
|
||||
this.classNames = classNames;
|
||||
this.attrs = attrs;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
var res = '';
|
||||
if (isPresent(this.element)) {
|
||||
res += this.element;
|
||||
}
|
||||
if (isPresent(this.classNames)) {
|
||||
for (var i=0; i<this.classNames.length; i++) {
|
||||
res += '.' + this.classNames[i];
|
||||
}
|
||||
}
|
||||
if (isPresent(this.attrs)) {
|
||||
for (var i=0; i<this.attrs.length; i++) {
|
||||
var attr = this.attrs[i];
|
||||
res += '[' + attr.name;
|
||||
if (isPresent(attr.value)) {
|
||||
res += '=' + attr.value;
|
||||
}
|
||||
res += ']';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import {Promise} from 'facade/async';
|
||||
//import {Document} from 'facade/dom';
|
||||
|
||||
/**
|
||||
* Strategy to load component templates.
|
||||
*/
|
||||
export class TemplateLoader {
|
||||
|
||||
constructor() {}
|
||||
|
||||
load(url:String):Promise/*<Document>*/ {
|
||||
load(url:String):Promise<Document> {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -2,22 +2,21 @@ import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facad
|
|||
import {ListWrapper} from 'facade/collection';
|
||||
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
|
||||
import {Record} from 'change_detection/record';
|
||||
import {AST} from 'change_detection/parser/ast';
|
||||
import {ProtoElementInjector, ElementInjector} from './element_injector';
|
||||
// Seems like we are stripping the generics part of List and dartanalyzer
|
||||
// complains about ElementBinder being unused. Comment back in once it makes it
|
||||
// into the generated code.
|
||||
// import {ElementBinder} from './element_binder';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {SetterFn} from 'change_detection/parser/closure_map';
|
||||
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
|
||||
import {List} from 'facade/collection';
|
||||
import {Injector} from 'di/di';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
|
||||
/***
|
||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||
*/
|
||||
@IMPLEMENTS(WatchGroupDispatcher)
|
||||
export class View {
|
||||
@FIELD('final fragment:DocumentFragment')
|
||||
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
||||
@FIELD('final rootElementInjectors:List<ElementInjector>')
|
||||
@FIELD('final elementInjectors:List<ElementInjector>')
|
||||
|
@ -28,12 +27,11 @@ export class View {
|
|||
/// to keep track of the nodes.
|
||||
@FIELD('final nodes:List<Node>')
|
||||
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
||||
constructor(fragment:DocumentFragment, elementInjector:List,
|
||||
constructor(nodes:List<Node>, elementInjectors:List,
|
||||
rootElementInjectors:List, textNodes:List, bindElements:List,
|
||||
protoWatchGroup:ProtoWatchGroup, context) {
|
||||
this.fragment = fragment;
|
||||
this.nodes = ListWrapper.clone(fragment.childNodes);
|
||||
this.elementInjectors = elementInjector;
|
||||
this.nodes = nodes;
|
||||
this.elementInjectors = elementInjectors;
|
||||
this.rootElementInjectors = rootElementInjectors;
|
||||
this.onChangeDispatcher = null;
|
||||
this.textNodes = textNodes;
|
||||
|
@ -60,27 +58,26 @@ export class View {
|
|||
}
|
||||
|
||||
export class ProtoView {
|
||||
@FIELD('final _template:TemplateElement')
|
||||
@FIELD('final _elementBinders:List<ElementBinder>')
|
||||
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
||||
@FIELD('final _useRootElement:bool')
|
||||
@FIELD('final element:Element')
|
||||
@FIELD('final elementBinders:List<ElementBinder>')
|
||||
@FIELD('final protoWatchGroup:ProtoWatchGroup')
|
||||
constructor(
|
||||
template:TemplateElement,
|
||||
elementBinders:List,
|
||||
protoWatchGroup:ProtoWatchGroup,
|
||||
useRootElement:boolean) {
|
||||
this._template = template;
|
||||
this._elementBinders = elementBinders;
|
||||
this._protoWatchGroup = protoWatchGroup;
|
||||
|
||||
// not implemented
|
||||
this._useRootElement = useRootElement;
|
||||
template:Element,
|
||||
protoWatchGroup:ProtoWatchGroup) {
|
||||
this.element = template;
|
||||
this.elementBinders = [];
|
||||
this.protoWatchGroup = protoWatchGroup;
|
||||
this.textNodesWithBindingCount = 0;
|
||||
this.elementsWithBindingCount = 0;
|
||||
}
|
||||
|
||||
instantiate(context, appInjector:Injector):View {
|
||||
var fragment = DOM.clone(this._template.content);
|
||||
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
|
||||
var binders = this._elementBinders;
|
||||
var clone = DOM.clone(this.element);
|
||||
var elements = ListWrapper.clone(DOM.getElementsByClassName(clone, NG_BINDING_CLASS));
|
||||
if (DOM.hasClass(clone, NG_BINDING_CLASS)) {
|
||||
ListWrapper.insert(elements, 0, clone);
|
||||
}
|
||||
var binders = this.elementBinders;
|
||||
|
||||
/**
|
||||
* TODO: vsavkin: benchmark
|
||||
|
@ -92,16 +89,77 @@ export class ProtoView {
|
|||
var bindElements = ProtoView._bindElements(elements, binders);
|
||||
ProtoView._instantiateDirectives(elementInjectors, appInjector);
|
||||
|
||||
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
|
||||
bindElements, this._protoWatchGroup, context);
|
||||
var viewNodes;
|
||||
if (clone instanceof TemplateElement) {
|
||||
viewNodes = ListWrapper.clone(clone.content.childNodes);
|
||||
} else {
|
||||
viewNodes = [clone];
|
||||
}
|
||||
return new View(viewNodes, elementInjectors, rootElementInjectors, textNodes,
|
||||
bindElements, this.protoWatchGroup, context);
|
||||
}
|
||||
|
||||
bindElement(protoElementInjector:ProtoElementInjector):ElementBinder {
|
||||
var elBinder = new ElementBinder(protoElementInjector);
|
||||
ListWrapper.push(this.elementBinders, elBinder);
|
||||
return elBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a text node binding for the last created ElementBinder via bindElement
|
||||
*/
|
||||
bindTextNode(indexInParent:int, expression:AST) {
|
||||
var elBinder = this.elementBinders[this.elementBinders.length-1];
|
||||
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
|
||||
this.protoWatchGroup.watch(expression, this.textNodesWithBindingCount++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element property binding for the last created ElementBinder via bindElement
|
||||
*/
|
||||
bindElementProperty(propertyName:string, expression:AST) {
|
||||
var elBinder = this.elementBinders[this.elementBinders.length-1];
|
||||
if (!elBinder.hasElementPropertyBindings) {
|
||||
elBinder.hasElementPropertyBindings = true;
|
||||
this.elementsWithBindingCount++;
|
||||
}
|
||||
this.protoWatchGroup.watch(expression,
|
||||
new ElementPropertyMemento(
|
||||
this.elementsWithBindingCount-1,
|
||||
propertyName
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a directive property binding for the last created ElementBinder via bindElement
|
||||
*/
|
||||
bindDirectiveProperty(
|
||||
directiveIndex:number,
|
||||
expression:AST,
|
||||
setterName:string,
|
||||
setter:SetterFn) {
|
||||
this.protoWatchGroup.watch(
|
||||
expression,
|
||||
new DirectivePropertyMemento(
|
||||
this.elementBinders.length-1,
|
||||
directiveIndex,
|
||||
setterName,
|
||||
setter
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
static _createElementInjectors(elements, binders) {
|
||||
var injectors = ListWrapper.createFixedSize(binders.length);
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var proto = binders[i].protoElementInjector;
|
||||
var parentElementInjector = isPresent(proto.parent) ? injectors[proto.parent.index] : null;
|
||||
injectors[i] = ProtoView._createElementInjector(elements[i], parentElementInjector, proto);
|
||||
if (isPresent(proto)) {
|
||||
var parentElementInjector = isPresent(proto.parent) ? injectors[proto.parent.index] : null;
|
||||
injectors[i] = ProtoView._createElementInjector(elements[i], parentElementInjector, proto);
|
||||
} else {
|
||||
injectors[i] = null;
|
||||
}
|
||||
}
|
||||
return injectors;
|
||||
}
|
||||
|
@ -115,7 +173,7 @@ export class ProtoView {
|
|||
|
||||
static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) {
|
||||
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
|
||||
return proto.hasBindings ? proto.instantiate(parent, null) : null;
|
||||
return proto.instantiate(parent, null);
|
||||
}
|
||||
|
||||
static _rootElementInjectors(injectors) {
|
||||
|
@ -150,7 +208,7 @@ export class ProtoView {
|
|||
|
||||
export class ElementPropertyMemento {
|
||||
@FIELD('final _elementIndex:int')
|
||||
@FIELD('final _propertyIndex:string')
|
||||
@FIELD('final _propertyName:string')
|
||||
constructor(elementIndex:int, propertyName:string) {
|
||||
this._elementIndex = elementIndex;
|
||||
this._propertyName = propertyName;
|
||||
|
|
|
@ -1,7 +1,121 @@
|
|||
import {describe, it} from 'test_lib/test_lib';
|
||||
//import {Compiler} from 'core/compiler/compiler';
|
||||
import {describe, beforeEach, it, expect, ddescribe, iit} from 'test_lib/test_lib';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {List} from 'facade/collection';
|
||||
|
||||
import {Compiler} from 'core/compiler/compiler';
|
||||
import {ProtoView} from 'core/compiler/view';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
import {TemplateLoader} from 'core/compiler/template_loader';
|
||||
import {Component} from 'core/annotations/component';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
|
||||
export function main() {
|
||||
describe('compiler', function() {
|
||||
var compiler, reflector;
|
||||
|
||||
beforeEach( () => {
|
||||
reflector = new Reflector();
|
||||
});
|
||||
|
||||
function createCompiler(processClosure) {
|
||||
var closureMap = new ClosureMap();
|
||||
var steps = [new MockStep(processClosure)];
|
||||
return new TestableCompiler(null, reflector, new Parser(new Lexer(), closureMap), closureMap, steps);
|
||||
}
|
||||
|
||||
it('should run the steps and return the ProtoView of the root element', (done) => {
|
||||
var rootProtoView = new ProtoView(null, null);
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = rootProtoView;
|
||||
});
|
||||
compiler.compile(MainComponent, createElement('<div></div>')).then( (protoView) => {
|
||||
expect(protoView).toBe(rootProtoView);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the given element', (done) => {
|
||||
var el = createElement('<div></div>');
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = new ProtoView(current.element, null);
|
||||
});
|
||||
compiler.compile(MainComponent, el).then( (protoView) => {
|
||||
expect(protoView.element).toBe(el);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the inline template if no element is given explicitly', (done) => {
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = new ProtoView(current.element, null);
|
||||
});
|
||||
compiler.compile(MainComponent, null).then( (protoView) => {
|
||||
expect(DOM.getInnerHTML(protoView.element)).toEqual('inline component');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load nested components', (done) => {
|
||||
var mainEl = createElement('<div></div>');
|
||||
var compiler = createCompiler( (parent, current, control) => {
|
||||
current.inheritedProtoView = new ProtoView(current.element, null);
|
||||
current.inheritedElementBinder = current.inheritedProtoView.bindElement(null);
|
||||
if (current.element === mainEl) {
|
||||
current.componentDirective = reflector.annotatedType(NestedComponent);
|
||||
}
|
||||
});
|
||||
compiler.compile(MainComponent, mainEl).then( (protoView) => {
|
||||
var nestedView = protoView.elementBinders[0].nestedProtoView;
|
||||
expect(DOM.getInnerHTML(nestedView.element)).toEqual('nested component');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
inline: 'inline component'
|
||||
})
|
||||
})
|
||||
class MainComponent {}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
inline: 'nested component'
|
||||
})
|
||||
})
|
||||
class NestedComponent {}
|
||||
|
||||
class TestableCompiler extends Compiler {
|
||||
constructor(templateLoader:TemplateLoader, reflector:Reflector, parser, closureMap, steps:List<CompileStep>) {
|
||||
super(templateLoader, reflector, parser, closureMap);
|
||||
this.steps = steps;
|
||||
}
|
||||
createSteps(component):List<CompileStep> {
|
||||
return this.steps;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import {describe, xit, it, expect, beforeEach, ddescribe, iit} from 'test_lib/test_lib';
|
||||
|
||||
import {DOM} from 'facade/dom';
|
||||
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
|
||||
import {Compiler} from 'core/compiler/compiler';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
|
||||
import {Component} from 'core/annotations/component';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
|
||||
export function main() {
|
||||
describe('integration tests', function() {
|
||||
var compiler;
|
||||
|
||||
beforeEach( () => {
|
||||
var closureMap = new ClosureMap();
|
||||
compiler = new Compiler(null, new Reflector(), new Parser(new Lexer(), closureMap), closureMap);
|
||||
});
|
||||
|
||||
describe('react to watch group changes', function() {
|
||||
var view, ctx, cd;
|
||||
function createView(pv) {
|
||||
ctx = new MyComp();
|
||||
view = pv.instantiate(ctx, null);
|
||||
cd = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
it('should consume text node changes', (done) => {
|
||||
compiler.compile(MyComp, createElement('<div>{{ctxProp}}</div>')).then((pv) => {
|
||||
createView(pv);
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
|
||||
cd.detectChanges();
|
||||
expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Hello World!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should consume element binding changes', (done) => {
|
||||
compiler.compile(MyComp, createElement('<div [id]="ctxProp"></div>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
cd.detectChanges();
|
||||
|
||||
expect(view.nodes[0].id).toEqual('Hello World!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change.', (done) => {
|
||||
compiler.compile(MyComp, createElement('<div my-dir [elprop]="ctxProp"></div>')).then((pv) => {
|
||||
createView(pv);
|
||||
|
||||
ctx.ctxProp = 'Hello World!';
|
||||
cd.detectChanges();
|
||||
|
||||
var elInj = view.elementInjectors[0];
|
||||
expect(elInj.get(MyDir).dirProp).toEqual('Hello World!');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[my-dir]',
|
||||
bind: {'elprop':'dirProp'}
|
||||
})
|
||||
class MyDir {
|
||||
constructor() {
|
||||
this.dirProp = '';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [MyDir]
|
||||
})
|
||||
})
|
||||
class MyComp {
|
||||
constructor() {
|
||||
this.ctxProp = 'initial value';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {DirectiveParser} from 'core/compiler/pipeline/directive_parser';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {Component} from 'core/annotations/component';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {Template} from 'core/annotations/template';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
|
||||
export function main() {
|
||||
describe('DirectiveParser', () => {
|
||||
var reflector, directives;
|
||||
|
||||
beforeEach( () => {
|
||||
reflector = new Reflector();
|
||||
directives = [SomeDecorator, SomeTemplate, SomeTemplate2, SomeComponent, SomeComponent2];
|
||||
});
|
||||
|
||||
function createPipeline(propertyBindings = null) {
|
||||
var annotatedDirectives = ListWrapper.create();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
ListWrapper.push(annotatedDirectives, reflector.annotatedType(directives[i]));
|
||||
}
|
||||
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(propertyBindings)) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
}
|
||||
}), new DirectiveParser(annotatedDirectives)]);
|
||||
}
|
||||
|
||||
it('should not add directives if they are not used', () => {
|
||||
var results = createPipeline().process(createElement('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toBe(null);
|
||||
expect(results[0].componentDirective).toBe(null);
|
||||
expect(results[0].templateDirective).toBe(null);
|
||||
});
|
||||
|
||||
it('should detect directives in attributes', () => {
|
||||
var results = createPipeline().process(createElement('<div some-decor some-templ some-comp></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reflector.annotatedType(SomeDecorator)]);
|
||||
expect(results[0].templateDirective).toEqual(reflector.annotatedType(SomeTemplate));
|
||||
expect(results[0].componentDirective).toEqual(reflector.annotatedType(SomeComponent));
|
||||
});
|
||||
|
||||
it('should detect directives in property bindings', () => {
|
||||
var pipeline = createPipeline(MapWrapper.createFromStringMap({
|
||||
'some-decor': 'someExpr',
|
||||
'some-templ': 'someExpr',
|
||||
'some-comp': 'someExpr'
|
||||
}));
|
||||
var results = pipeline.process(createElement('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reflector.annotatedType(SomeDecorator)]);
|
||||
expect(results[0].templateDirective).toEqual(reflector.annotatedType(SomeTemplate));
|
||||
expect(results[0].componentDirective).toEqual(reflector.annotatedType(SomeComponent));
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
it('should not allow multiple template directives on the same element', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
createElement('<div some-templ some-templ2></div>')
|
||||
);
|
||||
}).toThrowError('Only one template directive per element is allowed!');
|
||||
});
|
||||
|
||||
it('should not allow multiple component directives on the same element', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
createElement('<div some-comp some-comp2></div>')
|
||||
);
|
||||
}).toThrowError('Only one component directive per element is allowed!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[some-decor]'
|
||||
})
|
||||
class SomeDecorator {}
|
||||
|
||||
@Template({
|
||||
selector: '[some-templ]'
|
||||
})
|
||||
class SomeTemplate {}
|
||||
|
||||
@Template({
|
||||
selector: '[some-templ2]'
|
||||
})
|
||||
class SomeTemplate2 {}
|
||||
|
||||
@Component({
|
||||
selector: '[some-comp]'
|
||||
})
|
||||
class SomeComponent {}
|
||||
|
||||
@Component({
|
||||
selector: '[some-comp2]'
|
||||
})
|
||||
class SomeComponent2 {}
|
||||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [SomeDecorator, SomeTemplate, SomeTemplate2, SomeComponent, SomeComponent2]
|
||||
})
|
||||
})
|
||||
class MyComp {}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
|
||||
import {ElementBinderBuilder} from 'core/compiler/pipeline/element_binder_builder';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {Template} from 'core/annotations/template';
|
||||
import {Component} from 'core/annotations/component';
|
||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
|
||||
import {ProtoElementInjector} from 'core/compiler/element_injector';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
|
||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
|
||||
export function main() {
|
||||
describe('ElementBinderBuilder', () => {
|
||||
var evalContext, view, changeDetector;
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, directives, protoElementInjector}={}) {
|
||||
var reflector = new Reflector();
|
||||
var closureMap = new ClosureMap();
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
current.inheritedProtoView = new ProtoView(current.element, new ProtoWatchGroup());
|
||||
} else if (isPresent(parent)) {
|
||||
current.inheritedProtoView = parent.inheritedProtoView;
|
||||
} else {
|
||||
current.inheritedProtoView = null;
|
||||
}
|
||||
var hasBinding = false;
|
||||
if (isPresent(current.element.getAttribute('text-binding'))) {
|
||||
current.textNodeBindings = textNodeBindings;
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('prop-binding'))) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(protoElementInjector)) {
|
||||
current.inheritedProtoElementInjector = protoElementInjector;
|
||||
} else {
|
||||
current.inheritedProtoElementInjector = null;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('directives'))) {
|
||||
hasBinding = true;
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
current.addDirective(reflector.annotatedType(directives[i]));
|
||||
}
|
||||
}
|
||||
if (hasBinding) {
|
||||
current.hasBindings = true;
|
||||
DOM.addClass(current.element, 'ng-binding');
|
||||
}
|
||||
}), new ElementBinderBuilder(new Parser(new Lexer(), closureMap), closureMap)
|
||||
]);
|
||||
}
|
||||
|
||||
function instantiateView(protoView) {
|
||||
evalContext = new Context();
|
||||
view = protoView.instantiate(evalContext, null);
|
||||
changeDetector = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
it('should not create an ElementBinder for elements that have no bindings', () => {
|
||||
var pipeline = createPipeline();
|
||||
var results = pipeline.process(createElement('<div viewroot><span></span></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should create an ElementBinder for elements that have bindings', () => {
|
||||
var pipeline = createPipeline();
|
||||
var results = pipeline.process(createElement('<div viewroot prop-binding><span prop-binding></span></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders.length).toBe(2);
|
||||
expect(pv.elementBinders[1]).not.toBe(pv.elementBinders[0]);
|
||||
});
|
||||
|
||||
it('should inherit ElementBinders to children that have no bindings', () => {
|
||||
var pipeline = createPipeline();
|
||||
var results = pipeline.process(createElement('<div viewroot prop-binding><span></span></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders.length).toBe(1);
|
||||
expect(results[0].inheritedElementBinder).toBe(results[1].inheritedElementBinder);
|
||||
});
|
||||
|
||||
it('should store the current protoElementInjector', () => {
|
||||
var directives = [SomeSimpleDirective];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives);
|
||||
|
||||
var pipeline = createPipeline({protoElementInjector: protoElementInjector, directives: directives});
|
||||
var results = pipeline.process(createElement('<div viewroot directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].protoElementInjector).toBe(protoElementInjector);
|
||||
});
|
||||
|
||||
it('should bind text nodes', () => {
|
||||
var textNodeBindings = MapWrapper.create();
|
||||
MapWrapper.set(textNodeBindings, 0, 'prop1');
|
||||
MapWrapper.set(textNodeBindings, 2, 'prop2');
|
||||
var pipeline = createPipeline({textNodeBindings: textNodeBindings});
|
||||
var results = pipeline.process(createElement('<div viewroot text-binding>{{}}<span></span>{{}}</div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(sortArr(pv.elementBinders[0].textNodeIndices)).toEqual([0, 2]);
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
evalContext.prop2 = 'b';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.nodes[0].childNodes[0].nodeValue).toEqual('a');
|
||||
expect(view.nodes[0].childNodes[2].nodeValue).toEqual('b');
|
||||
});
|
||||
|
||||
it('should bind element properties', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'elprop1': 'prop1',
|
||||
'elprop2': 'prop2'
|
||||
});
|
||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||
var results = pipeline.process(createElement('<div viewroot prop-binding></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
evalContext.prop2 = 'b';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(DOM.getProperty(view.nodes[0], 'elprop1')).toEqual('a');
|
||||
expect(DOM.getProperty(view.nodes[0], 'elprop2')).toEqual('b');
|
||||
});
|
||||
|
||||
it('should bind directive properties', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'boundprop1': 'prop1',
|
||||
'boundprop2': 'prop2',
|
||||
'boundprop3': 'prop3'
|
||||
});
|
||||
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives);
|
||||
var pipeline = createPipeline({
|
||||
propertyBindings: propertyBindings,
|
||||
directives: directives,
|
||||
protoElementInjector: protoElementInjector
|
||||
});
|
||||
var results = pipeline.process(createElement('<div viewroot prop-binding directives></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
evalContext.prop2 = 'b';
|
||||
evalContext.prop3 = 'c';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.elementInjectors[0].get(SomeDecoratorDirective).decorProp).toBe('a');
|
||||
expect(view.elementInjectors[0].get(SomeTemplateDirective).templProp).toBe('b');
|
||||
expect(view.elementInjectors[0].get(SomeComponentDirective).compProp).toBe('c');
|
||||
});
|
||||
|
||||
it('should bind directive properties for sibling elements', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'boundprop1': 'prop1'
|
||||
});
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var protoElementInjector = new ProtoElementInjector(null, 0, directives);
|
||||
var pipeline = createPipeline({
|
||||
propertyBindings: propertyBindings,
|
||||
directives: directives,
|
||||
protoElementInjector: protoElementInjector
|
||||
});
|
||||
var results = pipeline.process(
|
||||
createElement('<div viewroot><div prop-binding directives>'+
|
||||
'</div><div prop-binding directives></div></div>'));
|
||||
var pv = results[0].inheritedProtoView;
|
||||
|
||||
instantiateView(pv);
|
||||
evalContext.prop1 = 'a';
|
||||
changeDetector.detectChanges();
|
||||
|
||||
expect(view.elementInjectors[1].get(SomeDecoratorDirective).decorProp).toBe('a');
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
it('should throw if there is no element property bindings for a directive property binding', () => {
|
||||
var pipeline = createPipeline({propertyBindings: MapWrapper.create(), directives: [SomeDecoratorDirective]});
|
||||
expect( () => {
|
||||
pipeline.process(createElement('<div viewroot prop-binding directives>'));
|
||||
}).toThrowError('No element binding found for property boundprop1 which is required by directive SomeDecoratorDirective');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Decorator()
|
||||
class SomeSimpleDirective {
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
bind: {'boundprop1': 'decorProp'}
|
||||
})
|
||||
class SomeDecoratorDirective {
|
||||
constructor() {
|
||||
this.decorProp = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Template({
|
||||
bind: {'boundprop2': 'templProp'}
|
||||
})
|
||||
class SomeTemplateDirective {
|
||||
constructor() {
|
||||
this.templProp = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
bind: {'boundprop3': 'compProp'}
|
||||
})
|
||||
class SomeComponentDirective {
|
||||
constructor() {
|
||||
this.compProp = null;
|
||||
}
|
||||
}
|
||||
|
||||
class Context {
|
||||
constructor() {
|
||||
this.prop1 = null;
|
||||
this.prop2 = null;
|
||||
this.prop3 = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
function sortArr(arr) {
|
||||
var arr2 = ListWrapper.clone(arr);
|
||||
arr2.sort();
|
||||
return arr2;
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
import {ElementBindingMarker} from 'core/compiler/pipeline/element_binding_marker';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
import {Template} from 'core/annotations/template';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {Component} from 'core/annotations/component';
|
||||
|
||||
export function main() {
|
||||
describe('ElementBindingMarker', () => {
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, directives}={}) {
|
||||
var reflector = new Reflector();
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (isPresent(textNodeBindings)) {
|
||||
current.textNodeBindings = textNodeBindings;
|
||||
}
|
||||
if (isPresent(propertyBindings)) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
}
|
||||
if (isPresent(directives)) {
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
current.addDirective(reflector.annotatedType(directives[i]));
|
||||
}
|
||||
}
|
||||
}), new ElementBindingMarker()
|
||||
]);
|
||||
}
|
||||
|
||||
it('should not mark empty elements', () => {
|
||||
var results = createPipeline().process(createElement('<div></div>'));
|
||||
assertBinding(results[0], false);
|
||||
});
|
||||
|
||||
it('should mark elements with text node bindings', () => {
|
||||
var textNodeBindings = MapWrapper.create();
|
||||
MapWrapper.set(textNodeBindings, 0, 'expr');
|
||||
var results = createPipeline({textNodeBindings: textNodeBindings}).process(createElement('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with property bindings', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({'a': 'expr'});
|
||||
var results = createPipeline({propertyBindings: propertyBindings}).process(createElement('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with decorator directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeDecoratorDirective]
|
||||
}).process(createElement('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with template directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeTemplateDirective]
|
||||
}).process(createElement('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with component directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeComponentDirective]
|
||||
}).process(createElement('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function assertBinding(pipelineElement, shouldBePresent) {
|
||||
expect(pipelineElement.hasBindings).toBe(shouldBePresent);
|
||||
expect(DOM.hasClass(pipelineElement.element, 'ng-binding')).toBe(shouldBePresent);
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
@Template()
|
||||
class SomeTemplateDirective {}
|
||||
|
||||
@Component()
|
||||
class SomeComponentDirective {}
|
||||
|
||||
@Decorator()
|
||||
class SomeDecoratorDirective {}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {ListWrapper} from 'facade/collection';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {isPresent, NumberWrapper} from 'facade/lang';
|
||||
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
|
||||
export function main() {
|
||||
describe('compile_pipeline', () => {
|
||||
var logs, pipeline, loggingStep;
|
||||
|
||||
beforeEach( () => {
|
||||
logs = [];
|
||||
loggingStep = new LoggingStep(logs);
|
||||
});
|
||||
|
||||
it('should walk the tree in depth first order including template contents', () => {
|
||||
var element = createElement('<div id="1"><template id="2"><span id="3"></span></template></div>');
|
||||
|
||||
var step0Log = [];
|
||||
var results = new CompilePipeline([createLoggerStep(step0Log)]).process(element);
|
||||
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(resultIdLog(results)).toEqual(['1', '2', '3']);
|
||||
});
|
||||
|
||||
describe('control.addParent', () => {
|
||||
it('should wrap the underlying DOM element', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>');
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', [])
|
||||
]);
|
||||
pipeline.process(element);
|
||||
|
||||
expect(DOM.getOuterHTML(element)).toEqual('<div id="1"><a id="wrap0#0"><span wrap0="1" id="2"><b id="3"></b></span></a></div>');
|
||||
});
|
||||
|
||||
it('should report the new parent to the following processor and the result', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createLoggerStep(step1Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', '3']);
|
||||
});
|
||||
|
||||
it('should allow to add a parent by multiple processors to the same element', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="1" wrap1="1" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var step2Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createWrapperStep('wrap1', step1Log),
|
||||
createLoggerStep(step2Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
|
||||
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap1#0', 'wrap1#0<2', '2<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap1#0', '2', '3']);
|
||||
});
|
||||
|
||||
it('should allow to add a parent by multiple processors to different elements', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3" wrap1="1"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var step2Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createWrapperStep('wrap1', step1Log),
|
||||
createLoggerStep(step2Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
|
||||
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<wrap1#0', 'wrap1#0<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', 'wrap1#0', '3']);
|
||||
});
|
||||
|
||||
it('should allow to add multiple parents by the same processor', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="2" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
var step1Log = [];
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', step0Log),
|
||||
createLoggerStep(step1Log)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(step0Log).toEqual(['1', '1<2', '2<3']);
|
||||
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap0#1', 'wrap0#1<2', '2<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap0#1', '2', '3']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingStep extends CompileStep {
|
||||
constructor(logs) {
|
||||
this.logs = logs;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
ListWrapper.push(this.logs, {'parent':parent, 'current':current});
|
||||
}
|
||||
}
|
||||
|
||||
function logEntry(log, parent, current) {
|
||||
var parentId = '';
|
||||
if (isPresent(parent)) {
|
||||
parentId = parent.element.getAttribute('id')+'<';
|
||||
}
|
||||
ListWrapper.push(log, parentId+current.element.getAttribute('id'));
|
||||
}
|
||||
|
||||
function createLoggerStep(log) {
|
||||
return new MockStep((parent, current, control) => {
|
||||
logEntry(log, parent, current);
|
||||
});
|
||||
}
|
||||
|
||||
function createWrapperStep(wrapperId, log) {
|
||||
var nextElementId = 0;
|
||||
return new MockStep((parent, current, control) => {
|
||||
var parentCountStr = current.element.getAttribute(wrapperId);
|
||||
if (isPresent(parentCountStr)) {
|
||||
var parentCount = NumberWrapper.parseInt(parentCountStr, 10);
|
||||
while (parentCount > 0) {
|
||||
control.addParent(new CompileElement(createElement(`<a id="${wrapperId}#${nextElementId++}"></a>`)));
|
||||
parentCount--;
|
||||
}
|
||||
}
|
||||
logEntry(log, parent, current);
|
||||
});
|
||||
}
|
||||
|
||||
function resultIdLog(result) {
|
||||
var idLog = [];
|
||||
ListWrapper.forEach(result, (current) => {
|
||||
logEntry(idLog, null, current);
|
||||
});
|
||||
return idLog;
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {PropertyBindingParser} from 'core/compiler/pipeline/property_binding_parser';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('PropertyBindingParser', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new PropertyBindingParser()]);
|
||||
}
|
||||
|
||||
it('should detect [] syntax', () => {
|
||||
var results = createPipeline().process(createElement('<div [a]="b"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a')).toEqual('b');
|
||||
});
|
||||
|
||||
it('should detect bind- syntax', () => {
|
||||
var results = createPipeline().process(createElement('<div bind-a="b"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a')).toEqual('b');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent, isBlank} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {ListWrapper} from 'facade/collection';
|
||||
|
||||
import {ProtoElementInjectorBuilder} from 'core/compiler/pipeline/proto_element_injector_builder';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
import {ProtoView} from 'core/compiler/view';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
import {Template} from 'core/annotations/template';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {Component} from 'core/annotations/component';
|
||||
import {ProtoElementInjector} from 'core/compiler/element_injector';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoElementInjectorBuilder', () => {
|
||||
var protoElementInjectorBuilder, protoView;
|
||||
beforeEach( () => {
|
||||
protoElementInjectorBuilder = new TestableProtoElementInjectorBuilder();
|
||||
protoView = new ProtoView(null, null);
|
||||
});
|
||||
|
||||
function createPipeline(directives = null) {
|
||||
if (isBlank(directives)) {
|
||||
directives = [];
|
||||
}
|
||||
var reflector = new Reflector();
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('directives'))) {
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
current.addDirective(reflector.annotatedType(directives[i]));
|
||||
}
|
||||
}
|
||||
current.inheritedProtoView = protoView;
|
||||
}), protoElementInjectorBuilder]);
|
||||
}
|
||||
|
||||
function assertProtoElementInjector(protoElementInjector, parent, index, bindings) {
|
||||
var args = protoElementInjectorBuilder.findArgsFor(protoElementInjector);
|
||||
expect(args).toEqual([parent, index, bindings]);
|
||||
}
|
||||
|
||||
it('should not create a ProtoElementInjector for elements without directives', () => {
|
||||
var results = createPipeline().process(createElement('<div></div>'));
|
||||
expect(results[0].inheritedProtoElementInjector).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a ProtoElementInjector for elements with directives', () => {
|
||||
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective];
|
||||
var results = createPipeline(directives).process(createElement('<div directives></div>'));
|
||||
assertProtoElementInjector(results[0].inheritedProtoElementInjector, null, 0, directives);
|
||||
});
|
||||
|
||||
it('should use the next ElementBinder index as index of the ProtoElementInjector', () => {
|
||||
// just adding some indices..
|
||||
ListWrapper.push(protoView.elementBinders, null);
|
||||
ListWrapper.push(protoView.elementBinders, null);
|
||||
var directives = [SomeDecoratorDirective];
|
||||
var results = createPipeline(directives).process(createElement('<div directives></div>'));
|
||||
assertProtoElementInjector(
|
||||
results[0].inheritedProtoElementInjector, null, protoView.elementBinders.length, directives);
|
||||
});
|
||||
|
||||
it('should inherit the ProtoElementInjector down to children without directives', () => {
|
||||
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective];
|
||||
var results = createPipeline(directives).process(createElement('<div directives><span></span></div>'));
|
||||
assertProtoElementInjector(results[0].inheritedProtoElementInjector, null, 0, directives);
|
||||
assertProtoElementInjector(results[1].inheritedProtoElementInjector, null, 0, directives);
|
||||
});
|
||||
|
||||
it('should use the ProtoElementInjector of the parent element as parent', () => {
|
||||
var el = createElement('<div directives><span><a directives></a></span></div>');
|
||||
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective];
|
||||
var results = createPipeline(directives).process(el);
|
||||
assertProtoElementInjector(results[2].inheritedProtoElementInjector,
|
||||
results[0].inheritedProtoElementInjector, 0, directives);
|
||||
});
|
||||
|
||||
it('should use a null parent for viewRoots', () => {
|
||||
var el = createElement('<div directives><span viewroot directives></span></div>');
|
||||
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective];
|
||||
var results = createPipeline(directives).process(el);
|
||||
assertProtoElementInjector(results[1].inheritedProtoElementInjector, null, 0, directives);
|
||||
});
|
||||
|
||||
it('should use a null parent if there is an intermediate viewRoot', () => {
|
||||
var el = createElement('<div directives><span viewroot><a directives></a></span></div>');
|
||||
var directives = [SomeDecoratorDirective, SomeTemplateDirective, SomeComponentDirective];
|
||||
var results = createPipeline(directives).process(el);
|
||||
assertProtoElementInjector(results[2].inheritedProtoElementInjector, null, 0, directives);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class TestableProtoElementInjectorBuilder extends ProtoElementInjectorBuilder {
|
||||
constructor() {
|
||||
this.debugObjects = [];
|
||||
}
|
||||
findArgsFor(protoElementInjector:ProtoElementInjector) {
|
||||
for (var i=0; i<this.debugObjects.length; i+=2) {
|
||||
if (this.debugObjects[i] === protoElementInjector) {
|
||||
return this.debugObjects[i+1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
internalCreateProtoElementInjector(parent, index, directives) {
|
||||
var result = new ProtoElementInjector(parent, index, directives);
|
||||
ListWrapper.push(this.debugObjects, result);
|
||||
ListWrapper.push(this.debugObjects, [parent, index, directives]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
@Template()
|
||||
class SomeTemplateDirective {}
|
||||
|
||||
@Component()
|
||||
class SomeComponentDirective {}
|
||||
|
||||
@Decorator()
|
||||
class SomeDecoratorDirective {}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {ElementBinder} from 'core/compiler/element_binder';
|
||||
import {ProtoViewBuilder} from 'core/compiler/pipeline/proto_view_builder';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
import {DOM} from 'facade/dom';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoViewBuilder', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
}
|
||||
current.inheritedElementBinder = new ElementBinder(null);
|
||||
}), new ProtoViewBuilder()]);
|
||||
}
|
||||
|
||||
it('should not create a ProtoView when the isViewRoot flag is not set', () => {
|
||||
var results = createPipeline().process(createElement('<div></div>'));
|
||||
expect(results[0].inheritedProtoView).toBe(null);
|
||||
});
|
||||
|
||||
it('should create a ProtoView when the isViewRoot flag is set', () => {
|
||||
var viewRootElement = createElement('<div viewroot></div>');
|
||||
var results = createPipeline().process(viewRootElement);
|
||||
expect(results[0].inheritedProtoView.element).toBe(viewRootElement);
|
||||
});
|
||||
|
||||
it('should inherit the ProtoView down to children that have no isViewRoot set', () => {
|
||||
var viewRootElement = createElement('<div viewroot><span></span></div>');
|
||||
var results = createPipeline().process(viewRootElement);
|
||||
expect(results[0].inheritedProtoView.element).toBe(viewRootElement);
|
||||
expect(results[1].inheritedProtoView.element).toBe(viewRootElement);
|
||||
});
|
||||
|
||||
it('should save ProtoView into elementBinder of parent element', () => {
|
||||
var el = createElement('<div viewroot><span><a viewroot></a></span></div>');
|
||||
var results = createPipeline().process(el);
|
||||
expect(results[1].inheritedElementBinder.nestedProtoView).toBe(results[2].inheritedProtoView);
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
it('should not allow multiple nested ProtoViews for the same parent element', () => {
|
||||
var el = createElement('<div viewroot><span><a viewroot></a><a viewroot></a></span></div>');
|
||||
expect( () => {
|
||||
createPipeline().process(el);
|
||||
}).toThrowError('Only one nested view per element is allowed');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import {describe, beforeEach, expect, it, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {TextInterpolationParser} from 'core/compiler/pipeline/text_interpolation_parser';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('TextInterpolationParser', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new TextInterpolationParser()]);
|
||||
}
|
||||
|
||||
it('should find text interpolation in normal elements', () => {
|
||||
var results = createPipeline().process(createElement('<div>{{expr1}}<span></span>{{expr2}}</div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0)).toEqual("''+expr1+''");
|
||||
expect(MapWrapper.get(bindings, 2)).toEqual("''+expr2+''");
|
||||
});
|
||||
|
||||
it('should find text interpolation in template elements', () => {
|
||||
var results = createPipeline().process(createElement('<template>{{expr1}}<span></span>{{expr2}}</template>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0)).toEqual("''+expr1+''");
|
||||
expect(MapWrapper.get(bindings, 2)).toEqual("''+expr2+''");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
import {ViewSplitter} from 'core/compiler/pipeline/view_splitter';
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
import {CompileStep} from 'core/compiler/pipeline/compile_step'
|
||||
import {CompileControl} from 'core/compiler/pipeline/compile_control';
|
||||
import {DOM, TemplateElement} from 'facade/dom';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
import {Template} from 'core/annotations/template';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {Component} from 'core/annotations/component';
|
||||
|
||||
export function main() {
|
||||
describe('ViewSplitter', () => {
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, directives}={}) {
|
||||
var reflector = new Reflector();
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('tmpl'))) {
|
||||
current.addDirective(reflector.annotatedType(SomeTemplateDirective));
|
||||
if (isPresent(textNodeBindings)) {
|
||||
current.textNodeBindings = textNodeBindings;
|
||||
}
|
||||
if (isPresent(propertyBindings)) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
}
|
||||
if (isPresent(directives)) {
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
current.addDirective(reflector.annotatedType(directives[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}), new ViewSplitter()
|
||||
]);
|
||||
}
|
||||
|
||||
function commonTests(useTemplateElement) {
|
||||
var rootElement;
|
||||
beforeEach( () => {
|
||||
if (useTemplateElement) {
|
||||
rootElement = createElement('<div><span tmpl></span></div>');
|
||||
} else {
|
||||
rootElement = createElement('<div><span tmpl></span></div>');
|
||||
}
|
||||
});
|
||||
|
||||
it('should insert an empty <template> element', () => {
|
||||
var originalChild = rootElement.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(results[1].element instanceof TemplateElement).toBe(true);
|
||||
expect(DOM.getInnerHTML(results[1].element)).toEqual('');
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should move the template directive to the new element', () => {
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].templateDirective.type).toBe(SomeTemplateDirective);
|
||||
expect(results[2].templateDirective).toBe(null);
|
||||
});
|
||||
|
||||
it('should split the property bindings depending on the bindings on the directive', () => {
|
||||
var propertyBindings = MapWrapper.createFromStringMap({
|
||||
'templateBoundProp': 'a',
|
||||
'nonBoundProp': 'c'
|
||||
});
|
||||
var results = createPipeline({propertyBindings: propertyBindings}).process(rootElement);
|
||||
expect(MapWrapper.get(results[1].propertyBindings, 'templateBoundProp')).toEqual('a');
|
||||
expect(MapWrapper.get(results[2].propertyBindings, 'nonBoundProp')).toEqual('c');
|
||||
});
|
||||
|
||||
it('should keep the component, decorator directives and text node bindings on the original element', () => {
|
||||
var textNodeBindings = MapWrapper.create();
|
||||
MapWrapper.set(textNodeBindings, 0, 'someExpr');
|
||||
var directives = [SomeDecoratorDirective, SomeComponentDirective];
|
||||
var results = createPipeline({
|
||||
textNodeBindings: textNodeBindings,
|
||||
directives: directives
|
||||
}).process(rootElement);
|
||||
expect(results[1].componentDirective).toBe(null);
|
||||
expect(results[1].decoratorDirectives).toBe(null);
|
||||
expect(results[1].textNodeBindings).toBe(null);
|
||||
expect(results[2].componentDirective.type).toEqual(SomeComponentDirective);
|
||||
expect(results[2].decoratorDirectives[0].type).toEqual(SomeDecoratorDirective);
|
||||
expect(results[2].textNodeBindings).toEqual(textNodeBindings);
|
||||
});
|
||||
|
||||
it('should set the isViewRoot flag for the root and nested views', () => {
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].isViewRoot).toBe(true);
|
||||
expect(results[1].isViewRoot).toBe(false);
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
}
|
||||
|
||||
describe('template directive on normal element', () => {
|
||||
commonTests(false);
|
||||
});
|
||||
|
||||
describe('template directive on <template> element', () => {
|
||||
commonTests(true);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockStep extends CompileStep {
|
||||
constructor(process) {
|
||||
this.processClosure = process;
|
||||
}
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
this.processClosure(parent, current, control);
|
||||
}
|
||||
}
|
||||
|
||||
@Template({
|
||||
bind: {
|
||||
'templateBoundProp': 'dirProp'
|
||||
}
|
||||
})
|
||||
class SomeTemplateDirective {}
|
||||
|
||||
@Component()
|
||||
class SomeComponentDirective {}
|
||||
|
||||
@Decorator()
|
||||
class SomeDecoratorDirective {}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {Reflector} from 'core/compiler/reflector';
|
||||
import {Decorator} from 'core/annotations/decorator';
|
||||
import {AnnotatedType} from 'core/compiler/annotated_type';
|
||||
|
||||
@Decorator({
|
||||
selector: 'someSelector'
|
||||
})
|
||||
class SomeDirective {
|
||||
}
|
||||
|
||||
class SomeDirectiveWithoutAnnotation {
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe("reflector", () => {
|
||||
var reflector;
|
||||
|
||||
beforeEach( () => {
|
||||
reflector = new Reflector();
|
||||
});
|
||||
|
||||
it('should read out the annotation', () => {
|
||||
var annoatedDirective = reflector.annotatedType(SomeDirective);
|
||||
expect(annoatedDirective).toEqual(
|
||||
new AnnotatedType(SomeDirective, new Decorator({selector: 'someSelector'})));
|
||||
});
|
||||
|
||||
it('should throw if not matching annotation is found', () => {
|
||||
expect(() => {
|
||||
reflector.annotatedType(SomeDirectiveWithoutAnnotation);
|
||||
}).toThrowError('No Directive annotation found on SomeDirectiveWithoutAnnotation');
|
||||
});
|
||||
|
||||
});
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import {describe, it, expect, beforeEach, ddescribe, iit} from 'test_lib/test_lib';
|
||||
import {SelectorMatcher, CssSelector, Attr} from 'core/compiler/selector';
|
||||
import {describe, it, expect, beforeEach, ddescribe, iit, xit} from 'test_lib/test_lib';
|
||||
import {SelectorMatcher} from 'core/compiler/selector';
|
||||
import {CssSelector} from 'core/compiler/selector';
|
||||
import {List, ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
|
||||
export function main() {
|
||||
describe('SelectorMatcher', () => {
|
||||
|
@ -44,7 +45,7 @@ export function main() {
|
|||
expect(matched).toEqual([1,2]);
|
||||
});
|
||||
|
||||
it('should select by attr name case insensitive', () => {
|
||||
it('should select by attr name case insensitive independent of the value', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr]'), 1);
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||
|
||||
|
@ -54,11 +55,26 @@ export function main() {
|
|||
matcher.match(CssSelector.parse('[SOMEATTR]'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('[SOMEATTR=someValue]'), selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
|
||||
reset();
|
||||
matcher.match(CssSelector.parse('[someAttr][someAttr2]'), selectableCollector);
|
||||
expect(matched).toEqual([1,2]);
|
||||
});
|
||||
|
||||
it('should select by attr name only once if the value is from the DOM', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[some-decor]'), 1);
|
||||
|
||||
var elementSelector = new CssSelector();
|
||||
var el = createElement('<div attr></div>');
|
||||
var empty = el.getAttribute('attr');
|
||||
elementSelector.addAttribute('some-decor', empty);
|
||||
matcher.match(elementSelector, selectableCollector);
|
||||
expect(matched).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should select by attr name and value case insensitive', () => {
|
||||
matcher.addSelectable(CssSelector.parse('[someAttr=someValue]'), 1);
|
||||
|
||||
|
@ -127,28 +143,28 @@ export function main() {
|
|||
|
||||
it('should detect attr names', () => {
|
||||
var cssSelector = CssSelector.parse('[attrname]');
|
||||
var attr = ListWrapper.get(cssSelector.attrs, 0);
|
||||
expect(attr.name).toEqual('attrname');
|
||||
expect(isPresent(attr.value)).toBe(false);
|
||||
expect(cssSelector.attrs).toEqual(['attrname', '']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('[attrname]');
|
||||
});
|
||||
|
||||
it('should detect attr values', () => {
|
||||
var cssSelector = CssSelector.parse('[attrname=attrvalue]');
|
||||
var attr = ListWrapper.get(cssSelector.attrs, 0);
|
||||
expect(attr.name).toEqual('attrname');
|
||||
expect(attr.value).toEqual('attrvalue');
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelector.toString()).toEqual('[attrname=attrvalue]');
|
||||
});
|
||||
|
||||
it('should detect multiple parts', () => {
|
||||
var cssSelector = CssSelector.parse('sometag[attrname=attrvalue].someclass');
|
||||
expect(cssSelector.element).toEqual('sometag');
|
||||
var attr = ListWrapper.get(cssSelector.attrs, 0);
|
||||
expect(attr.name).toEqual('attrname');
|
||||
expect(attr.value).toEqual('attrvalue');
|
||||
expect(cssSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||
expect(cssSelector.classNames).toEqual(['someclass']);
|
||||
|
||||
expect(cssSelector.toString()).toEqual('sometag.someclass[attrname=attrvalue]');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -1,14 +1,197 @@
|
|||
import {describe, xit, it, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit} from 'test_lib/test_lib';
|
||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
|
||||
import {Record} from 'change_detection/record';
|
||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||
import {ChangeDetector} from 'change_detection/change_detector';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
import {FIELD} from 'facade/lang';
|
||||
import {ImplicitReceiver, AccessMember} from 'change_detection/parser/ast';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {ElementBinder} from 'core/compiler/element_binder';
|
||||
|
||||
export function main() {
|
||||
describe('view', function() {
|
||||
var parser, closureMap;
|
||||
|
||||
beforeEach( () => {
|
||||
closureMap = new ClosureMap();
|
||||
parser = new Parser(new Lexer(), closureMap);
|
||||
});
|
||||
|
||||
describe('ProtoView.instantiate', function() {
|
||||
|
||||
describe('collect root nodes', () => {
|
||||
|
||||
it('should use the ProtoView element if it is no TemplateElement', () => {
|
||||
var pv = new ProtoView(createElement('<div id="1"></div>'), new ProtoWatchGroup());
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.nodes.length).toBe(1);
|
||||
expect(view.nodes[0].getAttribute('id')).toEqual('1');
|
||||
});
|
||||
|
||||
it('should use the ProtoView elements children if it is a TemplateElement', () => {
|
||||
var pv = new ProtoView(createElement('<template><div id="1"></div></template>'),
|
||||
new ProtoWatchGroup());
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.nodes.length).toBe(1);
|
||||
expect(view.nodes[0].getAttribute('id')).toEqual('1');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('collect elements with property bindings', () => {
|
||||
|
||||
it('should collect property bindings on the root element if it has the ng-binding class', () => {
|
||||
var pv = new ProtoView(createElement('<div [prop]="a" class="ng-binding"></div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('prop', parser.parseBinding('a'));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
expect(view.bindElements[0]).toBe(view.nodes[0]);
|
||||
});
|
||||
|
||||
it('should collect property bindings on child elements with ng-binding class', () => {
|
||||
var pv = new ProtoView(createElement('<div><span></span><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('a', parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
expect(view.bindElements[0]).toBe(view.nodes[0].childNodes[1]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('collect text nodes with bindings', () => {
|
||||
|
||||
it('should collect text nodes under the root element', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('a'));
|
||||
pv.bindTextNode(2, parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.textNodes.length).toEqual(2);
|
||||
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[0]);
|
||||
expect(view.textNodes[1]).toBe(view.nodes[0].childNodes[2]);
|
||||
});
|
||||
|
||||
it('should collect text nodes with bindings on child elements with ng-binding class', () => {
|
||||
var pv = new ProtoView(createElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('b'));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.textNodes.length).toEqual(1);
|
||||
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[1].childNodes[0]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('create ElementInjectors', () => {
|
||||
it('should use the directives of the ProtoElementInjector', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [Directive]));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.elementInjectors.length).toBe(1);
|
||||
expect(view.elementInjectors[0].get(Directive) instanceof Directive).toBe(true);
|
||||
});
|
||||
|
||||
it('should use the correct parent', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [Directive]);
|
||||
pv.bindElement(protoParent);
|
||||
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.elementInjectors.length).toBe(2);
|
||||
expect(view.elementInjectors[0].get(Directive) instanceof Directive).toBe(true);
|
||||
expect(view.elementInjectors[1].parent).toBe(view.elementInjectors[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collect root element injectors', () => {
|
||||
|
||||
it('should collect a single root element injector', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
var protoParent = new ProtoElementInjector(null, 0, [Directive]);
|
||||
pv.bindElement(protoParent);
|
||||
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.rootElementInjectors.length).toBe(1);
|
||||
expect(view.rootElementInjectors[0].get(Directive) instanceof Directive).toBe(true);
|
||||
});
|
||||
|
||||
it('should collect multiple root element injectors', () => {
|
||||
var pv = new ProtoView(createElement('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 1, [Directive]));
|
||||
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
expect(view.rootElementInjectors.length).toBe(2);
|
||||
expect(view.rootElementInjectors[0].get(Directive) instanceof Directive).toBe(true);
|
||||
expect(view.rootElementInjectors[1].get(AnotherDirective) instanceof AnotherDirective).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('react to watch group changes', () => {
|
||||
var view, cd, ctx;
|
||||
|
||||
function createView(protoView) {
|
||||
ctx = new MyEvaluationContext();
|
||||
view = protoView.instantiate(ctx, null);
|
||||
cd = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
it('should consume text node changes', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding">{{}}</div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('foo'));
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.textNodes[0].nodeValue).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume element binding changes', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('id', parser.parseBinding('foo'));
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.bindElements[0].id).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change.', () => {
|
||||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [Directive]));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('foo'), 'prop', closureMap.setter('prop'));
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.elementInjectors[0].get(Directive).prop).toEqual('buz');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class Directive {
|
||||
@FIELD('prop')
|
||||
|
@ -17,167 +200,11 @@ class Directive {
|
|||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
var oneFieldAst = (fieldName) => {
|
||||
var cm = new ClosureMap();
|
||||
return new AccessMember(new ImplicitReceiver(), fieldName,
|
||||
cm.getter(fieldName), cm.setter(fieldName));
|
||||
};
|
||||
|
||||
describe('view', function() {
|
||||
var tempalteWithThreeTypesOfBindings =
|
||||
'<section class="ng-binding">' +
|
||||
'Hello {}!' +
|
||||
'<div directive class="ng-binding">' +
|
||||
'<span class="ng-binding" [id]="exp">don\'t show me</span>' +
|
||||
'</div>' +
|
||||
'</section>';
|
||||
|
||||
function templateElementBinders() {
|
||||
var sectionPI = new ElementBinder(new ProtoElementInjector(null, 0, []),
|
||||
[0], false);
|
||||
|
||||
var divPI = new ElementBinder(new ProtoElementInjector(
|
||||
sectionPI.protoElementInjector, 1, [Directive]), [], false);
|
||||
|
||||
var spanPI = new ElementBinder(new ProtoElementInjector(
|
||||
divPI.protoElementInjector, 2, []), [], true);
|
||||
return [sectionPI, divPI, spanPI];
|
||||
}
|
||||
|
||||
describe('ProtoView', function() {
|
||||
it('should create view instance and locate basic parts', function() {
|
||||
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
||||
|
||||
var hasSingleRoot = false;
|
||||
var pv = new ProtoView(template, templateElementBinders(),
|
||||
new ProtoWatchGroup(), hasSingleRoot);
|
||||
|
||||
var view = pv.instantiate(null, null);
|
||||
|
||||
var section = DOM.firstChild(template.content);
|
||||
|
||||
expect(DOM.getInnerHTML(DOM.firstChild(view.fragment))).toEqual(DOM.getInnerHTML(section)); // exclude top level <section>
|
||||
|
||||
expect(view.elementInjectors.length).toEqual(3);
|
||||
expect(view.elementInjectors[0]).toBeNull();
|
||||
expect(view.elementInjectors[1]).toBeAnInstanceOf(ElementInjector);
|
||||
expect(view.elementInjectors[2]).toBeNull();
|
||||
|
||||
expect(view.textNodes.length).toEqual(1);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
expect(view.textNodes[0].nodeValue).toEqual('Hello {}!');
|
||||
});
|
||||
|
||||
it('should set root element injectors', function() {
|
||||
var template = DOM.createTemplate(
|
||||
'<section directive class="ng-binding">' +
|
||||
'<div directive class="ng-binding"></div>' +
|
||||
'</section>');
|
||||
|
||||
var sectionPI = new ElementBinder(new ProtoElementInjector(
|
||||
null, 0, [Directive]), [], false);
|
||||
var divPI = new ElementBinder(new ProtoElementInjector(
|
||||
sectionPI.protoElementInjector, 1, [Directive]), [], false);
|
||||
|
||||
var pv = new ProtoView(template, [sectionPI, divPI],
|
||||
new ProtoWatchGroup(), false);
|
||||
var view = pv.instantiate(null, null);
|
||||
|
||||
expect(view.rootElementInjectors.length).toEqual(1);
|
||||
});
|
||||
|
||||
describe('react to watch group changes', function() {
|
||||
var view;
|
||||
beforeEach(() => {
|
||||
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
||||
var pv = new ProtoView(template, templateElementBinders(),
|
||||
new ProtoWatchGroup(), false);
|
||||
view = pv.instantiate(null, null);
|
||||
});
|
||||
|
||||
it('should consume text node changes', () => {
|
||||
var record = new Record(null, null);
|
||||
record.currentValue = 'Hello World!';
|
||||
view.onRecordChange(record , 0);
|
||||
expect(view.textNodes[0].nodeValue).toEqual('Hello World!');
|
||||
});
|
||||
|
||||
it('should consume element binding changes', () => {
|
||||
var elementWithBinding = view.bindElements[0];
|
||||
expect(elementWithBinding.id).toEqual('');
|
||||
var record = new Record(null, null);
|
||||
var memento = new ElementPropertyMemento(0, 'id');
|
||||
record.currentValue = 'foo';
|
||||
view.onRecordChange(record, memento);
|
||||
expect(elementWithBinding.id).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change.', () => {
|
||||
var elInj = view.elementInjectors[1];
|
||||
|
||||
expect(elInj.get(Directive).prop).toEqual('foo');
|
||||
var record = new Record(null, null);
|
||||
var memento = new DirectivePropertyMemento(1, 0, 'prop',
|
||||
(o, v) => o.prop = v);
|
||||
record.currentValue = 'bar';
|
||||
view.onRecordChange(record, memento);
|
||||
expect(elInj.get(Directive).prop).toEqual('bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration view update with change detector', () => {
|
||||
var view, cd, ctx;
|
||||
function setUp(memento) {
|
||||
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
||||
|
||||
var protoWatchGroup = new ProtoWatchGroup();
|
||||
protoWatchGroup.watch(oneFieldAst('foo'), memento);
|
||||
|
||||
var pv = new ProtoView(template, templateElementBinders(),
|
||||
protoWatchGroup, false);
|
||||
|
||||
ctx = new MyEvaluationContext();
|
||||
view = pv.instantiate(ctx, null);
|
||||
|
||||
cd = new ChangeDetector(view.watchGroup);
|
||||
}
|
||||
|
||||
it('should consume text node changes', () => {
|
||||
setUp(0);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(view.textNodes[0].nodeValue).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume element binding changes', () => {
|
||||
setUp(new ElementPropertyMemento(0, 'id'));
|
||||
|
||||
var elementWithBinding = view.bindElements[0];
|
||||
expect(elementWithBinding.id).toEqual('');
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(elementWithBinding.id).toEqual('buz');
|
||||
});
|
||||
|
||||
it('should consume directive watch expression change.', () => {
|
||||
var memento = new DirectivePropertyMemento(1, 0, 'prop',
|
||||
(o, v) => o.prop = v);
|
||||
setUp(memento);
|
||||
|
||||
var elInj = view.elementInjectors[1];
|
||||
expect(elInj.get(Directive).prop).toEqual('foo');
|
||||
|
||||
ctx.foo = 'buz';
|
||||
cd.detectChanges();
|
||||
expect(elInj.get(Directive).prop).toEqual('buz');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
class AnotherDirective {
|
||||
@FIELD('prop')
|
||||
constructor() {
|
||||
this.prop = 'anotherFoo';
|
||||
}
|
||||
}
|
||||
|
||||
class MyEvaluationContext {
|
||||
|
@ -186,3 +213,7 @@ class MyEvaluationContext {
|
|||
this.foo = 'bar';
|
||||
};
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ export 'dart:core' show Map, List, Set;
|
|||
|
||||
class MapWrapper {
|
||||
static HashMap create() => new HashMap();
|
||||
static HashMap createFromStringMap(m) => m;
|
||||
static HashMap createFromPairs(List pairs) {
|
||||
return pairs.fold({}, (m, p){
|
||||
m[p[0]] = p[1];
|
||||
|
@ -31,6 +32,12 @@ class StringMapWrapper {
|
|||
static set(map, key, value) {
|
||||
map[key] = value;
|
||||
}
|
||||
static forEach(m, fn) {
|
||||
m.forEach((k,v) => fn(v,k));
|
||||
}
|
||||
static isEmpty(m) {
|
||||
return m.isEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
class ListWrapper {
|
||||
|
|
|
@ -6,6 +6,13 @@ export var Set = window.Set;
|
|||
|
||||
export class MapWrapper {
|
||||
static create():Map { return new Map(); }
|
||||
static createFromStringMap(stringMap):Map {
|
||||
var result = MapWrapper.create();
|
||||
for (var prop in stringMap) {
|
||||
MapWrapper.set(result, prop, stringMap[prop]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static createFromPairs(pairs:List):Map {return new Map(pairs);}
|
||||
static get(m, k) { return m.get(k); }
|
||||
static set(m, k, v) { m.set(k,v); }
|
||||
|
@ -30,6 +37,17 @@ export class StringMapWrapper {
|
|||
static set(map, key, value) {
|
||||
map[key] = value;
|
||||
}
|
||||
static isEmpty(map) {
|
||||
for (var prop in map) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static forEach(map, callback) {
|
||||
for (var prop in map) {
|
||||
callback(map[prop], prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ListWrapper {
|
||||
|
@ -67,7 +85,7 @@ export class ListWrapper {
|
|||
}
|
||||
static reduce(list:List, fn:Function, init) {
|
||||
return list.reduce(fn, init);
|
||||
}
|
||||
}
|
||||
static filter(array, pred:Function) {
|
||||
return array.filter(pred);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,14 @@ import 'dart:js' show JsObject;
|
|||
|
||||
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
|
||||
|
||||
// TODO(tbosch): Is there a builtin one? Why is Dart
|
||||
// removing unknown elements by default?
|
||||
class IdentitySanitizer implements NodeTreeSanitizer {
|
||||
void sanitizeTree(Node node) {}
|
||||
}
|
||||
|
||||
final identitySanitizer = new IdentitySanitizer();
|
||||
|
||||
class DOM {
|
||||
static query(selector) {
|
||||
return document.querySelector(selector);
|
||||
|
@ -18,21 +26,30 @@ class DOM {
|
|||
static getInnerHTML(el) {
|
||||
return el.innerHtml;
|
||||
}
|
||||
static getOuterHTML(el) {
|
||||
return el.outerHtml;
|
||||
}
|
||||
static setInnerHTML(el, value) {
|
||||
el.innerHtml = value;
|
||||
}
|
||||
static Node firstChild(el) {
|
||||
return el.firstChild;
|
||||
}
|
||||
static Element parentElement(el) {
|
||||
return el.parent;
|
||||
}
|
||||
static List<Node> childNodes(el) {
|
||||
return el.childNodes;
|
||||
}
|
||||
static appendChild(el, node) {
|
||||
el.append(node);
|
||||
}
|
||||
static setText(Text text, String value) {
|
||||
text.text = value;
|
||||
}
|
||||
static createTemplate(html) {
|
||||
var t = document.createElement('template');
|
||||
t.setInnerHtml(html);
|
||||
var t = new TemplateElement();
|
||||
t.setInnerHtml(html, treeSanitizer:identitySanitizer);
|
||||
return t;
|
||||
}
|
||||
static clone(Node node) {
|
||||
|
@ -41,10 +58,25 @@ class DOM {
|
|||
static setProperty(Element element, String name, value) {
|
||||
new JsObject.fromBrowserObject(element)[name] = value;
|
||||
}
|
||||
static getProperty(Element element, String name) {
|
||||
return new JsObject.fromBrowserObject(element)[name];
|
||||
}
|
||||
static getElementsByClassName(Element element, String name) {
|
||||
return element.getElementsByClassName(name);
|
||||
}
|
||||
static getElementsByTagName(Element element, String name) {
|
||||
return element.querySelectorAll(name);
|
||||
}
|
||||
static List classList(Element element) {
|
||||
return element.classes.toList();
|
||||
}
|
||||
static addClass(Element element, classname) {
|
||||
element.classes.add(classname);
|
||||
}
|
||||
static hasClass(Element element, classname) {
|
||||
return element.classes.contains(classname);
|
||||
}
|
||||
static attributeMap(Element element) {
|
||||
return element.attributes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ export var NodeList = window.NodeList;
|
|||
export var Text = window.Text;
|
||||
export var Element = window.HTMLElement;
|
||||
export var TemplateElement = window.HTMLTemplateElement;
|
||||
import {List} from 'facade/collection';
|
||||
import {List, MapWrapper} from 'facade/collection';
|
||||
|
||||
export class DOM {
|
||||
static query(selector) {
|
||||
|
@ -19,12 +19,21 @@ export class DOM {
|
|||
static getInnerHTML(el) {
|
||||
return el.innerHTML;
|
||||
}
|
||||
static getOuterHTML(el) {
|
||||
return el.outerHTML;
|
||||
}
|
||||
static firstChild(el):Node {
|
||||
return el.firstChild;
|
||||
}
|
||||
static parentElement(el) {
|
||||
return el.parentElement;
|
||||
}
|
||||
static childNodes(el):NodeList {
|
||||
return el.childNodes;
|
||||
}
|
||||
static appendChild(el, node) {
|
||||
el.appendChild(node);
|
||||
}
|
||||
static setInnerHTML(el, value) {
|
||||
el.innerHTML = value;
|
||||
}
|
||||
|
@ -42,10 +51,32 @@ export class DOM {
|
|||
static setProperty(element:Element, name:string, value) {
|
||||
element[name] = value;
|
||||
}
|
||||
static getProperty(element:Element, name:string) {
|
||||
return element[name];
|
||||
}
|
||||
static getElementsByClassName(element:Element, name:string) {
|
||||
return element.getElementsByClassName(name);
|
||||
}
|
||||
static getElementsByTagName(element:Element, name:string) {
|
||||
return element.getElementsByTagName(name);
|
||||
}
|
||||
static classList(element:Element):List {
|
||||
return Array.prototype.slice.call(element.classList, 0);
|
||||
}
|
||||
static addClass(element:Element, classname:string) {
|
||||
element.classList.add(classname);
|
||||
}
|
||||
static hasClass(element:Element, classname:string) {
|
||||
return element.classList.contains(classname);
|
||||
}
|
||||
static attributeMap(element:Element) {
|
||||
var res = MapWrapper.create();
|
||||
var elAttrs = element.attributes;
|
||||
for (var i = 0; i < elAttrs.length; i++) {
|
||||
var attrib = elAttrs[i];
|
||||
MapWrapper.set(res, attrib.name, attrib.value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,6 +58,24 @@ class StringWrapper {
|
|||
static charCodeAt(String s, int index) {
|
||||
return s.codeUnitAt(index);
|
||||
}
|
||||
|
||||
static split(String s, RegExp regExp) {
|
||||
var parts = [];
|
||||
var lastEnd = 0;
|
||||
regExp.allMatches(s).forEach((match) {
|
||||
parts.add(s.substring(lastEnd, match.start));
|
||||
lastEnd = match.end;
|
||||
for (var i=0; i<match.groupCount; i++) {
|
||||
parts.add(match.group(i+1));
|
||||
}
|
||||
});
|
||||
parts.add(s.substring(lastEnd));
|
||||
return parts;
|
||||
}
|
||||
|
||||
static equals(String s, String s2) {
|
||||
return s == s2;
|
||||
}
|
||||
}
|
||||
|
||||
class StringJoiner {
|
||||
|
@ -93,6 +111,9 @@ class RegExpWrapper {
|
|||
static RegExp create(regExpStr) {
|
||||
return new RegExp(regExpStr);
|
||||
}
|
||||
static firstMatch(regExp, input) {
|
||||
return regExp.firstMatch(input);
|
||||
}
|
||||
static matcher(regExp, input) {
|
||||
return regExp.allMatches(input).iterator;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import {assert} from 'rtts_assert/rtts_assert';
|
||||
|
||||
export var Type = Function;
|
||||
export var Math = window.Math;
|
||||
|
||||
|
@ -52,6 +54,14 @@ export class StringWrapper {
|
|||
static charCodeAt(s:string, index:int) {
|
||||
return s.charCodeAt(index);
|
||||
}
|
||||
|
||||
static split(s:string, regExp:RegExp) {
|
||||
return s.split(regExp.multiple);
|
||||
}
|
||||
|
||||
static equals(s:string, s2:string) {
|
||||
return s === s2;
|
||||
}
|
||||
}
|
||||
|
||||
export class StringJoiner {
|
||||
|
@ -125,15 +135,26 @@ int.assert = function(value) {
|
|||
return value == null || typeof value == 'number' && value === Math.floor(value);
|
||||
}
|
||||
|
||||
export var RegExp = window.RegExp;
|
||||
export var RegExp = assert.define('RegExp', function(obj) {
|
||||
assert(obj).is(assert.structure({
|
||||
single: window.RegExp,
|
||||
multiple: window.RegExp
|
||||
}));
|
||||
});
|
||||
|
||||
export class RegExpWrapper {
|
||||
static create(regExpStr):RegExp {
|
||||
return new RegExp(regExpStr, 'g');
|
||||
return {
|
||||
multiple: new window.RegExp(regExpStr, 'g'),
|
||||
single: new window.RegExp(regExpStr)
|
||||
};
|
||||
}
|
||||
static firstMatch(regExp, input) {
|
||||
return input.match(regExp.single);
|
||||
}
|
||||
static matcher(regExp, input) {
|
||||
return {
|
||||
re: regExp,
|
||||
re: regExp.multiple,
|
||||
input: input
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ export function main() {
|
|||
expect(actual).toEqual(expected);
|
||||
expect(falseActual).not.toEqual(expected);
|
||||
});
|
||||
|
||||
it('should work for arrays of maps', () => {
|
||||
expect([{'a':'b'}]).toEqual([{'a':'b'}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import {describe, it, expect} from 'test_lib/test_lib';
|
||||
import {describe, it, expect, iit} from 'test_lib/test_lib';
|
||||
import {readFirstAnnotation} from './fixtures/annotations';
|
||||
import {CONST} from 'facade/lang';
|
||||
|
||||
|
@ -35,6 +35,15 @@ class A {}
|
|||
@AnnotateMe({maybe: 'yes'})
|
||||
class B {}
|
||||
|
||||
@AnnotateMe({maybe: {'a': 'b'}})
|
||||
class SomeClassWithMapInAnnotation {}
|
||||
|
||||
@AnnotateMe({maybe: [23]})
|
||||
class SomeClassWithListInAnnotation {}
|
||||
|
||||
@AnnotateMe({maybe: new Provide(0)})
|
||||
class SomeClassWithConstObject {}
|
||||
|
||||
function annotatedParams(@Inject(Foo) f, @Inject(Bar) b) {}
|
||||
|
||||
export function main() {
|
||||
|
@ -49,5 +58,17 @@ export function main() {
|
|||
expect(readFirstAnnotation(A).maybe).toBe('default');
|
||||
expect(readFirstAnnotation(B).maybe).toBe('yes');
|
||||
});
|
||||
|
||||
it('should work with maps in named arguments', () => {
|
||||
expect(readFirstAnnotation(SomeClassWithMapInAnnotation).maybe).toEqual({'a': 'b'});
|
||||
});
|
||||
|
||||
it('should work with lists in named arguments', () => {
|
||||
expect(readFirstAnnotation(SomeClassWithListInAnnotation).maybe).toEqual([23]);
|
||||
});
|
||||
|
||||
it('should work with new instances in named arguments', () => {
|
||||
expect(readFirstAnnotation(SomeClassWithConstObject).maybe).toEqual(new Provide(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export class DartParseTreeWriter extends JavaScriptParseTreeWriter {
|
|||
constructor(moduleName, outputPath) {
|
||||
super(outputPath);
|
||||
this.libName = moduleName.replace(/\//g, '.').replace(/[^\w.\/]/g, '_');
|
||||
this.annotationContextCounter = 0;
|
||||
}
|
||||
|
||||
// CLASS FIELDS
|
||||
|
@ -377,9 +378,11 @@ export class DartParseTreeWriter extends JavaScriptParseTreeWriter {
|
|||
this.visitAny(tree.name);
|
||||
|
||||
if (tree.args !== null) {
|
||||
this.annotationContextCounter++;
|
||||
this.write_(OPEN_PAREN);
|
||||
this.writeList_(tree.args.args, COMMA, false);
|
||||
this.write_(CLOSE_PAREN);
|
||||
this.annotationContextCounter--;
|
||||
}
|
||||
|
||||
this.writeSpace_()
|
||||
|
@ -400,6 +403,31 @@ export class DartParseTreeWriter extends JavaScriptParseTreeWriter {
|
|||
this.visitAny(tree.body);
|
||||
}
|
||||
|
||||
visitObjectLiteralExpression(tree) {
|
||||
if (this.annotationContextCounter) {
|
||||
this.write_('const');
|
||||
}
|
||||
super.visitObjectLiteralExpression(tree);
|
||||
}
|
||||
|
||||
visitArrayLiteralExpression(tree) {
|
||||
if (this.annotationContextCounter) {
|
||||
this.write_('const');
|
||||
}
|
||||
super.visitArrayLiteralExpression(tree);
|
||||
}
|
||||
|
||||
visitNewExpression(tree) {
|
||||
if (this.annotationContextCounter) {
|
||||
this.write_('const');
|
||||
this.writeSpace_();
|
||||
this.visitAny(tree.operand);
|
||||
this.visitAny(tree.args);
|
||||
} else {
|
||||
super.visitNewExpression(tree);
|
||||
}
|
||||
}
|
||||
|
||||
visitNamedParameterList(tree) {
|
||||
this.writeList_(tree.parameterNameAndValues, COMMA, false);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue