feat(change_detector): notify directives on property changes

This commit is contained in:
vsavkin
2014-12-02 17:09:46 -08:00
parent 5bdefee6c9
commit 847cefcb7b
9 changed files with 320 additions and 62 deletions

View File

@ -1,25 +1,70 @@
import {ProtoRecordRange, RecordRange} from './record_range';
import {ProtoRecord, Record} from './record';
import {FIELD, int, isPresent} from 'facade/lang';
import {int, isPresent, isBlank} from 'facade/lang';
import {ListWrapper, List} from 'facade/collection';
export * from './record';
export * from './record_range'
export class ChangeDetector {
_rootRecordRange:RecordRange;
constructor(recordRange:RecordRange) {
this._rootRecordRange = recordRange;
}
detectChanges():int {
var count:int = 0;
for (var record = this._rootRecordRange.findFirstEnabledRecord();
isPresent(record);
record = record.nextEnabled) {
var count = 0;
var updatedRecords = null;
var record = this._rootRecordRange.findFirstEnabledRecord();
while (isPresent(record)) {
var currentRange = record.recordRange;
var currentGroup = record.groupMemento();
var nextEnabled = record.nextEnabled;
var nextRange = isPresent(nextEnabled) ? nextEnabled.recordRange : null;
var nextGroup = isPresent(nextEnabled) ? nextEnabled.groupMemento() : null;
if (record.check()) {
count++;
count ++;
if (record.terminatesExpression()) {
updatedRecords = this._addRecord(updatedRecords, record);
}
}
if (this._shouldNotifyDispatcher(currentRange, nextRange, currentGroup, nextGroup, updatedRecords)) {
currentRange.dispatcher.onRecordChange(currentGroup, updatedRecords);
updatedRecords = null;
}
record = record.nextEnabled;
}
return count;
}
_groupChanged(currentRange, nextRange, currentGroup, nextGroup) {
return currentRange != nextRange || currentGroup != nextGroup;
}
_shouldNotifyDispatcher(currentRange, nextRange, currentGroup, nextGroup, updatedRecords) {
return this._groupChanged(currentRange, nextRange, currentGroup, nextGroup) && isPresent(updatedRecords);
}
_addRecord(updatedRecords:List, record:Record) {
if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList;
updatedRecords[0] = record;
} else if (updatedRecords === _singleElementList) {
updatedRecords = [_singleElementList[0], record];
} else {
ListWrapper.push(updatedRecords, record);
}
return updatedRecords;
}
}
var _singleElementList = [null];

View File

@ -34,15 +34,19 @@ export class ProtoRecord {
arity:int;
name:string;
dest:any;
groupMemento:any;
next:ProtoRecord;
recordInConstruction:Record;
constructor(recordRange:ProtoRecordRange,
mode:int,
funcOrValue,
arity:int,
name:string,
dest) {
dest,
groupMemento) {
this.recordRange = recordRange;
this._mode = mode;
@ -50,6 +54,7 @@ export class ProtoRecord {
this.arity = arity;
this.name = name;
this.dest = dest;
this.groupMemento = groupMemento;
this.next = null;
// The concrete Record instantiated from this ProtoRecord
@ -190,21 +195,20 @@ export class Record {
check():boolean {
if (this.isCollection) {
var changed = this._checkCollection();
if (changed) {
this._notifyDispatcher();
return true;
}
return false;
return this._checkCollection();
} else {
this.previousValue = this.currentValue;
this.currentValue = this._calculateNewValue();
if (isSame(this.previousValue, this.currentValue)) return false;
this._updateDestination();
return true;
return this._checkSingleRecord();
}
}
_checkSingleRecord():boolean {
this.previousValue = this.currentValue;
this.currentValue = this._calculateNewValue();
if (isSame(this.previousValue, this.currentValue)) return false;
this._updateDestination();
return true;
}
_updateDestination() {
// todo(vicb): compute this info only once in ctor ? (add a bit in mode not to grow the mem req)
if (this.dest instanceof Record) {
@ -213,15 +217,9 @@ export class Record {
} else {
this.dest.updateContext(this.currentValue);
}
} else {
this._notifyDispatcher();
}
}
_notifyDispatcher() {
this.recordRange.dispatcher.onRecordChange(this, this.protoRecord.dest);
}
// return whether the content has changed
_checkCollection():boolean {
switch(this.type) {
@ -307,9 +305,21 @@ export class Record {
}
}
terminatesExpression():boolean {
return !(this.dest instanceof Record);
}
get isMarkerRecord() {
return this.type == RECORD_TYPE_MARKER;
}
expressionMemento() {
return this.protoRecord.dest;
}
groupMemento() {
return isPresent(this.protoRecord) ? this.protoRecord.groupMemento : null;
}
}
function isSame(a, b) {

View File

@ -45,21 +45,24 @@ export class ProtoRecordRange {
* Parses [ast] into [ProtoRecord]s and adds them to [ProtoRecordRange].
*
* @param ast The expression to watch
* @param memento an opaque object which will be passed to WatchGroupDispatcher on
* @param expressionMemento an opaque object which will be passed to WatchGroupDispatcher on
* detecting a change.
* @param content Wether to watch collection content (true) or reference (false, default)
*/
addRecordsFromAST(ast:AST,
memento,
expressionMemento,
groupMemento,
content:boolean = false)
{
if (this.recordCreator === null) {
this.recordCreator = new ProtoRecordCreator(this);
}
if (content) {
ast = new Collection(ast);
}
this.recordCreator.createRecordsFromAST(ast, memento);
this.recordCreator.createRecordsFromAST(ast, expressionMemento, groupMemento);
}
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
@ -85,6 +88,8 @@ export class ProtoRecordRange {
for (var proto = this.recordCreator.headRecord; proto != null; proto = proto.next) {
if (proto.dest instanceof Destination) {
proto.recordInConstruction.dest = proto.dest.record.recordInConstruction;
} else {
proto.recordInConstruction.dest = proto.dest;
}
proto.recordInConstruction = null;
}
@ -378,6 +383,8 @@ class ProtoRecordCreator {
protoRecordRange:ProtoRecordRange;
headRecord:ProtoRecord;
tailRecord:ProtoRecord;
groupMemento:any;
constructor(protoRecordRange) {
this.protoRecordRange = protoRecordRange;
this.headRecord = null;
@ -491,12 +498,13 @@ class ProtoRecordCreator {
visitTemplateBindings(ast, dest) {this._unsupported();}
createRecordsFromAST(ast:AST, memento){
ast.visit(this, memento);
createRecordsFromAST(ast:AST, expressionMemento:any, groupMemento:any){
this.groupMemento = groupMemento;
ast.visit(this, expressionMemento);
}
construct(recordType, funcOrValue, arity, name, dest) {
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, name, dest);
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, name, dest, this.groupMemento);
}
add(protoRecord:ProtoRecord) {