feat(compiler): new semantics for `template` attributes and view variables.
- Supports `<div template=“…”>`, including parsing the expressions within the attribute. - Supports `<template let-ng-repeat=“rows”>` - Adds attribute interpolation (was missing previously)
This commit is contained in:
parent
f864aa1f8e
commit
c6846f1163
|
@ -111,7 +111,7 @@ export class KeyedAccess extends AST {
|
|||
this.obj = obj;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
|
||||
eval(context) {
|
||||
var obj = this.obj.eval(context);
|
||||
var key = this.key.eval(context);
|
||||
|
@ -169,11 +169,11 @@ export class LiteralPrimitive extends AST {
|
|||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
eval(context) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
|
||||
visit(visitor, args) {
|
||||
visitor.visitLiteralPrimitive(this, args);
|
||||
}
|
||||
|
@ -184,11 +184,11 @@ export class LiteralArray extends AST {
|
|||
constructor(expressions:List) {
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
|
||||
eval(context) {
|
||||
return ListWrapper.map(this.expressions, (e) => e.eval(context));
|
||||
}
|
||||
|
||||
|
||||
visit(visitor, args) {
|
||||
visitor.visitLiteralArray(this, args);
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ export class Assignment extends AST {
|
|||
eval(context) {
|
||||
return this.target.assign(context, this.value.eval(context));
|
||||
}
|
||||
|
||||
|
||||
visit(visitor, args) {
|
||||
visitor.visitAssignment(this, args);
|
||||
}
|
||||
|
@ -336,6 +336,22 @@ export class FunctionCall extends AST {
|
|||
}
|
||||
}
|
||||
|
||||
export class ASTWithSource {
|
||||
constructor(ast:AST, source:string) {
|
||||
this.source = source;
|
||||
this.ast = ast;
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateBinding {
|
||||
constructor(key:string, name:string, expression:ASTWithSource) {
|
||||
this.key = key;
|
||||
// only either name or expression will be filled.
|
||||
this.name = name;
|
||||
this.expression = expression;
|
||||
}
|
||||
}
|
||||
|
||||
//INTERFACE
|
||||
export class AstVisitor {
|
||||
visitChain(ast:Chain, args){}
|
||||
|
|
|
@ -130,6 +130,7 @@ export const $CR = 13;
|
|||
export const $SPACE = 32;
|
||||
export const $BANG = 33;
|
||||
export const $DQ = 34;
|
||||
export const $HASH = 35;
|
||||
export const $$ = 36;
|
||||
export const $PERCENT = 37;
|
||||
export const $AMPERSAND = 38;
|
||||
|
@ -246,6 +247,8 @@ class _Scanner {
|
|||
case $SQ:
|
||||
case $DQ:
|
||||
return this.scanString();
|
||||
case $HASH:
|
||||
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
|
||||
case $PLUS:
|
||||
case $MINUS:
|
||||
case $STAR:
|
||||
|
@ -459,7 +462,8 @@ var OPERATORS = SetWrapper.createFromList([
|
|||
'&',
|
||||
'|',
|
||||
'!',
|
||||
'?'
|
||||
'?',
|
||||
'#'
|
||||
]);
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,10 @@ import {
|
|||
LiteralArray,
|
||||
LiteralMap,
|
||||
MethodCall,
|
||||
FunctionCall
|
||||
FunctionCall,
|
||||
TemplateBindings,
|
||||
TemplateBinding,
|
||||
ASTWithSource
|
||||
} from './ast';
|
||||
|
||||
var _implicitReceiver = new ImplicitReceiver();
|
||||
|
@ -32,14 +35,21 @@ export class Parser {
|
|||
this._closureMap = closureMap;
|
||||
}
|
||||
|
||||
parseAction(input:string):AST {
|
||||
parseAction(input:string):ASTWithSource {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
return new _ParseAST(input, tokens, this._closureMap, true).parseChain();
|
||||
var ast = new _ParseAST(input, tokens, this._closureMap, true).parseChain();
|
||||
return new ASTWithSource(ast, input);
|
||||
}
|
||||
|
||||
parseBinding(input:string):AST {
|
||||
parseBinding(input:string):ASTWithSource {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
return new _ParseAST(input, tokens, this._closureMap, false).parseChain();
|
||||
var ast = new _ParseAST(input, tokens, this._closureMap, false).parseChain();
|
||||
return new ASTWithSource(ast, input);
|
||||
}
|
||||
|
||||
parseTemplateBindings(input:string):List<TemplateBinding> {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
return new _ParseAST(input, tokens, this._closureMap, false).parseTemplateBindings();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,6 +417,29 @@ class _ParseAST {
|
|||
return positionals;
|
||||
}
|
||||
|
||||
parseTemplateBindings() {
|
||||
var bindings = [];
|
||||
while (this.index < this.tokens.length) {
|
||||
var key = this.expectIdentifierOrKeywordOrString();
|
||||
this.optionalCharacter($COLON);
|
||||
var name = null;
|
||||
var expression = null;
|
||||
if (this.optionalOperator("#")) {
|
||||
name = this.expectIdentifierOrKeyword();
|
||||
} else {
|
||||
var start = this.inputIndex;
|
||||
var ast = this.parseExpression();
|
||||
var source = this.input.substring(start, this.inputIndex);
|
||||
expression = new ASTWithSource(ast, source);
|
||||
}
|
||||
ListWrapper.push(bindings, new TemplateBinding(key, name, expression));
|
||||
if (!this.optionalCharacter($SEMICOLON)) {
|
||||
this.optionalCharacter($COMMA);
|
||||
};
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
error(message:string, index:int = null) {
|
||||
if (isBlank(index)) index = this.index;
|
||||
|
||||
|
|
|
@ -310,6 +310,8 @@ class ProtoRecordCreator {
|
|||
|
||||
visitAssignment(ast:Assignment, dest) {this.unsupported();}
|
||||
|
||||
visitTemplateBindings(ast, dest) {this.unsupported();}
|
||||
|
||||
createRecordsFromAST(ast:AST, memento){
|
||||
ast.visit(this, memento);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {Record} from 'change_detection/record';
|
|||
export function main() {
|
||||
function ast(exp:string) {
|
||||
var parser = new Parser(new Lexer(), new ClosureMap());
|
||||
return parser.parseBinding(exp);
|
||||
return parser.parseBinding(exp).ast;
|
||||
}
|
||||
|
||||
function createChangeDetector(memo:string, exp:string, context = null, formatters = null) {
|
||||
|
|
|
@ -237,6 +237,11 @@ export function main() {
|
|||
}).toThrowError("Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']");
|
||||
});
|
||||
|
||||
it('should tokenize hash as operator', function() {
|
||||
var tokens:List<Token> = lex("#");
|
||||
expectOperatorToken(tokens[0], 0, '#');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {ddescribe, describe, it, xit, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {BaseException, isBlank} from 'facade/lang';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
import {BaseException, isBlank, isPresent} from 'facade/lang';
|
||||
import {MapWrapper, ListWrapper} from 'facade/collection';
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
import {Formatter, LiteralPrimitive} from 'change_detection/parser/ast';
|
||||
|
@ -32,11 +32,15 @@ export function main() {
|
|||
}
|
||||
|
||||
function parseAction(text) {
|
||||
return createParser().parseAction(text);
|
||||
return createParser().parseAction(text).ast;
|
||||
}
|
||||
|
||||
function parseBinding(text) {
|
||||
return createParser().parseBinding(text);
|
||||
return createParser().parseBinding(text).ast;
|
||||
}
|
||||
|
||||
function parseTemplateBindings(text) {
|
||||
return createParser().parseTemplateBindings(text);
|
||||
}
|
||||
|
||||
function expectEval(text, passedInContext = null) {
|
||||
|
@ -48,6 +52,15 @@ export function main() {
|
|||
return expect(() => parseAction(text).eval(td()));
|
||||
}
|
||||
|
||||
function evalAsts(asts, passedInContext = null) {
|
||||
var c = isBlank(passedInContext) ? td() : passedInContext;
|
||||
var res = [];
|
||||
for (var i=0; i<asts.length; i++) {
|
||||
ListWrapper.push(res, asts[i].eval(c));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
describe("parser", () => {
|
||||
describe("parseAction", () => {
|
||||
describe("basic expressions", () => {
|
||||
|
@ -248,7 +261,7 @@ export function main() {
|
|||
expectEval('a["key"] = 200', context).toEqual(200);
|
||||
expect(MapWrapper.get(context.a, "key")).toEqual(200);
|
||||
});
|
||||
|
||||
|
||||
it("should support array/map updates", () => {
|
||||
var context = td([MapWrapper.createFromPairs([["key", 100]])]);
|
||||
expectEval('a[0]["key"] = 200', context).toEqual(200);
|
||||
|
@ -287,7 +300,7 @@ export function main() {
|
|||
|
||||
it('should pass exceptions', () => {
|
||||
expect(() => {
|
||||
createParser().parseAction('a()').eval(td(() => {throw new BaseException("boo to you")}));
|
||||
createParser().parseAction('a()').ast.eval(td(() => {throw new BaseException("boo to you")}));
|
||||
}).toThrowError('boo to you');
|
||||
});
|
||||
|
||||
|
@ -297,6 +310,10 @@ export function main() {
|
|||
expectEval("1;;").toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should store the source in the result', () => {
|
||||
expect(createParser().parseAction('someExpr').source).toBe('someExpr');
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseBinding", () => {
|
||||
|
@ -319,6 +336,11 @@ export function main() {
|
|||
expect(() => parseBinding('"Foo"|1234')).toThrowError(new RegExp('identifier or keyword'));
|
||||
expect(() => parseBinding('"Foo"|"uppercase"')).toThrowError(new RegExp('identifier or keyword'));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should store the source in the result', () => {
|
||||
expect(createParser().parseBinding('someExpr').source).toBe('someExpr');
|
||||
});
|
||||
|
||||
it('should throw on chain expressions', () => {
|
||||
|
@ -329,6 +351,90 @@ export function main() {
|
|||
expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression"));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTemplateBindings', () => {
|
||||
|
||||
function keys(templateBindings) {
|
||||
return ListWrapper.map(templateBindings, (binding) => binding.key );
|
||||
}
|
||||
|
||||
function names(templateBindings) {
|
||||
return ListWrapper.map(templateBindings, (binding) => binding.name );
|
||||
}
|
||||
|
||||
function exprSources(templateBindings) {
|
||||
return ListWrapper.map(templateBindings,
|
||||
(binding) => isPresent(binding.expression) ? binding.expression.source : null );
|
||||
}
|
||||
|
||||
function exprAsts(templateBindings) {
|
||||
return ListWrapper.map(templateBindings,
|
||||
(binding) => isPresent(binding.expression) ? binding.expression.ast : null );
|
||||
}
|
||||
|
||||
it('should parse an empty string', () => {
|
||||
var bindings = parseTemplateBindings("");
|
||||
expect(bindings).toEqual([]);
|
||||
});
|
||||
|
||||
it('should only allow identifier, string, or keyword as keys', () => {
|
||||
var bindings = parseTemplateBindings("a:'b'");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings("'a':'b'");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
bindings = parseTemplateBindings("\"a\":'b'");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
|
||||
expect( () => {
|
||||
parseTemplateBindings('(:0');
|
||||
}).toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
|
||||
expect( () => {
|
||||
parseTemplateBindings('1234:0');
|
||||
}).toThrowError(new RegExp('expected identifier, keyword, or string'));
|
||||
});
|
||||
|
||||
it('should detect expressions as value', () => {
|
||||
var bindings = parseTemplateBindings("a:b");
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
expect(evalAsts(exprAsts(bindings), td(0, 23))).toEqual([23]);
|
||||
|
||||
bindings = parseTemplateBindings("a:1+1");
|
||||
expect(exprSources(bindings)).toEqual(['1+1']);
|
||||
expect(evalAsts(exprAsts(bindings))).toEqual([2]);
|
||||
});
|
||||
|
||||
it('should detect names as value', () => {
|
||||
var bindings = parseTemplateBindings("a:#b");
|
||||
expect(names(bindings)).toEqual(['b']);
|
||||
expect(exprSources(bindings)).toEqual([null]);
|
||||
expect(exprAsts(bindings)).toEqual([null]);
|
||||
});
|
||||
|
||||
it('should allow space and colon as separators', () => {
|
||||
var bindings = parseTemplateBindings("a:b");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
|
||||
bindings = parseTemplateBindings("a b");
|
||||
expect(keys(bindings)).toEqual(['a']);
|
||||
expect(exprSources(bindings)).toEqual(['b']);
|
||||
});
|
||||
|
||||
it('should allow multiple pairs', () => {
|
||||
var bindings = parseTemplateBindings("a 1 b 2");
|
||||
expect(keys(bindings)).toEqual(['a', 'b']);
|
||||
expect(exprSources(bindings)).toEqual(['1 ', '2']);
|
||||
});
|
||||
|
||||
it('should store the sources in the result', () => {
|
||||
var bindings = parseTemplateBindings("a 1,b 2");
|
||||
expect(bindings[0].expression.source).toEqual('1');
|
||||
expect(bindings[1].expression.source).toEqual('2');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import {Decorator} from '../../annotations/decorator';
|
|||
import {Component} from '../../annotations/component';
|
||||
import {Template} from '../../annotations/template';
|
||||
|
||||
import {ASTWithSource} from 'change_detection/parser/ast';
|
||||
|
||||
/**
|
||||
* Collects all data that is needed to process an element
|
||||
* in the compile process. Fields are filled
|
||||
|
@ -18,6 +20,7 @@ export class CompileElement {
|
|||
this._classList = null;
|
||||
this.textNodeBindings = null;
|
||||
this.propertyBindings = null;
|
||||
this.variableBindings = null;
|
||||
this.decoratorDirectives = null;
|
||||
this.templateDirective = null;
|
||||
this.componentDirective = null;
|
||||
|
@ -60,20 +63,27 @@ export class CompileElement {
|
|||
return this._classList;
|
||||
}
|
||||
|
||||
addTextNodeBinding(indexInParent:int, expression:string) {
|
||||
addTextNodeBinding(indexInParent:int, expression:ASTWithSource) {
|
||||
if (isBlank(this.textNodeBindings)) {
|
||||
this.textNodeBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
|
||||
}
|
||||
|
||||
addPropertyBinding(property:string, expression:string) {
|
||||
addPropertyBinding(property:string, expression:ASTWithSource) {
|
||||
if (isBlank(this.propertyBindings)) {
|
||||
this.propertyBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.propertyBindings, property, expression);
|
||||
}
|
||||
|
||||
addVariableBinding(contextName:string, templateName:string) {
|
||||
if (isBlank(this.variableBindings)) {
|
||||
this.variableBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.variableBindings, contextName, templateName);
|
||||
}
|
||||
|
||||
addDirective(directive:AnnotatedType) {
|
||||
var annotation = directive.annotation;
|
||||
if (annotation instanceof Decorator) {
|
||||
|
|
|
@ -21,13 +21,13 @@ export function createDefaultSteps(
|
|||
directives: List<AnnotatedType>
|
||||
) {
|
||||
return [
|
||||
new PropertyBindingParser(),
|
||||
new TextInterpolationParser(),
|
||||
new ViewSplitter(parser),
|
||||
new TextInterpolationParser(parser),
|
||||
new PropertyBindingParser(parser),
|
||||
new DirectiveParser(directives),
|
||||
new ViewSplitter(),
|
||||
new ElementBindingMarker(),
|
||||
new ProtoViewBuilder(),
|
||||
new ProtoElementInjectorBuilder(),
|
||||
new ElementBinderBuilder(parser, closureMap)
|
||||
new ElementBinderBuilder(closureMap)
|
||||
];
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {isPresent, BaseException} from 'facade/lang';
|
||||
import {List, MapWrapper} from 'facade/collection';
|
||||
import {TemplateElement} from 'facade/dom';
|
||||
import {SelectorMatcher} from '../selector';
|
||||
import {CssSelector} from '../selector';
|
||||
|
||||
|
@ -12,7 +13,8 @@ import {CompileControl} from './compile_control';
|
|||
import {Reflector} from '../reflector';
|
||||
|
||||
/**
|
||||
* Parses the directives on a single element.
|
||||
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
||||
* <template> elements for template directives.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#decoratorDirectives
|
||||
|
@ -22,6 +24,8 @@ import {Reflector} from '../reflector';
|
|||
* Reads:
|
||||
* - CompileElement#propertyBindings (to find directives contained
|
||||
* in the property bindings)
|
||||
* - CompileElement#variableBindings (to find directives contained
|
||||
* in the property bindings)
|
||||
*/
|
||||
export class DirectiveParser extends CompileStep {
|
||||
constructor(directives:List<AnnotatedType>) {
|
||||
|
@ -47,17 +51,29 @@ export class DirectiveParser extends CompileStep {
|
|||
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);
|
||||
MapWrapper.forEach(current.propertyBindings, (expression, prop) => {
|
||||
cssSelector.addAttribute(prop, expression.source);
|
||||
});
|
||||
}
|
||||
if (isPresent(current.variableBindings)) {
|
||||
MapWrapper.forEach(current.variableBindings, (value, name) => {
|
||||
cssSelector.addAttribute(name, value);
|
||||
});
|
||||
}
|
||||
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
||||
// only be present on <template> elements any more!
|
||||
var isTemplateElement = current.element instanceof TemplateElement;
|
||||
this._selectorMatcher.match(cssSelector, (directive) => {
|
||||
if (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)) {
|
||||
if (directive.annotation instanceof Template) {
|
||||
if (!isTemplateElement) {
|
||||
throw new BaseException('Template directives need to be placed on <template> elements or elements with template attribute!');
|
||||
} else if (isPresent(current.templateDirective)) {
|
||||
throw new BaseException('Only one template directive per element is allowed!');
|
||||
}
|
||||
} else if (isTemplateElement) {
|
||||
throw new BaseException('Only template directives are allowed on <template> elements!');
|
||||
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
|
||||
throw new BaseException('Only one component directive per element is allowed!');
|
||||
}
|
||||
current.addDirective(directive);
|
||||
|
|
|
@ -43,8 +43,7 @@ import {CompileControl} from './compile_control';
|
|||
* with the flag `isViewRoot`.
|
||||
*/
|
||||
export class ElementBinderBuilder extends CompileStep {
|
||||
constructor(parser:Parser, closureMap:ClosureMap) {
|
||||
this._parser = parser;
|
||||
constructor(closureMap:ClosureMap) {
|
||||
this._closureMap = closureMap;
|
||||
}
|
||||
|
||||
|
@ -56,10 +55,10 @@ export class ElementBinderBuilder extends CompileStep {
|
|||
current.componentDirective, current.templateDirective);
|
||||
|
||||
if (isPresent(current.textNodeBindings)) {
|
||||
this._bindTextNodes(protoView, current.textNodeBindings);
|
||||
this._bindTextNodes(protoView, current);
|
||||
}
|
||||
if (isPresent(current.propertyBindings)) {
|
||||
this._bindElementProperties(protoView, current.propertyBindings);
|
||||
this._bindElementProperties(protoView, current);
|
||||
}
|
||||
this._bindDirectiveProperties(this._collectDirectives(current), current);
|
||||
} else if (isPresent(parent)) {
|
||||
|
@ -68,36 +67,36 @@ export class ElementBinderBuilder extends CompileStep {
|
|||
current.inheritedElementBinder = elementBinder;
|
||||
}
|
||||
|
||||
_bindTextNodes(protoView, textNodeBindings) {
|
||||
MapWrapper.forEach(textNodeBindings, (expression, indexInParent) => {
|
||||
protoView.bindTextNode(indexInParent, this._parser.parseBinding(expression));
|
||||
_bindTextNodes(protoView, compileElement) {
|
||||
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
|
||||
protoView.bindTextNode(indexInParent, expression.ast);
|
||||
});
|
||||
}
|
||||
|
||||
_bindElementProperties(protoView, propertyBindings) {
|
||||
MapWrapper.forEach(propertyBindings, (expression, property) => {
|
||||
protoView.bindElementProperty(property, this._parser.parseBinding(expression));
|
||||
_bindElementProperties(protoView, compileElement) {
|
||||
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
|
||||
protoView.bindElementProperty(property, expression.ast);
|
||||
});
|
||||
}
|
||||
|
||||
_collectDirectives(pipelineElement) {
|
||||
_collectDirectives(compileElement) {
|
||||
var directives;
|
||||
if (isPresent(pipelineElement.decoratorDirectives)) {
|
||||
directives = ListWrapper.clone(pipelineElement.decoratorDirectives);
|
||||
if (isPresent(compileElement.decoratorDirectives)) {
|
||||
directives = ListWrapper.clone(compileElement.decoratorDirectives);
|
||||
} else {
|
||||
directives = [];
|
||||
}
|
||||
if (isPresent(pipelineElement.templateDirective)) {
|
||||
ListWrapper.push(directives, pipelineElement.templateDirective);
|
||||
if (isPresent(compileElement.templateDirective)) {
|
||||
ListWrapper.push(directives, compileElement.templateDirective);
|
||||
}
|
||||
if (isPresent(pipelineElement.componentDirective)) {
|
||||
ListWrapper.push(directives, pipelineElement.componentDirective);
|
||||
if (isPresent(compileElement.componentDirective)) {
|
||||
ListWrapper.push(directives, compileElement.componentDirective);
|
||||
}
|
||||
return directives;
|
||||
}
|
||||
|
||||
_bindDirectiveProperties(typesWithAnnotations, pipelineElement) {
|
||||
var protoView = pipelineElement.inheritedProtoView;
|
||||
_bindDirectiveProperties(typesWithAnnotations, compileElement) {
|
||||
var protoView = compileElement.inheritedProtoView;
|
||||
var directiveIndex = 0;
|
||||
ListWrapper.forEach(typesWithAnnotations, (typeWithAnnotation) => {
|
||||
var annotation = typeWithAnnotation.annotation;
|
||||
|
@ -105,8 +104,8 @@ export class ElementBinderBuilder extends CompileStep {
|
|||
return;
|
||||
}
|
||||
StringMapWrapper.forEach(annotation.bind, (dirProp, elProp) => {
|
||||
var expression = isPresent(pipelineElement.propertyBindings) ?
|
||||
MapWrapper.get(pipelineElement.propertyBindings, elProp) :
|
||||
var expression = isPresent(compileElement.propertyBindings) ?
|
||||
MapWrapper.get(compileElement.propertyBindings, elProp) :
|
||||
null;
|
||||
if (isBlank(expression)) {
|
||||
throw new BaseException('No element binding found for property '+elProp
|
||||
|
@ -114,7 +113,7 @@ export class ElementBinderBuilder extends CompileStep {
|
|||
}
|
||||
protoView.bindDirectiveProperty(
|
||||
directiveIndex++,
|
||||
this._parser.parseBinding(expression),
|
||||
expression.ast,
|
||||
dirProp,
|
||||
this._closureMap.setter(dirProp)
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ const NG_BINDING_CLASS = 'ng-binding';
|
|||
* Reads:
|
||||
* - CompileElement#textNodeBindings
|
||||
* - CompileElement#propertyBindings
|
||||
* - CompileElement#variableBindings
|
||||
* - CompileElement#decoratorDirectives
|
||||
* - CompileElement#componentDirective
|
||||
* - CompileElement#templateDirective
|
||||
|
@ -27,6 +28,7 @@ export class ElementBindingMarker extends CompileStep {
|
|||
var hasBindings =
|
||||
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
|
||||
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
|
||||
(isPresent(current.variableBindings) && MapWrapper.size(current.variableBindings)>0) ||
|
||||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
|
||||
isPresent(current.templateDirective) ||
|
||||
isPresent(current.componentDirective);
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import {isPresent, isBlank, RegExpWrapper} from 'facade/lang';
|
||||
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'facade/lang';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
import {TemplateElement} from 'facade/dom';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ExpressionWithSource} from 'change_detection/parser/ast';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
import {interpolationToExpression} from './text_interpolation_parser';
|
||||
|
||||
// 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('\\[([^|]+)(?:\\|(.+))?\\]');
|
||||
var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(let))-(.+))|\\[([^\\]]+)\\]');
|
||||
|
||||
/**
|
||||
* Parses the property bindings on a single element.
|
||||
|
@ -16,15 +21,35 @@ var PROP_BIND_REGEXP = RegExpWrapper.create('\\[([^|]+)(?:\\|(.+))?\\]');
|
|||
* - CompileElement#propertyBindings
|
||||
*/
|
||||
export class PropertyBindingParser extends CompileStep {
|
||||
constructor(parser:Parser) {
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
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);
|
||||
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
||||
if (isPresent(bindParts)) {
|
||||
if (isPresent(bindParts[1])) {
|
||||
// match: bind-prop
|
||||
current.addPropertyBinding(bindParts[3], this._parser.parseBinding(attrValue));
|
||||
} else if (isPresent(bindParts[2])) {
|
||||
// match: let-prop
|
||||
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
||||
// only be present on <template> elements any more!
|
||||
if (!(current.element instanceof TemplateElement)) {
|
||||
throw new BaseException('let-* is only allowed on <template> elements!');
|
||||
}
|
||||
current.addVariableBinding(bindParts[3], attrValue);
|
||||
} else if (isPresent(bindParts[4])) {
|
||||
// match: [prop]
|
||||
current.addPropertyBinding(bindParts[4], this._parser.parseBinding(attrValue));
|
||||
}
|
||||
} else {
|
||||
var expression = interpolationToExpression(attrValue);
|
||||
if (isPresent(expression)) {
|
||||
current.addPropertyBinding(attrName, this._parser.parseBinding(expression));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {isPresent, BaseException} from 'facade/lang';
|
||||
import {ListWrapper} from 'facade/collection';
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
|
||||
import {ProtoView} from '../view';
|
||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||
|
@ -27,6 +27,11 @@ export class ProtoViewBuilder extends CompileStep {
|
|||
throw new BaseException('Only one nested view per element is allowed');
|
||||
}
|
||||
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
|
||||
if (isPresent(parent.variableBindings)) {
|
||||
MapWrapper.forEach(parent.variableBindings, (mappedName, varName) => {
|
||||
inheritedProtoView.bindVariable(varName, mappedName);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (isPresent(parent)) {
|
||||
inheritedProtoView = parent.inheritedProtoView;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {RegExpWrapper, StringWrapper, isPresent} from 'facade/lang';
|
||||
import {Node, DOM} from 'facade/dom';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
@ -9,6 +11,34 @@ import {CompileControl} from './compile_control';
|
|||
var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
|
||||
var QUOTE_REGEXP = RegExpWrapper.create("'");
|
||||
|
||||
export function interpolationToExpression(value:string):string {
|
||||
// TODO: add stringify formatter when we support formatters
|
||||
var parts = StringWrapper.split(value, INTERPOLATION_REGEXP);
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
var expression = '';
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
var expressionPart = null;
|
||||
if (i%2 === 0) {
|
||||
// fixed string
|
||||
if (parts[i].length > 0) {
|
||||
expressionPart = "'" + StringWrapper.replaceAll(parts[i], QUOTE_REGEXP, "\\'") + "'";
|
||||
}
|
||||
} else {
|
||||
// expression
|
||||
expressionPart = "(" + parts[i] + ")";
|
||||
}
|
||||
if (isPresent(expressionPart)) {
|
||||
if (expression.length > 0) {
|
||||
expression += '+';
|
||||
}
|
||||
expression += expressionPart;
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses interpolations in direct text child nodes of the current element.
|
||||
*
|
||||
|
@ -16,6 +46,10 @@ var QUOTE_REGEXP = RegExpWrapper.create("'");
|
|||
* - CompileElement#textNodeBindings
|
||||
*/
|
||||
export class TextInterpolationParser extends CompileStep {
|
||||
constructor(parser:Parser) {
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var element = current.element;
|
||||
var childNodes = DOM.templateAwareRoot(element).childNodes;
|
||||
|
@ -28,30 +62,10 @@ export class TextInterpolationParser extends CompileStep {
|
|||
}
|
||||
|
||||
_parseTextNode(pipelineElement, node, nodeIndex) {
|
||||
// TODO: add stringify formatter when we support formatters
|
||||
var parts = StringWrapper.split(node.nodeValue, INTERPOLATION_REGEXP);
|
||||
if (parts.length > 1) {
|
||||
var expression = '';
|
||||
for (var i=0; i<parts.length; i++) {
|
||||
var expressionPart = null;
|
||||
if (i%2 === 0) {
|
||||
// fixed string
|
||||
if (parts[i].length > 0) {
|
||||
expressionPart = "'" + StringWrapper.replaceAll(parts[i], QUOTE_REGEXP, "\\'") + "'";
|
||||
}
|
||||
} else {
|
||||
// expression
|
||||
expressionPart = "(" + parts[i] + ")";
|
||||
}
|
||||
if (isPresent(expressionPart)) {
|
||||
if (expression.length > 0) {
|
||||
expression += '+';
|
||||
}
|
||||
expression += expressionPart;
|
||||
}
|
||||
}
|
||||
var expression = interpolationToExpression(node.nodeValue);
|
||||
if (isPresent(expression)) {
|
||||
DOM.setText(node, ' ');
|
||||
pipelineElement.addTextNodeBinding(nodeIndex, expression);
|
||||
pipelineElement.addTextNodeBinding(nodeIndex, this._parser.parseBinding(expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import {isBlank, isPresent} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {DOM, TemplateElement} from 'facade/dom';
|
||||
import {MapWrapper, StringMapWrapper} from 'facade/collection';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
@ -13,50 +15,39 @@ import {CompileControl} from './compile_control';
|
|||
*
|
||||
* Fills:
|
||||
* - CompileElement#isViewRoot
|
||||
*
|
||||
* Updates:
|
||||
* - CompileElement#templateDirective
|
||||
* - CompileElement#propertyBindings
|
||||
*
|
||||
* Reads:
|
||||
* - CompileElement#templateDirective
|
||||
* - CompileElement#variableBindings
|
||||
* - CompileElement#propertyBindings
|
||||
*/
|
||||
export class ViewSplitter extends CompileStep {
|
||||
constructor(parser:Parser) {
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
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)) {
|
||||
if (isBlank(parent) || (current.element instanceof TemplateElement)) {
|
||||
current.isViewRoot = true;
|
||||
} else {
|
||||
var templateBindings = MapWrapper.get(current.attrs(), 'template');
|
||||
if (isPresent(templateBindings)) {
|
||||
current.isViewRoot = true;
|
||||
var templateElement = DOM.createTemplate('');
|
||||
var newParentElement = new CompileElement(templateElement);
|
||||
this._parseTemplateBindings(templateBindings, newParentElement);
|
||||
control.addParent(newParentElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_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);
|
||||
}
|
||||
});
|
||||
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
|
||||
var bindings = this._parser.parseTemplateBindings(templateBindings);
|
||||
for (var i=0; i<bindings.length; i++) {
|
||||
var binding = bindings[i];
|
||||
if (isPresent(binding.name)) {
|
||||
compileElement.addVariableBinding(binding.key, binding.name);
|
||||
} else {
|
||||
compileElement.addPropertyBinding(binding.key, binding.expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ export class ProtoView {
|
|||
protoWatchGroup:ProtoWatchGroup) {
|
||||
this.element = template;
|
||||
this.elementBinders = [];
|
||||
this.variableBindings = MapWrapper.create();
|
||||
this.protoWatchGroup = protoWatchGroup;
|
||||
this.textNodesWithBindingCount = 0;
|
||||
this.elementsWithBindingCount = 0;
|
||||
|
@ -124,6 +125,10 @@ export class ProtoView {
|
|||
return view;
|
||||
}
|
||||
|
||||
bindVariable(contextName:string, templateName:string) {
|
||||
MapWrapper.set(this.variableBindings, contextName, templateName);
|
||||
}
|
||||
|
||||
bindElement(protoElementInjector:ProtoElementInjector,
|
||||
componentDirective:AnnotatedType = null, templateDirective:AnnotatedType = null):ElementBinder {
|
||||
var elBinder = new ElementBinder(protoElementInjector, componentDirective, templateDirective);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {isPresent} from 'facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper} 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';
|
||||
|
@ -12,6 +12,9 @@ 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';
|
||||
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('DirectiveParser', () => {
|
||||
|
@ -22,7 +25,9 @@ export function main() {
|
|||
directives = [SomeDecorator, SomeTemplate, SomeTemplate2, SomeComponent, SomeComponent2];
|
||||
});
|
||||
|
||||
function createPipeline(propertyBindings = null) {
|
||||
function createPipeline({propertyBindings, variableBindings}={}) {
|
||||
var closureMap = new ClosureMap();
|
||||
var parser = new Parser(new Lexer(), closureMap);
|
||||
var annotatedDirectives = ListWrapper.create();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
ListWrapper.push(annotatedDirectives, reflector.annotatedType(directives[i]));
|
||||
|
@ -30,7 +35,12 @@ export function main() {
|
|||
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(propertyBindings)) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
StringMapWrapper.forEach(propertyBindings, (v, k) => {
|
||||
current.addPropertyBinding(k, parser.parseBinding(v));
|
||||
});
|
||||
}
|
||||
if (isPresent(variableBindings)) {
|
||||
current.variableBindings = MapWrapper.createFromStringMap(variableBindings);
|
||||
}
|
||||
}), new DirectiveParser(annotatedDirectives)]);
|
||||
}
|
||||
|
@ -42,33 +52,26 @@ export function main() {
|
|||
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));
|
||||
});
|
||||
describe('component directives', () => {
|
||||
it('should detect them in attributes', () => {
|
||||
var results = createPipeline().process(createElement('<div some-comp></div>'));
|
||||
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));
|
||||
});
|
||||
it('should detect them in property bindings', () => {
|
||||
var pipeline = createPipeline({propertyBindings: {
|
||||
'some-comp': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(createElement('<div></div>'));
|
||||
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 detect them in variable bindings', () => {
|
||||
var pipeline = createPipeline({variableBindings: {
|
||||
'some-comp': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(createElement('<div></div>'));
|
||||
expect(results[0].componentDirective).toEqual(reflector.annotatedType(SomeComponent));
|
||||
});
|
||||
|
||||
it('should not allow multiple component directives on the same element', () => {
|
||||
|
@ -78,6 +81,84 @@ export function main() {
|
|||
);
|
||||
}).toThrowError('Only one component directive per element is allowed!');
|
||||
});
|
||||
|
||||
it('should not allow component directives on <template> elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
createElement('<template some-comp></template>')
|
||||
);
|
||||
}).toThrowError('Only template directives are allowed on <template> elements!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template directives', () => {
|
||||
it('should detect them in attributes', () => {
|
||||
var results = createPipeline().process(createElement('<template some-templ></template>'));
|
||||
expect(results[0].templateDirective).toEqual(reflector.annotatedType(SomeTemplate));
|
||||
});
|
||||
|
||||
it('should detect them in property bindings', () => {
|
||||
var pipeline = createPipeline({propertyBindings: {
|
||||
'some-templ': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(createElement('<template></template>'));
|
||||
expect(results[0].templateDirective).toEqual(reflector.annotatedType(SomeTemplate));
|
||||
});
|
||||
|
||||
it('should detect them in variable bindings', () => {
|
||||
var pipeline = createPipeline({variableBindings: {
|
||||
'some-templ': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(createElement('<template></template>'));
|
||||
expect(results[0].templateDirective).toEqual(reflector.annotatedType(SomeTemplate));
|
||||
});
|
||||
|
||||
it('should not allow multiple template directives on the same element', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
createElement('<template some-templ some-templ2></template>')
|
||||
);
|
||||
}).toThrowError('Only one template directive per element is allowed!');
|
||||
});
|
||||
|
||||
it('should not allow template directives on non <template> elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
createElement('<div some-templ></div>')
|
||||
);
|
||||
}).toThrowError('Template directives need to be placed on <template> elements or elements with template attribute!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorator directives', () => {
|
||||
it('should detect them in attributes', () => {
|
||||
var results = createPipeline().process(createElement('<div some-decor></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reflector.annotatedType(SomeDecorator)]);
|
||||
});
|
||||
|
||||
it('should detect them in property bindings', () => {
|
||||
var pipeline = createPipeline({propertyBindings: {
|
||||
'some-decor': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(createElement('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reflector.annotatedType(SomeDecorator)]);
|
||||
});
|
||||
|
||||
it('should detect them in variable bindings', () => {
|
||||
var pipeline = createPipeline({variableBindings: {
|
||||
'some-decor': 'someExpr'
|
||||
}});
|
||||
var results = pipeline.process(createElement('<div></div>'));
|
||||
expect(results[0].decoratorDirectives).toEqual([reflector.annotatedType(SomeDecorator)]);
|
||||
});
|
||||
|
||||
it('should not allow decorator directives on <template> elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(
|
||||
createElement('<template some-decor></template>')
|
||||
);
|
||||
}).toThrowError('Only template directives are allowed on <template> elements!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ export function main() {
|
|||
}={}) {
|
||||
var reflector = new Reflector();
|
||||
var closureMap = new ClosureMap();
|
||||
var parser = new Parser(new Lexer(), closureMap);
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
|
@ -38,22 +39,24 @@ export function main() {
|
|||
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;
|
||||
MapWrapper.forEach(textNodeBindings, (v,k) => {
|
||||
current.addTextNodeBinding(k, parser.parseBinding(v));
|
||||
});
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('prop-binding'))) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
if (isPresent(propertyBindings)) {
|
||||
MapWrapper.forEach(propertyBindings, (v,k) => {
|
||||
current.addPropertyBinding(k, parser.parseBinding(v));
|
||||
});
|
||||
}
|
||||
hasBinding = true;
|
||||
}
|
||||
if (isPresent(protoElementInjector)) {
|
||||
current.inheritedProtoElementInjector = protoElementInjector;
|
||||
} else {
|
||||
current.inheritedProtoElementInjector = null;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('directives'))) {
|
||||
hasBinding = true;
|
||||
|
@ -65,7 +68,7 @@ export function main() {
|
|||
current.hasBindings = true;
|
||||
DOM.addClass(current.element, 'ng-binding');
|
||||
}
|
||||
}), new ElementBinderBuilder(new Parser(new Lexer(), closureMap), closureMap)
|
||||
}), new ElementBinderBuilder(closureMap)
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import {Component} from 'core/annotations/component';
|
|||
export function main() {
|
||||
describe('ElementBindingMarker', () => {
|
||||
|
||||
function createPipeline({textNodeBindings, propertyBindings, directives}={}) {
|
||||
function createPipeline({textNodeBindings, propertyBindings, variableBindings, directives}={}) {
|
||||
var reflector = new Reflector();
|
||||
return new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
|
@ -26,6 +26,9 @@ export function main() {
|
|||
if (isPresent(propertyBindings)) {
|
||||
current.propertyBindings = propertyBindings;
|
||||
}
|
||||
if (isPresent(variableBindings)) {
|
||||
current.variableBindings = variableBindings;
|
||||
}
|
||||
if (isPresent(directives)) {
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
current.addDirective(reflector.annotatedType(directives[i]));
|
||||
|
@ -53,6 +56,12 @@ export function main() {
|
|||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with variable bindings', () => {
|
||||
var variableBindings = MapWrapper.createFromStringMap({'a': 'expr'});
|
||||
var results = createPipeline({variableBindings: variableBindings}).process(createElement('<div></div>'));
|
||||
assertBinding(results[0], true);
|
||||
});
|
||||
|
||||
it('should mark elements with decorator directives', () => {
|
||||
var results = createPipeline({
|
||||
directives: [SomeDecoratorDirective]
|
||||
|
|
|
@ -4,20 +4,42 @@ import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
|||
import {DOM} from 'facade/dom';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
|
||||
export function main() {
|
||||
describe('PropertyBindingParser', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new PropertyBindingParser()]);
|
||||
return new CompilePipeline([new PropertyBindingParser(new Parser(new Lexer(), new ClosureMap()))]);
|
||||
}
|
||||
|
||||
it('should detect [] syntax', () => {
|
||||
var results = createPipeline().process(createElement('<div [a]="b"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a')).toEqual('b');
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).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');
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b');
|
||||
});
|
||||
|
||||
it('should detect interpolation syntax', () => {
|
||||
// Note: we don't test all corner cases of interpolation as we assume shared functionality between text interpolation
|
||||
// and attribute interpolation.
|
||||
var results = createPipeline().process(createElement('<div a="{{b}}"></div>'));
|
||||
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('(b)');
|
||||
});
|
||||
|
||||
it('should detect let- syntax', () => {
|
||||
var results = createPipeline().process(createElement('<template let-a="b"></template>'));
|
||||
expect(MapWrapper.get(results[0].variableBindings, 'a')).toEqual('b');
|
||||
});
|
||||
|
||||
it('should not allow let- syntax on non template elements', () => {
|
||||
expect( () => {
|
||||
createPipeline().process(createElement('<div let-a="b"></div>'))
|
||||
}).toThrowError('let-* is only allowed on <template> elements!');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,14 +7,18 @@ 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';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('ProtoViewBuilder', () => {
|
||||
function createPipeline() {
|
||||
function createPipeline(variableBindings=null) {
|
||||
return new CompilePipeline([new MockStep((parent, current, control) => {
|
||||
if (isPresent(current.element.getAttribute('viewroot'))) {
|
||||
current.isViewRoot = true;
|
||||
}
|
||||
if (isPresent(current.element.getAttribute('var-binding'))) {
|
||||
current.variableBindings = MapWrapper.createFromStringMap(variableBindings);
|
||||
}
|
||||
current.inheritedElementBinder = new ElementBinder(null, null, null);
|
||||
}), new ProtoViewBuilder()]);
|
||||
}
|
||||
|
@ -37,16 +41,29 @@ export function main() {
|
|||
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>');
|
||||
it('should save ProtoView into the elementBinder of parent element', () => {
|
||||
var el = createElement('<div viewroot><template><a viewroot></a></template></div>');
|
||||
var results = createPipeline().process(el);
|
||||
expect(results[1].inheritedElementBinder.nestedProtoView).toBe(results[2].inheritedProtoView);
|
||||
});
|
||||
|
||||
it('should bind variables to the nested ProtoView', () => {
|
||||
var el = createElement('<div viewroot><template var-binding><a viewroot></a></template></div>');
|
||||
var results = createPipeline({
|
||||
'var1': 'map1',
|
||||
'var2': 'map2'
|
||||
}).process(el);
|
||||
var npv = results[1].inheritedElementBinder.nestedProtoView;
|
||||
expect(npv.variableBindings).toEqual(MapWrapper.createFromStringMap({
|
||||
'var1': 'map1',
|
||||
'var2': 'map2'
|
||||
}));
|
||||
});
|
||||
|
||||
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>');
|
||||
var el = createElement('<div viewroot><template><a viewroot></a><a viewroot></a></template></div>');
|
||||
expect( () => {
|
||||
createPipeline().process(el);
|
||||
}).toThrowError('Only one nested view per element is allowed');
|
||||
|
|
|
@ -4,41 +4,45 @@ import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
|||
import {DOM} from 'facade/dom';
|
||||
import {MapWrapper} from 'facade/collection';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
|
||||
export function main() {
|
||||
describe('TextInterpolationParser', () => {
|
||||
function createPipeline() {
|
||||
return new CompilePipeline([new TextInterpolationParser()]);
|
||||
return new CompilePipeline([new TextInterpolationParser(new Parser(new Lexer(), new ClosureMap()))]);
|
||||
}
|
||||
|
||||
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)");
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("(expr1)");
|
||||
expect(MapWrapper.get(bindings, 2).source).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)");
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("(expr1)");
|
||||
expect(MapWrapper.get(bindings, 2).source).toEqual("(expr2)");
|
||||
});
|
||||
|
||||
it('should allow multiple expressions', () => {
|
||||
var results = createPipeline().process(createElement('<div>{{expr1}}{{expr2}}</div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0)).toEqual("(expr1)+(expr2)");
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("(expr1)+(expr2)");
|
||||
});
|
||||
|
||||
it('should allow fixed text before, in between and after expressions', () => {
|
||||
var results = createPipeline().process(createElement('<div>a{{expr1}}b{{expr2}}c</div>'));
|
||||
var bindings = results[0].textNodeBindings;
|
||||
expect(MapWrapper.get(bindings, 0)).toEqual("'a'+(expr1)+'b'+(expr2)+'c'");
|
||||
expect(MapWrapper.get(bindings, 0).source).toEqual("'a'+(expr1)+'b'+(expr2)+'c'");
|
||||
});
|
||||
|
||||
it('should escape quotes in fixed parts', () => {
|
||||
var results = createPipeline().process(createElement("<div>'\"a{{expr1}}</div>"));
|
||||
expect(MapWrapper.get(results[0].textNodeBindings, 0)).toEqual("'\\'\"a'+(expr1)");
|
||||
expect(MapWrapper.get(results[0].textNodeBindings, 0).source).toEqual("'\\'\"a'+(expr1)");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,51 +4,35 @@ 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';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||
import {Lexer} from 'change_detection/parser/lexer';
|
||||
|
||||
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 createPipeline() {
|
||||
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer(), new ClosureMap()))]);
|
||||
}
|
||||
|
||||
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 mark root elements as viewRoot', () => {
|
||||
var rootElement = createElement('<div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should mark <template> elements as viewRoot', () => {
|
||||
var rootElement = createElement('<div><template></template></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
describe('elements with template attribute', () => {
|
||||
|
||||
it('should insert an empty <template> element', () => {
|
||||
var rootElement = createElement('<div><div template></div></div>');
|
||||
var originalChild = rootElement.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
|
@ -57,79 +41,29 @@ export function main() {
|
|||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
it('should move the template directive to the new element', () => {
|
||||
it('should mark the element as viewRoot', () => {
|
||||
var rootElement = createElement('<div><div template></div></div>');
|
||||
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);
|
||||
});
|
||||
it('should add property bindings from the template attribute', () => {
|
||||
var rootElement = createElement('<div><div template="prop:expr"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(MapWrapper.get(results[1].propertyBindings, 'prop').source).toEqual('expr');
|
||||
});
|
||||
|
||||
it('should add variable mappings from the template attribute', () => {
|
||||
var rootElement = createElement('<div><div template="varName #mapName"></div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'varName': 'mapName'}));
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export function main() {
|
|||
it('should collect property bindings on the root element if it has the ng-binding class', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('prop', parser.parseBinding('a'));
|
||||
pv.bindElementProperty('prop', parser.parseBinding('a').ast);
|
||||
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
|
@ -57,7 +57,7 @@ export function main() {
|
|||
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('a', parser.parseBinding('b'));
|
||||
pv.bindElementProperty('a', parser.parseBinding('b').ast);
|
||||
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.bindElements.length).toEqual(1);
|
||||
|
@ -71,8 +71,8 @@ export function main() {
|
|||
it('should collect text nodes under the root element', () => {
|
||||
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('a'));
|
||||
pv.bindTextNode(2, parser.parseBinding('b'));
|
||||
pv.bindTextNode(0, parser.parseBinding('a').ast);
|
||||
pv.bindTextNode(2, parser.parseBinding('b').ast);
|
||||
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.textNodes.length).toEqual(2);
|
||||
|
@ -84,7 +84,7 @@ export function main() {
|
|||
var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('b'));
|
||||
pv.bindTextNode(0, parser.parseBinding('b').ast);
|
||||
|
||||
var view = pv.instantiate(null, null, null);
|
||||
expect(view.textNodes.length).toEqual(1);
|
||||
|
@ -176,7 +176,7 @@ export function main() {
|
|||
function createComponentWithSubPV(subProtoView) {
|
||||
var pv = new ProtoView(createElement('<cmp class="ng-binding"></cmp>'), new ProtoWatchGroup());
|
||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
|
||||
binder.componentDirective = someComponentDirective;
|
||||
binder.componentDirective = someComponentDirective;
|
||||
binder.nestedProtoView = subProtoView;
|
||||
return pv;
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ export function main() {
|
|||
var pv = new ProtoView(createElement('<div class="ng-binding">{{}}</div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindTextNode(0, parser.parseBinding('foo'));
|
||||
pv.bindTextNode(0, parser.parseBinding('foo').ast);
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
|
@ -251,7 +251,7 @@ export function main() {
|
|||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(null);
|
||||
pv.bindElementProperty('id', parser.parseBinding('foo'));
|
||||
pv.bindElementProperty('id', parser.parseBinding('foo').ast);
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
|
@ -263,7 +263,7 @@ export function main() {
|
|||
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
|
||||
new ProtoWatchGroup());
|
||||
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('foo'), 'prop', closureMap.setter('prop'));
|
||||
pv.bindDirectiveProperty( 0, parser.parseBinding('foo').ast, 'prop', closureMap.setter('prop'));
|
||||
createView(pv);
|
||||
|
||||
ctx.foo = 'buz';
|
||||
|
|
Loading…
Reference in New Issue