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
@@ -15,7 +15,7 @@ import {
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE
} 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{
return `
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});
@@ -283,7 +283,7 @@ export class ChangeDetectorJITGenerator {
fields = fields.concat(this.fieldNames);
this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) {
if (r.mode === RECORD_TYPE_PIPE) {
fields.push(this.pipeNames[r.selfIndex]);
}
});
@@ -314,7 +314,7 @@ export class ChangeDetectorJITGenerator {
}
genRecord(r:ProtoRecord):string {
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) {
if (r.mode === RECORD_TYPE_PIPE) {
return this.genPipeCheck (r);
} else {
return this.genReferenceCheck(r);
@@ -331,7 +331,7 @@ export class ChangeDetectorJITGenerator {
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
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 {
@@ -17,7 +17,7 @@ import {
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
@@ -81,7 +81,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
_check(proto:ProtoRecord) {
try {
if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
if (proto.mode == RECORD_TYPE_PIPE) {
return this._pipeCheck(proto);
} else {
return this._referenceCheck(proto);
@@ -184,7 +184,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
if (isPresent(storedPipe) && storedPipe.supports(context)) {
return storedPipe;
} else {
var pipe = this.pipeRegistry.get("[]", context);
var pipe = this.pipeRegistry.get(proto.name, context);
this._writePipe(proto, pipe);
return pipe;
}
+15 -17
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 {
eval(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 {
value;
constructor(value) {
@@ -460,9 +458,9 @@ export class AstVisitor {
visitAssignment(ast:Assignment) {}
visitBinary(ast:Binary) {}
visitChain(ast:Chain){}
visitStructural(ast:Structural) {}
visitConditional(ast:Conditional) {}
visitFormatter(ast:Formatter) {}
visitPipe(ast:Pipe) {}
visitFunctionCall(ast:FunctionCall) {}
visitImplicitReceiver(ast:ImplicitReceiver) {}
visitKeyedAccess(ast:KeyedAccess) {}
+10
View File
@@ -14,6 +14,7 @@ import {
PrefixNot,
Conditional,
Formatter,
Pipe,
Assignment,
Chain,
KeyedAccess,
@@ -52,6 +53,15 @@ export class Parser {
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> {
var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
@@ -16,6 +16,16 @@ import {
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 {
_collection;
_length:int;
@@ -1,6 +1,16 @@
import {isBlank} from 'angular2/src/facade/lang';
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 {
called:boolean;
constructor() {
@@ -16,12 +16,12 @@ export class PipeRegistry {
}
var matchingConfig = ListWrapper.find(listOfConfigs,
(pipeConfig) => pipeConfig["supports"](obj));
(pipeConfig) => pipeConfig.supports(obj));
if (isBlank(matchingConfig)) {
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
}
return matchingConfig["pipe"]();
return matchingConfig.create();
}
}
@@ -9,9 +9,9 @@ import {
AstVisitor,
Binary,
Chain,
Structural,
Conditional,
Formatter,
Pipe,
FunctionCall,
ImplicitReceiver,
Interpolation,
@@ -41,12 +41,12 @@ import {
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
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{
return null;
}
@@ -64,8 +64,8 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
this._recordBuilder = new ProtoRecordBuilder();
}
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural);
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
}
instantiate(dispatcher:any, formatters:Map) {
@@ -95,8 +95,8 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
this._recordBuilder = new ProtoRecordBuilder();
}
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural);
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
}
instantiate(dispatcher:any, formatters:Map) {
@@ -121,9 +121,7 @@ class ProtoRecordBuilder {
this.records = [];
}
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) {
if (structural) ast = new Structural(ast);
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
var last = ListWrapper.last(this.records);
if (isPresent(last) && last.directiveMemento == directiveMemento) {
last.lastInDirective = false;
@@ -228,9 +226,9 @@ class _ConvertAstIntoProtoRecords {
ChangeDetectionUtil.cond, [c,t,f], null, 0);
}
visitStructural(ast:Structural) {
var value = ast.value.visit(this);
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
visitPipe(ast:Pipe) {
var value = ast.exp.visit(this);
return this._addRecord(RECORD_TYPE_PIPE, ast.name, ast.name, [], null, value);
}
visitKeyedAccess(ast:KeyedAccess) {
+1 -1
View File
@@ -8,7 +8,7 @@ export const RECORD_TYPE_INVOKE_METHOD = 4;
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
export const RECORD_TYPE_KEYED_ACCESS = 6;
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 class ProtoRecord {
@@ -195,34 +195,38 @@ export class ElementBinderBuilder extends CompileStep {
var directive = ListWrapper.get(directives, directiveIndex);
var annotation = directive.annotation;
if (isBlank(annotation.bind)) continue;
var _this = this;
StringMapWrapper.forEach(annotation.bind, function (elProp, dirProp) {
var expression = isPresent(compileElement.propertyBindings) ?
StringMapWrapper.forEach(annotation.bind, (bindConfig, dirProp) => {
var bindConfigParts = this._splitBindConfig(bindConfig);
var elProp = bindConfigParts[0];
var pipes = ListWrapper.slice(bindConfigParts, 1, bindConfigParts.length);
var bindingAst = isPresent(compileElement.propertyBindings) ?
MapWrapper.get(compileElement.propertyBindings, elProp) :
null;
if (isBlank(expression)) {
if (isBlank(bindingAst)) {
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
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.
if (isPresent(expression)) {
var len = dirProp.length;
var dirBindingName = dirProp;
var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']';
if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2);
if (isPresent(bindingAst)) {
var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes);
protoView.bindDirectiveProperty(
directiveIndex,
expression,
dirBindingName,
reflector.setter(dirBindingName),
isContentWatch
fullExpAstWithBindPipes,
dirProp,
reflector.setter(dirProp)
);
}
});
}
}
}
_splitBindConfig(bindConfig:string) {
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
return ListWrapper.map(parts, (s) => s.trim());
}
}
+2 -3
View File
@@ -511,8 +511,7 @@ export class ProtoView {
directiveIndex:number,
expression:AST,
setterName:string,
setter:SetterFn,
isContentWatch: boolean) {
setter:SetterFn) {
var bindingMemento = new DirectiveBindingMemento(
this.elementBinders.length-1,
@@ -521,7 +520,7 @@ export class ProtoView {
setter
);
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>,
+1 -1
View File
@@ -7,7 +7,7 @@ import {ListWrapper} from 'angular2/src/facade/collection';
@Viewport({
selector: '[foreach][in]',
bind: {
'iterableChanges[]': 'in'
'iterableChanges': 'in | iterableDiff'
}
})
export class Foreach {