feat(change_detection): add mode to ChangeDetector
This commit is contained in:
parent
3067601961
commit
23a080026a
|
@ -1,12 +1,15 @@
|
|||
import {isPresent} from 'facade/src/lang';
|
||||
import {List, ListWrapper} from 'facade/src/collection';
|
||||
import {ChangeDetector} from './interfaces';
|
||||
import {ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
|
||||
|
||||
export class AbstractChangeDetector extends ChangeDetector {
|
||||
children:List;
|
||||
parent:ChangeDetector;
|
||||
status:string;
|
||||
|
||||
constructor() {
|
||||
this.children = [];
|
||||
this.status = CHECK_ALWAYS;
|
||||
}
|
||||
|
||||
addChild(cd:ChangeDetector) {
|
||||
|
@ -31,8 +34,20 @@ export class AbstractChangeDetector extends ChangeDetector {
|
|||
}
|
||||
|
||||
_detectChanges(throwOnChange:boolean) {
|
||||
if (this.mode === DETACHED || this.mode === CHECKED) return;
|
||||
|
||||
this.detectChangesInRecords(throwOnChange);
|
||||
this._detectChangesInChildren(throwOnChange);
|
||||
|
||||
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
|
||||
}
|
||||
|
||||
markAsCheckOnce(){
|
||||
var c = this;
|
||||
while(isPresent(c) && (c.mode === CHECKED || c.mode === CHECK_ALWAYS)) {
|
||||
if (c.mode === CHECKED) c.mode = CHECK_ONCE;
|
||||
c = c.parent;
|
||||
}
|
||||
}
|
||||
|
||||
detectChangesInRecords(throwOnChange:boolean){}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import {isPresent} from 'facade/lang';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'facade/collection';
|
||||
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_change_detector';
|
||||
|
||||
/**
|
||||
* Removes "duplicate" records. It assuming that record evaluation does not
|
||||
* have side-effects.
|
||||
*
|
||||
* Records that are not last in bindings are removed and all the indices
|
||||
* of the records that depend on them are updated.
|
||||
*
|
||||
* Records that are last in bindings CANNOT be removed, and instead are
|
||||
* replaced with very cheap SELF records.
|
||||
*/
|
||||
export function coalesce(records:List<ProtoRecord>):List<ProtoRecord> {
|
||||
var res = ListWrapper.create();
|
||||
var indexMap = MapWrapper.create();
|
||||
|
||||
for (var i = 0; i < records.length; ++i) {
|
||||
var r = records[i];
|
||||
var record = _replaceIndices(r, res.length + 1, indexMap);
|
||||
var matchingRecord = _findMatching(record, res);
|
||||
|
||||
if (isPresent(matchingRecord) && record.lastInBinding) {
|
||||
ListWrapper.push(res, _selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
||||
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
||||
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else {
|
||||
ListWrapper.push(res, record);
|
||||
MapWrapper.set(indexMap, r.selfIndex, record.selfIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord {
|
||||
return new ProtoRecord(
|
||||
RECORD_TYPE_SELF,
|
||||
"self",
|
||||
null,
|
||||
[],
|
||||
r.fixedArgs,
|
||||
contextIndex,
|
||||
selfIndex,
|
||||
r.bindingMemento,
|
||||
r.groupMemento,
|
||||
r.expressionAsString,
|
||||
r.lastInBinding,
|
||||
r.lastInGroup
|
||||
);
|
||||
}
|
||||
|
||||
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){
|
||||
return ListWrapper.find(rs, (rr) =>
|
||||
rr.mode === r.mode &&
|
||||
rr.funcOrValue === r.funcOrValue &&
|
||||
rr.contextIndex === r.contextIndex &&
|
||||
ListWrapper.equals(rr.args, r.args)
|
||||
);
|
||||
}
|
||||
|
||||
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
|
||||
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
|
||||
var contextIndex = _map(indexMap, r.contextIndex);
|
||||
return new ProtoRecord(
|
||||
r.mode,
|
||||
r.name,
|
||||
r.funcOrValue,
|
||||
args,
|
||||
r.fixedArgs,
|
||||
contextIndex,
|
||||
selfIndex,
|
||||
r.bindingMemento,
|
||||
r.groupMemento,
|
||||
r.expressionAsString,
|
||||
r.lastInBinding,
|
||||
r.lastInGroup
|
||||
);
|
||||
}
|
||||
|
||||
function _map(indexMap:Map, value:number) {
|
||||
var r = MapWrapper.get(indexMap, value)
|
||||
return isPresent(r) ? r : value;
|
||||
}
|
|
@ -19,12 +19,38 @@ export class ChangeRecord {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
|
||||
* will become CHECKED.
|
||||
*/
|
||||
export const CHECK_ONCE="CHECK_ONCE";
|
||||
|
||||
/**
|
||||
* CHECKED means that the change detector should be skipped until its mode changes to
|
||||
* CHECK_ONCE or CHECK_ALWAYS.
|
||||
*/
|
||||
export const CHECKED="CHECKED";
|
||||
|
||||
/**
|
||||
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
|
||||
* will remain CHECK_ALWAYS.
|
||||
*/
|
||||
export const CHECK_ALWAYS="ALWAYS_CHECK";
|
||||
|
||||
/**
|
||||
* DETACHED means that the change detector sub tree is not a part of the main tree and
|
||||
* should be skipped.
|
||||
*/
|
||||
export const DETACHED="DETACHED";
|
||||
|
||||
export class ChangeDispatcher {
|
||||
onRecordChange(groupMemento, records:List<ChangeRecord>) {}
|
||||
}
|
||||
|
||||
export class ChangeDetector {
|
||||
parent:ChangeDetector;
|
||||
mode:string;
|
||||
|
||||
addChild(cd:ChangeDetector) {}
|
||||
removeChild(cd:ChangeDetector) {}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWi
|
|||
|
||||
|
||||
import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'change_detection/src/proto_change_detector';
|
||||
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'change_detection/src/interfaces';
|
||||
|
||||
|
||||
export function main() {
|
||||
|
@ -473,6 +474,75 @@ export function main() {
|
|||
expect(count).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mode", () => {
|
||||
it("should not check a detached change detector", () => {
|
||||
var c = createChangeDetector('name', 'a', new TestData("value"));
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
cd.mode = DETACHED;
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.log).toEqual([]);
|
||||
});
|
||||
|
||||
it("should not check a checked change detector", () => {
|
||||
var c = createChangeDetector('name', 'a', new TestData("value"));
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
||||
cd.mode = CHECKED;
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.log).toEqual([]);
|
||||
});
|
||||
|
||||
it("should change CHECK_ONCE to CHECKED", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
cd.mode = CHECK_ONCE;
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(cd.mode).toEqual(CHECKED);
|
||||
});
|
||||
|
||||
it("should not change the CHECK_ALWAYS", () => {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
cd.mode = CHECK_ALWAYS;
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(cd.mode).toEqual(CHECK_ALWAYS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("markAsCheckOnce", () => {
|
||||
function changeDetector(mode, parent) {
|
||||
var cd = createProtoChangeDetector().instantiate(null, null);
|
||||
cd.mode = mode;
|
||||
if (isPresent(parent)) parent.addChild(cd);
|
||||
return cd;
|
||||
}
|
||||
|
||||
it("should mark all checked detectors as CHECK_ONCE " +
|
||||
"until reaching a detached one", () => {
|
||||
|
||||
var root = changeDetector(CHECK_ALWAYS, null);
|
||||
var disabled = changeDetector(DETACHED, root);
|
||||
var parent = changeDetector(CHECKED, disabled);
|
||||
var child = changeDetector(CHECK_ALWAYS, parent);
|
||||
var grandChild = changeDetector(CHECKED, child);
|
||||
|
||||
grandChild.markAsCheckOnce();
|
||||
|
||||
expect(root.mode).toEqual(CHECK_ALWAYS);
|
||||
expect(disabled.mode).toEqual(DETACHED);
|
||||
expect(parent.mode).toEqual(CHECK_ONCE);
|
||||
expect(child.mode).toEqual(CHECK_ALWAYS);
|
||||
expect(grandChild.mode).toEqual(CHECK_ONCE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue