feat(change_detection): change binding syntax to explicitly specify pipes

This commit is contained in:
vsavkin 2015-02-19 17:47:25 -08:00
parent 69e02ee76f
commit 58ba700b14
20 changed files with 236 additions and 101 deletions

View File

@ -18,8 +18,8 @@ export * from './src/change_detection/pipes/pipe';
import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
from './src/change_detection/proto_change_detector'; from './src/change_detection/proto_change_detector';
import {PipeRegistry} from './src/change_detection/pipes/pipe_registry'; import {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
import {ArrayChanges} from './src/change_detection/pipes/array_changes'; import {ArrayChangesFactory} from './src/change_detection/pipes/array_changes';
import {NullPipe} from './src/change_detection/pipes/null_pipe'; import {NullPipeFactory} from './src/change_detection/pipes/null_pipe';
export class ChangeDetection { export class ChangeDetection {
createProtoChangeDetector(name:string):ProtoChangeDetector{ createProtoChangeDetector(name:string):ProtoChangeDetector{
@ -29,15 +29,9 @@ export class ChangeDetection {
} }
export var defaultPipes = { export var defaultPipes = {
"[]" : [ "iterableDiff" : [
{ new ArrayChangesFactory(),
"supports" : ArrayChanges.supportsObj, new NullPipeFactory()
"pipe" : () => new ArrayChanges()
},
{
"supports" : NullPipe.supportsObj,
"pipe" : () => new NullPipe()
}
] ]
}; };

View File

@ -15,7 +15,7 @@ import {
RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER, RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK, RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
@ -163,11 +163,11 @@ if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
`; `;
} }
function pipeCheckTemplate(context:string, pipe:string, function pipeCheckTemplate(context:string, pipe:string, pipeType:string,
value:string, change:string, addRecord:string, notify:string):string{ value:string, change:string, addRecord:string, notify:string):string{
return ` return `
if (${pipe} === ${UTIL}.unitialized() || !${pipe}.supports(${context})) { if (${pipe} === ${UTIL}.unitialized() || !${pipe}.supports(${context})) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('[]', ${context}); ${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context});
} }
${CHANGE_LOCAL} = ${pipe}.transform(${context}); ${CHANGE_LOCAL} = ${pipe}.transform(${context});
@ -283,7 +283,7 @@ export class ChangeDetectorJITGenerator {
fields = fields.concat(this.fieldNames); fields = fields.concat(this.fieldNames);
this.records.forEach((r) => { this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) { if (r.mode === RECORD_TYPE_PIPE) {
fields.push(this.pipeNames[r.selfIndex]); fields.push(this.pipeNames[r.selfIndex]);
} }
}); });
@ -314,7 +314,7 @@ export class ChangeDetectorJITGenerator {
} }
genRecord(r:ProtoRecord):string { genRecord(r:ProtoRecord):string {
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) { if (r.mode === RECORD_TYPE_PIPE) {
return this.genPipeCheck (r); return this.genPipeCheck (r);
} else { } else {
return this.genReferenceCheck(r); return this.genReferenceCheck(r);
@ -331,7 +331,7 @@ export class ChangeDetectorJITGenerator {
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue); var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r); var notify = this.genNotify(r);
return pipeCheckTemplate(context, pipe, newValue, change, addRecord, notify); return pipeCheckTemplate(context, pipe, r.name, newValue, change, addRecord, notify);
} }
genReferenceCheck(r:ProtoRecord):string { genReferenceCheck(r:ProtoRecord):string {

View File

@ -17,7 +17,7 @@ import {
RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER, RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK, RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
@ -81,7 +81,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
_check(proto:ProtoRecord) { _check(proto:ProtoRecord) {
try { try {
if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) { if (proto.mode == RECORD_TYPE_PIPE) {
return this._pipeCheck(proto); return this._pipeCheck(proto);
} else { } else {
return this._referenceCheck(proto); return this._referenceCheck(proto);
@ -184,7 +184,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
if (isPresent(storedPipe) && storedPipe.supports(context)) { if (isPresent(storedPipe) && storedPipe.supports(context)) {
return storedPipe; return storedPipe;
} else { } else {
var pipe = this.pipeRegistry.get("[]", context); var pipe = this.pipeRegistry.get(proto.name, context);
this._writePipe(proto, pipe); this._writePipe(proto, pipe);
return pipe; return pipe;
} }

View File

@ -33,22 +33,6 @@ export class EmptyExpr extends AST {
} }
} }
export class Structural extends AST {
value:AST;
constructor(value:AST) {
super();
this.value = value;
}
eval(context) {
return value.eval(context);
}
visit(visitor) {
return visitor.visitStructural(this);
}
}
export class ImplicitReceiver extends AST { export class ImplicitReceiver extends AST {
eval(context) { eval(context) {
return context; return context;
@ -204,6 +188,20 @@ export class Formatter extends AST {
} }
} }
export class Pipe extends AST {
exp:AST;
name:string;
constructor(exp:AST, name:string) {
super();
this.exp = exp;
this.name = name;
}
visit(visitor) {
return visitor.visitPipe(this);
}
}
export class LiteralPrimitive extends AST { export class LiteralPrimitive extends AST {
value; value;
constructor(value) { constructor(value) {
@ -460,9 +458,9 @@ export class AstVisitor {
visitAssignment(ast:Assignment) {} visitAssignment(ast:Assignment) {}
visitBinary(ast:Binary) {} visitBinary(ast:Binary) {}
visitChain(ast:Chain){} visitChain(ast:Chain){}
visitStructural(ast:Structural) {}
visitConditional(ast:Conditional) {} visitConditional(ast:Conditional) {}
visitFormatter(ast:Formatter) {} visitFormatter(ast:Formatter) {}
visitPipe(ast:Pipe) {}
visitFunctionCall(ast:FunctionCall) {} visitFunctionCall(ast:FunctionCall) {}
visitImplicitReceiver(ast:ImplicitReceiver) {} visitImplicitReceiver(ast:ImplicitReceiver) {}
visitKeyedAccess(ast:KeyedAccess) {} visitKeyedAccess(ast:KeyedAccess) {}

View File

@ -14,6 +14,7 @@ import {
PrefixNot, PrefixNot,
Conditional, Conditional,
Formatter, Formatter,
Pipe,
Assignment, Assignment,
Chain, Chain,
KeyedAccess, KeyedAccess,
@ -52,6 +53,15 @@ export class Parser {
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
addPipes(bindingAst:ASTWithSource, pipes:List<String>):ASTWithSource {
if (ListWrapper.isEmpty(pipes)) return bindingAst;
var res = ListWrapper.reduce(pipes,
(result, currentPipeName) => new Pipe(result, currentPipeName),
bindingAst.ast);
return new ASTWithSource(res, bindingAst.source, bindingAst.location);
}
parseTemplateBindings(input:string, location:any):List<TemplateBinding> { parseTemplateBindings(input:string, location:any):List<TemplateBinding> {
var tokens = this._lexer.tokenize(input); var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings(); return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();

View File

@ -16,6 +16,16 @@ import {
import {NO_CHANGE, Pipe} from './pipe'; import {NO_CHANGE, Pipe} from './pipe';
export class ArrayChangesFactory {
supports(obj):boolean {
return ArrayChanges.supportsObj(obj);
}
create():Pipe {
return new ArrayChanges();
}
}
export class ArrayChanges extends Pipe { export class ArrayChanges extends Pipe {
_collection; _collection;
_length:int; _length:int;

View File

@ -1,6 +1,16 @@
import {isBlank} from 'angular2/src/facade/lang'; import {isBlank} from 'angular2/src/facade/lang';
import {Pipe, NO_CHANGE} from './pipe'; import {Pipe, NO_CHANGE} from './pipe';
export class NullPipeFactory {
supports(obj):boolean {
return NullPipe.supportsObj(obj);
}
create():Pipe {
return new NullPipe();
}
}
export class NullPipe extends Pipe { export class NullPipe extends Pipe {
called:boolean; called:boolean;
constructor() { constructor() {

View File

@ -16,12 +16,12 @@ export class PipeRegistry {
} }
var matchingConfig = ListWrapper.find(listOfConfigs, var matchingConfig = ListWrapper.find(listOfConfigs,
(pipeConfig) => pipeConfig["supports"](obj)); (pipeConfig) => pipeConfig.supports(obj));
if (isBlank(matchingConfig)) { if (isBlank(matchingConfig)) {
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`); throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
} }
return matchingConfig["pipe"](); return matchingConfig.create();
} }
} }

View File

@ -9,9 +9,9 @@ import {
AstVisitor, AstVisitor,
Binary, Binary,
Chain, Chain,
Structural,
Conditional, Conditional,
Formatter, Formatter,
Pipe,
FunctionCall, FunctionCall,
ImplicitReceiver, ImplicitReceiver,
Interpolation, Interpolation,
@ -41,12 +41,12 @@ import {
RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER, RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK, RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
export class ProtoChangeDetector { export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false){} addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
instantiate(dispatcher:any, formatters:Map):ChangeDetector{ instantiate(dispatcher:any, formatters:Map):ChangeDetector{
return null; return null;
} }
@ -64,8 +64,8 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
this._recordBuilder = new ProtoRecordBuilder(); this._recordBuilder = new ProtoRecordBuilder();
} }
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) { addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural); this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
} }
instantiate(dispatcher:any, formatters:Map) { instantiate(dispatcher:any, formatters:Map) {
@ -95,8 +95,8 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
this._recordBuilder = new ProtoRecordBuilder(); this._recordBuilder = new ProtoRecordBuilder();
} }
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) { addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural); this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
} }
instantiate(dispatcher:any, formatters:Map) { instantiate(dispatcher:any, formatters:Map) {
@ -121,9 +121,7 @@ class ProtoRecordBuilder {
this.records = []; this.records = [];
} }
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) { addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
if (structural) ast = new Structural(ast);
var last = ListWrapper.last(this.records); var last = ListWrapper.last(this.records);
if (isPresent(last) && last.directiveMemento == directiveMemento) { if (isPresent(last) && last.directiveMemento == directiveMemento) {
last.lastInDirective = false; last.lastInDirective = false;
@ -228,9 +226,9 @@ class _ConvertAstIntoProtoRecords {
ChangeDetectionUtil.cond, [c,t,f], null, 0); ChangeDetectionUtil.cond, [c,t,f], null, 0);
} }
visitStructural(ast:Structural) { visitPipe(ast:Pipe) {
var value = ast.value.visit(this); var value = ast.exp.visit(this);
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value); return this._addRecord(RECORD_TYPE_PIPE, ast.name, ast.name, [], null, value);
} }
visitKeyedAccess(ast:KeyedAccess) { visitKeyedAccess(ast:KeyedAccess) {

View File

@ -8,7 +8,7 @@ export const RECORD_TYPE_INVOKE_METHOD = 4;
export const RECORD_TYPE_INVOKE_CLOSURE = 5; export const RECORD_TYPE_INVOKE_CLOSURE = 5;
export const RECORD_TYPE_KEYED_ACCESS = 6; export const RECORD_TYPE_KEYED_ACCESS = 6;
export const RECORD_TYPE_INVOKE_FORMATTER = 7; export const RECORD_TYPE_INVOKE_FORMATTER = 7;
export const RECORD_TYPE_STRUCTURAL_CHECK = 8; export const RECORD_TYPE_PIPE = 8;
export const RECORD_TYPE_INTERPOLATE = 9; export const RECORD_TYPE_INTERPOLATE = 9;
export class ProtoRecord { export class ProtoRecord {

View File

@ -195,34 +195,38 @@ export class ElementBinderBuilder extends CompileStep {
var directive = ListWrapper.get(directives, directiveIndex); var directive = ListWrapper.get(directives, directiveIndex);
var annotation = directive.annotation; var annotation = directive.annotation;
if (isBlank(annotation.bind)) continue; if (isBlank(annotation.bind)) continue;
var _this = this; StringMapWrapper.forEach(annotation.bind, (bindConfig, dirProp) => {
StringMapWrapper.forEach(annotation.bind, function (elProp, dirProp) { var bindConfigParts = this._splitBindConfig(bindConfig);
var expression = isPresent(compileElement.propertyBindings) ? var elProp = bindConfigParts[0];
var pipes = ListWrapper.slice(bindConfigParts, 1, bindConfigParts.length);
var bindingAst = isPresent(compileElement.propertyBindings) ?
MapWrapper.get(compileElement.propertyBindings, elProp) : MapWrapper.get(compileElement.propertyBindings, elProp) :
null; null;
if (isBlank(expression)) {
if (isBlank(bindingAst)) {
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp); var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
if (isPresent(attributeValue)) { if (isPresent(attributeValue)) {
expression = _this._parser.wrapLiteralPrimitive(attributeValue, _this._compilationUnit); bindingAst = this._parser.wrapLiteralPrimitive(attributeValue, this._compilationUnit);
} }
} }
// Bindings are optional, so this binding only needs to be set up if an expression is given. // Bindings are optional, so this binding only needs to be set up if an expression is given.
if (isPresent(expression)) { if (isPresent(bindingAst)) {
var len = dirProp.length; var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes);
var dirBindingName = dirProp;
var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']';
if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2);
protoView.bindDirectiveProperty( protoView.bindDirectiveProperty(
directiveIndex, directiveIndex,
expression, fullExpAstWithBindPipes,
dirBindingName, dirProp,
reflector.setter(dirBindingName), reflector.setter(dirProp)
isContentWatch
); );
} }
}); });
} }
} }
}
_splitBindConfig(bindConfig:string) {
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
return ListWrapper.map(parts, (s) => s.trim());
}
}

View File

@ -511,8 +511,7 @@ export class ProtoView {
directiveIndex:number, directiveIndex:number,
expression:AST, expression:AST,
setterName:string, setterName:string,
setter:SetterFn, setter:SetterFn) {
isContentWatch: boolean) {
var bindingMemento = new DirectiveBindingMemento( var bindingMemento = new DirectiveBindingMemento(
this.elementBinders.length-1, this.elementBinders.length-1,
@ -521,7 +520,7 @@ export class ProtoView {
setter setter
); );
var directiveMemento = DirectiveMemento.get(bindingMemento); var directiveMemento = DirectiveMemento.get(bindingMemento);
this.protoChangeDetector.addAst(expression, bindingMemento, directiveMemento, isContentWatch); this.protoChangeDetector.addAst(expression, bindingMemento, directiveMemento);
} }
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>, // Create a rootView as if the compiler encountered <rootcmp></rootcmp>,

View File

@ -7,7 +7,7 @@ import {ListWrapper} from 'angular2/src/facade/collection';
@Viewport({ @Viewport({
selector: '[foreach][in]', selector: '[foreach][in]',
bind: { bind: {
'iterableChanges[]': 'in' 'iterableChanges': 'in | iterableDiff'
} }
}) })
export class Foreach { export class Foreach {

View File

@ -3,6 +3,7 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DAR
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang'; import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Pipe} from 'angular2/src/change_detection/parser/ast';
import {Parser} from 'angular2/src/change_detection/parser/parser'; import {Parser} from 'angular2/src/change_detection/parser/parser';
import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Lexer} from 'angular2/src/change_detection/parser/lexer';
@ -29,9 +30,13 @@ export function main() {
} }
function createChangeDetector(memo:string, exp:string, context = null, formatters = null, function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
registry = null, structural = false) { registry = null, pipeType:string = null) {
var pcd = createProtoChangeDetector(registry); var pcd = createProtoChangeDetector(registry);
pcd.addAst(ast(exp), memo, memo, structural); var parsedAst = ast(exp);
if (isPresent(pipeType)) {
parsedAst = new Pipe(parsedAst, pipeType);
}
pcd.addAst(parsedAst, memo, memo);
var dispatcher = new TestDispatcher(); var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, formatters); var cd = pcd.instantiate(dispatcher, formatters);
@ -40,9 +45,8 @@ export function main() {
return {"changeDetector" : cd, "dispatcher" : dispatcher}; return {"changeDetector" : cd, "dispatcher" : dispatcher};
} }
function executeWatch(memo:string, exp:string, context = null, formatters = null, function executeWatch(memo:string, exp:string, context = null, formatters = null) {
registry = null, content = false) { var res = createChangeDetector(memo, exp, context, formatters);
var res = createChangeDetector(memo, exp, context, formatters, registry, content);
res["changeDetector"].detectChanges(); res["changeDetector"].detectChanges();
return res["dispatcher"].log; return res["dispatcher"].log;
} }
@ -190,7 +194,7 @@ export function main() {
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
var ast = parser.parseInterpolation("B{{a}}A", "location"); var ast = parser.parseInterpolation("B{{a}}A", "location");
pcd.addAst(ast, "memo", "memo", false); pcd.addAst(ast, "memo", "memo");
var dispatcher = new TestDispatcher(); var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, MapWrapper.create()); var cd = pcd.instantiate(dispatcher, MapWrapper.create());
@ -428,10 +432,10 @@ export function main() {
describe("pipes", () => { describe("pipes", () => {
it("should support pipes", () => { it("should support pipes", () => {
var registry = new FakePipeRegistry(() => new CountingPipe()); var registry = new FakePipeRegistry('pipe', () => new CountingPipe());
var ctx = new Person("Megatron"); var ctx = new Person("Megatron");
var c = createChangeDetector("memo", "name", ctx, null, registry, true); var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
var cd = c["changeDetector"]; var cd = c["changeDetector"];
var dispatcher = c["dispatcher"]; var dispatcher = c["dispatcher"];
@ -446,10 +450,10 @@ export function main() {
}); });
it("should lookup pipes in the registry when the context is not supported", () => { it("should lookup pipes in the registry when the context is not supported", () => {
var registry = new FakePipeRegistry(() => new OncePipe()); var registry = new FakePipeRegistry('pipe', () => new OncePipe());
var ctx = new Person("Megatron"); var ctx = new Person("Megatron");
var c = createChangeDetector("memo", "name", ctx, null, registry, true); var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
var cd = c["changeDetector"]; var cd = c["changeDetector"];
cd.detectChanges(); cd.detectChanges();
@ -464,10 +468,10 @@ export function main() {
}); });
it("should do nothing when returns NO_CHANGE", () => { it("should do nothing when returns NO_CHANGE", () => {
var registry = new FakePipeRegistry(() => new IdentityPipe()) var registry = new FakePipeRegistry('pipe', () => new IdentityPipe())
var ctx = new Person("Megatron"); var ctx = new Person("Megatron");
var c = createChangeDetector("memo", "name", ctx, null, registry, true); var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
var cd = c["changeDetector"]; var cd = c["changeDetector"];
var dispatcher = c["dispatcher"]; var dispatcher = c["dispatcher"];
@ -537,15 +541,18 @@ class IdentityPipe {
class FakePipeRegistry extends PipeRegistry { class FakePipeRegistry extends PipeRegistry {
numberOfLookups:number; numberOfLookups:number;
pipeType:string;
factory:Function; factory:Function;
constructor(factory) { constructor(pipeType, factory) {
super({}); super({});
this.pipeType = pipeType;
this.factory = factory; this.factory = factory;
this.numberOfLookups = 0; this.numberOfLookups = 0;
} }
get(type:string, obj) { get(type:string, obj) {
if (type != this.pipeType) return null;
this.numberOfLookups ++; this.numberOfLookups ++;
return this.factory(); return this.factory();
} }

View File

@ -51,6 +51,10 @@ export function main() {
return createParser().parseInterpolation(text, location); return createParser().parseInterpolation(text, location);
} }
function addPipes(ast, pipes) {
return createParser().addPipes(ast, pipes);
}
function expectEval(text, passedInContext = null) { function expectEval(text, passedInContext = null) {
var c = isBlank(passedInContext) ? td() : passedInContext; var c = isBlank(passedInContext) ? td() : passedInContext;
return expect(parseAction(text).eval(c)); return expect(parseAction(text).eval(c));
@ -544,6 +548,29 @@ export function main() {
}); });
}); });
describe('addPipes', () => {
it('should return the given ast whe the list of pipes is empty', () => {
var ast = parseBinding("1 + 1", "Location");
var transformedAst = addPipes(ast, []);
expect(transformedAst).toBe(ast);
});
it('should append pipe ast nodes', () => {
var ast = parseBinding("1 + 1", "Location");
var transformedAst = addPipes(ast, ['one', 'two']);
expect(transformedAst.ast.name).toEqual("two");
expect(transformedAst.ast.exp.name).toEqual("one");
expect(transformedAst.ast.exp.exp.operation).toEqual("+");
});
it('should preserve location and source', () => {
var ast = parseBinding("1 + 1", "Location");
var transformedAst = addPipes(ast, ['one', 'two']);
expect(transformedAst.source).toEqual("1 + 1");
expect(transformedAst.location).toEqual("Location");
});
});
describe('wrapLiteralPrimitive', () => { describe('wrapLiteralPrimitive', () => {
it('should wrap a literal primitive', () => { it('should wrap a literal primitive', () => {
expect(createParser().wrapLiteralPrimitive("foo", null).eval(null)).toEqual("foo"); expect(createParser().wrapLiteralPrimitive("foo", null).eval(null)).toEqual("foo");

View File

@ -11,8 +11,8 @@ export function main() {
it("should return the first pipe supporting the data type", () => { it("should return the first pipe supporting the data type", () => {
var r = new PipeRegistry({ var r = new PipeRegistry({
"type": [ "type": [
{"supports": (obj) => false, "pipe": () => firstPipe}, new PipeFactory(false, firstPipe),
{"supports": (obj) => true, "pipe": () => secondPipe} new PipeFactory(true, secondPipe)
] ]
}); });
@ -37,3 +37,21 @@ export function main() {
}); });
}); });
} }
class PipeFactory {
shouldSupport:boolean;
pipe:any;
constructor(shouldSupport:boolean, pipe:any) {
this.shouldSupport = shouldSupport;
this.pipe = pipe;
}
supports(obj):boolean {
return this.shouldSupport;
}
create():Pipe {
return this.pipe;
}
}

View File

@ -1,5 +1,5 @@
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib'; import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/facade/dom'; import {DOM} from 'angular2/src/facade/dom';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
@ -15,7 +15,7 @@ import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'angul
import {ProtoElementInjector} from 'angular2/src/core/compiler/element_injector'; import {ProtoElementInjector} from 'angular2/src/core/compiler/element_injector';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector, import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector, PipeRegistry, Pipe
} from 'angular2/change_detection'; } from 'angular2/change_detection';
import {Injector} from 'angular2/di'; import {Injector} from 'angular2/di';
@ -23,8 +23,7 @@ export function main() {
describe('ElementBinderBuilder', () => { describe('ElementBinderBuilder', () => {
var evalContext, view, changeDetector; var evalContext, view, changeDetector;
function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector, registry}={}) {
}={}) {
var reflector = new DirectiveMetadataReader(); var reflector = new DirectiveMetadataReader();
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
return new CompilePipeline([ return new CompilePipeline([
@ -70,8 +69,10 @@ export function main() {
} }
if (isPresent(current.element.getAttribute('viewroot'))) { if (isPresent(current.element.getAttribute('viewroot'))) {
current.isViewRoot = true; current.isViewRoot = true;
current.inheritedProtoView = new ProtoView(current.element, current.inheritedProtoView = new ProtoView(
new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); current.element,
new DynamicProtoChangeDetector(normalizeBlank(registry)),
new NativeShadowDomStrategy());
} else if (isPresent(parent)) { } else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView; current.inheritedProtoView = parent.inheritedProtoView;
} }
@ -393,6 +394,37 @@ export function main() {
expect(view.elementInjectors[0].get(SomeComponentDirectiveWithBinding).compProp).toBe('c'); expect(view.elementInjectors[0].get(SomeComponentDirectiveWithBinding).compProp).toBe('c');
}); });
it('should bind directive properties with pipes', () => {
var propertyBindings = MapWrapper.createFromStringMap({
'boundprop': 'prop1'
});
var directives = [DirectiveWithBindingsThatHavePipes];
var protoElementInjector = new ProtoElementInjector(null, 0, directives, true);
var registry = new PipeRegistry({
"double" : [new DoublePipeFactory()]
});
var pipeline = createPipeline({
propertyBindings: propertyBindings,
directives: directives,
protoElementInjector: protoElementInjector,
registry: registry
});
var results = pipeline.process(el('<div viewroot prop-binding directives></div>'));
var pv = results[0].inheritedProtoView;
results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
el('<div></div>'), new DynamicProtoChangeDetector(registry), new NativeShadowDomStrategy());
instantiateView(pv);
evalContext.prop1 = 'a';
changeDetector.detectChanges();
expect(view.elementInjectors[0].get(DirectiveWithBindingsThatHavePipes).compProp).toEqual('aa');
});
it('should bind directive properties for sibling elements', () => { it('should bind directive properties for sibling elements', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'boundprop1': 'prop1' 'boundprop1': 'prop1'
@ -504,6 +536,34 @@ class SomeComponentDirectiveWithBinding {
} }
} }
@Component({bind: {'compProp':'boundprop | double'}})
class DirectiveWithBindingsThatHavePipes {
compProp;
constructor() {
this.compProp = null;
}
}
class DoublePipe extends Pipe {
supports(obj) {
return true;
}
transform(value) {
return `${value}${value}`;
}
}
class DoublePipeFactory {
supports(obj) {
return true;
}
create() {
return new DoublePipe();
}
}
class Context { class Context {
prop1; prop1;
prop2; prop2;

View File

@ -548,7 +548,7 @@ export function main() {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(null), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false); pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
ctx.foo = 'buz'; ctx.foo = 'buz';
@ -563,8 +563,8 @@ export function main() {
pv.bindElement(new ProtoElementInjector(null, 0, [ pv.bindElement(new ProtoElementInjector(null, 0, [
DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]})) DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]}))
])); ]));
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false); pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'));
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false); pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
ctx.a = 100; ctx.a = 100;
@ -582,8 +582,8 @@ export function main() {
pv.bindElement(new ProtoElementInjector(null, 0, [ pv.bindElement(new ProtoElementInjector(null, 0, [
DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]})) DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]}))
])); ]));
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false); pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'));
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false); pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
ctx.a = 0; ctx.a = 0;

View File

@ -119,7 +119,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) {
parser.parseBinding('field9', null) parser.parseBinding('field9', null)
]; ];
for (var j = 0; j < 10; ++j) { for (var j = 0; j < 10; ++j) {
proto.addAst(astWithSource[j].ast, "memo", j, false); proto.addAst(astWithSource[j].ast, "memo", j);
} }
for (var i = 0; i < iterations; ++i) { for (var i = 0; i < iterations; ++i) {

View File

@ -169,7 +169,7 @@ export function setupReflectorForAngular() {
'annotations' : [new Viewport({ 'annotations' : [new Viewport({
selector: '[foreach]', selector: '[foreach]',
bind: { bind: {
'iterableChanges[]': 'in' 'iterableChanges': 'in | iterableDiff'
} }
})] })]
}); });