feat(change_detection): ensure that expression do not change after they have been checked

This commit is contained in:
vsavkin
2014-12-04 18:30:54 -08:00
parent d02e192951
commit 8acf9fb609
11 changed files with 123 additions and 27 deletions

View File

@ -6,14 +6,37 @@ import {ListWrapper, List} from 'facade/collection';
export * from './record';
export * from './record_range'
class ExpressionChangedAfterItHasBeenChecked extends Error {
message:string;
constructor(record:Record) {
this.message = `Expression '${record.expressionAsString()}' has changed after it was checked. ` +
`Previous value: '${record.previousValue}'. Current value: '${record.currentValue}'`;
}
toString():string {
return this.message;
}
}
export class ChangeDetector {
_rootRecordRange:RecordRange;
_enforceNoNewChanges:boolean;
constructor(recordRange:RecordRange) {
constructor(recordRange:RecordRange, enforceNoNewChanges:boolean = false) {
this._rootRecordRange = recordRange;
this._enforceNoNewChanges = enforceNoNewChanges;
}
detectChanges():int {
var count = this._detectChanges(false);
if (this._enforceNoNewChanges) {
this._detectChanges(true)
}
return count;
}
_detectChanges(throwOnChange:boolean):int {
var count = 0;
var updatedRecords = null;
var record = this._rootRecordRange.findFirstEnabledRecord();
@ -23,6 +46,7 @@ export class ChangeDetector {
if (record.check()) {
count++;
if (record.terminatesExpression()) {
if (throwOnChange) throw new ExpressionChangedAfterItHasBeenChecked(record);
currentRange = record.recordRange;
currentGroup = record.groupMemento();
updatedRecords = this._addRecord(updatedRecords, record);

View File

@ -17,6 +17,10 @@ export class AST {
visit(visitor, args) {
}
toString():string {
return "AST";
}
}
export class EmptyExpr extends AST {
@ -378,13 +382,33 @@ export class FunctionCall extends AST {
}
}
export class ASTWithSource {
export class ASTWithSource extends AST {
ast:AST;
source:string;
constructor(ast:AST, source:string) {
this.source = source;
this.ast = ast;
}
eval(context) {
return this.ast.eval(context);
}
get isAssignable() {
return this.ast.isAssignable;
}
assign(context, value) {
return this.ast.assign(context, value);
}
visit(visitor, args) {
return this.ast.visit(visitor, args);
}
toString():string {
return this.source;
}
}
export class TemplateBinding {

View File

@ -35,6 +35,7 @@ export class ProtoRecord {
name:string;
dest:any;
groupMemento:any;
expressionAsString:string;
next:ProtoRecord;
@ -46,7 +47,8 @@ export class ProtoRecord {
arity:int,
name:string,
dest,
groupMemento) {
groupMemento,
expressionAsString:string) {
this.recordRange = recordRange;
this._mode = mode;
@ -55,6 +57,7 @@ export class ProtoRecord {
this.name = name;
this.dest = dest;
this.groupMemento = groupMemento;
this.expressionAsString = expressionAsString;
this.next = null;
// The concrete Record instantiated from this ProtoRecord
@ -341,6 +344,10 @@ export class Record {
return this.protoRecord.dest;
}
expressionAsString() {
return this.protoRecord.expressionAsString;
}
groupMemento() {
return isPresent(this.protoRecord) ? this.protoRecord.groupMemento : null;
}

View File

@ -44,7 +44,7 @@ export class ProtoRecordRange {
/**
* Parses [ast] into [ProtoRecord]s and adds them to [ProtoRecordRange].
*
* @param ast The expression to watch
* @param astWithSource The expression to watch
* @param expressionMemento an opaque object which will be passed to ChangeDispatcher on
* detecting a change.
* @param groupMemento
@ -62,7 +62,6 @@ export class ProtoRecordRange {
if (content) {
ast = new Collection(ast);
}
this.recordCreator.createRecordsFromAST(ast, expressionMemento, groupMemento);
}
@ -318,11 +317,13 @@ class ProtoRecordCreator {
headRecord:ProtoRecord;
tailRecord:ProtoRecord;
groupMemento:any;
expressionAsString:string;
constructor(protoRecordRange) {
this.protoRecordRange = protoRecordRange;
this.headRecord = null;
this.tailRecord = null;
this.expressionAsString = null;
}
visitImplicitReceiver(ast:ImplicitReceiver, args) {
@ -434,11 +435,13 @@ class ProtoRecordCreator {
createRecordsFromAST(ast:AST, expressionMemento:any, groupMemento:any){
this.groupMemento = groupMemento;
this.expressionAsString = ast.toString();
ast.visit(this, expressionMemento);
}
construct(recordType, funcOrValue, arity, name, dest) {
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, name, dest, this.groupMemento);
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity,
name, dest, this.groupMemento, this.expressionAsString);
}
add(protoRecord:ProtoRecord) {