angular-cn/modules/angular2/src/change_detection/dynamic_change_detector.js

250 lines
7.0 KiB
JavaScript
Raw Normal View History

import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {AbstractChangeDetector} from './abstract_change_detector';
import {PipeRegistry} from './pipes/pipe_registry';
import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
import {
ProtoRecord,
RECORD_TYPE_SELF,
RECORD_TYPE_PROPERTY,
RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
export class DynamicChangeDetector extends AbstractChangeDetector {
dispatcher:any;
pipeRegistry;
values:List;
changes:List;
pipes:List;
prevContexts:List;
protos:List<ProtoRecord>;
constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>) {
super();
this.dispatcher = dispatcher;
this.pipeRegistry = pipeRegistry;
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
this.pipes = ListWrapper.createFixedSize(protoRecords.length + 1);
this.prevContexts = ListWrapper.createFixedSize(protoRecords.length + 1);
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
this.protos = protoRecords;
}
setContext(context:any) {
ListWrapper.fill(this.values, uninitialized);
ListWrapper.fill(this.changes, false);
ListWrapper.fill(this.pipes, null);
ListWrapper.fill(this.prevContexts, uninitialized);
this.values[0] = context;
}
detectChangesInRecords(throwOnChange:boolean) {
var protos:List<ProtoRecord> = this.protos;
var updatedRecords = null;
for (var i = 0; i < protos.length; ++i) {
var proto:ProtoRecord = protos[i];
var change = this._check(proto);
if (isPresent(change)) {
var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
updatedRecords = ChangeDetectionUtil.addRecord(updatedRecords, record);
}
if (proto.lastInDirective && isPresent(updatedRecords)) {
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]);
this.dispatcher.onRecordChange(proto.directiveMemento, updatedRecords);
updatedRecords = null;
}
}
}
_check(proto:ProtoRecord) {
try {
if (proto.mode == RECORD_TYPE_PIPE) {
return this._pipeCheck(proto);
} else {
return this._referenceCheck(proto);
}
} catch (e) {
throw new ChangeDetectionError(proto, e);
}
}
2015-01-22 11:08:10 +01:00
_referenceCheck(proto:ProtoRecord) {
if (this._pureFuncAndArgsDidNotChange(proto)) {
this._setChanged(proto, false);
return null;
}
var prevValue = this._readSelf(proto);
var currValue = this._calculateCurrValue(proto);
if (!isSame(prevValue, currValue)) {
this._writeSelf(proto, currValue);
this._setChanged(proto, true);
if (proto.lastInBinding) {
return ChangeDetectionUtil.simpleChange(prevValue, currValue);
} else {
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
}
2015-01-22 11:08:10 +01:00
_calculateCurrValue(proto:ProtoRecord) {
switch (proto.mode) {
case RECORD_TYPE_SELF:
return this._readContext(proto);
case RECORD_TYPE_CONST:
return proto.funcOrValue;
case RECORD_TYPE_PROPERTY:
var context = this._readContext(proto);
var c = ChangeDetectionUtil.findContext(proto.name, context);
if (c instanceof ContextWithVariableBindings) {
return c.get(proto.name);
} else {
var propertyGetter:Function = proto.funcOrValue;
return propertyGetter(c);
}
break;
case RECORD_TYPE_INVOKE_METHOD:
var context = this._readContext(proto);
var args = this._readArgs(proto);
var c = ChangeDetectionUtil.findContext(proto.name, context);
if (c instanceof ContextWithVariableBindings) {
return FunctionWrapper.apply(c.get(proto.name), args);
} else {
var methodInvoker:Function = proto.funcOrValue;
return methodInvoker(c, args);
}
break;
case RECORD_TYPE_KEYED_ACCESS:
var arg = this._readArgs(proto)[0];
return this._readContext(proto)[arg];
case RECORD_TYPE_INVOKE_CLOSURE:
return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto));
case RECORD_TYPE_INTERPOLATE:
case RECORD_TYPE_PRIMITIVE_OP:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
default:
throw new BaseException(`Unknown operation ${proto.mode}`);
}
}
_pipeCheck(proto:ProtoRecord) {
var context = this._readContext(proto);
var pipe = this._pipeFor(proto, context);
var newValue = pipe.transform(context);
if (! ChangeDetectionUtil.noChangeMarker(newValue)) {
var prevValue = this._readSelf(proto);
this._writeSelf(proto, newValue);
this._setChanged(proto, true);
if (proto.lastInBinding) {
return ChangeDetectionUtil.simpleChange(prevValue, newValue);
} else {
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
}
_pipeFor(proto:ProtoRecord, context) {
var storedPipe = this._readPipe(proto);
if (isPresent(storedPipe) && storedPipe.supports(context)) {
return storedPipe;
} else {
var pipe = this.pipeRegistry.get(proto.name, context);
this._writePipe(proto, pipe);
return pipe;
}
}
2015-01-22 11:08:10 +01:00
_readContext(proto:ProtoRecord) {
return this.values[proto.contextIndex];
}
2015-01-22 11:08:10 +01:00
_readSelf(proto:ProtoRecord) {
return this.values[proto.selfIndex];
}
2015-01-22 11:08:10 +01:00
_writeSelf(proto:ProtoRecord, value) {
this.values[proto.selfIndex] = value;
}
_readPipe(proto:ProtoRecord) {
return this.pipes[proto.selfIndex];
}
_writePipe(proto:ProtoRecord, value) {
this.pipes[proto.selfIndex] = value;
}
_setChanged(proto:ProtoRecord, value:boolean) {
this.changes[proto.selfIndex] = value;
}
_pureFuncAndArgsDidNotChange(proto:ProtoRecord):boolean {
return proto.isPureFunction() && !this._argsChanged(proto);
}
_argsChanged(proto:ProtoRecord):boolean {
var args = proto.args;
for(var i = 0; i < args.length; ++i) {
if (this.changes[args[i]]) {
return true;
}
}
return false;
}
2015-01-22 11:08:10 +01:00
_readArgs(proto:ProtoRecord) {
var res = ListWrapper.createFixedSize(proto.args.length);
var args = proto.args;
for (var i = 0; i < args.length; ++i) {
res[i] = this.values[args[i]];
}
return res;
}
}
var _singleElementList = [null];
function isSame(a, b) {
if (a === b) return true;
if (a instanceof String && b instanceof String && a == b) return true;
if ((a !== a) && (b !== b)) return true;
return false;
2015-01-22 11:08:10 +01:00
}