feat(Change Detector): Add support for collection content watch
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
import {
|
||||
isListLikeIterable,
|
||||
iterateListLike,
|
||||
ListWrapper,
|
||||
MapWrapper
|
||||
} from 'facade/collection';
|
||||
@ -12,20 +14,21 @@ import {
|
||||
looseIdentical,
|
||||
} from 'facade/lang';
|
||||
|
||||
export class CollectionChanges {
|
||||
export class ArrayChanges {
|
||||
_collection;
|
||||
_length:int;
|
||||
_linkedRecords:_DuplicateMap;
|
||||
_unlinkedRecords:_DuplicateMap;
|
||||
_previousItHead:CollectionChangeRecord<V> ;
|
||||
_previousItHead:CollectionChangeRecord<V>;
|
||||
_itHead:CollectionChangeRecord<V>;
|
||||
_itTail:CollectionChangeRecord<V> ;
|
||||
_itTail:CollectionChangeRecord<V>;
|
||||
_additionsHead:CollectionChangeRecord<V>;
|
||||
_additionsTail:CollectionChangeRecord<V> ;
|
||||
_additionsTail:CollectionChangeRecord<V>;
|
||||
_movesHead:CollectionChangeRecord<V>;
|
||||
_movesTail:CollectionChangeRecord<V> ;
|
||||
_removalsHead:CollectionChangeRecord<V>;
|
||||
_removalsTail:CollectionChangeRecord<V> ;
|
||||
_removalsTail:CollectionChangeRecord<V>;
|
||||
|
||||
constructor() {
|
||||
this._collection = null;
|
||||
this._length = null;
|
||||
@ -45,6 +48,10 @@ export class CollectionChanges {
|
||||
this._removalsTail = null;
|
||||
}
|
||||
|
||||
static supports(obj):boolean {
|
||||
return isListLikeIterable(obj);
|
||||
}
|
||||
|
||||
get collection() {
|
||||
return this._collection;
|
||||
}
|
||||
@ -112,8 +119,19 @@ export class CollectionChanges {
|
||||
record = record._next;
|
||||
}
|
||||
} else {
|
||||
// todo(vicb) implement iterators
|
||||
throw "NYI";
|
||||
index = 0;
|
||||
iterateListLike(collection, (item) => {
|
||||
if (record === null || !looseIdentical(record.item, item)) {
|
||||
record = this._mismatch(record, item, index);
|
||||
mayBeDirty = true;
|
||||
} else if (mayBeDirty) {
|
||||
// TODO(misko): can we limit this to duplicates only?
|
||||
record = this._verifyReinsertion(record, item, index);
|
||||
}
|
||||
record = record._next;
|
||||
index++
|
||||
});
|
||||
this._length = index;
|
||||
}
|
||||
|
||||
this._truncate(record);
|
@ -1,19 +1,19 @@
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
|
||||
|
||||
import {stringify, looseIdentical} from 'facade/lang';
|
||||
import {stringify, looseIdentical, isJsObject} from 'facade/lang';
|
||||
|
||||
export class MapChanges {
|
||||
export class KeyValueChanges {
|
||||
_records:Map;
|
||||
_map:Map;
|
||||
_map:any;
|
||||
|
||||
_mapHead:MapChangeRecord;
|
||||
_previousMapHead:MapChangeRecord;
|
||||
_changesHead:MapChangeRecord;
|
||||
_changesTail:MapChangeRecord;
|
||||
_additionsHead:MapChangeRecord;
|
||||
_additionsTail:MapChangeRecord;
|
||||
_removalsHead:MapChangeRecord;
|
||||
_removalsTail:MapChangeRecord;
|
||||
_mapHead:KVChangeRecord;
|
||||
_previousMapHead:KVChangeRecord;
|
||||
_changesHead:KVChangeRecord;
|
||||
_changesTail:KVChangeRecord;
|
||||
_additionsHead:KVChangeRecord;
|
||||
_additionsTail:KVChangeRecord;
|
||||
_removalsHead:KVChangeRecord;
|
||||
_removalsTail:KVChangeRecord;
|
||||
|
||||
constructor() {
|
||||
this._records = MapWrapper.create();
|
||||
@ -28,57 +28,61 @@ export class MapChanges {
|
||||
this._removalsTail = null;
|
||||
}
|
||||
|
||||
static supports(obj):boolean {
|
||||
return obj instanceof Map || isJsObject(obj);
|
||||
}
|
||||
|
||||
get isDirty():boolean {
|
||||
return this._additionsHead !== null ||
|
||||
this._changesHead !== null ||
|
||||
this._removalsHead !== null;
|
||||
}
|
||||
|
||||
forEachItem(fn:Function) {
|
||||
var record:MapChangeRecord;
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
fn(record);
|
||||
}
|
||||
forEachItem(fn:Function) {
|
||||
var record:KVChangeRecord;
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachPreviousItem(fn:Function) {
|
||||
var record:MapChangeRecord;
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
fn(record);
|
||||
}
|
||||
forEachPreviousItem(fn:Function) {
|
||||
var record:KVChangeRecord;
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachChangedItem(fn:Function) {
|
||||
var record:MapChangeRecord;
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
fn(record);
|
||||
}
|
||||
forEachChangedItem(fn:Function) {
|
||||
var record:KVChangeRecord;
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachAddedItem(fn:Function){
|
||||
var record:MapChangeRecord;
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
fn(record);
|
||||
}
|
||||
forEachAddedItem(fn:Function){
|
||||
var record:KVChangeRecord;
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachRemovedItem(fn:Function){
|
||||
var record:MapChangeRecord;
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
fn(record);
|
||||
}
|
||||
forEachRemovedItem(fn:Function){
|
||||
var record:KVChangeRecord;
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
check(map):boolean {
|
||||
this._reset();
|
||||
this._map = map;
|
||||
var records = this._records;
|
||||
var oldSeqRecord:MapChangeRecord = this._mapHead;
|
||||
var lastOldSeqRecord:MapChangeRecord = null;
|
||||
var lastNewSeqRecord:MapChangeRecord = null;
|
||||
var oldSeqRecord:KVChangeRecord = this._mapHead;
|
||||
var lastOldSeqRecord:KVChangeRecord = null;
|
||||
var lastNewSeqRecord:KVChangeRecord = null;
|
||||
var seqChanged:boolean = false;
|
||||
|
||||
MapWrapper.forEach(map, (value, key) => {
|
||||
this._forEach(map, (value, key) => {
|
||||
var newSeqRecord;
|
||||
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
|
||||
newSeqRecord = oldSeqRecord;
|
||||
@ -97,7 +101,7 @@ export class MapChanges {
|
||||
if (MapWrapper.contains(records, key)) {
|
||||
newSeqRecord = MapWrapper.get(records, key);
|
||||
} else {
|
||||
newSeqRecord = new MapChangeRecord(key);
|
||||
newSeqRecord = new KVChangeRecord(key);
|
||||
MapWrapper.set(records, key, newSeqRecord);
|
||||
newSeqRecord._currentValue = value;
|
||||
this._addToAdditions(newSeqRecord);
|
||||
@ -124,7 +128,7 @@ export class MapChanges {
|
||||
|
||||
_reset() {
|
||||
if (this.isDirty) {
|
||||
var record:MapChangeRecord;
|
||||
var record:KVChangeRecord;
|
||||
// Record the state of the mapping
|
||||
for (record = this._previousMapHead = this._mapHead;
|
||||
record !== null;
|
||||
@ -171,7 +175,7 @@ export class MapChanges {
|
||||
}
|
||||
}
|
||||
|
||||
_truncate(lastRecord:MapChangeRecord, record:MapChangeRecord) {
|
||||
_truncate(lastRecord:KVChangeRecord, record:KVChangeRecord) {
|
||||
while (record !== null) {
|
||||
if (lastRecord === null) {
|
||||
this._mapHead = null;
|
||||
@ -189,20 +193,20 @@ export class MapChanges {
|
||||
record = nextRecord;
|
||||
}
|
||||
|
||||
for (var rec:MapChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
|
||||
for (var rec:KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
|
||||
rec._previousValue = rec._currentValue;
|
||||
rec._currentValue = null;
|
||||
MapWrapper.delete(this._records, rec.key);
|
||||
}
|
||||
}
|
||||
|
||||
_isInRemovals(record:MapChangeRecord) {
|
||||
_isInRemovals(record:KVChangeRecord) {
|
||||
return record === this._removalsHead ||
|
||||
record._nextRemoved !== null ||
|
||||
record._prevRemoved !== null;
|
||||
}
|
||||
|
||||
_addToRemovals(record:MapChangeRecord) {
|
||||
_addToRemovals(record:KVChangeRecord) {
|
||||
// todo(vicb) assert
|
||||
//assert(record._next == null);
|
||||
//assert(record._nextAdded == null);
|
||||
@ -218,7 +222,7 @@ export class MapChanges {
|
||||
}
|
||||
}
|
||||
|
||||
_removeFromSeq(prev:MapChangeRecord, record:MapChangeRecord) {
|
||||
_removeFromSeq(prev:KVChangeRecord, record:KVChangeRecord) {
|
||||
var next = record._next;
|
||||
if (prev === null) {
|
||||
this._mapHead = next;
|
||||
@ -232,7 +236,7 @@ export class MapChanges {
|
||||
//})());
|
||||
}
|
||||
|
||||
_removeFromRemovals(record:MapChangeRecord) {
|
||||
_removeFromRemovals(record:KVChangeRecord) {
|
||||
// todo(vicb) assert
|
||||
//assert(record._next == null);
|
||||
//assert(record._nextAdded == null);
|
||||
@ -253,7 +257,7 @@ export class MapChanges {
|
||||
record._prevRemoved = record._nextRemoved = null;
|
||||
}
|
||||
|
||||
_addToAdditions(record:MapChangeRecord) {
|
||||
_addToAdditions(record:KVChangeRecord) {
|
||||
// todo(vicb): assert
|
||||
//assert(record._next == null);
|
||||
//assert(record._nextAdded == null);
|
||||
@ -268,7 +272,7 @@ export class MapChanges {
|
||||
}
|
||||
}
|
||||
|
||||
_addToChanges(record:MapChangeRecord) {
|
||||
_addToChanges(record:KVChangeRecord) {
|
||||
// todo(vicb) assert
|
||||
//assert(record._nextAdded == null);
|
||||
//assert(record._nextChanged == null);
|
||||
@ -288,7 +292,7 @@ export class MapChanges {
|
||||
var changes = [];
|
||||
var additions = [];
|
||||
var removals = [];
|
||||
var record:MapChangeRecord;
|
||||
var record:KVChangeRecord;
|
||||
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
ListWrapper.push(items, stringify(record));
|
||||
@ -312,19 +316,29 @@ export class MapChanges {
|
||||
"changes: " + changes.join(', ') + "\n" +
|
||||
"removals: " + removals.join(', ') + "\n";
|
||||
}
|
||||
|
||||
_forEach(obj, fn:Function) {
|
||||
if (obj instanceof Map) {
|
||||
MapWrapper.forEach(obj, fn);
|
||||
} else {
|
||||
StringMapWrapper.forEach(obj, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MapChangeRecord {
|
||||
|
||||
|
||||
export class KVChangeRecord {
|
||||
key;
|
||||
_previousValue;
|
||||
_currentValue;
|
||||
|
||||
_nextPrevious:MapChangeRecord;
|
||||
_next:MapChangeRecord;
|
||||
_nextAdded:MapChangeRecord;
|
||||
_nextRemoved:MapChangeRecord;
|
||||
_prevRemoved:MapChangeRecord;
|
||||
_nextChanged:MapChangeRecord;
|
||||
_nextPrevious:KVChangeRecord;
|
||||
_next:KVChangeRecord;
|
||||
_nextAdded:KVChangeRecord;
|
||||
_nextRemoved:KVChangeRecord;
|
||||
_prevRemoved:KVChangeRecord;
|
||||
_nextChanged:KVChangeRecord;
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
@ -345,5 +359,4 @@ export class MapChangeRecord {
|
||||
(stringify(this.key) + '[' + stringify(this._previousValue) + '->' +
|
||||
stringify(this._currentValue) + ']');
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,21 @@ export class EmptyExpr extends AST {
|
||||
}
|
||||
}
|
||||
|
||||
export class Collection extends AST {
|
||||
value:AST;
|
||||
constructor(value:AST) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return value.eval(context);
|
||||
}
|
||||
|
||||
visit(visitor, args) {
|
||||
visitor.visitCollection(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
export class ImplicitReceiver extends AST {
|
||||
eval(context) {
|
||||
return context;
|
||||
@ -386,20 +401,21 @@ export class TemplateBinding {
|
||||
|
||||
//INTERFACE
|
||||
export class AstVisitor {
|
||||
visitChain(ast:Chain, args){}
|
||||
visitImplicitReceiver(ast:ImplicitReceiver, args) {}
|
||||
visitConditional(ast:Conditional, args) {}
|
||||
visitAccessMember(ast:AccessMember, args) {}
|
||||
visitKeyedAccess(ast:KeyedAccess, args) {}
|
||||
visitBinary(ast:Binary, args) {}
|
||||
visitPrefixNot(ast:PrefixNot, args) {}
|
||||
visitLiteralPrimitive(ast:LiteralPrimitive, args) {}
|
||||
visitFormatter(ast:Formatter, args) {}
|
||||
visitAssignment(ast:Assignment, args) {}
|
||||
visitBinary(ast:Binary, args) {}
|
||||
visitChain(ast:Chain, args){}
|
||||
visitCollection(ast:Collection, args) {}
|
||||
visitConditional(ast:Conditional, args) {}
|
||||
visitFormatter(ast:Formatter, args) {}
|
||||
visitFunctionCall(ast:FunctionCall, args) {}
|
||||
visitImplicitReceiver(ast:ImplicitReceiver, args) {}
|
||||
visitKeyedAccess(ast:KeyedAccess, args) {}
|
||||
visitLiteralArray(ast:LiteralArray, args) {}
|
||||
visitLiteralMap(ast:LiteralMap, args) {}
|
||||
visitLiteralPrimitive(ast:LiteralPrimitive, args) {}
|
||||
visitMethodCall(ast:MethodCall, args) {}
|
||||
visitFunctionCall(ast:FunctionCall, args) {}
|
||||
visitPrefixNot(ast:PrefixNot, args) {}
|
||||
}
|
||||
|
||||
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];
|
||||
@ -410,4 +426,4 @@ function evalList(context, exps:List){
|
||||
result[i] = exps[i].eval(context);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import {ProtoRecordRange, RecordRange} from './record_range';
|
||||
import {FIELD, isPresent, isBlank, int, StringWrapper, FunctionWrapper, BaseException} from 'facade/lang';
|
||||
import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {ArrayChanges} from './array_changes';
|
||||
import {KeyValueChanges} from './keyvalue_changes';
|
||||
|
||||
var _fresh = new Object();
|
||||
|
||||
@ -10,15 +12,15 @@ export const RECORD_TYPE_INVOKE_CLOSURE = 0x0001;
|
||||
export const RECORD_TYPE_INVOKE_FORMATTER = 0x0002;
|
||||
export const RECORD_TYPE_INVOKE_METHOD = 0x0003;
|
||||
export const RECORD_TYPE_INVOKE_PURE_FUNCTION = 0x0004;
|
||||
export const RECORD_TYPE_LIST = 0x0005;
|
||||
export const RECORD_TYPE_MAP = 0x0006;
|
||||
export const RECORD_TYPE_MARKER = 0x0007;
|
||||
const RECORD_TYPE_ARRAY = 0x0005;
|
||||
const RECORD_TYPE_KEY_VALUE = 0x0006;
|
||||
const RECORD_TYPE_MARKER = 0x0007;
|
||||
export const RECORD_TYPE_PROPERTY = 0x0008;
|
||||
const RECORD_TYPE_NULL= 0x0009;
|
||||
|
||||
const RECORD_FLAG_DISABLED = 0x0100;
|
||||
export const RECORD_FLAG_IMPLICIT_RECEIVER = 0x0200;
|
||||
|
||||
|
||||
export const RECORD_FLAG_COLLECTION = 0x0400;
|
||||
|
||||
/**
|
||||
* For now we are dropping expression coalescence. We can always add it later, but
|
||||
@ -112,7 +114,6 @@ export class Record {
|
||||
this.dest = null;
|
||||
|
||||
this.previousValue = null;
|
||||
this.currentValue = _fresh;
|
||||
|
||||
this.context = null;
|
||||
this.funcOrValue = null;
|
||||
@ -125,6 +126,11 @@ export class Record {
|
||||
|
||||
this._mode = protoRecord._mode;
|
||||
|
||||
// Return early for collections, further init delayed until updateContext()
|
||||
if (this.isCollection) return;
|
||||
|
||||
this.currentValue = _fresh;
|
||||
|
||||
var type = this.type;
|
||||
|
||||
if (type === RECORD_TYPE_CONST) {
|
||||
@ -150,15 +156,21 @@ export class Record {
|
||||
}
|
||||
}
|
||||
|
||||
get type() {
|
||||
// todo(vicb): getter / setters are much slower than regular methods
|
||||
// todo(vicb): update the whole code base
|
||||
get type():int {
|
||||
return this._mode & RECORD_TYPE_MASK;
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
set type(value:int) {
|
||||
this._mode = (this._mode & ~RECORD_TYPE_MASK) | value;
|
||||
}
|
||||
|
||||
get disabled():boolean {
|
||||
return (this._mode & RECORD_FLAG_DISABLED) === RECORD_FLAG_DISABLED;
|
||||
}
|
||||
|
||||
set disabled(value) {
|
||||
set disabled(value:boolean) {
|
||||
if (value) {
|
||||
this._mode |= RECORD_FLAG_DISABLED;
|
||||
} else {
|
||||
@ -166,23 +178,33 @@ export class Record {
|
||||
}
|
||||
}
|
||||
|
||||
get isImplicitReceiver() {
|
||||
get isImplicitReceiver():boolean {
|
||||
return (this._mode & RECORD_FLAG_IMPLICIT_RECEIVER) === RECORD_FLAG_IMPLICIT_RECEIVER;
|
||||
}
|
||||
|
||||
static createMarker(rr:RecordRange) {
|
||||
get isCollection():boolean {
|
||||
return (this._mode & RECORD_FLAG_COLLECTION) === RECORD_FLAG_COLLECTION;
|
||||
}
|
||||
|
||||
static createMarker(rr:RecordRange):Record {
|
||||
return new Record(rr, null, null);
|
||||
}
|
||||
|
||||
check():boolean {
|
||||
this.previousValue = this.currentValue;
|
||||
this.currentValue = this._calculateNewValue();
|
||||
|
||||
if (isSame(this.previousValue, this.currentValue)) return false;
|
||||
|
||||
this._updateDestination();
|
||||
|
||||
return true;
|
||||
if (this.isCollection) {
|
||||
var changed = this._checkCollection();
|
||||
if (changed) {
|
||||
this._notifyDispatcher();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
this.previousValue = this.currentValue;
|
||||
this.currentValue = this._calculateNewValue();
|
||||
if (isSame(this.previousValue, this.currentValue)) return false;
|
||||
this._updateDestination();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_updateDestination() {
|
||||
@ -194,13 +216,38 @@ export class Record {
|
||||
this.dest.updateContext(this.currentValue);
|
||||
}
|
||||
} else {
|
||||
this.recordRange.dispatcher.onRecordChange(this, this.protoRecord.dest);
|
||||
this._notifyDispatcher();
|
||||
}
|
||||
}
|
||||
|
||||
_notifyDispatcher() {
|
||||
this.recordRange.dispatcher.onRecordChange(this, this.protoRecord.dest);
|
||||
}
|
||||
|
||||
// return whether the content has changed
|
||||
_checkCollection():boolean {
|
||||
switch(this.type) {
|
||||
case RECORD_TYPE_KEY_VALUE:
|
||||
var kvChangeDetector:KeyValueChanges = this.currentValue;
|
||||
return kvChangeDetector.check(this.context);
|
||||
|
||||
case RECORD_TYPE_ARRAY:
|
||||
var arrayChangeDetector:ArrayChanges = this.currentValue;
|
||||
return arrayChangeDetector.check(this.context);
|
||||
|
||||
case RECORD_TYPE_NULL:
|
||||
// no need to check the content again unless the context changes
|
||||
this.recordRange.disableRecord(this);
|
||||
this.currentValue = null;
|
||||
return true;
|
||||
|
||||
default:
|
||||
throw new BaseException(`Unsupported record type (${this.type})`);
|
||||
}
|
||||
}
|
||||
|
||||
_calculateNewValue() {
|
||||
var type = this.type;
|
||||
switch (type) {
|
||||
switch (this.type) {
|
||||
case RECORD_TYPE_PROPERTY:
|
||||
return this.funcOrValue(this.context);
|
||||
|
||||
@ -219,17 +266,8 @@ export class Record {
|
||||
this.recordRange.disableRecord(this);
|
||||
return this.funcOrValue;
|
||||
|
||||
case RECORD_TYPE_MARKER:
|
||||
throw new BaseException('Marker not implemented');
|
||||
|
||||
case RECORD_TYPE_MAP:
|
||||
throw new BaseException('Map not implemented');
|
||||
|
||||
case RECORD_TYPE_LIST:
|
||||
throw new BaseException('List not implemented');
|
||||
|
||||
default:
|
||||
throw new BaseException(`Unsupported record type ($type)`);
|
||||
throw new BaseException(`Unsupported record type (${this.type})`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,6 +279,34 @@ export class Record {
|
||||
updateContext(value) {
|
||||
this.context = value;
|
||||
this.recordRange.enableRecord(this);
|
||||
|
||||
if (!this.isMarkerRecord) {
|
||||
this.recordRange.enableRecord(this);
|
||||
}
|
||||
|
||||
if (this.isCollection) {
|
||||
if (ArrayChanges.supports(value)) {
|
||||
if (this.type != RECORD_TYPE_ARRAY) {
|
||||
this.type = RECORD_TYPE_ARRAY;
|
||||
this.currentValue = new ArrayChanges();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (KeyValueChanges.supports(value)) {
|
||||
if (this.type != RECORD_TYPE_KEY_VALUE) {
|
||||
this.type = RECORD_TYPE_KEY_VALUE;
|
||||
this.currentValue = new KeyValueChanges();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBlank(value)) {
|
||||
this.type = RECORD_TYPE_NULL;
|
||||
} else {
|
||||
throw new BaseException("Collection records must be array like, map like or null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isMarkerRecord() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
ProtoRecord,
|
||||
Record,
|
||||
RECORD_FLAG_COLLECTION,
|
||||
RECORD_FLAG_IMPLICIT_RECEIVER,
|
||||
RECORD_TYPE_CONST,
|
||||
RECORD_TYPE_INVOKE_CLOSURE,
|
||||
@ -13,10 +14,26 @@ import {
|
||||
import {FIELD, IMPLEMENTS, isBlank, isPresent, int, toBool, autoConvertAdd, BaseException,
|
||||
NumberWrapper} from 'facade/lang';
|
||||
import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {AST, AccessMember, ImplicitReceiver, AstVisitor, LiteralPrimitive,
|
||||
Binary, Formatter, MethodCall, FunctionCall, PrefixNot, Conditional,
|
||||
LiteralArray, LiteralMap, KeyedAccess, Chain, Assignment} from './parser/ast';
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
import {
|
||||
AccessMember,
|
||||
Assignment,
|
||||
AST,
|
||||
AstVisitor,
|
||||
Binary,
|
||||
Chain,
|
||||
Collection,
|
||||
Conditional,
|
||||
Formatter,
|
||||
FunctionCall,
|
||||
ImplicitReceiver,
|
||||
KeyedAccess,
|
||||
LiteralArray,
|
||||
LiteralMap,
|
||||
LiteralPrimitive,
|
||||
MethodCall,
|
||||
PrefixNot
|
||||
} from './parser/ast';
|
||||
|
||||
export class ProtoRecordRange {
|
||||
headRecord:ProtoRecord;
|
||||
@ -32,13 +49,16 @@ export class ProtoRecordRange {
|
||||
* @param ast The expression to watch
|
||||
* @param memento an opaque object which will be passed to WatchGroupDispatcher on
|
||||
* detecting a change.
|
||||
* @param shallow Should collections be shallow watched
|
||||
* @param content Wether to watch collection content (true) or reference (false, default)
|
||||
*/
|
||||
addRecordsFromAST(ast:AST,
|
||||
memento,
|
||||
shallow = false)
|
||||
content:boolean = false)
|
||||
{
|
||||
var creator = new ProtoRecordCreator(this);
|
||||
if (content) {
|
||||
ast = new Collection(ast);
|
||||
}
|
||||
creator.createRecordsFromAST(ast, memento);
|
||||
this._addRecords(creator.headRecord, creator.tailRecord);
|
||||
}
|
||||
@ -436,6 +456,12 @@ class ProtoRecordCreator {
|
||||
this.add(record);
|
||||
}
|
||||
|
||||
visitCollection(ast: Collection, dest) {
|
||||
var record = this.construct(RECORD_FLAG_COLLECTION, null, null, null, dest);
|
||||
ast.value.visit(this, new Destination(record, null));
|
||||
this.add(record);
|
||||
}
|
||||
|
||||
visitConditional(ast:Conditional, dest) {
|
||||
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _cond, 3, null, dest);
|
||||
ast.condition.visit(this, new Destination(record, 0));
|
||||
|
Reference in New Issue
Block a user