2015-02-05 13:08:05 -08:00
|
|
|
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
|
|
|
|
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
2015-01-14 13:51:16 -08:00
|
|
|
|
2015-01-21 12:05:52 -08:00
|
|
|
import {AbstractChangeDetector} from './abstract_change_detector';
|
2015-04-09 07:57:33 -07:00
|
|
|
import {BindingRecord} from './binding_record';
|
2015-02-12 14:56:41 -08:00
|
|
|
import {PipeRegistry} from './pipes/pipe_registry';
|
2015-02-27 13:38:25 -08:00
|
|
|
import {ChangeDetectionUtil, uninitialized} from './change_detection_util';
|
2015-01-21 12:05:52 -08:00
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
|
|
|
|
import {
|
|
|
|
ProtoRecord,
|
|
|
|
RECORD_TYPE_SELF,
|
|
|
|
RECORD_TYPE_PROPERTY,
|
2015-03-11 21:11:39 -07:00
|
|
|
RECORD_TYPE_LOCAL,
|
2015-01-14 13:51:16 -08:00
|
|
|
RECORD_TYPE_INVOKE_METHOD,
|
|
|
|
RECORD_TYPE_CONST,
|
|
|
|
RECORD_TYPE_INVOKE_CLOSURE,
|
2015-01-21 12:05:52 -08:00
|
|
|
RECORD_TYPE_PRIMITIVE_OP,
|
|
|
|
RECORD_TYPE_KEYED_ACCESS,
|
2015-02-19 17:47:25 -08:00
|
|
|
RECORD_TYPE_PIPE,
|
2015-02-27 13:38:25 -08:00
|
|
|
RECORD_TYPE_BINDING_PIPE,
|
2015-02-19 10:15:06 +01:00
|
|
|
RECORD_TYPE_INTERPOLATE
|
|
|
|
} from './proto_record';
|
2015-01-14 13:51:16 -08:00
|
|
|
|
|
|
|
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
|
|
|
|
|
2015-01-21 12:05:52 -08:00
|
|
|
export class DynamicChangeDetector extends AbstractChangeDetector {
|
2015-01-14 13:51:16 -08:00
|
|
|
dispatcher:any;
|
2015-02-12 14:56:41 -08:00
|
|
|
pipeRegistry;
|
|
|
|
|
2015-03-11 21:11:39 -07:00
|
|
|
locals:any;
|
2015-01-14 13:51:16 -08:00
|
|
|
values:List;
|
2015-01-26 16:16:17 -08:00
|
|
|
changes:List;
|
2015-02-12 14:56:41 -08:00
|
|
|
pipes:List;
|
|
|
|
prevContexts:List;
|
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
protos:List<ProtoRecord>;
|
2015-04-01 15:49:14 -07:00
|
|
|
directives:any;
|
2015-04-09 07:57:33 -07:00
|
|
|
directiveRecords:List;
|
2015-03-30 16:54:10 -07:00
|
|
|
changeControlStrategy:string;
|
2015-01-14 13:51:16 -08:00
|
|
|
|
2015-03-30 16:54:10 -07:00
|
|
|
constructor(changeControlStrategy:string, dispatcher:any, pipeRegistry:PipeRegistry,
|
2015-04-09 07:57:33 -07:00
|
|
|
protoRecords:List<ProtoRecord>, directiveRecords:List) {
|
2015-01-21 12:05:52 -08:00
|
|
|
super();
|
2015-01-14 13:51:16 -08:00
|
|
|
this.dispatcher = dispatcher;
|
2015-02-12 14:56:41 -08:00
|
|
|
this.pipeRegistry = pipeRegistry;
|
2015-01-26 16:16:17 -08:00
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
|
2015-02-12 14:56:41 -08:00
|
|
|
this.pipes = ListWrapper.createFixedSize(protoRecords.length + 1);
|
|
|
|
this.prevContexts = ListWrapper.createFixedSize(protoRecords.length + 1);
|
2015-01-26 16:16:17 -08:00
|
|
|
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
|
|
|
|
|
2015-02-27 07:56:50 -08:00
|
|
|
ListWrapper.fill(this.values, uninitialized);
|
|
|
|
ListWrapper.fill(this.pipes, null);
|
|
|
|
ListWrapper.fill(this.prevContexts, uninitialized);
|
|
|
|
ListWrapper.fill(this.changes, false);
|
2015-03-11 21:11:39 -07:00
|
|
|
this.locals = null;
|
2015-04-01 15:49:14 -07:00
|
|
|
this.directives = null;
|
2015-02-27 07:56:50 -08:00
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
this.protos = protoRecords;
|
2015-04-09 07:57:33 -07:00
|
|
|
this.directiveRecords = directiveRecords;
|
2015-03-30 16:54:10 -07:00
|
|
|
this.changeControlStrategy = changeControlStrategy;
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
|
|
|
|
2015-04-01 15:49:14 -07:00
|
|
|
hydrate(context:any, locals:any, directives:any) {
|
2015-03-30 16:54:10 -07:00
|
|
|
this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy);
|
2015-02-27 07:56:50 -08:00
|
|
|
this.values[0] = context;
|
2015-03-11 21:11:39 -07:00
|
|
|
this.locals = locals;
|
2015-04-01 15:49:14 -07:00
|
|
|
this.directives = directives;
|
2015-02-27 07:56:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
dehydrate() {
|
|
|
|
this._destroyPipes();
|
2015-02-06 17:03:40 -08:00
|
|
|
ListWrapper.fill(this.values, uninitialized);
|
2015-02-12 14:56:41 -08:00
|
|
|
ListWrapper.fill(this.changes, false);
|
|
|
|
ListWrapper.fill(this.pipes, null);
|
|
|
|
ListWrapper.fill(this.prevContexts, uninitialized);
|
2015-03-11 21:11:39 -07:00
|
|
|
this.locals = null;
|
2015-02-27 07:56:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
_destroyPipes() {
|
|
|
|
for(var i = 0; i < this.pipes.length; ++i) {
|
|
|
|
if (isPresent(this.pipes[i])) {
|
|
|
|
this.pipes[i].onDestroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hydrated():boolean {
|
|
|
|
return this.values[0] !== uninitialized;
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
|
|
|
|
2015-01-21 12:05:52 -08:00
|
|
|
detectChangesInRecords(throwOnChange:boolean) {
|
2015-01-14 13:51:16 -08:00
|
|
|
var protos:List<ProtoRecord> = this.protos;
|
|
|
|
|
2015-03-31 09:07:01 -07:00
|
|
|
var changes = null;
|
2015-04-14 08:54:09 -07:00
|
|
|
var isChanged = false;
|
2015-01-14 13:51:16 -08:00
|
|
|
for (var i = 0; i < protos.length; ++i) {
|
|
|
|
var proto:ProtoRecord = protos[i];
|
2015-04-14 08:54:09 -07:00
|
|
|
var bindingRecord = proto.bindingRecord;
|
|
|
|
var directiveRecord = bindingRecord.directiveRecord;
|
2015-01-14 13:51:16 -08:00
|
|
|
|
2015-03-31 09:07:01 -07:00
|
|
|
var change = this._check(proto);
|
2015-01-21 12:05:52 -08:00
|
|
|
if (isPresent(change)) {
|
2015-03-31 09:07:01 -07:00
|
|
|
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
2015-04-14 08:54:09 -07:00
|
|
|
this._updateDirectiveOrElement(change, bindingRecord);
|
|
|
|
isChanged = true;
|
|
|
|
changes = this._addChange(bindingRecord, change, changes);
|
2015-03-31 09:07:01 -07:00
|
|
|
}
|
2015-01-21 12:05:52 -08:00
|
|
|
|
2015-04-14 08:54:09 -07:00
|
|
|
if (proto.lastInDirective) {
|
|
|
|
if (isPresent(changes)) {
|
2015-04-21 11:47:53 -07:00
|
|
|
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
|
2015-04-14 08:54:09 -07:00
|
|
|
changes = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
|
2015-04-21 11:47:53 -07:00
|
|
|
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
2015-04-14 08:54:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
isChanged = false;
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-31 07:47:26 -07:00
|
|
|
callOnAllChangesDone() {
|
2015-04-09 07:57:33 -07:00
|
|
|
var dirs = this.directiveRecords;
|
|
|
|
for (var i = dirs.length - 1; i >= 0; --i) {
|
|
|
|
var dir = dirs[i];
|
|
|
|
if (dir.callOnAllChangesDone) {
|
2015-04-21 11:47:53 -07:00
|
|
|
this._getDirectiveFor(dir.directiveIndex).onAllChangesDone();
|
2015-03-26 14:36:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-09 07:57:33 -07:00
|
|
|
_updateDirectiveOrElement(change, bindingRecord) {
|
|
|
|
if (isBlank(bindingRecord.directiveRecord)) {
|
|
|
|
this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue);
|
2015-04-01 15:49:14 -07:00
|
|
|
} else {
|
2015-04-21 11:47:53 -07:00
|
|
|
var directiveIndex = bindingRecord.directiveRecord.directiveIndex;
|
|
|
|
bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue);
|
2015-04-01 15:49:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-09 07:57:33 -07:00
|
|
|
_addChange(bindingRecord:BindingRecord, change, changes) {
|
|
|
|
if (bindingRecord.callOnChange()) {
|
|
|
|
return ChangeDetectionUtil.addChange(changes, bindingRecord.propertyName, change);
|
2015-04-01 15:49:14 -07:00
|
|
|
} else {
|
|
|
|
return changes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-21 11:47:53 -07:00
|
|
|
_getDirectiveFor(directiveIndex) {
|
|
|
|
return this.directives.getDirectiveFor(directiveIndex);
|
2015-04-14 08:54:09 -07:00
|
|
|
}
|
|
|
|
|
2015-04-21 11:47:53 -07:00
|
|
|
_getDetectorFor(directiveIndex) {
|
|
|
|
return this.directives.getDetectorFor(directiveIndex);
|
2015-04-01 15:49:14 -07:00
|
|
|
}
|
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
_check(proto:ProtoRecord) {
|
|
|
|
try {
|
2015-02-27 13:38:25 -08:00
|
|
|
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
|
2015-02-12 14:56:41 -08:00
|
|
|
return this._pipeCheck(proto);
|
2015-01-14 13:51:16 -08:00
|
|
|
} else {
|
|
|
|
return this._referenceCheck(proto);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
throw new ChangeDetectionError(proto, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-22 11:08:10 +01:00
|
|
|
_referenceCheck(proto:ProtoRecord) {
|
2015-01-26 16:16:17 -08:00
|
|
|
if (this._pureFuncAndArgsDidNotChange(proto)) {
|
|
|
|
this._setChanged(proto, false);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
var prevValue = this._readSelf(proto);
|
|
|
|
var currValue = this._calculateCurrValue(proto);
|
|
|
|
|
2015-01-22 11:52:36 +01:00
|
|
|
if (!isSame(prevValue, currValue)) {
|
2015-01-14 13:51:16 -08:00
|
|
|
this._writeSelf(proto, currValue);
|
2015-01-26 16:16:17 -08:00
|
|
|
this._setChanged(proto, true);
|
|
|
|
|
2015-01-21 12:05:52 -08:00
|
|
|
if (proto.lastInBinding) {
|
2015-01-27 17:30:32 -08:00
|
|
|
return ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
2015-01-21 12:05:52 -08:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2015-01-14 13:51:16 -08:00
|
|
|
} else {
|
2015-01-26 16:16:17 -08:00
|
|
|
this._setChanged(proto, false);
|
2015-01-14 13:51:16 -08:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-22 11:08:10 +01:00
|
|
|
_calculateCurrValue(proto:ProtoRecord) {
|
2015-01-14 13:51:16 -08:00
|
|
|
switch (proto.mode) {
|
|
|
|
case RECORD_TYPE_SELF:
|
2015-01-25 16:59:06 -08:00
|
|
|
return this._readContext(proto);
|
2015-01-14 13:51:16 -08:00
|
|
|
|
|
|
|
case RECORD_TYPE_CONST:
|
|
|
|
return proto.funcOrValue;
|
|
|
|
|
|
|
|
case RECORD_TYPE_PROPERTY:
|
|
|
|
var context = this._readContext(proto);
|
2015-03-11 21:11:39 -07:00
|
|
|
return proto.funcOrValue(context);
|
|
|
|
|
|
|
|
case RECORD_TYPE_LOCAL:
|
|
|
|
return this.locals.get(proto.name);
|
2015-01-14 13:51:16 -08:00
|
|
|
|
|
|
|
case RECORD_TYPE_INVOKE_METHOD:
|
2015-02-20 15:50:12 -08:00
|
|
|
var context = this._readContext(proto);
|
|
|
|
var args = this._readArgs(proto);
|
2015-03-11 21:11:39 -07:00
|
|
|
return proto.funcOrValue(context, args);
|
2015-01-14 13:51:16 -08:00
|
|
|
|
2015-01-21 12:05:52 -08:00
|
|
|
case RECORD_TYPE_KEYED_ACCESS:
|
|
|
|
var arg = this._readArgs(proto)[0];
|
|
|
|
return this._readContext(proto)[arg];
|
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
case RECORD_TYPE_INVOKE_CLOSURE:
|
|
|
|
return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto));
|
|
|
|
|
2015-01-21 12:05:52 -08:00
|
|
|
case RECORD_TYPE_INTERPOLATE:
|
|
|
|
case RECORD_TYPE_PRIMITIVE_OP:
|
2015-01-14 13:51:16 -08:00
|
|
|
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new BaseException(`Unknown operation ${proto.mode}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-12 14:56:41 -08:00
|
|
|
_pipeCheck(proto:ProtoRecord) {
|
2015-01-14 13:51:16 -08:00
|
|
|
var context = this._readContext(proto);
|
2015-02-12 14:56:41 -08:00
|
|
|
var pipe = this._pipeFor(proto, context);
|
2015-01-14 13:51:16 -08:00
|
|
|
|
2015-02-12 14:56:41 -08:00
|
|
|
var newValue = pipe.transform(context);
|
2015-04-21 11:47:53 -07:00
|
|
|
|
2015-02-12 14:56:41 -08:00
|
|
|
if (! ChangeDetectionUtil.noChangeMarker(newValue)) {
|
2015-02-20 16:23:16 -08:00
|
|
|
var prevValue = this._readSelf(proto);
|
2015-02-12 14:56:41 -08:00
|
|
|
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;
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
2015-02-27 07:56:50 -08:00
|
|
|
if (isPresent(storedPipe)) {
|
|
|
|
storedPipe.onDestroy();
|
|
|
|
}
|
2015-02-27 13:38:25 -08:00
|
|
|
|
|
|
|
// Currently, only pipes that used in bindings in the template get
|
2015-04-14 07:47:12 -07:00
|
|
|
// the changeDetectorRef of the encompassing component.
|
2015-02-27 13:38:25 -08:00
|
|
|
//
|
|
|
|
// In the future, pipes declared in the bind configuration should
|
2015-04-14 07:47:12 -07:00
|
|
|
// be able to access the changeDetectorRef of that component.
|
2015-04-14 08:54:09 -07:00
|
|
|
var cdr = proto.mode === RECORD_TYPE_BINDING_PIPE ? this.ref : null;
|
2015-04-14 07:47:12 -07:00
|
|
|
var pipe = this.pipeRegistry.get(proto.name, context, cdr);
|
2015-02-27 07:56:50 -08:00
|
|
|
this._writePipe(proto, pipe);
|
|
|
|
return pipe;
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
|
|
|
|
2015-01-22 11:08:10 +01:00
|
|
|
_readContext(proto:ProtoRecord) {
|
2015-04-21 11:47:53 -07:00
|
|
|
if (proto.contextIndex == -1) {
|
|
|
|
return this._getDirectiveFor(proto.directiveIndex);
|
|
|
|
} else {
|
|
|
|
return this.values[proto.contextIndex];
|
|
|
|
}
|
|
|
|
|
2015-01-14 13:51:16 -08:00
|
|
|
return this.values[proto.contextIndex];
|
|
|
|
}
|
|
|
|
|
2015-01-22 11:08:10 +01:00
|
|
|
_readSelf(proto:ProtoRecord) {
|
2015-01-21 12:05:52 -08:00
|
|
|
return this.values[proto.selfIndex];
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
|
|
|
|
2015-01-22 11:08:10 +01:00
|
|
|
_writeSelf(proto:ProtoRecord, value) {
|
2015-01-21 12:05:52 -08:00
|
|
|
this.values[proto.selfIndex] = value;
|
2015-01-14 13:51:16 -08:00
|
|
|
}
|
|
|
|
|
2015-02-12 14:56:41 -08:00
|
|
|
_readPipe(proto:ProtoRecord) {
|
|
|
|
return this.pipes[proto.selfIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
_writePipe(proto:ProtoRecord, value) {
|
|
|
|
this.pipes[proto.selfIndex] = value;
|
|
|
|
}
|
|
|
|
|
2015-01-26 16:16:17 -08:00
|
|
|
_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) {
|
2015-01-14 13:51:16 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2015-03-31 09:07:01 -07:00
|
|
|
|
|
|
|
|
|
|
|
|